Skip to content

[Tests] Define managed object proxy contract - #12612

Merged
jonathanpeppers merged 7 commits into
mainfrom
simonrozsival-managed-object-proxy-contract
Sep 1, 2026
Merged

[Tests] Define managed object proxy contract#12612
jonathanpeppers merged 7 commits into
mainfrom
simonrozsival-managed-object-proxy-contract

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Context

Fixes #11703.

Plain managed objects already preserve their managed reference across Java round trips in both llvm-ir and trimmable typemap builds. The remaining gap was explicit contract coverage, especially because trimmable proxies intentionally use Java identity semantics instead of forwarding managed Equals(), GetHashCode(), and ToString().

Changes

  • Add focused device coverage for JavaObjectArray<object>, JavaList<object>, and JavaDictionary<object, object> round trips.
  • Assert repeated lookup identity, duplicate references, nulls, distinct managed-equal values, nested collections, Java.Lang.Object views, and bounded GC/disposal lifetime.
  • Assert the intentional Java-visible semantic split: llvm-ir forwards managed object methods, while trimmable CoreCLR and NativeAOT use Java identity semantics.
  • Keep production code unchanged because the supported identity/type/value/lifetime contract already matches across all three configurations.

Validation

  • llvm-ir CoreCLR Release: 9 passed, 0 failed, 0 skipped

  • trimmable CoreCLR Release: 9 passed, 0 failed, 0 skipped

  • trimmable NativeAOT Release: 9 passed, 0 failed, 0 skipped

  • Java.Interop legacy proxy host contract: 14 passed, 0 failed, 0 skipped

  • Useful description of why the change is necessary

  • Links to issues fixed

  • Unit tests

Cover plain managed object round trips through Java arrays and collections, including identity, nulls, nested containers, Java-visible object semantics, and GC lifetime across runtime configurations.\n\nCo-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 1, 2026 04:11
State the shared round-trip contract separately from typemap-specific Java object methods.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟢 Approval recommended

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​ManagedObjectProxyTests.cs — 💡 suggestion — AssertEventuallyCollected uses a fixed attempt count with Thread.Yield(), which can…
What changed in this PR

Adds focused device tests to explicitly define and validate the managed object proxy round-trip contract (identity, equality semantics, and lifetime) across llvm-ir and trimmable typemap configurations, without changing production runtime behavior.

Changes:

  • Introduces ManagedObjectProxyTests covering JavaObjectArray<object>, JavaList<object>, and JavaDictionary<object, object> round-trip identity/semantics and bounded GC/disposal behavior.
  • Asserts the intentional semantic split for Java-visible object methods between llvm-ir (managed forwarding) and trimmable typemap (Java identity semantics).
  • Wires the new test file into the Mono.Android.NET device test project build.
File Description
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Mono.Android.NET-Tests.csproj Includes the new ManagedObjectProxy test fixture in the device test build.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​ManagedObjectProxyTests.cs New device tests defining managed proxy identity/semantics/lifetime contract across typemap modes.

simonrozsival and others added 2 commits September 1, 2026 06:28
Wait for an observed JNI bridge-processing generation when testing proxy lifetime, and derive the exact default Java object string from the runtime proxy class and unsigned identity hash.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Always perform a full collection and bridge synchronization cycle, while requiring bridge-generation advancement only on CoreCLR and NativeAOT.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
simonrozsival and others added 3 commits September 1, 2026 12:54
Use exception-safe JNI reference acquisition and collection disposal, and measure the bounded GC wait with a monotonic stopwatch.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Keep every JNI local reference exception-safe, retain caller ownership while constructing the unregistered Java object view, and create the lifetime-test target on a short-lived stack to avoid Mono false pinning.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Run weak-reference probes and retention assertions on short-lived stacks so Mono conservative scanning cannot retain stale strong target references between forced collections.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12612

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

❌ Reject

Findings: 1 error · 0 warnings · 0 suggestions.

The new tests provide useful coverage for proxy identity, JNI reference cleanup, and GC lifetime across typemap implementations. However, the Java collection equality assertions currently contradict the documented LLVM-IR behavior and will fail in that configuration; the inline comment identifies both affected tests.

CI build 1576501 was still in progress during review: 7 of 44 checks had passed, 36 were running, and 1 was queued, with no reported failures yet.

Generated by Android PR Reviewer for #12612 · gpt56 · 99.3 AIC · ⌖ 9.27 AIC · ⊞ 25.7K
Comment /review to run again

@simonrozsival

Copy link
Copy Markdown
Member Author

/review

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Android PR Reviewer completed successfully!

Generated by Android PR Reviewer for #12612

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review summary

Verdict: ⚠️ Follow-up suggested; CI is still in progress.

The typemap-specific identity, equality, hashing, string, nested-collection, and array-lifetime assertions match the runtime implementations I traced. I left one inline suggestion to extend lifetime coverage to the separate JavaList/JavaDictionary marshaling path.

Findings: 0 errors · 0 warnings · 1 suggestion

CI: Azure DevOps build 1576501 remains in progress; completed checks were successful when reviewed, with no known failure yet.

Generated by Android PR Reviewer for #12612 · gpt56 · 122.3 AIC · ⌖ 8.9 AIC · ⊞ 25.7K
Comment /review to run again

}

[Test]
public async Task JavaObjectArrayRetainsManagedValueUntilReleased ()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 💡 Testing — This lifecycle test only exercises JavaObjectArray<object>, which marshals arbitrary managed values through CreateLocalObjectReferenceArgument(). The JavaList and JavaDictionary tests above use the distinct JavaConvert.ToLocalJniHandle() / Android.Runtime.JavaObject fallback, but only round-trip values while strong managed references still exist. Please consider adding the same retain-while-Java-rooted and collect-after-release coverage for that collection path (one list case plus a dictionary key/value case would cover it), so the proxy lifetime contract is tested for both implementations.

Rule: Test each distinct marshaling path

@jonathanpeppers
jonathanpeppers merged commit ee8b0e0 into main Sep 1, 2026
44 checks passed
@jonathanpeppers
jonathanpeppers deleted the simonrozsival-managed-object-proxy-contract branch September 1, 2026 14:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Investigate JavaProxyObject support in trimmable typemap

3 participants