diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index 56484f7a99a..e2868a3b2c5 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -182,7 +182,7 @@ sealed record UcoMethodData /// An [UnmanagedCallersOnly] static wrapper for a constructor callback. /// Signature must match the full JNI native method signature (jnienv + self + ctor params) /// so the ABI is correct when JNI dispatches the call. -/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)). +/// Body: directly activates the target type using its generated activation ctor. /// sealed record UcoConstructorData { @@ -192,7 +192,7 @@ sealed record UcoConstructorData public required string WrapperName { get; init; } /// - /// Target type to pass to ActivateInstance. + /// Target type to activate in the generated wrapper. /// public required TypeRefData TargetType { get; init; } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs index 56ae75638b2..ddec657a085 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs @@ -239,7 +239,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size) int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1; var handle = Metadata.AddTypeDefinition ( - TypeAttributes.NestedPrivate | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass, + TypeAttributes.NestedAssembly | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass, default, Metadata.GetOrAddString ($"__utf8_{size}"), Metadata.AddTypeReference (SystemRuntimeRef, @@ -259,7 +259,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size) /// public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, Action encodeSig, Action emitIL) - => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null); + => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null, useBranches: false); /// /// Emits a method body and definition with optional local variable declarations. @@ -269,9 +269,19 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, /// and must write the full LOCAL_SIG blob (header 0x07, /// compressed count, then each variable type). /// + /// + /// If true, creates a so the emitted IL can use + /// , , + /// and . + /// public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, Action encodeSig, Action emitIL, Action? encodeLocals) + => EmitBody (name, attrs, encodeSig, emitIL, encodeLocals, useBranches: false); + + public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, + Action encodeSig, Action emitIL, + Action? encodeLocals, bool useBranches) { _sigBlob.Clear (); encodeSig (new BlobEncoder (_sigBlob)); @@ -287,7 +297,8 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs, } _codeBlob.Clear (); - var encoder = new InstructionEncoder (_codeBlob); + ControlFlowBuilder? cfb = useBranches ? new ControlFlowBuilder () : null; + var encoder = new InstructionEncoder (_codeBlob, cfb); emitIL (encoder); while (ILBuilder.Count % 4 != 0) { diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 34bfd15a9b6..a74f4c4955f 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -43,7 +43,9 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// /// [UnmanagedCallersOnly] /// public static void nctor_0_uco(IntPtr jnienv, IntPtr self) -/// => TrimmableNativeRegistration.ActivateInstance(self, typeof(Activity)); +/// => new Activity(self, JniHandleOwnership.DoNotTransfer); +/// // or: var obj = (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity)); +/// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer); /// /// // Registers JNI native methods (ACWs only): /// public void RegisterNatives(JniType jniType) @@ -77,7 +79,6 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _systemTypeRef; TypeReferenceHandle _runtimeTypeHandleRef; TypeReferenceHandle _jniTypeRef; - TypeReferenceHandle _trimmableNativeRegistrationRef; TypeReferenceHandle _notSupportedExceptionRef; TypeReferenceHandle _runtimeHelpersRef; @@ -87,7 +88,7 @@ sealed class TypeMapAssemblyEmitter MemberReferenceHandle _notSupportedExceptionCtorRef; MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; - MemberReferenceHandle _activateInstanceRef; + MemberReferenceHandle _withinNewObjectScopeRef; MemberReferenceHandle _ucoAttrCtorRef; BlobHandle _ucoAttrBlobHandle; MemberReferenceHandle _typeMapAttrCtorRef2Arg; @@ -184,8 +185,6 @@ void EmitTypeReferences () metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle")); _jniTypeRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniType")); - _trimmableNativeRegistrationRef = metadata.AddTypeReference (_pe.MonoAndroidRef, - metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("TrimmableNativeRegistration")); _notSupportedExceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException")); _runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, @@ -240,13 +239,11 @@ void EmitMemberReferences () p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); })); - _activateInstanceRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "ActivateInstance", - sig => sig.MethodSignature ().Parameters (2, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (_systemTypeRef, false); - })); + // JniEnvironment.get_WithinNewObjectScope() -> bool (static property) + _withinNewObjectScopeRef = _pe.AddMemberRef (_jniEnvironmentRef, "get_WithinNewObjectScope", + sig => sig.MethodSignature ().Parameters (0, + rt => rt.Type ().Boolean (), + p => { })); // JniNativeMethod..ctor(byte*, byte*, IntPtr) _jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor", @@ -347,6 +344,16 @@ void EmitTypeMapAssociationAttributeCtorRef () void EmitProxyType (JavaPeerProxyData proxy, Dictionary wrapperHandles) { + if (proxy.IsAcw) { + // RegisterNatives uses RVA-backed UTF-8 fields under . + // Materialize those helper types before adding the proxy TypeDef, otherwise the + // later RegisterNatives method can be attached to the helper type instead. + foreach (var reg in proxy.NativeRegistrations) { + _pe.GetOrAddUtf8Field (reg.JniMethodName); + _pe.GetOrAddUtf8Field (reg.JniSignature); + } + } + var metadata = _pe.Metadata; var typeDefHandle = metadata.AddTypeDefinition ( TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class, @@ -390,7 +397,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary), - // so the wrapper signature must include all parameters to match the ABI. // Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters - // are not forwarded because ActivateInstance creates the managed peer using the + // are not forwarded because we create the managed peer using the // activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor. var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature); int paramCount = 2 + jniParams.Count; - var handle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - sig => sig.MethodSignature ().Parameters (paramCount, - rt => rt.Void (), - p => { - p.AddParameter ().Type ().IntPtr (); // jnienv - p.AddParameter ().Type ().IntPtr (); // self - for (int j = 0; j < jniParams.Count; j++) - JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]); - }), - encoder => { - encoder.LoadArgument (1); // self - encoder.OpCode (ILOpCode.Ldtoken); - encoder.Token (userTypeRef); - encoder.Call (_getTypeFromHandleRef); - encoder.Call (_activateInstanceRef); - encoder.OpCode (ILOpCode.Ret); + Action encodeSig = sig => sig.MethodSignature ().Parameters (paramCount, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().IntPtr (); // jnienv + p.AddParameter ().Type ().IntPtr (); // self + for (int j = 0; j < jniParams.Count; j++) + JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]); }); + // Open generic types can't be activated — emit a no-op UCO. + if (proxy.IsGenericDefinition) { + var noopHandle = _pe.EmitBody (uco.WrapperName, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + encodeSig, + encoder => { + encoder.OpCode (ILOpCode.Ret); + }); + AddUnmanagedCallersOnlyAttribute (noopHandle); + return noopHandle; + } + + MethodDefinitionHandle handle; + if (activationCtor.Style == ActivationCtorStyle.JavaInterop) { + var ctorRef = AddJavaInteropActivationCtorRef ( + activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType)); + + handle = _pe.EmitBody (uco.WrapperName, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + encodeSig, + encoder => { + // Skip activation if the object is being created from managed code + // (e.g., JNIEnv.StartCreateInstance / JNIEnv.NewObject). + var skipLabel = encoder.DefineLabel (); + encoder.Call (_withinNewObjectScopeRef); + encoder.Branch (ILOpCode.Brtrue, skipLabel); + + if (!activationCtor.IsOnLeafType) { + encoder.OpCode (ILOpCode.Ldtoken); + encoder.Token (targetTypeRef); + encoder.Call (_getTypeFromHandleRef); + encoder.Call (_getUninitializedObjectRef); + encoder.OpCode (ILOpCode.Castclass); + encoder.Token (targetTypeRef); + } + + encoder.LoadLocalAddress (0); + encoder.LoadArgument (1); // self + encoder.Call (_jniObjectReferenceCtorRef); + + if (activationCtor.IsOnLeafType) { + encoder.LoadLocalAddress (0); + encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy + encoder.OpCode (ILOpCode.Newobj); + encoder.Token (ctorRef); + encoder.OpCode (ILOpCode.Pop); + } else { + encoder.LoadLocalAddress (0); + encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy + encoder.Call (ctorRef); + } + + encoder.MarkLabel (skipLabel); + encoder.OpCode (ILOpCode.Ret); + }, + EncodeJniObjectReferenceLocal, + useBranches: true); + } else { + var ctorRef = AddActivationCtorRef ( + activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType)); + + handle = _pe.EmitBody (uco.WrapperName, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + encodeSig, + encoder => { + // Skip activation if the object is being created from managed code + var skipLabel = encoder.DefineLabel (); + encoder.Call (_withinNewObjectScopeRef); + encoder.Branch (ILOpCode.Brtrue, skipLabel); + + if (activationCtor.IsOnLeafType) { + encoder.LoadArgument (1); // self + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.OpCode (ILOpCode.Newobj); + encoder.Token (ctorRef); + encoder.OpCode (ILOpCode.Pop); + } else { + encoder.OpCode (ILOpCode.Ldtoken); + encoder.Token (targetTypeRef); + encoder.Call (_getTypeFromHandleRef); + encoder.Call (_getUninitializedObjectRef); + encoder.OpCode (ILOpCode.Castclass); + encoder.Token (targetTypeRef); + + encoder.LoadArgument (1); // self + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.Call (ctorRef); + } + + encoder.MarkLabel (skipLabel); + encoder.OpCode (ILOpCode.Ret); + }, + encodeLocals: null, + useBranches: true); + } AddUnmanagedCallersOnlyAttribute (handle); return handle; } diff --git a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs index f2c535dd8c7..4b46fee009c 100644 --- a/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs +++ b/src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs @@ -147,39 +147,6 @@ internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transf return GetProxyForManagedType (type)?.GetContainerFactory (); } - /// - /// Creates a managed peer instance for a Java object being constructed. - /// Called from generated UCO constructor wrappers (nctor_*_uco). - /// - internal static void ActivateInstance (IntPtr self, Type targetType) - { - var instance = s_instance; - if (instance is null) { - throw new InvalidOperationException ("TrimmableTypeMap has not been initialized."); - } - - // Look up the proxy via JNI class name → TypeMap dictionary. - // We can't use targetType.GetCustomAttribute() because the - // self-application attribute is on the proxy type, not the target type. - var selfRef = new JniObjectReference (self); - var jniClass = JniEnvironment.Types.GetObjectClass (selfRef); - var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass); - JniObjectReference.Dispose (ref jniClass); - - if (className is null || !instance._typeMap.TryGetValue (className, out var proxyType)) { - throw new InvalidOperationException ( - $"Failed to create peer for type '{targetType.FullName}' (jniClass='{className}'). " + - "Ensure the type has a generated proxy in the TypeMap assembly."); - } - - var proxy = proxyType.GetCustomAttribute (inherit: false); - if (proxy is null || proxy.CreateInstance (self, JniHandleOwnership.DoNotTransfer) is null) { - throw new InvalidOperationException ( - $"Failed to create peer for type '{targetType.FullName}'. " + - "Ensure the type has a generated proxy in the TypeMap assembly."); - } - } - [UnmanagedCallersOnly] static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHandle) { @@ -208,4 +175,5 @@ static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHa Environment.FailFast ($"TrimmableTypeMap: Failed to register natives for class '{className}'.", ex); } } + } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index d892b0161da..3141793ba84 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -188,6 +188,25 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer () Assert.True (ctorRefs.Count >= 2, "Should have ctor refs for proxy base + target type"); } + [Fact] + public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation () + { + var peers = ScanFixtures (); + var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity"); + Assert.NotNull (simpleActivity.ActivationCtor); + Assert.NotEqual (simpleActivity.ManagedTypeName, simpleActivity.ActivationCtor.DeclaringTypeName); + + using var stream = GenerateAssembly (new [] { simpleActivity }, "InheritedCtorUcoTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + var memberNames = GetMemberRefNames (reader); + + Assert.Contains ("get_WithinNewObjectScope", memberNames); + Assert.Contains ("GetUninitializedObject", memberNames); + Assert.DoesNotContain ("ActivateInstance", memberNames); + Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames); + } + [Fact] public void Generate_GenericType_ThrowsNotSupportedException () { @@ -422,14 +441,26 @@ public void Generate_AcwProxy_HasRegisterNativesAndUcoMethods () using var pe = new PEReader (stream); var reader = pe.GetMetadataReader (); - var memberNames = GetMemberRefNames (reader); + var proxyType = reader.TypeDefinitions + .Select (h => reader.GetTypeDefinition (h)) + .Single (t => + reader.GetString (t.Namespace) == "_TypeMap.Proxies" && + reader.GetString (t.Name) == "MyApp_MainActivity_Proxy"); + var proxyMethodNames = proxyType.GetMethods () + .Select (h => reader.GetMethodDefinition (h)) + .Select (m => reader.GetString (m.Name)) + .ToList (); + Assert.Contains ("RegisterNatives", proxyMethodNames); + Assert.Contains (proxyMethodNames, name => name.Contains ("_uco_")); - // RegisterNatives is a method definition on the proxy type, not a member reference - var methodDefs = reader.MethodDefinitions + var privateImplDetailsType = reader.TypeDefinitions + .Select (h => reader.GetTypeDefinition (h)) + .Single (t => reader.GetString (t.Name) == ""); + var privateImplMethodNames = privateImplDetailsType.GetMethods () .Select (h => reader.GetMethodDefinition (h)) .Select (m => reader.GetString (m.Name)) .ToList (); - Assert.Contains ("RegisterNatives", methodDefs); + Assert.DoesNotContain ("RegisterNatives", privateImplMethodNames); } [Fact]