Warn on every method annotated with RequiresUnreferencedCode#1639
Warn on every method annotated with RequiresUnreferencedCode#1639mateoatr wants to merge 12 commits intodotnet:masterfrom
Conversation
Do not warn on overrides
test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs
Show resolved
Hide resolved
test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs
Outdated
Show resolved
Hide resolved
| [ExpectedWarning ("IL2026", "Calling 'Mono.Linker.Tests.Cases.RequiresCapability.RequiresUnreferencedCodeCapability.BaseType.VirtualMethodRequiresUnreferencedCode()' " + | ||
| "which has `RequiresUnreferencedCodeAttribute` can break functionality when trimming application code. Message for --VirtualMethodRequiresUnreferencedCode--.")] |
There was a problem hiding this comment.
This is really weird - the place where the warning is reported....
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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).
src/linker/Linker.Steps/MarkStep.cs
Outdated
| // the warning should be put in the virtual method instead. | ||
| if (sourceLocationMember.MemberDefinition != null && | ||
| Annotations.HasLinkerAttribute<RequiresUnreferencedCodeAttribute> (sourceLocationMember.MemberDefinition) || | ||
| Annotations.GetBaseMethods (method) != null) |
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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() {}
}There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs
Outdated
Show resolved
Hide resolved
8e162e2 to
c22081d
Compare
|
|
||
| class TypeWhichOverridesMethod : BaseType | ||
| { | ||
| [ExpectedWarning ("IL2046", "All overridden methods must have 'RequiresUnreferencedCodeAttribute'.")] |
There was a problem hiding this comment.
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).
test/Mono.Linker.Tests.Cases/RequiresCapability/RequiresUnreferencedCodeCapability.cs
Show resolved
Hide resolved
| } | ||
|
|
||
| [ExpectedWarning ("IL2026", "--VirtualMethodRequiresUnreferencedCode--")] | ||
| static void TestVirtualMethodRequiresUnreferencedCode () |
There was a problem hiding this comment.
And just like in the interface case, I would validate that we don't get a warning due to the virtual override marking.
src/linker/Linker.Steps/MarkStep.cs
Outdated
| if (reason.Kind != DependencyKind.DynamicallyAccessedMember) | ||
| ProcessRequiresUnreferencedCode (method, origin); |
There was a problem hiding this comment.
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?
src/linker/Linker.Steps/MarkStep.cs
Outdated
| // Overrides of methods annotated with `RequiresUnreferencedCodeAttribute` should not produce a warning here, | ||
| // the warning should be put in the virtual method instead. |
There was a problem hiding this comment.
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--")] |
There was a problem hiding this comment.
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")] |
There was a problem hiding this comment.
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.
|
@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. |
Fix #1607. This moves the processing of
RequiresUnreferencedCodetoMarkMethod, 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: