1 /*
  2  * Copyright (c) 2025, Oracle and/or its affiliates. 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 compiler.gcbarriers;
 25 
 26 import compiler.lib.ir_framework.*;
 27 import java.lang.ref.SoftReference;
 28 import java.lang.ref.WeakReference;
 29 import jdk.test.lib.Asserts;
 30 import jdk.internal.misc.Unsafe;
 31 
 32 /**
 33  * @test
 34  * @summary Test that implicit null checks are generated as expected for
 35             different GC memory accesses.
 36  * @library /test/lib /
 37  * @modules java.base/jdk.internal.misc
 38  * @run driver compiler.gcbarriers.TestImplicitNullChecks
 39  */
 40 
 41 
 42 public class TestImplicitNullChecks {
 43 
 44     static class Outer {
 45         Object f;
 46     }
 47 
 48     static class OuterWithVolatileField {
 49         volatile Object f;
 50     }
 51 
 52     static final Unsafe UNSAFE = Unsafe.getUnsafe();
 53     static final long F_OFFSET;
 54     static {
 55         try {
 56             F_OFFSET = UNSAFE.objectFieldOffset(Outer.class.getDeclaredField("f"));
 57         } catch (Exception e) {
 58             throw new Error(e);
 59         }
 60     }
 61 
 62     public static void main(String[] args) {
 63         TestFramework.runWithFlags("--add-exports", "java.base/jdk.internal.misc=ALL-UNNAMED",
 64                                    "-XX:CompileCommand=inline,java.lang.ref.*::*",
 65                                    "-XX:-TieredCompilation");
 66     }
 67 
 68     @Test
 69     // On AIX, implicit null checks are limited because the zero page is
 70     // readable (but not writable). See os::zero_page_read_protected().
 71     @IR(applyIfPlatform = {"aix", "false"},
 72         applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
 73         counts = {IRNode.NULL_CHECK, "1"},
 74         phase = CompilePhase.FINAL_CODE)
 75     static Object testLoad(Outer o) {
 76         return o.f;
 77     }
 78 
 79     @Test
 80     // On aarch64, volatile loads always use indirect memory operands, which
 81     // leads to a pattern that cannot be exploited by the current C2 analysis.
 82     // On PPC64, volatile loads are preceded by membar_volatile instructions,
 83     // which also inhibits the current C2 analysis.
 84     @IR(applyIfPlatformAnd = {"aarch64", "false", "ppc", "false"},
 85         applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
 86         counts = {IRNode.NULL_CHECK, "1"},
 87         phase = CompilePhase.FINAL_CODE)
 88     static Object testLoadVolatile(OuterWithVolatileField o) {
 89         return o.f;
 90     }
 91 
 92     @Run(test = {"testLoad",
 93                  "testLoadVolatile"},
 94          mode = RunMode.STANDALONE)
 95     static void runLoadTests() {
 96         {
 97             Outer o = new Outer();
 98             // Trigger compilation with implicit null check.
 99             for (int i = 0; i < 10_000; i++) {
100                 testLoad(o);
101             }
102             // Trigger null pointer exception.
103             o = null;
104             boolean nullPointerException = false;
105             try {
106                 testLoad(o);
107             } catch (NullPointerException e) { nullPointerException = true; }
108             Asserts.assertTrue(nullPointerException);
109         }
110         {
111             OuterWithVolatileField o = new OuterWithVolatileField();
112             // Trigger compilation with implicit null check.
113             for (int i = 0; i < 10_000; i++) {
114                 testLoadVolatile(o);
115             }
116             // Trigger null pointer exception.
117             o = null;
118             boolean nullPointerException = false;
119             try {
120                 testLoadVolatile(o);
121             } catch (NullPointerException e) { nullPointerException = true; }
122             Asserts.assertTrue(nullPointerException);
123         }
124     }
125 
126     @Test
127     // G1 and ZGC stores cannot be currently used to implement implicit null
128     // checks, because they expand into multiple memory access instructions that
129     // are not necessarily located at the initial instruction start address.
130     @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
131         failOn = IRNode.NULL_CHECK,
132         phase = CompilePhase.FINAL_CODE)
133     static void testStore(Outer o, Object o1) {
134         o.f = o1;
135     }
136 
137     @Run(test = {"testStore"})
138     static void runStoreTests() {
139         {
140             Outer o = new Outer();
141             Object o1 = new Object();
142             testStore(o, o1);
143         }
144     }
145 
146     @Test
147     // G1 and ZGC compare-and-exchange operations cannot be currently used to
148     // implement implicit null checks, because they expand into multiple memory
149     // access instructions that are not necessarily located at the initial
150     // instruction start address. The same holds for testCompareAndSwap and
151     // testGetAndSet below.
152     @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
153         failOn = IRNode.NULL_CHECK,
154         phase = CompilePhase.FINAL_CODE)
155     static Object testCompareAndExchange(Outer o, Object oldVal, Object newVal) {
156         return UNSAFE.compareAndExchangeReference(o, F_OFFSET, oldVal, newVal);
157     }
158 
159     @Test
160     @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
161         failOn = IRNode.NULL_CHECK,
162         phase = CompilePhase.FINAL_CODE)
163     static boolean testCompareAndSwap(Outer o, Object oldVal, Object newVal) {
164         return UNSAFE.compareAndSetReference(o, F_OFFSET, oldVal, newVal);
165     }
166 
167     @Test
168     @IR(applyIfOr = {"UseZGC", "true", "UseG1GC", "true"},
169         failOn = IRNode.NULL_CHECK,
170         phase = CompilePhase.FINAL_CODE)
171     static Object testGetAndSet(Outer o, Object newVal) {
172         return UNSAFE.getAndSetReference(o, F_OFFSET, newVal);
173     }
174 
175     @Run(test = {"testCompareAndExchange",
176                  "testCompareAndSwap",
177                  "testGetAndSet"})
178     static void runAtomicTests() {
179         {
180             Outer o = new Outer();
181             Object oldVal = new Object();
182             Object newVal = new Object();
183             testCompareAndExchange(o, oldVal, newVal);
184         }
185         {
186             Outer o = new Outer();
187             Object oldVal = new Object();
188             Object newVal = new Object();
189             testCompareAndSwap(o, oldVal, newVal);
190         }
191         {
192             Outer o = new Outer();
193             Object oldVal = new Object();
194             Object newVal = new Object();
195             testGetAndSet(o, newVal);
196         }
197     }
198 
199     @Test
200     // G1 reference loads use indirect memory operands, which leads to a pattern
201     // that cannot be exploited by the current C2 analysis. The same holds for
202     // testLoadWeakReference.
203     @IR(applyIf = {"UseZGC", "true"},
204         counts = {IRNode.NULL_CHECK, "1"},
205         phase = CompilePhase.FINAL_CODE)
206     static Object testLoadSoftReference(SoftReference<Object> ref) {
207         return ref.get();
208     }
209 
210     @Test
211     @IR(applyIf = {"UseZGC", "true"},
212         counts = {IRNode.NULL_CHECK, "1"},
213         phase = CompilePhase.FINAL_CODE)
214     static Object testLoadWeakReference(WeakReference<Object> ref) {
215         return ref.get();
216     }
217 
218     @Run(test = {"testLoadSoftReference",
219                  "testLoadWeakReference"})
220     static void runReferenceTests() {
221         {
222             Object o1 = new Object();
223             SoftReference<Object> sref = new SoftReference<Object>(o1);
224             Object o2 = testLoadSoftReference(sref);
225         }
226         {
227             Object o1 = new Object();
228             WeakReference<Object> wref = new WeakReference<Object>(o1);
229             Object o2 = testLoadWeakReference(wref);
230         }
231     }
232 
233 }