Skip to content

Conversation

@ArcadeMode
Copy link
Contributor

@ArcadeMode ArcadeMode commented Jan 23, 2026

When marshalling a Task<T> from JS to CS and the value fails a type assertion, this would crash kill the wasm runtime. It seems to cause some kind of loop that starts printing the same message nested into itself untill the page gives out, I've also seen the browser tab hit out of memory cases. The console gets filled with this: (look at that scrollbar)
image

The message gives the impression that the main method is not running, however the error hits a case that kills the mono runtime regardless of whether main is running or not.

I have added some tests that demonstrated the issue in a few cases and applied a fix that instead sets the assertion error as the Exception result of the Task being marshalled. This way the user will be informed on the C# side that the assertion failed. This seems to me the most reasonable way of propagating the exception.

Copilot AI review requested due to automatic review settings January 23, 2026 02:37
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Jan 23, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes a critical issue where the WASM runtime would crash when marshalling a Task<T> from JavaScript to C# if the task's result value failed a type assertion. The root cause was that type assertion failures in the res_converter function would call mono_assert, which aborts the entire runtime via nativeAbort.

Changes:

  • Wrapped the res_converter call in a try-catch block to capture exceptions and propagate them as Task exceptions instead of crashing the runtime
  • Added comprehensive test coverage for out-of-range value scenarios (short, byte) and type assertion failures (string vs long)
  • Added helper functions in both JavaScript and C# test infrastructure to support the new test cases

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/mono/browser/runtime/managed-exports.ts Added try-catch around res_converter call to catch type assertion errors and propagate them as Task exceptions instead of aborting the runtime
src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.mjs Added returnResolvedPromiseWithIntMaxValue helper function to test overflow scenarios
src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JavaScriptTestHelper.cs Added JSImport declarations for overflow tests and JSExport methods for awaiting Task and Task
src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSImportTest.cs Added test cases for short and byte overflow scenarios when marshalling from JS to C#
src/libraries/System.Runtime.InteropServices.JavaScript/tests/System.Runtime.InteropServices.JavaScript.UnitTests/System/Runtime/InteropServices/JavaScript/JSExportTest.cs Added test cases for type assertion failures (short overflow and string type mismatch)

@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@pavelsavara
Copy link
Member

pavelsavara commented Jan 23, 2026

#Hi @ArcadeMode, thanks for your effort! 💙

For this one, I guess we need to discuss the overall intent.

I would summarize it as: "don't abort whole runtime, when marshaling assert fails, instead just throw (and marshal) exception".
Right now, the situation is something between "undefined behavior" and "your app is a invalid program"

I think improving it makes sense.
I would like to hear more about your scenarios in which fixing "invalid program" is not the right action.
@maraf your thoughts ?

The scenarios when the JS value can't be marshaled are

  • passing wrong type from JS side
  • passing value out of range from JS side

Those conversions happen when

  • returning value from JSImport
  • passing parameters to JSExport
  • passing parameters to JS functions which is a marshaled Delegate
  • resolving JS Promise

The scenarios when the C# value can't be marshaled are

  • marshaling Int64 bigger than Int52.Max to JS number
  • maybe DateTime -> JS Date could be out of range

Those conversions happen when

  • returning value from JSExport
  • passing parameters to JSImport
  • passing parameters to Delegate which is a marshaled JS function
  • resolving Task

It would be great to cover all of those use-cases with a unit test and fix all paths.
Also note that we are in the middle of refactoring, and so there is another location that needs same changes.
See src\native\libs\System.Runtime.InteropServices.JavaScript.Native\interop\

@maraf
Copy link
Member

maraf commented Jan 23, 2026

+1 on "don't abort whole runtime, when marshaling assert fails, instead just throw (and marshal) exception"

@ArcadeMode
Copy link
Contributor Author

ArcadeMode commented Jan 23, 2026

Thanks for the pointers and agreement on direction @pavelsavara, @maraf.

I'll go ahead and look at the other cases you have mentioned and see what might have to be done. I have yet to look at System.Runtime.InteropServices.JavaScript.Native, but i'm curious to see what is happening there.

I'll drop a message here when i'm done with the feedback.

@pavelsavara pavelsavara changed the title Wasm runtime exit when task argument's value fails assertion [browser] throw and marshal exception caused by invalid arguments Jan 23, 2026
@ArcadeMode
Copy link
Contributor Author

ArcadeMode commented Jan 23, 2026

For completeness sake, I want to mention that 'regular' assertion failures during marshalling do not cause the runtime to exit, instead, an error is thrown back to the caller of the function. There are also unit tests already confirming this behavior such as System.Runtime.InteropServices.JavaScript.Tests.JSImportTest.OutOfRange.

This PR is revolving around method invocations that include Task/Promise specifically as either return type or argument type. These values may resolve later and thus cannot throw the assertion failure back to the caller (the result isnt known yet). Currently the promise/task resolving with a value that wont pass type assertion, will throw an unobservable error and cause the behavior described above.

The proposed (and thus far agreed upon) solution is to reject the promise / set exception on the task so that the receiver of the Task/Promise can observe the error. (receiver I see as either the return value receiver or method argument receiver).

So, with respect to the mentioned cases I want to scope the relevance to Promise/Task related conversions.

The scenarios when the JS value can't be marshaled are

  • passing wrong type from JS side ➡ through a Promise
  • passing value out of range from JS side ➡ through a Promise

Those conversions happen when

  • returning value from JSImport ✅ * Promise value , is tested
  • passing parameters to JSExport ✅ * Promise parameters, is tested
  • passing parameters to JS functions which is a marshaled Delegate ➡ not for the Promise itself but again upon promise resolve. Sounds different enough to warrant a test
  • resolving JS Promise ✅ Covered by the first two cases

The scenarios when the C# value can't be marshaled are

  • marshaling Int64 bigger than Int52.Max to JS number ➡ through a Task
  • maybe DateTime -> JS Date could be out of range ➡ through a Task

Those conversions happen when

  • returning value from JSExport ➡ * Task value
  • passing parameters to JSImport ➡ * Task parameters
  • passing parameters to Delegate which is a marshaled JS function ➡ * Task parameters
  • resolving Task ⚠ * Covered by first two cases

I will start with the ➡ cases. If this tailoring of scope is undesirable please let me know.

@pavelsavara
Copy link
Member

For completeness sake, I want to mention that 'regular' assertion failures during marshalling do not cause the runtime to exit, instead, an error is thrown back to the caller of the function. There are also unit tests already confirming this behavior such as System.Runtime.InteropServices.JavaScript.Tests.JSImportTest.OutOfRange.

Covering more data types and also JSExport direction would be appreciated.

@pavelsavara
Copy link
Member

pavelsavara commented Jan 24, 2026

there is another location that needs same changes. See src\native\libs\System.Runtime.InteropServices.JavaScript.Native\interop\

This is running on CoreCLR instead of Mono and has the separate JS interop.
And your new unit test is showing it's broken in the same way as the Mono version.

Log

[18:27:18] info: [STRT] System.Runtime.InteropServices.JavaScript.Tests.JSImportTest.TaskOfShortOutOfRange_ThrowsAssertionInTaskContinuation

...
[EXECUTION TIMED OUT]
Exit Code:-3Executor timed out after 1800 seconds and was killed
['WasmTestOnChrome-ST-System.Runtime.InteropServices.JavaScript.Tests' END OF WORK ITEM LOG: Command timed out, and was killed]

It manifests as spinning forever, probably in the "already exited".
I created separate issue for that.

Here we want to make the same fix to marshaling for CoreCLR.

@ArcadeMode
Copy link
Contributor Author

ArcadeMode commented Jan 25, 2026

@pavelsavara this should now cover the discussed cases, tested a few numerical types, dates and a numerical > string case, with and without Tasks and with delegates. Brought the fixes to the CLR implementation too.

I tried adding tests for delegates+Tasks but Func<Task<T>> does not seem to be supported. Simple Func is included in the tests however.

While testing I found that there were no range checks for Date implemented, this turned out to be a new path that caused the runtime to exit. So that was nice to fix while I was at it.

pavelsavara
pavelsavara previously approved these changes Jan 27, 2026
Copy link
Member

@pavelsavara pavelsavara left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, many thanks!

There are infra problems with CI last 2 days, I want to see it pass before we merge. I will trigger it again when I have some update

@pavelsavara
Copy link
Member

When looking at your other PR I realized that there are other out-of-range scenarios which would be nice to test here.

JS Array could contain

  • element of wrong type
  • element of value out of range
    • [JSMarshalAs<JSType.Array<JSType.Number>>] int[]? value
    • [JSMarshalAs<JSType.Array<JSType.Number>>] float[]? value - on the other PR

@pavelsavara
Copy link
Member

Also CI fails with

Log

[09:41:53] info: MONO_WASM: 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 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
[09:41:53] info: WASM EXIT 1

That killed the runtime instead of marshaling the exception

@pavelsavara pavelsavara self-requested a review January 28, 2026 10:43
@pavelsavara pavelsavara dismissed their stale review January 28, 2026 10:44

test failure

@ArcadeMode ArcadeMode changed the title [browser] throw and marshal exception caused by invalid arguments [browser] throw and marshal exception caused by invalid arguments in Tasks Jan 28, 2026
@ArcadeMode
Copy link
Contributor Author

ArcadeMode commented Jan 28, 2026

@pavelsavara I have resolved the failing test, used the wrong assertion method.

w.r.t the int32 overflow tests. I have made a test that demonstrates the current behavior (overflowing ints). While undesirable it it no regression caused by the fixes presented in this PR, so I'd like to move forward with this PR before having a look at the array value assertions. I hope thats okay with you.

I've created #123731 for the array value overflow. I'd like take a look at that after completing the span<float> implementation. then I can take a look at all array types in one go, I expect none of the types to get value assertions so there might be issues for other types as well.

@pavelsavara
Copy link
Member

pavelsavara commented Jan 29, 2026

  • CoreCLR passing test Log
  • Mono passing test on Firefox Log
  • Mono passing test on Chome Log

@pavelsavara
Copy link
Member

/ba-g CI timeouts

@pavelsavara pavelsavara merged commit a9d1ab6 into dotnet:main Jan 29, 2026
99 of 102 checks passed
@pavelsavara
Copy link
Member

Great work @ArcadeMode, many thanks!

If you want to chat sometimes, some of us are in https://aka.ms/dotnet-discord

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-System.Runtime.InteropServices.JavaScript community-contribution Indicates that the PR has been added by a community member os-browser Browser variant of arch-wasm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants