From 8eb8dc2014c7070b4bfcd37685bf85c04fc6da36 Mon Sep 17 00:00:00 2001 From: Xavier De Cock Date: Wed, 30 Oct 2024 13:26:50 +0100 Subject: [PATCH 1/5] feat(timeout): create a dedicated bytecode generator aiming at throwing an exception if the thread has been interrupted --- .../util/InterruptHandlerInjector.java | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 core/src/main/java/lucee/transformer/bytecode/util/InterruptHandlerInjector.java diff --git a/core/src/main/java/lucee/transformer/bytecode/util/InterruptHandlerInjector.java b/core/src/main/java/lucee/transformer/bytecode/util/InterruptHandlerInjector.java new file mode 100644 index 0000000000..c45142d7c0 --- /dev/null +++ b/core/src/main/java/lucee/transformer/bytecode/util/InterruptHandlerInjector.java @@ -0,0 +1,54 @@ +/** + * The goal of this helper is mostly to add a hooked bytecode to allow safe interruption of threads avoiding + * Tomcat ThreadDeath and the thread.stop() codepath + */ +package lucee.transformer.bytecode.util; + +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; + +import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.commons.Method; +import lucee.transformer.bytecode.util.Types; + +public final class InterruptHandlerInjector { + private static final Type TYPE_THREAD = Type.getType(Thread.class); + private static final Type TYPE_EXCEPTION = Type.getType(InterruptedException.class); + private static final Method METHOD_INTERRUPTED = new Method("interrupted", Type.BOOLEAN_TYPE, new Type[] {}); + + public static int writeLoopInit(GeneratorAdapter adapter) { + final int toIt = adapter.newLocal(Types.ITERATOR); + adapter.push(0); + adapter.storeLocal(toIt, Type.INT_TYPE); + return toIt; + } + + public static void writeLoopBodyEnd(GeneratorAdapter adapter, int iteratorRef, Label jumpLabel, String timeoutDescription) { + // count the loop, only check for interruptions once every 10K iterations + adapter.iinc(iteratorRef, 1); + adapter.loadLocal(iteratorRef); + adapter.push(10000); + adapter.ifICmp(Opcodes.IFLT, jumpLabel); + // reset counter + adapter.push(0); + adapter.storeLocal(iteratorRef); + // Check if the thread is interrupted + writePreempt(adapter, jumpLabel, timeoutDescription); + } + + public static void writeEndPreempt(GeneratorAdapter adapter, String timeoutDescription) { + Label endPreempt = new Label(); + writePreempt(adapter, endPreempt, timeoutDescription); + adapter.visitLabel(endPreempt); + } + + public static void writePreempt(GeneratorAdapter adapter, Label label, String timeoutDescription) { + // Check if the thread is interrupted + adapter.invokeStatic(TYPE_THREAD, METHOD_INTERRUPTED); + // Thread hasn't been interrupted, go to endPreempt + adapter.ifZCmp(Opcodes.IFEQ, label); + // Thread interrupted, throw Interrupted Exception + adapter.throwException(TYPE_EXCEPTION, "Timeout Exception ".concat(timeoutDescription)); + } +} \ No newline at end of file From 5cceca4e0d92e90de2c97604b32dda16e7283763 Mon Sep 17 00:00:00 2001 From: Xavier De Cock Date: Wed, 30 Oct 2024 13:28:04 +0100 Subject: [PATCH 2/5] feat: use the helper in loop transformers feat: use in statement loop --- .../java/lucee/transformer/bytecode/statement/DoWhile.java | 5 +++++ .../main/java/lucee/transformer/bytecode/statement/For.java | 5 +++++ .../java/lucee/transformer/bytecode/statement/ForEach.java | 4 +++- .../java/lucee/transformer/bytecode/statement/While.java | 4 +++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/DoWhile.java b/core/src/main/java/lucee/transformer/bytecode/statement/DoWhile.java index 3d1ce062c2..f1e4afed5f 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/DoWhile.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/DoWhile.java @@ -26,6 +26,7 @@ import lucee.transformer.TransformerException; import lucee.transformer.bytecode.Body; import lucee.transformer.bytecode.BytecodeContext; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; import lucee.transformer.expression.ExprBoolean; import lucee.transformer.expression.Expression; @@ -57,14 +58,18 @@ public DoWhile(Expression expr, Body body, Position start, Position end, String @Override public void _writeOut(BytecodeContext bc) throws TransformerException { GeneratorAdapter adapter = bc.getAdapter(); + final int loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); + adapter.visitLabel(begin); body.writeOut(bc); + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, beforeEnd, "during do while"); adapter.visitLabel(beforeEnd); expr.writeOut(bc, Expression.MODE_VALUE); adapter.ifZCmp(Opcodes.IFNE, begin); + InterruptHandlerInjector.writePreempt(adapter, end, "after do while"); adapter.visitLabel(end); } diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/For.java b/core/src/main/java/lucee/transformer/bytecode/statement/For.java index 2b8b118997..c214cd20a3 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/For.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/For.java @@ -28,6 +28,7 @@ import lucee.transformer.bytecode.Body; import lucee.transformer.bytecode.BytecodeContext; import lucee.transformer.bytecode.util.ASMUtil; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; import lucee.transformer.expression.Expression; public final class For extends StatementBaseNoFinal implements FlowControlBreak, FlowControlContinue, HasBody { @@ -69,6 +70,7 @@ public void _writeOut(BytecodeContext bc) throws TransformerException { Label beforeInit = new Label(); Label afterInit = new Label(); Label afterUpdate = new Label(); + final int loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); bc.visitLine(getStart()); adapter.visitLabel(beforeInit); @@ -87,6 +89,8 @@ public void _writeOut(BytecodeContext bc) throws TransformerException { update.writeOut(bc, Expression.MODE_VALUE); ASMUtil.pop(adapter, update, Expression.MODE_VALUE); } + + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, afterUpdate, "during for loop"); // ExpressionUtil.visitLine(bc, getStartLine()); adapter.visitLabel(afterUpdate); @@ -94,6 +98,7 @@ public void _writeOut(BytecodeContext bc) throws TransformerException { else bc.getFactory().TRUE().writeOut(bc, Expression.MODE_VALUE); adapter.visitJumpInsn(Opcodes.IFNE, afterInit); // ExpressionUtil.visitLine(bc, getEndLine()); + InterruptHandlerInjector.writePreempt(adapter, end, "after for loop"); adapter.visitLabel(end); } diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/ForEach.java b/core/src/main/java/lucee/transformer/bytecode/statement/ForEach.java index 6ef46daff3..15cbed3399 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/ForEach.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/ForEach.java @@ -31,6 +31,7 @@ import lucee.transformer.bytecode.BytecodeContext; import lucee.transformer.bytecode.expression.var.VariableRef; import lucee.transformer.bytecode.util.Types; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; import lucee.transformer.bytecode.visitor.OnFinally; import lucee.transformer.bytecode.visitor.TryFinallyVisitor; import lucee.transformer.expression.Expression; @@ -81,6 +82,7 @@ public void _writeOut(BytecodeContext bc) throws TransformerException { GeneratorAdapter adapter = bc.getAdapter(); final int it = adapter.newLocal(Types.ITERATOR); final int item = adapter.newLocal(Types.REFERENCE); + final int loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); // Value // ForEachUtil.toIterator(value) @@ -130,7 +132,7 @@ public void _writeOut(BytecodeContext bc) throws TransformerException { // Body body.writeOut(bc); - adapter.visitJumpInsn(Opcodes.GOTO, begin); + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, begin, "during foreach loop"); adapter.visitLabel(end); tfv.visitTryEnd(bc); diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/While.java b/core/src/main/java/lucee/transformer/bytecode/statement/While.java index 0e40d357fa..9b7199126a 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/While.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/While.java @@ -26,6 +26,7 @@ import lucee.transformer.TransformerException; import lucee.transformer.bytecode.Body; import lucee.transformer.bytecode.BytecodeContext; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; import lucee.transformer.expression.ExprBoolean; import lucee.transformer.expression.Expression; @@ -67,13 +68,14 @@ public While(boolean b, Body body, Position start, Position end, String label) { @Override public void _writeOut(BytecodeContext bc) throws TransformerException { GeneratorAdapter adapter = bc.getAdapter(); + final int loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); adapter.visitLabel(begin); expr.writeOut(bc, Expression.MODE_VALUE); adapter.ifZCmp(Opcodes.IFEQ, end); body.writeOut(bc); - adapter.visitJumpInsn(Opcodes.GOTO, begin); + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, begin, "during for loop"); adapter.visitLabel(end); } From ccd23ba4ef18df8ecd8d1dd57b2aa0953604da4a Mon Sep 17 00:00:00 2001 From: Xavier De Cock Date: Wed, 30 Oct 2024 13:32:51 +0100 Subject: [PATCH 3/5] feat: hook in Visitors --- .../lucee/transformer/bytecode/visitor/DoWhileVisitor.java | 5 +++++ .../transformer/bytecode/visitor/ForDoubleVisitor.java | 5 +++++ .../lucee/transformer/bytecode/visitor/ForIntVisitor.java | 5 +++++ .../java/lucee/transformer/bytecode/visitor/ForVisitor.java | 6 ++++++ .../lucee/transformer/bytecode/visitor/WhileVisitor.java | 5 +++++ 5 files changed, 26 insertions(+) diff --git a/core/src/main/java/lucee/transformer/bytecode/visitor/DoWhileVisitor.java b/core/src/main/java/lucee/transformer/bytecode/visitor/DoWhileVisitor.java index f865dea468..ea34a7eec3 100755 --- a/core/src/main/java/lucee/transformer/bytecode/visitor/DoWhileVisitor.java +++ b/core/src/main/java/lucee/transformer/bytecode/visitor/DoWhileVisitor.java @@ -23,6 +23,7 @@ import org.objectweb.asm.commons.GeneratorAdapter; import lucee.transformer.bytecode.BytecodeContext; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; // TODO testen wurde noch nicht getestet @@ -31,21 +32,25 @@ public final class DoWhileVisitor implements LoopVisitor { private Label begin; private Label end; private Label beforeEnd; + private int loopCounter; public void visitBeginBody(GeneratorAdapter mv) { end = new Label(); beforeEnd = new Label(); begin = new Label(); + loopCounter = InterruptHandlerInjector.writeLoopInit(mv); mv.visitLabel(begin); } public void visitEndBodyBeginExpr(GeneratorAdapter mv) { + InterruptHandlerInjector.writeLoopBodyEnd(mv, loopCounter, beforeEnd, "during do while"); mv.visitLabel(beforeEnd); } public void visitEndExpr(GeneratorAdapter mv) { mv.ifZCmp(Opcodes.IFNE, begin); + InterruptHandlerInjector.writePreempt(mv, end, "after do while"); mv.visitLabel(end); } diff --git a/core/src/main/java/lucee/transformer/bytecode/visitor/ForDoubleVisitor.java b/core/src/main/java/lucee/transformer/bytecode/visitor/ForDoubleVisitor.java index 40754566e1..7c9c3059c8 100755 --- a/core/src/main/java/lucee/transformer/bytecode/visitor/ForDoubleVisitor.java +++ b/core/src/main/java/lucee/transformer/bytecode/visitor/ForDoubleVisitor.java @@ -25,6 +25,7 @@ import lucee.transformer.Position; import lucee.transformer.bytecode.BytecodeContext; import lucee.transformer.bytecode.util.Types; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; public final class ForDoubleVisitor implements Opcodes, LoopVisitor { @@ -33,9 +34,11 @@ public final class ForDoubleVisitor implements Opcodes, LoopVisitor { public Label beforeBody = new Label(), afterBody = new Label(); public Label beforeUpdate = new Label(), afterUpdate = new Label(); public int i; + private int loopCounter; public int visitBeforeExpression(GeneratorAdapter adapter, int start, int step, boolean isLocal) { // init + loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); adapter.visitLabel(beforeInit); forInit(adapter, start, isLocal); adapter.goTo(beforeExpr); @@ -75,6 +78,8 @@ public void forUpdate(GeneratorAdapter adapter, int step, boolean isLocal) { adapter.visitVarInsn(DSTORE, i); } else adapter.visitIincInsn(i, step); + + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, beforeExpr, "during for loop"); } /** diff --git a/core/src/main/java/lucee/transformer/bytecode/visitor/ForIntVisitor.java b/core/src/main/java/lucee/transformer/bytecode/visitor/ForIntVisitor.java index 2b76d5f81e..d6f470cf36 100755 --- a/core/src/main/java/lucee/transformer/bytecode/visitor/ForIntVisitor.java +++ b/core/src/main/java/lucee/transformer/bytecode/visitor/ForIntVisitor.java @@ -25,6 +25,7 @@ import lucee.transformer.Position; import lucee.transformer.bytecode.BytecodeContext; import lucee.transformer.bytecode.util.Types; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; public final class ForIntVisitor implements Opcodes, LoopVisitor { @@ -33,9 +34,11 @@ public final class ForIntVisitor implements Opcodes, LoopVisitor { private Label beforeBody = new Label(), afterBody = new Label(); private Label beforeUpdate = new Label(), afterUpdate = new Label(); private int i; + private int loopCounter; public int visitBeforeExpression(GeneratorAdapter adapter, int start, int step, boolean isLocal) { // init + loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); adapter.visitLabel(beforeInit); forInit(adapter, start, isLocal); adapter.goTo(beforeExpr); @@ -75,6 +78,8 @@ private void forUpdate(GeneratorAdapter adapter, int step, boolean isLocal) { adapter.visitVarInsn(ISTORE, i); } else adapter.visitIincInsn(i, step); + + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, beforeExpr, "during for loop"); } /** diff --git a/core/src/main/java/lucee/transformer/bytecode/visitor/ForVisitor.java b/core/src/main/java/lucee/transformer/bytecode/visitor/ForVisitor.java index 37b9710ac5..e126d8ff37 100755 --- a/core/src/main/java/lucee/transformer/bytecode/visitor/ForVisitor.java +++ b/core/src/main/java/lucee/transformer/bytecode/visitor/ForVisitor.java @@ -25,6 +25,7 @@ import lucee.transformer.Position; import lucee.transformer.bytecode.BytecodeContext; import lucee.transformer.bytecode.util.Types; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; public final class ForVisitor implements Opcodes, LoopVisitor { @@ -35,8 +36,11 @@ public final class ForVisitor implements Opcodes, LoopVisitor { private int i; private Label lend = new Label(); private Label lbegin = new Label(); + private int loopCounter; public int visitBegin(GeneratorAdapter adapter, int start, boolean isLocal) { + loopCounter = InterruptHandlerInjector.writeLoopInit(adapter); + adapter.visitLabel(l0); forInit(adapter, start, isLocal); @@ -77,6 +81,8 @@ private void forInit(GeneratorAdapter adapter, int start, boolean isLocal) { if (isLocal) adapter.loadLocal(start); else adapter.push(start); adapter.visitVarInsn(ISTORE, i); + + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, l1, "during for loop"); } /** diff --git a/core/src/main/java/lucee/transformer/bytecode/visitor/WhileVisitor.java b/core/src/main/java/lucee/transformer/bytecode/visitor/WhileVisitor.java index 61cd6f9d94..f4727ed09c 100755 --- a/core/src/main/java/lucee/transformer/bytecode/visitor/WhileVisitor.java +++ b/core/src/main/java/lucee/transformer/bytecode/visitor/WhileVisitor.java @@ -23,16 +23,19 @@ import lucee.transformer.Position; import lucee.transformer.bytecode.BytecodeContext; +import lucee.transformer.bytecode.util.InterruptHandlerInjector; public final class WhileVisitor implements LoopVisitor { private Label begin; private Label end; + private int loopCounter; public void visitBeforeExpression(BytecodeContext bc) { begin = new Label(); end = new Label(); bc.getAdapter().visitLabel(begin); + loopCounter = InterruptHandlerInjector.writeLoopInit(bc.getAdapter()); } public void visitAfterExpressionBeforeBody(BytecodeContext bc) { @@ -41,6 +44,8 @@ public void visitAfterExpressionBeforeBody(BytecodeContext bc) { public void visitAfterBody(BytecodeContext bc, Position endline) { bc.getAdapter().visitJumpInsn(Opcodes.GOTO, begin); + + InterruptHandlerInjector.writeLoopBodyEnd(bc.getAdapter(), loopCounter, end, "during while loop"); bc.getAdapter().visitLabel(end); bc.visitLine(endline); } From 19206224f77d9b428c15970c01333d286c37075f Mon Sep 17 00:00:00 2001 From: Xavier De Cock Date: Wed, 30 Oct 2024 13:33:05 +0100 Subject: [PATCH 4/5] fix: remove thread.stop() --- core/src/main/java/lucee/commons/io/SystemUtil.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/lucee/commons/io/SystemUtil.java b/core/src/main/java/lucee/commons/io/SystemUtil.java index 090ea67b4e..6b30499dd1 100644 --- a/core/src/main/java/lucee/commons/io/SystemUtil.java +++ b/core/src/main/java/lucee/commons/io/SystemUtil.java @@ -1406,12 +1406,11 @@ public static void stop(PageContext pc, Thread thread) { } private static boolean _stop(Thread thread, Log log, boolean force) { - // we try to interrupt/stop the suspended thrad + // we try to interrupt/stop the suspended thread suspendEL(thread); try { if (isInLucee(thread)) { - if (!force) thread.interrupt(); - else thread.stop(); + thread.interrupt(); } else { if (log != null) { From ea1668b0cfe0b9a9ddb23412a9f188a617820384 Mon Sep 17 00:00:00 2001 From: De Cock Xavier Date: Wed, 30 Oct 2024 16:04:46 +0100 Subject: [PATCH 5/5] fix: error in exception message Fix copy paste, it's a while loop not a for loop --- .../main/java/lucee/transformer/bytecode/statement/While.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/lucee/transformer/bytecode/statement/While.java b/core/src/main/java/lucee/transformer/bytecode/statement/While.java index 9b7199126a..7012aafb21 100755 --- a/core/src/main/java/lucee/transformer/bytecode/statement/While.java +++ b/core/src/main/java/lucee/transformer/bytecode/statement/While.java @@ -75,7 +75,7 @@ public void _writeOut(BytecodeContext bc) throws TransformerException { adapter.ifZCmp(Opcodes.IFEQ, end); body.writeOut(bc); - InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, begin, "during for loop"); + InterruptHandlerInjector.writeLoopBodyEnd(adapter, loopCounter, begin, "during while loop"); adapter.visitLabel(end); }