From baa553e3a6b5ebc3d34baeebb1728ebd9766086a Mon Sep 17 00:00:00 2001 From: Ivan Krasilnikov Date: Sun, 15 Mar 2026 13:08:59 +0800 Subject: [PATCH 1/3] Expose $262 object in Jint.Repl So that it can be used to run test262 test suite via an external tester like eshost + test262-harness. Extracted out Test262Object.cs from Jint.Tests.Test262/Test262Test.cs and moved it together with Test262AgentManager.cs to Jint/ so that they could be used in both Jint.Repl and Jint.Tests.Test262. Both classes are internal and rely on some engine internals as well. Made visible to Jint.Repl via Jint/AssemblyInfoExtras.cs, similarly to how it was already done for Jint.Tests.Test262. --- Jint.Repl/Jint.Repl.csproj | 2 + Jint.Repl/Program.cs | 5 ++ Jint.Tests.Test262/Test262Test.cs | 45 +------------ Jint/AssemblyInfoExtras.cs | 1 + .../Test262AgentManager.cs | 15 +++-- Jint/Test262Object.cs | 67 +++++++++++++++++++ 6 files changed, 84 insertions(+), 51 deletions(-) rename {Jint.Tests.Test262 => Jint}/Test262AgentManager.cs (96%) create mode 100644 Jint/Test262Object.cs diff --git a/Jint.Repl/Jint.Repl.csproj b/Jint.Repl/Jint.Repl.csproj index 180e3d2e3b..adba7ee527 100644 --- a/Jint.Repl/Jint.Repl.csproj +++ b/Jint.Repl/Jint.Repl.csproj @@ -3,6 +3,8 @@ net10.0 Exe false + ..\Jint\Jint.snk + true true diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index c53c5c4d98..e73538e5ed 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -70,6 +70,11 @@ path => engine.Evaluate(File.ReadAllText(path))) ); +var test262 = Test262Object.Install(engine); + +var agentManager = new Test262AgentManager(); +agentManager.InstallAgent(engine, test262); + // Execute file if provided via -f if (!string.IsNullOrEmpty(inputFile)) { diff --git a/Jint.Tests.Test262/Test262Test.cs b/Jint.Tests.Test262/Test262Test.cs index 99425c04cf..c52bf25a85 100644 --- a/Jint.Tests.Test262/Test262Test.cs +++ b/Jint.Tests.Test262/Test262Test.cs @@ -60,54 +60,11 @@ private static Engine BuildTestExecutor(Test262File file, Test262AgentManager? a return JsValue.Undefined; })); - var o = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); - o.FastSetProperty("evalScript", new PropertyDescriptor(new ClrFunction(engine, "evalScript", - (_, args) => - { - if (args.Length > 1) - { - throw new Exception("only script parsing supported"); - } - - var script = Engine.PrepareScript(args.At(0).AsString(), options: new ScriptPreparationOptions - { - ParsingOptions = ScriptParsingOptions.Default with { Tolerant = false }, - }); - - return engine.Evaluate(script); - }), true, true, true)); - - o.FastSetProperty("createRealm", new PropertyDescriptor(new ClrFunction(engine, "createRealm", - (_, args) => - { - var realm = engine._host.CreateRealm(); - realm.GlobalObject.Set("global", realm.GlobalObject); - return realm.GlobalObject; - }), true, true, true)); - - o.FastSetProperty("detachArrayBuffer", new PropertyDescriptor(new ClrFunction(engine, "detachArrayBuffer", - (_, args) => - { - var buffer = (JsArrayBuffer) args.At(0); - buffer.DetachArrayBuffer(); - return JsValue.Undefined; - }), true, true, true)); - - o.FastSetProperty("gc", new PropertyDescriptor(new ClrFunction(engine, "gc", - (_, _) => - { - GC.Collect(); - GC.WaitForPendingFinalizers(); - return JsValue.Undefined; - }), true, true, true)); - - o.FastSetProperty("IsHTMLDDA", new PropertyDescriptor(new IsHTMLDDA(engine), true, true, true)); + var o = Test262Object.Install(engine); // Install agent support if needed agentManager?.InstallAgent(engine, o); - engine.SetValue("$262", o); - foreach (var include in file.Includes) { engine.Execute(State.Sources[include]); diff --git a/Jint/AssemblyInfoExtras.cs b/Jint/AssemblyInfoExtras.cs index c32a9295b8..47c26d423e 100644 --- a/Jint/AssemblyInfoExtras.cs +++ b/Jint/AssemblyInfoExtras.cs @@ -1,5 +1,6 @@ [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Jint.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Jint.Tests.Test262, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")] [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Jint.Benchmark, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")] +[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Jint.Repl, PublicKey=0024000004800000940000000602000000240000525341310004000001000100bf2553c9f214cb21f1f64ed62cadad8fe4f2fa11322a5dfa1d650743145c6085aba05b145b29867af656e0bb9bfd32f5d0deb1668263a38233e7e8e5bad1a3c6edd3f2ec6c512668b4aa797283101444628650949641b4f7cb16707efba542bb754afe87ce956f3a5d43f450d14364eb9571cbf213d1061852fb9dd47a6c05c4")] [module: System.Runtime.CompilerServices.SkipLocalsInit] diff --git a/Jint.Tests.Test262/Test262AgentManager.cs b/Jint/Test262AgentManager.cs similarity index 96% rename from Jint.Tests.Test262/Test262AgentManager.cs rename to Jint/Test262AgentManager.cs index a0c0783a0f..8fc08905da 100644 --- a/Jint.Tests.Test262/Test262AgentManager.cs +++ b/Jint/Test262AgentManager.cs @@ -2,13 +2,14 @@ using System.Collections.Concurrent; using System.Diagnostics; +using System.Threading; using Jint.Native; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; using Jint.Runtime.Interop; -namespace Jint.Tests.Test262; +namespace Jint; /// /// Implements the $262.agent API for Test262 multi-agent tests. @@ -21,7 +22,7 @@ internal sealed class Test262AgentManager : IDisposable private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private byte[]? _broadcastBufferData; private int _broadcastBufferLength; - private readonly object _broadcastLock = new(); + private readonly Lock _broadcastLock = new(); private readonly ManualResetEventSlim _broadcastEvent = new(false); private int _broadcastVersion; @@ -67,7 +68,7 @@ public void InstallAgent(Engine engine, ObjectInstance container) agent.FastSetProperty("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", (_, args) => { - var ms = (int)TypeConverter.ToNumber(args.At(0)); + var ms = (int) TypeConverter.ToNumber(args.At(0)); Thread.Sleep(ms); return JsValue.Undefined; }), true, true, true)); @@ -120,7 +121,7 @@ public void Dispose() List workers; lock (_workers) { - workers = [.._workers]; + workers = [.. _workers]; } foreach (var worker in workers) @@ -194,7 +195,7 @@ private void Run() agentObj.FastSetProperty("sleep", new PropertyDescriptor(new ClrFunction(engine, "sleep", (_, args) => { - var ms = (int)TypeConverter.ToNumber(args.At(0)); + var ms = (int) TypeConverter.ToNumber(args.At(0)); Thread.Sleep(ms); return JsValue.Undefined; }), true, true, true)); @@ -274,8 +275,8 @@ private static JsSharedArrayBuffer CreateSharedArrayBuffer(Engine engine, byte[] // 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) + var prototype = (ObjectInstance) constructor.Get(CommonProperties.Prototype); + var sab = new JsSharedArrayBuffer(engine, data, null, (uint) byteLength) { _prototype = prototype }; diff --git a/Jint/Test262Object.cs b/Jint/Test262Object.cs new file mode 100644 index 0000000000..756ba731c7 --- /dev/null +++ b/Jint/Test262Object.cs @@ -0,0 +1,67 @@ +using Jint.Native; +using Jint.Native.Object; +using Jint.Runtime; +using Jint.Runtime.Descriptors; +using Jint.Runtime.Interop; + +namespace Jint; + +/// +/// Provides the standard $262 test harness object for ECMAScript Test262 conformance testing. +/// See https://github.com/tc39/test262/blob/main/INTERPRETING.md +/// +internal static class Test262Object +{ + /// + /// Installs the $262 object into the engine's global scope and returns it. + /// + public static ObjectInstance Install(Engine engine) + { + var o = engine.Realm.Intrinsics.Object.Construct(Arguments.Empty); + + o.FastSetProperty("evalScript", new PropertyDescriptor(new ClrFunction(engine, "evalScript", + (_, args) => + { + if (args.Length > 1) + { + throw new ArgumentException("only script parsing supported", nameof(args)); + } + + var script = Engine.PrepareScript(args.At(0).AsString(), options: new ScriptPreparationOptions + { + ParsingOptions = ScriptParsingOptions.Default with { Tolerant = false }, + }); + + return engine.Evaluate(script); + }), true, true, true)); + + o.FastSetProperty("createRealm", new PropertyDescriptor(new ClrFunction(engine, "createRealm", + (_, _) => + { + var realm = engine._host.CreateRealm(); + realm.GlobalObject.Set("global", realm.GlobalObject); + return realm.GlobalObject; + }), true, true, true)); + + o.FastSetProperty("detachArrayBuffer", new PropertyDescriptor(new ClrFunction(engine, "detachArrayBuffer", + (_, args) => + { + var buffer = (JsArrayBuffer) args.At(0); + buffer.DetachArrayBuffer(); + return JsValue.Undefined; + }), true, true, true)); + + o.FastSetProperty("gc", new PropertyDescriptor(new ClrFunction(engine, "gc", + (_, _) => + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + return JsValue.Undefined; + }), true, true, true)); + + o.FastSetProperty("IsHTMLDDA", new PropertyDescriptor(new IsHTMLDDA(engine), true, true, true)); + + engine.SetValue("$262", o); + return o; + } +} From 29a495a9f47020ff1ce38117d1213634026a6751 Mon Sep 17 00:00:00 2001 From: Ivan Krasilnikov Date: Sun, 15 Mar 2026 13:09:08 +0800 Subject: [PATCH 2/3] Print exception type in Jint.Repl on errors Needed for running test262 via an external tester to properly support negative test cases - they prescribe checking exception type. Testing: $ echo x | ./Jint.Repl Uncaught exception: ReferenceError: x is not defined at stdin:1:1 $ echo 'function Test262Error(){}; throw new Test262Error();' | ./dist/Jint.Repl Uncaught exception: Test262Error at stdin:1:29 --- Jint.Repl/Program.cs | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index e73538e5ed..723c214cfa 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -96,7 +96,7 @@ } catch (JavaScriptException je) { - Console.Error.WriteLine($"Error: {je.Message}"); + Console.Error.WriteLine(FormatJavaScriptException(je)); Console.Error.WriteLine(je.JavaScriptStackTrace); return 1; } @@ -127,7 +127,7 @@ } catch (JavaScriptException je) { - Console.Error.WriteLine($"Error: {je.Message}"); + Console.Error.WriteLine(FormatJavaScriptException(je)); Console.Error.WriteLine(je.JavaScriptStackTrace); return 1; } @@ -190,7 +190,8 @@ catch (JavaScriptException je) { Console.ForegroundColor = ConsoleColor.Red; - Console.WriteLine(je.ToString()); + Console.WriteLine(FormatJavaScriptException(je)); + Console.Error.WriteLine(je.JavaScriptStackTrace); } catch (TimeoutException) { @@ -204,6 +205,31 @@ } } +static string FormatJavaScriptException(JavaScriptException je) +{ + if (!je.Error.IsObject()) + { + return $"Uncaught exception: {je.Error}"; + } + + var obj = je.Error.AsObject(); + var name = obj.Get(new JsString("name"), je.Error); + if (name.IsUndefined()) + { + var ctor = obj.Get(new JsString("constructor"), je.Error); + if (ctor.IsObject()) + { + name = ctor.AsObject().Get(new JsString("name"), ctor); + } + } + + var errorName = name.IsUndefined() ? "Error" : name.ToString(); + var message = obj.Get(new JsString("message"), je.Error); + return message.IsUndefined() || message.ToString().Length == 0 + ? $"Uncaught exception: {errorName}" + : $"Uncaught exception: {errorName}: {message}"; +} + static void PrintHelp() { Console.WriteLine("Jint REPL - JavaScript interpreter"); From 85947e5f5487b435085e827a19329ec10e8bfc61 Mon Sep 17 00:00:00 2001 From: Ivan Krasilnikov Date: Sun, 15 Mar 2026 13:21:28 +0800 Subject: [PATCH 3/3] Add -m/--module flag to Jint.Repl Needed for running test262's module tests via an external tester. Automatically set it if file has .mjs extension (d8/node convention). Testing: $ echo 'import { f } from "./b.js"; console.log(f("hel", "lo"));' >a.js $ echo 'export const f = (a, b) => a + b;' >b.js $ ./Jint.Repl -m a.js hello $ ./Jint.Repl a.js Uncaught exception: SyntaxError: Cannot use import statement outside a module (a.js:1:1) --- Jint.Repl/Program.cs | 62 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 55 insertions(+), 7 deletions(-) diff --git a/Jint.Repl/Program.cs b/Jint.Repl/Program.cs index 723c214cfa..d933aee877 100644 --- a/Jint.Repl/Program.cs +++ b/Jint.Repl/Program.cs @@ -5,6 +5,7 @@ using Jint.Native; using Jint.Native.Json; using Jint.Runtime; +using Jint.Runtime.Modules; // ReSharper disable LocalizableElement @@ -14,6 +15,7 @@ // Parse command line arguments string? inputFile = null; int? timeoutSeconds = null; +bool runAsModule = false; for (int i = 0; i < args.Length; i++) { @@ -23,6 +25,10 @@ if (i + 1 < args.Length) { inputFile = args[++i]; + if (inputFile.EndsWith(".mjs", StringComparison.OrdinalIgnoreCase)) + { + runAsModule = true; + } } else { @@ -41,6 +47,9 @@ return 1; } break; + case "-m" or "--module": + runAsModule = true; + break; case "-h" or "--help": PrintHelp(); return 0; @@ -49,6 +58,10 @@ if (!args[i].StartsWith("-") && inputFile == null) { inputFile = args[i]; + if (inputFile.EndsWith(".mjs", StringComparison.OrdinalIgnoreCase)) + { + runAsModule = true; + } } break; } @@ -61,6 +74,13 @@ { cfg.TimeoutInterval(TimeSpan.FromSeconds(timeoutSeconds.Value)); } + if (runAsModule) + { + var basePath = inputFile != null + ? Path.GetDirectoryName(Path.GetFullPath(inputFile))! + : Directory.GetCurrentDirectory(); + cfg.EnableModules(basePath, restrictToBasePath: false); + } }); engine @@ -86,11 +106,19 @@ try { - var script = File.ReadAllText(inputFile); - var result = engine.Evaluate(script, inputFile); - if (!result.IsUndefined()) + if (runAsModule) { - Console.WriteLine(result); + var absolutePath = Path.GetFullPath(inputFile); + engine.Modules.Import(new Uri(absolutePath).AbsoluteUri); + } + else + { + var script = File.ReadAllText(inputFile); + var result = engine.Evaluate(script, inputFile); + if (!result.IsUndefined()) + { + Console.WriteLine(result); + } } return 0; } @@ -100,6 +128,11 @@ Console.Error.WriteLine(je.JavaScriptStackTrace); return 1; } + catch (ModuleResolutionException mre) + { + Console.Error.WriteLine($"Error: {mre.Message}"); + return 1; + } catch (TimeoutException) { Console.Error.WriteLine("Error: Script execution timed out"); @@ -118,10 +151,18 @@ try { var script = Console.In.ReadToEnd(); - var result = engine.Evaluate(script, "stdin"); - if (!result.IsUndefined()) + if (runAsModule) + { + engine.Modules.Add("", script); + engine.Modules.Import(""); + } + else { - Console.WriteLine(result); + var result = engine.Evaluate(script, "stdin"); + if (!result.IsUndefined()) + { + Console.WriteLine(result); + } } return 0; } @@ -131,6 +172,11 @@ Console.Error.WriteLine(je.JavaScriptStackTrace); return 1; } + catch (ModuleResolutionException mre) + { + Console.Error.WriteLine($"Error: {mre.Message}"); + return 1; + } catch (TimeoutException) { Console.Error.WriteLine("Error: Script execution timed out"); @@ -238,12 +284,14 @@ static void PrintHelp() Console.WriteLine(); Console.WriteLine("Options:"); Console.WriteLine(" -f, --file Execute JavaScript file"); + Console.WriteLine(" -m, --module Run script as ES6 module"); 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 -m module.js Execute module.js as ES6 module"); 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");