KT-11499 Normalize stacks on return from an inline function when the function is inlined at call site.
This commit is contained in:
@@ -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() +
|
||||
" " +
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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<SourceValue>[] sources = analyzeMethodNodeBeforeInline(node);
|
||||
LocalReturnsNormalizer localReturnsNormalizer = LocalReturnsNormalizer.createFor(node, labelOwner, sources);
|
||||
|
||||
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected Frame<SourceValue> newFrame(
|
||||
int nLocals, int nStack
|
||||
) {
|
||||
return new Frame<SourceValue>(nLocals, nStack) {
|
||||
@Override
|
||||
public void execute(
|
||||
@NotNull AbstractInsnNode insn, Interpreter<SourceValue> interpreter
|
||||
) throws AnalyzerException {
|
||||
if (insn.getOpcode() == Opcodes.RETURN) {
|
||||
//there is exception on void non local return in frame
|
||||
return;
|
||||
}
|
||||
super.execute(insn, interpreter);
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
Frame<SourceValue>[] sources;
|
||||
try {
|
||||
sources = analyzer.analyze("fake", node);
|
||||
}
|
||||
catch (AnalyzerException e) {
|
||||
throw wrapException(e, node, "couldn't inline method call");
|
||||
}
|
||||
Set<AbstractInsnNode> 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<SourceValue>[] analyzeMethodNodeBeforeInline(@NotNull MethodNode node) {
|
||||
try {
|
||||
new MandatoryMethodTransformer().transform("fake", node);
|
||||
}
|
||||
catch (Throwable e) {
|
||||
throw wrapException(e, node, "couldn't inline method call");
|
||||
}
|
||||
|
||||
Analyzer<SourceValue> analyzer = new Analyzer<SourceValue>(new SourceInterpreter()) {
|
||||
@NotNull
|
||||
@Override
|
||||
protected Frame<SourceValue> newFrame(int nLocals, int nStack) {
|
||||
return new Frame<SourceValue>(nLocals, nStack) {
|
||||
@Override
|
||||
public void execute(@NotNull AbstractInsnNode insn, Interpreter<SourceValue> 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<SourceValue>[] 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<SourceValue> frame;
|
||||
|
||||
public LocalReturn(
|
||||
@NotNull AbstractInsnNode returnInsn,
|
||||
@NotNull AbstractInsnNode insertBeforeInsn,
|
||||
@NotNull Frame<SourceValue> 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<LocalReturn> localReturns = new SmartList<LocalReturn>();
|
||||
|
||||
private boolean needsReturnVariable = false;
|
||||
private int returnOpcode = -1;
|
||||
|
||||
private void addLocalReturnToTransform(
|
||||
@NotNull AbstractInsnNode returnInsn,
|
||||
@NotNull AbstractInsnNode insertBeforeInsn,
|
||||
@NotNull Frame<SourceValue> 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<SourceValue>[] frames
|
||||
) {
|
||||
LocalReturnsNormalizer result = new LocalReturnsNormalizer();
|
||||
|
||||
AbstractInsnNode[] instructions = methodNode.instructions.toArray();
|
||||
|
||||
for (int i = 0; i < instructions.length; ++i) {
|
||||
Frame<SourceValue> 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();
|
||||
|
||||
+35
-37
@@ -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<Frame<SourceValue>?> = Analyzer<SourceValue>(SourceInterpreter()).analyze("fake", methodNode)
|
||||
private val insns = insnList.toArray()
|
||||
|
||||
private val dontTouchInsns = hashSetOf<AbstractInsnNode>()
|
||||
private val transformations = hashMapOf<AbstractInsnNode, () -> Unit>()
|
||||
private val removableNops = hashSetOf<InsnNode>()
|
||||
|
||||
private val frames: Array<out Frame<SourceValue>?> = 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<out Frame<SourceValue>?> =
|
||||
Analyzer<SourceValue>(object : SourceInterpreter() {
|
||||
override fun naryOperation(insn: AbstractInsnNode, values: MutableList<out SourceValue>): 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<SourceValue>, 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()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
fun box() = null ?: null ?: "OK"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -0,0 +1,12 @@
|
||||
// FILE: 1.kt
|
||||
object CrashMe {
|
||||
fun <T> crash(value: T): T? = null
|
||||
}
|
||||
|
||||
internal inline fun <reified T> crashMe(value: T?): T? {
|
||||
return CrashMe.crash(value ?: return null)
|
||||
}
|
||||
|
||||
// FILE: 2.kt
|
||||
fun box(): String =
|
||||
crashMe<String>(null) ?: "OK"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
@@ -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");
|
||||
|
||||
@@ -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)
|
||||
|
||||
+75
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user