From 2cf094c309cd6edc371f6b1a714519ed90cba80a Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Sat, 5 Mar 2022 10:54:06 +0200 Subject: [PATCH] Module handling fixes and improvements --- Jint.Tests.Test262/Language/ModuleTestHost.cs | 51 ----- Jint.Tests.Test262/Language/ModuleTests.cs | 49 +---- Jint.Tests.Test262/Test262Test.cs | 12 +- Jint.Tests/Runtime/ModuleTests.cs | 19 ++ Jint/Engine.Modules.cs | 32 ++- Jint/Engine.cs | 29 +-- Jint/EsprimaExtensions.cs | 10 +- Jint/HoistingScope.cs | 2 +- Jint/ModuleBuilder.cs | 2 +- Jint/Native/Function/ClassDefinition.cs | 4 +- Jint/Options.cs | 5 + .../Environments/ModuleEnvironmentRecord.cs | 13 +- Jint/Runtime/Host.cs | 42 ++-- .../Expressions/JintClassExpression.cs | 2 +- .../Interpreter/Expressions/JintExpression.cs | 1 + .../Expressions/JintImportExpression.cs | 50 +++++ .../JintClassDeclarationStatement.cs | 4 +- .../JintExportDefaultDeclaration.cs | 74 ++++++- .../Statements/JintExportNamedDeclaration.cs | 25 +-- .../Statements/JintImportDeclaration.cs | 12 +- Jint/Runtime/Modules/DefaultModuleLoader.cs | 5 + Jint/Runtime/Modules/JsModule.cs | 189 +++++++++--------- Jint/Runtime/Modules/ModuleNamespace.cs | 65 +++++- 23 files changed, 421 insertions(+), 276 deletions(-) delete mode 100644 Jint.Tests.Test262/Language/ModuleTestHost.cs create mode 100644 Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs diff --git a/Jint.Tests.Test262/Language/ModuleTestHost.cs b/Jint.Tests.Test262/Language/ModuleTestHost.cs deleted file mode 100644 index 89b5604eae..0000000000 --- a/Jint.Tests.Test262/Language/ModuleTestHost.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Reflection; -using System.IO; -using Jint.Native; -using Jint.Native.Object; -using Jint.Runtime; -using Jint.Runtime.Interop; - -namespace Jint.Tests.Test262.Language -{ - // Hacky way to get objects from assert.js and sta.js into the module context - internal sealed class ModuleTestHost : Host - { - private readonly static Dictionary _staticValues = new(); - - static ModuleTestHost() - { - var assemblyPath = new Uri(typeof(ModuleTestHost).GetTypeInfo().Assembly.Location).LocalPath; - var assemblyDirectory = new FileInfo(assemblyPath).Directory; - - var basePath = assemblyDirectory.Parent.Parent.Parent.FullName; - - var engine = new Engine(); - var assertSource = File.ReadAllText(Path.Combine(basePath, "harness", "assert.js")); - var staSource = File.ReadAllText(Path.Combine(basePath, "harness", "sta.js")); - - engine.Execute(assertSource); - engine.Execute(staSource); - - _staticValues["assert"] = engine.GetValue("assert"); - _staticValues["Test262Error"] = engine.GetValue("Test262Error"); - _staticValues["$ERROR"] = engine.GetValue("$ERROR"); - _staticValues["$DONOTEVALUATE"] = engine.GetValue("$DONOTEVALUATE"); - - _staticValues["print"] = new ClrFunctionInstance(engine, "print", (thisObj, args) => TypeConverter.ToString(args.At(0))); - } - - protected override ObjectInstance CreateGlobalObject(Realm realm) - { - var globalObj = base.CreateGlobalObject(realm); - - foreach (var key in _staticValues.Keys) - { - globalObj.FastAddProperty(key, _staticValues[key], true, true, true); - } - - return globalObj; - } - } -} diff --git a/Jint.Tests.Test262/Language/ModuleTests.cs b/Jint.Tests.Test262/Language/ModuleTests.cs index b28be07f76..122fdb312f 100644 --- a/Jint.Tests.Test262/Language/ModuleTests.cs +++ b/Jint.Tests.Test262/Language/ModuleTests.cs @@ -1,10 +1,4 @@ -using Jint.Runtime; -using Jint.Runtime.Modules; -using System; -using System.IO; -using System.Reflection; using Xunit; -using Xunit.Sdk; namespace Jint.Tests.Test262.Language; @@ -15,7 +9,7 @@ public class ModuleTests : Test262Test [MemberData(nameof(SourceFiles), "language\\module-code", true, Skip = "Skipped")] protected void ModuleCode(SourceFile sourceFile) { - RunModuleTest(sourceFile); + RunTestInternal(sourceFile); } [Theory(DisplayName = "language\\export")] @@ -23,7 +17,7 @@ protected void ModuleCode(SourceFile sourceFile) [MemberData(nameof(SourceFiles), "language\\export", true, Skip = "Skipped")] protected void Export(SourceFile sourceFile) { - RunModuleTest(sourceFile); + RunTestInternal(sourceFile); } [Theory(DisplayName = "language\\import")] @@ -31,43 +25,6 @@ protected void Export(SourceFile sourceFile) [MemberData(nameof(SourceFiles), "language\\import", true, Skip = "Skipped")] protected void Import(SourceFile sourceFile) { - RunModuleTest(sourceFile); - } - - private static void RunModuleTest(SourceFile sourceFile) - { - if (sourceFile.Skip) - { - return; - } - - var code = sourceFile.Code; - - var options = new Options(); - options.Host.Factory = _ => new ModuleTestHost(); - options.EnableModules(Path.Combine(BasePath, "test")); - - var engine = new Engine(options); - - var negative = code.IndexOf("negative:", StringComparison.OrdinalIgnoreCase) != -1; - string lastError = null; - - try - { - engine.LoadModule(sourceFile.FullPath); - } - catch (JavaScriptException ex) - { - lastError = ex.ToString(); - } - catch (Exception ex) - { - lastError = ex.ToString(); - } - - if (!negative && !string.IsNullOrWhiteSpace(lastError)) - { - throw new XunitException(lastError); - } + RunTestInternal(sourceFile); } } \ No newline at end of file diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index 018f054f34..3b90df4501 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -101,6 +101,7 @@ protected void RunTestCode(string fileName, string code, bool strict) var engine = new Engine(cfg => cfg .LocalTimeZone(_pacificTimeZone) .Strict(strict) + .EnableModules(Path.Combine(BasePath, "test", Path.GetDirectoryName(fileName)!)) ); engine.Execute(Sources["sta.js"]); @@ -151,6 +152,8 @@ protected void RunTestCode(string fileName, string code, bool strict) engine.Execute(Sources[file.Trim()]); } } + + var module = Regex.IsMatch(code, @"flags:\s*?\[.*?module.*?]"); if (code.IndexOf("propertyHelper.js", StringComparison.OrdinalIgnoreCase) != -1) { @@ -162,7 +165,14 @@ protected void RunTestCode(string fileName, string code, bool strict) bool negative = code.IndexOf("negative:", StringComparison.Ordinal) > -1; try { - engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseScript()); + if (module) + { + engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseModule()); + } + else + { + engine.Execute(new JavaScriptParser(code, new ParserOptions(fileName)).ParseScript()); + } } catch (JavaScriptException j) { diff --git a/Jint.Tests/Runtime/ModuleTests.cs b/Jint.Tests/Runtime/ModuleTests.cs index 3a9fe13b68..0863f876e4 100644 --- a/Jint.Tests/Runtime/ModuleTests.cs +++ b/Jint.Tests/Runtime/ModuleTests.cs @@ -1,8 +1,10 @@ #if(NET6_0_OR_GREATER) using System.IO; using System.Reflection; +using Jint.Runtime.Modules; #endif using System; +using Esprima; using Jint.Native; using Jint.Runtime; using Xunit; @@ -234,6 +236,23 @@ public void CanImportFromFile() Assert.Equal("John Doe", result); } + + [Fact] + public void CanReferenceModuleImportFromFileInInlineScript() + { + var engine = new Engine(options => options.EnableModules(GetBasePath())); + const string script = @" + import { formatName } from './modules/format-name.js' + formatName('John', 'Doe'); + "; + + var ex = Assert.Throws(() => engine.Evaluate(script)); + Assert.Equal("Cannot use import/export statements outside a module", ex.Message); + + var value = engine.Evaluate(script, SourceType.Module); + Assert.IsType(value); + } + private static string GetBasePath() { diff --git a/Jint/Engine.Modules.cs b/Jint/Engine.Modules.cs index bd646684bd..f332532576 100644 --- a/Jint/Engine.Modules.cs +++ b/Jint/Engine.Modules.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; using Esprima; +using Esprima.Ast; using Jint.Native; using Jint.Native.Object; using Jint.Native.Promise; @@ -41,7 +42,7 @@ internal JsModule LoadModule(string? referencingModuleLocation, string specifier if (_builders.TryGetValue(specifier, out var moduleBuilder)) { var parsedModule = moduleBuilder.Parse(); - module = new JsModule(this, _host.CreateRealm(), parsedModule, null, false); + module = new JsModule(this, Realm, parsedModule, null, false); // Early link is required because we need to bind values before returning module.Link(); moduleBuilder.BindExportedValues(module); @@ -50,7 +51,7 @@ internal JsModule LoadModule(string? referencingModuleLocation, string specifier else { var parsedModule = ModuleLoader.LoadModule(this, moduleResolution); - module = new JsModule(this, _host.CreateRealm(), parsedModule, moduleResolution.Uri?.LocalPath, false); + module = new JsModule(this, Realm, parsedModule, moduleResolution.Uri?.LocalPath, false); } _modules[moduleResolution.Key] = module; @@ -79,13 +80,30 @@ public void AddModule(string specifier, ModuleBuilder moduleBuilder) public ObjectInstance ImportModule(string specifier) { - var moduleResolution = ModuleLoader.Resolve(null, specifier); + var moduleResolution = ModuleLoader.Resolve(referencingModuleLocation: null, specifier); if (!_modules.TryGetValue(moduleResolution.Key, out var module)) { module = LoadModule(null, specifier); } + return Execute(specifier, module); + } + + public Engine Execute(Module module) + { + Evaluate(module); + return this; + } + + public JsValue Evaluate(Module module) + { + var jsModule = new JsModule(this, Realm, module, location: null, async: false); + return Execute(specifier: null, jsModule); + } + + private ObjectInstance Execute(string? specifier, JsModule module) + { if (module.Status == ModuleStatus.Unlinked) { module.Link(); @@ -108,11 +126,7 @@ public ObjectInstance ImportModule(string specifier) } } - if (evaluationResult == null) - { - ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise"); - } - else if (evaluationResult is not PromiseInstance promise) + if (evaluationResult is not PromiseInstance promise) { ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}"); } @@ -129,7 +143,7 @@ public ObjectInstance ImportModule(string specifier) if (module.Status == ModuleStatus.Evaluated) { // TODO what about callstack and thrown exceptions? - RunAvailableContinuations(_eventLoop); + RunAvailableContinuations(); return JsModule.GetModuleNamespace(module); } diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 80dce79603..113c2303dc 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -239,20 +239,24 @@ public void ResetCallStack() CallStack.Clear(); } - public JsValue Evaluate(string source) - => Execute(source, DefaultParserOptions)._completionValue; + public JsValue Evaluate(string source, SourceType sourceType = SourceType.Script) + => Evaluate(source, DefaultParserOptions, sourceType); - public JsValue Evaluate(string source, ParserOptions parserOptions) - => Execute(source, parserOptions)._completionValue; + public JsValue Evaluate(string source, ParserOptions parserOptions, SourceType sourceType = SourceType.Script) + => sourceType == SourceType.Script + ? Evaluate(new JavaScriptParser(source, parserOptions).ParseScript()) + : Evaluate(new JavaScriptParser(source, parserOptions).ParseModule()); public JsValue Evaluate(Script script) => Execute(script)._completionValue; - public Engine Execute(string source) - => Execute(source, DefaultParserOptions); + public Engine Execute(string source, SourceType sourceType = SourceType.Script) + => Execute(source, DefaultParserOptions, sourceType); - public Engine Execute(string source, ParserOptions parserOptions) - => Execute(new JavaScriptParser(source, parserOptions).ParseScript()); + public Engine Execute(string source, ParserOptions parserOptions, SourceType sourceType = SourceType.Script) + => sourceType == SourceType.Script + ? Execute(new JavaScriptParser(source, parserOptions).ParseScript()) + : Execute(new JavaScriptParser(source, parserOptions).ParseModule()); public Engine Execute(Script script) { @@ -284,7 +288,7 @@ Engine DoInvoke() } // TODO what about callstack and thrown exceptions? - RunAvailableContinuations(_eventLoop); + RunAvailableContinuations(); _completionValue = result.GetValueOrDefault(); @@ -320,7 +324,7 @@ public ManualPromise RegisterPromise() Action SettleWith(FunctionInstance settle) => value => { settle.Call(JsValue.Undefined, new[] {value}); - RunAvailableContinuations(_eventLoop); + RunAvailableContinuations(); }; return new ManualPromise(promise, SettleWith(resolve), SettleWith(reject)); @@ -331,10 +335,9 @@ internal void AddToEventLoop(Action continuation) _eventLoop.Events.Enqueue(continuation); } - - private static void RunAvailableContinuations(EventLoop loop) + internal void RunAvailableContinuations() { - var queue = loop.Events; + var queue = _eventLoop.Events; while (true) { diff --git a/Jint/EsprimaExtensions.cs b/Jint/EsprimaExtensions.cs index 498f11ea30..749b0dac2e 100644 --- a/Jint/EsprimaExtensions.cs +++ b/Jint/EsprimaExtensions.cs @@ -305,7 +305,7 @@ internal static void GetExportEntries(this ExportDeclaration export, List GetExportNames(StatementListItem declaration) break; case VariableDeclaration variableDeclaration: - var declarators = variableDeclaration.Declarations; - foreach (var declarator in declarators) + ref readonly var declarators = ref variableDeclaration.Declarations; + for (var i = 0; i < declarators.Count; i++) { + var declarator = declarators[i]; var varName = declarator.Id.As()?.Name; if (varName is not null) { diff --git a/Jint/HoistingScope.cs b/Jint/HoistingScope.cs index f5197120b1..1e8977948a 100644 --- a/Jint/HoistingScope.cs +++ b/Jint/HoistingScope.cs @@ -30,7 +30,7 @@ private HoistingScope( } public static HoistingScope GetProgramLevelDeclarations( - Script script, + Program script, bool collectVarNames = false, bool collectLexicalNames = false) { diff --git a/Jint/ModuleBuilder.cs b/Jint/ModuleBuilder.cs index 00f6ac3d52..2616e40035 100644 --- a/Jint/ModuleBuilder.cs +++ b/Jint/ModuleBuilder.cs @@ -16,7 +16,7 @@ public sealed class ModuleBuilder private readonly List _sourceRaw = new(); private readonly Dictionary _exports = new(); - public ModuleBuilder(Engine engine) + internal ModuleBuilder(Engine engine) { _engine = engine; } diff --git a/Jint/Native/Function/ClassDefinition.cs b/Jint/Native/Function/ClassDefinition.cs index fefec0c377..23e2b627ef 100644 --- a/Jint/Native/Function/ClassDefinition.cs +++ b/Jint/Native/Function/ClassDefinition.cs @@ -35,11 +35,11 @@ static MethodDefinition CreateConstructorMethodDefinition(string source) } public ClassDefinition( - Identifier? className, + string? className, Expression? superClass, ClassBody body) { - _className = className?.Name; + _className = className; _superClass = superClass; _body = body; } diff --git a/Jint/Options.cs b/Jint/Options.cs index a34ec76950..8e422f16c9 100644 --- a/Jint/Options.cs +++ b/Jint/Options.cs @@ -74,6 +74,11 @@ public class Options /// public IReferenceResolver ReferenceResolver { get; set; } = DefaultReferenceResolver.Instance; + /// + /// Whether to return details of unhandled exceptions. Poses a security risk. Defaults to false. + /// + public bool IncludeExceptionDetails { get; set; } + /// /// Called by the instance that loads this /// once it is loaded. diff --git a/Jint/Runtime/Environments/ModuleEnvironmentRecord.cs b/Jint/Runtime/Environments/ModuleEnvironmentRecord.cs index df2bf7019f..c4357f1a74 100644 --- a/Jint/Runtime/Environments/ModuleEnvironmentRecord.cs +++ b/Jint/Runtime/Environments/ModuleEnvironmentRecord.cs @@ -16,18 +16,26 @@ internal ModuleEnvironmentRecord(Engine engine) : base(engine, false) { } + /// + /// https://tc39.es/ecma262/#sec-module-environment-records-getthisbinding + /// public override JsValue GetThisBinding() { return Undefined; } + /// + /// https://tc39.es/ecma262/#sec-createimportbinding + /// public void CreateImportBinding(string importName, JsModule module, string name) { _hasBindings = true; _importBindings[importName] = new IndirectBinding(module, name); } - // https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s + /// + /// https://tc39.es/ecma262/#sec-module-environment-records-getbindingvalue-n-s + /// public override JsValue GetBindingValue(string name, bool strict) { if (_importBindings.TryGetValue(name, out var indirectBinding)) @@ -50,6 +58,9 @@ internal override bool TryGetBinding(in BindingName name, bool strict, out Bindi return base.TryGetBinding(name, strict, out binding, out value); } + /// + /// https://tc39.es/ecma262/#sec-module-environment-records-hasthisbinding + /// public override bool HasThisBinding() => true; private readonly record struct IndirectBinding(JsModule Module, string BindingName); diff --git a/Jint/Runtime/Host.cs b/Jint/Runtime/Host.cs index 05a9ba4272..194c145ce3 100644 --- a/Jint/Runtime/Host.cs +++ b/Jint/Runtime/Host.cs @@ -1,3 +1,5 @@ +#nullable enable + using Jint.Native; using Jint.Native.Global; using Jint.Native.Object; @@ -11,7 +13,20 @@ namespace Jint.Runtime { public class Host { - protected Engine Engine { get; private set; } + private Engine? _engine; + + protected Engine Engine + { + get + { + if (_engine is null) + { + ExceptionHelper.ThrowInvalidOperationException("Initialize has not been called"); + } + return _engine!; + } + private set => _engine = value; + } /// /// Initializes the host. @@ -93,8 +108,6 @@ protected virtual void CreateIntrinsics(Realm realmRec) /// /// https://tc39.es/ecma262/#sec-hostensurecancompilestrings /// - /// - /// public virtual void EnsureCanCompileStrings(Realm callerRealm, Realm evalRealm) { } @@ -102,46 +115,35 @@ public virtual void EnsureCanCompileStrings(Realm callerRealm, Realm evalRealm) /// /// https://tc39.es/ecma262/#sec-hostresolveimportedmodule /// - /// - /// - /// - protected internal virtual JsModule ResolveImportedModule(JsModule referencingModule, string specifier) + protected internal virtual JsModule ResolveImportedModule(JsModule? referencingModule, string specifier) { - return Engine.LoadModule(referencingModule.Location, specifier); + return Engine.LoadModule(referencingModule?.Location, specifier); } /// /// https://tc39.es/ecma262/#sec-hostimportmoduledynamically /// - /// - /// - /// - internal virtual void ImportModuleDynamically(JsModule referencingModule, string specifier, PromiseCapability promiseCapability) + internal virtual void ImportModuleDynamically(JsModule? referencingModule, string specifier, PromiseCapability promiseCapability) { var promise = Engine.RegisterPromise(); try { - Engine.LoadModule(referencingModule.Location, specifier); + Engine.LoadModule(referencingModule?.Location, specifier); promise.Resolve(JsValue.Undefined); - } catch (JavaScriptException ex) { promise.Reject(ex.Error); } - FinishDynamicImport(referencingModule, specifier, promiseCapability, (PromiseInstance)promise.Promise); + FinishDynamicImport(referencingModule, specifier, promiseCapability, (PromiseInstance) promise.Promise); } /// /// https://tc39.es/ecma262/#sec-finishdynamicimport /// - /// - /// - /// - /// - internal virtual void FinishDynamicImport(JsModule referencingModule, string specifier, PromiseCapability promiseCapability, PromiseInstance innerPromise) + internal virtual void FinishDynamicImport(JsModule? referencingModule, string specifier, PromiseCapability promiseCapability, PromiseInstance innerPromise) { var onFulfilled = new ClrFunctionInstance(Engine, "", (thisObj, args) => { diff --git a/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs index 863c762fae..f17cce1c54 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintClassExpression.cs @@ -9,7 +9,7 @@ internal sealed class JintClassExpression : JintExpression public JintClassExpression(ClassExpression expression) : base(expression) { - _classDefinition = new ClassDefinition(expression.Id, expression.SuperClass, expression.Body); + _classDefinition = new ClassDefinition(expression.Id?.Name, expression.SuperClass, expression.Body); } protected override ExpressionResult EvaluateInternal(EvaluationContext context) diff --git a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs index a48bf85be0..f7d9476c98 100644 --- a/Jint/Runtime/Interpreter/Expressions/JintExpression.cs +++ b/Jint/Runtime/Interpreter/Expressions/JintExpression.cs @@ -193,6 +193,7 @@ protected internal static JintExpression Build(Engine engine, Expression express Nodes.TemplateLiteral => new JintTemplateLiteralExpression((TemplateLiteral) expression), Nodes.TaggedTemplateExpression => new JintTaggedTemplateExpression((TaggedTemplateExpression) expression), Nodes.ClassExpression => new JintClassExpression((ClassExpression) expression), + Nodes.Import => new JintImportExpression((Import) expression), Nodes.Super => new JintSuperExpression((Super) expression), Nodes.MetaProperty => new JintMetaPropertyExpression((MetaProperty) expression), Nodes.ChainExpression => ((ChainExpression) expression).Expression.Type == Nodes.CallExpression diff --git a/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs b/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs new file mode 100644 index 0000000000..7e5e76c871 --- /dev/null +++ b/Jint/Runtime/Interpreter/Expressions/JintImportExpression.cs @@ -0,0 +1,50 @@ +#nullable enable + +using Esprima.Ast; +using Jint.Native.Promise; +using Jint.Runtime.Modules; + +namespace Jint.Runtime.Interpreter.Expressions; + +internal sealed class JintImportExpression : JintExpression +{ + private JintExpression _importExpression; + + public JintImportExpression(Import expression) : base(expression) + { + _initialized = false; + _importExpression = null!; + } + + protected override void Initialize(EvaluationContext context) + { + var expression = ((Import) _expression).Source; + _importExpression = Build(context.Engine, expression!); + } + + /// + /// https://tc39.es/ecma262/#sec-import-calls + /// + protected override ExpressionResult EvaluateInternal(EvaluationContext context) + { + var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location); + + var argRef = _importExpression.Evaluate(context); + context.Engine.RunAvailableContinuations(); + var value = context.Engine.GetValue(argRef.Value); + var specifier = value.UnwrapIfPromise(); + + if (specifier is ModuleNamespace) + { + // already resolved + return NormalCompletion(value); + } + + var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise); + var specifierString = TypeConverter.ToString(specifier); + + // 6.IfAbruptRejectPromise(specifierString, promiseCapability); + context.Engine._host.ImportModuleDynamically(module, specifierString, promiseCapability); + return NormalCompletion(promiseCapability.PromiseInstance); + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs index 81b8773539..8220539938 100644 --- a/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs +++ b/Jint/Runtime/Interpreter/Statements/JintClassDeclarationStatement.cs @@ -11,7 +11,7 @@ internal sealed class JintClassDeclarationStatement : JintStatement { - private JintExpression? _init; + private JintClassDeclarationStatement? _classDeclaration; + private JintFunctionDeclarationStatement? _functionDeclaration; + private JintExpression? _assignmentExpression; + private JintExpression? _simpleExpression; public JintExportDefaultDeclaration(ExportDefaultDeclaration statement) : base(statement) { @@ -15,17 +21,69 @@ public JintExportDefaultDeclaration(ExportDefaultDeclaration statement) : base(s protected override void Initialize(EvaluationContext context) { - _init = JintExpression.Build(context.Engine, (Expression)_statement.Declaration); + if (_statement.Declaration is ClassDeclaration classDeclaration) + { + _classDeclaration = new JintClassDeclarationStatement(classDeclaration); + } + else if (_statement.Declaration is FunctionDeclaration functionDeclaration) + { + _functionDeclaration = new JintFunctionDeclarationStatement(functionDeclaration); + } + else if (_statement.Declaration is AssignmentExpression assignmentExpression) + { + _assignmentExpression = JintAssignmentExpression.Build(context.Engine, assignmentExpression); + } + else + { + _simpleExpression = JintExpression.Build(context.Engine, (Expression) _statement.Declaration); + } } - // https://tc39.es/ecma262/#sec-exports-runtime-semantics-evaluation + /// + /// https://tc39.es/ecma262/#sec-exports-runtime-semantics-evaluation + /// protected override Completion ExecuteInternal(EvaluationContext context) { - var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location); - - var completion = _init?.GetValue(context) ?? Completion.Empty(); - module._environment.CreateImmutableBindingAndInitialize("*default*", true, completion.Value); + JsValue value; + if (_classDeclaration is not null) + { + value = _classDeclaration.Execute(context).GetValueOrDefault(); + } + else if (_functionDeclaration is not null) + { + value = _functionDeclaration.Execute(context).GetValueOrDefault(); + } + else if (_assignmentExpression is not null) + { + value = _assignmentExpression.GetValue(context).GetValueOrDefault(); + } + else + { + value = _simpleExpression!.GetValue(context).GetValueOrDefault(); + } + if (value is ObjectInstance oi && !oi.HasOwnProperty("name")) + { + oi.SetFunctionName("default"); + } + + var env = context.Engine.ExecutionContext.LexicalEnvironment; + InitializeBoundName("*default*", value, env); return Completion.Empty(); } -} + + /// + /// https://tc39.es/ecma262/#sec-initializeboundname + /// + private void InitializeBoundName(string name, JsValue value, EnvironmentRecord? environment) + { + if (environment is not null) + { + environment.InitializeBinding(name, value); + } + else + { + ExceptionHelper.ThrowNotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs index 89cc09f445..0b0257b925 100644 --- a/Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintExportNamedDeclaration.cs @@ -2,6 +2,7 @@ using Esprima.Ast; using Jint.Native; +using Jint.Runtime.Environments; using Jint.Runtime.Interpreter.Expressions; namespace Jint.Runtime.Interpreter.Statements; @@ -12,10 +13,7 @@ internal sealed class JintExportNamedDeclaration : JintStatement protected override Completion ExecuteInternal(EvaluationContext context) { - var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location); + var env = (ModuleEnvironmentRecord) context.Engine.ExecutionContext.LexicalEnvironment; if (_specifiers != null) { + var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location); foreach (var specifier in _specifiers) { - if (specifier.Local is not JintIdentifierExpression local || specifier.Exported is not JintIdentifierExpression exported) - { - ExceptionHelper.ThrowSyntaxError(context.Engine.Realm, "", context.LastSyntaxNode.Location); - return default; - } - - var localKey = local._expressionName.Key.Name; - var exportedKey = exported._expressionName.Key.Name; + var localKey = specifier.Local; + var exportedKey = specifier.Exported; if (localKey != exportedKey) { - module._environment.CreateImportBinding(exportedKey, module, localKey); + env.CreateImportBinding(exportedKey, module, localKey); } } } diff --git a/Jint/Runtime/Interpreter/Statements/JintImportDeclaration.cs b/Jint/Runtime/Interpreter/Statements/JintImportDeclaration.cs index 2d55f66a75..d1bbbcb5a2 100644 --- a/Jint/Runtime/Interpreter/Statements/JintImportDeclaration.cs +++ b/Jint/Runtime/Interpreter/Statements/JintImportDeclaration.cs @@ -1,7 +1,6 @@ #nullable enable using Esprima.Ast; -using Jint.Native.Promise; namespace Jint.Runtime.Interpreter.Statements; @@ -17,13 +16,8 @@ protected override void Initialize(EvaluationContext context) protected override Completion ExecuteInternal(EvaluationContext context) { - var module = context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location); - var specifier = _statement.Source.StringValue; - var promiseCapability = PromiseConstructor.NewPromiseCapability(context.Engine, context.Engine.Realm.Intrinsics.Promise); - var specifierString = TypeConverter.ToString(specifier); - - // TODO: This comment was in @lahma's code: 6.IfAbruptRejectPromise(specifierString, promiseCapability); - context.Engine._host.ImportModuleDynamically(module, specifierString, promiseCapability); - return NormalCompletion(promiseCapability.PromiseInstance); + // just to ensure module context or valid + context.Engine.GetActiveScriptOrModule().AsModule(context.Engine, context.LastSyntaxNode.Location); + return Completion.Empty(); } } diff --git a/Jint/Runtime/Modules/DefaultModuleLoader.cs b/Jint/Runtime/Modules/DefaultModuleLoader.cs index 558a313a82..e7535b293c 100644 --- a/Jint/Runtime/Modules/DefaultModuleLoader.cs +++ b/Jint/Runtime/Modules/DefaultModuleLoader.cs @@ -145,6 +145,11 @@ public Module LoadModule(Engine engine, ResolvedSpecifier resolved) ExceptionHelper.ThrowSyntaxError(engine.Realm, $"Error while loading module: error in module '{resolved.Uri.LocalPath}': {ex.Error}"); module = null; } + catch (Exception) + { + ExceptionHelper.ThrowJavaScriptException(engine, $"Could not load module {resolved.Uri?.LocalPath}", Completion.Empty()); + module = null; + } return module; } diff --git a/Jint/Runtime/Modules/JsModule.cs b/Jint/Runtime/Modules/JsModule.cs index 2fde9ee713..10a55fbe73 100644 --- a/Jint/Runtime/Modules/JsModule.cs +++ b/Jint/Runtime/Modules/JsModule.cs @@ -1,7 +1,6 @@ using System; using Esprima.Ast; using System.Collections.Generic; -using System.Linq; using Esprima; using Jint.Native; using Jint.Native.Object; @@ -90,7 +89,6 @@ internal JsModule(Engine engine, Realm realm, Module source, string location, bo out _starExportEntries); //ToDo async modules - } public string Location { get; } @@ -102,7 +100,7 @@ internal JsModule(Engine engine, Realm realm, Module source, string location, bo public static ObjectInstance GetModuleNamespace(JsModule module) { var ns = module._namespace; - if(ns is null) + if (ns is null) { var exportedNames = module.GetExportedNames(); var unambiguousNames = new List(); @@ -110,7 +108,7 @@ public static ObjectInstance GetModuleNamespace(JsModule module) { var name = exportedNames[i]; var resolution = module.ResolveExport(name); - if(resolution is not null) + if (resolution is not null) { unambiguousNames.Add(name); } @@ -143,11 +141,11 @@ internal void BindExportedValue(string name, JsValue value) /// public List GetExportedNames(List exportStarSet = null) { - exportStarSet ??= new(); + exportStarSet ??= new List(); if (exportStarSet.Contains(this)) { //Reached the starting point of an export * circularity - return new(); + return new List(); } exportStarSet.Add(this); @@ -164,7 +162,7 @@ public List GetExportedNames(List exportStarSet = null) exportedNames.Add(e.ImportName ?? e.ExportName); } - for(var i = 0; i < _starExportEntries.Count; i++) + for (var i = 0; i < _starExportEntries.Count; i++) { var e = _starExportEntries[i]; var requestedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest); @@ -188,36 +186,35 @@ public List GetExportedNames(List exportStarSet = null) /// internal ResolvedBinding ResolveExport(string exportName, List resolveSet = null) { - resolveSet ??= new(); + resolveSet ??= new List(); - for(var i = 0; i < resolveSet.Count; i++) + for (var i = 0; i < resolveSet.Count; i++) { var r = resolveSet[i]; - if(this == r.Module && exportName == r.ExportName) + if (ReferenceEquals(this, r.Module) && exportName == r.ExportName) { - //circular import request + // circular import request return null; } } - resolveSet.Add(new(this, exportName)); - for(var i = 0; i < _localExportEntries.Count; i++) + resolveSet.Add(new ExportResolveSetItem(this, exportName)); + for (var i = 0; i < _localExportEntries.Count; i++) { var e = _localExportEntries[i]; - if (exportName == (e.ImportName ?? e.ExportName)) { return new ResolvedBinding(this, e.LocalName ?? e.ExportName); } } - for(var i = 0; i < _indirectExportEntries.Count; i++) + for (var i = 0; i < _indirectExportEntries.Count; i++) { - var e = _localExportEntries[i]; - if (exportName.Equals(e.ImportName ?? e.ExportName)) + var e = _indirectExportEntries[i]; + if (exportName == (e.ImportName ?? e.ExportName)) { var importedModule = _engine._host.ResolveImportedModule(this, e.ModuleRequest); - if(e.ImportName == "*") + if (e.ImportName == "*") { return new ResolvedBinding(importedModule, "*namespace*"); } @@ -235,25 +232,25 @@ internal ResolvedBinding ResolveExport(string exportName, List 0) { ExceptionHelper.ThrowInvalidOperationException("Error while linking module: One or more modules were not linked"); } @@ -330,19 +328,20 @@ public JsValue Evaluate() var stack = new Stack(); var capability = PromiseConstructor.NewPromiseCapability(_engine, _realm.Intrinsics.Promise); - int asyncEvalOrder = 0; + var asyncEvalOrder = 0; module._topLevelCapability = capability; var result = Evaluate(module, stack, 0, ref asyncEvalOrder); - if(result.Type != CompletionType.Normal) + if (result.Type != CompletionType.Normal) { - foreach(var m in stack) + foreach (var m in stack) { m.Status = ModuleStatus.Evaluated; m._evalError = result; } - capability.Reject.Call(Undefined, new [] { result.Value }); + + capability.Reject.Call(Undefined, new[] { result.Value }); } else { @@ -358,7 +357,7 @@ public JsValue Evaluate() if (!module._asyncEvaluation) { - if(module.Status != ModuleStatus.Evaluated) + if (module.Status != ModuleStatus.Evaluated) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -366,14 +365,13 @@ public JsValue Evaluate() capability.Resolve.Call(Undefined, Array.Empty()); } - if (stack.Any()) + if (stack.Count > 0) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } } return capability.PromiseInstance; - } /// @@ -381,16 +379,16 @@ public JsValue Evaluate() /// private int Link(JsModule module, Stack stack, int index) { - if(module.Status is - ModuleStatus.Linking or - ModuleStatus.Linked or - ModuleStatus.EvaluatingAsync or - ModuleStatus.Evaluating) + if (module.Status is + ModuleStatus.Linking or + ModuleStatus.Linked or + ModuleStatus.EvaluatingAsync or + ModuleStatus.Evaluating) { return index; } - if(module.Status != ModuleStatus.Unlinked) + if (module.Status != ModuleStatus.Unlinked) { ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Module in an invalid state"); } @@ -409,7 +407,9 @@ ModuleStatus.EvaluatingAsync or //TODO: Should we link only when a module is requested? https://tc39.es/ecma262/#sec-example-cyclic-module-record-graphs Should we support retry? if (requiredModule.Status == ModuleStatus.Unlinked) + { requiredModule.Link(); + } if (requiredModule.Status != ModuleStatus.Linking && requiredModule.Status != ModuleStatus.Linked && @@ -418,20 +418,20 @@ ModuleStatus.EvaluatingAsync or ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Required module is in an invalid state: {requiredModule.Status}"); } - if(requiredModule.Status == ModuleStatus.Linking && !stack.Contains(requiredModule)) + if (requiredModule.Status == ModuleStatus.Linking && !stack.Contains(requiredModule)) { ExceptionHelper.ThrowInvalidOperationException($"Error while linking module: Required module is in an invalid state: {requiredModule.Status}"); } if (requiredModule.Status == ModuleStatus.Linking) { - module._dfsAncestorIndex = System.Math.Min(module._dfsAncestorIndex, requiredModule._dfsAncestorIndex); + module._dfsAncestorIndex = Math.Min(module._dfsAncestorIndex, requiredModule._dfsAncestorIndex); } } module.InitializeEnvironment(); - if (stack.Count(m => m == module) != 1) + if (StackReferenceCount(stack, module) != 1) { ExceptionHelper.ThrowInvalidOperationException("Error while linking module: Recursive dependency detected"); } @@ -455,7 +455,6 @@ ModuleStatus.EvaluatingAsync or } return index; - } /// @@ -463,9 +462,9 @@ ModuleStatus.EvaluatingAsync or /// private Completion Evaluate(JsModule module, Stack stack, int index, ref int asyncEvalOrder) { - if(module.Status == ModuleStatus.EvaluatingAsync || module.Status == ModuleStatus.Evaluated) + if (module.Status == ModuleStatus.EvaluatingAsync || module.Status == ModuleStatus.Evaluated) { - if(module._evalError is null) + if (module._evalError is null) { return new Completion(CompletionType.Normal, index, null, default); } @@ -473,7 +472,7 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r return module._evalError.Value; } - if(module.Status == ModuleStatus.Evaluating) + if (module.Status == ModuleStatus.Evaluating) { return new Completion(CompletionType.Normal, index, null, default); } @@ -497,7 +496,7 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r { var requiredModule = _engine._host.ResolveImportedModule(module, moduleSpecifier); var result = Evaluate(module, stack, index, ref asyncEvalOrder); - if(result.Type != CompletionType.Normal) + if (result.Type != CompletionType.Normal) { return result; } @@ -538,14 +537,14 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module is in an invalid state: {requiredModule.Status}"); } - if(requiredModule.Status == ModuleStatus.Evaluating) + if (requiredModule.Status == ModuleStatus.Evaluating) { - module._dfsAncestorIndex = System.Math.Min(module._dfsAncestorIndex, requiredModule._dfsAncestorIndex); + module._dfsAncestorIndex = Math.Min(module._dfsAncestorIndex, requiredModule._dfsAncestorIndex); } else { requiredModule = requiredModule._cycleRoot; - if(requiredModule.Status != ModuleStatus.EvaluatingAsync && requiredModule.Status != ModuleStatus.Evaluated) + if (requiredModule.Status != ModuleStatus.EvaluatingAsync && requiredModule.Status != ModuleStatus.Evaluated) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -560,7 +559,7 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r Completion completion; - if(module._pendingAsyncDependencies > 0 || module._hasTLA) + if (module._pendingAsyncDependencies > 0 || module._hasTLA) { if (module._asyncEvaluation) { @@ -583,7 +582,7 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r completion = module.Execute(); } - if(stack.Count(x => x == module) != 1) + if (StackReferenceCount(stack, module) != 1) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -593,9 +592,9 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } - if(module._dfsIndex == module._dfsAncestorIndex) + if (module._dfsIndex == module._dfsAncestorIndex) { - bool done = false; + var done = false; while (!done) { var requiredModule = stack.Pop(); @@ -616,12 +615,26 @@ private Completion Evaluate(JsModule module, Stack stack, int index, r return completion; } + private static int StackReferenceCount(Stack stack, JsModule module) + { + var count = 0; + foreach (var item in stack) + { + if (ReferenceEquals(item, module)) + { + count++; + } + } + + return count; + } + /// /// https://tc39.es/ecma262/#sec-source-text-module-record-initialize-environment /// private void InitializeEnvironment() { - for(var i = 0; i < _indirectExportEntries.Count; i++) + for (var i = 0; i < _indirectExportEntries.Count; i++) { var e = _indirectExportEntries[i]; var resolution = ResolveExport(e.ExportName); @@ -652,7 +665,7 @@ private void InitializeEnvironment() var resolution = importedModule.ResolveExport(ie.ImportName); if (resolution is null || resolution == ResolvedBinding.Ambiguous) { - ExceptionHelper.ThrowSyntaxError(_realm, "Ambigous import statement for identifier " + ie.ImportName); + ExceptionHelper.ThrowSyntaxError(_realm, "Ambiguous import statement for identifier " + ie.ImportName); } if (resolution.BindingName == "*namespace*") @@ -677,23 +690,22 @@ private void InitializeEnvironment() var hoistingScope = HoistingScope.GetModuleLevelDeclarations(_source); var varDeclarations = hoistingScope._variablesDeclarations; - var declaredVarNames = new List(); - if(varDeclarations != null) + var declaredVarNames = new HashSet(); + if (varDeclarations != null) { var boundNames = new List(); - for(var i = 0; i < varDeclarations.Count; i++) + for (var i = 0; i < varDeclarations.Count; i++) { var d = varDeclarations[i]; boundNames.Clear(); d.GetBoundNames(boundNames); - for(var j = 0; j < boundNames.Count; j++) + for (var j = 0; j < boundNames.Count; j++) { var dn = boundNames[j]; - if (!declaredVarNames.Contains(dn)) + if (declaredVarNames.Add(dn)) { env.CreateMutableBinding(dn, false); env.InitializeBinding(dn, Undefined); - declaredVarNames.Add(dn); } } } @@ -701,10 +713,10 @@ private void InitializeEnvironment() var lexDeclarations = hoistingScope._lexicalDeclarations; - if(lexDeclarations != null) + if (lexDeclarations != null) { var boundNames = new List(); - for(var i = 0; i < lexDeclarations.Count; i++) + for (var i = 0; i < lexDeclarations.Count; i++) { var d = lexDeclarations[i]; boundNames.Clear(); @@ -712,7 +724,7 @@ private void InitializeEnvironment() for (var j = 0; j < boundNames.Count; j++) { var dn = boundNames[j]; - if(d.Kind == VariableDeclarationKind.Const) + if (d.Kind == VariableDeclarationKind.Const) { env.CreateImmutableBinding(dn, true); } @@ -726,14 +738,14 @@ private void InitializeEnvironment() var functionDeclarations = hoistingScope._functionDeclarations; - if(functionDeclarations != null) + if (functionDeclarations != null) { - for(var i = 0; i < functionDeclarations.Count; i++) + for (var i = 0; i < functionDeclarations.Count; i++) { var d = functionDeclarations[i]; - var fn = d.Id.Name; + var fn = d.Id?.Name ?? "*default*"; var fd = new JintFunctionDefinition(_engine, d); - env.CreateImmutableBinding(fn, true); + env.CreateMutableBinding(fn, true); var fo = realm.Intrinsics.Function.InstantiateFunctionObject(fd, env); env.InitializeBinding(fn, fo); } @@ -750,7 +762,7 @@ private Completion Execute(PromiseCapability capability = null) var moduleContext = new ExecutionContext(this, _environment, _environment, null, _realm); if (!_hasTLA) { - using (new StrictModeScope(strict: true)) + using (new StrictModeScope(true, force: true)) { _engine.EnterExecutionContext(moduleContext); var statementList = new JintStatementList(null, _source.Body); @@ -771,7 +783,7 @@ private Completion Execute(PromiseCapability capability = null) /// private Completion ExecuteAsync() { - if((Status != ModuleStatus.Evaluating && Status != ModuleStatus.EvaluatingAsync) || !_hasTLA) + if (Status != ModuleStatus.Evaluating && Status != ModuleStatus.EvaluatingAsync || !_hasTLA) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -781,10 +793,9 @@ private Completion ExecuteAsync() var onFullfilled = new ClrFunctionInstance(_engine, "fulfilled", AsyncModuleExecutionFulfilled, 1, PropertyFlag.Configurable); var onRejected = new ClrFunctionInstance(_engine, "rejected", AsyncModuleExecutionRejected, 1, PropertyFlag.Configurable); - PromiseOperations.PerformPromiseThen(_engine, (PromiseInstance)capability.PromiseInstance, onFullfilled, onRejected, null); + PromiseOperations.PerformPromiseThen(_engine, (PromiseInstance) capability.PromiseInstance, onFullfilled, onRejected, null); return Execute(capability); - } /// @@ -792,19 +803,19 @@ private Completion ExecuteAsync() /// private void GatherAvailableAncestors(List execList) { - foreach(var m in _asyncParentModules) + foreach (var m in _asyncParentModules) { - if(!execList.Contains(m) && m._cycleRoot._evalError is null) + if (!execList.Contains(m) && m._cycleRoot._evalError is null) { - if(m.Status != ModuleStatus.EvaluatingAsync || - m._evalError is not null || - !m._asyncEvaluation || - m._pendingAsyncDependencies <= 0) + if (m.Status != ModuleStatus.EvaluatingAsync || + m._evalError is not null || + !m._asyncEvaluation || + m._pendingAsyncDependencies <= 0) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } - if(--m._pendingAsyncDependencies == 0) + if (--m._pendingAsyncDependencies == 0) { execList.Add(m); if (!m._hasTLA) @@ -821,10 +832,10 @@ m._evalError is not null || /// private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] arguments) { - var module = (JsModule)arguments.At(0); + var module = (JsModule) arguments.At(0); if (module.Status == ModuleStatus.Evaluated) { - if(module._evalError is not null) + if (module._evalError is not null) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -841,7 +852,7 @@ private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] argumen if (module._topLevelCapability is not null) { - if(module._cycleRoot is null) + if (module._cycleRoot is null) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -853,7 +864,7 @@ private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] argumen module.GatherAvailableAncestors(execList); execList.Sort((x, y) => x._asyncEvalOrder - y._asyncEvalOrder); - for(var i = 0; i < execList.Count; i++) + for (var i = 0; i < execList.Count; i++) { var m = execList[i]; if (m.Status == ModuleStatus.Evaluated && m._evalError is null) @@ -867,14 +878,14 @@ private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] argumen else { var result = m.Execute(); - if(result.Type != CompletionType.Normal) + if (result.Type != CompletionType.Normal) { AsyncModuleExecutionRejected(Undefined, new[] { m, result.Value }); } else { m.Status = ModuleStatus.Evaluated; - if(m._topLevelCapability is not null) + if (m._topLevelCapability is not null) { if (m._cycleRoot is null) { @@ -895,12 +906,12 @@ private JsValue AsyncModuleExecutionFulfilled(JsValue thisObj, JsValue[] argumen /// private JsValue AsyncModuleExecutionRejected(JsValue thisObj, JsValue[] arguments) { - JsModule module = (JsModule)arguments.At(0); - JsValue error = arguments.At(1); + var module = (JsModule) arguments.At(0); + var error = arguments.At(1); if (module.Status == ModuleStatus.Evaluated) { - if(module._evalError is null) + if (module._evalError is null) { ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } @@ -932,7 +943,7 @@ private JsValue AsyncModuleExecutionRejected(JsValue thisObj, JsValue[] argument ExceptionHelper.ThrowInvalidOperationException("Error while evaluating module: Module is in an invalid state"); } - module._topLevelCapability.Reject.Call(Undefined, new [] { error }); + module._topLevelCapability.Reject.Call(Undefined, new[] { error }); } @@ -949,4 +960,4 @@ public override string ToString() { return $"{Type}: {Location}"; } -} +} \ No newline at end of file diff --git a/Jint/Runtime/Modules/ModuleNamespace.cs b/Jint/Runtime/Modules/ModuleNamespace.cs index a836d7d1b4..a8eb37a4e7 100644 --- a/Jint/Runtime/Modules/ModuleNamespace.cs +++ b/Jint/Runtime/Modules/ModuleNamespace.cs @@ -1,6 +1,8 @@ using System.Collections.Generic; +using Jint.Collections; using Jint.Native; using Jint.Native.Object; +using Jint.Native.Symbol; using Jint.Runtime.Descriptors; namespace Jint.Runtime.Modules; @@ -20,20 +22,47 @@ public ModuleNamespace(Engine engine, JsModule module, List exports) : b _exports = new HashSet(exports); } + protected override void Initialize() + { + var symbols = new SymbolDictionary(1) + { + [GlobalSymbolRegistry.ToStringTag] = new("Module", false, false, false) + }; + SetSymbols(symbols); + } + + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-getprototypeof + /// protected internal override ObjectInstance GetPrototypeOf() => null; + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-setprototypeof-v + /// public override bool SetPrototypeOf(JsValue value) => SetImmutablePrototype(value); + /// + /// https://tc39.es/ecma262/#sec-set-immutable-prototype + /// private bool SetImmutablePrototype(JsValue value) { var current = GetPrototypeOf(); return SameValue(value, current ?? Null); } + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-isextensible + /// public override bool Extensible => false; + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-preventextensions + /// public override bool PreventExtensions() => true; + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-getownproperty-p + /// public override PropertyDescriptor GetOwnProperty(JsValue property) { if (property.IsSymbol()) @@ -52,6 +81,9 @@ public override PropertyDescriptor GetOwnProperty(JsValue property) return new PropertyDescriptor(value, true, true, false); } + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-defineownproperty-p-desc + /// public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc) { if (property.IsSymbol()) @@ -66,7 +98,22 @@ public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc return false; } - if (desc.Configurable || desc.Enumerable || desc.IsAccessorDescriptor() || !desc.Writable) + if (desc.Configurable) + { + return false; + } + + if (desc.EnumerableSet && !desc.Enumerable) + { + return false; + } + + if (desc.IsAccessorDescriptor()) + { + return false; + } + + if (desc.WritableSet && !desc.Writable) { return false; } @@ -79,6 +126,9 @@ public override bool DefineOwnProperty(JsValue property, PropertyDescriptor desc return true; } + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-hasproperty-p + /// public override bool HasProperty(JsValue property) { if (property.IsSymbol()) @@ -90,7 +140,9 @@ public override bool HasProperty(JsValue property) return _exports.Contains(p); } - // https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-get-p-receiver + /// public override JsValue Get(JsValue property, JsValue receiver) { if (property.IsSymbol()) @@ -123,11 +175,17 @@ public override JsValue Get(JsValue property, JsValue receiver) return targetEnv.GetBindingValue(binding.BindingName, true); } + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-set-p-v-receiver + /// public override bool Set(JsValue property, JsValue value, JsValue receiver) { return false; } + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-delete-p + /// public override bool Delete(JsValue property) { if (property.IsSymbol()) @@ -139,6 +197,9 @@ public override bool Delete(JsValue property) return !_exports.Contains(p); } + /// + /// https://tc39.es/ecma262/#sec-module-namespace-exotic-objects-ownpropertykeys + /// public override List GetOwnPropertyKeys(Types types = Types.String | Types.Symbol) { var keys = base.GetOwnPropertyKeys(types);