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 base type marking for DAM annotated classes #110655

Merged
merged 4 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 2 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
Original file line number Diff line number Diff line change
Expand Up @@ -455,7 +455,7 @@ private partial bool TryHandleIntrinsic (

if (annotation != default)
{
_reflectionMarker.Dependencies.Add(_reflectionMarker.Factory.ObjectGetTypeFlowDependencies(closestMetadataType), "GetType called on this type");
_reflectionMarker.Dependencies.Add(_reflectionMarker.Factory.ObjectGetTypeCalled(closestMetadataType), "GetType called on this type");
}

// Return a value which is "unknown type" with annotation. For now we'll use the return value node
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,25 @@ public static DependencyList ProcessTypeGetTypeDataflow(NodeFactory factory, Flo
{
DynamicallyAccessedMemberTypes annotation = flowAnnotations.GetTypeAnnotation(type);
Debug.Assert(annotation != DynamicallyAccessedMemberTypes.None);

// We're on an interface and we're processing annotations for the purposes of a object.GetType() call.
// Most of the annotations don't apply to the members of interfaces - the result of object.GetType() is
// never the interface type, it's a concrete type that implements the interface. Limit this to the only
// annotations that are applicable in this situation.
if (type.IsInterface)
{
// .All applies to interface members same as to the type
if (annotation != DynamicallyAccessedMemberTypes.All)
{
// Filter to the MemberTypes that apply to interfaces
annotation &= DynamicallyAccessedMemberTypes.Interfaces;

// If we're left with nothing, we're done
if (annotation == DynamicallyAccessedMemberTypes.None)
return new DependencyList();
}
}

var reflectionMarker = new ReflectionMarker(logger, factory, flowAnnotations, typeHierarchyDataFlowOrigin: type, enabled: true);

// We need to apply annotations to this type, and its base/interface types (recursively)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,11 @@ private void CreateNodeCaches()
return new GenericStaticBaseInfoNode(type);
});

_objectGetTypeCalled = new NodeCache<MetadataType, ObjectGetTypeCalledNode>(type =>
{
return new ObjectGetTypeCalledNode(type);
});

_objectGetTypeFlowDependencies = new NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode>(type =>
{
return new ObjectGetTypeFlowDependenciesNode(type);
Expand Down Expand Up @@ -1147,6 +1152,12 @@ internal GenericStaticBaseInfoNode GenericStaticBaseInfo(MetadataType type)
return _genericStaticBaseInfos.GetOrAdd(type);
}

private NodeCache<MetadataType, ObjectGetTypeCalledNode> _objectGetTypeCalled;
internal ObjectGetTypeCalledNode ObjectGetTypeCalled(MetadataType type)
{
return _objectGetTypeCalled.GetOrAdd(type);
}

private NodeCache<MetadataType, ObjectGetTypeFlowDependenciesNode> _objectGetTypeFlowDependencies;
internal ObjectGetTypeFlowDependenciesNode ObjectGetTypeFlowDependencies(MetadataType type)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System;
using System.Collections.Generic;

using ILCompiler.DependencyAnalysisFramework;

using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis
{
/// <summary>
/// Represents the fact that object.GetType was called on a location typed as this
/// type or one of the subtypes of it.
/// </summary>
internal sealed class ObjectGetTypeCalledNode : DependencyNodeCore<NodeFactory>
{
private readonly MetadataType _type;

public ObjectGetTypeCalledNode(MetadataType type)
{
_type = type;
}

protected override string GetName(NodeFactory factory)
{
return $"Object.GetType called on {_type}";
}

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory) => null;
public override bool InterestingForDynamicDependencyAnalysis => false;
public override bool HasDynamicDependencies => false;
public override bool HasConditionalStaticDependencies => false;
public override bool StaticDependenciesAreComputed => true;
public override IEnumerable<CombinedDependencyListEntry> GetConditionalStaticDependencies(NodeFactory factory) => null;
public override IEnumerable<CombinedDependencyListEntry> SearchDynamicDependencies(List<DependencyNodeCore<NodeFactory>> markedNodes, int firstNode, NodeFactory factory) => null;

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

using ILCompiler.DependencyAnalysisFramework;

using ILLink.Shared.TrimAnalysis;

using Internal.TypeSystem;

namespace ILCompiler.DependencyAnalysis
Expand All @@ -25,21 +27,31 @@ public ObjectGetTypeFlowDependenciesNode(MetadataType type)

protected override string GetName(NodeFactory factory)
{
return $"Object.GetType dependencies called on {_type}";
return $"Object.GetType dependencies for {_type}";
}

public override IEnumerable<DependencyListEntry> GetStaticDependencies(NodeFactory factory)
{
var mdManager = (UsageBasedMetadataManager)factory.MetadataManager;
FlowAnnotations flowAnnotations = mdManager.FlowAnnotations;

DependencyList result = Dataflow.ReflectionMethodBodyScanner.ProcessTypeGetTypeDataflow(factory, mdManager.FlowAnnotations, mdManager.Logger, _type);

MetadataType baseType = _type.MetadataBaseType;
if (baseType != null && flowAnnotations.GetTypeAnnotation(baseType) != default)
{
result.Add(factory.ObjectGetTypeFlowDependencies(baseType), "Apply annotations to bases");
}

// We don't mark any members on interfaces - these nodes are only used as conditional dependencies
// of other nodes. Calling `object.GetType()` on something typed as an interface will return
// something that implements the interface, not the interface itself. We're not reflecting on the
// interface.
if (_type.IsInterface)
return Array.Empty<DependencyListEntry>();
foreach (DefType interfaceType in _type.RuntimeInterfaces)
{
if (flowAnnotations.GetTypeAnnotation(interfaceType) != default)
{
result.Add(factory.ObjectGetTypeFlowDependencies((MetadataType)interfaceType), "Apply annotations to interfaces");
}
}

return Dataflow.ReflectionMethodBodyScanner.ProcessTypeGetTypeDataflow(factory, mdManager.FlowAnnotations, mdManager.Logger, _type);
return result;
}

public override bool InterestingForDynamicDependencyAnalysis => false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,9 +408,19 @@ public override void GetConditionalDependenciesDueToEETypePresence(ref CombinedD
{
// Check to see if we have any dataflow annotations on the type.
// The check below also covers flow annotations inherited through base classes and implemented interfaces.
if (type.IsDefType
&& !type.IsInterface /* "IFoo x; x.GetType();" -> this doesn't actually return an interface type */
&& FlowAnnotations.GetTypeAnnotation(type) != default)
bool hasFlowAnnotations = type.IsDefType && FlowAnnotations.GetTypeAnnotation(type) != default;

if (hasFlowAnnotations)
{
dependencies ??= new CombinedDependencyList();
dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
factory.ObjectGetTypeFlowDependencies((MetadataType)type),
factory.ObjectGetTypeCalled((MetadataType)type),
"Type exists and GetType called on it"));
}

if (hasFlowAnnotations
&& !type.IsInterface /* "IFoo x; x.GetType();" -> this doesn't actually return an interface type */)
{
// We have some flow annotations on this type.
//
Expand All @@ -428,8 +438,8 @@ public override void GetConditionalDependenciesDueToEETypePresence(ref CombinedD
// Ensure we have the flow dependencies.
dependencies ??= new CombinedDependencyList();
dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
factory.ObjectGetTypeFlowDependencies((MetadataType)type),
factory.ObjectGetTypeFlowDependencies((MetadataType)baseType),
factory.ObjectGetTypeCalled((MetadataType)type),
factory.ObjectGetTypeCalled((MetadataType)baseType),
"GetType called on the base type"));

// We don't have to follow all the bases since the base MethodTable will bubble this up
Expand All @@ -444,8 +454,8 @@ public override void GetConditionalDependenciesDueToEETypePresence(ref CombinedD
// Ensure we have the flow dependencies.
dependencies ??= new CombinedDependencyList();
dependencies.Add(new DependencyNodeCore<NodeFactory>.CombinedDependencyListEntry(
factory.ObjectGetTypeFlowDependencies((MetadataType)type),
factory.ObjectGetTypeFlowDependencies((MetadataType)interfaceType),
factory.ObjectGetTypeCalled((MetadataType)type),
factory.ObjectGetTypeCalled((MetadataType)interfaceType),
"GetType called on the interface"));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@
<Compile Include="Compiler\DependencyAnalysis\MethodExceptionHandlingInfoNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ModuleInitializerListNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\NotReadOnlyFieldNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ObjectGetTypeCalledNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ObjectGetTypeFlowDependenciesNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\PointerTypeMapNode.cs" />
<Compile Include="Compiler\DependencyAnalysis\ReflectedDelegateNode.cs" />
Expand Down
5 changes: 1 addition & 4 deletions src/tests/nativeaot/SmokeTests/TrimmingBehaviors/Dataflow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -565,10 +565,7 @@ public static void Run()
// so that we can tests this doesn't lead to keeping the type.
Assert.Equal(666, s_neverAllocatedTypeAskingForNonPublicMethods.GetType().CountMethods());
}
// This is not great - the GetType() call above "wished" NeverAllocatedTypeAskingForNonPublicMethods
// into existence, but it shouldn't have. We could do better here if this is a problem.
// If we do that, change this .NotNull to .Null.
Assert.NotNull(typeof(TestObjectGetTypeDataflow).GetNestedTypeSecretly(nameof(NeverAllocatedTypeAskingForNonPublicMethods)));
Assert.Null(typeof(TestObjectGetTypeDataflow).GetNestedTypeSecretly(nameof(NeverAllocatedTypeAskingForNonPublicMethods)));
// Sanity check
Assert.NotNull(typeof(TestObjectGetTypeDataflow).GetNestedTypeSecretly(nameof(TypeWithNonPublicMethodsKept)));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public static void Main ()
{
SealedConstructorAsSource.Test ();
InstantiatedGenericAsSource.Test ();
InstantiatedGenericAsSourceInstantiated.Test ();
EnumTypeSatisfiesPublicFields.Test ();
EnumConstraintSatisfiesPublicFields.Test ();
InstantiatedTypeParameterAsSource.Test ();
Expand Down Expand Up @@ -50,15 +51,15 @@ class InstantiatedGenericAsSource
{
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
class Generic<T> {
[ExpectedWarning ("IL2112")]
[ExpectedWarning ("IL2112", Tool.Trimmer | Tool.Analyzer, "https://github.com/dotnet/runtime/issues/110563")]
[RequiresUnreferencedCode (nameof (KeptForMethodParameter))]
public void KeptForMethodParameter () {}

[ExpectedWarning ("IL2112")]
[ExpectedWarning ("IL2112", Tool.Trimmer | Tool.Analyzer, "https://github.com/dotnet/runtime/issues/110563")]
[RequiresUnreferencedCode (nameof (KeptForField))]
public void KeptForField () {}

[ExpectedWarning ("IL2112")]
[ExpectedWarning ("IL2112", Tool.Trimmer | Tool.Analyzer, "https://github.com/dotnet/runtime/issues/110563")]
[RequiresUnreferencedCode (nameof (KeptJustBecause))]
public void KeptJustBecause () {}
}
Expand All @@ -82,6 +83,43 @@ public static void Test ()
}
}

class InstantiatedGenericAsSourceInstantiated
{
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
class Generic<T>
{
[ExpectedWarning ("IL2112")]
[RequiresUnreferencedCode (nameof (KeptForMethodParameter))]
public void KeptForMethodParameter () { }

[ExpectedWarning ("IL2112")]
[RequiresUnreferencedCode (nameof (KeptForField))]
public void KeptForField () { }

[ExpectedWarning ("IL2112")]
[RequiresUnreferencedCode (nameof (KeptJustBecause))]
public void KeptJustBecause () { }
}

static void TestMethodParameter (Generic<int> instance)
{
instance.GetType ().GetMethod ("KeptForMethodParameter");
}

static Generic<int> field = new Generic<int> ();

static void TestField ()
{
field.GetType ().GetMethod ("KeptForField");
}

public static void Test ()
{
TestMethodParameter (null);
TestField ();
}
}

class EnumTypeSatisfiesPublicFields
{
static void ParameterType (Enum instance)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ public static void Main ()

class GenericReturnType
{
[KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute))]
[KeptAttributeAttribute (typeof (DynamicallyAccessedMembersAttribute), By = Tool.Trimmer /* Type is needed due to IL metadata only */)]
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
class ReturnType<T>
{
[Kept]
[Kept (By = Tool.Trimmer /* https://github.com/dotnet/runtime/issues/110563 */)]
public void Method () { }
}

Expand Down
Loading
Loading