diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index d129bcec02..8f19b923b9 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,9 +7,9 @@ assignees: '' --- -**Version used** +**Version/platform/runtime used** -With which version did you encounter this behavior. Please check against 3.x beta if you haven't yet, it might have the fix already. +With which version did you encounter this behavior, which OS and NET runtime. **Describe the bug** diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..3ba13e0cec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f83678e62..3c11336d57 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,11 +17,17 @@ jobs: runs-on: ubuntu-latest steps: + + - name: Setup NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0 + - name: Checkout source code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Test - run: dotnet test --configuration Release --logger GitHubActions + run: dotnet test --configuration Release --logger "GitHubActions;summary-include-passed=false;summary-include-skipped=false" - name: Pack with dotnet run: dotnet pack Jint/Jint.csproj --output artifacts --configuration Release -p:VersionSuffix=preview-$GITHUB_RUN_NUMBER -p:ContinuousIntegrationBuild=True diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 9d8e7dc16f..65cd847842 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -1,42 +1,45 @@ -name: PR Check +name: PR on: pull_request: - branches: [ main, release/2.x, 3.x ] + branches: [ main, 3.x ] env: DOTNET_NOLOGO: true DOTNET_CLI_TELEMETRY_OPTOUT: 1 +concurrency: + group: ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.run_id }} + cancel-in-progress: true + jobs: - windows: - runs-on: windows-latest + test: - steps: - - name: Checkout source code - uses: actions/checkout@v4 + name: ${{ matrix.os.name }} - - name: Test - run: dotnet test --configuration Release --logger GitHubActions + strategy: + fail-fast: false + matrix: + os: + - { name: 'linux', image: 'ubuntu-latest' } + - { name: 'linux - ARM', image: 'ubuntu-24.04-arm' } + - { name: 'windows', image: 'windows-latest' } + - { name: 'macos', image: 'macos-latest' } - linux: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os.image }} steps: - - name: Checkout source code - uses: actions/checkout@v4 - - name: Test - run: dotnet test --configuration Release --logger GitHubActions - - macos: - runs-on: macos-14 + - name: Setup NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0 - steps: - name: Checkout source code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Test - run: dotnet test --configuration Release --logger GitHubActions + run: dotnet test --configuration Release --logger "GitHubActions;summary-include-passed=false;summary-include-skipped=false" + diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d940d7f5ee..0526bc77ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,11 +14,17 @@ jobs: runs-on: ubuntu-latest steps: + + - name: Setup NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0 + - name: Checkout source code - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Test - run: dotnet test --configuration Release --logger GitHubActions + run: dotnet test --configuration Release --logger "GitHubActions;summary-include-passed=false;summary-include-skipped=false" - name: Pack with dotnet run: | diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000000..95baacd075 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,267 @@ +# CLAUDE.md + +**Current development**: Target against main branch and compare changes against it. + +## Build & Test Commands + +### Building +```bash +# Build entire solution +dotnet build --configuration Release + +# Build specific project +dotnet build --configuration Release Jint/Jint.csproj +``` + +Important: Never use --no-build - always ensure you are working with the latest compiled code. + +### Testing +```bash +# Run all tests +dotnet test --configuration Release + +# Run specific test project +dotnet test --configuration Release Jint.Tests/Jint.Tests.csproj + +# Run specific test class +dotnet test --configuration Release --filter "FullyQualifiedName~Jint.Tests.Runtime.EngineTests" + +# Run specific test method +dotnet test --configuration Release --filter "FullyQualifiedName~Jint.Tests.Runtime.EngineTests.CanEvaluateScripts" + +# Run Test262 conformance tests +dotnet test --configuration Release Jint.Tests.Test262/Jint.Tests.Test262.csproj +``` + +Important: Run tests in Release mode for faster feedback loop. + +## Requirements + +- The project uses central package management via Directory.Packages.props + +## Architecture + +Jint follows a layered interpreter architecture: + +``` +Acornima Parser (external) → AST → Interpreter → Runtime → Interop +``` + +### Core Components + +**Engine (`Jint/Engine.cs`)** +- Main API entry point split across multiple partial files (Engine.cs, Engine.Advanced.cs, Engine.Modules.cs, etc.) +- Manages execution context stack, intrinsics (built-in objects), and realms +- Configuration via `Options` class (constraints, CLR interop, modules, debugging) + +**JsValue Type System (`Jint/Native/`)** +- `JsValue` is the abstract base class for all JavaScript values +- Primitive types: JsUndefined, JsNull, JsBoolean, JsNumber, JsString, JsBigInt, JsSymbol +- Object types: ObjectInstance and derived classes (JsArray, JsDate, JsMap, JsSet, JsPromise, etc.) +- All built-in JavaScript objects are in `Jint/Native/` organized by type (Array/, Date/, Error/, Function/, etc.) + +**Runtime (`Jint/Runtime/`)** +- **TypeConverter.cs** (1,048 lines): All JavaScript type coercion (ToPrimitive, ToNumber, ToString, ToObject, etc.) +- **Intrinsics.cs**: Singleton containing all built-in object constructors and prototypes (lazily initialized) +- **Realm.cs**: ECMAScript Realm encapsulating global environment and intrinsics +- **Environments/**: Scope chain implementation (GlobalEnvironment, FunctionEnvironment, DeclarativeEnvironment, etc.) + +**Interpreter (`Jint/Runtime/Interpreter/`)** +- **Statements/**: One handler class per statement type (JintIfStatement, JintForStatement, JintTryStatement, etc.) +- **Expressions/**: One handler class per expression type (JintBinaryExpression, JintCallExpression, JintMemberExpression, etc.) +- AST nodes are evaluated via specialized Jint* handler classes +- Caching: JintFunctionDefinition caches compiled function metadata for reuse + +**Interop (`Jint/Runtime/Interop/`)** +- **ObjectWrapper.cs**: Wraps .NET objects for JavaScript access +- **TypeReference.cs**: Exposes CLR types to JavaScript (e.g., `System.String`) +- **ClrFunction.cs**: Wraps .NET methods/delegates as JavaScript functions +- **DefaultTypeConverter.cs**: Bidirectional conversion between JS values and CLR types +- **Reflection/**: Type discovery and method binding with caching + +**Modules (`Jint/Runtime/Modules/`)** +- ES6 module system with import/export support +- ModuleLoader handles module resolution and loading +- Supports cyclic dependencies via CyclicModule +- Modules can be defined from JavaScript source or programmatically via ModuleBuilder + +### Important Patterns + +**Lazy Initialization** +- Built-in objects (Intrinsics) are lazily initialized to reduce startup time +- Properties are typically null until first access + +**Object Pooling (`Jint/Pooling/`)** +- Reference, Arguments, and JsValue arrays are pooled to reduce GC pressure +- Use `ReferencePool`, `ArgumentsInstancePool`, `JsValueArrayPool` + +**Property Key Optimization** +- `KnownKeys.cs` contains pre-computed common property names +- `HybridDictionary` switches between list and hash based on property count +- `StringDictionarySlim` for string-only dictionary keys + +**Partial Classes** +- Large classes are split: Engine.*.cs, Intrinsics.*.cs, ObjectInstance.*.cs, etc. +- Keep related functionality together when editing + +**ECMAScript Spec References** +- Code includes TC39 spec section references in comments (e.g., `// https://tc39.es/ecma262/#sec-...`) +- Maintain these references when implementing new features +- The to follow TC 39 spec when possible +- The test files are located in ..\test262\test when source code needed + +**Type Flags** +- `InternalTypes` enum enables fast type checking without casting +- Many hot paths use type flags for performance + +## ECMAScript Compliance +- Follow ECMAScript specification behavior as closely as practical +- Do not introduce non-standard language extensions +- Support both strict and sloppy mode with spec-defined differences + +## Code Conventions + +- **Namespaces**: Global usings for Acornima and Acornima.Ast defined in Directory.Build.props +- **Nullable**: Enabled across the codebase (NRT) +- **Unsafe Code**: Allowed (used for performance-critical paths) +- **Warnings as Errors**: Enabled - all warnings must be fixed +- **Analysis**: Latest analyzers enabled with EnforceCodeStyleInBuild +- **Performance**: Try to make code as perfomant as possible. + +## Testing + +- **Jint.Tests/**: Main test suite using xUnit v3 + - Organized by topic (Runtime/, Parser/, Debugger/, etc.) + - Uses FluentAssertions for readable assertions + - Embedded test scripts in Runtime/Scripts/ and Parser/Scripts/ + - Use timeout of 30 seconds when invoking test runner + +- **Jint.Tests.Test262/**: ECMAScript conformance suite using NUnit + - Official TC39 Test262 integration + - Validates spec compliance + - The test files are located in ..\test262\test when source code needed, you are always allowed to read from this directory and its sub-directories + - The error output contains the failing script, you just need to remove line numbers from the JavaScript + - Never try to fix these tests + - No need to use timeout, engine has default timeout of 30 seconds + +- **Jint.Tests.CommonScripts/**: Real-world benchmark scripts using NUnit + - Performance validation (crypto, 3D rendering, etc.) + +- **Jint.Tests.PublicInterface/**: API contract tests using xUnit v3 + - Ensures public API stability + +## Performance Considerations + +- Jint uses Acornima for parsing (external, optimized) +- Cache `Script` or `Module` instances when executing the same script repeatedly +- Prefer strict mode execution (improves performance) +- Object pooling reduces GC pressure +- Expression and function definition caching reduces re-evaluation cost +- AggressiveInlining attributes mark hot paths + +## Working with ES Features + +When implementing new ECMAScript features: + +1. **Read the TC39 spec section** for the feature +2. **Check Native/ directory** for where the built-in should live (e.g., Array/ for Array methods) +3. **Add to Intrinsics.cs** if it's a new global constructor or well-known symbol +4. **Update TypeConverter.cs** if new type coercion rules apply +5. **Add Statement/Expression handler** in Runtime/Interpreter/ if it's new syntax +6. **Reference spec sections** in code comments +7. Jint.Tests.Test262\Test262Harness.settings.json contains exclusions and inclusions for test cases + +## Testing with Jint.Repl + +The Jint REPL (Read-Eval-Print Loop) is useful for quickly testing JavaScript code during development. + +### Running the REPL +```bash +# Build the REPL +dotnet build Jint.Repl --configuration Release + +# Run the REPL +dotnet run --project Jint.Repl --configuration Release +``` + +### Command Line Options +``` +-f, --file Execute JavaScript file +-t, --timeout Set execution timeout in seconds +-h, --help Show help message +``` + +### Testing Scripts + +**Always use a timeout when testing scripts using Jint.Repl** to prevent infinite loops from hanging your session: + +```bash +# Execute a file with 10 second timeout (recommended default) +dotnet run --project Jint.Repl --configuration Release -- -f script.js -t 10 + +# Execute from stdin with timeout +echo "1 + 2" | dotnet run --project Jint.Repl --configuration Release -- -t 10 + +# Execute multiline script from stdin +cat << 'EOF' | dotnet run --project Jint.Repl --configuration Release -- -t 10 +var result = []; +for (var i = 0; i < 5; i++) { + result.push(i * 2); +} +JSON.stringify(result); +EOF +``` + +### Quick Testing Patterns + +```bash +# Test a simple expression +echo "Math.sqrt(16)" | dotnet run --project Jint.Repl --configuration Release -- -t 10 + +# Test JSON parsing +echo 'JSON.parse("[1,2,3]")' | dotnet run --project Jint.Repl --configuration Release -- -t 10 + +# Test with a temporary file +echo 'var x = 5; x * 2' > /tmp/test.js +dotnet run --project Jint.Repl --configuration Release -- -f /tmp/test.js -t 10 +``` + +### When to Use REPL vs Jint.Tryouts + +- **REPL**: Quick one-off tests, verifying behavior, testing from command line +- **Jint.Tryouts**: Complex debugging scenarios, stepping through code, testing with C# interop + +## Module System + +Modules are enabled via: +```csharp +var engine = new Engine(options => options.EnableModules(@"C:\Scripts")); +var ns = engine.Modules.Import("./my-module.js"); +``` + +Or define modules programmatically: +```csharp +engine.Modules.Add("lib", builder => builder + .ExportType() + .ExportValue("version", 1) +); +``` + +## Constraints & Security + +Execution constraints prevent resource abuse: +- Memory limits: `options.LimitMemory(4_000_000)` +- Timeout: `options.TimeoutInterval(TimeSpan.FromSeconds(4))` +- Statement limits: `options.MaxStatements(1000)` +- Custom constraints: Derive from `Constraint` base class + +CLR access is disabled by default. Enable via: +```csharp +var engine = new Engine(cfg => cfg.AllowClr()); +``` + +## AOT Compatibility + +- Jint is AOT-compatible for .NET 7.0+ targets +- See Jint.AotExample/ for AOT usage patterns +- IsAotCompatible flag set for net7.0+ in Jint.csproj diff --git a/Directory.Build.props b/Directory.Build.props index dfa5898118..cece0beea2 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -11,7 +11,7 @@ BSD-2-Clause 0 - 4.0.0 + 4.5.0 beta-$(BuildNumber) $(VersionPrefix).$(BuildNumber) diff --git a/Directory.Packages.props b/Directory.Packages.props index 22eebc3ee6..98f1872359 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,36 +1,38 @@ true - false + true - - - + + + - + - - - - - + + + + + + + - - - - - + + + + + + - - - - - + + + + + - - + \ No newline at end of file diff --git a/Jint.AotExample/Jint.AotExample.csproj b/Jint.AotExample/Jint.AotExample.csproj index 68fa8a9fd4..2eb4d1b44d 100644 --- a/Jint.AotExample/Jint.AotExample.csproj +++ b/Jint.AotExample/Jint.AotExample.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 enable false true diff --git a/Jint.Benchmark/ClassBenchmark.cs b/Jint.Benchmark/ClassBenchmark.cs new file mode 100644 index 0000000000..2359b319c8 --- /dev/null +++ b/Jint.Benchmark/ClassBenchmark.cs @@ -0,0 +1,54 @@ +using BenchmarkDotNet.Attributes; + +namespace Jint.Benchmark; + +[MemoryDiagnoser] +public class ClassBenchmark +{ + private Engine _engine; + + [IterationSetup] + public void Setup() + { + _engine = new Engine(); + _engine.Execute(""" + class A { x = 1; }; + class B extends A { y = 2; }; + class C extends B { z = 3; }; + class D extends C { x2 = 1; }; + class E extends D { x3 = 1; }; + class F extends E { x4 = 1; } + """); + _engine.Execute("const target = new F();"); + } + + [Benchmark] + public void ConstructSimple() + { + var script = Engine.PrepareScript("new A();"); + for (var i = 0; i < 400_000; ++i) + { + _engine.Evaluate(script); + } + } + + [Benchmark] + public void ConstructDeepInheritance() + { + var script = Engine.PrepareScript("new F();"); + for (var i = 0; i < 80_000; ++i) + { + _engine.Evaluate(script); + } + } + + [Benchmark] + public void GetSet() + { + var script = Engine.PrepareScript("target.x4 = 42; target.x4;"); + for (var i = 0; i < 500_000; ++i) + { + _engine.Evaluate(script); + } + } +} diff --git a/Jint.Benchmark/DictionaryBenchmark.cs b/Jint.Benchmark/DictionaryBenchmark.cs index 359af3a585..b88f6f57bd 100644 --- a/Jint.Benchmark/DictionaryBenchmark.cs +++ b/Jint.Benchmark/DictionaryBenchmark.cs @@ -7,7 +7,7 @@ namespace Jint.Benchmark; public class DictionaryBenchmark { private static readonly string[] _keys = - { + [ "some", "key and", "another", @@ -20,7 +20,7 @@ public class DictionaryBenchmark "asdadsasa", "23323232323", "asdadsada sa213" - }; + ]; [Params(0, 2, 3, 5, 8, 9, 10)] public int N { get; set; } @@ -31,7 +31,7 @@ public void HybridDictionary() var hybridDictionary = new HybridDictionary(); for (var i = 0; i < N; i++) { - hybridDictionary.Add(_keys[i], _keys); + hybridDictionary[_keys[i]] = _keys; } foreach (var key in _keys) @@ -69,4 +69,4 @@ public void StringDictionarySlim() dictionary.ContainsKey(key); } } -} \ No newline at end of file +} diff --git a/Jint.Benchmark/DromaeoBenchmark.cs b/Jint.Benchmark/DromaeoBenchmark.cs index d569e64795..029e3d3e9d 100644 --- a/Jint.Benchmark/DromaeoBenchmark.cs +++ b/Jint.Benchmark/DromaeoBenchmark.cs @@ -1,70 +1,118 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; namespace Jint.Benchmark; [MemoryDiagnoser] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByMethod)] public class DromaeoBenchmark { private static readonly Dictionary _files = new() { - {"dromaeo-3d-cube", null}, - {"dromaeo-core-eval", null}, - {"dromaeo-object-array", null}, - {"dromaeo-object-regexp", null}, - {"dromaeo-object-string", null}, - {"dromaeo-string-base64", null} + { "dromaeo-3d-cube", null }, + { "dromaeo-core-eval", null }, + { "dromaeo-object-array", null }, + { "dromaeo-object-regexp", null }, + { "dromaeo-object-string", null }, + { "dromaeo-string-base64", null } }; private readonly Dictionary> _prepared = new(); - private Engine engine; + private Engine _engine; [GlobalSetup] public void Setup() { - foreach (var fileName in _files.Keys) + foreach (var fileName in _files.Keys.ToArray()) { - var script = File.ReadAllText($"Scripts/{fileName}.js"); - _files[fileName] = script; - _prepared[fileName] = Engine.PrepareScript(script); + foreach (var suffix in new[] {"", "-modern"}) + { + var name = fileName + suffix; + var script = File.ReadAllText($"Scripts/{name}.js"); + _files[name] = script; + _prepared[name] = Engine.PrepareScript(script, name); + } } + } + + [IterationSetup] + public void IterationSetup() + { + _engine = CreteEngine(); + } - engine = new Engine() + private static Engine CreteEngine() + { + var engine = new Engine() .SetValue("log", new Action(Console.WriteLine)) .SetValue("assert", new Action(b => { })); - engine.Execute(@" -var startTest = function () { }; -var test = function (name, fn) { fn(); }; -var endTest = function () { }; -var prep = function (fn) { fn(); }; -"); + engine.Execute(""" + + var startTest = function () { }; + var test = function (name, fn) { fn(); }; + var endTest = function () { }; + var prep = function (fn) { fn(); }; + + """); + + return engine; } - [ParamsSource(nameof(FileNames))] - public string FileName { get; set; } + [Params(false, true, Priority = 50)] + public bool Modern { get; set; } - [Params(true, false)] + [Params(true, false, Priority = 100)] public bool Prepared { get; set; } - public IEnumerable FileNames() + [Benchmark] + public void CoreEval() { - foreach (var entry in _files) - { - yield return entry.Key; - } + Run("dromaeo-core-eval"); + } + + [Benchmark] + public void Cube() + { + Run("dromaeo-3d-cube"); } [Benchmark] - public void Run() + public void ObjectArray() { + Run("dromaeo-object-array"); + } + + [Benchmark] + public void ObjectRegExp() + { + Run("dromaeo-object-regexp"); + } + + [Benchmark] + public void ObjectString() + { + Run("dromaeo-object-string"); + } + + [Benchmark] + public void StringBase64() + { + Run("dromaeo-string-base64"); + } + + private void Run(string fileName) + { + var finalName = Modern ? fileName + "-modern" : fileName; + if (Prepared) { - engine.Execute(_prepared[FileName]); + _engine.Execute(_prepared[finalName]); } else { - engine.Execute(_files[FileName]); + _engine.Execute(_files[finalName], finalName); } } } diff --git a/Jint.Benchmark/EngineComparisonBenchmark.cs b/Jint.Benchmark/EngineComparisonBenchmark.cs index 4499c8b2e9..adf8d47432 100644 --- a/Jint.Benchmark/EngineComparisonBenchmark.cs +++ b/Jint.Benchmark/EngineComparisonBenchmark.cs @@ -18,15 +18,23 @@ public class EngineComparisonBenchmark { { "array-stress", null }, { "evaluation", null }, + { "evaluation-modern", null }, { "linq-js", null }, { "minimal", null }, { "stopwatch", null }, + { "stopwatch-modern", null }, { "dromaeo-3d-cube", null }, + { "dromaeo-3d-cube-modern", null }, { "dromaeo-core-eval", null }, + { "dromaeo-core-eval-modern", null }, { "dromaeo-object-array", null }, + { "dromaeo-object-array-modern", null }, { "dromaeo-object-regexp", null }, + { "dromaeo-object-regexp-modern", null }, { "dromaeo-object-string", null }, + { "dromaeo-object-string-modern", null }, { "dromaeo-string-base64", null }, + { "dromaeo-string-base64-modern", null }, }; private static readonly string _dromaeoHelpers = @" diff --git a/Jint.Benchmark/EvaluationBenchmark.cs b/Jint.Benchmark/EvaluationBenchmark.cs index e156e50b86..292d65e87a 100644 --- a/Jint.Benchmark/EvaluationBenchmark.cs +++ b/Jint.Benchmark/EvaluationBenchmark.cs @@ -5,5 +5,8 @@ namespace Jint.Benchmark; [MemoryDiagnoser] public class EvaluationBenchmark : SingleScriptBenchmark { - protected override string FileName => "evaluation.js"; + [Params(false, true)] + public bool Modern { get; set; } + + protected override string FileName => Modern ? "evaluation-modern.js" : "evaluation.js"; } diff --git a/Jint.Benchmark/FunctionBenchmark.cs b/Jint.Benchmark/FunctionBenchmark.cs new file mode 100644 index 0000000000..d1992a86aa --- /dev/null +++ b/Jint.Benchmark/FunctionBenchmark.cs @@ -0,0 +1,27 @@ +using BenchmarkDotNet.Attributes; + +namespace Jint.Benchmark; + +[MemoryDiagnoser] +public class FunctionBenchmark +{ + private readonly Engine _engine; + + public FunctionBenchmark() + { + _engine = new Engine(); + _engine.Execute("function objectPattern({ toMessage: t, code: e, reasonCode: s, syntaxPlugin: r }) { return \"MissingPlugin\" === s || \"MissingOneOfPlugins\" === s; }"); + } + + [Benchmark] + public bool ObjectPattern() + { + var b = true; + for (var i = 0; i < 100; ++i) + { + b &= _engine.Evaluate("objectPattern({\"reasonCode\": \"MissingPlugin\"})").AsBoolean(); + } + + return b; + } +} diff --git a/Jint.Benchmark/InteropLambdaBenchmark.cs b/Jint.Benchmark/InteropLambdaBenchmark.cs new file mode 100644 index 0000000000..b088bfcf61 --- /dev/null +++ b/Jint.Benchmark/InteropLambdaBenchmark.cs @@ -0,0 +1,432 @@ +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; +using Jint.Native; +using Jint.Native.Function; + +namespace Jint.Benchmark; + +[RankColumn] +[MemoryDiagnoser] +[Orderer(SummaryOrderPolicy.FastestToSlowest)] +[GroupBenchmarksBy(BenchmarkLogicalGroupRule.ByParams)] +public class InteropLambdaBenchmark +{ + private TestData[] _testArray; + private TestDataRoot _root; + private object _data; + private const string FindValue = "SomeKind22222"; + + private const int Iterations = 10; + + private Engine _engine; + + private const string ScriptInline = """ + function findIt(data, value) { + return data.array.find(x => x.value == value); + } + """; + + private const string ScriptForLoop = """ + function findIt(data, value) { + const array = data.array; + const length = array.length; + for (let i = 0; i < length; i++) { + const item = array[i]; + if (item.value == value) { + return item; + } + } + + return null; + } + """; + + private Function _forLoopFunction; + private Function _inlineFunction; + private Func _inlineCSharpFunction; + + [Params(TestDataType.ClrObject, TestDataType.Dictionary, TestDataType.JsonNode, TestDataType.JsValue)] + public TestDataType Type { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + _engine = new Engine(); + + _testArray = [new TestData("SomeKind00000"), new TestData("SomeKind1111"), new TestData(FindValue)]; + _root = new TestDataRoot(_testArray); + + if (Type == TestDataType.ClrObject) + { + _data = _root; + } + else if (Type == TestDataType.JsonNode) + { + _data = JsonSerializer.SerializeToNode(_root, JsonDefaults.JsonSerializerOptions); + } + else if (Type == TestDataType.Dictionary) + { + _data = JsonSerializer.Deserialize>(JsonSerializer.Serialize(_root, JsonDefaults.JsonSerializerOptions), JsonDefaults.JsonSerializerOptions); + } + else if (Type == TestDataType.JsValue) + { + _data = JsonSerializer.Deserialize(JsonSerializer.Serialize(_root, JsonDefaults.JsonSerializerOptions), JsonDefaults.JsonSerializerOptions); + } + + _inlineFunction = (Function) _engine.Evaluate(ScriptInline + "findIt;"); + _inlineCSharpFunction = (Func) _inlineFunction.ToObject(); + _forLoopFunction = (Function) _engine.Evaluate(ScriptForLoop + "findIt;"); + } + + [Benchmark] + public void InlineEngineInvoke() + { + for (var i = 0; i < Iterations; i++) + { + var value = _engine.Invoke(_inlineFunction!, [_data, FindValue]).ToObject(); + } + } + + [Benchmark] + public void Inline() + { + for (var i = 0; i < Iterations; i++) + { + var value = _inlineFunction!.Call(JsValue.FromObject(_engine, _data), JsValue.FromObject(_engine, FindValue)).ToObject(); + } + } + + [Benchmark] + public void InlineCSharp() + { + for (var i = 0; i < Iterations; i++) + { + var value = _inlineCSharpFunction(JsValue.Undefined, [JsValue.FromObject(_engine, _data), JsValue.FromObject(_engine, FindValue)]).ToObject(); + } + } + + [Benchmark(Baseline = true)] + public void ForLoop() + { + for (var i = 0; i < Iterations; i++) + { + var value = _forLoopFunction!.Call(JsValue.FromObject(_engine, _data), JsValue.FromObject(_engine, FindValue)).ToObject(); + } + } + + [Benchmark] + public void ForLoopEngineInvoke() + { + for (var i = 0; i < Iterations; i++) + { + var value = _engine.Invoke(_forLoopFunction!, [_data, FindValue]).ToObject(); + } + } +} + +public class TestDataRoot +{ + public TestData[] array { get; set; } + + public TestDataRoot(TestData[] array) + { + this.array = array; + } +} + + +public record TestData(string value); + +public sealed class DictionaryStringObjectJsonConverter : JsonConverter> +{ + public override Dictionary Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var dictionary = new Dictionary(JsonDefaults.DictionaryCapacity); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return dictionary; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + string propertyName = reader.GetString(); + + reader.Read(); + + dictionary[propertyName] = ReadValue(ref reader, options); + } + + throw new JsonException(); + } + + private object ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return reader.GetString(); + case JsonTokenType.Number: + if (reader.TryGetInt64(out long l)) + { + return l; + } + + return reader.GetDouble(); + case JsonTokenType.True: + return true; + case JsonTokenType.False: + return false; + case JsonTokenType.Null: + return null; + case JsonTokenType.StartObject: + return Read(ref reader, typeof(Dictionary), options); + case JsonTokenType.StartArray: + var list = new List(); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + return list; + } + + list.Add(ReadValue(ref reader, options)); + } + + throw new JsonException(); + default: + throw new JsonException(); + } + } + + public override void Write(Utf8JsonWriter writer, Dictionary value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var kvp in value) + { + writer.WritePropertyName(kvp.Key); + WriteValue(writer, kvp.Value, options); + } + + writer.WriteEndObject(); + } + + private void WriteValue(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + switch (value) + { + case string s: + writer.WriteStringValue(s); + break; + case long l: + writer.WriteNumberValue(l); + break; + case double d: + writer.WriteNumberValue(d); + break; + case bool b: + writer.WriteBooleanValue(b); + break; + case null: + writer.WriteNullValue(); + break; + case Dictionary dict: + writer.WriteStartObject(); + foreach (var kvp in dict) + { + writer.WritePropertyName(kvp.Key); + WriteValue(writer, kvp.Value, options); + } + + writer.WriteEndObject(); + break; + case List list: + writer.WriteStartArray(); + foreach (var item in list) + { + WriteValue(writer, item, options); + } + + writer.WriteEndArray(); + break; + case JsonNode node: + JsonSerializer.Serialize(writer, node, options); + break; + default: + throw new InvalidOperationException($"Unsupported type: {value?.GetType()}"); + } + } +} + +public sealed class NativeJsValueJsonConverter : JsonConverter +{ + private readonly Engine _engine = new(); + + public override JsObject Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType != JsonTokenType.StartObject) + { + throw new JsonException(); + } + + var dictionary = new JsObject(_engine); + + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndObject) + { + return dictionary; + } + + if (reader.TokenType != JsonTokenType.PropertyName) + { + throw new JsonException(); + } + + string propertyName = reader.GetString(); + + reader.Read(); + + dictionary[propertyName] = ReadValue(ref reader, options); + } + + throw new JsonException(); + } + + private JsValue ReadValue(ref Utf8JsonReader reader, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return reader.GetString(); + case JsonTokenType.Number: + if (reader.TryGetInt64(out long l)) + { + return l; + } + + return reader.GetDouble(); + case JsonTokenType.True: + return true; + case JsonTokenType.False: + return false; + case JsonTokenType.Null: + return null; + case JsonTokenType.StartObject: + return Read(ref reader, typeof(JsObject), options); + case JsonTokenType.StartArray: + var list = new JsArray(_engine); + while (reader.Read()) + { + if (reader.TokenType == JsonTokenType.EndArray) + { + return list; + } + + list.Push(ReadValue(ref reader, options)); + } + + throw new JsonException(); + default: + throw new JsonException(); + } + } + + public override void Write(Utf8JsonWriter writer, JsObject value, JsonSerializerOptions options) + { + writer.WriteStartObject(); + + foreach (var kvp in value.GetOwnProperties()) + { + writer.WritePropertyName(kvp.Key.ToString()); + WriteValue(writer, kvp.Value, options); + } + + writer.WriteEndObject(); + } + + private void WriteValue(Utf8JsonWriter writer, object value, JsonSerializerOptions options) + { + switch (value) + { + case string s: + writer.WriteStringValue(s); + break; + case long l: + writer.WriteNumberValue(l); + break; + case double d: + writer.WriteNumberValue(d); + break; + case bool b: + writer.WriteBooleanValue(b); + break; + case null: + writer.WriteNullValue(); + break; + case Dictionary dict: + writer.WriteStartObject(); + foreach (var kvp in dict) + { + writer.WritePropertyName(kvp.Key); + WriteValue(writer, kvp.Value, options); + } + + writer.WriteEndObject(); + break; + case List list: + writer.WriteStartArray(); + foreach (var item in list) + { + WriteValue(writer, item, options); + } + + writer.WriteEndArray(); + break; + case JsonNode node: + JsonSerializer.Serialize(writer, node, options); + break; + default: + throw new InvalidOperationException($"Unsupported type: {value?.GetType()}"); + } + } +} + +public static class JsonDefaults +{ + public const int DictionaryCapacity = 4; + + public static JsonSerializerOptions JsonSerializerOptions { get; } + + static JsonDefaults() + { + var options = new JsonSerializerOptions(); + options.Converters.Add(new DictionaryStringObjectJsonConverter()); + options.Converters.Add(new NativeJsValueJsonConverter()); + + JsonSerializerOptions = options; + } +} + +public enum TestDataType +{ + ClrObject, + JsonNode, + Dictionary, + JsValue +} diff --git a/Jint.Benchmark/Jint.Benchmark.csproj b/Jint.Benchmark/Jint.Benchmark.csproj index 5a1f33f9fd..9de1514c17 100644 --- a/Jint.Benchmark/Jint.Benchmark.csproj +++ b/Jint.Benchmark/Jint.Benchmark.csproj @@ -1,6 +1,6 @@ - net8.0 + net10.0 Exe false false diff --git a/Jint.Benchmark/ObjectAccessBenchmark.cs b/Jint.Benchmark/ObjectAccessBenchmark.cs index 27a831af5c..e3886c9fef 100644 --- a/Jint.Benchmark/ObjectAccessBenchmark.cs +++ b/Jint.Benchmark/ObjectAccessBenchmark.cs @@ -9,7 +9,7 @@ public class ObjectAccessBenchmark public ObjectAccessBenchmark() { - const string Script = @"const summary = { res: 0; }; for (var i =0; i < 1_000_000; ++i){ summary.res = summary.res + 1; }"; + const string Script = @"const summary = { res: 0 }; for (var i =0; i < 1_000_000; ++i){ summary.res = summary.res + 1; }"; _script = Engine.PrepareScript(Script); } diff --git a/Jint.Benchmark/Scripts/dromaeo-3d-cube-modern.js b/Jint.Benchmark/Scripts/dromaeo-3d-cube-modern.js new file mode 100644 index 0000000000..e77a8d5d93 --- /dev/null +++ b/Jint.Benchmark/Scripts/dromaeo-3d-cube-modern.js @@ -0,0 +1,331 @@ +// 3D Cube Rotation +// http://www.speich.net/computer/moztesting/3d.htm +// Created by Simon Speich + +const Q = []; +let MTrans = []; // transformation matrix +let MQube = []; // position information of qube +let I = []; // entity matrix +const Origin = {}; +const Testing = {}; +let LoopTimer; + +const DisplArea = {}; +DisplArea.Width = 300; +DisplArea.Height = 300; + +const DrawLine = (From, To) => { + const x1 = From.V[0]; + const x2 = To.V[0]; + const y1 = From.V[1]; + const y2 = To.V[1]; + const dx = Math.abs(x2 - x1); + const dy = Math.abs(y2 - y1); + let x = x1; + let y = y1; + let IncX1, IncY1; + let IncX2, IncY2; + let Den; + let Num; + let NumAdd; + let NumPix; + + if (x2 >= x1) { IncX1 = 1; IncX2 = 1; } + else { IncX1 = -1; IncX2 = -1; } + if (y2 >= y1) { IncY1 = 1; IncY2 = 1; } + else { IncY1 = -1; IncY2 = -1; } + if (dx >= dy) { + IncX1 = 0; + IncY2 = 0; + Den = dx; + Num = dx / 2; + NumAdd = dy; + NumPix = dx; + } + else { + IncX2 = 0; + IncY1 = 0; + Den = dy; + Num = dy / 2; + NumAdd = dx; + NumPix = dy; + } + + NumPix = Math.round(Q.LastPx + NumPix); + + let i = Q.LastPx; + for (; i < NumPix; i++) { + Num += NumAdd; + if (Num >= Den) { + Num -= Den; + x += IncX1; + y += IncY1; + } + x += IncX2; + y += IncY2; + } + Q.LastPx = NumPix; +}; + +const CalcCross = (V0, V1) => { + const Cross = []; + Cross[0] = V0[1] * V1[2] - V0[2] * V1[1]; + Cross[1] = V0[2] * V1[0] - V0[0] * V1[2]; + Cross[2] = V0[0] * V1[1] - V0[1] * V1[0]; + return Cross; +}; + +const CalcNormal = (V0, V1, V2) => { + let A = []; + const B = []; + for (let i = 0; i < 3; i++) { + A[i] = V0[i] - V1[i]; + B[i] = V2[i] - V1[i]; + } + A = CalcCross(A, B); + const Length = Math.sqrt(A[0] * A[0] + A[1] * A[1] + A[2] * A[2]); + for (let i = 0; i < 3; i++) A[i] = A[i] / Length; + A[3] = 1; + return A; +}; + +const CreateP = function (X, Y, Z) { + this.V = [X, Y, Z, 1]; +}; + +// mulitplies two matrices +const MMulti = (M1, M2) => { + const M = [[], [], [], []]; + let i = 0; + let j = 0; + for (; i < 4; i++) { + j = 0; + for (; j < 4; j++) M[i][j] = M1[i][0] * M2[0][j] + M1[i][1] * M2[1][j] + M1[i][2] * M2[2][j] + M1[i][3] * M2[3][j]; + } + return M; +}; + +//multiplies matrix with vector +const VMulti = (M, V) => { + const Vect = []; + let i = 0; + for (; i < 4; i++) Vect[i] = M[i][0] * V[0] + M[i][1] * V[1] + M[i][2] * V[2] + M[i][3] * V[3]; + return Vect; +}; + +const VMulti2 = (M, V) => { + const Vect = []; + let i = 0; + for (; i < 3; i++) Vect[i] = M[i][0] * V[0] + M[i][1] * V[1] + M[i][2] * V[2]; + return Vect; +}; + +// add to matrices +const MAdd = (M1, M2) => { + const M = [[], [], [], []]; + let i = 0; + let j = 0; + for (; i < 4; i++) { + j = 0; + for (; j < 4; j++) M[i][j] = M1[i][j] + M2[i][j]; + } + return M; +}; + +const Translate = (M, Dx, Dy, Dz) => { + const T = [ + [1, 0, 0, Dx], + [0, 1, 0, Dy], + [0, 0, 1, Dz], + [0, 0, 0, 1] + ]; + return MMulti(T, M); +}; + +const RotateX = (M, Phi) => { + let a = Phi; + a *= Math.PI / 180; + const Cos = Math.cos(a); + const Sin = Math.sin(a); + const R = [ + [1, 0, 0, 0], + [0, Cos, -Sin, 0], + [0, Sin, Cos, 0], + [0, 0, 0, 1] + ]; + return MMulti(R, M); +}; + +const RotateY = (M, Phi) => { + let a = Phi; + a *= Math.PI / 180; + const Cos = Math.cos(a); + const Sin = Math.sin(a); + const R = [ + [Cos, 0, Sin, 0], + [0, 1, 0, 0], + [-Sin, 0, Cos, 0], + [0, 0, 0, 1] + ]; + return MMulti(R, M); +}; + +const RotateZ = (M, Phi) => { + let a = Phi; + a *= Math.PI / 180; + const Cos = Math.cos(a); + const Sin = Math.sin(a); + const R = [ + [Cos, -Sin, 0, 0], + [Sin, Cos, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + return MMulti(R, M); +}; + +const DrawQube = () => { + // calc current normals + const CurN = []; + let i = 5; + Q.LastPx = 0; + for (; i > -1; i--) CurN[i] = VMulti2(MQube, Q.Normal[i]); + if (CurN[0][2] < 0) { + if (!Q.Line[0]) { DrawLine(Q[0], Q[1]); Q.Line[0] = true; } + if (!Q.Line[1]) { DrawLine(Q[1], Q[2]); Q.Line[1] = true; } + if (!Q.Line[2]) { DrawLine(Q[2], Q[3]); Q.Line[2] = true; } + if (!Q.Line[3]) { DrawLine(Q[3], Q[0]); Q.Line[3] = true; } + } + if (CurN[1][2] < 0) { + if (!Q.Line[2]) { DrawLine(Q[3], Q[2]); Q.Line[2] = true; } + if (!Q.Line[9]) { DrawLine(Q[2], Q[6]); Q.Line[9] = true; } + if (!Q.Line[6]) { DrawLine(Q[6], Q[7]); Q.Line[6] = true; } + if (!Q.Line[10]) { DrawLine(Q[7], Q[3]); Q.Line[10] = true; } + } + if (CurN[2][2] < 0) { + if (!Q.Line[4]) { DrawLine(Q[4], Q[5]); Q.Line[4] = true; } + if (!Q.Line[5]) { DrawLine(Q[5], Q[6]); Q.Line[5] = true; } + if (!Q.Line[6]) { DrawLine(Q[6], Q[7]); Q.Line[6] = true; } + if (!Q.Line[7]) { DrawLine(Q[7], Q[4]); Q.Line[7] = true; } + } + if (CurN[3][2] < 0) { + if (!Q.Line[4]) { DrawLine(Q[4], Q[5]); Q.Line[4] = true; } + if (!Q.Line[8]) { DrawLine(Q[5], Q[1]); Q.Line[8] = true; } + if (!Q.Line[0]) { DrawLine(Q[1], Q[0]); Q.Line[0] = true; } + if (!Q.Line[11]) { DrawLine(Q[0], Q[4]); Q.Line[11] = true; } + } + if (CurN[4][2] < 0) { + if (!Q.Line[11]) { DrawLine(Q[4], Q[0]); Q.Line[11] = true; } + if (!Q.Line[3]) { DrawLine(Q[0], Q[3]); Q.Line[3] = true; } + if (!Q.Line[10]) { DrawLine(Q[3], Q[7]); Q.Line[10] = true; } + if (!Q.Line[7]) { DrawLine(Q[7], Q[4]); Q.Line[7] = true; } + } + if (CurN[5][2] < 0) { + if (!Q.Line[8]) { DrawLine(Q[1], Q[5]); Q.Line[8] = true; } + if (!Q.Line[5]) { DrawLine(Q[5], Q[6]); Q.Line[5] = true; } + if (!Q.Line[9]) { DrawLine(Q[6], Q[2]); Q.Line[9] = true; } + if (!Q.Line[1]) { DrawLine(Q[2], Q[1]); Q.Line[1] = true; } + } + Q.Line = [false, false, false, false, false, false, false, false, false, false, false, false]; + Q.LastPx = 0; +}; + +const Loop = () => { + if (Testing.LoopCount > Testing.LoopMax) return; + let TestingStr = String(Testing.LoopCount); + while (TestingStr.length < 3) TestingStr = "0" + TestingStr; + MTrans = Translate(I, -Q[8].V[0], -Q[8].V[1], -Q[8].V[2]); + MTrans = RotateX(MTrans, 1); + MTrans = RotateY(MTrans, 3); + MTrans = RotateZ(MTrans, 5); + MTrans = Translate(MTrans, Q[8].V[0], Q[8].V[1], Q[8].V[2]); + MQube = MMulti(MTrans, MQube); + let i = 8; + for (; i > -1; i--) { + Q[i].V = VMulti(MTrans, Q[i].V); + } + DrawQube(); + Testing.LoopCount++; + Loop(); +}; + +function Init(CubeSize) { + // init/reset vars + Origin.V = [150, 150, 20, 1]; + Testing.LoopCount = 0; + Testing.LoopMax = 50; + Testing.TimeMax = 0; + Testing.TimeAvg = 0; + Testing.TimeMin = 0; + Testing.TimeTemp = 0; + Testing.TimeTotal = 0; + Testing.Init = false; + + // transformation matrix + MTrans = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + // position information of qube + MQube = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + // entity matrix + I = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + // create qube + Q[0] = new CreateP(-CubeSize, -CubeSize, CubeSize); + Q[1] = new CreateP(-CubeSize, CubeSize, CubeSize); + Q[2] = new CreateP(CubeSize, CubeSize, CubeSize); + Q[3] = new CreateP(CubeSize, -CubeSize, CubeSize); + Q[4] = new CreateP(-CubeSize, -CubeSize, -CubeSize); + Q[5] = new CreateP(-CubeSize, CubeSize, -CubeSize); + Q[6] = new CreateP(CubeSize, CubeSize, -CubeSize); + Q[7] = new CreateP(CubeSize, -CubeSize, -CubeSize); + + // center of gravity + Q[8] = new CreateP(0, 0, 0); + + // anti-clockwise edge check + Q.Edge = [[0, 1, 2], [3, 2, 6], [7, 6, 5], [4, 5, 1], [4, 0, 3], [1, 5, 6]]; + + // calculate squad normals + Q.Normal = []; + for (let i = 0; i < Q.Edge.length; i++) Q.Normal[i] = CalcNormal(Q[Q.Edge[i][0]].V, Q[Q.Edge[i][1]].V, Q[Q.Edge[i][2]].V); + + // line drawn ? + Q.Line = [false, false, false, false, false, false, false, false, false, false, false, false]; + + // create line pixels + Q.NumPx = 9 * 2 * CubeSize; + for (let i = 0; i < Q.NumPx; i++) new CreateP(0, 0, 0); + + MTrans = Translate(MTrans, Origin.V[0], Origin.V[1], Origin.V[2]); + MQube = MMulti(MTrans, MQube); + + let i = 0; + for (; i < 9; i++) { + Q[i].V = VMulti(MTrans, Q[i].V); + } + DrawQube(); + Testing.Init = true; + Loop(); +} + +startTest("dromaeo-3d-cube", '979cd0f1'); + +test("Rotate 3D Cube", () => Init(20)); + +endTest(); \ No newline at end of file diff --git a/Jint.Benchmark/Scripts/dromaeo-core-eval-modern.js b/Jint.Benchmark/Scripts/dromaeo-core-eval-modern.js new file mode 100644 index 0000000000..6f82823ee4 --- /dev/null +++ b/Jint.Benchmark/Scripts/dromaeo-core-eval-modern.js @@ -0,0 +1,23 @@ +startTest("dromaeo-core-eval", 'efec1da2'); + +// Try to force real results +let ret, tmp; + +// The commands that we'll be evaling +const cmd = 'var str="";for(var i=0;i<1000;i++){str += "a";}ret = str;'; + +// TESTS: eval() +const num = 4; + +prep(() => { + tmp = cmd; + + for (let n = 0; n < num; n++) + tmp += tmp; +}); + +test("Normal eval", () => eval(tmp)); + +test("new Function", () => (new Function(tmp))()); + +endTest(); \ No newline at end of file diff --git a/Jint.Benchmark/Scripts/dromaeo-object-array-modern.js b/Jint.Benchmark/Scripts/dromaeo-object-array-modern.js new file mode 100644 index 0000000000..6741c8cd0f --- /dev/null +++ b/Jint.Benchmark/Scripts/dromaeo-object-array-modern.js @@ -0,0 +1,57 @@ +startTest("dromaeo-object-array", 'bde4f5f4'); + +let ret = [], tmp; +const num = 500; +const i = 1024; + +// TESTS: Array Building + +test("Array Construction, []", () => { + for (let j = 0; j < i * 15; j++) { + ret = []; + ret.length = i; + } +}); + +test("Array Construction, new Array()", () => { + for (let j = 0; j < i * 10; j++) + ret = new Array(i); +}); + +test("Array Construction, unshift", () => { + ret = []; + for (let j = 0; j < i; j++) + ret.unshift(j); +}); + +test("Array Construction, splice", () => { + ret = []; + for (let j = 0; j < i; j++) + ret.splice(0, 0, j); +}); + +test("Array Deconstruction, shift", () => { + const a = ret.slice(); + for (let j = 0; j < i; j++) + tmp = a.shift(); +}); + +test("Array Deconstruction, splice", () => { + const a = ret.slice(); + for (let j = 0; j < i; j++) + tmp = a.splice(0, 1); +}); + +test("Array Construction, push", () => { + ret = []; + for (let j = 0; j < i * 25; j++) + ret.push(j); +}); + +test("Array Deconstruction, pop", () => { + const a = ret.slice(); + for (let j = 0; j < i * 25; j++) + tmp = a.pop(); +}); + +endTest(); \ No newline at end of file diff --git a/Jint.Benchmark/Scripts/dromaeo-object-regexp-modern.js b/Jint.Benchmark/Scripts/dromaeo-object-regexp-modern.js new file mode 100644 index 0000000000..da8a6ef9c9 --- /dev/null +++ b/Jint.Benchmark/Scripts/dromaeo-object-regexp-modern.js @@ -0,0 +1,366 @@ +startTest("dromaeo-object-regexp", '812dde38'); + +// Try to force real results +let str = [], tmp, ret, re; +const testStrings = []; +var i = 65536; + +const randomChar = () => String.fromCharCode((25 * Math.random()) + 97); + +for (let i = 0; i < 16384; i++) + str.push(randomChar()); + +str = str.join(""); +str += str; +str += str; + +const generateTestStrings = count => { + let t, nest; + if (testStrings.length >= count) + return testStrings.slice(0, count); + for (let i = testStrings.length; i < count; i++) { + // Make all tested strings different + t = randomChar() + str + randomChar(); + nest = Math.floor(4 * Math.random()); + for (let j = 0; j < nest; j++) { + t = randomChar() + t + randomChar(); + } + // Try to minimize benchmark order dependencies by + // exercising the strings + for (let j = 0; j < t.length; j += 100) { + ret = t[j]; + ret = t.substring(j, j + 100); + } + testStrings[i] = t; + } + return testStrings; +}; + +// TESTS: split + +prep(() => { + // It's impossible to specify empty regexp by simply + // using two slashes as this will be interpreted as a + // comment start. See note to ECMA-262 5th 7.8.5. + re = /(?:)/; + tmp = generateTestStrings(30); +}); + +test("Compiled Object Empty Split", () => { + for (let i = 0; i < 30; i++) + ret = tmp[i].split(re); +}); + +prep(() => { + re = /a/; + tmp = generateTestStrings(30); +}); + +test("Compiled Object Char Split", () => { + for (let i = 0; i < 30; i++) + ret = tmp[i].split(re); +}); + +prep(() => { + re = /.*/; + tmp = generateTestStrings(100); +}); + +test("Compiled Object Variable Split", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].split(re); +}); + +// TESTS: Compiled RegExps + +prep(() => { + re = /aaaaaaaaaa/g; + tmp = generateTestStrings(100); +}); + +test("Compiled Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(re); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Compiled Test", () => { + for (let i = 0; i < 100; i++) + ret = re.test(tmp[i]); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Compiled Empty Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, ""); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled 12 Char Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, "asdfasdfasdf"); +}); + +prep(() => { + re = new RegExp("aaaaaaaaaa", "g"); + tmp = generateTestStrings(100); +}); + +test("Compiled Object Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(re); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Compiled Object Test", () => { + for (let i = 0; i < 100; i++) + ret = re.test(tmp[i]); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Object Empty Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, ""); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Object 12 Char Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, "asdfasdfasdf"); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Object 12 Char Replace Function", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, all => "asdfasdfasdf"); +}); + +// TESTS: Variable Length + +prep(() => { + re = /a.*a/; + tmp = generateTestStrings(100); +}); + +test("Compiled Variable Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(re); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Compiled Variable Test", () => { + for (let i = 0; i < 100; i++) + ret = re.test(tmp[i]); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Variable Empty Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, ""); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Variable 12 Char Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, "asdfasdfasdf"); +}); + +prep(() => { + re = new RegExp("aaaaaaaaaa", "g"); + tmp = generateTestStrings(100); +}); + +test("Compiled Variable Object Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(re); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Compiled Variable Object Test", () => { + for (let i = 0; i < 100; i++) + ret = re.test(tmp[i]); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Variable Object Empty Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, ""); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Variable Object 12 Char Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, "asdfasdfasdf"); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Variable Object 12 Char Replace Function", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, all => "asdfasdfasdf"); +}); + +// TESTS: Capturing + +prep(() => { + re = /aa(b)aa/g; + tmp = generateTestStrings(100); +}); + +test("Compiled Capture Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(re); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Capture Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, "asdfasdfasdf"); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Capture Replace with Capture", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, "asdf\\1asdfasdf"); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Capture Replace with Capture Function", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, (all, capture) => "asdf" + capture + "asdfasdf"); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Compiled Capture Replace with Upperase Capture Function", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(re, (all, capture) => capture.toUpperCase()); +}); + +// TESTS: Uncompiled RegExps + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Uncompiled Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(/aaaaaaaaaa/g); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Uncompiled Test", () => { + for (let i = 0; i < 100; i++) + ret = (/aaaaaaaaaa/g).test(tmp[i]); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Uncompiled Empty Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(/aaaaaaaaaa/g, ""); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Uncompiled 12 Char Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(/aaaaaaaaaa/g, "asdfasdfasdf"); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Uncompiled Object Match", () => { + for (let i = 0; i < 100; i++) + ret = tmp[i].match(new RegExp("aaaaaaaaaa", "g")); +}); + +prep(() => { + tmp = generateTestStrings(100); +}); + +test("Uncompiled Object Test", () => { + for (let i = 0; i < 100; i++) + ret = (new RegExp("aaaaaaaaaa", "g")).test(tmp[i]); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Uncompiled Object Empty Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(new RegExp("aaaaaaaaaa", "g"), ""); +}); + +prep(() => { + tmp = generateTestStrings(50); +}); + +test("Uncompiled Object 12 Char Replace", () => { + for (let i = 0; i < 50; i++) + ret = tmp[i].replace(new RegExp("aaaaaaaaaa", "g"), "asdfasdfasdf"); +}); + +endTest(); \ No newline at end of file diff --git a/Jint.Benchmark/Scripts/dromaeo-object-string-modern.js b/Jint.Benchmark/Scripts/dromaeo-object-string-modern.js new file mode 100644 index 0000000000..8ff9f6f188 --- /dev/null +++ b/Jint.Benchmark/Scripts/dromaeo-object-string-modern.js @@ -0,0 +1,186 @@ +startTest("dromaeo-object-string", 'ef8605c3'); + +// Try to force real results +let ret; +const num = 5000; + +// TESTS: String concatenation + +test("Concat String", () => { + let str = ""; + for (let i = 0; i < num; i++) + str += "a"; + ret = str; +}); + +test("Concat String Object", () => { + let str = new String(); + for (let i = 0; i < num; i++) + str += "a"; + ret = str; +}); + +test("Concat String from charCode", () => { + let str = ""; + for (let i = 0; i < num / 2; i++) + str += String.fromCharCode(97); + ret = str; +}); + +test("Array String Join", () => { + const str = []; + for (let i = 0; i < num / 2; i++) + str.push("a"); + ret = str.join(""); +}); + +let ostr = [], tmp, tmp2, tmpstr; + +for (let i = 0; i < 16384; i++) + ostr.push(String.fromCharCode((25 * Math.random()) + 97)); + +ostr = ostr.join(""); +ostr += ostr; +ostr += ostr; + +let str; +let i = 52288; + +prep(() => str = new String(ostr)); + +// TESTS: split +test("String Split", () => ret = str.split("")); + +prep(() => { + tmpstr = str; + tmpstr += tmpstr; + tmpstr += tmpstr; + tmpstr += tmpstr; + tmpstr += tmpstr; +}); + +test("String Split on Char", () => ret = tmpstr.split("a")); + +prep(() => str += str); + +// TESTS: characters + +test("charAt", () => { + for (let j = 0; j < num; j++) { + ret = str.charAt(0); + ret = str.charAt(str.length - 1); + ret = str.charAt(15000); + ret = str.charAt(12000); + } +}); + +test("[Number]", () => { + for (let j = 0; j < num; j++) { + ret = str[0]; + ret = str[str.length - 1]; + ret = str[15000]; + ret = str[10000]; + ret = str[5000]; + } +}); + +test("charCodeAt", () => { + for (let j = 0; j < num; j++) { + ret = str.charCodeAt(0); + ret = str.charCodeAt(str.length - 1); + ret = str.charCodeAt(15000); + ret = str.charCodeAt(10000); + ret = str.charCodeAt(5000); + } +}); + +// TESTS: indexOf + +test("indexOf", () => { + for (let j = 0; j < num; j++) { + ret = str.indexOf("a"); + ret = str.indexOf("b"); + ret = str.indexOf("c"); + ret = str.indexOf("d"); + } +}); + +test("lastIndexOf", () => { + for (let j = 0; j < num; j++) { + ret = str.lastIndexOf("a"); + ret = str.lastIndexOf("b"); + ret = str.lastIndexOf("c"); + ret = str.lastIndexOf("d"); + } +}); + +// TESTS: slice + +test("slice", () => { + for (let j = 0; j < num; j++) { + ret = str.slice(0); + ret = str.slice(0, 5); + ret = str.slice(-1); + ret = str.slice(-6, -1); + ret = str.slice(15000, 15005); + ret = str.slice(12000, -1); + } +}); + +// TESTS: substr + +test("substr", () => { + for (let j = 0; j < num; j++) { + ret = str.substr(0); + ret = str.substr(0, 5); + ret = str.substr(-1); + ret = str.substr(-6, 1); + ret = str.substr(15000, 5); + ret = str.substr(12000, 5); + } +}); + +// TESTS: substring + +test("substring", () => { + for (let j = 0; j < num; j++) { + ret = str.substring(0); + ret = str.substring(0, 5); + ret = str.substring(-1); + ret = str.substring(-6, -1); + ret = str.substring(15000, 15005); + ret = str.substring(12000, -1); + } +}); + +// TESTS: toLower/UpperCase + +test("toLowerCase", () => { + for (let j = 0; j < num / 1000; j++) { + ret = str.toLowerCase(); + } +}); + +test("toUpperCase", () => { + for (let j = 0; j < num / 1000; j++) { + ret = str.toUpperCase(); + } +}); + +// TESTS: comparing +prep(() => { + tmp = str; + tmp2 = str; +}); + +test("comparing", () => { + tmp = "a" + tmp + "a"; + tmp2 = "a" + tmp2 + "a"; + for (let j = 0; j < num / 1000; j++) { + ret = tmp == tmp2; + ret = tmp < tmp2; + ret = tmp > tmp2; + } +}); + +endTest(); \ No newline at end of file diff --git a/Jint.Benchmark/Scripts/dromaeo-object-string.js b/Jint.Benchmark/Scripts/dromaeo-object-string.js index 88e2030247..cbe9d31af7 100644 --- a/Jint.Benchmark/Scripts/dromaeo-object-string.js +++ b/Jint.Benchmark/Scripts/dromaeo-object-string.js @@ -2,7 +2,7 @@ // Try to force real results var ret; -var num = 80000; +var num = 5000; // TESTS: String concatenation diff --git a/Jint.Benchmark/Scripts/dromaeo-string-base64-modern.js b/Jint.Benchmark/Scripts/dromaeo-string-base64-modern.js new file mode 100644 index 0000000000..b962f00c3a --- /dev/null +++ b/Jint.Benchmark/Scripts/dromaeo-string-base64-modern.js @@ -0,0 +1,147 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XML-RPC Client component. + * + * The Initial Developer of the Original Code is + * Digital Creations 2, Inc. + * Portions created by the Initial Developer are Copyright (C) 2000 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Martijn Pieters (original author) + * Samuel Sieb + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +// From: http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956 + +/* Convert data (an array of integers) to a Base64 string. */ +const toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; +const base64Pad = '='; + +function toBase64(data) { + let result = ''; + const length = data.length; + let i; + // Convert every three bytes to 4 ascii characters. + for (i = 0; i < (length - 2) ; i += 3) { + result += toBase64Table[data.charCodeAt(i) >> 2]; + result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i + 1) >> 4)]; + result += toBase64Table[((data.charCodeAt(i + 1) & 0x0f) << 2) + (data.charCodeAt(i + 2) >> 6)]; + result += toBase64Table[data.charCodeAt(i + 2) & 0x3f]; + } + + // Convert the remaining 1 or 2 bytes, pad out to 4 characters. + if (length % 3) { + i = length - (length % 3); + result += toBase64Table[data.charCodeAt(i) >> 2]; + if ((length % 3) == 2) { + result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.chartCodeAt(i + 1) >> 4)]; + result += toBase64Table[(data.charCodeAt(i + 1) & 0x0f) << 2]; + result += base64Pad; + } else { + result += toBase64Table[(data.charCodeAt(i) & 0x03) << 4]; + result += base64Pad + base64Pad; + } + } + + return result; +} + +/* Convert Base64 data to a string */ +const toBinaryTable = [ + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1 +]; + +const base64ToString = data => { + let result = ''; + let leftbits = 0; // number of bits decoded, but yet to be appended + let leftdata = 0; // bits decoded, but yet to be appended + + // Convert one by one. + for (let i = 0; i < data.length; i++) { + const c = toBinaryTable[data.charCodeAt(i) & 0x7f]; + const padding = (data.charCodeAt(i) == base64Pad.charCodeAt(0)); + // Skip illegal characters and whitespace + if (c == -1) continue; + + // Collect data into leftdata, update bitcount + leftdata = (leftdata << 6) | c; + leftbits += 6; + + // If we have 8 or more bits, append 8 bits to the result + if (leftbits >= 8) { + leftbits -= 8; + // Append if not padding. + if (!padding) + result += String.fromCharCode((leftdata >> leftbits) & 0xff); + leftdata &= (1 << leftbits) - 1; + } + } + + // If there are any bits left, the base64 string was corrupted + if (leftbits) + throw Components.Exception('Corrupted base64 string'); + + return result; +}; + +startTest("dromaeo-string-base64", '09340c18'); + +let str = []; + +for (let i = 0; i < 4096; i++) + str.push(String.fromCharCode((25 * Math.random()) + 97)); + +str = str.join(""); +str += str; +str += str; + +let base64; + +test("Convert String to Base 64", () => { + base64 = toBase64(str); +}); + +prep(() => { + if (!base64) + base64 = toBase64(str); +}); + +test("Convert Base 64 to String", () => { + if (str !== base64ToString(base64)) { + throw "String conversion mis-match."; + } +}); + +endTest(); \ No newline at end of file diff --git a/Jint.Benchmark/Scripts/evaluation-modern.js b/Jint.Benchmark/Scripts/evaluation-modern.js new file mode 100644 index 0000000000..cdd7c9084d --- /dev/null +++ b/Jint.Benchmark/Scripts/evaluation-modern.js @@ -0,0 +1,16 @@ +const o = {}; +o.Foo = 'bar'; +o.Baz = 42.0001; +o.Blah = o.Foo + o.Baz; + +if(o.Blah != 'bar42.0001') throw TypeError; + +const fib = n => { + if(n<2) { + return n; + } + + return fib(n-1) + fib(n-2); +}; + +if(fib(3) != 2) throw TypeError; diff --git a/Jint.Benchmark/Scripts/stopwatch-modern.js b/Jint.Benchmark/Scripts/stopwatch-modern.js new file mode 100644 index 0000000000..90bf986f1c --- /dev/null +++ b/Jint.Benchmark/Scripts/stopwatch-modern.js @@ -0,0 +1,56 @@ +function Stopwatch() { + const sw = this; + let start = null; + let stop = null; + let isRunning = false; + + sw.Start = function () { + if (isRunning) + return; + + start = new Date(); + stop = null; + isRunning = true; + } + + sw.Stop = function () { + if (!isRunning) + return; + + stop = new Date(); + isRunning = false; + } + + sw.Reset = function () { + start = isRunning ? new Date() : null; + stop = null; + } + + sw.Restart = function () { + isRunning = true; + sw.Reset(); + } + + sw.ElapsedMilliseconds = function () { return (isRunning ? new Date() : stop) - start; } + sw.IsRunning = function() { return isRunning; } + +} + +var sw = new Stopwatch(); +sw.Start(); +for (let x = 0; x < 1021; x++) { + for (let y = 0; y < 383; y++) { + const z = x ^ y; + if (z % 2 == 0) + sw.Start(); + else if(z % 3 == 0) + sw.Stop(); + else if (z % 5 == 0) + sw.Reset(); + else if (z % 7 == 0) + sw.Restart(); + const ms = sw.ElapsedMilliseconds; + const rn = sw.IsRunning; + } +} +sw.Stop(); diff --git a/Jint.Benchmark/StopwatchBenchmark.cs b/Jint.Benchmark/StopwatchBenchmark.cs index fb2fd520de..25e5c9d7eb 100644 --- a/Jint.Benchmark/StopwatchBenchmark.cs +++ b/Jint.Benchmark/StopwatchBenchmark.cs @@ -5,5 +5,8 @@ namespace Jint.Benchmark; [MemoryDiagnoser] public class StopwatchBenchmark : SingleScriptBenchmark { - protected override string FileName => "stopwatch.js"; + [Params(false, true)] + public bool Modern { get; set; } + + protected override string FileName => Modern ? "stopwatch-modern.js" : "stopwatch.js"; } diff --git a/Jint.Benchmark/UncacheableExpressionsBenchmark.cs b/Jint.Benchmark/UncacheableExpressionsBenchmark.cs index 3031fc7781..9b118fac2c 100644 --- a/Jint.Benchmark/UncacheableExpressionsBenchmark.cs +++ b/Jint.Benchmark/UncacheableExpressionsBenchmark.cs @@ -103,6 +103,6 @@ private void CreateEngine(string script) engine = new Engine(InitializeEngine); engine.Execute(script); engine.Execute(targetObject); - targetJsObject = new[] { engine.GetValue("d") }; + targetJsObject = [engine.GetValue("d")]; } } diff --git a/Jint.Repl/Jint.Repl.csproj b/Jint.Repl/Jint.Repl.csproj index 7a6dbfcad9..180e3d2e3b 100644 --- a/Jint.Repl/Jint.Repl.csproj +++ b/Jint.Repl/Jint.Repl.csproj @@ -1,6 +1,6 @@  - net8.0 + net10.0 Exe false true diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index 36a13ac8e3..c53c5c4d98 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -1,42 +1,149 @@ -using System.Reflection; +#nullable enable + +using System.Reflection; using Jint; using Jint.Native; using Jint.Native.Json; using Jint.Runtime; +// ReSharper disable LocalizableElement + #pragma warning disable IL2026 #pragma warning disable IL2111 -var engine = new Engine(cfg => cfg - .AllowClr() -); +// Parse command line arguments +string? inputFile = null; +int? timeoutSeconds = null; + +for (int i = 0; i < args.Length; i++) +{ + switch (args[i]) + { + case "-f" or "--file": + if (i + 1 < args.Length) + { + inputFile = args[++i]; + } + else + { + Console.Error.WriteLine("Error: -f requires a file path"); + return 1; + } + break; + case "-t" or "--timeout": + if (i + 1 < args.Length && int.TryParse(args[++i], out var t)) + { + timeoutSeconds = t; + } + else + { + Console.Error.WriteLine("Error: -t requires a timeout value in seconds"); + return 1; + } + break; + case "-h" or "--help": + PrintHelp(); + return 0; + default: + // For backwards compatibility, treat first positional arg as filename + if (!args[i].StartsWith("-") && inputFile == null) + { + inputFile = args[i]; + } + break; + } +} + +var engine = new Engine(cfg => +{ + cfg.AllowClr(); + if (timeoutSeconds.HasValue) + { + cfg.TimeoutInterval(TimeSpan.FromSeconds(timeoutSeconds.Value)); + } +}); engine .SetValue("print", new Action(Console.WriteLine)) + .SetValue("console", new JsConsole()) .SetValue("load", new Func( path => engine.Evaluate(File.ReadAllText(path))) ); -var filename = args.Length > 0 ? args[0] : ""; -if (!string.IsNullOrEmpty(filename)) +// Execute file if provided via -f +if (!string.IsNullOrEmpty(inputFile)) { - if (!File.Exists(filename)) + if (!File.Exists(inputFile)) { - Console.WriteLine("Could not find file: {0}", filename); + Console.Error.WriteLine($"Error: Could not find file: {inputFile}"); + return 1; } - var script = File.ReadAllText(filename); - engine.Evaluate(script, "repl"); - return; + try + { + var script = File.ReadAllText(inputFile); + var result = engine.Evaluate(script, inputFile); + if (!result.IsUndefined()) + { + Console.WriteLine(result); + } + return 0; + } + catch (JavaScriptException je) + { + Console.Error.WriteLine($"Error: {je.Message}"); + Console.Error.WriteLine(je.JavaScriptStackTrace); + return 1; + } + catch (TimeoutException) + { + Console.Error.WriteLine("Error: Script execution timed out"); + return 1; + } + catch (Exception e) + { + Console.Error.WriteLine($"Error: {e.Message}"); + return 1; + } +} + +// Check if input is being piped via STDIN +if (Console.IsInputRedirected) +{ + try + { + var script = Console.In.ReadToEnd(); + var result = engine.Evaluate(script, "stdin"); + if (!result.IsUndefined()) + { + Console.WriteLine(result); + } + return 0; + } + catch (JavaScriptException je) + { + Console.Error.WriteLine($"Error: {je.Message}"); + Console.Error.WriteLine(je.JavaScriptStackTrace); + return 1; + } + catch (TimeoutException) + { + Console.Error.WriteLine("Error: Script execution timed out"); + return 1; + } + catch (Exception e) + { + Console.Error.WriteLine($"Error: {e.Message}"); + return 1; + } } +// Interactive REPL mode var assembly = Assembly.GetExecutingAssembly(); var version = assembly.GetName().Version?.ToString(); -Console.WriteLine("Welcome to Jint ({0})", version); -Console.WriteLine("Type 'exit' to leave, " + - "'print()' to write on the console, " + - "'load()' to load scripts."); +Console.WriteLine($"Welcome to Jint ({version})"); +Console.WriteLine("Type 'exit' to leave, 'print()' to write on the console, 'load()' to load scripts."); Console.WriteLine(); var defaultColor = Console.ForegroundColor; @@ -52,9 +159,9 @@ Console.ForegroundColor = defaultColor; Console.Write("jint> "); var input = Console.ReadLine(); - if (input is "exit" or ".exit") + if (input is null or "exit" or ".exit") { - return; + return 0; } try @@ -80,9 +187,48 @@ Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(je.ToString()); } + catch (TimeoutException) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine("Error: Script execution timed out"); + } catch (Exception e) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(e.Message); } } + +static void PrintHelp() +{ + Console.WriteLine("Jint REPL - JavaScript interpreter"); + Console.WriteLine(); + Console.WriteLine("Usage: jint [options] [file]"); + Console.WriteLine(); + Console.WriteLine("Options:"); + Console.WriteLine(" -f, --file Execute JavaScript file"); + Console.WriteLine(" -t, --timeout Set execution timeout in seconds"); + Console.WriteLine(" -h, --help Show this help message"); + Console.WriteLine(); + Console.WriteLine("Examples:"); + Console.WriteLine(" jint Start interactive REPL"); + Console.WriteLine(" jint script.js Execute script.js"); + Console.WriteLine(" jint -f script.js -t 10 Execute with 10 second timeout"); + Console.WriteLine(" echo \"1+1\" | jint Execute from stdin"); + Console.WriteLine(" echo \"1+1\" | jint -t 5 Execute from stdin with timeout"); +} + +file sealed class JsConsole +{ + public void Log(object value) + { + Console.WriteLine(value?.ToString() ?? "null"); + } + + public void Error(object value) + { + Console.ForegroundColor = ConsoleColor.Red; + Console.WriteLine(value?.ToString() ?? "null"); + Console.ResetColor(); + } +} diff --git a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj index 4e7a8dbcae..5081bdf590 100644 --- a/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj +++ b/Jint.Tests.CommonScripts/Jint.Tests.CommonScripts.csproj @@ -1,7 +1,8 @@ - net462;net8.0 + net10.0 + $(TargetFrameworks);net472 false @@ -15,6 +16,7 @@ + diff --git a/Jint.Tests.PublicInterface/CallStackTests.cs b/Jint.Tests.PublicInterface/CallStackTests.cs index 959681aff6..0a12615c40 100644 --- a/Jint.Tests.PublicInterface/CallStackTests.cs +++ b/Jint.Tests.PublicInterface/CallStackTests.cs @@ -1,3 +1,6 @@ +using Jint.Runtime; +using SourceMaps; + namespace Jint.Tests.PublicInterface; public class CallStackTests @@ -14,10 +17,10 @@ public void CanInjectTraceFunction() const string Expected = """ Trace - at trace :1:16 - at x () :1:16 - at y () :1:51 - at :1:58 + at trace (:1:16) + at x (:1:16) + at y (:1:51) + at :1:58 """; @@ -46,4 +49,62 @@ public void Trace() _output.WriteLine($"Trace{Environment.NewLine}{_engine.Advanced.StackTrace}"); } } + + [Fact] + public void ShouldReturnTheSourceMapStack() + { + var sourceMap = SourceMapParser.Parse("""{"version":3,"file":"custom.js","sourceRoot":"","sources":["custom.ts"],"names":[],"mappings":"AAEA,SAAS,CAAC,CAAC,CAAM;IAChB,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,IAAI,CAAC,GAAG,UAAU,CAAM;IACvB,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AACb,CAAC,CAAA;AAED,CAAC,CAAC,CAAC,CAAC,CAAC"}"""); + + string BuildCallStackHandler(string description, SourceLocation location, string[] arguments) + { + if (location.SourceFile != sourceMap.File) + { + return null; + } + + var originalPosition = sourceMap.OriginalPositionFor(location.End.Line, location.Start.Column + 1); + + if (originalPosition is null) + { + return null; + } + + var str = $" at { + (!string.IsNullOrWhiteSpace(description) ? $"{description} (" : "") + }{ + originalPosition.Value.OriginalFileName + }:{ + originalPosition.Value.OriginalLineNumber + 1 + }:{ + originalPosition.Value.OriginalColumnNumber + }{ + (!string.IsNullOrWhiteSpace(description) ? ")" : "") + }{ + Environment.NewLine + }"; + + return str; + } + + var engine = new Engine(opt => + { + opt.SetBuildCallStackHandler(BuildCallStackHandler); + }); + + const string Script = @"function a(v) { + throw new Error(v); +} +var b = function (v) { + return a(v); +}; +b(7); +//# sourceMappingURL=custom.js.map"; + var ex = Assert.Throws(() => engine.Execute(Script, "custom.js")); + + var stack = ex.JavaScriptStackTrace!; + Assert.Equal(@" at a (custom.ts:4:7) + at b (custom.ts:8:9) + at custom.ts:11:1".Replace("\r\n", "\n"), stack.Replace("\r\n", "\n")); + } + } diff --git a/Jint.Tests.PublicInterface/FunctionToStringTest.cs b/Jint.Tests.PublicInterface/FunctionToStringTest.cs new file mode 100644 index 0000000000..f3ff767626 --- /dev/null +++ b/Jint.Tests.PublicInterface/FunctionToStringTest.cs @@ -0,0 +1,26 @@ +using FluentAssertions; + +namespace Jint.Tests.PublicInterface; + +public class FunctionToStringTest +{ + [Fact] + public void CanRegisterCustomFunctionToString() + { + const string Code = "var x = 1; var y = 3; function testFunction() { return 'Something'; }; testFunction.toString(); var z = x + y;"; + + // we can rewrite back with AST to get custom formatting + var engine = new Engine(options => + options.Host.FunctionToStringHandler = (function, node) => node.ToJavaScript(KnRJavaScriptTextFormatterOptions.Default) + ); + + engine.Evaluate(Code).AsString().Should().Be($"function testFunction() {{{Environment.NewLine} return 'Something';{Environment.NewLine}}}"); + + // or we can brute force the original input when we use node's location information + engine = new Engine(options => + options.Host.FunctionToStringHandler = (function, node) => Code.Substring(node.Start, node.End - node.Start) + ); + + engine.Evaluate(Code).AsString().Should().Be("function testFunction() { return 'Something'; }"); + } +} diff --git a/Jint.Tests.PublicInterface/InteropTests.Json.cs b/Jint.Tests.PublicInterface/InteropTests.Json.cs index 65e6c073b7..690a8ec0ec 100644 --- a/Jint.Tests.PublicInterface/InteropTests.Json.cs +++ b/Jint.Tests.PublicInterface/InteropTests.Json.cs @@ -1,4 +1,6 @@ using System.Dynamic; +using System.Text; +using FluentAssertions; using Jint.Runtime.Interop; namespace Jint.Tests.PublicInterface; @@ -102,31 +104,27 @@ public void CanStringifyTimeSpanUsingCustomToJsonHook() Assert.Equal(expected, value); } - + [Fact] public void CanStringifyUsingSerializeToJson() { object testObject = new { Foo = "bar", FooBar = new { Foo = 123.45, Foobar = new DateTime(2022, 7, 16, 0, 0, 0, DateTimeKind.Utc) } }; - + // without interop - - var engineNoInterop = new Engine(); - engineNoInterop.SetValue("TimeSpan", TypeReference.CreateTypeReference(engineNoInterop)); - Assert.Throws( - () => engineNoInterop.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))")); - - engineNoInterop.SetValue("TestObject", testObject); - Assert.Equal( - "{\"Foo\":\"bar\",\"FooBar\":{\"Foo\":123.45,\"Foobar\":\"2022-07-16T00:00:00.000Z\"}}", - engineNoInterop.Evaluate("JSON.stringify(TestObject)")); - + + var e = new Engine(); + e.SetValue("TimeSpan", typeof(TimeSpan)); +#if NETFRAMEWORK + e.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))").AsString().Should().Be("""{"Ticks":30000000,"Days":0,"Hours":0,"Milliseconds":0,"Minutes":0,"Seconds":3,"TotalDays":0.00003472222222222222,"TotalHours":0.0008333333333333333,"TotalMilliseconds":3000,"TotalMinutes":0.05,"TotalSeconds":3}"""); +#else + e.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3))").AsString().Should().Be("""{"Ticks":30000000,"Days":0,"Hours":0,"Milliseconds":0,"Microseconds":0,"Nanoseconds":0,"Minutes":0,"Seconds":3,"TotalDays":0.00003472222222222222,"TotalHours":0.0008333333333333334,"TotalMilliseconds":3000,"TotalMicroseconds":3000000,"TotalNanoseconds":3000000000,"TotalMinutes":0.05,"TotalSeconds":3}"""); +#endif + + e.SetValue("TestObject", testObject); + e.Evaluate("JSON.stringify(TestObject)").AsString().Should().Be("""{"Foo":"bar","FooBar":{"Foo":123.45,"Foobar":"2022-07-16T00:00:00.000Z"}}"""); + // interop using Newtonsoft serializer, for example with snake case naming - - string Serialize(object o) => - Newtonsoft.Json.JsonConvert.SerializeObject(o, - new Newtonsoft.Json.JsonSerializerSettings { - ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver { - NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy() } }); + var engine = new Engine(options => { options.Interop.SerializeToJson = Serialize; @@ -134,15 +132,109 @@ string Serialize(object o) => engine.SetValue("TimeSpan", TypeReference.CreateTypeReference(engine)); engine.SetValue("TestObject", testObject); - var expected = Serialize(TimeSpan.FromSeconds(3)); + var expected = Serialize(TimeSpan.FromSeconds(3), string.Empty); var actual = engine.Evaluate("JSON.stringify(TimeSpan.FromSeconds(3));"); - Assert.Equal(expected, actual); - - expected = Serialize(testObject); + actual.AsString().Should().Be(expected); + + expected = Serialize(testObject, string.Empty); actual = engine.Evaluate("JSON.stringify(TestObject)"); - Assert.Equal(expected, actual); + actual.AsString().Should().Be(expected); actual = engine.Evaluate("JSON.stringify({ nestedValue: TestObject })"); - Assert.Equal($@"{{""nestedValue"":{expected}}}", actual); + actual.AsString().Should().Be($$"""{"nestedValue":{{expected}}}"""); + + static string Serialize(object o, string space, string currentIndent = null) + { + var settings = new Newtonsoft.Json.JsonSerializerSettings + { + ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver + { + NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy() + } + }; + return Newtonsoft.Json.JsonConvert.SerializeObject(o, settings); + } + } + + [Fact] + public void CanStringifyUsingSerializeToJsonWithIndentation() + { + object testObject = new { Foo = "bar", FooBar = new { Foo = 123.45, Array = Array.Empty() } }; + var e = new Engine(o => o.Interop.SerializeToJson = SerializeIndentation); + e.SetValue("TestObject", testObject); + e.Evaluate("JSON.stringify(TestObject, null, 4)").AsString().Should().Be( + """ + { + "foo": "bar", + "foo_bar": { + "foo": 123.45, + "array": [] + } + } + """ + ); + + e.Evaluate("JSON.stringify({ nestedValue: TestObject }, null, 4)").AsString().Should().Be( + """ + { + "nestedValue": { + "foo": "bar", + "foo_bar": { + "foo": 123.45, + "array": [] + } + } + } + """.Replace("\r\n", "\n") + ); + + static string SerializeIndentation(object o, string space, string currentIndent) + { + var jsonSerializer = Newtonsoft.Json.JsonSerializer.Create(new Newtonsoft.Json.JsonSerializerSettings + { + ContractResolver = new Newtonsoft.Json.Serialization.DefaultContractResolver + { + NamingStrategy = new Newtonsoft.Json.Serialization.SnakeCaseNamingStrategy() + } + }); + + var sb = new StringBuilder(256); + var sw = new StringWriter(sb); + using var jsonWriter = string.IsNullOrEmpty(space) + ? new Newtonsoft.Json.JsonTextWriter(sw) + : new(sw) + { + Formatting = Newtonsoft.Json.Formatting.Indented, + Indentation = space.Length, + IndentChar = space[0] // assuming `space` only contains one kind of character + }; + + jsonSerializer.Serialize(jsonWriter, o); + + if (string.IsNullOrEmpty(currentIndent)) + { + return sw.ToString(); + } + else + { + var lines = sw.ToString().Split('\n'); + var stringBuilder = new StringBuilder(); + + for (var i = 0; i < lines.Length; i++) + { + if (i == 0) + { + stringBuilder.AppendLine(lines[i]); + } + else + { + stringBuilder.AppendLine(currentIndent + lines[i]); + } + } + + return stringBuilder.ToString().Replace("\r", string.Empty).TrimEnd('\n'); + } + } } + } diff --git a/Jint.Tests.PublicInterface/JavaScriptExceptionTests.cs b/Jint.Tests.PublicInterface/JavaScriptExceptionTests.cs index 9bf5587fee..736413c7b9 100644 --- a/Jint.Tests.PublicInterface/JavaScriptExceptionTests.cs +++ b/Jint.Tests.PublicInterface/JavaScriptExceptionTests.cs @@ -33,4 +33,75 @@ public void CanCreateAndThrowJavaScriptException() var jsString = Assert.IsType(result2); Assert.Equal("message 2", jsString.ToString()); } + + [Fact] + public void GetBaseExceptionReturnsJavaScriptException() + { + // https://github.com/sebastienros/jint/issues/2210 + try + { + new Engine().Evaluate("x * x"); + Assert.Fail("Expected JavaScriptException to be thrown"); + } + catch (Exception ex) + { + // GetBaseException() should return the JavaScriptException itself, + // not the private inner exception + var baseException = ex.GetBaseException(); + Assert.IsType(baseException); + Assert.Same(ex, baseException); + } + } + + [Fact] + public void GetBaseExceptionAllowsPatternMatching() + { + // https://github.com/sebastienros/jint/issues/2210 + try + { + new Engine().Evaluate("throw new Error('test error')"); + Assert.Fail("Expected JavaScriptException to be thrown"); + } + catch (Exception ex) + { + // Pattern matching with GetBaseException() should work + if (ex.GetBaseException() is JavaScriptException jsError) + { + Assert.NotNull(jsError.Error); + Assert.Equal("test error", jsError.Error.AsObject().Get("message").ToString()); + } + else + { + Assert.Fail("GetBaseException() should return JavaScriptException"); + } + } + } + + [Fact] + public void GetBaseExceptionWorksWithExplicitlyThrownJavaScriptException() + { + // https://github.com/sebastienros/jint/issues/2210 + var engine = new Engine(); + + engine.SetValue("throwFromDotNet", () => + { + throw new JavaScriptException(engine.Intrinsics.Error, "from .NET"); + }); + + try + { + engine.Evaluate("throwFromDotNet()"); + Assert.Fail("Expected JavaScriptException to be thrown"); + } + catch (Exception ex) + { + var baseException = ex.GetBaseException(); + Assert.IsType(baseException); + + if (baseException is JavaScriptException jsError) + { + Assert.Equal("from .NET", jsError.Error.AsObject().Get("message").ToString()); + } + } + } } \ No newline at end of file diff --git a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj index bf3c659d53..06a6714ce9 100644 --- a/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj +++ b/Jint.Tests.PublicInterface/Jint.Tests.PublicInterface.csproj @@ -1,10 +1,12 @@ - net462;net8.0 + net10.0 + $(TargetFrameworks);net472 false - 612 + $(NoWarn);612;xUnit1051 true + Exe @@ -14,14 +16,16 @@ + + - - + + @@ -29,4 +33,8 @@ + + + + diff --git a/Jint.Tests.PublicInterface/MapTests.cs b/Jint.Tests.PublicInterface/MapTests.cs new file mode 100644 index 0000000000..42e96ac954 --- /dev/null +++ b/Jint.Tests.PublicInterface/MapTests.cs @@ -0,0 +1,43 @@ +using FluentAssertions; +using Jint.Native; + +namespace Jint.Tests.Runtime; + +public class MapTests +{ + [Fact] + public void ConConstructMap() + { + var engine = new Engine(); + + var map = engine.Intrinsics.Map.Construct(); + map.Set(42, "the meaning of life"); + map.Set("foo", "bar"); + map.Size.Should().Be(2); + + map.Has(42).Should().BeTrue(); + map.Has("foo").Should().BeTrue(); + map.Has(24).Should().BeFalse(); + + map.Get(42).Should().Be((JsString) "the meaning of life"); + map.Get("foo").Should().Be((JsString) "bar"); + map.Get(24).Should().Be(JsValue.Undefined); + + engine.SetValue("m", map); + engine.Evaluate("m.size").Should().Be((JsNumber) 2); + engine.Evaluate("m.has(42)").Should().Be(JsBoolean.True); + engine.Evaluate("m.has('foo')").Should().Be(JsBoolean.True); + engine.Evaluate("m.has(24)").Should().Be(JsBoolean.False); + + map.Should().Contain((JsNumber) 42, (JsString) "the meaning of life"); + map.Remove(42).Should().BeTrue(); + map.Has(42).Should().BeFalse(); + engine.Evaluate("m.has(42)").Should().Be(JsBoolean.False); + engine.Evaluate("m.size").Should().Be((JsNumber) 1); + + map.Clear(); + map.Should().BeEmpty(); + map.Size.Should().Be(0); + engine.Evaluate("m.size").Should().Be((JsNumber) 0); + } +} diff --git a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs index 69627337bc..fbb8a7d6ca 100644 --- a/Jint.Tests.PublicInterface/RavenApiUsageTests.cs +++ b/Jint.Tests.PublicInterface/RavenApiUsageTests.cs @@ -229,7 +229,9 @@ function output(doc) { var rows = result.AsObject()["Rows"]; var custom = rows.AsArray()[0].AsObject()["Custom"]; - Assert.Equal(JsValue.Null, custom); + // With the spec-compliant IsPropertyReference, the custom resolver's TryGetCallable + // returns a function that produces undefined, so the result is undefined + Assert.Equal(JsValue.Undefined, custom); } } diff --git a/Jint.Tests.PublicInterface/SetTests.cs b/Jint.Tests.PublicInterface/SetTests.cs new file mode 100644 index 0000000000..839bbc0899 --- /dev/null +++ b/Jint.Tests.PublicInterface/SetTests.cs @@ -0,0 +1,40 @@ +using FluentAssertions; +using Jint.Native; + +namespace Jint.Tests.Runtime; + +public class SetTests +{ + [Fact] + public void ConConstructSet() + { + var engine = new Engine(); + + var set = engine.Intrinsics.Set.Construct(); + set.Add(42); + set.Add("foo"); + set.Size.Should().Be(2); + + set.Should().ContainInOrder(42, "foo"); + + set.Has(42).Should().BeTrue(); + set.Has("foo").Should().BeTrue(); + set.Has(24).Should().BeFalse(); + + engine.SetValue("s", set); + engine.Evaluate("s.size").Should().Be((JsNumber) 2); + engine.Evaluate("s.has(42)").Should().Be(JsBoolean.True); + engine.Evaluate("s.has('foo')").Should().Be(JsBoolean.True); + engine.Evaluate("s.has(24)").Should().Be(JsBoolean.False); + + set.Delete(42).Should().BeTrue(); + set.Has(42).Should().BeFalse(); + engine.Evaluate("s.has(42)").Should().Be(JsBoolean.False); + engine.Evaluate("s.size").Should().Be((JsNumber) 1); + + set.Clear(); + set.Should().BeEmpty(); + set.Size.Should().Be(0); + engine.Evaluate("s.size").Should().Be((JsNumber) 0); + } +} diff --git a/Jint.Tests.Test262/.config/dotnet-tools.json b/Jint.Tests.Test262/.config/dotnet-tools.json index 2b92de041b..7f995b437c 100644 --- a/Jint.Tests.Test262/.config/dotnet-tools.json +++ b/Jint.Tests.Test262/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "test262harness.console": { - "version": "1.0.1", + "version": "1.0.3", "commands": [ "test262" ] diff --git a/Jint.Tests.Test262/IcuCldrProvider.cs b/Jint.Tests.Test262/IcuCldrProvider.cs new file mode 100644 index 0000000000..8c5c76c013 --- /dev/null +++ b/Jint.Tests.Test262/IcuCldrProvider.cs @@ -0,0 +1,404 @@ +#nullable enable + +using ICU4N.Globalization; +using ICU4N.Impl; +using ICU4N.Text; +using ICU4N.Util; +using Jint.Native.Intl; + +namespace Jint.Tests.Test262; + +/// +/// CLDR provider implementation that combines ICU4N features with default provider fallback. +/// Uses ICU4N's PluralRules for plural category selection and ICUResourceBundle for +/// direct CLDR data access (unit patterns, list patterns, etc.). +/// Falls back to DefaultCldrProvider when ICU4N data is not available. +/// +public sealed class IcuCldrProvider : ICldrProvider +{ + private readonly ICldrProvider _fallback = DefaultCldrProvider.Instance; + + /// + /// Singleton instance. + /// + public static readonly IcuCldrProvider Instance = new(); + + private IcuCldrProvider() + { + } + + // === CLDR Resource Bundle Access === + + private static UResourceBundle? GetBundle(string locale) + { + try + { + var culture = new UCultureInfo(locale); + return UResourceBundle.GetBundleInstance(ICUData.IcuBaseName, culture); + } + catch + { + return null; + } + } + + private static UResourceBundle? GetBundleAt(UResourceBundle bundle, string path) + { + try + { + foreach (var segment in path.Split('/')) + { + bundle = bundle.Get(segment); + } + return bundle; + } + catch + { + return null; + } + } + + private static string? TryGetString(UResourceBundle bundle, string key) + { + try + { + return bundle.GetString(key); + } + catch + { + return null; + } + } + + // === List Patterns === + // ICU4N doesn't have ListFormatter API, but we can access CLDR data directly + + public ListPatterns? GetListPatterns(string locale, string type, string style) + { + // Map Intl type/style to CLDR path + var cldrType = (type, style) switch + { + ("conjunction", "long") => "standard", + ("conjunction", "short") => "standard-short", + ("conjunction", "narrow") => "standard-narrow", + ("disjunction", "long") => "or", + ("disjunction", "short") => "or-short", + ("disjunction", "narrow") => "or-narrow", + ("unit", "long") => "unit", + ("unit", "short") => "unit-short", + ("unit", "narrow") => "unit-narrow", + _ => "standard" + }; + + var path = $"listPattern/{cldrType}"; + + try + { + var bundle = GetBundle(locale); + if (bundle == null) + { + return _fallback.GetListPatterns(locale, type, style); + } + + var listBundle = GetBundleAt(bundle, path); + if (listBundle == null) + { + return _fallback.GetListPatterns(locale, type, style); + } + + var two = TryGetString(listBundle, "2"); + var start = TryGetString(listBundle, "start"); + var middle = TryGetString(listBundle, "middle"); + var end = TryGetString(listBundle, "end"); + + // If we don't have the patterns, fall back + if (two == null && start == null && end == null) + { + return _fallback.GetListPatterns(locale, type, style); + } + + return new ListPatterns + { + Two = two ?? "{0}, {1}", + Start = start ?? "{0}, {1}", + Middle = middle ?? "{0}, {1}", + End = end ?? "{0}, {1}" + }; + } + catch + { + return _fallback.GetListPatterns(locale, type, style); + } + } + + // === Relative Time Patterns === + // ICU4N doesn't have RelativeDateTimeFormatter, use fallback + public RelativeTimePatterns? GetRelativeTimePatterns(string locale, string unit, string style) + => _fallback.GetRelativeTimePatterns(locale, unit, style); + + public string? GetRelativeTimeSpecialPhrase(string locale, string unit, int value, bool past, string style) + => _fallback.GetRelativeTimeSpecialPhrase(locale, unit, value, past, style); + + // === Number Formatting === + + public string? GetNumberingSystemDigits(string numberingSystem) + { + // Use ICU's numbering system data + try + { + var ns = NumberingSystem.GetInstanceByName(numberingSystem); + if (ns != null && !ns.IsAlgorithmic) + { + return ns.Description; // Contains the digit characters + } + return _fallback.GetNumberingSystemDigits(numberingSystem); + } + catch + { + return _fallback.GetNumberingSystemDigits(numberingSystem); + } + } + + public CompactPatterns? GetCompactPatterns(string locale, string style) + => _fallback.GetCompactPatterns(locale, style); + + public Jint.Native.Intl.CurrencyData? GetCurrencyData(string locale, string currencyCode) + => _fallback.GetCurrencyData(locale, currencyCode); + + public UnitPatterns? GetUnitPatterns(string locale, string unit, string style) + { + // Map Intl unit names to CLDR paths + var cldrUnit = MapToCldrUnit(unit); + var path = $"units/{style}/{cldrUnit}"; + + try + { + var bundle = GetBundle(locale); + if (bundle == null) + { + return _fallback.GetUnitPatterns(locale, unit, style); + } + + var unitBundle = GetBundleAt(bundle, path); + if (unitBundle == null) + { + return _fallback.GetUnitPatterns(locale, unit, style); + } + + var displayName = TryGetString(unitBundle, "displayName"); + + // Get patterns for each plural category + var other = TryGetString(unitBundle, "unitPattern-count-other"); + var one = TryGetString(unitBundle, "unitPattern-count-one"); + var zero = TryGetString(unitBundle, "unitPattern-count-zero"); + var two = TryGetString(unitBundle, "unitPattern-count-two"); + var few = TryGetString(unitBundle, "unitPattern-count-few"); + var many = TryGetString(unitBundle, "unitPattern-count-many"); + + // If we don't have any patterns, fall back + if (other == null && one == null) + { + return _fallback.GetUnitPatterns(locale, unit, style); + } + + return new UnitPatterns + { + DisplayName = displayName ?? unit, + Other = other ?? $"{{0}} {unit}", + One = one, + Zero = zero, + Two = two, + Few = few, + Many = many + }; + } + catch + { + return _fallback.GetUnitPatterns(locale, unit, style); + } + } + + private static string MapToCldrUnit(string unit) + { + return unit switch + { + // Duration units + "year" or "years" => "duration-year", + "month" or "months" => "duration-month", + "week" or "weeks" => "duration-week", + "day" or "days" => "duration-day", + "hour" or "hours" => "duration-hour", + "minute" or "minutes" => "duration-minute", + "second" or "seconds" => "duration-second", + "millisecond" or "milliseconds" => "duration-millisecond", + "microsecond" or "microseconds" => "duration-microsecond", + "nanosecond" or "nanoseconds" => "duration-nanosecond", + // Length units + "meter" => "length-meter", + "kilometer" => "length-kilometer", + "centimeter" => "length-centimeter", + "millimeter" => "length-millimeter", + "inch" => "length-inch", + "foot" => "length-foot", + "yard" => "length-yard", + "mile" => "length-mile", + // Mass units + "gram" => "mass-gram", + "kilogram" => "mass-kilogram", + "milligram" => "mass-milligram", + "pound" => "mass-pound", + "ounce" => "mass-ounce", + // Other common units + "liter" => "volume-liter", + "milliliter" => "volume-milliliter", + "gallon" => "volume-gallon", + "celsius" => "temperature-celsius", + "fahrenheit" => "temperature-fahrenheit", + "percent" => "concentr-percent", + "byte" => "digital-byte", + "kilobyte" => "digital-kilobyte", + "megabyte" => "digital-megabyte", + "gigabyte" => "digital-gigabyte", + "terabyte" => "digital-terabyte", + _ => unit + }; + } + + // === Date/Time Formatting === + // Note: ICU4N doesn't have DateFormatSymbols ported yet, so we use the fallback provider + // which uses .NET's CultureInfo for basic date/time data. + + public DateTimePatterns? GetDateTimePatterns(string locale, string? dateStyle, string? timeStyle) + => _fallback.GetDateTimePatterns(locale, dateStyle, timeStyle); + + public string[]? GetMonthNames(string locale, string style) + => _fallback.GetMonthNames(locale, style); + + public string[]? GetWeekdayNames(string locale, string style) + => _fallback.GetWeekdayNames(locale, style); + + public string[]? GetDayPeriods(string locale, string style) + => _fallback.GetDayPeriods(locale, style); + + public string[]? GetEraNames(string locale, string style) + => _fallback.GetEraNames(locale, style); + + // === Display Names === + + public string? GetLanguageDisplayName(string locale, string code) + { + try + { + var displayLocale = new UCultureInfo(locale); + var codeLocale = new UCultureInfo(code); + var result = codeLocale.GetDisplayLanguage(displayLocale); + return string.IsNullOrEmpty(result) ? _fallback.GetLanguageDisplayName(locale, code) : result; + } + catch + { + return _fallback.GetLanguageDisplayName(locale, code); + } + } + + public string? GetRegionDisplayName(string locale, string code) + { + try + { + var displayLocale = new UCultureInfo(locale); + // Create a locale with just the region + var codeLocale = new UCultureInfo("und-" + code); + var result = codeLocale.GetDisplayCountry(displayLocale); + return string.IsNullOrEmpty(result) ? _fallback.GetRegionDisplayName(locale, code) : result; + } + catch + { + return _fallback.GetRegionDisplayName(locale, code); + } + } + + public string? GetScriptDisplayName(string locale, string code) + { + try + { + var displayLocale = new UCultureInfo(locale); + // Create a locale with just the script + var codeLocale = new UCultureInfo("und-" + code); + var result = codeLocale.GetDisplayScript(displayLocale); + return string.IsNullOrEmpty(result) ? _fallback.GetScriptDisplayName(locale, code) : result; + } + catch + { + return _fallback.GetScriptDisplayName(locale, code); + } + } + + public string? GetCurrencyDisplayName(string locale, string code) + => _fallback.GetCurrencyDisplayName(locale, code); + + // === Locale Data === + + public string? GetLikelySubtags(string locale) + { + try + { + var icuLocale = new UCultureInfo(locale); + var maximized = UCultureInfo.AddLikelySubtags(icuLocale); + return maximized?.Name ?? _fallback.GetLikelySubtags(locale); + } + catch + { + return _fallback.GetLikelySubtags(locale); + } + } + + public WeekInfo? GetWeekInfo(string locale) + => _fallback.GetWeekInfo(locale); + + // === Supported Values === + + public IReadOnlyCollection GetSupportedCalendars() + => _fallback.GetSupportedCalendars(); + + public IReadOnlyCollection GetSupportedCollations() + => _fallback.GetSupportedCollations(); + + public IReadOnlyCollection GetSupportedCurrencies() + => _fallback.GetSupportedCurrencies(); + + public IReadOnlyCollection GetSupportedNumberingSystems() + { + // Use fallback - ICU4N's list may not include all required numbering systems + // Our embedded NumberingSystemData has the complete ECMA-402 spec list + return _fallback.GetSupportedNumberingSystems(); + } + + public IReadOnlyCollection GetSupportedTimeZones() + => _fallback.GetSupportedTimeZones(); + + public IReadOnlyCollection GetSupportedUnits() + => _fallback.GetSupportedUnits(); + + // === Plural Rules === + + public string SelectPluralCategory(string locale, double value, string type) + { + try + { + var culture = new UCultureInfo(locale); + var pluralType = string.Equals(type, "ordinal", StringComparison.Ordinal) + ? PluralType.Ordinal + : PluralType.Cardinal; + + var rules = PluralRules.GetInstance(culture, pluralType); + var category = rules.Select(value); + + // ICU4N returns the category name (e.g., "one", "other", "few", "many", "zero", "two") + return category ?? "other"; + } + catch + { + // Fallback to default provider's English rules + return _fallback.SelectPluralCategory(locale, value, type); + } + } +} diff --git a/Jint.Tests.Test262/Jint.Tests.Test262.csproj b/Jint.Tests.Test262/Jint.Tests.Test262.csproj index 52449c656e..8a973847ce 100644 --- a/Jint.Tests.Test262/Jint.Tests.Test262.csproj +++ b/Jint.Tests.Test262/Jint.Tests.Test262.csproj @@ -1,12 +1,15 @@ - net8.0 + net10.0 ..\Jint\Jint.snk true false $(NoWarn);CS8002 Generated + true + Test262Harness.settings.json + $(GeneratedTestSuiteDir)\.settings.hash @@ -15,6 +18,8 @@ + + @@ -31,24 +36,57 @@ - - - - - - - - + + + + <_SettingsHashFilePath>$([System.IO.Path]::Combine($(MSBuildThisFileDirectory), $(SettingsHashFile))) + <_SettingsFilePath>$([System.IO.Path]::Combine($(MSBuildThisFileDirectory), $(SettingsFile))) + <_GeneratedDirPath>$([System.IO.Path]::Combine($(MSBuildThisFileDirectory), $(GeneratedTestSuiteDir))) + + + + + + + + + + + + + + + <_NeedsRegeneration>false + <_NeedsRegeneration Condition="!Exists('$(_GeneratedDirPath)')">true + <_NeedsRegeneration Condition="'$(_CurrentSettingsHash)' != '$(_StoredSettingsHash)'">true + + + + + + + + + + + + + + - - + + + + + - + + - \ No newline at end of file + diff --git a/Jint.Tests.Test262/Test262AgentManager.cs b/Jint.Tests.Test262/Test262AgentManager.cs new file mode 100644 index 0000000000..a0c0783a0f --- /dev/null +++ b/Jint.Tests.Test262/Test262AgentManager.cs @@ -0,0 +1,285 @@ +#nullable enable + +using System.Collections.Concurrent; +using System.Diagnostics; +using Jint.Native; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint.Tests.Test262; + +/// +/// Implements the $262.agent API for Test262 multi-agent tests. +/// See https://github.com/tc39/test262/blob/main/INTERPRETING.md +/// +internal sealed class Test262AgentManager : IDisposable +{ + private readonly ConcurrentQueue _reports = new(); + private readonly List _workers = []; + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + private byte[]? _broadcastBufferData; + private int _broadcastBufferLength; + private readonly object _broadcastLock = new(); + private readonly ManualResetEventSlim _broadcastEvent = new(false); + private int _broadcastVersion; + + /// + /// Adds the $262.agent object to the engine's $262 object. + /// + public void InstallAgent(Engine engine, ObjectInstance container) + { + var agent = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); + + // start(scriptSource) - spawn a new agent running the given script + agent.FastSetProperty("start", new PropertyDescriptor(new ClrFunction(engine, "start", + (_, args) => + { + var scriptSource = args.At(0).AsString(); + StartAgent(scriptSource); + return JsValue.Undefined; + }), true, true, true)); + + // broadcast(sab) - share SharedArrayBuffer with all agents + agent.FastSetProperty("broadcast", new PropertyDescriptor(new ClrFunction(engine, "broadcast", + (_, args) => + { + var buffer = args.At(0) switch + { + JsSharedArrayBuffer sab => sab, + JsTypedArray ta when ta._viewedArrayBuffer is JsSharedArrayBuffer sab2 => sab2, + _ => throw new JavaScriptException(engine.Realm.Intrinsics.TypeError, "broadcast requires SharedArrayBuffer") + }; + Broadcast(buffer); + return JsValue.Undefined; + }), true, true, true)); + + // getReport() - get a report from the queue (returns null if none) + agent.FastSetProperty("getReport", new PropertyDescriptor(new ClrFunction(engine, "getReport", + (_, _) => + { + var report = GetReport(); + return report is null ? JsValue.Null : new JsString(report); + }), true, true, true)); + + // sleep(ms) - sleep for ms milliseconds + agent.FastSetProperty("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", + (_, args) => + { + var ms = (int)TypeConverter.ToNumber(args.At(0)); + Thread.Sleep(ms); + return JsValue.Undefined; + }), true, true, true)); + + // monotonicNow() - high-resolution timestamp in milliseconds + agent.FastSetProperty("monotonicNow", new PropertyDescriptor(new ClrFunction(engine, "monotonicNow", + (_, _) => + { + return MonotonicNow(); + }), true, true, true)); + + container.FastSetProperty("agent", new PropertyDescriptor(agent, true, true, true)); + } + + private void StartAgent(string scriptSource) + { + var worker = new AgentWorker(this, scriptSource); + lock (_workers) + { + _workers.Add(worker); + } + worker.Start(); + } + + private void Broadcast(JsSharedArrayBuffer buffer) + { + lock (_broadcastLock) + { + // Store the raw buffer data so it can be shared across threads + _broadcastBufferData = buffer._arrayBufferData; + _broadcastBufferLength = buffer.ArrayBufferByteLength; + _broadcastVersion++; + _broadcastEvent.Set(); + } + } + + private string? GetReport() + { + return _reports.TryDequeue(out var report) ? report : null; + } + + private double MonotonicNow() + { + return _stopwatch.Elapsed.TotalMilliseconds; + } + + public void Dispose() + { + // Wait for all workers to finish + List workers; + lock (_workers) + { + workers = [.._workers]; + } + + foreach (var worker in workers) + { + worker.WaitForExit(TimeSpan.FromSeconds(30)); + } + + _broadcastEvent.Dispose(); + } + + private sealed class AgentWorker + { + private readonly Test262AgentManager _manager; + private readonly string _scriptSource; + private Thread? _thread; + private int _lastBroadcastVersion; + + public AgentWorker(Test262AgentManager manager, string scriptSource) + { + _manager = manager; + _scriptSource = scriptSource; + } + + public void Start() + { + _thread = new Thread(Run) + { + IsBackground = true, + Name = "Test262Agent" + }; + _thread.Start(); + } + + public void WaitForExit(TimeSpan timeout) + { + _thread?.Join(timeout); + } + + private void Run() + { + try + { + var engine = new Engine(cfg => + { + cfg.ExperimentalFeatures = ExperimentalFeature.All; + cfg.TimeoutInterval(TimeSpan.FromSeconds(30)); + }); + + // Setup agent-side $262.agent + var agentObj = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); + + // receiveBroadcast(callback) - wait for broadcast and call callback with SAB + agentObj.FastSetProperty("receiveBroadcast", new PropertyDescriptor(new ClrFunction(engine, "receiveBroadcast", + (thisValue, args) => + { + var callback = args.At(0); + WaitForBroadcast(engine, callback); + return JsValue.Undefined; + }), true, true, true)); + + // report(value) - send a report back to main thread + agentObj.FastSetProperty("report", new PropertyDescriptor(new ClrFunction(engine, "report", + (_, args) => + { + var value = TypeConverter.ToString(args.At(0)); + _manager._reports.Enqueue(value); + return JsValue.Undefined; + }), true, true, true)); + + // sleep(ms) - sleep for ms milliseconds + agentObj.FastSetProperty("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", + (_, args) => + { + var ms = (int)TypeConverter.ToNumber(args.At(0)); + Thread.Sleep(ms); + return JsValue.Undefined; + }), true, true, true)); + + // leaving() - signal agent is done + agentObj.FastSetProperty("leaving", new PropertyDescriptor(new ClrFunction(engine, "leaving", + (_, _) => + { + // Nothing special needed here + return JsValue.Undefined; + }), true, true, true)); + + // monotonicNow() - high-resolution timestamp in milliseconds + agentObj.FastSetProperty("monotonicNow", new PropertyDescriptor(new ClrFunction(engine, "monotonicNow", + (_, _) => + { + return _manager.MonotonicNow(); + }), true, true, true)); + + var container = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); + container.FastSetProperty("agent", new PropertyDescriptor(agentObj, true, true, true)); + engine.SetValue("$262", container); + + // Execute the agent script + var script = Engine.PrepareScript(_scriptSource, options: new ScriptPreparationOptions + { + ParsingOptions = ScriptParsingOptions.Default with { Tolerant = false }, + }); + engine.Execute(script); + } + catch (Exception ex) + { + // Report any exceptions back + _manager._reports.Enqueue($"AGENT ERROR: {ex.Message}"); + } + } + + private void WaitForBroadcast(Engine engine, JsValue callback) + { + while (true) + { + _manager._broadcastEvent.Wait(); + + byte[]? bufferData; + int bufferLength; + int version; + + lock (_manager._broadcastLock) + { + bufferData = _manager._broadcastBufferData; + bufferLength = _manager._broadcastBufferLength; + version = _manager._broadcastVersion; + } + + if (bufferData is not null && version > _lastBroadcastVersion) + { + _lastBroadcastVersion = version; + + // Create a SharedArrayBuffer instance in this engine that shares the same data + var sab = CreateSharedArrayBuffer(engine, bufferData, bufferLength); + + // Call the callback with the SAB + if (callback is ICallable callable) + { + callable.Call(JsValue.Undefined, [sab]); + } + + break; + } + + Thread.Sleep(1); + } + } + + private static JsSharedArrayBuffer CreateSharedArrayBuffer(Engine engine, byte[] data, int byteLength) + { + // Create a new SharedArrayBuffer that shares the same underlying byte array + // Get prototype via the constructor's "prototype" property + var constructor = engine.Realm.Intrinsics.SharedArrayBuffer; + var prototype = (ObjectInstance)constructor.Get(CommonProperties.Prototype); + var sab = new JsSharedArrayBuffer(engine, data, null, (uint)byteLength) + { + _prototype = prototype + }; + return sab; + } + } +} diff --git a/Jint.Tests.Test262/Test262Harness.settings.json b/Jint.Tests.Test262/Test262Harness.settings.json index 37bc78d5b1..a2a9f090cc 100644 --- a/Jint.Tests.Test262/Test262Harness.settings.json +++ b/Jint.Tests.Test262/Test262Harness.settings.json @@ -1,36 +1,44 @@ { - "SuiteGitSha": "941813e1f0e64ae9a5c4c5c8075f49ff89b0c642", + "SuiteGitSha": "f85c9511a380b9abb2877c42cc47e8fce06d56c5", //"SuiteDirectory": "//mnt/c/work/test262", "TargetPath": "./Generated", "Namespace": "Jint.Tests.Test262", "Parallel": true, "ExcludedFeatures": [ - "Array.fromAsync", - "async-iteration", - "Atomics", - "decorators", - "explicit-resource-management", - "import-assertions", - "iterator-helpers", - "Math.sumPrecise", + "import-defer", "regexp-lookbehind", "regexp-modifiers", "regexp-unicode-property-escapes", "regexp-v-flag", - "RegExp.escape", "source-phase-imports", "tail-call-optimization", - "Temporal", - "u180e" + "Temporal" ], "ExcludedFlags": [ + "CanBlockIsFalse" ], "ExcludedDirectories": [ - "annexB", - "intl402" + "annexB" ], "ExcludedFiles": [ + // requires rewrite of destructing towards spec + "language/destructuring/binding/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js", + "language/expressions/assignment/destructuring/keyed-destructuring-property-reference-target-evaluation-order-with-bindings.js", + "language/expressions/assignment/destructuring/default-expr-throws-iterator-return-get-throws.js", + "language/expressions/assignment/destructuring/default-expr-throws-iterator-return-is-not-callable.js", + "language/expressions/assignment/destructuring/target-assign-throws-iterator-return-get-throws.js", + "language/expressions/assignment/destructuring/target-assign-throws-iterator-return-is-not-callable.js", + + // Acornima doesn't implement unicode 15/16/17 yet + "language/identifiers/*-unicode-15.*.js", + "language/identifiers/*-unicode-16.*.js", + "language/identifiers/*-unicode-17.*.js", + + // Acornima parsing limitations + "language/statements/await-using/syntax/await-using-valid-for-await-using-of-of.js", + "language/statements/using/syntax/using-for-statement.js", + // Currently quite impossible to detect if assignment target is CoverParenthesizedExpression "language/expressions/assignment/fn-name-lhs-cover.js", @@ -43,6 +51,19 @@ "built-ins/RegExp/quantifier-integer-limit.js", "language/literals/regexp/named-groups/forward-reference.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-negative-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-digit-class-escape-positive-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-negative-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-non-digit-class-escape-positive-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-negative-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-non-whitespace-class-escape-positive-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-negative-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-non-word-class-escape-positive-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-negative-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-whitespace-class-escape-positive-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-negative-cases.js", + "built-ins/RegExp/CharacterClassEscapes/character-class-word-class-escape-positive-cases.js", + // RegExp handling problems "built-ins/RegExp/nullable-quantifier.js", "built-ins/RegExp/prototype/exec/S15.10.6.2_A1_T6.js", @@ -68,1202 +89,98 @@ "built-ins/String/prototype/match/duplicate-named-indices-groups-properties.js", "built-ins/String/prototype/match/duplicate-named-indices-groups-properties.js", - // needs "small" async rewrite - "language/module-code/top-level-await/async-module-does-not-block-sibling-modules.js", - // requires investigation how to process complex function name evaluation for property "built-ins/Function/prototype/toString/method-computed-property-name.js", "language/expressions/class/elements/class-name-static-initializer-anonymous.js", - // http://www.ecma-international.org/ecma-262/#sec-block-level-function-declarations-web-legacy-compatibility-semantics not implemented (block level functions) - "language/statements/let/block-local-closure-set-before-initialization.js", - - // Windows line ending differences - "built-ins/String/raw/special-characters.js", - // delete/add detection not implemented for map iterator during iteration "built-ins/Map/prototype/forEach/iterates-values-deleted-then-readded.js", "built-ins/MapIteratorPrototype/next/iteration-mutable.js", "built-ins/Set/prototype/forEach/iterates-values-revisits-after-delete-re-add.js", - // Line feed problems (git, windows, linux) - "language/expressions/template-literal/tv-line-terminator-sequence.js", - // 100 fraction digits is not supported due to .NET format specifier limitation "built-ins/Number/prototype/toFixed/range.js", - // C# can't distinguish 1.797693134862315808e+308 and 1.797693134862315708145274237317e+308 - "language/types/number/8.5.1.js", + // special casing data + "built-ins/**/special_casing*.js", - // generators - "built-ins/GeneratorFunction/instance-yield-expr-in-param.js", - "built-ins/GeneratorFunction/instance-yield-expr-in-param.js", - "built-ins/GeneratorPrototype/next/return-yield-expr.js", - "built-ins/GeneratorPrototype/next/return-yield-expr.js", - "built-ins/GeneratorPrototype/return/try-catch-before-try.js", - "built-ins/GeneratorPrototype/return/try-catch-before-try.js", - "built-ins/GeneratorPrototype/return/try-catch-following-catch.js", - "built-ins/GeneratorPrototype/return/try-catch-following-catch.js", - "built-ins/GeneratorPrototype/return/try-catch-within-catch.js", - "built-ins/GeneratorPrototype/return/try-catch-within-catch.js", - "built-ins/GeneratorPrototype/return/try-catch-within-try.js", - "built-ins/GeneratorPrototype/return/try-catch-within-try.js", - "built-ins/GeneratorPrototype/return/try-finally-before-try.js", - "built-ins/GeneratorPrototype/return/try-finally-before-try.js", - "built-ins/GeneratorPrototype/return/try-finally-following-finally.js", - "built-ins/GeneratorPrototype/return/try-finally-following-finally.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-catch.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-catch.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-finally.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-finally.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-inner-try.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-inner-try.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-outer-try-after-nested.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-outer-try-after-nested.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-outer-try-before-nested.js", - "built-ins/GeneratorPrototype/return/try-finally-nested-try-catch-within-outer-try-before-nested.js", - "built-ins/GeneratorPrototype/return/try-finally-within-finally.js", - "built-ins/GeneratorPrototype/return/try-finally-within-finally.js", - "built-ins/GeneratorPrototype/return/try-finally-within-try.js", - "built-ins/GeneratorPrototype/return/try-finally-within-try.js", - "built-ins/GeneratorPrototype/throw/try-catch-before-try.js", - "built-ins/GeneratorPrototype/throw/try-catch-before-try.js", - "built-ins/GeneratorPrototype/throw/try-catch-following-catch.js", - "built-ins/GeneratorPrototype/throw/try-catch-following-catch.js", - "built-ins/GeneratorPrototype/throw/try-catch-within-catch.js", - "built-ins/GeneratorPrototype/throw/try-catch-within-catch.js", - "built-ins/GeneratorPrototype/throw/try-catch-within-try.js", - "built-ins/GeneratorPrototype/throw/try-catch-within-try.js", - "built-ins/GeneratorPrototype/throw/try-finally-before-try.js", - "built-ins/GeneratorPrototype/throw/try-finally-before-try.js", - "built-ins/GeneratorPrototype/throw/try-finally-following-finally.js", - "built-ins/GeneratorPrototype/throw/try-finally-following-finally.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-catch.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-catch.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-finally.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-finally.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-inner-try.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-inner-try.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-outer-try-after-nested.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-outer-try-after-nested.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-outer-try-before-nested.js", - "built-ins/GeneratorPrototype/throw/try-finally-nested-try-catch-within-outer-try-before-nested.js", - "built-ins/GeneratorPrototype/throw/try-finally-within-finally.js", - "built-ins/GeneratorPrototype/throw/try-finally-within-finally.js", - "built-ins/GeneratorPrototype/throw/try-finally-within-try.js", - "built-ins/GeneratorPrototype/throw/try-finally-within-try.js", - "language/expressions/arrow-function/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/expressions/arrow-function/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/expressions/arrow-function/dstr/ary-ptrn-elision.js", - "language/expressions/arrow-function/dstr/ary-ptrn-elision.js", - "language/expressions/arrow-function/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/arrow-function/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/arrow-function/dstr/dflt-ary-ptrn-elision.js", - "language/expressions/arrow-function/dstr/dflt-ary-ptrn-elision.js", - "language/expressions/assignment/dstr/array-elem-init-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-init-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-iter-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-elem-iter-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-elem-iter-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-elem-iter-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-elem-iter-rtrn-close.js", - "language/expressions/assignment/dstr/array-elem-iter-rtrn-close.js", - "language/expressions/assignment/dstr/array-elem-nested-array-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-nested-array-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-nested-obj-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-nested-obj-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-target-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-target-yield-expr.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-list-rtrn-close.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close.js", - "language/expressions/assignment/dstr/array-elem-trlg-iter-rest-rtrn-close.js", - "language/expressions/assignment/dstr/array-iteration.js", - "language/expressions/assignment/dstr/array-iteration.js", - "language/expressions/assignment/dstr/array-rest-iter-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-rest-iter-rtrn-close-err.js", - "language/expressions/assignment/dstr/array-rest-iter-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-rest-iter-rtrn-close-null.js", - "language/expressions/assignment/dstr/array-rest-iter-rtrn-close.js", - "language/expressions/assignment/dstr/array-rest-iter-rtrn-close.js", - "language/expressions/assignment/dstr/array-rest-nested-array-yield-expr.js", - "language/expressions/assignment/dstr/array-rest-nested-array-yield-expr.js", - "language/expressions/assignment/dstr/array-rest-nested-obj-yield-expr.js", - "language/expressions/assignment/dstr/array-rest-nested-obj-yield-expr.js", - "language/expressions/assignment/dstr/array-rest-yield-expr.js", - "language/expressions/assignment/dstr/array-rest-yield-expr.js", - "language/expressions/assignment/dstr/obj-id-identifier-yield-ident-valid.js", - "language/expressions/assignment/dstr/obj-id-init-yield-expr.js", - "language/expressions/assignment/dstr/obj-id-init-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-elem-init-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-elem-init-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-elem-target-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-elem-target-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-nested-array-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-nested-array-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-nested-obj-yield-expr.js", - "language/expressions/assignment/dstr/obj-prop-nested-obj-yield-expr.js", - "language/expressions/class/accessor-name-inst-computed-yield-expr.js", - "language/expressions/class/accessor-name-inst-computed-yield-expr.js", - "language/expressions/class/accessor-name-static-computed-yield-expr.js", - "language/expressions/class/accessor-name-static-computed-yield-expr.js", - "language/expressions/class/dstr/gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/gen-meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/gen-meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-static-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/class/dstr/private-meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/dstr/private-meth-static-dflt-ary-ptrn-elision.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/gen-private-method-static/yield-spread-arr-multiple.js", - "language/expressions/class/elements/gen-private-method-static/yield-spread-arr-multiple.js", - "language/expressions/class/elements/gen-private-method-static/yield-spread-arr-single.js", - "language/expressions/class/elements/gen-private-method-static/yield-spread-arr-single.js", - "language/expressions/class/elements/gen-private-method-static/yield-spread-obj.js", - "language/expressions/class/elements/gen-private-method-static/yield-spread-obj.js", - "language/expressions/class/elements/gen-private-method/yield-spread-arr-multiple.js", - "language/expressions/class/elements/gen-private-method/yield-spread-arr-multiple.js", - "language/expressions/class/elements/gen-private-method/yield-spread-arr-single.js", - "language/expressions/class/elements/gen-private-method/yield-spread-arr-single.js", - "language/expressions/class/elements/gen-private-method/yield-spread-obj.js", - "language/expressions/class/elements/gen-private-method/yield-spread-obj.js", - "language/expressions/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier-alt.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier.js", - "language/expressions/class/gen-method-static/yield-spread-arr-multiple.js", - "language/expressions/class/gen-method-static/yield-spread-arr-multiple.js", - "language/expressions/class/gen-method-static/yield-spread-arr-single.js", - "language/expressions/class/gen-method-static/yield-spread-arr-single.js", - "language/expressions/class/gen-method-static/yield-spread-obj.js", - "language/expressions/class/gen-method-static/yield-spread-obj.js", - "language/expressions/class/gen-method/yield-spread-arr-multiple.js", - "language/expressions/class/gen-method/yield-spread-arr-multiple.js", - "language/expressions/class/gen-method/yield-spread-arr-single.js", - "language/expressions/class/gen-method/yield-spread-arr-single.js", - "language/expressions/class/gen-method/yield-spread-obj.js", - "language/expressions/class/gen-method/yield-spread-obj.js", - "language/expressions/dynamic-import/eval-export-dflt-expr-gen-anon.js", - "language/expressions/dynamic-import/eval-export-dflt-expr-gen-named.js", - "language/expressions/dynamic-import/import-attributes/2nd-param-evaluation-abrupt-return.js", + // eval-code arguments bindings in direct eval + "language/eval-code/direct/*-arguments-*.js", + // dynamic import attributes abrupt return "language/expressions/dynamic-import/import-attributes/2nd-param-evaluation-abrupt-return.js", - "language/expressions/dynamic-import/import-attributes/2nd-param-yield-expr.js", - "language/expressions/dynamic-import/import-attributes/2nd-param-yield-expr.js", - "language/expressions/function/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/expressions/function/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/expressions/function/dstr/ary-ptrn-elision.js", - "language/expressions/function/dstr/ary-ptrn-elision.js", - "language/expressions/function/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/function/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/function/dstr/dflt-ary-ptrn-elision.js", - "language/expressions/function/dstr/dflt-ary-ptrn-elision.js", - "language/expressions/generators/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/expressions/generators/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/expressions/generators/dstr/ary-ptrn-elision.js", - "language/expressions/generators/dstr/ary-ptrn-elision.js", - "language/expressions/generators/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/generators/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/generators/dstr/dflt-ary-ptrn-elision.js", - "language/expressions/generators/dstr/dflt-ary-ptrn-elision.js", - "language/expressions/generators/eval-var-scope-syntax-err.js", - "language/expressions/generators/named-yield-identifier-non-strict.js", - "language/expressions/generators/named-yield-identifier-spread-non-strict.js", - "language/expressions/generators/named-yield-spread-arr-multiple.js", - "language/expressions/generators/named-yield-spread-arr-multiple.js", - "language/expressions/generators/named-yield-spread-arr-single.js", - "language/expressions/generators/named-yield-spread-arr-single.js", - "language/expressions/generators/named-yield-spread-obj.js", - "language/expressions/generators/named-yield-spread-obj.js", - "language/expressions/generators/scope-param-elem-var-close.js", - "language/expressions/generators/scope-param-elem-var-open.js", - "language/expressions/generators/scope-param-rest-elem-var-close.js", - "language/expressions/generators/scope-param-rest-elem-var-open.js", - "language/expressions/generators/yield-as-function-expression-binding-identifier.js", - "language/expressions/generators/yield-as-literal-property-name.js", - "language/expressions/generators/yield-as-literal-property-name.js", - "language/expressions/generators/yield-as-property-name.js", - "language/expressions/generators/yield-as-property-name.js", - "language/expressions/generators/yield-as-yield-operand.js", - "language/expressions/generators/yield-as-yield-operand.js", - "language/expressions/generators/yield-identifier-non-strict.js", - "language/expressions/generators/yield-identifier-spread-non-strict.js", - "language/expressions/generators/yield-newline.js", - "language/expressions/generators/yield-newline.js", - "language/expressions/generators/yield-spread-arr-multiple.js", - "language/expressions/generators/yield-spread-arr-multiple.js", - "language/expressions/generators/yield-spread-arr-single.js", - "language/expressions/generators/yield-spread-arr-single.js", - "language/expressions/generators/yield-spread-obj.js", - "language/expressions/generators/yield-spread-obj.js", - "language/expressions/generators/yield-star-before-newline.js", - "language/expressions/generators/yield-star-before-newline.js", - "language/expressions/in/private-field-rhs-yield-present.js", - "language/expressions/in/private-field-rhs-yield-present.js", - "language/expressions/in/rhs-yield-present.js", - "language/expressions/in/rhs-yield-present.js", - "language/expressions/object/accessor-name-computed-yield-expr.js", - "language/expressions/object/accessor-name-computed-yield-expr.js", - "language/expressions/object/accessor-name-computed.js", - "language/expressions/object/accessor-name-computed.js", - "language/expressions/object/dstr/gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/gen-meth-ary-ptrn-elision.js", - "language/expressions/object/dstr/gen-meth-ary-ptrn-elision.js", - "language/expressions/object/dstr/gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/gen-meth-dflt-ary-ptrn-elision.js", - "language/expressions/object/dstr/gen-meth-dflt-ary-ptrn-elision.js", - "language/expressions/object/dstr/meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/meth-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/meth-ary-ptrn-elision.js", - "language/expressions/object/dstr/meth-ary-ptrn-elision.js", - "language/expressions/object/dstr/meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/expressions/object/dstr/meth-dflt-ary-ptrn-elision.js", - "language/expressions/object/dstr/meth-dflt-ary-ptrn-elision.js", - "language/expressions/object/method-definition/computed-property-name-yield-expression.js", - "language/expressions/object/method-definition/gen-meth-eval-var-scope-syntax-err.js", - "language/expressions/object/method-definition/gen-yield-identifier-non-strict.js", - "language/expressions/object/method-definition/gen-yield-identifier-spread-non-strict.js", - "language/expressions/object/method-definition/gen-yield-spread-arr-multiple.js", - "language/expressions/object/method-definition/gen-yield-spread-arr-multiple.js", - "language/expressions/object/method-definition/gen-yield-spread-arr-single.js", - "language/expressions/object/method-definition/gen-yield-spread-arr-single.js", - "language/expressions/object/method-definition/gen-yield-spread-obj.js", - "language/expressions/object/method-definition/gen-yield-spread-obj.js", - "language/expressions/object/method-definition/generator-prop-name-yield-expr.js", - "language/expressions/object/method-definition/name-prop-name-yield-expr.js", - "language/expressions/object/method-definition/yield-as-expression-with-rhs.js", - "language/expressions/object/method-definition/yield-as-expression-with-rhs.js", - "language/expressions/object/method-definition/yield-as-expression-without-rhs.js", - "language/expressions/object/method-definition/yield-as-expression-without-rhs.js", - "language/expressions/object/method-definition/yield-as-function-expression-binding-identifier.js", - "language/expressions/object/method-definition/yield-as-generator-method-binding-identifier.js", - "language/expressions/object/method-definition/yield-as-generator-method-binding-identifier.js", - "language/expressions/object/method-definition/yield-as-literal-property-name.js", - "language/expressions/object/method-definition/yield-as-literal-property-name.js", - "language/expressions/object/method-definition/yield-as-property-name.js", - "language/expressions/object/method-definition/yield-as-property-name.js", - "language/expressions/object/method-definition/yield-as-yield-operand.js", - "language/expressions/object/method-definition/yield-as-yield-operand.js", - "language/expressions/object/method-definition/yield-newline.js", - "language/expressions/object/method-definition/yield-newline.js", - "language/expressions/object/method-definition/yield-star-before-newline.js", - "language/expressions/object/method-definition/yield-star-before-newline.js", - "language/expressions/object/scope-gen-meth-param-elem-var-close.js", - "language/expressions/object/scope-gen-meth-param-elem-var-open.js", - "language/expressions/object/scope-gen-meth-param-rest-elem-var-close.js", - "language/expressions/object/scope-gen-meth-param-rest-elem-var-open.js", - "language/expressions/yield/in-rltn-expr.js", - "language/expressions/yield/in-rltn-expr.js", - "language/expressions/yield/iter-value-specified.js", - "language/expressions/yield/iter-value-specified.js", - "language/expressions/yield/rhs-omitted.js", - "language/expressions/yield/rhs-omitted.js", - "language/expressions/yield/rhs-primitive.js", - "language/expressions/yield/rhs-primitive.js", - "language/expressions/yield/rhs-regexp.js", - "language/expressions/yield/rhs-regexp.js", - "language/expressions/yield/rhs-template-middle.js", - "language/expressions/yield/rhs-template-middle.js", - "language/expressions/yield/rhs-unresolvable.js", - "language/expressions/yield/rhs-unresolvable.js", - "language/expressions/yield/rhs-yield.js", - "language/expressions/yield/rhs-yield.js", - "language/expressions/yield/star-array.js", - "language/expressions/yield/star-array.js", - "language/expressions/yield/star-in-rltn-expr.js", - "language/expressions/yield/star-in-rltn-expr.js", - "language/expressions/yield/star-iterable.js", - "language/expressions/yield/star-iterable.js", - "language/expressions/yield/star-return-is-null.js", - "language/expressions/yield/star-return-is-null.js", - "language/expressions/yield/star-rhs-iter-get-call-err.js", - "language/expressions/yield/star-rhs-iter-get-call-err.js", - "language/expressions/yield/star-rhs-iter-get-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-get-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-get-get-err.js", - "language/expressions/yield/star-rhs-iter-get-get-err.js", - "language/expressions/yield/star-rhs-iter-nrml-next-call-err.js", - "language/expressions/yield/star-rhs-iter-nrml-next-call-err.js", - "language/expressions/yield/star-rhs-iter-nrml-next-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-nrml-next-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-nrml-next-get-err.js", - "language/expressions/yield/star-rhs-iter-nrml-next-get-err.js", - "language/expressions/yield/star-rhs-iter-nrml-next-invoke.js", - "language/expressions/yield/star-rhs-iter-nrml-next-invoke.js", - "language/expressions/yield/star-rhs-iter-nrml-res-done-no-value.js", - "language/expressions/yield/star-rhs-iter-nrml-res-done-no-value.js", - "language/expressions/yield/star-rhs-iter-nrml-res-value-err.js", - "language/expressions/yield/star-rhs-iter-nrml-res-value-err.js", - "language/expressions/yield/star-rhs-iter-nrml-res-value-final.js", - "language/expressions/yield/star-rhs-iter-nrml-res-value-final.js", - "language/expressions/yield/star-rhs-iter-rtrn-no-rtrn.js", - "language/expressions/yield/star-rhs-iter-rtrn-no-rtrn.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-done-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-done-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-done-no-value.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-done-no-value.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-value-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-value-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-value-final.js", - "language/expressions/yield/star-rhs-iter-rtrn-res-value-final.js", - "language/expressions/yield/star-rhs-iter-rtrn-rtrn-call-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-rtrn-call-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-rtrn-get-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-rtrn-get-err.js", - "language/expressions/yield/star-rhs-iter-rtrn-rtrn-invoke.js", - "language/expressions/yield/star-rhs-iter-rtrn-rtrn-invoke.js", - "language/expressions/yield/star-rhs-iter-thrw-res-done-err.js", - "language/expressions/yield/star-rhs-iter-thrw-res-done-err.js", - "language/expressions/yield/star-rhs-iter-thrw-res-done-no-value.js", - "language/expressions/yield/star-rhs-iter-thrw-res-done-no-value.js", - "language/expressions/yield/star-rhs-iter-thrw-res-value-err.js", - "language/expressions/yield/star-rhs-iter-thrw-res-value-err.js", - "language/expressions/yield/star-rhs-iter-thrw-res-value-final.js", - "language/expressions/yield/star-rhs-iter-thrw-res-value-final.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-call-err.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-call-err.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-get-err.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-get-err.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-invoke.js", - "language/expressions/yield/star-rhs-iter-thrw-thrw-invoke.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-no-rtrn.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-no-rtrn.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-call-err.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-call-err.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-call-non-obj.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-get-err.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-get-err.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-invoke.js", - "language/expressions/yield/star-rhs-iter-thrw-violation-rtrn-invoke.js", - "language/expressions/yield/star-rhs-unresolvable.js", - "language/expressions/yield/star-rhs-unresolvable.js", - "language/expressions/yield/star-string.js", - "language/expressions/yield/star-string.js", - "language/expressions/yield/star-throw-is-null.js", - "language/expressions/yield/star-throw-is-null.js", - "language/expressions/yield/within-for.js", - "language/expressions/yield/within-for.js", - "language/statements/class/accessor-name-inst-computed-yield-expr.js", - "language/statements/class/accessor-name-inst-computed-yield-expr.js", - "language/statements/class/accessor-name-static-computed-yield-expr.js", - "language/statements/class/accessor-name-static-computed-yield-expr.js", - "language/statements/class/definition/methods-gen-yield-as-expression-with-rhs.js", - "language/statements/class/definition/methods-gen-yield-as-expression-with-rhs.js", - "language/statements/class/definition/methods-gen-yield-as-expression-without-rhs.js", - "language/statements/class/definition/methods-gen-yield-as-expression-without-rhs.js", - "language/statements/class/definition/methods-gen-yield-as-generator-method-binding-identifier.js", - "language/statements/class/definition/methods-gen-yield-as-generator-method-binding-identifier.js", - "language/statements/class/definition/methods-gen-yield-as-literal-property-name.js", - "language/statements/class/definition/methods-gen-yield-as-literal-property-name.js", - "language/statements/class/definition/methods-gen-yield-as-property-name.js", - "language/statements/class/definition/methods-gen-yield-as-property-name.js", - "language/statements/class/definition/methods-gen-yield-as-yield-operand.js", - "language/statements/class/definition/methods-gen-yield-as-yield-operand.js", - "language/statements/class/definition/methods-gen-yield-newline.js", - "language/statements/class/definition/methods-gen-yield-newline.js", - "language/statements/class/definition/methods-gen-yield-star-before-newline.js", - "language/statements/class/definition/methods-gen-yield-star-before-newline.js", - "language/statements/class/dstr/gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/gen-meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/gen-meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-gen-meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-static-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-static-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-static-dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/class/dstr/private-meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/dstr/private-meth-static-dflt-ary-ptrn-elision.js", - "language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-async-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/after-same-line-static-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/gen-private-method-static/yield-spread-arr-multiple.js", - "language/statements/class/elements/gen-private-method-static/yield-spread-arr-multiple.js", - "language/statements/class/elements/gen-private-method-static/yield-spread-arr-single.js", - "language/statements/class/elements/gen-private-method-static/yield-spread-arr-single.js", - "language/statements/class/elements/gen-private-method-static/yield-spread-obj.js", - "language/statements/class/elements/gen-private-method-static/yield-spread-obj.js", - "language/statements/class/elements/gen-private-method/yield-spread-arr-multiple.js", - "language/statements/class/elements/gen-private-method/yield-spread-arr-multiple.js", - "language/statements/class/elements/gen-private-method/yield-spread-arr-single.js", - "language/statements/class/elements/gen-private-method/yield-spread-arr-single.js", - "language/statements/class/elements/gen-private-method/yield-spread-obj.js", - "language/statements/class/elements/gen-private-method/yield-spread-obj.js", - "language/statements/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-definitions-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/multiple-stacked-definitions-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-no-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/new-sc-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/regular-definitions-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/regular-definitions-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-async-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-gen-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-method-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-method-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/same-line-method-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-method-privatename-identifier.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier-alt.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/elements/wrapped-in-sc-rs-static-generator-method-privatename-identifier.js", - "language/statements/class/gen-method-static/yield-spread-arr-multiple.js", - "language/statements/class/gen-method-static/yield-spread-arr-multiple.js", - "language/statements/class/gen-method-static/yield-spread-arr-single.js", - "language/statements/class/gen-method-static/yield-spread-arr-single.js", - "language/statements/class/gen-method-static/yield-spread-obj.js", - "language/statements/class/gen-method-static/yield-spread-obj.js", - "language/statements/class/gen-method/yield-spread-arr-multiple.js", - "language/statements/class/gen-method/yield-spread-arr-multiple.js", - "language/statements/class/gen-method/yield-spread-arr-single.js", - "language/statements/class/gen-method/yield-spread-arr-single.js", - "language/statements/class/gen-method/yield-spread-obj.js", - "language/statements/class/gen-method/yield-spread-obj.js", - "language/statements/const/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/const/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/const/dstr/ary-ptrn-elision.js", - "language/statements/const/dstr/ary-ptrn-elision.js", - "language/statements/for-of/break-from-catch.js", - "language/statements/for-of/break-from-catch.js", - "language/statements/for-of/break-from-finally.js", - "language/statements/for-of/break-from-finally.js", - "language/statements/for-of/break-from-try.js", - "language/statements/for-of/break-from-try.js", - "language/statements/for-of/break-label-from-catch.js", - "language/statements/for-of/break-label-from-catch.js", - "language/statements/for-of/break-label-from-finally.js", - "language/statements/for-of/break-label-from-finally.js", - "language/statements/for-of/break-label-from-try.js", - "language/statements/for-of/break-label-from-try.js", - "language/statements/for-of/break-label.js", - "language/statements/for-of/break-label.js", - "language/statements/for-of/break.js", - "language/statements/for-of/break.js", - "language/statements/for-of/dstr/array-elem-init-yield-expr.js", - "language/statements/for-of/dstr/array-elem-init-yield-expr.js", - "language/statements/for-of/dstr/array-elem-iter-rtrn-close-err.js", - "language/statements/for-of/dstr/array-elem-iter-rtrn-close-err.js", - "language/statements/for-of/dstr/array-elem-iter-rtrn-close-null.js", - "language/statements/for-of/dstr/array-elem-iter-rtrn-close-null.js", - "language/statements/for-of/dstr/array-elem-iter-rtrn-close.js", - "language/statements/for-of/dstr/array-elem-iter-rtrn-close.js", - "language/statements/for-of/dstr/array-elem-nested-array-yield-expr.js", - "language/statements/for-of/dstr/array-elem-nested-array-yield-expr.js", - "language/statements/for-of/dstr/array-elem-nested-obj-yield-expr.js", - "language/statements/for-of/dstr/array-elem-nested-obj-yield-expr.js", - "language/statements/for-of/dstr/array-elem-target-yield-expr.js", - "language/statements/for-of/dstr/array-elem-target-yield-expr.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close-err.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close-err.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close-null.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close-null.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-list-rtrn-close.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close-err.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close-err.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close-null.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close-null.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close.js", - "language/statements/for-of/dstr/array-elem-trlg-iter-rest-rtrn-close.js", - "language/statements/for-of/dstr/array-iteration.js", - "language/statements/for-of/dstr/array-iteration.js", - "language/statements/for-of/dstr/array-rest-iter-rtrn-close-err.js", - "language/statements/for-of/dstr/array-rest-iter-rtrn-close-err.js", - "language/statements/for-of/dstr/array-rest-iter-rtrn-close-null.js", - "language/statements/for-of/dstr/array-rest-iter-rtrn-close-null.js", - "language/statements/for-of/dstr/array-rest-iter-rtrn-close.js", - "language/statements/for-of/dstr/array-rest-iter-rtrn-close.js", - "language/statements/for-of/dstr/array-rest-nested-array-yield-expr.js", - "language/statements/for-of/dstr/array-rest-nested-array-yield-expr.js", - "language/statements/for-of/dstr/array-rest-nested-obj-yield-expr.js", - "language/statements/for-of/dstr/array-rest-nested-obj-yield-expr.js", - "language/statements/for-of/dstr/array-rest-yield-expr.js", - "language/statements/for-of/dstr/array-rest-yield-expr.js", - "language/statements/for-of/dstr/const-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for-of/dstr/const-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for-of/dstr/const-ary-ptrn-elision.js", - "language/statements/for-of/dstr/const-ary-ptrn-elision.js", - "language/statements/for-of/dstr/let-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for-of/dstr/let-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for-of/dstr/let-ary-ptrn-elision.js", - "language/statements/for-of/dstr/let-ary-ptrn-elision.js", - "language/statements/for-of/dstr/obj-id-identifier-yield-ident-valid.js", - "language/statements/for-of/dstr/obj-id-init-yield-expr.js", - "language/statements/for-of/dstr/obj-id-init-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-elem-init-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-elem-init-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-elem-target-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-elem-target-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-nested-array-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-nested-array-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-nested-obj-yield-expr.js", - "language/statements/for-of/dstr/obj-prop-nested-obj-yield-expr.js", - "language/statements/for-of/dstr/var-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for-of/dstr/var-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for-of/dstr/var-ary-ptrn-elision.js", - "language/statements/for-of/dstr/var-ary-ptrn-elision.js", - "language/statements/for-of/generator-close-via-break.js", - "language/statements/for-of/generator-close-via-break.js", - "language/statements/for-of/generator-close-via-continue.js", - "language/statements/for-of/generator-close-via-continue.js", - "language/statements/for-of/generator-close-via-return.js", - "language/statements/for-of/generator-close-via-return.js", - "language/statements/for-of/generator-close-via-throw.js", - "language/statements/for-of/generator-close-via-throw.js", - "language/statements/for-of/nested.js", - "language/statements/for-of/nested.js", - "language/statements/for-of/return-from-catch.js", - "language/statements/for-of/return-from-catch.js", - "language/statements/for-of/return-from-finally.js", - "language/statements/for-of/return-from-finally.js", - "language/statements/for-of/return-from-try.js", - "language/statements/for-of/return-from-try.js", - "language/statements/for-of/return.js", - "language/statements/for-of/return.js", - "language/statements/for-of/yield-from-catch.js", - "language/statements/for-of/yield-from-catch.js", - "language/statements/for-of/yield-from-finally.js", - "language/statements/for-of/yield-from-finally.js", - "language/statements/for-of/yield-from-try.js", - "language/statements/for-of/yield-from-try.js", - "language/statements/for-of/yield-star-from-catch.js", - "language/statements/for-of/yield-star-from-catch.js", - "language/statements/for-of/yield-star-from-finally.js", - "language/statements/for-of/yield-star-from-finally.js", - "language/statements/for-of/yield-star-from-try.js", - "language/statements/for-of/yield-star-from-try.js", - "language/statements/for-of/yield-star.js", - "language/statements/for-of/yield-star.js", - "language/statements/for-of/yield.js", - "language/statements/for-of/yield.js", - "language/statements/for/dstr/const-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for/dstr/const-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for/dstr/const-ary-ptrn-elision.js", - "language/statements/for/dstr/const-ary-ptrn-elision.js", - "language/statements/for/dstr/let-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for/dstr/let-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for/dstr/let-ary-ptrn-elision.js", - "language/statements/for/dstr/let-ary-ptrn-elision.js", - "language/statements/for/dstr/var-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for/dstr/var-ary-ptrn-elem-ary-elision-init.js", - "language/statements/for/dstr/var-ary-ptrn-elision.js", - "language/statements/for/dstr/var-ary-ptrn-elision.js", - "language/statements/function/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/function/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/function/dstr/ary-ptrn-elision.js", - "language/statements/function/dstr/ary-ptrn-elision.js", - "language/statements/function/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/function/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/function/dstr/dflt-ary-ptrn-elision.js", - "language/statements/function/dstr/dflt-ary-ptrn-elision.js", - "language/statements/generators/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/generators/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/generators/dstr/ary-ptrn-elision.js", - "language/statements/generators/dstr/ary-ptrn-elision.js", - "language/statements/generators/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/generators/dstr/dflt-ary-ptrn-elem-ary-elision-init.js", - "language/statements/generators/dstr/dflt-ary-ptrn-elision.js", - "language/statements/generators/dstr/dflt-ary-ptrn-elision.js", - "language/statements/generators/eval-var-scope-syntax-err.js", - "language/statements/generators/scope-param-elem-var-close.js", - "language/statements/generators/scope-param-elem-var-open.js", - "language/statements/generators/scope-param-rest-elem-var-close.js", - "language/statements/generators/scope-param-rest-elem-var-open.js", - "language/statements/generators/yield-as-function-expression-binding-identifier.js", - "language/statements/generators/yield-as-generator-declaration-binding-identifier.js", - "language/statements/generators/yield-as-literal-property-name.js", - "language/statements/generators/yield-as-literal-property-name.js", - "language/statements/generators/yield-as-property-name.js", - "language/statements/generators/yield-as-property-name.js", - "language/statements/generators/yield-as-yield-operand.js", - "language/statements/generators/yield-as-yield-operand.js", - "language/statements/generators/yield-identifier-non-strict.js", - "language/statements/generators/yield-identifier-spread-non-strict.js", - "language/statements/generators/yield-newline.js", - "language/statements/generators/yield-newline.js", - "language/statements/generators/yield-spread-arr-multiple.js", - "language/statements/generators/yield-spread-arr-multiple.js", - "language/statements/generators/yield-spread-arr-single.js", - "language/statements/generators/yield-spread-arr-single.js", - "language/statements/generators/yield-spread-obj.js", - "language/statements/generators/yield-spread-obj.js", - "language/statements/generators/yield-star-before-newline.js", - "language/statements/generators/yield-star-before-newline.js", - "language/statements/labeled/value-yield-non-strict-escaped.js", - "language/statements/labeled/value-yield-non-strict.js", - "language/statements/let/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/let/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/let/dstr/ary-ptrn-elision.js", - "language/statements/let/dstr/ary-ptrn-elision.js", - "language/statements/try/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/try/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/try/dstr/ary-ptrn-elision.js", - "language/statements/try/dstr/ary-ptrn-elision.js", - "language/statements/variable/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/variable/dstr/ary-ptrn-elem-ary-elision-init.js", - "language/statements/variable/dstr/ary-ptrn-elision.js", - "language/statements/variable/dstr/ary-ptrn-elision.js", + // TLA with complex module graph - dynamic import inside TLA module doesn't settle + // when module has both static and dynamic imports with cyclic dependencies + "language/module-code/top-level-await/module-graphs-does-not-hang.js", - // SharedArrayBuffer not implemented - "built-ins/SharedArrayBuffer/prototype/prop-desc.js", + // === INTL402 EXCLUSIONS === + // The following tests require full CLDR locale data support which is not available in the default provider. + // Many of these tests require non-English locales (Spanish, Polish, German, Chinese, etc.) or + // advanced features like formatRange, calendar-specific formatting, or complex locale canonicalization. - // special casing data - "built-ins/**/special_casing*.js", + // Collator - German phonebook collation and locale-specific collation data + "intl402/Collator/prototype/compare/non-normative-phonebook.js", + "intl402/Collator/prototype/resolvedOptions/resolved-collation-unicode-extensions-and-options.js", + "intl402/Collator/unicode-ext-seq-in-private-tag.js", + "intl402/Collator/unicode-ext-value-collation.js", + "intl402/Collator/usage-de.js", + + // DateTimeFormat - hanidec numbering system has different AM/PM behavior in ICU/CLDR + "intl402/DateTimeFormat/prototype/format/numbering-system.js", + + // DateTimeFormat - Chinese/Dangi calendar date range issues (.NET ChineseLunisolarCalendar limitations) + "intl402/DateTimeFormat/prototype/formatRangeToParts/chinese-calendar-dates.js", + "intl402/DateTimeFormat/prototype/formatRangeToParts/dangi-calendar-dates.js", + "intl402/DateTimeFormat/prototype/formatToParts/chinese-calendar-dates.js", + + // DisplayNames - locale-specific display names and Symbol edge cases + "intl402/DisplayNames/locales-symbol-length.js", + + // DurationFormat - formatting with specific styles and locales + "intl402/DurationFormat/prototype/format/duration-out-of-range-3.js", + "intl402/DurationFormat/prototype/format/duration-out-of-range-4.js", + // Requires Spanish unit patterns (ICU4N doesn't include unit data, only core locale data) + "intl402/DurationFormat/prototype/format/mixed-non-numeric-styles-es.js", + "intl402/DurationFormat/prototype/format/precision-exact-mathematical-values.js", + "intl402/DurationFormat/prototype/resolvedOptions/resolved-numbering-system-unicode-extensions-and-options.js", + + // Locale canonicalization - variant/region aliasing requires CLDR data + "intl402/Intl/getCanonicalLocales/complex-region-subtag-replacement.js", + "intl402/Intl/getCanonicalLocales/non-iana-canon.js", + "intl402/Intl/getCanonicalLocales/preferred-variant.js", + "intl402/Locale/reject-duplicate-variants-in-tlang.js", + + // NumberFormat - locale-specific formatting and formatRange + "intl402/NumberFormat/prototype/format/format-significant-digits.js", + "intl402/NumberFormat/prototype/format/useGrouping-extended-en-IN.js", + "intl402/NumberFormat/prototype/format/value-decimal-string.js", + // formatRange requires locale-specific CLDR range patterns and part collapsing + "intl402/NumberFormat/prototype/formatRange/en-US.js", + "intl402/NumberFormat/prototype/formatRange/pt-PT.js", + "intl402/NumberFormat/prototype/formatRangeToParts/en-US.js", + // Unit formatting formatToParts not yet implemented + "intl402/NumberFormat/prototype/formatToParts/unit-de-DE.js", + "intl402/NumberFormat/prototype/formatToParts/unit-ja-JP.js", + "intl402/NumberFormat/prototype/formatToParts/unit-ko-KR.js", + "intl402/NumberFormat/prototype/formatToParts/unit-zh-TW.js", + + // PluralRules - plural categories require full CLDR data, notation requires compact support + "intl402/PluralRules/prototype/resolvedOptions/plural-categories-order.js", + "intl402/PluralRules/prototype/select/notation.js", + + // String - Turkish/Lithuanian/Azeri locale-specific case conversions + "intl402/String/prototype/toLocaleLowerCase/capital_I_with_dot.js", + "intl402/String/prototype/toLocaleLowerCase/special_casing_Azeri.js", + "intl402/String/prototype/toLocaleLowerCase/special_casing_Lithuanian.js", + "intl402/String/prototype/toLocaleLowerCase/special_casing_Turkish.js", + "intl402/String/prototype/toLocaleLowerCase/validates-all-locale-identifiers.js", + "intl402/String/prototype/toLocaleUpperCase/validates-all-locale-identifiers.js", + + // supportedValuesOf - currencies accepted by DisplayNames check + "intl402/Intl/supportedValuesOf/currencies-accepted-by-DisplayNames.js", + + // Misc intl402 tests requiring full locale support + "intl402/fallback-locales-are-supported.js", + "intl402/supportedLocalesOf-consistent-with-resolvedOptions.js" - // failing tests in new test suite (due to updating to latest and using whole set) - "language/eval-code/direct/arrow-fn-a-following-parameter-is-named-arguments-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js", - "language/eval-code/direct/arrow-fn-a-following-parameter-is-named-arguments-arrow-func-declare-arguments-assign.js", - "language/eval-code/direct/arrow-fn-a-preceding-parameter-is-named-arguments-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js", - "language/eval-code/direct/arrow-fn-a-preceding-parameter-is-named-arguments-arrow-func-declare-arguments-assign.js", - "language/eval-code/direct/arrow-fn-body-cntns-arguments-func-decl-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js", - "language/eval-code/direct/arrow-fn-body-cntns-arguments-lex-bind-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js", - "language/eval-code/direct/arrow-fn-body-cntns-arguments-var-bind-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js", - "language/eval-code/direct/arrow-fn-no-pre-existing-arguments-bindings-are-present-arrow-func-declare-arguments-assign-incl-def-param-arrow-arguments.js", - "language/eval-code/direct/arrow-fn-no-pre-existing-arguments-bindings-are-present-arrow-func-declare-arguments-assign.js", - "language/eval-code/direct/async-gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/async-gen-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/async-gen-func-expr-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-expr-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-func-expr-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-expr-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-func-expr-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-expr-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/async-gen-func-expr-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-expr-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-func-expr-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-expr-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/async-gen-meth-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-meth-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-meth-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-meth-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-meth-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-meth-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/async-gen-meth-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-meth-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-meth-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-meth-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/async-gen-named-func-expr-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-named-func-expr-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-named-func-expr-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-named-func-expr-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-named-func-expr-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/async-gen-named-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/async-gen-named-func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/func-decl-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/func-decl-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/func-decl-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/func-decl-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/func-decl-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/func-decl-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/func-expr-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/func-expr-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/func-expr-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/func-expr-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/func-expr-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/func-expr-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/func-expr-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/func-expr-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/func-expr-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/func-expr-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/func-expr-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-decl-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-decl-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-decl-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/gen-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-decl-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/gen-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-decl-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/gen-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-decl-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-named-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-named-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-named-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-named-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-named-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-named-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-named-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-named-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-named-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-named-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-named-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-named-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-nameless-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-nameless-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-nameless-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-nameless-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-nameless-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-nameless-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-nameless-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-nameless-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-nameless-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-nameless-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/gen-func-expr-nameless-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-func-expr-nameless-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/gen-meth-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-meth-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-meth-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-meth-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/gen-meth-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-meth-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/gen-meth-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-meth-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/gen-meth-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-meth-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/gen-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/gen-meth-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/meth-a-following-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/meth-a-following-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/meth-a-preceding-parameter-is-named-arguments-declare-arguments-and-assign.js", - "language/eval-code/direct/meth-a-preceding-parameter-is-named-arguments-declare-arguments.js", - "language/eval-code/direct/meth-fn-body-cntns-arguments-func-decl-declare-arguments-and-assign.js", - "language/eval-code/direct/meth-fn-body-cntns-arguments-func-decl-declare-arguments.js", - "language/eval-code/direct/meth-fn-body-cntns-arguments-lex-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/meth-fn-body-cntns-arguments-lex-bind-declare-arguments.js", - "language/eval-code/direct/meth-fn-body-cntns-arguments-var-bind-declare-arguments-and-assign.js", - "language/eval-code/direct/meth-fn-body-cntns-arguments-var-bind-declare-arguments.js", - "language/eval-code/direct/meth-no-pre-existing-arguments-bindings-are-present-declare-arguments-and-assign.js", - "language/eval-code/direct/meth-no-pre-existing-arguments-bindings-are-present-declare-arguments.js", - "language/eval-code/direct/new.target-fn.js", - "language/eval-code/direct/super-prop-method.js", - "language/eval-code/indirect/always-non-strict.js", - "language/eval-code/indirect/non-definable-global-function.js", - "language/eval-code/indirect/non-definable-global-var.js", - "language/eval-code/indirect/realm.js", - "language/eval-code/indirect/var-env-func-init-global-new.js", - "language/eval-code/indirect/var-env-func-init-global-update-configurable.js", - "language/eval-code/indirect/var-env-func-init-multi.js", - "language/eval-code/indirect/var-env-func-non-strict.js", - "language/eval-code/indirect/var-env-global-lex-non-strict.js", - "language/eval-code/indirect/var-env-var-init-global-exstng.js", - "language/eval-code/indirect/var-env-var-init-global-new.js", - "language/eval-code/indirect/var-env-var-non-strict.js", - "language/expressions/arrow-function/dstr/ary-init-iter-get-err-array-prototype.js", - "language/expressions/arrow-function/dstr/dflt-ary-init-iter-get-err-array-prototype.js", - "language/expressions/arrow-function/eval-var-scope-syntax-err.js", - "language/expressions/arrow-function/scope-param-elem-var-close.js", - "language/expressions/arrow-function/scope-param-elem-var-open.js", - "language/expressions/arrow-function/scope-param-rest-elem-var-close.js", - "language/expressions/arrow-function/scope-param-rest-elem-var-open.js", - "language/expressions/assignment/assignment-operator-calls-putvalue-lref--rval--1.js", - "language/expressions/assignment/assignment-operator-calls-putvalue-lref--rval-.js", - "language/expressions/assignment/dstr/array-elem-put-obj-literal-prop-ref-init-active.js", - "language/expressions/assignment/dstr/array-elem-put-obj-literal-prop-ref-init.js", - "language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-prop-ref-init-active.js", - "language/expressions/assignment/dstr/obj-prop-elem-target-obj-literal-prop-ref-init.js", - "language/expressions/assignment/target-member-computed-reference-null.js", - "language/expressions/assignment/target-member-computed-reference-undefined.js", - "language/expressions/assignment/target-member-identifier-reference-null.js", - "language/expressions/assignment/target-member-identifier-reference-undefined.js", - "language/expressions/assignment/target-super-computed-reference-null.js", - "language/expressions/assignment/target-super-identifier-reference-null.js", - "language/expressions/delete/super-property-null-base.js", - "language/expressions/function/eval-var-scope-syntax-err.js", - "language/expressions/function/scope-param-elem-var-close.js", - "language/expressions/function/scope-param-elem-var-open.js", - "language/expressions/function/scope-param-rest-elem-var-close.js", - "language/expressions/function/scope-param-rest-elem-var-open.js", - "language/expressions/object/cpn-obj-lit-computed-property-name-from-null.js", - "language/expressions/object/dstr/meth-ary-init-iter-get-err-array-prototype.js", - "language/expressions/object/dstr/meth-dflt-ary-init-iter-get-err-array-prototype.js", - "language/expressions/object/dstr/object-rest-proxy-get-not-called-on-dontenum-keys.js", - "language/expressions/object/dstr/object-rest-proxy-gopd-not-called-on-excluded-keys.js", - "language/expressions/object/method-definition/forbidden-ext/b1/meth-forbidden-ext-direct-access-prop-arguments.js", - "language/expressions/object/method-definition/forbidden-ext/b1/meth-forbidden-ext-direct-access-prop-caller.js", - "language/expressions/object/method-definition/meth-eval-var-scope-syntax-err.js", - "language/expressions/object/object-spread-proxy-get-not-called-on-dontenum-keys.js", - "language/expressions/object/scope-meth-param-elem-var-close.js", - "language/expressions/object/scope-meth-param-elem-var-open.js", - "language/expressions/object/scope-meth-param-rest-elem-var-close.js", - "language/expressions/object/scope-meth-param-rest-elem-var-open.js", - "language/expressions/super/call-proto-not-ctor.js", - "language/expressions/template-literal/tv-line-continuation.js", - "language/function-code/eval-param-env-with-computed-key.js", - "language/function-code/eval-param-env-with-prop-initializer.js", - "language/literals/string/line-separator-eval.js", - "language/literals/string/line-separator.js", - "language/literals/string/paragraph-separator-eval.js", - "language/literals/string/paragraph-separator.js", - "language/statementList/eval-block-with-statment-block.js", - "language/statements/for-of/dstr/array-elem-put-obj-literal-prop-ref-init-active.js", - "language/statements/for-of/dstr/array-elem-put-obj-literal-prop-ref-init.js", - "language/statements/for-of/dstr/obj-id-init-let.js", - "language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-prop-ref-init-active.js", - "language/statements/for-of/dstr/obj-prop-elem-target-obj-literal-prop-ref-init.js", - "language/statements/function/cptn-decl.js", - "language/statements/function/dstr/ary-init-iter-get-err-array-prototype.js", - "language/statements/function/dstr/dflt-ary-init-iter-get-err-array-prototype.js", - "language/statements/function/eval-var-scope-syntax-err.js", - "language/statements/function/S13_A12_T2.js", - "language/statements/function/scope-param-elem-var-close.js", - "language/statements/function/scope-param-elem-var-open.js", - "language/statements/function/scope-param-rest-elem-var-close.js", - "language/statements/function/scope-param-rest-elem-var-open.js", - "language/statements/with/cptn-abrupt-empty.js" ] } \ No newline at end of file diff --git a/Jint.Tests.Test262/Test262ModuleLoader.cs b/Jint.Tests.Test262/Test262ModuleLoader.cs index 7169cef94f..5d36e17253 100644 --- a/Jint.Tests.Test262/Test262ModuleLoader.cs +++ b/Jint.Tests.Test262/Test262ModuleLoader.cs @@ -29,7 +29,7 @@ protected override string LoadModuleContents(Engine engine, ResolvedSpecifier re var fileName = Path.Combine(_basePath, resolved.Key).Replace('\\', '/'); if (!_fileSystem.FileExists(fileName)) { - ExceptionHelper.ThrowModuleResolutionException("Module Not Found", resolved.ModuleRequest.Specifier, parent: null, fileName); + Throw.ModuleResolutionException("Module Not Found", resolved.ModuleRequest.Specifier, parent: null, fileName); } using var stream = new StreamReader(_fileSystem.OpenFile(fileName, FileMode.Open, FileAccess.Read)); return stream.ReadToEnd(); diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index e0ae188798..d19161e7a3 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -1,4 +1,6 @@ -using Jint.Native; +#nullable enable + +using Jint.Native; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; @@ -8,13 +10,20 @@ namespace Jint.Tests.Test262; public abstract partial class Test262Test { - private Engine BuildTestExecutor(Test262File file) + // Thread-local storage for agent manager (tests run in parallel) + [ThreadStatic] + private static Test262AgentManager? _currentAgentManager; + + private static Engine BuildTestExecutor(Test262File file, Test262AgentManager? agentManager) { var engine = new Engine(cfg => { - var relativePath = Path.GetDirectoryName(file.FileName); + var relativePath = Path.GetDirectoryName(file.FileName) ?? ""; cfg.EnableModules(new Test262ModuleLoader(State.Test262Stream.Options.FileSystem, relativePath)); cfg.ExperimentalFeatures = ExperimentalFeature.All; + cfg.TimeoutInterval(TimeSpan.FromSeconds(30)); + // Use ICU-based CLDR provider for better Intl support + cfg.Intl.CldrProvider = IcuCldrProvider.Instance; }); if (file.Flags.Contains("raw")) @@ -28,6 +37,27 @@ private Engine BuildTestExecutor(Test262File file) engine.SetValue("print", new ClrFunction(engine, "print", (_, args) => TypeConverter.ToString(args.At(0)))); + // Provide a basic setTimeout for async tests that need it + engine.SetValue("setTimeout", new ClrFunction(engine, "setTimeout", (thisObj, args) => + { + var callback = args.At(0); + var delay = (int)TypeConverter.ToNumber(args.At(1)); + if (callback is ICallable callable) + { + _ = Task.Run(async () => + { + await Task.Delay(delay); + // Queue callback to event loop instead of calling directly from background thread + // to avoid race conditions with concurrent JavaScript execution + engine.AddToEventLoop(() => + { + callable.Call(JsValue.Undefined, Arguments.Empty); + }); + }); + } + return JsValue.Undefined; + })); + var o = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); o.FastSetProperty("evalScript", new PropertyDescriptor(new ClrFunction(engine, "evalScript", (_, args) => @@ -69,6 +99,9 @@ private Engine BuildTestExecutor(Test262File file) return JsValue.Undefined; }), true, true, true)); + // Install agent support if needed + agentManager?.InstallAgent(engine, o); + engine.SetValue("$262", o); foreach (var include in file.Includes) @@ -84,22 +117,58 @@ private Engine BuildTestExecutor(Test262File file) return engine; } - private static void ExecuteTest(Engine engine, Test262File file) + private static bool NeedsAgentSupport(Test262File file) { - if (file.Type == ProgramType.Module) + // Check if test includes atomicsHelper.js which indicates multi-agent test + return file.Includes.Contains("atomicsHelper.js"); + } + + // Wrapper that maintains backward compatibility with generated code + private static Engine BuildTestExecutor(Test262File file) + { + // Clean up any previous agent manager + _currentAgentManager?.Dispose(); + _currentAgentManager = null; + + // Create agent manager if test needs it + if (NeedsAgentSupport(file)) { - var specifier = "./" + Path.GetFileName(file.FileName); - engine.Modules.Add(specifier, builder => builder.AddSource(file.Program)); - engine.Modules.Import(specifier); + _currentAgentManager = new Test262AgentManager(); } - else + + return BuildTestExecutor(file, _currentAgentManager); + } + + private static void CleanupAgentManager() + { + _currentAgentManager?.Dispose(); + _currentAgentManager = null; + } + + private static void ExecuteTest(Engine engine, Test262File file) + { + try { - var script = Engine.PrepareScript(file.Program, source: file.FileName, options: new ScriptPreparationOptions + if (file.Type == ProgramType.Module) { - ParsingOptions = ScriptParsingOptions.Default with { Tolerant = false }, - }); + var specifier = "./" + Path.GetFileName(file.FileName); + engine.Modules.Add(specifier, builder => builder.AddSource(file.Program)); + engine.Modules.Import(specifier); + } + else + { + var script = Engine.PrepareScript(file.Program, source: file.FileName, options: new ScriptPreparationOptions + { + ParsingOptions = ScriptParsingOptions.Default with { Tolerant = false }, + }); - engine.Execute(script); + engine.Execute(script); + } + } + finally + { + // Cleanup agent manager after test execution + CleanupAgentManager(); } } diff --git a/Jint.Tests/Jint.Tests.csproj b/Jint.Tests/Jint.Tests.csproj index ae68c8015d..3b738265ec 100644 --- a/Jint.Tests/Jint.Tests.csproj +++ b/Jint.Tests/Jint.Tests.csproj @@ -1,12 +1,13 @@  - net8.0 - $(TargetFrameworks);net462 + net10.0 + $(TargetFrameworks);net472 ..\Jint\Jint.snk true false - $(NoWarn);612 + $(NoWarn);612;xUnit1051 + Exe @@ -21,12 +22,13 @@ + - - + + diff --git a/Jint.Tests/ReplaceCulture.cs b/Jint.Tests/ReplaceCulture.cs index 869cc5371d..ce32bcafc0 100644 --- a/Jint.Tests/ReplaceCulture.cs +++ b/Jint.Tests/ReplaceCulture.cs @@ -3,7 +3,7 @@ using System.Globalization; using System.Reflection; -using Xunit.Sdk; +using Xunit.v3; namespace Jint.Tests; @@ -64,7 +64,7 @@ public ReplaceCultureAttribute(string currentCulture, string currentUICulture) #endif public CultureInfo UICulture { get; } - public override void Before(MethodInfo methodUnderTest) + public override void Before(MethodInfo methodUnderTest, IXunitTest test) { _originalCulture = CultureInfo.CurrentCulture; _originalUICulture = CultureInfo.CurrentUICulture; @@ -73,20 +73,20 @@ public override void Before(MethodInfo methodUnderTest) System.Threading.Thread.CurrentThread.CurrentCulture = Culture; System.Threading.Thread.CurrentThread.CurrentUICulture = UICulture; #else - CultureInfo.CurrentCulture = Culture; - CultureInfo.CurrentUICulture = UICulture; + CultureInfo.CurrentCulture = Culture; + CultureInfo.CurrentUICulture = UICulture; #endif } - public override void After(MethodInfo methodUnderTest) + public override void After(MethodInfo methodUnderTest, IXunitTest test) { #if NETFRAMEWORK System.Threading.Thread.CurrentThread.CurrentCulture = _originalCulture; System.Threading.Thread.CurrentThread.CurrentUICulture = _originalUICulture; #else - CultureInfo.CurrentCulture = _originalCulture; - CultureInfo.CurrentUICulture = _originalUICulture; + CultureInfo.CurrentCulture = _originalCulture; + CultureInfo.CurrentUICulture = _originalUICulture; #endif } -} \ No newline at end of file +} diff --git a/Jint.Tests/RunnableInDebugOnlyAttribute.cs b/Jint.Tests/RunnableInDebugOnlyAttribute.cs index 2c974bc9d5..2fa8ef114f 100644 --- a/Jint.Tests/RunnableInDebugOnlyAttribute.cs +++ b/Jint.Tests/RunnableInDebugOnlyAttribute.cs @@ -1,14 +1,17 @@ using System.Diagnostics; +using System.Runtime.CompilerServices; namespace Jint.Tests; public class RunnableInDebugOnlyAttribute : FactAttribute { - public RunnableInDebugOnlyAttribute() + public RunnableInDebugOnlyAttribute( + [CallerFilePath] string sourceFilePath = null, + [CallerLineNumber] int sourceLineNumber = -1) : base(sourceFilePath, sourceLineNumber) { if (!Debugger.IsAttached) { Skip = "Only running in interactive mode."; } } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs b/Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs new file mode 100644 index 0000000000..ded91fe4d7 --- /dev/null +++ b/Jint.Tests/Runtime/ArgumentsCacheBehaviorTests.cs @@ -0,0 +1,152 @@ +using Jint.Native; + +namespace Jint.Tests.Runtime; + +public class ArgumentsCacheBehaviorTests +{ + [Fact] + public void ArgsForGeneratorsAreNotReusedFromCache() + { + // Arrange + List logValues = new(); + var engine = new Engine(); + engine.SetValue("log", logValues.Add); + + // Act + engine.Evaluate( + """ + function *method() { + log(arguments[0]); + log(arguments[1]); + } + + function *other() { + log(arguments[0]); + log(arguments[1]); + } + + var generator1 = method(42, undefined); + var generator2 = other(10, undefined); + generator1.next(); + generator2.next(); + """); + + // Assert + Assert.Equal([ + JsNumber.Create(42), + JsValue.Undefined, + JsNumber.Create(10), + JsValue.Undefined, + ], logValues); + } + + [Fact] + public void NamedArgsForGeneratorsAreNotReusedFromCache() + { + // Arrange + List logValues = new(); + var engine = new Engine(); + engine.SetValue("log", logValues.Add); + + // Act + engine.Evaluate( + """ + function *method(a, b) { + log(a); + log(b); + } + + function *other(a, b) { + log(a); + log(b); + } + + var generator1 = method(42, undefined); + var generator2 = other(10, undefined); + generator1.next(); + generator2.next(); + """); + + // Assert + Assert.Equal([ + JsNumber.Create(42), + JsValue.Undefined, + JsNumber.Create(10), + JsValue.Undefined, + ], logValues); + } + + [Fact] + public void ArgsForGeneratorsWithBindAreNotReusedFromCache() + { + // Arrange + List logValues = new(); + var engine = new Engine(); + engine.SetValue("log", logValues.Add); + + // Act + engine.Evaluate( + """ + function *method() { + log(arguments[0]); + log(arguments[1]); + } + + function *other() { + log(arguments[0]); + log(arguments[1]); + } + + var methodWithBind = method.bind({}); + var otherWithBind = other.bind({}); + + var generator1 = methodWithBind(42, undefined); + var generator2 = otherWithBind(10, undefined); + generator1.next(); + generator2.next(); + """); + + // Assert + Assert.Equal([ + JsNumber.Create(42), + JsValue.Undefined, + JsNumber.Create(10), + JsValue.Undefined, + ], logValues); + } + + [Fact] + public void ArgsForAsyncFunctionsAreNotReused() + { + // Arrange + List logValues = new(); + var engine = new Engine(new Options(){ ExperimentalFeatures = ExperimentalFeature.All }); + engine.SetValue("log", logValues.Add); + + // Act + engine.Execute( + """ + async function method() { + log(arguments[0]); + log(arguments[1]); + } + + async function other() { + log(arguments[0]); + log(arguments[1]); + } + + method(42, undefined); + other(10, undefined); + """); + engine.RunAvailableContinuations(); + + // Assert + Assert.Equal([ + JsNumber.Create(42), + JsValue.Undefined, + JsNumber.Create(10), + JsValue.Undefined, + ], logValues); + } +} diff --git a/Jint.Tests/Runtime/ArrayTests.cs b/Jint.Tests/Runtime/ArrayTests.cs index 549de4c254..e9d9a3b5f5 100644 --- a/Jint.Tests/Runtime/ArrayTests.cs +++ b/Jint.Tests/Runtime/ArrayTests.cs @@ -39,6 +39,22 @@ public void ArrayPrototypeToStringWithObject() Assert.Equal("[object Object]", result); } + [Fact] + public void ArrayPrototypeJoinWithCircularReference() + { + var result = _engine.Evaluate("Array.prototype.join.call((c = [1, 2, 3, 4], b = [1, 2, 3, 4], b[1] = c, c[1] = b, c))").AsString(); + + Assert.Equal("1,1,,3,4,3,4", result); + } + + [Fact] + public void ArrayPrototypeToLocaleStringWithCircularReference() + { + var result = _engine.Evaluate("Array.prototype.toLocaleString.call((c = [1, 2, 3, 4], b = [1, 2, 3, 4], b[1] = c, c[1] = b, c))").AsString(); + + Assert.Equal("1,1,,3,4,3,4", result); + } + [Fact] public void EmptyStringKey() { @@ -330,7 +346,7 @@ public void Shift() public void ShouldBeAbleToInitFromArray() { var engine = new Engine(); - var propertyDescriptors = new JsArray(engine, new JsValue[] { 1 }).GetOwnProperties().ToArray(); + var propertyDescriptors = new JsArray(engine, [1]).GetOwnProperties().ToArray(); Assert.Equal(2, propertyDescriptors.Length); Assert.Equal("0", propertyDescriptors[0].Key); Assert.Equal(1, propertyDescriptors[0].Value.Value); @@ -357,4 +373,18 @@ public void ArrayFromSortTest() enumerableResult[0].Key.Should().Be(item2.Key); enumerableResult[1].Key.Should().Be(item1.Key); } + + [Fact] + public void PopWrappedGenericList() + { + var engine = new Engine(); + var list = new List { 1, 2, 3 }; + engine.SetValue("list", list); + var result = engine.Evaluate("list.pop()").AsNumber(); + + Assert.Equal(3, result); + Assert.Equal(2, list.Count); + Assert.Equal(1, list[0]); + Assert.Equal(2, list[1]); + } } diff --git a/Jint.Tests/Runtime/AsyncTests.cs b/Jint.Tests/Runtime/AsyncTests.cs index da31e21332..c8be7d615b 100644 --- a/Jint.Tests/Runtime/AsyncTests.cs +++ b/Jint.Tests/Runtime/AsyncTests.cs @@ -1,5 +1,6 @@ using System.Collections.Concurrent; using Jint.Native; +using Jint.Runtime; using Jint.Tests.Runtime.TestClasses; namespace Jint.Tests.Runtime; @@ -42,6 +43,31 @@ public void ShouldReturnedTaskConvertedToPromiseInJS() Assert.Equal(AsyncTestClass.TestString, result); } + [Fact(Skip = "Flaky test")] + public void ShouldRespectCustomProvidedTimeoutWhenUnwrapping() + { + Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + var result = engine.Evaluate("asyncTestClass.ReturnDelayedTaskAsync().then(x=>x)"); + var timeout = TimeSpan.FromMilliseconds(1); + var exception = Assert.Throws(() => result.UnwrapIfPromise(timeout)); + Assert.Equal($"Promise was rejected with value Timeout of {timeout} reached", exception.Message); + } + + [Fact] + public void ShouldAwaitUnwrapPromiseWithCustomTimeout() + { + Engine engine = new(options => { options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(500); }); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + engine.Execute(""" + async function test() { + return await asyncTestClass.ReturnDelayedTaskAsync(); + } + """); + var result = engine.Invoke("test").UnwrapIfPromise(); + Assert.Equal(AsyncTestClass.TestString, result); + } + [Fact] public void ShouldReturnedCompletedTaskConvertedToPromiseInJS() { @@ -58,11 +84,14 @@ public void ShouldTaskCatchWhenCancelled() Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); CancellationTokenSource cancel = new(); cancel.Cancel(); + + engine.SetValue("cancelled", JsValue.Undefined); engine.SetValue("token", cancel.Token); engine.SetValue("callable", Callable); - engine.SetValue("assert", new Action(Assert.True)); - var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))"); - result = result.UnwrapIfPromise(); + + engine.Evaluate("callable(token).then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); static async Task Callable(CancellationToken token) { await Task.FromCanceled(token); @@ -75,20 +104,28 @@ public void ShouldReturnedTaskCatchWhenCancelled() Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); CancellationTokenSource cancel = new(); cancel.Cancel(); + + engine.SetValue("cancelled", JsValue.Undefined); engine.SetValue("token", cancel.Token); engine.SetValue("asyncTestClass", new AsyncTestClass()); engine.SetValue("assert", new Action(Assert.True)); - var result = engine.Evaluate("asyncTestClass.ReturnCancelledTask(token).then(_ => assert(false)).catch(_ => assert(true))"); - result = result.UnwrapIfPromise(); + + engine.Evaluate("asyncTestClass.ReturnCancelledTask(token).then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); } [Fact] public void ShouldTaskCatchWhenThrowError() { Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + + engine.SetValue("cancelled", JsValue.Undefined); engine.SetValue("callable", Callable); engine.SetValue("assert", new Action(Assert.True)); - var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))"); + + engine.Evaluate("callable().then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); static async Task Callable() { @@ -101,10 +138,12 @@ static async Task Callable() public void ShouldReturnedTaskCatchWhenThrowError() { Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + + engine.SetValue("cancelled", JsValue.Undefined); engine.SetValue("asyncTestClass", new AsyncTestClass()); - engine.SetValue("assert", new Action(Assert.True)); - var result = engine.Evaluate("asyncTestClass.ThrowAfterDelayAsync().then(_ => assert(false)).catch(_ => assert(true))"); - result = result.UnwrapIfPromise(); + + engine.Evaluate("asyncTestClass.ThrowAfterDelayAsync().then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); } [Fact] @@ -125,11 +164,109 @@ public void ShouldTaskAwaitCurrentStack() })); engine.SetValue("asyncTestClass", asyncTestClass); - engine.Execute("async function hello() {await myAsyncMethod();mySyncMethod2();await asyncTestClass.AddToStringDelayedAsync(\"3\")} hello();"); + engine.Evaluate("async function hello() {await myAsyncMethod();mySyncMethod2();await asyncTestClass.AddToStringDelayedAsync(\"3\")} hello();").UnwrapIfPromise(); + Assert.Equal("123", asyncTestClass.StringToAppend); } -#if NETFRAMEWORK == false + [Fact] + public void ShouldCompleteWithAsyncTaskCallbacks() + { + Engine engine = new(options => + { + options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; + options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(-1); + }); + engine.SetValue("asyncTestMethod", new Func, Task>(async callback => { await Task.Delay(10); await callback(); return "Hello World"; })); + engine.SetValue("asyncWork", new Func(() => Task.Delay(100))); + + var result = engine.Evaluate("async function hello() {return await asyncTestMethod(async () =>{ await asyncWork(); })} hello();"); + result = result.UnwrapIfPromise(TimeSpan.FromSeconds(30)); + + Assert.Equal("Hello World", result); + } + + [Fact] + public void ShouldFromAsyncTaskCallbacks() + { + Engine engine = new(options => + { + options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; + options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(-1); + }); + engine.SetValue("asyncTestMethod", new Func>, Task>(async callback => { await Task.Delay(10); return await callback(); })); + engine.SetValue("asyncWork", new Func>(async () => { await Task.Delay(100); return "Hello World"; })); + + var result = engine.Evaluate("async function hello() {return await asyncTestMethod(async () =>{ return await asyncWork(); })} hello();"); + result = result.UnwrapIfPromise(TimeSpan.FromSeconds(30)); + + Assert.Equal("Hello World", result); + } + + [Fact] + public void ShouldAllowLetReassignmentInsideAsyncLoop() + { + Engine engine = new(options => { options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; }); + var script = """ + async function getItems() { + return Promise.resolve([1, 2, 3]); + } + + async function main() { + let iteration = 0; + do { + let items = await getItems(); + items = await getItems(); + iteration++; + } while (iteration < 2); + return 'success'; + } + + export { main }; + """; + + engine.Modules.Add("test", script); + var module = engine.Modules.Import("test"); + var main = module.Get("main"); + var result = engine.Invoke(main).UnwrapIfPromise(); + + Assert.Equal("success", result); + } + +#if !NETFRAMEWORK + + [Fact] + public void ShouldCompleteWithAsyncValueTaskCallbacks() + { + Engine engine = new(options => + { + options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; + options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(-1); + }); + engine.SetValue("asyncTestMethod", new Func, Task>(async callback => { await Task.Delay(100); await callback(); return "Hello World"; })); + engine.SetValue("asyncWork", new Func(() => Task.Delay(100))); + engine.SetValue("assert", new Action(Assert.True)); + var result = engine.Evaluate("async function hello() {return await asyncTestMethod(async () =>{ await asyncWork(); })} hello();"); + result = result.UnwrapIfPromise(TimeSpan.FromSeconds(30)); + Assert.Equal("Hello World", result); + } + + [Fact] + public void ShouldFromAsyncValueTaskCallbacks() + { + Engine engine = new(options => + { + options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; + options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(-1); + }); + engine.SetValue("asyncTestMethod", new Func>, Task>(async callback => { await Task.Delay(100); return await callback(); })); + engine.SetValue("asyncWork", new Func>(async () => { await Task.Delay(100); return "Hello World"; })); + engine.SetValue("assert", new Action(Assert.True)); + var result = engine.Evaluate("async function hello() {return await asyncTestMethod(async () =>{ return await asyncWork(); })} hello();"); + result = result.UnwrapIfPromise(TimeSpan.FromSeconds(30)); + Assert.Equal("Hello World", result); + } + [Fact] public void ShouldValueTaskConvertedToPromiseInJS() { @@ -153,11 +290,13 @@ public void ShouldValueTaskCatchWhenCancelled() Engine engine = new(); CancellationTokenSource cancel = new(); cancel.Cancel(); + + engine.SetValue("cancelled", JsValue.Undefined); engine.SetValue("token", cancel.Token); engine.SetValue("callable", Callable); - engine.SetValue("assert", new Action(Assert.True)); - var result = engine.Evaluate("callable(token).then(_ => assert(false)).catch(_ => assert(true))"); - result = result.UnwrapIfPromise(); + + engine.Evaluate("callable(token).then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); static async ValueTask Callable(CancellationToken token) { await ValueTask.FromCanceled(token); @@ -168,9 +307,12 @@ static async ValueTask Callable(CancellationToken token) public void ShouldValueTaskCatchWhenThrowError() { Engine engine = new(); + + engine.SetValue("cancelled", JsValue.Undefined); engine.SetValue("callable", Callable); - engine.SetValue("assert", new Action(Assert.True)); - var result = engine.Evaluate("callable().then(_ => assert(false)).catch(_ => assert(true))"); + + engine.Evaluate("callable().then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); static async ValueTask Callable() { @@ -194,12 +336,94 @@ public void ShouldValueTaskAwaitCurrentStack() { log += "2"; })); - engine.Execute("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();"); + var result = engine.Evaluate("async function hello() {await myAsyncMethod();myAsyncMethod2();} hello();"); + result.UnwrapIfPromise(); Assert.Equal("12", log); } + + [Fact] + public void ShouldReturnedValueTaskOfTConvertedToPromiseInJS() + { + Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + var result = engine.Evaluate("asyncTestClass.ReturnDelayedValueTaskAsync().then(x=>x)"); + result = result.UnwrapIfPromise(); + Assert.Equal(AsyncTestClass.TestString, result); + } + + [Fact] + public void ShouldReturnedCompletedValueTaskOfTConvertedToPromiseInJS() + { + Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + var result = engine.Evaluate("asyncTestClass.ReturnCompletedValueTask().then(x=>x)"); + result = result.UnwrapIfPromise(); + Assert.Equal(AsyncTestClass.TestString, result); + } + + [Fact] + public void ShouldReturnedValueTaskOfTCatchWhenCancelled() + { + Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + CancellationTokenSource cancel = new(); + cancel.Cancel(); + + engine.SetValue("cancelled", JsValue.Undefined); + engine.SetValue("token", cancel.Token); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + + engine.Evaluate("asyncTestClass.ReturnCancelledValueTask(token).then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); + } + + [Fact] + public void ShouldReturnedValueTaskOfTCatchWhenThrowError() + { + Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + + engine.SetValue("cancelled", JsValue.Undefined); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + + engine.Evaluate("asyncTestClass.ThrowAfterDelayValueTaskAsync().then(_ => cancelled = false).catch(_ => cancelled = true)").UnwrapIfPromise(); + Assert.Equal(true, engine.Evaluate("cancelled").AsBoolean()); + } + + [Fact] + public void ShouldAwaitUnwrapValueTaskOfTPromiseWithCustomTimeout() + { + Engine engine = new(options => { options.ExperimentalFeatures = ExperimentalFeature.TaskInterop; options.Constraints.PromiseTimeout = TimeSpan.FromMilliseconds(500); }); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + engine.Execute(""" + async function test() { + return await asyncTestClass.ReturnDelayedValueTaskAsync(); + } + """); + var result = engine.Invoke("test").UnwrapIfPromise(); + Assert.Equal(AsyncTestClass.TestString, result); + } + + [Fact] + public void ShouldIterateOverAsyncEnumeratorConvertedToPromiseInJS() + { + Engine engine = new(options => options.ExperimentalFeatures = ExperimentalFeature.TaskInterop); + engine.SetValue("asyncTestClass", new AsyncTestClass()); + engine.Execute(""" + async function test() { + var result = ''; + var iter = asyncTestClass.AsyncEnumerable().GetAsyncEnumerator(); + while (await iter.MoveNextAsync()) { + result += iter.Current; + } + return result; + } + """); + var result = engine.Invoke("test").UnwrapIfPromise(); + Assert.Equal(AsyncTestClass.TestString, result); + } #endif - [Fact(Skip = "TODO es6-await https://github.com/sebastienros/jint/issues/1385")] + [Fact] public void ShouldHaveCorrectOrder() { var engine = new Engine(); @@ -230,7 +454,7 @@ async function foo(name) { Assert.Equal(expected, log.Select(x => x.AsString()).ToArray()); } - + [Fact] public void ShouldPromiseBeResolved() { @@ -240,7 +464,7 @@ public void ShouldPromiseBeResolved() { log.Add(str); }); - + const string Script = """ async function main() { return new Promise(function (resolve) { @@ -268,7 +492,7 @@ public void ShouldPromiseBeResolved2() { Task.Delay(ms).ContinueWith(_ => action()); }); - + const string Script = """ var delay = (ms) => new Promise(resolve => setTimeout(resolve, ms)); async function main() { @@ -281,12 +505,16 @@ async function main() { Assert.Equal(1, val.UnwrapIfPromise().AsInteger()); } +#if !NETFRAMEWORK // we are having trouble with timeouts on .NET Framework CI runs [Fact] public async Task ShouldEventLoopBeThreadSafeWhenCalledConcurrently() { - const int ParallelCount = 1000; + // This test verifies that multiple independent Engine instances can safely + // run async JavaScript code in parallel threads. Each Engine instance is + // isolated with its own event loop, so there should be no cross-thread issues. + const int ParallelCount = 100; - // [NOTE] perform 5 runs since the bug does not always happen + // [NOTE] perform 5 runs since concurrency bugs don't always manifest for (int run = 0; run < 5; run++) { var tasks = new List>(); @@ -306,10 +534,10 @@ public async Task ShouldEventLoopBeThreadSafeWhenCalledConcurrently() const string Script = """ async function main(testObj) { async function run(i) { - await testObj.Delay(100); + await testObj.Delay(10); await testObj.Add(`${i}`); } - + const tasks = []; for (let i = 0; i < 10; i++) { tasks.push(run(i)); @@ -323,6 +551,11 @@ async function run(i) { var result = engine.Execute(Script); var testObj = JsValue.FromObject(engine, new TestAsyncClass()); var val = result.GetValue("main").Call(testObj); + + // Wait for the async function to complete (non-blocking async model) + val = val.UnwrapIfPromise(TimeSpan.FromSeconds(30)); + Assert.Equal(1, val.AsInteger()); + tasks[taskIdx].SetResult(null); } catch (Exception ex) @@ -333,9 +566,110 @@ async function run(i) { } await Task.WhenAll(tasks.Select(t => t.Task)); - await Task.Delay(100); + await Task.Delay(100, TestContext.Current.CancellationToken); } } +#endif + + [Fact] + public void AsyncFunctionShouldNotBlockWhenCalledWithoutAwait() + { + // #2069: https://github.com/sebastienros/jint/issues/2069 + // Async functions should return immediately when called without await + var engine = new Engine(); + var callbackExecuted = false; + + engine.SetValue("setTimeout", + (Action action, int ms) => + { + Task.Delay(ms).ContinueWith(_ => + { + callbackExecuted = true; + action(); + }); + }); + + engine.Execute(""" + var x = ''; + async function f() { + await new Promise(resolve => setTimeout(resolve, 100)); + x += 'promise resolved - '; + } + f(); // Call without await + x += 'f() called - '; + """); + + var x = engine.Evaluate("x").AsString(); + + // The function should return immediately, so x should start with "f() called -" + // and NOT include "promise resolved -" yet + Assert.Equal("f() called - ", x); + Assert.False(callbackExecuted, "Promise callback should not have executed yet"); + } + + [Fact] + public void WrappedAsyncFunctionsShouldExecuteConcurrently() + { + // #2199: https://github.com/sebastienros/jint/issues/2199 + // When async functions wrap other async functions, they should execute concurrently + // not sequentially. All wrapped tasks should start before any complete. + var engine = new Engine(); + + engine.Execute(""" + var log = []; + var callbacks = []; + + // Simulate an async operation that resolves via callback + function asyncOperation(id) { + return new Promise(function(resolve) { + log.push('AsyncOp ' + id + ' registered'); + callbacks.push({ id: id, resolve: resolve }); + }); + } + + // Simple async function + async function simpleAsync(id) { + log.push('SimpleAsync ' + id + ' start'); + var result = await asyncOperation(id); + log.push('SimpleAsync ' + id + ' got result: ' + result); + return result; + } + + // Wrapped async function - the scenario from issue #2199 + async function wrappedAsync(id) { + log.push('WrappedAsync ' + id + ' start'); + var result = await simpleAsync(id); + log.push('WrappedAsync ' + id + ' got result: ' + result); + return result; + } + + // Create promises for wrapped async calls + var p1 = wrappedAsync('A'); + var p2 = wrappedAsync('B'); + var p3 = wrappedAsync('C'); + """); + + // All 3 async operations should have registered callbacks concurrently + var callbacks = engine.Evaluate("callbacks").AsArray(); + Assert.Equal(3, (int) callbacks.Length); + + var log = engine.Evaluate("log").AsArray(); + var logStrings = log.Select(x => x.AsString()).ToArray(); + + // Verify all wrapped/simple tasks started and registered before any completed + Assert.Contains("WrappedAsync A start", logStrings); + Assert.Contains("WrappedAsync B start", logStrings); + Assert.Contains("WrappedAsync C start", logStrings); + Assert.Contains("SimpleAsync A start", logStrings); + Assert.Contains("SimpleAsync B start", logStrings); + Assert.Contains("SimpleAsync C start", logStrings); + Assert.Contains("AsyncOp A registered", logStrings); + Assert.Contains("AsyncOp B registered", logStrings); + Assert.Contains("AsyncOp C registered", logStrings); + + // None should have completed yet (no "got result" messages) + Assert.DoesNotContain(logStrings, s => s.Contains("got result")); + } class TestAsyncClass { diff --git a/Jint.Tests/Runtime/BigIntTests.cs b/Jint.Tests/Runtime/BigIntTests.cs index 8953b048ba..c3ef814d8f 100644 --- a/Jint.Tests/Runtime/BigIntTests.cs +++ b/Jint.Tests/Runtime/BigIntTests.cs @@ -1,3 +1,4 @@ +using System.Numerics; using Jint.Native; namespace Jint.Tests.Runtime; @@ -9,10 +10,12 @@ public class BigIntTests [InlineData("a = a - b;", "100")] [InlineData("a = a * b;", "2829")] [InlineData("a = a / b;", "5")] + [InlineData("a = a % b;", "8")] [InlineData("a += b;", "146")] [InlineData("a -= b;", "100")] [InlineData("a *= b;", "2829")] [InlineData("a /= b;", "5")] + [InlineData("a %= b;", "8")] public void BasicOperations(string statement, string expected) { var outputValues = new List(); @@ -26,4 +29,42 @@ public void BasicOperations(string statement, string expected) Assert.True(outputValues[0].IsBigInt(), "The type of the value is expected to stay BigInt"); Assert.Equal(expected, outputValues[0].ToString()); } + + [Theory] + [InlineData("let right = 123n;")] + [InlineData("let right = BigInt(123);")] + [InlineData("let right = BigInt('123');")] + public void ObjectIsReturnsTrueForSameBigInts(string statement) + { + // Arrange + var engine = new Engine(); + engine.SetValue("left", new JsBigInt(new BigInteger(123))); + engine.Evaluate(statement); + + // Act + var result = engine.Evaluate("Object.is(left, right);"); + + // Assert + Assert.True(result.AsBoolean()); + } + + [Theory] + [InlineData("let right = false;")] + [InlineData("let right = 'test';")] + [InlineData("let right = 123;")] + [InlineData("let right = 321n;")] + [InlineData("let right = BigInt('321');")] + public void ObjectIsReturnsFalseDifferentComparisonsWithBigInt(string statement) + { + // Arrange + var engine = new Engine(); + engine.SetValue("left", new JsBigInt(new BigInteger(123))); + engine.Evaluate(statement); + + // Act + var result = engine.Evaluate("Object.is(left, right);"); + + // Assert + Assert.False(result.AsBoolean()); + } } diff --git a/Jint.Tests/Runtime/ConstructorSignatureTests.cs b/Jint.Tests/Runtime/ConstructorSignatureTests.cs index 25cdbf92ac..c954d235a0 100644 --- a/Jint.Tests/Runtime/ConstructorSignatureTests.cs +++ b/Jint.Tests/Runtime/ConstructorSignatureTests.cs @@ -3,7 +3,7 @@ namespace Jint.Tests.Runtime; -public class ConstructorSignature +public class ConstructorSignatureTests { [Fact] public void OptionalConstructorParametersShouldBeSupported() @@ -54,6 +54,17 @@ public void CanConstructWithMixedFloatAndEnumConstructorParameters() Assert.Equal(LengthUnit.Pixel, (LengthUnit) engine.Evaluate("new Length(12.3, 42).UnitValue").AsInteger()); } + [Fact] + public void ShouldBeAbleToIgnoreDefaultParameters() + { + var engine = new Engine(); + engine.SetValue("C", TypeReference.CreateTypeReference(engine, typeof(C))); + + // Should not throw an error + engine.Evaluate("new C(1, 2)"); + engine.Evaluate("new C(1, 2, 'context')"); + } + private class A { public A(int param1, int param2 = 5) => Result = (param1 + param2).ToString(); @@ -97,4 +108,11 @@ public Length(float value, LengthUnit unit) public float Value { get; } public LengthUnit UnitValue { get; } } + + private class C + { + public C(int tileCoordsX, int tileCoordsY, string context = null) + { + } + } } diff --git a/Jint.Tests/Runtime/DateTests.cs b/Jint.Tests/Runtime/DateTests.cs index 1698b2871f..b24d75826c 100644 --- a/Jint.Tests/Runtime/DateTests.cs +++ b/Jint.Tests/Runtime/DateTests.cs @@ -1,3 +1,5 @@ +using Jint.Native; + namespace Jint.Tests.Runtime; public class DateTests @@ -140,4 +142,28 @@ public void CanParseEmptyDate() { Assert.True(double.IsNaN(_engine.Evaluate("Date.parse('')").AsNumber())); } + + [Fact] + public void DateTimeMinValueFlag() + { + var date = DateTime.MinValue; + var jsDate = new JsDate(_engine, date); + Assert.Equal(DateFlags.DateTimeMinValue, jsDate._dateValue.Flags); + + date = date.AddMilliseconds(1); + jsDate = new JsDate(_engine, date); + Assert.Equal(DateFlags.None, jsDate._dateValue.Flags); + } + + [Fact] + public void DateTimeMaxValueFlag() + { + var date = DateTime.MaxValue; + var jsDate = new JsDate(_engine, date); + Assert.Equal(DateFlags.DateTimeMaxValue, jsDate._dateValue.Flags); + + date = date.AddMilliseconds(-1); + jsDate = new JsDate(_engine, date); + Assert.Equal(DateFlags.None, jsDate._dateValue.Flags); + } } diff --git a/Jint.Tests/Runtime/Domain/Dimensional.cs b/Jint.Tests/Runtime/Domain/Dimensional.cs index dc05d3ebdc..f4a8f85aa9 100644 --- a/Jint.Tests/Runtime/Domain/Dimensional.cs +++ b/Jint.Tests/Runtime/Domain/Dimensional.cs @@ -2,7 +2,7 @@ public class Dimensional : IComparable, IEquatable { - private readonly MeasureUnit[] PossibleMeasureUnits = new MeasureUnit[] { new MeasureUnit("Mass", "kg", 1.0), new MeasureUnit("Mass", "gr", 0.001), new MeasureUnit("Count", "piece", 1.0) }; + private readonly MeasureUnit[] PossibleMeasureUnits = [new MeasureUnit("Mass", "kg", 1.0), new MeasureUnit("Mass", "gr", 0.001), new MeasureUnit("Count", "piece", 1.0)]; public MeasureUnit MeasureUnit { get; private set; } diff --git a/Jint.Tests/Runtime/Domain/HiddenMembers.cs b/Jint.Tests/Runtime/Domain/HiddenMembers.cs index 34a21c225d..546d7f1d7f 100644 --- a/Jint.Tests/Runtime/Domain/HiddenMembers.cs +++ b/Jint.Tests/Runtime/Domain/HiddenMembers.cs @@ -2,6 +2,15 @@ public class HiddenMembers { + public HiddenMembers() + { + } + + public HiddenMembers(string member1) + { + Member1 = member1; + } + [Obsolete] public string Field1 = "Field1"; @@ -18,4 +27,4 @@ public class HiddenMembers public string Method2() => "Method2"; public Type Type => GetType(); -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/Domain/IntegerIndexer.cs b/Jint.Tests/Runtime/Domain/IntegerIndexer.cs index bdafd7c378..e4b67a0622 100644 --- a/Jint.Tests/Runtime/Domain/IntegerIndexer.cs +++ b/Jint.Tests/Runtime/Domain/IntegerIndexer.cs @@ -6,7 +6,7 @@ public class IntegerIndexer public IntegerIndexer() { - data = new[] {123, 0, 0, 0, 0}; + data = [123, 0, 0, 0, 0]; } public int this[int i] diff --git a/Jint.Tests/Runtime/EngineLimitTests.cs b/Jint.Tests/Runtime/EngineLimitTests.cs index 30d479e4d2..823a065ee2 100644 --- a/Jint.Tests/Runtime/EngineLimitTests.cs +++ b/Jint.Tests/Runtime/EngineLimitTests.cs @@ -11,7 +11,7 @@ public class EngineLimitTests #if RELEASE const int FunctionNestingCount = 990; #else - const int FunctionNestingCount = 495; + const int FunctionNestingCount = 465; #endif [Fact] diff --git a/Jint.Tests/Runtime/EngineTests.cs b/Jint.Tests/Runtime/EngineTests.cs index 7137610fec..eaa0589a5a 100644 --- a/Jint.Tests/Runtime/EngineTests.cs +++ b/Jint.Tests/Runtime/EngineTests.cs @@ -7,7 +7,6 @@ using Jint.Runtime; using Jint.Runtime.Debugger; using Jint.Tests.Runtime.Debugger; -using Xunit.Abstractions; #pragma warning disable 618 @@ -597,6 +596,21 @@ public void EvalFunctionParseAndExecuteCode() "); } + [Fact] + public void EvalFunctionWithTargetNewParse() + { + RunTest(@" + const code = `function MyClass() { + if (!new.target) throw new Error('Use MyClass as constructor!'); + }`; + eval(code); + const code2 = `var x = function () { + if (!new.target) throw new Error('Use as constructor!'); + }`; + eval(code2); + "); + } + [Fact] public void ForInStatement() { @@ -1187,12 +1201,12 @@ public static System.Collections.Generic.IEnumerable TestDates { get { - yield return new object[] { new DateTime(2000, 1, 1) }; - yield return new object[] { new DateTime(2000, 1, 1, 0, 15, 15, 15) }; - yield return new object[] { new DateTime(2000, 6, 1, 0, 15, 15, 15) }; - yield return new object[] { new DateTime(1900, 1, 1) }; - yield return new object[] { new DateTime(1900, 1, 1, 0, 15, 15, 15) }; - yield return new object[] { new DateTime(1900, 6, 1, 0, 15, 15, 15) }; + yield return [new DateTime(2000, 1, 1)]; + yield return [new DateTime(2000, 1, 1, 0, 15, 15, 15)]; + yield return [new DateTime(2000, 6, 1, 0, 15, 15, 15)]; + yield return [new DateTime(1900, 1, 1)]; + yield return [new DateTime(1900, 1, 1, 0, 15, 15, 15)]; + yield return [new DateTime(1900, 6, 1, 0, 15, 15, 15)]; } } @@ -1836,9 +1850,10 @@ public void DateToStringMethodsShouldUseCurrentTimeZoneAndCulture() equal('Mon Jun 01 2015 05:00:00 GMT-0700 (Pacific Standard Time)', d.toString()); equal('Mon Jun 01 2015', d.toDateString()); equal('05:00:00 GMT-0700 (Pacific Standard Time)', d.toTimeString()); - equal('lundi 1 juin 2015 05:00:00', d.toLocaleString()); - equal('lundi 1 juin 2015', d.toLocaleDateString()); - equal('05:00:00', d.toLocaleTimeString()); + // ECMA-402 compliant: numeric defaults used when no options specified + equal('1/6/2015 5:00:00', d.toLocaleString()); + equal('1/6/2015', d.toLocaleDateString()); + equal('5:00:00', d.toLocaleTimeString()); "); } @@ -3098,4 +3113,4 @@ public bool TryConvert(object value, Type type, IFormatProvider formatProvider, throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/ErrorTests.cs b/Jint.Tests/Runtime/ErrorTests.cs index 3df86be76a..17e73b4bb5 100644 --- a/Jint.Tests/Runtime/ErrorTests.cs +++ b/Jint.Tests/Runtime/ErrorTests.cs @@ -75,9 +75,9 @@ public void CanProduceCorrectStackTraceForInternalError() Assert.Equal("custom.js", e.Location.SourceFile); var stack = e.JavaScriptStackTrace; - EqualIgnoringNewLineDifferences(@" at a (v) custom.js:3:16 - at b (v) custom.js:7:10 - at main.js:1:9", stack); + EqualIgnoringNewLineDifferences(@" at a (custom.js:3:16) + at b (custom.js:7:10) + at main.js:1:9", stack); } [Fact] @@ -102,9 +102,9 @@ public void CanProduceCorrectStackTraceForScriptError() Assert.Equal("custom.js", e.Location.SourceFile); var stack = e.JavaScriptStackTrace; - EqualIgnoringNewLineDifferences(@" at a (v) custom.js:3:9 - at b (v) custom.js:7:10 - at main.js:1:9", stack); + EqualIgnoringNewLineDifferences(@" at a (custom.js:3:9) + at b (custom.js:7:10) + at main.js:1:9", stack); } [Fact] @@ -125,9 +125,9 @@ public void ErrorObjectHasTheStackTraceImmediately() var e = engine.Evaluate(@"b(7)", "main.js").AsString(); var stack = e; - EqualIgnoringNewLineDifferences(@" at a (v) custom.js:3:10 - at b (v) custom.js:7:10 - at main.js:1:1", stack); + EqualIgnoringNewLineDifferences(@" at a (custom.js:3:10) + at b (custom.js:7:10) + at main.js:1:1", stack); } [Fact] @@ -152,9 +152,9 @@ public void ThrownErrorObjectHasStackTraceInCatch() var e = engine.Evaluate(@"b(7)", "main.js").AsString(); var stack = e; - EqualIgnoringNewLineDifferences(@" at a (v) custom.js:4:11 - at b (v) custom.js:11:10 - at main.js:1:1", stack); + EqualIgnoringNewLineDifferences(@" at a (custom.js:4:11) + at b (custom.js:11:10) + at main.js:1:1", stack); } @@ -180,9 +180,9 @@ public void GeneratedErrorHasStackTraceInCatch() var e = engine.Evaluate(@"b(7)", "main.js").AsString(); var stack = e; - EqualIgnoringNewLineDifferences(@" at a (v) custom.js:4:13 - at b (v) custom.js:11:10 - at main.js:1:1", stack); + EqualIgnoringNewLineDifferences(@" at a (custom.js:4:13) + at b (custom.js:11:10) + at main.js:1:1", stack); } [Fact] @@ -237,11 +237,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.JavaScriptStackTrace); + EqualIgnoringNewLineDifferences(@" at recursive (:6:44) + at recursive (:8:32) + at recursive (:8:32) + at recursive (:8:32) + at :12:17", javaScriptException.JavaScriptStackTrace); var expected = new List { @@ -267,9 +267,9 @@ public void StackTraceCollectedOnThreeLevels() var ex = Assert.Throws(() => engine.Execute(script)); const string expected = @"Error: Cannot read property 'yyy' of undefined - at a (v) :2:18 - at b (v) :6:12 - at :9:9"; + at a (:2:18) + at b (:6:12) + at :9:9"; EqualIgnoringNewLineDifferences(expected, ex.GetJavaScriptErrorString()); Assert.Equal(2, ex.Location.Start.Line); @@ -302,9 +302,9 @@ public void StackTraceCollectedForImmediatelyInvokedFunctionExpression() var ex = Assert.Throws(() => engine.Execute(script, "get-item.js", parsingOptions)); const string expected = @"Error: Cannot read property '5' of null - at getItem (items, itemIndex) get-item.js:2:22 - at (anonymous) (getItem) get-item.js:9:16 - at get-item.js:13:2"; + at getItem (get-item.js:2:22) + at (anonymous) (get-item.js:9:16) + at get-item.js:13:2"; EqualIgnoringNewLineDifferences(expected, ex.GetJavaScriptErrorString()); @@ -335,8 +335,8 @@ function throwIt(message) { } "; var stack = engine.Evaluate(Script).AsString(); - EqualIgnoringNewLineDifferences(@" at throwIt (message) :3:11 - at :11:5", stack); + EqualIgnoringNewLineDifferences(@" at throwIt (:3:11) + at :11:5", stack); static void Handler(Action callback) { @@ -418,9 +418,9 @@ static ScriptParsingOptions CreateParsingOptions() Assert.Equal("nuм is not defined", e.Message); - const string Expected = @" at delegate second-file.js:2:1 - at delegate first-file.js:2:1 - at main-file.js:2:1"; + const string Expected = @" at delegate (second-file.js:2:1) + at delegate (first-file.js:2:1) + at main-file.js:2:1"; EqualIgnoringNewLineDifferences(Expected, e.JavaScriptStackTrace); } @@ -439,7 +439,7 @@ public void ShouldReportCorrectColumn() Assert.Equal(5, e.Location.Start.Line); Assert.Equal(14, e.Location.Start.Column); - Assert.Equal(" at :5:15", e.JavaScriptStackTrace); + Assert.Equal(" at :5:15", e.JavaScriptStackTrace); } [Fact] @@ -488,4 +488,4 @@ private static void ContainsIgnoringNewLineDifferences(string expectedSubstring, actualString = actualString.Replace("\r\n", "\n"); Assert.Contains(expectedSubstring, actualString); } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/ExecutionConstraintTests.cs b/Jint.Tests/Runtime/ExecutionConstraintTests.cs index 31b45de3f2..c8dd513c86 100644 --- a/Jint.Tests/Runtime/ExecutionConstraintTests.cs +++ b/Jint.Tests/Runtime/ExecutionConstraintTests.cs @@ -13,6 +13,20 @@ public void ShouldThrowStatementCountOverflow() ); } + [Fact] + public void ShouldCountStatementsPrecisely() + { + var script = "var x = 0; x++; x + 5"; + + // Should not throw if MaxStatements is not exceeded. + new Engine(cfg => cfg.MaxStatements(3)).Execute(script); + + // Should throw if MaxStatements is exceeded. + Assert.Throws( + () => new Engine(cfg => cfg.MaxStatements(2)).Evaluate(script) + ); + } + [Fact] public void ShouldThrowMemoryLimitExceeded() { @@ -35,18 +49,38 @@ public void ShouldThrowExecutionCanceled() Assert.Throws( () => { - using (var tcs = new CancellationTokenSource()) + using (var cts = new CancellationTokenSource()) using (var waitHandle = new ManualResetEvent(false)) + using (var cancellationTokenSet = new ManualResetEvent(initialState: false)) { - var engine = new Engine(cfg => cfg.CancellationToken(tcs.Token)); - - ThreadPool.QueueUserWorkItem(state => + var engine = new Engine(cfg => cfg.CancellationToken(cts.Token)); + + /* + * To ensure that the action "threadPoolAction" has actually been called by the ThreadPool + * (which can happen very late, depending on the ThreadPool usage, etc.), the + * "cancellationTokenSet" event will be an indicator for that. + * + * 1. The "cancellationTokenSet" will be set after the "cts" has been cancelled. + * 2. Within the JS, the "test" code will only start as soon as the "cancellationTokenSet" + * event will be set. + * 3. The cancellationToken and its source has not been used on purpose for this test to + * not mix "test infrastructure" and "test code". + * 4. To verify that this test still works under heavy load, you can add + * a "Thread.Sleep(10000)" call anywhere within the "threadPoolAction" action. + */ + + WaitCallback threadPoolAction = _ => { waitHandle.WaitOne(); - tcs.Cancel(); - }); + cts.Cancel(); + cancellationTokenSet.Set(); + }; + ThreadPool.QueueUserWorkItem(threadPoolAction); engine.SetValue("waitHandle", waitHandle); + engine.SetValue("waitForTokenToBeSet", new Action(() => cancellationTokenSet.WaitOne())); + engine.SetValue("mustNotBeCalled", new Action(() => + throw new InvalidOperationException("A cancellation of the script execution has been requested, but the script did continue to run."))); engine.Evaluate(@" function sleep(millisecondsTimeout) { var totalMilliseconds = new Date().getTime() + millisecondsTimeout; @@ -56,11 +90,16 @@ function sleep(millisecondsTimeout) { sleep(100); waitHandle.Set(); + waitForTokenToBeSet(); + + // Now it is ensured that the cancellationToken has been cancelled. + // The following JS code execution should get aborted by the engine. sleep(1000); sleep(1000); sleep(1000); sleep(1000); sleep(1000); + mustNotBeCalled(); "); } } @@ -287,7 +326,7 @@ public void ShouldConsiderConstraintsWhenCallingInvoke() { var engine = new Engine(options => { - options.TimeoutInterval(TimeSpan.FromMilliseconds(100)); + options.TimeoutInterval(TimeSpan.FromMilliseconds(1000)); }); var myApi = new MyApi(); @@ -298,7 +337,7 @@ public void ShouldConsiderConstraintsWhenCallingInvoke() foreach (var callback in dataReceivedCallbacks) { engine.Invoke(callback.Value, "Data Received #1"); - Thread.Sleep(101); + Thread.Sleep(200); engine.Invoke(callback.Value, "Data Received #2"); } } @@ -376,4 +415,4 @@ public void Log(string logMessage) Console.WriteLine(logMessage); } } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/ExtensionMethods/CustomStringExtensions.cs b/Jint.Tests/Runtime/ExtensionMethods/CustomStringExtensions.cs index 2ea629842a..96fac5af06 100644 --- a/Jint.Tests/Runtime/ExtensionMethods/CustomStringExtensions.cs +++ b/Jint.Tests/Runtime/ExtensionMethods/CustomStringExtensions.cs @@ -22,13 +22,13 @@ public static ExpandoObject DeserializeObject(this string json) public static string[] Split(this string value, string split, StringSplitOptions options) { - return Array.Empty(); + return []; } public static string[] Split(this string value, int position) { var first = value.Substring(0, position); var second = value.Substring(position); - return new string[] { first, second }; + return [first, second]; } } \ No newline at end of file diff --git a/Jint.Tests/Runtime/FunctionTests.cs b/Jint.Tests/Runtime/FunctionTests.cs index 5d9522310f..a9e2095c41 100644 --- a/Jint.Tests/Runtime/FunctionTests.cs +++ b/Jint.Tests/Runtime/FunctionTests.cs @@ -84,7 +84,7 @@ function test() { engine .SetValue("testFunction", new ClrFunction(engine, "testFunction", (thisValue, args) => { - return engine.Invoke(thisValue, "then", new[] { JsValue.Undefined, args.At(0) }); + return engine.Invoke(thisValue, "then", [JsValue.Undefined, args.At(0)]); })) .SetValue("assertEqual", new Action((a, b) => Assert.Equal(b, a))) .Execute(Script); @@ -142,10 +142,10 @@ void addListener(Func callback) Assert.Equal(5, engine.Evaluate("a")); - ev(null, new JsValue[0]); + ev(null, []); Assert.Equal(10, engine.Evaluate("a")); - ev(null, new JsValue[] { 20 }); + ev(null, [20]); Assert.Equal(30, engine.Evaluate("a")); } @@ -174,10 +174,10 @@ void addListener(Func callback) Assert.Equal(5, engine.Evaluate("a")); - ev(null, new JsValue[0]); + ev(null, []); Assert.Equal(10, engine.Evaluate("a")); - ev(null, new JsValue[] { 20 }); + ev(null, [20]); Assert.Equal(30, engine.Evaluate("a")); } @@ -196,11 +196,11 @@ void addListener(Func callback) engine.Execute(@"addListener(Boolean)"); - Assert.Equal(true, ev(JsValue.Undefined, new JsValue[] { "test" })); - Assert.Equal(true, ev(JsValue.Undefined, new JsValue[] { 5 })); - Assert.Equal(false, ev(JsValue.Undefined, new JsValue[] { false })); - Assert.Equal(false, ev(JsValue.Undefined, new JsValue[] { 0})); - Assert.Equal(false, ev(JsValue.Undefined, new JsValue[] { JsValue.Undefined })); + Assert.Equal(true, ev(JsValue.Undefined, ["test"])); + Assert.Equal(true, ev(JsValue.Undefined, [5])); + Assert.Equal(false, ev(JsValue.Undefined, [false])); + Assert.Equal(false, ev(JsValue.Undefined, [0])); + Assert.Equal(false, ev(JsValue.Undefined, [JsValue.Undefined])); } [Fact] @@ -271,4 +271,53 @@ public void ArrowFunction() const string Script = @"var f = (function() { return z => arguments[0]; }(5)); equal(5, f(6));"; _engine.Execute(Script); } + + [Fact] + public void MultipleCallsShouldNotCacheFunctionEnvironment() + { + var engine = new Engine(); + engine.Evaluate( + """ + function findInArray(arr, predicate) { + for (let i = 0; i) findIt.ToObject()!; + + var values = new List + { + new { kind = 'a' }, + new { kind = 'b' } + }; + + var found1 = interop( + JsValue.Undefined, + [ + JsValue.FromObject(engine, values), + JsValue.FromObject(engine, "a") + ]) + .ToObject(); + + var found2 = interop( + JsValue.Undefined, + [ + JsValue.FromObject(engine, values), + JsValue.FromObject(engine, "b") + ]) + .ToObject(); + + Assert.Equal(values[0], found1); + Assert.Equal(values[1], found2); + } } diff --git a/Jint.Tests/Runtime/GarbageCollectionTests.cs b/Jint.Tests/Runtime/GarbageCollectionTests.cs new file mode 100644 index 0000000000..042c733d6d --- /dev/null +++ b/Jint.Tests/Runtime/GarbageCollectionTests.cs @@ -0,0 +1,65 @@ +namespace Jint.Tests.Runtime; + +// This needs to run without any parallelization because it uses +// garbage collector metrics which cannot be isolated. +[CollectionDefinition(nameof(GarbageCollectionTests), DisableParallelization = true)] +[Collection(nameof(GarbageCollectionTests))] +public class GarbageCollectionTests +{ + [Fact] + public void InternalCachingDoesNotPreventGarbageCollection() + { + // This test ensures that memory allocated within functions + // can be garbage collected by the .NET runtime. To test that, + // the "allocate" functions allocates a big chunk of memory, + // which is not used anywhere. So the GC should have no problem + // releasing that memory after the "allocate" function leaves. + + // Arrange + var engine = new Engine(); + const string script = + """ + function allocate(runAllocation) { + if (runAllocation) { + // Allocate ~200 MB of data (not 100 because .NET uses UTF-16 for strings) + var test = Array.from({ length: 100 }) + .map(() => ' '.repeat(1 * 1024 * 1024)); + } + return 2; + } + """; + engine.Evaluate(script); + + // Create a baseline for memory usage. + engine.Evaluate("allocate(false);"); + var usedMemoryBytesBaseline = CurrentlyUsedMemory(); + + // Act + engine.Evaluate("allocate(true);"); + + // Assert + var usedMemoryBytesAfterJsScript = CurrentlyUsedMemory(); + var epsilon = 10 * 1024 * 1024; // allowing up to 10 MB of other allocations should be enough to prevent false positives + Assert.True( + usedMemoryBytesAfterJsScript - usedMemoryBytesBaseline < epsilon, + userMessage: $""" + The garbage collector did not free the allocated but unreachable 200 MB from the script.; + Before Call : {BytesToString(usedMemoryBytesBaseline)} + After Call : {BytesToString(usedMemoryBytesAfterJsScript)} + --- + Acceptable : {BytesToString(usedMemoryBytesBaseline + epsilon)} + """); + return; + + static string BytesToString(long bytes) + => $"{(bytes / 1024.0 / 1024.0),6:0.0} MB"; + + static long CurrentlyUsedMemory() + { + // Just try to ensure that everything possible gets collected. + GC.Collect(2, GCCollectionMode.Forced, blocking: true); + var currentlyUsed = GC.GetTotalMemory(forceFullCollection: true); + return currentlyUsed; + } + } +} diff --git a/Jint.Tests/Runtime/GeneratorTests.cs b/Jint.Tests/Runtime/GeneratorTests.cs index ef4531945c..bbdbcf77f9 100644 --- a/Jint.Tests/Runtime/GeneratorTests.cs +++ b/Jint.Tests/Runtime/GeneratorTests.cs @@ -6,7 +6,25 @@ public class GeneratorTests public GeneratorTests() { - _engine = new Engine(options => options.ExperimentalFeatures = ExperimentalFeature.Generators); + _engine = new Engine(); + } + + [Fact(Timeout = 10000)] + public void YieldInForLoopUpdateExpression() + { + const string Script = """ + const foo = function*() { + for(var i = 0; i < 5; yield i++) {} + }; + + let str = ''; + for (const val of foo()) { + str += val; + } + return str; + """; + + Assert.Equal("01234", _engine.Evaluate(Script)); } [Fact] @@ -166,7 +184,7 @@ public void CorrectThisBinding() Assert.True(_engine.Evaluate("item.done").AsBoolean()); } - [Fact(Skip = "TODO es6-generators")] + [Fact] public void Sending() { const string Script = """ @@ -186,7 +204,7 @@ public void Sending() Assert.Equal("bar", _engine.Evaluate("sent[1]")); } - [Fact(Skip = "TODO es6-generators")] + [Fact] public void Sending2() { const string Script = """ @@ -214,7 +232,7 @@ public void Sending2() Assert.Equal(26, _engine.Evaluate("generatorFunc.next(10).value")); // 26 } - [Fact(Skip = "TODO es6-generators")] + [Fact] public void Fibonacci() { const string Script = """ @@ -242,10 +260,10 @@ public void Fibonacci() Assert.Equal(2, _engine.Evaluate("sequence.next().value")); Assert.Equal(3, _engine.Evaluate("sequence.next().value")); Assert.Equal(5, _engine.Evaluate("sequence.next().value")); - Assert.Equal(9, _engine.Evaluate("sequence.next().value")); + Assert.Equal(8, _engine.Evaluate("sequence.next().value")); Assert.Equal(0, _engine.Evaluate("sequence.next(true).value")); - Assert.Equal(1, _engine.Evaluate("sequence.next().value)")); - Assert.Equal(1, _engine.Evaluate("sequence.next().value)")); - Assert.Equal(2, _engine.Evaluate("sequence.next().value)")); + Assert.Equal(1, _engine.Evaluate("sequence.next().value")); + Assert.Equal(1, _engine.Evaluate("sequence.next().value")); + Assert.Equal(2, _engine.Evaluate("sequence.next().value")); } } diff --git a/Jint.Tests/Runtime/InteropDisposeTests.cs b/Jint.Tests/Runtime/InteropDisposeTests.cs new file mode 100644 index 0000000000..76aeb1ddb8 --- /dev/null +++ b/Jint.Tests/Runtime/InteropDisposeTests.cs @@ -0,0 +1,64 @@ +namespace Jint.Tests.Runtime; + +public class InteropDisposeTests +{ + private readonly Engine _engine; + private readonly SyncDisposable _syncDisposable = new(); + +#if NETCOREAPP + private readonly AsyncDisposable _asyncDisposable = new(); +#endif + + public InteropDisposeTests() + { + _engine = new Engine(); + _engine.SetValue("getSync", () => _syncDisposable); + +#if NETCOREAPP + _engine.SetValue("getAsync", () => _asyncDisposable); +#endif + } + + [Theory] + [InlineData("{ using temp = getSync(); }")] + [InlineData("(function x() { using temp = getSync(); })()")] + [InlineData("class X { constructor() { using temp = getSync(); } } new X();")] + [InlineData("class X { static { using temp = getSync(); } } new X();")] + [InlineData("for (let i = 0; i < 1; i++) { using temp = getSync(); }")] + public void ShouldSyncDispose(string program) + { + _engine.Execute(program); + _syncDisposable.Disposed.Should().BeTrue(); + } + + private class SyncDisposable : IDisposable + { + public bool Disposed { get; private set; } + + public void Dispose() + { + Disposed = true; + } + } + +#if NETCOREAPP + [Theory] + [InlineData("(async function x() { await using temp = getAsync(); })();")] + public void ShouldAsyncDispose(string program) + { + _engine.Evaluate(program).UnwrapIfPromise(); + _asyncDisposable.Disposed.Should().BeTrue(); + } + + private class AsyncDisposable : IAsyncDisposable + { + public bool Disposed { get; private set; } + + public ValueTask DisposeAsync() + { + Disposed = true; + return default; + } + } +#endif +} diff --git a/Jint.Tests/Runtime/InteropTests.MemberAccess.cs b/Jint.Tests/Runtime/InteropTests.MemberAccess.cs index aaba07b3b9..c4f2c04304 100644 --- a/Jint.Tests/Runtime/InteropTests.MemberAccess.cs +++ b/Jint.Tests/Runtime/InteropTests.MemberAccess.cs @@ -1,6 +1,7 @@ using System.Collections; using System.Diagnostics.CodeAnalysis; using Jint.Native; +using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Interop; using Jint.Tests.Runtime.Domain; @@ -76,6 +77,23 @@ public void ShouldBeAbleToFilterMembers() Assert.True(engine.Evaluate("m.GetType").IsUndefined()); } + [Fact] + public void ShouldBeAbleToFilterConstructors() + { + var engine = new Engine(options => options + .SetTypeResolver(new TypeResolver + { + MemberFilter = member => member is System.Reflection.ConstructorInfo ci && ci.GetParameters().Length == 0 + }) + ); + + engine.SetValue("HiddenMembers", TypeReference.CreateTypeReference(engine)); + engine.Evaluate("new HiddenMembers()").Should().BeAssignableTo(); + + var act = () => engine.Evaluate("new HiddenMembers('/etc/passwd')"); + act.Should().Throw().WithMessage("Could not resolve a constructor*"); + } + [Fact] public void ShouldBeAbleToExposeGetType() { @@ -280,4 +298,44 @@ public void CanConfigureStrictAccess() engine.Invoking(e => e.Evaluate("obj.AgeMissing")).Should().Throw(); } -} \ No newline at end of file + + public class ClassWithPropertyToHide + { + public int x { get; set; } = 2; + public int y { get; set; } = 3; + } + + public class ClassThatHidesProperty : ClassWithPropertyToHide + { + public new bool x { get; set; } = true; + } + + [Fact] + public void ShouldRespectExplicitHiding() + { + var engine = new Engine(); + + engine.SetValue("obj", new ClassThatHidesProperty()); + engine.Evaluate("obj.x").AsBoolean().Should().BeTrue(); + engine.Evaluate("obj.y").AsNumber().Should().Be(3); + } + + [Fact] + public void ShouldSkipEnumIndexerWhenNoMatch() + { + var engine = new Engine(); + engine.SetValue("obj", new ObjectWithEnumIndexer()); + engine.Evaluate("obj.Foo()").AsString().Should().Be("Foo called"); + } + + private class ObjectWithEnumIndexer + { + public string this[TestEnumInt32 key] + { + get => ""; + set { } + } + + public string Foo() => "Foo called"; + } +} diff --git a/Jint.Tests/Runtime/InteropTests.TypeReference.cs b/Jint.Tests/Runtime/InteropTests.TypeReference.cs index dc1557f26f..9692efd457 100644 --- a/Jint.Tests/Runtime/InteropTests.TypeReference.cs +++ b/Jint.Tests/Runtime/InteropTests.TypeReference.cs @@ -1,6 +1,4 @@ using Jint.Native; -using Jint.Native.Symbol; -using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; using Jint.Tests.Runtime.Domain; using Microsoft.Extensions.DependencyInjection; @@ -184,22 +182,18 @@ public void CanConfigureCustomInstanceCreator() } [Fact] - public void CanRegisterToStringTag() + public void ToStringTagShouldReflectType() { var reference = TypeReference.CreateTypeReference(_engine); - reference.FastSetProperty(GlobalSymbolRegistry.ToStringTag, new PropertyDescriptor(nameof(Dependency), false, false, true)); - reference.FastSetDataProperty("abc", 123); _engine.SetValue("MyClass", reference); _engine.Execute("var c = new MyClass();"); Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c);")); - Assert.Equal(123, _engine.Evaluate("c.abc")); // engine uses registered type reference _engine.SetValue("c2", new Dependency()); Assert.Equal("[object Dependency]", _engine.Evaluate("Object.prototype.toString.call(c2);")); - Assert.Equal(123, _engine.Evaluate("c2.abc")); } private class Injectable diff --git a/Jint.Tests/Runtime/InteropTests.cs b/Jint.Tests/Runtime/InteropTests.cs index 07d6c15924..e2afcef808 100644 --- a/Jint.Tests/Runtime/InteropTests.cs +++ b/Jint.Tests/Runtime/InteropTests.cs @@ -4,6 +4,7 @@ using System.Runtime.CompilerServices; using Jint.Native; using Jint.Native.Function; +using Jint.Native.Number; using Jint.Runtime; using Jint.Runtime.Interop; using Jint.Tests.Runtime.Converters; @@ -57,7 +58,7 @@ public class Bar [Fact] public void ShouldStringifyNetObjects() { - _engine.SetValue("foo", new Foo()); + _engine.SetValue("foo", typeof(Foo)); var json = _engine.Evaluate("JSON.stringify(foo.GetBar())").AsString(); Assert.Equal("{\"Test\":\"123\"}", json); } @@ -264,6 +265,70 @@ public void ExtraParametersAreIgnored() "); } + class Example() + { + public T ExchangeGenericViaFunc(Func objViaFunc) + { + return objViaFunc(); + } + + public object ExchangeObjectViaFunc(Func objViaFunc) + { + return objViaFunc(); + } + + public int ExchangeValueViaFunc(Func objViaFunc) + { + return objViaFunc(); + } + } + + [Fact] + public void ExchangeGenericViaFunc() + { + _engine.SetValue("Example", new Example()); + + RunTest(@" + const result = Example.ExchangeGenericViaFunc(() => { + return { + value: 42 + }; + }); + + assert(result.value === 42); + "); + } + + [Fact] + public void ExchangeObjectViaFunc() + { + _engine.SetValue("Example", new Example()); + + RunTest(@" + const result = Example.ExchangeObjectViaFunc(() => { + return { + value: 42 + }; + }); + + assert(result.value === 42); + "); + } + + [Fact] + public void ExchangeValueViaFunc() + { + _engine.SetValue("Example", new Example()); + + RunTest(@" + const result = Example.ExchangeValueViaFunc(() => { + return 42; + }); + + assert(result === 42); + "); + } + private delegate string callParams(params object[] values); private delegate string callArgumentAndParams(string firstParam, params object[] values); @@ -2750,7 +2815,7 @@ public void CanCheckIfCallable() var result = engine.GetValue("f"); Assert.True(result.IsCallable); - Assert.True(result.Call(Array.Empty()).AsBoolean()); + Assert.True(result.Call([]).AsBoolean()); Assert.True(result.Call().AsBoolean()); } @@ -2783,20 +2848,32 @@ static IEnumerable MemberNameCreator(MemberInfo prop) options.SetTypeResolver(customTypeResolver); options.AddExtensionMethods(typeof(CustomNamedExtensions)); }); + engine.SetValue("o", new CustomNamed()); Assert.Equal("StringField", engine.Evaluate("o.jsStringField").AsString()); Assert.Equal("StringField", engine.Evaluate("o.jsStringField2").AsString()); - Assert.Equal("StaticStringField", engine.Evaluate("o.jsStaticStringField").AsString()); Assert.Equal("StringProperty", engine.Evaluate("o.jsStringProperty").AsString()); Assert.Equal("Method", engine.Evaluate("o.jsMethod()").AsString()); - Assert.Equal("StaticMethod", engine.Evaluate("o.jsStaticMethod()").AsString()); Assert.Equal("InterfaceStringProperty", engine.Evaluate("o.jsInterfaceStringProperty").AsString()); Assert.Equal("InterfaceMethod", engine.Evaluate("o.jsInterfaceMethod()").AsString()); Assert.Equal("ExtensionMethod", engine.Evaluate("o.jsExtensionMethod()").AsString()); + // static methods are reported by default, unlike properties and fields + Assert.Equal("StaticMethod", engine.Evaluate("o.jsStaticMethod()").AsString()); + + engine.SetValue("CustomNamed", typeof(CustomNamed)); + Assert.Equal("StaticStringField", engine.Evaluate("CustomNamed.jsStaticStringField").AsString()); + Assert.Equal("StaticMethod", engine.Evaluate("CustomNamed.jsStaticMethod()").AsString()); + engine.SetValue("XmlHttpRequest", typeof(CustomNamedEnum)); engine.Evaluate("o.jsEnumProperty = XmlHttpRequest.HEADERS_RECEIVED;"); Assert.Equal((int) CustomNamedEnum.HeadersReceived, engine.Evaluate("o.jsEnumProperty").AsNumber()); + + // can get static members with different configuration + var engineWithStaticsReported = new Engine(options => options.Interop.ObjectWrapperReportedFieldBindingFlags |= BindingFlags.Static); + engineWithStaticsReported.SetValue("o", new CustomNamed()); + Assert.Equal("StaticMethod", engineWithStaticsReported.Evaluate("o.staticMethod()").AsString()); + Assert.Equal("StaticStringField", engineWithStaticsReported.Evaluate("o.staticStringField").AsString()); } [Fact] @@ -2914,7 +2991,7 @@ public void ArrayPrototypeFindWithInteropList() { var engine = new Jint.Engine(); var list = new List { "A", "B", "C" }; - + engine.SetValue("list", list); Assert.Equal(1, engine.Evaluate("list.findIndex((x) => x === 'B')")); @@ -3299,9 +3376,13 @@ private class Container public BaseClass Get() => _child; } - private class BaseClass { } + private class BaseClass + { + } - private class Child : BaseClass { } + private class Child : BaseClass + { + } [Fact] public void AccessingBaseTypeShouldBeEqualToAccessingDerivedType() @@ -3330,6 +3411,7 @@ public interface IStringCollection : IIndexer, ICountable public class Strings : IStringCollection { private readonly string[] _strings; + public Strings(string[] strings) { _strings = strings; @@ -3342,7 +3424,7 @@ public Strings(string[] strings) public class Utils { - public IStringCollection GetStrings() => new Strings(new [] { "a", "b", "c" }); + public IStringCollection GetStrings() => new Strings(["a", "b", "c"]); } [Fact] @@ -3386,6 +3468,7 @@ private class MetadataWrapper : IDictionary public bool ContainsKey(string key) => throw new NotImplementedException(); public void Add(string key, object value) => throw new NotImplementedException(); public bool Remove(string key) => throw new NotImplementedException(); + public bool TryGetValue(string key, out object value) { value = "from-wrapper"; @@ -3469,6 +3552,7 @@ public void ShouldRespectConcreteGenericReturnTypes() }); var result = new List(); + void Debug(object o) { result.Add($"{o?.GetType().Name ?? "null"}: {o ?? "null"}"); @@ -3489,73 +3573,97 @@ void Debug(object o) private class ClrMembersVisibilityTestClass { - public int A { get; set; } = 10; + public string Field = "field"; + + public int Property { get; set; } = 10; - public int F() + public int Method() { return 4; } + + public string Extras { get; set; } } [Fact] - public void ShouldNotSeeClrMethods() + public void PropertiesShouldNotSeeReportMethodsWhenMemberTypesActive() { var engine = new Engine(opt => { opt.Interop.ObjectWrapperReportedMemberTypes = MemberTypes.Field | MemberTypes.Property; }); - + engine.SetValue("clrInstance", new ClrMembersVisibilityTestClass()); - - var val = engine.GetValue("clrInstance"); - var obj = val.AsObject(); - var props = obj.GetOwnProperties().Select(x => x.Key.ToString()).ToList(); - - props.Should().BeEquivalentTo(["A"]); + var val = engine.GetValue("clrInstance"); + + var obj = val.AsObject(); + var props = obj.GetOwnProperties().Select(x => x.Key.ToString()).ToList(); + + props.Should().BeEquivalentTo("Property", "Extras", "Field"); } - + [Fact] - public void ShouldSeeClrMethods() + public void PropertyKeysShouldReportMethods() { var engine = new Engine(); - + + engine.SetValue("clrInstance", new ClrMembersVisibilityTestClass()); + + var val = engine.GetValue("clrInstance"); + var obj = val.AsObject(); + var props = obj.GetOwnProperties().Select(x => x.Key.ToString()).ToList(); + + props.Should().BeEquivalentTo("Property", "Extras", "Field", "Method"); + } + + [Fact] + public void PropertyKeysShouldObeyMemberFilter() + { + var engine = new Engine(options => + { + options.SetTypeResolver(new TypeResolver + { + MemberFilter = member => member.Name == "Extras" + }); + }); + engine.SetValue("clrInstance", new ClrMembersVisibilityTestClass()); - + var val = engine.GetValue("clrInstance"); var obj = val.AsObject(); var props = obj.GetOwnProperties().Select(x => x.Key.ToString()).ToList(); - props.Should().BeEquivalentTo(["A", "F"]); + props.Should().BeEquivalentTo("Extras"); } - + private class ClrMembersVisibilityTestClass2 { public int Get_A { get; set; } = 5; } - + [Fact] public void ShouldSeeClrMethods2() { var engine = new Engine(); - + engine.SetValue("clrInstance", new ClrMembersVisibilityTestClass2()); - + var val = engine.GetValue("clrInstance"); var obj = val.AsObject(); var props = obj.GetOwnProperties().Select(x => x.Key.ToString()).ToList(); - - props.Should().BeEquivalentTo(["Get_A"]); + + props.Should().BeEquivalentTo("Get_A"); } - + [Fact] public void ShouldNotThrowOnInspectingClrFunction() { var engine = new Engine(); - + engine.SetValue("clrDelegate", () => 4); - + var val = engine.GetValue("clrDelegate"); var fn = val as Function; @@ -3563,7 +3671,7 @@ public void ShouldNotThrowOnInspectingClrFunction() decl.Should().BeNull(); } - + private class ShouldNotThrowOnInspectingClrFunctionTestClass { public int MyInt() @@ -3571,20 +3679,20 @@ public int MyInt() return 4; } } - + [Fact] public void ShouldNotThrowOnInspectingClrClassFunction() { var engine = new Engine(); - + engine.SetValue("clrCls", new ShouldNotThrowOnInspectingClrFunctionTestClass()); - + var val = engine.GetValue("clrCls"); var clrFn = val.Get("MyInt"); - + var fn = clrFn as Function; var decl = fn!.FunctionDeclaration; - + decl.Should().BeNull(); } @@ -3594,13 +3702,259 @@ public void StringifyShouldIncludeInheritedFieldsAndProperties() var engine = new Engine(); engine.SetValue("c", new Circle(12.34)); engine.Evaluate("JSON.stringify(c)").ToString().Should().Be("{\"Radius\":12.34,\"Color\":0,\"Id\":123}"); + } + + public class Animal + { + public virtual string name { get; set; } = "animal"; + } + + public class Elephant : Animal + { + public override string name { get; set; } = "elephant"; + public int earSize = 5; + } + public class Lion : Animal + { + public override string name { get; set; } = "lion"; + public int maneLength = 10; + } - engine = new Engine(options => + public class Zoo + { + public Animal king { get => (new Animal[] { new Lion() })[0]; } + public Animal[] animals { get => [new Lion(), new Elephant()]; } + } + + [Fact] + public void CanFindDerivedPropertiesFail() // Fails in 4.01 but success in 2.11 + { + var engine = new Engine(); + engine.SetValue("zoo", new Zoo()); + var kingManeLength = engine.Evaluate("zoo.King.maneLength"); + Assert.Equal(10, kingManeLength.AsNumber()); + } + + [Fact] + public void CanFindDerivedPropertiesSucceed() // Similar case that continues to succeed + { + var engine = new Engine(); + engine.SetValue("zoo", new Zoo()); + var lionManeLength = engine.Evaluate("zoo.animals[0].maneLength"); + Assert.Equal(10, lionManeLength.AsNumber()); + } + + [Fact] + public void StaticFieldsShouldFollowJsSemantics() + { + _engine.Evaluate("Number.MAX_SAFE_INTEGER").AsNumber().Should().Be(NumberConstructor.MaxSafeInteger); + _engine.Evaluate("new Number().MAX_SAFE_INTEGER").Should().Be(JsValue.Undefined); + + _engine.Execute("class MyJsClass { static MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER; }"); + _engine.Evaluate("MyJsClass.MAX_SAFE_INTEGER").AsNumber().Should().Be(NumberConstructor.MaxSafeInteger); + _engine.Evaluate("new MyJsClass().MAX_SAFE_INTEGER").Should().Be(JsValue.Undefined); + + _engine.SetValue("MyCsClass", typeof(MyClass)); + _engine.Evaluate("MyCsClass.MAX_SAFE_INTEGER").AsNumber().Should().Be(NumberConstructor.MaxSafeInteger); + _engine.Evaluate("new MyCsClass().MAX_SAFE_INTEGER").Should().Be(JsValue.Undefined); + } + + private class MyClass + { + public static JsNumber MAX_SAFE_INTEGER = new JsNumber(NumberConstructor.MaxSafeInteger); + } + + [Fact] + public void ShouldFindShortOverload() + { + _engine.SetValue("target", new ShortOverloadWithBoolean()); + _engine.Evaluate("target.method(42)").AsString().Should().Be("short"); + } + + private class ShortOverloadWithBoolean + { + public string Method(short s, bool b = true) { - options.Interop.ObjectWrapperReportOnlyDeclaredMembers = true; - }); - engine.SetValue("c", new Circle(12.34)); - engine.Evaluate("JSON.stringify(c)").ToString().Should().Be("{\"Radius\":12.34}"); + return "short"; + } + + public string Method(bool b) + { + return "boolean"; + } + } + + [Fact] + public void MultipleInteropCallsShouldNotCacheFunctionEnvironment() + { + var engine = new Engine(); + engine.Evaluate( + """ + function findIt(array, kind) { + let found = array.find(function sub(x) { + return x.kind == kind; + }); + return found; + }; + """); + var findIt = (ScriptFunction) engine.GetValue("findIt"); + var interop = (Func) findIt.ToObject()!; + + var values = new List + { + new { kind = 'a' }, + new { kind = 'b' } + }; + + var found1 = interop( + JsValue.Undefined, + [ + JsValue.FromObject(engine, values), + JsValue.FromObject(engine, "a") + ]) + .ToObject(); + + var found2 = interop( + JsValue.Undefined, + [ + JsValue.FromObject(engine, values), + JsValue.FromObject(engine, "b") + ]) + .ToObject(); + + Assert.Equal(values[0], found1); + Assert.Equal(values[1], found2); + } + + [Fact] + public void CanCallBoundJavascriptFunctionFromDotnet() + { + var ticker = new Ticker(); + _engine.SetValue("ticker", ticker); + + var counter = (double) _engine.Evaluate(""" + function tickHandler() { + counter++; + } + + let counter = 0; + const dummyThisObject = {}; + + // bind javascript function to new this-object + const tickerHandlerBinding = tickHandler.bind(dummyThisObject); + + // register it with .NET + ticker.add_Ticked(tickerHandlerBinding); + ticker.Tick(); + + // unregister it + ticker.remove_Ticked(tickerHandlerBinding); + ticker.Tick(); + + // return counter as result + counter; + """).ToObject(); + + ticker.Tick(); + counter.Should().Be(1); + } + + internal class Ticker + { + public event EventHandler Ticked; + + public void Tick() + { + Ticked?.Invoke(this, EventArgs.Empty); + } + } + + [Fact] + public void ShouldBeAbleToWriteLengthOfListLike() + { + var list = new List { "a", "b", "c" }; + _engine.SetValue("list", list); + + _engine.Evaluate("list.length = 2;"); + list.Should().HaveCount(2); + list[0].Should().Be("a"); + list[1].Should().Be("b"); + + _engine.Evaluate("list.length = 0;"); + list.Should().BeEmpty(); + + var act = () => _engine.Evaluate("list.length = -1;"); + act.Should().Throw().WithMessage("Invalid array length"); + + _engine.Evaluate("list.length = 1;"); + list.Should().HaveCount(1); + list[0].Should().Be(null); + } + + // GitHub issue #2173 - Type resolution should use runtime type when declared type has indexer + private class WrapperWithIndexer + { + private readonly Dictionary _properties = new(); + + public object this[string key] + { + get => _properties.TryGetValue(key, out var value) ? value : null!; + set => _properties[key] = value; + } + } + + private class GeometryWrapperWithProperty : WrapperWithIndexer + { + public double X { get; set; } + public double Y { get; set; } + } + + private class FeatureWithBaseTypeProperty + { + public WrapperWithIndexer Geometry { get; set; } = new GeometryWrapperWithProperty { X = 10.5, Y = 20.5 }; + } + + [Fact] + public void ShouldAccessDerivedTypePropertyWhenDeclaredTypeHasIndexer() + { + // GitHub issue #2173: When a property is declared with a base type that has an indexer, + // but the actual runtime value is a derived type with a property, the property should be accessible + var engine = new Engine(); + var feature = new FeatureWithBaseTypeProperty(); + engine.SetValue("feature", feature); + + // Should access the X property from GeometryWrapperWithProperty, not the indexer from WrapperWithIndexer + var result = engine.Evaluate("feature.Geometry.x").AsNumber(); + Assert.Equal(10.5, result); + + var resultY = engine.Evaluate("feature.Geometry.y").AsNumber(); + Assert.Equal(20.5, resultY); + } + + [Fact] + public void ShouldStillAccessIndexerWhenPropertyDoesNotExist() + { + // Ensure the indexer still works when the property doesn't exist on the derived type + var engine = new Engine(); + var feature = new FeatureWithBaseTypeProperty(); + ((GeometryWrapperWithProperty) feature.Geometry)["customKey"] = "customValue"; + engine.SetValue("feature", feature); + + var result = engine.Evaluate("feature.Geometry.customKey"); + Assert.Equal("customValue", result.AsString()); + } + + [Fact] + public void ShouldSetDerivedTypePropertyWhenDeclaredTypeHasIndexer() + { + var engine = new Engine(cfg => cfg.AllowClrWrite()); + var feature = new FeatureWithBaseTypeProperty(); + engine.SetValue("feature", feature); + + engine.Evaluate("feature.Geometry.x = 99.9"); + + var geometry = (GeometryWrapperWithProperty) feature.Geometry; + Assert.Equal(99.9, geometry.X); } } diff --git a/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs b/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs new file mode 100644 index 0000000000..806b2ffda9 --- /dev/null +++ b/Jint.Tests/Runtime/Interpreter/JintFunctionDefinitionTest.cs @@ -0,0 +1,53 @@ +using Jint.Runtime.Interpreter; + +namespace Jint.Tests.Runtime.Interpreter; + +public class JintFunctionDefinitionTest +{ + [Theory] + [InlineData("function f(_ = probeParams = function() { return 42; }) { }", true)] + [InlineData("function* g(_ = probeParams = function() { return 42; }) { }", true)] + [InlineData("function x(t = {}) {}", false)] + [InlineData("function x(e, t = {}) {}", false)] + [InlineData("function x([t, e]) { }", false)] + public void ShouldDetectParameterExpression(string functionCode, bool hasExpressions) + { + var parser = new Parser(); + var script = parser.ParseScript(functionCode); + var function = (IFunction) script.Body.First(); + + var state = JintFunctionDefinition.BuildState(function); + state.HasParameterExpressions.Should().Be(hasExpressions); + } + + [Theory] + [InlineData("function g() { }", false)] + [InlineData("function* g() { }", false)] + [InlineData("async function g() { }", false)] + [InlineData("() => { }", false)] + [InlineData("async () => { }", false)] + [InlineData("function g(a) { }", false)] + [InlineData("function* g(a) { }", false)] + [InlineData("async function g(a) { }", false)] + [InlineData("(a) => { }", false)] + [InlineData("async (a) => { }", false)] + [InlineData("function g(a) { _ = arguments[0] }", false)] + [InlineData("function* g(a) { _ = arguments[0] }", true)] + [InlineData("async function g(a) { _ = arguments[0] }", true)] + [InlineData("(a) => { _ = arguments[0] }", false)] + [InlineData("async (a) => { _ = arguments[0] }", true)] + public void ShouldIndicateArgumentsOwnershipIfNeeded(string functionCode, bool requiresOwnership) + { + var parser = new Parser(); + var script = parser.ParseScript(functionCode); + Node statement = script.Body.First(); + var function = (IFunction) ( + statement is ExpressionStatement expr + ? expr.Expression + : statement + ); + + var state = JintFunctionDefinition.BuildState(function); + state.RequiresInputArgumentsOwnership.Should().Be(requiresOwnership); + } +} diff --git a/Jint.Tests/Runtime/IntlTests.cs b/Jint.Tests/Runtime/IntlTests.cs new file mode 100644 index 0000000000..4dac9841f6 --- /dev/null +++ b/Jint.Tests/Runtime/IntlTests.cs @@ -0,0 +1,655 @@ +namespace Jint.Tests.Runtime; + +public class IntlTests +{ + private readonly Engine _engine; + + public IntlTests() + { + _engine = new Engine(); + } + + [Fact] + public void IntlObjectExists() + { + var result = _engine.Evaluate("typeof Intl"); + Assert.Equal("object", result.AsString()); + } + + [Fact] + public void IntlHasGetCanonicalLocales() + { + var result = _engine.Evaluate("typeof Intl.getCanonicalLocales"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void IntlHasSupportedValuesOf() + { + var result = _engine.Evaluate("typeof Intl.supportedValuesOf"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void GetCanonicalLocalesWithUndefined() + { + var result = _engine.Evaluate("Intl.getCanonicalLocales(undefined)"); + Assert.True(result.IsArray()); + Assert.Equal((uint) 0, result.AsArray().Length); + } + + [Fact] + public void GetCanonicalLocalesWithString() + { + var result = _engine.Evaluate("Intl.getCanonicalLocales('en-US')"); + Assert.True(result.IsArray()); + var array = result.AsArray(); + Assert.Equal((uint) 1, array.Length); + Assert.Equal("en-US", array[0].AsString()); + } + + [Fact] + public void GetCanonicalLocalesWithArray() + { + var result = _engine.Evaluate("Intl.getCanonicalLocales(['en-US', 'de-DE'])"); + Assert.True(result.IsArray()); + var array = result.AsArray(); + Assert.Equal((uint) 2, array.Length); + } + + [Fact] + public void GetCanonicalLocalesCanonicalizesCase() + { + // Should canonicalize 'en-us' to 'en-US' + var result = _engine.Evaluate("Intl.getCanonicalLocales('en-us')"); + Assert.True(result.IsArray()); + var array = result.AsArray(); + Assert.Equal((uint) 1, array.Length); + // Result should be canonicalized + Assert.NotNull(array[0].AsString()); + } + + [Fact] + public void SupportedValuesOfCalendar() + { + var result = _engine.Evaluate("Intl.supportedValuesOf('calendar')"); + Assert.True(result.IsArray()); + var array = result.AsArray(); + Assert.True(array.Length > 0); + } + + [Fact] + public void SupportedValuesOfCurrency() + { + var result = _engine.Evaluate("Intl.supportedValuesOf('currency')"); + Assert.True(result.IsArray()); + var array = result.AsArray(); + Assert.True(array.Length > 0); + } + + [Fact] + public void SupportedValuesOfUnit() + { + var result = _engine.Evaluate("Intl.supportedValuesOf('unit')"); + Assert.True(result.IsArray()); + var array = result.AsArray(); + Assert.True(array.Length > 0); + // Should contain common units + var units = new List(); + for (uint i = 0; i < array.Length; i++) + { + units.Add(array[i].AsString()); + } + Assert.Contains("meter", units); + Assert.Contains("second", units); + } + + [Fact] + public void SupportedValuesOfInvalidKeyThrows() + { + Assert.Throws(() => + _engine.Evaluate("Intl.supportedValuesOf('invalid')")); + } + + [Fact] + public void IntlToStringTag() + { + var result = _engine.Evaluate("Object.prototype.toString.call(Intl)"); + Assert.Equal("[object Intl]", result.AsString()); + } + + // Intl.Locale tests + [Fact] + public void LocaleConstructorExists() + { + var result = _engine.Evaluate("typeof Intl.Locale"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void LocaleCanBeConstructed() + { + var result = _engine.Evaluate("new Intl.Locale('en-US')"); + Assert.NotNull(result); + Assert.True(result.IsObject()); + } + + [Fact] + public void LocaleRequiresNew() + { + Assert.Throws(() => + _engine.Evaluate("Intl.Locale('en-US')")); + } + + [Fact] + public void LocaleToStringReturnsTag() + { + var result = _engine.Evaluate("new Intl.Locale('en-US').toString()"); + Assert.Equal("en-US", result.AsString()); + } + + [Fact] + public void LocaleLanguageProperty() + { + var result = _engine.Evaluate("new Intl.Locale('en-US').language"); + Assert.Equal("en", result.AsString()); + } + + [Fact] + public void LocaleRegionProperty() + { + var result = _engine.Evaluate("new Intl.Locale('en-US').region"); + Assert.Equal("US", result.AsString()); + } + + [Fact] + public void LocaleScriptProperty() + { + var result = _engine.Evaluate("new Intl.Locale('zh-Hans-CN').script"); + Assert.Equal("Hans", result.AsString()); + } + + [Fact] + public void LocaleBaseNameProperty() + { + var result = _engine.Evaluate("new Intl.Locale('en-US').baseName"); + Assert.Equal("en-US", result.AsString()); + } + + [Fact] + public void LocaleWithCalendarOption() + { + var result = _engine.Evaluate("new Intl.Locale('en-US', { calendar: 'gregory' }).calendar"); + Assert.Equal("gregory", result.AsString()); + } + + [Fact] + public void LocaleWithHourCycleOption() + { + var result = _engine.Evaluate("new Intl.Locale('en-US', { hourCycle: 'h12' }).hourCycle"); + Assert.Equal("h12", result.AsString()); + } + + [Fact] + public void LocaleWithNumericOption() + { + var result = _engine.Evaluate("new Intl.Locale('en-US', { numeric: true }).numeric"); + Assert.True(result.AsBoolean()); + } + + [Fact] + public void LocaleToStringTagIsCorrect() + { + var result = _engine.Evaluate("Object.prototype.toString.call(new Intl.Locale('en-US'))"); + Assert.Equal("[object Intl.Locale]", result.AsString()); + } + + [Fact] + public void LocaleMinimize() + { + var result = _engine.Evaluate("new Intl.Locale('en-US').minimize().toString()"); + Assert.Equal("en", result.AsString()); + } + + [Fact] + public void LocaleWithUnicodeExtension() + { + var result = _engine.Evaluate("new Intl.Locale('en-US-u-ca-gregory').calendar"); + Assert.Equal("gregory", result.AsString()); + } + + [Fact] + public void LocaleInvalidTagThrows() + { + Assert.Throws(() => + _engine.Evaluate("new Intl.Locale('invalid tag with spaces')")); + } + + // Intl.Collator tests + [Fact] + public void CollatorConstructorExists() + { + var result = _engine.Evaluate("typeof Intl.Collator"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void CollatorCanBeConstructed() + { + var result = _engine.Evaluate("new Intl.Collator('en-US')"); + Assert.NotNull(result); + Assert.True(result.IsObject()); + } + + [Fact] + public void CollatorToStringTagIsCorrect() + { + var result = _engine.Evaluate("Object.prototype.toString.call(new Intl.Collator('en-US'))"); + Assert.Equal("[object Intl.Collator]", result.AsString()); + } + + [Fact] + public void CollatorCompareReturnsFunction() + { + var result = _engine.Evaluate("typeof new Intl.Collator('en-US').compare"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void CollatorCompareSortsStrings() + { + var result = _engine.Evaluate(@" + var collator = new Intl.Collator('en-US'); + var compare = collator.compare; + compare('a', 'b') + "); + Assert.True(result.AsNumber() < 0); + } + + [Fact] + public void CollatorCompareWithCaseSensitivity() + { + var result = _engine.Evaluate(@" + var collator = new Intl.Collator('en-US', { sensitivity: 'case' }); + collator.compare('a', 'A') + "); + // With 'case' sensitivity, 'a' and 'A' should be different + Assert.True(result.AsNumber() != 0); + } + + [Fact] + public void CollatorCompareWithBaseSensitivity() + { + var result = _engine.Evaluate(@" + var collator = new Intl.Collator('en-US', { sensitivity: 'base' }); + collator.compare('a', 'A') + "); + // With 'base' sensitivity, 'a' and 'A' should be equal + Assert.Equal(0, result.AsNumber()); + } + + [Fact] + public void CollatorResolvedOptions() + { + var result = _engine.Evaluate(@" + var options = new Intl.Collator('en-US', { usage: 'search' }).resolvedOptions(); + options.usage + "); + Assert.Equal("search", result.AsString()); + } + + [Fact] + public void CollatorSupportedLocalesOf() + { + var result = _engine.Evaluate("Intl.Collator.supportedLocalesOf(['en-US', 'de-DE'])"); + Assert.True(result.IsArray()); + } + + [Fact] + public void CollatorSortingWithCompare() + { + var result = _engine.Evaluate(@" + var items = ['z', 'a', 'c', 'b']; + items.sort(new Intl.Collator('en-US').compare); + items.join(',') + "); + Assert.Equal("a,b,c,z", result.AsString()); + } + + // Intl.NumberFormat tests + [Fact] + public void NumberFormatConstructorExists() + { + var result = _engine.Evaluate("typeof Intl.NumberFormat"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void NumberFormatCanBeConstructed() + { + var result = _engine.Evaluate("new Intl.NumberFormat('en-US')"); + Assert.NotNull(result); + Assert.True(result.IsObject()); + } + + [Fact] + public void NumberFormatToStringTagIsCorrect() + { + var result = _engine.Evaluate("Object.prototype.toString.call(new Intl.NumberFormat('en-US'))"); + Assert.Equal("[object Intl.NumberFormat]", result.AsString()); + } + + [Fact] + public void NumberFormatReturnsFunction() + { + var result = _engine.Evaluate("typeof new Intl.NumberFormat('en-US').format"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void NumberFormatFormatsDecimal() + { + var result = _engine.Evaluate("new Intl.NumberFormat('en-US').format(1234.567)"); + Assert.Contains("1", result.AsString()); + Assert.Contains("234", result.AsString()); + } + + [Fact] + public void NumberFormatFormatsCurrency() + { + var result = _engine.Evaluate("new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD' }).format(1234.56)"); + // Should contain the dollar sign and the number + Assert.Contains("1", result.AsString()); + } + + [Fact] + public void NumberFormatFormatsPercent() + { + var result = _engine.Evaluate("new Intl.NumberFormat('en-US', { style: 'percent' }).format(0.5)"); + // Should contain "50" for 50% + Assert.Contains("50", result.AsString()); + } + + [Fact] + public void NumberFormatResolvedOptions() + { + var result = _engine.Evaluate(@" + var options = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'EUR' }).resolvedOptions(); + options.currency + "); + Assert.Equal("EUR", result.AsString()); + } + + [Fact] + public void NumberFormatResolvedOptionsStyle() + { + var result = _engine.Evaluate(@" + var options = new Intl.NumberFormat('en-US', { style: 'percent' }).resolvedOptions(); + options.style + "); + Assert.Equal("percent", result.AsString()); + } + + [Fact] + public void NumberFormatSupportedLocalesOf() + { + var result = _engine.Evaluate("Intl.NumberFormat.supportedLocalesOf(['en-US', 'de-DE'])"); + Assert.True(result.IsArray()); + } + + [Fact] + public void NumberFormatWithUnit() + { + var result = _engine.Evaluate("new Intl.NumberFormat('en-US', { style: 'unit', unit: 'kilometer' }).format(100)"); + Assert.Contains("100", result.AsString()); + Assert.Contains("km", result.AsString()); + } + + [Fact] + public void NumberFormatCurrencyRequiresCurrency() + { + Assert.Throws(() => + _engine.Evaluate("new Intl.NumberFormat('en-US', { style: 'currency' })")); + } + + [Fact] + public void NumberFormatUnitRequiresUnit() + { + Assert.Throws(() => + _engine.Evaluate("new Intl.NumberFormat('en-US', { style: 'unit' })")); + } + + // Intl.DateTimeFormat tests + [Fact] + public void DateTimeFormatConstructorExists() + { + var result = _engine.Evaluate("typeof Intl.DateTimeFormat"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void DateTimeFormatCanBeConstructed() + { + var result = _engine.Evaluate("new Intl.DateTimeFormat('en-US')"); + Assert.NotNull(result); + Assert.True(result.IsObject()); + } + + [Fact] + public void DateTimeFormatToStringTagIsCorrect() + { + var result = _engine.Evaluate("Object.prototype.toString.call(new Intl.DateTimeFormat('en-US'))"); + Assert.Equal("[object Intl.DateTimeFormat]", result.AsString()); + } + + [Fact] + public void DateTimeFormatFormatReturnsFunction() + { + var result = _engine.Evaluate("typeof new Intl.DateTimeFormat('en-US').format"); + Assert.Equal("function", result.AsString()); + } + + [Fact] + public void DateTimeFormatFormatsDate() + { + var result = _engine.Evaluate("new Intl.DateTimeFormat('en-US').format(new Date(2024, 0, 15, 12, 0, 0))"); + // Should contain the date components - use noon to avoid timezone issues + Assert.Contains("1", result.AsString()); // Month or day + Assert.Contains("2024", result.AsString()); // Year + // Day could be 14, 15, or 16 depending on timezone, just check it's a valid date + Assert.True(result.AsString().Length > 0); + } + + [Fact] + public void DateTimeFormatWithDateStyle() + { + var result = _engine.Evaluate("new Intl.DateTimeFormat('en-US', { dateStyle: 'short' }).format(new Date(2024, 0, 15))"); + Assert.NotNull(result.AsString()); + Assert.True(result.AsString().Length > 0); + } + + [Fact] + public void DateTimeFormatWithTimeStyle() + { + var result = _engine.Evaluate("new Intl.DateTimeFormat('en-US', { timeStyle: 'short' }).format(new Date(2024, 0, 15, 14, 30, 0))"); + Assert.NotNull(result.AsString()); + Assert.True(result.AsString().Length > 0); + } + + [Fact] + public void DateTimeFormatResolvedOptions() + { + var result = _engine.Evaluate(@" + var options = new Intl.DateTimeFormat('en-US', { dateStyle: 'full' }).resolvedOptions(); + options.dateStyle + "); + Assert.Equal("full", result.AsString()); + } + + [Fact] + public void DateTimeFormatResolvedOptionsLocale() + { + var result = _engine.Evaluate(@" + var options = new Intl.DateTimeFormat('en-US').resolvedOptions(); + options.locale + "); + // Should return a locale string + Assert.NotNull(result.AsString()); + } + + [Fact] + public void DateTimeFormatResolvedOptionsCalendar() + { + var result = _engine.Evaluate(@" + var options = new Intl.DateTimeFormat('en-US').resolvedOptions(); + options.calendar + "); + // Default calendar should be 'gregory' + Assert.Equal("gregory", result.AsString()); + } + + [Fact] + public void DateTimeFormatSupportedLocalesOf() + { + var result = _engine.Evaluate("Intl.DateTimeFormat.supportedLocalesOf(['en-US', 'de-DE'])"); + Assert.True(result.IsArray()); + } + + [Fact] + public void DateTimeFormatWithYearMonthDay() + { + var result = _engine.Evaluate(@" + new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: 'numeric', + day: 'numeric' + }).format(new Date(2024, 0, 15)) + "); + Assert.Contains("2024", result.AsString()); + } + + [Fact] + public void DateTimeFormatWithHourMinute() + { + var result = _engine.Evaluate(@" + new Intl.DateTimeFormat('en-US', { + hour: 'numeric', + minute: '2-digit' + }).format(new Date(2024, 0, 15, 14, 30, 0)) + "); + // Should contain time components + Assert.True(result.AsString().Contains("30") || result.AsString().Contains(":")); + } + + [Fact] + public void DateTimeFormatFormatToParts() + { + var result = _engine.Evaluate(@" + var parts = new Intl.DateTimeFormat('en-US').formatToParts(new Date(2024, 0, 15)); + Array.isArray(parts) + "); + Assert.True(result.AsBoolean()); + } + + [Fact] + public void DateTimeFormatFormatToPartsHasTypeAndValue() + { + var result = _engine.Evaluate(@" + var parts = new Intl.DateTimeFormat('en-US').formatToParts(new Date(2024, 0, 15)); + parts.length > 0 && parts[0].type !== undefined && parts[0].value !== undefined + "); + Assert.True(result.AsBoolean()); + } + + [Fact] + public void DateTimeFormatWithWeekday() + { + var result = _engine.Evaluate(@" + new Intl.DateTimeFormat('en-US', { + weekday: 'long', + year: 'numeric', + month: 'long', + day: 'numeric' + }).format(new Date(2024, 0, 15)) + "); + // Should contain a weekday name (Monday, January 15, 2024) + Assert.True(result.AsString().Length > 10); + } + + [Fact] + public void DateTimeFormatWithHourCycle() + { + var result = _engine.Evaluate(@" + var options = new Intl.DateTimeFormat('en-US', { + hour: 'numeric', + hourCycle: 'h23' + }).resolvedOptions(); + options.hourCycle + "); + Assert.Equal("h23", result.AsString()); + } + + [Fact] + public void DateTimeFormatWithTimeZone() + { + var result = _engine.Evaluate(@" + var options = new Intl.DateTimeFormat('en-US', { + timeZone: 'UTC' + }).resolvedOptions(); + options.timeZone + "); + Assert.Equal("UTC", result.AsString()); + } + + [Fact] + public void DateTimeFormatInvalidTimeZoneThrows() + { + Assert.Throws(() => + _engine.Evaluate("new Intl.DateTimeFormat('en-US', { timeZone: 'Invalid/TimeZone' })")); + } + + [Fact] + public void DateTimeFormatFormatsUndefinedAsNow() + { + var result = _engine.Evaluate("new Intl.DateTimeFormat('en-US').format()"); + // Should return a non-empty string (current date formatted) + Assert.NotNull(result.AsString()); + Assert.True(result.AsString().Length > 0); + } + + [Fact] + public void DateTimeFormatFormatsTimestamp() + { + var result = _engine.Evaluate("new Intl.DateTimeFormat('en-US').format(1705363200000)"); // Jan 15, 2024 UTC + Assert.NotNull(result.AsString()); + Assert.True(result.AsString().Length > 0); + } + + [Fact] + public void DateTimeFormatWithMonth2Digit() + { + var result = _engine.Evaluate(@" + new Intl.DateTimeFormat('en-US', { + year: 'numeric', + month: '2-digit', + day: '2-digit' + }).format(new Date(2024, 0, 5)) + "); + // With 2-digit, should have leading zeros + Assert.Contains("01", result.AsString()); // Month + } + + [Fact] + public void DateTimeFormatInvalidDateStyleThrows() + { + Assert.Throws(() => + _engine.Evaluate("new Intl.DateTimeFormat('en-US', { dateStyle: 'invalid' })")); + } + + [Fact] + public void DateTimeFormatInvalidWeekdayThrows() + { + Assert.Throws(() => + _engine.Evaluate("new Intl.DateTimeFormat('en-US', { weekday: 'invalid' })")); + } +} diff --git a/Jint.Tests/Runtime/JsonSerializerTests.cs b/Jint.Tests/Runtime/JsonSerializerTests.cs index 1e61f62db3..684d60d824 100644 --- a/Jint.Tests/Runtime/JsonSerializerTests.cs +++ b/Jint.Tests/Runtime/JsonSerializerTests.cs @@ -59,7 +59,7 @@ public void JsonStringOutputIsIndentedWhenSpacerDefined() JsObject instance = new JsObject(engine); instance["a"] = "b"; instance["b"] = 2; - instance["c"] = new JsArray(engine, new JsValue[] { new JsNumber(4), new JsNumber(5), new JsNumber(6) }); + instance["c"] = new JsArray(engine, [new JsNumber(4), new JsNumber(5), new JsNumber(6)]); instance["d"] = true; string actual = serializer.Serialize(instance, JsValue.Undefined, new JsNumber(2)).ToString(); @@ -75,7 +75,7 @@ public void JsonStringOutputIsCompactWithoutSpacer() JsObject instance = new JsObject(engine); instance["a"] = "b"; instance["b"] = 2; - instance["c"] = new JsArray(engine, new JsValue[] { new JsNumber(4), new JsNumber(5), new JsNumber(6) }); + instance["c"] = new JsArray(engine, [new JsNumber(4), new JsNumber(5), new JsNumber(6)]); instance["d"] = true; string actual = serializer.Serialize(instance, JsValue.Undefined, JsValue.Undefined).ToString(); @@ -88,7 +88,7 @@ public void ArrayWithUndefinedWillBeNull() using var engine = new Engine(); var serializer = new JsonSerializer(engine); - JsArray array = new JsArray(engine, new JsValue[] { JsValue.Undefined, new JsNumber(42) }); + JsArray array = new JsArray(engine, [JsValue.Undefined, new JsNumber(42)]); string actual = serializer.Serialize(array, JsValue.Undefined, JsValue.Undefined).ToString(); Assert.Equal("[null,42]", actual); } @@ -125,7 +125,7 @@ public void InfinityAndNaNGetsSerializedAsNull() { using var engine = new Engine(); var serializer = new JsonSerializer(engine); - JsArray array = new JsArray(engine, new JsValue[] { JsNumber.DoubleNegativeInfinity, JsNumber.DoublePositiveInfinity, JsNumber.DoubleNaN }); + JsArray array = new JsArray(engine, [JsNumber.DoubleNegativeInfinity, JsNumber.DoublePositiveInfinity, JsNumber.DoubleNaN]); string actual = serializer.Serialize(array, JsValue.Undefined, JsValue.Undefined).ToString(); Assert.Equal("[null,null,null]", actual); } @@ -139,7 +139,7 @@ public void ArrayAsReplacedDictatesPropertiesToSerializer() JsObject instance = new JsObject(engine); instance["a"] = 21; instance["b"] = 42; - JsValue replacer = new JsArray(engine, new JsValue[] { new JsString("b"), new JsString("z") }); + JsValue replacer = new JsArray(engine, [new JsString("b"), new JsString("z")]); string actual = serializer.Serialize(instance, replacer, JsValue.Undefined).ToString(); Assert.Equal("{\"b\":42}", actual); } diff --git a/Jint.Tests/Runtime/JsonTests.cs b/Jint.Tests/Runtime/JsonTests.cs index e8b896cf92..38b003acf9 100644 --- a/Jint.Tests/Runtime/JsonTests.cs +++ b/Jint.Tests/Runtime/JsonTests.cs @@ -7,13 +7,19 @@ namespace Jint.Tests.Runtime; public class JsonTests { - [Fact] - public void CanParseTabsInProperties() + [Theory] + [InlineData(@"JSON.parse(""{\""abc\\tdef\"": \""42\""}"");", "abc\tdef")] + [InlineData(@"JSON.parse(""{\""abc\\ndef\"": \""42\""}"");", "abc\ndef")] + [InlineData(@"JSON.parse(""{\""abc\\fdef\"": \""42\""}"");", "abc\fdef")] + [InlineData(@"JSON.parse(""{\""abc\\bdef\"": \""42\""}"");", "abc\bdef")] + [InlineData(@"JSON.parse(""{\""abc\\rdef\"": \""42\""}"");", "abc\rdef")] + [InlineData(@"JSON.parse(""{\""abc\\r\\ndef\"": \""42\""}"");", "abc\r\ndef")] + [InlineData(@"JSON.parse(""{\""abc\\\""def\"": \""42\""}"");", "abc\"def")] + public void CanParseEscapeSequencesInProperties(string script, string expectedPropertyName) { var engine = new Engine(); - const string script = @"JSON.parse(""{\""abc\\tdef\"": \""42\""}"");"; var obj = engine.Evaluate(script).AsObject(); - Assert.True(obj.HasOwnProperty("abc\tdef")); + Assert.True(obj.HasOwnProperty(expectedPropertyName)); } [Theory] @@ -314,4 +320,4 @@ private static string GenerateDeepNestedObject(int depth, string mostInnerValue) string objectClose = new string('}', depth); return $"{objectOpen}{mostInnerValue}{objectClose}"; } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/MethodAmbiguityTests.cs b/Jint.Tests/Runtime/MethodAmbiguityTests.cs index 2f1fd484d6..1020d7b919 100644 --- a/Jint.Tests/Runtime/MethodAmbiguityTests.cs +++ b/Jint.Tests/Runtime/MethodAmbiguityTests.cs @@ -116,6 +116,49 @@ public void ShouldMatchCorrectConstructors() "); } + [Fact] + public void ShouldSelectTypeReferenceOverloadCorrectly() + { + var engine = new Engine(cfg => cfg.AllowClr()); + engine.SetValue("Player", TypeReference.CreateTypeReference(engine)); + engine.SetValue("DamageClass", TypeReference.CreateTypeReference(engine)); + + var damageClassInstance = new DamageClass(); + engine.SetValue("damageClassInstance", damageClassInstance); + + // When passing a TypeReference, should select the TypeReference overload + var typeRefResult = engine.Evaluate("Player.GetArmorPenetration(DamageClass);"); + Assert.Equal("TypeReference", typeRefResult.AsString()); + + // When passing a domain object instance, should select the domain class overload + var instanceResult = engine.Evaluate("Player.GetArmorPenetration(damageClassInstance);"); + Assert.Equal("DamageClass", instanceResult.AsString()); + } + + [Fact] + public void ShouldSelectCorrectExtensionMethodOverloadForTypeReference() + { + var engine = new Engine(cfg => cfg + .AllowClr() + .AddExtensionMethods(typeof(PlayerExtensions))); + + engine.SetValue("DamageClass", TypeReference.CreateTypeReference(engine)); + + var player = new Player(); + engine.SetValue("player", player); + + var damageClassInstance = new DamageClass(); + engine.SetValue("damageClassInstance", damageClassInstance); + + // When calling extension method with TypeReference, should select TypeReference overload + var typeRefResult = engine.Evaluate("player.GetPenetration(DamageClass);"); + Assert.Equal("TypeReference:10", typeRefResult.AsString()); + + // When calling extension method with instance, should select DamageClass overload + var instanceResult = engine.Evaluate("player.GetPenetration(damageClassInstance);"); + Assert.Equal("DamageClass:20", instanceResult.AsString()); + } + private struct Class1 { public static string Print(ExpandoObject eo) => nameof(Class1) + "." + nameof(ExpandoObject); @@ -143,4 +186,27 @@ public Class3() { } public void Print(object a) => Console.WriteLine("Print(object a): " + a); } +} + +public class Player +{ + // Static methods for testing overload resolution + public static string GetArmorPenetration(TypeReference damageClass) => "TypeReference"; + public static string GetArmorPenetration(Type damageClass) => "Type"; + public static string GetArmorPenetration(DamageClass damageClass) => "DamageClass"; + + // Variant that only takes Type (for testing explicit Type parameter) + public static string GetArmorPenetrationForType(Type damageClass) => "Type"; +} + +public class DamageClass +{ + public string Name { get; set; } = "Default"; +} + +public static class PlayerExtensions +{ + public static string GetPenetration(this Player player, TypeReference damageClass) => "TypeReference:10"; + public static string GetPenetration(this Player player, Type damageClass) => "Type:15"; + public static string GetPenetration(this Player player, DamageClass damageClass) => "DamageClass:20"; } \ No newline at end of file diff --git a/Jint.Tests/Runtime/ModuleTests.cs b/Jint.Tests/Runtime/ModuleTests.cs index 65b5804578..16285cdca1 100644 --- a/Jint.Tests/Runtime/ModuleTests.cs +++ b/Jint.Tests/Runtime/ModuleTests.cs @@ -229,20 +229,18 @@ public void ShouldAddModuleFromClrFunction() export const result = [fns.act_noargs(), fns.act_args('ok'), fns.fn_noargs(), fns.fn_args('ok')];"); var ns = _engine.Modules.Import("my-module"); - Assert.Equal(new[] - { + Assert.Equal([ "act_noargs", "act_args:ok", "fn_noargs", "fn_args:ok" - }, received.ToArray()); - Assert.Equal(new[] - { + ], received.ToArray()); + Assert.Equal([ "undefined", "undefined", "ret", "ret" - }, ns.Get("result").AsArray().Select(x => x.ToString()).ToArray()); + ], ns.Get("result").AsArray().Select(x => x.ToString()).ToArray()); } private class ImportedClass @@ -398,7 +396,7 @@ public void EngineExecutePassesSourceForModuleResolving() log(value); })(); "; - List logStatements = new List(); + List logStatements = []; engine.SetValue("log", logStatements.Add); engine.Execute(code, source: "file:///folder/main.js"); @@ -423,7 +421,7 @@ public void EngineExecuteUsesScriptSourceForSource() log(value); })(); "; - List logStatements = new List(); + List logStatements = []; engine.SetValue("log", logStatements.Add); var script = Engine.PrepareScript(code, source: "file:///folder/main.js"); @@ -449,7 +447,7 @@ public void EngineEvaluatePassesSourceForModuleResolving() log(value); })(); "; - List logStatements = new List(); + List logStatements = []; engine.SetValue("log", logStatements.Add); engine.Evaluate(code, source: "file:///folder/main.js"); @@ -474,7 +472,7 @@ public void EngineEvaluateUsesScriptSourceForSource() log(value); })(); "; - List logStatements = new List(); + List logStatements = []; engine.SetValue("log", logStatements.Add); var script = Engine.PrepareScript(code, source: "file:///folder/main.js"); diff --git a/Jint.Tests/Runtime/NumberTests.cs b/Jint.Tests/Runtime/NumberTests.cs index 63e82052b9..b43ae7fb57 100644 --- a/Jint.Tests/Runtime/NumberTests.cs +++ b/Jint.Tests/Runtime/NumberTests.cs @@ -137,11 +137,33 @@ public void ToLocaleString(string parseNumber, string culture, string result) } [Theory] - // Does not add extra zeros of there is no cuture argument. + // Does not add extra zeros of there is no culture argument. [InlineData("123456")] public void ToLocaleStringNoArg(string parseNumber) { var value = _engine.Evaluate($"({parseNumber}).toLocaleString()").AsString(); Assert.DoesNotContain(".0", value); } -} \ No newline at end of file + + [Fact] + public void CoercingOverflowFromString() + { + var engine = new Engine(); + + Assert.Equal(double.PositiveInfinity, engine.Evaluate("Number(1e1000)").ToObject()); + Assert.Equal(double.PositiveInfinity, engine.Evaluate("+1e1000").ToObject()); + Assert.Equal("Infinity", engine.Evaluate("(+1e1000).toString()").ToObject()); + + Assert.Equal(double.PositiveInfinity, engine.Evaluate("Number('1e1000')").ToObject()); + Assert.Equal(double.PositiveInfinity, engine.Evaluate("+'1e1000'").ToObject()); + Assert.Equal("Infinity", engine.Evaluate("(+'1e1000').toString()").ToObject()); + + Assert.Equal(double.NegativeInfinity, engine.Evaluate("Number(-1e1000)").ToObject()); + Assert.Equal(double.NegativeInfinity, engine.Evaluate("-1e1000").ToObject()); + Assert.Equal("-Infinity", engine.Evaluate("(-1e1000).toString()").ToObject()); + + Assert.Equal(double.NegativeInfinity, engine.Evaluate("Number('-1e1000')").ToObject()); + Assert.Equal(double.NegativeInfinity, engine.Evaluate("-'1e1000'").ToObject()); + Assert.Equal("-Infinity", engine.Evaluate("(-'1e1000').toString()").ToObject()); + } +} diff --git a/Jint.Tests/Runtime/PromiseTests.cs b/Jint.Tests/Runtime/PromiseTests.cs index a4e86f3256..b77425bde4 100644 --- a/Jint.Tests/Runtime/PromiseTests.cs +++ b/Jint.Tests/Runtime/PromiseTests.cs @@ -497,4 +497,126 @@ public void ManualPromise_HasCorrectStackTrace() Assert.Equal("at :1:56", logMessage?.Trim()); } + + [Fact] + public void WithResolvers_calling_resolve_resolves_promise() + { + // Arrange + using var engine = new Engine(); + List logMessages = []; + engine.SetValue("log", logMessages.Add); + + // Act + engine.Execute(""" + const p = Promise.withResolvers(); + const next = p.promise + .then(() => log('resolved')) + .catch(() => log('rejected')); + + log('start'); + p.resolve(); + log('end'); + """); + engine.RunAvailableContinuations(); + + // Assert + List expected = ["start", "end", "resolved"]; + Assert.Equal(expected, logMessages); + } + + [Fact] + public void WithResolvers_calling_reject_rejects_promise() + { + // Arrange + using var engine = new Engine(); + List logMessages = []; + engine.SetValue("log", logMessages.Add); + + // Act + engine.Execute(""" + const p = Promise.withResolvers(); + const next = p.promise + .then(() => log('resolved')) + .catch(() => log('rejected')); + + log('start'); + p.reject(); + log('end'); + """); + engine.RunAvailableContinuations(); + + // Assert + List expected = ["start", "end", "rejected"]; + Assert.Equal(expected, logMessages); + } + + [Fact] + public void UnwrapIfPromise_WithCancellationToken_ResolvesCorrectly() + { + Action resolveFunc = null!; + + var engine = new Engine(); + engine.SetValue("f", new Func(() => + { + var (promise, resolve, _) = engine.RegisterPromise(); + resolveFunc = resolve; + return promise; + })); + + var promise = engine.Evaluate("f();"); + + using var cts = new CancellationTokenSource(); + resolveFunc(42); + Assert.Equal(42, promise.UnwrapIfPromise(cts.Token)); + } + + [Fact] + public void UnwrapIfPromise_WithCancellationToken_ThrowsOperationCanceledException() + { + var engine = new Engine(); + engine.SetValue("f", new Func(() => + { + var (promise, _, _) = engine.RegisterPromise(); + return promise; + })); + + var promise = engine.Evaluate("f();"); + + using var cts = new CancellationTokenSource(); + cts.CancelAfter(TimeSpan.FromMilliseconds(50)); + + Assert.Throws(() => promise.UnwrapIfPromise(cts.Token)); + } + + [Fact] + public void UnwrapIfPromise_WithCancellationToken_NonPromiseReturnsValue() + { + var engine = new Engine(); + var result = engine.Evaluate("42"); + + using var cts = new CancellationTokenSource(); + Assert.Equal(42, result.UnwrapIfPromise(cts.Token)); + } + + [Fact] + public void UnwrapIfPromise_WithCancellationToken_RejectsCorrectly() + { + Action rejectFunc = null!; + + var engine = new Engine(); + engine.SetValue("f", new Func(() => + { + var (promise, _, reject) = engine.RegisterPromise(); + rejectFunc = reject; + return promise; + })); + + var promise = engine.Evaluate("f();"); + + using var cts = new CancellationTokenSource(); + rejectFunc("error!"); + + var ex = Assert.Throws(() => promise.UnwrapIfPromise(cts.Token)); + Assert.Equal("error!", ex.RejectedValue.AsString()); + } } diff --git a/Jint.Tests/Runtime/PropertyDescriptorTests.cs b/Jint.Tests/Runtime/PropertyDescriptorTests.cs index 7f1b0fda39..7c2c7ac23b 100644 --- a/Jint.Tests/Runtime/PropertyDescriptorTests.cs +++ b/Jint.Tests/Runtime/PropertyDescriptorTests.cs @@ -189,7 +189,7 @@ JsValue ExtractClrAccessDescriptor(JsValue jsArugments) public void PropertyDescriptorMethod() { var pdMethod = _engine.Evaluate("Object.getOwnPropertyDescriptor(testClass, 'Method')"); - CheckPropertyDescriptor(pdMethod, false, false, false, true, false, false); + CheckPropertyDescriptor(jsPropertyDescriptor: pdMethod, configurable: true, enumerable: false, writable: false, hasValue: true, hasGet: false, hasSet: false); var pd = _engine.Evaluate("testClass").AsObject().GetOwnProperty("Method"); // use PropertyDescriptor to wrap method directly diff --git a/Jint.Tests/Runtime/ProxyTests.cs b/Jint.Tests/Runtime/ProxyTests.cs index f86e61cd55..eb6b5fc3a5 100644 --- a/Jint.Tests/Runtime/ProxyTests.cs +++ b/Jint.Tests/Runtime/ProxyTests.cs @@ -234,6 +234,8 @@ class TestClass public int IntValue => 42424242; // avoid small numbers cache public TestClass ObjectWrapper => Instance; + public List IntList { get; } = [1, 2, 3]; + private int x = 1; public int PropertySideEffect => x++; @@ -455,7 +457,7 @@ public void ClrPropertySideEffect() Assert.Equal(2, TestClass.Instance.PropertySideEffect); // second call to PropertySideEffect } - [Fact] + [Fact] public void ToObjectReturnsProxiedToObject() { _engine @@ -482,4 +484,127 @@ public void ToObjectReturnsProxiedToObject() """); } + + [Fact] + public void ProxyIterateClrList() + { + var res = _engine + .SetValue("obj", TestClass.Instance) + .Evaluate(""" + //const obj = { + // IntList: [1, 2, 3] + //}; + + const objProxy = new Proxy(obj, { + get(target, prop, receiver) { + const targetValue = Reflect.get(target, prop, receiver); + if (prop == 'IntList') { + return new Proxy(targetValue, { + get(target, prop, receiver) { + return Reflect.get(target, prop, receiver); + } + }); + } + + return targetValue; + } + }); + + const arr = [] + for (const item of objProxy.IntList) + { + arr.push(item); + } + arr.push(objProxy.IntList.length) + + return arr; + """); + + Assert.Equal([1, 2, 3, 3], res.AsArray()); + } + + [Fact] + public void ProxyClrObjectMethod() + { + var res = _engine + .SetValue("T", new TestClass()) + .Evaluate(""" + const handler = { + get(target, property, receiver) { + + if (property == "Add") { + return function(...args) { return 42}; + } + + return Reflect.get(...arguments); + } + }; + + const p = new Proxy(T, handler); + p.Add(5,3); // throws 'get' on proxy: property 'Add' is a read-only and non-configurable data property + // on the proxy target but the proxy did not return its actual value + // (expected 'function Jint.Tests.Runtime.ProxyTests+TestClass.Add() { [native code] }' but got 'function () { [native code] }') + """); + + Assert.Equal(42, res.AsInteger()); + } + + [Fact] + public void ProxyClrObjectMethodWithDelegate() + { + var res = _engine + .SetValue("T", new TestClass()) + .Evaluate(""" + const handler = { + get(target, property, receiver) { + + if (property == "Add") { + return (...args) => 42; + } + + return Reflect.get(...arguments); + } + }; + + const p = new Proxy(T, handler); + p.Add(5,3); // throws 'get' on proxy: property 'Add' is a read-only and non-configurable data property + // on the proxy target but the proxy did not return its actual value + // (expected 'function Jint.Tests.Runtime.ProxyTests+TestClass.Add() { [native code] }' but got 'function () { [native code] }') + """); + + Assert.Equal(42, res.AsInteger()); + } + + [Fact] + public void ProxyClrObjectWithTmpObjectMethod() + { + var res = _engine + .SetValue("T", new TestClass()) + .Evaluate(""" + const handler = { + get(target, property, receiver) { + + if (property == "Add") { + return (...args) => target.target[property](...args) + 34; + } + + if (typeof target.target[property] === "function") + return (...args) => target.target[property](...args); + + return Reflect.get(target.target, property, receiver) + } + }; + + const tmpObj = { target: T }; + const p = new Proxy(tmpObj, handler); + + const name = p.Name; + p.SayHello(); + const res = p.Add(5,3); // works now + + name + " " + res + """); + + Assert.Equal("My Name is Test 42", res.AsString()); + } } diff --git a/Jint.Tests/Runtime/RegExpTests.cs b/Jint.Tests/Runtime/RegExpTests.cs index 20d3d3dc2d..6508123533 100644 --- a/Jint.Tests/Runtime/RegExpTests.cs +++ b/Jint.Tests/Runtime/RegExpTests.cs @@ -125,7 +125,7 @@ public void ShouldAllowProblematicGroupNames() var match = engine.Evaluate("'abc'.match(/(?<$group>b)/)").AsArray(); var groups = match.Get("groups").AsObject(); - Assert.Equal(new[] { "$group" }, groups.GetOwnPropertyKeys().Select(k => k.AsString())); + Assert.Equal(["$group"], groups.GetOwnPropertyKeys().Select(k => k.AsString())); Assert.Equal("b", groups["$group"]); var result = engine.Evaluate("'abc'.replace(/(?<$group>b)/g, '-$<$group>-')").AsString(); diff --git a/Jint.Tests/Runtime/TestClasses/AsyncTestClass.cs b/Jint.Tests/Runtime/TestClasses/AsyncTestClass.cs index d294a9d6e8..847fd86d1b 100644 --- a/Jint.Tests/Runtime/TestClasses/AsyncTestClass.cs +++ b/Jint.Tests/Runtime/TestClasses/AsyncTestClass.cs @@ -8,14 +8,14 @@ internal class AsyncTestClass public async Task AddToStringDelayedAsync(string appendWith) { - await Task.Delay(1000).ConfigureAwait(false); + await Task.Delay(100).ConfigureAwait(false); StringToAppend += appendWith; } public async Task ReturnDelayedTaskAsync() { - await Task.Delay(1000).ConfigureAwait(false); + await Task.Delay(100).ConfigureAwait(false); return TestString; } @@ -36,4 +36,39 @@ public async Task ThrowAfterDelayAsync() throw new Exception("Task threw exception"); } -} \ No newline at end of file + +#if !NETFRAMEWORK + public async ValueTask ReturnDelayedValueTaskAsync() + { + await Task.Delay(100).ConfigureAwait(false); + + return TestString; + } + + public ValueTask ReturnCompletedValueTask() + { + return ValueTask.FromResult(TestString); + } + + public ValueTask ReturnCancelledValueTask(CancellationToken token) + { + return ValueTask.FromCanceled(token); + } + + public async ValueTask ThrowAfterDelayValueTaskAsync() + { + await Task.Delay(100).ConfigureAwait(false); + + throw new Exception("ValueTask threw exception"); + } + + public async IAsyncEnumerable AsyncEnumerable() + { + yield return "Hello"; + await Task.Delay(10).ConfigureAwait(false); + yield return " "; + await Task.Delay(10).ConfigureAwait(false); + yield return "World"; + } +#endif +} diff --git a/Jint.Tests/Runtime/TypeConverterTests.cs b/Jint.Tests/Runtime/TypeConverterTests.cs index 781c893995..e35a02355b 100644 --- a/Jint.Tests/Runtime/TypeConverterTests.cs +++ b/Jint.Tests/Runtime/TypeConverterTests.cs @@ -1,6 +1,5 @@ using Jint.Native; using Jint.Runtime; -using Xunit.Abstractions; namespace Jint.Tests.Runtime; @@ -17,7 +16,7 @@ public TypeConverterTests(ITestOutputHelper output) ; } - public static readonly IEnumerable ConvertNumberToInt32AndUint32TestData = new TheoryData() + public static readonly TheoryData ConvertNumberToInt32AndUint32TestData = new() { { 0.0, 0 }, { -0.0, 0 }, @@ -91,4 +90,4 @@ public void ToPrimitiveShouldEvaluateOnlyOnceDuringInExpression() equal(1, bval); "); } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/TypedArrayInteropTests.cs b/Jint.Tests/Runtime/TypedArrayInteropTests.cs index ce0a4a2161..87b8402c1f 100644 --- a/Jint.Tests/Runtime/TypedArrayInteropTests.cs +++ b/Jint.Tests/Runtime/TypedArrayInteropTests.cs @@ -133,6 +133,9 @@ public void CanInteropWithFloat16() var fromEngine = engine.GetValue("testSubject"); Assert.True(fromEngine.IsFloat16Array()); Assert.Equal(source, fromEngine.AsFloat16Array()); + + engine.SetValue("testFunc", new Func(v => v)); + Assert.Equal(source, engine.Evaluate("testFunc(testSubject)").AsFloat16Array()); } #endif @@ -179,4 +182,4 @@ private static void ValidateCreatedBigIntegerTypeArray(Engine engine, string arr Assert.Equal(42, engine.Evaluate("testSubject[0]").AsBigInt()); Assert.Equal(12, engine.Evaluate("testSubject[1]").AsBigInt()); } -} \ No newline at end of file +} diff --git a/Jint.Tests/Runtime/WeakSetMapTests.cs b/Jint.Tests/Runtime/WeakSetMapTests.cs index 03cc5017f6..582eda7951 100644 --- a/Jint.Tests/Runtime/WeakSetMapTests.cs +++ b/Jint.Tests/Runtime/WeakSetMapTests.cs @@ -21,7 +21,7 @@ public void WeakSetShouldThrowWhenCalledWithoutNew() Assert.Equal("Constructor WeakSet requires 'new'", e.Message); } - public static IEnumerable PrimitiveKeys = new TheoryData + public static TheoryData PrimitiveKeys = new() { JsValue.Null, JsValue.Undefined, diff --git a/Jint.sln b/Jint.sln deleted file mode 100644 index 79994b1e70..0000000000 --- a/Jint.sln +++ /dev/null @@ -1,95 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26406.1 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint", "Jint\Jint.csproj", "{4B6F353A-5D01-407D-9DC8-96D1FD4F7052}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint.Benchmark", "Jint.Benchmark\Jint.Benchmark.csproj", "{228D9FA5-CBC0-4489-9805-9B4456747647}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint.Repl", "Jint.Repl\Jint.Repl.csproj", "{BB3AC010-C2CE-41A5-93C6-763600EF67B3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint.Tests", "Jint.Tests\Jint.Tests.csproj", "{9FBCAB58-FE6D-4804-92B8-7CF886113708}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint.Tests.CommonScripts", "Jint.Tests.CommonScripts\Jint.Tests.CommonScripts.csproj", "{B815F239-6409-4BA7-9461-18317AA2DBED}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint.Tests.Test262", "Jint.Tests.Test262\Jint.Tests.Test262.csproj", "{62FFFDBD-AB58-490D-9A50-AA7C53BF0409}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Jint.Tests.PublicInterface", "Jint.Tests.PublicInterface\Jint.Tests.PublicInterface.csproj", "{70198CE9-7DFE-40CA-BBAC-1454C92C4109}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{4EB9DD72-0566-4EE4-9550-B064ED2C8332}" - ProjectSection(SolutionItems) = preProject - README.md = README.md - .editorconfig = .editorconfig - Directory.Packages.props = Directory.Packages.props - Directory.Build.props = Directory.Build.props - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Jint.AotExample", "Jint.AotExample\Jint.AotExample.csproj", "{E07CC7D2-2ADF-4D71-A5B1-88209FFC29CD}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{D60F0DFE-8860-4E25-8973-E1F11AE2466A}" - ProjectSection(SolutionItems) = preProject - .github\dependabot.yml = .github\dependabot.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "workflows", "workflows", "{85015C4E-5BBE-475D-9A17-A63434F9AA2A}" - ProjectSection(SolutionItems) = preProject - .github\workflows\build.yml = .github\workflows\build.yml - .github\workflows\pr.yml = .github\workflows\pr.yml - .github\workflows\release.yml = .github\workflows\release.yml - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ISSUE_TEMPLATE", "ISSUE_TEMPLATE", "{EEFDE656-C1C0-432A-941E-4EDD84452410}" - ProjectSection(SolutionItems) = preProject - .github\ISSUE_TEMPLATE\bug_report.md = .github\ISSUE_TEMPLATE\bug_report.md - .github\ISSUE_TEMPLATE\feature_request.md = .github\ISSUE_TEMPLATE\feature_request.md - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4B6F353A-5D01-407D-9DC8-96D1FD4F7052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4B6F353A-5D01-407D-9DC8-96D1FD4F7052}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4B6F353A-5D01-407D-9DC8-96D1FD4F7052}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4B6F353A-5D01-407D-9DC8-96D1FD4F7052}.Release|Any CPU.Build.0 = Release|Any CPU - {228D9FA5-CBC0-4489-9805-9B4456747647}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {228D9FA5-CBC0-4489-9805-9B4456747647}.Debug|Any CPU.Build.0 = Debug|Any CPU - {228D9FA5-CBC0-4489-9805-9B4456747647}.Release|Any CPU.ActiveCfg = Release|Any CPU - {228D9FA5-CBC0-4489-9805-9B4456747647}.Release|Any CPU.Build.0 = Release|Any CPU - {BB3AC010-C2CE-41A5-93C6-763600EF67B3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BB3AC010-C2CE-41A5-93C6-763600EF67B3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BB3AC010-C2CE-41A5-93C6-763600EF67B3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BB3AC010-C2CE-41A5-93C6-763600EF67B3}.Release|Any CPU.Build.0 = Release|Any CPU - {9FBCAB58-FE6D-4804-92B8-7CF886113708}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9FBCAB58-FE6D-4804-92B8-7CF886113708}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9FBCAB58-FE6D-4804-92B8-7CF886113708}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9FBCAB58-FE6D-4804-92B8-7CF886113708}.Release|Any CPU.Build.0 = Release|Any CPU - {B815F239-6409-4BA7-9461-18317AA2DBED}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B815F239-6409-4BA7-9461-18317AA2DBED}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B815F239-6409-4BA7-9461-18317AA2DBED}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B815F239-6409-4BA7-9461-18317AA2DBED}.Release|Any CPU.Build.0 = Release|Any CPU - {62FFFDBD-AB58-490D-9A50-AA7C53BF0409}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {62FFFDBD-AB58-490D-9A50-AA7C53BF0409}.Debug|Any CPU.Build.0 = Debug|Any CPU - {62FFFDBD-AB58-490D-9A50-AA7C53BF0409}.Release|Any CPU.ActiveCfg = Release|Any CPU - {62FFFDBD-AB58-490D-9A50-AA7C53BF0409}.Release|Any CPU.Build.0 = Release|Any CPU - {70198CE9-7DFE-40CA-BBAC-1454C92C4109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {70198CE9-7DFE-40CA-BBAC-1454C92C4109}.Debug|Any CPU.Build.0 = Debug|Any CPU - {70198CE9-7DFE-40CA-BBAC-1454C92C4109}.Release|Any CPU.ActiveCfg = Release|Any CPU - {70198CE9-7DFE-40CA-BBAC-1454C92C4109}.Release|Any CPU.Build.0 = Release|Any CPU - {E07CC7D2-2ADF-4D71-A5B1-88209FFC29CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E07CC7D2-2ADF-4D71-A5B1-88209FFC29CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E07CC7D2-2ADF-4D71-A5B1-88209FFC29CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E07CC7D2-2ADF-4D71-A5B1-88209FFC29CD}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {D60F0DFE-8860-4E25-8973-E1F11AE2466A} = {4EB9DD72-0566-4EE4-9550-B064ED2C8332} - {85015C4E-5BBE-475D-9A17-A63434F9AA2A} = {D60F0DFE-8860-4E25-8973-E1F11AE2466A} - {EEFDE656-C1C0-432A-941E-4EDD84452410} = {D60F0DFE-8860-4E25-8973-E1F11AE2466A} - EndGlobalSection -EndGlobal diff --git a/Jint.slnx b/Jint.slnx new file mode 100644 index 0000000000..fbacb65e82 --- /dev/null +++ b/Jint.slnx @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Jint/AstExtensions.cs b/Jint/AstExtensions.cs index b891d43edf..1bde5ce247 100644 --- a/Jint/AstExtensions.cs +++ b/Jint/AstExtensions.cs @@ -27,7 +27,7 @@ public static JsValue GetKey(this Expression expression, Engine engine, bool res return TypeConverter.ToPropertyKey(key); } - ExceptionHelper.ThrowArgumentException("Unable to extract correct key, node type: " + expression.Type); + Throw.ArgumentException("Unable to extract correct key, node type: " + expression.Type); return JsValue.Undefined; } @@ -77,10 +77,16 @@ or NodeType.ConditionalExpression or NodeType.ArrowFunctionExpression or NodeType.FunctionExpression or NodeType.YieldExpression - or NodeType.TemplateLiteral) + or NodeType.TemplateLiteral + or NodeType.ArrayExpression + or NodeType.ObjectExpression) { var context = engine._activeEvaluationContext ?? new EvaluationContext(engine); - return JintExpression.Build(expression).GetValue(context); + var result = JintExpression.Build(expression).GetValue(context); + + // If the expression suspended the generator (e.g., yield in computed property name), + // return the value. The caller should check ExecutionContext.Suspended. + return result; } return JsValue.Undefined; @@ -299,7 +305,7 @@ internal static void BindingInitialization( if (expression is Identifier identifier) { var catchEnvRecord = (DeclarativeEnvironment) env; - catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value); + catchEnvRecord.CreateMutableBindingAndInitialize(identifier.Name, canBeDeleted: false, value, DisposeHint.Normal); } else if (expression is DestructuringPattern pattern) { @@ -324,7 +330,7 @@ internal static Record DefineMethod(this T m, ObjectInstance obj, ObjectInsta var function = m.Value as IFunction; if (function is null) { - ExceptionHelper.ThrowSyntaxError(engine.Realm); + Throw.SyntaxError(engine.Realm); } var definition = new JintFunctionDefinition(function); @@ -362,7 +368,7 @@ private static ModuleImportAttribute[] GetAttributes(in NodeList(); + return []; } var attributes = new ModuleImportAttribute[importAttributes.Count]; @@ -498,6 +504,16 @@ internal static void AllPrivateIdentifiersValid(this Script script, Realm realm, validator.Visit(script); } + internal static DisposeHint GetDisposeHint(this VariableDeclarationKind statement) + { + return statement switch + { + VariableDeclarationKind.AwaitUsing => DisposeHint.Async, + VariableDeclarationKind.Using => DisposeHint.Sync, + _ => DisposeHint.Normal, + }; + } + private sealed class MinimalSyntaxElement : Node { public MinimalSyntaxElement(in SourceLocation location) : base(NodeType.Unknown) @@ -542,7 +558,7 @@ protected override object VisitClassBody(ClassBody classBody) [MethodImpl(MethodImplOptions.NoInlining)] private static void Throw(Realm r, PrivateIdentifier id) { - ExceptionHelper.ThrowSyntaxError(r, $"Private field '#{id.Name}' must be declared in an enclosing class"); + Runtime.Throw.SyntaxError(r, $"Private field '#{id.Name}' must be declared in an enclosing class"); } } -} \ No newline at end of file +} diff --git a/Jint/Collections/DictionaryBase.cs b/Jint/Collections/DictionaryBase.cs new file mode 100644 index 0000000000..5611902620 --- /dev/null +++ b/Jint/Collections/DictionaryBase.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; + +namespace Jint.Collections; + +internal abstract class DictionaryBase : IEngineDictionary +{ + public ref TValue this[Key key] + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + get => ref GetValueRefOrAddDefault(key, out _); + } + + public bool TryGetValue(Key key, [NotNullWhen(true)] out TValue? value) + { + value = default; + ref var temp = ref GetValueRefOrNullRef(key); + if (Unsafe.IsNullRef(ref temp)) + { + return false; + } + + value = temp!; + return true; + } + + public bool ContainsKey(Key key) + { + ref var valueRefOrNullRef = ref GetValueRefOrNullRef(key); + return !Unsafe.IsNullRef(ref valueRefOrNullRef); + } + + public abstract int Count { get; } + + public abstract ref TValue GetValueRefOrNullRef(Key key); + public abstract ref TValue GetValueRefOrAddDefault(Key key, out bool exists); +} diff --git a/Jint/Collections/DictionarySlim.cs b/Jint/Collections/DictionarySlim.cs index 85f3320636..35ec3812f0 100644 --- a/Jint/Collections/DictionarySlim.cs +++ b/Jint/Collections/DictionarySlim.cs @@ -18,7 +18,7 @@ namespace Jint.Collections; /// 3) It does not accept an equality comparer (assumes Object.GetHashCode() and Object.Equals() or overridden implementation are cheap and sufficient). /// [DebuggerDisplay("Count = {Count}")] -internal class DictionarySlim : IReadOnlyCollection> where TKey : IEquatable +internal sealed class DictionarySlim : IReadOnlyCollection> where TKey : IEquatable { // We want to initialize without allocating arrays. We also want to avoid null checks. // Array.Empty would give divide by zero in modulo operation. So we use static one element arrays. @@ -75,8 +75,8 @@ public void Clear() public bool ContainsKey(TKey key) { Entry[] entries = _entries; - for (int i = _buckets[key.GetHashCode() & (_buckets.Length-1)] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) + for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1; + (uint) i < (uint) entries.Length; i = entries[i].next) { if (key.Equals(entries[i].key)) return true; @@ -89,7 +89,7 @@ public bool TryGetValue(TKey key, out TValue value) { Entry[] entries = _entries; for (int i = _buckets[key.GetHashCode() & (_buckets.Length - 1)] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) + (uint) i < (uint) entries.Length; i = entries[i].next) { if (key.Equals(entries[i].key)) { @@ -152,7 +152,7 @@ public ref TValue GetOrAddValueRef(TKey key) Entry[] entries = _entries; int bucketIndex = key.GetHashCode() & (_buckets.Length - 1); for (int i = _buckets[bucketIndex] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) + (uint) i < (uint) entries.Length; i = entries[i].next) { if (key.Equals(entries[i].key)) return ref entries[i].value; @@ -200,7 +200,7 @@ private Entry[] Resize() Debug.Assert(_entries.Length == _count || _entries.Length == 1); // We only copy _count, so if it's longer we will miss some int count = _count; int newSize = _entries.Length * 2; - if ((uint)newSize > (uint)int.MaxValue) // uint cast handles overflow + if ((uint) newSize > int.MaxValue) // uint cast handles overflow throw new InvalidOperationException("Capacity Overflow"); var entries = new Entry[newSize]; diff --git a/Jint/Collections/HybridDictionary.cs b/Jint/Collections/HybridDictionary.cs index b11bf0229b..10c67b0586 100644 --- a/Jint/Collections/HybridDictionary.cs +++ b/Jint/Collections/HybridDictionary.cs @@ -5,7 +5,7 @@ namespace Jint.Collections; -internal class HybridDictionary : IEnumerable> +internal sealed class HybridDictionary : IEngineDictionary, IEnumerable> { private const int CutoverPoint = 9; private const int InitialDictionarySize = 13; @@ -28,40 +28,34 @@ public HybridDictionary(int initialSize, bool checkExistingKeys) } } - protected HybridDictionary(StringDictionarySlim dictionary) + public HybridDictionary(StringDictionarySlim dictionary) { _checkExistingKeys = true; _dictionary = dictionary; } - public TValue this[Key key] + public ref TValue this[Key key] { get - { - TryGetValue(key, out var value); - return value; - } - set { if (_dictionary != null) { - _dictionary[key] = value; + return ref _dictionary[key]; } - else if (_list != null) + + if (_list != null) { if (_list.Count >= CutoverPoint - 1) { - SwitchToDictionary(key, value, tryAdd: false); + return ref SwitchToDictionary(key); } - else - { - _list[key] = value; - } - } - else - { - _list = new ListDictionary(key, value, _checkExistingKeys); + + return ref _list[key]; } + + var head = new ListDictionary.DictionaryNode { Key = key, Value = default }; + _list = new ListDictionary(head, _checkExistingKeys); + return ref head.Value; } } @@ -81,43 +75,83 @@ public bool TryGetValue(Key key, out TValue value) return false; } - public void SetOrUpdateValue(Key key, Func updater, TState state) + public ref TValue GetValueRefOrNullRef(Key key) { if (_dictionary != null) { - _dictionary.SetOrUpdateValue(key, updater, state); + return ref _dictionary.GetValueRefOrNullRef(key); } - else if (_list != null) - { - _list.SetOrUpdateValue(key, updater, state); - } - else + + if (_list != null) { - _list = new ListDictionary(key, updater(default, state), _checkExistingKeys); + return ref _list.GetValueRefOrNullRef(key); } + + return ref Unsafe.NullRef(); } - private bool SwitchToDictionary(Key key, TValue value, bool tryAdd) + public ref TValue GetValueRefOrAddDefault(Key key, out bool exists) { - var dictionary = new StringDictionarySlim(InitialDictionarySize); - foreach (var pair in _list) + if (_dictionary != null) + { + return ref _dictionary.GetValueRefOrAddDefault(key, out exists); + } + + if (_list != null) { - dictionary[pair.Key] = pair.Value; + return ref _list.GetValueRefOrAddDefault(key, out exists); } - bool result; + var head = new ListDictionary.DictionaryNode + { + Key = key, + }; + + _list = new ListDictionary(head, _checkExistingKeys); + exists = false; + return ref head.Value; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetOrUpdateValue(Key key, Func updater, TState state) + { + ref var currentValue = ref GetValueRefOrAddDefault(key, out _); + currentValue = updater(currentValue, state); + } + + private bool SwitchToDictionary(Key key, TValue value, bool tryAdd, int capacity = InitialDictionarySize) + { + SwitchToDictionary(capacity); + if (tryAdd) { - result = dictionary.TryAdd(key, value); + return _dictionary.TryAdd(key, value); } - else + + _dictionary[key] = value; + return true; + } + + private ref TValue SwitchToDictionary(Key key, int capacity = InitialDictionarySize) + { + SwitchToDictionary(capacity); + return ref _dictionary[key]; + } + + private void SwitchToDictionary(int capacity = InitialDictionarySize) + { + var dictionary = new StringDictionarySlim(capacity); + + if (_list is not null) { - dictionary[key] = value; - result = true; + foreach (var pair in _list) + { + dictionary[pair.Key] = pair.Value; + } } + _dictionary = dictionary; _list = null; - return result; } public int Count @@ -126,32 +160,46 @@ public int Count get => _dictionary?.Count ?? _list?.Count ?? 0; } + public void EnsureCapacity(int capacity) + { + if (_dictionary is not null) + { + // not implemented yet + return; + } + + if (capacity >= CutoverPoint) + { + SwitchToDictionary(capacity); + } + } + + public bool TryAdd(Key key, TValue value) { if (_dictionary != null) { return _dictionary.TryAdd(key, value); } - else - { - _list ??= new ListDictionary(key, value, _checkExistingKeys); - if (_list.Count + 1 >= CutoverPoint) - { - return SwitchToDictionary(key, value, tryAdd: true); - } - else - { - return _list.Add(key, value, tryAdd: true); - } + _list ??= new ListDictionary(key, value, _checkExistingKeys); + + if (_list.Count + 1 >= CutoverPoint) + { + return SwitchToDictionary(key, value, tryAdd: true); } + + return _list.Add(key, value, tryAdd: true); } - public void Add(Key key, TValue value) + /// + /// Adds a new item and expects key to not exist. + /// + public void AddDangerous(Key key, TValue value) { if (_dictionary != null) { - _dictionary.GetOrAddValueRef(key) = value; + _dictionary.AddDangerous(key, value); } else { @@ -163,11 +211,11 @@ public void Add(Key key, TValue value) { if (_list.Count + 1 >= CutoverPoint) { - SwitchToDictionary(key, value, tryAdd: false); + SwitchToDictionary(key) = value; } else { - _list.Add(key, value); + _list.AddDangerous(key, value); } } } @@ -181,17 +229,8 @@ public void Clear() public bool ContainsKey(Key key) { - if (_dictionary != null) - { - return _dictionary.ContainsKey(key); - } - - if (_list != null) - { - return _list.ContainsKey(key); - } - - return false; + ref var valueRefOrNullRef = ref GetValueRefOrNullRef(key); + return !Unsafe.IsNullRef(ref valueRefOrNullRef); } IEnumerator> IEnumerable>.GetEnumerator() @@ -207,7 +246,6 @@ IEnumerator> IEnumerable>.Ge } return System.Linq.Enumerable.Empty>().GetEnumerator(); - } IEnumerator IEnumerable.GetEnumerator() diff --git a/Jint/Collections/IEngineDictionary.cs b/Jint/Collections/IEngineDictionary.cs new file mode 100644 index 0000000000..0982718126 --- /dev/null +++ b/Jint/Collections/IEngineDictionary.cs @@ -0,0 +1,18 @@ + + +// ReSharper disable once CheckNamespace +namespace Jint; + +/// +/// Contract for custom dictionaries that Jint uses. +/// +internal interface IEngineDictionary +{ + int Count { get; } + + ref TValue this[TKey name] { get; } + + public ref TValue GetValueRefOrNullRef(TKey key); + + public ref TValue GetValueRefOrAddDefault(TKey key, out bool exists); +} diff --git a/Jint/Collections/ListDictionary.cs b/Jint/Collections/ListDictionary.cs index 9ee4556369..3b457f72a0 100644 --- a/Jint/Collections/ListDictionary.cs +++ b/Jint/Collections/ListDictionary.cs @@ -6,7 +6,7 @@ namespace Jint.Collections; -internal sealed class ListDictionary : IEnumerable> +internal sealed class ListDictionary : DictionaryBase, IEnumerable> { private DictionaryNode _head; private int _count; @@ -23,77 +23,49 @@ public ListDictionary(Key key, TValue value, bool checkExistingKeys) _count = 1; } - public TValue this[Key key] + public ListDictionary(DictionaryNode head, bool checkExistingKeys) { - get - { - TryGetValue(key, out var value); - return value; - } - set - { - DictionaryNode last = null; - DictionaryNode node; - var checkExistingKeys = _checkExistingKeys; - for (node = _head; node != null; node = node.Next) - { - var oldKey = node.Key; - if (checkExistingKeys && oldKey == key) - { - break; - } - - last = node; - } - - if (node != null) - { - // Found it - node.Value = value; - return; - } - - AddNode(key, value, last); - } + _checkExistingKeys = checkExistingKeys; + _head = head; + _count = 1; } - public bool TryGetValue(Key key, out TValue value) + public override ref TValue GetValueRefOrNullRef(Key key) { - var node = _head; - while (node != null) + DictionaryNode node; + for (node = _head; node != null; node = node.Next) { if (node.Key == key) { - value = node.Value; - return true; + return ref node.Value; } - - node = node.Next; } - value = default; - return false; + return ref Unsafe.NullRef(); } - public void SetOrUpdateValue(Key key, Func updater, TState state) + public override ref TValue GetValueRefOrAddDefault(Key key, out bool exists) { DictionaryNode last = null; DictionaryNode node; + var checkExistingKeys = _checkExistingKeys; for (node = _head; node != null; node = node.Next) { - if (node.Key == key) + var oldKey = node.Key; + if (checkExistingKeys && oldKey == key) { - node.Value = updater(node.Value, state); - return; + exists = true; + return ref node.Value; } last = node; } - AddNode(key, updater(default, state), last); + exists = false; + return ref AddNode(key, default, last).Value; } - public int Count + public override int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count; @@ -113,7 +85,7 @@ public bool Add(Key key, TValue value, bool tryAdd = false) { return false; } - ExceptionHelper.ThrowArgumentException(); + Throw.ArgumentException(); } last = node; @@ -123,7 +95,24 @@ public bool Add(Key key, TValue value, bool tryAdd = false) return true; } - private void AddNode(Key key, TValue value, DictionaryNode last) + /// + /// Adds a new item and expects key to not exist. + /// + public void AddDangerous(Key key, TValue value) + { + DictionaryNode node; + for (node = _head; node != null; node = node.Next) + { + if (node.Next is null) + { + AddNode(key, value, node); + return; + } + } + } + + + private DictionaryNode AddNode(Key key, TValue value, DictionaryNode last) { var newNode = new DictionaryNode { @@ -139,6 +128,7 @@ private void AddNode(Key key, TValue value, DictionaryNode last) last.Next = newNode; } _count++; + return newNode; } public void Clear() @@ -147,20 +137,6 @@ public void Clear() _head = null; } - public bool ContainsKey(Key key) - { - for (var node = _head; node != null; node = node.Next) - { - var oldKey = node.Key; - if (oldKey == key) - { - return true; - } - } - - return false; - } - internal bool CheckExistingKeys { set => _checkExistingKeys = value; diff --git a/Jint/Collections/ObjectTraverseStack.cs b/Jint/Collections/ObjectTraverseStack.cs index 41cedbdd0c..1c5258af8b 100644 --- a/Jint/Collections/ObjectTraverseStack.cs +++ b/Jint/Collections/ObjectTraverseStack.cs @@ -16,19 +16,29 @@ public ObjectTraverseStack(Engine engine) _engine = engine; } - public void Enter(JsValue value) + public bool TryEnter(JsValue value) { if (value is null) { - ExceptionHelper.ThrowArgumentNullException(nameof(value)); + Throw.ArgumentNullException(nameof(value)); } if (_stack.Contains(value)) { - ExceptionHelper.ThrowTypeError(_engine.Realm, "Cyclic reference detected."); + return false; } _stack.Push(value); + + return true; + } + + public void Enter(JsValue value) + { + if (!TryEnter(value)) + { + Throw.TypeError(_engine.Realm, "Cyclic reference detected."); + } } public void Exit() diff --git a/Jint/Collections/PropertyDictionary.cs b/Jint/Collections/PropertyDictionary.cs deleted file mode 100644 index 40277a5ba8..0000000000 --- a/Jint/Collections/PropertyDictionary.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Jint.Runtime.Descriptors; - -namespace Jint.Collections; - -internal sealed class PropertyDictionary : HybridDictionary -{ - public PropertyDictionary() - { - } - - public PropertyDictionary(int capacity, bool checkExistingKeys) : base(capacity, checkExistingKeys) - { - } - - public PropertyDictionary(StringDictionarySlim properties) : base(properties) - { - } -} diff --git a/Jint/Collections/RefStack.cs b/Jint/Collections/RefStack.cs index de5e905e98..94b9ab3ee3 100644 --- a/Jint/Collections/RefStack.cs +++ b/Jint/Collections/RefStack.cs @@ -49,16 +49,47 @@ public bool TryPeek([NotNullWhen(true)] out T item) return false; } + /// + /// Returns the last added item from the stack and decreases the stack size by one. + /// + /// Is thrown if the stack is empty. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref readonly T Pop() + public T Pop() { if (_size == 0) { - ExceptionHelper.ThrowInvalidOperationException("stack is empty"); + Throw.InvalidOperationException("stack is empty"); } _size--; - return ref _array[_size]; + // Create a copy of the value because it will get overridden + // in the statement. + var result = _array[_size]; + // Ensure that the item can get garbage collected if possible. + _array[_size] = default; + return result; + } + + /// + /// Same as but without copying the value. + /// + /// + /// Use this method when the return value is not used and + /// is a value type. In that case, this methods + /// avoids one value type copy compared to . + /// + /// Is thrown if the stack is empty. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void PopAndDiscard() + { + if (_size == 0) + { + Throw.InvalidOperationException("stack is empty"); + } + + _size--; + // Ensure that the item can get garbage collected if possible. + _array[_size] = default; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -106,14 +137,25 @@ private void Resize(int value) } else { - _array = Array.Empty(); + _array = []; } } } public void Clear() { - _size = 0; + if (_size > 0) + { +#if NETFRAMEWORK || NETSTANDARD2_0 + for (var i = 0; i < _size; i++) + { + _array[i] = default; + } +#else + Array.Fill(_array, default, 0, _size); +#endif + _size = 0; + } } public Enumerator GetEnumerator() diff --git a/Jint/Collections/StringDictionarySlim.cs b/Jint/Collections/StringDictionarySlim.cs index cc8ea11833..6a43054a28 100644 --- a/Jint/Collections/StringDictionarySlim.cs +++ b/Jint/Collections/StringDictionarySlim.cs @@ -22,7 +22,7 @@ namespace Jint.Collections; /// [DebuggerTypeProxy(typeof(DictionarySlimDebugView<>))] [DebuggerDisplay("Count = {Count}")] -internal sealed class StringDictionarySlim : IReadOnlyCollection> +internal sealed class StringDictionarySlim : DictionaryBase, IReadOnlyCollection> { // We want to initialize without allocating arrays. We also want to avoid null checks. // Array.Empty would give divide by zero in modulo operation. So we use static one element arrays. @@ -62,7 +62,7 @@ public StringDictionarySlim(int capacity) _entries = new Entry[capacity]; } - public int Count + public override int Count { [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _count; @@ -79,34 +79,11 @@ public void Clear() _entries = InitialEntries; } - public bool ContainsKey(Key key) - { - Entry[] entries = _entries; - for (int i = _buckets[key.HashCode & (_buckets.Length-1)] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) - { - if (key.Name == entries[i].key.Name) - return true; - } - - return false; - } - - public bool TryGetValue(Key key, out TValue value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void SetOrUpdateValue(Key key, Func updater, TState state) { - Entry[] entries = _entries; - for (int i = _buckets[key.HashCode & (_buckets.Length - 1)] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) - { - if (key.Name == entries[i].key.Name) - { - value = entries[i].value; - return true; - } - } - - value = default; - return false; + ref var currentValue = ref GetValueRefOrAddDefault(key, out _); + currentValue = updater(currentValue, state); } public bool Remove(Key key) @@ -145,12 +122,23 @@ public bool Remove(Key key) return false; } - public void SetOrUpdateValue(Key key, Func updater, TState state) + public override ref TValue GetValueRefOrNullRef(Key key) { - ref var currentValue = ref GetOrAddValueRef(key); - currentValue = updater(currentValue, state); + Entry[] entries = _entries; + int bucketIndex = key.HashCode & (_buckets.Length - 1); + for (int i = _buckets[bucketIndex] - 1; + (uint) i < (uint) entries.Length; i = entries[i].next) + { + if (key.Name == entries[i].key.Name) + { + return ref entries[i].value; + } + } + + return ref Unsafe.NullRef(); } + // Not safe for concurrent _reads_ (at least, if either of them add) // For concurrent reads, prefer TryGetValue(key, out value) /// @@ -159,18 +147,23 @@ public void SetOrUpdateValue(Key key, Func updat /// add or update a value in a single look up operation. /// /// Key to look for + /// Whether the value existed /// Reference to the new or existing value - public ref TValue GetOrAddValueRef(Key key) + public override ref TValue GetValueRefOrAddDefault(Key key, out bool exists) { Entry[] entries = _entries; int bucketIndex = key.HashCode & (_buckets.Length - 1); for (int i = _buckets[bucketIndex] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) + (uint) i < (uint) entries.Length; i = entries[i].next) { if (key.Name == entries[i].key.Name) + { + exists = true; return ref entries[i].value; + } } + exists = false; return ref AddKey(key, bucketIndex); } @@ -179,7 +172,7 @@ public bool TryAdd(Key key, TValue value) Entry[] entries = _entries; int bucketIndex = key.HashCode & (_buckets.Length - 1); for (int i = _buckets[bucketIndex] - 1; - (uint)i < (uint)entries.Length; i = entries[i].next) + (uint) i < (uint) entries.Length; i = entries[i].next) { if (key.Name == entries[i].key.Name) { @@ -192,7 +185,7 @@ public bool TryAdd(Key key, TValue value) } /// - /// Adds a new item and expects key to not to exist. + /// Adds a new item and expects key to not exist. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] public void AddDangerous(in Key key, TValue value) @@ -200,12 +193,6 @@ public void AddDangerous(in Key key, TValue value) AddKey(key, key.HashCode & (_buckets.Length - 1)) = value; } - public ref TValue this[Key key] - { - [MethodImpl(MethodImplOptions.AggressiveInlining)] - get => ref GetOrAddValueRef(key); - } - [MethodImpl(MethodImplOptions.NoInlining)] private ref TValue AddKey(Key key, int bucketIndex) { @@ -239,7 +226,7 @@ private Entry[] Resize() Debug.Assert(_entries.Length == _count || _entries.Length == 1); // We only copy _count, so if it's longer we will miss some int count = _count; int newSize = _entries.Length * 2; - if ((uint)newSize > (uint)int.MaxValue) // uint cast handles overflow + if ((uint) newSize > int.MaxValue) // uint cast handles overflow throw new InvalidOperationException("Capacity Overflow"); var entries = new Entry[newSize]; diff --git a/Jint/Collections/SymbolDictionary.cs b/Jint/Collections/SymbolDictionary.cs deleted file mode 100644 index 1627ec7e83..0000000000 --- a/Jint/Collections/SymbolDictionary.cs +++ /dev/null @@ -1,15 +0,0 @@ -using Jint.Native; -using Jint.Runtime.Descriptors; - -namespace Jint.Collections; - -internal sealed class SymbolDictionary : DictionarySlim -{ - public SymbolDictionary() - { - } - - public SymbolDictionary(int capacity) : base(capacity) - { - } -} diff --git a/Jint/Constraints/CancellationConstraint.cs b/Jint/Constraints/CancellationConstraint.cs index 8417828482..dbe17ef55e 100644 --- a/Jint/Constraints/CancellationConstraint.cs +++ b/Jint/Constraints/CancellationConstraint.cs @@ -16,7 +16,7 @@ public override void Check() { if (_cancellationToken.IsCancellationRequested) { - ExceptionHelper.ThrowExecutionCanceledException(); + Throw.ExecutionCanceledException(); } } diff --git a/Jint/Constraints/ConstraintsOptionsExtensions.cs b/Jint/Constraints/ConstraintsOptionsExtensions.cs index 88064ebe10..7a77d2424e 100644 --- a/Jint/Constraints/ConstraintsOptionsExtensions.cs +++ b/Jint/Constraints/ConstraintsOptionsExtensions.cs @@ -27,7 +27,7 @@ public static Options LimitMemory(this Options options, long memoryLimit) { options.WithoutConstraint(x => x is MemoryLimitConstraint); - if (memoryLimit > 0 && memoryLimit < int.MaxValue) + if (memoryLimit > 0 && memoryLimit < long.MaxValue) { options.Constraint(new MemoryLimitConstraint(memoryLimit)); } diff --git a/Jint/Constraints/MaxStatementsConstraint.cs b/Jint/Constraints/MaxStatementsConstraint.cs index b8e57518d9..1f533bb366 100644 --- a/Jint/Constraints/MaxStatementsConstraint.cs +++ b/Jint/Constraints/MaxStatementsConstraint.cs @@ -18,9 +18,9 @@ internal MaxStatementsConstraint(int maxStatements) public override void Check() { - if (MaxStatements > 0 && _statementsCount++ > MaxStatements) + if (MaxStatements > 0 && ++_statementsCount > MaxStatements) { - ExceptionHelper.ThrowStatementsCountOverflowException(); + Throw.StatementsCountOverflowException(); } } diff --git a/Jint/Constraints/MemoryLimitConstraint.cs b/Jint/Constraints/MemoryLimitConstraint.cs index 21bc8e00f1..c513c21d61 100644 --- a/Jint/Constraints/MemoryLimitConstraint.cs +++ b/Jint/Constraints/MemoryLimitConstraint.cs @@ -4,19 +4,22 @@ namespace Jint.Constraints; public sealed class MemoryLimitConstraint : Constraint { - private static readonly Func? GetAllocatedBytesForCurrentThread; private readonly long _memoryLimit; private long _initialMemoryUsage; +#if !NET8_0_OR_GREATER + private static readonly Func? _getAllocatedBytesForCurrentThread; + static MemoryLimitConstraint() { var methodInfo = typeof(GC).GetMethod("GetAllocatedBytesForCurrentThread"); if (methodInfo != null) { - GetAllocatedBytesForCurrentThread = (Func)Delegate.CreateDelegate(typeof(Func), null, methodInfo); + _getAllocatedBytesForCurrentThread = (Func) Delegate.CreateDelegate(typeof(Func), null, methodInfo); } } +#endif internal MemoryLimitConstraint(long memoryLimit) { @@ -25,28 +28,33 @@ internal MemoryLimitConstraint(long memoryLimit) public override void Check() { - if (_memoryLimit > 0) + if (_memoryLimit <= 0) { - if (GetAllocatedBytesForCurrentThread != null) - { - var memoryUsage = GetAllocatedBytesForCurrentThread() - _initialMemoryUsage; - if (memoryUsage > _memoryLimit) - { - ExceptionHelper.ThrowMemoryLimitExceededException($"Script has allocated {memoryUsage} but is limited to {_memoryLimit}"); - } - } - else - { - ExceptionHelper.ThrowPlatformNotSupportedException("The current platform doesn't support MemoryLimit."); - } + return; + } + +#if NET8_0_OR_GREATER + var usage = GC.GetAllocatedBytesForCurrentThread(); +#else + if (_getAllocatedBytesForCurrentThread == null) + { + Throw.PlatformNotSupportedException("The current platform doesn't support MemoryLimit."); + } + + var usage = _getAllocatedBytesForCurrentThread(); +#endif + if (usage - _initialMemoryUsage > _memoryLimit) + { + Throw.MemoryLimitExceededException($"Script has allocated {usage - _initialMemoryUsage} but is limited to {_memoryLimit}"); } } public override void Reset() { - if (GetAllocatedBytesForCurrentThread != null) - { - _initialMemoryUsage = GetAllocatedBytesForCurrentThread(); - } +#if NET8_0_OR_GREATER + _initialMemoryUsage = GC.GetAllocatedBytesForCurrentThread(); +#else + _initialMemoryUsage = _getAllocatedBytesForCurrentThread?.Invoke() ?? 0; +#endif } } diff --git a/Jint/Constraints/TimeConstraint.cs b/Jint/Constraints/TimeConstraint.cs index b7f295c671..4e9e3e3e52 100644 --- a/Jint/Constraints/TimeConstraint.cs +++ b/Jint/Constraints/TimeConstraint.cs @@ -19,7 +19,7 @@ public override void Check() { if (_cts?.IsCancellationRequested == true) { - ExceptionHelper.ThrowTimeoutException(); + Throw.TimeoutException(); } } diff --git a/Jint/Engine.Advanced.cs b/Jint/Engine.Advanced.cs index 61b8672a0d..2bd8aace37 100644 --- a/Jint/Engine.Advanced.cs +++ b/Jint/Engine.Advanced.cs @@ -28,7 +28,7 @@ public string StackTrace return string.Empty; } - return _engine.CallStack.BuildCallStackString(lastSyntaxElement.Location); + return _engine.CallStack.BuildCallStackString(_engine, lastSyntaxElement.Location); } } diff --git a/Jint/Engine.Ast.cs b/Jint/Engine.Ast.cs index 0c6d7a703d..31548c1bb6 100644 --- a/Jint/Engine.Ast.cs +++ b/Jint/Engine.Ast.cs @@ -1,4 +1,3 @@ -using System.Runtime.InteropServices; using Jint.Native; using Jint.Runtime; using Jint.Runtime.Interpreter; @@ -100,8 +99,7 @@ public void NodeVisitor(Node node, OnNodeContext _) case NodeType.ArrowFunctionExpression: case NodeType.FunctionDeclaration: case NodeType.FunctionExpression: - var function = (IFunction) node; - node.UserData = JintFunctionDefinition.BuildState(function); + node.UserData = JintFunctionDefinition.BuildState((IFunction) node); break; case NodeType.Program: @@ -151,6 +149,10 @@ public void NodeVisitor(Node node, OnNodeContext _) } } break; + + case NodeType.BlockStatement: + node.UserData = JintBlockStatement.BuildState((BlockStatement) node); + break; } } } @@ -162,11 +164,10 @@ public CachedHoistingScope(Program program) { Scope = HoistingScope.GetProgramLevelDeclarations(program); - VarNames = new List(); + VarNames = []; GatherVarNames(Scope, VarNames); - LexNames = new List(); - GatherLexNames(Scope, LexNames); + LexNames = DeclarationCacheBuilder.Build(Scope._lexicalDeclarations); } internal static void GatherVarNames(HoistingScope scope, List boundNames) @@ -182,31 +183,9 @@ internal static void GatherVarNames(HoistingScope scope, List boundNames) } } - internal static void GatherLexNames(HoistingScope scope, List boundNames) - { - var lexDeclarations = scope._lexicalDeclarations; - if (lexDeclarations != null) - { - var temp = new List(); - for (var i = 0; i < lexDeclarations.Count; i++) - { - var d = lexDeclarations[i]; - temp.Clear(); - d.GetBoundNames(temp); - for (var j = 0; j < temp.Count; j++) - { - boundNames.Add(new CachedLexicalName(temp[j], d.IsConstantDeclaration())); - } - } - } - } - - [StructLayout(LayoutKind.Auto)] - internal readonly record struct CachedLexicalName(Key Name, bool Constant); - public HoistingScope Scope { get; } public List VarNames { get; } - public List LexNames { get; } + public DeclarationCache LexNames { get; } } internal static class AstPreparationExtensions @@ -225,26 +204,25 @@ internal static List GetVarNames(this Program program, HoistingScope hoisti } else { - boundNames = new List(); + boundNames = []; CachedHoistingScope.GatherVarNames(hoistingScope, boundNames); } return boundNames; } - internal static List GetLexNames(this Program program, HoistingScope hoistingScope) + internal static List GetLexNames(this Program program, HoistingScope hoistingScope) { - List boundNames; + DeclarationCache cache; if (program.UserData is CachedHoistingScope cached) { - boundNames = cached.LexNames; + cache = cached.LexNames; } else { - boundNames = new List(); - CachedHoistingScope.GatherLexNames(hoistingScope, boundNames); + cache = DeclarationCacheBuilder.Build(hoistingScope._lexicalDeclarations); } - return boundNames; + return cache.Declarations; } } diff --git a/Jint/Engine.Defaults.cs b/Jint/Engine.Defaults.cs index f6f637dbde..95a942dfbe 100644 --- a/Jint/Engine.Defaults.cs +++ b/Jint/Engine.Defaults.cs @@ -7,7 +7,10 @@ public partial class Engine internal static readonly ParserOptions BaseParserOptions = ParserOptions.Default with { EcmaVersion = EcmaVersion.ES2023, - ExperimentalESFeatures = ExperimentalESFeatures.ImportAttributes | ExperimentalESFeatures.RegExpDuplicateNamedCapturingGroups, + ExperimentalESFeatures = ExperimentalESFeatures.ImportAttributes + | ExperimentalESFeatures.RegExpDuplicateNamedCapturingGroups + | ExperimentalESFeatures.ExplicitResourceManagement + | ExperimentalESFeatures.Decorators, Tolerant = false, }; } diff --git a/Jint/Engine.Modules.cs b/Jint/Engine.Modules.cs index c1a7b313d4..2ddfca36d6 100644 --- a/Jint/Engine.Modules.cs +++ b/Jint/Engine.Modules.cs @@ -63,7 +63,8 @@ internal Module Load(string? referencingModuleLocation, ModuleRequest request) private BuilderModule LoadFromBuilder(string specifier, ModuleBuilder moduleBuilder, ResolvedSpecifier moduleResolution) { var parsedModule = moduleBuilder.Parse(); - var module = new BuilderModule(_engine, _engine.Realm, parsedModule, location: parsedModule.Program!.Location.SourceFile, async: false); + var hasTopLevelAwait = HoistingScope.HasTopLevelAwait(parsedModule.Program!); + var module = new BuilderModule(_engine, _engine.Realm, parsedModule, location: parsedModule.Program!.Location.SourceFile, async: hasTopLevelAwait); _modules[moduleResolution.Key] = module; moduleBuilder.BindExportedValues(module); _builders.Remove(specifier); @@ -131,7 +132,7 @@ internal ObjectInstance Import(ModuleRequest request, string? referencingModuleL if (cyclicModule.Status != ModuleStatus.Evaluated) { - ExceptionHelper.ThrowNotSupportedException($"Error while evaluating module: Module is in an invalid state: '{cyclicModule.Status}'"); + Throw.NotSupportedException($"Error while evaluating module: Module is in an invalid state: '{cyclicModule.Status}'"); } } @@ -148,7 +149,7 @@ private static void LinkModule(string specifier, Module module) private JsValue EvaluateModule(string specifier, Module module) { var ownsContext = _engine._activeEvaluationContext is null; - _engine. _activeEvaluationContext ??= new EvaluationContext(_engine); + _engine._activeEvaluationContext ??= new EvaluationContext(_engine); JsValue evaluationResult; try { @@ -165,20 +166,59 @@ private JsValue EvaluateModule(string specifier, Module module) // This should instead be returned and resolved in ImportModule(specifier) only so Host.ImportModuleDynamically can use this promise if (evaluationResult is not JsPromise promise) { - ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}"); + Throw.InvalidOperationException($"Error while evaluating module: Module evaluation did not return a promise: {evaluationResult.Type}"); + return null; } - else if (promise.State == PromiseState.Rejected) + + // For async modules (TLA), we need to run the event loop to process pending jobs + // which will resolve the module's promise. With complex module graphs and dynamic + // imports, promise handlers may be registered asynchronously, so we need to allow + // multiple iterations even when the queue appears empty. + var emptyQueueIterations = 0; + const int maxEmptyQueueIterations = 10; + + while (promise.State == PromiseState.Pending) + { + _engine.RunAvailableContinuations(); + + // Check if promise settled after processing continuations + if (promise.State != PromiseState.Pending) + { + break; + } + + // If no more jobs to process, this could mean: + // 1. True deadlock - promise will never resolve (error condition) + // 2. Complex module graph where async dependencies are chained and need more time + // We allow several iterations with empty queue before assuming deadlock. + if (_engine._eventLoop.IsEmpty) + { + emptyQueueIterations++; + if (emptyQueueIterations >= maxEmptyQueueIterations) + { + // Queue has been empty for multiple iterations - likely a real issue + break; + } + } + else + { + // Queue has events, reset the counter + emptyQueueIterations = 0; + } + } + + if (promise.State == PromiseState.Rejected) { var location = module is CyclicModule cyclicModuleRecord ? cyclicModuleRecord.AbnormalCompletionLocation : SourceLocation.From(new Position(), new Position()); var node = AstExtensions.CreateLocationNode(location); - ExceptionHelper.ThrowJavaScriptException(_engine, promise.Value, node.Location); + Throw.JavaScriptException(_engine, promise.Value, node.Location); } else if (promise.State != PromiseState.Fulfilled) { - ExceptionHelper.ThrowInvalidOperationException($"Error while evaluating module: Module evaluation did not return a fulfilled promise: {promise.State}"); + Throw.InvalidOperationException($"Error while evaluating module: Module evaluation did not return a fulfilled promise: {promise.State}"); } return evaluationResult; diff --git a/Jint/Engine.cs b/Jint/Engine.cs index 40bf799dd6..ed51887c51 100644 --- a/Jint/Engine.cs +++ b/Jint/Engine.cs @@ -1,7 +1,7 @@ using System.Diagnostics; -using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; +using Jint.Collections; using Jint.Native; using Jint.Native.Function; using Jint.Native.Generator; @@ -39,6 +39,7 @@ public sealed partial class Engine : IDisposable internal ErrorDispatchInfo? _error; private readonly EventLoop _eventLoop = new(); + internal EventLoop EventLoop => _eventLoop; private readonly Agent _agent = new(); @@ -51,7 +52,7 @@ public sealed partial class Engine : IDisposable internal readonly bool _isDebugMode; internal readonly bool _isStrict; - private bool _customResolver; + private readonly bool _customResolver; internal readonly IReferenceResolver _referenceResolver; internal readonly ReferencePool _referencePool; @@ -403,7 +404,7 @@ public Engine Execute(in Prepared