-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
MarshalingPInvokeScanner.cs
157 lines (131 loc) · 6.44 KB
/
MarshalingPInvokeScanner.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Diagnostics.CodeAnalysis;
using System.Reflection.Metadata;
using System.Reflection.Metadata.Ecma335;
using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Reflection.PortableExecutable;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;
namespace MonoTargetsTasks
{
public class MarshalingPInvokeScanner : Task
{
[Required]
public string[] Assemblies { get; set; } = Array.Empty<string>();
[Output]
public string[]? IncompatibleAssemblies { get; private set; }
public override bool Execute()
{
if (Assemblies is null || Assemblies!.Length == 0)
{
Log.LogError($"{nameof(MarshalingPInvokeScanner)}.{nameof(Assemblies)} cannot be empty");
return false;
}
try
{
ExecuteInternal();
return !Log.HasLoggedErrors;
}
catch (LogAsErrorException e)
{
Log.LogError(e.Message);
return false;
}
}
private void ExecuteInternal()
{
IncompatibleAssemblies = ScanAssemblies(Assemblies);
}
private string[] ScanAssemblies(string[] assemblies)
{
HashSet<string> incompatible = new HashSet<string>();
MinimalMarshalingTypeCompatibilityProvider mmtcp = new(Log);
foreach (string aname in assemblies)
{
if (IsAssemblyIncompatible(aname, mmtcp))
incompatible.Add(aname);
}
if (mmtcp.IsSecondPassNeeded)
{
foreach (string aname in assemblies)
ResolveInconclusiveTypes(incompatible, aname, mmtcp);
}
return incompatible.ToArray();
}
private static string GetMethodName(MetadataReader mr, MethodDefinition md) => mr.GetString(md.Name);
private void ResolveInconclusiveTypes(HashSet<string> incompatible, string assyPath, MinimalMarshalingTypeCompatibilityProvider mmtcp)
{
string assyName = MetadataReader.GetAssemblyName(assyPath).Name!;
HashSet<string> inconclusiveTypes = mmtcp.GetInconclusiveTypesForAssembly(assyName);
if(inconclusiveTypes.Count == 0)
return;
using FileStream file = new FileStream(assyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using PEReader peReader = new PEReader(file);
MetadataReader mdtReader = peReader.GetMetadataReader();
SignatureDecoder<Compatibility, object> decoder = new(mmtcp, mdtReader, null!);
foreach (TypeDefinitionHandle typeDefHandle in mdtReader.TypeDefinitions)
{
TypeDefinition typeDef = mdtReader.GetTypeDefinition(typeDefHandle);
string fullTypeName = string.Join(":", mdtReader.GetString(typeDef.Namespace), mdtReader.GetString(typeDef.Name));
// This is not perfect, but should work right for enums defined in other assemblies,
// which is the only case where we use Compatibility.Inconclusive.
if (inconclusiveTypes.Contains(fullTypeName) &&
mmtcp.GetTypeFromDefinition(mdtReader, typeDefHandle, 0) != Compatibility.Compatible)
{
Log.LogMessage(MessageImportance.Low, string.Format("Type {0} is marshaled and requires marshal-ilgen.", fullTypeName));
incompatible.Add("(unknown assembly)");
}
}
}
private bool IsAssemblyIncompatible(string assyPath, MinimalMarshalingTypeCompatibilityProvider mmtcp)
{
using FileStream file = new FileStream(assyPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
using PEReader peReader = new PEReader(file);
MetadataReader mdtReader = peReader.GetMetadataReader();
foreach(CustomAttributeHandle attrHandle in mdtReader.CustomAttributes)
{
CustomAttribute attr = mdtReader.GetCustomAttribute(attrHandle);
if(attr.Constructor.Kind == HandleKind.MethodDefinition)
{
MethodDefinitionHandle mdh = (MethodDefinitionHandle)attr.Constructor;
MethodDefinition md = mdtReader.GetMethodDefinition(mdh);
TypeDefinitionHandle tdh = md.GetDeclaringType();
TypeDefinition td = mdtReader.GetTypeDefinition(tdh);
if(mdtReader.GetString(td.Namespace) == "System.Runtime.CompilerServices" &&
mdtReader.GetString(td.Name) == "DisableRuntimeMarshallingAttribute")
return false;
}
}
foreach (TypeDefinitionHandle typeDefHandle in mdtReader.TypeDefinitions)
{
TypeDefinition typeDef = mdtReader.GetTypeDefinition(typeDefHandle);
string ns = mdtReader.GetString(typeDef.Namespace);
string name = mdtReader.GetString(typeDef.Name);
foreach(MethodDefinitionHandle mthDefHandle in typeDef.GetMethods())
{
MethodDefinition mthDef = mdtReader.GetMethodDefinition(mthDefHandle);
if(!mthDef.Attributes.HasFlag(MethodAttributes.PinvokeImpl))
continue;
BlobReader sgnBlobReader = mdtReader.GetBlobReader(mthDef.Signature);
SignatureDecoder<Compatibility, object> decoder = new(mmtcp, mdtReader, null!);
MethodSignature<Compatibility> sgn = decoder.DecodeMethodSignature(ref sgnBlobReader);
if(sgn.ReturnType == Compatibility.Incompatible || sgn.ParameterTypes.Any(p => p == Compatibility.Incompatible))
{
Log.LogMessage(MessageImportance.Low, string.Format("Assembly {0} requires marhsal-ilgen for method {1}.{2}:{3} (first pass).",
assyPath, ns, name, mdtReader.GetString(mthDef.Name)));
return true;
}
}
}
return false;
}
}
}