Welcome to mirror list, hosted at ThFree Co, Russian Federation.

fixing-warnings.md « docs - github.com/mono/linker.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 17ed8d4363d194cbe3668ed48b001c3205e4a813 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194

# Trim warnings in .NET 6

[In .NET Core 3.1 and 5.0 we introduced
trimming](https://devblogs.microsoft.com/dotnet/app-trimming-in-net-5/) as a new preview feature
for self-contained .NET core applications. Conceptually the feature is very simple: when
publishing the application the .NET SDK analyzes the entire application and removes all unused
code. In the time trimming has been in preview, we've learned that trimming is very powerful --
it can reduce application size by half or more. However, we've also learned about the
difficulties in safely trimming applications.

The most difficult question in trimming is what is unused, or more precisely, what is used. For
most standard C# code this is trivial -- the trimmer can easily walk method calls, field and
property references, etc, and determine what code is used. Unfortunately, some features, like
reflection, present a significant problem. Consider the following code:

```C#
string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}
```

In this example, `Type.GetType` dynamically requests a type with an unknown name, and then prints
the names of all of its methods. Because there's no way to know at publish time what type name is
going to be used, there's no way for the trimmer to know which type to preserve in the output.
It's very likely that this code could have worked before trimming (as long as the input is
something known to exist in the target framework), but would probably produce a null reference
exception after trimming (due to `Type.GetType` returning null).

This is a frustrating situation. Trimming often works just fine but occasionally it can produce
breaking behavior, sometimes in rare code paths, and it can be very hard to trace down the cause.

For .NET 6 we want to bring a new feature to trimming: trim warnings. Trim warnings happen
because the trimmer sees a call which may access other code in the app but the trimmer can't
determine which code. This could mean that the trimmer would trim away code which is used at
runtime.

## Reacting to trim warnings

Trim warnings are meant to bring predictability to trimming. The problem with trimming is that some
code patterns can depend on code in a way that isn't understandable by the trimmer. Whenever the
trimmer encounters code like that, that's when you should expect a trim warning.

There are two big categories of warnings which you will likely see:

 1. `RequiresUnreferencedCode`
 2. `DynamicallyAccessedMembers`

### RequiresUnreferencedCode

[RequiresUnreferencedCode](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.requiresunreferencedcodeattribute?view=net-5.0) is simple: it's an attribute that can be placed on methods to indicate
that the method is not trim-compatible, meaning that it might use reflection or some other
mechanism to access code that may be trimmed away. This attribute is used when it's not possible
for the trimmer to understand what's necessary, and a blanket warning is needed. This would often
be true for methods which use the C# `dynamic` keyword, `Assembly.LoadFrom`, or other runtime
code generation technologies.
An example would be:

```C#
[RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")]
void MethodWithAssemblyLoad() { ... }

void TestMethod()
{
    // IL2026: Using method 'MethodWithAssemblyLoad' which has 'RequiresUnreferencedCodeAttribute'
    // can break functionality when trimming application code. Use 'MethodFriendlyToTrimming' instead.
    MethodWithAssemblyLoad();
}
```

There aren't many workarounds for `RequiresUnreferencedCode`. The best way is to avoid calling
the method at all when trimming and use something else which is trim-compatible. If you're
writing a library and it's not in your control whether or not to call the method and you just
want to communicate to *your* caller, you can also add `RequiresUnreferencedCode` to your own
method. This silences all trimming warnings in your code, but will produce a warning whenever
someone calls your method.

If you can somehow determine that the call is safe, and all the code that's needed won't be
trimmed away, you can also suppress the warning using
[UnconditionalSuppressMessageAttribute](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.unconditionalsuppressmessageattribute?view=net-5.0).
For example:

```C#
[RequiresUnreferencedCode("Use 'MethodFriendlyToTrimming' instead")]
void MethodWithAssemblyLoad() { ... }

[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2026:RequiresUnreferencedCode",
    Justification = "Everything referenced in the loaded assembly is manually preserved, so it's safe")]
void TestMethod()
{
    MethodWithAssemblyLoad(); // Warning suppressed
}
```

`UnconditionalSuppressMessage` is like `SuppressMessage` but it is preserved into IL, so the
trimmer can see the suppression even after build and publish. `SuppressMessage` and `#pragma`
directives are only present in source so they can't be used to silence warnings from the
trimmer. Be very careful when suppressing trim warnings: it's possible that the call may be
trim-compatible now, but as you change your code that may change and you may forget to review all
the suppressions.

### DynamicallyAccessedMembers

[DynamicallyAccessedMembers](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.codeanalysis.dynamicallyaccessedmembersattribute?view=net-5.0) is usually about reflection. Unlike `RequiresUnreferencedCode`,
reflection can sometimes be understood by the trimmer as long as it's annotated correctly.
Let's take another look at the original example:

```C#
string s = Console.ReadLine();
Type type = Type.GetType(s);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}
```

In the example above, the real problem is `Console.ReadLine()`. Because *any* type could
be read the trimmer has no way to know if you need methods on `System.Tuple` or `System.Guid`
or any other type. On the other hand, if your code looked like,

```C#
Type type = typeof(System.Tuple);
foreach (var m in type.GetMethods())
{
    Console.WriteLine(m.Name);
}
```

This would be fine. Here the trimmer can see the exact type being referenced: `System.Tuple`. Now
it can use flow analysis to determine that it needs to keep all public methods. So where does
`DynamicallyAccessMembers` come in? What happens if the reflection is split across methods?

```C#
void Method1()
{
    Method2(typeof(System.Tuple));
}
void Method2(Type type)
{
    var methods = type.GetMethods();
    ...
}
```

If you compile the above, now you see a warning:

```
Trim analysis warning IL2070: net6.Program.Method2(Type): 'this' argument does not satisfy
'DynamicallyAccessedMemberTypes.PublicMethods' in call to 'System.Type.GetMethods()'. The
parameter 'type' of method 'net6.Program.Method2(Type)' does not have matching annotations. The
source value must declare at least the same requirements as those declared on the target
location it is assigned to.
```

For performance and stability flow analysis isn't performed between
methods, so annotation is needed to pass information upward, from the reflection call
(`GetMethods`) to the source of the `Type` (`typeof`). In the above example, the trimmer warning
is saying that `GetMethods` requires the `PublicMethods` annotation on types, but the `type`
variable doesn't have the same requirement. In other words, we need to pass the requirements from
`GetMethods` up to the caller:

```C#
void Method1()
{
    Method2(typeof(System.Tuple));
}
void Method2(
    [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type type)
{
    var methods = type.GetMethods();
    ...
}
```

Now the warning disappears, because the trimmer knows exactly which members to preserve, and
which type(s) to preserve them on. In general, this is the best way to deal with
`DynamicallyAccessedMembers` warnings: add annotations so the trimmer knows what to preserve.

As with `RequiresUnreferencedCode` warnings, adding `RequiresUnreferencedCode` or
`UnconditionalSuppressMessage` attributes also works, but none of these options make the code
compatible with trimming, while adding `DynamicallyAccessedMembers` does.

## Conclusion

This description should cover the most common situations you end up in while trimming your
application. Over time we'll continue to improve the diagnostic experience and tooling.

As we continue developing trimming we hope to see more code that's fully annotated, so users can
trim with confidence. Because trimming involves the whole application, trimming is as much a
feature of the ecosystem as it is of the product and we're depending on all developers to help
improve the ecosystem.