From aca7050656d6a6328db3126f8304156b010a88c8 Mon Sep 17 00:00:00 2001 From: Dmitry Petrov Date: Thu, 7 Apr 2016 20:00:12 +0300 Subject: [PATCH] KT-11499 Normalize stacks on return from an inline function when the function is inlined at call site. --- .../kotlin/codegen/FunctionCodegen.java | 2 +- .../codegen/inline/InlineCodegenUtil.java | 18 +- .../kotlin/codegen/inline/MethodInliner.java | 202 ++++++++++++++---- .../RedundantCoercionToUnitTransformer.kt | 72 +++---- .../testData/codegen/box/elvis/nullNullOk.kt | 1 + .../codegen/boxInline/stackOnReturn/elvis.kt | 10 + .../boxInline/stackOnReturn/ifThenElse.kt | 10 + .../boxInline/stackOnReturn/kt11499.kt | 12 ++ .../stackOnReturn/mixedTypesOnStack1.kt | 10 + .../stackOnReturn/mixedTypesOnStack2.kt | 10 + .../stackOnReturn/mixedTypesOnStack3.kt | 10 + .../stackOnReturn/nonLocalReturn1.kt | 15 ++ .../stackOnReturn/nonLocalReturn2.kt | 15 ++ .../stackOnReturn/nonLocalReturn3.kt | 17 ++ .../boxInline/stackOnReturn/returnLong.kt | 12 ++ .../boxInline/stackOnReturn/tryFinally.kt | 17 ++ .../codegen/BlackBoxCodegenTestGenerated.java | 6 + .../BlackBoxInlineCodegenTestGenerated.java | 75 +++++++ ...otlinAgainstInlineKotlinTestGenerated.java | 75 +++++++ 19 files changed, 502 insertions(+), 87 deletions(-) create mode 100644 compiler/testData/codegen/box/elvis/nullNullOk.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/elvis.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/ifThenElse.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/kt11499.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack1.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack2.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack3.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn1.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn2.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt create mode 100644 compiler/testData/codegen/boxInline/stackOnReturn/tryFinally.kt diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 753b242a924..61bd5faeff8 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -528,7 +528,7 @@ public class FunctionCodegen { catch (Throwable t) { String bytecode = renderByteCodeIfAvailable(mv); throw new CompilationException( - "wrong code generated" + + "wrong code generated\n" + (description != null ? " for " + description : "") + t.getClass().getName() + " " + diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java index 39480c85689..2f4f6f12402 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegenUtil.java @@ -314,11 +314,21 @@ public class InlineCodegenUtil { //marked return could be either non-local or local in case of labeled lambda self-returns public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) { - if (!isReturnOpcode(returnIns.getOpcode())) { - return false; + return getMarkedReturnLabelOrNull(returnIns) != null; + } + + public static @Nullable String getMarkedReturnLabelOrNull(@NotNull AbstractInsnNode returnInsn) { + if (!isReturnOpcode(returnInsn.getOpcode())) { + return null; } - AbstractInsnNode globalFlag = returnIns.getPrevious(); - return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner); + AbstractInsnNode previous = returnInsn.getPrevious(); + if (previous instanceof MethodInsnNode) { + MethodInsnNode marker = (MethodInsnNode) previous; + if (NON_LOCAL_RETURN.equals(marker.owner)) { + return marker.name; + } + } + return null; } public static void generateGlobalReturnFlag(@NotNull InstructionAdapter iv, @NotNull String labelName) { diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java index 83031a89b80..0f9be2c5a64 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.java @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.codegen.StackValue; import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods; import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer; import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; +import org.jetbrains.kotlin.utils.SmartList; import org.jetbrains.kotlin.utils.SmartSet; import org.jetbrains.org.objectweb.asm.Label; import org.jetbrains.org.objectweb.asm.MethodVisitor; @@ -35,6 +36,7 @@ import org.jetbrains.org.objectweb.asm.commons.Method; import org.jetbrains.org.objectweb.asm.commons.RemappingMethodAdapter; import org.jetbrains.org.objectweb.asm.tree.*; import org.jetbrains.org.objectweb.asm.tree.analysis.*; +import org.jetbrains.org.objectweb.asm.util.Printer; import java.util.*; @@ -121,7 +123,7 @@ public class MethodInliner { int finallyDeepShift ) { //analyze body - MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, finallyDeepShift); + MethodNode transformedNode = markPlacesForInlineAndRemoveInlinable(node, labelOwner, finallyDeepShift); //substitute returns with "goto end" instruction to keep non local returns in lambdas Label end = new Label(); @@ -398,44 +400,12 @@ public class MethodInliner { } @NotNull - protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) { + protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, int finallyDeepShift) { node = prepareNode(node, finallyDeepShift); - try { - new MandatoryMethodTransformer().transform("fake", node); - } - catch (Throwable e) { - throw wrapException(e, node, "couldn't inline method call"); - } + Frame[] sources = analyzeMethodNodeBeforeInline(node); + LocalReturnsNormalizer localReturnsNormalizer = LocalReturnsNormalizer.createFor(node, labelOwner, sources); - Analyzer analyzer = new Analyzer(new SourceInterpreter()) { - @NotNull - @Override - protected Frame newFrame( - int nLocals, int nStack - ) { - return new Frame(nLocals, nStack) { - @Override - public void execute( - @NotNull AbstractInsnNode insn, Interpreter interpreter - ) throws AnalyzerException { - if (insn.getOpcode() == Opcodes.RETURN) { - //there is exception on void non local return in frame - return; - } - super.execute(insn, interpreter); - } - }; - } - }; - - Frame[] sources; - try { - sources = analyzer.analyze("fake", node); - } - catch (AnalyzerException e) { - throw wrapException(e, node, "couldn't inline method call"); - } Set toDelete = SmartSet.create(); InsnList instructions = node.instructions; AbstractInsnNode cur = instructions.getFirst(); @@ -547,9 +517,44 @@ public class MethodInliner { } } + localReturnsNormalizer.transform(node); + return node; } + private Frame[] analyzeMethodNodeBeforeInline(@NotNull MethodNode node) { + try { + new MandatoryMethodTransformer().transform("fake", node); + } + catch (Throwable e) { + throw wrapException(e, node, "couldn't inline method call"); + } + + Analyzer analyzer = new Analyzer(new SourceInterpreter()) { + @NotNull + @Override + protected Frame newFrame(int nLocals, int nStack) { + return new Frame(nLocals, nStack) { + @Override + public void execute(@NotNull AbstractInsnNode insn, Interpreter interpreter) throws AnalyzerException { + // This can be a void non-local return from a non-void method; Frame#execute would throw and do nothing else. + if (insn.getOpcode() == Opcodes.RETURN) return; + super.execute(insn, interpreter); + } + }; + } + }; + + Frame[] sources; + try { + sources = analyzer.analyze("fake", node); + } + catch (AnalyzerException e) { + throw wrapException(e, node, "couldn't inline method call"); + } + return sources; + } + private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) { LabelNode start = tryCatchBlockNode.start; AbstractInsnNode end = tryCatchBlockNode.end; @@ -758,20 +763,14 @@ public class MethodInliner { AbstractInsnNode insnNode = instructions.getFirst(); while (insnNode != null) { if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) { - AbstractInsnNode previous = insnNode.getPrevious(); - MethodInsnNode flagNode; boolean isLocalReturn = true; - String labelName = null; - if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) { - flagNode = (MethodInsnNode) previous; - labelName = flagNode.name; - } + String labelName = InlineCodegenUtil.getMarkedReturnLabelOrNull(insnNode); if (labelName != null) { isLocalReturn = labelOwner.isMyLabel(labelName); //remove global return flag if (isLocalReturn) { - instructions.remove(previous); + instructions.remove(insnNode.getPrevious()); } } @@ -783,7 +782,7 @@ public class MethodInliner { insnNode = jumpInsnNode; } - //genetate finally block before nonLocalReturn flag/return/goto + //generate finally block before nonLocalReturn flag/return/goto LabelNode label = new LabelNode(); instructions.insert(insnNode, label); result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn), @@ -795,6 +794,119 @@ public class MethodInliner { return result; } + private static class LocalReturnsNormalizer { + private static class LocalReturn { + private final AbstractInsnNode returnInsn; + private final AbstractInsnNode insertBeforeInsn; + private final Frame frame; + + public LocalReturn( + @NotNull AbstractInsnNode returnInsn, + @NotNull AbstractInsnNode insertBeforeInsn, + @NotNull Frame frame + ) { + this.returnInsn = returnInsn; + this.insertBeforeInsn = insertBeforeInsn; + this.frame = frame; + } + + public void transform(@NotNull InsnList insnList, int returnVariableIndex) { + boolean isReturnWithValue = returnInsn.getOpcode() != Opcodes.RETURN; + + int expectedStackSize = isReturnWithValue ? 1 : 0; + int actualStackSize = frame.getStackSize(); + if (expectedStackSize == actualStackSize) return; + + int stackSize = actualStackSize; + if (isReturnWithValue) { + int storeOpcode = Opcodes.ISTORE + returnInsn.getOpcode() - Opcodes.IRETURN; + insnList.insertBefore(insertBeforeInsn, new VarInsnNode(storeOpcode, returnVariableIndex)); + stackSize--; + } + + while (stackSize > 0) { + int stackElementSize = frame.getStack(stackSize - 1).getSize(); + int popOpcode = stackElementSize == 1 ? Opcodes.POP : Opcodes.POP2; + insnList.insertBefore(insertBeforeInsn, new InsnNode(popOpcode)); + stackSize--; + } + + if (isReturnWithValue) { + int loadOpcode = Opcodes.ILOAD + returnInsn.getOpcode() - Opcodes.IRETURN; + insnList.insertBefore(insertBeforeInsn, new VarInsnNode(loadOpcode, returnVariableIndex)); + } + } + } + + private final List localReturns = new SmartList(); + + private boolean needsReturnVariable = false; + private int returnOpcode = -1; + + private void addLocalReturnToTransform( + @NotNull AbstractInsnNode returnInsn, + @NotNull AbstractInsnNode insertBeforeInsn, + @NotNull Frame sourceValueFrame + ) { + assert InlineCodegenUtil.isReturnOpcode(returnInsn.getOpcode()) : "return instruction expected"; + assert returnOpcode < 0 || returnOpcode == returnInsn.getOpcode() : + "Return op should be " + Printer.OPCODES[returnOpcode] + ", got " + Printer.OPCODES[returnInsn.getOpcode()]; + returnOpcode = returnInsn.getOpcode(); + + localReturns.add(new LocalReturn(returnInsn, insertBeforeInsn, sourceValueFrame)); + + if (returnInsn.getOpcode() != Opcodes.RETURN && sourceValueFrame.getStackSize() > 1) { + needsReturnVariable = true; + } + } + + public void transform(MethodNode methodNode) { + int returnVariableIndex = -1; + if (needsReturnVariable) { + returnVariableIndex = methodNode.maxLocals; + methodNode.maxLocals++; + } + + for (LocalReturn localReturn : localReturns) { + localReturn.transform(methodNode.instructions, returnVariableIndex); + } + } + + public static LocalReturnsNormalizer createFor( + @NotNull MethodNode methodNode, + @NotNull LabelOwner owner, + @NotNull Frame[] frames + ) { + LocalReturnsNormalizer result = new LocalReturnsNormalizer(); + + AbstractInsnNode[] instructions = methodNode.instructions.toArray(); + + for (int i = 0; i < instructions.length; ++i) { + Frame frame = frames[i]; + // Don't care about dead code, it will be eliminated + if (frame == null) continue; + + AbstractInsnNode insnNode = instructions[i]; + if (!InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) continue; + + AbstractInsnNode insertBeforeInsn = insnNode; + + // TODO extract isLocalReturn / isNonLocalReturn, see processReturns + String labelName = getMarkedReturnLabelOrNull(insnNode); + if (labelName != null) { + if (!owner.isMyLabel(labelName)) continue; + insertBeforeInsn = insnNode.getPrevious(); + } + + result.addLocalReturnToTransform(insnNode, insertBeforeInsn, frame); + } + + return result; + } + } + + + @NotNull private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) { return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt index dd60bc6bbc2..8d7a9d2cb5e 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/boxing/RedundantCoercionToUnitTransformer.kt @@ -16,9 +16,7 @@ package org.jetbrains.kotlin.codegen.optimization.boxing -import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence import org.jetbrains.kotlin.codegen.optimization.common.isMeaningful -import org.jetbrains.kotlin.codegen.optimization.fixStack.peek import org.jetbrains.kotlin.codegen.optimization.fixStack.top import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext import org.jetbrains.kotlin.codegen.optimization.replaceNodeGetNext @@ -39,55 +37,55 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() { private class Transformer(val methodNode: MethodNode) { private val insnList = methodNode.instructions - private val frames: Array?> = Analyzer(SourceInterpreter()).analyze("fake", methodNode) private val insns = insnList.toArray() private val dontTouchInsns = hashSetOf() private val transformations = hashMapOf Unit>() private val removableNops = hashSetOf() + private val frames: Array?> = analyzeMethodBody() + fun transform() { - computeDontTouchInsns() computeTransformations() transformations.values.forEach { it() } postprocessNops() } - private fun computeDontTouchInsns() { - for (i in 0..insns.lastIndex) { - val frame = frames[i] ?: continue - val insn = insns[i] + private fun analyzeMethodBody(): Array?> = + Analyzer(object : SourceInterpreter() { + override fun naryOperation(insn: AbstractInsnNode, values: MutableList): SourceValue { + for (value in values) { + dontTouchInsns.addAll(value.insns) + } + return super.naryOperation(insn, values) + } - when (insn.opcode) { - Opcodes.DUP -> - dontTouchWordsOnTop(i, frame, 1) - Opcodes.DUP_X1 -> - dontTouchWordsOnTop(i, frame, 2) - Opcodes.DUP_X2 -> - dontTouchWordsOnTop(i, frame, 3) - Opcodes.DUP2 -> - dontTouchWordsOnTop(i, frame, 2) - Opcodes.DUP2_X1 -> - dontTouchWordsOnTop(i, frame, 3) - Opcodes.DUP2_X2 -> - dontTouchWordsOnTop(i, frame, 4) - Opcodes.SWAP -> - dontTouchWordsOnTop(i, frame, 2) - } - } - } + override fun copyOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue { + dontTouchInsns.addAll(value.insns) + return super.copyOperation(insn, value) + } + + override fun unaryOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue { + if (insn.opcode != Opcodes.CHECKCAST) { + dontTouchInsns.addAll(value.insns) + } + return super.unaryOperation(insn, value) + } + + override fun binaryOperation(insn: AbstractInsnNode, value1: SourceValue, value2: SourceValue): SourceValue { + dontTouchInsns.addAll(value1.insns) + dontTouchInsns.addAll(value2.insns) + return super.binaryOperation(insn, value1, value2) + } + + override fun ternaryOperation(insn: AbstractInsnNode, value1: SourceValue, value2: SourceValue, value3: SourceValue): SourceValue { + dontTouchInsns.addAll(value1.insns) + dontTouchInsns.addAll(value2.insns) + dontTouchInsns.addAll(value3.insns) + return super.ternaryOperation(insn, value1, value2, value3) + } + }).analyze("fake", methodNode) - private fun dontTouchWordsOnTop(at: Int, frame: Frame, expectedWords: Int) { - var words = 0 - var offset = 0 - while (words < expectedWords) { - val value = frame.peek(offset) ?: throwIllegalStackInsn(at) - offset++ - words += value.size - dontTouchInsns.addAll(value.insns) - } - if (words != expectedWords) throwIllegalStackInsn(at) - } private fun computeTransformations() { transformations.clear() diff --git a/compiler/testData/codegen/box/elvis/nullNullOk.kt b/compiler/testData/codegen/box/elvis/nullNullOk.kt new file mode 100644 index 00000000000..e25fe160183 --- /dev/null +++ b/compiler/testData/codegen/box/elvis/nullNullOk.kt @@ -0,0 +1 @@ +fun box() = null ?: null ?: "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/elvis.kt b/compiler/testData/codegen/boxInline/stackOnReturn/elvis.kt new file mode 100644 index 00000000000..6e4d98a72b9 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/elvis.kt @@ -0,0 +1,10 @@ +// FILE: 1.kt +fun foo(x: Any?, y: Any?) = null + +inline fun test(value: Any?): String? { + return foo(null, value ?: return null) +} + +// FILE: 2.kt +fun box(): String = + test(null) ?: "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/ifThenElse.kt b/compiler/testData/codegen/boxInline/stackOnReturn/ifThenElse.kt new file mode 100644 index 00000000000..0e59babfb80 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/ifThenElse.kt @@ -0,0 +1,10 @@ +// FILE: 1.kt +fun foo(x: Any?, y: Any?) = null + +inline fun test(value: Any?): String? { + return foo(null, if (value != null) value else return null) +} + +// FILE: 2.kt +fun box(): String = + test(null) ?: "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/kt11499.kt b/compiler/testData/codegen/boxInline/stackOnReturn/kt11499.kt new file mode 100644 index 00000000000..1194efbfdc3 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/kt11499.kt @@ -0,0 +1,12 @@ +// FILE: 1.kt +object CrashMe { + fun crash(value: T): T? = null +} + +internal inline fun crashMe(value: T?): T? { + return CrashMe.crash(value ?: return null) +} + +// FILE: 2.kt +fun box(): String = + crashMe(null) ?: "OK" diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack1.kt b/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack1.kt new file mode 100644 index 00000000000..08d6a84c259 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack1.kt @@ -0,0 +1,10 @@ +// FILE: 1.kt +fun foo1(x: Long, xx: Int, y: Any?) = null + +inline fun test1(value: Any?): String? { + return foo1(0L, 0, value ?: return null) +} + +// FILE: 2.kt +fun box(): String = + test1(null) ?: "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack2.kt b/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack2.kt new file mode 100644 index 00000000000..22268ac2f98 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack2.kt @@ -0,0 +1,10 @@ +// FILE: 1.kt +fun foo2(x: Int, xx: Long, y: Any?) = null + +inline fun test2(value: Any?): String? { + return foo2(0, 0L, value ?: return null) +} + +// FILE: 2.kt +fun box(): String = + test2(null) ?: "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack3.kt b/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack3.kt new file mode 100644 index 00000000000..64a61b6f014 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack3.kt @@ -0,0 +1,10 @@ +// FILE: 1.kt +fun foo3(x: Int, xx: Long, xxx: Int, y: Any?) = null + +inline fun test3(value: Any?): String? { + return foo3(0, 0L, 0, value ?: return null) +} + +// FILE: 2.kt +fun box(): String = + test3(null) ?: "OK" \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn1.kt b/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn1.kt new file mode 100644 index 00000000000..1f1779b04fe --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn1.kt @@ -0,0 +1,15 @@ +// FILE: 1.kt +inline fun run(f: () -> Unit) = f() +inline fun withAny(f: Any.() -> Unit) = Any().f() + +// FILE: 2.kt +fun foo(x: Any?, y: Any?) {} + +fun box(): String { + run outer@{ + withAny inner@{ + foo(null, null ?: return@outer) + } + } + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn2.kt b/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn2.kt new file mode 100644 index 00000000000..10b2f78b209 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn2.kt @@ -0,0 +1,15 @@ +// FILE: 1.kt +inline fun run(f: () -> Unit) = f() +inline fun withAny(f: Any.() -> Unit) = Any().f() + +// FILE: 2.kt +fun foo(x: Any?, y: Any?) {} + +fun box(): String { + run outer@{ + withAny inner@{ + foo(null, null ?: return@inner) + } + } + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt b/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt new file mode 100644 index 00000000000..53330c09d22 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt @@ -0,0 +1,17 @@ +// FILE: 1.kt +inline fun run(f: () -> Unit) = f() +inline fun withAny(f: Any.() -> Unit) = Any().f() +inline fun foo(x: Any?, y: Any?, f: () -> Unit) = f() + +// FILE: 2.kt + +fun box(): String { + run outer@{ + withAny inner@{ + foo(null, 0 ?: return@outer) { + foo(null, null ?: return@inner) {} + } + } + } + return "OK" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt b/compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt new file mode 100644 index 00000000000..2f5cdbc9a0a --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt @@ -0,0 +1,12 @@ +// FILE: 1.kt +fun foo(x: Any?, y: Any?) = 0L + +inline fun test(value: Any?): Long { + return foo(null, value ?: return 1L) +} + +// FILE: 2.kt +fun box(): String { + val t = test(null) + return if (t == 1L) "OK" else "fail: t=$t" +} \ No newline at end of file diff --git a/compiler/testData/codegen/boxInline/stackOnReturn/tryFinally.kt b/compiler/testData/codegen/boxInline/stackOnReturn/tryFinally.kt new file mode 100644 index 00000000000..6b5b4ec2f30 --- /dev/null +++ b/compiler/testData/codegen/boxInline/stackOnReturn/tryFinally.kt @@ -0,0 +1,17 @@ +// FILE: 1.kt +fun foo(x: Any?, y: Any?) = null + +var finallyFlag = false + +inline fun test(value: Any?): String? { + try { + return foo(null, value ?: return null) + } + finally { + finallyFlag = true + } +} + +// FILE: 2.kt +fun box(): String = + test(null) ?: if (finallyFlag) "OK" else "finallyFlag not set" \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 16f0989f9ee..acbf6349a89 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5326,6 +5326,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("nullNullOk.kt") + public void testNullNullOk() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/elvis/nullNullOk.kt"); + doTest(fileName); + } + @TestMetadata("primitive.kt") public void testPrimitive() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/elvis/primitive.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index 1438eb763b0..caf445df7a3 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -1997,6 +1997,81 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo } } + @TestMetadata("compiler/testData/codegen/boxInline/stackOnReturn") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class StackOnReturn extends AbstractBlackBoxInlineCodegenTest { + public void testAllFilesPresentInStackOnReturn() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/stackOnReturn"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("elvis.kt") + public void testElvis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/elvis.kt"); + doTest(fileName); + } + + @TestMetadata("ifThenElse.kt") + public void testIfThenElse() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/ifThenElse.kt"); + doTest(fileName); + } + + @TestMetadata("kt11499.kt") + public void testKt11499() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/kt11499.kt"); + doTest(fileName); + } + + @TestMetadata("mixedTypesOnStack1.kt") + public void testMixedTypesOnStack1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack1.kt"); + doTest(fileName); + } + + @TestMetadata("mixedTypesOnStack2.kt") + public void testMixedTypesOnStack2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack2.kt"); + doTest(fileName); + } + + @TestMetadata("mixedTypesOnStack3.kt") + public void testMixedTypesOnStack3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack3.kt"); + doTest(fileName); + } + + @TestMetadata("nonLocalReturn1.kt") + public void testNonLocalReturn1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn1.kt"); + doTest(fileName); + } + + @TestMetadata("nonLocalReturn2.kt") + public void testNonLocalReturn2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn2.kt"); + doTest(fileName); + } + + @TestMetadata("nonLocalReturn3.kt") + public void testNonLocalReturn3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt"); + doTest(fileName); + } + + @TestMetadata("returnLong.kt") + public void testReturnLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt"); + doTest(fileName); + } + + @TestMetadata("tryFinally.kt") + public void testTryFinally() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/tryFinally.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/syntheticAccessors") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index 37085dd83e5..f2c3eebdfd2 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -1997,6 +1997,81 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi } } + @TestMetadata("compiler/testData/codegen/boxInline/stackOnReturn") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class StackOnReturn extends AbstractCompileKotlinAgainstInlineKotlinTest { + public void testAllFilesPresentInStackOnReturn() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/stackOnReturn"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("elvis.kt") + public void testElvis() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/elvis.kt"); + doTest(fileName); + } + + @TestMetadata("ifThenElse.kt") + public void testIfThenElse() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/ifThenElse.kt"); + doTest(fileName); + } + + @TestMetadata("kt11499.kt") + public void testKt11499() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/kt11499.kt"); + doTest(fileName); + } + + @TestMetadata("mixedTypesOnStack1.kt") + public void testMixedTypesOnStack1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack1.kt"); + doTest(fileName); + } + + @TestMetadata("mixedTypesOnStack2.kt") + public void testMixedTypesOnStack2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack2.kt"); + doTest(fileName); + } + + @TestMetadata("mixedTypesOnStack3.kt") + public void testMixedTypesOnStack3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/mixedTypesOnStack3.kt"); + doTest(fileName); + } + + @TestMetadata("nonLocalReturn1.kt") + public void testNonLocalReturn1() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn1.kt"); + doTest(fileName); + } + + @TestMetadata("nonLocalReturn2.kt") + public void testNonLocalReturn2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn2.kt"); + doTest(fileName); + } + + @TestMetadata("nonLocalReturn3.kt") + public void testNonLocalReturn3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/nonLocalReturn3.kt"); + doTest(fileName); + } + + @TestMetadata("returnLong.kt") + public void testReturnLong() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/returnLong.kt"); + doTest(fileName); + } + + @TestMetadata("tryFinally.kt") + public void testTryFinally() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/stackOnReturn/tryFinally.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/syntheticAccessors") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)