Welcome to mirror list, hosted at ThFree Co, Russian Federation.

ReflectionMethodBodyScanner.cs « Linker.Dataflow « linker « src - github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 804e5a21430d144c15baf52f98623378e0cdac51 (plain)
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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using ILLink.Shared;
using ILLink.Shared.TrimAnalysis;
using ILLink.Shared.TypeSystemProxy;
using Mono.Cecil;
using Mono.Cecil.Cil;
using Mono.Linker.Steps;
using MultiValue = ILLink.Shared.DataFlow.ValueSet<ILLink.Shared.DataFlow.SingleValue>;

namespace Mono.Linker.Dataflow
{
	sealed class ReflectionMethodBodyScanner : MethodBodyScanner
	{
		readonly MarkStep _markStep;
		MessageOrigin _origin;
		readonly FlowAnnotations _annotations;
		readonly ReflectionMarker _reflectionMarker;
		public readonly TrimAnalysisPatternStore TrimAnalysisPatterns;

		public static bool RequiresReflectionMethodBodyScannerForCallSite (LinkContext context, MethodReference calledMethod)
		{
			MethodDefinition? methodDefinition = context.TryResolve (calledMethod);
			if (methodDefinition == null)
				return false;

			return Intrinsics.GetIntrinsicIdForMethod (methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel ||
				context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (methodDefinition) ||
				context.Annotations.DoesMethodRequireUnreferencedCode (methodDefinition, out _) ||
				methodDefinition.IsPInvokeImpl && ComDangerousMethod (methodDefinition, context);
		}

		public static bool RequiresReflectionMethodBodyScannerForMethodBody (LinkContext context, MethodDefinition methodDefinition)
		{
			return Intrinsics.GetIntrinsicIdForMethod (methodDefinition) > IntrinsicId.RequiresReflectionBodyScanner_Sentinel ||
				context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (methodDefinition);
		}

		public static bool RequiresReflectionMethodBodyScannerForAccess (LinkContext context, FieldReference field)
		{
			FieldDefinition? fieldDefinition = context.TryResolve (field);
			if (fieldDefinition == null)
				return false;

			return context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (fieldDefinition);
		}

		public ReflectionMethodBodyScanner (LinkContext context, MarkStep parent, MessageOrigin origin)
			: base (context)
		{
			_markStep = parent;
			_origin = origin;
			_annotations = context.Annotations.FlowAnnotations;
			_reflectionMarker = new ReflectionMarker (context, parent, enabled: false);
			TrimAnalysisPatterns = new TrimAnalysisPatternStore (MultiValueLattice, context);
		}

		public override void InterproceduralScan (MethodIL methodIL)
		{
			base.InterproceduralScan (methodIL);

			var reflectionMarker = new ReflectionMarker (_context, _markStep, enabled: true);
			TrimAnalysisPatterns.MarkAndProduceDiagnostics (reflectionMarker, _markStep);
		}

		protected override void Scan (MethodIL methodIL, ref InterproceduralState interproceduralState)
		{
			_origin = new MessageOrigin (methodIL.Method);
			base.Scan (methodIL, ref interproceduralState);

			if (!methodIL.Method.ReturnsVoid ()) {
				var method = methodIL.Method;
				var methodReturnValue = _annotations.GetMethodReturnValue (method);
				if (methodReturnValue.DynamicallyAccessedMemberTypes != 0)
					HandleAssignmentPattern (_origin, ReturnValue, methodReturnValue);
			}
		}

		protected override void WarnAboutInvalidILInMethod (MethodBody method, int ilOffset)
		{
			// Serves as a debug helper to make sure valid IL is not considered invalid.
			//
			// The .NET Native compiler used to warn if it detected invalid IL during treeshaking,
			// but the warnings were often triggered in autogenerated dead code of a major game engine
			// and resulted in support calls. No point in warning. If the code gets exercised at runtime,
			// an InvalidProgramException will likely be raised.
			Debug.Fail ("Invalid IL or a bug in the scanner");
		}

		protected override ValueWithDynamicallyAccessedMembers GetMethodParameterValue (ParameterProxy parameter)
			=> GetMethodParameterValue (parameter, _context.Annotations.FlowAnnotations.GetParameterAnnotation (parameter));

		MethodParameterValue GetMethodParameterValue (ParameterProxy parameter, DynamicallyAccessedMemberTypes dynamicallyAccessedMemberTypes)
			=> _annotations.GetMethodParameterValue (parameter, dynamicallyAccessedMemberTypes);

		protected override MultiValue GetFieldValue (FieldDefinition field) => _annotations.GetFieldValue (field);

		private void HandleStoreValueWithDynamicallyAccessedMembers (ValueWithDynamicallyAccessedMembers targetValue, Instruction operation, MultiValue sourceValue)
		{
			if (targetValue.DynamicallyAccessedMemberTypes != 0) {
				_origin = _origin.WithInstructionOffset (operation.Offset);
				HandleAssignmentPattern (_origin, sourceValue, targetValue);
			}
		}

		protected override void HandleStoreField (MethodDefinition method, FieldValue field, Instruction operation, MultiValue valueToStore)
			=> HandleStoreValueWithDynamicallyAccessedMembers (field, operation, valueToStore);

		protected override void HandleStoreParameter (MethodDefinition method, MethodParameterValue parameter, Instruction operation, MultiValue valueToStore)
			=> HandleStoreValueWithDynamicallyAccessedMembers (parameter, operation, valueToStore);

		protected override void HandleStoreMethodReturnValue (MethodDefinition method, MethodReturnValue returnValue, Instruction operation, MultiValue valueToStore)
			=> HandleStoreValueWithDynamicallyAccessedMembers (returnValue, operation, valueToStore);

		public override bool HandleCall (MethodBody callingMethodBody, MethodReference calledMethod, Instruction operation, ValueNodeList methodParams, out MultiValue methodReturnValue)
		{
			methodReturnValue = new ();

			var reflectionProcessed = _markStep.ProcessReflectionDependency (callingMethodBody, operation);
			if (reflectionProcessed)
				return false;

			Debug.Assert (callingMethodBody.Method == _origin.Provider);
			var calledMethodDefinition = _context.TryResolve (calledMethod);
			if (calledMethodDefinition == null)
				return false;

			_origin = _origin.WithInstructionOffset (operation.Offset);

			MultiValue instanceValue;
			ImmutableArray<MultiValue> arguments;
			if (calledMethodDefinition.HasImplicitThis ()) {
				instanceValue = methodParams[0];
				arguments = methodParams.Skip (1).ToImmutableArray ();
			} else {
				instanceValue = MultiValueLattice.Top;
				arguments = methodParams.ToImmutableArray ();
			}

			TrimAnalysisPatterns.Add (new TrimAnalysisMethodCallPattern (
				operation,
				calledMethod,
				instanceValue,
				arguments,
				_origin
			));

			var diagnosticContext = new DiagnosticContext (_origin, diagnosticsEnabled: false, _context);
			return HandleCall (
				operation,
				calledMethod,
				instanceValue,
				arguments,
				diagnosticContext,
				_reflectionMarker,
				_context,
				_markStep,
				out methodReturnValue);
		}

		public static bool HandleCall (
			Instruction operation,
			MethodReference calledMethod,
			MultiValue instanceValue,
			ImmutableArray<MultiValue> argumentValues,
			DiagnosticContext diagnosticContext,
			ReflectionMarker reflectionMarker,
			LinkContext context,
			MarkStep markStep,
			out MultiValue methodReturnValue)
		{
			var origin = diagnosticContext.Origin;
			var calledMethodDefinition = context.TryResolve (calledMethod);
			Debug.Assert (calledMethodDefinition != null);
			var callingMethodDefinition = origin.Provider as MethodDefinition;
			Debug.Assert (callingMethodDefinition != null);

			bool requiresDataFlowAnalysis = context.Annotations.FlowAnnotations.RequiresDataFlowAnalysis (calledMethodDefinition);
			var annotatedMethodReturnValue = context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition);
			Debug.Assert (requiresDataFlowAnalysis || annotatedMethodReturnValue.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.None);

			MultiValue? maybeMethodReturnValue = null;

			var handleCallAction = new HandleCallAction (context, reflectionMarker, diagnosticContext, callingMethodDefinition);
			var intrinsicId = Intrinsics.GetIntrinsicIdForMethod (calledMethodDefinition);
			switch (intrinsicId) {
			case IntrinsicId.IntrospectionExtensions_GetTypeInfo:
			case IntrinsicId.TypeInfo_AsType:
			case IntrinsicId.Type_get_UnderlyingSystemType:
			case IntrinsicId.Type_GetTypeFromHandle:
			case IntrinsicId.Type_get_TypeHandle:
			case IntrinsicId.Type_GetInterface:
			case IntrinsicId.Type_get_AssemblyQualifiedName:
			case IntrinsicId.RuntimeHelpers_RunClassConstructor:
			case IntrinsicId.Type_GetConstructors__BindingFlags:
			case IntrinsicId.Type_GetMethods__BindingFlags:
			case IntrinsicId.Type_GetFields__BindingFlags:
			case IntrinsicId.Type_GetProperties__BindingFlags:
			case IntrinsicId.Type_GetEvents__BindingFlags:
			case IntrinsicId.Type_GetNestedTypes__BindingFlags:
			case IntrinsicId.Type_GetMembers__BindingFlags:
			case IntrinsicId.Type_GetField:
			case IntrinsicId.Type_GetProperty:
			case IntrinsicId.Type_GetEvent:
			case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeEvent:
			case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeField:
			case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeMethod:
			case IntrinsicId.RuntimeReflectionExtensions_GetRuntimeProperty:
			case IntrinsicId.Type_GetMember:
			case IntrinsicId.Type_GetMethod:
			case IntrinsicId.Type_GetNestedType:
			case IntrinsicId.Nullable_GetUnderlyingType:
			case IntrinsicId.Expression_Property:
			case IntrinsicId.Expression_Field:
			case IntrinsicId.Type_get_BaseType:
			case IntrinsicId.Type_GetConstructor:
			case IntrinsicId.MethodBase_GetMethodFromHandle:
			case IntrinsicId.MethodBase_get_MethodHandle:
			case IntrinsicId.Type_MakeGenericType:
			case IntrinsicId.MethodInfo_MakeGenericMethod:
			case IntrinsicId.Expression_Call:
			case IntrinsicId.Expression_New:
			case IntrinsicId.Type_GetType:
			case IntrinsicId.Activator_CreateInstance__Type:
			case IntrinsicId.Activator_CreateInstance__AssemblyName_TypeName:
			case IntrinsicId.Activator_CreateInstanceFrom:
			case IntrinsicId.AppDomain_CreateInstance:
			case IntrinsicId.AppDomain_CreateInstanceAndUnwrap:
			case IntrinsicId.AppDomain_CreateInstanceFrom:
			case IntrinsicId.AppDomain_CreateInstanceFromAndUnwrap:
			case IntrinsicId.Assembly_CreateInstance: {
					return handleCallAction.Invoke (calledMethodDefinition, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
				}

			case IntrinsicId.None: {
					if (calledMethodDefinition.IsPInvokeImpl && ComDangerousMethod (calledMethodDefinition, context))
						diagnosticContext.AddDiagnostic (DiagnosticId.CorrectnessOfCOMCannotBeGuaranteed, calledMethodDefinition.GetDisplayName ());
					if (context.Annotations.DoesMethodRequireUnreferencedCode (calledMethodDefinition, out RequiresUnreferencedCodeAttribute? requiresUnreferencedCode))
						MarkStep.ReportRequiresUnreferencedCode (calledMethodDefinition.GetDisplayName (), requiresUnreferencedCode, diagnosticContext);

					return handleCallAction.Invoke (calledMethodDefinition, instanceValue, argumentValues, intrinsicId, out methodReturnValue);
				}

			case IntrinsicId.TypeDelegator_Ctor: {
					// This is an identity function for analysis purposes
					if (operation.OpCode == OpCodes.Newobj)
						AddReturnValue (argumentValues[0]);
				}
				break;

			case IntrinsicId.Array_Empty: {
					AddReturnValue (ArrayValue.Create (0, ((GenericInstanceMethod) calledMethod).GenericArguments[0]));
				}
				break;

			case IntrinsicId.Enum_GetValues:
			case IntrinsicId.Marshal_SizeOf:
			case IntrinsicId.Marshal_OffsetOf:
			case IntrinsicId.Marshal_PtrToStructure:
			case IntrinsicId.Marshal_DestroyStructure:
			case IntrinsicId.Marshal_GetDelegateForFunctionPointer:
				// These intrinsics are not interesting for trimmer (they are interesting for AOT and that's why they are recognized)
				break;

			//
			// System.Object
			//
			// GetType()
			//
			case IntrinsicId.Object_GetType: {
					foreach (var valueNode in instanceValue) {
						// Note that valueNode can be statically typed in IL as some generic argument type.
						// For example:
						//   void Method<T>(T instance) { instance.GetType().... }
						// Currently this case will end up with null StaticType - since there's no typedef for the generic argument type.
						// But it could be that T is annotated with for example PublicMethods:
						//   void Method<[DAM(PublicMethods)] T>(T instance) { instance.GetType().GetMethod("Test"); }
						// In this case it's in theory possible to handle it, by treating the T basically as a base class
						// for the actual type of "instance". But the analysis for this would be pretty complicated (as the marking
						// has to happen on the callsite, which doesn't know that GetType() will be used...).
						// For now we're intentionally ignoring this case - it will produce a warning.
						// The counter example is:
						//   Method<Base>(new Derived);
						// In this case to get correct results, trimmer would have to mark all public methods on Derived. Which
						// currently it won't do.

						TypeDefinition? staticType = (valueNode as IValueWithStaticType)?.StaticType;
						if (staticType is null) {
							// We don't know anything about the type GetType was called on. Track this as a usual result of a method call without any annotations
							AddReturnValue (context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition));
						} else if (staticType.IsSealed || staticType.IsTypeOf ("System", "Delegate")) {
							// We can treat this one the same as if it was a typeof() expression

							// We can allow Object.GetType to be modeled as System.Delegate because we keep all methods
							// on delegates anyway so reflection on something this approximation would miss is actually safe.

							// We ignore the fact that the type can be annotated (see below for handling of annotated types)
							// This means the annotations (if any) won't be applied - instead we rely on the exact knowledge
							// of the type. So for example even if the type is annotated with PublicMethods
							// but the code calls GetProperties on it - it will work - mark properties, don't mark methods
							// since we ignored the fact that it's annotated.
							// This can be seen a little bit as a violation of the annotation, but we already have similar cases
							// where a parameter is annotated and if something in the method sets a specific known type to it
							// we will also make it just work, even if the annotation doesn't match the usage.
							AddReturnValue (new SystemTypeValue (staticType));
						} else {
							// Make sure the type is marked (this will mark it as used via reflection, which is sort of true)
							// This should already be true for most cases (method params, fields, ...), but just in case
							reflectionMarker.MarkType (origin, staticType);

							var annotation = markStep.DynamicallyAccessedMembersTypeHierarchy
								.ApplyDynamicallyAccessedMembersToTypeHierarchy (staticType);

							// Return a value which is "unknown type" with annotation. For now we'll use the return value node
							// for the method, which means we're loosing the information about which staticType this
							// started with. For now we don't need it, but we can add it later on.
							AddReturnValue (context.Annotations.FlowAnnotations.GetMethodReturnValue (calledMethodDefinition, annotation));
						}
					}
				}
				break;

			// Note about Activator.CreateInstance<T>
			// There are 2 interesting cases:
			//  - The generic argument for T is either specific type or annotated - in that case generic instantiation will handle this
			//    since from .NET 6+ the T is annotated with PublicParameterlessConstructor annotation, so the linker would apply this as for any other method.
			//  - The generic argument for T is unannotated type - the generic instantiantion handling has a special case for handling PublicParameterlessConstructor requirement
			//    in such that if the generic argument type has the "new" constraint it will not warn (as it is effectively the same thing semantically).
			//    For all other cases, the linker would have already produced a warning.

			default:
				throw new NotImplementedException ("Unhandled intrinsic");
			}

			// If we get here, we handled this as an intrinsic.  As a convenience, if the code above
			// didn't set the return value (and the method has a return value), we will set it to be an
			// unknown value with the return type of the method.
			bool returnsVoid = calledMethod.ReturnsVoid ();
			methodReturnValue = maybeMethodReturnValue ?? (returnsVoid ?
				MultiValueLattice.Top :
				annotatedMethodReturnValue);

			// Validate that the return value has the correct annotations as per the method return value annotations
			if (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes != 0) {
				foreach (var uniqueValue in methodReturnValue) {
					if (uniqueValue is ValueWithDynamicallyAccessedMembers methodReturnValueWithMemberTypes) {
						if (!methodReturnValueWithMemberTypes.DynamicallyAccessedMemberTypes.HasFlag (annotatedMethodReturnValue.DynamicallyAccessedMemberTypes))
							throw new InvalidOperationException ($"Internal linker error: processing of call from {callingMethodDefinition.GetDisplayName ()} to {calledMethod.GetDisplayName ()} returned value which is not correctly annotated with the expected dynamic member access kinds.");
					} else if (uniqueValue is SystemTypeValue) {
						// SystemTypeValue can fulfill any requirement, so it's always valid
						// The requirements will be applied at the point where it's consumed (passed as a method parameter, set as field value, returned from the method)
					} else {
						throw new InvalidOperationException ($"Internal linker error: processing of call from {callingMethodDefinition.GetDisplayName ()} to {calledMethod.GetDisplayName ()} returned value which is not correctly annotated with the expected dynamic member access kinds.");
					}
				}
			}

			return true;

			void AddReturnValue (MultiValue value)
			{
				maybeMethodReturnValue = (maybeMethodReturnValue is null) ? value : MultiValueLattice.Meet ((MultiValue) maybeMethodReturnValue, value);
			}
		}

		static bool IsComInterop (IMarshalInfoProvider marshalInfoProvider, TypeReference parameterType, LinkContext context)
		{
			// This is best effort. One can likely find ways how to get COM without triggering these alarms.
			// AsAny marshalling of a struct with an object-typed field would be one, for example.

			// This logic roughly corresponds to MarshalInfo::MarshalInfo in CoreCLR,
			// not trying to handle invalid cases and distinctions that are not interesting wrt
			// "is this COM?" question.

			NativeType nativeType = NativeType.None;
			if (marshalInfoProvider.HasMarshalInfo) {
				nativeType = marshalInfoProvider.MarshalInfo.NativeType;
			}

			if (nativeType == NativeType.IUnknown || nativeType == NativeType.IDispatch || nativeType == NativeType.IntF) {
				// This is COM by definition
				return true;
			}

			if (nativeType == NativeType.None) {
				// Resolve will look at the element type
				var parameterTypeDef = context.TryResolve (parameterType);

				if (parameterTypeDef != null) {
					if (parameterTypeDef.IsTypeOf (WellKnownType.System_Array)) {
						// System.Array marshals as IUnknown by default
						return true;
					} else if (parameterTypeDef.IsTypeOf (WellKnownType.System_String) ||
						parameterTypeDef.IsTypeOf ("System.Text", "StringBuilder")) {
						// String and StringBuilder are special cased by interop
						return false;
					}

					if (parameterTypeDef.IsValueType) {
						// Value types don't marshal as COM
						return false;
					} else if (parameterTypeDef.IsInterface) {
						// Interface types marshal as COM by default
						return true;
					} else if (parameterTypeDef.IsMulticastDelegate ()) {
						// Delegates are special cased by interop
						return false;
					} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "CriticalHandle", context)) {
						// Subclasses of CriticalHandle are special cased by interop
						return false;
					} else if (parameterTypeDef.IsSubclassOf ("System.Runtime.InteropServices", "SafeHandle", context)) {
						// Subclasses of SafeHandle are special cased by interop
						return false;
					} else if (!parameterTypeDef.IsSequentialLayout && !parameterTypeDef.IsExplicitLayout) {
						// Rest of classes that don't have layout marshal as COM
						return true;
					}
				}
			}

			return false;
		}

		void HandleAssignmentPattern (
			in MessageOrigin origin,
			in MultiValue value,
			ValueWithDynamicallyAccessedMembers targetValue)
		{
			TrimAnalysisPatterns.Add (new TrimAnalysisAssignmentPattern (value, targetValue, origin));
		}

		private static bool ComDangerousMethod (MethodDefinition methodDefinition, LinkContext context)
		{
			bool comDangerousMethod = IsComInterop (methodDefinition.MethodReturnType, methodDefinition.ReturnType, context);
#pragma warning disable RS0030 // MethodDefinition.Parameters is banned. Here we iterate through the parameters and don't need to worry about the 'this' parameter.
			foreach (ParameterDefinition pd in methodDefinition.Parameters) {
				comDangerousMethod |= IsComInterop (pd, pd.ParameterType, context);
			}
#pragma warning restore RS0030

			return comDangerousMethod;
		}
	}
}