Skip to content

Honor DoNotRegister for Java.Lang.Throwable handles - #13052

Merged
simonrozsival merged 2 commits into
mainfrom
simonrozsival-throwable-registration-contract
Oct 10, 2026
Merged

simonrozsival merged 2 commits into
mainfrom
simonrozsival-throwable-registration-contract

Conversation

@simonrozsival

Copy link
Copy Markdown
Member

Pull Request

Java.Lang.Throwable.SetHandle ignored JniHandleOwnership.DoNotRegister and always registered its wrapper. This caused premature registry insertion during typemap activation, before the peer was marked Replaceable, and could displace an existing peer; a reentrant lookup could similarly lose its alias as winner. SetHandle now passes JNIEnv.ToJniObjectReferenceOptions(transfer) for non-null references while retaining None for null. This preserves the wrapper's copy semantics and leaves raw transferred-input cleanup unchanged. The regression tests cover normal registration, borrowed/local/global inputs with combined raw ownership and DoNotRegister flags, existing replaceable-peer preservation, and reentrant alias identity.

Validation

  • Original-code API29/CoreCLR reproduction: 56 total, 49 passed, 7 failed. Failures were ThrowableConstructionHonorsDoNotRegister for three DoNotRegister ownership cases, ThrowableActivationPreservesExistingReplaceablePeer, and ThrowableDoNotRegisterPreservesReentrantAlias for three ownership cases.
  • With the fix, CoreCLR TransferredReferences + TrimmableTypeMapRuntimeCoverage: 78 passed, 0 failed.
  • With the fix, NativeAOT for the same categories: 78 passed, 0 failed. NativeAOT selection was verified by the APK's libMono.Android.NET-Tests.so; it requires PublishAot=true and Optimize=true in Debug. The build emitted the expected experimental-runtime and ILC analysis warnings, with 0 errors.
  • The source SDK build succeeded using the local workaround for the unavailable Microsoft.NETCore.App.Ref 10.0.13 feed package. make prepare without that workaround had failed with NU1102.
ANDROID_HOME="$ANDROID_SDK_ROOT" RuntimeFrameworkVersion=10.0.0 DOTNET_ROLL_FORWARD=Major JavaSdkDirectory="$JAVA_HOME" make all MSBUILD_FLAGS="-p:RuntimeFrameworkVersion=10.0.0 -p:GenerateDocumentationFile=True -p:JavaSdkDirectory=$JAVA_HOME -p:AndroidSdkDirectory=$ANDROID_SDK_ROOT"

CoreCLR (Debug, RuntimeIdentifier=android-arm64, PublishAot=false, Optimize=false; install/build):

ANDROID_HOME="$ANDROID_SDK_ROOT" ANDROID_ADB_SERVER_PORT=5063 DOTNET_ROLL_FORWARD=Major JavaSdkDirectory="$JAVA_HOME" ./dotnet-local.sh build -t:Clean,Install -c Debug tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -p:RuntimeIdentifier=android-arm64 -p:PublishAot=false -p:Optimize=false '-p:IncludeCategories=TransferredReferences%2CTrimmableTypeMapRuntimeCoverage' '-p:AdbTarget=-s emulator-5580' -p:JavaSdkDirectory="$JAVA_HOME" -p:AndroidSdkDirectory="$ANDROID_SDK_ROOT"
cd tests/Mono.Android-Tests/Mono.Android-Tests && ANDROID_ADB_SERVER_PORT=5063 ../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj --no-build -c Debug -p:RuntimeIdentifier=android-arm64 -p:PublishAot=false -p:Optimize=false '-p:IncludeCategories=TransferredReferences%2CTrimmableTypeMapRuntimeCoverage' --device emulator-5580 --report-trx --results-directory ../../../bin/TestDebug/TestResults --timeout 10m

NativeAOT (Debug, RuntimeIdentifier=android-arm64, PublishAot=true, Optimize=true; install/build and test):

ANDROID_HOME="$ANDROID_SDK_ROOT" ANDROID_ADB_SERVER_PORT=5063 DOTNET_ROLL_FORWARD=Major JavaSdkDirectory="$JAVA_HOME" ./dotnet-local.sh build -t:Clean,Install -c Debug tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj -p:RuntimeIdentifier=android-arm64 -p:PublishAot=true -p:Optimize=true '-p:IncludeCategories=TransferredReferences%2CTrimmableTypeMapRuntimeCoverage' '-p:AdbTarget=-s emulator-5580' -p:JavaSdkDirectory="$JAVA_HOME" -p:AndroidSdkDirectory="$ANDROID_SDK_ROOT"
cd tests/Mono.Android-Tests/Mono.Android-Tests && ANDROID_ADB_SERVER_PORT=5063 ../../../dotnet-local.sh test Mono.Android.NET-Tests.csproj --no-build -c Debug -p:RuntimeIdentifier=android-arm64 -p:PublishAot=true -p:Optimize=true '-p:IncludeCategories=TransferredReferences%2CTrimmableTypeMapRuntimeCoverage' --device emulator-5580 --report-trx --results-directory ../../../bin/TestDebug/TestResults --timeout 10m

This demonstrates premature registration and peer-winner displacement, not a permanent GREF leak; no GREF-count test was run. Refs #13010.

title and
description
should follow the
commit-messages.md workflow documentation, and in particular should include:

  • Useful description of why the change is necessary.
  • Links to issues fixed
  • Unit tests

simonrozsival and others added 2 commits October 9, 2026 15:08
Refs #13010

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

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 9, 2026 13:22

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.

🟢 Approval recommended

The fix matches the existing ownership conversion contract and has comprehensive regression coverage; the inline suggestion is non-blocking.

1 open finding
What changed in this PR

Updates Throwable JNI peer construction to honor DoNotRegister, preventing premature registry replacement during typemap activation.

Changes:

  • Preserves DoNotRegister while retaining copy semantics.
  • Adds ownership, replaceable-peer, and reentrant-alias regression tests.
File Description
src/​Mono.Android/​Java.Lang/​Throwable.cs Converts ownership flags into appropriate JNI reference options.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​TransferredReferenceTests.cs Adds regression coverage and flag-aware reference tracking.

🧠 Review effort: Balanced

Comment on lines +434 to +435
BeforeConstruct?.Invoke (handle);
return handle;
@simonrozsival

Copy link
Copy Markdown
Member Author

@dalexsoto review

@dalexsoto dalexsoto left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Throwable construction now preserves DoNotRegister without changing reference-copy or transferred-input cleanup semantics. Typemap activation and reentrant lookup retain the existing peer winner; the complete correctness and integration review leaves no blocking issue.

@simonrozsival
simonrozsival enabled auto-merge (squash) October 10, 2026 05:34
@simonrozsival
simonrozsival merged commit dcc2abf into main Oct 10, 2026
43 checks passed
@simonrozsival
simonrozsival deleted the simonrozsival-throwable-registration-contract branch October 10, 2026 06:14
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.

3 participants