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