diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt index ef2c859bedd..80c726b7813 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/OptimizationMethodVisitor.kt @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantBoxingMethodTra import org.jetbrains.kotlin.codegen.optimization.boxing.RedundantCoercionToUnitTransformer import org.jetbrains.kotlin.codegen.optimization.captured.CapturedVarsOptimizationMethodTransformer import org.jetbrains.kotlin.codegen.optimization.common.prepareForEmitting -import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckV2MethodTransformer +import org.jetbrains.kotlin.codegen.optimization.nullCheck.RedundantNullCheckMethodTransformer import org.jetbrains.kotlin.codegen.optimization.transformer.CompositeMethodTransformer import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.tree.MethodNode @@ -51,7 +51,7 @@ class OptimizationMethodVisitor( private val OPTIMIZATION_TRANSFORMER = CompositeMethodTransformer( CapturedVarsOptimizationMethodTransformer(), - RedundantNullCheckV2MethodTransformer(), + RedundantNullCheckMethodTransformer(), RedundantCheckCastEliminationMethodTransformer(), RedundantBoxingMethodTransformer(), RedundantCoercionToUnitTransformer(), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt index cf742f74150..94e0c166a9c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckMethodTransformer.kt @@ -16,82 +16,352 @@ 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.boxing.ProgressionIteratorBasicValue +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.safeAs +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.tree.InsnNode -import org.jetbrains.org.objectweb.asm.tree.JumpInsnNode -import org.jetbrains.org.objectweb.asm.tree.MethodNode -import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue +import org.jetbrains.org.objectweb.asm.Type +import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter +import org.jetbrains.org.objectweb.asm.tree.* class RedundantNullCheckMethodTransformer : MethodTransformer() { - private val deadCodeElimination = DeadCodeEliminationMethodTransformer() - override fun transform(internalClassName: String, methodNode: MethodNode) { - while (runSingleNullCheckEliminationPass(internalClassName, methodNode)) { - deadCodeElimination.transform(internalClassName, methodNode) - } + while (TransformerPass(internalClassName, methodNode).run()) {} } - private fun isAlwaysFalse(opcode: Int, nullability: Nullability) = - (opcode == Opcodes.IFNULL && nullability == Nullability.NOT_NULL) || - (opcode == Opcodes.IFNONNULL && nullability == Nullability.NULL) + private class TransformerPass(val internalClassName: String, val methodNode: MethodNode) { + private var changes = false - private fun isAlwaysTrue(opcode: Int, nullability: Nullability) = - (opcode == Opcodes.IFNULL && nullability == Nullability.NULL) || - (opcode == Opcodes.IFNONNULL && nullability == Nullability.NOT_NULL) + private fun AbstractInsnNode.getIndex() = + methodNode.instructions.indexOf(this) + fun run(): Boolean { + val checkedReferenceTypes = analyzeTypesAndRemoveDeadCode() + eliminateRedundantChecks(checkedReferenceTypes) - private fun runSingleNullCheckEliminationPass(internalClassName: String, methodNode: MethodNode): Boolean { - val insnList = methodNode.instructions - val instructions = insnList.toArray() - - val nullCheckIfs = instructions.mapNotNullTo(SmartList()) { - it.safeAs()?.takeIf { - it.opcode == Opcodes.IFNULL || - it.opcode == Opcodes.IFNONNULL - } + return changes } - if (nullCheckIfs.isEmpty()) return false - val frames = analyze(internalClassName, methodNode, NullabilityInterpreter()) + private fun analyzeTypesAndRemoveDeadCode(): Map { + val insns = methodNode.instructions.toArray() + val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) - val redundantNullCheckIfs = nullCheckIfs.mapNotNull { insn -> - frames[instructions.indexOf(insn)]?.top()?.let { top -> - val nullability = top.getNullability() - if (nullability == Nullability.NULLABLE) - null - else - Pair(insn, nullability) + val checkedReferenceTypes = HashMap() + 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 } - if (redundantNullCheckIfs.isEmpty()) return false - for ((insn, nullability) in redundantNullCheckIfs) { - val previous = insn.previous - when (previous?.opcode) { - Opcodes.ALOAD, Opcodes.DUP -> - insnList.remove(previous) - else -> - insnList.insert(previous, InsnNode(Opcodes.POP)) + private fun eliminateRedundantChecks(checkedReferenceTypes: Map) { + val nullabilityAssumptions = injectNullabilityAssumptions(checkedReferenceTypes) + + val nullabilityMap = analyzeNullabilities() + + nullabilityAssumptions.revert() + + transformTrivialChecks(nullabilityMap) + } + + private fun injectNullabilityAssumptions(checkedReferenceTypes: Map) = + NullabilityAssumptionsBuilder(checkedReferenceTypes).injectNullabilityAssumptions() + + private fun analyzeNullabilities(): Map { + val frames = analyze(internalClassName, methodNode, NullabilityInterpreter()) + val insns = methodNode.instructions.toArray() + val nullabilityMap = HashMap() + 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 + } - when { - isAlwaysTrue(insn.opcode, nullability) -> - insnList.set(insn, JumpInsnNode(Opcodes.GOTO, insn.label)) - isAlwaysFalse(insn.opcode, nullability) -> - insnList.remove(insn) + private fun transformTrivialChecks(nullabilityMap: Map) { + 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) + } } } - return true + 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 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) { + + private val checksDependingOnVariable = HashMap>() + + 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() + }.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() + val syntheticInstructions = ArrayList() + + fun 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(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)) + } +} diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckV2MethodTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckV2MethodTransformer.kt deleted file mode 100644 index 326c37431ff..00000000000 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/nullCheck/RedundantNullCheckV2MethodTransformer.kt +++ /dev/null @@ -1,367 +0,0 @@ -/* - * 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 { - val insns = methodNode.instructions.toArray() - val frames = analyze(internalClassName, methodNode, OptimizationBasicInterpreter()) - - val checkedReferenceTypes = HashMap() - 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) { - val nullabilityAssumptions = injectNullabilityAssumptions(checkedReferenceTypes) - - val nullabilityMap = analyzeNullabilities() - - nullabilityAssumptions.revert() - - transformTrivialChecks(nullabilityMap) - } - - private fun injectNullabilityAssumptions(checkedReferenceTypes: Map) = - NullabilityAssumptionsBuilder(checkedReferenceTypes).injectNullabilityAssumptions() - - private fun analyzeNullabilities(): Map { - val frames = analyze(internalClassName, methodNode, NullabilityInterpreter()) - val insns = methodNode.instructions.toArray() - val nullabilityMap = HashMap() - 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) { - 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 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) { - - private val checksDependingOnVariable = HashMap>() - - 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() - }.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() - val syntheticInstructions = ArrayList() - - fun 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(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)) - } -}