From d43ad6d8cf217d6857da386370c4228d2a4722b1 Mon Sep 17 00:00:00 2001 From: Robbe Pincket <7889478+Kroppeb@users.noreply.github.com> Date: Wed, 19 Jul 2023 01:05:37 +0200 Subject: [PATCH] Very early implementation of suspend function handling --- .../vineflower/kotlin/KotlinDomHelper.java | 196 ++++++++ .../org/vineflower/kotlin/KotlinPlugin.java | 2 +- .../org/vineflower/kotlin/KotlinTests.java | 2 + .../results/pkg/TestCoroutinesLoops.dec | 466 ++++++++++++++++++ .../results/pkg/TestSimpleCoroutines.dec | 449 +++++++++++++++++ .../src/kt/pkg/TestCoroutinesLoops.kt | 84 ++++ .../src/kt/pkg/TestSimpleCoroutines.kt | 87 ++++ 7 files changed, 1285 insertions(+), 1 deletion(-) create mode 100644 plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDomHelper.java create mode 100644 plugins/kotlin/testData/results/pkg/TestCoroutinesLoops.dec create mode 100644 plugins/kotlin/testData/results/pkg/TestSimpleCoroutines.dec create mode 100644 plugins/kotlin/testData/src/kt/pkg/TestCoroutinesLoops.kt create mode 100644 plugins/kotlin/testData/src/kt/pkg/TestSimpleCoroutines.kt diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDomHelper.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDomHelper.java new file mode 100644 index 0000000000..e3891634d0 --- /dev/null +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinDomHelper.java @@ -0,0 +1,196 @@ +package org.vineflower.kotlin; + +import org.jetbrains.java.decompiler.api.GraphParser; +import org.jetbrains.java.decompiler.code.CodeConstants; +import org.jetbrains.java.decompiler.code.Instruction; +import org.jetbrains.java.decompiler.code.cfg.BasicBlock; +import org.jetbrains.java.decompiler.code.cfg.ControlFlowGraph; +import org.jetbrains.java.decompiler.modules.code.DeadCodeHelper; +import org.jetbrains.java.decompiler.modules.decompiler.decompose.DomHelper; +import org.jetbrains.java.decompiler.modules.decompiler.stats.RootStatement; +import org.jetbrains.java.decompiler.struct.StructMethod; + +import java.util.HashSet; +import java.util.Set; + +public class KotlinDomHelper implements GraphParser { + /** + * This is a prototype + */ + + private final GraphParser domHelper = new DomHelper(); + + @Override + public RootStatement createStatement(ControlFlowGraph graph, StructMethod mt) { + var entry = this.isCoroutine(mt); + if (entry >= 0) { + this.fixCoroutineGraph(graph, entry); + } + + return this.domHelper.createStatement(graph, mt); + } + + private void fixCoroutineGraph(ControlFlowGraph graph, int continuationParamIndex) { + // Step 1: check for the existence of the "coroutine preamble" + + removePreamble(graph, continuationParamIndex); + } + + private void removePreamble(ControlFlowGraph graph, int continuationParamIndex) { + var entryBlock = graph.getFirst(); + if (entryBlock == null || entryBlock.size() != 3 || entryBlock.getSuccs().size() != 2) { + return; + } + + var firstInst = entryBlock.getInstruction(0); + + if (firstInst.opcode != CodeConstants.opc_aload || firstInst.operand(0) != continuationParamIndex) { + return; + } + +// var ifInst = entryBlock.getInstruction(2); +// if (ifInst.opcode != CodeConstants.opc_ifeq) { +// return -1; +// } + + // Now we just assume that the preamble is there + + // place where the new ContinuationObject is made + var newBlock = entryBlock.getSuccs().get(0); + // + var innerIfBlock = entryBlock.getSuccs().get(1); + + if (innerIfBlock.getSuccs().size() != 2) { + return; + } + + + var lastInst = newBlock.getLastInstruction(); + + if (lastInst.opcode != CodeConstants.opc_astore) { + return; + } + + // This is the '$continuation' variable's index + var storeIndex = lastInst.operand(0); + + // Now we need to remove the preamble + if (newBlock.getSuccs().size() != 1) { + return; + } + + var switchBlock = newBlock.getSuccs().get(0); + var case0Block = switchBlock.getSuccs().get(1); + + var resultIndex = switchBlock.getInstruction(2).operand(0); + var suspendIndex = switchBlock.getInstruction(4).operand(0); + + graph.setFirst(case0Block); + + // Remove no longer needed instructions + Set seen = new HashSet<>(); + + // remove the ResultKt.throwOnFailure(`$result`) call + if (case0Block.size() > 2) { + var firstInst2 = case0Block.getInstruction(0); + var secondInst2 = case0Block.getInstruction(1); + + if (firstInst2.opcode == CodeConstants.opc_aload && firstInst2.operand(0) == resultIndex && + secondInst2.opcode == CodeConstants.opc_invokestatic) { + var instructions = case0Block.getSeq(); + instructions.removeInstruction(0); + instructions.removeInstruction(0); + } + } + + removeInstructions(case0Block, storeIndex, suspendIndex, seen); + + DeadCodeHelper.removeDeadBlocks(graph); + + + } + + private static void removeInstructions(BasicBlock currentBlock, int storeIndex, int suspendIndex, Set seen) { + if (!seen.add(currentBlock)) { + // Already seen + return; + } + + var instructions = currentBlock.getSeq(); + for (int i = 0; i < instructions.length(); i++) { + var inst = instructions.getInstr(i); + + if (inst.opcode == CodeConstants.opc_aload) { + if (inst.operand(0) == storeIndex) { + if (i + 2 < instructions.length()) { +// var loadVar = instructions.getInstr(i + 1); + var storeField = instructions.getInstr(i + 2); + + if (storeField.opcode == CodeConstants.opc_putfield) { + instructions.removeInstruction(i); + instructions.removeInstruction(i); + instructions.removeInstruction(i); + + //noinspection AssignmentToForLoopParameter + i--; // the i'th instruction is now the next instruction + continue; + } + } + + // load null instead, this is probably an arg to a suspend function + instructions.removeInstruction(i); + instructions.addInstruction( + i, + Instruction.create(CodeConstants.opc_aconst_null, false, CodeConstants.GROUP_GENERAL, + inst.bytecodeVersion, new int[0], 1), -1); + } + } + } + + if (instructions.length() > 2) { + var lastInst = instructions.getInstr(instructions.length() - 1); + var secondLastInst = instructions.getInstr(instructions.length() - 2); + + if (lastInst.opcode == CodeConstants.opc_if_acmpne && + secondLastInst.opcode == CodeConstants.opc_aload && secondLastInst.operand(0) == suspendIndex) { + // Remove the last two instructions + instructions.removeInstruction(instructions.length() - 1); + instructions.removeInstruction(instructions.length() - 1); + currentBlock.removeSuccessor(currentBlock.getSuccs().get(1)); // remove return block + } + } + + + for (var succ : currentBlock.getSuccs()) { + removeInstructions(succ, storeIndex, suspendIndex, seen); + } + + } + + private int isCoroutine(StructMethod mt) { + // TODO: make this an option? + // TODO: don't fix the graph when a "suspendCoroutineUninterceptedOrReturn" intrinsic is used + + var params = mt.methodDescriptor().params; + + if (params.length == 0) { + return -1; + } + + var lastParam = params[params.length - 1]; + if (lastParam.type != CodeConstants.TYPE_OBJECT || !lastParam.value.equals("kotlin/coroutines/Continuation")) { + return -1; + } + + int index = params.length; + + // Is there already a method for this? + for (var param : params) { + if (param.type == CodeConstants.TYPE_LONG || param.type == CodeConstants.TYPE_DOUBLE) { + index++; + } + } + + return index; + } +} diff --git a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java index ac059d3e8a..5e79fe6221 100644 --- a/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java +++ b/plugins/kotlin/src/main/java/org/vineflower/kotlin/KotlinPlugin.java @@ -24,7 +24,7 @@ public String id() { @Override public LanguageSpec getLanguageSpec() { - return new LanguageSpec("kotlin", new KotlinChooser(), new DomHelper(), new KotlinWriter(), makePass()); + return new LanguageSpec("kotlin", new KotlinChooser(), new KotlinDomHelper(), new KotlinWriter(), makePass()); } private static Pass makePass() { diff --git a/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java b/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java index 4f0f5cd444..e8066c3b4d 100644 --- a/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java +++ b/plugins/kotlin/src/test/java/org/vineflower/kotlin/KotlinTests.java @@ -83,5 +83,7 @@ private void registerKotlinTests() { register(KOTLIN, "TestSynchronized"); register(KOTLIN, "TestReflection"); register(KOTLIN, "TestConstructors"); + register(KOTLIN, "TestSimpleCoroutines"); + register(KOTLIN, "TestCoroutinesLoops"); } } diff --git a/plugins/kotlin/testData/results/pkg/TestCoroutinesLoops.dec b/plugins/kotlin/testData/results/pkg/TestCoroutinesLoops.dec new file mode 100644 index 0000000000..47693c32d7 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestCoroutinesLoops.dec @@ -0,0 +1,466 @@ +package pkg + +import kotlin.coroutines.Continuation +import kotlin.coroutines.jvm.internal.Boxing + +class TestCoroutinesLoops { + fun testA(a: Int, `$completion`: Continuation): Any? { + return Boxing.boxInt(0);// 6 + } + + fun testTruthMachine(a: Int, var2: Continuation): Any? { + if (a != 0) {// 9 + while (true) { + this.testA(a, null);// 15 + } + } + + return Boxing.boxInt(0);// 11 + } + + fun testForLoop(a: Int, b: Int, var3: Continuation): Any? { + var i: Int = a;// 19 + if (a <= b) {// 20 + while (true) { + if ((this.testA(i, null) as Number).intValue() == 17) {// 21 + return Boxing.boxInt(i);// 22 + } + + if (i == 17) {// 25 + this.testTruthMachine(a, null);// 26 + } + + if (i == b) { + break; + } + + ++i; + } + } + + return Boxing.boxInt(0);// 30 + } + + fun testForLoopWithBreak(a: Int, b: Int, var3: Continuation): Any? { + var i: Int = a;// 33 + if (a <= b) {// 34 + while (((Number)this.testA(i, null)).intValue() != 17) {// 35 + if (i == 17) {// 39 + this.testTruthMachine(a, null);// 40 + } + + if (i == b) { + break; + } + + ++i; + } + } + + return Boxing.boxInt(0);// 44 + } + + fun testForLoopWithContinue(a: Int, b: Int, var3: Continuation): Any? { + var i: Int = a;// 47 + if (a <= b) {// 48 + while (true) { + if ((this.testA(i, null) as Number).intValue() != 17 && i == 17) {// 49 53 + this.testTruthMachine(a, null);// 54 + } + + if (i == b) { + break; + } + + ++i; + } + } + + return Boxing.boxInt(0);// 58 + } + + fun testNestedForLoop(a: Int, b: Int, var3: Continuation): Any? { + var i: Int = a;// 61 + if (a <= b) { + while (true) { + var j: Int = a;// 63 + if (a <= b) { + while (true) { + if ((this.testA(i * j, null) as Number).intValue() == 17) {// 64 + return Boxing.boxInt(i);// 65 + } + + if (i == 17 * j) {// 68 + this.testTruthMachine(a + j, null);// 69 + } + + if ((this.testA(j, null) as Number).intValue() == 17) {// 72 + break; + } + + if (j == 17 - i) {// 76 + this.testTruthMachine(a, null);// 77 + } + + if (j == b) { + break; + } + + ++j; + } + } + + if (i == b) {// 62 + break; + } + + ++i; + } + } + + return Boxing.boxInt(0);// 82 + } +} + +class 'pkg/TestCoroutinesLoops' { + method 'testA (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 0 7 + 1 7 + 2 7 + 3 7 + 4 7 + } + + method 'testTruthMachine (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 58 11 + 59 11 + 5a 11 + 5b 11 + 60 17 + 61 17 + 62 17 + 63 17 + 64 17 + 66 13 + 67 13 + 68 13 + 69 13 + 6a 13 + 6b 13 + } + + method 'testForLoop (IILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 5c 21 + 5d 21 + 5e 21 + 5f 21 + 60 21 + 61 22 + 62 22 + 63 22 + 64 22 + 65 22 + 6a 24 + 6b 24 + 6c 24 + 6d 24 + 6e 24 + 6f 24 + 70 24 + bd 24 + be 24 + bf 24 + c0 24 + c1 24 + c2 24 + c3 24 + c4 24 + c5 24 + c8 25 + c9 25 + ca 25 + cb 25 + cc 25 + cd 25 + ce 28 + cf 28 + d0 28 + d1 28 + d2 28 + d5 29 + d6 29 + d7 29 + d8 29 + d9 29 + da 29 + 128 32 + 129 32 + 12a 32 + 12b 32 + 12e 36 + 12f 36 + 130 36 + 134 40 + 135 40 + 136 40 + 137 40 + 138 40 + } + + method 'testForLoopWithBreak (IILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 5c 44 + 5d 44 + 5e 44 + 5f 44 + 60 44 + 61 45 + 62 45 + 63 45 + 64 45 + 65 45 + 6a 46 + 6b 46 + 6c 46 + 6d 46 + 6e 46 + 6f 46 + 70 46 + bd 46 + be 46 + bf 46 + c0 46 + c1 46 + c2 46 + c3 46 + c4 46 + c5 46 + cb 47 + cc 47 + cd 47 + ce 47 + cf 47 + d2 48 + d3 48 + d4 48 + d5 48 + d6 48 + d7 48 + 125 51 + 126 51 + 127 51 + 128 51 + 12b 55 + 12c 55 + 12d 55 + 131 59 + 132 59 + 133 59 + 134 59 + 135 59 + } + + method 'testForLoopWithContinue (IILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 5c 63 + 5d 63 + 5e 63 + 5f 63 + 60 63 + 61 64 + 62 64 + 63 64 + 64 64 + 65 64 + 6a 66 + 6b 66 + 6c 66 + 6d 66 + 6e 66 + 6f 66 + 70 66 + bd 66 + be 66 + bf 66 + c0 66 + c1 66 + c2 66 + c3 66 + c4 66 + c5 66 + cb 66 + cc 66 + cd 66 + ce 66 + cf 66 + d2 67 + d3 67 + d4 67 + d5 67 + d6 67 + d7 67 + 125 70 + 126 70 + 127 70 + 128 70 + 12b 74 + 12c 74 + 12d 74 + 131 78 + 132 78 + 133 78 + 134 78 + 135 78 + } + + method 'testNestedForLoop (IILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 64 82 + 65 82 + 66 82 + 67 82 + 68 82 + 69 83 + 6a 83 + 6b 83 + 6c 83 + 6d 83 + 72 85 + 73 85 + 74 85 + 75 86 + 76 86 + 77 86 + 78 86 + 7b 88 + 7c 88 + 7d 88 + 7e 88 + 7f 88 + 80 88 + 81 88 + 82 88 + 83 88 + 84 88 + df 88 + e0 88 + e1 88 + e2 88 + e3 88 + e4 88 + e5 88 + e6 88 + e7 88 + ea 89 + eb 89 + ec 89 + ed 89 + ee 89 + ef 89 + f0 92 + f1 92 + f2 92 + f3 92 + f4 92 + f5 92 + f6 92 + f7 92 + fa 93 + fb 93 + fc 93 + fd 93 + fe 93 + ff 93 + 100 93 + 101 93 + 102 93 + 15e 96 + 15f 96 + 160 96 + 161 96 + 162 96 + 163 96 + 164 96 + 1bf 96 + 1c0 96 + 1c1 96 + 1c2 96 + 1c3 96 + 1c4 96 + 1c5 96 + 1c6 96 + 1c7 96 + 1cd 100 + 1ce 100 + 1cf 100 + 1d0 100 + 1d1 100 + 1d2 100 + 1d3 100 + 1d4 100 + 1d7 101 + 1d8 101 + 1d9 101 + 1da 101 + 1db 101 + 1dc 101 + 238 104 + 239 104 + 23a 104 + 23b 104 + 23e 108 + 23f 108 + 240 108 + 244 112 + 245 112 + 246 112 + 247 112 + 24a 116 + 24b 116 + 24c 116 + 250 120 + 251 120 + 252 120 + 253 120 + 254 120 + } +} + +Lines mapping: +6 <-> 8 +9 <-> 12 +11 <-> 18 +15 <-> 14 +19 <-> 22 +20 <-> 23 +21 <-> 25 +22 <-> 26 +25 <-> 29 +26 <-> 30 +30 <-> 41 +33 <-> 45 +34 <-> 46 +35 <-> 47 +39 <-> 48 +40 <-> 49 +44 <-> 60 +47 <-> 64 +48 <-> 65 +49 <-> 67 +53 <-> 67 +54 <-> 68 +58 <-> 79 +61 <-> 83 +62 <-> 113 +63 <-> 86 +64 <-> 89 +65 <-> 90 +68 <-> 93 +69 <-> 94 +72 <-> 97 +76 <-> 101 +77 <-> 102 +82 <-> 121 +Not mapped: +10 +14 +36 +50 +73 diff --git a/plugins/kotlin/testData/results/pkg/TestSimpleCoroutines.dec b/plugins/kotlin/testData/results/pkg/TestSimpleCoroutines.dec new file mode 100644 index 0000000000..beb3fa5094 --- /dev/null +++ b/plugins/kotlin/testData/results/pkg/TestSimpleCoroutines.dec @@ -0,0 +1,449 @@ +package pkg + +import kotlin.coroutines.Continuation +import kotlin.coroutines.jvm.internal.Boxing + +class TestSimpleCoroutines { + fun testA(a: Int, `$completion`: Continuation): Any? { + return Boxing.boxInt(0);// 6 + } + + fun testSingleWait(a: Int, var2: Continuation): Any? { + return Boxing.boxInt(a + (this.testA(a, null) as Number).intValue());// 9 10 + } + + fun testSingleWaitAtTail(a: Int, `$completion`: Continuation): Any? { + return this.testA(a + 5, `$completion`);// 14 + } + + fun testDoubleWait(a: Int, var2: Continuation): Any? { + return Boxing.boxInt(a + (this.testA(a, null) as Number).intValue() + (this.testSingleWait(a + 1, null) as Number).intValue());// 17 18 + } + + fun testWithWaitAtEnd(a: Int, var2: Continuation): Any? { + return this.testSingleWaitAtTail((this.testA(a, null) as Number).intValue() + (this.testSingleWait(a + 1, null) as Number).intValue(), null);// 21 22 + } + + fun testConditionalWait(a: Int, var2: Continuation): Any? { + return if (a > 0) Boxing.boxInt(a + (this.testA(a, null) as Number).intValue()) else Boxing.boxInt(a);// 25 27 30 + } + + fun testConditionalWaitPhi(a: Int, var2: Continuation): Any? { + var var9: Int; + if (a > 0) {// 34 + var9 = a + (this.testA(a, null) as Number).intValue();// 36 + } else { + var9 = a + 3;// 38 + } + + return Boxing.boxInt(var9);// 41 + } + + fun testConditionalWaitAtTail(a: Int, `$completion`: Continuation): Any? { + return if (a > 0) this.testA(a + 5, `$completion`) else Boxing.boxInt(a);// 45 46 49 + } + + fun testMultipleConditionalWait(a: Int, var2: Continuation): Any? { + label24: + if (a > 0) {// 52 + return Boxing.boxInt(a + (this.testA(a, null) as Number).intValue());// 54 + } else { + return if (a < 10) Boxing.boxInt(a + (this.testA(a + 9, null) as Number).intValue()) else Boxing.boxInt(a);// 57 58 61 + } + } + + fun testSuspendInCondition(a: Int, var2: Continuation): Any? { + if ((this.testA(a, null) as Number).intValue() == 0) {// 64 65 + System.out.println("testA is 0");// 66 + return Boxing.boxBoolean(true);// 67 + } else { + return Boxing.boxBoolean(false);// 70 + } + } + + fun testSuspendedBooleanInCondition(a: Int, var2: Continuation): Any? { + if (this.testSuspendInCondition(a, null) as Boolean) {// 73 74 + System.out.println("testSuspendInCondition is true");// 75 + return Boxing.boxBoolean(true);// 76 + } else { + return Boxing.boxBoolean(false);// 79 + } + } +} + +class 'pkg/TestSimpleCoroutines' { + method 'testA (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 0 7 + 1 7 + 2 7 + 3 7 + 4 7 + } + + method 'testSingleWait (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 58 11 + 59 11 + 5d 11 + 5e 11 + 5f 11 + 60 11 + 8a 11 + 8b 11 + 8c 11 + 8d 11 + 8e 11 + 8f 11 + 90 11 + 91 11 + 92 11 + 93 11 + 94 11 + 95 11 + 96 11 + 97 11 + } + + method 'testSingleWaitAtTail (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 0 15 + 1 15 + 2 15 + 3 15 + 4 15 + 5 15 + 6 15 + 7 15 + 8 15 + } + + method 'testDoubleWait (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 5c 19 + 5d 19 + 61 19 + 62 19 + 63 19 + 64 19 + a9 19 + aa 19 + ab 19 + ac 19 + ad 19 + ae 19 + af 19 + b0 19 + b1 19 + b2 19 + b4 19 + b5 19 + b6 19 + b7 19 + b8 19 + b9 19 + ba 19 + bb 19 + e7 19 + e8 19 + e9 19 + ea 19 + eb 19 + ec 19 + ed 19 + ee 19 + ef 19 + f0 19 + f1 19 + f2 19 + f3 19 + f4 19 + } + + method 'testWithWaitAtEnd (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 60 23 + 61 23 + 65 23 + 66 23 + 67 23 + 68 23 + ae 23 + b0 23 + b1 23 + b2 23 + b3 23 + b4 23 + b5 23 + b9 23 + ba 23 + bb 23 + bc 23 + bd 23 + be 23 + bf 23 + c0 23 + fd 23 + fe 23 + ff 23 + 100 23 + 101 23 + 102 23 + 103 23 + 104 23 + 105 23 + 106 23 + 107 23 + 108 23 + 109 23 + 10a 23 + 10b 23 + 10c 23 + 12a 23 + } + + method 'testConditionalWait (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 58 27 + 59 27 + 5a 27 + 5b 27 + 5c 27 + 61 27 + 63 27 + 64 27 + 65 27 + 66 27 + 67 27 + 68 27 + 8e 27 + 8f 27 + 90 27 + 91 27 + 92 27 + 93 27 + 94 27 + 95 27 + 96 27 + 97 27 + 98 27 + 99 27 + 9a 27 + 9c 27 + 9d 27 + 9e 27 + 9f 27 + } + + method 'testConditionalWaitPhi (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 5d 32 + 5e 32 + 63 33 + 66 33 + 67 33 + 68 33 + 69 33 + 6a 33 + 6b 33 + 93 33 + 94 33 + 95 33 + 96 33 + 97 33 + 98 33 + 99 33 + 9a 33 + 9b 33 + 9c 33 + 9d 33 + 9e 33 + a2 35 + a3 35 + a4 35 + a5 35 + a6 38 + a7 38 + a8 38 + a9 38 + aa 38 + } + + method 'testConditionalWaitAtTail (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 0 42 + 1 42 + 4 42 + 5 42 + 6 42 + 7 42 + 8 42 + 9 42 + a 42 + b 42 + d 42 + e 42 + f 42 + 10 42 + } + + method 'testMultipleConditionalWait (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 5c 47 + 5d 47 + 5e 47 + 5f 47 + 60 47 + 65 48 + 67 48 + 68 48 + 69 48 + 6a 48 + 6b 48 + 6c 48 + 92 48 + 93 48 + 94 48 + 95 48 + 96 48 + 97 48 + 98 48 + 99 48 + 9a 48 + 9b 48 + 9c 48 + 9d 48 + 9e 48 + 9f 48 + a0 50 + a1 50 + a2 50 + a3 50 + a6 50 + a8 50 + a9 50 + aa 50 + ab 50 + ac 50 + ad 50 + ae 50 + af 50 + b0 50 + d6 50 + d7 50 + d8 50 + d9 50 + da 50 + db 50 + dc 50 + dd 50 + de 50 + df 50 + e0 50 + e1 50 + e2 50 + e4 50 + e5 50 + e6 50 + e7 50 + } + + method 'testSuspendInCondition (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 58 55 + 59 55 + 5a 55 + 5b 55 + 5c 55 + 5d 55 + 77 55 + 78 55 + 79 55 + 7a 55 + 7b 55 + 7c 55 + 7d 55 + 80 56 + 81 56 + 82 56 + 83 56 + 84 56 + 86 56 + 87 56 + 88 56 + 89 57 + 8a 57 + 8b 57 + 8c 57 + 8d 57 + 8e 59 + 8f 59 + 90 59 + 91 59 + 92 59 + } + + method 'testSuspendedBooleanInCondition (ILkotlin/coroutines/Continuation;)Ljava/lang/Object;' { + 58 64 + 59 64 + 5a 64 + 5b 64 + 5c 64 + 5d 64 + 77 64 + 78 64 + 79 64 + 7a 64 + 7b 64 + 7c 64 + 7d 64 + 80 65 + 81 65 + 82 65 + 83 65 + 84 65 + 86 65 + 87 65 + 88 65 + 89 66 + 8a 66 + 8b 66 + 8c 66 + 8d 66 + 8e 68 + 8f 68 + 90 68 + 91 68 + 92 68 + } +} + +Lines mapping: +6 <-> 8 +9 <-> 12 +10 <-> 12 +14 <-> 16 +17 <-> 20 +18 <-> 20 +21 <-> 24 +22 <-> 24 +25 <-> 28 +27 <-> 28 +30 <-> 28 +34 <-> 33 +36 <-> 34 +38 <-> 36 +41 <-> 39 +45 <-> 43 +46 <-> 43 +49 <-> 43 +52 <-> 48 +54 <-> 49 +57 <-> 51 +58 <-> 51 +61 <-> 51 +64 <-> 56 +65 <-> 56 +66 <-> 57 +67 <-> 58 +70 <-> 60 +73 <-> 65 +74 <-> 65 +75 <-> 66 +76 <-> 67 +79 <-> 69 +Not mapped: +26 +33 +35 +53 diff --git a/plugins/kotlin/testData/src/kt/pkg/TestCoroutinesLoops.kt b/plugins/kotlin/testData/src/kt/pkg/TestCoroutinesLoops.kt new file mode 100644 index 0000000000..6f72fff4a1 --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestCoroutinesLoops.kt @@ -0,0 +1,84 @@ +package pkg + +class TestCoroutinesLoops { + + suspend fun testA(a: Int): Int { + return 0 + } + + suspend fun testTruthMachine(a: Int): Int { + if (a == 0) { + return 0 + } + + while (true) { + testA(a) + } + } + + suspend fun testForLoop(a: Int, b: Int) : Int { + for (i in a..b) { + if (testA(i) == 17) { + return i + } + + if (i == 17) { + testTruthMachine(a) + } + } + + return 0 + } + + suspend fun testForLoopWithBreak(a: Int, b: Int) : Int { + for (i in a..b) { + if (testA(i) == 17) { + break + } + + if (i == 17) { + testTruthMachine(a) + } + } + + return 0 + } + + suspend fun testForLoopWithContinue(a: Int, b: Int) : Int { + for (i in a..b) { + if (testA(i) == 17) { + continue + } + + if (i == 17) { + testTruthMachine(a) + } + } + + return 0 + } + + suspend fun testNestedForLoop(a: Int, b: Int) : Int { + for (i in a..b) { + for (j in a..b) { + if (testA(i * j) == 17) { + return i + } + + if (i == 17 * j) { + testTruthMachine(a + j) + } + + if (testA(j) == 17) { + break + } + + if (j == 17 - i) { + testTruthMachine(a) + } + } + } + + return 0 + } +} \ No newline at end of file diff --git a/plugins/kotlin/testData/src/kt/pkg/TestSimpleCoroutines.kt b/plugins/kotlin/testData/src/kt/pkg/TestSimpleCoroutines.kt new file mode 100644 index 0000000000..800905859a --- /dev/null +++ b/plugins/kotlin/testData/src/kt/pkg/TestSimpleCoroutines.kt @@ -0,0 +1,87 @@ +package pkg + +import java.util.Dictionary + +class TestSimpleCoroutines { + + suspend fun testA(a: Int): Int { + return 0 + } + + suspend fun testSingleWait(a: Int): Int { + return a + testA(a) + } + + suspend fun testSingleWaitAtTail(a: Int): Int { + return testA(a + 5) + } + + suspend fun testDoubleWait(a: Int): Int { + return a + testA(a) + testSingleWait(a + 1) + } + + suspend fun testWithWaitAtEnd(a: Int): Int { + return testSingleWaitAtTail(testA(a) + testSingleWait(a + 1)) + } + + suspend fun testConditionalWait(a: Int): Int { + if (a > 0) { + return a + testA(a) + } + + return a + } + + suspend fun testConditionalWaitPhi(a: Int): Int { + var x = 0 + if (a > 0) { + x = a + testA(a) + } else { + x = a + 3 + } + + return x + } + + suspend fun testConditionalWaitAtTail(a: Int): Int { + if (a > 0) { + return testA(a + 5) + } + + return a + } + + suspend fun testMultipleConditionalWait(a: Int): Int { + if (a > 0) { + return a + testA(a) + } + + if (a < 10) { + return a + testA(a + 9) + } + + return a + } + + suspend fun testSuspendInCondition(a: Int): Boolean { + if (testA(a) == 0) { + println("testA is 0") + return true + } + + return false + } + + suspend fun testSuspendedBooleanInCondition(a: Int): Boolean { + if (testSuspendInCondition(a)) { + println("testSuspendInCondition is true") + return true + } + + return false + } + + suspend fun testReturnNullability(a: Int): Dictionary, List>?> { + TODO() + } +} \ No newline at end of file