Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mark generic arguments of dynamically accessed types passed as string #1566

Merged
merged 13 commits into from
Oct 26, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -140,15 +140,15 @@ internal sealed class MultiDimArrayTypeName : HasElementTypeName
public MultiDimArrayTypeName(TypeName elementTypeName, int rank)
: base(elementTypeName)
{
_rank = rank;
Rank = rank;
}

public sealed override string ToString()
{
return ElementTypeName + "[" + (_rank == 1 ? "*" : new string(',', _rank - 1)) + "]";
return ElementTypeName + "[" + (Rank == 1 ? "*" : new string(',', Rank - 1)) + "]";
}

private int _rank;
public int Rank { get; }
}

//
Expand Down
2 changes: 1 addition & 1 deletion src/linker/Linker.Dataflow/MethodBodyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ private static void ScanLdtoken (Instruction operation, Stack<StackSlot> current
}

if (operation.Operand is TypeReference typeReference) {
var resolvedReference = typeReference.Resolve ();
var resolvedReference = typeReference.ResolveToMainTypeDefinition ();
if (resolvedReference != null) {
StackSlot slot = new StackSlot (new RuntimeTypeHandleValue (resolvedReference));
currentStack.Push (slot);
Expand Down
36 changes: 23 additions & 13 deletions src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ static ValueNode GetValueNodeForCustomAttributeArgument (CustomAttributeArgument
{
ValueNode valueNode;
if (argument.Type.Name == "Type") {
TypeDefinition referencedType = ((TypeReference) argument.Value).Resolve ();
TypeDefinition referencedType = ((TypeReference) argument.Value).ResolveToMainTypeDefinition ();
valueNode = referencedType == null ? null : new SystemTypeValue (referencedType);
} else if (argument.Type.MetadataType == MetadataType.String) {
valueNode = new KnownStringValue ((string) argument.Value);
Expand All @@ -130,22 +130,22 @@ public void ProcessGenericArgumentDataFlow (GenericParameter genericParameter, T
var annotation = _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (genericParameter);
Debug.Assert (annotation != DynamicallyAccessedMemberTypes.None);

ValueNode valueNode = GetValueNodeFromGenericArgument (genericArgument);
ValueNode valueNode = GetTypeValueNodeFromGenericArgument (genericArgument);
bool enableReflectionPatternReporting = !(source is MethodDefinition sourceMethod) || ShouldEnableReflectionPatternReporting (sourceMethod);

var reflectionContext = new ReflectionPatternContext (_context, enableReflectionPatternReporting, source, genericParameter);
reflectionContext.AnalyzingPattern ();
RequireDynamicallyAccessedMembers (ref reflectionContext, annotation, valueNode, genericParameter);
}

ValueNode GetValueNodeFromGenericArgument (TypeReference genericArgument)
ValueNode GetTypeValueNodeFromGenericArgument (TypeReference genericArgument)
{
if (genericArgument is GenericParameter inputGenericParameter) {
// Technically this should be a new value node type as it's not a System.Type instance representation, but just the generic parameter
// That said we only use it to perform the dynamically accessed members checks and for that purpose treating it as System.Type is perfectly valid.
return new SystemTypeForGenericParameterValue (inputGenericParameter, _context.Annotations.FlowAnnotations.GetGenericParameterAnnotation (inputGenericParameter));
} else {
TypeDefinition genericArgumentTypeDef = genericArgument.Resolve ();
TypeDefinition genericArgumentTypeDef = genericArgument.ResolveToMainTypeDefinition ();
if (genericArgumentTypeDef != null) {
return new SystemTypeValue (genericArgumentTypeDef);
} else {
Expand Down Expand Up @@ -752,13 +752,13 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c
if (methodParam.ParameterIndex == 0) {
staticType = callingMethodDefinition.DeclaringType;
} else {
staticType = callingMethodDefinition.Parameters[methodParam.ParameterIndex - 1].ParameterType.Resolve ();
staticType = callingMethodDefinition.Parameters[methodParam.ParameterIndex - 1].ParameterType.ResolveToMainTypeDefinition ();
}
} else {
staticType = callingMethodDefinition.Parameters[methodParam.ParameterIndex].ParameterType.Resolve ();
staticType = callingMethodDefinition.Parameters[methodParam.ParameterIndex].ParameterType.ResolveToMainTypeDefinition ();
}
} else if (methodParams[0] is LoadFieldValue loadedField) {
staticType = loadedField.Field.FieldType.Resolve ();
staticType = loadedField.Field.FieldType.ResolveToMainTypeDefinition ();
}

if (staticType != null) {
Expand Down Expand Up @@ -802,7 +802,7 @@ public override bool HandleCall (MethodBody callingMethodBody, MethodReference c
foreach (var typeNameValue in methodParams[0].UniqueValues ()) {
if (typeNameValue is KnownStringValue knownStringValue) {
TypeReference foundTypeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents);
TypeDefinition foundType = foundTypeRef?.Resolve ();
TypeDefinition foundType = foundTypeRef?.ResolveToMainTypeDefinition ();
if (foundType == null) {
// Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back.
reflectionContext.RecordHandledPattern ();
Expand Down Expand Up @@ -1161,7 +1161,7 @@ methodParams[argsParam] is ArrayValue arrayValue &&
RequireDynamicallyAccessedMembers (
ref reflectionContext,
DynamicallyAccessedMemberTypes.PublicParameterlessConstructor,
GetValueNodeFromGenericArgument (genericCalledMethod.GenericArguments[0]),
GetTypeValueNodeFromGenericArgument (genericCalledMethod.GenericArguments[0]),
calledMethodDefinition.GenericParameters[0]);
}
break;
Expand Down Expand Up @@ -1340,11 +1340,13 @@ void ProcessCreateInstanceByName (ref ReflectionPatternContext reflectionContext
continue;
}

var resolvedType = _context.TypeNameResolver.ResolveTypeName (resolvedAssembly, typeNameStringValue.Contents)?.Resolve ();
if (resolvedType == null) {
var typeRef = _context.TypeNameResolver.ResolveTypeName (resolvedAssembly, typeNameStringValue.Contents);
var resolvedType = typeRef?.Resolve ();
if (resolvedType == null || typeRef is ArrayType) {
// It's not wrong to have a reference to non-existing type - the code may well expect to get an exception in this case
// Note that we did find the assembly, so it's not a linker config problem, it's either intentional, or wrong versions of assemblies
// but linker can't know that.
// but linker can't know that. In case a user tries to create an array using System.Activator we should simply ignore it, the user
// might expect an exception to be thrown.
reflectionContext.RecordHandledPattern ();
continue;
}
Expand Down Expand Up @@ -1571,11 +1573,13 @@ void RequireDynamicallyAccessedMembers (ref ReflectionPatternContext reflectionC
} else if (uniqueValue is SystemTypeValue systemTypeValue) {
MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, systemTypeValue.TypeRepresented, requiredMemberTypes);
} else if (uniqueValue is KnownStringValue knownStringValue) {
TypeDefinition foundType = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents)?.Resolve ();
TypeReference typeRef = _context.TypeNameResolver.ResolveTypeName (knownStringValue.Contents);
TypeDefinition foundType = typeRef?.ResolveToMainTypeDefinition ();
if (foundType == null) {
// Intentionally ignore - it's not wrong for code to call Type.GetType on non-existing name, the code might expect null/exception back.
reflectionContext.RecordHandledPattern ();
} else {
MarkType (ref reflectionContext, typeRef);
MarkTypeForDynamicallyAccessedMembers (ref reflectionContext, foundType, requiredMemberTypes);
}
} else if (uniqueValue == NullValue.Instance) {
Expand Down Expand Up @@ -1649,6 +1653,12 @@ void MarkTypeForDynamicallyAccessedMembers (ref ReflectionPatternContext reflect
}
}

void MarkType (ref ReflectionPatternContext reflectionContext, TypeReference typeReference)
{
var source = reflectionContext.Source;
reflectionContext.RecordRecognizedPattern (typeReference?.Resolve (), () => _markStep.MarkTypeVisibleToReflection (typeReference, new DependencyInfo (DependencyKind.AccessedViaReflection, source), source));
}

void MarkMethod (ref ReflectionPatternContext reflectionContext, MethodDefinition method)
{
var source = reflectionContext.Source;
Expand Down
9 changes: 8 additions & 1 deletion src/linker/Linker/TypeNameResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,14 @@ TypeReference ResolveTypeName (AssemblyDefinition assembly, TypeName typeName)
if (elementType == null)
return null;

return elementType;
return typeName switch
{
ArrayTypeName _ => new ArrayType (elementType),
MultiDimArrayTypeName multiDimArrayTypeName => new ArrayType (elementType, multiDimArrayTypeName.Rank),
ByRefTypeName _ => new ByReferenceType (elementType),
PointerTypeName _ => new PointerType (elementType),
_ => elementType
};
}

return assembly.MainModule.GetType (typeName.ToString ());
Expand Down
12 changes: 12 additions & 0 deletions src/linker/Linker/TypeReferenceExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -375,5 +375,17 @@ public static bool IsSubclassOf (this TypeReference type, string ns, string name

return false;
}

// Array types that are dynamically accessed should resolve to System.Array instead of its element type - which is what Cecil resolves to.
// Any data flow annotations placed on a type parameter which receives an array type apply to the array itself. None of the members in its
// element type should be marked.
public static TypeDefinition ResolveToMainTypeDefinition (this TypeReference type)
vitek-karas marked this conversation as resolved.
Show resolved Hide resolved
{
return type switch
{
ArrayType _ => type.Module.ImportReference (typeof (Array))?.Resolve (),
_ => type?.Resolve ()
};
}
}
}
51 changes: 48 additions & 3 deletions test/Mono.Linker.Tests.Cases/DataFlow/ApplyTypeAnnotations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,7 @@ private static void RequireCombinationOnString (
{
}

// Issue: https://github.com/mono/linker/issues/1537
//[Kept]
//[KeptMember (".ctor()")]
[Kept]
class FromStringConstantWithGenericInner
{
}
Expand All @@ -143,10 +141,57 @@ class FromStringConstantWithGeneric<T>
public T GetValue () { return default (T); }
}

[Kept]
class FromStringConstantWithGenericInnerInner
{
[Kept]
public void Method ()
{
}

int unusedField;
}

[Kept]
class FromStringConstantWithGenericInnerOne<
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
[KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))]
T>
{
}

[Kept]
class FromStringConstantWithGenericInnerTwo
{
void UnusedMethod ()
{
}
}

[Kept]
class FromStringConstantWitGenericInnerMultiDimArray
{
}

[Kept]
class FromStringConstantWithMultiDimArray
{
public void UnusedMethod () { }
}

[Kept]
[KeptMember (".ctor()")]
class FromStringConstantWithGenericTwoParameters<T, S>
{
}

[Kept]
static void TestFromStringConstantWithGeneric ()
{
RequireCombinationOnString ("Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGeneric`1[[Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGenericInner]]");
RequireCombinationOnString ("Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGenericTwoParameters`2[Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGenericInnerOne`1[Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGenericInnerInner],Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGenericInnerTwo]");
RequireCombinationOnString ("Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithGeneric`1[[Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWitGenericInnerMultiDimArray[,]]]");
RequireCombinationOnString ("Mono.Linker.Tests.Cases.DataFlow.ApplyTypeAnnotations+FromStringConstantWithMultiDimArray[,]");
}

[Kept]
Expand Down
15 changes: 6 additions & 9 deletions test/Mono.Linker.Tests.Cases/DataFlow/ComplexTypeHandling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@

namespace Mono.Linker.Tests.Cases.DataFlow
{
[IgnoreTestCase ("Active issue https://github.com/mono/linker/issues/1559")]
public class ComplexTypeHandling
{
public static void Main ()
Expand All @@ -33,7 +32,6 @@ class ArrayElementType
{
public ArrayElementType () { }

[Kept]
public void PublicMethod () { }

private int _privateField;
Expand Down Expand Up @@ -62,7 +60,6 @@ class ArrayElementInGenericType
{
public ArrayElementInGenericType () { }

[Kept]
public void PublicMethod () { }

private int _privateField;
Expand All @@ -89,6 +86,7 @@ static void TestGenericArrayOnGeneric ()
RequirePublicMethodsOnArrayOfGenericParameter<ArrayElementInGenericType> ();
}

[Kept]
static void RequirePublicMethodsOnArrayOfGenericParameter<T> ()
{
_ = new RequirePublicMethodsGeneric<T[]> ();
Expand All @@ -97,7 +95,7 @@ static void RequirePublicMethodsOnArrayOfGenericParameter<T> ()
[Kept]
sealed class ArrayGetTypeFromMethodParamElement
{
[Kept] // This is a bug - the method should not be marked, instead Array.* should be marked
// This method should not be marked, instead Array.* should be marked
public void PublicMethod () { }
}

Expand All @@ -116,7 +114,7 @@ static void TestArrayGetTypeFromMethodParam ()
[Kept]
sealed class ArrayGetTypeFromFieldElement
{
[Kept] // This is a bug - the method should not be marked, instead Array.* should be marked
// This method should not be marked, instead Array.* should be marked
public void PublicMethod () { }
}

Expand All @@ -132,7 +130,7 @@ static void TestArrayGetTypeFromField ()
[Kept]
sealed class ArrayTypeGetTypeElement
{
[Kept] // This is a bug - the method should not be marked, instead Array.* should be marked
// This method should not be marked, instead Array.* should be marked
public void PublicMethod () { }
}

Expand All @@ -142,10 +140,9 @@ static void TestArrayTypeGetType ()
RequirePublicMethods (Type.GetType ("Mono.Linker.Tests.Cases.DataFlow.ComplexTypeHandling+ArrayTypeGetTypeElement[]"));
}

[Kept]
// Nothing should be marked as CreateInstance doesn't work on arrays
class ArrayCreateInstanceByNameElement
{
[Kept] // This is a bug - the .ctor should not be marked - in fact in this case nothing should be marked as CreateInstance doesn't work on arrays
public ArrayCreateInstanceByNameElement ()
{
}
Expand All @@ -160,7 +157,7 @@ static void TestArrayCreateInstanceByName ()
[Kept]
class ArrayInAttributeParamElement
{
[Kept] // This is a bug - the method should not be marked, instead Array.* should be marked
// This method should not be marked, instead Array.* should be marked
public void PublicMethod () { }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ public static void Main ()
TestNullName ();
TestEmptyName ();
TestNonExistingName ();
TestPropertyOfArray ();
TestNullType ();
TestDataFlowType ();
TestIfElse (1);
Expand Down Expand Up @@ -82,6 +83,16 @@ static void TestNonExistingName ()
var property = typeof (PropertyUsedViaReflection).GetProperty ("NonExisting");
}

[Kept]
[RecognizedReflectionAccessPattern (
typeof (Type), nameof (Type.GetProperty), new Type[] { typeof (string) },
typeof (Array), nameof (Array.LongLength))]
static void TestPropertyOfArray ()
{
var property = typeof (int[]).GetProperty ("LongLength");
property.GetValue (null);
}

[Kept]
static void TestNullType ()
{
Expand Down