Use Intrinsics.checkNotNull and throw NPE in !! operator generation

This method was introduced in c204e8fc67 "just in case" and was never
used. Therefore we're free to change its semantics and use it in all new
generated code (with API version >= 1.4), without even worrying that the
newly used API will leak from inline functions in stdlib when used with
an older API version. Since we agreed to change the type of thrown
exceptions to java.lang.NPE in KT-22275, invoke a new method
throwJavaNpe now which throws that exception instead of KNPE.

Note that the additional method that takes an exception message is still
unused and exists just in case we need to use it in the future. The new
method throwJavaNpe is public also "just in case" we need to invoke it
in the future; currently it's not invoked from the bytecode.

 #KT-22275 In Progress
This commit is contained in:
Alexander Udalov
2019-07-26 17:05:47 +02:00
parent 6106b72283
commit a7c8fdcbe2
21 changed files with 148 additions and 13 deletions
@@ -4022,10 +4022,14 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
return StackValue.operation(base.type, base.kotlinType, v -> {
base.put(base.type, base.kotlinType, v);
v.dup();
Label ok = new Label();
v.ifnonnull(ok);
v.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "throwNpe", "()V", false);
v.mark(ok);
if (state.getLanguageVersionSettings().getApiVersion().compareTo(ApiVersion.KOTLIN_1_4) >= 0) {
v.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "checkNotNull", "(Ljava/lang/Object;)V", false);
} else {
Label ok = new Label();
v.ifnonnull(ok);
v.invokestatic(IntrinsicMethods.INTRINSICS_CLASS_NAME, "throwNpe", "()V", false);
v.mark(ok);
}
return null;
});
}
@@ -38,6 +38,7 @@ import org.jetbrains.org.objectweb.asm.tree.*
class RedundantNullCheckMethodTransformer(private val generationState: GenerationState) : MethodTransformer() {
override fun transform(internalClassName: String, methodNode: MethodNode) {
@Suppress("ControlFlowWithEmptyBody")
while (TransformerPass(internalClassName, methodNode, generationState).run()) {
}
}
@@ -69,6 +70,7 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
val value = when {
insn.isInstanceOfOrNullCheck() -> frame.top()
insn.isCheckNotNull() -> frame.top()
insn.isCheckExpressionValueIsNotNull() -> frame.peek(1)
else -> null
} as? StrictBasicValue ?: continue
@@ -84,6 +86,7 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
opcode == Opcodes.IFNULL ||
opcode == Opcodes.IFNONNULL ||
opcode == Opcodes.INSTANCEOF ||
isCheckNotNull() ||
isCheckExpressionValueIsNotNull()
private fun transformTrivialChecks(nullabilityMap: Map<AbstractInsnNode, StrictBasicValue>) {
@@ -95,6 +98,9 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
Opcodes.INSTANCEOF -> transformInstanceOf(insn as TypeInsnNode, nullability, value)
Opcodes.INVOKESTATIC -> {
if (insn.isCheckNotNull()) {
transformTrivialCheckNotNull(insn, nullability)
}
if (insn.isCheckExpressionValueIsNotNull()) {
transformTrivialCheckExpressionValueIsNotNull(insn, nullability)
}
@@ -134,6 +140,15 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
}
}
private fun transformTrivialCheckNotNull(insn: AbstractInsnNode, nullability: Nullability) {
if (nullability != Nullability.NOT_NULL) return
val dupInsn = insn.previous?.takeIf { it.opcode == Opcodes.DUP } ?: return
methodNode.instructions.run {
remove(dupInsn)
remove(insn)
}
}
private fun transformTrivialCheckExpressionValueIsNotNull(insn: AbstractInsnNode, nullability: Nullability) {
if (nullability != Nullability.NOT_NULL) return
val ldcInsn = insn.previous?.takeIf { it.opcode == Opcodes.LDC } ?: return
@@ -168,6 +183,14 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
}
}
insn.isCheckNotNull() -> {
val dupInsn = insn.previous ?: continue@insnLoop
if (dupInsn.opcode != Opcodes.DUP) continue@insnLoop
val aLoadInsn = dupInsn.previous ?: continue@insnLoop
if (aLoadInsn.opcode != Opcodes.ALOAD) continue@insnLoop
addDependentCheck(insn, aLoadInsn as VarInsnNode)
}
insn.isCheckParameterIsNotNull() -> {
val ldcInsn = insn.previous ?: continue@insnLoop
if (ldcInsn.opcode != Opcodes.LDC) continue@insnLoop
@@ -198,7 +221,7 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
private fun addDependentCheck(insn: AbstractInsnNode, aLoadInsn: VarInsnNode) {
checksDependingOnVariable.getOrPut(aLoadInsn.`var`) {
SmartList<AbstractInsnNode>()
SmartList()
}.add(insn)
}
@@ -224,8 +247,7 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode)
Opcodes.INVOKESTATIC -> {
when {
insn.isCheckParameterIsNotNull() ||
insn.isCheckExpressionValueIsNotNull() ->
insn.isCheckNotNull() || insn.isCheckParameterIsNotNull() || insn.isCheckExpressionValueIsNotNull() ->
injectAssumptionsForNotNullAssertion(varIndex, insn)
insn.isPseudo(PseudoInsn.STORE_NOT_NULL) ->
injectCodeForStoreNotNull(insn)
@@ -276,6 +298,11 @@ class RedundantNullCheckMethodTransformer(private val generationState: Generatio
}
private fun NullabilityAssumptions.injectAssumptionsForNotNullAssertion(varIndex: Int, insn: AbstractInsnNode) {
// ALOAD v
// DUP
// INVOKESTATIC checkNotNull
// <...> -- v is not null here (otherwise an exception was thrown)
// ALOAD v
// LDC *
// INVOKESTATIC checkParameterIsNotNull
@@ -396,6 +423,13 @@ internal fun AbstractInsnNode.isInstanceOfOrNullCheck() =
opcode == Opcodes.IFNULL ||
opcode == Opcodes.IFNONNULL
internal fun AbstractInsnNode.isCheckNotNull() =
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
name == "checkNotNull" &&
desc == "(Ljava/lang/Object;)V"
}
internal fun AbstractInsnNode.isCheckParameterIsNotNull() =
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
owner == IntrinsicMethods.INTRINSICS_CLASS_NAME &&
@@ -0,0 +1,14 @@
// !API_VERSION: LATEST
// TARGET_BACKEND: JVM
// IGNORE_BACKEND: JVM_IR
fun box(): String {
val s: String? = null
try {
s!!
return "Fail: NPE should have been thrown"
} catch (e: Throwable) {
if (e::class != NullPointerException::class) return "Fail: exception class should be NPE: ${e::class}"
return "OK"
}
}
@@ -6,3 +6,4 @@ fun box() {
// 0 IFNONNULL
// 0 ATHROW
// 0 throwNpe
// 0 checkNotNull
@@ -20,3 +20,4 @@ fun box() {
// 1 IFNONNULL
// 1 throwNpe
// 0 ATHROW
// 0 checkNotNull
@@ -19,3 +19,4 @@ fun box(u: Int) {
// 1 IFNONNULL
// 1 throwNpe
// 0 ATHROW
// 0 checkNotNull
@@ -10,3 +10,4 @@ fun box() {
// 0 IFNONNULL
// 0 throwNpe
// 0 ATHROW
// 0 checkNotNull
@@ -13,3 +13,4 @@ fun box() {
// 0 IFNONNULL
// 0 throwNpe
// 0 ATHROW
// 0 checkNotNull
@@ -17,3 +17,4 @@ fun box() {
// 1 IFNONNULL
// 1 throwNpe
// 0 ATHROW
// 0 checkNotNull
@@ -16,3 +16,4 @@ fun box(u: Int) {
// 1 IFNONNULL
// 1 throwNpe
// 0 ATHROW
// 0 checkNotNull
@@ -1,4 +1,4 @@
// NB '!!' uses Intrinsics.throwNpe()
// NB '!!' uses Intrinsics.throwNpe/checkNotNull
inline fun exit(): Nothing = null!!
fun box(): String {
@@ -13,4 +13,4 @@ fun box(): String {
return a
}
// 1 ATHROW
// 1 ATHROW
@@ -0,0 +1,13 @@
// !API_VERSION: LATEST
// IGNORE_BACKEND: JVM_IR
fun test(s: String?): Int {
s!!
s!!
s!!
s!!
s!!
return 0
}
// 1 checkNotNull
@@ -0,0 +1,14 @@
// !API_VERSION: LATEST
// IGNORE_BACKEND: JVM_IR
fun test(s: String?): Int {
s!!
if (s == null) {
return 5
}
return 3
}
// 1 checkNotNull
// 0 IFNULL
// 0 IFNONNULL
@@ -16404,6 +16404,11 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("exclExclThrowsNpe_1_4.kt")
public void testExclExclThrowsNpe_1_4() throws Exception {
runTest("compiler/testData/codegen/box/nullCheckOptimization/exclExclThrowsNpe_1_4.kt");
}
@TestMetadata("isNullable.kt")
public void testIsNullable() throws Exception {
runTest("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt");
@@ -3266,11 +3266,21 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt");
}
@TestMetadata("multipleExclExcl_1_4.kt")
public void testMultipleExclExcl_1_4() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/multipleExclExcl_1_4.kt");
}
@TestMetadata("notNullAsNotNullable.kt")
public void testNotNullAsNotNullable() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt");
}
@TestMetadata("nullCheckAfterExclExcl_1_4.kt")
public void testNullCheckAfterExclExcl_1_4() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/nullCheckAfterExclExcl_1_4.kt");
}
@TestMetadata("primitiveCheck.kt")
public void testPrimitiveCheck() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/primitiveCheck.kt");
@@ -16404,6 +16404,11 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("exclExclThrowsNpe_1_4.kt")
public void testExclExclThrowsNpe_1_4() throws Exception {
runTest("compiler/testData/codegen/box/nullCheckOptimization/exclExclThrowsNpe_1_4.kt");
}
@TestMetadata("isNullable.kt")
public void testIsNullable() throws Exception {
runTest("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt");
@@ -15289,6 +15289,11 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
}
@TestMetadata("exclExclThrowsNpe_1_4.kt")
public void testExclExclThrowsNpe_1_4() throws Exception {
runTest("compiler/testData/codegen/box/nullCheckOptimization/exclExclThrowsNpe_1_4.kt");
}
@TestMetadata("isNullable.kt")
public void testIsNullable() throws Exception {
runTest("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt");
@@ -3236,11 +3236,21 @@ public class IrBytecodeTextTestGenerated extends AbstractIrBytecodeTextTest {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt");
}
@TestMetadata("multipleExclExcl_1_4.kt")
public void testMultipleExclExcl_1_4() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/multipleExclExcl_1_4.kt");
}
@TestMetadata("notNullAsNotNullable.kt")
public void testNotNullAsNotNullable() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt");
}
@TestMetadata("nullCheckAfterExclExcl_1_4.kt")
public void testNullCheckAfterExclExcl_1_4() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/nullCheckAfterExclExcl_1_4.kt");
}
@TestMetadata("primitiveCheck.kt")
public void testPrimitiveCheck() throws Exception {
runTest("compiler/testData/codegen/bytecodeText/nullCheckOptimization/primitiveCheck.kt");
@@ -52,6 +52,9 @@ class ApiVersion private constructor(
@JvmField
val KOTLIN_1_3 = createByLanguageVersion(LanguageVersion.KOTLIN_1_3)
@JvmField
val KOTLIN_1_4 = createByLanguageVersion(LanguageVersion.KOTLIN_1_4)
@JvmField
val LATEST: ApiVersion = createByLanguageVersion(LanguageVersion.values().last())
@@ -10,9 +10,8 @@ import kotlin.SinceKotlin;
import kotlin.UninitializedPropertyAccessException;
import java.util.Arrays;
import java.util.List;
@SuppressWarnings("unused")
@SuppressWarnings({"unused", "WeakerAccess"})
public class Intrinsics {
private Intrinsics() {
}
@@ -23,13 +22,13 @@ public class Intrinsics {
public static void checkNotNull(Object object) {
if (object == null) {
throwNpe();
throwJavaNpe();
}
}
public static void checkNotNull(Object object, String message) {
if (object == null) {
throwNpe(message);
throwJavaNpe(message);
}
}
@@ -41,6 +40,16 @@ public class Intrinsics {
throw sanitizeStackTrace(new KotlinNullPointerException(message));
}
@SinceKotlin(version = "1.4")
public static void throwJavaNpe() {
throw sanitizeStackTrace(new NullPointerException());
}
@SinceKotlin(version = "1.4")
public static void throwJavaNpe(String message) {
throw sanitizeStackTrace(new NullPointerException(message));
}
public static void throwUninitializedProperty(String message) {
throw sanitizeStackTrace(new UninitializedPropertyAccessException(message));
}
@@ -3451,6 +3451,8 @@ public class kotlin/jvm/internal/Intrinsics {
public static fun throwIllegalArgument (Ljava/lang/String;)V
public static fun throwIllegalState ()V
public static fun throwIllegalState (Ljava/lang/String;)V
public static fun throwJavaNpe ()V
public static fun throwJavaNpe (Ljava/lang/String;)V
public static fun throwNpe ()V
public static fun throwNpe (Ljava/lang/String;)V
public static fun throwUndefinedForReified ()V