diff --git a/build-tools/installers/create-installers.targets b/build-tools/installers/create-installers.targets index ce7f3580148..c307f183d1d 100644 --- a/build-tools/installers/create-installers.targets +++ b/build-tools/installers/create-installers.targets @@ -129,12 +129,16 @@ <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)bundletool.jar" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_net6.jar" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_net6.jar" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_trimmable_net6.jar" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_net6.dex" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_net6.dex" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_trimmable_net6.dex" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_clr.jar" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_clr.jar" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_trimmable_clr.jar" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_clr.dex" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_fastdev_clr.dex" ExcludeFromLegacy="true" /> + <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)java_runtime_trimmable_clr.dex" ExcludeFromLegacy="true" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)manifestmerger.jar" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)proguard-android.txt" /> <_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)protobuf-net.dll" /> diff --git a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets index dd2ab89a61e..3eb8349a09d 100644 --- a/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets +++ b/src/Xamarin.Android.Build.Tasks/Xamarin.Android.Common.targets @@ -1391,14 +1391,24 @@ because xbuild doesn't support framework reference assemblies. + - + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_net6.jar - + + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_trimmable_net6.jar + + + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_clr.jar + + + <_RuntimeJar>$(MSBuildThisFileDirectory)\java_runtime_trimmable_clr.jar + $(IntermediateOutputPath)release-net6.txt ..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\clr\MonoPackageManager.java + <_RuntimeOutput Include="$(OutputPath)java_runtime_trimmable_net6.jar"> + $(OutputPath)java_runtime_trimmable_net6.jar + $(OutputPath)java_runtime_trimmable_net6.dex + $(IntermediateOutputPath)release-trimmable-net6 + $(IntermediateOutputPath)release-trimmable-net6.txt + ..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\clr\MonoPackageManager.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyObject.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyThrowable.java + java-trimmable\net\dot\jni\internal\JavaProxyObject.java;java-trimmable\net\dot\jni\internal\JavaProxyThrowable.java + <_RuntimeOutput Include="$(OutputPath)java_runtime_fastdev_net6.jar"> $(OutputPath)java_runtime_fastdev_net6.jar $(OutputPath)java_runtime_fastdev_net6.dex @@ -39,6 +47,14 @@ $(IntermediateOutputPath)release-clr.txt ..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\MonoPackageManager.java + <_RuntimeOutput Include="$(OutputPath)java_runtime_trimmable_clr.jar"> + $(OutputPath)java_runtime_trimmable_clr.jar + $(OutputPath)java_runtime_trimmable_clr.dex + $(IntermediateOutputPath)release-trimmable-clr + $(IntermediateOutputPath)release-trimmable-clr.txt + ..\..\src-ThirdParty\bazel\java\mono\android\debug\MultiDexLoader.java;java\mono\android\debug-net6\BuildConfig.java;java\mono\android\debug\BuildConfig.java;java\mono\android\release\BuildConfig.java;java\mono\android\MonoPackageManager.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyObject.java;$(JavaInteropSourceDirectory)\src\Java.Interop\java\net\dot\jni\internal\JavaProxyThrowable.java + java-trimmable\net\dot\jni\internal\JavaProxyObject.java;java-trimmable\net\dot\jni\internal\JavaProxyThrowable.java + <_RuntimeOutput Include="$(OutputPath)java_runtime_fastdev_clr.jar"> $(OutputPath)java_runtime_fastdev_clr.jar $(OutputPath)java_runtime_fastdev_clr.dex @@ -50,14 +66,17 @@ + + <_RuntimeSource Include="@(AllRuntimeSource)" /> <_RuntimeSource Remove="%(_RuntimeOutput.RemoveItems)" /> + <_RuntimeSource Include="%(_RuntimeOutput.AddItems)" Condition=" '%(_RuntimeOutput.AddItems)' != '' " /> managedReferences = new ArrayList (); + + // This trimmable runtime copy cannot use Java.Interop's native object methods: + // those are registered through ManagedPeer.registerNativeMembers, which is not + // supported in the trimmable typemap path. + // Trimmable proxies use Java identity semantics: equals/hashCode/toString + // do not delegate to the wrapped .NET object. + @Override + public boolean equals (Object obj) + { + return this == obj; + } + + @Override + public int hashCode () + { + return System.identityHashCode (this); + } + + @Override + public String toString () + { + return super.toString (); + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyThrowable.java b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyThrowable.java new file mode 100644 index 00000000000..4b5bad5874d --- /dev/null +++ b/src/java-runtime/java-trimmable/net/dot/jni/internal/JavaProxyThrowable.java @@ -0,0 +1,31 @@ +package net.dot.jni.internal; + +import java.util.ArrayList; + +import net.dot.jni.GCUserPeerable; + +/* package */ final class JavaProxyThrowable + extends java.lang.Error + implements GCUserPeerable +{ + ArrayList managedReferences = new ArrayList (); + + public JavaProxyThrowable () + { + } + + public JavaProxyThrowable (String message) + { + super (message); + } + + public void jiAddManagedReference (java.lang.Object obj) + { + managedReferences.add (obj); + } + + public void jiClearManagedReferences () + { + managedReferences.clear (); + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs index 44671758e54..3ccae0da3ad 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/TrimmableTypeMapTypeManagerTests.cs @@ -179,6 +179,84 @@ public void RegisteredPeer_CanCreateGenericHolder () Assert.AreEqual (42, holder.Value); } + [Test] + public void JavaProxyObject_ValueMarshalerUsesProxyType () + { + AssumeTrimmableTypeMapEnabled (); + + var value = new object (); + var marshaler = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (typeof (object)); + var state = marshaler.CreateObjectReferenceArgumentState (value); + + try { + Assert.AreEqual ("net/dot/jni/internal/JavaProxyObject", JNIEnv.GetClassNameFromInstance (state.ReferenceValue.Handle)); + } finally { + marshaler.DestroyArgumentState (value, ref state); + } + } + + [Test] + public void JavaProxyObject_CanBeUsedInObjectArray () + { + AssumeTrimmableTypeMapEnabled (); + + using var values = new JavaObjectArray (1); + values [0] = new object (); + + Assert.AreEqual ("[Ljava/lang/Object;", values.GetJniTypeName ()); + } + + [Test] + public void JavaProxyObject_ObjectMethodsUseJavaIdentitySemantics () + { + AssumeTrimmableTypeMapEnabled (); + + var value = new object (); + var other = new object (); + var marshaler = JniEnvironment.Runtime.ValueManager.GetValueMarshaler (typeof (object)); + var state = marshaler.CreateObjectReferenceArgumentState (value); + var otherState = marshaler.CreateObjectReferenceArgumentState (other); + + try { + var localProxy = state.ReferenceValue.NewLocalRef (); + var localOtherProxy = otherState.ReferenceValue.NewLocalRef (); + + try { + IntPtr proxyClass = JNIEnv.GetObjectClass (localProxy.Handle); + try { + IntPtr equals = JNIEnv.GetMethodID (proxyClass, "equals", "(Ljava/lang/Object;)Z"); + IntPtr hashCode = JNIEnv.GetMethodID (proxyClass, "hashCode", "()I"); + IntPtr toString = JNIEnv.GetMethodID (proxyClass, "toString", "()Ljava/lang/String;"); + var systemClass = JniEnvironment.Types.FindClass ("java/lang/System"); + + try { + IntPtr identityHashCode = JNIEnv.GetStaticMethodID (systemClass.Handle, "identityHashCode", "(Ljava/lang/Object;)I"); + + Assert.IsTrue (JNIEnv.CallBooleanMethod (localProxy.Handle, equals, new JValue (localProxy.Handle))); + Assert.IsFalse (JNIEnv.CallBooleanMethod (localProxy.Handle, equals, new JValue (localOtherProxy.Handle))); + Assert.AreEqual ( + JNIEnv.CallStaticIntMethod (systemClass.Handle, identityHashCode, new JValue (localProxy.Handle)), + JNIEnv.CallIntMethod (localProxy.Handle, hashCode)); + var proxyString = JNIEnv.GetString (JNIEnv.CallObjectMethod (localProxy.Handle, toString), JniHandleOwnership.TransferLocalRef); + Assert.IsTrue ( + proxyString.StartsWith ("net.dot.jni.internal.JavaProxyObject@", StringComparison.Ordinal), + proxyString); + } finally { + JniObjectReference.Dispose (ref systemClass); + } + } finally { + JNIEnv.DeleteLocalRef (proxyClass); + } + } finally { + JniObjectReference.Dispose (ref localProxy); + JniObjectReference.Dispose (ref localOtherProxy); + } + } finally { + marshaler.DestroyArgumentState (other, ref otherState); + marshaler.DestroyArgumentState (value, ref state); + } + } + [Test] public void TryGetArrayType_PrimitiveLeaf_DoesNotRequireRankMapEntry () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs index d69013f4063..ccff36c26c4 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Xamarin.Android.RuntimeTests/NUnitInstrumentation.cs @@ -34,29 +34,6 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer) // net.dot.jni.test.CallVirtualFromConstructorDerived Java class not in APK "Java.InteropTests.InvokeVirtualFromConstructorTests", - // net.dot.jni.internal.JavaProxyObject. calls - // net.dot.jni.ManagedPeer.registerNativeMembers, which the trimmable - // typemap path rejects (Native methods must be registered by JCW - // static initializer blocks). Fixing this requires a parallel - // Android-trimmable variant of JavaProxyObject.java that registers - // its native equals/hashCode/toString via mono.android.Runtime.register - // — an architectural change tracked separately from the JavaCast / JavaAs - // work in this PR. See https://github.com/dotnet/android/issues/11170. - "Java.InteropTests.JavaObjectArray_object_ContractTest", - - // Same root cause as above (JavaProxyObject static init). - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateGenericArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateGenericObjectReferenceArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateGenericValue", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateObjectReferenceArgumentState", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.JniValueMarshalerContractTests`1.CreateValue", - "Java.InteropTests.JniValueMarshaler_object_ContractTests.SpecificTypesAreUsed", - - // net.dot.jni.internal.JavaProxyThrowable static init — same JavaProxy* - // root cause as the JavaProxyObject exclusions above. - "Java.InteropTests.JavaExceptionTests.InnerExceptionIsNotAProxy", - // JNI method remapping not supported in trimmable typemap "Java.InteropTests.JniPeerMembersTests.ReplaceInstanceMethodName", "Java.InteropTests.JniPeerMembersTests.ReplaceInstanceMethodWithStaticMethod",