Skip to content

[TrimmableTypeMap] Add exception handling to UCO constructor callbacks (nctor_*_uco)#11178

Merged
jonathanpeppers merged 3 commits intomainfrom
copilot/add-exception-handling-to-uco-constructors
Apr 22, 2026
Merged

[TrimmableTypeMap] Add exception handling to UCO constructor callbacks (nctor_*_uco)#11178
jonathanpeppers merged 3 commits intomainfrom
copilot/add-exception-handling-to-uco-constructors

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 22, 2026

UCO constructor callbacks (nctor_0_uco, nctor_1_uco, …) emitted by EmitUcoConstructor had no exception handling. An exception thrown by the activation constructor would cross the JNI boundary unhandled, causing a SIGABRT. This mirrors the same pattern already used by ManagedPeer.Construct.

Changes

PEAssemblyBuilder

  • New EmitBody(…, Action<InstructionEncoder, ControlFlowBuilder>, …) overload — lets callers both emit IL and register exception regions (catch/finally) in a single pass.

TypeMapAssemblyEmitter

  • New type refs: JniTransition, JniRuntime, System.Exception
  • New member refs: JniEnvironment.BeginMarshalMethod, JniEnvironment.EndMarshalMethod, JniRuntime.OnUserUnhandledException
  • EmitUcoConstructorBodyWithMarshal helper emits the standard marshal wrapper pattern:
// Generated nctor_0_uco — before
ldarg.0; ldarg.1; call get_WithinNewObjectScope; brtrue skip;
// ... activation ...
skip: ret

// Generated nctor_0_uco — after
ldarg.0; ldloca envp; ldloca runtime
call BeginMarshalMethod; brfalse done
.try {
    call get_WithinNewObjectScope; brtrue skip
    // ... activation ...
    skip: leave done
} catch (Exception) {
    stloc e; ldloc runtime; brfalse end
    ldloc runtime; ldloca envp; ldloc e
    callvirt OnUserUnhandledException
    end: leave done
} finally {
    ldloca envp; call EndMarshalMethod; endfinally
}
done: ret
  • EmitUcoConstructor updated for both standard ((IntPtr, JniHandleOwnership)) and JavaInterop ((ref JniObjectReference, JniObjectReferenceOptions)) activation styles. Open-generic (no-op) UCO constructors are unchanged.
  • New local variable encoding helpers (EncodeUcoConstructorLocals_Standard, _JavaInterop) for the expanded local layouts (JniTransition/JniRuntime/Exception + optional JniObjectReference).

Tests (TypeMapAssemblyGeneratorTests)

  • 4 new tests verifying catch+finally exception regions are present in generated UCO constructor method bodies: standard leaf, JavaInterop, inherited ctor, and generic no-op.

Wrap the activation code in EmitUcoConstructor with BeginMarshalMethod/
EndMarshalMethod + try/catch/finally to prevent exceptions from crossing
the JNI boundary (SIGABRT). Mirrors the ManagedPeer.Construct pattern.

- PEAssemblyBuilder: New EmitBody overload for exception regions via ControlFlowBuilder
- TypeMapAssemblyEmitter: New type/member refs, EmitUcoConstructorBodyWithMarshal
  helper, updated EmitUcoConstructor to use exception handling
- TypeMapAssemblyGeneratorTests: 4 new tests verifying exception regions

Agent-Logs-Url: https://github.com/dotnet/android/sessions/c97aa44d-9497-4c2e-b1d1-1b8c382d1789

Co-authored-by: simonrozsival <374616+simonrozsival@users.noreply.github.com>
Copilot AI changed the title [WIP] Add exception handling to UCO constructor callbacks [TrimmableTypeMap] Add exception handling to UCO constructor callbacks (nctor_*_uco) Apr 22, 2026
Copilot AI requested a review from simonrozsival April 22, 2026 08:25
@simonrozsival simonrozsival marked this pull request as ready for review April 22, 2026 12:22
Copilot AI review requested due to automatic review settings April 22, 2026 12:22
@simonrozsival
Copy link
Copy Markdown
Member

/review

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 22, 2026

Android PR Reviewer completed successfully!

Copy link
Copy Markdown
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 updates the TrimmableTypeMap generator so that generated UCO constructor callbacks (nctor_*_uco) wrap activation in the standard JniEnvironment.BeginMarshalMethod / EndMarshalMethod try/catch/finally pattern, preventing managed exceptions from crossing the JNI boundary and causing aborts.

Changes:

  • Added a PEAssemblyBuilder.EmitBody overload that passes a ControlFlowBuilder to allow emitting IL and registering exception regions in one pass.
  • Updated TypeMapAssemblyEmitter.EmitUcoConstructor to emit the marshal wrapper + catch/finally regions for both standard and JavaInterop activation styles.
  • Added new tests verifying exception regions are present for non-generic UCO constructors and absent for open-generic no-op constructors.

Reviewed changes

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

File Description
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs Adds tests asserting exception regions (catch+finally) for generated nctor_*_uco methods across activation styles and inheritance cases.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs Emits marshal-method wrapper (Begin/End + catch/finally) around UCO constructor activation and adds required type/member references and local signatures.
src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs Introduces a new EmitBody overload that exposes ControlFlowBuilder so callers can register exception regions while emitting IL.

Copy link
Copy Markdown

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ LGTM

Solid, well-structured change that fixes a real crash (SIGABRT from unhandled exceptions crossing the JNI boundary in UCO constructor callbacks). The implementation correctly mirrors the ManagedPeer.Construct marshal-method pattern.

What I verified:

  • IL correctness: The exception region encoding follows ECMA-335 correctly — the finally region's try range [tryStart, finallyStart) properly encompasses both the try body and catch handler, so leave from either will trigger the finally.
  • Early-return path: BeginMarshalMethod returning false branches to afterAll before tryStart, so no exception regions are entered — EndMarshalMethod is correctly skipped when no transition was started.
  • Local variable layouts: Standard (3 locals) and JavaInterop (4 locals) signatures encode the correct types (valuetype for JniTransition/JniObjectReference, class for JniRuntime/Exception). Local indices in IL match the declared layouts.
  • Null-safety in catch: The runtime null-check before callvirt OnUserUnhandledException correctly handles the case where BeginMarshalMethod returns true but runtime is null.
  • Generic no-op: Open-generic UCO constructors are correctly left as simple ret stubs without exception handling.
  • Tests: 4 tests covering all activation paths (standard leaf, JavaInterop, inherited ctor, generic no-op).
  • CI: Public CI checks are green.

By severity:

Count
💡 Suggestion 2

Two minor suggestions (missing invariant comment on the new EmitBody overload, and test helper extraction for repeated search pattern) — neither blocks merge.

Generated by Android PR Reviewer for issue #11178 · ● 4M

@simonrozsival

This comment was marked as outdated.

@simonrozsival simonrozsival added trimmable-type-map copilot `copilot-cli` or other AIs were used to author this labels Apr 22, 2026
…invariant comment

- Extract FindNctorUcoMethod helper into FixtureTestBase to eliminate
  duplication across 4 tests
- Add ILContainsCallToken helper and verify nctor_*_uco IL actually
  calls BeginMarshalMethod/EndMarshalMethod/OnUserUnhandledException
- Add sigBlobHandle capture ordering comment to new EmitBody overload
- Keep generic no-op test conditional (GenericHolder doesn't emit
  nctor_*_uco)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival
Copy link
Copy Markdown
Member

CI failure unrelated:

Build_XAML_Change(False)
Exceeded expected time of 3000ms, actual 3119.268ms

@simonrozsival simonrozsival added the ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). label Apr 22, 2026
@jonathanpeppers jonathanpeppers merged commit 6106016 into main Apr 22, 2026
2 of 3 checks passed
@jonathanpeppers jonathanpeppers deleted the copilot/add-exception-handling-to-uco-constructors branch April 22, 2026 19:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

copilot `copilot-cli` or other AIs were used to author this ready-to-review This PR is ready to review/merge, I think any CI failures are just flaky (ignorable). trimmable-type-map

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TrimmableTypeMap] Add exception handling to UCO constructor callbacks (nctor_*_uco)

4 participants