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

Fix behavior of intrinsics with empty inputs #2652

Merged
merged 6 commits into from
Feb 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 44 additions & 18 deletions src/ILLink.Shared/TrimAnalysis/HandleCallAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,11 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl
break;

case IntrinsicId.Type_get_TypeHandle:
if (instanceValue.IsEmpty ()) {
returnValue = MultiValueLattice.Top;
break;
}

foreach (var value in instanceValue) {
if (value is SystemTypeValue typeValue)
AddReturnValue (new RuntimeTypeHandleValue (typeValue.RepresentedType));
Expand All @@ -91,23 +96,37 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl
// GetInterface (String, bool)
//
case IntrinsicId.Type_GetInterface: {
if (instanceValue.IsEmpty () || argumentValues[0].IsEmpty ()) {
returnValue = MultiValueLattice.Top;
break;
}

var targetValue = GetMethodThisParameterValue (calledMethod, DynamicallyAccessedMemberTypesOverlay.Interfaces);
foreach (var value in instanceValue) {
// For now no support for marking a single interface by name. We would have to correctly support
// mangled names for generics to do that correctly. Simply mark all interfaces on the type for now.

// Require Interfaces annotation
_requireDynamicallyAccessedMembersAction.Invoke (value, targetValue);

// Interfaces is transitive, so the return values will always have at least Interfaces annotation
DynamicallyAccessedMemberTypes returnMemberTypes = DynamicallyAccessedMemberTypesOverlay.Interfaces;

// Propagate All annotation across the call - All is a superset of Interfaces
if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
&& valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.All)
returnMemberTypes = DynamicallyAccessedMemberTypes.All;

AddReturnValue (GetMethodReturnValue (calledMethod, returnMemberTypes));
foreach (var interfaceName in argumentValues[0]) {
if (interfaceName == NullValue.Instance) {
// Throws on null string, so no return value.
returnValue ??= MultiValueLattice.Top;
} else if (interfaceName is KnownStringValue stringValue && stringValue.Contents.Length == 0) {
AddReturnValue (NullValue.Instance);
} else {
// For now no support for marking a single interface by name. We would have to correctly support
// mangled names for generics to do that correctly. Simply mark all interfaces on the type for now.

// Require Interfaces annotation
_requireDynamicallyAccessedMembersAction.Invoke (value, targetValue);

// Interfaces is transitive, so the return values will always have at least Interfaces annotation
DynamicallyAccessedMemberTypes returnMemberTypes = DynamicallyAccessedMemberTypesOverlay.Interfaces;

// Propagate All annotation across the call - All is a superset of Interfaces
if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers
&& valueWithDynamicallyAccessedMembers.DynamicallyAccessedMemberTypes == DynamicallyAccessedMemberTypes.All)
returnMemberTypes = DynamicallyAccessedMemberTypes.All;

AddReturnValue (GetMethodReturnValue (calledMethod, returnMemberTypes));
}
}
}
}
break;
Expand All @@ -116,6 +135,11 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl
// AssemblyQualifiedName
//
case IntrinsicId.Type_get_AssemblyQualifiedName: {
if (instanceValue.IsEmpty ()) {
returnValue = MultiValueLattice.Top;
break;
}

foreach (var value in instanceValue) {
if (value is ValueWithDynamicallyAccessedMembers valueWithDynamicallyAccessedMembers) {
// Currently we don't need to track the difference between Type and String annotated values
Expand Down Expand Up @@ -467,9 +491,9 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl
return true;
}

// For some intrinsics we just use the return value from the annotations.
if (returnValue == null)
returnValue = calledMethod.ReturnsVoid () ? MultiValueLattice.Top : GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes);
// For now, if the intrinsic doesn't set a return value, fall back on the annotations.
// Note that this will be DynamicallyAccessedMembers.None for the intrinsics which don't return types.
returnValue ??= calledMethod.ReturnsVoid () ? MultiValueLattice.Top : GetMethodReturnValue (calledMethod, returnValueDynamicallyAccessedMemberTypes);

// Validate that the return value has the correct annotations as per the method return value annotations
if (returnValueDynamicallyAccessedMemberTypes != 0) {
Expand All @@ -480,6 +504,8 @@ public bool Invoke (MethodProxy calledMethod, MultiValue instanceValue, IReadOnl
} else if (uniqueValue is SystemTypeValue) {
// SystemTypeValue can fullfill 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 if (uniqueValue == NullValue.Instance) {
// NullValue can fulfill any requirements because reflection access to it will typically throw.
} else {
throw new InvalidOperationException ($"Internal linker error: in {GetContainingSymbolDisplayName ()} processing call to {calledMethod.GetDisplayName ()} returned value which is not correctly annotated with the expected dynamic member access kinds.");
}
Expand Down
10 changes: 9 additions & 1 deletion src/linker/Linker.Dataflow/ReflectionMethodBodyScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1015,7 +1015,7 @@ bool AnalyzeGenericInstantiationTypeArray (in AnalysisContext analysisContext, i

bool allIndicesKnown = true;
for (int i = 0; i < size.Value; i++) {
if (!array.TryGetValueByIndex (i, out MultiValue value) || value.IsEmpty () || value.AsSingleValue () is UnknownValue) {
if (!array.TryGetValueByIndex (i, out MultiValue value) || value.AsSingleValue () is UnknownValue) {
sbomer marked this conversation as resolved.
Show resolved Hide resolved
allIndicesKnown = false;
break;
}
Expand Down Expand Up @@ -1058,7 +1058,15 @@ void ProcessCreateInstanceByName (in AnalysisContext analysisContext, MethodDefi

foreach (var assemblyNameValue in methodParams[methodParamsOffset]) {
if (assemblyNameValue is KnownStringValue assemblyNameStringValue) {
if (assemblyNameStringValue.Contents is string assemblyName && assemblyName.Length == 0) {
// Throws exception for zero-length assembly name.
continue;
}
foreach (var typeNameValue in methodParams[methodParamsOffset + 1]) {
if (typeNameValue is NullValue) {
// Throws exception for null type name.
continue;
}
if (typeNameValue is KnownStringValue typeNameStringValue) {
var resolvedAssembly = _context.TryResolve (assemblyNameStringValue.Contents);
if (resolvedAssembly == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ static void Main ()
TestNull ();
TestMultipleValues ();
TestUnknownValue ();
TestNoValue ();
TestObjectGetTypeValue ();
}

[ExpectedWarning ("IL2072", nameof (RequirePublicConstructors))]
Expand Down Expand Up @@ -92,6 +94,28 @@ static void TestUnknownValue (object[] o = null)
RequireNothing (unknown); // shouldn't warn
}

static void TestNoValue ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
// t.TypeHandle throws at runtime so don't warn here.
RequirePublicConstructors (noValue.AssemblyQualifiedName);
}

[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicConstructors)]
class AnnotatedType
{
}

static void TestObjectGetTypeValue (AnnotatedType instance = null)
{
string type = instance.GetType ().AssemblyQualifiedName;
// Currently Object.GetType is unimplemented in the analyzer, but
// this still shouldn't warn.
RequirePublicConstructors (type);
RequireNothing (type);
}

private static void RequirePublicParameterlessConstructor (
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)]
string type)
Expand Down
36 changes: 36 additions & 0 deletions test/Mono.Linker.Tests.Cases/DataFlow/GetInterfaceDataFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,23 @@ public static void Main ()

class GetInterface_Name
{
static void TestNullName (Type type)
{
type.GetInterface (null);
}

static void TestEmptyName (Type type)
{
type.GetInterface (string.Empty);
}

static void TestNoValueName (Type type)
{
Type t = null;
string noValue = t.AssemblyQualifiedName;
type.GetInterface (noValue);
}

[ExpectedWarning ("IL2070", nameof (Type.GetInterface), nameof (DynamicallyAccessedMemberTypes) + "." + nameof (DynamicallyAccessedMemberTypes.Interfaces))]
static void TestNoAnnotation (Type type)
{
Expand Down Expand Up @@ -76,6 +93,20 @@ static void TestMergedValues (int p, [DynamicallyAccessedMembers (DynamicallyAcc
type.GetInterface ("ITestInterface").RequiresInterfaces ();
}

static void TestNullValue ()
{
Type t = null;
t.GetInterface ("ITestInterface").RequiresInterfaces ();
}

static void TestNoValue ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
// t.TypeHandle throws at runtime so don't warn here.
noValue.GetInterface ("ITestInterface").RequiresInterfaces ();
}

class GetInterfaceInCtor
{
public GetInterfaceInCtor ([DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.Interfaces)] Type type)
Expand All @@ -86,13 +117,18 @@ public GetInterfaceInCtor ([DynamicallyAccessedMembers (DynamicallyAccessedMembe

public static void Test ()
{
TestNullName (typeof (TestType));
TestEmptyName (typeof (TestType));
TestNoValueName (typeof (TestType));
TestNoAnnotation (typeof (TestType));
TestWithAnnotation (typeof (TestType));
TestWithAnnotation (typeof (ITestInterface));
TestWithAll (typeof (TestType));
TestKnownType ();
TestMultipleValues (0, typeof (TestType));
TestMergedValues (0, typeof (TestType));
TestNullValue ();
TestNoValue ();
var _ = new GetInterfaceInCtor (typeof (TestType));
}
}
Expand Down
18 changes: 18 additions & 0 deletions test/Mono.Linker.Tests.Cases/DataFlow/GetTypeDataFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ public static void Main ()
TestPublicParameterlessConstructor ();
TestPublicConstructors ();
TestConstructors ();
TestNull ();
TestNoValue ();
TestUnknownType ();

TestTypeNameFromParameter (null);
Expand Down Expand Up @@ -69,6 +71,22 @@ static void TestConstructors ()
type.RequiresNone ();
}

[ExpectedWarning ("IL2072", nameof (DataFlowTypeExtensions.RequiresAll) + "(Type)", nameof (Type.GetType) + "(String)")]
static void TestNull ()
{
// Warns about the return value of GetType, even though this throws at runtime.
Type.GetType (null).RequiresAll ();
sbomer marked this conversation as resolved.
Show resolved Hide resolved
}

[ExpectedWarning ("IL2072", nameof (DataFlowTypeExtensions.RequiresAll) + "(Type)", nameof (Type.GetType) + "(String)")]
static void TestNoValue ()
{
Type t = null;
string noValue = t.AssemblyQualifiedName;
// Warns about the return value of GetType, even though AssemblyQualifiedName throws at runtime.
Type.GetType (noValue).RequiresAll ();
}

[ExpectedWarning ("IL2057", nameof (GetType))]
static void TestUnknownType ()
{
Expand Down
15 changes: 15 additions & 0 deletions test/Mono.Linker.Tests.Cases/DataFlow/GetTypeInfoDataFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public static void Main ()
{
TestNoAnnotations (typeof (TestType));
TestWithAnnotations (typeof (TestType));
TestWithNull ();
TestWithNoValue ();
}

[ExpectedWarning ("IL2067", nameof (DataFlowTypeExtensions.RequiresPublicMethods))]
Expand All @@ -34,6 +36,19 @@ static void TestWithAnnotations ([DynamicallyAccessedMembers (DynamicallyAccesse
t.GetTypeInfo ().RequiresNone ();
}

static void TestWithNull ()
{
Type t = null;
t.GetTypeInfo ().RequiresPublicMethods ();
}

static void TestWithNoValue ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
noValue.GetTypeInfo ().RequiresPublicMethods ();
}

class TestType { }
}
}
49 changes: 49 additions & 0 deletions test/Mono.Linker.Tests.Cases/DataFlow/MakeGenericDataFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@ class MakeGenericType
public static void Test ()
{
TestNullType ();
TestNoValueInput ();
TestUnknownInput (null);
TestNullTypeArgument ();
TestNoValueTypeArgument ();
TestWithUnknownTypeArray (null);
TestWithArrayUnknownIndexSet (0);
TestWithArrayUnknownLengthSet (1);
Expand Down Expand Up @@ -53,6 +56,26 @@ static void TestNullType ()
nullType.MakeGenericType (typeof (TestType));
}

static void TestNoValueInput ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
noValue.MakeGenericType (typeof (TestType));
}

static void TestNullTypeArgument ()
{
Type t = null;
typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (t);
}

static void TestNoValueTypeArgument ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
typeof (GenericWithPublicFieldsArgument<>).MakeGenericType (noValue);
}

[ExpectedWarning ("IL2055", nameof (Type.MakeGenericType))]
static void TestUnknownInput (Type inputType)
{
Expand Down Expand Up @@ -194,8 +217,11 @@ class MakeGenericMethod
public static void Test ()
{
TestNullMethod ();
TestNoValueMethod ();
TestUnknownMethod (null);
TestUnknownMethodButNoTypeArguments (null);
TestNullTypeArgument ();
TestNoValueTypeArgument ();
TestWithUnknownTypeArray (null);
TestWithArrayUnknownIndexSet (0);
TestWithArrayUnknownIndexSetByRef (0);
Expand Down Expand Up @@ -235,6 +261,14 @@ static void TestNullMethod ()
mi.MakeGenericMethod (typeof (TestType));
}

[ExpectedWarning ("IL2060", nameof (MethodInfo.MakeGenericMethod) + "(Type[])")]
static void TestNoValueMethod ()
{
// GetMethod(null) throws at runtime.
MethodInfo noValue = typeof (MakeGenericMethod).GetMethod (null);
noValue.MakeGenericMethod (typeof (TestType));
}

[ExpectedWarning ("IL2060", nameof (MethodInfo.MakeGenericMethod))]
static void TestUnknownMethod (MethodInfo mi)
{
Expand All @@ -248,6 +282,21 @@ static void TestUnknownMethodButNoTypeArguments (MethodInfo mi)
mi.MakeGenericMethod (Type.EmptyTypes);
}

static void TestNullTypeArgument ()
{
Type t = null;
typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static)
.MakeGenericMethod (t);
}

static void TestNoValueTypeArgument ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
typeof (MakeGenericMethod).GetMethod (nameof (GenericWithRequirements), BindingFlags.Static)
.MakeGenericMethod (noValue);
}

[ExpectedWarning ("IL2060", nameof (MethodInfo.MakeGenericMethod))]
static void TestWithUnknownTypeArray (Type[] types)
{
Expand Down
10 changes: 10 additions & 0 deletions test/Mono.Linker.Tests.Cases/DataFlow/TypeBaseTypeDataFlow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public static void Main ()
TestNoAnnotation (typeof (TestType));
TestAnnotatedAndUnannotated (typeof (TestType), typeof (TestType), 0);
TestNull ();
TestNoValue ();

Mixed_Derived.Test (typeof (TestType), 0);
}
Expand Down Expand Up @@ -211,6 +212,15 @@ static void TestNull ()
type.BaseType.RequiresPublicMethods ();
}

[ExpectedWarning ("IL2072", nameof (DataFlowTypeExtensions) + "." + nameof (DataFlowTypeExtensions.RequiresPublicMethods))]
static void TestNoValue ()
{
Type t = null;
Type noValue = Type.GetTypeFromHandle (t.TypeHandle);
// Warns about the base type even though the above throws an exception at runtime.
noValue.BaseType.RequiresPublicMethods ();
sbomer marked this conversation as resolved.
Show resolved Hide resolved
}

class Mixed_Base
{
public static void PublicMethod () { }
Expand Down
Loading