1

I have been struggling with an issue using the java scripting API to control the execution of some user defined javascript. I am using the builtin Rhino engine under the hood, which says you can set the InstructionObserverThreshold and it will take care of stopping execution if the limit is reached. I have been playing with the following sample app for a while now and I am stumped as to why it won't work. You will see that I have set the MaximumInterpreterStackDepth, as well. This works perfectly, but the instruction observer doesn't appear to do anything.

Any ideas on what is missing with this code to make it work?

Thanks!

import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

import com.sun.script.javascript.RhinoScriptEngine;


public class RhinoTester2 {

    public static void main(String[] args) {

    new RhinoScriptEngine(); // initialize the global context.

     sun.org.mozilla.javascript.internal.ContextFactory cx =  sun.org.mozilla.javascript.internal.ContextFactory.getGlobal();
     cx.addListener( new sun.org.mozilla.javascript.internal.ContextFactory.Listener() {
         public void contextCreated( sun.org.mozilla.javascript.internal.Context cxt ) {
                cxt.setInstructionObserverThreshold(10000 ); // This should stop after 10 seconds or so.
                cxt.setMaximumInterpreterStackDepth(1);  // this should not be a factor for the moment
                System.out.println("Context Factory threshold set. "+cxt.getInstructionObserverThreshold());
          }

        @Override
        public void contextReleased(
            sun.org.mozilla.javascript.internal.Context arg0) {
            System.out.println("Context Released.");
        }

     });

       // Now run the script to see if it will be stopped after a short time.
        ScriptEngineManager mgr = new ScriptEngineManager();
        ScriptEngine engine = mgr.getEngineByName("javascript"); 
        try {
            // engine.eval("while(true){println(\"hello\");};"); // This will fail immediately due to the interpreter stack depth.
            engine.eval("while(true){};");  // this never fails.  runs happily forever.
        } catch (ScriptException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

}
Todd Patch
  • 348
  • 1
  • 4
  • 15
  • Unfortunately, it appears that the recommended way to handle the InstructionObservationCount is not provided in the base ContextFactory used by the scripting api. In looking at RhinoScriptEngine, the user is locked out of modifying the ContextFactory to add the implementation (sealed class and anonymous static block calling initGlobal) . From what I can tell you only have two options - either create your own script engine and replace the built in version or manage it in a thread and kill the thread when it exceeds the timeout. Disappointing..... – Todd Patch Jan 29 '14 at 16:12

1 Answers1

0

You can use your own overwritten version of ContextFactory to execute context actions. There in method makeContext() you can create a context from scratch and set the instruction count you want to have, along with other settings.

An example from my implementation:

public class MyContextFactory extends ContextFactory {
    protected Context makeContext() {
        Context cx = new MyContext(this);
        cx.setOptimizationLevel(0);
        cx.setLanguageVersion(Context.VERSION_1_8);
        cx.setInstructionObserverThreshold(100000);
    return cx;
    }
}

As you can see I also create my own Context subclass because the Context constructor which takes a factory as parameter is protected:

public class RhinoContext extends Context {

    public RhinoContext(ContextFactory factory) {
        super(factory);
    }

}

Then you should be able to inject an instance of your own context factory via method ContextFactory.initGlobal(factory) before invoking any scripts.

Note however that the instruction observer in my tests only works when optimization level is <= 0. In fully optimized scripts - where everything is compiled to bytecode - this does not work. I'm using this myself only in development, not on productive systems.

  • I tried this at one point. The problem is you can't call ContextFactory.initGlobal yourself. It will break the RhinoScriptEngine class which tries to call ContextFactory.initGlobal. It can only be called once and will throw an exception on any attempt afterwards. – Todd Patch Feb 04 '14 at 19:47
  • Ok, I don't use RhinoScriptEngine, that might be why this works for me. I'm still using the "old" way using the Context object directly described [here](https://developer.mozilla.org/en-US/docs/Rhino/Embedding_tutorial). – Oliver Weise Mar 12 '14 at 16:26