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

Adding rule SARIF1012 #1944

Merged
merged 7 commits into from
Jun 30, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Sarif.Multitool/Rules/RuleResources.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 6 additions & 6 deletions src/Sarif.Multitool/Rules/RuleResources.resx
Original file line number Diff line number Diff line change
Expand Up @@ -331,14 +331,14 @@ Many tool use similar names for 'uriBaseId' symbols. We suggest 'REPOROOT' for t
<data name="SARIF2011_ProvideContextRegion_Note_Default_Text" xml:space="preserve">
<value>Placeholder_SARIF2011_ProvideContextRegion_Note_Default_Text</value>
</data>
<data name="SARIF2012_ProvideHelpUris_FullDescription_Text" xml:space="preserve">
<value>Placeholder_SARIF2012_ProvideHelpUris_FullDescription_Text</value>
<data name="SARIF1012_MessageArgumentsMustBeConsistentWithRule_Error_MessageIdMustExist_Text" xml:space="preserve">
<value>{0}: Placeholder '{1}' '{2}'</value>
</data>
<data name="SARIF2012_ProvideHelpUris_Note_Default_Text" xml:space="preserve">
<value>Placeholder_SARIF2012_ProvideHelpUris_Note_Default_Text</value>
<data name="SARIF1012_MessageArgumentsMustBeConsistentWithRule_Error_SupplyEnoughMessageArguments_Text" xml:space="preserve">
<value>{0}: Placeholder '{1}' '{2}' '{3}' '{4}' '{5}'</value>
</data>
<data name="SARIF2013_ProvideEmbeddedFileContent_FullDescription_Text" xml:space="preserve">
<value>Placeholder_SARIF2013_ProvideEmbeddedFileContent_FullDescription_Text</value>
<data name="SARIF1012_MessageArgumentsMustBeConsistentWithRule_FullDescription_Text" xml:space="preserve">
<value>Placeholder</value>
</data>
<data name="SARIF2013_ProvideEmbeddedFileContent_Note_Default_Text" xml:space="preserve">
<value>Placeholder_SARIF2013_ProvideEmbeddedFileContent_Note_Default_Text</value>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace Microsoft.CodeAnalysis.Sarif.Multitool.Rules
{
public class MessageArgumentsMustBeConsistentWithRule : SarifValidationSkimmerBase
{
/// <summary>
/// SARIF1012
/// </summary>
public override string Id => RuleId.MessageArgumentsMustBeConsistentWithRule;

/// <summary>
/// Placeholder
/// </summary>
public override MultiformatMessageString FullDescription => new MultiformatMessageString { Text = RuleResources.SARIF1012_MessageArgumentsMustBeConsistentWithRule_FullDescription_Text };

protected override IEnumerable<string> MessageResourceNames => new string[] {
nameof(RuleResources.SARIF1012_MessageArgumentsMustBeConsistentWithRule_Error_MessageIdMustExist_Text),
nameof(RuleResources.SARIF1012_MessageArgumentsMustBeConsistentWithRule_Error_SupplyEnoughMessageArguments_Text)
};

public override FailureLevel DefaultLevel => FailureLevel.Error;

private IList<ReportingDescriptor> currentRules;

protected override void Analyze(Run run, string runPointer)
{
this.currentRules = run?.Tool?.Driver?.Rules;
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

?.Tool? [](start = 35, length = 7)

Actually:

  • Analyze won't be called if run is null.
  • Tool is a required property of Run, so if it's missing, the schema validation (which takes place before any validation rules are run) will catch it, and the MultiTool will stop.
  • Similarly, Driver is a required property of Tool.
    So really all you need is run.Tool.Driver?.Rules. #Closed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Will push soon!


In reply to: 447331417 [](ancestors = 447331417)

}

protected override void Analyze(Result result, string resultPointer)
{
// if message.id is not null, we have to check if that exists in rules
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

rules [](start = 77, length = 5)

Use full sentences for comments (capitalize first letter, end with period). Also I suggest this phrasing:

If message.id is present, check that a message with that id exists in the rule. #Closed

if (!string.IsNullOrEmpty(result.Message.Id))
{
ReportingDescriptor rule = result.RuleIndex >= 0
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

rule [](start = 36, length = 4)

The SARIF SDK has a great helper for this: result.GetRule(run). So:

  1. In Analyze(Run run...) above, save the run in this.run.
  2. Here, just say
ReportingDescriptor rule = result.GetRule(this.run);
``` #Closed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Ok. With that in mind, we will probably change the following condition to this:

if (this.currentRules == null
                    || rule.MessageStrings == null
                    || !rule.MessageStrings.ContainsKey(result.Message.Id))
                {

Because we dont initialize MessageStrings in the last scenario. What do you think?


In reply to: 447332733 [](ancestors = 447332733)

Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

This looks good. You could simplify to

if (this.currentRules == null || rule.MessageStrings?.ContainsKey(result.Message.Id) == false)

You need the explicit comparison to false because the result of a null coalescing operator is a Nullable<bool>, not a bool, so you can't do an implicit logical test on it.


In reply to: 447336153 [](ancestors = 447336153,447332733)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will push soon


In reply to: 447339939 [](ancestors = 447339939,447336153,447332733)

? (this.currentRules?[result.RuleIndex])
: (this.currentRules?.FirstOrDefault(r => r.Id == (result.RuleId ?? result.Rule?.Id)));

if (this.currentRules == null
|| rule == null
|| !rule.MessageStrings.ContainsKey(result.Message.Id))
{
// {0}: Placeholder {1} {2}
LogResult(
resultPointer,
nameof(RuleResources.SARIF1012_MessageArgumentsMustBeConsistentWithRule_Error_MessageIdMustExist_Text),
result.Message.Id,
(result.RuleIndex >= 0
? result.RuleIndex.ToString()
: (result.RuleId ?? result.Rule?.Id ?? "null")));
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

Here you want to display the rule id, and the SARIF SDK has a helper for that too: result.ResolveRuleId(run). It might return null, so you do have to keep the null check logic. #Closed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

will push soon


In reply to: 447337251 [](ancestors = 447337251)

return;
}

// we have rules, we find it but, the count of parameters are invalid
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

w [](start = 19, length = 1)

"A message with the specified key is present in the rule. Check if the result supplied enough arguments." #Closed

string messageText = rule.MessageStrings[result.Message.Id].Text;
int countOfPlaceholders = CountOfPlaceholders(messageText);
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

CountOfPlaceholders [](start = 42, length = 19)

It's not the number of placeholders that matters. It's the highest numbered placeholder. If the highest numbered placeholder is {4} then there must be at least 5 arguments. #Closed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

soon will push the change


In reply to: 447340538 [](ancestors = 447340538)

if (countOfPlaceholders > (result.Message.Arguments?.Count ?? 0))
{
// {0}: Placeholder {1} {2} {3} {4} {5}
LogResult(
resultPointer,
nameof(RuleResources.SARIF1012_MessageArgumentsMustBeConsistentWithRule_Error_SupplyEnoughMessageArguments_Text),
result.Message.Arguments.Count.ToString(),
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

.ToString() [](start = 54, length = 11)

ToString() not necessary (here and a few lines down). #Closed

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

LogResult only accepts strings.


In reply to: 447716571 [](ancestors = 447716571)

Copy link

Choose a reason for hiding this comment

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

Oh wow. That's not good, ok, thanks.


In reply to: 447719968 [](ancestors = 447719968,447716571)

result.Message.Id,
(result.RuleIndex >= 0
? result.RuleIndex.ToString()
: (result.RuleId ?? result.Rule?.Id ?? "null")),
countOfPlaceholders.ToString(),
messageText);
}
}
}

private int CountOfPlaceholders(string text)
{
MatchCollection matchCollection = Regex.Matches(text, "{\\d+}");
Copy link

@ghost ghost Jun 30, 2020

Choose a reason for hiding this comment

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

"{\d+}" [](start = 66, length = 8)

You can use a regex "capture" to get just the integer part: @"\{(<index>\d+)\}" #Closed

Copy link

Choose a reason for hiding this comment

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

Also, '{' and } are regex metacharacters, so you must escape them (as I did above).


In reply to: 447722378 [](ancestors = 447722378)

Copy link

Choose a reason for hiding this comment

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

Finally, note that "verbatim strings" (@"...") are very useful for regexes because you need lots of backslashes, and with a verbatim string you don't have to double the backslashes.


In reply to: 447723010 [](ancestors = 447723010,447722378)

HashSet<string> matches = new HashSet<string>();
foreach (Match match in matchCollection)
{
matches.Add(match.Value);
}

return matches.Count;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public void SARIF1011_ReferenceFinalSchema_Valid()
public void SARIF1011_ReferenceFinalSchema_Invalid()
=> RunTest(MakeInvalidTestFileName(RuleId.ReferenceFinalSchema, nameof(RuleId.ReferenceFinalSchema)));

[Fact]
public void SARIF1012_MessageArgumentsMustBeConsistentWithRule_Valid()
=> RunTest(MakeValidTestFileName(RuleId.MessageArgumentsMustBeConsistentWithRule, nameof(RuleId.MessageArgumentsMustBeConsistentWithRule)));

[Fact]
public void SARIF1012_MessageArgumentsMustBeConsistentWithRule_Invalid()
=> RunTest(MakeInvalidTestFileName(RuleId.MessageArgumentsMustBeConsistentWithRule, nameof(RuleId.MessageArgumentsMustBeConsistentWithRule)));

[Fact]
public void SARIF2001_AuthorHighQualityMessages_Valid()
=> RunTest(MakeValidTestFileName(RuleId.AuthorHighQualityMessages, nameof(RuleId.AuthorHighQualityMessages)));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
{
"$schema": "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0-rtm.5.json",
"version": "2.1.0",
"runs": [
{
"tool": {
"driver": {
"name": "SARIF Functional Testing",
"rules": [
{
"id": "SARIF1012",
"name": "MessageArgumentsMustBeConsistentWithRule",
"shortDescription": {
"text": "Placeholder."
},
"fullDescription": {
"text": "Placeholder"
},
"messageStrings": {
"Error_MessageIdMustExist": {
"text": "{0}: Placeholder '{1}' '{2}'"
},
"Error_SupplyEnoughMessageArguments": {
"text": "{0}: Placeholder '{1}' '{2}' '{3}' '{4}' '{5}'"
}
},
"helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html"
},
{
"id": "SARIF2001",
"name": "AuthorHighQualityMessages",
"shortDescription": {
"text": "Follow authoring practices that make your rule messages readable, understandable, and actionable."
},
"fullDescription": {
"text": "Follow authoring practices that make your rule messages readable, understandable, and actionable.\r\n\r\nIncluding \"dynamic content\" (information that varies among results from the same rule) makes your messages more specific. It avoids the \"wall of bugs\" phenomenon, where hundreds of occurrences of the same message appear unapproachable.\r\n\r\nPlacing dynamic content in quotes sets it off from the static text, making it easier to spot. It's especially helpful when the dynamic content is a string that might contain spaces, and most especially when the string might be empty (and so would be invisible if it weren't for the quotes). We recommend single quotes for a less cluttered appearance, even though English usage would require double quotes.\r\n\r\nFinally, write in complete sentences and end each sentence with a period. This guidance does not apply to Markdown messages, which might include formatting that makes the punctuation unnecessary."
},
"messageStrings": {
"Warning_EnquoteDynamicContent": {
"text": "{0}: In rule '{1}', the message with id '{2}' includes dynamic content that is not enclosed in single quotes. Enquoting dynamic content makes it easier to spot, and single quotes give a less cluttered appearance."
},
"Warning_IncludeDynamicContent": {
"text": "{0}: In rule '{1}', the message with id '{2}' does not include any dynamic content. Dynamic content makes your messages more specific and avoids the \"wall of bugs\" phenomenon."
},
"Warning_TerminateWithPeriod": {
"text": "{0}: In rule '{1}', the message with id '{2}' does not end in a period. Write rule messages as complete sentences."
}
},
"helpUri": "http://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html"
}
]
}
},
"invocations": [
{
"executionSuccessful": true
}
],
"artifacts": [
{
"location": {
"uri": "FunctionalTestOutput.ValidateCommand/Inputs.SARIF1012.MessageArgumentsMustBeConsistentWithRule_Invalid.sarif",
"uriBaseId": "TEST_DIR"
}
}
],
"results": [
{
"ruleId": "SARIF1012",
"ruleIndex": 0,
"level": "error",
"message": {
"id": "Error_MessageIdMustExist",
"arguments": [
"runs[0].results[0]",
"DoesNotExist",
"0"
]
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"index": 0
},
"region": {
"startLine": 44,
"startColumn": 9
}
}
}
]
},
{
"ruleId": "SARIF1012",
"ruleIndex": 0,
"level": "error",
"message": {
"id": "Error_SupplyEnoughMessageArguments",
"arguments": [
"runs[0].results[1]",
"1",
"DoesExist",
"0",
"3",
"{0}: Placeholder {1} {2}."
]
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"index": 0
},
"region": {
"startLine": 69,
"startColumn": 9
}
}
}
]
},
{
"ruleId": "SARIF2001",
"ruleIndex": 1,
"message": {
"id": "Warning_EnquoteDynamicContent",
"arguments": [
"runs[0].tool.driver.rules[0].messageStrings.DoesExist.text",
"TEST1001",
"DoesExist"
]
},
"locations": [
{
"physicalLocation": {
"artifactLocation": {
"index": 0
},
"region": {
"startLine": 22,
"startColumn": 53
}
}
}
]
}
],
"columnKind": "utf16CodeUnits"
}
]
}
Loading