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) {
|
catch (Throwable t) {
|
||||||
String bytecode = renderByteCodeIfAvailable(mv);
|
String bytecode = renderByteCodeIfAvailable(mv);
|
||||||
throw new CompilationException(
|
throw new CompilationException(
|
||||||
"wrong code generated" +
|
"wrong code generated\n" +
|
||||||
(description != null ? " for " + description : "") +
|
(description != null ? " for " + description : "") +
|
||||||
t.getClass().getName() +
|
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
|
//marked return could be either non-local or local in case of labeled lambda self-returns
|
||||||
public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
|
public static boolean isMarkedReturn(@NotNull AbstractInsnNode returnIns) {
|
||||||
if (!isReturnOpcode(returnIns.getOpcode())) {
|
return getMarkedReturnLabelOrNull(returnIns) != null;
|
||||||
return false;
|
}
|
||||||
|
|
||||||
|
public static @Nullable String getMarkedReturnLabelOrNull(@NotNull AbstractInsnNode returnInsn) {
|
||||||
|
if (!isReturnOpcode(returnInsn.getOpcode())) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
AbstractInsnNode globalFlag = returnIns.getPrevious();
|
AbstractInsnNode previous = returnInsn.getPrevious();
|
||||||
return globalFlag instanceof MethodInsnNode && NON_LOCAL_RETURN.equals(((MethodInsnNode)globalFlag).owner);
|
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) {
|
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.intrinsics.IntrinsicMethods;
|
||||||
import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer;
|
import org.jetbrains.kotlin.codegen.optimization.MandatoryMethodTransformer;
|
||||||
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
|
||||||
|
import org.jetbrains.kotlin.utils.SmartList;
|
||||||
import org.jetbrains.kotlin.utils.SmartSet;
|
import org.jetbrains.kotlin.utils.SmartSet;
|
||||||
import org.jetbrains.org.objectweb.asm.Label;
|
import org.jetbrains.org.objectweb.asm.Label;
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor;
|
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.commons.RemappingMethodAdapter;
|
||||||
import org.jetbrains.org.objectweb.asm.tree.*;
|
import org.jetbrains.org.objectweb.asm.tree.*;
|
||||||
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
import org.jetbrains.org.objectweb.asm.tree.analysis.*;
|
||||||
|
import org.jetbrains.org.objectweb.asm.util.Printer;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ public class MethodInliner {
|
|||||||
int finallyDeepShift
|
int finallyDeepShift
|
||||||
) {
|
) {
|
||||||
//analyze body
|
//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
|
//substitute returns with "goto end" instruction to keep non local returns in lambdas
|
||||||
Label end = new Label();
|
Label end = new Label();
|
||||||
@@ -398,44 +400,12 @@ public class MethodInliner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, int finallyDeepShift) {
|
protected MethodNode markPlacesForInlineAndRemoveInlinable(@NotNull MethodNode node, @NotNull LabelOwner labelOwner, int finallyDeepShift) {
|
||||||
node = prepareNode(node, finallyDeepShift);
|
node = prepareNode(node, finallyDeepShift);
|
||||||
|
|
||||||
try {
|
Frame<SourceValue>[] sources = analyzeMethodNodeBeforeInline(node);
|
||||||
new MandatoryMethodTransformer().transform("fake", node);
|
LocalReturnsNormalizer localReturnsNormalizer = LocalReturnsNormalizer.createFor(node, labelOwner, sources);
|
||||||
}
|
|
||||||
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 {
|
|
||||||
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();
|
Set<AbstractInsnNode> toDelete = SmartSet.create();
|
||||||
InsnList instructions = node.instructions;
|
InsnList instructions = node.instructions;
|
||||||
AbstractInsnNode cur = instructions.getFirst();
|
AbstractInsnNode cur = instructions.getFirst();
|
||||||
@@ -547,9 +517,44 @@ public class MethodInliner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localReturnsNormalizer.transform(node);
|
||||||
|
|
||||||
return 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) {
|
private static boolean isEmptyTryInterval(@NotNull TryCatchBlockNode tryCatchBlockNode) {
|
||||||
LabelNode start = tryCatchBlockNode.start;
|
LabelNode start = tryCatchBlockNode.start;
|
||||||
AbstractInsnNode end = tryCatchBlockNode.end;
|
AbstractInsnNode end = tryCatchBlockNode.end;
|
||||||
@@ -758,20 +763,14 @@ public class MethodInliner {
|
|||||||
AbstractInsnNode insnNode = instructions.getFirst();
|
AbstractInsnNode insnNode = instructions.getFirst();
|
||||||
while (insnNode != null) {
|
while (insnNode != null) {
|
||||||
if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
|
if (InlineCodegenUtil.isReturnOpcode(insnNode.getOpcode())) {
|
||||||
AbstractInsnNode previous = insnNode.getPrevious();
|
|
||||||
MethodInsnNode flagNode;
|
|
||||||
boolean isLocalReturn = true;
|
boolean isLocalReturn = true;
|
||||||
String labelName = null;
|
String labelName = InlineCodegenUtil.getMarkedReturnLabelOrNull(insnNode);
|
||||||
if (previous != null && previous instanceof MethodInsnNode && InlineCodegenUtil.NON_LOCAL_RETURN.equals(((MethodInsnNode) previous).owner)) {
|
|
||||||
flagNode = (MethodInsnNode) previous;
|
|
||||||
labelName = flagNode.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (labelName != null) {
|
if (labelName != null) {
|
||||||
isLocalReturn = labelOwner.isMyLabel(labelName);
|
isLocalReturn = labelOwner.isMyLabel(labelName);
|
||||||
//remove global return flag
|
//remove global return flag
|
||||||
if (isLocalReturn) {
|
if (isLocalReturn) {
|
||||||
instructions.remove(previous);
|
instructions.remove(insnNode.getPrevious());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -783,7 +782,7 @@ public class MethodInliner {
|
|||||||
insnNode = jumpInsnNode;
|
insnNode = jumpInsnNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
//genetate finally block before nonLocalReturn flag/return/goto
|
//generate finally block before nonLocalReturn flag/return/goto
|
||||||
LabelNode label = new LabelNode();
|
LabelNode label = new LabelNode();
|
||||||
instructions.insert(insnNode, label);
|
instructions.insert(insnNode, label);
|
||||||
result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn),
|
result.add(new PointForExternalFinallyBlocks(getInstructionToInsertFinallyBefore(insnNode, isLocalReturn),
|
||||||
@@ -795,6 +794,119 @@ public class MethodInliner {
|
|||||||
return result;
|
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
|
@NotNull
|
||||||
private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) {
|
private static AbstractInsnNode getInstructionToInsertFinallyBefore(@NotNull AbstractInsnNode nonLocalReturnOrJump, boolean isLocal) {
|
||||||
return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
|
return isLocal ? nonLocalReturnOrJump : nonLocalReturnOrJump.getPrevious();
|
||||||
|
|||||||
+35
-37
@@ -16,9 +16,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.optimization.boxing
|
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.common.isMeaningful
|
||||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.peek
|
|
||||||
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
import org.jetbrains.kotlin.codegen.optimization.fixStack.top
|
||||||
import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext
|
import org.jetbrains.kotlin.codegen.optimization.removeNodeGetNext
|
||||||
import org.jetbrains.kotlin.codegen.optimization.replaceNodeGetNext
|
import org.jetbrains.kotlin.codegen.optimization.replaceNodeGetNext
|
||||||
@@ -39,55 +37,55 @@ class RedundantCoercionToUnitTransformer : MethodTransformer() {
|
|||||||
private class Transformer(val methodNode: MethodNode) {
|
private class Transformer(val methodNode: MethodNode) {
|
||||||
private val insnList = methodNode.instructions
|
private val insnList = methodNode.instructions
|
||||||
|
|
||||||
private val frames: Array<Frame<SourceValue>?> = Analyzer<SourceValue>(SourceInterpreter()).analyze("fake", methodNode)
|
|
||||||
private val insns = insnList.toArray()
|
private val insns = insnList.toArray()
|
||||||
|
|
||||||
private val dontTouchInsns = hashSetOf<AbstractInsnNode>()
|
private val dontTouchInsns = hashSetOf<AbstractInsnNode>()
|
||||||
private val transformations = hashMapOf<AbstractInsnNode, () -> Unit>()
|
private val transformations = hashMapOf<AbstractInsnNode, () -> Unit>()
|
||||||
private val removableNops = hashSetOf<InsnNode>()
|
private val removableNops = hashSetOf<InsnNode>()
|
||||||
|
|
||||||
|
private val frames: Array<out Frame<SourceValue>?> = analyzeMethodBody()
|
||||||
|
|
||||||
fun transform() {
|
fun transform() {
|
||||||
computeDontTouchInsns()
|
|
||||||
computeTransformations()
|
computeTransformations()
|
||||||
transformations.values.forEach { it() }
|
transformations.values.forEach { it() }
|
||||||
postprocessNops()
|
postprocessNops()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun computeDontTouchInsns() {
|
private fun analyzeMethodBody(): Array<out Frame<SourceValue>?> =
|
||||||
for (i in 0..insns.lastIndex) {
|
Analyzer<SourceValue>(object : SourceInterpreter() {
|
||||||
val frame = frames[i] ?: continue
|
override fun naryOperation(insn: AbstractInsnNode, values: MutableList<out SourceValue>): SourceValue {
|
||||||
val insn = insns[i]
|
for (value in values) {
|
||||||
|
dontTouchInsns.addAll(value.insns)
|
||||||
|
}
|
||||||
|
return super.naryOperation(insn, values)
|
||||||
|
}
|
||||||
|
|
||||||
when (insn.opcode) {
|
override fun copyOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue {
|
||||||
Opcodes.DUP ->
|
dontTouchInsns.addAll(value.insns)
|
||||||
dontTouchWordsOnTop(i, frame, 1)
|
return super.copyOperation(insn, value)
|
||||||
Opcodes.DUP_X1 ->
|
}
|
||||||
dontTouchWordsOnTop(i, frame, 2)
|
|
||||||
Opcodes.DUP_X2 ->
|
override fun unaryOperation(insn: AbstractInsnNode, value: SourceValue): SourceValue {
|
||||||
dontTouchWordsOnTop(i, frame, 3)
|
if (insn.opcode != Opcodes.CHECKCAST) {
|
||||||
Opcodes.DUP2 ->
|
dontTouchInsns.addAll(value.insns)
|
||||||
dontTouchWordsOnTop(i, frame, 2)
|
}
|
||||||
Opcodes.DUP2_X1 ->
|
return super.unaryOperation(insn, value)
|
||||||
dontTouchWordsOnTop(i, frame, 3)
|
}
|
||||||
Opcodes.DUP2_X2 ->
|
|
||||||
dontTouchWordsOnTop(i, frame, 4)
|
override fun binaryOperation(insn: AbstractInsnNode, value1: SourceValue, value2: SourceValue): SourceValue {
|
||||||
Opcodes.SWAP ->
|
dontTouchInsns.addAll(value1.insns)
|
||||||
dontTouchWordsOnTop(i, frame, 2)
|
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() {
|
private fun computeTransformations() {
|
||||||
transformations.clear()
|
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);
|
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")
|
@TestMetadata("primitive.kt")
|
||||||
public void testPrimitive() throws Exception {
|
public void testPrimitive() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/elvis/primitive.kt");
|
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")
|
@TestMetadata("compiler/testData/codegen/boxInline/syntheticAccessors")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@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")
|
@TestMetadata("compiler/testData/codegen/boxInline/syntheticAccessors")
|
||||||
@TestDataPath("$PROJECT_ROOT")
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
@RunWith(JUnit3RunnerWithInners.class)
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
|||||||
Reference in New Issue
Block a user