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

Fix NullReferenceException in WorkItemFiler when all results baseline state are unchange/absent/updated #2412

Merged
merged 21 commits into from
Feb 25, 2022

Conversation

yongyan-gh
Copy link
Collaborator

@yongyan-gh yongyan-gh commented Nov 30, 2021

Description:

BUG: Got NullReferenceException when WorkItemFiler tries to file a work item for the result that has baseline state should be ignored.

Fixes:

  • Handle null results case in SarifLog CreateWorkItemDescription extension method, which happens if all results are not eligible for filing work item. result.ShouldBeFiled() == false
  • In WorkItemFiler.FileWorkItems(), do not create work item if all the results of target Sarif Log are not eligible for filing a work item.

Currently the result is treated as it should not be file as work item if

  • Its baseline State is one of Unchanged, Absent, Updated
  • It's suppressed

@lgtm-com
Copy link

lgtm-com bot commented Nov 30, 2021

This pull request introduces 1 alert when merging d1928a1 into 678e283 - view on LGTM.com

new alerts:

  • 1 for Dereferenced variable may be null

In reply to: 983119894

@yongyan-gh yongyan-gh changed the title WorkItemFiler fix Fix NullReferenceException in WorkItemFiler when all results baseline state are unchange/absent/updated Nov 30, 2021
@yongyan-gh yongyan-gh force-pushed the users/yongyan-gh/fileWorkItemFix branch from fdf350f to e2ce908 Compare December 1, 2021 00:24
@yongyan-gh yongyan-gh marked this pull request as ready for review December 2, 2021 00:06
@yongyan-gh
Copy link
Collaborator Author

the issue has been resolved in commits after this.


In reply to: 983119894

public void SarifWorkItemExtensions_CreateWorkItemDescription_ResultsShouldNotBeFiled()
{
string toolName = "TestToolName";
string firstLocation = @"C:\Test\Data\File{0}sarif";
Copy link
Collaborator

@shaopeng-gh shaopeng-gh Dec 7, 2021

Choose a reason for hiding this comment

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

@"C:\Test\Data\File{0}sarif"

suggest change to not using windows specific format, like:
Path.Combine(Directory.GetCurrentDirectory(), "Test", "Data", "File{0}sarif");
#Closed

@@ -152,7 +152,7 @@ public static string CreateWorkItemDescription(this SarifLog log, SarifWorkItemC
Uri runRepositoryUri = log?.Runs.FirstOrDefault()?.VersionControlProvenance?.FirstOrDefault().RepositoryUri;
Uri detectionLocationUri = !string.IsNullOrEmpty(runRepositoryUri?.OriginalString) ?
runRepositoryUri :
results?.FirstOrDefault().Locations?[0].PhysicalLocation?.ArtifactLocation?.Uri;
results?.FirstOrDefault()?.Locations?[0].PhysicalLocation?.ArtifactLocation?.Uri;
Copy link
Collaborator

@eddynaka eddynaka Dec 8, 2021

Choose a reason for hiding this comment

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

results?.FirstOrDefault()?.Locations?[0].PhysicalLocation?.ArtifactLocation?.Uri

can u unwrap and wrap making ? and : be in separate lines? #Closed

@eddynaka
Copy link
Collaborator

eddynaka commented Dec 8, 2021

                       .Count();

you can replace where for count and do thee unwrap/wrap as well


In reply to: 989110458


In reply to: 989110458


In reply to: 989110458


In reply to: 989110458


Refers to: src/Sarif.WorkItems/SarifWorkItemsExtensions.cs:166 in 9665225. [](commit_id = 9665225, deletion_comment = False)

@@ -290,6 +290,12 @@ public SarifWorkItemModel FileWorkItemInternal(SarifLog sarifLog, SarifWorkItemC
{
string logId = sarifLog.GetProperty<Guid>(LOGID_PROPERTY_NAME).ToString();

if (sarifLog.Runs?.Any(run => run.Results?.Any(result => result.ShouldBeFiled()) == true) != true)
Copy link
Collaborator

@shaopeng-gh shaopeng-gh Jan 3, 2022

Choose a reason for hiding this comment

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

if (sarifLog.Runs?.Any(run => run.Results?.Any(result => result.ShouldBeFiled()) == true) != true)

was thinking about the case of one run have work item and the other have empty array if this condiftion works.

do we think better to expand the nested condition, easiler to read and maintenance :) #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.

removed in latest change

@@ -290,6 +290,12 @@ public SarifWorkItemModel FileWorkItemInternal(SarifLog sarifLog, SarifWorkItemC
{
string logId = sarifLog.GetProperty<Guid>(LOGID_PROPERTY_NAME).ToString();

if (sarifLog.Runs?.Any(run => run.Results?.Any(result => result.ShouldBeFiled()) == true) != true)
Copy link
Collaborator

@shaopeng-gh shaopeng-gh Jan 4, 2022

Choose a reason for hiding this comment

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

do we think also add a safety check in this method as well

public static bool ShouldBeFiled(this Result result)
{
if(result ==null) return false;
......
} #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.

Added the check to ShouldBeFiled() and added test cases.

Copy link
Collaborator

@shaopeng-gh shaopeng-gh left a comment

Choose a reason for hiding this comment

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

:shipit:

@yongyan-gh yongyan-gh changed the title Fix NullReferenceException in WorkItemFiler when all results baseline state are unchange/absent/updated Fix NullReferenceException in WorkItemFiler when all results baseline state are unchange/absent/updated Jan 4, 2022
@michaelcfanning
Copy link
Member

@marmegh @EasyRhinoMSFT

context.SetProperty(ExpectedWorkItemsCount, numberOfResults);
context.SetProperty(ExpectedFilingResult, FilingResult.None);

TestWorkItemFiler(sarifLog, context, true);
Copy link
Member

@michaelcfanning michaelcfanning Jan 4, 2022

Choose a reason for hiding this comment

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

true

As a rule don't embed literals in method calls. What does true here mean? We don't know.

Instead, qualify the literal with the parameter name. If the parameter name is good, the code will be cleaner:

TestWorkItemFiler(sarifLog, context, adoClient: true) #Resolved

context.SetProperty(ExpectedWorkItemsCount, numberOfResults);
context.SetProperty(ExpectedFilingResult, FilingResult.None);

TestWorkItemFiler(sarifLog, context, true);
Copy link
Member

@michaelcfanning michaelcfanning Jan 4, 2022

Choose a reason for hiding this comment

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

true

Is it important for these tests to verify the adoClient == false code path?

I think probably it is important, because we want to ensure the GitHub filer also doesn't file work items?

Or is the relevant code sufficiently covered by the adoClient case? #Resolved

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Parameter adoClient controls which FilingClient mock object it generates, adoClient or githubClient. Added/updated test cases also covers githubClient.

@@ -17,6 +18,8 @@
{
public class SarifWorkItemExtensionsTests
{
private readonly string testSarifFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Test", "Data", "File{0}.sarif");
Copy link
Member

@michaelcfanning michaelcfanning Jan 4, 2022

Choose a reason for hiding this comment

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

readonly

Please add a comment here explaining that we create the test file path in this way so that we don't use a single, platform-specific file path rendering (i.e., we'll get Windows paths on Windows and Linux running xplat). #Closed

@@ -290,6 +290,12 @@ public SarifWorkItemModel FileWorkItemInternal(SarifLog sarifLog, SarifWorkItemC
{
string logId = sarifLog.GetProperty<Guid>(LOGID_PROPERTY_NAME).ToString();

if (sarifLog.Runs?.Any(run => run.Results?.Any(result => result.ShouldBeFiled()) == true) != true)
Copy link
Member

@michaelcfanning michaelcfanning Jan 4, 2022

Choose a reason for hiding this comment

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

sarifLog

You should add logging to this code path, don't you think? @rtaket, does someone from your team need to review this change as well?

@michaelcfanning
Copy link
Member

                        if (updatedSarifWorkItemModel == null)

Notice this 'return null' code path has good logging.


Refers to: src/Sarif.WorkItems/SarifWorkItemFiler.cs:329 in 5ebe0c8. [](commit_id = 5ebe0c8, deletion_comment = False)

@@ -290,6 +290,12 @@ public SarifWorkItemModel FileWorkItemInternal(SarifLog sarifLog, SarifWorkItemC
{
string logId = sarifLog.GetProperty<Guid>(LOGID_PROPERTY_NAME).ToString();

if (sarifLog.Runs?.Any(run => run.Results?.Any(result => result.ShouldBeFiled()) == true) != true)
Copy link
Member

Choose a reason for hiding this comment

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

sarifLog

I don't understand why this code path is what we want. You are going to traverse the entire log file, which could be very large. Is that what we want? What is this going to save us in work that we won't perform (when there are no issues to file)?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

If the SARIF log doesn't have any eligible result to file as a work item and it doesn't return null, the following code will create a SarifWorkItemModel:

var sarifWorkItemModel = new SarifWorkItemModel(sarifLog, filingContext);

In SarifWorkItemModel code, it iterates all results and get first result which should be filed, to construct work item title. If none of result is eligible, the SairfWorkItemModel's title will be set to null, which causes NullReferenceException when calling filingClient.FileWorkItems() to file work item.

Another solution I can think is before calling FileWorkItemInternal(), in SplitLogFile method, make sure there is only eligible results in split logs. But seems traverse of the results is still needed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Moved the check to SplitLogFile method, this way the sarifLog only includes eligible results to file work item, no need to traverse results. Pls let me know what do you think?

@@ -12,6 +12,11 @@ public static class SarifWorkItemsExtensions
{
public static bool ShouldBeFiled(this Result result)
{
if (result == null)
Copy link
Member

Choose a reason for hiding this comment

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

result

Why did we add this? Why wouldn't a null dereference be the expected condition?

i.e., why do we require

myObject?.ThisIsAnActualMethod()

to protect against a null variable but

myObject.ThisIsAnExtensionMethod()

should work when myObject is null?

I am asking a sincere question. But I'm not sure this change is a good design pattern, Shaopeng, what's your argument for it? :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I think it depends on which one should be responsible for the null check, the caller or the extension method.

If the caller should handle null value then we don't need this check inside extension method.

myObject?.ThisIsAnExtensionMethod()

Copy link
Collaborator

Choose a reason for hiding this comment

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

I am not sure, my thought was similar to the IDictionary cast that we were looking at last PR, either a helper already did that for you or need to cast to IDictionary everywhere it is used,
in this case if the extension method did the check for you,
or where you call the extension method.
only differ when user forgot to check null before call this extension seeing an unhandled exception because we call result.BaselineState at the first line, the other case is no exception returns false.

A quick find reference I see about 10 usages of this method.

In .net source code they also do null check at the first line of the
extension methods, like below, our logic doe not need to throw:

public static partial class StringNormalizationExtensions
{
public static bool IsNormalized(this string strInput, NormalizationForm normalizationForm)
{
if (strInput == null)
{
throw new ArgumentNullException(nameof(strInput));
}

        return strInput.IsNormalized(normalizationForm);
    }

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Like the way how .net handles in the extension method. The extension method will have similar behavior to instance method and can be protected by ? operation:

myObject?.ThisIsAnExtensionMethod()

Copy link
Collaborator

@eddynaka eddynaka left a comment

Choose a reason for hiding this comment

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

:shipit:

PartitionFunction<string> partitionFunction = null;

Stopwatch splittingStopwatch = Stopwatch.StartNew();

switch (splittingStrategy)
{
case SplittingStrategy.None:
{
// get ride of results should not be filed
Copy link
Member

Choose a reason for hiding this comment

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

Remove this comment. First, it does not follow convention, starting with a capital letter, ending with a period. It doesn't read very well grammatically. Not sure it provides useful additional information. Finally, this line of code is identical to other lines in this file, why are they commented? If they don't need to be, why is this line commented?

{
throw new ArgumentNullException(nameof(result));
}

Copy link
Member

Choose a reason for hiding this comment

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

I suggest removing this, because it is additional work and suggests a general pattern we should adhere that doesn't seem to have good ROI.

? 0
: results.Where(r => r.Locations != null)
.SelectMany(r => r.Locations)
.Count(l => l.PhysicalLocation?.ArtifactLocation?.Uri != null);
Copy link
Member

Choose a reason for hiding this comment

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

my personal opinion is this code isn't super readable and i avoid linq statements that potentially entail multiple iterations over a collection (in a pathological case).

@@ -17,6 +18,9 @@
{
public class SarifWorkItemExtensionsTests
{
// Use portable file path so the tests can run on windows and other platforms.
Copy link
Member

@michaelcfanning michaelcfanning Feb 9, 2022

Choose a reason for hiding this comment

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

// Use portable file path so the tests can run on windows and other platforms.

What's the utility of this comment? #Closed

@@ -17,6 +18,9 @@
{
public class SarifWorkItemExtensionsTests
{
// Use portable file path so the tests can run on windows and other platforms.
private readonly string testSarifFilePath = Path.Combine(Directory.GetCurrentDirectory(), "Test", "Data", "File{0}.sarif");
Copy link
Member

@michaelcfanning michaelcfanning Feb 9, 2022

Choose a reason for hiding this comment

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

testSarifFilePath

please make this static and give it an s_ prefix #Closed

@yongyan-gh
Copy link
Collaborator Author

Test coverage data:

image

Copy link
Member

@michaelcfanning michaelcfanning left a comment

Choose a reason for hiding this comment

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

:shipit:

@michaelcfanning michaelcfanning enabled auto-merge (squash) February 25, 2022 21:47
@michaelcfanning michaelcfanning merged commit 1b7d87c into main Feb 25, 2022
@michaelcfanning michaelcfanning deleted the users/yongyan-gh/fileWorkItemFix branch February 25, 2022 22:01
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.

None yet

4 participants