Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pausing script executions from Java through Context.observeInstructionCount & .captureContinuation #1475

Open
RaycusMX opened this issue May 14, 2024 · 3 comments

Comments

@RaycusMX
Copy link

RaycusMX commented May 14, 2024

Rhino version: 1.7.15
Java version: 21
I'm trying to imeplement a feature that allows Java to pause script executions while observing based on the example in the docs of ContextFactory. There will be untrusted scripts in my case so I need to pause them from Java side.

public class MyFactory extends ContextFactory {

	@Override
	protected void observeInstructionCount(Context cx, int instructionCount) {
		System.out.println("observeInstructionCount");
		cx = Context.enter();
		try {
			throw cx.captureContinuation();
		} finally {
			Context.exit();
		}
	}

	@Override
	protected Context makeContext() {
		Context cx = super.makeContext();
		cx.setInstructionObserverThreshold(10000);
		cx.setOptimizationLevel(-1);
		cx.setLanguageVersion(Context.VERSION_ES6);
		return cx;
	}

}
public class RhinoTest {

	public static void main(String[] args) {
		ContextFactory.initGlobal(new MyFactory());
		Context cx = Context.enter();
		try {
			Script script = cx.compileString("while (true) {}", null, 1, null);
			ScriptableObject scope = cx.initSafeStandardObjects();
			try {
				cx.executeScriptWithContinuations(script, scope);
			} catch (ContinuationPending pending) {
				cx.resumeContinuation(pending.getContinuation(), scope, null);
			}
		} finally {
			Context.exit();
		}
	}

}

The codes above throw exceptions however.

observeInstructionCount
java.lang.IllegalStateException: FAILED ASSERTION
	at org.mozilla.javascript.Kit.codeBug(Kit.java:356)
	at org.mozilla.javascript.Interpreter.captureContinuation(Interpreter.java:3468)
	at org.mozilla.javascript.Interpreter.captureContinuation(Interpreter.java:3445)
	at org.mozilla.javascript.Context.captureContinuation(Context.java:1270)
	at MyFactory.observeInstructionCount(MyFactory.java:11)
	at org.mozilla.javascript.Context.observeInstructionCount(Context.java:2298)
	at org.mozilla.javascript.Interpreter.addInstructionCount(Interpreter.java:3668)
	at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:2445)
	at org.mozilla.javascript.Interpreter.interpret(Interpreter.java:1078)
	at org.mozilla.javascript.InterpretedFunction.call(InterpretedFunction.java:87)
	at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:383)
	at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3940)
	at org.mozilla.javascript.Context.callFunctionWithContinuations(Context.java:1255)
	at org.mozilla.javascript.Context.executeScriptWithContinuations(Context.java:1223)
	at RhinoTest.main(RhinoTest.java:16)
java.lang.IllegalStateException: FAILED ASSERTION
	at org.mozilla.javascript.Kit.codeBug(Kit.java:356)
	at org.mozilla.javascript.Interpreter.setCallResult(Interpreter.java:3436)
	at org.mozilla.javascript.Interpreter.processThrowable(Interpreter.java:3200)
	at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:1196)
	at org.mozilla.javascript.Interpreter.restartContinuation(Interpreter.java:1132)
	at org.mozilla.javascript.NativeContinuation.call(NativeContinuation.java:43)
	at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:383)
	at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3940)
	at org.mozilla.javascript.Interpreter.restartContinuation(Interpreter.java:1113)
	at org.mozilla.javascript.Context.resumeContinuation(Context.java:1289)
	at RhinoTest.main(RhinoTest.java:18)
java.lang.IllegalStateException: FAILED ASSERTION
	at org.mozilla.javascript.Kit.codeBug(Kit.java:356)
	at org.mozilla.javascript.Interpreter.setCallResult(Interpreter.java:3436)
	at org.mozilla.javascript.Interpreter.processThrowable(Interpreter.java:3200)
	at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:1196)
	at org.mozilla.javascript.Interpreter.restartContinuation(Interpreter.java:1132)
	at org.mozilla.javascript.NativeContinuation.call(NativeContinuation.java:43)
	at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:383)
	at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3940)
	at org.mozilla.javascript.Interpreter.restartContinuation(Interpreter.java:1113)
	at org.mozilla.javascript.Context.resumeContinuation(Context.java:1289)
	at RhinoTest.main(RhinoTest.java:18)
Exception in thread "main" java.lang.IllegalStateException
	at org.mozilla.javascript.Interpreter.interpretLoop(Interpreter.java:2479)
	at org.mozilla.javascript.Interpreter.restartContinuation(Interpreter.java:1132)
	at org.mozilla.javascript.NativeContinuation.call(NativeContinuation.java:43)
	at org.mozilla.javascript.ContextFactory.doTopCall(ContextFactory.java:383)
	at org.mozilla.javascript.ScriptRuntime.doTopCall(ScriptRuntime.java:3940)
	at org.mozilla.javascript.Interpreter.restartContinuation(Interpreter.java:1113)
	at org.mozilla.javascript.Context.resumeContinuation(Context.java:1289)
	at RhinoTest.main(RhinoTest.java:18)
observeInstructionCount

Is this not supported?

@p-bakker
Copy link
Collaborator

Which ContextFactory docs are you taking your inspiration from?

As to the exception you're getting: based on where in the code the exception in thrown (Rhino expected there to be a new keyword) and this 'documentation' of the java API for continuations I'm assuming that Rhino expects the capturing of a continuation to occur directly in Javascript (using the new keyword) or to be called from a java method that is being called from JavaScript (the pause method in the example given in the release notes)

That would make your usecase (currently) not supported, but maybe you can figure out a way to support your usecase and provide a PR

@RaycusMX
Copy link
Author

@p-bakker

Which ContextFactory docs are you taking your inspiration from?

I meant the javadoc of org.mozilla.javascript.ContextFactory. sry about that.
https://javadoc.io/static/org.mozilla/rhino/1.7.15/org/mozilla/javascript/ContextFactory.html

As to the exception you're getting: based on where in the code the exception in thrown (Rhino expected there to be a new keyword) and this 'documentation' of the java API for continuations I'm assuming that Rhino expects the capturing of a continuation to occur directly in Javascript (using the new keyword) or to be called from a java method that is being called from JavaScript (the pause method in the example given in the release notes)

That would make your usecase (currently) not supported, but maybe you can figure out a way to support your usecase and provide a PR

I'm pretty new to rhino and still learning how it works. I'll be glad to provide a PR if I figure the solution out someday.

@p-bakker p-bakker changed the title Try pausing script executions from Java Pausing script executions from Java through Context.observeInstructionCount &.capturContinuation Jun 23, 2024
@p-bakker p-bakker changed the title Pausing script executions from Java through Context.observeInstructionCount &.capturContinuation Pausing script executions from Java through Context.observeInstructionCount & .captureContinuation Jun 23, 2024
@szegedi
Copy link
Contributor

szegedi commented Jun 30, 2024

Unfortunately, you won't be able to use continuations for this purpose. Continuations must be captured either at a point in code that is a function call, or a constructor (so, either call to a Java method that does cx.captureContinuation() or call the Continuation constructor either as a function or as a constructor.)

Additionally, when you resume a continuation you need to pass it a return value. Somewhat counterintuitively, the way continuations are resumed is not that the execution continues at the point where the continuation is captured. Rather, the function in which the continuation was captured immediately returns with the given return value.

Instruction count observer can interrupt script execution anywhere, and continuations must be captured at function/constructor calls, hence your bug when you attempted to capture or resume them from an arbitrary point. I'm not saying this is impossible to solve, but it would be pretty hard and I'm not sure we'd want to support the additional complexity.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants