diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs index c9b620ff18f..afa9ea3c3d9 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/JniSignatureHelper.cs @@ -109,14 +109,15 @@ static JniParamKind ParseSingleType (string sig, ref int i) /// Encodes a JNI type as its CLR equivalent for [UnmanagedCallersOnly] UCO wrapper signatures. /// /// - /// JNI boolean (Z) maps to byte (unsigned, blittable for the JNI ABI). + /// JNI boolean (Z) maps to byte and JNI char (C) maps to ushort, + /// preserving the JNI ABI with blittable UCO parameter types. /// public static void EncodeClrType (SignatureTypeEncoder encoder, JniParamKind kind) { switch (kind) { case JniParamKind.Boolean: encoder.Byte (); break; // JNI jboolean is unsigned byte; blittable for UCO case JniParamKind.Byte: encoder.SByte (); break; - case JniParamKind.Char: encoder.Char (); break; + case JniParamKind.Char: encoder.UInt16 (); break; case JniParamKind.Short: encoder.Int16 (); break; case JniParamKind.Int: encoder.Int32 (); break; case JniParamKind.Long: encoder.Int64 (); break; diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs index b9126586bf4..ecd92f53e19 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs @@ -204,6 +204,7 @@ sealed record UcoMethodData /// JNI method signature, e.g., "(Landroid/os/Bundle;)V". Used to determine CLR parameter types. /// public required string JniSignature { get; init; } + } /// @@ -228,6 +229,16 @@ sealed record UcoConstructorData /// JNI constructor signature, e.g., "(Landroid/content/Context;)V". Used for RegisterNatives registration. /// public required string JniSignature { get; init; } + + /// + /// Managed constructor parameter type names, in declaration order. + /// + public IReadOnlyList ManagedParameterTypes { get; init; } = []; + + /// + /// True when this Java constructor has a matching managed constructor on the target type. + /// + public bool HasManagedConstructor { get; init; } } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs index 7547c5ac38f..21ce7d7d66d 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ModelBuilder.cs @@ -365,6 +365,8 @@ static void BuildUcoConstructors (JavaPeerInfo peer, JavaPeerProxyData proxy) ManagedTypeName = peer.ManagedTypeName, AssemblyName = peer.AssemblyName, }, + ManagedParameterTypes = ctor.ManagedParameterTypes, + HasManagedConstructor = ctor.HasManagedConstructor, }); } } diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs index f48ebd8c72a..18fe552561d 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs @@ -611,6 +611,12 @@ public void Throw () SetStack (0); } + public void PopValue () + { + Encoder.OpCode (ILOpCode.Pop); + Pop (1); + } + public void OpCode (ILOpCode code) { Encoder.OpCode (code); diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs index 67761e0d617..1eb16ffbf8b 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs @@ -87,7 +87,9 @@ sealed class TypeMapAssemblyEmitter TypeReferenceHandle _jniObjectReferenceOptionsRef; TypeReferenceHandle _iAndroidCallableWrapperRef; TypeReferenceHandle _jniEnvRef; + TypeReferenceHandle _javaLangObjectRef; TypeReferenceHandle _systemTypeRef; + TypeReferenceHandle _systemArrayRef; TypeReferenceHandle _runtimeTypeHandleRef; TypeReferenceHandle _jniTypeRef; TypeReferenceHandle _notSupportedExceptionRef; @@ -100,7 +102,13 @@ sealed class TypeMapAssemblyEmitter MemberReferenceHandle _notSupportedExceptionCtorRef; MemberReferenceHandle _jniObjectReferenceCtorRef; MemberReferenceHandle _jniEnvDeleteRefRef; + MemberReferenceHandle _jniEnvGetStringRef; + MemberReferenceHandle _jniEnvGetArrayRef; + MemberReferenceHandle _javaLangObjectGetObjectRef; MemberReferenceHandle _shouldSkipActivationRef; + MemberReferenceHandle _getActivationPeerRef; + MemberReferenceHandle _setActivationPeerReferenceRef; + MemberReferenceHandle _markActivationPeerReplaceableRef; MemberReferenceHandle _waitForBridgeProcessingRef; MemberReferenceHandle _androidEnvironmentUnhandledExceptionRef; MemberReferenceHandle _ucoAttrCtorRef; @@ -228,6 +236,8 @@ void EmitTypeReferences () metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JniHandleOwnership")); _jniEnvRef = metadata.AddTypeReference (_pe.MonoAndroidRef, metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("JNIEnv")); + _javaLangObjectRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Java.Lang"), metadata.GetOrAddString ("Object")); _jniObjectReferenceRef = metadata.AddTypeReference (_javaInteropRef, metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniObjectReference")); _jniObjectReferenceTypeRef = metadata.AddTypeReference (_javaInteropRef, @@ -238,6 +248,8 @@ void EmitTypeReferences () metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("IAndroidCallableWrapper")); _systemTypeRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Type")); + _systemArrayRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, + metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Array")); _runtimeTypeHandleRef = metadata.AddTypeReference (_pe.SystemRuntimeRef, metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle")); _jniTypeRef = metadata.AddTypeReference (_javaInteropRef, @@ -357,12 +369,56 @@ void EmitMemberReferences () p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); })); + _jniEnvGetStringRef = _pe.AddMemberRef (_jniEnvRef, "GetString", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().String (), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + })); + + _jniEnvGetArrayRef = _pe.AddMemberRef (_jniEnvRef, "GetArray", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Type ().Type (_systemArrayRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + p.AddParameter ().Type ().Type (_systemTypeRef, false); + })); + + _javaLangObjectGetObjectRef = _pe.AddMemberRef (_javaLangObjectRef, "GetObject", + sig => sig.MethodSignature ().Parameters (3, + rt => rt.Type ().Type (_iJavaPeerableRef, false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true); + p.AddParameter ().Type ().Type (_systemTypeRef, false); + })); + // JavaPeerProxy.ShouldSkipActivation(IntPtr) -> bool (static method) _shouldSkipActivationRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "ShouldSkipActivation", sig => sig.MethodSignature ().Parameters (1, rt => rt.Type ().Boolean (), p => { p.AddParameter ().Type ().IntPtr (); })); + _getActivationPeerRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "GetActivationPeer", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().Type (_iJavaPeerableRef, false), + p => { p.AddParameter ().Type ().IntPtr (); })); + + _setActivationPeerReferenceRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "SetActivationPeerReference", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Void (), + p => { + p.AddParameter ().Type ().Type (_iJavaPeerableRef, false); + p.AddParameter ().Type ().IntPtr (); + })); + + _markActivationPeerReplaceableRef = _pe.AddMemberRef (_javaPeerProxyNonGenericRef, "MarkActivationPeerReplaceable", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Void (), + p => p.AddParameter ().Type ().IntPtr ())); + _waitForBridgeProcessingRef = _pe.AddMemberRef (_androidRuntimeInternalRef, "WaitForBridgeProcessing", sig => sig.MethodSignature ().Parameters (0, rt => rt.Void (), p => { })); @@ -918,6 +974,18 @@ MemberReferenceHandle AddActivationCtorRef (EntityHandle declaringTypeRef) })); } + MemberReferenceHandle AddManagedCtorRef (EntityHandle declaringTypeRef, IReadOnlyList parameterTypes, string defaultAssemblyName) + { + var blob = new BlobBuilder (32); + blob.WriteByte (0x20); // HASTHIS + blob.WriteCompressedInteger (parameterTypes.Count); + blob.WriteByte (0x01); // ELEMENT_TYPE_VOID + foreach (var parameterType in parameterTypes) { + WriteManagedTypeSignature (blob, parameterType, defaultAssemblyName); + } + return _pe.Metadata.AddMemberReference (declaringTypeRef, _pe.Metadata.GetOrAddString (".ctor"), _pe.Metadata.GetOrAddBlob (blob)); + } + MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco, JavaPeerProxyData proxy) { var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature); @@ -1011,9 +1079,8 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy $"UCO constructor wrapper requires an activation ctor for '{uco.TargetType.ManagedTypeName}'"); // UCO constructor wrappers must match the JNI native method signature exactly. - // Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters - // are not forwarded because we create the managed peer using the - // activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor. + // jnienv and self are followed by the Java constructor parameters, which are + // forwarded when scanner metadata can identify the matching managed constructor. var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature); int paramCount = 2 + jniParams.Count; @@ -1028,19 +1095,65 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy // Open generic types can't be activated because Java construction cannot provide the type arguments. if (proxy.IsGenericDefinition) { - var openGenericHandle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => { + var openGenericHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { enc.LoadString (_pe.Metadata.GetOrAddUserString ("Constructing instances of generic types from Java is not supported, as the type parameters cannot be determined.")); enc.NewObject (_notSupportedExceptionCtorRef, parameterCount: 1); enc.Throw (); - }), + }, EncodeUcoConstructorLocals_Standard); AddUnmanagedCallersOnlyAttribute (openGenericHandle); return openGenericHandle; } + if (proxy.InvokerType != null) { + var invokerTypeRef = _pe.ResolveTypeRef (proxy.InvokerType); + MethodDefinitionHandle invokerHandle; + if (proxy.InvokerActivationCtorStyle == ActivationCtorStyle.JavaInterop) { + var ctorRef = AddJavaInteropActivationCtorRef (invokerTypeRef); + invokerHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { + enc.LoadLocalAddress (3); // jniRef + enc.LoadArgument (1); // self + enc.LoadConstantI4 (0); // JniObjectReferenceType.Invalid + enc.Call (_jniObjectReferenceCtorRef, parameterCount: 2, isInstance: true); + + enc.LoadLocalAddress (3); // ref jniRef + enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy + enc.NewObject (ctorRef, parameterCount: 2); + enc.PopValue (); + + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, + EncodeUcoConstructorLocals_JavaInterop); + } else { + var ctorRef = AddActivationCtorRef (invokerTypeRef); + invokerHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { + enc.LoadArgument (1); // self + enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + enc.NewObject (ctorRef, parameterCount: 2); + enc.PopValue (); + + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, + EncodeUcoConstructorLocals_Standard); + } + AddUnmanagedCallersOnlyAttribute (invokerHandle); + return invokerHandle; + } + + if (uco.HasManagedConstructor && uco.ManagedParameterTypes.Count == jniParams.Count) { + var ctorRef = AddManagedCtorRef (targetTypeRef, uco.ManagedParameterTypes, uco.TargetType.AssemblyName); + var managedCtorHandle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => EmitManagedConstructorActivation (enc, targetTypeRef, ctorRef, uco.ManagedParameterTypes, jniParams, uco.TargetType.AssemblyName), + blob => EncodeUcoConstructorLocals_DefaultConstructor (blob, targetTypeRef)); + AddUnmanagedCallersOnlyAttribute (managedCtorHandle); + return managedCtorHandle; + } + MethodDefinitionHandle handle; if (activationCtor.Style == ActivationCtorStyle.JavaInterop) { var ctorRef = AddJavaInteropActivationCtorRef ( @@ -1051,10 +1164,8 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy // 1: JniRuntime? (runtime) — out-parameter for BeginMarshalMethod // 2: Exception (e) — catch variable // 3: JniObjectReference (jniRef) — needed for JavaInterop-style activation - handle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => { + handle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { if (!activationCtor.IsOnLeafType) { enc.LoadToken (targetTypeRef); enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); @@ -1071,13 +1182,15 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy enc.LoadLocalAddress (3); // ref jniRef enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy enc.NewObject (ctorRef, parameterCount: 2); - enc.OpCode (ILOpCode.Pop); + enc.PopValue (); } else { enc.LoadLocalAddress (3); // ref jniRef enc.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy enc.Call (ctorRef, parameterCount: 2, isInstance: true); } - }), + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, EncodeUcoConstructorLocals_JavaInterop); } else { var ctorRef = AddActivationCtorRef ( @@ -1087,15 +1200,13 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy // 0: JniTransition (envp) — out-parameter for BeginMarshalMethod // 1: JniRuntime? (runtime) — out-parameter for BeginMarshalMethod // 2: Exception (e) — catch variable - handle = _pe.EmitBody (uco.WrapperName, - MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, - encodeSig, - (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, enc => { + handle = EmitUcoConstructorBody (uco.WrapperName, encodeSig, + enc => { if (activationCtor.IsOnLeafType) { enc.LoadArgument (1); // self enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer enc.NewObject (ctorRef, parameterCount: 2); - enc.OpCode (ILOpCode.Pop); + enc.PopValue (); } else { enc.LoadToken (targetTypeRef); enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); @@ -1106,13 +1217,67 @@ MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxy enc.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer enc.Call (ctorRef, parameterCount: 2, isInstance: true); } - }), + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + }, EncodeUcoConstructorLocals_Standard); } AddUnmanagedCallersOnlyAttribute (handle); return handle; } + MethodDefinitionHandle EmitUcoConstructorBody ( + string wrapperName, + Action encodeSig, + Action emitActivation, + Action encodeLocals) + { + return _pe.EmitBody (wrapperName, + MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig, + encodeSig, + (encoder, cfb) => EmitUcoConstructorBodyWithMarshal (encoder, cfb, emitActivation), + encodeLocals); + } + + void EmitManagedConstructorActivation ( + TrackedInstructionEncoder enc, + EntityHandle targetTypeRef, + MemberReferenceHandle ctorRef, + IReadOnlyList managedParameterTypes, + IReadOnlyList jniParams, + string defaultAssemblyName) + { + var havePeer = enc.DefineLabel (); + + enc.LoadArgument (1); + enc.Call (_getActivationPeerRef, parameterCount: 1, returnsValue: true); + enc.CastClass (targetTypeRef); + enc.StoreLocal (4); + + enc.LoadLocal (4); + enc.Branch (ILOpCode.Brtrue, havePeer); + + enc.LoadToken (targetTypeRef); + enc.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + enc.Call (_getUninitializedObjectRef, parameterCount: 1, returnsValue: true); + enc.CastClass (targetTypeRef); + enc.StoreLocal (4); + + enc.LoadLocal (4); + enc.LoadArgument (1); // self + enc.Call (_setActivationPeerReferenceRef, parameterCount: 2); + + enc.MarkLabel (havePeer); + enc.LoadLocal (4); + for (int i = 0; i < managedParameterTypes.Count; i++) { + EmitManagedConstructorArgument (enc, managedParameterTypes [i], jniParams [i], i + 2, defaultAssemblyName); + } + enc.Call (ctorRef, managedParameterTypes.Count, isInstance: true); + + enc.LoadArgument (1); // self + enc.Call (_markActivationPeerReplaceableRef, parameterCount: 1); + } + /// /// Emits the common try/catch/finally marshal-method wrapper pattern used by all /// non-generic UCO constructor bodies: @@ -1185,6 +1350,111 @@ void EmitUcoConstructorBodyWithMarshal (TrackedInstructionEncoder encoder, Contr cfb.AddFinallyRegion (tryStart, finallyStart, finallyStart, afterAll); } + void EmitManagedConstructorArgument (TrackedInstructionEncoder encoder, string managedType, JniParamKind jniKind, int argumentIndex, string defaultAssemblyName) + { + if (jniKind != JniParamKind.Object) { + encoder.LoadArgument (argumentIndex); + return; + } + + if (managedType == "System.String") { + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.Call (_jniEnvGetStringRef, parameterCount: 2, returnsValue: true); + return; + } + + if (TryGetSzArrayElementType (managedType, out var elementType)) { + var arrayType = ResolveManagedTypeHandle (managedType, defaultAssemblyName); + var elementTypeHandle = ResolveManagedTypeHandle (elementType, defaultAssemblyName); + + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.LoadToken (elementTypeHandle); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_jniEnvGetArrayRef, parameterCount: 3, returnsValue: true); + encoder.CastClass (arrayType); + return; + } + + var managedTypeHandle = ResolveManagedTypeHandle (managedType, defaultAssemblyName); + encoder.LoadArgument (argumentIndex); + encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer + encoder.LoadToken (managedTypeHandle); + encoder.Call (_getTypeFromHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_javaLangObjectGetObjectRef, parameterCount: 3, returnsValue: true); + encoder.CastClass (managedTypeHandle); + } + + EntityHandle ResolveManagedTypeHandle (string managedType, string defaultAssemblyName) + { + if (TryGetSzArrayElementType (managedType, out var elementType)) { + var blob = new BlobBuilder (32); + blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY + WriteManagedTypeSignature (blob, elementType, defaultAssemblyName); + return _pe.Metadata.AddTypeSpecification (_pe.Metadata.GetOrAddBlob (blob)); + } + + return _pe.ResolveTypeRef (new TypeRefData { + ManagedTypeName = managedType, + AssemblyName = GetAssemblyNameForManagedType (managedType, defaultAssemblyName), + }); + } + + static bool TryGetSzArrayElementType (string managedType, out string elementType) + { + if (managedType.EndsWith ("[]", StringComparison.Ordinal)) { + elementType = managedType.Substring (0, managedType.Length - 2); + return true; + } + + elementType = ""; + return false; + } + + void WriteManagedTypeSignature (BlobBuilder blob, string managedType, string defaultAssemblyName) + { + if (TryGetSzArrayElementType (managedType, out var elementType)) { + blob.WriteByte (0x1D); // ELEMENT_TYPE_SZARRAY + WriteManagedTypeSignature (blob, elementType, defaultAssemblyName); + return; + } + + switch (managedType) { + case "System.Boolean": blob.WriteByte (0x02); return; + case "System.Char": blob.WriteByte (0x03); return; + case "System.SByte": blob.WriteByte (0x04); return; + case "System.Byte": blob.WriteByte (0x05); return; + case "System.Int16": blob.WriteByte (0x06); return; + case "System.UInt16": blob.WriteByte (0x07); return; + case "System.Int32": blob.WriteByte (0x08); return; + case "System.UInt32": blob.WriteByte (0x09); return; + case "System.Int64": blob.WriteByte (0x0A); return; + case "System.UInt64": blob.WriteByte (0x0B); return; + case "System.Single": blob.WriteByte (0x0C); return; + case "System.Double": blob.WriteByte (0x0D); return; + case "System.String": blob.WriteByte (0x0E); return; + case "System.Object": blob.WriteByte (0x1C); return; + } + + var typeHandle = ResolveManagedTypeHandle (managedType, defaultAssemblyName); + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (typeHandle)); + } + + static string GetAssemblyNameForManagedType (string managedType, string defaultAssemblyName) + { + if (managedType.StartsWith ("System.", StringComparison.Ordinal)) { + return "System.Runtime"; + } + if (managedType.StartsWith ("Android.", StringComparison.Ordinal) || + managedType.StartsWith ("Java.", StringComparison.Ordinal) || + managedType.StartsWith ("Javax.", StringComparison.Ordinal)) { + return "Mono.Android"; + } + return defaultAssemblyName; + } + /// /// LOCAL_SIG for UCO constructors without JavaInterop-style activation. /// Locals: 0=JniTransition, 1=JniRuntime, 2=Exception. @@ -1226,6 +1496,31 @@ void EncodeUcoConstructorLocals_JavaInterop (BlobBuilder blob) blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef)); } + /// + /// LOCAL_SIG for UCO constructors that invoke a no-arg managed constructor. + /// Locals: 0=JniTransition, 1=JniRuntime, 2=Exception, 3=JniObjectReference, 4=target type. + /// + void EncodeUcoConstructorLocals_DefaultConstructor (BlobBuilder blob, EntityHandle targetTypeRef) + { + blob.WriteByte (0x07); // LOCAL_SIG + blob.WriteCompressedInteger (5); + // local 0: JniTransition (valuetype) + blob.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniTransitionRef)); + // local 1: JniRuntime (class) + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniRuntimeRef)); + // local 2: Exception (class) + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_exceptionRef)); + // local 3: JniObjectReference (valuetype) + blob.WriteByte (0x11); // ELEMENT_TYPE_VALUETYPE + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (_jniObjectReferenceRef)); + // local 4: target type (class) + blob.WriteByte (0x12); // ELEMENT_TYPE_CLASS + blob.WriteCompressedInteger (CodedIndex.TypeDefOrRefOrSpec (targetTypeRef)); + } + void EmitRegisterNatives (JavaPeerProxyData proxy, Dictionary wrapperHandles) { diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs index ee285b42798..97606a4b5af 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerInfo.cs @@ -224,6 +224,17 @@ public sealed record MarshalMethodInfo /// public string? SuperArgumentsString { get; init; } + /// + /// Managed method parameter type names, in declaration order. + /// + public IReadOnlyList ManagedParameterTypes { get; init; } = []; + + /// + /// True when this constructor registration maps to an actual managed constructor on the target type. + /// False for constructors inherited from Java base types that only exist to generate Java source. + /// + public bool HasManagedConstructor { get; init; } + /// /// True if this method was collected from an implemented interface /// (Pass 4: CollectInterfaceMethodImplementations), not from the type itself. @@ -267,6 +278,16 @@ public sealed record JavaConstructorInfo /// Null for [Register] constructors. /// public string? SuperArgumentsString { get; init; } + + /// + /// Managed constructor parameter type names, in declaration order. + /// + public IReadOnlyList ManagedParameterTypes { get; init; } = []; + + /// + /// True when this Java constructor has a matching managed constructor on the target type. + /// + public bool HasManagedConstructor { get; init; } } /// diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs index dda7460271c..40959a5de2c 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Scanner/JavaPeerScanner.cs @@ -556,6 +556,7 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, continue; } if (!alreadyRegisteredSignatures.Contains (signature)) { + var managedParameterTypes = TryGetMatchingPublicConstructorParameterTypes (typeDef, index, baseCtor.Method); methods.Add (new MarshalMethodInfo { JniName = baseCtor.RegisterInfo.JniName, JniSignature = signature, @@ -563,6 +564,8 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, ManagedMethodName = ".ctor", NativeCallbackName = "n_ctor", IsConstructor = true, + ManagedParameterTypes = managedParameterTypes ?? [], + HasManagedConstructor = managedParameterTypes != null, }); alreadyRegisteredSignatures.Add (signature); } @@ -614,6 +617,8 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, NativeCallbackName = "n_ctor", IsConstructor = true, SuperArgumentsString = "", + ManagedParameterTypes = sig.ParameterTypes, + HasManagedConstructor = true, }); alreadyRegisteredSignatures.Add (jniSignature); } @@ -621,6 +626,27 @@ void CollectBaseConstructorChain (TypeDefinition typeDef, AssemblyIndex index, } } + IReadOnlyList? TryGetMatchingPublicConstructorParameterTypes (TypeDefinition typeDef, AssemblyIndex index, MethodDefinition baseCtor) + { + foreach (var methodHandle in typeDef.GetMethods ()) { + var methodDef = index.Reader.GetMethodDefinition (methodHandle); + if (index.Reader.GetString (methodDef.Name) != ".ctor") { + continue; + } + if ((methodDef.Attributes & MethodAttributes.MemberAccessMask) != MethodAttributes.Public) { + continue; + } + if (!HaveIdenticalParameterTypes (methodDef, baseCtor)) { + continue; + } + + var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); + return sig.ParameterTypes; + } + + return null; + } + string? BuildJniCtorSignature (MethodSignature sig) { var sb = new System.Text.StringBuilder (); @@ -892,6 +918,7 @@ static void AddMarshalMethod (List methods, RegisterInfo regi bool isExport = exportInfo is not null; string managedName = index.Reader.GetString (methodDef.Name); string jniSignature = registerInfo.Signature ?? "()V"; + var sig = methodDef.DecodeSignature (SignatureTypeProvider.Instance, genericContext: default); string declaringTypeName = ""; string declaringAssemblyName = ""; @@ -911,6 +938,8 @@ static void AddMarshalMethod (List methods, RegisterInfo regi JavaAccess = isExport ? GetJavaAccess (methodDef.Attributes & MethodAttributes.MemberAccessMask) : null, ThrownNames = exportInfo?.ThrownNames, SuperArgumentsString = exportInfo?.SuperArgumentsString, + ManagedParameterTypes = sig.ParameterTypes, + HasManagedConstructor = isConstructor, }); } @@ -1553,6 +1582,8 @@ static List BuildJavaConstructors (List JniSignature = mm.JniSignature, ConstructorIndex = ctorIndex, SuperArgumentsString = mm.SuperArgumentsString, + ManagedParameterTypes = mm.ManagedParameterTypes, + HasManagedConstructor = mm.HasManagedConstructor, }); ctorIndex++; } diff --git a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs index 88f7fa32244..fec5337d347 100644 --- a/src/Mono.Android/Java.Interop/JavaPeerProxy.cs +++ b/src/Mono.Android/Java.Interop/JavaPeerProxy.cs @@ -93,15 +93,45 @@ protected JavaPeerProxy ( /// already registered the peer). /// public static bool ShouldSkipActivation (IntPtr jniSelf) + { + var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference); + if (peer != null && !IsActivationPeer (peer)) { + return true; + } + return JniEnvironment.WithinNewObjectScope; + } + + public static IJavaPeerable? GetActivationPeer (IntPtr jniSelf) + { + var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference); + return peer != null && IsActivationPeer (peer) ? peer : null; + } + + public static void SetActivationPeerReference (IJavaPeerable peer, IntPtr jniSelf) + { + var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); + peer.SetPeerReference (reference); + peer.SetJniIdentityHashCode (JniEnvironment.References.GetIdentityHashCode (reference)); + } + + public static void MarkActivationPeerReplaceable (IntPtr jniSelf) { var reference = new JniObjectReference (jniSelf, JniObjectReferenceType.Invalid); var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (reference); if (peer == null) { - return false; + return; } + + peer.SetJniManagedPeerState (peer.JniManagedPeerState | JniManagedPeerStates.Replaceable); + } + + static bool IsActivationPeer (IJavaPeerable peer) + { var state = peer.JniManagedPeerState; - return (state & JniManagedPeerStates.Activatable) != JniManagedPeerStates.Activatable - && (state & JniManagedPeerStates.Replaceable) != JniManagedPeerStates.Replaceable; + return (state & JniManagedPeerStates.Activatable) == JniManagedPeerStates.Activatable + || (state & JniManagedPeerStates.Replaceable) == JniManagedPeerStates.Replaceable; } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs index 44b5bff4f85..2355555e9ac 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/FixtureTestBase.cs @@ -91,7 +91,7 @@ private protected static JavaPeerInfo MakeAcwPeer (string jniName, string manage return MakePeerWithActivation (jniName, managedName, asmName) with { DoNotGenerateAcw = false, JavaConstructors = new List { - new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V" }, + new JavaConstructorInfo { ConstructorIndex = 0, JniSignature = "()V", HasManagedConstructor = true }, }, MarshalMethods = new List { new MarshalMethodInfo { @@ -100,6 +100,7 @@ private protected static JavaPeerInfo MakeAcwPeer (string jniName, string manage JniSignature = "()V", ManagedMethodName = ".ctor", IsConstructor = true, + HasManagedConstructor = true, }, }, }; diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index e84d8bcf392..63e166b11b3 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -288,7 +288,7 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer () } [Fact] - public void Generate_InheritedCtor_ReferencesGuardAndActivationCtor () + public void Generate_InheritedCtor_NctorUcoCallsDefaultConstructor () { var peers = ScanFixtures (); var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity"); @@ -302,18 +302,24 @@ public void Generate_InheritedCtor_ReferencesGuardAndActivationCtor () Assert.Contains ("ShouldSkipActivation", memberNames); Assert.Contains ("GetUninitializedObject", memberNames); + Assert.Contains ("SetActivationPeerReference", memberNames); + Assert.Contains ("MarkActivationPeerReplaceable", memberNames); Assert.DoesNotContain ("Invoke", memberNames); Assert.DoesNotContain ("ActivateInstance", memberNames); Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames); - Assert.NotEmpty (FindCtorMemberRefs (reader, "Android.App", "Activity", + // The new no-arg nctor codegen calls the target type's parameterless .ctor() + // directly, not the legacy (IntPtr, JniHandleOwnership) activation ctor on the base. + Assert.NotEmpty (FindCtorMemberRefs (reader, "MyApp", "SimpleActivity")); + Assert.Empty (FindCtorMemberRefs (reader, "Android.App", "Activity", "System.IntPtr", "Android.Runtime.JniHandleOwnership")); + var nctorMethodHandle = FindNctorUcoMethod (reader); Assert.False (nctorMethodHandle.IsNil, "SimpleActivity should have a nctor_*_uco method"); } [Fact] - public void Generate_InheritedJavaInteropCtor_ReferencesActivationCtor () + public void Generate_InheritedJavaInteropCtor_NctorUcoCallsDefaultConstructor () { var peer = MakeAcwPeer ("test/JiInheritedTarget", "Test.JiInheritedTarget", "TestAsm") with { ActivationCtor = new ActivationCtorInfo { @@ -332,10 +338,17 @@ public void Generate_InheritedJavaInteropCtor_ReferencesActivationCtor () var memberNames = GetMemberRefNames (reader); Assert.Contains ("GetUninitializedObject", memberNames); + Assert.Contains ("SetActivationPeerReference", memberNames); + Assert.Contains ("MarkActivationPeerReplaceable", memberNames); Assert.DoesNotContain ("Invoke", memberNames); - Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "JiInheritedBase", + // The new no-arg nctor codegen calls the target type's parameterless .ctor() + // directly, not the JI-style (JniObjectReference&, JniObjectReferenceOptions) + // activation ctor on the inherited base. + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "JiInheritedTarget")); + Assert.Empty (FindCtorMemberRefs (reader, "Test", "JiInheritedBase", "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions")); + var nctorMethodHandle = FindNctorUcoMethod (reader); Assert.False (nctorMethodHandle.IsNil, "The ACW peer should have a nctor_*_uco method"); } @@ -539,6 +552,41 @@ sig.ParameterTypes [0].Contains ("JniObjectReference")) { Assert.True (foundByRefCtor, "Expected to find a JI-style invoker .ctor with byref JniObjectReference parameter"); } + [Fact] + public void Generate_UcoConstructor_InvokerUsesXamarinAndroidActivationCtor () + { + var peer = MakeAcwPeer ("test/AbstractCtorTarget", "Test.AbstractCtorTarget", "TestAsm") with { + InvokerTypeName = "Test.AbstractCtorInvoker", + }; + + using var stream = GenerateAssembly (new [] { peer }, "InvokerCtorUcoTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "AbstractCtorInvoker", + "System.IntPtr", "Android.Runtime.JniHandleOwnership")); + Assert.Empty (FindCtorMemberRefs (reader, "Test", "AbstractCtorTarget", + "System.IntPtr", "Android.Runtime.JniHandleOwnership")); + } + + [Fact] + public void Generate_UcoConstructor_InvokerUsesJavaInteropActivationCtor () + { + var peer = MakeAcwPeer ("test/AbstractJiCtorTarget", "Test.AbstractJiCtorTarget", "TestAsm") with { + InvokerTypeName = "Test.AbstractJiCtorInvoker", + InvokerActivationCtorStyle = ActivationCtorStyle.JavaInterop, + }; + + using var stream = GenerateAssembly (new [] { peer }, "JiInvokerCtorUcoTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "AbstractJiCtorInvoker", + "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions")); + Assert.Empty (FindCtorMemberRefs (reader, "Test", "AbstractJiCtorTarget", + "Java.Interop.JniObjectReference&", "Java.Interop.JniObjectReferenceOptions")); + } + [Fact] public void Generate_JiStyleCtor_EmitsDeleteRefCall () { @@ -672,7 +720,7 @@ public void Generate_AcwProxy_HasUnmanagedCallersOnlyAttribute () [Theory] [InlineData (1, 0x05)] // Boolean → byte (unsigned) for JNI ABI [InlineData (2, 0x04)] // Byte → sbyte - [InlineData (3, 0x03)] // Char → char + [InlineData (3, 0x07)] // Char → uint16 (blittable JNI jchar) [InlineData (4, 0x06)] // Short → int16 [InlineData (5, 0x08)] // Int → int32 [InlineData (6, 0x0A)] // Long → int64 @@ -739,7 +787,6 @@ public void EncodeClrTypeForCallback_Void_Throws () [Theory] [InlineData (2)] // Byte - [InlineData (3)] // Char [InlineData (4)] // Short [InlineData (5)] // Int [InlineData (6)] // Long @@ -1160,6 +1207,98 @@ public void Generate_UcoConstructor_HasMarshalMethodMetadataAndExceptionRegions Assert.Contains (regions, r => r.Kind == ExceptionRegionKind.Finally); } + [Fact] + public void Generate_UcoConstructor_ParameterizedPrimitiveCtorCallsManagedConstructor () + { + string jniSignature = "(ZBCSIJFD)V"; + var managedTypes = new [] { + "System.Boolean", + "System.SByte", + "System.Char", + "System.Int16", + "System.Int32", + "System.Int64", + "System.Single", + "System.Double", + }; + var peer = MakeAcwPeer ("test/PrimitiveCtorArgs", "Test.PrimitiveCtorArgs", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = jniSignature, + ManagedParameterTypes = managedTypes, + HasManagedConstructor = true, + }, + }, + MarshalMethods = new List { + new MarshalMethodInfo { + JniName = "", + NativeCallbackName = "n_ctor", + JniSignature = jniSignature, + ManagedMethodName = ".ctor", + IsConstructor = true, + ManagedParameterTypes = managedTypes, + }, + }, + }; + + using var stream = GenerateAssembly (new [] { peer }, "ParameterizedPrimitiveCtorTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "PrimitiveCtorArgs", managedTypes)); + + var nctorMethod = reader.GetMethodDefinition (FindNctorUcoMethod (reader)); + var nctorSignature = nctorMethod.DecodeSignature (SignatureTypeProvider.Instance, null); + Assert.Equal ( + new [] { "System.IntPtr", "System.IntPtr", "System.Byte", "System.SByte", "System.UInt16", "System.Int16", "System.Int32", "System.Int64", "System.Single", "System.Double" }, + nctorSignature.ParameterTypes); + } + + [Fact] + public void Generate_UcoConstructor_ParameterizedObjectCtorUsesExplicitMarshalHelpers () + { + string jniSignature = "(Ljava/lang/String;[I[Ljava/lang/String;Landroid/content/Context;)V"; + var managedTypes = new [] { + "System.String", + "System.Int32[]", + "System.String[]", + "Android.Content.Context", + }; + var peer = MakeAcwPeer ("test/ObjectCtorArgs", "Test.ObjectCtorArgs", "TestAsm") with { + JavaConstructors = new List { + new JavaConstructorInfo { + ConstructorIndex = 0, + JniSignature = jniSignature, + ManagedParameterTypes = managedTypes, + HasManagedConstructor = true, + }, + }, + MarshalMethods = new List { + new MarshalMethodInfo { + JniName = "", + NativeCallbackName = "n_ctor", + JniSignature = jniSignature, + ManagedMethodName = ".ctor", + IsConstructor = true, + ManagedParameterTypes = managedTypes, + }, + }, + }; + + using var stream = GenerateAssembly (new [] { peer }, "ParameterizedObjectCtorTest"); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + + Assert.NotEmpty (FindCtorMemberRefs (reader, "Test", "ObjectCtorArgs", managedTypes)); + + var memberNames = GetMemberRefNames (reader); + Assert.Contains ("GetString", memberNames); + Assert.Contains ("GetArray", memberNames); + Assert.Contains ("GetObject", memberNames); + Assert.DoesNotContain ("FromJniHandle", memberNames); + } + [Fact] public void Generate_UcoConstructor_JiStyle_HasExceptionRegions () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs new file mode 100644 index 00000000000..1fd51eff2e1 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/ConstructorActivationTests.cs @@ -0,0 +1,956 @@ +using System; + +using Android.App; +using Android.Content; +using Android.Runtime; +using Android.Util; +using Android.Views; + +using Java.Interop; + +using NUnit.Framework; + +namespace Java.InteropTests +{ + [TestFixture] + public class ConstructorActivationTests + { + [Test] + public void JavaSideDefaultConstructorRunsOnceAndRegistersPeer () + { + ConstructorActivationDefault.Reset (); + + using (var instance = CreateFromJava ("()V")) { + Assert.IsNotNull (instance); + Assert.AreEqual (1, ConstructorActivationDefault.ConstructorInvocations); + Assert.AreEqual (1, instance.ConstructorOrdinal); + AssertRegisteredSame (instance); + } + } + + [Test] + public void ManagedConstructionDoesNotReenterThroughJavaConstructorActivation () + { + ConstructorActivationDefault.Reset (); + + using (var instance = new ConstructorActivationDefault ()) { + Assert.IsNotNull (instance); + Assert.AreEqual (1, ConstructorActivationDefault.ConstructorInvocations); + Assert.AreEqual (1, instance.ConstructorOrdinal); + AssertRegisteredSame (instance); + } + } + + [Test] + public void CreateInstanceDoesNotActivateManagedConstructorInsideNewObjectScope () + { + ConstructorActivationDefault.Reset (); + + IntPtr handle = JNIEnv.CreateInstance (typeof (ConstructorActivationDefault), "()V"); + try { + var peer = JniEnvironment.Runtime.ValueManager.PeekPeer (new JniObjectReference (handle)); + + Assert.AreEqual (0, ConstructorActivationDefault.ConstructorInvocations); + Assert.IsNull (peer); + } finally { + JNIEnv.DeleteLocalRef (handle); + } + } + + [Test] + public void JavaSideConstructorReinvokesExistingActivatablePeer () + { + ConstructorActivationDefault.Reset (); + + using (var instance = new ConstructorActivationDefault ()) { + var peer = (IJavaPeerable) instance; + peer.SetJniManagedPeerState (instance.JniManagedPeerState | JniManagedPeerStates.Activatable | JniManagedPeerStates.Replaceable); + + JNIEnv.FinishCreateInstance (instance.Handle, "()V"); + + Assert.AreEqual (2, ConstructorActivationDefault.ConstructorInvocations); + Assert.AreEqual (2, instance.ConstructorOrdinal); + AssertRegisteredSame (instance); + } + } + + [Test] + public void JavaSideThrowingConstructorPropagatesException () + { + ConstructorActivationThrowing.Reset (); + + var exception = Assert.Catch (() => CreateFromJavaExpectingConstructorException ("()V")); + Assert.IsNotNull (exception); + Assert.AreEqual (1, ConstructorActivationThrowing.ConstructorInvocations); + Assert.IsTrue (exception.ToString ().Contains (ConstructorActivationThrowing.ExceptionMessage)); + } + + [Test] + public void JavaSideContextConstructorForwardsArgument () + { + ConstructorActivationContextView.Reset (); + + using (var instance = CreateFromJava ( + "(Landroid/content/Context;)V", + new JValue (Application.Context))) { + Assert.AreEqual (1, ConstructorActivationContextView.ConstructorInvocations); + Assert.IsNotNull (instance.ContextValue); + Assert.AreEqual (Application.Context.Handle, instance.ContextValue.Handle); + } + } + + [Test] + public void JavaSideContextAttributeSetConstructorForwardsArguments () + { + ConstructorActivationAttributeSetView.Reset (); + + using (var instance = CreateFromJava ( + "(Landroid/content/Context;Landroid/util/AttributeSet;)V", + new JValue (Application.Context), + JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationAttributeSetView.ConstructorInvocations); + Assert.IsNotNull (instance.ContextValue); + Assert.AreEqual (Application.Context.Handle, instance.ContextValue.Handle); + Assert.IsNull (instance.AttributeSetValue); + } + } + + [Test] + public void JavaSideContextAttributeSetStyleConstructorForwardsArguments () + { + ConstructorActivationStyledView.Reset (); + + using (var instance = CreateFromJava ( + "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", + new JValue (Application.Context), + JValue.Zero, + new JValue (42))) { + Assert.AreEqual (1, ConstructorActivationStyledView.ConstructorInvocations); + Assert.IsNotNull (instance.ContextValue); + Assert.AreEqual (Application.Context.Handle, instance.ContextValue.Handle); + Assert.IsNull (instance.AttributeSetValue); + Assert.AreEqual (42, instance.DefStyleAttrValue); + } + } + + [Test] + public void JavaSideBooleanConstructorForwardsTrue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(Z)V", new JValue (true))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (true, instance.BooleanValue); + } + } + + [Test] + public void JavaSideBooleanConstructorForwardsFalse () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(Z)V", new JValue (false))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (false, instance.BooleanValue); + } + } + + [Test] + public void JavaSideByteConstructorForwardsSignedValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(B)V", new JValue ((sbyte) -12))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ((sbyte) -12, instance.ByteValue); + } + } + + [Test] + public void JavaSideCharConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(C)V", new JValue ('Q'))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ('Q', instance.CharValue); + } + } + + [Test] + public void JavaSideShortConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("(S)V", new JValue ((short) -1234))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ((short) -1234, instance.ShortValue); + } + } + + [Test] + public void JavaSideIntConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(I)V", new JValue (0x1234567))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (0x1234567, instance.IntValue); + } + } + + [Test] + public void JavaSideLongConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(J)V", new JValue (0x123456789L))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (0x123456789L, instance.LongValue); + } + } + + [Test] + public void JavaSideFloatConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(F)V", new JValue (12.5f))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (12.5f, instance.FloatValue); + } + } + + [Test] + public void JavaSideDoubleConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ("(D)V", new JValue (-42.25))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (-42.25, instance.DoubleValue); + } + } + + [Test] + public void JavaSidePrimitiveMixedConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + + using (var instance = CreateFromJava ( + "(IZJ)V", + new JValue (12345), + new JValue (true), + new JValue (9876543210L))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (12345, instance.MultiIntValue); + Assert.AreEqual (true, instance.MultiBooleanValue); + Assert.AreEqual (9876543210L, instance.MultiLongValue); + } + } + + [Test] + public void JavaSideStringConstructorForwardsValue () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var value = new Java.Lang.String ("hello constructor")) + using (var instance = CreateFromJava ("(Ljava/lang/String;)V", new JValue (value))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("hello constructor", instance.StringValue); + } + } + + [Test] + public void JavaSideStringConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("(Ljava/lang/String;)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.StringValue); + } + } + + [Test] + public void JavaSideTwoStringConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var first = new Java.Lang.String ("first")) + using (var second = new Java.Lang.String ("second")) + using (var instance = CreateFromJava ( + "(Ljava/lang/String;Ljava/lang/String;)V", + new JValue (first), + new JValue (second))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("first", instance.FirstStringValue); + Assert.AreEqual ("second", instance.SecondStringValue); + } + } + + [Test] + public void JavaSideTwoStringConstructorForwardsNullSecondValue () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var first = new Java.Lang.String ("first")) + using (var instance = CreateFromJava ( + "(Ljava/lang/String;Ljava/lang/String;)V", + new JValue (first), + JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("first", instance.FirstStringValue); + Assert.IsNull (instance.SecondStringValue); + } + } + + [Test] + public void JavaSideStringIntConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var text = new Java.Lang.String ("string-int")) + using (var instance = CreateFromJava ( + "(Ljava/lang/String;I)V", + new JValue (text), + new JValue (17))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("string-int", instance.StringIntStringValue); + Assert.AreEqual (17, instance.StringIntValue); + } + } + + [Test] + public void JavaSideIntArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([I)V", + JNIEnv.NewArray (new [] { 1, 2, 3, 5 }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { 1, 2, 3, 5 }, instance.IntArrayValue); + } + } + + [Test] + public void JavaSideIntArrayConstructorForwardsEmptyArray () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([I)V", + JNIEnv.NewArray (Array.Empty ()))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNotNull (instance.IntArrayValue); + Assert.AreEqual (0, instance.IntArrayValue.Length); + } + } + + [Test] + public void JavaSideIntArrayConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("([I)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.IntArrayValue); + } + } + + [Test] + public void JavaSideStringIntArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var label = new Java.Lang.String ("string-array")) + { + IntPtr array = JNIEnv.NewArray (new [] { 8, 13, 21 }); + try { + using (var instance = CreateFromJava ( + "(Ljava/lang/String;[I)V", + new JValue (label), + new JValue (array))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual ("string-array", instance.StringArrayLabel); + Assert.AreEqual (new [] { 8, 13, 21 }, instance.StringArrayValues); + } + } finally { + JNIEnv.DeleteLocalRef (array); + } + } + } + + [Test] + public void JavaSideBooleanArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([Z)V", + JNIEnv.NewArray (new [] { true, false, true }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { true, false, true }, instance.BooleanArrayValue); + } + } + + [Test] + public void JavaSideByteArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([B)V", + JNIEnv.NewArray (new byte [] { 1, 127, 255 }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new byte [] { 1, 127, 255 }, instance.ByteArrayValue); + } + } + + [Test] + public void JavaSideStringArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([Ljava/lang/String;)V", + JNIEnv.NewArray (new [] { "red", "green", "blue" }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { "red", "green", "blue" }, instance.StringArrayValue); + } + } + + [Test] + public void JavaSideStringArrayConstructorForwardsNullElement () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([Ljava/lang/String;)V", + JNIEnv.NewArray (new [] { "red", null, "blue" }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.AreEqual (new [] { "red", null, "blue" }, instance.StringArrayValue); + } + } + + [Test] + public void JavaSideStringArrayConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("([Ljava/lang/String;)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.StringArrayValue); + } + } + + [Test] + public void JavaSideObjectArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var first = new Java.Lang.String ("object-array-value")) + using (var instance = CreateFromJavaWithLocalArray ( + "([Ljava/lang/Object;)V", + JNIEnv.NewArray (new Java.Lang.Object [] { first, Application.Context }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNotNull (instance.ObjectArrayValue); + Assert.AreEqual (2, instance.ObjectArrayValue.Length); + Assert.AreEqual ("object-array-value", instance.ObjectArrayValue [0].ToString ()); + Assert.AreEqual (Application.Context.Handle, instance.ObjectArrayValue [1].Handle); + } + } + + [Test] + public void JavaSideObjectArrayConstructorForwardsNull () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJava ("([Ljava/lang/Object;)V", JValue.Zero)) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNull (instance.ObjectArrayValue); + } + } + + [Test] + public void JavaSideNestedIntArrayConstructorForwardsValues () + { + ConstructorActivationMarshalObject.Reset (); + AssumeTrimmableConstructorParameterMarshalling (); + + using (var instance = CreateFromJavaWithLocalArray ( + "([[I)V", + JNIEnv.NewArray (new [] { + new [] { 1, 2 }, + new [] { 3, 4, 5 }, + }))) { + Assert.AreEqual (1, ConstructorActivationMarshalObject.ConstructorInvocations); + Assert.IsNotNull (instance.NestedIntArrayValue); + Assert.AreEqual (new [] { 1, 2 }, instance.NestedIntArrayValue [0]); + Assert.AreEqual (new [] { 3, 4, 5 }, instance.NestedIntArrayValue [1]); + } + } + + static void AssumeTrimmableConstructorParameterMarshalling () + { + if (!Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { + Assert.Ignore ("Legacy TypeManager.n_Activate does not marshal string, short, or array constructor parameters; this case validates trimmable constructor UCO parameter marshalling."); + } + } + + static T CreateFromJava (string constructorSignature, params JValue [] arguments) + where T : Java.Lang.Object + { + var instance = JNIEnv.StartCreateInstance (typeof (T), constructorSignature, arguments); + JNIEnv.FinishCreateInstance (instance, constructorSignature, arguments); + var result = Java.Lang.Object.GetObject (instance, JniHandleOwnership.TransferLocalRef); + Assert.IsNotNull (result); + return result; + } + + static T CreateFromJavaWithLocalArray (string constructorSignature, IntPtr array) + where T : Java.Lang.Object + { + try { + return CreateFromJava (constructorSignature, new JValue (array)); + } finally { + JNIEnv.DeleteLocalRef (array); + } + } + + static void CreateFromJavaExpectingConstructorException (string constructorSignature, params JValue [] arguments) + where T : Java.Lang.Object + { + IntPtr instance = IntPtr.Zero; + try { + instance = JNIEnv.StartCreateInstance (typeof (T), constructorSignature, arguments); + JNIEnv.FinishCreateInstance (instance, constructorSignature, arguments); + } finally { + if (instance != IntPtr.Zero) { + JNIEnv.DeleteLocalRef (instance); + } + } + } + + static void AssertRegisteredSame (T instance) + where T : Java.Lang.Object + { + var registered = Java.Lang.Object.GetObject (instance.Handle, JniHandleOwnership.DoNotTransfer); + try { + Assert.AreSame (instance, registered); + if (Microsoft.Android.Runtime.RuntimeFeature.TrimmableTypeMap) { + Assert.AreEqual (Java.Lang.JavaSystem.IdentityHashCode (instance), instance.JniIdentityHashCode); + } + } finally { + if (registered != null && !object.ReferenceEquals (instance, registered)) + registered.Dispose (); + } + } + } + + [Register ("net/dot/android/test/ConstructorActivationThrowing")] + public class ConstructorActivationThrowing : Java.Lang.Object + { + public const string ExceptionMessage = "constructor activation throw"; + + public static int ConstructorInvocations; + + public ConstructorActivationThrowing () + { + ConstructorInvocations++; + throw new InvalidOperationException (ExceptionMessage); + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationBase", DoNotGenerateAcw = true)] + public class ConstructorActivationBase : Java.Lang.Object + { + public ConstructorActivationBase () + { + } + + public ConstructorActivationBase (bool value) + { + } + + public ConstructorActivationBase (sbyte value) + { + } + + public ConstructorActivationBase (char value) + { + } + + public ConstructorActivationBase (short value) + { + } + + public ConstructorActivationBase (int value) + { + } + + public ConstructorActivationBase (long value) + { + } + + public ConstructorActivationBase (float value) + { + } + + public ConstructorActivationBase (double value) + { + } + + public ConstructorActivationBase (int number, bool flag, long longValue) + { + } + + public ConstructorActivationBase (string value) + { + } + + public ConstructorActivationBase (string first, string second) + { + } + + public ConstructorActivationBase (string text, int number) + { + } + + public ConstructorActivationBase (int[] value) + { + } + + public ConstructorActivationBase (string label, int[] value) + { + } + + public ConstructorActivationBase (bool[] value) + { + } + + public ConstructorActivationBase (byte[] value) + { + } + + public ConstructorActivationBase (string[] value) + { + } + + public ConstructorActivationBase (Java.Lang.Object[] value) + { + } + + public ConstructorActivationBase (int[][] value) + { + } + + public ConstructorActivationBase (IntPtr handle, JniHandleOwnership transfer) + : base (handle, transfer) + { + } + } + + [Register ("net/dot/android/test/ConstructorActivationDefault")] + public class ConstructorActivationDefault : Java.Lang.Object + { + public static int ConstructorInvocations; + + public int ConstructorOrdinal; + + public ConstructorActivationDefault () + { + ConstructorOrdinal = ++ConstructorInvocations; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationMarshalObject")] + public class ConstructorActivationMarshalObject : ConstructorActivationBase + { + public static int ConstructorInvocations; + + public bool BooleanValue; + public sbyte ByteValue; + public char CharValue; + public short ShortValue; + public int IntValue; + public long LongValue; + public float FloatValue; + public double DoubleValue; + public int MultiIntValue; + public bool MultiBooleanValue; + public long MultiLongValue; + public string StringValue; + public string FirstStringValue; + public string SecondStringValue; + public string StringIntStringValue; + public int StringIntValue; + public int[] IntArrayValue; + public string StringArrayLabel; + public int[] StringArrayValues; + public bool[] BooleanArrayValue; + public byte[] ByteArrayValue; + public string[] StringArrayValue; + public Java.Lang.Object[] ObjectArrayValue; + public int[][] NestedIntArrayValue; + + [Register (".ctor", "(Z)V", "")] + public ConstructorActivationMarshalObject (bool value) + : base (value) + { + ConstructorInvocations++; + BooleanValue = value; + } + + [Register (".ctor", "(B)V", "")] + public ConstructorActivationMarshalObject (sbyte value) + : base (value) + { + ConstructorInvocations++; + ByteValue = value; + } + + [Register (".ctor", "(C)V", "")] + public ConstructorActivationMarshalObject (char value) + : base (value) + { + ConstructorInvocations++; + CharValue = value; + } + + [Register (".ctor", "(S)V", "")] + public ConstructorActivationMarshalObject (short value) + : base (value) + { + ConstructorInvocations++; + ShortValue = value; + } + + [Register (".ctor", "(I)V", "")] + public ConstructorActivationMarshalObject (int value) + : base (value) + { + ConstructorInvocations++; + IntValue = value; + } + + [Register (".ctor", "(J)V", "")] + public ConstructorActivationMarshalObject (long value) + : base (value) + { + ConstructorInvocations++; + LongValue = value; + } + + [Register (".ctor", "(F)V", "")] + public ConstructorActivationMarshalObject (float value) + : base (value) + { + ConstructorInvocations++; + FloatValue = value; + } + + [Register (".ctor", "(D)V", "")] + public ConstructorActivationMarshalObject (double value) + : base (value) + { + ConstructorInvocations++; + DoubleValue = value; + } + + [Register (".ctor", "(IZJ)V", "")] + public ConstructorActivationMarshalObject (int number, bool flag, long longValue) + : base (number, flag, longValue) + { + ConstructorInvocations++; + MultiIntValue = number; + MultiBooleanValue = flag; + MultiLongValue = longValue; + } + + [Register (".ctor", "(Ljava/lang/String;)V", "")] + public ConstructorActivationMarshalObject (string value) + : base (value) + { + ConstructorInvocations++; + StringValue = value; + } + + [Register (".ctor", "(Ljava/lang/String;Ljava/lang/String;)V", "")] + public ConstructorActivationMarshalObject (string first, string second) + : base (first, second) + { + ConstructorInvocations++; + FirstStringValue = first; + SecondStringValue = second; + } + + [Register (".ctor", "(Ljava/lang/String;I)V", "")] + public ConstructorActivationMarshalObject (string text, int number) + : base (text, number) + { + ConstructorInvocations++; + StringIntStringValue = text; + StringIntValue = number; + } + + [Register (".ctor", "([I)V", "")] + public ConstructorActivationMarshalObject (int[] value) + : base (value) + { + ConstructorInvocations++; + IntArrayValue = value; + } + + [Register (".ctor", "(Ljava/lang/String;[I)V", "")] + public ConstructorActivationMarshalObject (string label, int[] value) + : base (label, value) + { + ConstructorInvocations++; + StringArrayLabel = label; + StringArrayValues = value; + } + + [Register (".ctor", "([Z)V", "")] + public ConstructorActivationMarshalObject (bool[] value) + : base (value) + { + ConstructorInvocations++; + BooleanArrayValue = value; + } + + [Register (".ctor", "([B)V", "")] + public ConstructorActivationMarshalObject (byte[] value) + : base (value) + { + ConstructorInvocations++; + ByteArrayValue = value; + } + + [Register (".ctor", "([Ljava/lang/String;)V", "")] + public ConstructorActivationMarshalObject (string[] value) + : base (value) + { + ConstructorInvocations++; + StringArrayValue = value; + } + + [Register (".ctor", "([Ljava/lang/Object;)V", "")] + public ConstructorActivationMarshalObject (Java.Lang.Object[] value) + : base (value) + { + ConstructorInvocations++; + ObjectArrayValue = value; + } + + [Register (".ctor", "([[I)V", "")] + public ConstructorActivationMarshalObject (int[][] value) + : base (value) + { + ConstructorInvocations++; + NestedIntArrayValue = value; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationContextView")] + public class ConstructorActivationContextView : View + { + public static int ConstructorInvocations; + + public Context ContextValue; + + [Register (".ctor", "(Landroid/content/Context;)V", "")] + public ConstructorActivationContextView (Context context) + : base (context) + { + ConstructorInvocations++; + ContextValue = context; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationAttributeSetView")] + public class ConstructorActivationAttributeSetView : View + { + public static int ConstructorInvocations; + + public Context ContextValue; + public IAttributeSet AttributeSetValue; + + [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;)V", "")] + public ConstructorActivationAttributeSetView (Context context, IAttributeSet attrs) + : base (context, attrs) + { + ConstructorInvocations++; + ContextValue = context; + AttributeSetValue = attrs; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } + + [Register ("net/dot/android/test/ConstructorActivationStyledView")] + public class ConstructorActivationStyledView : View + { + public static int ConstructorInvocations; + + public Context ContextValue; + public IAttributeSet AttributeSetValue; + public int DefStyleAttrValue; + + [Register (".ctor", "(Landroid/content/Context;Landroid/util/AttributeSet;I)V", "")] + public ConstructorActivationStyledView (Context context, IAttributeSet attrs, int defStyleAttr) + : base (context, attrs, defStyleAttr) + { + ConstructorInvocations++; + ContextValue = context; + AttributeSetValue = attrs; + DefStyleAttrValue = defStyleAttr; + } + + public static void Reset () + { + ConstructorInvocations = 0; + } + } +} diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs index 0eacc4eddad..13fa0962b58 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Interop/JnienvTest.cs @@ -301,14 +301,11 @@ public void ActivatedDirectObjectSubclassesShouldBeRegistered () } } - // TODO: https://github.com/dotnet/android/issues/11170 — throwable subclass not registered under trimmable typemap [Test] public void ActivatedDirectThrowableSubclassesShouldBeRegistered () { if (Build.VERSION.SdkInt <= BuildVersionCodes.GingerbreadMr1) Assert.Ignore ("Skipping test due to Bug #34141"); - - Console.Error.WriteLine ($"# jonp: BEGIN ActivatedDirectThrowableSubclassesShouldBeRegistered!!!"); using (var ThrowableActivatedFromJava_class = Java.Lang.Class.FromType (typeof (ThrowableActivatedFromJava))) { var ThrowableActivatedFromJava_init = JNIEnv.GetMethodID (ThrowableActivatedFromJava_class.Handle, "", "()V"); @@ -324,7 +321,6 @@ public void ActivatedDirectThrowableSubclassesShouldBeRegistered () Assert.IsTrue (v.Constructed); v.Dispose (); } - Console.Error.WriteLine ($"# jonp: END ActivatedDirectThrowableSubclassesShouldBeRegistered!!!"); } [Test] diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs index b4d921acd17..764fc416379 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Java.Lang/ObjectTest.cs @@ -19,7 +19,6 @@ namespace Java.LangTests [TestFixture] public class ObjectTest { - // TODO: https://github.com/dotnet/android/issues/11170 — trimmable typemap doesn't resolve most-derived managed type [Test] public void GetObject_ReturnsMostDerivedType () { diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj index f1575d1e45d..6831b511fa6 100644 --- a/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/Mono.Android.NET-Tests.csproj @@ -102,6 +102,7 @@ + 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 966903726aa..b03e62bb551 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,12 +34,6 @@ protected NUnitInstrumentation(IntPtr handle, JniHandleOwnership transfer) // net.dot.jni.test.CallVirtualFromConstructorDerived Java class not in APK "Java.InteropTests.InvokeVirtualFromConstructorTests", - // Throwable subclass registration - "Java.InteropTests.JnienvTest.ActivatedDirectThrowableSubclassesShouldBeRegistered", - - // Instance identity after JNI round-trip - "Java.LangTests.ObjectTest.JnienvCreateInstance_RegistersMultipleInstances", - // Global ref leak when inflating custom views "Xamarin.Android.RuntimeTests.CustomWidgetTests.InflateCustomView_ShouldNotLeakGlobalRefs", }; diff --git a/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java new file mode 100644 index 00000000000..20283efcfc9 --- /dev/null +++ b/tests/Mono.Android-Tests/Mono.Android-Tests/java/net/dot/android/test/ConstructorActivationBase.java @@ -0,0 +1,24 @@ +package net.dot.android.test; + +public class ConstructorActivationBase { + public ConstructorActivationBase() {} + public ConstructorActivationBase(boolean value) {} + public ConstructorActivationBase(byte value) {} + public ConstructorActivationBase(char value) {} + public ConstructorActivationBase(short value) {} + public ConstructorActivationBase(int value) {} + public ConstructorActivationBase(long value) {} + public ConstructorActivationBase(float value) {} + public ConstructorActivationBase(double value) {} + public ConstructorActivationBase(int number, boolean flag, long longValue) {} + public ConstructorActivationBase(String value) {} + public ConstructorActivationBase(String first, String second) {} + public ConstructorActivationBase(String text, int number) {} + public ConstructorActivationBase(int[] value) {} + public ConstructorActivationBase(String label, int[] value) {} + public ConstructorActivationBase(boolean[] value) {} + public ConstructorActivationBase(byte[] value) {} + public ConstructorActivationBase(String[] value) {} + public ConstructorActivationBase(Object[] value) {} + public ConstructorActivationBase(int[][] value) {} +}