KT-16245 Redundant null-check generated for a cast of already non-nullable value
KT-16194 Code with unnecessary safe call contains redundant boxing/unboxing for primitive values
KT-12839 Two null checks are generated when manually null checking platform type
Recognize some additional cases of trivial null checks and trivial instance-of checks.
A variable is "checked for null", if it is:
- a function parameter checked with 'INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull'
- checked for nullability with 'IFNULL/IFNONNULL'
- checked for nullability with 'INSTANCEOF'
(if objectref is instance-of T, then objectref is non-null)
Before analyzing nullability, introduce synthetic assumptions for execution branches
where a variable is guaranteed to be null or not null. For example, the following bytecode:
ALOAD 1 // Ljava/lang/String;
IFNULL L
<non-null branch>
L:
<null branch>
is transformed to
ALOAD 1
IFNULL L1
NEW java/lang/String
ASTORE 1 // tells analyzer that variable 1 is non-null
<non-null branch>
L:
<null branch>
L1:
ACONST_NULL
ASTORE 1 // tells analyzer that variable 1 is null
GOTO L
After the analysis is performed on a preprocessed method,
remember the results for "interesting" instructions
and revert the preprocessing transformations.
After that, perform bytecode transformations as usual.
Do not transform INSTANCEOF to-be-reified, because reification at call site
can introduce null checks. E.g.,
inline fun <reified T> isNullable() = null is T
...
assert(isNullable<String?>())
This commit is contained in:
+1
-1
@@ -421,7 +421,7 @@ private fun InstructionAdapter.generateResumeWithExceptionCheck() {
|
|||||||
|
|
||||||
private fun Type.fieldNameForVar(index: Int) = descriptor.first() + "$" + index
|
private fun Type.fieldNameForVar(index: Int) = descriptor.first() + "$" + index
|
||||||
|
|
||||||
private fun withInstructionAdapter(block: InstructionAdapter.() -> Unit): InsnList {
|
inline fun withInstructionAdapter(block: InstructionAdapter.() -> Unit): InsnList {
|
||||||
val tmpMethodNode = MethodNode()
|
val tmpMethodNode = MethodNode()
|
||||||
|
|
||||||
InstructionAdapter(tmpMethodNode).apply(block)
|
InstructionAdapter(tmpMethodNode).apply(block)
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ReifiedTypeInliner(private val parametersMapping: TypeParameterMappings?)
|
|||||||
const val REIFIED_OPERATION_MARKER_METHOD_NAME = "reifiedOperationMarker"
|
const val REIFIED_OPERATION_MARKER_METHOD_NAME = "reifiedOperationMarker"
|
||||||
const val NEED_CLASS_REIFICATION_MARKER_METHOD_NAME = "needClassReification"
|
const val NEED_CLASS_REIFICATION_MARKER_METHOD_NAME = "needClassReification"
|
||||||
|
|
||||||
private fun isOperationReifiedMarker(insn: AbstractInsnNode) =
|
fun isOperationReifiedMarker(insn: AbstractInsnNode) =
|
||||||
isReifiedMarker(insn) { it == REIFIED_OPERATION_MARKER_METHOD_NAME }
|
isReifiedMarker(insn) { it == REIFIED_OPERATION_MARKER_METHOD_NAME }
|
||||||
|
|
||||||
private fun isReifiedMarker(insn: AbstractInsnNode, namePredicate: (String) -> Boolean): Boolean {
|
private fun isReifiedMarker(insn: AbstractInsnNode, namePredicate: (String) -> Boolean): Boolean {
|
||||||
|
|||||||
+6
-1
@@ -29,9 +29,13 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun transformWithResult(internalClassName: String, methodNode: MethodNode): Result {
|
fun transformWithResult(internalClassName: String, methodNode: MethodNode): Result {
|
||||||
|
val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter())
|
||||||
|
return removeDeadCodeByFrames(methodNode, frames)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeDeadCodeByFrames(methodNode: MethodNode, frames: Array<out Any?>): Result {
|
||||||
val removedNodes = HashSet<AbstractInsnNode>()
|
val removedNodes = HashSet<AbstractInsnNode>()
|
||||||
|
|
||||||
val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter())
|
|
||||||
val insnList = methodNode.instructions
|
val insnList = methodNode.instructions
|
||||||
val insnsArray = insnList.toArray()
|
val insnsArray = insnList.toArray()
|
||||||
|
|
||||||
@@ -51,6 +55,7 @@ class DeadCodeEliminationMethodTransformer : MethodTransformer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Result(val removedNodes: Set<AbstractInsnNode>) {
|
class Result(val removedNodes: Set<AbstractInsnNode>) {
|
||||||
|
fun hasRemovedAnything() = removedNodes.isNotEmpty()
|
||||||
fun isRemoved(node: AbstractInsnNode) = removedNodes.contains(node)
|
fun isRemoved(node: AbstractInsnNode) = removedNodes.contains(node)
|
||||||
fun isAlive(node: AbstractInsnNode) = !isRemoved(node)
|
fun isAlive(node: AbstractInsnNode) = !isRemoved(node)
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -23,7 +23,7 @@ import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTra
|
|||||||
import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantCoercionToUnitTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantCoercionToUnitTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.captured.CapturedVarsOptimizationMethodTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.captured.CapturedVarsOptimizationMethodTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.UtilKt;
|
import org.jetbrains.kotlin.codegen.optimization.common.UtilKt;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckV2MethodTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer;
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
||||||
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode;
|
||||||
@@ -35,7 +35,7 @@ public class OptimizationMethodVisitor extends TransformationMethodVisitor {
|
|||||||
|
|
||||||
private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] {
|
private static final MethodTransformer[] OPTIMIZATION_TRANSFORMERS = new MethodTransformer[] {
|
||||||
new CapturedVarsOptimizationMethodTransformer(),
|
new CapturedVarsOptimizationMethodTransformer(),
|
||||||
new RedundantNullCheckMethodTransformer(),
|
new RedundantNullCheckV2MethodTransformer(),
|
||||||
new RedundantBoxingMethodTransformer(),
|
new RedundantBoxingMethodTransformer(),
|
||||||
new RedundantCoercionToUnitTransformer(),
|
new RedundantCoercionToUnitTransformer(),
|
||||||
new DeadCodeEliminationMethodTransformer(),
|
new DeadCodeEliminationMethodTransformer(),
|
||||||
|
|||||||
+1
-1
@@ -26,7 +26,7 @@ fun <V : Value> Frame<V>.top(): V? =
|
|||||||
peek(0)
|
peek(0)
|
||||||
|
|
||||||
fun <V : Value> Frame<V>.peek(offset: Int): V? =
|
fun <V : Value> Frame<V>.peek(offset: Int): V? =
|
||||||
if (stackSize >= offset) getStack(stackSize - offset - 1) else null
|
if (stackSize > offset) getStack(stackSize - offset - 1) else null
|
||||||
|
|
||||||
class SavedStackDescriptor(
|
class SavedStackDescriptor(
|
||||||
val savedValues: List<BasicValue>,
|
val savedValues: List<BasicValue>,
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
|||||||
import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||||
|
|
||||||
class NullabilityV2Interpreter : OptimizationBasicInterpreter() {
|
class NullabilityInterpreter : OptimizationBasicInterpreter() {
|
||||||
override fun newOperation(insn: AbstractInsnNode): BasicValue? {
|
override fun newOperation(insn: AbstractInsnNode): BasicValue? {
|
||||||
val defaultResult = super.newOperation(insn)
|
val defaultResult = super.newOperation(insn)
|
||||||
val resultType = defaultResult?.type
|
val resultType = defaultResult?.type
|
||||||
|
|||||||
+1
-13
@@ -37,18 +37,6 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum class Nullability {
|
|
||||||
NULL, NOT_NULL, NULLABLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BasicValue.getNullability(): Nullability =
|
|
||||||
when (this) {
|
|
||||||
is NullBasicValue -> Nullability.NULL
|
|
||||||
is NotNullBasicValue -> Nullability.NOT_NULL
|
|
||||||
is ProgressionIteratorBasicValue -> Nullability.NOT_NULL
|
|
||||||
else -> Nullability.NULLABLE
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isAlwaysFalse(opcode: Int, nullability: Nullability) =
|
private fun isAlwaysFalse(opcode: Int, nullability: Nullability) =
|
||||||
(opcode == Opcodes.IFNULL && nullability == Nullability.NOT_NULL) ||
|
(opcode == Opcodes.IFNULL && nullability == Nullability.NOT_NULL) ||
|
||||||
(opcode == Opcodes.IFNONNULL && nullability == Nullability.NULL)
|
(opcode == Opcodes.IFNONNULL && nullability == Nullability.NULL)
|
||||||
@@ -70,7 +58,7 @@ class RedundantNullCheckMethodTransformer : MethodTransformer() {
|
|||||||
}
|
}
|
||||||
if (nullCheckIfs.isEmpty()) return false
|
if (nullCheckIfs.isEmpty()) return false
|
||||||
|
|
||||||
val frames = analyze(internalClassName, methodNode, NullabilityV2Interpreter())
|
val frames = analyze(internalClassName, methodNode, NullabilityInterpreter())
|
||||||
|
|
||||||
val redundantNullCheckIfs = nullCheckIfs.mapNotNull { insn ->
|
val redundantNullCheckIfs = nullCheckIfs.mapNotNull { insn ->
|
||||||
frames[instructions.indexOf(insn)]?.top()?.let { top ->
|
frames[instructions.indexOf(insn)]?.top()?.let { top ->
|
||||||
|
|||||||
+367
@@ -0,0 +1,367 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2017 JetBrains s.r.o.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.codegen.optimization.nullCheck
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.withInstructionAdapter
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.OptimizationBasicInterpreter
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.isInsn
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer
|
||||||
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
|
||||||
|
import org.jetbrains.org.objectweb.asm.Label
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
|
|
||||||
|
class RedundantNullCheckV2MethodTransformer : MethodTransformer() {
|
||||||
|
override fun transform(internalClassName: String, methodNode: MethodNode) {
|
||||||
|
while (TransformerPass(internalClassName, methodNode).run()) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TransformerPass(val internalClassName: String, val methodNode: MethodNode) {
|
||||||
|
private var changes = false
|
||||||
|
|
||||||
|
private fun AbstractInsnNode.getIndex() =
|
||||||
|
methodNode.instructions.indexOf(this)
|
||||||
|
|
||||||
|
fun run(): Boolean {
|
||||||
|
val checkedReferenceTypes = analyzeTypesAndRemoveDeadCode()
|
||||||
|
eliminateRedundantChecks(checkedReferenceTypes)
|
||||||
|
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun analyzeTypesAndRemoveDeadCode(): Map<AbstractInsnNode, Type> {
|
||||||
|
val insns = methodNode.instructions.toArray()
|
||||||
|
val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter())
|
||||||
|
|
||||||
|
val checkedReferenceTypes = HashMap<AbstractInsnNode, Type>()
|
||||||
|
for (i in insns.indices) {
|
||||||
|
val insn = insns[i]
|
||||||
|
val frame = frames[i]
|
||||||
|
if (insn.isInstanceOfOrNullCheck()) {
|
||||||
|
checkedReferenceTypes[insn] = frame?.top()?.type ?: continue
|
||||||
|
}
|
||||||
|
else if (insn.isCheckParameterNotNull()) {
|
||||||
|
checkedReferenceTypes[insn] = frame?.peek(1)?.type ?: continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val dceResult = DeadCodeEliminationMethodTransformer().removeDeadCodeByFrames(methodNode, frames)
|
||||||
|
if (dceResult.hasRemovedAnything()) {
|
||||||
|
changes = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkedReferenceTypes
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun eliminateRedundantChecks(checkedReferenceTypes: Map<AbstractInsnNode, Type>) {
|
||||||
|
val nullabilityAssumptions = injectNullabilityAssumptions(checkedReferenceTypes)
|
||||||
|
|
||||||
|
val nullabilityMap = analyzeNullabilities()
|
||||||
|
|
||||||
|
nullabilityAssumptions.revert()
|
||||||
|
|
||||||
|
transformTrivialChecks(nullabilityMap)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectNullabilityAssumptions(checkedReferenceTypes: Map<AbstractInsnNode, Type>) =
|
||||||
|
NullabilityAssumptionsBuilder(checkedReferenceTypes).injectNullabilityAssumptions()
|
||||||
|
|
||||||
|
private fun analyzeNullabilities(): Map<AbstractInsnNode, Nullability> {
|
||||||
|
val frames = analyze(internalClassName, methodNode, NullabilityInterpreter())
|
||||||
|
val insns = methodNode.instructions.toArray()
|
||||||
|
val nullabilityMap = HashMap<AbstractInsnNode, Nullability>()
|
||||||
|
for (i in insns.indices) {
|
||||||
|
val nullability = frames[i]?.top()?.getNullability() ?: continue
|
||||||
|
if (nullability == Nullability.NULLABLE) continue
|
||||||
|
|
||||||
|
val insn = insns[i]
|
||||||
|
if (insn.isInstanceOfOrNullCheck()) {
|
||||||
|
nullabilityMap[insn] = nullability
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullabilityMap
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformTrivialChecks(nullabilityMap: Map<AbstractInsnNode, Nullability>) {
|
||||||
|
for ((insn, nullability) in nullabilityMap) {
|
||||||
|
when (insn.opcode) {
|
||||||
|
Opcodes.IFNULL -> transformTrivialNullJump(insn as JumpInsnNode, nullability == Nullability.NULL)
|
||||||
|
Opcodes.IFNONNULL -> transformTrivialNullJump(insn as JumpInsnNode, nullability == Nullability.NOT_NULL)
|
||||||
|
Opcodes.INSTANCEOF -> transformInstanceOf(insn, nullability)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformTrivialNullJump(insn: JumpInsnNode, alwaysTrue: Boolean) {
|
||||||
|
changes = true
|
||||||
|
|
||||||
|
methodNode.instructions.run {
|
||||||
|
popReferenceValueBefore(insn)
|
||||||
|
if (alwaysTrue) {
|
||||||
|
set(insn, JumpInsnNode(Opcodes.GOTO, insn.label))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remove(insn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformInstanceOf(insn: AbstractInsnNode, nullability: Nullability) {
|
||||||
|
if (nullability != Nullability.NULL) return
|
||||||
|
if (ReifiedTypeInliner.isOperationReifiedMarker(insn.previous)) return
|
||||||
|
|
||||||
|
changes = true
|
||||||
|
|
||||||
|
val nextOpcode = insn.next?.opcode
|
||||||
|
if (nextOpcode == Opcodes.IFEQ || nextOpcode == Opcodes.IFNE)
|
||||||
|
transformNullInstanceOfWithJump(insn)
|
||||||
|
else
|
||||||
|
transformNullInstanceOf(insn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformNullInstanceOf(insn: AbstractInsnNode) {
|
||||||
|
methodNode.instructions.run {
|
||||||
|
popReferenceValueBefore(insn)
|
||||||
|
set(insn, InsnNode(Opcodes.ICONST_0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun transformNullInstanceOfWithJump(insn: AbstractInsnNode) {
|
||||||
|
methodNode.instructions.run {
|
||||||
|
popReferenceValueBefore(insn)
|
||||||
|
val jump = insn.next.assertedCast<JumpInsnNode> { "JumpInsnNode expected" }
|
||||||
|
remove(insn)
|
||||||
|
if (jump.opcode == Opcodes.IFEQ) {
|
||||||
|
set(jump, JumpInsnNode(Opcodes.GOTO, jump.label))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
remove(jump)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class NullabilityAssumptionsBuilder(val checkedReferenceTypes: Map<AbstractInsnNode, Type>) {
|
||||||
|
|
||||||
|
private val checksDependingOnVariable = HashMap<Int, MutableList<AbstractInsnNode>>()
|
||||||
|
|
||||||
|
fun injectNullabilityAssumptions(): NullabilityAssumptions {
|
||||||
|
collectVariableDependentChecks()
|
||||||
|
return injectAssumptions()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectVariableDependentChecks() {
|
||||||
|
for (insn in methodNode.instructions) {
|
||||||
|
if (insn.isInstanceOfOrNullCheck()) {
|
||||||
|
val previous = insn.previous ?: continue
|
||||||
|
if (previous.opcode == Opcodes.ALOAD) {
|
||||||
|
addDependentCheck(insn, previous as VarInsnNode)
|
||||||
|
}
|
||||||
|
else if (previous.opcode == Opcodes.DUP) {
|
||||||
|
val previous2 = previous.previous ?: continue
|
||||||
|
if (previous2.opcode == Opcodes.ALOAD) {
|
||||||
|
addDependentCheck(insn, previous2 as VarInsnNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (insn.isCheckParameterNotNull()) {
|
||||||
|
val ldcInsn = insn.previous ?: continue
|
||||||
|
if (ldcInsn.opcode != Opcodes.LDC) continue
|
||||||
|
val aLoadInsn = ldcInsn.previous ?: continue
|
||||||
|
if (aLoadInsn.opcode != Opcodes.ALOAD) continue
|
||||||
|
addDependentCheck(insn, aLoadInsn as VarInsnNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addDependentCheck(insn: AbstractInsnNode, aLoadInsn: VarInsnNode) {
|
||||||
|
checksDependingOnVariable.getOrPut(aLoadInsn.`var`) {
|
||||||
|
SmartList<AbstractInsnNode>()
|
||||||
|
}.add(insn)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectAssumptions(): NullabilityAssumptions {
|
||||||
|
val nullabilityAssumptions = NullabilityAssumptions()
|
||||||
|
for ((varIndex, dependentChecks) in checksDependingOnVariable) {
|
||||||
|
for (checkInsn in dependentChecks) {
|
||||||
|
val varType = checkedReferenceTypes[checkInsn]
|
||||||
|
?: throw AssertionError("No var type @${checkInsn.getIndex()}")
|
||||||
|
nullabilityAssumptions.injectAssumptionsForCheck(varIndex, checkInsn, varType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullabilityAssumptions
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NullabilityAssumptions.injectAssumptionsForCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) {
|
||||||
|
when (insn.opcode) {
|
||||||
|
Opcodes.IFNULL,
|
||||||
|
Opcodes.IFNONNULL ->
|
||||||
|
injectAssumptionsForNullCheck(varIndex, insn as JumpInsnNode, varType)
|
||||||
|
Opcodes.INVOKESTATIC -> {
|
||||||
|
assert(insn.isCheckParameterNotNull()) { "Expected non-null parameter check @${insn.getIndex()}"}
|
||||||
|
injectAssumptionsForParameterNotNullCheck(varIndex, insn, varType)
|
||||||
|
}
|
||||||
|
Opcodes.INSTANCEOF ->
|
||||||
|
injectAssumptionsForInstanceOfCheck(varIndex, insn, varType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NullabilityAssumptions.injectAssumptionsForNullCheck(varIndex: Int, insn: JumpInsnNode, varType: Type) {
|
||||||
|
// ALOAD v
|
||||||
|
// IFNULL L
|
||||||
|
// <...> -- v is not null here
|
||||||
|
// L:
|
||||||
|
// <...> -- v is null here
|
||||||
|
|
||||||
|
val jumpsIfNull = insn.opcode == Opcodes.IFNULL
|
||||||
|
val originalLabel = insn.label
|
||||||
|
originalLabels[insn] = originalLabel
|
||||||
|
insn.label = synthetic(LabelNode(Label()))
|
||||||
|
|
||||||
|
val insertAfterNull = if (jumpsIfNull) insn.label else insn
|
||||||
|
val insertAfterNonNull = if (jumpsIfNull) insn else insn.label
|
||||||
|
|
||||||
|
methodNode.instructions.run {
|
||||||
|
add(insn.label)
|
||||||
|
|
||||||
|
insert(insertAfterNull, listOfSynthetics {
|
||||||
|
aconst(null)
|
||||||
|
store(varIndex, varType)
|
||||||
|
if (jumpsIfNull) {
|
||||||
|
goTo(originalLabel.label)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
insert(insertAfterNonNull, listOfSynthetics {
|
||||||
|
anew(varType)
|
||||||
|
store(varIndex, varType)
|
||||||
|
if (!jumpsIfNull) {
|
||||||
|
goTo(originalLabel.label)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NullabilityAssumptions.injectAssumptionsForParameterNotNullCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) {
|
||||||
|
// ALOAD v
|
||||||
|
// LDC param_name
|
||||||
|
// INVOKESTATIC checkParameterIsNotNull
|
||||||
|
// <...> -- v is not null here (otherwise an exception was thrown)
|
||||||
|
|
||||||
|
methodNode.instructions.insert(insn, listOfSynthetics {
|
||||||
|
anew(varType)
|
||||||
|
store(varIndex, varType)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun NullabilityAssumptions.injectAssumptionsForInstanceOfCheck(varIndex: Int, insn: AbstractInsnNode, varType: Type) {
|
||||||
|
// ALOAD v
|
||||||
|
// INSTANCEOF T
|
||||||
|
// IFEQ L
|
||||||
|
// <...> -- v is not null here (because it is an instance of T)
|
||||||
|
// L:
|
||||||
|
// <...> -- v is something else here (maybe null)
|
||||||
|
|
||||||
|
val next = insn.next ?: return
|
||||||
|
if (next.opcode != Opcodes.IFEQ && next.opcode != Opcodes.IFNE) return
|
||||||
|
if (next !is JumpInsnNode) return
|
||||||
|
val jumpsIfInstance = next.opcode == Opcodes.IFNE
|
||||||
|
|
||||||
|
val originalLabel: LabelNode?
|
||||||
|
val insertAfterNotNull: AbstractInsnNode
|
||||||
|
if (jumpsIfInstance) {
|
||||||
|
originalLabel = next.label
|
||||||
|
originalLabels[next] = next.label
|
||||||
|
val newLabel = synthetic(LabelNode(Label()))
|
||||||
|
methodNode.instructions.add(newLabel)
|
||||||
|
next.label = newLabel
|
||||||
|
insertAfterNotNull = newLabel
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
originalLabel = null
|
||||||
|
insertAfterNotNull = next
|
||||||
|
}
|
||||||
|
|
||||||
|
methodNode.instructions.run {
|
||||||
|
insert(insertAfterNotNull, listOfSynthetics {
|
||||||
|
anew(varType)
|
||||||
|
store(varIndex, varType)
|
||||||
|
if (originalLabel != null) {
|
||||||
|
goTo(originalLabel.label)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class NullabilityAssumptions {
|
||||||
|
val originalLabels = HashMap<JumpInsnNode, LabelNode>()
|
||||||
|
val syntheticInstructions = ArrayList<AbstractInsnNode>()
|
||||||
|
|
||||||
|
fun <T : AbstractInsnNode> synthetic(insn: T): T {
|
||||||
|
syntheticInstructions.add(insn)
|
||||||
|
return insn
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun listOfSynthetics(block: InstructionAdapter.() -> Unit): InsnList {
|
||||||
|
val insnList = withInstructionAdapter(block)
|
||||||
|
for (insn in insnList) {
|
||||||
|
synthetic(insn)
|
||||||
|
}
|
||||||
|
return insnList
|
||||||
|
}
|
||||||
|
|
||||||
|
fun revert() {
|
||||||
|
methodNode.instructions.run {
|
||||||
|
syntheticInstructions.forEach { remove(it) }
|
||||||
|
}
|
||||||
|
for ((jumpInsn, originalLabel) in originalLabels) {
|
||||||
|
jumpInsn.label = originalLabel
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun AbstractInsnNode.isInstanceOfOrNullCheck() =
|
||||||
|
opcode == Opcodes.INSTANCEOF || opcode == Opcodes.IFNULL || opcode == Opcodes.IFNONNULL
|
||||||
|
|
||||||
|
internal fun AbstractInsnNode.isCheckParameterNotNull() =
|
||||||
|
isInsn<MethodInsnNode>(Opcodes.INVOKESTATIC) {
|
||||||
|
owner == "kotlin/jvm/internal/Intrinsics" &&
|
||||||
|
name == "checkParameterIsNotNull" &&
|
||||||
|
desc == "(Ljava/lang/Object;Ljava/lang/String;)V"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun InsnList.popReferenceValueBefore(insn: AbstractInsnNode) {
|
||||||
|
val prev = insn.previous
|
||||||
|
when (prev?.opcode) {
|
||||||
|
Opcodes.ACONST_NULL,
|
||||||
|
Opcodes.DUP,
|
||||||
|
Opcodes.ALOAD ->
|
||||||
|
remove(prev)
|
||||||
|
else ->
|
||||||
|
insertBefore(insn, InsnNode(Opcodes.POP))
|
||||||
|
}
|
||||||
|
}
|
||||||
+14
-3
@@ -16,12 +16,10 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization.nullCheck
|
package org.jetbrains.kotlin.codegen.optimization.nullCheck
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.optimization.boxing.isUnitInstance
|
import org.jetbrains.kotlin.codegen.optimization.boxing.ProgressionIteratorBasicValue
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue
|
||||||
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
|
||||||
import org.jetbrains.org.objectweb.asm.Opcodes
|
|
||||||
import org.jetbrains.org.objectweb.asm.Type
|
import org.jetbrains.org.objectweb.asm.Type
|
||||||
import org.jetbrains.org.objectweb.asm.tree.AbstractInsnNode
|
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue
|
||||||
|
|
||||||
class NotNullBasicValue(type: Type?) : StrictBasicValue(type) {
|
class NotNullBasicValue(type: Type?) : StrictBasicValue(type) {
|
||||||
@@ -37,3 +35,16 @@ class NotNullBasicValue(type: Type?) : StrictBasicValue(type) {
|
|||||||
|
|
||||||
object NullBasicValue : StrictBasicValue(AsmTypes.OBJECT_TYPE)
|
object NullBasicValue : StrictBasicValue(AsmTypes.OBJECT_TYPE)
|
||||||
|
|
||||||
|
enum class Nullability {
|
||||||
|
NULL, NOT_NULL, NULLABLE;
|
||||||
|
fun isNull() = this == NULL
|
||||||
|
fun isNotNull() = this == NOT_NULL
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BasicValue.getNullability(): Nullability =
|
||||||
|
when (this) {
|
||||||
|
is NullBasicValue -> Nullability.NULL
|
||||||
|
is NotNullBasicValue -> Nullability.NOT_NULL
|
||||||
|
is ProgressionIteratorBasicValue -> Nullability.NOT_NULL
|
||||||
|
else -> Nullability.NULLABLE
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
inline fun <reified T> isNullable() = null is T
|
||||||
|
|
||||||
|
fun box(): String =
|
||||||
|
if (isNullable<String?>()) "OK" else "Fail"
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
fun test(x: String?) {
|
||||||
|
if (x !is String) return
|
||||||
|
|
||||||
|
if (x == null) println("dead code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 println
|
||||||
+7
@@ -0,0 +1,7 @@
|
|||||||
|
fun test(x: String?) {
|
||||||
|
if (x == null) return
|
||||||
|
|
||||||
|
if (x == null) println("dead code")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0 println
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
val value = System.getProperty("key")
|
||||||
|
if (value != null) {
|
||||||
|
value.toUpperCase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1 IFNULL
|
||||||
|
// 0 IFNONNULL
|
||||||
+4
@@ -0,0 +1,4 @@
|
|||||||
|
fun foo(s: String) = s as CharSequence
|
||||||
|
|
||||||
|
// 0 IFNULL
|
||||||
|
// 0 IFNONNULL
|
||||||
+6
@@ -0,0 +1,6 @@
|
|||||||
|
fun test(s: String) = s?.length
|
||||||
|
|
||||||
|
// 0 IFNULL
|
||||||
|
// 0 IFNONNULL
|
||||||
|
// 0 intValue
|
||||||
|
// 0 valueOf
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
inline fun <reified T> Any?.isa() = this is T
|
||||||
|
|
||||||
|
// 1 INSTANCEOF
|
||||||
|
// 1 reifiedOperationMarker
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
inline fun <reified T> isNullable() = null is T
|
||||||
|
|
||||||
|
// 1 INSTANCEOF
|
||||||
|
// 1 reifiedOperationMarker
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
@kotlin.Metadata
|
||||||
|
public final class IsNullableKt {
|
||||||
|
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
|
||||||
|
private final static method isNullable(): boolean
|
||||||
|
}
|
||||||
+6
@@ -11021,6 +11021,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("isNullable.kt")
|
||||||
|
public void testIsNullable() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt7774.kt")
|
@TestMetadata("kt7774.kt")
|
||||||
public void testKt7774() throws Exception {
|
public void testKt7774() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");
|
||||||
|
|||||||
@@ -11021,6 +11021,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("isNullable.kt")
|
||||||
|
public void testIsNullable() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt7774.kt")
|
@TestMetadata("kt7774.kt")
|
||||||
public void testKt7774() throws Exception {
|
public void testKt7774() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");
|
||||||
|
|||||||
@@ -1634,6 +1634,18 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("alreadyCheckedForIs.kt")
|
||||||
|
public void testAlreadyCheckedForIs() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForIs.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("alreadyCheckedForNull.kt")
|
||||||
|
public void testAlreadyCheckedForNull() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/alreadyCheckedForNull.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("ifNullEqualsNull.kt")
|
@TestMetadata("ifNullEqualsNull.kt")
|
||||||
public void testIfNullEqualsNull() throws Exception {
|
public void testIfNullEqualsNull() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifNullEqualsNull.kt");
|
||||||
@@ -1657,6 +1669,36 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/ifUnitEqualsNullInline.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt12839.kt")
|
||||||
|
public void testKt12839() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/kt12839.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("notNullAsNotNullable.kt")
|
||||||
|
public void testNotNullAsNotNullable() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/notNullAsNotNullable.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("redundantSafeCall.kt")
|
||||||
|
public void testRedundantSafeCall() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/redundantSafeCall.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("reifiedIs.kt")
|
||||||
|
public void testReifiedIs() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedIs.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("reifiedNullIs.kt")
|
||||||
|
public void testReifiedNullIs() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/bytecodeText/nullCheckOptimization/reifiedNullIs.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/ranges")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/ranges")
|
||||||
|
|||||||
@@ -12433,6 +12433,12 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/nullCheckOptimization"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("isNullable.kt")
|
||||||
|
public void testIsNullable() throws Exception {
|
||||||
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/isNullable.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("kt7774.kt")
|
@TestMetadata("kt7774.kt")
|
||||||
public void testKt7774() throws Exception {
|
public void testKt7774() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/nullCheckOptimization/kt7774.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user