Skip to content

Commit 2d453fc

Browse files
[TrimmableTypeMap] Fix UCO constructor activations (#11094)
* [TrimmableTypeMap] Fix UCO constructor activation parity with legacy Refactor UCO constructor wrappers to match legacy ManagedPeer.Construct activation behavior: - Add WithinNewObjectScope guard: skip activation when the object is being created from managed code (JNIEnv.StartCreateInstance/NewObject), preventing double peer creation and GC crashes. - For non-leaf types (activation ctor on a base type), call ActivatePeerFromJavaConstructor at runtime instead of inline IL. This finds the matching managed ctor for the JNI signature (e.g., parameterless for "()V") and invokes it via ActivatePeer, preserving user constructor side effects like field initialization. - For leaf types, inline the activation ctor call directly in the UCO with the WithinNewObjectScope guard. - Emit no-op UCO for open generic type definitions (type params unknown). - Add ControlFlowBuilder support to PEAssemblyBuilder.EmitBody for branch instructions (useBranches parameter). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * [TrimmableTypeMap] Remove UCO reflection fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Inline activation ctor in UCO wrappers - UCO constructors directly call activation ctor (no ActivateInstance indirection) - WithinNewObjectScope guard prevents double peer creation - No-op UCO for open generic type definitions - ControlFlowBuilder support in PEAssemblyBuilder - Remove TrimmableNativeRegistration wrapper and ActivateInstance Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * [TrimmableTypeMap] Remove invalid proxy attribute metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * [TrimmableTypeMap] Strengthen RegisterNatives regression test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent f09c2f2 commit 2d453fc

5 files changed

Lines changed: 179 additions & 78 deletions

File tree

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/Model/TypeMapAssemblyData.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,7 +182,7 @@ sealed record UcoMethodData
182182
/// An [UnmanagedCallersOnly] static wrapper for a constructor callback.
183183
/// Signature must match the full JNI native method signature (jnienv + self + ctor params)
184184
/// so the ABI is correct when JNI dispatches the call.
185-
/// Body: TrimmableNativeRegistration.ActivateInstance(self, typeof(TargetType)).
185+
/// Body: directly activates the target type using its generated activation ctor.
186186
/// </summary>
187187
sealed record UcoConstructorData
188188
{
@@ -192,7 +192,7 @@ sealed record UcoConstructorData
192192
public required string WrapperName { get; init; }
193193

194194
/// <summary>
195-
/// Target type to pass to ActivateInstance.
195+
/// Target type to activate in the generated wrapper.
196196
/// </summary>
197197
public required TypeRefData TargetType { get; init; }
198198

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/PEAssemblyBuilder.cs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
239239
int typeMethodStart = Metadata.GetRowCount (TableIndex.MethodDef) + 1;
240240

241241
var handle = Metadata.AddTypeDefinition (
242-
TypeAttributes.NestedPrivate | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
242+
TypeAttributes.NestedAssembly | TypeAttributes.ExplicitLayout | TypeAttributes.Sealed | TypeAttributes.AnsiClass,
243243
default,
244244
Metadata.GetOrAddString ($"__utf8_{size}"),
245245
Metadata.AddTypeReference (SystemRuntimeRef,
@@ -259,7 +259,7 @@ TypeDefinitionHandle GetOrCreateSizedType (int size)
259259
/// </summary>
260260
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
261261
Action<BlobEncoder> encodeSig, Action<InstructionEncoder> emitIL)
262-
=> EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null);
262+
=> EmitBody (name, attrs, encodeSig, emitIL, encodeLocals: null, useBranches: false);
263263

264264
/// <summary>
265265
/// Emits a method body and definition with optional local variable declarations.
@@ -269,9 +269,19 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
269269
/// <see cref="BlobBuilder"/> and must write the full <c>LOCAL_SIG</c> blob (header 0x07,
270270
/// compressed count, then each variable type).
271271
/// </param>
272+
/// <param name="useBranches">
273+
/// If true, creates a <see cref="ControlFlowBuilder"/> so the emitted IL can use
274+
/// <see cref="InstructionEncoder.DefineLabel"/>, <see cref="InstructionEncoder.Branch"/>,
275+
/// and <see cref="InstructionEncoder.MarkLabel"/>.
276+
/// </param>
272277
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
273278
Action<BlobEncoder> encodeSig, Action<InstructionEncoder> emitIL,
274279
Action<BlobBuilder>? encodeLocals)
280+
=> EmitBody (name, attrs, encodeSig, emitIL, encodeLocals, useBranches: false);
281+
282+
public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
283+
Action<BlobEncoder> encodeSig, Action<InstructionEncoder> emitIL,
284+
Action<BlobBuilder>? encodeLocals, bool useBranches)
275285
{
276286
_sigBlob.Clear ();
277287
encodeSig (new BlobEncoder (_sigBlob));
@@ -287,7 +297,8 @@ public MethodDefinitionHandle EmitBody (string name, MethodAttributes attrs,
287297
}
288298

289299
_codeBlob.Clear ();
290-
var encoder = new InstructionEncoder (_codeBlob);
300+
ControlFlowBuilder? cfb = useBranches ? new ControlFlowBuilder () : null;
301+
var encoder = new InstructionEncoder (_codeBlob, cfb);
291302
emitIL (encoder);
292303

293304
while (ILBuilder.Count % 4 != 0) {

src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/TypeMapAssemblyEmitter.cs

Lines changed: 127 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,9 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap;
4343
///
4444
/// [UnmanagedCallersOnly]
4545
/// public static void nctor_0_uco(IntPtr jnienv, IntPtr self)
46-
/// =&gt; TrimmableNativeRegistration.ActivateInstance(self, typeof(Activity));
46+
/// =&gt; new Activity(self, JniHandleOwnership.DoNotTransfer);
47+
/// // or: var obj = (Activity)RuntimeHelpers.GetUninitializedObject(typeof(Activity));
48+
/// // obj.BaseCtor(self, JniHandleOwnership.DoNotTransfer);
4749
///
4850
/// // Registers JNI native methods (ACWs only):
4951
/// public void RegisterNatives(JniType jniType)
@@ -77,7 +79,6 @@ sealed class TypeMapAssemblyEmitter
7779
TypeReferenceHandle _systemTypeRef;
7880
TypeReferenceHandle _runtimeTypeHandleRef;
7981
TypeReferenceHandle _jniTypeRef;
80-
TypeReferenceHandle _trimmableNativeRegistrationRef;
8182
TypeReferenceHandle _notSupportedExceptionRef;
8283
TypeReferenceHandle _runtimeHelpersRef;
8384

@@ -87,7 +88,7 @@ sealed class TypeMapAssemblyEmitter
8788
MemberReferenceHandle _notSupportedExceptionCtorRef;
8889
MemberReferenceHandle _jniObjectReferenceCtorRef;
8990
MemberReferenceHandle _jniEnvDeleteRefRef;
90-
MemberReferenceHandle _activateInstanceRef;
91+
MemberReferenceHandle _withinNewObjectScopeRef;
9192
MemberReferenceHandle _ucoAttrCtorRef;
9293
BlobHandle _ucoAttrBlobHandle;
9394
MemberReferenceHandle _typeMapAttrCtorRef2Arg;
@@ -184,8 +185,6 @@ void EmitTypeReferences ()
184185
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("RuntimeTypeHandle"));
185186
_jniTypeRef = metadata.AddTypeReference (_javaInteropRef,
186187
metadata.GetOrAddString ("Java.Interop"), metadata.GetOrAddString ("JniType"));
187-
_trimmableNativeRegistrationRef = metadata.AddTypeReference (_pe.MonoAndroidRef,
188-
metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("TrimmableNativeRegistration"));
189188
_notSupportedExceptionRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
190189
metadata.GetOrAddString ("System"), metadata.GetOrAddString ("NotSupportedException"));
191190
_runtimeHelpersRef = metadata.AddTypeReference (_pe.SystemRuntimeRef,
@@ -245,13 +244,11 @@ void EmitMemberReferences ()
245244
p.AddParameter ().Type ().Type (_jniHandleOwnershipRef, true);
246245
}));
247246

248-
_activateInstanceRef = _pe.AddMemberRef (_trimmableNativeRegistrationRef, "ActivateInstance",
249-
sig => sig.MethodSignature ().Parameters (2,
250-
rt => rt.Void (),
251-
p => {
252-
p.AddParameter ().Type ().IntPtr ();
253-
p.AddParameter ().Type ().Type (_systemTypeRef, false);
254-
}));
247+
// JniEnvironment.get_WithinNewObjectScope() -> bool (static property)
248+
_withinNewObjectScopeRef = _pe.AddMemberRef (_jniEnvironmentRef, "get_WithinNewObjectScope",
249+
sig => sig.MethodSignature ().Parameters (0,
250+
rt => rt.Type ().Boolean (),
251+
p => { }));
255252

256253
// JniNativeMethod..ctor(byte*, byte*, IntPtr)
257254
_jniNativeMethodCtorRef = _pe.AddMemberRef (_jniNativeMethodRef, ".ctor",
@@ -352,6 +349,16 @@ void EmitTypeMapAssociationAttributeCtorRef ()
352349

353350
void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinitionHandle> wrapperHandles)
354351
{
352+
if (proxy.IsAcw) {
353+
// RegisterNatives uses RVA-backed UTF-8 fields under <PrivateImplementationDetails>.
354+
// Materialize those helper types before adding the proxy TypeDef, otherwise the
355+
// later RegisterNatives method can be attached to the helper type instead.
356+
foreach (var reg in proxy.NativeRegistrations) {
357+
_pe.GetOrAddUtf8Field (reg.JniMethodName);
358+
_pe.GetOrAddUtf8Field (reg.JniSignature);
359+
}
360+
}
361+
355362
var metadata = _pe.Metadata;
356363
var typeDefHandle = metadata.AddTypeDefinition (
357364
TypeAttributes.Public | TypeAttributes.Sealed | TypeAttributes.Class,
@@ -397,7 +404,7 @@ void EmitProxyType (JavaPeerProxyData proxy, Dictionary<string, MethodDefinition
397404
}
398405

399406
foreach (var uco in proxy.UcoConstructors) {
400-
var handle = EmitUcoConstructor (uco);
407+
var handle = EmitUcoConstructor (uco, proxy);
401408
wrapperHandles [uco.WrapperName] = handle;
402409
}
403410

@@ -686,39 +693,123 @@ MethodDefinitionHandle EmitUcoMethod (UcoMethodData uco)
686693
return handle;
687694
}
688695

689-
MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco)
696+
MethodDefinitionHandle EmitUcoConstructor (UcoConstructorData uco, JavaPeerProxyData proxy)
690697
{
691-
var userTypeRef = _pe.ResolveTypeRef (uco.TargetType);
698+
var targetTypeRef = _pe.ResolveTypeRef (uco.TargetType);
699+
var activationCtor = proxy.ActivationCtor ?? throw new InvalidOperationException (
700+
$"UCO constructor wrapper requires an activation ctor for '{uco.TargetType.ManagedTypeName}'");
692701

693702
// UCO constructor wrappers must match the JNI native method signature exactly.
694-
// The Java JCW declares e.g. "private native void nctor_0(Context p0)" and calls
695-
// it with arguments. JNI dispatches with (JNIEnv*, jobject, <ctor params...>),
696-
// so the wrapper signature must include all parameters to match the ABI.
697703
// Only jnienv (arg 0) and self (arg 1) are used — the constructor parameters
698-
// are not forwarded because ActivateInstance creates the managed peer using the
704+
// are not forwarded because we create the managed peer using the
699705
// activation ctor (IntPtr, JniHandleOwnership), not the user-visible constructor.
700706
var jniParams = JniSignatureHelper.ParseParameterTypes (uco.JniSignature);
701707
int paramCount = 2 + jniParams.Count;
702708

703-
var handle = _pe.EmitBody (uco.WrapperName,
704-
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
705-
sig => sig.MethodSignature ().Parameters (paramCount,
706-
rt => rt.Void (),
707-
p => {
708-
p.AddParameter ().Type ().IntPtr (); // jnienv
709-
p.AddParameter ().Type ().IntPtr (); // self
710-
for (int j = 0; j < jniParams.Count; j++)
711-
JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]);
712-
}),
713-
encoder => {
714-
encoder.LoadArgument (1); // self
715-
encoder.OpCode (ILOpCode.Ldtoken);
716-
encoder.Token (userTypeRef);
717-
encoder.Call (_getTypeFromHandleRef);
718-
encoder.Call (_activateInstanceRef);
719-
encoder.OpCode (ILOpCode.Ret);
709+
Action<BlobEncoder> encodeSig = sig => sig.MethodSignature ().Parameters (paramCount,
710+
rt => rt.Void (),
711+
p => {
712+
p.AddParameter ().Type ().IntPtr (); // jnienv
713+
p.AddParameter ().Type ().IntPtr (); // self
714+
for (int j = 0; j < jniParams.Count; j++)
715+
JniSignatureHelper.EncodeClrType (p.AddParameter ().Type (), jniParams [j]);
720716
});
721717

718+
// Open generic types can't be activated — emit a no-op UCO.
719+
if (proxy.IsGenericDefinition) {
720+
var noopHandle = _pe.EmitBody (uco.WrapperName,
721+
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
722+
encodeSig,
723+
encoder => {
724+
encoder.OpCode (ILOpCode.Ret);
725+
});
726+
AddUnmanagedCallersOnlyAttribute (noopHandle);
727+
return noopHandle;
728+
}
729+
730+
MethodDefinitionHandle handle;
731+
if (activationCtor.Style == ActivationCtorStyle.JavaInterop) {
732+
var ctorRef = AddJavaInteropActivationCtorRef (
733+
activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));
734+
735+
handle = _pe.EmitBody (uco.WrapperName,
736+
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
737+
encodeSig,
738+
encoder => {
739+
// Skip activation if the object is being created from managed code
740+
// (e.g., JNIEnv.StartCreateInstance / JNIEnv.NewObject).
741+
var skipLabel = encoder.DefineLabel ();
742+
encoder.Call (_withinNewObjectScopeRef);
743+
encoder.Branch (ILOpCode.Brtrue, skipLabel);
744+
745+
if (!activationCtor.IsOnLeafType) {
746+
encoder.OpCode (ILOpCode.Ldtoken);
747+
encoder.Token (targetTypeRef);
748+
encoder.Call (_getTypeFromHandleRef);
749+
encoder.Call (_getUninitializedObjectRef);
750+
encoder.OpCode (ILOpCode.Castclass);
751+
encoder.Token (targetTypeRef);
752+
}
753+
754+
encoder.LoadLocalAddress (0);
755+
encoder.LoadArgument (1); // self
756+
encoder.Call (_jniObjectReferenceCtorRef);
757+
758+
if (activationCtor.IsOnLeafType) {
759+
encoder.LoadLocalAddress (0);
760+
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
761+
encoder.OpCode (ILOpCode.Newobj);
762+
encoder.Token (ctorRef);
763+
encoder.OpCode (ILOpCode.Pop);
764+
} else {
765+
encoder.LoadLocalAddress (0);
766+
encoder.LoadConstantI4 (1); // JniObjectReferenceOptions.Copy
767+
encoder.Call (ctorRef);
768+
}
769+
770+
encoder.MarkLabel (skipLabel);
771+
encoder.OpCode (ILOpCode.Ret);
772+
},
773+
EncodeJniObjectReferenceLocal,
774+
useBranches: true);
775+
} else {
776+
var ctorRef = AddActivationCtorRef (
777+
activationCtor.IsOnLeafType ? targetTypeRef : _pe.ResolveTypeRef (activationCtor.DeclaringType));
778+
779+
handle = _pe.EmitBody (uco.WrapperName,
780+
MethodAttributes.Public | MethodAttributes.Static | MethodAttributes.HideBySig,
781+
encodeSig,
782+
encoder => {
783+
// Skip activation if the object is being created from managed code
784+
var skipLabel = encoder.DefineLabel ();
785+
encoder.Call (_withinNewObjectScopeRef);
786+
encoder.Branch (ILOpCode.Brtrue, skipLabel);
787+
788+
if (activationCtor.IsOnLeafType) {
789+
encoder.LoadArgument (1); // self
790+
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
791+
encoder.OpCode (ILOpCode.Newobj);
792+
encoder.Token (ctorRef);
793+
encoder.OpCode (ILOpCode.Pop);
794+
} else {
795+
encoder.OpCode (ILOpCode.Ldtoken);
796+
encoder.Token (targetTypeRef);
797+
encoder.Call (_getTypeFromHandleRef);
798+
encoder.Call (_getUninitializedObjectRef);
799+
encoder.OpCode (ILOpCode.Castclass);
800+
encoder.Token (targetTypeRef);
801+
802+
encoder.LoadArgument (1); // self
803+
encoder.LoadConstantI4 (0); // JniHandleOwnership.DoNotTransfer
804+
encoder.Call (ctorRef);
805+
}
806+
807+
encoder.MarkLabel (skipLabel);
808+
encoder.OpCode (ILOpCode.Ret);
809+
},
810+
encodeLocals: null,
811+
useBranches: true);
812+
}
722813
AddUnmanagedCallersOnlyAttribute (handle);
723814
return handle;
724815
}

src/Mono.Android/Microsoft.Android.Runtime/TrimmableTypeMap.cs

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -147,39 +147,6 @@ internal bool TryCreatePeer (Type type, IntPtr handle, JniHandleOwnership transf
147147
return GetProxyForManagedType (type)?.GetContainerFactory ();
148148
}
149149

150-
/// <summary>
151-
/// Creates a managed peer instance for a Java object being constructed.
152-
/// Called from generated UCO constructor wrappers (nctor_*_uco).
153-
/// </summary>
154-
internal static void ActivateInstance (IntPtr self, Type targetType)
155-
{
156-
var instance = s_instance;
157-
if (instance is null) {
158-
throw new InvalidOperationException ("TrimmableTypeMap has not been initialized.");
159-
}
160-
161-
// Look up the proxy via JNI class name → TypeMap dictionary.
162-
// We can't use targetType.GetCustomAttribute<JavaPeerProxy>() because the
163-
// self-application attribute is on the proxy type, not the target type.
164-
var selfRef = new JniObjectReference (self);
165-
var jniClass = JniEnvironment.Types.GetObjectClass (selfRef);
166-
var className = JniEnvironment.Types.GetJniTypeNameFromClass (jniClass);
167-
JniObjectReference.Dispose (ref jniClass);
168-
169-
if (className is null || !instance._typeMap.TryGetValue (className, out var proxyType)) {
170-
throw new InvalidOperationException (
171-
$"Failed to create peer for type '{targetType.FullName}' (jniClass='{className}'). " +
172-
"Ensure the type has a generated proxy in the TypeMap assembly.");
173-
}
174-
175-
var proxy = proxyType.GetCustomAttribute<JavaPeerProxy> (inherit: false);
176-
if (proxy is null || proxy.CreateInstance (self, JniHandleOwnership.DoNotTransfer) is null) {
177-
throw new InvalidOperationException (
178-
$"Failed to create peer for type '{targetType.FullName}'. " +
179-
"Ensure the type has a generated proxy in the TypeMap assembly.");
180-
}
181-
}
182-
183150
[UnmanagedCallersOnly]
184151
static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHandle)
185152
{
@@ -208,4 +175,5 @@ static void OnRegisterNatives (IntPtr jnienv, IntPtr klass, IntPtr nativeClassHa
208175
Environment.FailFast ($"TrimmableTypeMap: Failed to register natives for class '{className}'.", ex);
209176
}
210177
}
178+
211179
}

tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,25 @@ public void Generate_LeafCtor_DoesNotUseCreateManagedPeer ()
188188
Assert.True (ctorRefs.Count >= 2, "Should have ctor refs for proxy base + target type");
189189
}
190190

191+
[Fact]
192+
public void Generate_InheritedCtor_UcoUsesGuardAndInlinedActivation ()
193+
{
194+
var peers = ScanFixtures ();
195+
var simpleActivity = peers.First (p => p.JavaName == "my/app/SimpleActivity");
196+
Assert.NotNull (simpleActivity.ActivationCtor);
197+
Assert.NotEqual (simpleActivity.ManagedTypeName, simpleActivity.ActivationCtor.DeclaringTypeName);
198+
199+
using var stream = GenerateAssembly (new [] { simpleActivity }, "InheritedCtorUcoTest");
200+
using var pe = new PEReader (stream);
201+
var reader = pe.GetMetadataReader ();
202+
var memberNames = GetMemberRefNames (reader);
203+
204+
Assert.Contains ("get_WithinNewObjectScope", memberNames);
205+
Assert.Contains ("GetUninitializedObject", memberNames);
206+
Assert.DoesNotContain ("ActivateInstance", memberNames);
207+
Assert.DoesNotContain ("ActivatePeerFromJavaConstructor", memberNames);
208+
}
209+
191210
[Fact]
192211
public void Generate_GenericType_ThrowsNotSupportedException ()
193212
{
@@ -422,14 +441,26 @@ public void Generate_AcwProxy_HasRegisterNativesAndUcoMethods ()
422441
using var pe = new PEReader (stream);
423442
var reader = pe.GetMetadataReader ();
424443

425-
var memberNames = GetMemberRefNames (reader);
444+
var proxyType = reader.TypeDefinitions
445+
.Select (h => reader.GetTypeDefinition (h))
446+
.Single (t =>
447+
reader.GetString (t.Namespace) == "_TypeMap.Proxies" &&
448+
reader.GetString (t.Name) == "MyApp_MainActivity_Proxy");
449+
var proxyMethodNames = proxyType.GetMethods ()
450+
.Select (h => reader.GetMethodDefinition (h))
451+
.Select (m => reader.GetString (m.Name))
452+
.ToList ();
453+
Assert.Contains ("RegisterNatives", proxyMethodNames);
454+
Assert.Contains (proxyMethodNames, name => name.Contains ("_uco_"));
426455

427-
// RegisterNatives is a method definition on the proxy type, not a member reference
428-
var methodDefs = reader.MethodDefinitions
456+
var privateImplDetailsType = reader.TypeDefinitions
457+
.Select (h => reader.GetTypeDefinition (h))
458+
.Single (t => reader.GetString (t.Name) == "<PrivateImplementationDetails>");
459+
var privateImplMethodNames = privateImplDetailsType.GetMethods ()
429460
.Select (h => reader.GetMethodDefinition (h))
430461
.Select (m => reader.GetString (m.Name))
431462
.ToList ();
432-
Assert.Contains ("RegisterNatives", methodDefs);
463+
Assert.DoesNotContain ("RegisterNatives", privateImplMethodNames);
433464
}
434465

435466
[Fact]

0 commit comments

Comments
 (0)