diff --git a/Jint.Tests.Test262/Language/Expressions/ClassTests.cs b/Jint.Tests.Test262/Language/Expressions/ClassTests.cs new file mode 100644 index 0000000000..d8812785a8 --- /dev/null +++ b/Jint.Tests.Test262/Language/Expressions/ClassTests.cs @@ -0,0 +1,15 @@ +using Xunit; + +namespace Jint.Tests.Test262.Language.Expressions +{ + public class ClassTests : Test262Test + { + [Theory(DisplayName = "language\\expressions\\class")] + [MemberData(nameof(SourceFiles), "language\\expressions\\class", false)] + [MemberData(nameof(SourceFiles), "language\\expressions\\class", true, Skip = "Skipped")] + protected void Class(SourceFile sourceFile) + { + RunTestInternal(sourceFile); + } + } +} \ No newline at end of file diff --git a/Jint.Tests.Test262/Language/Statements/ClassTests.cs b/Jint.Tests.Test262/Language/Statements/ClassTests.cs new file mode 100644 index 0000000000..7736a7471a --- /dev/null +++ b/Jint.Tests.Test262/Language/Statements/ClassTests.cs @@ -0,0 +1,15 @@ +using Xunit; + +namespace Jint.Tests.Test262.Language.Statements +{ + public class ClassTests : Test262Test + { + [Theory(DisplayName = "language\\statements\\class")] + [MemberData(nameof(SourceFiles), "language\\statements\\class", false)] + [MemberData(nameof(SourceFiles), "language\\statements\\class", true, Skip = "Skipped")] + protected void Class(SourceFile sourceFile) + { + RunTestInternal(sourceFile); + } + } +} \ No newline at end of file diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index 2571749ec2..25c01788e0 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -84,8 +84,8 @@ protected void RunTestCode(string code, bool strict) .Strict(strict) ); - engine.Execute(Sources["sta.js"]); - engine.Execute(Sources["assert.js"]); + engine.Execute(Sources["sta.js"], CreateParserOptions("sta.js")); + engine.Execute(Sources["assert.js"], CreateParserOptions("assert.js")); engine.SetValue("print", new ClrFunctionInstance(engine, "print", (thisObj, args) => TypeConverter.ToString(args.At(0)))); var o = engine.Object.Construct(Arguments.Empty); @@ -112,13 +112,13 @@ protected void RunTestCode(string code, bool strict) var files = includes.Groups[1].Captures[0].Value.Split(','); foreach (var file in files) { - engine.Execute(Sources[file.Trim()]); + engine.Execute(Sources[file.Trim()], CreateParserOptions(file.Trim())); } } if (code.IndexOf("propertyHelper.js", StringComparison.OrdinalIgnoreCase) != -1) { - engine.Execute(Sources["propertyHelper.js"]); + engine.Execute(Sources["propertyHelper.js"], CreateParserOptions("propertyHelper.js")); } string lastError = null; @@ -210,10 +210,6 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) skip = true; reason = "tail-calls not implemented"; break; - case "class": - skip = true; - reason = "class keyword not implemented"; - break; case "BigInt": skip = true; reason = "BigInt not implemented"; @@ -230,6 +226,11 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) skip = true; reason = "async not implemented"; break; + case "class-fields-private": + case "class-fields-public": + skip = true; + reason = "private/public class fields not implemented in esprima"; + break; case "new.target": skip = true; reason = "MetaProperty not implemented"; @@ -290,6 +291,48 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) reason = "Unicode support and its special cases need more work"; } + if (name.StartsWith("language/statements/class/subclass/builtin-objects/Promise")) + { + skip = true; + reason = "Promise not implemented"; + } + + if (name.StartsWith("language/statements/class/subclass/builtin-objects/TypedArray")) + { + skip = true; + reason = "TypedArray not implemented"; + } + + if (name.StartsWith("language/statements/class/subclass/builtin-objects/WeakMap")) + { + skip = true; + reason = "WeakMap not implemented"; + } + + if (name.StartsWith("language/statements/class/subclass/builtin-objects/WeakSet")) + { + skip = true; + reason = "WeakSet not implemented"; + } + + if (name.StartsWith("language/statements/class/subclass/builtin-objects/ArrayBuffer/")) + { + skip = true; + reason = "ArrayBuffer not implemented"; + } + + if (name.StartsWith("language/statements/class/subclass/builtin-objects/DataView")) + { + skip = true; + reason = "DataView not implemented"; + } + + if (name.StartsWith("language/statements/class/subclass/builtins.js")) + { + skip = true; + reason = "Uint8Array not implemented"; + } + if (name.StartsWith("built-ins/RegExp/CharacterClassEscapes/")) { skip = true; @@ -322,8 +365,16 @@ public static IEnumerable SourceFiles(string pathPrefix, bool skipped) return results; } + + private static ParserOptions CreateParserOptions(string fileName) => + new ParserOptions(fileName) + { + AdaptRegexp = true, + Tolerant = true, + Loc = true + }; } - + public class SourceFile : IXunitSerializable { public SourceFile() diff --git a/Jint.Tests.Test262/test/skipped.json b/Jint.Tests.Test262/test/skipped.json index 6283b2e78d..b6aa9086eb 100644 --- a/Jint.Tests.Test262/test/skipped.json +++ b/Jint.Tests.Test262/test/skipped.json @@ -94,6 +94,10 @@ "source": "language/expressions/object/method-definition/object-method-returns-promise.js", "reason": "Promise not implemented" }, + { + "source": "language/statements/class/definition/class-method-returns-promise.js", + "reason": "Promise not implemented" + }, { "source": "built-ins/Symbol/species/subclassing.js", "reason": "subclassing not implemented" @@ -319,249 +323,6 @@ "reason": "inner binding rejects modification (from parameters) Expected a Error to be thrown but no exception was thrown at all" }, - // class support - { - "source": "built-ins/Function/prototype/toString/class-declaration-complex-heritage.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/class-declaration-explicit-ctor.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/class-declaration-implicit-ctor.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/class-expression-explicit-ctor.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/class-expression-implicit-ctor.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/getter-class-expression-static.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/getter-class-expression.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/getter-class-statement-static.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/getter-class-statement.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/method-class-expression-static.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/method-class-expression.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/method-class-statement-static.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/method-class-statement.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/setter-class-expression-static.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/setter-class-expression.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/setter-class-statement-static.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Function/prototype/toString/setter-class-statement.js", - "reason": "class not implemented" - }, - { - "source": "language/global-code/script-decl-lex.js", - "reason": "class not implemented" - }, - { - "source": "language/global-code/script-decl-lex-deletion.js", - "reason": "class not implemented" - }, - { - "source": "language/global-code/script-decl-var-collision.js", - "reason": "class not implemented" - }, - { - "source": "language/global-code/script-decl-lex-lex.js", - "reason": "class not implemented" - }, - { - "source": "language/global-code/decl-lex.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/let/dstr-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/global-code/decl-lex-deletion.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/let/dstr-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/const/dstr-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/const/dstr-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/dstr-dflt-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "built-ins/Array/prototype/concat/Array.prototype.concat_non-array.js", - "reason": "class not implemented" - }, - { - "source": "language/rest-parameters/with-new-target.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/dstr-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for/dstr-const-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for/dstr-let-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for/dstr-let-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for/dstr-const-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for/dstr-var-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for/dstr-var-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/dstr-dflt-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/dstr-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/lexical-super-property-from-within-constructor.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/lexical-supercall-from-immediately-invoked-arrow.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/lexical-super-property.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/arrow-function/lexical-super-call-from-within-constructor.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/function/dstr-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/function/dstr-dflt-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/function/dstr-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/function/dstr-dflt-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/dstr-meth-dflt-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/dstr-meth-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/dstr-meth-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/dstr-meth-dflt-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/method-definition/name-invoke-ctor", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/method-definition/name-invoke-ctor.js", - "reason": "class not implemented" - }, - { - "source": "language/expressions/object/method-definition/name-prototype-prop.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for-of/dstr-const-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for-of/dstr-const-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for-of/dstr-let-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for-of/dstr-let-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for-of/dstr-var-ary-ptrn-elem-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - { - "source": "language/statements/for-of/dstr-var-obj-ptrn-id-init-fn-name-class.js", - "reason": "class not implemented" - }, - - { "source": "language/expressions/object/accessor-name-computed-yield-id.js", "reason": "accessor / yield not implemented" @@ -623,7 +384,6 @@ { - "source": "language/expressions/object/fn-name-arrow.js", "source": "language/expressions/arrow-function/scope-paramsbody-var-open.js", "reason": "not implemented: Creation of new variable environment for the function body (as distinct from that for the function's parameters)" }, @@ -644,6 +404,14 @@ "source": "built-ins/Object/prototype/toString/proxy-function.js", "reason": "generators not implemented" }, + { + "source": "language/statements/class/subclass/builtin-objects/GeneratorFunction/instance-prototype.js", + "reason": "generators not implemented" + }, + { + "source": "language/statements/class/subclass/builtin-objects/GeneratorFunction/regular-subclassing.js", + "reason": "generators not implemented" + }, // Esprima problems diff --git a/Jint.Tests/Runtime/Domain/UuidConstructor.cs b/Jint.Tests/Runtime/Domain/UuidConstructor.cs index 031954cdc8..9e181c80ce 100644 --- a/Jint.Tests/Runtime/Domain/UuidConstructor.cs +++ b/Jint.Tests/Runtime/Domain/UuidConstructor.cs @@ -30,7 +30,7 @@ private JsValue Parse(JsValue @this, JsValue[] arguments) return Undefined; } - protected override ObjectInstance GetPrototypeOf() => _prototype; + protected internal override ObjectInstance GetPrototypeOf() => _prototype; internal ObjectInstance _prototype; diff --git a/Jint.Tests/Runtime/Domain/UuidInstance.cs b/Jint.Tests/Runtime/Domain/UuidInstance.cs index 06c7c7b6cf..dd0cd381c3 100644 --- a/Jint.Tests/Runtime/Domain/UuidInstance.cs +++ b/Jint.Tests/Runtime/Domain/UuidInstance.cs @@ -5,7 +5,7 @@ namespace Jint.Tests.Runtime.Domain { internal class UuidInstance : ObjectInstance, IObjectWrapper { - protected override ObjectInstance GetPrototypeOf() => _prototype; + protected internal override ObjectInstance GetPrototypeOf() => _prototype; internal ObjectInstance _prototype; diff --git a/Jint.Tests/Runtime/ErrorTests.cs b/Jint.Tests/Runtime/ErrorTests.cs index 8f5b4a2cc4..9d1a7ec242 100644 --- a/Jint.Tests/Runtime/ErrorTests.cs +++ b/Jint.Tests/Runtime/ErrorTests.cs @@ -73,9 +73,9 @@ public void CanProduceCorrectStackTrace() Assert.Equal("custom.js", e.Location.Source); var stack = e.StackTrace; - EqualIgnoringNewLineDifferences(@" at a (v) custom.js:2:18 - at b (v) custom.js:6:9 - at main.js:1:9", stack); + EqualIgnoringNewLineDifferences(@" at a (v) custom.js:2:18 + at b (v) custom.js:6:9 + at main.js:1:9", stack); } private class Folder @@ -122,11 +122,11 @@ public void CallStackBuildingShouldSkipResolvingFromEngine() )); Assert.Equal("Cannot read property 'Name' of null", javaScriptException.Message); - EqualIgnoringNewLineDifferences(@" at recursive (folderInstance) :6:44 - at recursive (folderInstance) :8:32 - at recursive (folderInstance) :8:32 - at recursive (folderInstance) :8:32 - at :12:17", javaScriptException.StackTrace); + EqualIgnoringNewLineDifferences(@" at recursive (folderInstance) :6:44 + at recursive (folderInstance) :8:32 + at recursive (folderInstance) :8:32 + at recursive (folderInstance) :8:32 + at :12:17", javaScriptException.StackTrace); var expected = new List { @@ -152,9 +152,9 @@ public void StackTraceCollectedOnThreeLevels() var ex = Assert.Throws(() => engine.Execute(script)); const string expected = @"Jint.Runtime.JavaScriptException: Cannot read property 'yyy' of undefined - at a (v) :2:18 - at b (v) :6:12 - at :9:9"; + at a (v) :2:18 + at b (v) :6:12 + at :9:9"; EqualIgnoringNewLineDifferences(expected, ex.ToString()); } diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 04ce059662..f322246df8 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -32,13 +32,14 @@ using Jint.Runtime.Interop; using Jint.Runtime.Interop.Reflection; using Jint.Runtime.Interpreter; +using Jint.Runtime.Interpreter.Expressions; using Jint.Runtime.References; namespace Jint { public class Engine { - private static readonly ParserOptions DefaultParserOptions = new("") + internal static readonly ParserOptions DefaultParserOptions = new("") { AdaptRegexp = true, Tolerant = true, @@ -100,7 +101,7 @@ public class Engine { typeof(UInt16), (engine, v) => JsNumber.Create((UInt16)v) }, { typeof(UInt32), (engine, v) => JsNumber.Create((UInt32)v) }, { typeof(UInt64), (engine, v) => JsNumber.Create((UInt64)v) }, - { typeof(System.Text.RegularExpressions.Regex), (engine, v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "", engine) } + { typeof(System.Text.RegularExpressions.Regex), (engine, v) => engine.RegExp.Construct((System.Text.RegularExpressions.Regex)v, "") } }; // shared frozen version @@ -463,7 +464,7 @@ internal JsValue GetValue(Reference reference, bool returnReferenceToPool) if (baseValue.IsObject()) { var o = TypeConverter.ToObject(this, baseValue); - var v = o.Get(property); + var v = o.Get(property, reference.GetThisValue()); return v; } else @@ -680,7 +681,7 @@ internal Node GetLastSyntaxNode() /// The name of the property to return. public JsValue GetValue(JsValue scope, JsValue property) { - var reference = _referencePool.Rent(scope, property, _isStrict); + var reference = _referencePool.Rent(scope, property, _isStrict, thisValue: null); var jsValue = GetValue(reference, false); _referencePool.Return(reference); return jsValue; @@ -695,20 +696,20 @@ internal Reference ResolveBinding(string name, LexicalEnvironment env = null) return GetIdentifierReference(env, name, StrictModeScope.IsStrictModeCode); } - private Reference GetIdentifierReference(LexicalEnvironment lex, string name, bool strict) + private Reference GetIdentifierReference(LexicalEnvironment env, string name, bool strict) { - if (lex is null) + if (env is null) { return new Reference(JsValue.Undefined, name, strict); } - var envRec = lex._record; + var envRec = env._record; if (envRec.HasBinding(name)) { return new Reference(envRec, name, strict); } - return GetIdentifierReference(lex._outer, name, strict); + return GetIdentifierReference(env._outer, name, strict); } /// @@ -791,6 +792,12 @@ private void GlobalDeclarationInstantiation( for (var j = 0; j < boundNames.Count; j++) { var vn = boundNames[j]; + + if (envRec.HasLexicalDeclaration(vn)) + { + ExceptionHelper.ThrowSyntaxError(this, $"Identifier '{vn}' has already been declared"); + } + if (!declaredFunctionNames.Contains(vn)) { var vnDefinable = envRec.CanDeclareGlobalVar(vn); @@ -836,7 +843,13 @@ private void GlobalDeclarationInstantiation( foreach (var f in functionToInitialize) { - var fn = f.Id.Name; + var fn = f.Id!.Name; + + if (envRec.HasLexicalDeclaration(fn)) + { + ExceptionHelper.ThrowSyntaxError(this, $"Identifier '{fn}' has already been declared"); + } + var fo = Function.CreateFunctionObject(f, env); envRec.CreateGlobalFunctionBinding(fn, fo, canBeDeleted: false); } @@ -1201,25 +1214,33 @@ internal void UpdateVariableEnvironment(LexicalEnvironment newEnv) _executionContexts.ReplaceTopVariableEnvironment(newEnv); } - internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, Location? location) + internal JsValue Call(ICallable callable, JsValue thisObject, JsValue[] arguments, JintExpression expression) { if (callable is FunctionInstance functionInstance) { - return Call(functionInstance, thisObject, arguments, location); + return Call(functionInstance, thisObject, arguments, expression); } return callable.Call(thisObject, arguments); } + internal JsValue Construct(IConstructor constructor, JsValue[] arguments, JsValue newTarget, JintExpression expression) + { + if (constructor is FunctionInstance functionInstance) + { + return Construct(functionInstance, arguments, newTarget, expression); + } + + return constructor.Construct(arguments, newTarget); + } + internal JsValue Call( FunctionInstance functionInstance, JsValue thisObject, JsValue[] arguments, - Location? location) + JintExpression expression) { - location ??= ((Node) functionInstance._functionDefinition?.Function)?.Location; - - var callStackElement = new CallStackElement(functionInstance, location); + var callStackElement = new CallStackElement(functionInstance, expression); var recursionDepth = CallStack.Push(callStackElement); if (recursionDepth > Options.MaxRecursionDepth) @@ -1245,5 +1266,38 @@ internal JsValue Call( return result; } + + internal JsValue Construct( + FunctionInstance functionInstance, + JsValue[] arguments, + JsValue newTarget, + JintExpression expression) + { + var callStackElement = new CallStackElement(functionInstance, expression); + var recursionDepth = CallStack.Push(callStackElement); + + if (recursionDepth > Options.MaxRecursionDepth) + { + // pop the current element as it was never reached + CallStack.Pop(); + ExceptionHelper.ThrowRecursionDepthOverflowException(CallStack, callStackElement.ToString()); + } + + if (_isDebugMode) + { + DebugHandler.AddToDebugCallStack(functionInstance); + } + + var result = ((IConstructor) functionInstance).Construct(arguments, newTarget); + + if (_isDebugMode) + { + DebugHandler.PopDebugCallStack(); + } + + CallStack.Pop(); + + return result; + } } } diff --git a/Jint/EsprimaExtensions.cs b/Jint/EsprimaExtensions.cs index bf9137c812..c2a6e52d1a 100644 --- a/Jint/EsprimaExtensions.cs +++ b/Jint/EsprimaExtensions.cs @@ -1,19 +1,23 @@ +#nullable enable + using System; using System.Collections.Generic; -using System.Globalization; using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native; +using Jint.Native.Function; +using Jint.Native.Object; using Jint.Runtime; +using Jint.Runtime.Interpreter; using Jint.Runtime.Interpreter.Expressions; namespace Jint { public static class EsprimaExtensions { - public static JsValue GetKey(this Property property, Engine engine) => GetKey(property.Key, engine, property.Computed); + public static JsValue GetKey(this ClassProperty property, Engine engine) => GetKey(property.Key, engine, property.Computed); - public static JsValue GetKey(this T expression, Engine engine, bool resolveComputed = false) where T : Expression + public static JsValue GetKey(this Expression expression, Engine engine, bool resolveComputed = false) { if (expression is Literal literal) { @@ -40,6 +44,7 @@ private static bool TryGetComputedPropertyKey(T expression, Engine engine, ou || expression.Type == Nodes.CallExpression || expression.Type == Nodes.BinaryExpression || expression.Type == Nodes.UpdateExpression + || expression.Type == Nodes.AssignmentExpression || expression is StaticMemberExpression) { propertyKey = TypeConverter.ToPropertyKey(JintExpression.Build(engine, expression).GetValue()); @@ -54,7 +59,10 @@ private static bool TryGetComputedPropertyKey(T expression, Engine engine, ou internal static bool IsFunctionWithName(this T node) where T : Node { var type = node.Type; - return type == Nodes.FunctionExpression || type == Nodes.ArrowFunctionExpression || type == Nodes.ArrowParameterPlaceHolder; + return type == Nodes.FunctionExpression + || type == Nodes.ArrowFunctionExpression + || type == Nodes.ArrowParameterPlaceHolder + || type == Nodes.ClassExpression; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -63,16 +71,10 @@ internal static string LiteralKeyToString(Literal literal) // prevent conversion to scientific notation if (literal.Value is double d) { - return DoubleToString(d); + return TypeConverter.ToString(d); } return literal.Value as string ?? Convert.ToString(literal.Value, provider: null); } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static string DoubleToString(double d) - { - return (d - (long) d) == 0 ? ((long) d).ToString() : d.ToString(CultureInfo.InvariantCulture); - } internal static void GetBoundNames(this VariableDeclaration variableDeclaration, List target) { @@ -84,7 +86,7 @@ internal static void GetBoundNames(this VariableDeclaration variableDeclaration, } } - internal static void GetBoundNames(this Node parameter, List target) + internal static void GetBoundNames(this Node? parameter, List target) { if (parameter is null || parameter.Type == Nodes.Literal) { @@ -94,7 +96,7 @@ internal static void GetBoundNames(this Node parameter, List target) // try to get away without a loop if (parameter is Identifier id) { - target.Add(id.Name); + target.Add(id.Name!); return; } @@ -102,7 +104,7 @@ internal static void GetBoundNames(this Node parameter, List target) { if (parameter is Identifier identifier) { - target.Add(identifier.Name); + target.Add(identifier.Name!); return; } @@ -139,10 +141,56 @@ internal static void GetBoundNames(this Node parameter, List target) else if (parameter is AssignmentPattern assignmentPattern) { parameter = assignmentPattern.Left; + if (assignmentPattern.Right is ClassExpression classExpression) + { + // TODO check if there's more generic rule + if (classExpression.Id is not null) + { + target.Add(classExpression.Id.Name!); + } + } continue; } break; } } + + /// + /// https://tc39.es/ecma262/#sec-runtime-semantics-definemethod + /// + internal static Record DefineMethod(this ClassProperty m, ObjectInstance obj, ObjectInstance? functionPrototype = null) + { + var engine = obj.Engine; + var property = TypeConverter.ToPropertyKey(m.GetKey(engine)); + var prototype = functionPrototype ?? engine.Function.PrototypeObject; + var function = m.Value as IFunction ?? ExceptionHelper.ThrowSyntaxError(engine); + var functionDefinition = new JintFunctionDefinition(engine, function); + var functionThisMode = functionDefinition.Strict || engine._isStrict + ? FunctionThisMode.Strict + : FunctionThisMode.Global; + + var closure = new ScriptFunctionInstance( + engine, + functionDefinition, + engine.ExecutionContext.LexicalEnvironment, + functionThisMode, + prototype); + + closure.MakeMethod(obj); + + return new Record(property, closure); + } + + internal readonly struct Record + { + public Record(JsValue key, ScriptFunctionInstance closure) + { + Key = key; + Closure = closure; + } + + public readonly JsValue Key; + public readonly ScriptFunctionInstance Closure; + } } } \ No newline at end of file diff --git a/Jint/Native/Array/ArrayPrototype.cs b/Jint/Native/Array/ArrayPrototype.cs index cb8e1112cd..73ffb7b1d6 100644 --- a/Jint/Native/Array/ArrayPrototype.cs +++ b/Jint/Native/Array/ArrayPrototype.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Jint.Collections; +using Jint.Native.Number; using Jint.Native.Object; using Jint.Native.Symbol; using Jint.Pooling; @@ -20,6 +21,7 @@ namespace Jint.Native.Array public sealed class ArrayPrototype : ArrayInstance { private ArrayConstructor _arrayConstructor; + internal ClrFunctionInstance _originalIteratorFunction; private ArrayPrototype(Engine engine) : base(engine) { @@ -90,9 +92,10 @@ protected override void Initialize() }; SetProperties(properties); + _originalIteratorFunction = new ClrFunctionInstance(Engine, "iterator", Values, 1); var symbols = new SymbolDictionary(2) { - [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "iterator", Values, 1), propertyFlags), + [GlobalSymbolRegistry.Iterator] = new PropertyDescriptor(_originalIteratorFunction, propertyFlags), [GlobalSymbolRegistry.Unscopables] = new PropertyDescriptor(unscopables, PropertyFlag.Configurable) }; SetSymbols(symbols); @@ -1059,10 +1062,10 @@ private JsValue Concat(JsValue thisObj, JsValue[] arguments) // try to find best capacity bool hasObjectSpreadables = false; - uint capacity = 0; + ulong capacity = 0; for (var i = 0; i < items.Count; i++) { - uint increment; + ulong increment; if (!(items[i] is ObjectInstance objectInstance)) { increment = 1; @@ -1071,11 +1074,23 @@ private JsValue Concat(JsValue thisObj, JsValue[] arguments) { var isConcatSpreadable = objectInstance.IsConcatSpreadable; hasObjectSpreadables |= isConcatSpreadable; - increment = isConcatSpreadable ? ArrayOperations.For(objectInstance).GetLength() : 1; + if (isConcatSpreadable) + { + increment = ArrayOperations.For(objectInstance).GetLongLength(); + } + else + { + increment = 1; + } } capacity += increment; } + if (capacity > NumberConstructor.MaxSafeInteger) + { + ExceptionHelper.ThrowTypeError(_engine, "Invalid array length"); + } + uint n = 0; var a = Engine.Array.ArraySpeciesCreate(TypeConverter.ToObject(_engine, thisObj), capacity); var aOperations = ArrayOperations.For(a); diff --git a/Jint/Native/Boolean/BooleanConstructor.cs b/Jint/Native/Boolean/BooleanConstructor.cs index dbff652e82..b0c693f3b9 100644 --- a/Jint/Native/Boolean/BooleanConstructor.cs +++ b/Jint/Native/Boolean/BooleanConstructor.cs @@ -13,6 +13,8 @@ private BooleanConstructor(Engine engine) : base(engine, _functionName) { } + + public BooleanPrototype PrototypeObject { get; private set; } public static BooleanConstructor CreateBooleanConstructor(Engine engine) { @@ -41,20 +43,23 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments) } /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.2.1 + /// https://tc39.es/ecma262/#sec-boolean-constructor-boolean-value /// public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { - return Construct(TypeConverter.ToBoolean(arguments.At(0))); - } + var b = TypeConverter.ToBoolean(arguments.At(0)) + ? JsBoolean.True + : JsBoolean.False; - public BooleanPrototype PrototypeObject { get; private set; } + if (newTarget.IsUndefined()) + { + return Construct(b); + } - public BooleanInstance Construct(bool value) - { - return Construct(value ? JsBoolean.True : JsBoolean.False); + var o = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, state) => new BooleanInstance(engine, (JsBoolean) state), b); + return Construct(b); } - + public BooleanInstance Construct(JsBoolean value) { var instance = new BooleanInstance(Engine) diff --git a/Jint/Native/Boolean/BooleanInstance.cs b/Jint/Native/Boolean/BooleanInstance.cs index 087d1da366..a4a0e96ddb 100644 --- a/Jint/Native/Boolean/BooleanInstance.cs +++ b/Jint/Native/Boolean/BooleanInstance.cs @@ -9,6 +9,12 @@ public BooleanInstance(Engine engine) : base(engine, ObjectClass.Boolean) { } + + public BooleanInstance(Engine engine, JsBoolean value) + : base(engine, ObjectClass.Boolean) + { + PrimitiveValue = value; + } Types IPrimitiveInstance.Type => Types.Boolean; diff --git a/Jint/Native/Error/ErrorConstructor.cs b/Jint/Native/Error/ErrorConstructor.cs index 53a79931b2..ae0dff2153 100644 --- a/Jint/Native/Error/ErrorConstructor.cs +++ b/Jint/Native/Error/ErrorConstructor.cs @@ -45,23 +45,26 @@ public ObjectInstance Construct(JsValue[] arguments) public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { - var instance = new ErrorInstance(Engine, _name); - instance._prototype = PrototypeObject; + var o = OrdinaryCreateFromConstructor( + newTarget, + PrototypeObject, + static (e, state) => new ErrorInstance(e, (JsString) state), + _name); var jsValue = arguments.At(0); if (!jsValue.IsUndefined()) { var msg = TypeConverter.ToString(jsValue); var msgDesc = new PropertyDescriptor(msg, true, false, true); - instance.DefinePropertyOrThrow("message", msgDesc); + o.DefinePropertyOrThrow("message", msgDesc); } - return instance; + return o; } public ErrorPrototype PrototypeObject { get; private set; } - protected override ObjectInstance GetPrototypeOf() + protected internal override ObjectInstance GetPrototypeOf() { return _name._value != "Error" ? _engine.Error : _prototype; } diff --git a/Jint/Native/Function/ArrowFunctionInstance.cs b/Jint/Native/Function/ArrowFunctionInstance.cs deleted file mode 100644 index b7c75a230e..0000000000 --- a/Jint/Native/Function/ArrowFunctionInstance.cs +++ /dev/null @@ -1,84 +0,0 @@ -using Esprima.Ast; -using Jint.Runtime; -using Jint.Runtime.Descriptors; -using Jint.Runtime.Descriptors.Specialized; -using Jint.Runtime.Environments; -using Jint.Runtime.Interpreter; - -namespace Jint.Native.Function -{ - public sealed class ArrowFunctionInstance : FunctionInstance - { - private readonly JintFunctionDefinition _function; - - /// - /// http://www.ecma-international.org/ecma-262/6.0/#sec-arrow-function-definitions - /// - public ArrowFunctionInstance( - Engine engine, - IFunction functionDeclaration, - LexicalEnvironment scope, - bool strict) - : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict) - { - } - - internal ArrowFunctionInstance( - Engine engine, - JintFunctionDefinition function, - LexicalEnvironment scope, - bool strict) - : base(engine, function, scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Lexical) - { - _function = function; - - _prototype = Engine.Function.PrototypeObject; - - _length = new LazyPropertyDescriptor(() => JsNumber.Create(function.Initialize(engine, this).Length), PropertyFlag.Configurable); - } - - // for example RavenDB wants to inspect this - public IFunction FunctionDeclaration => _function.Function; - - /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1 - /// - public override JsValue Call(JsValue thisArg, JsValue[] arguments) - { - var strict = Strict || _engine._isStrict; - using (new StrictModeScope(strict, true)) - { - var localEnv = LexicalEnvironment.NewFunctionEnvironment(_engine, this, Undefined); - _engine.EnterExecutionContext(localEnv, localEnv); - - try - { - _engine.FunctionDeclarationInstantiation( - functionInstance: this, - arguments, - localEnv); - - var result = _function.Execute(); - - var value = result.GetValueOrDefault().Clone(); - - if (result.Type == CompletionType.Throw) - { - ExceptionHelper.ThrowJavaScriptException(_engine, value, result); - } - - if (result.Type == CompletionType.Return) - { - return value; - } - } - finally - { - _engine.LeaveExecutionContext(); - } - - return Undefined; - } - } - } -} \ No newline at end of file diff --git a/Jint/Native/Function/BindFunctionInstance.cs b/Jint/Native/Function/BindFunctionInstance.cs index 15827526eb..c176097b9b 100644 --- a/Jint/Native/Function/BindFunctionInstance.cs +++ b/Jint/Native/Function/BindFunctionInstance.cs @@ -38,6 +38,12 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) } var args = CreateArguments(arguments); + + if (ReferenceEquals(this, newTarget)) + { + newTarget = TargetFunction; + } + var value = target.Construct(args, newTarget); _engine._jsValueArrayPool.ReturnArray(args); @@ -62,7 +68,7 @@ private JsValue[] CreateArguments(JsValue[] arguments) return combined; } - internal override bool IsConstructor => TargetFunction is IConstructor; + internal override bool IsConstructor => TargetFunction.IsConstructor; public override string ToString() => "function () { [native code] }"; } diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs new file mode 100644 index 0000000000..d6040a7d3a --- /dev/null +++ b/Jint/Native/Function/ClassDefinition.cs @@ -0,0 +1,204 @@ +#nullable enable + +using Esprima; +using Esprima.Ast; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Environments; +using Jint.Runtime.Interpreter.Expressions; + +namespace Jint.Native.Function +{ + internal class ClassDefinition + { + private static readonly MethodDefinition _superConstructor; + private static readonly MethodDefinition _emptyConstructor; + + internal readonly string? _className; + private readonly Expression? _superClass; + private readonly ClassBody _body; + + static ClassDefinition() + { + // generate missing constructor AST only once + static MethodDefinition CreateConstructorMethodDefinition(string source) + { + var parser = new JavaScriptParser(source); + var script = parser.ParseScript(); + return (MethodDefinition) script.Body[0].ChildNodes[2].ChildNodes[0]; + } + + _superConstructor = CreateConstructorMethodDefinition("class temp { constructor(...args) { super(...args); } }"); + _emptyConstructor = CreateConstructorMethodDefinition("class temp { constructor() {} }"); + } + + public ClassDefinition( + Identifier? className, + Expression? superClass, + ClassBody body) + { + _className = className?.Name; + _superClass = superClass; + _body = body; + } + + /// + /// https://tc39.es/ecma262/#sec-runtime-semantics-classdefinitionevaluation + /// + public ScriptFunctionInstance BuildConstructor( + Engine engine, + LexicalEnvironment env) + { + // A class definition is always strict mode code. + using var _ = (new StrictModeScope(true, true)); + + var classScope = LexicalEnvironment.NewDeclarativeEnvironment(engine, env); + + if (_className is not null) + { + classScope._record.CreateImmutableBinding(_className, true); + } + + ObjectInstance? protoParent = null; + ObjectInstance? constructorParent = null; + if (_superClass is null) + { + protoParent = engine.Object.PrototypeObject; + constructorParent = engine.Function.PrototypeObject; + } + else + { + engine.UpdateLexicalEnvironment(classScope); + var superclass = JintExpression.Build(engine, _superClass).GetValue(); + engine.UpdateLexicalEnvironment(env); + + if (superclass.IsNull()) + { + protoParent = null; + constructorParent = engine.Function.PrototypeObject; + } + else if (!superclass.IsConstructor) + { + ExceptionHelper.ThrowTypeError(engine, "super class is not a constructor"); + } + else + { + var temp = superclass.Get("prototype"); + if (temp is ObjectInstance protoParentObject) + { + protoParent = protoParentObject; + } + else if (temp._type == InternalTypes.Null) + { + // OK + } + else + { + ExceptionHelper.ThrowTypeError(engine); + return null!; + } + + constructorParent = (ObjectInstance) superclass; + } + } + + var proto = new ObjectInstance(engine) + { + _prototype = protoParent + }; + + MethodDefinition? constructor = null; + var classBody = _body.Body; + for (var i = 0; i < classBody.Count; ++i) + { + if (classBody[i].Kind == PropertyKind.Constructor) + { + constructor = (MethodDefinition) classBody[i]; + break; + } + } + + constructor ??= _superClass != null + ? _superConstructor + : _emptyConstructor; + + engine.UpdateLexicalEnvironment(classScope); + + ScriptFunctionInstance F; + try + { + var constructorInfo = constructor.DefineMethod(proto, constructorParent); + F = constructorInfo.Closure; + if (_className is not null) + { + F.SetFunctionName(_className); + } + + F.MakeConstructor(false, proto); + F._constructorKind = _superClass is null ? ConstructorKind.Base : ConstructorKind.Derived; + F.MakeClassConstructor(); + proto.CreateMethodProperty(CommonProperties.Constructor, F); + + foreach (var classProperty in _body.Body) + { + if (classProperty is not MethodDefinition m || m.Kind == PropertyKind.Constructor) + { + continue; + } + + var target = !m.Static ? proto : F; + PropertyDefinitionEvaluation(engine, target, m); + } + } + finally + { + engine.UpdateLexicalEnvironment(env); + } + + if (_className is not null) + { + classScope._record.InitializeBinding(_className, F); + } + + return F; + } + + /// + /// https://tc39.es/ecma262/#sec-method-definitions-runtime-semantics-propertydefinitionevaluation + /// + private static void PropertyDefinitionEvaluation( + Engine engine, + ObjectInstance obj, + MethodDefinition method) + { + if (method.Kind != PropertyKind.Get && method.Kind != PropertyKind.Set) + { + var methodDef = method.DefineMethod(obj); + methodDef.Closure.SetFunctionName(methodDef.Key); + var desc = new PropertyDescriptor(methodDef.Closure, PropertyFlag.NonEnumerable); + obj.DefinePropertyOrThrow(methodDef.Key, desc); + } + else + { + var propKey = TypeConverter.ToPropertyKey(method.GetKey(engine)); + var function = method.Value as IFunction ?? ExceptionHelper.ThrowSyntaxError(obj.Engine); + + var closure = new ScriptFunctionInstance( + obj.Engine, + function, + obj.Engine.ExecutionContext.LexicalEnvironment, + true); + closure.SetFunctionName(propKey, method.Kind == PropertyKind.Get ? "get" : "set"); + closure.MakeMethod(obj); + + var propDesc = new GetSetPropertyDescriptor( + method.Kind == PropertyKind.Get ? closure : null, + method.Kind == PropertyKind.Set ? closure : null, + PropertyFlag.Configurable); + + obj.DefinePropertyOrThrow(propKey, propDesc); + } + } + } +} \ No newline at end of file diff --git a/Jint/Native/Function/ConstructorKind.cs b/Jint/Native/Function/ConstructorKind.cs new file mode 100644 index 0000000000..088a4a274f --- /dev/null +++ b/Jint/Native/Function/ConstructorKind.cs @@ -0,0 +1,8 @@ +namespace Jint.Native.Function +{ + internal enum ConstructorKind + { + Base, + Derived + } +} \ No newline at end of file diff --git a/Jint/Native/Function/EvalFunctionInstance.cs b/Jint/Native/Function/EvalFunctionInstance.cs index a372515a80..1adfe8d222 100644 --- a/Jint/Native/Function/EvalFunctionInstance.cs +++ b/Jint/Native/Function/EvalFunctionInstance.cs @@ -21,19 +21,39 @@ public EvalFunctionInstance(Engine engine) public override JsValue Call(JsValue thisObject, JsValue[] arguments) { - return Call(thisObject, arguments, false); + return PerformEval(arguments, false); } /// /// https://tc39.es/ecma262/#sec-performeval /// - public JsValue Call(JsValue thisObject, JsValue[] arguments, bool direct) + public JsValue PerformEval(JsValue[] arguments, bool direct) { if (!(arguments.At(0) is JsString x)) { return arguments.At(0); } + var inFunction = false; + var inMethod = false; + var inDerivedConstructor = false; + + if (direct) + { + var thisEnvRec = _engine.GetThisEnvironment(); + if (thisEnvRec is FunctionEnvironmentRecord functionEnvironmentRecord) + { + var F = functionEnvironmentRecord._functionObject; + inFunction = true; + inMethod = thisEnvRec.HasSuperBinding(); + + if (F._constructorKind == ConstructorKind.Derived) + { + inDerivedConstructor = true; + } + } + } + var parser = new JavaScriptParser(x.ToString(), ParserOptions); Script script; try @@ -53,6 +73,19 @@ public JsValue Call(JsValue thisObject, JsValue[] arguments, bool direct) return Undefined; } + if (!inFunction) + { + // if body Contains NewTarget, throw a SyntaxError exception. + } + if (!inMethod) + { + // if body Contains SuperProperty, throw a SyntaxError exception. + } + if (!inDerivedConstructor) + { + // if body Contains SuperCall, throw a SyntaxError exception. + } + var strictEval = script.Strict || _engine._isStrict; var ctx = _engine.ExecutionContext; diff --git a/Jint/Native/Function/FunctionConstructor.cs b/Jint/Native/Function/FunctionConstructor.cs index c6906a00ff..e84b3f375b 100644 --- a/Jint/Native/Function/FunctionConstructor.cs +++ b/Jint/Native/Function/FunctionConstructor.cs @@ -113,6 +113,8 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) function, _engine.GlobalEnvironment, function.Strict); + + functionObject.MakeConstructor(); // the function is not actually a named function functionObject.SetFunctionName(_functionNameAnonymous, force: true); @@ -132,47 +134,10 @@ public FunctionInstance CreateFunctionObject(FunctionDeclaration functionDeclara functionDeclaration, env, functionDeclaration.Strict || _engine._isStrict); + + functionObject.MakeConstructor(); return functionObject; } - - public object Apply(JsValue thisObject, JsValue[] arguments) - { - if (arguments.Length != 2) - { - ExceptionHelper.ThrowArgumentException("Apply has to be called with two arguments."); - } - - var func = thisObject.TryCast(); - var thisArg = arguments[0]; - var argArray = arguments[1]; - - if (func is null) - { - return ExceptionHelper.ThrowTypeError(Engine); - } - - if (argArray.IsNullOrUndefined()) - { - return func.Call(thisArg, Arguments.Empty); - } - - var argArrayObj = argArray.TryCast(); - if (ReferenceEquals(argArrayObj, null)) - { - ExceptionHelper.ThrowTypeError(Engine); - } - - var len = argArrayObj.Get(CommonProperties.Length, argArrayObj); - var n = TypeConverter.ToUint32(len); - var argList = new JsValue[n]; - for (var index = 0; index < n; index++) - { - var indexName = TypeConverter.ToString(index); - var nextArg = argArrayObj.Get(JsString.Create(indexName), argArrayObj); - argList[index] = nextArg; - } - return func.Call(thisArg, argList); - } } } \ No newline at end of file diff --git a/Jint/Native/Function/FunctionInstance.cs b/Jint/Native/Function/FunctionInstance.cs index f21076b2a9..adfefeb9a7 100644 --- a/Jint/Native/Function/FunctionInstance.cs +++ b/Jint/Native/Function/FunctionInstance.cs @@ -1,5 +1,7 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Runtime.CompilerServices; +using Esprima.Ast; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; @@ -10,21 +12,16 @@ namespace Jint.Native.Function { public abstract class FunctionInstance : ObjectInstance, ICallable { - internal enum FunctionThisMode - { - Lexical, - Strict, - Global - } - - protected internal PropertyDescriptor _prototypeDescriptor; + protected PropertyDescriptor _prototypeDescriptor; protected internal PropertyDescriptor _length; - internal PropertyDescriptor _nameDescriptor; + private PropertyDescriptor _nameDescriptor; protected internal LexicalEnvironment _environment; internal readonly JintFunctionDefinition _functionDefinition; internal readonly FunctionThisMode _thisMode; + internal JsValue _homeObject = Undefined; + internal ConstructorKind _constructorKind = ConstructorKind.Base; internal FunctionInstance( Engine engine, @@ -44,7 +41,7 @@ internal FunctionInstance( ObjectClass objectClass = ObjectClass.Function) : base(engine, objectClass) { - if (!(name is null)) + if (name is not null) { _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable); } @@ -57,6 +54,9 @@ protected FunctionInstance( : this(engine, name, FunctionThisMode.Global, ObjectClass.Function) { } + + // for example RavenDB wants to inspect this + public IFunction FunctionDeclaration => _functionDefinition.Function; /// /// Executed when a function object is used as a function @@ -68,6 +68,8 @@ protected FunctionInstance( public bool Strict => _thisMode == FunctionThisMode.Strict; + internal override bool IsConstructor => this is IConstructor; + public virtual bool HasInstance(JsValue v) { if (!(v is ObjectInstance o)) @@ -234,15 +236,23 @@ internal void SetFunctionName(JsValue name, string prefix = null, bool force = f _nameDescriptor = new PropertyDescriptor(name, PropertyFlag.Configurable); } + /// + /// https://tc39.es/ecma262/#sec-ordinarycreatefromconstructor + /// + /// + /// Uses separate builder to get correct type with state support to prevent allocations. + /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal ObjectInstance OrdinaryCreateFromConstructor(JsValue constructor, ObjectInstance intrinsicDefaultProto) + internal T OrdinaryCreateFromConstructor( + JsValue constructor, + ObjectInstance intrinsicDefaultProto, + Func objectCreator, + JsValue state = null) where T : ObjectInstance { var proto = GetPrototypeFromConstructor(constructor, intrinsicDefaultProto); - var obj = new ObjectInstance(_engine) - { - _prototype = proto - }; + var obj = objectCreator(_engine, state); + obj._prototype = proto; return obj; } @@ -255,6 +265,85 @@ private static ObjectInstance GetPrototypeFromConstructor(JsValue constructor, O // Set proto to realm's intrinsic object named intrinsicDefaultProto. return proto ?? intrinsicDefaultProto; } + + internal void MakeMethod(ObjectInstance homeObject) + { + _homeObject = homeObject; + } + + /// + /// https://tc39.es/ecma262/#sec-ordinarycallbindthis + /// + protected void OrdinaryCallBindThis(ExecutionContext calleeContext, JsValue thisArgument) + { + var thisMode = _thisMode; + if (thisMode == FunctionThisMode.Lexical) + { + return; + } + + // Let calleeRealm be F.[[Realm]]. + + var localEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment._record; + + JsValue thisValue; + if (_thisMode == FunctionThisMode.Strict) + { + thisValue = thisArgument; + } + else + { + if (thisArgument.IsNullOrUndefined()) + { + // Let globalEnv be calleeRealm.[[GlobalEnv]]. + var globalEnv = _engine.GlobalEnvironment; + var globalEnvRec = (GlobalEnvironmentRecord) globalEnv._record; + thisValue = globalEnvRec.GlobalThisValue; + } + else + { + thisValue = TypeConverter.ToObject(_engine, thisArgument); + } + } + + localEnv.BindThisValue(thisValue); + } + + protected Completion OrdinaryCallEvaluateBody( + JsValue[] arguments, + ExecutionContext calleeContext) + { + var argumentsInstance = _engine.FunctionDeclarationInstantiation( + functionInstance: this, + arguments, + calleeContext.LexicalEnvironment); + + var result = _functionDefinition.Execute(); + var value = result.GetValueOrDefault().Clone(); + + argumentsInstance?.FunctionWasCalled(); + + return new Completion(result.Type, value, result.Identifier, result.Location); + } + + /// + /// https://tc39.es/ecma262/#sec-prepareforordinarycall + /// + protected ExecutionContext PrepareForOrdinaryCall(JsValue newTarget) + { + // ** PrepareForOrdinaryCall ** + // var callerContext = _engine.ExecutionContext; + // Let calleeRealm be F.[[Realm]]. + // Set the Realm of calleeContext to calleeRealm. + // Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]]. + var calleeContext = LexicalEnvironment.NewFunctionEnvironment(_engine, this, newTarget); + // If callerContext is not already suspended, suspend callerContext. + // Push calleeContext onto the execution context stack; calleeContext is now the running execution context. + // NOTE: Any exception objects produced after this point are associated with calleeRealm. + // Return calleeContext. + + return _engine.EnterExecutionContext(calleeContext, calleeContext); + } public override string ToString() { diff --git a/Jint/Native/Function/FunctionPrototype.cs b/Jint/Native/Function/FunctionPrototype.cs index 181c9d576d..76d0cfb4cb 100644 --- a/Jint/Native/Function/FunctionPrototype.cs +++ b/Jint/Native/Function/FunctionPrototype.cs @@ -65,19 +65,13 @@ private static JsValue HasInstance(JsValue thisObj, JsValue[] arguments) private JsValue Bind(JsValue thisObj, JsValue[] arguments) { - if (!(thisObj is ICallable)) + if (thisObj is not ICallable) { ExceptionHelper.ThrowTypeError(Engine, "Bind must be called on a function"); } var thisArg = arguments.At(0); - var f = new BindFunctionInstance(Engine) - { - TargetFunction = thisObj, - BoundThis = thisObj is ArrowFunctionInstance ? Undefined : thisArg, - BoundArgs = arguments.Skip(1), - _prototype = Engine.Function.PrototypeObject, - }; + var f = BoundFunctionCreate((ObjectInstance) thisObj, thisArg, arguments.Skip(1)); JsNumber l; var targetHasLength = thisObj.HasOwnProperty(CommonProperties.Length); @@ -114,6 +108,22 @@ private JsValue Bind(JsValue thisObj, JsValue[] arguments) return f; } + /// + /// https://tc39.es/ecma262/#sec-boundfunctioncreate + /// + private FunctionInstance BoundFunctionCreate(ObjectInstance targetFunction, JsValue boundThis, JsValue[] boundArgs) + { + var proto = targetFunction.GetPrototypeOf(); + var obj = new BindFunctionInstance(Engine) + { + _prototype = proto, + TargetFunction = targetFunction, + BoundThis = boundThis, + BoundArgs = boundArgs + }; + return obj; + } + private JsValue ToString(JsValue thisObj, JsValue[] arguments) { if (thisObj.IsObject() && thisObj.IsCallable) diff --git a/Jint/Native/Function/FunctionThisMode.cs b/Jint/Native/Function/FunctionThisMode.cs new file mode 100644 index 0000000000..9f22766a45 --- /dev/null +++ b/Jint/Native/Function/FunctionThisMode.cs @@ -0,0 +1,9 @@ +namespace Jint.Native.Function +{ + internal enum FunctionThisMode + { + Lexical, + Strict, + Global + } +} \ No newline at end of file diff --git a/Jint/Native/Function/ScriptFunctionInstance.cs b/Jint/Native/Function/ScriptFunctionInstance.cs index 8da44d0811..f6ae1df495 100644 --- a/Jint/Native/Function/ScriptFunctionInstance.cs +++ b/Jint/Native/Function/ScriptFunctionInstance.cs @@ -11,7 +11,7 @@ namespace Jint.Native.Function { public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor { - private readonly JintFunctionDefinition _function; + private bool _isClassConstructor; /// /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2 @@ -20,8 +20,9 @@ public ScriptFunctionInstance( Engine engine, IFunction functionDeclaration, LexicalEnvironment scope, - bool strict) - : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Global) + bool strict, + ObjectInstance proto = null) + : this(engine, new JintFunctionDefinition(engine, functionDeclaration), scope, strict ? FunctionThisMode.Strict : FunctionThisMode.Global, proto) { } @@ -29,74 +30,34 @@ internal ScriptFunctionInstance( Engine engine, JintFunctionDefinition function, LexicalEnvironment scope, - FunctionThisMode thisMode) + FunctionThisMode thisMode, + ObjectInstance proto = null) : base(engine, function, scope, thisMode) { - _function = function; - - _prototype = _engine.Function.PrototypeObject; - + _prototype = proto ?? _engine.Function.PrototypeObject; _length = new LazyPropertyDescriptor(() => JsNumber.Create(function.Initialize(engine, this).Length), PropertyFlag.Configurable); - var proto = new ObjectInstanceWithConstructor(engine, this) - { - _prototype = _engine.Object.PrototypeObject - }; - - _prototypeDescriptor = new PropertyDescriptor(proto, PropertyFlag.OnlyWritable); - - if (!function.Strict && !engine._isStrict) + if (!function.Strict && !engine._isStrict && function.Function is not ArrowFunctionExpression) { DefineOwnProperty(CommonProperties.Arguments, engine._callerCalleeArgumentsThrowerConfigurable); DefineOwnProperty(CommonProperties.Caller, new PropertyDescriptor(Undefined, PropertyFlag.Configurable)); } } - // for example RavenDB wants to inspect this - public IFunction FunctionDeclaration => _function.Function; - /// /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-call-thisargument-argumentslist /// public override JsValue Call(JsValue thisArgument, JsValue[] arguments) { - // ** PrepareForOrdinaryCall ** - // var callerContext = _engine.ExecutionContext; - // Let calleeRealm be F.[[Realm]]. - // Set the Realm of calleeContext to calleeRealm. - // Set the ScriptOrModule of calleeContext to F.[[ScriptOrModule]]. - var localEnv = LexicalEnvironment.NewFunctionEnvironment(_engine, this, Undefined); - // If callerContext is not already suspended, suspend callerContext. - // Push calleeContext onto the execution context stack; calleeContext is now the running execution context. - // NOTE: Any exception objects produced after this point are associated with calleeRealm. - // Return calleeContext. - - _engine.EnterExecutionContext(localEnv, localEnv); - - // ** OrdinaryCallBindThis ** - - JsValue thisValue; - if (_thisMode == FunctionThisMode.Strict) + if (_isClassConstructor) { - thisValue = thisArgument; - } - else - { - if (thisArgument.IsNullOrUndefined()) - { - var globalEnv = _engine.GlobalEnvironment; - var globalEnvRec = (GlobalEnvironmentRecord) globalEnv._record; - thisValue = globalEnvRec.GlobalThisValue; - } - else - { - thisValue = TypeConverter.ToObject(_engine, thisArgument); - } + ExceptionHelper.ThrowTypeError(_engine, $"Class constructor {_functionDefinition.Name} cannot be invoked without 'new'"); } - var envRec = (FunctionEnvironmentRecord) localEnv._record; - envRec.BindThisValue(thisValue); - + var calleeContext = PrepareForOrdinaryCall(Undefined); + + OrdinaryCallBindThis(calleeContext, thisArgument); + // actual call var strict = _thisMode == FunctionThisMode.Strict || _engine._isStrict; @@ -104,23 +65,16 @@ public override JsValue Call(JsValue thisArgument, JsValue[] arguments) { try { - var argumentsInstance = _engine.FunctionDeclarationInstantiation( - functionInstance: this, - arguments, - localEnv); - - var result = _function.Execute(); - var value = result.GetValueOrDefault().Clone(); - argumentsInstance?.FunctionWasCalled(); + var result = OrdinaryCallEvaluateBody(arguments, calleeContext); if (result.Type == CompletionType.Throw) { - ExceptionHelper.ThrowJavaScriptException(_engine, value, result); + ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result); } if (result.Type == CompletionType.Return) { - return value; + return result.Value; } } finally @@ -132,22 +86,90 @@ public override JsValue Call(JsValue thisArgument, JsValue[] arguments) } } + internal override bool IsConstructor => + (_homeObject.IsUndefined() || _isClassConstructor) + && _functionDefinition?.Function is not ArrowFunctionExpression; + /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.2 + /// https://tc39.es/ecma262/#sec-ecmascript-function-objects-construct-argumentslist-newtarget /// public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { - var thisArgument = OrdinaryCreateFromConstructor(TypeConverter.ToObject(_engine, newTarget), _engine.Object.PrototypeObject); + var kind = _constructorKind; - var result = Call(thisArgument, arguments).TryCast(); - if (!ReferenceEquals(result, null)) + var thisArgument = Undefined; + + if (kind == ConstructorKind.Base) + { + thisArgument = OrdinaryCreateFromConstructor(newTarget, _engine.Object.PrototypeObject, static (engine, _) => new ObjectInstance(engine)); + } + + var calleeContext = PrepareForOrdinaryCall(newTarget); + + if (kind == ConstructorKind.Base) { - return result; + OrdinaryCallBindThis(calleeContext, thisArgument); } - return thisArgument; + var constructorEnv = (FunctionEnvironmentRecord) calleeContext.LexicalEnvironment._record; + + var strict = _thisMode == FunctionThisMode.Strict || _engine._isStrict; + using (new StrictModeScope(strict, force: true)) + { + try + { + var result = OrdinaryCallEvaluateBody(arguments, calleeContext); + + if (result.Type == CompletionType.Return) + { + if (result.Value is ObjectInstance oi) + { + return oi; + } + + if (kind == ConstructorKind.Base) + { + return (ObjectInstance) thisArgument!; + } + + if (!result.Value.IsUndefined()) + { + ExceptionHelper.ThrowTypeError(_engine); + } + } + else if (result.Type == CompletionType.Throw) + { + ExceptionHelper.ThrowJavaScriptException(_engine, result.Value, result); + } + } + finally + { + _engine.LeaveExecutionContext(); + } + } + + return (ObjectInstance) constructorEnv.GetThisBinding(); } + + internal void MakeConstructor(bool writableProperty = true, ObjectInstance prototype = null) + { + _constructorKind = ConstructorKind.Base; + if (prototype is null) + { + prototype = new ObjectInstanceWithConstructor(_engine, this) + { + _prototype = _engine.Object.PrototypeObject + }; + } + _prototypeDescriptor = new PropertyDescriptor(prototype, writableProperty, enumerable: false, configurable: false); + } + + internal void MakeClassConstructor() + { + _isClassConstructor = true; + } + private class ObjectInstanceWithConstructor : ObjectInstance { private PropertyDescriptor _constructor; diff --git a/Jint/Native/Iterator/IteratorInstance.cs b/Jint/Native/Iterator/IteratorInstance.cs index caf06cedaa..ef3b9cd769 100644 --- a/Jint/Native/Iterator/IteratorInstance.cs +++ b/Jint/Native/Iterator/IteratorInstance.cs @@ -329,15 +329,18 @@ private ObjectInstance IteratorNext() public void Close(CompletionType completion) { - if (!_target.TryGetValue(CommonProperties.Return, out var func)) + if (!_target.TryGetValue(CommonProperties.Return, out var func) + || func.IsNullOrUndefined()) { return; } + var callable = func as ICallable ?? ExceptionHelper.ThrowTypeError(_target.Engine, func + " is not a function"); + var innerResult = Undefined; try { - innerResult = ((ICallable) func).Call(_target, Arguments.Empty); + innerResult = callable.Call(_target, Arguments.Empty); } catch { diff --git a/Jint/Native/JsNumber.cs b/Jint/Native/JsNumber.cs index 6e951b1112..16f901995c 100644 --- a/Jint/Native/JsNumber.cs +++ b/Jint/Native/JsNumber.cs @@ -166,7 +166,7 @@ internal static JsNumber Create(long value) public override string ToString() { - return EsprimaExtensions.DoubleToString(_value); + return TypeConverter.ToString(_value); } public override bool Equals(JsValue obj) diff --git a/Jint/Native/JsValue.cs b/Jint/Native/JsValue.cs index 7623b229ad..ab4142b7b6 100644 --- a/Jint/Native/JsValue.cs +++ b/Jint/Native/JsValue.cs @@ -271,7 +271,7 @@ public Types Type : (Types) (_type & ~InternalTypes.InternalFlags); } - internal virtual bool IsConstructor => this is IConstructor; + internal virtual bool IsConstructor => false; /// /// Creates a valid instance from any instance diff --git a/Jint/Native/Map/MapConstructor.cs b/Jint/Native/Map/MapConstructor.cs index 61e5c06460..e732d89590 100644 --- a/Jint/Native/Map/MapConstructor.cs +++ b/Jint/Native/Map/MapConstructor.cs @@ -62,13 +62,17 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments) return Construct(arguments, thisObject); } + /// + /// https://tc39.es/ecma262/#sec-map-iterable + /// public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { - var map = new MapInstance(Engine) + if (newTarget.IsUndefined()) { - _prototype = PrototypeObject - }; + ExceptionHelper.ThrowTypeError(_engine); + } + var map = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, _) => new MapInstance(engine)); if (arguments.Length > 0 && !arguments[0].IsNullOrUndefined()) { var adder = map.Get("set"); diff --git a/Jint/Native/Number/NumberConstructor.cs b/Jint/Native/Number/NumberConstructor.cs index 7ddd7b7760..f81258dbd3 100644 --- a/Jint/Native/Number/NumberConstructor.cs +++ b/Jint/Native/Number/NumberConstructor.cs @@ -131,22 +131,25 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments) } /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.7.2.1 + /// https://tc39.es/ecma262/#sec-number-constructor-number-value /// - /// - /// public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { - return Construct(arguments.Length > 0 ? TypeConverter.ToNumber(arguments[0]) : 0); - } + var value = arguments.Length > 0 + ? JsNumber.Create(TypeConverter.ToNumber(arguments[0])) + : JsNumber.PositiveZero; - public NumberPrototype PrototypeObject { get; private set; } + if (newTarget.IsUndefined()) + { + return Construct(value); + } - public NumberInstance Construct(double value) - { - return Construct(JsNumber.Create(value)); + var o = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, state) => new NumberInstance(engine, (JsNumber) state), value); + return o; } + public NumberPrototype PrototypeObject { get; private set; } + public NumberInstance Construct(JsNumber value) { var instance = new NumberInstance(Engine) diff --git a/Jint/Native/Number/NumberInstance.cs b/Jint/Native/Number/NumberInstance.cs index 59232a5d8d..1134bb51ec 100644 --- a/Jint/Native/Number/NumberInstance.cs +++ b/Jint/Native/Number/NumberInstance.cs @@ -14,6 +14,12 @@ public NumberInstance(Engine engine) { } + public NumberInstance(Engine engine, JsNumber value) + : base(engine, ObjectClass.Number) + { + NumberData = value; + } + Types IPrimitiveInstance.Type => Types.Number; JsValue IPrimitiveInstance.PrimitiveValue => NumberData; diff --git a/Jint/Native/Object/ObjectConstructor.cs b/Jint/Native/Object/ObjectConstructor.cs index 1363bab453..dd4baf9dac 100644 --- a/Jint/Native/Object/ObjectConstructor.cs +++ b/Jint/Native/Object/ObjectConstructor.cs @@ -41,7 +41,7 @@ protected override void Initialize() ["entries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "entries", Entries, 1, lengthFlags), propertyFlags), ["fromEntries"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "fromEntries", FromEntries, 1, lengthFlags), propertyFlags), ["getPrototypeOf"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getPrototypeOf", GetPrototypeOf, 1), propertyFlags), - ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2), propertyFlags), + ["getOwnPropertyDescriptor"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptor", GetOwnPropertyDescriptor, 2, lengthFlags), propertyFlags), ["getOwnPropertyDescriptors"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyDescriptors", GetOwnPropertyDescriptors, 1, lengthFlags), propertyFlags), ["getOwnPropertyNames"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertyNames", GetOwnPropertyNames, 1), propertyFlags), ["getOwnPropertySymbols"] = new PropertyDescriptor(new ClrFunctionInstance(Engine, "getOwnPropertySymbols", GetOwnPropertySymbols, 1, lengthFlags), propertyFlags), @@ -153,6 +153,11 @@ public ObjectInstance Construct(JsValue[] arguments) public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { + if (!ReferenceEquals(this, newTarget) && !newTarget.IsUndefined()) + { + return OrdinaryCreateFromConstructor(newTarget, _engine.Object.PrototypeObject, (engine, state) => new ObjectInstance(engine)); + } + if (arguments.Length > 0) { var value = arguments[0]; diff --git a/Jint/Native/Object/ObjectInstance.cs b/Jint/Native/Object/ObjectInstance.cs index 397ea550e2..f15ac8e7af 100644 --- a/Jint/Native/Object/ObjectInstance.cs +++ b/Jint/Native/Object/ObjectInstance.cs @@ -342,7 +342,7 @@ private static JsValue UnwrapFromGetter(PropertyDescriptor desc, JsValue thisObj } var functionInstance = (FunctionInstance) getter; - return functionInstance._engine.Call(functionInstance, thisObject, Arguments.Empty, location: null); + return functionInstance._engine.Call(functionInstance, thisObject, Arguments.Empty, expression: null); } /// @@ -501,7 +501,7 @@ public override bool Set(JsValue property, JsValue value, JsValue receiver) } var functionInstance = (FunctionInstance) setter; - _engine.Call(functionInstance, receiver, new[] { value }, location: null); + _engine.Call(functionInstance, receiver, new[] { value }, expression: null); return true; } @@ -622,7 +622,7 @@ public bool DefinePropertyOrThrow(JsValue property, PropertyDescriptor desc) { if (!DefineOwnProperty(property, desc)) { - ExceptionHelper.ThrowTypeError(_engine); + ExceptionHelper.ThrowTypeError(_engine, "Cannot redefine property: " + property); } return true; @@ -1103,7 +1103,7 @@ public virtual JsValue PreventExtensions() return JsBoolean.True; } - protected virtual ObjectInstance GetPrototypeOf() + protected internal virtual ObjectInstance GetPrototypeOf() { return _prototype; } @@ -1158,6 +1158,15 @@ public virtual bool SetPrototypeOf(JsValue value) return true; } + /// + /// https://tc39.es/ecma262/#sec-createmethodproperty + /// + internal virtual bool CreateMethodProperty(JsValue p, JsValue v) + { + var newDesc = new PropertyDescriptor(v, PropertyFlag.NonEnumerable); + return DefineOwnProperty(p, newDesc); + } + /// /// https://tc39.es/ecma262/#sec-createdatapropertyorthrow /// @@ -1199,13 +1208,13 @@ internal static ICallable GetMethod(Engine engine, JsValue v, JsValue p) internal void CopyDataProperties( ObjectInstance target, - HashSet processedProperties) + HashSet excludedItems) { var keys = GetOwnPropertyKeys(); for (var i = 0; i < keys.Count; i++) { var key = keys[i]; - if (processedProperties == null || !processedProperties.Contains(key)) + if (excludedItems == null || !excludedItems.Contains(key)) { var desc = GetOwnProperty(key); if (desc.Enumerable) diff --git a/Jint/Native/Object/ObjectPrototype.cs b/Jint/Native/Object/ObjectPrototype.cs index 2e1d9db0a2..d305ccfe4c 100644 --- a/Jint/Native/Object/ObjectPrototype.cs +++ b/Jint/Native/Object/ObjectPrototype.cs @@ -96,11 +96,8 @@ private JsValue ToLocaleString(JsValue thisObject, JsValue[] arguments) } /// - /// http://www.ecma-international.org/ecma-262/5.1/#sec-15.2.4.2 + /// https://tc39.es/ecma262/#sec-object.prototype.tostring /// - /// - /// - /// public JsValue ToObjectString(JsValue thisObject, JsValue[] arguments) { if (thisObject.IsUndefined()) diff --git a/Jint/Native/Proxy/ProxyConstructor.cs b/Jint/Native/Proxy/ProxyConstructor.cs index e127faa28d..b7f430b907 100644 --- a/Jint/Native/Proxy/ProxyConstructor.cs +++ b/Jint/Native/Proxy/ProxyConstructor.cs @@ -54,7 +54,7 @@ protected override void Initialize() SetProperties(properties); } - protected override ObjectInstance GetPrototypeOf() + protected internal override ObjectInstance GetPrototypeOf() { return _engine.Function.Prototype; } diff --git a/Jint/Native/Proxy/ProxyInstance.cs b/Jint/Native/Proxy/ProxyInstance.cs index 6b194b4795..8ac54175af 100644 --- a/Jint/Native/Proxy/ProxyInstance.cs +++ b/Jint/Native/Proxy/ProxyInstance.cs @@ -46,7 +46,12 @@ public JsValue Call(JsValue thisObject, JsValue[] arguments) return result; } - return ((ICallable) _target).Call(thisObject, arguments); + if (!(_target is ICallable callable)) + { + return ExceptionHelper.ThrowTypeError(_engine, _target + " is not a function"); + } + + return callable.Call(thisObject, arguments); } public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) @@ -77,7 +82,9 @@ public override bool IsArray() } internal override bool IsConstructor => - _handler.TryGetValue(TrapConstruct, out var handlerFunction) && handlerFunction is IConstructor; + _handler != null + && _handler.TryGetValue(TrapConstruct, out var handlerFunction) + && handlerFunction is IConstructor; public override JsValue Get(JsValue property, JsValue receiver) { @@ -137,17 +144,7 @@ public override List GetOwnPropertyKeys(Types types) } - if (extensibleTarget && targetNonconfigurableKeys.Count == 0) - { - return trapResult; - } - - var uncheckedResultKeys = new HashSet(); - foreach (var jsValue in trapResult) - { - uncheckedResultKeys.Add(jsValue); - } - + var uncheckedResultKeys = new HashSet(trapResult); for (var i = 0; i < targetNonconfigurableKeys.Count; i++) { var key = targetNonconfigurableKeys[i]; @@ -392,7 +389,7 @@ public override bool Extensible } } - protected override ObjectInstance GetPrototypeOf() + protected internal override ObjectInstance GetPrototypeOf() { if (!TryCallHandler(TrapGetProtoTypeOf, new [] { _target }, out var handlerProto )) { diff --git a/Jint/Native/RegExp/RegExpConstructor.cs b/Jint/Native/RegExp/RegExpConstructor.cs index 5828d3c321..f813e77bd2 100644 --- a/Jint/Native/RegExp/RegExpConstructor.cs +++ b/Jint/Native/RegExp/RegExpConstructor.cs @@ -97,7 +97,7 @@ public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) f = flags; } - var r = RegExpAlloc(); + var r = RegExpAlloc(newTarget); return RegExpInitialize(r, p, f); } @@ -137,40 +137,13 @@ private ObjectInstance RegExpInitialize(RegExpInstance r, JsValue pattern, JsVal return r; } - private RegExpInstance RegExpAlloc() + private RegExpInstance RegExpAlloc(JsValue newTarget) { - var r = new RegExpInstance(Engine) - { - _prototype = PrototypeObject - }; - return r; - } - - public RegExpInstance Construct(string regExp, Engine engine) - { - var r = new RegExpInstance(Engine); - r._prototype = PrototypeObject; - - var scanner = new Scanner(regExp, new ParserOptions { AdaptRegexp = true }); - var body = (string)scanner.ScanRegExpBody().Value; - var flags = (string)scanner.ScanRegExpFlags().Value; - r.Value = scanner.TestRegExp(body, flags); - - var timeout = engine.Options._RegexTimeoutInterval; - if (timeout.Ticks > 0) - { - r.Value = new Regex(r.Value.ToString(), r.Value.Options); - } - - r.Flags = flags; - r.Source = string.IsNullOrEmpty(body) ? "(?:)" : body; - - RegExpInitialize(r); - + var r = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static(engine, value) => new RegExpInstance(engine)); return r; } - public RegExpInstance Construct(Regex regExp, string flags, Engine engine) + public RegExpInstance Construct(Regex regExp, string flags) { var r = new RegExpInstance(Engine); r._prototype = PrototypeObject; diff --git a/Jint/Native/Set/SetConstructor.cs b/Jint/Native/Set/SetConstructor.cs index 9b238dcd50..7646659787 100644 --- a/Jint/Native/Set/SetConstructor.cs +++ b/Jint/Native/Set/SetConstructor.cs @@ -62,12 +62,17 @@ public override JsValue Call(JsValue thisObject, JsValue[] arguments) return Construct(arguments, thisObject); } + /// + /// https://tc39.es/ecma262/#sec-set-iterable + /// public ObjectInstance Construct(JsValue[] arguments, JsValue newTarget) { - var set = new SetInstance(Engine) + if (newTarget.IsUndefined()) { - _prototype = PrototypeObject - }; + ExceptionHelper.ThrowTypeError(_engine); + } + + var set = OrdinaryCreateFromConstructor(newTarget, PrototypeObject, static (engine, _) => new SetInstance(engine)); if (arguments.Length > 0 && !arguments[0].IsNullOrUndefined()) { var adderValue = set.Get("add"); diff --git a/Jint/Native/Symbol/GlobalSymbolRegistry.cs b/Jint/Native/Symbol/GlobalSymbolRegistry.cs index bb4467665c..35ed43ea16 100644 --- a/Jint/Native/Symbol/GlobalSymbolRegistry.cs +++ b/Jint/Native/Symbol/GlobalSymbolRegistry.cs @@ -4,6 +4,7 @@ namespace Jint.Native.Symbol { public class GlobalSymbolRegistry { + public static readonly JsSymbol AsyncIterator = new JsSymbol("Symbol.asyncIterator"); public static readonly JsSymbol HasInstance = new JsSymbol("Symbol.hasInstance"); public static readonly JsSymbol IsConcatSpreadable = new JsSymbol("Symbol.isConcatSpreadable"); public static readonly JsSymbol Iterator = new JsSymbol("Symbol.iterator"); diff --git a/Jint/Native/Symbol/SymbolConstructor.cs b/Jint/Native/Symbol/SymbolConstructor.cs index f52688f2c2..5bee40c519 100644 --- a/Jint/Native/Symbol/SymbolConstructor.cs +++ b/Jint/Native/Symbol/SymbolConstructor.cs @@ -58,7 +58,8 @@ protected override void Initialize() ["split"] = new PropertyDescriptor(GlobalSymbolRegistry.Split, propertyFlags), ["toPrimitive"] = new PropertyDescriptor(GlobalSymbolRegistry.ToPrimitive, propertyFlags), ["toStringTag"] = new PropertyDescriptor(GlobalSymbolRegistry.ToStringTag, propertyFlags), - ["unscopables"] = new PropertyDescriptor(GlobalSymbolRegistry.Unscopables, propertyFlags) + ["unscopables"] = new PropertyDescriptor(GlobalSymbolRegistry.Unscopables, propertyFlags), + ["asyncIterator"] = new PropertyDescriptor(GlobalSymbolRegistry.AsyncIterator, propertyFlags) }; SetProperties(properties); } diff --git a/Jint/Pooling/ReferencePool.cs b/Jint/Pooling/ReferencePool.cs index 94096fc090..fd65af7c5c 100644 --- a/Jint/Pooling/ReferencePool.cs +++ b/Jint/Pooling/ReferencePool.cs @@ -18,12 +18,12 @@ public ReferencePool() private static Reference Factory() { - return new Reference(JsValue.Undefined, JsString.Empty, false); + return new Reference(JsValue.Undefined, JsString.Empty, false, null); } - public Reference Rent(JsValue baseValue, JsValue name, bool strict) + public Reference Rent(JsValue baseValue, JsValue name, bool strict, JsValue thisValue) { - return _pool.Allocate().Reassign(baseValue, name, strict); + return _pool.Allocate().Reassign(baseValue, name, strict, thisValue); } public void Return(Reference reference) diff --git a/Jint/Runtime/CallStack/CallStackElement.cs b/Jint/Runtime/CallStack/CallStackElement.cs index 213f259a49..2cbfc8893e 100644 --- a/Jint/Runtime/CallStack/CallStackElement.cs +++ b/Jint/Runtime/CallStack/CallStackElement.cs @@ -1,5 +1,9 @@ -using Esprima; +#nullable enable + +using Esprima; +using Esprima.Ast; using Jint.Native.Function; +using Jint.Runtime.Interpreter.Expressions; namespace Jint.Runtime.CallStack { @@ -7,18 +11,34 @@ internal readonly struct CallStackElement { public CallStackElement( FunctionInstance function, - Location? location) + JintExpression expression) { Function = function; - Location = location; + Expression = expression; } public readonly FunctionInstance Function; - public readonly Location? Location; + public readonly JintExpression? Expression; + + public Location Location => + Expression?._expression.Location ?? ((Node?) Function._functionDefinition?.Function)?.Location ?? default; + + public NodeList? Arguments => + Function._functionDefinition?.Function.Params; public override string ToString() { - return TypeConverter.ToString(Function?.Get(CommonProperties.Name)); + var name = TypeConverter.ToString(Function.Get(CommonProperties.Name)); + + if (string.IsNullOrWhiteSpace(name)) + { + if (Expression is not null) + { + name = JintExpression.ToString(Expression._expression); + } + } + + return name ?? "(anonymous)"; } } } diff --git a/Jint/Runtime/CallStack/JintCallStack.cs b/Jint/Runtime/CallStack/JintCallStack.cs index 59df1c7754..3ea079d2fd 100644 --- a/Jint/Runtime/CallStack/JintCallStack.cs +++ b/Jint/Runtime/CallStack/JintCallStack.cs @@ -77,34 +77,37 @@ static void AppendLocation( StringBuilder sb, string shortDescription, Location loc, - in NodeList arguments) + in NodeList? arguments) { sb - .Append(" at ") - .Append(shortDescription); + .Append(" at"); - if (arguments.Count > 0) + if (!string.IsNullOrWhiteSpace(shortDescription)) { - sb.Append(" ("); + sb + .Append(" ") + .Append(shortDescription); } - for (var index = 0; index < arguments.Count; index++) + if (arguments is not null) { - if (index != 0) + // it's a function + sb.Append(" ("); + for (var index = 0; index < arguments.Value.Count; index++) { - sb.Append(", "); - } + if (index != 0) + { + sb.Append(", "); + } - var arg = arguments[index]; - sb.Append(GetPropertyKey(arg)); - } - - if (arguments.Count > 0) - { - sb.Append(") "); + var arg = arguments.Value[index]; + sb.Append(GetPropertyKey(arg)); + } + sb.Append(")"); } sb + .Append(" ") .Append(loc.Source) .Append(":") .Append(loc.Start.Line) @@ -119,9 +122,8 @@ static void AppendLocation( var index = _stack._size - 1; var element = index >= 0 ? _stack[index] : (CallStackElement?) null; var shortDescription = element?.ToString() ?? ""; - var arguments = element?.Function._functionDefinition?.Function.Params ?? new NodeList(); - AppendLocation(sb.Builder, shortDescription, location, arguments); + AppendLocation(sb.Builder, shortDescription, location, element?.Arguments); location = element?.Location ?? default; index--; @@ -130,9 +132,8 @@ static void AppendLocation( { element = index >= 0 ? _stack[index] : null; shortDescription = element?.ToString() ?? ""; - arguments = element?.Function._functionDefinition?.Function.Params ?? new NodeList(); - AppendLocation(sb.Builder, shortDescription, location, arguments); + AppendLocation(sb.Builder, shortDescription, location, element?.Arguments); location = element?.Location ?? default; index--; diff --git a/Jint/Runtime/Environments/EnvironmentRecord.cs b/Jint/Runtime/Environments/EnvironmentRecord.cs index af5b72cc5a..0cb0b083b1 100644 --- a/Jint/Runtime/Environments/EnvironmentRecord.cs +++ b/Jint/Runtime/Environments/EnvironmentRecord.cs @@ -104,6 +104,8 @@ public override bool Equals(JsValue other) public abstract JsValue GetThisBinding(); + public JsValue NewTarget { get; protected set; } + /// /// Helper to cache JsString/Key when environments use different lookups. /// diff --git a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs index 73a347be8d..9a4eb236bb 100644 --- a/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/FunctionEnvironmentRecord.cs @@ -24,9 +24,7 @@ private enum ThisBindingStatus private JsValue _thisValue; private ThisBindingStatus _thisBindingStatus; - private readonly FunctionInstance _functionObject; - private readonly JsValue _homeObject = Undefined; - private readonly JsValue _newTarget; + internal readonly FunctionInstance _functionObject; public FunctionEnvironmentRecord( Engine engine, @@ -34,8 +32,8 @@ public FunctionEnvironmentRecord( JsValue newTarget) : base(engine) { _functionObject = functionObject; - _newTarget = newTarget; - if (functionObject is ArrowFunctionInstance) + NewTarget = newTarget; + if (functionObject._functionDefinition.Function is ArrowFunctionExpression) { _thisBindingStatus = ThisBindingStatus.Lexical; } @@ -49,7 +47,7 @@ public FunctionEnvironmentRecord( public override bool HasThisBinding() => _thisBindingStatus != ThisBindingStatus.Lexical; public override bool HasSuperBinding() => - _thisBindingStatus != ThisBindingStatus.Lexical && !_homeObject.IsUndefined(); + _thisBindingStatus != ThisBindingStatus.Lexical && !_functionObject._homeObject.IsUndefined(); public override JsValue WithBaseObject() { @@ -78,12 +76,12 @@ public override JsValue GetThisBinding() public JsValue GetSuperBase() { - return _homeObject.IsUndefined() + var home = _functionObject._homeObject; + return home.IsUndefined() ? Undefined - : ((ObjectInstance) _homeObject).Prototype; + : ((ObjectInstance) home).GetPrototypeOf(); } - // optimization to have logic near record internal structures. internal void InitializeParameters( diff --git a/Jint/Runtime/ExceptionHelper.cs b/Jint/Runtime/ExceptionHelper.cs index 694932fbbe..34574fad87 100644 --- a/Jint/Runtime/ExceptionHelper.cs +++ b/Jint/Runtime/ExceptionHelper.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Jint.Native; using Jint.Runtime.CallStack; @@ -14,6 +15,7 @@ public static T ThrowSyntaxError(Engine engine, string message = null) return default; } + [DoesNotReturn] public static void ThrowSyntaxError(Engine engine, string message = null) { throw new JavaScriptException(engine.SyntaxError, message); @@ -25,21 +27,25 @@ public static T ThrowArgumentException(string message = null) return default; } + [DoesNotReturn] public static void ThrowArgumentException(string message = null) { ThrowArgumentException(message, null); } + [DoesNotReturn] public static void ThrowArgumentException(string message, string paramName) { throw new ArgumentException(message, paramName); } + [DoesNotReturn] public static void ThrowReferenceError(Engine engine, Reference reference) { ThrowReferenceError(engine, reference?.GetReferencedName()?.ToString()); } + [DoesNotReturn] public static void ThrowReferenceError(Engine engine, string name) { var message = name != null ? name + " is not defined" : null; @@ -62,6 +68,7 @@ public static T ThrowTypeError(Engine engine, string message = null, Exceptio return default; } + [DoesNotReturn] public static void ThrowTypeError(Engine engine, string message = null, Exception exception = null) { throw new JavaScriptException(engine.TypeError, message, exception); @@ -72,16 +79,19 @@ public static T ThrowRangeError(Engine engine, string message = null) throw new JavaScriptException(engine.RangeError, message); } + [DoesNotReturn] public static void ThrowRangeError(Engine engine, string message = null) { throw new JavaScriptException(engine.RangeError, message); } + [DoesNotReturn] public static void ThrowUriError(Engine engine) { throw new JavaScriptException(engine.UriError); } + [DoesNotReturn] public static void ThrowNotImplementedException(string message = null) { throw new NotImplementedException(message); @@ -102,21 +112,25 @@ public static T ThrowArgumentOutOfRangeException(string paramName, string mes throw new ArgumentOutOfRangeException(paramName, message); } + [DoesNotReturn] public static void ThrowArgumentOutOfRangeException(string paramName, string message) { throw new ArgumentOutOfRangeException(paramName, message); } + [DoesNotReturn] public static void ThrowTimeoutException() { throw new TimeoutException(); } + [DoesNotReturn] public static void ThrowStatementsCountOverflowException() { throw new StatementsCountOverflowException(); } + [DoesNotReturn] public static void ThrowArgumentOutOfRangeException() { throw new ArgumentOutOfRangeException(); @@ -127,26 +141,31 @@ public static T ThrowNotSupportedException(string message = null) throw new NotSupportedException(message); } + [DoesNotReturn] public static void ThrowNotSupportedException(string message = null) { throw new NotSupportedException(message); } + [DoesNotReturn] public static void ThrowInvalidOperationException(string message = null) { throw new InvalidOperationException(message); } + [DoesNotReturn] public static void ThrowJavaScriptException(Engine engine, JsValue value, in Completion result) { throw new JavaScriptException(value).SetCallstack(engine, result.Location); } + [DoesNotReturn] public static void ThrowRecursionDepthOverflowException(JintCallStack currentStack, string currentExpressionReference) { throw new RecursionDepthOverflowException(currentStack, currentExpressionReference); } + [DoesNotReturn] public static void ThrowArgumentNullException(string paramName) { throw new ArgumentNullException(paramName); @@ -157,6 +176,7 @@ public static T ThrowArgumentNullException(string paramName) throw new ArgumentNullException(paramName); } + [DoesNotReturn] public static void ThrowMeaningfulException(Engine engine, TargetInvocationException exception) { var meaningfulException = exception.InnerException ?? exception; @@ -168,21 +188,25 @@ public static void ThrowMeaningfulException(Engine engine, TargetInvocationExcep throw meaningfulException; } + [DoesNotReturn] public static void ThrowError(Engine engine, string message) { throw new JavaScriptException(engine.Error, message); } + [DoesNotReturn] public static void ThrowPlatformNotSupportedException(string message) { throw new PlatformNotSupportedException(message); } + [DoesNotReturn] public static void ThrowMemoryLimitExceededException(string message) { throw new MemoryLimitExceededException(message); } + [DoesNotReturn] public static void ThrowExecutionCanceledException() { throw new ExecutionCanceledException(); diff --git a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs index 3bab70c3c1..0890cef89e 100644 --- a/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/BindingPatternAssignmentExpression.cs @@ -291,7 +291,7 @@ private static void HandleObjectPattern(Engine engine, ObjectPattern pattern, Js sourceKey = identifier.Name; } - processedProperties?.Add(sourceKey.ToString()); + processedProperties?.Add(sourceKey); if (p.Value is AssignmentPattern assignmentPattern) { source.TryGetValue(sourceKey, out var value); diff --git a/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs index 9bc493a7be..54f1c7a711 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintArrowFunctionExpression.cs @@ -1,6 +1,5 @@ using Esprima.Ast; using Jint.Native.Function; -using Jint.Runtime.Environments; namespace Jint.Runtime.Interpreter.Expressions { @@ -16,14 +15,15 @@ public JintArrowFunctionExpression(Engine engine, IFunction function) protected override object EvaluateInternal() { - var funcEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _engine.ExecutionContext.LexicalEnvironment); + var scope = _engine.ExecutionContext.LexicalEnvironment; - var closure = new ArrowFunctionInstance( + var closure = new ScriptFunctionInstance( _engine, _function, - funcEnv, - _function.Strict); - + scope, + FunctionThisMode.Lexical, + proto: _engine.Function.PrototypeObject); + return closure; } } diff --git a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs index 005e5df85c..ea3e0182e8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs @@ -1,6 +1,7 @@ using Esprima.Ast; using Jint.Native; using Jint.Native.Function; +using Jint.Native.Object; using Jint.Runtime.Environments; using Jint.Runtime.References; @@ -11,24 +12,24 @@ internal sealed class JintCallExpression : JintExpression private CachedArgumentsHolder _cachedArguments; private bool _cached; - private readonly JintExpression _calleeExpression; + private JintExpression _calleeExpression; private bool _hasSpreads; public JintCallExpression(Engine engine, CallExpression expression) : base(engine, expression) { _initialized = false; - _calleeExpression = Build(engine, expression.Callee); } protected override void Initialize() { var expression = (CallExpression) _expression; + _calleeExpression = Build(_engine, expression.Callee); var cachedArgumentsHolder = new CachedArgumentsHolder { JintArguments = new JintExpression[expression.Arguments.Count] }; - bool CanSpread(Node e) + static bool CanSpread(Node e) { return e?.Type == Nodes.SpreadElement || e is AssignmentExpression ae && ae.Right?.Type == Nodes.SpreadElement; @@ -67,33 +68,55 @@ bool CanSpread(Node e) } protected override object EvaluateInternal() + { + return _calleeExpression is JintSuperExpression + ? SuperCall() + : Call(); + } + + private object SuperCall() + { + var thisEnvironment = (FunctionEnvironmentRecord) _engine.GetThisEnvironment(); + var newTarget = GetNewTarget(thisEnvironment); + var func = GetSuperConstructor(thisEnvironment); + if (!func.IsConstructor) + { + ExceptionHelper.ThrowTypeError(_engine, "Not a constructor"); + } + + var argList = ArgumentListEvaluation(); + var result = ((IConstructor) func).Construct(argList, newTarget); + var thisER = (FunctionEnvironmentRecord) _engine.GetThisEnvironment(); + return thisER.BindThisValue(result); + } + + /// + /// https://tc39.es/ecma262/#sec-getsuperconstructor + /// + private ObjectInstance GetSuperConstructor(FunctionEnvironmentRecord thisEnvironment) + { + var envRec = thisEnvironment; + var activeFunction = envRec._functionObject; + var superConstructor = activeFunction.GetPrototypeOf(); + return superConstructor; + } + + /// + /// https://tc39.es/ecma262/#sec-getnewtarget + /// + private JsValue GetNewTarget(FunctionEnvironmentRecord thisEnvironment) + { + return thisEnvironment.NewTarget; + } + + private object Call() { var callee = _calleeExpression.Evaluate(); var expression = (CallExpression) _expression; // todo: implement as in http://www.ecma-international.org/ecma-262/5.1/#sec-11.2.4 - var cachedArguments = _cachedArguments; - var arguments = System.Array.Empty(); - if (_cached) - { - arguments = cachedArguments.CachedArguments; - } - else - { - if (cachedArguments.JintArguments.Length > 0) - { - if (_hasSpreads) - { - arguments = BuildArgumentsWithSpreads(cachedArguments.JintArguments); - } - else - { - arguments = _engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length); - BuildArguments(cachedArguments.JintArguments, arguments); - } - } - } + var arguments = ArgumentListEvaluation(); var func = _engine.GetValue(callee, false); var r = callee as Reference; @@ -107,7 +130,8 @@ protected override object EvaluateInternal() { if (!_engine._referenceResolver.TryGetCallable(_engine, callee, out func)) { - ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function"); + ExceptionHelper.ThrowTypeError(_engine, + r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function"); } } @@ -134,13 +158,13 @@ protected override object EvaluateInternal() // is it a direct call to eval ? http://www.ecma-international.org/ecma-262/5.1/#sec-15.1.2.1.1 if (r.GetReferencedName() == CommonProperties.Eval && callable is EvalFunctionInstance instance) { - var value = instance.Call(thisObject, arguments, true); + var value = instance.PerformEval(arguments, true); _engine._referencePool.Return(r); return value; } } - var result = _engine.Call(callable, thisObject, arguments, expression.Location); + var result = _engine.Call(callable, thisObject, arguments, _calleeExpression); if (!_cached && arguments.Length > 0) { @@ -151,6 +175,33 @@ protected override object EvaluateInternal() return result; } + private JsValue[] ArgumentListEvaluation() + { + var cachedArguments = _cachedArguments; + var arguments = System.Array.Empty(); + if (_cached) + { + arguments = cachedArguments.CachedArguments; + } + else + { + if (cachedArguments.JintArguments.Length > 0) + { + if (_hasSpreads) + { + arguments = BuildArgumentsWithSpreads(cachedArguments.JintArguments); + } + else + { + arguments = _engine._jsValueArrayPool.RentArray(cachedArguments.JintArguments.Length); + BuildArguments(cachedArguments.JintArguments, arguments); + } + } + } + + return arguments; + } + private class CachedArgumentsHolder { internal JintExpression[] JintArguments; diff --git a/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs new file mode 100644 index 0000000000..9330ebc1f6 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs @@ -0,0 +1,21 @@ +using Esprima.Ast; +using Jint.Native.Function; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal sealed class JintClassExpression : JintExpression + { + public JintClassExpression(Engine engine, ClassExpression expression) : base(engine, expression) + { + } + + protected override object EvaluateInternal() + { + var env = _engine.ExecutionContext.LexicalEnvironment; + var expression = (ClassExpression) _expression; + var classDefinition = new ClassDefinition(expression.Id, expression.SuperClass, expression.Body); + var closure = classDefinition.BuildConstructor(_engine, env); + return closure; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index d5bb6c4997..6ca96040c8 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -1,9 +1,12 @@ +#nullable enable + using System.Runtime.CompilerServices; using Esprima.Ast; using Jint.Native; using Jint.Native.Array; using Jint.Native.Iterator; using Jint.Native.Number; +using Jint.Native.Symbol; namespace Jint.Runtime.Interpreter.Expressions { @@ -52,6 +55,40 @@ protected virtual void Initialize() protected abstract object EvaluateInternal(); + /// + /// If we'd get Esprima source, we would just refer to it, but this makes error messages easier to decipher. + /// + internal string SourceText => ToString(_expression) ?? "*unknown*"; + + internal static string? ToString(Expression expression) + { + while (true) + { + if (expression is Literal literal) + { + return EsprimaExtensions.LiteralKeyToString(literal); + } + + if (expression is Identifier identifier) + { + return identifier.Name; + } + + if (expression is MemberExpression memberExpression) + { + return ToString(memberExpression.Object) + "." + ToString(memberExpression.Property); + } + + if (expression is CallExpression callExpression) + { + expression = callExpression.Callee; + continue; + } + + return null; + } + } + protected internal static JintExpression Build(Engine engine, Expression expression) { return expression.Type switch @@ -82,6 +119,8 @@ protected internal static JintExpression Build(Engine engine, Expression express Nodes.SpreadElement => new JintSpreadExpression(engine, (SpreadElement) expression), Nodes.TemplateLiteral => new JintTemplateLiteralExpression(engine, (TemplateLiteral) expression), Nodes.TaggedTemplateExpression => new JintTaggedTemplateExpression(engine, (TaggedTemplateExpression) expression), + Nodes.ClassExpression => new JintClassExpression(engine, (ClassExpression) expression), + Nodes.Super => new JintSuperExpression(engine, (Super) expression), _ => ExceptionHelper.ThrowArgumentOutOfRangeException(nameof(expression), $"unsupported expression type '{expression.Type}'") }; } @@ -321,8 +360,9 @@ protected JsValue[] BuildArgumentsWithSpreads(JintExpression[] jintExpressions) if (jintExpression is JintSpreadExpression jse) { jse.GetValueAndCheckIterator(out var objectInstance, out var iterator); - // optimize for array - if (objectInstance is ArrayInstance ai) + // optimize for array unless someone has touched the iterator + if (objectInstance is ArrayInstance ai + && ReferenceEquals(ai.Get(GlobalSymbolRegistry.Iterator), _engine.Array.PrototypeObject._originalIteratorFunction)) { var length = ai.GetLength(); for (uint j = 0; j < length; ++j) diff --git a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs index b0ed18516c..d862f0ede7 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintFunctionExpression.cs @@ -19,8 +19,8 @@ protected override object EvaluateInternal() var funcEnv = LexicalEnvironment.NewDeclarativeEnvironment(_engine, _engine.ExecutionContext.LexicalEnvironment); var functionThisMode = _function.Strict || _engine._isStrict - ? FunctionInstance.FunctionThisMode.Strict - : FunctionInstance.FunctionThisMode.Global; + ? FunctionThisMode.Strict + : FunctionThisMode.Global; var closure = new ScriptFunctionInstance( _engine, @@ -28,6 +28,8 @@ protected override object EvaluateInternal() funcEnv, functionThisMode); + closure.MakeConstructor(); + if (_function.Name != null) { var envRec = (DeclarativeEnvironmentRecord) funcEnv._record; diff --git a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs index 28fe2253fd..23b6c6c567 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintIdentifierExpression.cs @@ -29,7 +29,7 @@ protected override object EvaluateInternal() ? temp : JsValue.Undefined; - return _engine._referencePool.Rent(identifierEnvironment, _expressionName.StringValue, strict); + return _engine._referencePool.Rent(identifierEnvironment, _expressionName.StringValue, strict, thisValue: null); } public override JsValue GetValue() @@ -51,7 +51,7 @@ public override JsValue GetValue() out _, out var value) ? value ?? ExceptionHelper.ThrowReferenceError(_engine, _expressionName.Key.Name + " has not been initialized") - : _engine.GetValue(_engine._referencePool.Rent(JsValue.Undefined, _expressionName.StringValue, strict), true); + : _engine.GetValue(_engine._referencePool.Rent(JsValue.Undefined, _expressionName.StringValue, strict, thisValue: null), true); } } } \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs index c6ff0f9210..2e28b9a741 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintLiteralExpression.cs @@ -66,7 +66,7 @@ private JsValue ResolveValue() var expression = (Literal) _expression; if (expression.TokenType == TokenType.RegularExpression) { - return _engine.RegExp.Construct((System.Text.RegularExpressions.Regex) expression.Value, expression.Regex.Flags, _engine); + return _engine.RegExp.Construct((System.Text.RegularExpressions.Regex) expression.Value, expression.Regex.Flags); } return JsValue.FromObject(_engine, expression.Value); diff --git a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs index c9eb518c2f..d5c57efed3 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintMemberExpression.cs @@ -11,11 +11,7 @@ namespace Jint.Runtime.Interpreter.Expressions internal sealed class JintMemberExpression : JintExpression { private MemberExpression _memberExpression; - private JintExpression _objectExpression; - private JintIdentifierExpression _objectIdentifierExpression; - private JintThisExpression _objectThisExpression; - private JintExpression _propertyExpression; private JsValue _determinedProperty; @@ -28,8 +24,6 @@ protected override void Initialize() { _memberExpression = (MemberExpression) _expression; _objectExpression = Build(_engine, _memberExpression.Object); - _objectIdentifierExpression = _objectExpression as JintIdentifierExpression; - _objectThisExpression = _objectExpression as JintThisExpression; if (!_memberExpression.Computed) { @@ -48,25 +42,32 @@ protected override void Initialize() protected override object EvaluateInternal() { + JsValue actualThis = null; string baseReferenceName = null; JsValue baseValue = null; var isStrictModeCode = StrictModeScope.IsStrictModeCode; - if (_objectIdentifierExpression != null) + if (_objectExpression is JintIdentifierExpression identifierExpression) { - baseReferenceName = _objectIdentifierExpression._expressionName.Key.Name; + baseReferenceName = identifierExpression._expressionName.Key.Name; var strict = isStrictModeCode; var env = _engine.ExecutionContext.LexicalEnvironment; LexicalEnvironment.TryGetIdentifierEnvironmentWithBindingValue( env, - _objectIdentifierExpression._expressionName, + identifierExpression._expressionName, strict, out _, out baseValue); } - else if (_objectThisExpression != null) + else if (_objectExpression is JintThisExpression thisExpression) + { + baseValue = thisExpression.GetValue(); + } + else if (_objectExpression is JintSuperExpression) { - baseValue = _objectThisExpression.GetValue(); + var env = (FunctionEnvironmentRecord) _engine.GetThisEnvironment(); + actualThis = env.GetThisBinding(); + baseValue = env.GetSuperBase(); } if (baseValue is null) @@ -96,7 +97,7 @@ protected override object EvaluateInternal() ? property : TypeConverter.ToPropertyKey(property); - return _engine._referencePool.Rent(baseValue, propertyKey, isStrictModeCode); + return _engine._referencePool.Rent(baseValue, propertyKey, isStrictModeCode, thisValue: actualThis); } } } \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs index ff1c10bd66..6bfaa49425 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintNewExpression.cs @@ -36,6 +36,9 @@ protected override void Initialize() protected override object EvaluateInternal() { + // todo: optimize by defining a common abstract class or interface + var jsValue = _calleeExpression.GetValue(); + JsValue[] arguments; if (_jintArguments.Length == 0) { @@ -51,15 +54,13 @@ protected override object EvaluateInternal() BuildArguments(_jintArguments, arguments); } - // todo: optimize by defining a common abstract class or interface - var jsValue = _calleeExpression.GetValue(); - if (!(jsValue is IConstructor callee)) + if (!jsValue.IsConstructor) { - return ExceptionHelper.ThrowTypeError(_engine, "The object can't be used as constructor."); + ExceptionHelper.ThrowTypeError(_engine, _calleeExpression.SourceText + " is not a constructor"); } // construct the new instance using the Function's constructor method - var instance = callee.Construct(arguments, jsValue); + var instance = _engine.Construct((IConstructor) jsValue, arguments, jsValue, _calleeExpression); _engine._jsValueArrayPool.ReturnArray(arguments); diff --git a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs index fe5f99d054..2f34fe7f72 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintObjectExpression.cs @@ -1,3 +1,5 @@ +#nullable enable + using Esprima.Ast; using Jint.Collections; using Jint.Native; @@ -8,12 +10,12 @@ namespace Jint.Runtime.Interpreter.Expressions { /// - /// http://www.ecma-international.org/ecma-262/#sec-object-initializer + /// https://tc39.es/ecma262/#sec-object-initializer /// internal sealed class JintObjectExpression : JintExpression { private JintExpression[] _valueExpressions = System.Array.Empty(); - private ObjectProperty[] _properties = System.Array.Empty(); + private ObjectProperty?[] _properties = System.Array.Empty(); // check if we can do a shortcut when all are object properties // and don't require duplicate checking @@ -21,17 +23,17 @@ internal sealed class JintObjectExpression : JintExpression private class ObjectProperty { - internal readonly string _key; - private JsString _keyJsString; + internal readonly string? _key; + private JsString? _keyJsString; internal readonly Property _value; - public ObjectProperty(string key, Property property) + public ObjectProperty(string? key, Property property) { _key = key; _value = property; } - public JsString KeyJsString => _keyJsString ??= _key != null ? JsString.Create(_key) : null; + public JsString? KeyJsString => _keyJsString ??= _key != null ? JsString.Create(_key) : null; } public JintObjectExpression(Engine engine, ObjectExpression expression) : base(engine, expression) @@ -54,7 +56,7 @@ protected override void Initialize() for (var i = 0; i < _properties.Length; i++) { - string propName = null; + string? propName = null; var property = expression.Properties[i]; if (property is Property p) { @@ -120,12 +122,15 @@ private object BuildObjectFast() var objectProperty = _properties[i]; var valueExpression = _valueExpressions[i]; var propValue = valueExpression.GetValue().Clone(); - properties[objectProperty._key] = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable); + properties[objectProperty!._key] = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable); } obj.SetProperties(properties); return obj; } + /// + /// https://tc39.es/ecma262/#sec-object-initializer-runtime-semantics-propertydefinitionevaluation + /// private object BuildObjectNormal() { var obj = _engine.Object.Construct(_properties.Length); @@ -146,45 +151,48 @@ private object BuildObjectNormal() } var property = objectProperty._value; - var propName = objectProperty.KeyJsString ?? property.GetKey(_engine); - - PropertyDescriptor propDesc; + if (property.Method) + { + var methodDef = property.DefineMethod(obj); + methodDef.Closure.SetFunctionName(methodDef.Key); + var desc = new PropertyDescriptor(methodDef.Closure, PropertyFlag.ConfigurableEnumerableWritable); + obj.DefinePropertyOrThrow(methodDef.Key, desc); + continue; + } + + var propName = objectProperty.KeyJsString ?? property.GetKey(_engine); if (property.Kind == PropertyKind.Init || property.Kind == PropertyKind.Data) { var expr = _valueExpressions[i]; - var propValue = expr.GetValue().Clone(); + JsValue propValue = expr.GetValue().Clone(); if (expr._expression.IsFunctionWithName()) { - var functionInstance = (FunctionInstance) propValue; - functionInstance.SetFunctionName(propName); + var closure = (FunctionInstance) propValue; + closure.SetFunctionName(propName); } - propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable); + + var propDesc = new PropertyDescriptor(propValue, PropertyFlag.ConfigurableEnumerableWritable); + obj.DefinePropertyOrThrow(propName, propDesc); } else if (property.Kind == PropertyKind.Get || property.Kind == PropertyKind.Set) { var function = property.Value as IFunction ?? ExceptionHelper.ThrowSyntaxError(_engine); - var functionInstance = new ScriptFunctionInstance( + var closure = new ScriptFunctionInstance( _engine, function, _engine.ExecutionContext.LexicalEnvironment, - isStrictModeCode - ); - functionInstance.SetFunctionName(propName); - functionInstance._prototypeDescriptor = null; - - propDesc = new GetSetPropertyDescriptor( - get: property.Kind == PropertyKind.Get ? functionInstance : null, - set: property.Kind == PropertyKind.Set ? functionInstance : null, + isStrictModeCode); + closure.SetFunctionName(propName, property.Kind == PropertyKind.Get ? "get" : "set"); + + var propDesc = new GetSetPropertyDescriptor( + get: property.Kind == PropertyKind.Get ? closure : null, + set: property.Kind == PropertyKind.Set ? closure : null, PropertyFlag.Enumerable | PropertyFlag.Configurable); + + obj.DefinePropertyOrThrow(propName, propDesc); } - else - { - return ExceptionHelper.ThrowArgumentOutOfRangeException(); - } - - obj.DefineOwnProperty(propName, propDesc); } return obj; diff --git a/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs new file mode 100644 index 0000000000..5564332de7 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintSuperExpression.cs @@ -0,0 +1,20 @@ +using Esprima.Ast; +using Jint.Runtime.Environments; + +namespace Jint.Runtime.Interpreter.Expressions +{ + internal class JintSuperExpression : JintExpression + { + public JintSuperExpression(Engine engine, Super expression) : base(engine, expression) + { + } + + protected override object EvaluateInternal() + { + var envRec = (FunctionEnvironmentRecord) _engine.GetThisEnvironment(); + var activeFunction = envRec._functionObject; + var superConstructor = activeFunction.GetPrototypeOf(); + return superConstructor; + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs index 20d6568cb1..c35f71ae6b 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintUnaryExpression.cs @@ -79,6 +79,11 @@ protected override object EvaluateInternal() if (r.IsPropertyReference()) { + if (r.IsSuperReference()) + { + ExceptionHelper.ThrowReferenceError(_engine, r); + } + var o = TypeConverter.ToObject(_engine, r.GetBase()); var deleteStatus = o.Delete(r.GetReferencedName()); if (!deleteStatus && r.IsStrictReference()) diff --git a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs index 17fd808e60..f294f044bf 100644 --- a/Jint/Runtime/Interpreter/JintFunctionDefinition.cs +++ b/Jint/Runtime/Interpreter/JintFunctionDefinition.cs @@ -128,7 +128,7 @@ private State DoInitialize(FunctionInstance functionInstance) const string ParameterNameArguments = "arguments"; state.ArgumentsObjectNeeded = true; - if (functionInstance._thisMode == FunctionInstance.FunctionThisMode.Lexical) + if (functionInstance._thisMode == FunctionThisMode.Lexical) { state.ArgumentsObjectNeeded = false; } diff --git a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs new file mode 100644 index 0000000000..9c4969e9da --- /dev/null +++ b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs @@ -0,0 +1,32 @@ +#nullable enable + +using Esprima.Ast; +using Jint.Native.Function; + +namespace Jint.Runtime.Interpreter.Statements +{ + internal sealed class JintClassDeclarationStatement : JintStatement + { + private readonly ClassDefinition _classDefinition; + + public JintClassDeclarationStatement(Engine engine, ClassDeclaration classDeclaration) : base(engine, classDeclaration) + { + _classDefinition = new ClassDefinition(className: classDeclaration.Id, classDeclaration.SuperClass, classDeclaration.Body); + } + + protected override Completion ExecuteInternal() + { + var env = _engine.ExecutionContext.LexicalEnvironment; + var F = _classDefinition.BuildConstructor(_engine, env); + + var classBinding = _classDefinition._className; + if (classBinding != null) + { + env._record.CreateMutableBinding(classBinding); + env._record.InitializeBinding(classBinding, F); + } + + return new Completion(CompletionType.Normal, null, null, Location); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs index 11485f1351..e93ae890f3 100644 --- a/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintForInForOfStatement.cs @@ -255,11 +255,13 @@ private Completion BodyEvaluation( if (result.Type == CompletionType.Break && (result.Identifier == null || result.Identifier == _statement?.LabelSet?.Name)) { + completionType = CompletionType.Normal; return new Completion(CompletionType.Normal, v, null, Location); } if (result.Type != CompletionType.Continue || (result.Identifier != null && result.Identifier != _statement?.LabelSet?.Name)) { + completionType = result.Type; if (result.Type != CompletionType.Normal) { return result; @@ -276,7 +278,18 @@ private Completion BodyEvaluation( { if (close) { - iteratorRecord.Close(completionType); + try + { + iteratorRecord.Close(completionType); + } + catch + { + // if we already have and exception, use it + if (completionType != CompletionType.Throw) + { + throw; + } + } } _engine.UpdateLexicalEnvironment(oldEnv); } diff --git a/Jint/Runtime/Interpreter/Statements/JintStatement.cs b/Jint/Runtime/Interpreter/Statements/JintStatement.cs index 2c5d6a82d6..636238bd4e 100644 --- a/Jint/Runtime/Interpreter/Statements/JintStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintStatement.cs @@ -83,6 +83,7 @@ protected internal static JintStatement Build(Engine engine, Statement statement Nodes.WithStatement => new JintWithStatement(engine, (WithStatement) statement), Nodes.DebuggerStatement => new JintDebuggerStatement(engine, (DebuggerStatement) statement), Nodes.Program => new JintScript(engine, statement as Script ?? ExceptionHelper.ThrowArgumentException