-
Notifications
You must be signed in to change notification settings - Fork 299
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
Partially annotate IL2026 #1958
Changes from all commits
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 |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
using System; | ||
using System.Data; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Runtime.CompilerServices; | ||
|
||
namespace Microsoft.Data.SqlClient | ||
|
@@ -14,6 +15,8 @@ namespace Microsoft.Data.SqlClient | |
/// </summary> | ||
internal static class SqlClientDiagnosticListenerExtensions | ||
{ | ||
private const string EventSourceSuppressMessage = "Parameters to this method are statically known and thus are trimmer safe"; | ||
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. Can you explain why suppression is required while the parameters are genuinely trimmer-safe? I expect the analyzer to distinguish it. VS recognizes them as an unnecessary suppression that could be removed though. 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. IL trimmer made design decision operate on IL level, when it see virtual call he don't perform full application analysys to prove that this is actuall specific call. So each virtual call should have same annotations as base class. I know, probably that can be relaxed, but it is how it is. @vitek-karas maybe give more perspective why this is needed. 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. ooh, here. I should not be that fast in answering. EventSource.Write annotated as RequireUnreferencedCode, and trimmer think that if somebody use such methods it's dangerous. It is expected suppress warning like that 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. This is actually not correct and should not be suppressed as-is. This code for example means that OpenTelemetry's SqlClient instrumentation is not trim/AOT compatible. https://github.com/open-telemetry/opentelemetry-dotnet/blob/b091af1899b66a516d753109ce6bf8182be416e0/src/OpenTelemetry.Instrumentation.SqlClient/SqlClientInstrumentation.cs#L56C9-L61 Sorry it fell through cracks on my side, I meant to file an issue around this long time ago... so I finally did it: #2184. Other libraries are in similar (although not as bad) problem, so we're slowly trying to get this fixed. OTel in its latest version is now fully AOT compatible, with the exception of the SqlClient instrumentation as there's no good way to make that work correctly right now. As described in the issue - the best solution is to declare all of these payload types as public. That way the consumer (OTel for example) can just cast they payload instance to that type and directly access its properties. That would also fix all of the trimming issues related to in-proc consumers which are using the public types. Separate issue is all the consumers which are either using reflection or rely on diagnostic event source to do the same. For those the only solution is to explicitely hint the trimmer to preserve certain properties. For example System.Http, ASP.NET and GRPC all do this to some degree. For example HTTP stack: This is not a precise solution and never will be, but it's worth preserving at least properties used by OTel and other popular consumers (personally I don't know what they would be though). |
||
|
||
public const string DiagnosticListenerName = "SqlClientDiagnosticListener"; | ||
|
||
private const string SqlClientPrefix = "Microsoft.Data.SqlClient."; | ||
|
@@ -38,6 +41,8 @@ internal static class SqlClientDiagnosticListenerExtensions | |
public const string SqlAfterRollbackTransaction = SqlClientPrefix + nameof(WriteTransactionRollbackAfter); | ||
public const string SqlErrorRollbackTransaction = SqlClientPrefix + nameof(WriteTransactionRollbackError); | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static Guid WriteCommandBefore(this SqlDiagnosticListener @this, SqlCommand sqlCommand, SqlTransaction transaction, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlBeforeExecuteCommand)) | ||
|
@@ -62,6 +67,8 @@ public static Guid WriteCommandBefore(this SqlDiagnosticListener @this, SqlComma | |
return Guid.Empty; | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteCommandAfter(this SqlDiagnosticListener @this, Guid operationId, SqlCommand sqlCommand, SqlTransaction transaction, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlAfterExecuteCommand)) | ||
|
@@ -81,6 +88,8 @@ public static void WriteCommandAfter(this SqlDiagnosticListener @this, Guid oper | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteCommandError(this SqlDiagnosticListener @this, Guid operationId, SqlCommand sqlCommand, SqlTransaction transaction, Exception ex, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlErrorExecuteCommand)) | ||
|
@@ -100,6 +109,8 @@ public static void WriteCommandError(this SqlDiagnosticListener @this, Guid oper | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static Guid WriteConnectionOpenBefore(this SqlDiagnosticListener @this, SqlConnection sqlConnection, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlBeforeOpenConnection)) | ||
|
@@ -123,6 +134,8 @@ public static Guid WriteConnectionOpenBefore(this SqlDiagnosticListener @this, S | |
return Guid.Empty; | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteConnectionOpenAfter(this SqlDiagnosticListener @this, Guid operationId, SqlConnection sqlConnection, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlAfterOpenConnection)) | ||
|
@@ -142,6 +155,8 @@ public static void WriteConnectionOpenAfter(this SqlDiagnosticListener @this, Gu | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteConnectionOpenError(this SqlDiagnosticListener @this, Guid operationId, SqlConnection sqlConnection, Exception ex, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlErrorOpenConnection)) | ||
|
@@ -161,6 +176,8 @@ public static void WriteConnectionOpenError(this SqlDiagnosticListener @this, Gu | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static Guid WriteConnectionCloseBefore(this SqlDiagnosticListener @this, SqlConnection sqlConnection, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlBeforeCloseConnection)) | ||
|
@@ -185,6 +202,8 @@ public static Guid WriteConnectionCloseBefore(this SqlDiagnosticListener @this, | |
return Guid.Empty; | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteConnectionCloseAfter(this SqlDiagnosticListener @this, Guid operationId, Guid clientConnectionId, SqlConnection sqlConnection, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlAfterCloseConnection)) | ||
|
@@ -203,6 +222,8 @@ public static void WriteConnectionCloseAfter(this SqlDiagnosticListener @this, G | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteConnectionCloseError(this SqlDiagnosticListener @this, Guid operationId, Guid clientConnectionId, SqlConnection sqlConnection, Exception ex, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlErrorCloseConnection)) | ||
|
@@ -222,6 +243,8 @@ public static void WriteConnectionCloseError(this SqlDiagnosticListener @this, G | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static Guid WriteTransactionCommitBefore(this SqlDiagnosticListener @this, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlBeforeCommitTransaction)) | ||
|
@@ -246,6 +269,8 @@ public static Guid WriteTransactionCommitBefore(this SqlDiagnosticListener @this | |
return Guid.Empty; | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteTransactionCommitAfter(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlAfterCommitTransaction)) | ||
|
@@ -264,6 +289,8 @@ public static void WriteTransactionCommitAfter(this SqlDiagnosticListener @this, | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteTransactionCommitError(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, Exception ex, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlErrorCommitTransaction)) | ||
|
@@ -283,6 +310,8 @@ public static void WriteTransactionCommitError(this SqlDiagnosticListener @this, | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static Guid WriteTransactionRollbackBefore(this SqlDiagnosticListener @this, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName = null, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlBeforeRollbackTransaction)) | ||
|
@@ -308,6 +337,8 @@ public static Guid WriteTransactionRollbackBefore(this SqlDiagnosticListener @th | |
return Guid.Empty; | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteTransactionRollbackAfter(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, string transactionName = null, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlAfterRollbackTransaction)) | ||
|
@@ -327,6 +358,8 @@ public static void WriteTransactionRollbackAfter(this SqlDiagnosticListener @thi | |
} | ||
} | ||
|
||
[SuppressMessage("ReflectionAnalysis", "IL2026:UnrecognizedReflectionPattern", | ||
Justification = EventSourceSuppressMessage)] | ||
public static void WriteTransactionRollbackError(this SqlDiagnosticListener @this, Guid operationId, IsolationLevel isolationLevel, SqlConnection connection, SqlInternalTransaction transaction, Exception ex, string transactionName = null, [CallerMemberName] string operation = "") | ||
{ | ||
if (@this.IsEnabled(SqlErrorRollbackTransaction)) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -121,6 +121,9 @@ private static readonly ConcurrentDictionary<string, IList<string>> _ColumnEncry | |
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] | ||
public SqlRetryLogicBaseProvider RetryLogicProvider | ||
{ | ||
#if NET6_0_OR_GREATER | ||
[RequiresUnreferencedCode("RetryLogicProvider can be read from app.config which is unsafe for trimming")] | ||
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. Typical pattern is to define a const string field with the message and the use that. It makes it easy to improve the message and make it consistent across multiple usages. It also makes it clearer if the |
||
#endif | ||
get | ||
{ | ||
if (_retryLogicProvider == null) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ | |
using System; | ||
using System.Data.Common; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO; | ||
using Microsoft.Data.Common; | ||
using Microsoft.Data.ProviderBase; | ||
|
@@ -296,6 +297,13 @@ override internal void SetInnerConnectionTo(DbConnection owningObject, DbConnect | |
} | ||
} | ||
|
||
#if NET6_0_OR_GREATER | ||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(GroupByBehavior))] | ||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(IdentifierCase))] | ||
[DynamicDependency(DynamicallyAccessedMemberTypes.All, typeof(SupportedJoinOperators))] | ||
Comment on lines
+301
to
+303
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. Is it really required to have |
||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", | ||
Justification = "All non-primitive types which are contained in the XML stream marked as dynamic dependencies.")] | ||
Comment on lines
+301
to
+305
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. In cases like these we would generally recommend adding a unit test which validates the assumption here. So in this case a test which would deserialize the embedded XML and then walk the returned object graph (using reflection) and check that it only encountered expected set of types. You could go even the final step and read the expected set of types from the The main goal of such a test is to make the code maintainable. If in the future somebody changes the XML and adds a new type into it, the assumption here would break, but there would be no way to tell (nothing would produce a warning). So the unit test acts as the guard of that. We've done things like this in other repos as well - and it seems to work pretty well. |
||
#endif | ||
protected override DbMetaDataFactory CreateMetaDataFactory(DbConnectionInternal internalConnection, out bool cacheMetaDataFactory) | ||
{ | ||
Debug.Assert(internalConnection != null, "internalConnection may not be null."); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
using System.Data; | ||
using System.Data.Common; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.Globalization; | ||
using System.IO; | ||
|
||
|
@@ -36,6 +37,9 @@ internal class DbMetaDataFactory | |
private const string SqlCommandKey = "SQLCommand"; | ||
private const string PrepareCollectionKey = "PrepareCollection"; | ||
|
||
#if NET6_0_OR_GREATER | ||
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. "Metadata loaded from XML stream may require types that have been trimmed away" ? |
||
[RequiresUnreferencedCode("Metadata loaded from XML stream may require types which was trimmed out")] | ||
kant2002 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
#endif | ||
public DbMetaDataFactory(Stream xmlStream, string serverVersion, string normalizedServerVersion) | ||
{ | ||
ADP.CheckArgumentNull(xmlStream, nameof(xmlStream)); | ||
|
@@ -495,6 +499,9 @@ private bool IncludeThisColumn(DataColumn sourceColumn, string[] hiddenColumnNam | |
return result; | ||
} | ||
|
||
#if NET6_0_OR_GREATER | ||
[RequiresUnreferencedCode("LoadDataSetFromXml uses System.Data.DataSet.ReadXml(Stream)")] | ||
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. In general would avoid using messages like this - the message should be telling the caller of this method what is the problem. Describing that the problem is that this method is going to call another method is not very helpful to the caller. I know that these are private methods, so it doesn't matter much, but that's only true now. 6 months from now if somebody adds a call to this method they will get a warning with this message. And it would effectively require them to go search through the code and across multiple methods to determine the real problem. It's also a good habit, to avoid doing this on a public API. |
||
#endif | ||
private void LoadDataSetFromXml(Stream XmlStream) | ||
{ | ||
_metaDataCollectionsDataSet = new DataSet | ||
|
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.
Should it be
UnconditionalSuppressMessage
? It's available on .NET 5+ so it needs an#if
guard.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.
Yes - it MUST be
UnconditionalSuppressMessage
.SuppressMessage
is "conditional" and the compiler will not emit it to IL - it disappears after compilation. As a result the trimmer doesn't get to see it.We've been discussing some possibilities to provide an analyzer for these "bugs" but it's difficult (
SuppressMessage
is handled by the compiler intrinsically...). I could not find a tracking issue, so I created dotnet/runtime#93453.