GithubHelp home page GithubHelp logo

neilfraser / js-interpreter Goto Github PK

View Code? Open in Web Editor NEW
1.9K 50.0 347.0 2.28 MB

A sandboxed JavaScript interpreter in JavaScript.

License: Apache License 2.0

JavaScript 93.25% HTML 6.47% Shell 0.28%
javascript js-interpreter sandbox

js-interpreter's Introduction

js-interpreter's People

Contributors

aminmarashi avatar brownbear2 avatar bvibber avatar carloslfu avatar cpirich avatar ecampver avatar ijc8 avatar joshlory avatar jsearles avatar kirish-google avatar llorx avatar neilfraser avatar pawsong avatar pinnisi avatar ramonlamana avatar soegaard avatar wastl-junior avatar youpy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

js-interpreter's Issues

Parsing JSON Object.

Hi I am using this with Blockly, great work. I am having trouble parsing JSON objects and to a lesser extent []s. If I return a JSON String from an external call It comes through OK but calling JSON.parse throws an error stating unknown identify JSON. Is there a way around this?

I had a similar problem with arrays and ended up sending a coma separated string and then splitting it in Blockly, not optimal but it worked :)

Cheers,

David

Switch statement buggy

The switch statement implementation doesn't correctly yield the expected output.

For example, this code from MDN, alerts 'This animal will not.' rather than 'This animal will go on Noah\'s Ark.'

var Animal = 'Giraffe';
switch (Animal) {
  case 'Cow':
  case 'Giraffe':
  case 'Dog':
  case 'Pig':
    alert('This animal will go on Noah\'s Ark.');
    break;
  case 'Dinosaur':
  default:
    alert('This animal will not.');
}

Thrown TypeError 'undefined is not a function' not caught by try/catch

Running this in https://neil.fraser.name/software/JS-Interpreter/index.html demonstrates the issue:

try {
  var foo = {};
  var bar = foo.nx.map(function(baz) {return baz['qux'];})
} catch (e) {
  alert("CAUGHT");
}

Because foo.nx is undefined the var bar line throws TypeError: undefined is not a function which should be caught by the try{}catch(e){}.

Expected

Should see the modal alert in the browser.

Actual

Error is uncaught by the try{}catch(e){} block within JS-Interpreter; is raised in the host javascript environment instead.

Unable to call new functions added with appendCode

I can call existing functions from code added to an interpreter with appendCode, but if I add a new function I'm unable to call it.

For example, the following code throws "ReferenceError: test is not defined":

var int = new Interpreter("");
int.appendCode(  "function test(){}; test();" );
int.run();

This code works fine:

var int = new Interpreter( "function test(){};" );
int.appendCode( "test();" );
int.run();

Does this work in Node.js?

I wrapped the interpreter code:

var acorn = require('acorn');
(function(window){

...

})(typeof window == "undefined" ? global : window);

But it doesn't seem to be working inside Node.js. Am I missing something?

Asynchronous result

I'm looking to add support for making asynchronous results appear synchronous (specifically, allowing my code to return a Future to the interpreter and having the interpreter wait until the Future is resolved before returning a result to the program being interpreted). Would you have a preferred API for this? Would you prefer not using Futures?

Passing functions into the interpreter.

Hi,
I'm trying to infuse a function defined outside the interpreter to work inside. Not like a native function, but actually copy the function body inside. I'm using this code so far (excuse the coffeescript):

  Interpreter.prototype.transferFunction = (scope, name, fn) ->
    ast = acorn.parse "$ = " + fn.toString()

    func = @createObject @FUNCTION
    func.node = ast.body[0].expression.right

    @setProperty func, 'length', @createPrimitive(1), true
    @setProperty scope, name, func

  ip.transferFunction scope, "test",  ->
    return 5 + 5

This works, but when I change data after calling test() in the sandbox, the data I recieve via getProperty() doesn't include those changes. I guess I need to create a new scope for the function and fix the parameter length thingy, but I don't have any idea yet how exactly, so if you could point me in the right direction I would appreciate it. Thanks

`for ... in ...` reveals array method names too

Using the interpreter demo and entering:

var result = [];
var foo = [
  {a: 1},
  {b: 2},
  {c: 3}
];
for (var i in foo) {
  result.push(i);
}
alert(result.join(', '));

Gives the result:

0, 1, 2, every, filter, forEach, map, reduce, reduceRight, some, sort, toLocaleString

Expected result (e.g. what Chrome gives) is

0, 1, 2

(I recognise that for ... in ... shouldn't be used for looping over arrays, and that this can be solved by wrapping with foo.hasOwnProperty(i) but since it differs from a modern JS engine I thought you might be interested to know.)

Accessing data outside the sandbox

Hi Neil,

I'd like to be able to pass an object from outside the sandbox inside, but without creating a copy it. Rather I'd like to directly access the outside one. How would you go about doing that?
I'm thinking about creating a new kind of datatype for this in the interpreter. My goal is to be able to change an object inside the sandbox and have it be changed outside as well.

Thanks,
BrownBear2

Literal {} not supported?

Maybe I'm using your API wrong, but to me it seems like the literal {} (empty object) isn't supported.

var interpreter = new Interpreter('{}')
interpreter.run()
console.log(interpreter.value) // Logs undefined.

Should it really work the way it does now?

Proposal: conveniance functions

Hi, I want to propose to add the following functions to the interpreter. They allow users to use the interpreter more easily. Excuse the coffee-script, you can use js2.coffee to convert them to JS if necessary.

# convert a value to pseudo values (for use inside the sandbox)
Interpreter::convertToPseudo = (value) ->
  if typeof value == "function"
    ast = acorn.parse "$ = " + value.toString()

    func = @createObject @FUNCTION
    func.node = ast.body[0].expression.right
    func.parentScope = @scope

    @setProperty func, 'length', @createPrimitive(func.node.params.length), true
    return func

  if typeof value != "object" or value == null
    return @createPrimitive value

  if value instanceof Array
    pseudoArray = @createObject @ARRAY
    for item, i in value
      @setProperty pseudoArray, i, @convertToPseudo item

    return pseudoArray

  pseudoObject = @createObject @OBJECT
  for key, val of value
    @setProperty pseudoObject, key, @convertToPseudo val

  return pseudoObject

# convert pseudo objects from the sandbox into real objects
Interpreter::convertToNative = (value) ->
  return value.data if value.isPrimitive

  if value.length? # array
    newArray = []
    for i in [0...value.length]
      newArray.push @convertToNative value.properties[i]

    return newArray

  if value.type == "function"
    return value

  newObject = {}
  for key, val of value.properties
    newObject[key] = @convertToNative val

  return newObject

# convert a list of arguments from pseudo to native (see convertToNative)
Interpreter::convertArgsToNative = (args...) ->
  nativeArgs = []
  for arg in args
    nativeArgs.push @convertToNative arg

  return nativeArgs

# fully wrap a native function to be used inside the interpreter
# parent: scope of the function to be added to
# name: name of the function in said scope
# fn: the native function
# thisObj: the `this` object the function should be called by
Interpreter::wrapNativeFn = (parent, name, fn, thisObj) ->
  thisIP = @
  @setProperty parent, name, @createNativeFunction (args...) ->
    thisObj ?= @ if [email protected] # don't convert window
    thisIP.convertToPseudo fn.apply thisObj, thisIP.convertArgsToNative args...
  return

# fully wrap an asynchronous native function, see wrapNativeFn
Interpreter::wrapNativeAsyncFn = (parent, name, fn, thisObj) ->
  thisIP = @
  @setProperty parent, name, @createAsyncFunction (args..., callback) ->
    thisObj ?= @ if [email protected] # don't convert window
    nativeArgs = thisIP.convertArgsToNative args...
    nativeArgs.unshift (result) -> callback thisIP.convertToPseudo(result), true
    fn.apply thisObj, nativeArgs
  return

# wrap a whole class, see wrapNativeFn (doesn't work with async functions)
# scope: the scope for the class to be added to
# name: name of the class in said scope
# $class: the native class instance
# fns: optional, list of names of functions to be wrapped
Interpreter::wrapClass = (scope, name, $class, fns) ->
  obj = @createObject @OBJECT
  @setProperty scope, name, obj

  if !fns?
    fns = []
    for key, fn of $class
      fns.push key if typeof fn == "function"

  for fn in fns
    @wrapNativeFn obj, fn, $class[fn], $class

  return

# transfer object from the sandbox to the outside by name
Interpreter::retrieveObject = (scope, name) ->
  return @convertToNative @getProperty scope, name

# transfer object from the outside into the sandbox by name
Interpreter::transferObject = (scope, name, obj) ->
  @setProperty scope, name, @convertToPseudo obj
  return

Array.prototype.XXX

I see in the commit history that Array.prototype.forEach was removed. Also, filter, map, and reduce were never implemented. Is this a design decision? Are PR accepted if someone adds them? Thanks.

Variable hoisting assigns state.value to undefined instead of this.UNDEFINED

When testing the code below:
if(!a){
var a = 2;
}
I get the following errors when the interpreter tries to interpret the "if(!a){" line.
TypeError: Cannot call method 'toNumber' of undefined at Interpreter.stepUnaryExpression (Line 1961)
TypeError: Cannot call method 'toBoolean' of undefined at Interpreter.stepConditionalExpression (Line 1614)

It seems like this is occurring because whatever mechanism is used for hoisting simply assigns state.value to undefined rather than this.UNDEFINED. I'm not sure where this is happening though.

acorn_interpreter.js acorn

Using the require system in BlocklyCraft/ScriptCraft, which runs in the JVM via Nashorn (OpenJDK 1.8), I can place acorn.js and interpreter.js in src/main/js/modules/, add the line var acorn = require('acorn'); to L25 of interpreter.js, and everything works like a charm. I can require('interpreter'); elsewhere and things work well.

However, if I attempt to place acorn_interpreter.js in the modules directory and then attempt to require that, I get an acorn is not defined error (as per the stacktrace below) when I create a new interpreter object. The error does not replicate in browsers, where acorn_interpreter.js works fine. So I'm curious as to what could cause this! Any ideas?

This issue is not at all important, so feel free to ignore it unless you're also curious.

[15:41:52 WARN]: [scriptcraft] Task #6 for scriptcraft v3.2.0-2016-06-28 generated an exception
jdk.nashorn.internal.runtime.ECMAException: ReferenceError: "acorn" is not defined
    at jdk.nashorn.internal.runtime.ECMAErrors.error(ECMAErrors.java:57) ~[nashorn.jar:?]
    at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:319) ~[nashorn.jar:?]
    at jdk.nashorn.internal.runtime.ECMAErrors.referenceError(ECMAErrors.java:291) ~[nashorn.jar:?]
    at jdk.nashorn.internal.objects.Global.__noSuchProperty__(Global.java:1428) ~[nashorn.jar:?]
    at jdk.nashorn.internal.scripts.Script$Recompilation$399$29046AA$\^eval\_.L:1$Interpreter(<eval>:41) ~[?:?]
    at jdk.nashorn.internal.scripts.Script$Recompilation$398$742A$\^eval\_.L:1$get_interpreter(<eval>:25) ~[?:?]
    at jdk.nashorn.internal.scripts.Script$Recompilation$386$573A$\^eval\_.L:1$run_scripts(<eval>:20) ~[?:?]
    at jdk.nashorn.internal.scripts.Script$Recompilation$385$409A$\^eval\_.L:1$L:13(<eval>:15) ~[?:?]
    at jdk.nashorn.internal.scripts.Script$Recompilation$384$3676A$\^eval\_.L:1$fileWatcher(<eval>:152) ~[?:?]
    at jdk.nashorn.internal.scripts.Script$Recompilation$200$4801$\^eval\_.L:1$monitorDirAndFiles(<eval>:184) ~[?:?]
    at jdk.nashorn.javaadapters.java.lang.Runnable.run(Unknown Source) ~[?:?]

Proposal: add method to call sandboxed functions at current state of interpreter

This function will take a sandbox typed function and execute it in a child interpreter. When it's done it will run a given callback function.

The function will create a scope for the pseudo-function, apply all arguments, remove the return statement at the end and execute the function in a new interpreter. Once the interpreter is done it will call back. Note: this needs the callback and convenience patches I submitted earlier.

# call a sandbox function at the current state of the interpreter
# fn: sandbox type function
# args: any native arguments to the function
# done: callback to be run when the function call is done
Interpreter::call = (fn, args..., done) ->
  scope = @createScope fn.node.body, fn.parentScope
  for p, i in fn.node.params
    @setProperty scope, @createPrimitive(p.name), @convertToPseudo(args[i])

  argsList = @createObject @ARRAY
  for arg, i in args
    @setProperty argsList, @createPrimitive(i), @convertToPseudo(arg)

  @setProperty scope, "arguments", argsList

  # remove returns from callbacks
  [..., last] = fn.node.body.body
  if last.type == "ReturnStatement"
    last.type = "ExpressionStatement"
    last.expression = last.argument
    delete last.argument

  funcState =
    node: fn.node.body
    scope: scope
    thisExpression: @stateStack[0].funcThis_

  ip = new Interpreter ""
  ip.stateStack.unshift funcState
  ip.run done
  return

Reduce not working correctly

The reduce implementation doesn't yield the expected output.

E.g. in the interpreter demo I tried this without any output:

var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

var sum = numbers.reduce(function(prev, curr) { return prev + curr; }, 0);

alert(sum);

This example from MDN also doesn't work:

var sum = [0, 1, 2, 3].reduce(function(a, b) {
  return a + b;
}, 0);

alert(sum)

Bug of "indexOf"?

If I set
a=[123,321].indexOf("321");
a should be -1 but Interpreter return 1

return "undefined" if I try to get array.length

var myCode = 'log(arr.length);';
var initFunc = function(interpreter, scope) {
  interpreter.setProperty(scope, 'arr',
      interpreter.createPrimitive([1,2]));

  var wrapper = function(text) {
    text = text ? text.toString() : '';
    return interpreter.createPrimitive(console.log(text));
  };
  interpreter.setProperty(scope, 'log',
      interpreter.createNativeFunction(wrapper));
};
new Interpreter(myCode, initFunc).run()

Detect and avoid infinite loops

In the live demo code, if we chang

for (var i = 0; i < n; i++)

to

for (var i = 0; ; i++)

, an error will happen.

Uncaught TypeError: Cannot call method 'toBoolean' of undefined. (interpreter.js:1761)

Function.prototype.apply/call don't return undefined

The call and apply functions don't return undefined when they are used on a function with no return statement. They instead return the last parameter value given to them.

Example 1:
var fun = function(){
1+1;
};
var result = fun.call(this, 1,2,3); //result will be 3

Example 2:
var fun = function(){
1+1;
};
var result = fun.apply(this, [1,2,3]); // result will be [1,2,3]

How to set a global variable?

I have multi interpreter instances,

var code1='var x=1;';
var code2='var y=2;';
var interpreterA=new Interpreter(code1);
var interpreterB=new Interpreter(code2);

I wanna get x from interpreterB but they are in different sandbox,so I wanna set "global variable" for all the interpreters.

Support for serializing current state

Do you think it would be possible to halt execution halfway through a program, and serialize the entire state of the program, so it may be resumed at a later date? If so would you mind letting me know some of the pitfalls I would encounter so I may expedite implementing this feature?

Interpreter is stepping through polyfill code

As of 08f81e7 the step() method is stopping inside polyfilled code like Array.prototype.sort.

The root cause seems to be that node locations are not getting stripped properly. The interpreter checks for a node .end location to decide whether the node is in user code, but I'm seeing nodes with end locations well outside of user code (probably in polyfill code). I did some quick instrumentation and found that the call to stripLocations_ from the polyfill code is missing a lot of nodes.

  • At c08300d running new Interpreter('') it strips locations from 860 nodes.
  • At 08f81e7 it only strips locations from 101 nodes.

I don't totally understand the root cause or have a fix yet, but if I come up with one I'll submit it here. Thanks!

"makePredicate" without "new Function"?

I want to include acorn in a Firefox extension, and for signing purposes I want to avoif the constructs "eval" and "new Function". The only place I see it used in acorn is at https://github.com/NeilFraser/JS-Interpreter/blob/master/acorn.js#L354, and the comment above that function indicate that it is perhaps only done for speed reasons. I'm all for speed, would it be possible for me to re-implement makePredicate using something like a split + a loop, or even a regex?

I'm confused about 'child.parent.prototype'


Interpreter.prototype.isa = function(child, parent) {
  if (!child || !parent) {
    return false;
  } else if (child.parent == parent) {
    return true;
  } else if (!child.parent || !child.parent.prototype) {
    return false;
  }
  return this.isa(child.parent.prototype, parent);
};

'child.parent' is a constructor object which has prototype property in 'properties'.
Maybe you should use 'child.parent.properties.prototype' here?

SetTimeout inside an AsyncFunction

I'm trying to implement the AsyncFunction in the latest version. However, I can't seem to get a simple example working. Does this work with SetTimeout?

'next' gets called but execution doesn't seem to continue.

//For some reason this does not work.
    var wrapper = function(d, next) {
        window.setTimeout(function() {
            next();
        }, d);
    };
    interpreter.setProperty(scope, 'wait',
        interpreter.createAsyncFunction(wrapper));

Code passed to the interpreter.

wait(2000);
alert('Continued');

Debugger type actions

Is there any way to "Step over" a line rather than go through all of the parse steps involved in that line?

In the Blockly Demo: Js Interpreter it looks like one methods is to inject hilightBlock(%1) before every statement. Is this the best way to implement line by line stepping?

A couple of helper functions proposal

I was implementing custom blocks in my Blockly project, and quickly filled my init function with boilerplate code consisting from this (for each function):

      var wrapper = function(text) {
        text = text ? text.toString() : '';
        return interpreter.createPrimitive(alert(text));
      };
      interpreter.setProperty(scope, 'alert',
          interpreter.createNativeFunction(wrapper));

So, I created a couple of functions to make this binding easier:

    function extractJavaScriptValue(valueObject) {
      if (valueObject.isPrimitive) {
        return valueObject.data;
      }
      if ("length" in valueObject) {
        // This probably is a list
        var output = [];
        for (var i = 0; i < valueObject.length; i++)
        {
          output.push(extractJavaScriptValue(valueObject.properties[i]));
        }
        return output;
      } 
    }
    function bindJavaScriptFn(interpreter, scope, fnName, fn) {
      var wrapper = function(arg) {
        var argValues = [];
        for (var i = 0; i < arguments.length; i++) {
          argValues[i] = extractJavaScriptValue(arguments[i]);
        }
        return interpreter.createPrimitive(fn.apply(window, argValues));
      };
      interpreter.setProperty(scope, fnName,
          interpreter.createNativeFunction(wrapper));
    }

Now, the function binding is simple one-liner:

      bindJavaScriptFn(interpreter, scope, 'alert', alert);

I have not studied the JS-Interpreter internals too deeply, maybe this approach has issues? Or maybe I have created something that already exists but is not particularly documented?.

Interpreter does not support multiple assignment

An expressions such as
var a = b = c = 10;

does not work and throws an error 'b' is undefined

and assignment like this should be evaluated as
var a = (b = (c = 10));

instead the interpreter tries to evaluate b as a variable and get its value

Running Headless

I'm attempting to use the JS-Interpreter in an environment that doesn't have a window variable. For now I've just added a shim var window = {} which allows the code to run without error. Is there a better way to handle this?

Throw Error instead of String

Errors contain much more useful information, like the stack trace at the point of creating the Error. It's much harder to track these errors down when just strings are thrown.

I'd be happy to submit a PR for this.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.