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