-
Notifications
You must be signed in to change notification settings - Fork 128
Design proposal for DAM-on-type and RUC interactions #2168
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
Changes from all commits
f3064f7
1d919dd
20b57e3
f4ed417
d4f9255
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
# Interaction of DAM and RUC on type and members | ||
|
||
DAM = `DynamicallyAccessedMembers` attribute | ||
RUC = `RequiresUnreferencedCode` attribute | ||
|
||
## Existing rules | ||
|
||
* RUC on method suppresses warnings from the method's body and attributes. | ||
* RUC on method doesn't suppress warnings about the method usage (call to the method, accessing the method via reflection, ...) | ||
* RUC on type externally behaves as if all static members and constructors have RUC | ||
* RUC on type suppresses warnings as if all methods in the type have RUC on them (so method bodies and attributes) | ||
* RUC on type does NOT suppress warnings about/on nested types in any way | ||
|
||
## DAM on type behavior | ||
|
||
Annotating type with DAM effectively creates a reflection access to all affected members (depends on the annotation). The behavior in terms of getting a warning or not should be equivalent to accessing the member directly through reflection (`GetMethod` or similar). | ||
Applying "reflection access" on a member can produce additional warnings. Specifically if the member in question has RUC on it (or if itself has DAM annotation somewhere in it, like method parameters for example). | ||
|
||
For better warning UX and suppression we decided to generate the warnings with this origin: | ||
|
||
* The member itself if the type or one of its base types is annotated with a DAM which accesses the member in question | ||
* The type which adds DAM annotation which accesses a member on a base type (so unlike the above rule, we don't warn on the member, but on the type which added the DAM) | ||
|
||
*Note: the reasoning behind this behavior is to warn on the thing which is to blame for the problem. If type derives from annotated type, then the source of the problem is adding a member which will be accessed by such DAM and has issues (like RUC on it). If type adds DAM on itself, but such DAM causes access to a problematic member on base type, then the one to blame is the annotated type.* | ||
|
||
## RUC on type and interaction with DAM on type | ||
|
||
RUC on type doesn't suppress warnings caused by DAM on type. | ||
|
||
```csharp | ||
[DAM(All)] | ||
class AnnotatedBase {} | ||
|
||
[RUC] | ||
class DerivedWithRUC : AnnotatedBase | ||
{ | ||
public DerivedWithRUC () {} | ||
|
||
[RUC] | ||
public void MethodWithRUC() {} | ||
|
||
public void MethodWithDAM([DAM(Fields)] Type type) {} | ||
|
||
private static void StaticMethod() {} | ||
} | ||
``` | ||
|
||
Following the existing rules, accessing `MethodWithRUC` or `MethodWithDAM` via reflection will generate a warning, regardless of the RUC on type. | ||
Since DAM on type is meant to behave as if accessing the members via reflection, those warnings should not be affected by the RUC on type either. | ||
|
||
Note that RUC on type as mentioned above is equivalent to applying RUC to all static members and constructors. So following that logic it means | ||
that the DAM will cause warnings generated by static members and .ctors. So in the above example both `DerivedWithRUC..ctor` and `DerivedWithRUC.StaticMethod` | ||
should generate warning due to the `RUC` on the type. | ||
|
||
## RUC on method and interaction with DAM on type | ||
|
||
RUC on a method which is made accessible due to DAM on type will generate a warning (the method is "accessed"). | ||
|
||
```csharp | ||
[DAM(All)] | ||
class AnnotatedBase {} | ||
|
||
class Derived : AnnotatedBase | ||
{ | ||
[RUC] | ||
public void MethodWithRUC() {} | ||
|
||
public void MethodWithDAM([DAM(Fields)] Type type) {} | ||
|
||
[RUC] | ||
public void MethodWithRUCAndDAM([DAM(Fields)] Type type) {} | ||
} | ||
``` | ||
|
||
Following the existing rules, accessing `MethodWithRUC` or `MethodWithDAM` via reflection will generate a warning. The same rules also imply that accessing `MethodWithRUCAndDAM` via reflection will also generate a warning (actually two of them). | ||
RUC on method does not affect usage of the method (other than it should generate a warning on the usage). | ||
|
||
## RUC or DAM on nested type | ||
|
||
```csharp | ||
[DAM(All)] | ||
class AnnotatedBase {} | ||
|
||
class Derived : AnnotatedBase | ||
{ | ||
[RUC] | ||
class NestedWithRUC {} | ||
sbomer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
[DAM] | ||
class NestedWithDAM {} | ||
} | ||
``` | ||
|
||
This is probably an existing hole. Accessing a RUC or DAM type itself via reflection is not problematic on its own (since both annotations should behave as applying to members only). But using such type can be problematic. | ||
Since just asking for the type of a DAM annotated type is considered reflection access to all of the members affected by the DAM, RUC on nested types must be reported if the nested type has any static members or .ctors. | ||
|
||
```csharp | ||
void TestMethod(Derived instance) | ||
{ | ||
// This will warn that it's accessing NestedWithRUC..ctor | ||
Type typeOfDerived = instance.GetType(); // The type value is effectively annotated with All | ||
Type nestedWithRUC = typeOfDerived.GetNestedType("NestedWithRUC"); // The resulting type value is effectively annotated with All | ||
Activator.CreateInstance(nestedWithRUC); // No warning - nestedWithRUC has All annotation on it | ||
} | ||
``` | ||
|
||
DAM on nested type doesn't seem to have a problem mainly because for marking purposes it's not needed (the DAM on the outer type if it applies to the nested type will mark the entire nested type, so DAM on it can't mark more). | ||
Also accessing DAM annotated types themselves is not a problem, and accessing their members is driven by the DAM annotation. | ||
|
||
## Conclusion | ||
|
||
Personally I'm not a big fan of the fact that RUC on type doesn't suppress the new warnings on the type's members - as a normal user I would expect that to happen. That said, doing so would introduce an "Exception" to the existing rules, which is not ideal either. If we warn more (RUC on type doesn't affect the new warnings), it sort of creates noise, but it's not exactly wrong. And we can change our mind later on (by not warning anymore some day in the future). | ||
sbomer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
In the end the interaction is very simple, the new warnings caused by DAM on type are not affected by RUC in any way (as in, they're never suppressed by RUC). | ||
|
||
**Important note:** Suppressing the new warnings is very likely to create unexpected breaks in the app. The DAM annotation on the type is effectively "public" in that anybody can use it. So for example reasoning like "This warning is OK because the one use where the DAM is utilized will not actually access this method." is very problematic, since if there's more than one use of the DAM (either already, or in the future), the trimmer will not generate new warnings. The DAM on type is effectively part of the type's contract, and as such the type should fulfill the contract. And part of such contract is that none of the members referenced by the DAM is dangerous to use in trimmed apps. | ||
|
||
## Samples | ||
|
||
```csharp | ||
[DAM(All)] | ||
class AnnotatedBase {} | ||
|
||
[RUC] | ||
class DerivedWithRUC : AnnotatedBase | ||
{ | ||
// IL2112 | ||
[RUC] | ||
public void MethodWithRUC() {} | ||
|
||
// IL2114 | ||
public static void MethodWithDAM([DAM] Type type) {} | ||
|
||
// IL2112 | ||
// IL2114 | ||
[RUC] | ||
public void MethodWithDAMAndRUC([DAM] Type type) {} | ||
|
||
// IL2114 | ||
[DAM] | ||
public Type FieldWithDAM; | ||
|
||
// IL2112 - we must do this here, since we won't be able to see the RUC anywhere else | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the type also has explicit RUC annotated methods, with the warn-on-member approach I would expect the warning to originate on the method itself. Then I would expect the same for the implicitly RUC methods implied by the RUC on type. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this goes back to the thing about us marking all members of nested types - which will produce warnings. |
||
[RUC] | ||
public class NestedWithRUC {} | ||
|
||
// No warnings - ??? Probably ??? - I think the DAM on the outer type effectively implies DAM(All) on the nested type | ||
// and thus declaring it explicitly has no effect. But this needs validation specifically for cases where the outer type | ||
// has DAM(PublicNestedTypes) on it (the new behavior should still imply All on the nested type in such case I think) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, a DAM nested type shouldn't warn because of the outer DAM. A related question is whether RUC members of the nested DAM type should produce two warnings - one for the outer DAM, one for the inner DAM. I think it should - while the outer DAM effectively implies the inner DAM, it's possible that the app only uses the nested type's DAM requirements (they are really separate references to the RUC member). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed - I would not worry about producing "duplicate" warnings at this point - that's something we can worry about later if needed. |
||
[DAM(Fields)] | ||
public class NestedWithDAM {} | ||
} | ||
|
||
class Derived : AnnotatedBase | ||
{ | ||
// IL2112 | ||
[RUC] | ||
public void MethodWithRUC() {} | ||
|
||
// IL2114 | ||
public static void MethodWithDAM([DAM] Type type) {} | ||
|
||
// IL2112 | ||
// IL2114 | ||
[RUC] | ||
public void MethodWithDAMAndRUC([DAM] Type type) {} | ||
} | ||
``` |
Uh oh!
There was an error while loading. Please reload this page.