Skip to content

Warn on every method annotated with RequiresUnreferencedCode#1639

Closed
mateoatr wants to merge 12 commits intodotnet:masterfrom
mateoatr:requiresUnreferencedCode
Closed

Warn on every method annotated with RequiresUnreferencedCode#1639
mateoatr wants to merge 12 commits intodotnet:masterfrom
mateoatr:requiresUnreferencedCode

Conversation

@mateoatr
Copy link
Contributor

Fix #1607. This moves the processing of RequiresUnreferencedCode to MarkMethod, so that we produce a warning on every method which is annotated with this attribute. There are only two cases in which we'll not warn:

  1. Annotated methods whose caller is annotated as well:
[RequiresUnreferencedCode("")]
static void Foo() { Bar(); } // Calling this method will warn
[RequiresUnreferencedCode("")]
static void Bar() { } // Calling this from Foo() will not warn
  1. Annotated override methods: the warning should be produced when marking the inherited method instead.

Mateo Torres Ruiz added 2 commits November 17, 2020 21:20
@mateoatr mateoatr requested a review from vitek-karas November 19, 2020 22:52
@mateoatr mateoatr self-assigned this Nov 19, 2020
Comment on lines +18 to +19
[ExpectedWarning ("IL2026", "Calling 'Mono.Linker.Tests.Cases.RequiresCapability.RequiresUnreferencedCodeCapability.BaseType.VirtualMethodRequiresUnreferencedCode()' " +
"which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --VirtualMethodRequiresUnreferencedCode--.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really weird - the place where the warning is reported....

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree - although I'm not sure what to do in cases like this, where a method is kept because of a DynamicallyAccessedMember attribute. The method is not being called, we are just marking it in case the user later calls it, so I think maybe this should not warn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right that we preserve it because it may be needed later - but that's kind of the point. Linker can't figure out when exactly it's going to be used, so instead we have data flow annotations and will "detect" the usage somewhere else. Since we can't tell where it's actually being used, there's no point trying to report it there. Instead we should report it where we know about it - it's not 100% but it's pretty close.

Not warning is not an option - if the method is going to be used for real (linker can't tell), that would break the promise and potentially the app. So we have to play it safe and warn.

I think the linker should warn at the place where it detects the need to preserve the method - in cases of DynamicallyAccessedMember, it's the same place where we would issue warnings about problems with the DynamicallyAccessedMember attribute itself (for example if the type being used is not statically known).

// the warning should be put in the virtual method instead.
if (sourceLocationMember.MemberDefinition != null &&
Annotations.HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (sourceLocationMember.MemberDefinition) ||
Annotations.GetBaseMethods (method) != null)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand the last check about the base methods.
If the method is in a direct call (can be non-virtual in fact) then the warning should be reported.
I think the interesting question is what happens if I have:

interface ITest
{
    [RequiresUnreferencedCode]
    void DoDanger();
}

class Test : ITest
{
    [RequiredUnreferencedCode]
    void DoDanger() { }
}

void TestIt()
{
    ITest it = new Test();
    it.DoDanger(); // This should warn, but that is the easy part
}

// There should not be another warning about DoDanger even though there will be a MarkMethod(Test.DoDanger) probably coming from the interface as it's detected to be instantiated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I understand the last check about the base methods.

It should prevent us from producing two warnings in cases such as:

Bar(typeof(DerivedClass));

void Bar([[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type) {}

class BaseClass
{
	[RequiresUnreferencedCode ("")]
	public virtual void Foo() {}
}

class DerivedClass : BaseClass
{
	[RequiresUnreferencedCode ("")]
	public override void Foo() {}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I finally figured out the counter example:

		abstract class CovariantReturnBase
		{
			[RequiresUnreferencedCode ("Message for --CovariantReturnBase.GetRequiresUnreferencedCode--")]
			public abstract BaseReturnType GetRequiresUnreferencedCode ();
		}

		class CovariantReturnDerived : CovariantReturnBase
		{
			[RequiresUnreferencedCode ("Message for --CovariantReturnBase.GetRequiresUnreferencedCode--")]
			public override DerivedReturnType GetRequiresUnreferencedCode ()
			{
				return null;
			}
		}

		[LogDoesNotContain("--CovariantReturnBase.GetRequiresUnreferencedCode--")]
		[ExpectedWarning ("IL2026", "--CovariantReturnBase.GetRequiresUnreferencedCode--")]
		static void TestCovariantReturnCallOnDerived ()
		{
			var tmp = new CovariantReturnDerived ();
			tmp.GetRequiresUnreferencedCode ();
		}

The problem is that the callvirt in this case is not to the base method. The code as written in the MarkStep basically assumes that if an override is marked, it is never a target of a call - it's only because of virtual hierarchy marking. Which is mostly true for C#, since the compiler makes sure it generates callvirt to the base method regardless of which type the method was called on. This might not be true for non-C#, but it's definitely not true with covariant returns as shown above, in this case C# intentionally generates call to the override method.

The above test sample fails because there's no warning generated. Which is weird for multiple reasons, I'm still looking into it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a change with the new test and modification to the markStep which makes all of the tests pass: mateoatr/linker@requiresUnreferencedCode...vitek-karas:requiresUnreferencedCode

@mateoatr mateoatr force-pushed the requiresUnreferencedCode branch from 8e162e2 to c22081d Compare November 24, 2020 21:35

class TypeWhichOverridesMethod : BaseType
{
[ExpectedWarning ("IL2046", "All overridden methods must have 'RequiresUnreferencedCodeAttribute'.")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think testing the behavior with only partially annotated hierarchies is necessary. I think it's more important to test the case where the hierarchy is fully annotated (otherwise the user will get warnings anyway).

}

[ExpectedWarning ("IL2026", "--VirtualMethodRequiresUnreferencedCode--")]
static void TestVirtualMethodRequiresUnreferencedCode ()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And just like in the interface case, I would validate that we don't get a warning due to the virtual override marking.

Comment on lines +2340 to +2341
if (reason.Kind != DependencyKind.DynamicallyAccessedMember)
ProcessRequiresUnreferencedCode (method, origin);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?
I thought we want to warn in this case - it was kind of the point of the bug which caused this whole change, wasn't it?

Comment on lines 2352 to 2353
// Overrides of methods annotated with `RequiresUnreferencedCodeAttribute` should not produce a warning here,
// the warning should be put in the virtual method instead.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment needs updating. While it's technically true, it's not obvious how that matters for the if below - since the if doesn't have anything to do with virtual methods anymore:

  • We rely on the fact that all overrides should have the same annotations as the base method (and we have a verification step which would warn if that's not the case)
  • We rely on the fact that the base method is the "origin" for marking overrides
  • And so if the override has the annotation, the base should have it as well -> we will not produce warning since the "origin" will have the annotation.

tmp.VirtualMethodRequiresUnreferencedCode ();
}

[ExpectedWarning ("IL2026", "--TestStaticCtor--")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally this warning would be generated in the TestStaticCctorRequiresUnreferencedCode method.
Basically I should be able to add RequiresUnreferencedCode onto the test method and not get any warnings.
The way it works now is problematic because there's no way to get rid of the warning without explicit suppression.

public class DynamicallyAccessedTypeWithRequiresUnreferencedCode
{
[RequiresUnreferencedCode ("Message for --RequiresUnreferencedCode--")]
[LogDoesNotContain ("DynamicallyAccessedTypeWithRequiresUnreferencedCode.RequiresUnreferencedCode")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand why the warning shouldn't be generated. We will keep the method - it's marked as problematic - it may be actually used by the code (we don't know for sure, but that's why we included it) - we need to report warning to the user.
The warning should be generated on the caller - in this case the Main - just like it was before.

@marek-safar
Copy link
Contributor

@mateoatr what are the next steps here?

@mateoatr
Copy link
Contributor Author

@mateoatr what are the next steps here?

I'll make a new PR with this changes -- more convenient than trying to rebase. This PR already had the fix for places where we don't warn on direct calls to methods annotated with the attribute. I also need to make sure that we warn on annotated methods that are referenced via reflection and that we don't for methods that are kept because of an assembly being copied but are otherwise not used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RequiresUnreferencedCode does not produce warning if the method is not seen in a call instruction

3 participants