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..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; @@ -405,11 +405,11 @@ public async Task JsExportTaskOfLong(long value) [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(); @@ -469,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 b862a970c4aa6b..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,6 +106,42 @@ public unsafe void DotnetInstance() Assert.Equal("Yoda", JSHost.DotnetInstance.GetPropertyAsString("testString")); } + [Fact] + public async Task RejectString() + { + var ex = await Assert.ThrowsAsync(() => JavaScriptTestHelper.Reject("noodles")); + Assert.Contains("noodles", ex.Message); + } + + [Fact] + public async Task RejectException() + { + var expected = new Exception("noodles"); + var actual = await Assert.ThrowsAsync(() => JavaScriptTestHelper.Reject(expected)); + Assert.Equal(expected, actual); + } + + [Fact] + public async Task RejectNull() + { + var ex = await Assert.ThrowsAsync(() => JavaScriptTestHelper.Reject(null)); + } + + [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] + public unsafe void OptimizedPaths() + { + JavaScriptTestHelper.optimizedReached = 0; + JavaScriptTestHelper.invoke0V(); + Assert.Equal(1, JavaScriptTestHelper.optimizedReached); + JavaScriptTestHelper.invoke1V(42); + Assert.Equal(43, JavaScriptTestHelper.optimizedReached); + Assert.Equal(124, JavaScriptTestHelper.invoke1R(123)); + Assert.Equal(43 + 123, JavaScriptTestHelper.optimizedReached); + Assert.Equal(32, JavaScriptTestHelper.invoke2R(15, 16)); + Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached); + } + + #region Assertion Errors [Fact] public unsafe void BadCast() { @@ -136,40 +172,29 @@ public unsafe void OutOfRange() } [Fact] - public async Task RejectString() + public async Task TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation() { - var ex = await Assert.ThrowsAsync(() => JavaScriptTestHelper.Reject("noodles")); - Assert.Contains("noodles", ex.Message); + Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsShortToBeOutOfRange(); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of -32768 32767 range", ex.Message); } [Fact] - public async Task RejectException() + public async Task TaskOfByteOutOfRange_ThrowsAssertionInTaskContinuation() { - var expected = new Exception("noodles"); - var actual = await Assert.ThrowsAsync(() => JavaScriptTestHelper.Reject(expected)); - Assert.Equal(expected, actual); + Task res = JavaScriptTestHelper.ReturnResolvedPromiseWithIntMaxValue_AsByteToBeOutOfRange(); + JSException ex = await Assert.ThrowsAsync(() => res); + Assert.Equal("Error: Assert failed: Overflow: value 2147483647 is out of 0 255 range", ex.Message); } [Fact] - public async Task RejectNull() - { - var ex = await Assert.ThrowsAsync(() => JavaScriptTestHelper.Reject(null)); - } - - [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotWasmThreadingSupported))] - public unsafe void OptimizedPaths() + public async Task TaskOfDateTimeOutOfRange_ThrowsAssertionInTaskContinuation() { - JavaScriptTestHelper.optimizedReached = 0; - JavaScriptTestHelper.invoke0V(); - Assert.Equal(1, JavaScriptTestHelper.optimizedReached); - JavaScriptTestHelper.invoke1V(42); - Assert.Equal(43, JavaScriptTestHelper.optimizedReached); - Assert.Equal(124, JavaScriptTestHelper.invoke1R(123)); - Assert.Equal(43 + 123, JavaScriptTestHelper.optimizedReached); - Assert.Equal(32, JavaScriptTestHelper.invoke2R(15, 16)); - Assert.Equal(43 + 123 + 31, JavaScriptTestHelper.optimizedReached); + 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 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..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,24 +432,61 @@ 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("returnResolvedPromiseWithDateMaxValue", "JavaScriptTestHelper")] + [return: JSMarshalAs>] + internal static partial Task ReturnResolvedPromiseWithDateMaxValue(); + [JSExport] internal static Task ReturnCompletedTask() { @@ -472,6 +509,30 @@ public static async Task AwaitTaskOfInt64([JSMarshalAs>] + public static async Task AwaitTaskOfShort([JSMarshalAs>] Task arg1) + { + var res = await arg1; + return res; + } + + [JSExport] + [return: JSMarshalAs>] + public static async Task AwaitTaskOfString([JSMarshalAs>] Task arg1) + { + var res = await arg1; + return res; + } + + [JSExport] + [return: 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 f4f2772fdda8b5..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,10 +246,34 @@ 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(); } +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"; @@ -490,4 +514,4 @@ export function isSetTimeoutHit() { export function isPromiseThenHit() { return promiseThenHit; -} \ No newline at end of file +} 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 { 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 {