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 }