Repository navigation
AndroidTypeMapImplementation trimmable removes types that are used in type casts #13286
Copy link
Copy link
Open
Labels
Area: App RuntimeIssues in `libmonodroid.so`.Issues in `libmonodroid.so`.bugComponent does not function as intended.Component does not function as intended.trimmable-type-map
Milestone
Description
Android framework version
Other, net11.0-android (Preview)
Affected platform version
11.0 RC2
Description
When you have <AndroidTypeMapImplementation>trimmable</AndroidTypeMapImplementation> in your csproj and you use a method that retuns Java.Lang.Object and you use a regular type cast. Like in my repro sample I use this.GetSystemService(Context.StorageStatsService) and cast it to StorageStatsManager? then the StorageStatsManager type is trimmed away and the code ends up throwing a InvalidCastException.
var service = (StorageStatsManager?)this.GetSystemService(Context.StorageStatsService);If I use .JavaCast<StorageStatsManager>() the type is retained and all is good.
var service = this.GetSystemService(Context.StorageStatsService).JavaCast<StorageStatsManager>();Steps to Reproduce
I have a small repro here: https://github.com/Cheesebaron/JavaLangObjectCastIssueNet11
Run it with dotnet run --project net11cast/net11cast.csproj /p:Configuration=Release
- Create new Android project targeting
net11.0-android - Add
<AndroidTypeMapImplementation>trimmable</AndroidTypeMapImplementation>in your release config - Use a method like
GetSystemService()which returnsJava.Lang.Object - Type cast the
Java.Lang.Objectto something concrete likeStorageStatsManager - Run in release mode
Did you find any workaround?
No response
Relevant log output
10-27 15:38:04.500 1508 1508 D AndroidRuntime: Shutting down VM
10-27 15:38:04.500 1508 1508 E AndroidRuntime: FATAL EXCEPTION: main
10-27 15:38:04.500 1508 1508 E AndroidRuntime: Process: com.companyname.net11cast, PID: 1508
10-27 15:38:04.500 1508 1508 E AndroidRuntime: android.runtime.JavaProxyThrowable: [System.InvalidCastException]: Unable to cast object of type 'Java.Lang.Object' to type 'Android.App.Usage.StorageStatsManager'.
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.<IsInstanceOf_NoCacheLookup>g____PInvoke|4_0(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.<IsInstanceOf_NoCacheLookup>g____PInvoke|4_0(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.IsInstanceOf_NoCacheLookup + 0x5(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.ChkCastAny_NoCacheLookup + 0x0(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.ChkCast_Helper + 0x16(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.ChkCastClassSpecial + 0x4b(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at System.Runtime.CompilerServices.CastHelpers.ChkCastClass + 0xe(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at net11cast.MainActivity+<GetStorageInformation>d__1.MoveNext + 0x26(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at net11cast.MainActivity.OnCreate + 0x3f(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at Android.App.Activity.__n_OnCreate_Landroid_os_Bundle_ + 0x0(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at Java.Interop.JniMarshal.SafeInvokeAction + 0x10(Unknown Source)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at scrc648536b707fa8b2047.MainActivity.n_OnCreate_Landroid_os_Bundle_(Native Method)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at scrc648536b707fa8b2047.MainActivity.onCreate(r8-map-id-36836d1d3470e82bd783ca3e552fdc39854a9895817d0fb5a9b359a5c4c62e10:1)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:7825)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:7814)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1307)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3246)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3410)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:83)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2017)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.os.Handler.dispatchMessage(Handler.java:107)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.os.Looper.loop(Looper.java:214)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at android.app.ActivityThread.main(ActivityThread.java:7397)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at java.lang.reflect.Method.invoke(Native Method)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
10-27 15:38:04.500 1508 1508 E AndroidRuntime: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)
10-27 15:38:04.508 1508 1508 I MonoDroid: UNHANDLED EXCEPTION:
10-27 15:38:04.519 1508 1508 I MonoDroid: Android.Runtime.JavaProxyThrowable: Exception_WasThrown, Android.Runtime.JavaProxyThrowable
10-27 15:38:04.519 1508 1508 I MonoDroid:
10-27 15:38:04.519 1508 1508 I MonoDroid: --- End of managed Android.Runtime.JavaProxyThrowable stack trace ---
10-27 15:38:04.519 1508 1508 I MonoDroid: android.runtime.JavaProxyThrowable
10-27 15:38:04.519 1508 1508 I MonoDroid:
10-27 15:38:04.525 3252 4030 W ActivityTaskManager: Force finishing activity com.companyname.net11cast/scrc648536b707fa8b2047.MainActivity
10-27 15:38:04.525 3252 1539 I DropBoxManagerService: add tag=data_app_crash isTagEnabled=true flags=0x2
10-27 15:38:04.558 3252 4030 D ActivityTrigger: ActivityTrigger activityPauseTrigger
10-27 15:38:04.566 1508 1508 I Process : Sending signal. PID: 1508 SIG: 9
10-27 15:38:04.570 3252 23572 I AdrenoGLES: QUALCOMM build : 99d190e401, I5e6feab613
10-27 15:38:04.570 3252 23572 I AdrenoGLES: Build Date : 12/06/21
10-27 15:38:04.570 3252 23572 I AdrenoGLES: OpenGL ES Shader Compiler Version: EV031.27.05.11_01
10-27 15:38:04.570 3252 23572 I AdrenoGLES: Local Branch :
10-27 15:38:04.570 3252 23572 I AdrenoGLES: Remote Branch : quic/LA.UM.8.11.1.c25
10-27 15:38:04.570 3252 23572 I AdrenoGLES: Remote Branch : NONE
10-27 15:38:04.570 3252 23572 I AdrenoGLES: Reconstruct Branch : NOTHING
10-27 15:38:04.570 3252 23572 I AdrenoGLES: Build Config : S P 8.0.12 AArch64
10-27 15:38:04.576 3252 23572 I AdrenoGLES: PFP: 0x016ee187, ME: 0x00000000
10-27 15:38:04.589 3252 6425 I ActivityManager: Process com.companyname.net11cast (pid 1508) has died: fore TOPReactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Area: App RuntimeIssues in `libmonodroid.so`.Issues in `libmonodroid.so`.bugComponent does not function as intended.Component does not function as intended.trimmable-type-map
Activity
simonrozsival commentedon Oct 27, 2026
Thanks for the bugreport, @Cheesebaron. I'll take a look. It seems that for some reason we're either trimming out the
StorageStatsManagertype in the first place or we incorrectly create the wrapper inthis.GetSystemService(Context.StorageStatsService)asJava.Lang.Objectinstead ofStorageStatsManager.simonrozsival commentedon Oct 28, 2026
I reproduced this locally and reduced it to a two-file application. The failure is caused by an ILLink TypeMap retention bug; the JNI reference/global-reference wrapping itself is working correctly.
Minimal reproduction
The only required non-default project property is:
The framework type can be reduced from
StorageStatsManagerto the universally availablePowerManager:The cast, property access, and log call all remain in the final linked application, so this is not dead-code elimination.
Results
PowerManagerproxy removed; cast failsIsInteractive=TrueJavaCast<PowerManager>()Inspection of the linked CoreCLR output shows:
castclass Android.OS.PowerManager.Mono.Android.dllstill contains thePowerManagerwrapper and the calledIsInteractivemember._Mono.Android.TypeMap.dllno longer containsAndroid_OS_PowerManager_Proxy.java/lang/Objectproxy and createsJava.Lang.Object; the surviving CLRcastclassthen throws.JavaCast<T>()works because its generic parameter hasDynamicallyAccessedMembers(PublicConstructors | NonPublicConstructors), providing stronger rooting than an ordinary CLR cast.ILLink root cause
The final TypeMap specification from dotnet/runtime#113362 explicitly requires an External Type Map entry to be retained when its trim target is referenced by
isinstorcastclass(and also by several other operations):https://github.com/dotnet/runtime/blob/523349107aca82ae8fa2feaaca5a1767619f1111/docs/design/features/typemap.md#external-type-map
ILLink initially records the Android attribute as a deferred entry keyed by
PowerManager:However,
MarkStep.MarkInstructiononly notifiesTypeMapHandlerforCode.Isinst:https://github.com/dotnet/runtime/blob/523349107aca82ae8fa2feaaca5a1767619f1111/src/tools/illink/src/linker/Linker.Steps/MarkStep.cs#L4097-L4135
There is no
Code.Castclasscase. The instruction falls through to ordinaryMarkType(...), which preserves thePowerManagertype but does not callTypeMapHandler.ProcessType. The deferred external entry therefore remains unmarked and its target,Android_OS_PowerManager_Proxy, is removed.NativeAOT handles both opcodes through the same
ImportCastingdependency and marks the necessary type symbol:https://github.com/dotnet/runtime/blob/523349107aca82ae8fa2feaaca5a1767619f1111/src/coreclr/tools/aot/ILCompiler.Compiler/IL/ILImporter.Scanner.cs#L319-L330
This accounts for the CoreCLR/NativeAOT difference.
The ILLink implementation also currently makes
isinstTypeMap processing conditional on theUnusedTypeChecksoptimization and does not appear to implement the specification's retention rule for the owning type of instancecall/callvirtinstructions. The latter is relevant here because the survivingPowerManager.IsInteractivecall should independently retain the entry.Disabling
unusedtypechecks, as done by commit b90c559 for an earlier trimming issue, does not fix this case: the cast already survives; the missing operation is the TypeMap dependency notification.I did not find an existing dotnet/runtime issue covering this exact
castclassomission. dotnet/runtime#127004 and dotnet/runtime#127504 are related TypeMap retention/order bugs, fixed by dotnet/runtime#127005, but their conditions do not match this failure.The likely runtime fix is to process direct
castclassandisinsttargets before theisinst-specific removal optimization, plus add a linker test where a reference-typecastclassis the sole reason an External Type Map entry is retained. The current TypeMap tests coverisinstand value-type unboxing, but not this case.