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 
 25 /*
 26  * @test id=default
 27  * @summary Test that identity hash codes are stable across Shenandoah evacuation
 28  * @bug 8379910
 29  * @requires vm.gc.Shenandoah
 30  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 31  * @library /test/lib
 32  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC
 33  *      -Xms64m -Xmx64m
 34  *      TestHashCodeEvacRace
 35  */
 36 
 37 /*
 38  * @test id=generational
 39  * @summary Test that identity hash codes are stable across Shenandoah evacuation
 40  * @bug 8379910
 41  * @requires vm.gc.Shenandoah
 42  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 43  * @library /test/lib
 44  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseShenandoahGC
 45  *      -XX:ShenandoahGCMode=generational -Xms64m -Xmx64m
 46  *      TestHashCodeEvacRace
 47  */
 48 
 49 /*
 50  * @test id=serial
 51  * @summary Test that identity hash codes are stable across Serial GC
 52  * @bug 8379910
 53  * @requires vm.gc.Serial
 54  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 55  * @library /test/lib
 56  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseSerialGC
 57  *      -Xms64m -Xmx64m
 58  *      TestHashCodeEvacRace
 59  */
 60 
 61 /*
 62  * @test id=parallel
 63  * @summary Test that identity hash codes are stable across Parallel GC
 64  * @bug 8379910
 65  * @requires vm.gc.Parallel
 66  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 67  * @library /test/lib
 68  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseParallelGC
 69  *      -Xms64m -Xmx64m
 70  *      TestHashCodeEvacRace
 71  */
 72 
 73 /*
 74  * @test id=g1
 75  * @summary Test that identity hash codes are stable across G1 GC
 76  * @bug 8379910
 77  * @requires vm.gc.G1
 78  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 79  * @library /test/lib
 80  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseG1GC
 81  *      -Xms64m -Xmx64m
 82  *      TestHashCodeEvacRace
 83  */
 84 
 85 /*
 86  * @test id=zgc
 87  * @summary Test that identity hash codes are stable across ZGC
 88  * @bug 8379910
 89  * @requires vm.gc.Z
 90  * @requires vm.opt.UseCompactObjectHeaders == null | vm.opt.UseCompactObjectHeaders == true
 91  * @library /test/lib
 92  * @run main/othervm -XX:+UseCompactObjectHeaders -XX:+UseZGC
 93  *      -Xms64m -Xmx64m
 94  *      TestHashCodeEvacRace
 95  */
 96 
 97 /**
 98  * Regression test for a race between reading the identity hash code and
 99  * Shenandoah concurrent evacuation with compact object headers.
100  *
101  * With compact headers, objects whose layout has no internal gap for the
102  * identity hash (e.g. a class with a single int field: 4-byte header +
103  * 4-byte int = 8 bytes) require an extra word ("hash expansion") when
104  * evacuated. The bug was that initialize_hash_if_necessary() was called
105  * AFTER the forwarding CAS, leaving a window where the copy is already
106  * visible to other threads (via the forwarding pointer) but the hash
107  * value in the expansion word has not been written yet. A thread reading
108  * the hash during that window would see an uninitialized value.
109  *
110  * The fix moves initialize_hash_if_necessary() before the forwarding CAS
111  * so the copy is fully initialized when it becomes visible.
112  *
113  * This test hashes objects, records the expected values, then continuously
114  * verifies them while GC evacuates the objects. Without the fix, a reader
115  * thread can observe a wrong (uninitialized) hash during evacuation.
116  */
117 public class TestHashCodeEvacRace {
118 
119     // With compact headers: 4-byte header + 4-byte int = 8 bytes.
120     // No room for the 4-byte identity hash — requires expansion on evacuation.
121     static class IntHolder {
122         int value;
123         IntHolder(int v) { value = v; }
124     }
125 
126     static final int NUM_OBJECTS = 50_000;
127     static final int NUM_READERS = 4;
128     static final int DURATION_MS = 10_000;
129 
130     static final IntHolder[] objects = new IntHolder[NUM_OBJECTS];
131     static final int[] expectedHash = new int[NUM_OBJECTS];
132 
133     static volatile boolean running = true;
134     static volatile String failure = null;
135 
136     public static void main(String[] args) throws Exception {
137         // Create objects and record their identity hash codes.
138         // After this, each object has is_hashed_not_expanded state.
139         for (int i = 0; i < NUM_OBJECTS; i++) {
140             objects[i] = new IntHolder(i);
141             expectedHash[i] = System.identityHashCode(objects[i]);
142         }
143 
144         // Reader threads: continuously read identity hash codes and verify
145         // they match the recorded values. During concurrent evacuation,
146         // readers may follow forwarding pointers to to-space copies.
147         // Without the fix, the copy may be visible before its hash is
148         // initialized, causing a mismatch.
149         Thread[] readers = new Thread[NUM_READERS];
150         for (int t = 0; t < NUM_READERS; t++) {
151             readers[t] = new Thread(() -> {
152                 while (running && failure == null) {
153                     for (int i = 0; i < NUM_OBJECTS; i++) {
154                         IntHolder obj = objects[i];
155                         if (obj == null) continue;
156                         int actual = System.identityHashCode(obj);
157                         int expected = expectedHash[i];
158                         if (actual != expected) {
159                             failure = "Hash mismatch at index " + i
160                                     + ": expected=" + expected
161                                     + " actual=" + actual;
162                             return;
163                         }
164                     }
165                 }
166             });
167             readers[t].setDaemon(true);
168             readers[t].start();
169         }
170 
171         // Main thread: allocate garbage to trigger GC and evacuation.
172         // The objects[] array keeps the hashed objects alive so they get
173         // evacuated rather than collected.
174         long deadline = System.currentTimeMillis() + DURATION_MS;
175         while (System.currentTimeMillis() < deadline && failure == null) {
176             for (int i = 0; i < 100; i++) {
177                 byte[] garbage = new byte[4096];
178             }
179             Thread.yield();
180         }
181 
182         running = false;
183         for (Thread t : readers) t.join(5000);
184 
185         if (failure != null) {
186             throw new RuntimeException(failure);
187         }
188         System.out.println("PASSED");
189     }
190 }