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 import java.util.Random;
 92 
 93 /**
 94  * Regression test for JDK-8372151.
 95  *
 96  * With compact object headers (4-byte mark-word), the identity hash state is
 97  * tracked via two "hashctrl" bits in the mark-word. When an object is hashed
 98  * and later relocated by GC, the GC may "expand" it by one HeapWord to store
 99  * the hash value, setting the hashctrl bits to "hashed-expanded" (11).
100  *
101  * The bug: Object.clone() copied the mark-word from source to destination,
102  * including the hashctrl bits. A clone of an expanded object would therefore
103  * appear expanded (hashctrl=11) without actually having the extra HeapWord
104  * allocated. This causes two problems:
105  *  1. The clone's identity hash is the same as the source's (semantic bug).
106  *  2. JVM_Clone allocates the clone at expanded size but then resets the
107  *     mark-word to prototype (not-hashed, not-expanded), creating a size
108  *     mismatch that crashes GCs using linear heap iteration (e.g., Serial).
109  *
110  * This test creates objects whose layout guarantees hash-expansion (a single
111  * int field: header(4) + int(4) = 8 bytes, no room for the 4-byte hash),
112  * hashes them, forces a GC to expand them, then clones them. It verifies
113  * both that clones get unique identity hashes (semantic correctness) and that
114  * the heap remains intact (-XX:+VerifyDuringGC detects size mismatches).
115  */
116 public class TestStressIHash {
117 
118     // With compact headers: header(4) + int(4) = 8 bytes (1 HeapWord).
119     // The identity hash needs 4 bytes but there is no gap in the layout,
120     // so GC must expand the object by one word when preserving the hash.
121     static class Payload implements Cloneable {
122         int field;
123 
124         Payload(int v) { field = v; }
125 
126         @Override
127         public Payload clone() {
128             try {
129                 return (Payload) super.clone();
130             } catch (CloneNotSupportedException e) {
131                 throw new RuntimeException(e);
132             }
133         }
134     }
135 
136     static final int BATCH_SIZE = 100_000;
137     static final int MAX_SURVIVORS = 1_000_000;
138 
139     public static void main(String[] args) {
140         Random rng = new Random(12345);
141         Object[] survivors = new Object[MAX_SURVIVORS];
142         int survivorCount = 0;
143         int totalCreated = 0;
144         int sharedHashes = 0;
145         int totalCloned = 0;
146 
147         while (survivorCount < MAX_SURVIVORS) {
148             // Phase 1: Create a batch of objects and hash every one of them.
149             Payload[] batch = new Payload[BATCH_SIZE];
150             int[] srcHashes = new int[BATCH_SIZE];
151             for (int i = 0; i < BATCH_SIZE; i++) {
152                 batch[i] = new Payload(totalCreated++);
153                 srcHashes[i] = System.identityHashCode(batch[i]);
154             }
155 
156             // Phase 2: Force a GC cycle. Surviving hashed objects that need
157             // expansion get relocated with an extra HeapWord and their
158             // hashctrl bits set to "hashed-expanded" (11).
159             System.gc();
160 
161             // Phase 3: Clone the (now expanded) batch objects and verify that
162             // each clone gets a unique identity hash. With the bug, clones
163             // inherit the source's hash because the hashctrl bits and stored
164             // hash value are copied from the source mark-word.
165             for (int i = 0; i < BATCH_SIZE; i++) {
166                 Payload clone = batch[i].clone();
167                 totalCloned++;
168                 int cloneHash = System.identityHashCode(clone);
169                 if (cloneHash == srcHashes[i]) {
170                     sharedHashes++;
171                 }
172                 if (rng.nextInt(10) == 0 && survivorCount < MAX_SURVIVORS) {
173                     survivors[survivorCount++] = clone;
174                 }
175             }
176 
177             // Let the originals die.
178             batch = null;
179         }
180 
181         // Final GC: with VerifyDuringGC, this walks the heap and will detect
182         // corrupt clones if the allocation-size vs mark-word bug is present.
183         System.gc();
184 
185         // Verify that clones got independent identity hashes. Random hash
186         // collisions are possible but extremely rare (~1 in 2^32 per pair).
187         // The threshold of 10 accounts for any statistical noise.
188         if (sharedHashes > 10) {
189             throw new RuntimeException("FAIL: " + sharedHashes + " / " + totalCloned
190                 + " clones share identity hash with source object");
191         }
192     }
193 }