Skip to content

[Unsafe] Prepare JNI and runtime memory-safety contracts - #13191

Open
simonrozsival wants to merge 2 commits into
simonrozsival-unsafe-binding-contracts-stackfrom
simonrozsival-unsafe-runtime-contracts-stack
Open

simonrozsival wants to merge 2 commits into
simonrozsival-unsafe-binding-contracts-stackfrom
simonrozsival-unsafe-runtime-contracts-stack

Conversation

@simonrozsival

@simonrozsival simonrozsival commented Oct 19, 2026 •

Copy link
Copy Markdown
Member

Pull Request
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

Related to #11467; this preparation does not close it. Layer 9 of stack #13185, after #13190 and before activation #13192.

Published change

The published head is 88a2829eb7d3d91fa27ebfd085c7d1be2c176c62, based on binding preparation 898022e97e31e51f88d09d92b0cf14324132d047: two commits, 80 files, +6443/-3893. It separates caller-owned JNI handle/member-ID, buffer, callback and VM-creation obligations from local unsafe implementation contexts. Managed overrides and interfaces remain safe-callable; canonical JNI generation, typed callback templates/output, native imports, readonly managed UnsafeAccessors and JValue storage are covered without changing their layouts.

Three demonstrated bug fixes are distinct from the annotations: Collection.remove(Object) uses its Boolean ABI; AbstractSpliterator.TryAdvance uses instance/Boolean dispatch and releases its Consumer local; ToArray<T>() validates compatible array kind before native array access. Each has regression coverage.

Product memory-safety Features remain off in this preparation. The unchanged 31-line runtime-creation compiler test and its two API records belong to final activation, not this layer.

Uncommitted revision — not part of the published PR diff

A local 47-file revision (+1210/-488) is preserved but is neither committed nor synchronized with revised parents. Source work is paused for churn reassessment.

The proposal adds 23 individually audited raw caller annotations, makes two managed-only remapping lookups safe-callable, documents the matching-lock/alias obligation of Bitmap.UnlockPixels, and adds narrower implementation scopes with concrete // SAFETY: rationale. It includes regenerated documentation/comments for all 60 typed callbacks and one directly coupled MarkCreatedPeer callsite scope. It introduces no new runtime algorithm, ownership redesign or emitted-IL behavior change.

New unsafe enforcement on 13 raw peer constructors is deferred by user decision. Their raw obligations and enforcement gap are documented, but the proposed markers are not applied; existing safe initialization chains and the approved Object/Throwable contracts and Throwable None-only path are retained.

Only canonical T4 generation, an independent repeat/byte comparison, XML-documentation parsing and structural/token-preservation checks have passed for this revision. The updated outer JNI generator compilation/regeneration, GeneratedContractsCompileAndEnforceRawCalls, product SDK builds, and host/device execution are pending. No new host or device pass is claimed.

The generation-only checks were:

t4 src/Mono.Android/Java.Interop/JniMarshalTyped.tt -o src/Mono.Android/Java.Interop/JniMarshalTyped.cs
t4 src/Mono.Android/Java.Interop/JniMarshalTyped.tt -o "$AUDIT_RESULTS/JniMarshalTyped.regenerated.cs"
cmp src/Mono.Android/Java.Interop/JniMarshalTyped.cs "$AUDIT_RESULTS/JniMarshalTyped.regenerated.cs"
git diff --check

All exited 0. AUDIT_RESULTS denotes the saved local revision directory. T4 used the coordinator-provided existing SDK via DOTNET_ROOT/DOTNET_ROOT_ARM64, with DOTNET_ROLL_FORWARD=Major, DOTNET_ROLL_FORWARD_TO_PRERELEASE=1 and DOTNET_PROCESSOR_COUNT=2; it did not build or restore the product. Static checks found no executable-token change in the 60 regenerated helpers.

Validation boundaries

The historical results below apply only to the published preparation chain ending at 88a2829e, whose original root was 31001bbb. SDK/product source was built at bf917a5d; 88a2829e only reorders equivalent Java-source XML items. They do not validate the current stack root 0074a9bb, revised parents, or the uncommitted local proposal.

Historical published-source coverage Result
Debug/Release SDK, APIs 37/37.1/37.2 and workload setup Passed
Host suites, in command order below 182 / 26 / 4 / 7 passed; 0 failed or skipped
Full default-category CoreCLR Debug, API35 arm64 1012 passed, 0 failed, 7 skipped
Full default-category NativeAOT Release, API35 arm64 946 passed, 0 failed, 7 skipped

The separately attributed activated cd9e12f2 results are documented in #13192. Those published-source results likewise do not validate that PR's newer local proposal.

Exact historical build and test commands/results

ROOT is the original validation worktree and ART its saved result directory. The pinned SDK was 12.0.100-alpha.1.26509.101. These are recorded commands, not newly executed checks.

export DOTNET_PROCESSOR_COUNT=2 CMAKE_BUILD_PARALLEL_LEVEL=2 UseSharedCompilation=false
make prepare CONFIGURATION=Debug MSBUILD_ARGS='-m:1 -p:UseSharedCompilation=false'
make leeroy CONFIGURATION=Debug MSBUILD_ARGS='-m:1 -p:UseSharedCompilation=false'
make prepare CONFIGURATION=Release MSBUILD_ARGS='-m:1 -p:UseSharedCompilation=false'
make leeroy CONFIGURATION=Release MSBUILD_ARGS='-m:1 -p:UseSharedCompilation=false'

All four passed; SDK builds reported 0 warnings/errors. leeroy included extra API levels, compatibility checks and workload setup.

export DOTNET_ROOT="$ROOT/bin/Debug/dotnet" PATH="$ROOT/bin/Debug/dotnet:$PATH"
export DOTNETSDK_WORKLOAD_MANIFEST_ROOTS="$ROOT/bin/Debug/lib/sdk-manifests"
export DOTNETSDK_WORKLOAD_PACK_ROOTS="$ROOT/bin/Debug/lib"
export NUNIT_MSBUILD_ARGS=$'-m:1\n-p:UseSharedCompilation=false'
bin/Debug/dotnet/dotnet test bin/TestDebug/net10.0/Xamarin.Android.Build.Tests.dll \
  --filter 'FullyQualifiedName~JniEnvMemorySafetyTests|FullyQualifiedName~TrimmableTypeMapBuildTests|FullyQualifiedName~UnmanagedCallersOnlyCallbacksUseDefaultTypeMap|FullyQualifiedName~RuntimeEventSourceFeatureSwitch' \
  --settings "$ART/serial-host.runsettings" --logger 'trx;LogFileName=host-debug.trx' --results-directory "$ART/host-results"
bin/Debug/dotnet/dotnet test src/Microsoft.Android.Build.Tasks/Tests/Microsoft.Android.Build.Tasks.Tests/Microsoft.Android.Build.Tasks.Tests.csproj \
  --no-restore -m:1 -p:UseSharedCompilation=false \
  --filter 'FullyQualifiedName~GenerateTrimmableTypeMapTests|FullyQualifiedName~TrimmableTypeMapRidCallbackTests' \
  --settings "$ART/serial-host.runsettings" --logger 'trx;LogFileName=typemap.trx' --results-directory "$ART/host-results" -v:minimal

export DOTNET_ROOT="$ROOT/bin/Release/dotnet" PATH="$ROOT/bin/Release/dotnet:$PATH"
export DOTNETSDK_WORKLOAD_MANIFEST_ROOTS="$ROOT/bin/Release/lib/sdk-manifests"
export DOTNETSDK_WORKLOAD_PACK_ROOTS="$ROOT/bin/Release/lib"
./dotnet-local.sh test bin/TestRelease/net10.0/Xamarin.Android.Build.Tests.dll \
  --filter 'FullyQualifiedName~JniEnvMemorySafetyTests|FullyQualifiedName~RuntimeEventSourceFeatureSwitch' \
  --settings "$ART/serial-host.runsettings" --logger 'trx;LogFileName=host-release.trx' --results-directory "$ART/host-results"
./dotnet-local.sh test external/Java.Interop/tests/generator-Tests/generator-Tests.csproj \
  -c Release --no-restore -m:1 -p:UseSharedCompilation=false --filter 'FullyQualifiedName~BindingMemorySafetyTests' \
  --settings "$ART/serial-host.runsettings" --logger 'trx;LogFileName=binding-harness.trx' --results-directory "$ART/host-results" -v:minimal

In order: 182/26/4/7 passed, no failures/skips. An earlier malformed MSBuild response-file argument failed with MSB1030; these corrected runs passed without product changes or test exclusions.

bin/Debug/dotnet/dotnet build tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -t:SignAndroidPackage -c Debug -p:AndroidRuntime=CoreCLR -p:RuntimeIdentifier=android-arm64 \
  -p:UseSharedCompilation=false -m:1 -bl:"$ART/fixture-coreclr-debug.binlog" -v:normal
./dotnet-local.sh build tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj \
  -t:SignAndroidPackage -c Release -p:PublishAot=true -p:RuntimeIdentifier=android-arm64 \
  -p:UseSharedCompilation=false -m:1 -bl:"$ART/fixture-nativeaot-release.binlog" -v:normal

Both fixture builds passed. CoreCLR: 0 warnings/errors; NativeAOT: 10 retained analysis warnings, 0 errors, with a genuine AArch64 native app library.

The verified installed fixtures then ran through the same AndroidTestAdapter 1.0.0 in module-mode MTP, without project evaluation or redeployment:

# NativeAOT: CONFIG=Release; CoreCLR: CONFIG=Debug.
# DOTNET_ROOT, PATH and workload roots select the same CONFIG as the fixture.
(cd tests/Mono.Android-Tests/Mono.Android-Tests &&
  "$ROOT/bin/$CONFIG/dotnet/dotnet" test --test-modules Microsoft.Android.Run.dll \
    --root-directory "$ROOT/bin/$CONFIG/lib/packs/Microsoft.Android.Sdk.Darwin/37.99.0/tools" -- \
    "--adb=$ANDROID_HOME/platform-tools/adb" '--adb-target=-s emulator-5598' \
    --package=Mono.Android.NET_Tests \
    --instrument=xamarin.android.runtimetests.TestInstrumentation \
    --report-trx --results-directory "$ART/legacy-retry-20261019T090325Z/$RUNTIME-results")

With CONFIG=Release, RUNTIME=NativeAOT: 946 passed/0 failed/7 skipped. With CONFIG=Debug, RUNTIME=CoreCLR: 1012 passed/0 failed/7 skipped. Default category exclusions were unchanged. Installed APK identities and CoreCLR's deployed managed/config payload were verified before and after execution; all owned fixtures and emulator resources were cleaned up.

Earlier deployment/identity failures were retained, not counted as passes. Module mode avoided the pinned SDK's project-mode DeployToDevice redeployment that omitted the NativeAOT app library; that separate packaging bug is not fixed here.

No CI-green claim is made.

simonrozsival and others added 2 commits October 16, 2026 10:14
Extract only the published JNI runtime preparation delta onto pinned main, preserving the current typed GC-bridge startup and deleted internal native surface. Keep legacy constructor initialization and legacy memory-safety compilation. Relocate the runtime-creation activation assertion and unsafe API baseline expectations to the preserved session patch for final Java.Interop activation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Move the existing Spliterator source Update before the unrelated Java fixture Updates. Evaluated item identities and all metadata remain identical; preserve current-main additions without importing new upstream source.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 19, 2026 10:20
@simonrozsival
simonrozsival added this pull request to stack #13185 October 19, 2026 10: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.

🟡 Changes recommended

Array validation rejects previously supported element-wise conversions from Java Object arrays.

1 open issue
What changed in this PR

Prepares .NET for Android’s JNI and runtime contracts for updated C# memory-safety rules, following the binding preparation layer. Product activation remains off.

Changes:

  • Documents caller obligations and moves implementation-only unsafe operations into local blocks.
  • Corrects collection removal, spliterator dispatch, and array validation.
  • Adds compiler-contract and runtime regression coverage.
File Description
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Mono.Android.NET-Tests.csproj Includes spliterator tests and fixture.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​java/​net/​dot/​android/​test/​JniSpliteratorFixture.java Adds Java spliterator fixture.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Util/​SpliteratorTests.cs Tests instance Boolean dispatch.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Lang/​ObjectArrayMarshaling.cs Tests array validation and conversions.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​JnienvTest.cs Categorizes string-length coverage.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Java.Interop/​JavaMarshalGCBridgeTests.cs Categorizes GC-bridge coverage.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Android.Runtime/​RuntimeEventSourceTests.cs Categorizes event coverage.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Android.Runtime/​JnienvArrayMarshaling.cs Categorizes array-marshaling coverage.
tests/​Mono.Android-Tests/​Mono.Android-Tests/​Android.Runtime/​JavaCollectionTest.cs Tests collection removal signature.
src/​Xamarin.Android.Build.Tasks/​Tests/​Xamarin.Android.Build.Tests/​JniEnvMemorySafetyTests.cs Tests generated contracts and compiler enforcement.
src/​Mono.Android/​System.Linq/​Extensions.cs Scopes iterator JNI operations locally.
src/​Mono.Android/​Mono.Android.csproj Enables preview syntax without activation.
src/​Mono.Android/​Microsoft.Android.Runtime/​TrimmableTypeMapValueManager.cs Scopes cast-diagnostic JNI access.
src/​Mono.Android/​Microsoft.Android.Runtime/​TrimmableTypeMapTypeManager.cs Documents remapping pointer lifetimes.
src/​Mono.Android/​Microsoft.Android.Runtime/​RuntimeEventSource.cs Keeps event methods safe-callable.
src/​Mono.Android/​Microsoft.Android.Runtime/​JavaMarshalRegisteredPeers.cs Scopes tracking-handle operations.
src/​Mono.Android/​Microsoft.Android.Runtime/​JavaMarshalGCBridgeJni.cs Scopes GC-bridge JNI calls.
src/​Mono.Android/​Microsoft.Android.Runtime/​HandleContext.cs Documents native context obligations.
src/​Mono.Android/​Java.Util/​Spliterators.cs Fixes dispatch, signature, and reference cleanup.
src/​Mono.Android/​Java.Util/​ISortedMap.cs Keeps interface implementation safe-callable.
src/​Mono.Android/​Java.Util/​IList.cs Keeps interface implementation safe-callable.
src/​Mono.Android/​Java.Nio/​Buffer.cs Scopes direct-buffer address lookup.
src/​Mono.Android/​Java.Lang/​Throwable.cs Documents raw construction; scopes Class access.
src/​Mono.Android/​Java.Lang/​Thread.cs Scopes Runnable initialization.
src/​Mono.Android/​Java.Lang/​String.cs Keeps managed constructors safe-callable.
src/​Mono.Android/​Java.Lang/​Runnable.cs Scopes peer initialization.
src/​Mono.Android/​Java.Lang/​Object.cs Documents raw lookup and retains array peer lifetime.
src/​Mono.Android/​Java.Lang/​Class.cs Scopes cached method lookup.
src/​Mono.Android/​Java.Interop/​JniMarshalTyped.tt Emits callback contracts and local regions.
src/​Mono.Android/​Java.Interop/​JavaConvert.cs Scopes marshaling operations.
src/​Mono.Android/​Android.Widget/​TextView.cs Scopes listener registration.
src/​Mono.Android/​Android.Widget/​BaseAdapter.cs Scopes peer construction.
src/​Mono.Android/​Android.Widget/​AdapterViewAnimator.cs Keeps adapter accessors safe-callable.
src/​Mono.Android/​Android.Widget/​AdapterView.cs Scopes constructor JNI calls.
src/​Mono.Android/​Android.Widget/​AbsListView.cs Scopes adapter callbacks and accessors.
src/​Mono.Android/​Android.Util/​SparseArray.cs Classifies accessor and scopes JNI calls.
src/​Mono.Android/​Android.Text/​ITextWatcher.cs Scopes watcher initialization.
src/​Mono.Android/​Android.Telephony/​CellInfo.cs Keeps getters safe-callable.
src/​Mono.Android/​Android.Security/​KeyChain.cs Documents certificate handle lifetime.
src/​Mono.Android/​Android.Runtime/​XmlReaderPullParser.cs Scopes parser-reference duplication.
src/​Mono.Android/​Android.Runtime/​XAPeerMembers.cs Scopes threshold-class lookup.
src/​Mono.Android/​Android.Runtime/​OutputStreamAdapter.cs Scopes stream peer initialization.
src/​Mono.Android/​Android.Runtime/​Logger.cs Documents native logging format obligations.
src/​Mono.Android/​Android.Runtime/​JValue.cs Classifies union fields without layout changes.
src/​Mono.Android/​Android.Runtime/​JNIEnvInit.cs Documents initialization contracts and scopes startup.
src/​Mono.Android/​Android.Runtime/​JavaSet.cs Scopes collection JNI operations.
src/​Mono.Android/​Android.Runtime/​JavaObject.cs Scopes conversion and initialization.
src/​Mono.Android/​Android.Runtime/​JavaCollection.cs Fixes removal signature and scopes JNI calls.
src/​Mono.Android/​Android.Runtime/​JavaArray.cs Scopes managed array operations.
src/​Mono.Android/​Android.Runtime/​InputStreamAdapter.cs Scopes stream peer initialization.
src/​Mono.Android/​Android.Runtime/​AndroidRuntime.cs Documents VM construction obligations.
src/​Mono.Android/​Android.Runtime/​AndroidEnvironment.cs Classifies exit import and scopes throwable access.
src/​Mono.Android/​Android.OS/​Handler.cs Scopes callback initialization.
src/​Mono.Android/​Android.OS/​AsyncTask.cs Classifies accessor and scopes JNI calls.
src/​Mono.Android/​Android.Hardware/​HardwareBuffer.cs Keeps managed APIs safe-callable.
src/​Mono.Android/​Android.Graphics/​Bitmap.cs Scopes native bitmap operations.
src/​Mono.Android/​Android.Content/​CursorLoader.cs Keeps override safe-callable.
src/​Mono.Android/​Android.Bluetooth/​BluetoothGattServerCallback.cs Scopes callback marshaling.
src/​Mono.Android/​Android.App/​Notification.cs Scopes vibration-array field access.
src/​Mono.Android/​Android.App/​Application.cs Scopes application-context lookup.
src/​Mono.Android/​Android.App/​Activity.cs Scopes reporting and callback operations.
src/​Mono.Android/​Android.App/​ActionBar.cs Scopes dispatcher initialization.
src/​Mono.Android/​Android.Animation/​ValueAnimator.cs Keeps duration override safe-callable.
src/​Mono.Android/​Android.Animation/​AnimatorSet.cs Keeps duration override safe-callable.
src/​Mono.Android/​Android.Animation/​Animator.cs Scopes dispatcher initialization.
src/​Microsoft.Android.Runtime.NativeAOT/​Microsoft.Android.Runtime.NativeAOT.csproj Enables preview syntax without activation.
src/​Microsoft.Android.Runtime.NativeAOT/​Java.Interop/​JreRuntime.cs Documents VM creation contracts.
src/​Microsoft.Android.Runtime.NativeAOT/​Android.Runtime.NativeAOT/​Logging.cs Documents logging format obligations.
src/​Microsoft.Android.Runtime.NativeAOT/​Android.Runtime.NativeAOT/​JavaInteropRuntime.cs Documents and scopes native startup calls.
external/​Java.Interop/​src/​Java.Interop/​Java.Interop/​JniRuntime.cs Adds caller-unsafe runtime construction contract.
build-tools/​jnienv-gen/​JNIEnvEntries.cs Defines operation-specific caller obligations.
build-tools/​jnienv-gen/​Generator.cs Emits safety contracts and implementation regions.

Review effort: Balanced


if (typeof (T).IsValueType)
AssertCompatibleArrayTypes (array_ptr, destElementType: typeof (T));

This branch has not been deployed

No deployments
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.

2 participants