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.

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