-
Notifications
You must be signed in to change notification settings - Fork 4.7k
/
ReflectionMarker.cs
349 lines (286 loc) · 17 KB
/
ReflectionMarker.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
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
// 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.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Reflection.Metadata;
using ILCompiler.DependencyAnalysis;
using ILCompiler.Logging;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using Internal.TypeSystem;
using DependencyList = ILCompiler.DependencyAnalysisFramework.DependencyNodeCore<ILCompiler.DependencyAnalysis.NodeFactory>.DependencyList;
#nullable enable
#pragma warning disable IDE0060
namespace ILCompiler.Dataflow
{
public class ReflectionMarker
{
private DependencyList _dependencies = new DependencyList();
private readonly Logger _logger;
private readonly MetadataType? _typeHierarchyDataFlowOrigin;
private readonly bool _enabled;
public NodeFactory Factory { get; }
public FlowAnnotations Annotations { get; }
public DependencyList Dependencies { get => _dependencies; }
internal enum AccessKind
{
Unspecified,
DynamicallyAccessedMembersMark,
TokenAccess
}
public ReflectionMarker(Logger logger, NodeFactory factory, FlowAnnotations annotations, MetadataType? typeHierarchyDataFlowOrigin, bool enabled)
{
_logger = logger;
Factory = factory;
Annotations = annotations;
_typeHierarchyDataFlowOrigin = typeHierarchyDataFlowOrigin;
_enabled = enabled;
}
internal void MarkTypeForDynamicallyAccessedMembers(in MessageOrigin origin, TypeDesc typeDefinition, DynamicallyAccessedMemberTypes requiredMemberTypes, string reason, bool declaredOnly = false)
{
if (!_enabled)
return;
foreach (var member in typeDefinition.GetDynamicallyAccessedMembers(requiredMemberTypes, declaredOnly))
{
MarkTypeSystemEntity(origin, member, reason, AccessKind.DynamicallyAccessedMembersMark);
}
}
internal void MarkTypeSystemEntity(in MessageOrigin origin, TypeSystemEntity entity, string reason, AccessKind accessKind = AccessKind.Unspecified)
{
switch (entity)
{
case MethodDesc method:
MarkMethod(origin, method, reason, accessKind);
break;
case FieldDesc field:
MarkField(origin, field, reason, accessKind);
break;
case MetadataType nestedType:
MarkType(origin, nestedType, reason, accessKind);
break;
case PropertyPseudoDesc property:
MarkProperty(origin, property, reason, accessKind);
break;
case EventPseudoDesc @event:
MarkEvent(origin, @event, reason, accessKind);
break;
// case InterfaceImplementation
// Nothing to do currently as Native AOT will preserve all interfaces on a preserved type
}
}
internal bool TryResolveTypeNameAndMark(string typeName, in DiagnosticContext diagnosticContext, bool needsAssemblyName, string reason, [NotNullWhen(true)] out TypeDesc? type)
{
ModuleDesc? callingModule = ((diagnosticContext.Origin.MemberDefinition as MethodDesc)?.OwningType as MetadataType)?.Module;
List<ModuleDesc> referencedModules = new();
TypeDesc foundType = System.Reflection.TypeNameParser.ResolveType(typeName, callingModule, diagnosticContext.Origin.MemberDefinition!.Context,
referencedModules, out bool typeWasNotFoundInAssemblyNorBaseLibrary);
if (foundType == null)
{
if (needsAssemblyName && typeWasNotFoundInAssemblyNorBaseLibrary)
diagnosticContext.AddDiagnostic(DiagnosticId.TypeWasNotFoundInAssemblyNorBaseLibrary, typeName);
type = default;
return false;
}
if (_enabled)
{
foreach (ModuleDesc referencedModule in referencedModules)
{
// Also add module metadata in case this reference was through a type forward
if (Factory.MetadataManager.CanGenerateMetadata(referencedModule.GetGlobalModuleType()))
_dependencies.Add(Factory.ModuleMetadata(referencedModule), reason);
}
MarkType(diagnosticContext.Origin, foundType, reason);
}
type = foundType;
return true;
}
internal void MarkType(in MessageOrigin origin, TypeDesc type, string reason, AccessKind accessKind = AccessKind.Unspecified)
{
if (!_enabled)
return;
RootingHelpers.TryGetDependenciesForReflectedType(ref _dependencies, Factory, type, reason);
}
internal void MarkMethod(in MessageOrigin origin, MethodDesc method, string reason, AccessKind accessKind = AccessKind.Unspecified)
{
if (!_enabled)
return;
CheckAndWarnOnReflectionAccess(origin, method, accessKind);
RootingHelpers.TryGetDependenciesForReflectedMethod(ref _dependencies, Factory, method, reason);
}
internal void MarkField(in MessageOrigin origin, FieldDesc field, string reason, AccessKind accessKind = AccessKind.Unspecified)
{
if (!_enabled)
return;
CheckAndWarnOnReflectionAccess(origin, field, accessKind);
RootingHelpers.TryGetDependenciesForReflectedField(ref _dependencies, Factory, field, reason);
}
internal void MarkProperty(in MessageOrigin origin, PropertyPseudoDesc property, string reason, AccessKind accessKind = AccessKind.Unspecified)
{
if (!_enabled)
return;
if (property.GetMethod != null)
MarkMethod(origin, property.GetMethod, reason);
if (property.SetMethod != null)
MarkMethod(origin, property.SetMethod, reason);
}
private void MarkEvent(in MessageOrigin origin, EventPseudoDesc @event, string reason, AccessKind accessKind = AccessKind.Unspecified)
{
if (!_enabled)
return;
if (@event.AddMethod != null)
MarkMethod(origin, @event.AddMethod, reason);
if (@event.RemoveMethod != null)
MarkMethod(origin, @event.RemoveMethod, reason);
}
internal void MarkConstructorsOnType(in MessageOrigin origin, TypeDesc type, Func<MethodDesc, bool>? filter, string reason, BindingFlags? bindingFlags = null)
{
if (!_enabled)
return;
foreach (var ctor in type.GetConstructorsOnType(filter, bindingFlags))
MarkMethod(origin, ctor, reason);
}
internal void MarkFieldsOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func<FieldDesc, bool> filter, string reason, BindingFlags? bindingFlags = BindingFlags.Default)
{
if (!_enabled)
return;
foreach (var field in type.GetFieldsOnTypeHierarchy(filter, bindingFlags))
MarkField(origin, field, reason);
}
internal void MarkPropertiesOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func<PropertyPseudoDesc, bool> filter, string reason, BindingFlags? bindingFlags = BindingFlags.Default)
{
if (!_enabled)
return;
foreach (var property in type.GetPropertiesOnTypeHierarchy(filter, bindingFlags))
MarkProperty(origin, property, reason);
}
internal void MarkEventsOnTypeHierarchy(in MessageOrigin origin, TypeDesc type, Func<EventPseudoDesc, bool> filter, string reason, BindingFlags? bindingFlags = BindingFlags.Default)
{
if (!_enabled)
return;
foreach (var @event in type.GetEventsOnTypeHierarchy(filter, bindingFlags))
MarkEvent(origin, @event, reason);
}
internal void MarkStaticConstructor(in MessageOrigin origin, TypeDesc type, string reason)
{
if (!_enabled)
return;
if (type.HasStaticConstructor)
CheckAndWarnOnReflectionAccess(origin, type.GetStaticConstructor());
if (!type.IsGenericDefinition && !type.ContainsSignatureVariables(treatGenericParameterLikeSignatureVariable: true) && Factory.PreinitializationManager.HasLazyStaticConstructor(type))
{
// Mark the GC static base - it contains a pointer to the class constructor, but also info
// about whether the class constructor already executed and it's what is looked at at runtime.
_dependencies.Add(Factory.TypeNonGCStaticsSymbol((MetadataType)type), "RunClassConstructor reference");
}
}
internal void CheckAndWarnOnReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, AccessKind accessKind = AccessKind.Unspecified)
{
if (!_enabled)
return;
if (_typeHierarchyDataFlowOrigin is not null)
{
ReportWarningsForTypeHierarchyReflectionAccess(origin, entity);
}
else
{
ReportWarningsForReflectionAccess(origin, entity, accessKind);
}
}
private void ReportWarningsForReflectionAccess(in MessageOrigin origin, TypeSystemEntity entity, AccessKind accessKind)
{
Debug.Assert(entity is MethodDesc or FieldDesc);
// Note that we're using `ShouldSuppressAnalysisWarningsForRequires` instead of `DoesMemberRequire`.
// This is because reflection access is actually problematic on all members which are in a "requires" scope
// so for example even instance methods. See for example https://github.com/dotnet/linker/issues/3140 - it's possible
// to call a method on a "null" instance via reflection.
if (_logger.ShouldSuppressAnalysisWarningsForRequires(entity, DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out CustomAttributeValue<TypeDesc>? requiresAttribute) &&
ShouldProduceRequiresWarningForReflectionAccess(entity, accessKind))
ReportRequires(origin, entity, DiagnosticUtilities.RequiresUnreferencedCodeAttribute, requiresAttribute.Value);
if (_logger.ShouldSuppressAnalysisWarningsForRequires(entity, DiagnosticUtilities.RequiresAssemblyFilesAttribute, out requiresAttribute) &&
ShouldProduceRequiresWarningForReflectionAccess(entity, accessKind))
ReportRequires(origin, entity, DiagnosticUtilities.RequiresAssemblyFilesAttribute, requiresAttribute.Value);
if (_logger.ShouldSuppressAnalysisWarningsForRequires(entity, DiagnosticUtilities.RequiresDynamicCodeAttribute, out requiresAttribute) &&
ShouldProduceRequiresWarningForReflectionAccess(entity, accessKind))
ReportRequires(origin, entity, DiagnosticUtilities.RequiresDynamicCodeAttribute, requiresAttribute.Value);
// Below is about accessing DAM annotated members, so only RUC is applicable as a suppression scope
if (_logger.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute))
return;
bool isReflectionAccessCoveredByDAM = Annotations.ShouldWarnWhenAccessedForReflection(entity);
if (isReflectionAccessCoveredByDAM && ShouldProduceRequiresWarningForReflectionAccess(entity, accessKind))
{
if (entity is MethodDesc)
_logger.LogWarning(origin, DiagnosticId.DynamicallyAccessedMembersMethodAccessedViaReflection, entity.GetDisplayName());
else
_logger.LogWarning(origin, DiagnosticId.DynamicallyAccessedMembersFieldAccessedViaReflection, entity.GetDisplayName());
}
// We decided to not warn on reflection access to compiler-generated methods:
// https://github.com/dotnet/runtime/issues/85042
static bool ShouldProduceRequiresWarningForReflectionAccess(TypeSystemEntity entity, AccessKind accessKind)
{
bool isCompilerGenerated = CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(entity);
// Compiler generated code accessed via a token is considered a "hard" reference
// even though we also have to treat it as reflection access.
// So we need to enforce RUC check/warn in this case.
bool forceRequiresWarning = accessKind == AccessKind.TokenAccess;
return !isCompilerGenerated || forceRequiresWarning;
}
}
private void ReportWarningsForTypeHierarchyReflectionAccess(MessageOrigin origin, TypeSystemEntity entity)
{
Debug.Assert(entity is MethodDesc or FieldDesc);
// Don't check whether the current scope is a RUC type or RUC method because these warnings
// are not suppressed in RUC scopes. Here the scope represents the DynamicallyAccessedMembers
// annotation on a type, not a callsite which uses the annotation. We always want to warn about
// possible reflection access indicated by these annotations.
Debug.Assert(_typeHierarchyDataFlowOrigin != null);
static bool IsDeclaredWithinType(TypeSystemEntity member, TypeDesc type)
{
TypeDesc owningType = member.GetOwningType();
while (owningType != null)
{
if (owningType == type)
return true;
owningType = owningType.GetOwningType();
}
return false;
}
var reportOnMember = IsDeclaredWithinType(entity, _typeHierarchyDataFlowOrigin);
if (reportOnMember)
origin = new MessageOrigin(entity);
// For now we decided to not report single-file or dynamic-code warnings due to type hierarchy marking.
// It is considered too complex to figure out for the user and the likelihood of this
// causing problems is pretty low.
bool isReflectionAccessCoveredByRUC = _logger.ShouldSuppressAnalysisWarningsForRequires(entity, DiagnosticUtilities.RequiresUnreferencedCodeAttribute, out CustomAttributeValue<TypeDesc>? requiresUnreferencedCodeAttribute);
bool isCompilerGenerated = CompilerGeneratedState.IsNestedFunctionOrStateMachineMember(entity);
if (isReflectionAccessCoveredByRUC && !isCompilerGenerated)
{
var id = reportOnMember ? DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithRequiresUnreferencedCode : DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithRequiresUnreferencedCode;
_logger.LogWarning(origin, id, _typeHierarchyDataFlowOrigin.GetDisplayName(),
entity.GetDisplayName(),
MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeMessage(requiresUnreferencedCodeAttribute!.Value)),
MessageFormat.FormatRequiresAttributeMessageArg(DiagnosticUtilities.GetRequiresAttributeUrl(requiresUnreferencedCodeAttribute!.Value)));
}
bool isReflectionAccessCoveredByDAM = Annotations.ShouldWarnWhenAccessedForReflection(entity);
if (isReflectionAccessCoveredByDAM && !isCompilerGenerated)
{
var id = reportOnMember ? DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberWithDynamicallyAccessedMembers : DiagnosticId.DynamicallyAccessedMembersOnTypeReferencesMemberOnBaseWithDynamicallyAccessedMembers;
_logger.LogWarning(origin, id, _typeHierarchyDataFlowOrigin.GetDisplayName(), entity.GetDisplayName());
}
// We decided to not warn on reflection access to compiler-generated methods:
// https://github.com/dotnet/runtime/issues/85042
}
private void ReportRequires(in MessageOrigin origin, TypeSystemEntity entity, string requiresAttributeName, in CustomAttributeValue<TypeDesc> requiresAttribute)
{
var diagnosticContext = new DiagnosticContext(
origin,
_logger.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresUnreferencedCodeAttribute),
_logger.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresDynamicCodeAttribute),
_logger.ShouldSuppressAnalysisWarningsForRequires(origin.MemberDefinition, DiagnosticUtilities.RequiresAssemblyFilesAttribute),
_logger);
ReflectionMethodBodyScanner.ReportRequires(diagnosticContext, entity, requiresAttributeName, requiresAttribute);
}
}
}