Skip to content
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

Add UnconditionalSuppressMessageAttribute #1185

Merged
merged 37 commits into from
May 29, 2020

Conversation

mateoatr
Copy link
Contributor

@mateoatr mateoatr commented May 13, 2020

Related to #1147. This PR contains the first effort to support the UnconditionalSuppressMessageAttribute. It adds the ability to suppress a specific warning in members and types. Tests were added to show its usage.

@mateoatr mateoatr added this to the .NET5.0 milestone May 13, 2020
@mateoatr mateoatr requested a review from vitek-karas May 13, 2020 07:10
@mateoatr mateoatr self-assigned this May 13, 2020
src/linker/Linker/MessageContainer.cs Outdated Show resolved Hide resolved
@@ -39,6 +51,15 @@ public MessageOrigin (string fileName, int sourceLine = 0, int sourceColumn = 0)
return null;
}

internal static MessageOrigin? TryGetOrigin (IMemberDefinition sourceMethod, int ilOffset,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the right place. We want the developer to be able to suppress any warning message (e.g. from 3rd party custom step). I think the whole logic should be close to MessageContainer.CreateWarning which probably will need to be expanded with additional "context" state parameter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Absolutely agree.
The suppression should work without any special code from the place where we create the warning - with the sole exception that we require the "context" parameter. I would even go as far as make some strict rule that the context must be specified unless the code is somehow special (like internal errors, command line parsing errors and so on) - otherwise we should always be able to tell what the warning applies to (assembly or more specific).

I think from the API perspective this change should add the "context" attribute to the CreateWarning method (and maybe also CreateError for consistency). Then we should have another API which is something like IsSuppressed - I would probably put it onto the MessageContainer if it's possible, or if that doesn't work then the LinkContext. It should take the necessary parameters to decide if a warning will be suppressed or not. Notable it should not require the full warning message - the indented usage would be to avoid computing potentially expensive warning message if we know it's going to be suppressed.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've moved this logic out of MessageOrigin by adding the mentioned IsSuppressed API into the LinkContext, which is called from within CreateWarningMessage. It's now mandatory to specify a MessageOrigin for all created warnings, which must be either a filename or an objetct of a class implementing IMetadataTokenProvider (modules, types and members). The way to create warnings feels a little bit cumbersome though (Context.LogMessage (MessageContainer.CreateWarningMessage (Context, Message, Code, MessageOrigin, [optional] Subcategory))), I think this could be enhanced by adding new methods (LogMessage for info/diags, LogError for errors, LogWarning for warnings) and stop passing around the LinkContext.

@@ -1426,6 +1428,8 @@ bool MarkSpecialCustomAttributeDependencies (CustomAttribute ca, ICustomAttribut
l.Parameters.Count == 1 && l.Parameters[0].ParameterType.IsTypeOf ("System", "Type"),
provider);
return true;
} else if (dt.Name == "UnconditionalSuppressMessageAttribute" && dt.Namespace == "System.Diagnostics.CodeAnalysis") {
_context.Suppressions.AddLocalSuppression (ca, provider.MetadataToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it really have to be MetadataToken when the attribute cannot be attached to tokens?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All local suppressions can be attached to a a specific md token. For suppressions which cannot be attached to tokens we'll probably be storing a string instead, but I think this case only applies for suppressions that use the target/scope argument. Support for this will be incorporated in a future PR, since the signature parser will be needed for some scenarios (global suppressions made by specifying target/scope).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok but to do the decoding here is too late as the suppression on metadata tokens would work for steps starting from MarkStep only

@mateoatr mateoatr requested a review from marek-safar May 20, 2020 17:42
src/linker/Linker/MessageContainer.cs Show resolved Hide resolved
src/linker/Linker/MessageContainer.cs Show resolved Hide resolved
@@ -1426,6 +1428,8 @@ bool MarkSpecialCustomAttributeDependencies (CustomAttribute ca, ICustomAttribut
l.Parameters.Count == 1 && l.Parameters[0].ParameterType.IsTypeOf ("System", "Type"),
provider);
return true;
} else if (dt.Name == "UnconditionalSuppressMessageAttribute" && dt.Namespace == "System.Diagnostics.CodeAnalysis") {
_context.Suppressions.AddLocalSuppression (ca, provider.MetadataToken);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok but to do the decoding here is too late as the suppression on metadata tokens would work for steps starting from MarkStep only

src/linker/ref/Linker/LinkContext.cs Outdated Show resolved Hide resolved
public class UnconditionalSuppressMessageAttributeState
{
private readonly AssemblyDefinition _assembly;
private GlobalSuppressions _lazyGlobalSuppressions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why have a class with single field?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're just making a clear distinction between the global suppressions and the local ones with this class, just as it's done in Roslyn. We could of course have it all in one class but I don't see a reason to not have this.

Copy link
Contributor

@marek-safar marek-safar May 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be more readable to have private readonly Dictionary<MetadataToken, Dictionary<string, SuppressMessageInfo>> _globalSuppressions; here and hidden inside nested class

@mateoatr
Copy link
Contributor Author

I've removed all things related to global suppressions from this PR. I'll work on them and put them on a following PR to keep this one short and don't delay much in merging the basic support for the UnconditionalSuppressMessage attribute. The code introduced here gives basic capabilities for suppressing warnings in local scopes (members and types).

Mateo Torres Ruiz added 2 commits May 21, 2020 16:46
@@ -1012,6 +1015,8 @@ protected void MarkAssembly (AssemblyDefinition assembly)

foreach (ModuleDefinition module in assembly.Modules)
LazyMarkCustomAttributes (module, module);

_context.Suppressions = new UnconditionalSuppressMessageAttributeState (assembly);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably not right. The _context is global - for all assemblies, and here we're assigning a state which is for one assembly only. Also - what if there already is another state in that variable? MarkStep doesn't work one assembly at a time - it jumps randomly between assemblies.

We either need to store the suppressions per-assembly or have some kind of global storage keyed of the metadata token providers.

mateoatr and others added 4 commits May 22, 2020 09:47
This changes the behavior of AddLocalSuppression to add or update a (provider, id) tuple with the last seen suppression instead of keeping the first one seen.
!Regex.IsMatch (warningId, "^IL\\d{4}")) {
warningId.Length < 6 ||
!warningId.StartsWith ("IL") ||
!int.TryParse (warningId.Substring (2, 4), out info.Id)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means that "IL1234HiThere" will be read as 1234. Probably not what we want. Just check that Length != 6.

Copy link
Contributor Author

@mateoatr mateoatr May 22, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SuppressMessageAttribute allows the user to write anything after a colon, mainly for documentation purposes (e.g., "IL1234:Suppress this"). I think we should keep this capability.

Mateo Torres Ruiz added 3 commits May 28, 2020 16:12
Check for null before calling `ToLower` on `Scope` parameter

Use IMemberDefinition instead of IMdToken for specifying the MessageOrigin
@marek-safar marek-safar merged commit d51be36 into dotnet:master May 29, 2020
tkapin pushed a commit to tkapin/runtime that referenced this pull request Jan 31, 2023
Co-authored-by: Marek Safar <[email protected]>
Co-authored-by: Vitek Karas <[email protected]>

Commit migrated from dotnet/linker@d51be36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants