diff --git a/src/main/java/org/ssssssss/script/compile/MagicScriptCompiler.java b/src/main/java/org/ssssssss/script/compile/MagicScriptCompiler.java index 7a565feaf3d8e747f4974a40d0f38dcd95233ec3..38f54418697aa0a2f0977e19fc7993c25d34f233 100644 --- a/src/main/java/org/ssssssss/script/compile/MagicScriptCompiler.java +++ b/src/main/java/org/ssssssss/script/compile/MagicScriptCompiler.java @@ -1,7 +1,12 @@ package org.ssssssss.script.compile; import org.ssssssss.script.MagicScriptContext; -import org.ssssssss.script.asm.*; +import org.ssssssss.script.asm.ClassWriter; +import org.ssssssss.script.asm.Handle; +import org.ssssssss.script.asm.Label; +import org.ssssssss.script.asm.MethodVisitor; +import org.ssssssss.script.asm.Opcodes; +import org.ssssssss.script.asm.Type; import org.ssssssss.script.parsing.Span; import org.ssssssss.script.parsing.VarIndex; import org.ssssssss.script.parsing.ast.Expression; @@ -9,8 +14,8 @@ import org.ssssssss.script.parsing.ast.Node; import org.ssssssss.script.parsing.ast.VariableSetter; import org.ssssssss.script.parsing.ast.binary.AssigmentOperation; import org.ssssssss.script.parsing.ast.statement.VariableAccess; -import org.ssssssss.script.runtime.RuntimeContext; import org.ssssssss.script.runtime.MagicScriptRuntime; +import org.ssssssss.script.runtime.RuntimeContext; import org.ssssssss.script.runtime.Variables; import org.ssssssss.script.runtime.function.MagicScriptLambdaFunction; import org.ssssssss.script.runtime.handle.ArithmeticHandle; @@ -18,8 +23,18 @@ import org.ssssssss.script.runtime.handle.BitHandle; import org.ssssssss.script.runtime.handle.FunctionCallHandle; import org.ssssssss.script.runtime.handle.OperatorHandle; -import java.lang.invoke.*; -import java.util.*; +import java.lang.invoke.CallSite; +import java.lang.invoke.LambdaMetafactory; +import java.lang.invoke.MethodHandle; +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.Stack; import java.util.concurrent.atomic.AtomicLong; import static org.ssssssss.script.asm.ClassWriter.COMPUTE_FRAMES; @@ -63,7 +78,10 @@ public class MagicScriptCompiler implements Opcodes { private final Stack labelStack = new Stack<>(); - private final Stack> finallyStack = new Stack<>(); + /* + * 替换为双端队列实现栈,因为Stack在for遍历时先从栈尾开始 + */ + private final Deque> finallyStack = new LinkedList<>(); private final Set varIndices; @@ -117,7 +135,6 @@ public class MagicScriptCompiler implements Opcodes { visitor.visitCode(); methodVisitors.push(visitor); vars.push(new ArrayList<>()); - finallyStack.push(null); labelStack.push(new Label[2]); return this; } @@ -252,6 +269,11 @@ public class MagicScriptCompiler implements Opcodes { return pop && node instanceof Expression ? insn(POP) : this; } + /** + * 设定tryCatch跳转 + * 如果在Label start到Label end代码范围内捕获到type异常,则跳转到Label handler + * type为null则表示finally,只要抛异常就跳转到handler + */ public MagicScriptCompiler tryCatch(Label start, Label end, Label handle, Class target) { self().visitTryCatchBlock(start, end, handle, getJvmType(target)); return this; @@ -649,30 +671,19 @@ public class MagicScriptCompiler implements Opcodes { return visitMethod(methodName, childNodes, parameters, () -> this.compile(childNodes)); } - public List finallyBlock() { - if (finallyStack.isEmpty()) { - return null; - } - return finallyStack.peek(); - } - - public void putHeadFinallyBlock(List finallyBlock) { - finallyStack.insertElementAt(finallyBlock, 0); + public Deque> finallyBlockStack() { + return finallyStack; } - public void putFinallyBlock(List finallyBlock) { - finallyStack.push(finallyBlock); + public void pushFinallyBlock(List finallyBlock) { + finallyStack.addFirst(finallyBlock); } - public List getFinallyBlock() { - if (finallyStack.isEmpty()) { - return null; - } - return finallyStack.pop(); + public List popFinallyBlock() { + return finallyStack.pollFirst(); } public MagicScriptCompiler pop() { - getFinallyBlock(); MethodVisitor visitor = methodVisitors.pop(); visitor.visitInsn(ACONST_NULL); visitor.visitInsn(ARETURN); diff --git a/src/main/java/org/ssssssss/script/parsing/ast/statement/Return.java b/src/main/java/org/ssssssss/script/parsing/ast/statement/Return.java index 96cd499b5bf999d5ed168afe2bf25b1c05ec83ad..837058d4769acab2f6900734c5995496c5befa0e 100644 --- a/src/main/java/org/ssssssss/script/parsing/ast/statement/Return.java +++ b/src/main/java/org/ssssssss/script/parsing/ast/statement/Return.java @@ -4,6 +4,8 @@ import org.ssssssss.script.compile.MagicScriptCompiler; import org.ssssssss.script.parsing.Span; import org.ssssssss.script.parsing.ast.Node; +import java.util.Deque; +import java.util.LinkedList; import java.util.List; public class Return extends Node { @@ -21,30 +23,57 @@ public class Return extends Node { @Override public void visitMethod(MagicScriptCompiler compiler) { - if(returnValue != null){ + if (returnValue != null) { returnValue.visitMethod(compiler); } } @Override public void compile(MagicScriptCompiler compiler) { - List block = compiler.getFinallyBlock(); - if (returnValue == null) { // return - if (block != null) { // 如果有finally块 - compiler.compile(block); // 执行finally块 + /* + * 单独弹出栈顶的finallyBlock + * 防止在return_1所属的finallyBlock_1中存在return_2语句时, + * 编译过程: return_1 -> finallyBlock_1 -> return_2 -> finallyBlock_1 (loop),最终造成堆栈溢出 + */ + // 单独弹出栈顶的finallyBlock_1 + List finallyBlock = compiler.popFinallyBlock(); + if (returnValue == null) { + // return; or return null; + // 在返回前执行finallyBlockStack + if (finallyBlock != null) { + // 编译return_1的finallyBlock_1,此时若finallyBlock_1中存在return_2,不会再在return_2中编译finallyBlock_1 + compiler.compile(finallyBlock); + // 编译栈中剩余的finallyBlock。这里需要拷贝到新的Deque,防止编译finallyBlock的return时抛出ConcurrentModificationException + Deque> finallyBlockStack = new LinkedList<>(compiler.finallyBlockStack()); + for (List fb : finallyBlockStack) { + compiler.compile(fb); + } + // 压入finallyBlock_1,供try-catch-finally其他地方的return再次编译finallyBlock_1 + compiler.pushFinallyBlock(finallyBlock); } - compiler.insn(ACONST_NULL); // 压入 NULL - } else { // return expr; + // 压入 NULL + compiler.insn(ACONST_NULL); + } else { + // return expr; compiler.visit(returnValue); - if (block != null) { // 如果有finally块 - compiler.store(3) // 保存返回结果 - .compile(block) // 执行 finally - .load3(); // 加载返回结果 + // 在返回前执行finallyBlockStack + if (finallyBlock != null) { + // 保存返回结果 + compiler.store(3); + // 编译return_1的finallyBlock_1,此时若finallyBlock_1中存在return_2,不会再在return_2中编译finallyBlock_1 + compiler.compile(finallyBlock); + // 编译栈中剩余的finallyBlock。这里需要拷贝到新的Deque,防止编译finallyBlock的return时抛出ConcurrentModificationException + Deque> finallyBlockStack = new LinkedList<>(compiler.finallyBlockStack()); + for (List fb : finallyBlockStack) { + compiler.compile(fb); + } + // 加载返回结果 + compiler.load3(); + // 压入finallyBlock_1,供try-catch-finally其他地方的return再次编译finallyBlock_1 + compiler.pushFinallyBlock(finallyBlock); } } - if(block != null){ - compiler.putFinallyBlock(block); - } - compiler.insn(ARETURN); // 返回 + // 返回 + compiler.insn(ARETURN); } -} \ No newline at end of file +} diff --git a/src/main/java/org/ssssssss/script/parsing/ast/statement/Throw.java b/src/main/java/org/ssssssss/script/parsing/ast/statement/Throw.java index 3788b082b459723f35f4da5d796ec0e7694146bc..5bee2fe332b1aa200d7ce1904aa3c21159e418ec 100644 --- a/src/main/java/org/ssssssss/script/parsing/ast/statement/Throw.java +++ b/src/main/java/org/ssssssss/script/parsing/ast/statement/Throw.java @@ -22,6 +22,7 @@ public class Throw extends Node { @Override public void compile(MagicScriptCompiler compiler) { + // throw MagicScriptRuntimeException.create(expr); compiler.visit(expression) .invoke(INVOKESTATIC, MagicScriptRuntimeException.class, "create", MagicScriptRuntimeException.class, Object.class) .insn(ATHROW); diff --git a/src/main/java/org/ssssssss/script/parsing/ast/statement/TryStatement.java b/src/main/java/org/ssssssss/script/parsing/ast/statement/TryStatement.java index e48ef94e2f320b51cb64a20485fc8427bfa33fd8..a890002417292ee498bfce75c8ae6060c1ccaaf9 100644 --- a/src/main/java/org/ssssssss/script/parsing/ast/statement/TryStatement.java +++ b/src/main/java/org/ssssssss/script/parsing/ast/statement/TryStatement.java @@ -30,6 +30,7 @@ public class TryStatement extends Node { public void visitMethod(MagicScriptCompiler compiler) { tryResources.forEach(it -> it.visitMethod(compiler)); } + @Override public void compile(MagicScriptCompiler compiler) { tryResources.forEach(it -> compiler.load(it.getVarIndex()).invoke(INVOKESTATIC, TryStatement.class, "autoClose", void.class, Object.class)); @@ -55,57 +56,103 @@ public class TryStatement extends Node { @Override public void compile(MagicScriptCompiler compiler) { - Label l0 = new Label(); - Label l1 = new Label(); - Label l2 = new Label(); - Label l3 = new Label(); - Label l4 = new Label(); - Label l5 = new Label(); - Label end = new Label(); boolean hasCatch = exceptionVarNode != null; boolean hasFinally = !finallyBlock.isEmpty(); - if (hasFinally) { // try + catch + finally - compiler.putFinallyBlock(finallyBlock); - } - compiler.label(l0) - .compile(tryResources) - .compile(tryBlock) // try - .label(l1); if (hasFinally) { - compiler.compile(finallyBlock) - .jump(GOTO, end); // 跳转至结束 + compileWithFinally(compiler, hasCatch); + } else { + compileWithoutFinally(compiler, hasCatch); } - compiler.label(l2) // catch MagicExitException - .store(3) -// .compile(this.autoClose) - .load3() - .insn(ATHROW); // throw e - if(hasCatch){ - compiler.label(l3) + } + + private void compileWithFinally(MagicScriptCompiler compiler, boolean hasCatch) { + Label tryStart = new Label(); + Label tryEnd = new Label(); + Label catchMagicExitException = new Label(); + Label catchThrowable = new Label(); + Label catchEnd = new Label(); + Label finallyStart = new Label(); + Label finallyThrowStart = new Label(); + Label end = new Label(); + // try中如果有return语句,需要取这里压入的finallyBlock在return之前执行 + compiler.pushFinallyBlock(finallyBlock); + compiler.label(tryStart) + // tryResources + .compile(tryResources) + // try + .compile(tryBlock) + .label(tryEnd); + // 弹出自身压入的finallyBlock + compiler.popFinallyBlock(); + compiler + .label(finallyStart) + .compile(finallyBlock) + // 跳转至结束 + .jump(GOTO, end); + compiler.label(catchMagicExitException) + // catch (MagicExitException e) { throw e; } + .insn(ATHROW); + if (hasCatch) { + // catch中如果有return语句,需要取这里压入的finallyBlock在return之前执行 + compiler.pushFinallyBlock(finallyBlock); + compiler.label(catchThrowable) + // catch (Throwable e) { } + // 将异常变量存入variables .store(3) - .pre_store(exceptionVarNode) //保存异常变量前准备 + .pre_store(exceptionVarNode) .load3() .store(exceptionVarNode) - .compile(catchBlock); // 编译catch代码块 + .compile(catchBlock) + .label(catchEnd); + // 弹出自身压入的finallyBlock + compiler.popFinallyBlock(); + compiler + // 跳转至finally + .jump(GOTO, finallyStart); + } + compiler.label(finallyThrowStart) + .store(3) + .compile(finallyBlock) + .load3() + .insn(ATHROW); + compiler.label(end); + compiler.tryCatch(tryStart, tryEnd, finallyThrowStart, MagicExitException.class); + if (hasCatch) { + compiler.tryCatch(tryStart, tryEnd, catchThrowable, Throwable.class); + compiler.tryCatch(catchThrowable, catchEnd, finallyThrowStart, null); } - if (hasFinally) { - compiler.label(l5).compile(finallyBlock).jump(GOTO, end); // 跳转至结束 - compiler.label(l4) + compiler.tryCatch(tryStart, tryEnd, finallyThrowStart, null); + } + + private void compileWithoutFinally(MagicScriptCompiler compiler, boolean hasCatch) { + Label tryStart = new Label(); + Label tryEnd = new Label(); + Label catchMagicExitException = new Label(); + Label catchThrowable = new Label(); + compiler.label(tryStart) + // tryResources + .compile(tryResources) + // try + .compile(tryBlock) + .label(tryEnd); + compiler.label(catchMagicExitException) + // catch (MagicExitException e) { throw e; } + .insn(ATHROW); + if (hasCatch) { + compiler.label(catchThrowable) + // catch (Throwable e) { } + // 将异常变量存入variables .store(3) - .compile(finallyBlock) + .pre_store(exceptionVarNode) .load3() - .insn(ATHROW); - compiler.getFinallyBlock(); - } - compiler.label(end); - compiler.tryCatch(l0, l1, l2, MagicExitException.class); - if(hasCatch){ - compiler.tryCatch(l0, l1, l3, Throwable.class); + .store(exceptionVarNode) + .compile(catchBlock); } - if (hasFinally) { // try + catch + finally - compiler.tryCatch(l0, l1, l4, null); - compiler.tryCatch(l2, l5, l4, null); + + compiler.tryCatch(tryStart, tryEnd, catchMagicExitException, MagicExitException.class); + if (hasCatch) { + compiler.tryCatch(tryStart, tryEnd, catchThrowable, Throwable.class); } } } diff --git a/src/test/java/org/ssssssss/script/TryCatchFinallyReturnTest.java b/src/test/java/org/ssssssss/script/TryCatchFinallyReturnTest.java new file mode 100644 index 0000000000000000000000000000000000000000..7c51daaea33f5b5cc753db399cea78472260b021 --- /dev/null +++ b/src/test/java/org/ssssssss/script/TryCatchFinallyReturnTest.java @@ -0,0 +1,95 @@ +package org.ssssssss.script; + +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +public class TryCatchFinallyReturnTest extends BaseTest { + + @Test + public void finallyReturn() { + Assert.assertEquals(finallyReturnJAVA().toString(), execute("grammar/try_catch_finally_return_3.ms").toString()); + } + + private static List finallyReturnJAVA() { + int a = 1; + StringBuilder sb = new StringBuilder(); + try { + int b = a++ / 0; + } catch (Exception e) { + a++; + sb.append("a: " + a + " " + e + ", "); + int c = a++ / 0; + } finally { + sb.append("b: " + a++ + ", "); + return returnWarp(a, sb); + } + } + + @Test + public void tryNestedReturn() { + Assert.assertEquals(tryNestedReturnJAVA().toString(), execute("grammar/try_catch_finally_return_5.ms").toString()); + } + + private static List tryNestedReturnJAVA() { + int a = 1; + StringBuilder sb = new StringBuilder(); + try { + a++; + try { + a++; + return returnWarp(a, sb); + } finally { + sb.append("a: " + a++ + ", "); + } + } finally { + sb.append("b: " + a++ + ", "); + } + } + + @Test + public void tryFinallyNestedReturn() { + Assert.assertEquals(tryFinallyNestedReturnJAVA().toString(), execute("grammar/try_catch_finally_return_6.ms").toString()); + } + + private static List tryFinallyNestedReturnJAVA() { + int a = 1; + StringBuilder sb = new StringBuilder(); + try { + a++; + try { + a++; + try { + a++; + try { + a++; + sb.append("a: " + a++ + ", "); + return returnWarp(a, sb); + } finally { + sb.append("b: " + a++ + ", "); + return returnWarp(a, sb); + } + } finally { + sb.append("c: " + a++ + ", "); + return returnWarp(a, sb); + } + } finally { + sb.append("d: " + a++ + ", "); + return returnWarp(a, sb); + } + } finally { + sb.append("e: " + a++ + ", "); + return returnWarp(a, sb); + } + } + + private static List returnWarp(int a, StringBuilder sb) { + List ret = new ArrayList<>(2); + ret.add(a); + ret.add(sb); + return ret; + } + +} diff --git a/src/test/resources/grammar/try_catch_finally_return_3.ms b/src/test/resources/grammar/try_catch_finally_return_3.ms new file mode 100644 index 0000000000000000000000000000000000000000..2f00c3a2a941ff05f2d4e15a2eb7d01b240a53fb --- /dev/null +++ b/src/test/resources/grammar/try_catch_finally_return_3.ms @@ -0,0 +1,12 @@ +a = 1 +s = '' +try { + b= a++ / 0 +} catch(e) { + a++; + s = s + 'a: ' + a + ' ' + e + ', '; + c = a++ / 0 +} finally { + s = s + 'b: ' + a++ + ', '; + return [a, s] +} \ No newline at end of file diff --git a/src/test/resources/grammar/try_catch_finally_return_5.ms b/src/test/resources/grammar/try_catch_finally_return_5.ms new file mode 100644 index 0000000000000000000000000000000000000000..5829c8ab484812b1fd1b990a71cb47e127175f98 --- /dev/null +++ b/src/test/resources/grammar/try_catch_finally_return_5.ms @@ -0,0 +1,13 @@ +a = 1 +StringBuilder sb = new StringBuilder(); +try { + a++; + try { + a++; + return [a, sb]; + } finally { + sb.append('a: ' + a++ + ', '); + } +} finally { + sb.append('b: ' + a++ + ', '); +} diff --git a/src/test/resources/grammar/try_catch_finally_return_6.ms b/src/test/resources/grammar/try_catch_finally_return_6.ms new file mode 100644 index 0000000000000000000000000000000000000000..ec019f8b0416eabdd84ea4cd74f92151aa1e2e5e --- /dev/null +++ b/src/test/resources/grammar/try_catch_finally_return_6.ms @@ -0,0 +1,28 @@ +a = 1 +StringBuilder sb = new StringBuilder(); +try { + a++; + try { + a++; + try { + a++; + try { + a++; + sb.append('a: ' + a++ + ', '); + return [a, sb]; + } finally { + sb.append('b: ' + a++ + ', '); + return [a, sb]; + } + } finally { + sb.append('c: ' + a++ + ', '); + return [a, sb]; + } + } finally { + sb.append('d: ' + a++ + ', '); + return [a, sb]; + } +} finally { + sb.append('e: ' + a++ + ', '); + return [a, sb]; +}