package org.dynjs.runtime; import org.dynjs.Clock; import org.dynjs.Config; import org.dynjs.compiler.CompilationContext; import org.dynjs.compiler.JSCompiler; import org.dynjs.debugger.Debugger; import org.dynjs.debugger.js.DebuggerAPI; import org.dynjs.exception.ThrowException; import org.dynjs.ir.IRJSFunction; import org.dynjs.ir.JITCompiler; import org.dynjs.parser.Statement; import org.dynjs.parser.ast.FunctionDeclaration; import org.dynjs.parser.ast.VariableDeclaration; import org.dynjs.runtime.BlockManager.Entry; import org.dynjs.runtime.builtins.types.error.StackElement; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.TimeZone; public class ExecutionContext implements CompilationContext { public static ExecutionContext createGlobalExecutionContext(DynJS runtime) { // 10.4.1.1 LexicalEnvironment env = LexicalEnvironment.newGlobalEnvironment(runtime); return new ExecutionContext(runtime, null, env, env, runtime.getGlobalContext().getObject(), false); } public static ExecutionContext createDefaultGlobalExecutionContext(DynJS runtime) { // 10.4.1.1 LexicalEnvironment env = LexicalEnvironment.newGlobalEnvironment(runtime); ExecutionContext context = new ExecutionContext(runtime, null, env, env, runtime.getGlobalContext().getObject(), false); context.blockManager = new BlockManager(); return context; } public static ExecutionContext createGlobalExecutionContext(DynJS runtime, InitializationListener listener) { ExecutionContext context = ExecutionContext.createEvalExecutionContext(runtime); listener.initialize(context); return context; } public static ExecutionContext createEvalExecutionContext(DynJS runtime) { // 10.4.2 (no caller) return createGlobalExecutionContext(runtime); } private SourceProvider source; private DynJS runtime; private ExecutionContext parent; private LexicalEnvironment lexicalEnvironment; private LexicalEnvironment variableEnvironment; private Object thisBinding; private boolean strict; private boolean inEval; private int lineNumber; private int columnNumber; private String fileName; private String debugContext = "<eval>"; private VariableValues vars; // Just stash functions passed into this context with no processing. IRScope will detect things like 'attributes' and // generate those on a case-by-case basis. private Object[] functionParameters; private Object functionReference; private JSFunction function; private boolean isConstructor; private List<StackElement> throwStack; private BlockManager blockManager; private Debugger debugger; public ExecutionContext(DynJS runtime, ExecutionContext parent, LexicalEnvironment lexicalEnvironment, LexicalEnvironment variableEnvironment, Object thisBinding, boolean strict) { this.runtime = runtime; this.parent = parent; this.lexicalEnvironment = lexicalEnvironment; this.variableEnvironment = variableEnvironment; this.thisBinding = thisBinding; this.strict = strict; } public void setFunctionParameters(Object[] args) { this.functionParameters = args; } public Object[] getFunctionParameters() { return functionParameters; } public Object getFunctionParameter(int offset) { return getFunctionParameters()[offset]; } public JSFunction getFunction() { return this.function; } public boolean isConstructor() { return this.isConstructor; } public VariableValues getVars() { return vars; } public VariableValues allocVars(int size, VariableValues parent) { vars = new VariableValues(size, parent); return vars; } public Object getFunctionReference() { return this.functionReference; } public ExecutionContext getParent() { return this.parent; } public LexicalEnvironment getLexicalEnvironment() { return this.lexicalEnvironment; } public LexicalEnvironment getVariableEnvironment() { return this.variableEnvironment; } public Object getThisBinding() { return this.thisBinding; } public boolean isStrict() { return this.strict; } public boolean inEval() { return this.inEval; } public boolean isDebug() { return getDebugger() != null; } public void debug(Statement statement, Statement previousStatement) { Debugger d = getDebugger(); if (d != null) { try { d.debug(this, statement, previousStatement); } catch (InterruptedException e) { e.printStackTrace(); } } } public Clock getClock() { return this.runtime.getConfig().getClock(); } public TimeZone getTimeZone() { return this.runtime.getConfig().getTimeZone(); } public Locale getLocale() { return this.runtime.getConfig().getLocale(); } void setStrict(boolean strict) { this.strict = strict; } public Reference resolve(String name) { Reference result = this.lexicalEnvironment.getIdentifierReference(this, name, isStrict()); return result; } public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } public int getLineNumber() { return this.lineNumber; } public String getFileName() { return this.fileName; } // ---------------------------------------------------------------------- private void setupDebugger(Debugger debugger) { if (debugger == null) { return; } this.debugger = debugger; JSObject globalObject = getGlobalContext().getObject(); String debuggerName = getRuntime().getConfig().getExposeDebugAs(); Object currentDebugger = globalObject.get(this, debuggerName); if (!(currentDebugger instanceof DebuggerAPI)) { DebuggerAPI api = new DebuggerAPI(getGlobalContext(), debugger); globalObject.put(this, debuggerName, api, false); } } public Debugger getDebugger() { if (this.debugger != null) { return this.debugger; } if (this.parent != null) { return this.parent.getDebugger(); } return null; } public SourceProvider getSource() { if ( this.source != null ) { return this.source; } if ( this.parent != null ) { return this.parent.getSource(); } return null; } public Completion execute(JSProgram program) { return execute(program, null); } public Completion execute(JSProgram program, Debugger debugger) { this.source = program.getSource(); this.fileName = program.getFileName(); //System.err.println( this.fileName + " >> " + this.source ); BlockManager originalBlockManager = this.blockManager; try { setupDebugger(debugger); this.blockManager = program.getBlockManager(); ThreadContextManager.pushContext(this); setStrict(program.isStrict()); performDeclarationBindingInstantiation(program); try { return program.execute(this); } catch (ThrowException e) { throw e; } } finally { ThreadContextManager.popContext(); this.blockManager = originalBlockManager; } } public Object eval(JSProgram eval, boolean direct) { return eval(eval, direct, null); } public Object eval(JSProgram eval, boolean direct, Debugger debugger) { BlockManager originalBlockManager = this.blockManager; try { setupDebugger(debugger); ExecutionContext evalContext = createEvalExecutionContext(eval, direct); evalContext.blockManager = eval.getBlockManager(); ThreadContextManager.pushContext(evalContext); Completion result = eval.execute(evalContext); return result.value; } finally { ThreadContextManager.popContext(); this.blockManager = originalBlockManager; } } public Object call(JSFunction function, Object self, Object... args) { return call(null, function, self, args); } public Object call(Object functionReference, JSFunction function, Object self, Object... args) { return call( false, functionReference, function, self, args ); } public Object call(boolean isConstructor, Object functionReference, JSFunction function, Object self, Object... args) { // 13.2.1 ExecutionContext fnContext = null; try { fnContext = createFunctionExecutionContext(isConstructor, functionReference, function, self, args); ThreadContextManager.pushContext(fnContext); try { Object value = function.call(fnContext); if (value == null) { return Types.NULL; } return value; } catch (ThrowException e) { throw e; //} catch (Throwable e) { // throw new ThrowException(fnContext, e); } } catch (ThrowException t) { if (t.getCause() != null) { recordThrow(t.getCause(), fnContext); } else if (t.getValue() instanceof Throwable) { recordThrow((Throwable) t.getValue(), fnContext); } throw t; } catch (Throwable t) { recordThrow(t, fnContext); throw t; } finally { ThreadContextManager.popContext(); } } public Object construct(Reference reference, Object... args) { Object value = reference.getValue(this); if (value instanceof JSFunction) { return construct(reference, (JSFunction) value, args); } return null; } //public Object construct(JSFunction function, Object... args) { //return construct( null, function, args ); //} public Object construct(Object reference, JSFunction function, Object... args) { if (!function.isConstructor()) { throw new ThrowException(this, createTypeError("not a constructor")); } Object ctorName = function.get(this, "name"); if (ctorName == Types.UNDEFINED) { if (reference instanceof Reference) { ctorName = ((Reference) reference).getReferencedName(); } else { ctorName = function.getDebugContext(); } } // 13.2.2 // 1. create the new object JSObject obj = function.createNewObject(this); // 2. set internal methods per 8.12 [DynObject] // 3. set class name [DynObject subclass (defaults true)] // 4. Set Extensible [DynObject subclass (defaults true)] // 5. Get the function's prototype // 6. If prototype is an object make that the new object's prototype // 7. If prototype is not an object set to the standard builtin object prototype 15.2.4 [AbstractJavascriptFunction] // [AbstractJavascriptFunction] handles #7, subclasses may handle #6 if necessary (see BuiltinArray#createNewObject) Object p = function.get(this, "prototype"); if (p != Types.UNDEFINED && p instanceof JSObject) { obj.setPrototype((JSObject) p); } else { JSObject defaultObjectProto = getPrototypeFor("Object"); obj.setPrototype(defaultObjectProto); } // 8. Call the function with obj as self Object result = call(true, reference, function, obj, args); // 9. If result is a JSObject return it if (result instanceof JSObject) { obj = (JSObject) result; } // Otherwise return obj return obj; } protected void recordThrow(Throwable t, ExecutionContext fnContext) { if (this.throwStack == null) { this.throwStack = new ArrayList<StackElement>(); this.throwStack.add(fnContext.getStackElement()); } else { // handled lower } } public boolean isThrowInProgress() { return this.throwStack != null && !this.throwStack.isEmpty(); } public List<StackElement> getThrowStack() { return this.throwStack; } public void addThrowStack(List<StackElement> throwStack) { if (this.throwStack == null) { this.throwStack = new ArrayList<>(); } this.throwStack.addAll(throwStack); } // ---------------------------------------------------------------------- public ExecutionContext createEvalExecutionContext(JSProgram eval, boolean direct) { // 10.4.2 (with caller) //System.err.println( "CREATE EVAL EXEC CONTEXT" ); ExecutionContext context = null; Object evalThisBinding = null; LexicalEnvironment evalLexEnv = null; LexicalEnvironment evalVarEnv = null; if (!direct) { evalThisBinding = getGlobalContext().getObject(); evalLexEnv = LexicalEnvironment.newGlobalEnvironment(getGlobalContext().getObject()); evalVarEnv = LexicalEnvironment.newGlobalEnvironment(getGlobalContext().getObject()); } else { evalThisBinding = this.thisBinding; evalLexEnv = this.getLexicalEnvironment(); evalVarEnv = this.getVariableEnvironment(); } if (eval.isStrict()) { LexicalEnvironment strictVarEnv = LexicalEnvironment.newDeclarativeEnvironment(this.getLexicalEnvironment()); evalLexEnv = strictVarEnv; evalVarEnv = strictVarEnv; } context = new ExecutionContext(this.runtime, this, evalLexEnv, evalVarEnv, evalThisBinding, eval.isStrict()); context.source = eval.getSource(); context.fileName = eval.getFileName(); //System.err.println( context.fileName + " >> " + context.source ); context.performFunctionDeclarationBindings(eval, true); context.performVariableDeclarationBindings(eval, true); return context; } public ExecutionContext createFunctionExecutionContext(boolean isConstructor, Object functionReference, JSFunction function, Object thisArg, Object... arguments) { // 10.4.3 Object thisBinding = null; if (function.isStrict()) { thisBinding = thisArg; } else { if (thisArg == null || thisArg == Types.NULL || thisArg == Types.UNDEFINED) { thisBinding = getGlobalContext().getObject(); } else if (!(thisArg instanceof JSObject)) { // thisBinding = Types.toObject(this, thisArg); thisBinding = Types.toThisObject(this, thisArg); } else { thisBinding = thisArg; } } LexicalEnvironment scope = function.getScope(); LexicalEnvironment localEnv = LexicalEnvironment.newDeclarativeEnvironment(scope); ExecutionContext context = new ExecutionContext(this.runtime, this, localEnv, localEnv, thisBinding, function.isStrict()); context.isConstructor = isConstructor; context.source = function.getSource(); context.fileName = function.getFileName(); if (!(function instanceof IRJSFunction && !(function instanceof JITCompiler.CompiledFunction))) { context.performDeclarationBindingInstantiation(function, arguments); } // System.err.println( "debug null: " + ( function.getDebugContext() == null ? function : "not null") ); context.debugContext = function.getDebugContext(); context.functionReference = functionReference; context.source = function.getSource(); context.function = function; //System.err.println( "fnContext: " + context.debugContext + " // " + context.source ); context.setFunctionParameters(arguments); return context; } public Completion executeCatch(BasicBlock block, String identifier, Object thrown) { // 12.14 if (thrown instanceof Throwable && this.throwStack != null && !this.throwStack.isEmpty()) { StackTraceElement[] originalStack = ((Throwable) thrown).getStackTrace(); List<StackTraceElement> newStack = new ArrayList<>(); for (int i = 0; i < originalStack.length; ++i) { String cn = originalStack[i].getClassName(); if (cn.startsWith("org.dynjs") || cn.startsWith("java.lang.invoke")) { break; } newStack.add(originalStack[i]); } int throwLen = this.throwStack.size(); for (int i = throwLen - 1; i >= 0; --i) { newStack.add(throwStack.get(i).toStackTraceElement()); } ((Throwable) thrown).setStackTrace(newStack.toArray(new StackTraceElement[0])); } LexicalEnvironment oldEnv = this.lexicalEnvironment; LexicalEnvironment catchEnv = LexicalEnvironment.newDeclarativeEnvironment(oldEnv); catchEnv.getRecord().createMutableBinding(this, identifier, false); catchEnv.getRecord().setMutableBinding(this, identifier, thrown, false); try { this.lexicalEnvironment = catchEnv; return block.call(this); } catch (ThrowException e) { throw e; } catch (Throwable t) { throw new ThrowException(this, t); } finally { this.lexicalEnvironment = oldEnv; } } public Completion executeWith(JSObject withObj, BasicBlock block) { LexicalEnvironment oldEnv = this.lexicalEnvironment; LexicalEnvironment withEnv = LexicalEnvironment.newObjectEnvironment(withObj, true, oldEnv); try { this.lexicalEnvironment = withEnv; return block.call(this); } finally { this.lexicalEnvironment = oldEnv; } } private void performDeclarationBindingInstantiation(JSProgram program) { performFunctionDeclarationBindings(program, false); performVariableDeclarationBindings(program, false); } private void performDeclarationBindingInstantiation(JSFunction function, Object[] arguments) { // 10.5 (Functions) String[] names = function.getFormalParameters(); Object v = null; DeclarativeEnvironmentRecord env = (DeclarativeEnvironmentRecord) this.variableEnvironment.getRecord(); // * 4 for (int i = 0; i < names.length; ++i) { if ((i + 1) > arguments.length) { v = Types.UNDEFINED; } else { v = arguments[i]; } env.assignMutableBinding(this, names[i], v, false, function.isStrict()); } // * 5 performFunctionDeclarationBindings(function, false); // * 6 if (!env.hasBinding(this, "arguments")) { // * 7 Arguments argsObj = createArgumentsObject(function, arguments); if (function.isStrict()) { env.createImmutableBinding("arguments"); env.initializeImmutableBinding("arguments", argsObj); } else { env.createMutableBinding(this, "arguments", false); env.setMutableBinding(this, "arguments", argsObj, false); } } // * 8 performVariableDeclarationBindings(function, false); } private Arguments createArgumentsObject(final JSFunction function, final Object[] arguments) { // 10.6 Arguments obj = new Arguments(getGlobalContext()); obj.defineOwnProperty(this, "length", PropertyDescriptor.newDataPropertyDescriptor(arguments.length, true, true, false), false); String[] names = function.getFormalParameters(); JSObject map = new DynObject(getGlobalContext()); List<String> mappedNames = new ArrayList<>(); final LexicalEnvironment env = getVariableEnvironment(); for (int i = 0; i < arguments.length; ++i) { final Object val = arguments[i]; obj.defineOwnProperty(this, "" + i, PropertyDescriptor.newDataPropertyDescriptor(val, true, true, true), false); if (i < names.length) { if (!function.isStrict()) { final String name = names[i]; if (i < names.length) { if (!mappedNames.contains(name)) { mappedNames.add(name); PropertyDescriptor desc = new PropertyDescriptor(); desc.setSetter(new ArgSetter(getGlobalContext(), env, name)); desc.setGetter(new ArgGetter(getGlobalContext(), env, name)); desc.setConfigurable(true); map.defineOwnProperty(this, "" + i, desc, false); } } } } } if (!mappedNames.isEmpty()) { obj.setParameterMap(map); } if (function.isStrict()) { final JSFunction thrower = getGlobalContext().getThrowTypeError(); obj.defineOwnProperty(this, "caller", PropertyDescriptor.newAccessorPropertyDescriptor(thrower, thrower), false); obj.defineOwnProperty(this, "callee", PropertyDescriptor.newAccessorPropertyDescriptor(thrower, thrower), false); } else { obj.defineOwnProperty(this, "callee", PropertyDescriptor.newDataPropertyDescriptor(function, true, true, false), false); } return obj; } private void performFunctionDeclarationBindings(final JSCode code, final boolean configurableBindings) { // 10.5 Function Declaration Binding List<FunctionDeclaration> decls = code.getFunctionDeclarations(); EnvironmentRecord env = this.variableEnvironment.getRecord(); for (FunctionDeclaration each : decls) { String identifier = each.getIdentifier(); if (!env.hasBinding(this, identifier)) { env.createMutableBinding(this, identifier, configurableBindings); } else if (env.isGlobal()) { JSObject globalObject = ((ObjectEnvironmentRecord) env).getBindingObject(); PropertyDescriptor existingProp = (PropertyDescriptor) globalObject.getProperty(this, identifier, false); if (existingProp.isConfigurable()) { globalObject.defineOwnProperty(this, identifier, PropertyDescriptor.newDataPropertyDescriptor(Types.UNDEFINED, true, configurableBindings, true), true); } else if (existingProp.isAccessorDescriptor() || (!existingProp.isWritable() && !existingProp.isEnumerable())) { throw new ThrowException(this, createTypeError("unable to bind function '" + identifier + "'")); } } JSFunction function = getCompiler().compileFunction(this, identifier, each.getFormalParameters(), each.getBlock(), each.isStrict()); //System.err.println( identifier + " >> " + function.getFileName() + " >> " + getSource() ); function.setSource(code.getSource()); function.setDebugContext(identifier); env.setMutableBinding(this, identifier, function, code.isStrict()); } } private void performVariableDeclarationBindings(final JSCode code, final boolean configurableBindings) { List<VariableDeclaration> decls = code.getVariableDeclarations(); EnvironmentRecord env = this.variableEnvironment.getRecord(); for (VariableDeclaration decl : decls) { String identifier = decl.getIdentifier(); if (!env.hasBinding(this, identifier)) { env.createMutableBinding(this, identifier, configurableBindings); env.setMutableBinding(this, identifier, Types.UNDEFINED, code.isStrict()); } } } public Config getConfig() { return this.runtime.getConfig(); } public GlobalContext getGlobalContext() { return this.runtime.getGlobalContext(); } public JSCompiler getCompiler() { return this.runtime.getCompiler(); } public BlockManager getBlockManager() { if (this.blockManager != null) { return this.blockManager; } if (this.parent != null) { return this.parent.getBlockManager(); } return null; } public DynJS getRuntime() { return this.runtime; } public Reference createPropertyReference(Object base, String propertyName) { return new Reference(propertyName, base, isStrict()); } public Entry retrieveBlockEntry(int statementNumber) { return getBlockManager().retrieve(statementNumber); } public JSObject createTypeError(String message) { return createError("TypeError", message); } public JSObject createReferenceError(String message) { return createError("ReferenceError", message); } public JSObject createRangeError(String message) { return createError("RangeError", message); } public JSObject createSyntaxError(String message) { return createError("SyntaxError", message); } public JSObject createUriError(String message) { return createError("URIError", message); } public JSObject createError(String type, String message) { JSFunction func = getGlobalContext().getType(type); JSObject err = null; if (message == null) { err = (JSObject) construct((Object) null, func); } else { err = (JSObject) construct((Object) null, func, message); } err.put(this, "__native", true, false); return err; } public void collectStackElements(List<StackElement> elements) { elements.add(getStackElement()); if (parent != null) { parent.collectStackElements(elements); } } public StackElement getStackElement() { String locationContext = this.debugContext; if (locationContext.equals("<anonymous>")) { if (this.functionReference != null && this.functionReference instanceof Reference) { locationContext = ((Reference) this.functionReference).getReferencedName(); } } return new StackElement(locationContext, this); } public JSObject getPrototypeFor(String type) { return getGlobalContext().getPrototypeFor(type); } public String toString() { return "ExecutionContext: " + System.identityHashCode(this);// + "; parent=" + this.parent; } public DynamicClassLoader getClassLoader() { return getRuntime().getConfig().getClassLoader(); } public void setColumnNumber(int column) { this.columnNumber = column; } public int getColumnNumber() { return this.columnNumber; } public void inEval(boolean b) { this.inEval = b; } }