-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
Expand file tree
/
Copy pathDecompilerTypeSystem.cs
More file actions
429 lines (407 loc) · 17.4 KB
/
DecompilerTypeSystem.cs
File metadata and controls
429 lines (407 loc) · 17.4 KB
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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// Copyright (c) 2018 Daniel Grunwald
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem.Implementation;
using ICSharpCode.Decompiler.Util;
using NetEscapades.EnumGenerators;
using static ICSharpCode.Decompiler.Metadata.MetadataExtensions;
using SRM = System.Reflection.Metadata;
namespace ICSharpCode.Decompiler.TypeSystem
{
/// <summary>
/// Options that control how metadata is represented in the type system.
/// </summary>
[Flags]
[EnumExtensions]
public enum TypeSystemOptions
{
/// <summary>
/// No options enabled; stay as close to the metadata as possible.
/// </summary>
None = 0,
/// <summary>
/// [DynamicAttribute] is used to replace 'object' types with the 'dynamic' type.
///
/// If this option is not active, the 'dynamic' type is not used, and the attribute is preserved.
/// </summary>
Dynamic = 1,
/// <summary>
/// Tuple types are represented using the TupleType class.
/// [TupleElementNames] is used to name the tuple elements.
///
/// If this option is not active, the tuples are represented using their underlying type, and the attribute is preserved.
/// </summary>
Tuple = 2,
/// <summary>
/// If this option is active, [ExtensionAttribute] is removed and methods are marked as IsExtensionMethod.
/// Otherwise, the attribute is preserved but the methods are not marked.
/// </summary>
ExtensionMethods = 4,
/// <summary>
/// Only load the public API into the type system.
/// </summary>
OnlyPublicAPI = 8,
/// <summary>
/// Do not cache accessed entities.
/// In a normal type system (without this option), every type or member definition has exactly one ITypeDefinition/IMember
/// instance. This instance is kept alive until the whole type system can be garbage-collected.
/// When this option is specified, the type system avoids these caches.
/// This reduces the memory usage in many cases, but increases the number of allocations.
/// Also, some code in the decompiler expects to be able to compare type/member definitions by reference equality,
/// and thus will fail with uncached type systems.
/// </summary>
Uncached = 0x10,
/// <summary>
/// If this option is active, [DecimalConstantAttribute] is removed and constant values are transformed into simple decimal literals.
/// </summary>
DecimalConstants = 0x20,
/// <summary>
/// If this option is active, modopt and modreq types are preserved in the type system.
///
/// Note: the decompiler currently does not support handling modified types;
/// activating this option may lead to incorrect decompilation or internal errors.
/// </summary>
KeepModifiers = 0x40,
/// <summary>
/// If this option is active, [IsReadOnlyAttribute] on parameters+structs is removed
/// and parameters are marked as in, structs as readonly.
/// Otherwise, the attribute is preserved but the parameters and structs are not marked.
/// </summary>
ReadOnlyStructsAndParameters = 0x80,
/// <summary>
/// If this option is active, [IsByRefLikeAttribute] is removed and structs are marked as ref.
/// Otherwise, the attribute is preserved but the structs are not marked.
/// </summary>
RefStructs = 0x100,
/// <summary>
/// If this option is active, [IsUnmanagedAttribute] is removed from type parameters,
/// and HasUnmanagedConstraint is set instead.
/// </summary>
UnmanagedConstraints = 0x200,
/// <summary>
/// If this option is active, [NullableAttribute] is removed and reference types with
/// nullability annotations are used instead.
/// </summary>
NullabilityAnnotations = 0x400,
/// <summary>
/// If this option is active, [IsReadOnlyAttribute] on methods is removed
/// and the method marked as ThisIsRefReadOnly.
/// </summary>
ReadOnlyMethods = 0x800,
/// <summary>
/// [NativeIntegerAttribute] is used to replace 'IntPtr' types with the 'nint' type.
/// </summary>
NativeIntegers = 0x1000,
/// <summary>
/// Allow function pointer types. If this option is not enabled, function pointers are
/// replaced with the 'IntPtr' type.
/// </summary>
FunctionPointers = 0x2000,
/// <summary>
/// Allow C# 11 scoped annotation. If this option is not enabled, ScopedRefAttribute
/// will be reported as custom attribute.
/// </summary>
ScopedRef = 0x4000,
/// <summary>
/// Replace 'IntPtr' types with the 'nint' type even in absence of [NativeIntegerAttribute].
/// Note: DecompilerTypeSystem constructor removes this setting from the options if
/// not targeting .NET 7 or later.
/// </summary>
NativeIntegersWithoutAttribute = 0x8000,
/// <summary>
/// If this option is active, [RequiresLocationAttribute] on parameters is removed
/// and parameters are marked as ref readonly.
/// Otherwise, the attribute is preserved but the parameters are not marked
/// as if it was a ref parameter without any attributes.
/// </summary>
RefReadOnlyParameters = 0x10000,
/// <summary>
/// If this option is active, [ParamCollectionAttribute] on parameters is removed
/// and parameters are marked as params.
/// Otherwise, the attribute is preserved but the parameters are not marked
/// as if it was a normal parameter without any attributes.
/// </summary>
ParamsCollections = 0x20000,
/// <summary>
/// If this option is active, span types (Span<T> and ReadOnlySpan<T>) are treated like
/// built-in types and language rules of C# 14 and later are applied.
/// </summary>
FirstClassSpanTypes = 0x40000,
/// <summary>
/// Default settings: typical options for the decompiler, with all C# language features enabled.
/// </summary>
Default = Dynamic | Tuple | ExtensionMethods | DecimalConstants | ReadOnlyStructsAndParameters
| RefStructs | UnmanagedConstraints | NullabilityAnnotations | ReadOnlyMethods
| NativeIntegers | FunctionPointers | ScopedRef | NativeIntegersWithoutAttribute
| RefReadOnlyParameters | ParamsCollections | FirstClassSpanTypes
}
/// <summary>
/// Manages the NRefactory type system for the decompiler.
/// </summary>
/// <remarks>
/// This class is thread-safe.
/// </remarks>
public class DecompilerTypeSystem : SimpleCompilation, IDecompilerTypeSystem
{
public static TypeSystemOptions GetOptions(DecompilerSettings settings)
{
var typeSystemOptions = TypeSystemOptions.None;
if (settings.Dynamic)
typeSystemOptions |= TypeSystemOptions.Dynamic;
if (settings.TupleTypes)
typeSystemOptions |= TypeSystemOptions.Tuple;
if (settings.ExtensionMethods)
typeSystemOptions |= TypeSystemOptions.ExtensionMethods;
if (settings.DecimalConstants)
typeSystemOptions |= TypeSystemOptions.DecimalConstants;
if (settings.IntroduceRefModifiersOnStructs)
typeSystemOptions |= TypeSystemOptions.RefStructs;
if (settings.IntroduceReadonlyAndInModifiers)
typeSystemOptions |= TypeSystemOptions.ReadOnlyStructsAndParameters;
if (settings.IntroduceUnmanagedConstraint)
typeSystemOptions |= TypeSystemOptions.UnmanagedConstraints;
if (settings.NullableReferenceTypes)
typeSystemOptions |= TypeSystemOptions.NullabilityAnnotations;
if (settings.ReadOnlyMethods)
typeSystemOptions |= TypeSystemOptions.ReadOnlyMethods;
if (settings.NativeIntegers)
typeSystemOptions |= TypeSystemOptions.NativeIntegers;
if (settings.FunctionPointers)
typeSystemOptions |= TypeSystemOptions.FunctionPointers;
if (settings.ScopedRef)
typeSystemOptions |= TypeSystemOptions.ScopedRef;
if (settings.NumericIntPtr)
typeSystemOptions |= TypeSystemOptions.NativeIntegersWithoutAttribute;
if (settings.RefReadOnlyParameters)
typeSystemOptions |= TypeSystemOptions.RefReadOnlyParameters;
if (settings.ParamsCollections)
typeSystemOptions |= TypeSystemOptions.ParamsCollections;
if (settings.FirstClassSpanTypes)
typeSystemOptions |= TypeSystemOptions.FirstClassSpanTypes;
return typeSystemOptions;
}
public static Task<DecompilerTypeSystem> CreateAsync(PEFile mainModule, IAssemblyResolver assemblyResolver)
{
return CreateAsync(mainModule, assemblyResolver, TypeSystemOptions.Default);
}
public static Task<DecompilerTypeSystem> CreateAsync(PEFile mainModule, IAssemblyResolver assemblyResolver, DecompilerSettings settings)
{
return CreateAsync(mainModule, assemblyResolver, GetOptions(settings ?? throw new ArgumentNullException(nameof(settings))));
}
public static async Task<DecompilerTypeSystem> CreateAsync(PEFile mainModule, IAssemblyResolver assemblyResolver, TypeSystemOptions typeSystemOptions)
{
if (mainModule == null)
throw new ArgumentNullException(nameof(mainModule));
if (assemblyResolver == null)
throw new ArgumentNullException(nameof(assemblyResolver));
var ts = new DecompilerTypeSystem(typeSystemOptions);
await ts.InitializeAsync(mainModule, assemblyResolver)
.ConfigureAwait(false);
return ts;
}
private MetadataModule mainModule;
private TypeSystemOptions typeSystemOptions;
private DecompilerTypeSystem(TypeSystemOptions typeSystemOptions)
{
this.typeSystemOptions = typeSystemOptions;
}
public DecompilerTypeSystem(MetadataFile mainModule, IAssemblyResolver assemblyResolver)
: this(mainModule, assemblyResolver, TypeSystemOptions.Default)
{
}
public DecompilerTypeSystem(MetadataFile mainModule, IAssemblyResolver assemblyResolver, DecompilerSettings settings)
: this(mainModule, assemblyResolver, GetOptions(settings ?? throw new ArgumentNullException(nameof(settings))))
{
}
public DecompilerTypeSystem(MetadataFile mainModule, IAssemblyResolver assemblyResolver, TypeSystemOptions typeSystemOptions)
: this(typeSystemOptions)
{
if (mainModule == null)
throw new ArgumentNullException(nameof(mainModule));
if (assemblyResolver == null)
throw new ArgumentNullException(nameof(assemblyResolver));
InitializeAsync(mainModule, assemblyResolver).GetAwaiter().GetResult();
}
static readonly string[] implicitReferences = new[] {
"System.Runtime.InteropServices",
"System.Runtime.CompilerServices.Unsafe"
};
private async Task InitializeAsync(MetadataFile mainModule, IAssemblyResolver assemblyResolver)
{
// Load referenced assemblies and type-forwarder references.
// This is necessary to make .NET Core/PCL binaries work better.
var referencedAssemblies = new List<MetadataFile>();
var assemblyReferenceQueue = new Queue<(bool IsAssembly, MetadataFile MainModule, object Reference, Task<MetadataFile> ResolveTask)>();
var comparer = KeyComparer.Create(((bool IsAssembly, MetadataFile MainModule, object Reference) reference) =>
reference.IsAssembly ? "A:" + ((IAssemblyReference)reference.Reference).FullName :
"M:" + reference.Reference);
var assemblyReferencesInQueue = new HashSet<(bool IsAssembly, MetadataFile Parent, object Reference)>(comparer);
var mainMetadata = mainModule.Metadata;
var tfm = mainModule.DetectTargetFrameworkId();
var (identifier, version) = UniversalAssemblyResolver.ParseTargetFramework(tfm);
foreach (var h in mainMetadata.GetModuleReferences())
{
try
{
var moduleRef = mainMetadata.GetModuleReference(h);
var moduleName = mainMetadata.GetString(moduleRef.Name);
foreach (var fileHandle in mainMetadata.AssemblyFiles)
{
var file = mainMetadata.GetAssemblyFile(fileHandle);
if (mainMetadata.StringComparer.Equals(file.Name, moduleName) && file.ContainsMetadata)
{
AddToQueue(false, mainModule, moduleName);
break;
}
}
}
catch (BadImageFormatException)
{
}
}
foreach (var refs in mainModule.AssemblyReferences)
{
AddToQueue(true, mainModule, refs);
}
while (assemblyReferenceQueue.Count > 0)
{
var asmRef = assemblyReferenceQueue.Dequeue();
var asm = await asmRef.ResolveTask.ConfigureAwait(false);
if (asm != null)
{
referencedAssemblies.Add(asm);
var metadata = asm.Metadata;
foreach (var h in metadata.ExportedTypes)
{
var exportedType = metadata.GetExportedType(h);
switch (exportedType.Implementation.Kind)
{
case SRM.HandleKind.AssemblyReference:
AddToQueue(true, asm, new AssemblyReference(asm, (SRM.AssemblyReferenceHandle)exportedType.Implementation));
break;
case SRM.HandleKind.AssemblyFile:
var file = metadata.GetAssemblyFile((SRM.AssemblyFileHandle)exportedType.Implementation);
AddToQueue(false, asm, metadata.GetString(file.Name));
break;
}
}
}
if (assemblyReferenceQueue.Count == 0)
{
// For .NET Core and .NET 5 and newer, we need to pull in implicit references which are not included in the metadata,
// as they contain compile-time-only types, such as System.Runtime.InteropServices.dll (for DllImport, MarshalAs, etc.)
switch (identifier)
{
case TargetFrameworkIdentifier.NETCoreApp:
case TargetFrameworkIdentifier.NETStandard:
case TargetFrameworkIdentifier.NET:
foreach (var item in implicitReferences)
{
var existing = referencedAssemblies.FirstOrDefault(asm => asm.Name == item);
if (existing == null)
{
AddToQueue(true, mainModule, AssemblyNameReference.Parse(item + ", Version=" + version.ToString(3) + ".0, Culture=neutral"));
}
}
break;
}
}
}
if (!(identifier == TargetFrameworkIdentifier.NET && version >= new Version(7, 0)))
{
typeSystemOptions &= ~TypeSystemOptions.NativeIntegersWithoutAttribute;
}
var mainModuleWithOptions = mainModule.WithOptions(typeSystemOptions);
// create IModuleReferences for all references
var referencedAssembliesWithOptions = new List<IModuleReference>(referencedAssemblies.Count);
Dictionary<string, (Version version, int insertionIndex)> referenceAssemblyVersionMap = new();
foreach (var file in referencedAssemblies)
{
// if the file is an assembly, we need to make sure to deduplicate all assemblies,
// with the same name, but different version. We keep the highest version number.
if (file.IsAssembly)
{
var newFileVersion = file.Metadata.GetAssemblyDefinition().Version;
if (referenceAssemblyVersionMap.TryGetValue(file.Name, out var info))
{
if (newFileVersion >= info.version)
{
referencedAssembliesWithOptions[info.insertionIndex] = file.WithOptions(typeSystemOptions);
referenceAssemblyVersionMap[file.Name] = (newFileVersion, info.insertionIndex);
}
continue;
}
else
{
referenceAssemblyVersionMap[file.Name] = (file.Metadata.GetAssemblyDefinition().Version, referencedAssembliesWithOptions.Count);
}
}
referencedAssembliesWithOptions.Add(file.WithOptions(typeSystemOptions));
}
// Primitive types are necessary to avoid assertions in ILReader.
// Other known types are necessary in order for transforms to work (e.g. Task<T> for async transform).
// Figure out which known types are missing from our type system so far:
var missingKnownTypes = KnownTypeReference.AllKnownTypes.Where(IsMissing).ToList();
if (missingKnownTypes.Count > 0)
{
Init(mainModuleWithOptions, referencedAssembliesWithOptions.Concat(new[] { MinimalCorlib.CreateWithTypes(missingKnownTypes) }));
}
else
{
Init(mainModuleWithOptions, referencedAssembliesWithOptions);
}
this.mainModule = (MetadataModule)base.MainModule;
void AddToQueue(bool isAssembly, MetadataFile mainModule, object reference)
{
if (assemblyReferencesInQueue.Add((isAssembly, mainModule, reference)))
{
// Immediately start loading the referenced module as we add the entry to the queue.
// This allows loading multiple modules in parallel.
Task<MetadataFile> asm;
if (isAssembly)
{
asm = assemblyResolver.ResolveAsync((IAssemblyReference)reference);
}
else
{
asm = assemblyResolver.ResolveModuleAsync(mainModule, (string)reference);
}
assemblyReferenceQueue.Enqueue((isAssembly, mainModule, reference, asm));
}
}
bool IsMissing(KnownTypeReference knownType)
{
var name = knownType.TypeName;
if (!mainModule.GetTypeDefinition(name).IsNil)
return false;
foreach (var file in referencedAssemblies)
{
if (!file.GetTypeDefinition(name).IsNil)
return false;
}
return true;
}
}
public new MetadataModule MainModule => mainModule;
public override TypeSystemOptions TypeSystemOptions => typeSystemOptions;
}
}