using System; using System.Collections.Generic; using System.Linq; using Jint.Native; using Jint.Parser.Ast; using Jint.Runtime.Descriptors; using Jint.Runtime.Environments; using Jint.Runtime.References; namespace Jint.Runtime { public class StatementInterpreter { private readonly Engine _engine; public StatementInterpreter(Engine engine) { _engine = engine; } private Completion ExecuteStatement(Statement statement) { return _engine.ExecuteStatement(statement); } public Completion ExecuteEmptyStatement(EmptyStatement emptyStatement) { return new Completion(Completion.Normal, null, null); } public Completion ExecuteExpressionStatement(ExpressionStatement expressionStatement) { var exprRef = _engine.EvaluateExpression(expressionStatement.Expression); return new Completion(Completion.Normal, _engine.GetValue(exprRef), null); } public Completion ExecuteIfStatement(IfStatement ifStatement) { var exprRef = _engine.EvaluateExpression(ifStatement.Test); Completion result; if (TypeConverter.ToBoolean(_engine.GetValue(exprRef))) { result = ExecuteStatement(ifStatement.Consequent); } else if (ifStatement.Alternate != null) { result = ExecuteStatement(ifStatement.Alternate); } else { return new Completion(Completion.Normal, null, null); } return result; } public Completion ExecuteLabelledStatement(LabelledStatement labelledStatement) { labelledStatement.Body.LabelSet = labelledStatement.Label.Name; var result = ExecuteStatement(labelledStatement.Body); if (result.Type == Completion.Break && result.Identifier == labelledStatement.Label.Name) { return new Completion(Completion.Normal, result.Value, null); } return result; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.1 /// /// /// public Completion ExecuteDoWhileStatement(DoWhileStatement doWhileStatement) { JsValue v = Undefined.Instance; bool iterating; do { var stmt = ExecuteStatement(doWhileStatement.Body); if (stmt.Value != null) { v = stmt.Value; } if (stmt.Type != Completion.Continue || stmt.Identifier != doWhileStatement.LabelSet) { if (stmt.Type == Completion.Break && (stmt.Identifier == null || stmt.Identifier == doWhileStatement.LabelSet)) { return new Completion(Completion.Normal, v, null); } if (stmt.Type != Completion.Normal) { return stmt; } } var exprRef = _engine.EvaluateExpression(doWhileStatement.Test); iterating = TypeConverter.ToBoolean(_engine.GetValue(exprRef)); } while (iterating); return new Completion(Completion.Normal, v, null); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.2 /// /// /// public Completion ExecuteWhileStatement(WhileStatement whileStatement) { JsValue v = Undefined.Instance; while (true) { var exprRef = _engine.EvaluateExpression(whileStatement.Test); if (!TypeConverter.ToBoolean(_engine.GetValue(exprRef))) { return new Completion(Completion.Normal, v, null); } var stmt = ExecuteStatement(whileStatement.Body); if (stmt.Value != null) { v = stmt.Value; } if (stmt.Type != Completion.Continue || stmt.Identifier != whileStatement.LabelSet) { if (stmt.Type == Completion.Break && (stmt.Identifier == null || stmt.Identifier == whileStatement.LabelSet)) { return new Completion(Completion.Normal, v, null); } if (stmt.Type != Completion.Normal) { return stmt; } } } } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.3 /// /// /// public Completion ExecuteForStatement(ForStatement forStatement) { if (forStatement.Init != null) { if (forStatement.Init.Type == SyntaxNodes.VariableDeclaration) { ExecuteStatement(forStatement.Init.As()); } else { _engine.GetValue(_engine.EvaluateExpression(forStatement.Init.As())); } } JsValue v = Undefined.Instance; while (true) { if (forStatement.Test != null) { var testExprRef = _engine.EvaluateExpression(forStatement.Test); if (!TypeConverter.ToBoolean(_engine.GetValue(testExprRef))) { return new Completion(Completion.Normal, v, null); } } var stmt = ExecuteStatement(forStatement.Body); if (stmt.Value != null) { v = stmt.Value; } if (stmt.Type == Completion.Break && (stmt.Identifier == null || stmt.Identifier == forStatement.LabelSet)) { return new Completion(Completion.Normal, v, null); } if (stmt.Type != Completion.Continue || ((stmt.Identifier != null) && stmt.Identifier != forStatement.LabelSet)) { if (stmt.Type != Completion.Normal) { return stmt; } } if (forStatement.Update != null) { var incExprRef = _engine.EvaluateExpression(forStatement.Update); _engine.GetValue(incExprRef); } } } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.6.4 /// /// /// public Completion ExecuteForInStatement(ForInStatement forInStatement) { Identifier identifier = forInStatement.Left.Type == SyntaxNodes.VariableDeclaration ? forInStatement.Left.As().Declarations.First().Id : forInStatement.Left.As(); var varRef = _engine.EvaluateExpression(identifier) as Reference; var exprRef = _engine.EvaluateExpression(forInStatement.Right); var experValue = _engine.GetValue(exprRef); if (experValue == Undefined.Instance || experValue == Null.Instance) { return new Completion(Completion.Normal, null, null); } var obj = TypeConverter.ToObject(_engine, experValue); JsValue v = Null.Instance; // keys are constructed using the prototype chain var cursor = obj; var processedKeys = new HashSet(); while (cursor != null) { var keys = _engine.Object.GetOwnPropertyNames(Undefined.Instance, Arguments.From(cursor)).AsArray(); for (var i = 0; i < keys.GetLength(); i++) { var p = keys.GetOwnProperty(i.ToString()).Value.AsString(); if (processedKeys.Contains(p)) { continue; } processedKeys.Add(p); // collection might be modified by inner statement if (cursor.GetOwnProperty(p) == PropertyDescriptor.Undefined) { continue; } var value = cursor.GetOwnProperty(p); if (!value.Enumerable.HasValue || !value.Enumerable.Value) { continue; } _engine.PutValue(varRef, p); var stmt = ExecuteStatement(forInStatement.Body); if (stmt.Value != null) { v = stmt.Value; } if (stmt.Type == Completion.Break) { return new Completion(Completion.Normal, v, null); } if (stmt.Type != Completion.Continue) { if (stmt.Type != Completion.Normal) { return stmt; } } } cursor = cursor.Prototype; } return new Completion(Completion.Normal, v, null); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.7 /// /// /// public Completion ExecuteContinueStatement(ContinueStatement continueStatement) { return new Completion(Completion.Continue, null, continueStatement.Label != null ? continueStatement.Label.Name : null); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.8 /// /// /// public Completion ExecuteBreakStatement(BreakStatement breakStatement) { return new Completion(Completion.Break, null, breakStatement.Label != null ? breakStatement.Label.Name : null); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.9 /// /// /// public Completion ExecuteReturnStatement(ReturnStatement statement) { if (statement.Argument == null) { return new Completion(Completion.Return, Undefined.Instance, null); } var exprRef = _engine.EvaluateExpression(statement.Argument); return new Completion(Completion.Return, _engine.GetValue(exprRef), null); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.10 /// /// /// public Completion ExecuteWithStatement(WithStatement withStatement) { var val = _engine.EvaluateExpression(withStatement.Object); var obj = TypeConverter.ToObject(_engine, _engine.GetValue(val)); var oldEnv = _engine.ExecutionContext.LexicalEnvironment; var newEnv = LexicalEnvironment.NewObjectEnvironment(_engine, obj, oldEnv, true); _engine.ExecutionContext.LexicalEnvironment = newEnv; Completion c; try { c = ExecuteStatement(withStatement.Body); } catch (JavaScriptException e) { c = new Completion(Completion.Throw, e.Error, null); c.Location = withStatement.Location; } finally { _engine.ExecutionContext.LexicalEnvironment = oldEnv; } return c; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.11 /// /// /// public Completion ExecuteSwitchStatement(SwitchStatement switchStatement) { var exprRef = _engine.EvaluateExpression(switchStatement.Discriminant); var r = ExecuteSwitchBlock(switchStatement.Cases, _engine.GetValue(exprRef)); if (r.Type == Completion.Break && r.Identifier == switchStatement.LabelSet) { return new Completion(Completion.Normal, r.Value, null); } return r; } public Completion ExecuteSwitchBlock(IEnumerable switchBlock, JsValue input) { JsValue v = Undefined.Instance; SwitchCase defaultCase = null; bool hit = false; foreach (var clause in switchBlock) { if (clause.Test == null) { defaultCase = clause; } else { var clauseSelector = _engine.GetValue(_engine.EvaluateExpression(clause.Test)); if (ExpressionInterpreter.StrictlyEqual(clauseSelector, input)) { hit = true; } } if (hit && clause.Consequent != null) { var r = ExecuteStatementList(clause.Consequent); if (r.Type != Completion.Normal) { return r; } v = r.Value != null ? r.Value : Undefined.Instance; } } // do we need to execute the default case ? if (hit == false && defaultCase != null) { var r = ExecuteStatementList(defaultCase.Consequent); if (r.Type != Completion.Normal) { return r; } v = r.Value != null ? r.Value : Undefined.Instance; } return new Completion(Completion.Normal, v, null); } public Completion ExecuteStatementList(IEnumerable statementList) { var c = new Completion(Completion.Normal, null, null); Completion sl = c; Statement s = null; try { foreach (var statement in statementList) { s = statement; c = ExecuteStatement(statement); if (c.Type != Completion.Normal) { return new Completion(c.Type, c.Value != null ? c.Value : sl.Value, c.Identifier) { Location = c.Location }; } sl = c; } } catch (JavaScriptException v) { c = new Completion(Completion.Throw, v.Error, null); c.Location = v.Location ?? s.Location; return c; } return new Completion(c.Type, c.GetValueOrDefault(), c.Identifier); } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.13 /// /// /// public Completion ExecuteThrowStatement(ThrowStatement throwStatement) { var exprRef = _engine.EvaluateExpression(throwStatement.Argument); Completion c = new Completion(Completion.Throw, _engine.GetValue(exprRef), null); c.Location = throwStatement.Location; return c; } /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-12.14 /// /// /// public Completion ExecuteTryStatement(TryStatement tryStatement) { var b = ExecuteStatement(tryStatement.Block); if (b.Type == Completion.Throw) { // execute catch if (tryStatement.Handlers.Any()) { foreach (var catchClause in tryStatement.Handlers) { var c = _engine.GetValue(b); var oldEnv = _engine.ExecutionContext.LexicalEnvironment; var catchEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, oldEnv); catchEnv.Record.CreateMutableBinding(catchClause.Param.Name); catchEnv.Record.SetMutableBinding(catchClause.Param.Name, c, false); _engine.ExecutionContext.LexicalEnvironment = catchEnv; b = ExecuteStatement(catchClause.Body); _engine.ExecutionContext.LexicalEnvironment = oldEnv; } } } if (tryStatement.Finalizer != null) { var f = ExecuteStatement(tryStatement.Finalizer); if (f.Type == Completion.Normal) { return b; } return f; } return b; } public Completion ExecuteProgram(Program program) { return ExecuteStatementList(program.Body); } public Completion ExecuteVariableDeclaration(VariableDeclaration statement) { foreach (var declaration in statement.Declarations) { if (declaration.Init != null) { var lhs = _engine.EvaluateExpression(declaration.Id) as Reference; if (lhs == null) { throw new ArgumentException(); } if (lhs.IsStrict() && lhs.GetBase().TryCast() != null && (lhs.GetReferencedName() == "eval" || lhs.GetReferencedName() == "arguments")) { throw new JavaScriptException(_engine.SyntaxError); } lhs.GetReferencedName(); var value = _engine.GetValue(_engine.EvaluateExpression(declaration.Init)); _engine.PutValue(lhs, value); } } return new Completion(Completion.Normal, Undefined.Instance, null); } public Completion ExecuteBlockStatement(BlockStatement blockStatement) { return ExecuteStatementList(blockStatement.Body); } public Completion ExecuteDebuggerStatement(DebuggerStatement debuggerStatement) { if (_engine.Options._IsDebuggerStatementAllowed) { if (!System.Diagnostics.Debugger.IsAttached) { System.Diagnostics.Debugger.Launch(); } System.Diagnostics.Debugger.Break(); } return new Completion(Completion.Normal, null, null); } } }