From b40abf87e6b855d9b0b7b4ba5ae8ab77e88c3a2b Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 01:14:24 +0100 Subject: [PATCH 01/21] add test demonstrating runtime exit on failing to marshal task result due to conversion err --- .../InteropServices/JavaScript/JSExportTest.cs | 9 +++++++++ .../JavaScript/JavaScriptTestHelper.cs | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs index f6e7e99abd310b..dad1ac50c2e654 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs @@ -403,6 +403,15 @@ public async Task JsExportTaskOfLong(long value) Assert.Equal(value, rr); } + [Fact] + public async Task JsExportTaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() + { + // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws + Task res = JavaScriptTestHelper.invoke1_TaskOfOutOfRangeShort(Task.FromResult(1 << 16), nameof(JavaScriptTestHelper.AwaitTaskOfShort)); + JSException ex = await Assert.ThrowsAsync(async () => await res); + Assert.Contains("Overflow: value 65536 is out of -32768 32767 range", ex.Message); + } + [Theory] [MemberData(nameof(MarshalBigInt64Cases))] public async Task JsExportCompletedTaskOfLong(long value) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index c064839d530677..cfc79620e59fea 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -444,6 +444,9 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfLong([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task invoke1_TaskOfOutOfRangeShort([JSMarshalAs>] Task value, [JSMarshalAs] string name); [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] internal static partial Task ReturnResolvedPromise(); @@ -472,6 +475,14 @@ public static async Task AwaitTaskOfInt64([JSMarshalAs>] + public static async Task AwaitTaskOfShort([JSMarshalAs>] Task arg1) + { + var res = await arg1; + return res; + } + #endregion #region Action + Func From c5358b7ca434df92a37090121aee4fe496ed5c33 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 01:15:15 +0100 Subject: [PATCH 02/21] fix by marshalling error to dotnet task --- src/mono/browser/runtime/managed-exports.ts | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/mono/browser/runtime/managed-exports.ts b/src/mono/browser/runtime/managed-exports.ts index 159b250d58f1e0..e1d2dc06cb5027 100644 --- a/src/mono/browser/runtime/managed-exports.ts +++ b/src/mono/browser/runtime/managed-exports.ts @@ -151,13 +151,18 @@ export function complete_task (holder_gc_handle: GCHandle, error?: any, data?: a set_arg_type(arg1, MarshalerType.Object); set_gc_handle(arg1, holder_gc_handle); const arg2 = get_arg(args, 3); - if (error) { - marshal_exception_to_cs(arg2, error); - } else { + if (!error) { set_arg_type(arg2, MarshalerType.None); const arg3 = get_arg(args, 4); mono_assert(res_converter, "res_converter missing"); - res_converter(arg3, data); + try { + res_converter(arg3, data); + } catch (ex) { + error = ex; + } + } + if (error) { + marshal_exception_to_cs(arg2, error); } invoke_async_jsexport(runtimeHelpers.ioThreadTID, managedExports.CompleteTask, args, size); } finally { From 5004c25f31431c7c1bd17d807930091041355e3d Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 02:57:29 +0100 Subject: [PATCH 03/21] add jsimport test --- .../Runtime/InteropServices/JavaScript/JSImportTest.cs | 8 ++++++++ .../InteropServices/JavaScript/JavaScriptTestHelper.cs | 3 +++ .../InteropServices/JavaScript/JavaScriptTestHelper.mjs | 6 +++++- 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index b862a970c4aa6b..609871f2f969f8 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -170,6 +170,14 @@ public unsafe void OptimizedPaths() Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached); } + [Fact] + public async Task TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() + { + // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws + Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithNumberMaxValue_AsShortToBeOutOfRange(); + JSException ex = await Assert.ThrowsAsync(async () => await res); + Assert.Contains("Overflow: value 65536 is out of -32768 32767 range", ex.Message); + } #region Get/Set Property diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index cfc79620e59fea..e02a0f87eae126 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -447,6 +447,9 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfOutOfRangeShort([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("returnResolvedPromiseWithNumberMaxValue", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task ReturnResolvedPromiseWithNumberMaxValue_AsShortToBeOutOfRange(); [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] internal static partial Task ReturnResolvedPromise(); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index f4f2772fdda8b5..6ab68d30dfcd9f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -250,6 +250,10 @@ export function returnResolvedPromise() { return Promise.resolve(); } +export function returnResolvedPromiseWithNumberMaxValue() { + return Promise.resolve(Number.MAX_VALUE); +} + export async function invokeReturnCompletedTask() { await dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper.ReturnCompletedTask(); return "resolved"; @@ -490,4 +494,4 @@ export function isSetTimeoutHit() { export function isPromiseThenHit() { return promiseThenHit; -} \ No newline at end of file +} From ab6efdd5ce085bb09d38d1377925fd3c395f50c6 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 03:16:14 +0100 Subject: [PATCH 04/21] test another range limited type --- .../InteropServices/JavaScript/JSExportTest.cs | 2 +- .../InteropServices/JavaScript/JSImportTest.cs | 17 +++++++++++++---- .../JavaScript/JavaScriptTestHelper.cs | 7 +++++-- .../JavaScript/JavaScriptTestHelper.mjs | 4 ++-- 4 files changed, 21 insertions(+), 9 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs index dad1ac50c2e654..6a09de020f5911 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs @@ -409,7 +409,7 @@ public async Task JsExportTaskOfShortOutOfRange_ThrowsAssertionInTaskContinuatio // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws Task res = JavaScriptTestHelper.invoke1_TaskOfOutOfRangeShort(Task.FromResult(1 << 16), nameof(JavaScriptTestHelper.AwaitTaskOfShort)); JSException ex = await Assert.ThrowsAsync(async () => await res); - Assert.Contains("Overflow: value 65536 is out of -32768 32767 range", ex.Message); + Assert.Equal("Error: Assert failed: Overflow: value 65536 is out of -32768 32767 range", ex.Message); } [Theory] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 609871f2f969f8..1b382e461294a5 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -173,10 +173,19 @@ public unsafe void OptimizedPaths() [Fact] public async Task TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() { - // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws - Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithNumberMaxValue_AsShortToBeOutOfRange(); - JSException ex = await Assert.ThrowsAsync(async () => await res); - Assert.Contains("Overflow: value 65536 is out of -32768 32767 range", ex.Message); + Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsShortToBeOutOfRange(); + JSException ex = await Assert.ThrowsAsync(() => res); + Console.WriteLine(ex.Message); + Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of -32768 32767 range", ex.Message); + } + + [Fact] + public async Task TaskOfByteOutOfRange_ThrowsAssertionInTaskContinuation() + { + Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsByteToBeOutOfRange(); + JSException ex = await Assert.ThrowsAsync(() => res); + Console.WriteLine(ex.Message); + Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of 0 255 range", ex.Message); } #region Get/Set Property diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index e02a0f87eae126..ccb1f58eed41bc 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -447,9 +447,12 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfOutOfRangeShort([JSMarshalAs>] Task value, [JSMarshalAs] string name); - [JSImport("returnResolvedPromiseWithNumberMaxValue", "JavaScriptTestHelper")] + [JSImport("returnResolvedPromiseWithIntMaxValue", "JavaScriptTestHelper")] [return: JSMarshalAs>] - internal static partial Task ReturnResolvedPromiseWithNumberMaxValue_AsShortToBeOutOfRange(); + internal static partial Task ReturnResolvedPromiseWithIntMaxValue_AsShortToBeOutOfRange(); + [JSImport("returnResolvedPromiseWithIntMaxValue", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task ReturnResolvedPromiseWithIntMaxValue_AsByteToBeOutOfRange(); [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] internal static partial Task ReturnResolvedPromise(); diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index 6ab68d30dfcd9f..f186d0be768a74 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -250,8 +250,8 @@ export function returnResolvedPromise() { return Promise.resolve(); } -export function returnResolvedPromiseWithNumberMaxValue() { - return Promise.resolve(Number.MAX_VALUE); +export function returnResolvedPromiseWithIntMaxValue() { + return Promise.resolve(2147483647); } export async function invokeReturnCompletedTask() { From c2bfc285eb0013f83b482c83d9f6f4b159447192 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 03:24:16 +0100 Subject: [PATCH 05/21] demonstrate with a different assertion --- .../InteropServices/JavaScript/JSExportTest.cs | 11 ++++++++++- .../JavaScript/JavaScriptTestHelper.cs | 11 +++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs index 6a09de020f5911..2224b15406b24d 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs @@ -408,10 +408,19 @@ public async Task JsExportTaskOfShortOutOfRange_ThrowsAssertionInTaskContinuatio { // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws Task res = JavaScriptTestHelper.invoke1_TaskOfOutOfRangeShort(Task.FromResult(1 << 16), nameof(JavaScriptTestHelper.AwaitTaskOfShort)); - JSException ex = await Assert.ThrowsAsync(async () => await res); + JSException ex = await Assert.ThrowsAsync(() => res); Assert.Equal("Error: Assert failed: Overflow: value 65536 is out of -32768 32767 range", ex.Message); } + [Fact] + public async Task JsExportTaskOfStringTypeAssertion_ThrowsAssertionInTaskContinuation() + { + // long value cannot be converted to string, error thrown through continuation in CS + Task res = JavaScriptTestHelper.invoke1_TaskOfLong_ExceptionReturnTypeAssert(Task.FromResult(1L << 32), nameof(JavaScriptTestHelper.AwaitTaskOfString)); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Value is not a String", ex.Message); + } + [Theory] [MemberData(nameof(MarshalBigInt64Cases))] public async Task JsExportCompletedTaskOfLong(long value) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index ccb1f58eed41bc..f49737fca07439 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -445,6 +445,9 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfLong([JSMarshalAs>] Task value, [JSMarshalAs] string name); [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task invoke1_TaskOfLong_ExceptionReturnTypeAssert([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfOutOfRangeShort([JSMarshalAs>] Task value, [JSMarshalAs] string name); [JSImport("returnResolvedPromiseWithIntMaxValue", "JavaScriptTestHelper")] @@ -489,6 +492,14 @@ public static async Task AwaitTaskOfShort([JSMarshalAs>] + public static async Task AwaitTaskOfString([JSMarshalAs>] Task arg1) + { + var res = await arg1; + return res; + } + #endregion #region Action + Func From 5aed0267c7c487841cca2cc44842978be1c3c131 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 10:57:27 +0100 Subject: [PATCH 06/21] remove leftover console writes --- .../System/Runtime/InteropServices/JavaScript/JSImportTest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 1b382e461294a5..0b1e4a43458b1c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -175,7 +175,6 @@ public async Task TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() { Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsShortToBeOutOfRange(); JSException ex = await Assert.ThrowsAsync(() => res); - Console.WriteLine(ex.Message); Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of -32768 32767 range", ex.Message); } @@ -184,7 +183,6 @@ public async Task TaskOfByteOutOfRange_ThrowsAssertionInTaskContinuation() { Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsByteToBeOutOfRange(); JSException ex = await Assert.ThrowsAsync(() => res); - Console.WriteLine(ex.Message); Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of 0 255 range", ex.Message); } From d049f55f32eb9dcd91e1d0b4f44438c7972f549a Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 17:58:17 +0100 Subject: [PATCH 07/21] colocate tests, add datetime overflow im+export tests that crash runtime --- .../JavaScript/JSExportTest.cs | 72 +++++++++++++------ .../JavaScript/JSImportTest.cs | 68 ++++++++++-------- .../JavaScript/JavaScriptTestHelper.cs | 41 +++++++++-- .../JavaScript/JavaScriptTestHelper.mjs | 20 ++++++ 4 files changed, 146 insertions(+), 55 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs index 2224b15406b24d..16e24640c17946 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs @@ -392,10 +392,10 @@ public async Task JsExportTaskOfInt(int value) [Theory] [MemberData(nameof(MarshalBigInt64Cases))] - public async Task JsExportTaskOfLong(long value) + public async Task JsExportTaskOfBigLong(long value) { TaskCompletionSource tcs = new TaskCompletionSource(); - var res = JavaScriptTestHelper.invoke1_TaskOfLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64)); + var res = JavaScriptTestHelper.invoke1_TaskOfBigLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64)); tcs.SetResult(value); // unresolved task marshalls promise and resolves on completion await Task.Yield(); var rr = await res; @@ -403,31 +403,13 @@ public async Task JsExportTaskOfLong(long value) Assert.Equal(value, rr); } - [Fact] - public async Task JsExportTaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() - { - // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws - Task res = JavaScriptTestHelper.invoke1_TaskOfOutOfRangeShort(Task.FromResult(1 << 16), nameof(JavaScriptTestHelper.AwaitTaskOfShort)); - JSException ex = await Assert.ThrowsAsync(() => res); - Assert.Equal("Error: Assert failed: Overflow: value 65536 is out of -32768 32767 range", ex.Message); - } - - [Fact] - public async Task JsExportTaskOfStringTypeAssertion_ThrowsAssertionInTaskContinuation() - { - // long value cannot be converted to string, error thrown through continuation in CS - Task res = JavaScriptTestHelper.invoke1_TaskOfLong_ExceptionReturnTypeAssert(Task.FromResult(1L << 32), nameof(JavaScriptTestHelper.AwaitTaskOfString)); - JSException ex = await Assert.ThrowsAsync(() => res); - Assert.Equal("Error: Assert failed: Value is not a String", ex.Message); - } - [Theory] [MemberData(nameof(MarshalBigInt64Cases))] - public async Task JsExportCompletedTaskOfLong(long value) + public async Task JsExportCompletedTaskOfBigLong(long value) { TaskCompletionSource tcs = new TaskCompletionSource(); tcs.SetResult(value); // completed task marshalls value immediately - var res = JavaScriptTestHelper.invoke1_TaskOfLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64)); + var res = JavaScriptTestHelper.invoke1_TaskOfBigLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64)); await Task.Yield(); var rr = await res; await Task.Yield(); @@ -487,5 +469,51 @@ public async Task JSExportCompletedTaskReturnsResolvedPromise() string result = await JavaScriptTestHelper.InvokeReturnCompletedTask(); Assert.Equal("resolved", result); } + + #region Assertion Errors + [Fact] + public async Task JsExportTaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() + { + // 1<<16 is out of range, passed to js and back, marshalling ts code asserts out of range and throws + Task res = JavaScriptTestHelper.invoke1_TaskOfOutOfRangeShort(Task.FromResult(1 << 16), nameof(JavaScriptTestHelper.AwaitTaskOfShort)); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Overflow: value 65536 is out of -32768 32767 range", ex.Message); + } + + [Fact] + public async Task JsExportTaskOfStringTypeAssertion_ThrowsAssertionInTaskContinuation() + { + // long value cannot be converted to string, error thrown through continuation in CS + Task res = JavaScriptTestHelper.invoke1_TaskOfLong_ExceptionReturnTypeAssert(Task.FromResult(1L << 32), nameof(JavaScriptTestHelper.AwaitTaskOfString)); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Value is not a String", ex.Message); + } + + [Fact] + public async Task JsExportTaskOfLong_OverflowInt52() + { + long value = 1L << 53; + TaskCompletionSource tcs = new TaskCompletionSource(); + var res = JavaScriptTestHelper.invoke1_TaskOfLong(tcs.Task, nameof(JavaScriptTestHelper.AwaitTaskOfInt64)); + tcs.SetResult(value); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Value is not an integer: 9007199254740992 (bigint)", ex.Message); + } + + [Fact] + public async Task JsExportTaskOfDateTime_OverflowNETDateTime() + { + var res = JavaScriptTestHelper.invokeExportWithTaskOfMaxJSDateTime(nameof(JavaScriptTestHelper.AwaitTaskOfDateTime)); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Overflow: value 8640000000000000 is out of -62135596800000 253402300799999 range", ex.Message); + } + + [Fact] + public async Task JsExportDateTime_OverflowNETDateTime() + { + JSException ex = Assert.Throws(() => JavaScriptTestHelper.invokeExportWithMaxJSDateTime(nameof(JavaScriptTestHelper.EchoDateTime))); + Assert.Equal("Error: Assert failed: Overflow: value 8640000000000000 is out of -62135596800000 253402300799999 range", ex.Message); + } + #endregion } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 0b1e4a43458b1c..1284dbc34192fe 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -106,35 +106,6 @@ public unsafe void DotnetInstance() Assert.Equal("Yoda", JSHost.DotnetInstance.GetPropertyAsString("testString")); } - [Fact] - public unsafe void BadCast() - { - JSException ex; - JSHost.DotnetInstance.SetProperty("testBool", true); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsInt32("testBool")); - Assert.Contains("Value is not an integer", ex.Message); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsDouble("testBool")); - Assert.Contains("Value is not a Number", ex.Message); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsString("testBool")); - Assert.Contains("Value is not a String", ex.Message); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsJSObject("testBool")); - Assert.Contains("JSObject proxy of boolean is not supported", ex.Message); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsByteArray("testBool")); - Assert.Contains("Value is not an Array or Uint8Array", ex.Message); - JSHost.DotnetInstance.SetProperty("testInt", 42); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsBoolean("testInt")); - Assert.Contains("Value is not a Boolean", ex.Message); - } - - [Fact] - public unsafe void OutOfRange() - { - JSException ex; - JSHost.DotnetInstance.SetProperty("testDouble", 9007199254740991L); - ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsInt32("testDouble")); - Assert.Contains("Overflow: value 9007199254740991 is out of -2147483648 2147483647 range", ex.Message); - } - [Fact] public async Task RejectString() { @@ -170,6 +141,36 @@ public unsafe void OptimizedPaths() Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached); } + #region Assertion Errors + [Fact] + public unsafe void BadCast() + { + JSException ex; + JSHost.DotnetInstance.SetProperty("testBool", true); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsInt32("testBool")); + Assert.Contains("Value is not an integer", ex.Message); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsDouble("testBool")); + Assert.Contains("Value is not a Number", ex.Message); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsString("testBool")); + Assert.Contains("Value is not a String", ex.Message); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsJSObject("testBool")); + Assert.Contains("JSObject proxy of boolean is not supported", ex.Message); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsByteArray("testBool")); + Assert.Contains("Value is not an Array or Uint8Array", ex.Message); + JSHost.DotnetInstance.SetProperty("testInt", 42); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsBoolean("testInt")); + Assert.Contains("Value is not a Boolean", ex.Message); + } + + [Fact] + public unsafe void OutOfRange() + { + JSException ex; + JSHost.DotnetInstance.SetProperty("testDouble", 9007199254740991L); + ex = Assert.Throws(() => JSHost.DotnetInstance.GetPropertyAsInt32("testDouble")); + Assert.Contains("Overflow: value 9007199254740991 is out of -2147483648 2147483647 range", ex.Message); + } + [Fact] public async Task TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() { @@ -186,6 +187,15 @@ public async Task TaskOfByteOutOfRange_ThrowsAssertionInTaskContinuation() Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of 0 255 range", ex.Message); } + [Fact] + public async Task TaskOfDateTimeOutOfRange_ThrowsAssertionInTaskContinuation() + { + Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithDateMaxValue(); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Overflow: value 8640000000000000 is out of -62135596800000 253402300799999 range", ex.Message); + } + #endregion + #region Get/Set Property [Fact] diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index f49737fca07439..4d8f912b5cd3b6 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -432,35 +432,60 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [JSImport("await1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task await1([JSMarshalAs>] Task arg1); + [JSImport("await1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task await1_TaskOfException([JSMarshalAs>] Task arg1); + [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfObject([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfInt([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] + internal static partial Task invoke1_TaskOfBigLong([JSMarshalAs>] Task value, [JSMarshalAs] string name); + + [JSImport("invoke1", "JavaScriptTestHelper")] + [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfLong([JSMarshalAs>] Task value, [JSMarshalAs] string name); + + [JSImport("invokeExportWithPromiseWithDateMaxValue", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task invokeExportWithTaskOfMaxJSDateTime([JSMarshalAs] string name); + + [JSImport("invokeExportWithDateMaxValue", "JavaScriptTestHelper")] + [return: JSMarshalAs] + internal static partial DateTime invokeExportWithMaxJSDateTime([JSMarshalAs] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfLong_ExceptionReturnTypeAssert([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("invoke1", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfOutOfRangeShort([JSMarshalAs>] Task value, [JSMarshalAs] string name); + + [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] + internal static partial Task ReturnResolvedPromise(); + + [JSImport("invokeReturnCompletedTask", "JavaScriptTestHelper")] + internal static partial Task InvokeReturnCompletedTask(); + [JSImport("returnResolvedPromiseWithIntMaxValue", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task ReturnResolvedPromiseWithIntMaxValue_AsShortToBeOutOfRange(); + [JSImport("returnResolvedPromiseWithIntMaxValue", "JavaScriptTestHelper")] [return: JSMarshalAs>] internal static partial Task ReturnResolvedPromiseWithIntMaxValue_AsByteToBeOutOfRange(); - [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] - internal static partial Task ReturnResolvedPromise(); - [JSImport("invokeReturnCompletedTask", "JavaScriptTestHelper")] - internal static partial Task InvokeReturnCompletedTask(); + [JSImport("returnResolvedPromiseWithDateMaxValue", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task ReturnResolvedPromiseWithDateMaxValue(); [JSExport] internal static Task ReturnCompletedTask() @@ -500,6 +525,14 @@ public static async Task AwaitTaskOfString([JSMarshalAs>] + public static async Task AwaitTaskOfDateTime([JSMarshalAs>] Task arg1) + { + var res = await arg1; + return res; + } + #endregion #region Action + Func diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index f186d0be768a74..a30ada1ae65485 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -246,6 +246,18 @@ export function invoke2(arg1, name) { return res; } +export function invokeExportWithPromiseWithDateMaxValue(exportName) { + const fn1 = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper[exportName]; + const res = fn1(returnResolvedPromiseWithDateMaxValue()); + return res; +} + +export function invokeExportWithDateMaxValue(exportName) { + const fn1 = dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper[exportName]; + const res = fn1(returnDateMaxValue()); + return res; +} + export function returnResolvedPromise() { return Promise.resolve(); } @@ -254,6 +266,14 @@ export function returnResolvedPromiseWithIntMaxValue() { return Promise.resolve(2147483647); } +export function returnResolvedPromiseWithDateMaxValue() { + return Promise.resolve(new Date(8640000000000000)); +} + +export function returnDateMaxValue() { + return new Date(8640000000000000); +} + export async function invokeReturnCompletedTask() { await dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper.ReturnCompletedTask(); return "resolved"; From bc4ff5134d935ef2f8cbbb40ce3fc677ec40e867 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Fri, 23 Jan 2026 17:59:45 +0100 Subject: [PATCH 08/21] add range assert for date unitTime that .net accepts --- src/mono/browser/runtime/marshal.ts | 4 ++-- src/mono/browser/runtime/memory.ts | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index e369da6418e9cd..23579a2ae00930 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; -import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, forceThreadMemoryViewRefresh, fixupPointer, setB8, getB8 } from "./memory"; +import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, forceThreadMemoryViewRefresh, fixupPointer, setB8, getB8, setF64Date } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType, PThreadPtr, PThreadPtrNull, VoidPtrNull } from "./types/internal"; import { TypedArray, VoidPtr } from "./types/emscripten"; @@ -311,7 +311,7 @@ export function set_arg_date (arg: JSMarshalerArgument, value: Date): void { mono_assert(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); - setF64(arg, unixTime); + setF64Date(arg, unixTime); } export function set_arg_f64 (arg: JSMarshalerArgument, value: number): void { diff --git a/src/mono/browser/runtime/memory.ts b/src/mono/browser/runtime/memory.ts index 06d2bbdda0cd7a..39b26c0a9c2cf7 100644 --- a/src/mono/browser/runtime/memory.ts +++ b/src/mono/browser/runtime/memory.ts @@ -197,6 +197,11 @@ export function setF64 (offset: MemOffset, value: number): void { Module.HEAPF64[offset >>> 3] = value; } +export function setF64Date (offset: MemOffset, value: number): void { + assert_int_in_range(value, -0x3883122CD800, 0xE677D21FDBFF); + setF64(offset, value); +} + let warnDirtyBool = true; export function getB32 (offset: MemOffset): boolean { From 3e42d91cc0d60d81c337ef4f97622c71e1cf5c94 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sat, 24 Jan 2026 15:24:55 +0100 Subject: [PATCH 09/21] move range check to marshal.ts --- src/mono/browser/runtime/marshal.ts | 7 +++++-- src/mono/browser/runtime/memory.ts | 5 ----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index 23579a2ae00930..e94648ad48b7ee 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -5,7 +5,7 @@ import WasmEnableThreads from "consts:wasmEnableThreads"; import { js_owned_gc_handle_symbol, teardown_managed_proxy } from "./gc-handles"; import { Module, loaderHelpers, mono_assert, runtimeHelpers } from "./globals"; -import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, forceThreadMemoryViewRefresh, fixupPointer, setB8, getB8, setF64Date } from "./memory"; +import { getF32, getF64, getI16, getI32, getI64Big, getU16, getU32, getU8, setF32, setF64, setI16, setI32, setI64Big, setU16, setU32, setU8, localHeapViewF64, localHeapViewI32, localHeapViewU8, _zero_region, forceThreadMemoryViewRefresh, fixupPointer, setB8, getB8 } from "./memory"; import { mono_wasm_new_external_root } from "./roots"; import { GCHandle, JSHandle, MonoObject, MonoString, GCHandleNull, JSMarshalerArguments, JSFunctionSignature, JSMarshalerType, JSMarshalerArgument, MarshalerToJs, MarshalerToCs, WasmRoot, MarshalerType, PThreadPtr, PThreadPtrNull, VoidPtrNull } from "./types/internal"; import { TypedArray, VoidPtr } from "./types/emscripten"; @@ -307,11 +307,14 @@ export function set_arg_i64_big (arg: JSMarshalerArgument, value: bigint): void setI64Big(arg, value); } +const min_dateUnixTime = -0x3883122CD800; +const max_dateUnixTime = 0xE677D21FDBFF; export function set_arg_date (arg: JSMarshalerArgument, value: Date): void { mono_assert(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); - setF64Date(arg, unixTime); + mono_assert(unixTime >= min_dateUnixTime && unixTime <= max_dateUnixTime, `Overflow: value ${value} is out of ${min_dateUnixTime} ${max_dateUnixTime} range`); + setF64(arg, unixTime); } export function set_arg_f64 (arg: JSMarshalerArgument, value: number): void { diff --git a/src/mono/browser/runtime/memory.ts b/src/mono/browser/runtime/memory.ts index 39b26c0a9c2cf7..06d2bbdda0cd7a 100644 --- a/src/mono/browser/runtime/memory.ts +++ b/src/mono/browser/runtime/memory.ts @@ -197,11 +197,6 @@ export function setF64 (offset: MemOffset, value: number): void { Module.HEAPF64[offset >>> 3] = value; } -export function setF64Date (offset: MemOffset, value: number): void { - assert_int_in_range(value, -0x3883122CD800, 0xE677D21FDBFF); - setF64(offset, value); -} - let warnDirtyBool = true; export function getB32 (offset: MemOffset): boolean { From 51cb10af4c849d1387e18e64ef23446741091639 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sat, 24 Jan 2026 17:42:46 +0100 Subject: [PATCH 10/21] line up error message with mono impl --- src/native/corehost/browserhost/loader/logging.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/native/corehost/browserhost/loader/logging.ts b/src/native/corehost/browserhost/loader/logging.ts index 1f8530fa870fc1..65ee07bc4ea8de 100644 --- a/src/native/corehost/browserhost/loader/logging.ts +++ b/src/native/corehost/browserhost/loader/logging.ts @@ -5,7 +5,7 @@ import { loaderConfig } from "./config"; export function check(condition: unknown, message: string): asserts condition { if (!condition) { - throw new Error(`dotnetAssert failed: ${message}`); + throw new Error(`Assert failed: ${message}`); } } @@ -15,7 +15,7 @@ export function check(condition: unknown, message: string): asserts condition { export function fastCheck(condition: unknown, messageFactory: (() => string)): asserts condition { if (!condition) { const message = messageFactory(); - throw new Error(`dotnetAssert failed: ${message}`); + throw new Error(`Assert failed: ${message}`); } } From 4534795f90db13b7e4621f9213efb0dad485289b Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 18:35:48 +0100 Subject: [PATCH 11/21] marshal exception if task result conversion fails in native interop impl --- .../interop/managed-exports.ts | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts index bbabc7a024e907..653ab7ebfaaed0 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/managed-exports.ts @@ -101,13 +101,18 @@ export function completeTask(holderGcHandle: GCHandle, error?: any, data?: any, setArgType(arg1, MarshalerType.Object); setGcHandle(arg1, holderGcHandle); const arg2 = getArg(args, 3); + if (!error) { + try { + setArgType(arg2, MarshalerType.None); + const arg3 = getArg(args, 4); + dotnetAssert.check(resConverter, "resConverter missing"); + resConverter(arg3, data); + } catch (e) { + error = e; + } + } if (error) { marshalExceptionToCs(arg2, error); - } else { - setArgType(arg2, MarshalerType.None); - const arg3 = getArg(args, 4); - dotnetAssert.check(resConverter, "resConverter missing"); - resConverter(arg3, data); } dotnetInteropJSExports.SystemInteropJS_CompleteTask(args); } finally { From baf1281a465fea63861719102f58be3f8feb259b Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 18:37:07 +0100 Subject: [PATCH 12/21] assert supported date range in mono and native --- src/mono/browser/runtime/marshal.ts | 2 +- .../interop/marshal.ts | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index e94648ad48b7ee..89bfeb9d4c64d5 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -313,7 +313,7 @@ export function set_arg_date (arg: JSMarshalerArgument, value: Date): void { mono_assert(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); - mono_assert(unixTime >= min_dateUnixTime && unixTime <= max_dateUnixTime, `Overflow: value ${value} is out of ${min_dateUnixTime} ${max_dateUnixTime} range`); + mono_assert(unixTime >= min_dateUnixTime && unixTime <= max_dateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(min_dateUnixTime).toISOString()} ${new Date(max_dateUnixTime).toISOString()} range`); setF64(arg, unixTime); } diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts index 7d50e3aa090eab..a3d126f10c4b94 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts @@ -273,10 +273,13 @@ export function setArgI64Big(arg: JSMarshalerArgument, value: bigint): void { dotnetApi.setHeapI64Big(arg, value); } +const min_dateUnixTime = -0x3883122CD800; +const max_dateUnixTime = 0xE677D21FDBFF; export function setArgDate(arg: JSMarshalerArgument, value: Date): void { dotnetAssert.check(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); + dotnetAssert.check(unixTime >= min_dateUnixTime && unixTime <= max_dateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(min_dateUnixTime).toISOString()} ${new Date(max_dateUnixTime).toISOString()} range`); dotnetApi.setHeapF64(arg, unixTime); } From 593e417fdbb789c07dfaaaf48a54b70b20e4b964 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 18:37:24 +0100 Subject: [PATCH 13/21] update tests to reflect friendly messages --- .../System/Runtime/InteropServices/JavaScript/JSExportTest.cs | 4 ++-- .../System/Runtime/InteropServices/JavaScript/JSImportTest.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs index 16e24640c17946..d7ea25c0929181 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs @@ -505,14 +505,14 @@ public async Task JsExportTaskOfDateTime_OverflowNETDateTime() { var res = JavaScriptTestHelper.invokeExportWithTaskOfMaxJSDateTime(nameof(JavaScriptTestHelper.AwaitTaskOfDateTime)); JSException ex = await Assert.ThrowsAsync(() => res); - Assert.Equal("Error: Assert failed: Overflow: value 8640000000000000 is out of -62135596800000 253402300799999 range", ex.Message); + Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } [Fact] public async Task JsExportDateTime_OverflowNETDateTime() { JSException ex = Assert.Throws(() => JavaScriptTestHelper.invokeExportWithMaxJSDateTime(nameof(JavaScriptTestHelper.EchoDateTime))); - Assert.Equal("Error: Assert failed: Overflow: value 8640000000000000 is out of -62135596800000 253402300799999 range", ex.Message); + Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } #endregion } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 1284dbc34192fe..62ff50533ff11a 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -192,7 +192,7 @@ public async Task TaskOfDateTimeOutOfRange_ThrowsAssertionInTaskContinuation() { Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithDateMaxValue(); JSException ex = await Assert.ThrowsAsync(() => res); - Assert.Equal("Error: Assert failed: Overflow: value 8640000000000000 is out of -62135596800000 253402300799999 range", ex.Message); + Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } #endregion From 9c7afeaf2f3eaeae94b1e2ff344badcb483130ea Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 18:48:38 +0100 Subject: [PATCH 14/21] test correctness of date boundary value marshalling --- .../InteropServices/JavaScript/JSImportTest.cs | 15 +++++++++++++++ .../JavaScript/JavaScriptTestHelper.cs | 4 ++++ .../JavaScript/JavaScriptTestHelper.mjs | 4 ++++ 3 files changed, 23 insertions(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 62ff50533ff11a..d60bff684c15f7 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -194,6 +194,21 @@ public async Task TaskOfDateTimeOutOfRange_ThrowsAssertionInTaskContinuation() JSException ex = await Assert.ThrowsAsync(() => res); Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } + + [Fact] + public async Task TaskOfDateTimeRangeBoundaryConditions() + { + DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MaxValue, 0); + Assert.Equal(DateTime.MaxValue.AddTicks(-9_999), t); // JavaScript Date has millisecond precision only + t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, 0); + Assert.Equal(DateTime.MinValue, t); + + JSException ex = Assert.Throws(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MaxValue, 1)); + Assert.Equal("Error: Assert failed: Overflow: value +010000-01-01T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); + + ex = Assert.Throws(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, -1)); + Assert.Equal("Error: Assert failed: Overflow: value 0000-12-31T23:59:59.999Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); + } #endregion #region Get/Set Property diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index 4d8f912b5cd3b6..02ada76545f328 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -487,6 +487,10 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [return: JSMarshalAs>] internal static partial Task ReturnResolvedPromiseWithDateMaxValue(); + [JSImport("returnDateWithOffset", "JavaScriptTestHelper")] + [return: JSMarshalAs] + internal static partial DateTime ReturnDateTimeWithOffset([JSMarshalAs] DateTime date, int offset); + [JSExport] internal static Task ReturnCompletedTask() { diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index a30ada1ae65485..126861a180b969 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -274,6 +274,10 @@ export function returnDateMaxValue() { return new Date(8640000000000000); } +export function returnDateWithOffset(date, offset) { + return new Date(date.getTime() + offset); +} + export async function invokeReturnCompletedTask() { await dllExports.System.Runtime.InteropServices.JavaScript.Tests.JavaScriptTestHelper.ReturnCompletedTask(); return "resolved"; From 253aa3c8a870d492c5eade8f13c9c380025f74a9 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 19:22:10 +0100 Subject: [PATCH 15/21] min/max boundary tests --- .../InteropServices/JavaScript/JSImportTest.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index d60bff684c15f7..53caac0ba2e4c6 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -196,19 +196,23 @@ public async Task TaskOfDateTimeOutOfRange_ThrowsAssertionInTaskContinuation() } [Fact] - public async Task TaskOfDateTimeRangeBoundaryConditions() + public async Task DateTimeMaxValueBoundaryCondition() { DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MaxValue, 0); - Assert.Equal(DateTime.MaxValue.AddTicks(-9_999), t); // JavaScript Date has millisecond precision only - t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, 0); - Assert.Equal(DateTime.MinValue, t); - + Assert.Equal(DateTime.MaxValue.AddTicks(-9_999), t); // Microseconds are lost during marshalling JSException ex = Assert.Throws(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MaxValue, 1)); Assert.Equal("Error: Assert failed: Overflow: value +010000-01-01T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); + } - ex = Assert.Throws(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, -1)); + [Fact] + public async Task DateTimeMinValueBoundaryCondition() + { + DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, 0); + Assert.Equal(DateTime.MinValue, t); + JSException ex = Assert.Throws(() => JavaScriptTestHelper.ReturnDateTimeWithOffset(DateTime.MinValue, -1)); Assert.Equal("Error: Assert failed: Overflow: value 0000-12-31T23:59:59.999Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } + #endregion #region Get/Set Property From e34135820cbf06ea464daa715fc373c7439f8c89 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 19:24:07 +0100 Subject: [PATCH 16/21] add test for date precision loss --- .../Runtime/InteropServices/JavaScript/JSImportTest.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 53caac0ba2e4c6..40b1504a3f6099 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -762,6 +762,15 @@ public void JSImportDateTime(DateTime value) "object", "Date"); } + [Fact] // JavaScript Date has millisecond precision, the microseconds are lost during marshalling + public async Task DateTimeMarshallingLosesMicrosecondComponentPrecisionLoss() + { + DateTime now = new DateTime(1995, 4, 1, 10, 43, 6, 94, microsecond: 20); + DateTime t = JavaScriptTestHelper.ReturnDateTimeWithOffset(now, 0); + Assert.NotEqual(now, t); + DateTime nowWithJSPrecision = now.AddMicroseconds(-now.Microsecond); + Assert.Equal(nowWithJSPrecision, t); + } #endregion Datetime #region DateTimeOffset From b6ce6bc7d1ed763dbcaf627646f09658bbc24848 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 21:18:35 +0100 Subject: [PATCH 17/21] add datetime and delegate datetime/long overflow tests --- .../JavaScript/JSExportTest.cs | 57 ++++++++++++++++++- .../JavaScript/JavaScriptTestHelper.cs | 24 +++++++- .../JavaScript/JavaScriptTestHelper.mjs | 8 +++ 3 files changed, 85 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs index d7ea25c0929181..9d74128c107601 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs @@ -449,6 +449,18 @@ public void JsExportCallback_FunctionIntIntThrow() Assert.Same(expected, actual); } + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] + public async Task JsExportFunctionDateTimeDateTime() + { + DateTime input = DateTime.Now; + DateTime receivedArg = DateTime.MinValue; + DateTime returnVal = JavaScriptTestHelper.invokeDelegateOfDateTime((DateTime date) => { + return receivedArg = date; + }, input, 0); + Assert.Equal(input, receivedArg); + Assert.Equal(input, returnVal); + } + private void JsExportTest<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] T>(T value , Func invoke, string echoName, string jsType, string? jsClass = null) { @@ -490,7 +502,7 @@ public async Task JsExportTaskOfStringTypeAssertion_ThrowsAssertionInTaskContinu } [Fact] - public async Task JsExportTaskOfLong_OverflowInt52() + public async Task JsExportTaskOfLong_TaskReturnValue_OverflowInt52() { long value = 1L << 53; TaskCompletionSource tcs = new TaskCompletionSource(); @@ -501,7 +513,7 @@ public async Task JsExportTaskOfLong_OverflowInt52() } [Fact] - public async Task JsExportTaskOfDateTime_OverflowNETDateTime() + public async Task JsExportTaskOfDateTime_TaskReturnValue_OverflowNETDateTime() { var res = JavaScriptTestHelper.invokeExportWithTaskOfMaxJSDateTime(nameof(JavaScriptTestHelper.AwaitTaskOfDateTime)); JSException ex = await Assert.ThrowsAsync(() => res); @@ -509,11 +521,50 @@ public async Task JsExportTaskOfDateTime_OverflowNETDateTime() } [Fact] - public async Task JsExportDateTime_OverflowNETDateTime() + public async Task JsExportDateTime_ReturnValue_OverflowNETDateTime() { JSException ex = Assert.Throws(() => JavaScriptTestHelper.invokeExportWithMaxJSDateTime(nameof(JavaScriptTestHelper.EchoDateTime))); Assert.Equal("Error: Assert failed: Overflow: value +275760-09-13T00:00:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] + public async Task JsExportFuncOfDateTime_Argument_OverflowNETDateTime() + { + DateTime receivedArg = DateTime.MinValue; + JSException ex = Assert.Throws(() => JavaScriptTestHelper.invokeDelegateOfDateTime((DateTime date) => { + return receivedArg = date; + }, DateTime.MaxValue, 60_001)); + Assert.Equal("Error: Assert failed: Overflow: value +010000-01-01T00:01:00.000Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); + Assert.Equal(DateTime.MinValue, receivedArg); // delegate invoke failed, no change to arg + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] + public void JsExportCallback_FunctionLongLong_OverflowInt52_JSSide() + { + long called = -1; + Assert.Equal(-1, called); + JSException ex = Assert.Throws(() => JavaScriptTestHelper.invokeFuncOfLongLong((long a) => + { + return called = a; + }, 9007199254740991, offset: 1)); + Assert.Equal(-1, called); + Assert.Equal("Error: Assert failed: Value is not an integer: 9007199254740992 (number)", ex.Message); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] + public void JsExportCallback_FunctionLongLong_OverflowInt52_NETSide() + { + long called = -1; + var chain = JavaScriptTestHelper.invoke1_FuncOfLongLong((long a) => + { + return called = a; + }, nameof(JavaScriptTestHelper.BackFuncOfLongLong)); + + Assert.Equal(-1, called); + OverflowException ex = Assert.Throws(() => chain(long.MaxValue)); + Assert.Equal(-1, called); + Assert.Equal("Overflow: value 9223372036854775807 is out of -9007199254740991 9007199254740991 range.", ex.Message); + } #endregion } } diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index 02ada76545f328..f7df498126858f 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -469,6 +469,10 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [return: JSMarshalAs>] internal static partial Task invoke1_TaskOfOutOfRangeShort([JSMarshalAs>] Task value, [JSMarshalAs] string name); + [JSImport("invokeDelegate_DateTimeWithOffset", "JavaScriptTestHelper")] + [return: JSMarshalAs] + internal static partial DateTime invokeDelegateOfDateTime([JSMarshalAs>] Func datetransformer, [JSMarshalAs] DateTime date, int offsetMilliseconds); + [JSImport("returnResolvedPromise", "JavaScriptTestHelper")] internal static partial Task ReturnResolvedPromise(); @@ -489,7 +493,7 @@ public static Exception EchoException([JSMarshalAs] Exception arg1 [JSImport("returnDateWithOffset", "JavaScriptTestHelper")] [return: JSMarshalAs] - internal static partial DateTime ReturnDateTimeWithOffset([JSMarshalAs] DateTime date, int offset); + internal static partial DateTime ReturnDateTimeWithOffset([JSMarshalAs] DateTime date, int offsetMilliseconds); [JSExport] internal static Task ReturnCompletedTask() @@ -600,6 +604,24 @@ public static Func BackFuncOfIntInt([JSMarshalAs>] + internal static partial Func invoke1_FuncOfLongLong([JSMarshalAs>] Func value, [JSMarshalAs] string name); + + [JSExport] + [return: JSMarshalAs>] + public static Func BackFuncOfLongLong([JSMarshalAs>] Func arg1) + { + return (long a) => + { + return arg1(a); + }; + } + + [JSImport("invokeFuncWithOffset", "JavaScriptTestHelper")] + [return: JSMarshalAs] + internal static partial long invokeFuncOfLongLong([JSMarshalAs>] Func fn, [JSMarshalAs] long value, [JSMarshalAs] int offset); + #endregion #region Boolean diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index 126861a180b969..8c1db44f67a5f4 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -258,6 +258,10 @@ export function invokeExportWithDateMaxValue(exportName) { return res; } +export function invokeDelegate_DateTimeWithOffset(delegate, date, offset) { + return delegate(returnDateWithOffset(date, offset)); +} + export function returnResolvedPromise() { return Promise.resolve(); } @@ -283,6 +287,10 @@ export async function invokeReturnCompletedTask() { return "resolved"; } +export function invokeFuncWithOffset(fn, arg, offset) { + return fn(arg + offset); +} + export function invokeStructClassRecords(arg1) { return [ dllExports.JavaScriptTestHelperNamespace.JavaScriptTestHelper.EchoString(arg1), From 0c15303ce621b89fde6e6e0c8ed8c23645de8eaa Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Sun, 25 Jan 2026 21:54:57 +0100 Subject: [PATCH 18/21] naming convention --- src/mono/browser/runtime/marshal.ts | 6 +++--- .../interop/marshal.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index 89bfeb9d4c64d5..b01c36dac49036 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -307,13 +307,13 @@ export function set_arg_i64_big (arg: JSMarshalerArgument, value: bigint): void setI64Big(arg, value); } -const min_dateUnixTime = -0x3883122CD800; -const max_dateUnixTime = 0xE677D21FDBFF; +const minDateUnixTime = -0x3883122CD800; +const maxDateUnixTime = 0xE677D21FDBFF; export function set_arg_date (arg: JSMarshalerArgument, value: Date): void { mono_assert(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); - mono_assert(unixTime >= min_dateUnixTime && unixTime <= max_dateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(min_dateUnixTime).toISOString()} ${new Date(max_dateUnixTime).toISOString()} range`); + mono_assert(unixTime >= minDateUnixTime && unixTime <= maxDateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(minDateUnixTime).toISOString()} ${new Date(maxDateUnixTime).toISOString()} range`); setF64(arg, unixTime); } diff --git a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts index a3d126f10c4b94..8606d20fe4d055 100644 --- a/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts +++ b/src/native/libs/System.Runtime.InteropServices.JavaScript.Native/interop/marshal.ts @@ -273,13 +273,13 @@ export function setArgI64Big(arg: JSMarshalerArgument, value: bigint): void { dotnetApi.setHeapI64Big(arg, value); } -const min_dateUnixTime = -0x3883122CD800; -const max_dateUnixTime = 0xE677D21FDBFF; +const minDateUnixTime = -0x3883122CD800; +const maxDateUnixTime = 0xE677D21FDBFF; export function setArgDate(arg: JSMarshalerArgument, value: Date): void { dotnetAssert.check(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); - dotnetAssert.check(unixTime >= min_dateUnixTime && unixTime <= max_dateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(min_dateUnixTime).toISOString()} ${new Date(max_dateUnixTime).toISOString()} range`); + dotnetAssert.check(unixTime >= minDateUnixTime && unixTime <= maxDateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(minDateUnixTime).toISOString()} ${new Date(maxDateUnixTime).toISOString()} range`); dotnetApi.setHeapF64(arg, unixTime); } From 3078e9d5f56c7b52e53948ac5b7dc49bef1e1af9 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Wed, 28 Jan 2026 19:39:53 +0100 Subject: [PATCH 19/21] mono check instead of assert --- src/mono/browser/runtime/marshal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mono/browser/runtime/marshal.ts b/src/mono/browser/runtime/marshal.ts index b01c36dac49036..f3186d7cf2592a 100644 --- a/src/mono/browser/runtime/marshal.ts +++ b/src/mono/browser/runtime/marshal.ts @@ -313,7 +313,7 @@ export function set_arg_date (arg: JSMarshalerArgument, value: Date): void { mono_assert(arg, "Null arg"); // getTime() is always UTC const unixTime = value.getTime(); - mono_assert(unixTime >= minDateUnixTime && unixTime <= maxDateUnixTime, `Overflow: value ${value.toISOString()} is out of ${new Date(minDateUnixTime).toISOString()} ${new Date(maxDateUnixTime).toISOString()} range`); + mono_check(unixTime >= minDateUnixTime && unixTime <= maxDateUnixTime, () => `Overflow: value ${value.toISOString()} is out of ${new Date(minDateUnixTime).toISOString()} ${new Date(maxDateUnixTime).toISOString()} range`); setF64(arg, unixTime); } From 78ba06c09f2572322f1d8a9bfc2ed2b02c6d3971 Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Wed, 28 Jan 2026 22:05:07 +0100 Subject: [PATCH 20/21] demonstrate array elements value overflow for int32 --- .../InteropServices/JavaScript/JSImportTest.cs | 14 ++++++++++++++ .../JavaScript/JavaScriptTestHelper.cs | 4 ++++ .../JavaScript/JavaScriptTestHelper.mjs | 4 ++++ 3 files changed, 22 insertions(+) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index 40b1504a3f6099..bf5605fe4c6d6c 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -213,6 +213,20 @@ public async Task DateTimeMinValueBoundaryCondition() Assert.Equal("Error: Assert failed: Overflow: value 0000-12-31T23:59:59.999Z is out of 0001-01-01T00:00:00.000Z 9999-12-31T23:59:59.999Z range", ex.Message); } + [Fact] + public async Task Int32ArrayFromJSWithAnOutOfRangeValue() + { + int[] arr = JavaScriptTestHelper.getInt32ArrayWithOutOfRangeValues(); + Assert.Equal(0, arr[0]); + Assert.Equal(1, arr[1]); + Assert.Equal(-2147483648, arr[2]); + Assert.Equal(-1147483648, arr[3]); + Assert.Equal(-1, arr[4]); + Assert.Equal(0, arr[5]); + // Currently, values > int32.MaxValue are wrapped around when marshaled from JS to C#. + // TODO: Instead throw OverflowException when out of range value is encountered. + } + #endregion #region Get/Set Property diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs index f7df498126858f..8d0ca9efe49422 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs @@ -247,6 +247,10 @@ internal static partial void Relaxed(string a1, Exception ex, [return: JSMarshalAs] internal static partial JSObject? store_JSObjectArray([JSMarshalAs>] JSObject[]? value, [JSMarshalAs] int index); + [JSImport("getInt32ArrayWithOutOfRangeValues", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial int[] getInt32ArrayWithOutOfRangeValues(); + #endregion #region Views diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs index 8c1db44f67a5f4..1c91dbdb1dfe6b 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs @@ -262,6 +262,10 @@ export function invokeDelegate_DateTimeWithOffset(delegate, date, offset) { return delegate(returnDateWithOffset(date, offset)); } +export function getInt32ArrayWithOutOfRangeValues() { + return [0, 1, 2147483648, 3147483648, 9007199254740991, 9007199254740992]; +} + export function returnResolvedPromise() { return Promise.resolve(); } From bb43231b2559869429041d1da9b98bcd499db5bb Mon Sep 17 00:00:00 2001 From: Marc Zwart Date: Wed, 28 Jan 2026 22:10:30 +0100 Subject: [PATCH 21/21] test name --- .../System/Runtime/InteropServices/JavaScript/JSImportTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs index bf5605fe4c6d6c..254338ea4cee29 100644 --- a/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs +++ b/src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs @@ -214,7 +214,7 @@ public async Task DateTimeMinValueBoundaryCondition() } [Fact] - public async Task Int32ArrayFromJSWithAnOutOfRangeValue() + public async Task Int32ArrayWithOutOfRangeValues() { int[] arr = JavaScriptTestHelper.getInt32ArrayWithOutOfRangeValues(); Assert.Equal(0, arr[0]);