303757b3a0
Reviewed-by: kvn, roland
389 lines
15 KiB
Java
389 lines
15 KiB
Java
/*
|
|
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
|
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
*
|
|
* This code is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 only, as
|
|
* published by the Free Software Foundation.
|
|
*
|
|
* This code is distributed in the hope that it will be useful, but WITHOUT
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* version 2 for more details (a copy is included in the LICENSE file that
|
|
* accompanied this code).
|
|
*
|
|
* You should have received a copy of the GNU General Public License version
|
|
* 2 along with this work; if not, write to the Free Software Foundation,
|
|
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
*
|
|
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
|
* or visit www.oracle.com if you need additional information or have any
|
|
* questions.
|
|
*/
|
|
|
|
/*
|
|
* @test NullCheckDroppingsTest
|
|
* @bug 8054492
|
|
* @summary Casting can result in redundant null checks in generated code
|
|
* @requires vm.hasJFR
|
|
* @requires vm.flavor == "server" & !vm.emulatedClient & !vm.graal.enabled
|
|
* @library /test/lib
|
|
* @modules java.base/jdk.internal.misc
|
|
* java.management
|
|
*
|
|
* @build jdk.test.whitebox.WhiteBox
|
|
* @run driver jdk.test.lib.helpers.ClassFileInstaller jdk.test.whitebox.WhiteBox
|
|
* @run main/othervm -Xbootclasspath/a:. -XX:+IgnoreUnrecognizedVMOptions -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI
|
|
* -Xmixed -XX:-BackgroundCompilation -XX:-TieredCompilation -XX:CompileThreshold=1000
|
|
* -XX:+UnlockExperimentalVMOptions -XX:PerMethodTrapLimit=100 -XX:-StressReflectiveCode
|
|
* -XX:+UncommonNullCast -XX:-StressMethodHandleLinkerInlining -XX:TypeProfileLevel=0
|
|
* -XX:-AlwaysIncrementalInline -XX:-StressIncrementalInlining
|
|
* -XX:CompileCommand=exclude,compiler.intrinsics.klass.CastNullCheckDroppingsTest::runTest
|
|
* compiler.intrinsics.klass.CastNullCheckDroppingsTest
|
|
*/
|
|
|
|
package compiler.intrinsics.klass;
|
|
|
|
import jdk.jfr.Recording;
|
|
import jdk.jfr.consumer.RecordedEvent;
|
|
import jdk.test.lib.Platform;
|
|
import jdk.test.lib.jfr.EventNames;
|
|
import jdk.test.lib.jfr.Events;
|
|
import jdk.test.whitebox.WhiteBox;
|
|
import jdk.test.whitebox.code.NMethod;
|
|
|
|
import java.io.IOException;
|
|
import java.lang.invoke.MethodHandle;
|
|
import java.lang.invoke.MethodHandles;
|
|
import java.lang.invoke.MethodType;
|
|
import java.lang.reflect.Method;
|
|
import java.util.List;
|
|
import java.util.function.BiFunction;
|
|
|
|
public class CastNullCheckDroppingsTest {
|
|
|
|
private static final WhiteBox WHITE_BOX = WhiteBox.getWhiteBox();
|
|
|
|
static final BiFunction<Class, Object, Object> fCast = (c, o) -> c.cast(o);
|
|
|
|
static final MethodHandle SET_SSINK;
|
|
static final MethodHandle MH_CAST;
|
|
|
|
static {
|
|
try {
|
|
SET_SSINK = MethodHandles.lookup().findSetter(CastNullCheckDroppingsTest.class, "ssink", String.class);
|
|
MH_CAST = MethodHandles.lookup().findVirtual(Class.class,
|
|
"cast",
|
|
MethodType.methodType(Object.class, Object.class));
|
|
}
|
|
catch (Exception e) {
|
|
throw new Error(e);
|
|
}
|
|
}
|
|
|
|
static volatile String svalue = "A";
|
|
static volatile Object onull = null;
|
|
static volatile Integer iobj = new Integer(0);
|
|
static volatile int[] arr = new int[2];
|
|
static volatile Class objClass = String.class;
|
|
static volatile Class nullClass = null;
|
|
|
|
String ssink;
|
|
Integer isink;
|
|
int[] asink;
|
|
|
|
public static void main(String[] args) throws Exception {
|
|
if (!Platform.isServer() || Platform.isEmulatedClient()) {
|
|
throw new Error("TESTBUG: Not server mode");
|
|
}
|
|
// Make sure background compilation is disabled
|
|
if (WHITE_BOX.getBooleanVMFlag("BackgroundCompilation")) {
|
|
throw new Error("TESTBUG: Background compilation enabled");
|
|
}
|
|
// Make sure Tiered compilation is disabled
|
|
if (WHITE_BOX.getBooleanVMFlag("TieredCompilation")) {
|
|
throw new Error("TESTBUG: Tiered compilation enabled");
|
|
}
|
|
|
|
Method methodClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testClassCast", String.class);
|
|
Method methodMHCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testMHCast", String.class);
|
|
Method methodMHSetter = CastNullCheckDroppingsTest.class.getDeclaredMethod("testMHSetter", String.class);
|
|
Method methodFunction = CastNullCheckDroppingsTest.class.getDeclaredMethod("testFunction", String.class);
|
|
|
|
CastNullCheckDroppingsTest t = new CastNullCheckDroppingsTest();
|
|
t.runTest(methodClassCast, false, svalue);
|
|
t.runTest(methodMHCast, false, svalue);
|
|
t.runTest(methodMHSetter, false, svalue);
|
|
t.runTest(methodFunction, false, svalue);
|
|
|
|
// Edge cases
|
|
Method methodClassCastNull = CastNullCheckDroppingsTest.class.getDeclaredMethod("testClassCastNull", String.class);
|
|
Method methodNullClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testNullClassCast", String.class);
|
|
Method methodClassCastObj = CastNullCheckDroppingsTest.class.getDeclaredMethod("testClassCastObj", Object.class);
|
|
Method methodObjClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testObjClassCast", String.class);
|
|
Method methodClassCastInt = CastNullCheckDroppingsTest.class.getDeclaredMethod("testClassCastInt", Object.class);
|
|
Method methodIntClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testIntClassCast", Object.class);
|
|
Method methodClassCastint = CastNullCheckDroppingsTest.class.getDeclaredMethod("testClassCastint", Object.class);
|
|
Method methodintClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testintClassCast", Object.class);
|
|
Method methodClassCastPrim = CastNullCheckDroppingsTest.class.getDeclaredMethod("testClassCastPrim", Object.class);
|
|
Method methodPrimClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testPrimClassCast", Object.class);
|
|
Method methodVarClassCast = CastNullCheckDroppingsTest.class.getDeclaredMethod("testVarClassCast", Class.class);
|
|
|
|
t.runTest(methodClassCastNull, false, svalue);
|
|
t.runTest(methodNullClassCast, false, svalue);
|
|
t.runTest(methodClassCastObj, false, svalue);
|
|
t.runTest(methodObjClassCast, false, svalue);
|
|
t.runTest(methodClassCastInt, false, svalue);
|
|
t.runTest(methodIntClassCast, true, svalue);
|
|
t.runTest(methodClassCastint, false, svalue);
|
|
t.runTest(methodintClassCast, false, svalue);
|
|
t.runTest(methodClassCastPrim, false, svalue);
|
|
t.runTest(methodPrimClassCast, true, svalue);
|
|
t.runTest(methodVarClassCast, true, objClass);
|
|
}
|
|
|
|
void testClassCast(String s) {
|
|
try {
|
|
ssink = String.class.cast(s);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testClassCastNull(String s) {
|
|
try {
|
|
ssink = String.class.cast(null);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testNullClassCast(String s) {
|
|
try {
|
|
ssink = (String)nullClass.cast(s);
|
|
throw new AssertionError("NullPointerException is not thrown");
|
|
} catch (NullPointerException t) {
|
|
// Ignore NullPointerException
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testClassCastObj(Object s) {
|
|
try {
|
|
ssink = String.class.cast(s);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testObjClassCast(String s) {
|
|
try {
|
|
ssink = (String)objClass.cast(s);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testVarClassCast(Class cl) {
|
|
try {
|
|
ssink = (String)cl.cast(svalue);
|
|
if (cl == null) {
|
|
throw new AssertionError("NullPointerException is not thrown");
|
|
}
|
|
} catch (NullPointerException t) {
|
|
// Ignore NullPointerException
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testClassCastInt(Object s) {
|
|
try {
|
|
ssink = String.class.cast(iobj);
|
|
throw new AssertionError("ClassCastException is not thrown");
|
|
} catch (ClassCastException t) {
|
|
// Ignore ClassCastException: Cannot cast java.lang.Integer to java.lang.String
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testIntClassCast(Object s) {
|
|
try {
|
|
isink = Integer.class.cast(s);
|
|
if (s != null) {
|
|
throw new AssertionError("ClassCastException is not thrown");
|
|
}
|
|
} catch (ClassCastException t) {
|
|
// Ignore ClassCastException: Cannot cast java.lang.String to java.lang.Integer
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testClassCastint(Object s) {
|
|
try {
|
|
ssink = String.class.cast(45);
|
|
throw new AssertionError("ClassCastException is not thrown");
|
|
} catch (ClassCastException t) {
|
|
// Ignore ClassCastException: Cannot cast java.lang.Integer to java.lang.String
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testintClassCast(Object s) {
|
|
try {
|
|
isink = int.class.cast(s);
|
|
if (s != null) {
|
|
throw new AssertionError("ClassCastException is not thrown");
|
|
}
|
|
} catch (ClassCastException t) {
|
|
// Ignore ClassCastException: Cannot cast java.lang.String to java.lang.Integer
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testClassCastPrim(Object s) {
|
|
try {
|
|
ssink = String.class.cast(arr);
|
|
throw new AssertionError("ClassCastException is not thrown");
|
|
} catch (ClassCastException t) {
|
|
// Ignore ClassCastException: Cannot cast [I to java.lang.String
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testPrimClassCast(Object s) {
|
|
try {
|
|
asink = int[].class.cast(s);
|
|
if (s != null) {
|
|
throw new AssertionError("ClassCastException is not thrown");
|
|
}
|
|
} catch (ClassCastException t) {
|
|
// Ignore ClassCastException: Cannot cast java.lang.String to [I
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testMHCast(String s) {
|
|
try {
|
|
ssink = (String) (Object) MH_CAST.invokeExact(String.class, (Object) s);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testMHSetter(String s) {
|
|
try {
|
|
SET_SSINK.invokeExact(this, s);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void testFunction(String s) {
|
|
try {
|
|
ssink = (String) fCast.apply(String.class, s);
|
|
} catch (Throwable t) {
|
|
throw new Error(t);
|
|
}
|
|
}
|
|
|
|
void runTest(Method method, boolean deopt, Object value) {
|
|
if (method == null) {
|
|
throw new AssertionError("method was not found");
|
|
}
|
|
// Ensure method is compiled
|
|
WHITE_BOX.testSetDontInlineMethod(method, true);
|
|
Recording recording = new Recording();
|
|
recording.enable(EventNames.Deoptimization);
|
|
recording.start();
|
|
|
|
for (int i = 0; i < 3000; i++) {
|
|
try {
|
|
method.invoke(this, value);
|
|
} catch (Exception e) {
|
|
throw new Error("Unexpected exception: ", e);
|
|
}
|
|
}
|
|
NMethod nm = getNMethod(method);
|
|
|
|
// Passing null should cause a de-optimization
|
|
// if method is compiled with a null-check.
|
|
try {
|
|
method.invoke(this, onull);
|
|
} catch (Exception e) {
|
|
throw new Error("Unexpected exception: ", e);
|
|
}
|
|
recording.stop();
|
|
List<RecordedEvent> events;
|
|
try {
|
|
events = Events.fromRecording(recording);
|
|
} catch (IOException e) {
|
|
throw new Error("failed to read jfr events", e);
|
|
}
|
|
|
|
checkDeoptimization(events, nm.compile_id, deopt);
|
|
|
|
if (!deopt) {
|
|
checkNoRecompilation(method, nm);
|
|
}
|
|
}
|
|
|
|
static NMethod getNMethod(Method test) {
|
|
// Because background compilation is disabled, method should now be compiled
|
|
if (!WHITE_BOX.isMethodCompiled(test)) {
|
|
throw new AssertionError(test + " not compiled");
|
|
}
|
|
|
|
NMethod nm = NMethod.get(test, false); // not OSR nmethod
|
|
if (nm == null) {
|
|
throw new AssertionError(test + " missing nmethod?");
|
|
}
|
|
if (nm.comp_level != 4) {
|
|
throw new AssertionError(test + " compiled by not C2: " + nm);
|
|
}
|
|
return nm;
|
|
}
|
|
|
|
static void checkDeoptimization(List<RecordedEvent> events, int compilerId, boolean mustExist) {
|
|
boolean exist = events.stream()
|
|
.filter(e -> e.getEventType().getName().equals(EventNames.Deoptimization))
|
|
.anyMatch(e -> compilerId == Events.assertField(e, "compileId").<Integer>getValue());
|
|
|
|
if (exist != mustExist) {
|
|
System.err.println("events:");
|
|
System.err.println(events);
|
|
throw new AssertionError("compilation must " + (mustExist ? "" : " not ") + " get deoptimized");
|
|
}
|
|
|
|
if (mustExist && events.stream()
|
|
.filter(e -> e.getEventType().getName().equals(EventNames.Deoptimization))
|
|
.filter(e -> compilerId == Events.assertField(e, "compileId").<Integer>getValue())
|
|
.noneMatch(e -> "null_check".equals(Events.assertField(e, "reason").getValue()))) {
|
|
System.err.println("events:");
|
|
System.err.println(events);
|
|
throw new AssertionError("no deoptimizations due to null_check found");
|
|
}
|
|
|
|
}
|
|
|
|
static void checkNoRecompilation(Method method, NMethod nmOrig) {
|
|
NMethod nm = NMethod.get(method, false); // not OSR nmethod
|
|
if (nm == null) {
|
|
throw new AssertionError(method + " missing nmethod?");
|
|
}
|
|
if (nm.comp_level != 4) {
|
|
throw new AssertionError(method + " compiled by not C2: " + nm);
|
|
}
|
|
if (nm.compile_id != nmOrig.compile_id) {
|
|
throw new AssertionError(method + " was recompiled: old nmethod=" + nmOrig + ", new nmethod=" + nm);
|
|
}
|
|
}
|
|
}
|