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 }