1 /*
  2  * Copyright (c) 2026, Datadog, Inc. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 package gc.stress.ihash;
 25 
 26 /*
 27  * @test id=Serial
 28  * @bug 8372151
 29  * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
 30  * @requires vm.gc.Serial
 31  * @key stress
 32  * @run main/othervm/timeout=300
 33  *      -XX:+UseCompactObjectHeaders -XX:+UseSerialGC
 34  *      -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
 35  *      -Xmx256m
 36  *      gc.stress.ihash.TestStressIHash
 37  */
 38 
 39 /*
 40  * @test id=Parallel
 41  * @bug 8372151
 42  * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
 43  * @requires vm.gc.Parallel
 44  * @key stress
 45  * @run main/othervm/timeout=300
 46  *      -XX:+UseCompactObjectHeaders -XX:+UseParallelGC
 47  *      -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
 48  *      -Xmx256m
 49  *      gc.stress.ihash.TestStressIHash
 50  */
 51 
 52 /*
 53  * @test id=G1
 54  * @bug 8372151
 55  * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
 56  * @requires vm.gc.G1
 57  * @key stress
 58  * @run main/othervm/timeout=300
 59  *      -XX:+UseCompactObjectHeaders -XX:+UseG1GC
 60  *      -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
 61  *      -Xmx256m
 62  *      gc.stress.ihash.TestStressIHash
 63  */
 64 
 65 /*
 66  * @test id=Shenandoah
 67  * @bug 8372151
 68  * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
 69  * @requires vm.gc.Shenandoah
 70  * @key stress
 71  * @run main/othervm/timeout=300
 72  *      -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC
 73  *      -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
 74  *      -Xmx256m
 75  *      gc.stress.ihash.TestStressIHash
 76  */
 77 
 78 /*
 79  * @test id=Z
 80  * @bug 8372151
 81  * @summary Stress test: cloning identity-hashed objects must not copy mark-word hash-control bits
 82  * @requires vm.gc.Z
 83  * @key stress
 84  * @run main/othervm/timeout=300
 85  *      -XX:+UseCompactObjectHeaders -XX:+UseZGC
 86  *      -XX:+UnlockDiagnosticVMOptions -XX:+VerifyDuringGC
 87  *      -Xmx256m
 88  *      gc.stress.ihash.TestStressIHash
 89  */
 90 
 91 /*
 92  * @test id=C2-Serial
 93  * @bug 8372151
 94  * @summary C2 clone of objects with narrowOop at offset 4 must use mismatched access
 95  * @requires vm.gc.Serial
 96  * @requires vm.opt.TieredCompilation != true
 97  * @key stress
 98  * @run main/othervm/timeout=300
 99  *      -XX:+UseCompactObjectHeaders -XX:+UseSerialGC
100  *      -XX:-TieredCompilation
101  *      -Xmx256m
102  *      gc.stress.ihash.TestStressIHash clone-ref
103  */
104 
105 /*
106  * @test id=C2-Parallel
107  * @bug 8372151
108  * @summary C2 clone of objects with narrowOop at offset 4 must use mismatched access
109  * @requires vm.gc.Parallel
110  * @requires vm.opt.TieredCompilation != true
111  * @key stress
112  * @run main/othervm/timeout=300
113  *      -XX:+UseCompactObjectHeaders -XX:+UseParallelGC
114  *      -XX:-TieredCompilation
115  *      -Xmx256m
116  *      gc.stress.ihash.TestStressIHash clone-ref
117  */
118 
119 /*
120  * @test id=C2-G1
121  * @bug 8372151
122  * @summary C2 clone of objects with narrowOop at offset 4 must use mismatched access
123  * @requires vm.gc.G1
124  * @requires vm.opt.TieredCompilation != true
125  * @key stress
126  * @run main/othervm/timeout=300
127  *      -XX:+UseCompactObjectHeaders -XX:+UseG1GC
128  *      -XX:-TieredCompilation
129  *      -Xmx256m
130  *      gc.stress.ihash.TestStressIHash clone-ref
131  */
132 
133 import java.util.Random;
134 
135 /**
136  * Regression test for JDK-8372151.
137  *
138  * With compact object headers (4-byte mark-word), the identity hash state is
139  * tracked via two "hashctrl" bits in the mark-word. When an object is hashed
140  * and later relocated by GC, the GC may "expand" it by one HeapWord to store
141  * the hash value, setting the hashctrl bits to "hashed-expanded" (11).
142  *
143  * The bug: Object.clone() copied the mark-word from source to destination,
144  * including the hashctrl bits. A clone of an expanded object would therefore
145  * appear expanded (hashctrl=11) without actually having the extra HeapWord
146  * allocated. This causes two problems:
147  *  1. The clone's identity hash is the same as the source's (semantic bug).
148  *  2. JVM_Clone allocates the clone at expanded size but then resets the
149  *     mark-word to prototype (not-hashed, not-expanded), creating a size
150  *     mismatch that crashes GCs using linear heap iteration (e.g., Serial).
151  *
152  * This test creates objects whose layout guarantees hash-expansion (a single
153  * int field: header(4) + int(4) = 8 bytes, no room for the 4-byte hash),
154  * hashes them, forces a GC to expand them, then clones them. It verifies
155  * both that clones get unique identity hashes (semantic correctness) and that
156  * the heap remains intact (-XX:+VerifyDuringGC detects size mismatches).
157  */
158 public class TestStressIHash {
159 
160     // With compact headers: header(4) + int(4) = 8 bytes (1 HeapWord).
161     // The identity hash needs 4 bytes but there is no gap in the layout,
162     // so GC must expand the object by one word when preserving the hash.
163     static class Payload implements Cloneable {
164         int field;
165 
166         Payload(int v) { field = v; }
167 
168         @Override
169         public Payload clone() {
170             try {
171                 return (Payload) super.clone();
172             } catch (CloneNotSupportedException e) {
173                 throw new RuntimeException(e);
174             }
175         }
176     }
177 
178     // With compact headers: header(4) + narrowOop(4) + narrowOop(4) + ...
179     // All fields are object references so HotSpot's field layout places a
180     // narrowOop at offset 4 (the first slot after the 4-byte compact header).
181     // The C2 clone intrinsic (BarrierSetC2::clone) pre-copies the 4 bytes at
182     // offset 4 as a raw T_INT. When C2 later expands the ArrayCopyNode via
183     // try_clone_instance, it creates typed stores for ALL fields, including a
184     // StoreN (narrowOop store) at offset 4. Without the mismatched-access flag
185     // on the pre-copy StoreI, IGVN's StoreNode::Ideal walks the memory chain
186     // and asserts that chained stores have matching opcodes (StoreN vs StoreI).
187     //
188     // Important: NO int/long/float/double fields — HotSpot's field sorter
189     // would place those at offset 4 to fill the gap, pushing the narrowOop
190     // to a higher (8-byte-aligned) offset where no mismatch occurs.
191     static class RefPayload implements Cloneable {
192         Object ref;
193         Object ref2;
194         Object ref3;
195         Object ref4;
196 
197         RefPayload(Object r) {
198             ref = r;
199             ref2 = r;
200             ref3 = r;
201             ref4 = r;
202         }
203 
204         @Override
205         public RefPayload clone() {
206             try {
207                 return (RefPayload) super.clone();
208             } catch (CloneNotSupportedException e) {
209                 throw new RuntimeException(e);
210             }
211         }
212     }
213 
214     static final int BATCH_SIZE = 100_000;
215     static final int MAX_SURVIVORS = 1_000_000;
216 
217     // Small helper methods that C2 will compile and inline the clone
218     // intrinsic into. Keeping them small encourages C2 to intrinsify
219     // Object.clone() rather than emitting a call.
220     static Payload clonePayload(Payload p) {
221         return p.clone();
222     }
223 
224     // Allocate, clone, and return the clone's ref field. When C2 compiles
225     // this, it sees the source allocation inline and can constant-fold the
226     // LoadN from the clone expansion (try_clone_instance), leaving the
227     // pre-copy StoreI at offset 4 with outcnt==1. IGVN then walks the
228     // memory chain from the typed StoreN and hits the StoreI, triggering
229     // the assertion for mismatched store opcodes.
230     static Object cloneRefPayload(Object r) {
231         RefPayload p = new RefPayload(r);
232         RefPayload c = p.clone();
233         return c.ref;
234     }
235 
236     public static void main(String[] args) {
237         boolean cloneRef = args.length > 0 && "clone-ref".equals(args[0]);
238 
239         if (cloneRef) {
240             testCloneRefPayload();
241         } else {
242             testClonePayload();
243         }
244     }
245 
246     // C2 regression test: exercises the clone pre-copy StoreI/StoreN
247     // mismatch at offset 4. Runs cloneRefPayload in a tight loop so C2
248     // compiles it and hits the assertion during IGVN.
249     static void testCloneRefPayload() {
250         Object anchor = new Object();
251         for (int i = 0; i < 100_000; i++) {
252             Object result = cloneRefPayload(anchor);
253             if (result != anchor) {
254                 throw new RuntimeException("FAIL: RefPayload clone has wrong ref field");
255             }
256         }
257     }
258 
259     // GC stress test: clones identity-hashed Payload objects across GC
260     // cycles and verifies hash uniqueness and heap integrity.
261     static void testClonePayload() {
262         Random rng = new Random(12345);
263         Object[] survivors = new Object[MAX_SURVIVORS];
264         int survivorCount = 0;
265         int totalCreated = 0;
266         int sharedHashes = 0;
267         int totalCloned = 0;
268 
269         while (survivorCount < MAX_SURVIVORS) {
270             // Phase 1: Create a batch of objects and hash every one of them.
271             Payload[] batch = new Payload[BATCH_SIZE];
272             int[] srcHashes = new int[BATCH_SIZE];
273             for (int i = 0; i < BATCH_SIZE; i++) {
274                 batch[i] = new Payload(totalCreated++);
275                 srcHashes[i] = System.identityHashCode(batch[i]);
276             }
277 
278             // Phase 2: Force a GC cycle. Surviving hashed objects that need
279             // expansion get relocated with an extra HeapWord and their
280             // hashctrl bits set to "hashed-expanded" (11).
281             System.gc();
282 
283             // Phase 3: Clone the (now expanded) batch objects and verify that
284             // each clone gets a unique identity hash. With the bug, clones
285             // inherit the source's hash because the hashctrl bits and stored
286             // hash value are copied from the source mark-word.
287             for (int i = 0; i < BATCH_SIZE; i++) {
288                 Payload clone = clonePayload(batch[i]);
289                 totalCloned++;
290                 int cloneHash = System.identityHashCode(clone);
291                 if (cloneHash == srcHashes[i]) {
292                     sharedHashes++;
293                 }
294                 if (rng.nextInt(10) == 0 && survivorCount < MAX_SURVIVORS) {
295                     survivors[survivorCount++] = clone;
296                 }
297             }
298 
299             // Let the originals die.
300             batch = null;
301         }
302 
303         // Final GC: with VerifyDuringGC, this walks the heap and will detect
304         // corrupt clones if the allocation-size vs mark-word bug is present.
305         System.gc();
306 
307         // Verify that clones got independent identity hashes. Random hash
308         // collisions are possible but extremely rare (~1 in 2^32 per pair).
309         // The threshold of 10 accounts for any statistical noise.
310         if (sharedHashes > 10) {
311             throw new RuntimeException("FAIL: " + sharedHashes + " / " + totalCloned
312                 + " clones share identity hash with source object");
313         }
314     }
315 }