-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Warn on field keyword references in trim analyzer #117072
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
Conversation
…mWarningParity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR introduces a new warning for dataflow assignments that use the literal 'field' keyword as a source and updates many test cases and diagnostics accordingly. Key changes include:
- Updating ExpectedWarning attributes in numerous test files to include GitHub URLs (e.g. https://github.com/dotnet/linker/issues/1971, #102830) and "NativeAOT-specific warning" strings.
- Modifying the trim analysis visitor and related classes to better handle backing field access and property dataflow.
- Adding new overloads and adjustments related to generic argument dataflow and backing field annotations.
Reviewed Changes
Copilot reviewed 30 out of 30 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs | Added VisitInvalid override to detect a 'field' keyword and updated GetFieldTargetValue to use a new helper for backing fields. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FieldValue.cs | Introduced a constructor overload for IPropertySymbol to support backing field analysis. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs | Added a branch for backing field annotation that currently throws NotImplementedException. |
| src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs | Added abstract GetBackingFieldTargetValue to support property backing field dataflow. |
| Multiple test files under src/tools/illink/test/Mono.Linker.Tests.Cases/… | Updated warning attributes to align with new diagnostic messages and include GitHub issue URLs. |
Comments suppressed due to low confidence (3)
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs:97
- Consider documenting the rationale for checking if operation.Syntax.ToString() equals 'field' in VisitInvalid, to ensure that this string-based check remains robust against potential syntax changes.
public override MultiValue VisitInvalid(IInvalidOperation invalidOperation, StateValue argument)
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FieldValue.cs:19
- The addition of a constructor overload in FieldValue to support IPropertySymbol is clear; please ensure that treating property backing fields similar to regular fields in dataflow analysis is intended and sufficiently documented.
public FieldValue(IPropertySymbol propertySymbol)
src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs:134
- The introduction of GetBackingFieldTargetValue improves handling of property backing fields in dataflow analysis. Please verify that all call sites invoking field dataflow now account for property references appropriately.
public abstract TValue GetBackingFieldTargetValue(IPropertyReferenceOperation propertyReference, in TContext context);
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs
Outdated
Show resolved
Hide resolved
|
Tagging subscribers to this area: @dotnet/illink |
…ferences to find backing field
| { | ||
| [Kept] | ||
| [ExpectedWarning("IL2077", nameof(MismatchAssignedFromField), nameof(WithMethods))] | ||
| [UnexpectedWarning("IL2078", nameof(MismatchAssignedFromField), Tool.NativeAot, "https://github.com/dotnet/runtime/issues/118873")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we instead make equivalent update to ILC's copy of FlowAnnotations.cs? We don't allow these shared files to diverge; it's a recipe for trouble.
I'm a bit suspicious of the FlowAnnotations change. I would instead tighten the heuristic to make sure it really only works on get; set; properties like we originally intended before the field keyword. The ScanMethodBodyForFieldAccess is a heuristic that only really works for get; set; properties and can come up with confusing conclusions for arbitrary IL.
Or to put it this way: if we want to allow arbitrary IL in get/set accessors of properties marked DAM, why not drop the IsCompilerGenerated filter:
| !found.IsCompilerGenerated()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like some of the changes in ILCompiler's FlowAnnotations actually do unify the behavior, for some reason the build process wasn't copying the latest built ILCompiler.dll to the test directory in local tests.
I think it would still make sense to slightly loosen the heuristic to work on properties with the field keyword. I can see this being commonly used for lazy properties and input validation, which would have more than one store/read of the same field for the accessors. I can't think of any situation where a compiler generated field is the only field accessed in an accessor that isn't the compiler generated backing field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I overall prefer to avoid reversing compiler-generated code, but this feels aligned with what we already do for get; set; properties, and I also can't think of any problems with the current approach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually maybe there are problems if the generated field isn't used as a backing store for the value. I couldn't come up with a realistic example, but it's possible to write code where the generated field shouldn't necessarily inherit the property annotations:
using System;
using System.Diagnostics.CodeAnalysis;
C.Type = typeof(Value); // warns for Value.RUCMethod
public class C
{
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
static Type? type;
[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]
static public Type? Type
{
get
{
if (field == typeof(Sentinel))
return type;
return null;
}
set
{
if (value == null)
{
field = null;
type = null;
}
else
{
field = typeof(Sentinel); // with this change, would preserve Sentinel.Unused
type = value;
}
}
}
}
class Sentinel {
[RequiresUnreferencedCode("Unused")]
public static void Unused() { }
}
class Value()
{
[RequiresUnreferencedCode("RUCMethod")]
public static void RUCMethod() { }
}There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep - that's what I mean - we currently restrict the propagation attempt to IsCompilerGenerated field.
But it's basically a dice roll (as far as the user is concerned) whether it will propagate correctly to where it should or propagate incorrectly and result in more preservation or warnings or fail to propagate.
At that point we could just drop the IsCompilerGenerated condition and let it try doing magic everywhere. (I am not a fan of doing that and not actually proposing it.)
...tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisBackingFieldAccessPattern.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/FlowAnnotations.cs
Outdated
Show resolved
Hide resolved
src/tools/illink/src/ILLink.RoslynAnalyzer/TrimAnalysis/TrimAnalysisVisitor.cs
Show resolved
Hide resolved
sbomer
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with @MichalStrehovsky's comment - I'd prefer not to propagate the property annotation to field. The compiler makes no guarantee that it's actually used as a backing field for the property, so we shouldn't assume that (even if it's a nice convenience feature).
Could we limit the auto-propagation to compiler-generated properties?
| && field.AssociatedSymbol is IPropertySymbol property) | ||
| { | ||
| // If the field is a backing field for a property and is not otherwise annotated (with [field: DAM]), | ||
| // the field assumes the annotation of the property it is associated with. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we shouldn't do this propagation at all, not just when it doesn't conflict with existing annotations. It shouldn't be required to place any annotations on the field.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, I stuck with this because it felt like it would be consistent with the trimmer and ilc more often since the IL generated for semi-auto/field keyword properties can be the identical as full-auto properties. Whether we treat the field as having no annotation or treat it as having the same as the property, we get potential inconsistencies with the tools, at least the way they detect auto-properties now.
I think we can align behavior better if we tighten the compiler generated field finder to make sure the getter and setter are compiler generated as well. We currently don't check that, but it looks like only full-auto properties have the attribute so we can be sure we're only propagating the annotations for full-auto properties where all accessors are compiler generated. The only concern then is if that breaks existing code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My vote is for tightening it to work only for compiler-generated getters or setters. I would only be concerned with breaking existing code if the compiler didn't always emit CompilerGeneratedAttributes on the get_/set_ method - I think we should go ahead and try it. @MichalStrehovsky @agocke any opinions on this?
(for get_Mix I would probably leave the get_ case working as-is, and warn in the set_ accessor - anything else probably requires extra work)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the situation with CompilerLoweringPreserve? I would expect the compiler to automatically lower to the backing field now. That would probably influence my perspective.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CompilerLoweringPreserve doesn't propagate to otherwise annotatable things. Since the field can still be annotated with [field: DAM], the attribute isn't propagated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, well, in that case can we avoid being too clever, since the user can just annotate the field?
I'm not sure what all the options are and what are their consequences. What's the current behavior and what are the possible new behaviors? And would this introduce any breaks?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My vote is for tightening it to work only for compiler-generated getters or setters. I would only be concerned with breaking existing code if the compiler didn't always emit
CompilerGeneratedAttributeson theget_/set_method - I think we should go ahead and try it. @MichalStrehovsky @agocke any opinions on this?(for
get_MixI would probably leave theget_case working as-is, and warn in theset_accessor - anything else probably requires extra work)
I would vote for this too. Add an extra restriction that we'll only look for the compiler generate field if the getter/setter is also compiler generated.
I think the only thing this could break (before the field keyword was introduced) is code where the user went and explicitly added a CompilerGenerated attribute to their field. Otherwise I don't see a way to access the backing field of a property from a non-compilergenerated method.
|
Closing this and tightening the propagation heuristic in #119329. |
…nly (#119329) In the trimmer and ILC, we searched for backing fields with heuristics that validated a backing field had a `CompilerGeneratedAttribute`. In C# 14, the `field` keyword / semi-auto property feature means that some properties will have a compiler-generated field, but a user-written accessor. This can cause holes and unexpected behavior with the current heuristics. The semi-auto property accessors do not have `CompilerGeneratedAttribute`, so we can tighten the heuristic to only find backing fields when all accessors _and_ the found backing field have `CompilerGeneratedAttribute`. This should apply backing field propagation heuristics to properties with at least one auto accessor (e.g. `int Prop { get => field; set; }`) only (and also warns when it is unable to find the backing field for these). For all other properties, it is possible to annotate the backing field and accessors to achieve the same behavior. Additional context: #117072 (comment) and #117072 (comment)
…nly (dotnet#119329) In the trimmer and ILC, we searched for backing fields with heuristics that validated a backing field had a `CompilerGeneratedAttribute`. In C# 14, the `field` keyword / semi-auto property feature means that some properties will have a compiler-generated field, but a user-written accessor. This can cause holes and unexpected behavior with the current heuristics. The semi-auto property accessors do not have `CompilerGeneratedAttribute`, so we can tighten the heuristic to only find backing fields when all accessors _and_ the found backing field have `CompilerGeneratedAttribute`. This should apply backing field propagation heuristics to properties with at least one auto accessor (e.g. `int Prop { get => field; set; }`) only (and also warns when it is unable to find the backing field for these). For all other properties, it is possible to annotate the backing field and accessors to achieve the same behavior. Additional context: dotnet#117072 (comment) and dotnet#117072 (comment)
…nly (#119329) (#119407) In the trimmer and ILC, we searched for backing fields with heuristics that validated a backing field had a `CompilerGeneratedAttribute`. In C# 14, the `field` keyword / semi-auto property feature means that some properties will have a compiler-generated field, but a user-written accessor. This can cause holes and unexpected behavior with the current heuristics. The semi-auto property accessors do not have `CompilerGeneratedAttribute`, so we can tighten the heuristic to only find backing fields when all accessors _and_ the found backing field have `CompilerGeneratedAttribute`. This should apply backing field propagation heuristics to properties with at least one auto accessor (e.g. `int Prop { get => field; set; }`) only (and also warns when it is unable to find the backing field for these). For all other properties, it is possible to annotate the backing field and accessors to achieve the same behavior. Additional context: #117072 (comment) and #117072 (comment)
Adds warning when a 'field' keyword is the source or target in a dataflow assignment. The IFieldSymbol of in these scenarios take on the annotation of the associated property.