diff options
9 files changed, 424 insertions, 107 deletions
diff --git a/src/linker/Linker.Dataflow/MethodBodyScanner.cs b/src/linker/Linker.Dataflow/MethodBodyScanner.cs index 5ad2a17db..a13773f3f 100644 --- a/src/linker/Linker.Dataflow/MethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/MethodBodyScanner.cs @@ -166,33 +166,33 @@ namespace Mono.Linker.Dataflow } } - public struct ValueBasicBlockPair - { - public ValueNode Value; - public int BasicBlockIndex; - } - private static void StoreMethodLocalValue<KeyType> ( Dictionary<KeyType, ValueBasicBlockPair> valueCollection, ValueNode valueToStore, KeyType collectionKey, - int curBasicBlock) + int curBasicBlock, + int? maxTrackedValues = null) { ValueBasicBlockPair newValue = new ValueBasicBlockPair { BasicBlockIndex = curBasicBlock }; ValueBasicBlockPair existingValue; - if (valueCollection.TryGetValue (collectionKey, out existingValue) - && existingValue.BasicBlockIndex == curBasicBlock) { - // If the previous value was stored in the current basic block, then we can safely - // overwrite the previous value with the new one. + if (valueCollection.TryGetValue (collectionKey, out existingValue)) { + if (existingValue.BasicBlockIndex == curBasicBlock) { + // If the previous value was stored in the current basic block, then we can safely + // overwrite the previous value with the new one. + newValue.Value = valueToStore; + } else { + // If the previous value came from a previous basic block, then some other use of + // the local could see the previous value, so we must merge the new value with the + // old value. + newValue.Value = MergePointValue.MergeValues (existingValue.Value, valueToStore); + } + valueCollection[collectionKey] = newValue; + } else if (maxTrackedValues == null || valueCollection.Count < maxTrackedValues) { + // We're not currently tracking a value a this index, so store the value now. newValue.Value = valueToStore; - } else { - // If the previous value came from a previous basic block, then some other use of - // the local could see the previous value, so we must merge the new value with the - // old value. - newValue.Value = MergePointValue.MergeValues (existingValue.Value, valueToStore); + valueCollection[collectionKey] = newValue; } - valueCollection[collectionKey] = newValue; } public void Scan (MethodBody methodBody) @@ -246,22 +246,9 @@ namespace Mono.Linker.Dataflow case Code.Cgt_Un: case Code.Clt: case Code.Clt_Un: - case Code.Ldelem_I: - case Code.Ldelem_I1: - case Code.Ldelem_I2: - case Code.Ldelem_I4: - case Code.Ldelem_I8: - case Code.Ldelem_R4: - case Code.Ldelem_R8: - case Code.Ldelem_U1: - case Code.Ldelem_U2: - case Code.Ldelem_U4: case Code.Shl: case Code.Shr: case Code.Shr_Un: - case Code.Ldelem_Any: - case Code.Ldelem_Ref: - case Code.Ldelema: case Code.Ceq: PopUnknown (currentStack, 2, methodBody, operation.Offset); PushUnknown (currentStack); @@ -432,12 +419,10 @@ namespace Mono.Linker.Dataflow case Code.Newarr: { StackSlot count = PopUnknown (currentStack, 1, methodBody, operation.Offset); - currentStack.Push (new StackSlot (new ArrayValue (count.Value))); + currentStack.Push (new StackSlot (new ArrayValue (count.Value, (TypeReference) operation.Operand))); } break; - case Code.Cpblk: - case Code.Initblk: case Code.Stelem_I: case Code.Stelem_I1: case Code.Stelem_I2: @@ -447,6 +432,27 @@ namespace Mono.Linker.Dataflow case Code.Stelem_R8: case Code.Stelem_Any: case Code.Stelem_Ref: + ScanStelem (operation, currentStack, methodBody, curBasicBlock); + break; + + case Code.Ldelem_I: + case Code.Ldelem_I1: + case Code.Ldelem_I2: + case Code.Ldelem_I4: + case Code.Ldelem_I8: + case Code.Ldelem_R4: + case Code.Ldelem_R8: + case Code.Ldelem_U1: + case Code.Ldelem_U2: + case Code.Ldelem_U4: + case Code.Ldelem_Any: + case Code.Ldelem_Ref: + case Code.Ldelema: + ScanLdelem (operation, currentStack, methodBody, curBasicBlock); + break; + + case Code.Cpblk: + case Code.Initblk: PopUnknown (currentStack, 3, methodBody, operation.Offset); break; @@ -527,7 +533,7 @@ namespace Mono.Linker.Dataflow case Code.Call: case Code.Callvirt: case Code.Newobj: - HandleCall (methodBody, operation, currentStack); + HandleCall (methodBody, operation, currentStack, curBasicBlock); break; case Code.Jmp: @@ -852,7 +858,8 @@ namespace Mono.Linker.Dataflow private void HandleCall ( MethodBody callingMethodBody, Instruction operation, - Stack<StackSlot> currentStack) + Stack<StackSlot> currentStack, + int curBasicBlock) { MethodReference calledMethod = (MethodReference) operation.Operand; @@ -886,6 +893,12 @@ namespace Mono.Linker.Dataflow if (methodReturnValue != null) currentStack.Push (new StackSlot (methodReturnValue, calledMethod.ReturnType.IsByRefOrPointer ())); + + foreach (var param in methodParams) { + if (param is ArrayValue arr) { + MarkArrayValuesAsUnknown (arr, curBasicBlock); + } + } } public abstract bool HandleCall ( @@ -894,5 +907,74 @@ namespace Mono.Linker.Dataflow Instruction operation, ValueNodeList methodParams, out ValueNode methodReturnValue); + + // Limit tracking array values to 32 values for performance reasons. There are many arrays much longer than 32 elements in .NET, but the interesting ones for the linker are nearly always less than 32 elements. + private const int MaxTrackedArrayValues = 32; + + private static void MarkArrayValuesAsUnknown (ArrayValue arrValue, int curBasicBlock) + { + // Since we can't know the current index we're storing the value at, clear all indices. + // That way we won't accidentally think we know the value at a given index when we cannot. + foreach (var knownIndex in arrValue.IndexValues.Keys) { + // Don't pass MaxTrackedArrayValues since we are only looking at keys we've already seen. + StoreMethodLocalValue (arrValue.IndexValues, UnknownValue.Instance, knownIndex, curBasicBlock); + } + } + + private void ScanStelem ( + Instruction operation, + Stack<StackSlot> currentStack, + MethodBody methodBody, + int curBasicBlock) + { + StackSlot valueToStore = PopUnknown (currentStack, 1, methodBody, operation.Offset); + StackSlot indexToStoreAt = PopUnknown (currentStack, 1, methodBody, operation.Offset); + StackSlot arrayToStoreIn = PopUnknown (currentStack, 1, methodBody, operation.Offset); + int? indexToStoreAtInt = indexToStoreAt.Value.AsConstInt (); + foreach (var array in arrayToStoreIn.Value.UniqueValues ()) { + if (array is ArrayValue arrValue) { + if (indexToStoreAtInt == null) { + MarkArrayValuesAsUnknown (arrValue, curBasicBlock); + } else { + // When we know the index, we can record the value at that index. + StoreMethodLocalValue (arrValue.IndexValues, valueToStore.Value, indexToStoreAtInt.Value, curBasicBlock, MaxTrackedArrayValues); + } + } + } + } + + private void ScanLdelem ( + Instruction operation, + Stack<StackSlot> currentStack, + MethodBody methodBody, + int curBasicBlock) + { + StackSlot indexToLoadFrom = PopUnknown (currentStack, 1, methodBody, operation.Offset); + StackSlot arrayToLoadFrom = PopUnknown (currentStack, 1, methodBody, operation.Offset); + if (arrayToLoadFrom.Value is not ArrayValue arr) { + PushUnknown (currentStack); + return; + } + bool isByRef = operation.OpCode.Code == Code.Ldelema; + + int? index = indexToLoadFrom.Value.AsConstInt (); + if (index == null) { + PushUnknown (currentStack); + if (isByRef) { + MarkArrayValuesAsUnknown (arr, curBasicBlock); + } + return; + } + + + ValueBasicBlockPair arrayIndexValue; + arr.IndexValues.TryGetValue (index.Value, out arrayIndexValue); + if (arrayIndexValue.Value != null) { + ValueNode valueToPush = arrayIndexValue.Value; + currentStack.Push (new StackSlot (valueToPush, isByRef)); + } else { + currentStack.Push (new StackSlot (null, isByRef)); + } + } } } diff --git a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs index b52f1dd10..061a985f7 100644 --- a/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs +++ b/src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs @@ -192,7 +192,7 @@ namespace Mono.Linker.Dataflow { switch (field.Name) { case "EmptyTypes" when field.DeclaringType.IsTypeOf ("System", "Type"): { - return new ArrayValue (new ConstIntValue (0)); + return new ArrayValue (new ConstIntValue (0), field.DeclaringType); } case "Empty" when field.DeclaringType.IsTypeOf ("System", "String"): { return new KnownStringValue (string.Empty); @@ -656,7 +656,7 @@ namespace Mono.Linker.Dataflow break; case IntrinsicId.Array_Empty: { - methodReturnValue = new ArrayValue (new ConstIntValue (0)); + methodReturnValue = new ArrayValue (new ConstIntValue (0), ((GenericInstanceMethod) calledMethod).GenericArguments[0]); } break; @@ -702,20 +702,28 @@ namespace Mono.Linker.Dataflow reflectionContext.AnalyzingPattern (); foreach (var value in methodParams[0].UniqueValues ()) { if (value is SystemTypeValue typeValue) { - foreach (var genericParameter in typeValue.TypeRepresented.GenericParameters) { - if (_context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (genericParameter) != DynamicallyAccessedMemberTypes.None || - (genericParameter.HasDefaultConstructorConstraint && !typeValue.TypeRepresented.IsTypeOf ("System", "Nullable`1"))) { - // There is a generic parameter which has some requirements on the input types. - // For now we don't support tracking actual array elements, so we can't validate that the requirements are fulfilled. - - // Special case: Nullable<T> where T : struct - // The struct constraint in C# implies new() constraints, but Nullable doesn't make a use of that part. - // There are several places even in the framework where typeof(Nullable<>).MakeGenericType would warn - // without any good reason to do so. + if (AnalyzeGenericInstatiationTypeArray (methodParams[1], ref reflectionContext, calledMethodDefinition, typeValue.TypeRepresented.GenericParameters)) { + reflectionContext.RecordHandledPattern (); + } else { + bool hasUncheckedAnnotation = false; + foreach (var genericParameter in typeValue.TypeRepresented.GenericParameters) { + if (_context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (genericParameter) != DynamicallyAccessedMemberTypes.None || + (genericParameter.HasDefaultConstructorConstraint && !typeValue.TypeRepresented.IsTypeOf ("System", "Nullable`1"))) { + // If we failed to analyze the array, we go through the analyses again + // and intentionally ignore one particular annotation: + // Special case: Nullable<T> where T : struct + // The struct constraint in C# implies new() constraints, but Nullable doesn't make a use of that part. + // There are several places even in the framework where typeof(Nullable<>).MakeGenericType would warn + // without any good reason to do so. + hasUncheckedAnnotation = true; + break; + } + } + if (hasUncheckedAnnotation) { reflectionContext.RecordUnrecognizedPattern ( - 2055, - $"Call to '{calledMethodDefinition.GetDisplayName ()}' can not be statically analyzed. " + - $"It's not possible to guarantee the availability of requirements of the generic type."); + 2055, + $"Call to '{calledMethodDefinition.GetDisplayName ()}' can not be statically analyzed. " + + $"It's not possible to guarantee the availability of requirements of the generic type."); } } @@ -811,7 +819,7 @@ namespace Mono.Linker.Dataflow foreach (var stringParam in methodParams[1].UniqueValues ()) { if (stringParam is KnownStringValue stringValue) { foreach (var method in systemTypeValue.TypeRepresented.GetMethodsOnTypeHierarchy (m => m.Name == stringValue.Contents, bindingFlags)) { - ValidateGenericMethodInstantiation (ref reflectionContext, method, calledMethod); + ValidateGenericMethodInstantiation (ref reflectionContext, method, methodParams[2], calledMethod); MarkMethod (ref reflectionContext, method); } @@ -1630,8 +1638,7 @@ namespace Mono.Linker.Dataflow foreach (var methodValue in methodParams[0].UniqueValues ()) { if (methodValue is SystemReflectionMethodBaseValue methodBaseValue) { - ValidateGenericMethodInstantiation (ref reflectionContext, methodBaseValue.MethodRepresented, calledMethod); - reflectionContext.RecordHandledPattern (); + ValidateGenericMethodInstantiation (ref reflectionContext, methodBaseValue.MethodRepresented, methodParams[1], calledMethod); } else if (methodValue == NullValue.Instance) { reflectionContext.RecordHandledPattern (); } else { @@ -1712,6 +1719,42 @@ namespace Mono.Linker.Dataflow return true; } + private bool AnalyzeGenericInstatiationTypeArray (ValueNode arrayParam, ref ReflectionPatternContext reflectionContext, MethodReference calledMethod, IList<GenericParameter> genericParameters) + { + foreach (var typesValue in arrayParam.UniqueValues ()) { + if (typesValue.Kind != ValueNodeKind.Array) { + return false; + } + ArrayValue array = (ArrayValue) typesValue; + int? size = array.Size.AsConstInt (); + if (size == null || size != genericParameters.Count) { + return false; + } + bool allIndicesKnown = true; + for (int i = 0; i < size.Value; i++) { + if (!array.IndexValues.TryGetValue (i, out ValueBasicBlockPair value) || value.Value is null or { Kind: ValueNodeKind.Unknown }) { + allIndicesKnown = false; + break; + } + } + + if (!allIndicesKnown) { + return false; + } + + for (int i = 0; i < size.Value; i++) { + if (array.IndexValues.TryGetValue (i, out ValueBasicBlockPair value)) { + RequireDynamicallyAccessedMembers ( + ref reflectionContext, + _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (genericParameters[i]), + value.Value, + calledMethod.Resolve ()); + } + } + } + return true; + } + void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext, MethodDefinition calledMethod, ValueNodeList methodParams) { reflectionContext.AnalyzingPattern (); @@ -2176,20 +2219,19 @@ namespace Mono.Linker.Dataflow void ValidateGenericMethodInstantiation ( ref ReflectionPatternContext reflectionContext, MethodDefinition genericMethod, + ValueNode genericParametersArray, MethodReference reflectionMethod) { - if (!genericMethod.HasGenericParameters) - return; - - foreach (var genericParameter in genericMethod.GenericParameters) { - if (_context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (genericParameter) != DynamicallyAccessedMemberTypes.None || - genericParameter.HasDefaultConstructorConstraint) { - // There is a generic parameter which has some requirements on input types. - // For now we don't support tracking actual array elements, so we can't validate that the requirements are fulfilled. - reflectionContext.RecordUnrecognizedPattern ( - 2060, string.Format (Resources.Strings.IL2060, - DiagnosticUtilities.GetMethodSignatureDisplayName (reflectionMethod))); - } + if (!genericMethod.HasGenericParameters) { + reflectionContext.RecordHandledPattern (); + } + + if (!AnalyzeGenericInstatiationTypeArray (genericParametersArray, ref reflectionContext, reflectionMethod, genericMethod.GenericParameters)) { + reflectionContext.RecordUnrecognizedPattern ( + 2060, + string.Format (Resources.Strings.IL2060, DiagnosticUtilities.GetMethodSignatureDisplayName (reflectionMethod))); + } else { + reflectionContext.RecordHandledPattern (); } } diff --git a/src/linker/Linker.Dataflow/ValueNode.cs b/src/linker/Linker.Dataflow/ValueNode.cs index dd547b79b..f1b5b3b6d 100644 --- a/src/linker/Linker.Dataflow/ValueNode.cs +++ b/src/linker/Linker.Dataflow/ValueNode.cs @@ -414,6 +414,9 @@ namespace Mono.Linker.Dataflow case ValueNodeKind.Array: ArrayValue av = (ArrayValue) node; foundCycle = av.Size.DetectCycle (seenNodes, allNodesSeen); + foreach (ValueBasicBlockPair pair in av.IndexValues.Values) { + foundCycle |= pair.Value.DetectCycle (seenNodes, allNodesSeen); + } break; default: @@ -1211,12 +1214,12 @@ namespace Mono.Linker.Dataflow class ArrayValue : ValueNode { - protected override int NumChildren => 1; + protected override int NumChildren => 1 + IndexValues.Count; /// <summary> /// Constructs an array value of the given size /// </summary> - public ArrayValue (ValueNode size) + public ArrayValue (ValueNode size, TypeReference elementType) { Kind = ValueNodeKind.Array; @@ -1224,9 +1227,19 @@ namespace Mono.Linker.Dataflow StaticType = null; Size = size ?? UnknownValue.Instance; + ElementType = elementType.ResolveToMainTypeDefinition (); + IndexValues = new Dictionary<int, ValueBasicBlockPair> (); + } + + private ArrayValue (ValueNode size, TypeReference elementType, Dictionary<int, ValueBasicBlockPair> indexValues) + : this (size, elementType) + { + IndexValues = indexValues; } public ValueNode Size { get; } + public TypeReference ElementType { get; } + public Dictionary<int, ValueBasicBlockPair> IndexValues { get; } public override int GetHashCode () { @@ -1239,23 +1252,36 @@ namespace Mono.Linker.Dataflow return false; ArrayValue otherArr = (ArrayValue) other; - return Size.Equals (otherArr.Size); + bool equals = Size.Equals (otherArr.Size); + equals &= IndexValues.Count == otherArr.IndexValues.Count; + if (!equals) + return false; + + // If both sets T and O are the same size and "T intersect O" is empty, then T == O. + HashSet<KeyValuePair<int, ValueBasicBlockPair>> thisValueSet = new (IndexValues); + HashSet<KeyValuePair<int, ValueBasicBlockPair>> otherValueSet = new (otherArr.IndexValues); + thisValueSet.ExceptWith (otherValueSet); + return thisValueSet.Count == 0; } protected override string NodeToString () { - return ValueNodeDump.ValueNodeToString (this, Size); + // TODO: Use StringBuilder and remove Linq usage. + return $"(Array Size:{ValueNodeDump.ValueNodeToString (this, Size)}, Values:({string.Join (',', IndexValues.Select (v => $"({v.Key},{ValueNodeDump.ValueNodeToString (v.Value.Value)})"))})"; } protected override IEnumerable<ValueNode> EvaluateUniqueValues () { foreach (var sizeConst in Size.UniqueValuesInternal) - yield return new ArrayValue (sizeConst); + yield return new ArrayValue (sizeConst, ElementType, IndexValues); } protected override ValueNode ChildAt (int index) { if (index == 0) return Size; + if (index - 1 <= IndexValues.Count) + return IndexValues.Values.ElementAt (index - 1).Value; + throw new InvalidOperationException (); } } @@ -1337,4 +1363,10 @@ namespace Mono.Linker.Dataflow return hashCode.ToHashCode (); } } + + public struct ValueBasicBlockPair + { + public ValueNode Value; + public int BasicBlockIndex; + } } diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs index 6965f80ef..380b72a70 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs @@ -148,7 +148,12 @@ namespace Mono.Linker.Tests.Cases.DataFlow { var array = new object[1]; array[0] = this.GetType (); + MakeArrayValuesUnknown (array); TypeStore._staticTypeWithPublicParameterlessConstructor = (Type) array[0]; + + static void MakeArrayValuesUnknown (object[] array) + { + } } private static void RequirePublicParameterlessConstructor ( diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs index 08bb958cd..27d487a3e 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/GenericParameterDataFlow.cs @@ -735,6 +735,9 @@ namespace Mono.Linker.Tests.Cases.DataFlow { TestNullType (); TestUnknownInput (null); + TestWithUnknownTypeArray (null); + TestWithArrayUnknownIndexSet (0); + TestWithArrayUnknownLengthSet (1); TestNoArguments (); TestWithRequirements (); @@ -768,6 +771,31 @@ namespace Mono.Linker.Tests.Cases.DataFlow inputType.MakeGenericType (typeof (TestType)); } + [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, + messageCode: "IL2055")] + static void TestWithUnknownTypeArray (Type[] types) + { + typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (types); + } + + [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, + messageCode: "IL2055")] + static void TestWithArrayUnknownIndexSet (int indexToSet) + { + Type[] types = new Type[1]; + types[indexToSet] = typeof (TestType); + typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (types); + } + + [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, + messageCode: "IL2055")] + static void TestWithArrayUnknownLengthSet (int arrayLen) + { + Type[] types = new Type[arrayLen]; + types[0] = typeof (TestType); + typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (types); + } + [RecognizedReflectionAccessPattern] static void TestNoArguments () { @@ -778,8 +806,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithRequirements () { // Currently this is not analyzable since we don't track array elements. @@ -787,16 +814,14 @@ namespace Mono.Linker.Tests.Cases.DataFlow typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (typeof (TestType)); } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithRequirementsFromParam ( [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] Type type) { typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (type); } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithRequirementsFromGenericParam< [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T> () { @@ -823,8 +848,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithMultipleArgumentsWithRequirements () { typeof (GenericWithMultipleArgumentsWithRequirements<,>).MakeGenericType (typeof (TestType), typeof (TestType)); @@ -836,8 +860,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithNewConstraint () { typeof (GenericWithNewConstraint<>).MakeGenericType (typeof (TestType)); @@ -847,8 +870,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithStructConstraint () { typeof (GenericWithStructConstraint<>).MakeGenericType (typeof (TestType)); @@ -858,8 +880,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (Type), nameof (Type.MakeGenericType), new Type[] { typeof (Type[]) }, - messageCode: "IL2055")] + [RecognizedReflectionAccessPattern] static void TestWithUnmanagedConstraint () { typeof (GenericWithUnmanagedConstraint<>).MakeGenericType (typeof (TestType)); @@ -881,8 +902,13 @@ namespace Mono.Linker.Tests.Cases.DataFlow public static void Test () { TestNullMethod (); - TestUnknownInput (null); + TestUnknownMethod (null); TestUnknownMethodButNoTypeArguments (null); + TestWithUnknownTypeArray (null); + TestWithArrayUnknownIndexSet (0); + TestWithArrayUnknownIndexSetByRef (0); + TestWithArrayUnknownLengthSet (1); + TestWithArrayPassedToAnotherMethod (); TestWithNoArguments (); TestWithRequirements (); @@ -891,6 +917,11 @@ namespace Mono.Linker.Tests.Cases.DataFlow TestWithRequirementsViaRuntimeMethod (); TestWithRequirementsButNoTypeArguments (); + TestWithMultipleKnownGenericParameters (); + TestWithOneUnknownGenericParameter (null); + TestWithPartiallyInitializedGenericTypeArray (); + TestWithConditionalGenericTypeSet (true); + TestWithNoRequirements (); TestWithNoRequirementsFromParam (null); TestWithNoRequirementsViaRuntimeMethod (); @@ -911,7 +942,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, messageCode: "IL2060")] - static void TestUnknownInput (MethodInfo mi) + static void TestUnknownMethod (MethodInfo mi) { mi.MakeGenericMethod (typeof (TestType)); } @@ -924,6 +955,61 @@ namespace Mono.Linker.Tests.Cases.DataFlow mi.MakeGenericMethod (Type.EmptyTypes); } + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithUnknownTypeArray (Type[] types) + { + typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) + .MakeGenericMethod (types); + } + + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithArrayUnknownIndexSet (int indexToSet) + { + Type[] types = new Type[1]; + types[indexToSet] = typeof (TestType); + typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) + .MakeGenericMethod (types); + } + + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithArrayUnknownIndexSetByRef (int indexToSet) + { + Type[] types = new Type[1]; + types[0] = typeof (TestType); + ref Type t = ref types[indexToSet]; + t = typeof (TestType); + typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) + .MakeGenericMethod (types); + } + + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithArrayUnknownLengthSet (int arrayLen) + { + Type[] types = new Type[arrayLen]; + types[0] = typeof (TestType); + typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) + .MakeGenericMethod (types); + } + + static void MethodThatTakesArrayParameter (Type[] types) + { + } + + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithArrayPassedToAnotherMethod () + { + Type[] types = new Type[1]; + types[0] = typeof (TestType); + MethodThatTakesArrayParameter (types); + typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) + .MakeGenericMethod (types); + } + [RecognizedReflectionAccessPattern] static void TestWithNoArguments () { @@ -935,16 +1021,14 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + [RecognizedReflectionAccessPattern] static void TestWithRequirements () { typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) .MakeGenericMethod (typeof (TestType)); } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + [RecognizedReflectionAccessPattern] static void TestWithRequirementsFromParam ( [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] Type type) { @@ -959,8 +1043,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow .MakeGenericMethod (typeof (T)); } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + + [RecognizedReflectionAccessPattern] static void TestWithRequirementsViaRuntimeMethod () { typeof (MakeGenericMethod).GetRuntimeMethod (nameof (GenericWithRequirements), Type.EmptyTypes) @@ -971,7 +1055,6 @@ namespace Mono.Linker.Tests.Cases.DataFlow messageCode: "IL2060")] static void TestWithRequirementsButNoTypeArguments () { - // Linker could figure out that this is not a problem, but it's not worth the complexity, since this will always throw at runtime typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static) .MakeGenericMethod (Type.EmptyTypes); } @@ -981,6 +1064,54 @@ namespace Mono.Linker.Tests.Cases.DataFlow } [RecognizedReflectionAccessPattern] + static void TestWithMultipleKnownGenericParameters () + { + typeof (MakeGenericMethod).GetMethod (nameof (GenericMultipleParameters), BindingFlags.Static) + .MakeGenericMethod (typeof (TestType), typeof (TestType), typeof (TestType)); + } + + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithOneUnknownGenericParameter (Type[] types) + { + typeof (MakeGenericMethod).GetMethod (nameof (GenericMultipleParameters), BindingFlags.Static) + .MakeGenericMethod (typeof (TestType), typeof (TestStruct), types[0]); + } + + [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, + messageCode: "IL2060")] + static void TestWithPartiallyInitializedGenericTypeArray () + { + Type[] types = new Type[3]; + types[0] = typeof (TestType); + types[1] = typeof (TestStruct); + typeof (MakeGenericMethod).GetMethod (nameof (GenericMultipleParameters), BindingFlags.Static) + .MakeGenericMethod (types); + } + + [RecognizedReflectionAccessPattern] + static void TestWithConditionalGenericTypeSet (bool thirdParameterIsStruct) + { + Type[] types = new Type[3]; + types[0] = typeof (TestType); + types[1] = typeof (TestStruct); + if (thirdParameterIsStruct) { + types[2] = typeof (TestStruct); + } else { + types[2] = typeof (TestType); + } + typeof (MakeGenericMethod).GetMethod (nameof (GenericMultipleParameters), BindingFlags.Static) + .MakeGenericMethod (types); + } + + public static void GenericMultipleParameters< + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] T, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] U, + [DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicFields)] V> () + { + } + + [RecognizedReflectionAccessPattern] static void TestWithNoRequirements () { typeof (MakeGenericMethod).GetMethod (nameof (GenericWithNoRequirements), BindingFlags.Static) @@ -1005,8 +1136,8 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + + [RecognizedReflectionAccessPattern] static void TestWithMultipleArgumentsWithRequirements () { typeof (MakeGenericMethod).GetMethod (nameof (GenericWithMultipleArgumentsWithRequirements), BindingFlags.Static | BindingFlags.NonPublic) @@ -1019,8 +1150,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow { } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + [RecognizedReflectionAccessPattern] static void TestWithNewConstraint () { typeof (MakeGenericMethod).GetMethod (nameof (GenericWithNewConstraint), BindingFlags.Static | BindingFlags.NonPublic) @@ -1032,8 +1162,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow var t = new T (); } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + [RecognizedReflectionAccessPattern] static void TestWithStructConstraint () { typeof (MakeGenericMethod).GetMethod (nameof (GenericWithStructConstraint), BindingFlags.Static | BindingFlags.NonPublic) @@ -1045,8 +1174,7 @@ namespace Mono.Linker.Tests.Cases.DataFlow var t = new T (); } - [UnrecognizedReflectionAccessPattern (typeof (MethodInfo), nameof (MethodInfo.MakeGenericMethod), new Type[] { typeof (Type[]) }, - messageCode: "IL2060")] + [RecognizedReflectionAccessPattern] static void TestWithUnmanagedConstraint () { typeof (MakeGenericMethod).GetMethod (nameof (GenericWithUnmanagedConstraint), BindingFlags.Static | BindingFlags.NonPublic) diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs index 2897c4705..fe11b6b03 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/MethodParametersDataFlow.cs @@ -161,7 +161,12 @@ namespace Mono.Linker.Tests.Cases.DataFlow { var array = new object[1]; array[0] = this.GetType (); + MakeArrayValuesUnknown (array); RequirePublicParameterlessConstructor ((Type) array[0]); + + static void MakeArrayValuesUnknown (object[] array) + { + } } [RecognizedReflectionAccessPattern] diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs index f66a25e74..c0925a264 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/MethodReturnParameterDataFlow.cs @@ -126,7 +126,12 @@ namespace Mono.Linker.Tests.Cases.DataFlow { var array = new object[1]; array[0] = this.GetType (); + MakeArrayValuesUnknown (array); return (Type) array[0]; + + static void MakeArrayValuesUnknown (object[] array) + { + } } [UnrecognizedReflectionAccessPattern (typeof (MethodReturnParameterDataFlow), nameof (RequirePublicConstructors), new Type[] { typeof (Type) }, messageCode: "IL2072")] diff --git a/test/Mono.Linker.Tests.Cases/DataFlow/MethodThisDataFlow.cs b/test/Mono.Linker.Tests.Cases/DataFlow/MethodThisDataFlow.cs index 9dc4813b8..a8bfbed1f 100644 --- a/test/Mono.Linker.Tests.Cases/DataFlow/MethodThisDataFlow.cs +++ b/test/Mono.Linker.Tests.Cases/DataFlow/MethodThisDataFlow.cs @@ -100,7 +100,12 @@ namespace Mono.Linker.Tests.Cases.DataFlow { var array = new object[1]; array[0] = array.GetType (); + MakeArrayValuesUnknown (array); ((MethodThisDataFlowTypeTest) array[0]).RequireThisNonPublicMethods (); + + static void MakeArrayValuesUnknown (object[] array) + { + } } [UnrecognizedReflectionAccessPattern (typeof (MethodThisDataFlowTypeTest), nameof (MethodThisDataFlowTypeTest.RequireThisPublicMethods), new Type[] { }, diff --git a/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs b/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs index 866fab24b..7f47ff4a3 100644 --- a/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs +++ b/test/Mono.Linker.Tests.Cases/Reflection/ExpressionCallString.cs @@ -238,10 +238,11 @@ namespace Mono.Linker.Tests.Cases.Reflection { } [Kept] + [ExpectedWarning ("IL2060", "Expression::Call")] static void TestWithNoTypeParameters () { - // Linker doesn't check if it's valid to call a generic method without generic parameters, it looks like a non-generic call - // so it will preserve the target method. + // Linker warns since this is a call to a generic method with a mismatching number of generic parameters + // and provided type values for the generic instantiation. Expression.Call (typeof (TestGenericMethods), nameof (GenericMethodCalledAsNonGeneric), Type.EmptyTypes); } @@ -253,18 +254,24 @@ namespace Mono.Linker.Tests.Cases.Reflection } [Kept] - [ExpectedWarning ("IL2060", "Expression::Call")] static void TestMethodWithRequirements () { - // This must warn - as this is dangerous - Expression.Call (typeof (TestGenericMethods), nameof (GenericMethodWithRequirements), new Type[] { GetUnknownType () }); + // This may not warn - as it's safe + Expression.Call (typeof (TestGenericMethods), nameof (GenericMethodWithRequirements), new Type[] { GetUnknownTypeWithRequrements () }); + } + + [Kept] + [ExpectedWarning ("IL2060", "Expression::Call")] + static void TestMethodWithRequirementsUnknownTypeArray (Type[] types) + { + // The passed in types array cannot be analyzed, so a warning is produced. + Expression.Call (typeof (TestGenericMethods), nameof (GenericMethodWithRequirements), types); } [Kept] [ExpectedWarning ("IL2060", "Expression::Call")] static void TestMethodWithRequirementsButNoTypeArguments () { - // Linker could figure out that this is not a problem, but it's not worth the complexity, since this will always throw at runtime Expression.Call (typeof (TestGenericMethods), nameof (GenericMethodWithRequirementsNoArguments), Type.EmptyTypes); } @@ -333,6 +340,7 @@ namespace Mono.Linker.Tests.Cases.Reflection TestWithNoTypeParameters (); TestMethodWithoutRequirements (); TestMethodWithRequirements (); + TestMethodWithRequirementsUnknownTypeArray (null); TestMethodWithRequirementsButNoTypeArguments (); UnknownMethodWithRequirements.TestWithTypeParameters (); UnknownMethodWithRequirements.TestWithoutTypeParameters (); @@ -342,6 +350,11 @@ namespace Mono.Linker.Tests.Cases.Reflection [Kept] static Type GetUnknownType () { return null; } + + [Kept] + [return: KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))] + [return: DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicProperties)] + static Type GetUnknownTypeWithRequrements () { return null; } } } } |