neilfraser / js-interpreter Goto Github PK
View Code? Open in Web Editor NEWA sandboxed JavaScript interpreter in JavaScript.
License: Apache License 2.0
A sandboxed JavaScript interpreter in JavaScript.
License: Apache License 2.0
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()
var a = [1,2,3];
a[100] = 'test';
alert(a);
Expected: "1,2,3,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,test"
Actual: "Uncaught TypeError: Cannot read property 'toString' of undefined"
If I set
a=[123,321].indexOf("321");
a should be -1 but Interpreter return 1
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?
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]
Error
s 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.
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
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
Hello @NeilFraser
I am the member of cdnjs project.
We want to host this library.
Please help us add git tag.
Git tag can help us to know your release version.
Thanks for your help!
I may work on this eventually.
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)
Being able to Object.create(foo) or Object.create(null) would be quite useful. Here is the spec:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
Supporting create's second argument isn't important for now.
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
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
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) ~[?:?]
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.)
https://neil.fraser.name/software/JS-Interpreter/
var a = 'a b c d e f'.split(' ');
a.splice(3, 1);
alert(a);
Expected: ["a", "b", "c", "e", "f"]
Actual: TypeError: Cannot read property 'toString' of undefined
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();
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.
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?
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.');
}
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?
Is it protected against memory flooding? Like is it possible to set a memory limit?
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.
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
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)
var x = 1;
var x;
After running this code, x
should be 1
, but instead it is currently undefined
.
I'll plan to submit a PR with a fix.
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?
Expected behaviour:
typeof foo should return "undefined"
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.
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.
new Interpreter('')
it strips locations from 860 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!
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?
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?.
On: https://neil.fraser.name/software/JS-Interpreter/
alert('abc'.replace('b', ''));
Throws: "Uncaught TypeError: undefined is not a function"
Acorn is supporting ECMAScript 6. Is there any plan?
Thanks.
Seems only has 3 types( number,boolean or string ) can be transferred. If I call:
foo([1,2],{});
arg1 and arg2 will be
{
//...
type:"object"
}
And I can't get the values "1,2" inside.
I have the need to execute some user created code, which is valid inside the sandbox, uses existing sandbox objects and functions, but from within a native function, exported into the sandbox as well.
I have gone through docs and code but can not figure out how to do it, would appreciate any suggestion regarding this.
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){}
.
Should see the modal alert in the browser.
Error is uncaught by the try{}catch(e){}
block within JS-Interpreter; is raised in the host javascript environment instead.
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
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?
alert('hi'.toString());
Expected: "hi"
Actual: "NaN"
alert([1,2,3].toString());
Expected: "1,2,3"
Actual: "0"
In Chrome, the following:
for (var i = 0; ; i++) alert(1);
Behaves the same as:
for (var i = 0; true; i++) alert(1);
However on https://neil.fraser.name/software/JS-Interpreter/ it only alerts 1 time.
Hi Neil, I found another one.
To reproduce, enter the following code in https://neil.fraser.name/software/JS-Interpreter/index.html :
try {
Object.prototype.hasOwnProperty.call(null, 'foo');
alert('done');
} catch (e) {
alert('error');
}
Should result in an alert, actually results in an error in the javascript console.
Sorry about this, whilst debugging an issue I came across this and thought you'd want to know
To reproduce:
try {
throw new Error("Foo");
} catch(e) {
throw e;
}
Looks like you just need a state &&
in that if
clause?
Also } while (state || state.node.type == 'Program');
looks wrong - perhaps you meant &&
?
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?
Example:
var fun = function(){
1+1;
};
var result = fun(); // result will be set to "fun"
result = fun(1); // result will be 1
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?
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');
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.