Skip to content

[TrimmableTypeMap] Propagate deferred registerNatives to base classes#11105

Merged
simonrozsival merged 7 commits intomainfrom
dev/simonrozsival/fix-deferred-registration-propagation
Apr 14, 2026
Merged

[TrimmableTypeMap] Propagate deferred registerNatives to base classes#11105
simonrozsival merged 7 commits intomainfrom
dev/simonrozsival/fix-deferred-registration-propagation

Conversation

@simonrozsival
Copy link
Copy Markdown
Member

@simonrozsival simonrozsival commented Apr 13, 2026

Summary

Fixes a startup crash (UnsatisfiedLinkError: No implementation found for void mono.android.Runtime.registerNatives) when running device tests with the trimmable typemap enabled.

Background

Deferred registerNatives support was introduced across several PRs:

None of these PRs propagated the flag to base classes in the Java type hierarchy. When NUnitInstrumentation (listed in the manifest) correctly uses deferred registration, its base class TestInstrumentation_1 still emits static { registerNatives(...); }. Since Java loads the base class <clinit> before the managed runtime registers the JNI native method, this crashes with UnsatisfiedLinkError.

Changes

Add PropagateDeferredRegistrationToBaseClasses() in TrimmableTypeMapGenerator, called after RootManifestReferencedTypes. It walks each flagged peer's BaseJavaName chain with a simple linear scan and sets CannotRegisterInStaticConstructor = true on all base peers that have JCW stubs (i.e., !DoNotGenerateAcw). Framework MCW types are skipped.

In practice only 1–2 types need propagation (one Application, maybe one Instrumentation), each with a short base-class chain. A linear scan per ancestor is simpler and cheaper than building a Dictionary<JavaName, List<Peer>> lookup over all peers up front.

Test

New unit test PropagateDeferredRegistrationToBaseClasses_PropagatesToBaseClassesOfManifestReferencedTypes verifies a 3-level instrumentation hierarchy (base → mid → leaf) where only the leaf is in the manifest. After propagation, all three peers have deferred registration.

All 353 existing unit tests continue to pass.

Application and Instrumentation types use deferred registerNatives via
__md_registerNatives() to avoid calling the JNI native before the managed
runtime is ready. However, CannotRegisterInStaticConstructor was only set
on types directly matched in the manifest — base classes in the hierarchy
(e.g., TestInstrumentation<TRunner>) still used the static initializer
pattern, causing UnsatisfiedLinkError crashes at startup.

Add PropagateDeferredRegistrationToBaseClasses() which walks each flagged
peer's BaseJavaName chain and propagates the deferred registration flag
to all base peers with JCW stubs. This ensures the entire inheritance
chain uses the lazy __md_registerNatives() pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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

Fixes a startup crash in trimmable typemap device tests by ensuring deferred Runtime.registerNatives() behavior is applied not only to manifest-rooted peers (e.g., Instrumentation) but also to their Java base classes, preventing premature base-class <clinit> registration.

Changes:

  • Invoke a new deferred-registration propagation step after manifest rooting in TrimmableTypeMapGenerator.Execute().
  • Add PropagateDeferredRegistrationToBaseClasses() to walk BaseJavaName chains and mark base peers for deferred registration.
  • Add a unit test covering a 3-level instrumentation hierarchy where only the leaf is manifest-rooted.

Reviewed changes

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

File Description
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs Adds and wires up deferred-registration propagation across base Java peers.
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs Adds regression coverage for deferred-registration propagation across a base chain.

simonrozsival and others added 5 commits April 13, 2026 18:01
- Use Dictionary<string, List<JavaPeerInfo>> to handle multiple peers
  sharing the same JavaName (alias entries).
- Use BFS with Queue + visited set for robust hierarchy traversal.
- Rename test to match what it actually exercises.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Split PropagateDeferredRegistrationToBaseClasses into three focused
static methods: BuildJniNameLookup, PropagateToAncestors, and the
top-level orchestrator.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Only 1-2 types need propagation in practice, so a linear scan per
ancestor is simpler and cheaper than building a dictionary lookup
over all peers up front. Drop Dictionary, Queue, and HashSet.

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

@simonrozsival simonrozsival left a comment

Choose a reason for hiding this comment

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

🤖 AI Review Summary

Verdict: ✅ LGTM

Clean, focused fix for a real startup crash. The change is minimal — one new method + one test — and the linear scan approach is well-justified given the tiny number of types that need propagation.

Found 1 suggestion:

  • 💡 Performance: The string != comparison in the inner loop could use StringComparison.Ordinal for explicit intent (TrimmableTypeMapGenerator.cs:234)

👍 Good things:

  • Excellent PR description tracing the history of deferred registration across PRs
  • XML doc comment on the method clearly explains the "why"
  • The inline comment explaining the linear scan vs dictionary trade-off is helpful for future readers
  • Test covers a 3-level hierarchy with assertions both before and after propagation — good boundary verification

Review generated by android-reviewer from review guidelines.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@simonrozsival simonrozsival merged commit c4dbc85 into main Apr 14, 2026
5 of 6 checks passed
@simonrozsival simonrozsival deleted the dev/simonrozsival/fix-deferred-registration-propagation branch April 14, 2026 14:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants