1 /*
   2  * Copyright (c) 2014, 2015, 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 /*
  25  * @test NullCheckDroppingsTest
  26  * @bug 8054492
  27  * @summary "Casting can result in redundant null checks in generated code"
  28  * @library /testlibrary /../../test/lib /testlibrary/com/oracle/java/testlibrary
  29  * @modules java.base/sun.misc
  30  *          java.management
  31  * @build NullCheckDroppingsTest
  32  * @run main ClassFileInstaller sun.hotspot.WhiteBox
  33  *                              sun.hotspot.WhiteBox$WhiteBoxPermission
  34  * @run main ClassFileInstaller jdk.test.lib.Platform
  35  * @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
  36  *                   -Xmixed -XX:-BackgroundCompilation -XX:-TieredCompilation -XX:CompileThreshold=1000
  37  *                   -XX:CompileCommand=exclude,NullCheckDroppingsTest::runTest NullCheckDroppingsTest
  38  */
  39 
  40 import sun.hotspot.WhiteBox;
  41 import sun.hotspot.code.NMethod;
  42 import jdk.test.lib.Platform;
  43 
  44 import java.lang.reflect.Method;
  45 import java.lang.invoke.MethodHandle;
  46 import java.lang.invoke.MethodHandles;
  47 import java.lang.invoke.MethodType;
  48 import java.util.function.BiFunction;
  49 
  50 public class NullCheckDroppingsTest {
  51 
  52     private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
  53 
  54     static final BiFunction<Class, Object, Object> fCast = (c, o) -> c.cast(o);
  55 
  56     static final MethodHandle SET_SSINK;
  57     static final MethodHandle MH_CAST;
  58 
  59     static {
  60         try {
  61             SET_SSINK = MethodHandles.lookup().findSetter(NullCheckDroppingsTest.class, "ssink", String.class);
  62             MH_CAST = MethodHandles.lookup().findVirtual(Class.class,
  63                                                          "cast",
  64                                                          MethodType.methodType(Object.class, Object.class));
  65         }
  66         catch (Exception e) {
  67             throw new Error(e);
  68         }
  69     }
  70 
  71     static volatile String svalue = "A";
  72     static volatile String snull = null;
  73     static volatile Integer iobj = new Integer(0);
  74     static volatile int[] arr = new int[2];
  75     static volatile Class objClass = String.class;
  76     static volatile Class nullClass = null;
  77 
  78     String  ssink;
  79     Integer isink;
  80     int[]   asink;
  81 
  82     public static void main(String[] args) throws Exception {
  83 
  84         // Only test C2 in Server VM
  85         if (!Platform.isServer()) {
  86             return;
  87         }
  88         // Make sure background compilation is disabled
  89         if (WHITE_BOX.getBooleanVMFlag("BackgroundCompilation")) {
  90             throw new AssertionError("Background compilation enabled");
  91         }
  92         // Make sure Tiered compilation is disabled
  93         if (WHITE_BOX.getBooleanVMFlag("TieredCompilation")) {
  94             throw new AssertionError("Tiered compilation enabled");
  95         }
  96 
  97         Method methodClassCast = NullCheckDroppingsTest.class.getDeclaredMethod("testClassCast", String.class);
  98         Method methodMHCast    = NullCheckDroppingsTest.class.getDeclaredMethod("testMHCast",    String.class);
  99         Method methodMHSetter  = NullCheckDroppingsTest.class.getDeclaredMethod("testMHSetter",  String.class);
 100         Method methodFunction  = NullCheckDroppingsTest.class.getDeclaredMethod("testFunction",  String.class);
 101 
 102         NullCheckDroppingsTest t = new NullCheckDroppingsTest();
 103         t.runTest(methodClassCast, false);
 104         t.runTest(methodMHCast,    false);
 105         t.runTest(methodMHSetter,  false);
 106         t.runTest(methodFunction,  false);
 107 
 108         // Edge cases
 109         Method methodClassCastNull = NullCheckDroppingsTest.class.getDeclaredMethod("testClassCastNull", String.class);
 110         Method methodNullClassCast = NullCheckDroppingsTest.class.getDeclaredMethod("testNullClassCast", String.class);
 111         Method methodClassCastObj  = NullCheckDroppingsTest.class.getDeclaredMethod("testClassCastObj",  Object.class);
 112         Method methodObjClassCast  = NullCheckDroppingsTest.class.getDeclaredMethod("testObjClassCast",  String.class);
 113         Method methodVarClassCast  = NullCheckDroppingsTest.class.getDeclaredMethod("testVarClassCast",  String.class);
 114         Method methodClassCastInt  = NullCheckDroppingsTest.class.getDeclaredMethod("testClassCastInt",  Object.class);
 115         Method methodIntClassCast  = NullCheckDroppingsTest.class.getDeclaredMethod("testIntClassCast",  Object.class);
 116         Method methodClassCastint  = NullCheckDroppingsTest.class.getDeclaredMethod("testClassCastint",  Object.class);
 117         Method methodintClassCast  = NullCheckDroppingsTest.class.getDeclaredMethod("testintClassCast",  Object.class);
 118         Method methodClassCastPrim = NullCheckDroppingsTest.class.getDeclaredMethod("testClassCastPrim", Object.class);
 119         Method methodPrimClassCast = NullCheckDroppingsTest.class.getDeclaredMethod("testPrimClassCast", Object.class);
 120 
 121         t.runTest(methodClassCastNull, false);
 122         t.runTest(methodNullClassCast, false);
 123         t.runTest(methodClassCastObj,  false);
 124         t.runTest(methodObjClassCast,  true);
 125         t.runTest(methodVarClassCast,  true);
 126         t.runTest(methodClassCastInt,  false);
 127         t.runTest(methodIntClassCast,  true);
 128         t.runTest(methodClassCastint,  false);
 129         t.runTest(methodintClassCast,  false);
 130         t.runTest(methodClassCastPrim, false);
 131         t.runTest(methodPrimClassCast, true);
 132     }
 133 
 134     void testClassCast(String s) {
 135         try {
 136             ssink = String.class.cast(s);
 137         } catch (Throwable t) {
 138             throw new Error(t);
 139         }
 140     }
 141 
 142     void testClassCastNull(String s) {
 143         try {
 144             ssink = String.class.cast(null);
 145         } catch (Throwable t) {
 146             throw new Error(t);
 147         }
 148     }
 149 
 150     void testNullClassCast(String s) {
 151         try {
 152             ssink = (String)nullClass.cast(s);
 153             throw new AssertionError("NullPointerException is not thrown");
 154         } catch (NullPointerException t) {
 155             // Ignore NullPointerException
 156         } catch (Throwable t) {
 157             throw new Error(t);
 158         }
 159     }
 160 
 161     void testClassCastObj(Object s) {
 162         try {
 163             ssink = String.class.cast(s);
 164         } catch (Throwable t) {
 165             throw new Error(t);
 166         }
 167     }
 168 
 169     void testObjClassCast(String s) {
 170         try {
 171             ssink = (String)objClass.cast(s);
 172         } catch (Throwable t) {
 173             throw new Error(t);
 174         }
 175     }
 176 
 177     void testVarClassCast(String s) {
 178         Class cl = (s == null) ? null : String.class;
 179         try {
 180             ssink = (String)cl.cast(svalue);
 181             if (s == null) {
 182                 throw new AssertionError("NullPointerException is not thrown");
 183             }
 184         } catch (NullPointerException t) {
 185             // Ignore NullPointerException
 186         } catch (Throwable t) {
 187             throw new Error(t);
 188         }
 189     }
 190 
 191     void testClassCastInt(Object s) {
 192         try {
 193             ssink = String.class.cast(iobj);
 194             throw new AssertionError("ClassCastException is not thrown");
 195         } catch (ClassCastException t) {
 196             // Ignore ClassCastException: Cannot cast java.lang.Integer to java.lang.String
 197         } catch (Throwable t) {
 198             throw new Error(t);
 199         }
 200     }
 201 
 202     void testIntClassCast(Object s) {
 203         try {
 204             isink = Integer.class.cast(s);
 205             if (s != null) {
 206                 throw new AssertionError("ClassCastException is not thrown");
 207             }
 208         } catch (ClassCastException t) {
 209             // Ignore ClassCastException: Cannot cast java.lang.String to java.lang.Integer
 210         } catch (Throwable t) {
 211             throw new Error(t);
 212         }
 213     }
 214 
 215     void testClassCastint(Object s) {
 216         try {
 217             ssink = String.class.cast(45);
 218             throw new AssertionError("ClassCastException is not thrown");
 219         } catch (ClassCastException t) {
 220             // Ignore ClassCastException: Cannot cast java.lang.Integer to java.lang.String
 221         } catch (Throwable t) {
 222             throw new Error(t);
 223         }
 224     }
 225 
 226     void testintClassCast(Object s) {
 227         try {
 228             isink = int.class.cast(s);
 229             if (s != null) {
 230                 throw new AssertionError("ClassCastException is not thrown");
 231             }
 232         } catch (ClassCastException t) {
 233             // Ignore ClassCastException: Cannot cast java.lang.String to java.lang.Integer
 234         } catch (Throwable t) {
 235             throw new Error(t);
 236         }
 237     }
 238 
 239     void testClassCastPrim(Object s) {
 240         try {
 241             ssink = String.class.cast(arr);
 242             throw new AssertionError("ClassCastException is not thrown");
 243         } catch (ClassCastException t) {
 244             // Ignore ClassCastException: Cannot cast [I to java.lang.String
 245         } catch (Throwable t) {
 246             throw new Error(t);
 247         }
 248     }
 249 
 250     void testPrimClassCast(Object s) {
 251         try {
 252             asink = int[].class.cast(s);
 253             if (s != null) {
 254                 throw new AssertionError("ClassCastException is not thrown");
 255             }
 256         } catch (ClassCastException t) {
 257             // Ignore ClassCastException: Cannot cast java.lang.String to [I
 258         } catch (Throwable t) {
 259             throw new Error(t);
 260         }
 261     }
 262 
 263     void testMHCast(String s) {
 264         try {
 265             ssink = (String) (Object) MH_CAST.invokeExact(String.class, (Object) s);
 266         } catch (Throwable t) {
 267             throw new Error(t);
 268         }
 269     }
 270 
 271     void testMHSetter(String s) {
 272         try {
 273             SET_SSINK.invokeExact(this, s);
 274         } catch (Throwable t) {
 275             throw new Error(t);
 276         }
 277     }
 278 
 279     void testFunction(String s) {
 280         try {
 281             ssink = (String) fCast.apply(String.class, s);
 282         } catch (Throwable t) {
 283             throw new Error(t);
 284         }
 285     }
 286 
 287     void runTest(Method method, boolean deopt) {
 288         if (method == null) {
 289             throw new AssertionError("method was not found");
 290         }
 291         // Ensure method is compiled
 292         WHITE_BOX.testSetDontInlineMethod(method, true);
 293         for (int i = 0; i < 3000; i++) {
 294             try {
 295                 method.invoke(this, svalue);
 296             } catch (Exception e) {
 297                 throw new Error("Unexpected exception: ", e);
 298             }
 299         }
 300         NMethod nm = getNMethod(method);
 301 
 302         // Passing null should cause a de-optimization
 303         // if method is compiled with a null-check.
 304         try {
 305             method.invoke(this, snull);
 306         } catch (Exception e) {
 307             throw new Error("Unexpected exception: ", e);
 308         }
 309         checkDeoptimization(method, nm, deopt);
 310     }
 311 
 312     static NMethod getNMethod(Method test) {
 313         // Because background compilation is disabled, method should now be compiled
 314         if (!WHITE_BOX.isMethodCompiled(test)) {
 315             throw new AssertionError(test + " not compiled");
 316         }
 317 
 318         NMethod nm = NMethod.get(test, false); // not OSR nmethod
 319         if (nm == null) {
 320             throw new AssertionError(test + " missing nmethod?");
 321         }
 322         if (nm.comp_level != 4) {
 323             throw new AssertionError(test + " compiled by not C2: " + nm);
 324         }
 325         return nm;
 326     }
 327 
 328     static void checkDeoptimization(Method method, NMethod nmOrig, boolean deopt) {
 329         // Check deoptimization event (intrinsic Class.cast() works).
 330         if (WHITE_BOX.isMethodCompiled(method) == deopt) {
 331             throw new AssertionError(method + " was" + (deopt ? " not" : "") + " deoptimized");
 332         }
 333         if (deopt) {
 334             return;
 335         }
 336         // Ensure no recompilation when no deoptimization is expected.
 337         NMethod nm = NMethod.get(method, false); // not OSR nmethod
 338         if (nm == null) {
 339             throw new AssertionError(method + " missing nmethod?");
 340         }
 341         if (nm.comp_level != 4) {
 342             throw new AssertionError(method + " compiled by not C2: " + nm);
 343         }
 344         if (nm.compile_id != nmOrig.compile_id) {
 345             throw new AssertionError(method + " was recompiled: old nmethod=" + nmOrig + ", new nmethod=" + nm);
 346         }
 347     }
 348 }