[TrimmableTypeMap] Propagate deferred registerNatives to base classes#11105
Conversation
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>
There was a problem hiding this comment.
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 walkBaseJavaNamechains 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. |
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
Outdated
Show resolved
Hide resolved
tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TrimmableTypeMapGeneratorTests.cs
Outdated
Show resolved
Hide resolved
- 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>
simonrozsival
left a comment
There was a problem hiding this comment.
🤖 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 useStringComparison.Ordinalfor 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.
src/Microsoft.Android.Sdk.TrimmableTypeMap/TrimmableTypeMapGenerator.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
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
registerNativessupport was introduced across several PRs:CannotRegisterInStaticConstructorflag and set it inJavaPeerScannerfor types with[Application]or[Instrumentation]attributes. The JCW generator skips thestatic { registerNatives(...); }block for these types and emits a lazy__md_registerNatives()helper instead.RootManifestReferencedTypessetsCannotRegisterInStaticConstructoron types matched by<application>or<instrumentation>manifest entries.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 classTestInstrumentation_1still emitsstatic { registerNatives(...); }. Since Java loads the base class<clinit>before the managed runtime registers the JNI native method, this crashes withUnsatisfiedLinkError.Changes
Add
PropagateDeferredRegistrationToBaseClasses()inTrimmableTypeMapGenerator, called afterRootManifestReferencedTypes. It walks each flagged peer'sBaseJavaNamechain with a simple linear scan and setsCannotRegisterInStaticConstructor = trueon 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_PropagatesToBaseClassesOfManifestReferencedTypesverifies 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.