From 4664f102604813450df501de33f548796ccb0732 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Thu, 17 Sep 2026 08:24:12 +0200 Subject: [PATCH 1/3] [trimmable-type-map] Avoid unused XML references Create XML marshalling metadata lazily so non-XML Export methods do not root System.Xml.ReaderWriter and System.Private.Xml. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../Generator/ExportMethodDispatchEmitter.cs | 8 +- .../ExportMethodDispatchEmitterContext.cs | 137 +++++++++++++----- .../TypeMapAssemblyGeneratorTests.cs | 29 ++++ 3 files changed, 132 insertions(+), 42 deletions(-) diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs index 8a33dac9b4f..8cc1e386b74 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitter.cs @@ -444,12 +444,12 @@ bool TryEmitExportParameterArgument (TrackedInstructionEncoder encoder, ExportPa case ExportParameterKindInfo.XmlPullParser: encoder.LoadArgument (argumentIndex); encoder.LoadConstantI4 (0); - encoder.Call (_context.XmlPullParserReaderFromJniHandleRef, parameterCount: 2, returnsValue: true); + encoder.Call (_context.GetXmlPullParserReaderFromJniHandleRef (), parameterCount: 2, returnsValue: true); return true; case ExportParameterKindInfo.XmlResourceParser: encoder.LoadArgument (argumentIndex); encoder.LoadConstantI4 (0); - encoder.Call (_context.XmlResourceParserReaderFromJniHandleRef, parameterCount: 2, returnsValue: true); + encoder.Call (_context.GetXmlResourceParserReaderFromJniHandleRef (), parameterCount: 2, returnsValue: true); return true; default: return false; @@ -534,10 +534,10 @@ bool TryEmitExportParameterReturn (TrackedInstructionEncoder encoder, ExportPara encoder.Call (_context.OutputStreamAdapterToLocalJniHandleRef, parameterCount: 1, returnsValue: true); return true; case ExportParameterKindInfo.XmlPullParser: - encoder.Call (_context.XmlReaderPullParserToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_context.GetXmlReaderPullParserToLocalJniHandleRef (), parameterCount: 1, returnsValue: true); return true; case ExportParameterKindInfo.XmlResourceParser: - encoder.Call (_context.XmlReaderResourceParserToLocalJniHandleRef, parameterCount: 1, returnsValue: true); + encoder.Call (_context.GetXmlReaderResourceParserToLocalJniHandleRef (), parameterCount: 1, returnsValue: true); return true; default: return false; diff --git a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs index 95afef57d86..eaf4a6287b8 100644 --- a/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs +++ b/src/Microsoft.Android.Sdk.TrimmableTypeMap/Generator/ExportMethodDispatchEmitterContext.cs @@ -10,6 +10,19 @@ namespace Microsoft.Android.Sdk.TrimmableTypeMap; /// sealed class ExportMethodDispatchEmitterContext { + readonly PEAssemblyBuilder _pe; + + TypeReferenceHandle _systemXmlReaderRef; + MemberReferenceHandle _xmlPullParserReaderFromJniHandleRef; + MemberReferenceHandle _xmlResourceParserReaderFromJniHandleRef; + MemberReferenceHandle _xmlReaderPullParserToLocalJniHandleRef; + MemberReferenceHandle _xmlReaderResourceParserToLocalJniHandleRef; + + ExportMethodDispatchEmitterContext (PEAssemblyBuilder pe) + { + _pe = pe; + } + public static ExportMethodDispatchEmitterContext Create ( PEAssemblyBuilder pe, TypeReferenceHandle iJavaPeerableRef, @@ -35,9 +48,6 @@ public static ExportMethodDispatchEmitterContext Create ( metadata.GetOrAddString ("System"), metadata.GetOrAddString ("Array")); var systemStreamRef = metadata.AddTypeReference (pe.SystemRuntimeRef, metadata.GetOrAddString ("System.IO"), metadata.GetOrAddString ("Stream")); - var systemXmlRef = pe.FindOrAddAssemblyRef ("System.Xml.ReaderWriter"); - var systemXmlReaderRef = metadata.AddTypeReference (systemXmlRef, - metadata.GetOrAddString ("System.Xml"), metadata.GetOrAddString ("XmlReader")); var inputStreamInvokerRef = metadata.AddTypeReference (pe.MonoAndroidRef, metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("InputStreamInvoker")); var outputStreamInvokerRef = metadata.AddTypeReference (pe.MonoAndroidRef, @@ -46,14 +56,6 @@ public static ExportMethodDispatchEmitterContext Create ( metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("InputStreamAdapter")); var outputStreamAdapterRef = metadata.AddTypeReference (pe.MonoAndroidRef, metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("OutputStreamAdapter")); - var xmlPullParserReaderRef = metadata.AddTypeReference (pe.MonoAndroidRef, - metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlPullParserReader")); - var xmlResourceParserReaderRef = metadata.AddTypeReference (pe.MonoAndroidRef, - metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlResourceParserReader")); - var xmlReaderPullParserRef = metadata.AddTypeReference (pe.MonoAndroidRef, - metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlReaderPullParser")); - var xmlReaderResourceParserRef = metadata.AddTypeReference (pe.MonoAndroidRef, - metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlReaderResourceParser")); var charSequenceRef = metadata.AddTypeReference (pe.MonoAndroidRef, metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("CharSequence")); var iCharSequenceRef = metadata.AddTypeReference (pe.MonoAndroidRef, @@ -71,8 +73,9 @@ public static ExportMethodDispatchEmitterContext Create ( var systemCollectionsICollectionRef = metadata.AddTypeReference (pe.SystemRuntimeRef, metadata.GetOrAddString ("System.Collections"), metadata.GetOrAddString ("ICollection")); - return new ExportMethodDispatchEmitterContext { + return new ExportMethodDispatchEmitterContext (pe) { IJavaObjectRef = iJavaObjectRef, + JniHandleOwnershipRef = jniHandleOwnershipRef, GetTypeFromHandleRef = getTypeFromHandleRef, JniEnvGetStringRef = pe.AddMemberRef (jniEnvRef, "GetString", sig => sig.MethodSignature ().Parameters (2, @@ -142,28 +145,6 @@ public static ExportMethodDispatchEmitterContext Create ( sig => sig.MethodSignature ().Parameters (1, rt => rt.Type ().IntPtr (), p => p.AddParameter ().Type ().Type (systemStreamRef, false))), - XmlPullParserReaderFromJniHandleRef = pe.AddMemberRef (xmlPullParserReaderRef, "FromJniHandle", - sig => sig.MethodSignature ().Parameters (2, - rt => rt.Type ().Type (systemXmlReaderRef, false), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); - })), - XmlResourceParserReaderFromJniHandleRef = pe.AddMemberRef (xmlResourceParserReaderRef, "FromJniHandle", - sig => sig.MethodSignature ().Parameters (2, - rt => rt.Type ().Type (systemXmlReaderRef, false), - p => { - p.AddParameter ().Type ().IntPtr (); - p.AddParameter ().Type ().Type (jniHandleOwnershipRef, true); - })), - XmlReaderPullParserToLocalJniHandleRef = pe.AddMemberRef (xmlReaderPullParserRef, "ToLocalJniHandle", - sig => sig.MethodSignature ().Parameters (1, - rt => rt.Type ().IntPtr (), - p => p.AddParameter ().Type ().Type (systemXmlReaderRef, false))), - XmlReaderResourceParserToLocalJniHandleRef = pe.AddMemberRef (xmlReaderResourceParserRef, "ToLocalJniHandle", - sig => sig.MethodSignature ().Parameters (1, - rt => rt.Type ().IntPtr (), - p => p.AddParameter ().Type ().Type (systemXmlReaderRef, false))), CharSequenceToLocalJniHandleRef = pe.AddMemberRef (charSequenceRef, "ToLocalJniHandle", sig => sig.MethodSignature ().Parameters (1, rt => rt.Type ().IntPtr (), @@ -191,6 +172,89 @@ public static ExportMethodDispatchEmitterContext Create ( }; } + TypeReferenceHandle GetSystemXmlReaderRef () + { + if (!_systemXmlReaderRef.IsNil) { + return _systemXmlReaderRef; + } + + var metadata = _pe.Metadata; + var systemXmlRef = _pe.FindOrAddAssemblyRef ("System.Xml.ReaderWriter"); + _systemXmlReaderRef = metadata.AddTypeReference (systemXmlRef, + metadata.GetOrAddString ("System.Xml"), metadata.GetOrAddString ("XmlReader")); + return _systemXmlReaderRef; + } + + public MemberReferenceHandle GetXmlPullParserReaderFromJniHandleRef () + { + if (!_xmlPullParserReaderFromJniHandleRef.IsNil) { + return _xmlPullParserReaderFromJniHandleRef; + } + + var metadata = _pe.Metadata; + var xmlPullParserReaderRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlPullParserReader")); + _xmlPullParserReaderFromJniHandleRef = _pe.AddMemberRef (xmlPullParserReaderRef, "FromJniHandle", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (GetSystemXmlReaderRef (), false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (JniHandleOwnershipRef, true); + })); + return _xmlPullParserReaderFromJniHandleRef; + } + + public MemberReferenceHandle GetXmlResourceParserReaderFromJniHandleRef () + { + if (!_xmlResourceParserReaderFromJniHandleRef.IsNil) { + return _xmlResourceParserReaderFromJniHandleRef; + } + + var metadata = _pe.Metadata; + var xmlResourceParserReaderRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlResourceParserReader")); + _xmlResourceParserReaderFromJniHandleRef = _pe.AddMemberRef (xmlResourceParserReaderRef, "FromJniHandle", + sig => sig.MethodSignature ().Parameters (2, + rt => rt.Type ().Type (GetSystemXmlReaderRef (), false), + p => { + p.AddParameter ().Type ().IntPtr (); + p.AddParameter ().Type ().Type (JniHandleOwnershipRef, true); + })); + return _xmlResourceParserReaderFromJniHandleRef; + } + + public MemberReferenceHandle GetXmlReaderPullParserToLocalJniHandleRef () + { + if (!_xmlReaderPullParserToLocalJniHandleRef.IsNil) { + return _xmlReaderPullParserToLocalJniHandleRef; + } + + var metadata = _pe.Metadata; + var xmlReaderPullParserRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlReaderPullParser")); + _xmlReaderPullParserToLocalJniHandleRef = _pe.AddMemberRef (xmlReaderPullParserRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (GetSystemXmlReaderRef (), false))); + return _xmlReaderPullParserToLocalJniHandleRef; + } + + public MemberReferenceHandle GetXmlReaderResourceParserToLocalJniHandleRef () + { + if (!_xmlReaderResourceParserToLocalJniHandleRef.IsNil) { + return _xmlReaderResourceParserToLocalJniHandleRef; + } + + var metadata = _pe.Metadata; + var xmlReaderResourceParserRef = metadata.AddTypeReference (_pe.MonoAndroidRef, + metadata.GetOrAddString ("Android.Runtime"), metadata.GetOrAddString ("XmlReaderResourceParser")); + _xmlReaderResourceParserToLocalJniHandleRef = _pe.AddMemberRef (xmlReaderResourceParserRef, "ToLocalJniHandle", + sig => sig.MethodSignature ().Parameters (1, + rt => rt.Type ().IntPtr (), + p => p.AddParameter ().Type ().Type (GetSystemXmlReaderRef (), false))); + return _xmlReaderResourceParserToLocalJniHandleRef; + } + public required TypeReferenceHandle IJavaObjectRef { get; init; } public required MemberReferenceHandle GetTypeFromHandleRef { get; init; } public required MemberReferenceHandle JniEnvGetStringRef { get; init; } @@ -204,10 +268,6 @@ public static ExportMethodDispatchEmitterContext Create ( public required MemberReferenceHandle OutputStreamInvokerFromJniHandleRef { get; init; } public required MemberReferenceHandle InputStreamAdapterToLocalJniHandleRef { get; init; } public required MemberReferenceHandle OutputStreamAdapterToLocalJniHandleRef { get; init; } - public required MemberReferenceHandle XmlPullParserReaderFromJniHandleRef { get; init; } - public required MemberReferenceHandle XmlResourceParserReaderFromJniHandleRef { get; init; } - public required MemberReferenceHandle XmlReaderPullParserToLocalJniHandleRef { get; init; } - public required MemberReferenceHandle XmlReaderResourceParserToLocalJniHandleRef { get; init; } public required MemberReferenceHandle CharSequenceToLocalJniHandleRef { get; init; } public required MemberReferenceHandle JavaListToLocalJniHandleRef { get; init; } public required MemberReferenceHandle JavaDictionaryToLocalJniHandleRef { get; init; } @@ -225,4 +285,5 @@ public static ExportMethodDispatchEmitterContext Create ( public required MemberReferenceHandle BeginMarshalMethodRef { get; init; } public required MemberReferenceHandle EndMarshalMethodRef { get; init; } public required MemberReferenceHandle OnUserUnhandledExceptionRef { get; init; } + public required TypeReferenceHandle JniHandleOwnershipRef { get; init; } } diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index c06151ad8c9..d0ec23d42ef 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -97,6 +97,35 @@ public void Generate_HasRequiredAssemblyReferences () Assert.Contains ("System.Runtime.InteropServices", asmRefs); } + [Fact] + public void Generate_ExportWithoutXmlMarshalling_DoesNotReferenceSystemXml () + { + var peer = ScanFixtures ().Single (p => p.JavaName == "my/app/ExportExample"); + using var stream = GenerateAssembly (new [] { peer }); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + var asmRefs = reader.AssemblyReferences + .Select (h => reader.GetString (reader.GetAssemblyReference (h).Name)) + .ToList (); + + Assert.DoesNotContain ("System.Xml.ReaderWriter", asmRefs); + Assert.DoesNotContain ("System.Private.Xml", asmRefs); + } + + [Fact] + public void Generate_ExportWithXmlMarshalling_ReferencesSystemXml () + { + var peer = ScanFixtures ().Single (p => p.JavaName == "my/app/ExportMarshallingShapes"); + using var stream = GenerateAssembly (new [] { peer }); + using var pe = new PEReader (stream); + var reader = pe.GetMetadataReader (); + var asmRefs = reader.AssemblyReferences + .Select (h => reader.GetString (reader.GetAssemblyReference (h).Name)) + .ToList (); + + Assert.Contains ("System.Xml.ReaderWriter", asmRefs); + } + [Fact] public void Generate_InheritedGenericBaseCallback_UsesValueTypeGenericArgument () { From 2869e257961d36f731d0d5bfc337aea26edaf2d5 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 21 Sep 2026 11:32:47 +0200 Subject: [PATCH 2/3] [trimmable-type-map] Verify lazy XML adapter metadata Decode each XML adapter member reference and verify generated IL calls it, so the regression test covers the lazy metadata path instead of only observing the callback assembly reference. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TypeMapAssemblyGeneratorTests.cs | 47 ++++++++++++++++++- 1 file changed, 45 insertions(+), 2 deletions(-) diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index d0ec23d42ef..df8d5ae479d 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -107,13 +107,18 @@ public void Generate_ExportWithoutXmlMarshalling_DoesNotReferenceSystemXml () var asmRefs = reader.AssemblyReferences .Select (h => reader.GetString (reader.GetAssemblyReference (h).Name)) .ToList (); + var typeNames = GetTypeRefNames (reader); Assert.DoesNotContain ("System.Xml.ReaderWriter", asmRefs); - Assert.DoesNotContain ("System.Private.Xml", asmRefs); + Assert.DoesNotContain ("XmlReader", typeNames); + Assert.DoesNotContain ("XmlPullParserReader", typeNames); + Assert.DoesNotContain ("XmlResourceParserReader", typeNames); + Assert.DoesNotContain ("XmlReaderPullParser", typeNames); + Assert.DoesNotContain ("XmlReaderResourceParser", typeNames); } [Fact] - public void Generate_ExportWithXmlMarshalling_ReferencesSystemXml () + public void Generate_ExportWithXmlMarshalling_EmitsAdapterReferences () { var peer = ScanFixtures ().Single (p => p.JavaName == "my/app/ExportMarshallingShapes"); using var stream = GenerateAssembly (new [] { peer }); @@ -124,6 +129,39 @@ public void Generate_ExportWithXmlMarshalling_ReferencesSystemXml () .ToList (); Assert.Contains ("System.Xml.ReaderWriter", asmRefs); + + var expectedAdapters = new [] { + (Parent: "XmlPullParserReader", Method: "FromJniHandle", ReturnType: "System.Xml.XmlReader", + ParameterTypes: new [] { "System.IntPtr", "Android.Runtime.JniHandleOwnership" }), + (Parent: "XmlResourceParserReader", Method: "FromJniHandle", ReturnType: "System.Xml.XmlReader", + ParameterTypes: new [] { "System.IntPtr", "Android.Runtime.JniHandleOwnership" }), + (Parent: "XmlReaderPullParser", Method: "ToLocalJniHandle", ReturnType: "System.IntPtr", + ParameterTypes: new [] { "System.Xml.XmlReader" }), + (Parent: "XmlReaderResourceParser", Method: "ToLocalJniHandle", ReturnType: "System.IntPtr", + ParameterTypes: new [] { "System.Xml.XmlReader" }), + }; + var adapterHandles = new List (); + foreach (var expected in expectedAdapters) { + var handle = FindMemberReferenceHandle (reader, "Android.Runtime", expected.Parent, expected.Method); + var signature = reader.GetMemberReference (handle).DecodeMethodSignature (SignatureTypeProvider.Instance, null); + + Assert.Equal (expected.ReturnType, signature.ReturnType); + Assert.Equal (expected.ParameterTypes, signature.ParameterTypes); + adapterHandles.Add (handle); + } + + var callTokens = reader.MethodDefinitions + .Select (handle => reader.GetMethodDefinition (handle)) + .Where (method => method.RelativeVirtualAddress != 0) + .SelectMany (method => { + var ilBytes = pe.GetMethodBody (method.RelativeVirtualAddress).GetILBytes (); + Assert.NotNull (ilBytes); + return ReadCallTokens (ilBytes); + }) + .ToHashSet (); + foreach (var handle in adapterHandles) { + Assert.Contains (MetadataTokens.GetToken (handle), callTokens); + } } [Fact] @@ -1340,6 +1378,11 @@ static MemberReference FindCallbackMemberRef (MetadataReader reader, string meth } static MemberReferenceHandle FindCallbackMemberRefHandle (MetadataReader reader, string methodName, string parentNamespace, string parentName) + { + return FindMemberReferenceHandle (reader, parentNamespace, parentName, methodName); + } + + static MemberReferenceHandle FindMemberReferenceHandle (MetadataReader reader, string parentNamespace, string parentName, string methodName) { var refs = Enumerable.Range (1, reader.GetTableRowCount (TableIndex.MemberRef)) .Select (MetadataTokens.MemberReferenceHandle) From 7728d3ba92345f31ac5cf3144c2937b12d336162 Mon Sep 17 00:00:00 2001 From: Simon Rozsival Date: Mon, 21 Sep 2026 14:26:47 +0200 Subject: [PATCH 3/3] [trimmable-type-map] Verify XML adapters per wrapper Assert each generated XML export wrapper calls only its matching input and return adapters so pull-parser and resource-parser mappings cannot be swapped unnoticed. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../TypeMapAssemblyGeneratorTests.cs | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs index df8d5ae479d..8665c3f91ba 100644 --- a/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs +++ b/tests/Microsoft.Android.Sdk.TrimmableTypeMap.Tests/Generator/TypeMapAssemblyGeneratorTests.cs @@ -131,37 +131,36 @@ public void Generate_ExportWithXmlMarshalling_EmitsAdapterReferences () Assert.Contains ("System.Xml.ReaderWriter", asmRefs); var expectedAdapters = new [] { - (Parent: "XmlPullParserReader", Method: "FromJniHandle", ReturnType: "System.Xml.XmlReader", + (Key: "PullParserFromHandle", Parent: "XmlPullParserReader", Method: "FromJniHandle", ReturnType: "System.Xml.XmlReader", ParameterTypes: new [] { "System.IntPtr", "Android.Runtime.JniHandleOwnership" }), - (Parent: "XmlResourceParserReader", Method: "FromJniHandle", ReturnType: "System.Xml.XmlReader", + (Key: "ResourceParserFromHandle", Parent: "XmlResourceParserReader", Method: "FromJniHandle", ReturnType: "System.Xml.XmlReader", ParameterTypes: new [] { "System.IntPtr", "Android.Runtime.JniHandleOwnership" }), - (Parent: "XmlReaderPullParser", Method: "ToLocalJniHandle", ReturnType: "System.IntPtr", + (Key: "PullParserToHandle", Parent: "XmlReaderPullParser", Method: "ToLocalJniHandle", ReturnType: "System.IntPtr", ParameterTypes: new [] { "System.Xml.XmlReader" }), - (Parent: "XmlReaderResourceParser", Method: "ToLocalJniHandle", ReturnType: "System.IntPtr", + (Key: "ResourceParserToHandle", Parent: "XmlReaderResourceParser", Method: "ToLocalJniHandle", ReturnType: "System.IntPtr", ParameterTypes: new [] { "System.Xml.XmlReader" }), }; - var adapterHandles = new List (); + var adapterHandles = new Dictionary (); foreach (var expected in expectedAdapters) { var handle = FindMemberReferenceHandle (reader, "Android.Runtime", expected.Parent, expected.Method); var signature = reader.GetMemberReference (handle).DecodeMethodSignature (SignatureTypeProvider.Instance, null); Assert.Equal (expected.ReturnType, signature.ReturnType); Assert.Equal (expected.ParameterTypes, signature.ParameterTypes); - adapterHandles.Add (handle); + adapterHandles.Add (expected.Key, handle); } - var callTokens = reader.MethodDefinitions - .Select (handle => reader.GetMethodDefinition (handle)) - .Where (method => method.RelativeVirtualAddress != 0) - .SelectMany (method => { - var ilBytes = pe.GetMethodBody (method.RelativeVirtualAddress).GetILBytes (); - Assert.NotNull (ilBytes); - return ReadCallTokens (ilBytes); - }) - .ToHashSet (); - foreach (var handle in adapterHandles) { - Assert.Contains (MetadataTokens.GetToken (handle), callTokens); - } + var pullParserCalls = ReadMethodCallTokens (pe, reader, "n_readXml_uco_"); + Assert.Contains (MetadataTokens.GetToken (adapterHandles ["PullParserFromHandle"]), pullParserCalls); + Assert.Contains (MetadataTokens.GetToken (adapterHandles ["PullParserToHandle"]), pullParserCalls); + Assert.DoesNotContain (MetadataTokens.GetToken (adapterHandles ["ResourceParserFromHandle"]), pullParserCalls); + Assert.DoesNotContain (MetadataTokens.GetToken (adapterHandles ["ResourceParserToHandle"]), pullParserCalls); + + var resourceParserCalls = ReadMethodCallTokens (pe, reader, "n_readResourceXml_uco_"); + Assert.Contains (MetadataTokens.GetToken (adapterHandles ["ResourceParserFromHandle"]), resourceParserCalls); + Assert.Contains (MetadataTokens.GetToken (adapterHandles ["ResourceParserToHandle"]), resourceParserCalls); + Assert.DoesNotContain (MetadataTokens.GetToken (adapterHandles ["PullParserFromHandle"]), resourceParserCalls); + Assert.DoesNotContain (MetadataTokens.GetToken (adapterHandles ["PullParserToHandle"]), resourceParserCalls); } [Fact] @@ -1425,6 +1424,16 @@ static List ReadLdftnTokens (byte [] ilBytes) return ReadInlineMetadataTokens (ilBytes, 0xFE, 0x06); } + static List ReadMethodCallTokens (PEReader pe, MetadataReader reader, string methodNamePrefix) + { + var method = reader.MethodDefinitions + .Select (handle => reader.GetMethodDefinition (handle)) + .Single (method => reader.GetString (method.Name).StartsWith (methodNamePrefix, StringComparison.Ordinal)); + var ilBytes = pe.GetMethodBody (method.RelativeVirtualAddress).GetILBytes (); + Assert.NotNull (ilBytes); + return ReadCallTokens (ilBytes); + } + static List ReadCallTokens (byte [] ilBytes) { return ReadInlineMetadataTokens (ilBytes, 0x28);