-
Notifications
You must be signed in to change notification settings - Fork 91
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
Changes from 5 commits
03fb5eb
e2ce908
d39dc31
bb7d051
80d790a
9665225
8beb6dc
8349513
44b4fe2
5ebe0c8
9456fb9
9618143
f14d972
d7e093d
516bdf7
c58b2f9
8eb2f43
479801d
716ee18
91184b8
489e1ad
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 |
---|---|---|
|
@@ -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) | ||
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. do we think also add a safety check in this method as well public static bool ShouldBeFiled(this Result result) 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. Added the check to ShouldBeFiled() and added test cases. 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. 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? 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. 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. 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. 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. 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? |
||
{ | ||
// If the sarifLog does not contain result which should be filed as a work item, return null. | ||
return null; | ||
} | ||
|
||
// The helper below will initialize the sarif work item model with a copy | ||
// of the root pipeline filing context. This context will then be initialized | ||
// based on the current sarif log file that we're processing. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
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. |
||
|
||
string detectionLocation = (detectionLocationUri?.IsAbsoluteUri == true && detectionLocationUri?.Scheme == "https") | ||
? context.CreateLinkText(detectionLocationUri.OriginalString, detectionLocationUri?.OriginalString) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -271,6 +271,49 @@ public void SarifWorkItemExtensions_CreateWorkItemDescription_MultipleResults() | |
description.Should().BeEquivalentTo($"This work item contains {numOfResult} '{toolName}' issue(s) detected in {string.Format(firstLocation, 1)} (+{additionLocationCount} locations). Click the 'Scans' tab to review results."); | ||
} | ||
|
||
[Fact] | ||
public void SarifWorkItemExtensions_CreateWorkItemDescription_ResultsShouldNotBeFiled() | ||
{ | ||
string toolName = "TestToolName"; | ||
string firstLocation = @"C:\Test\Data\File{0}sarif"; | ||
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. |
||
int numOfResult = 8; | ||
int expectedNumOfResult = 0; | ||
|
||
int index = 1; | ||
SarifLog sarifLog = TestData.CreateSimpleLogWithRules(0, numOfResult); | ||
sarifLog.Runs[0].Results.ForEach(r => r.Locations | ||
= new[] | ||
{ | ||
new Location | ||
{ | ||
PhysicalLocation = new PhysicalLocation | ||
{ | ||
ArtifactLocation = new ArtifactLocation | ||
{ | ||
Uri = new Uri(string.Format(firstLocation, index++)) | ||
} | ||
} | ||
} | ||
}); | ||
|
||
sarifLog.Runs[0].Results.ForEach(r => r.BaselineState = BaselineState.Unchanged); | ||
string description = sarifLog.CreateWorkItemDescription(new SarifWorkItemContext() { CurrentProvider = Microsoft.WorkItems.FilingClient.SourceControlProvider.AzureDevOps }); | ||
|
||
description.Should().BeEquivalentTo($"This work item contains {expectedNumOfResult} '{toolName}' issue(s) detected in . Click the 'Scans' tab to review results."); | ||
} | ||
|
||
[Fact] | ||
public void SarifWorkItemExtensions_CreateWorkItemDescription_WithoutResults() | ||
{ | ||
int numOfResult = 0; | ||
|
||
SarifLog sarifLog = TestData.CreateSimpleLogWithRules(0, numOfResult); | ||
|
||
string description = sarifLog.CreateWorkItemDescription(new SarifWorkItemContext() { CurrentProvider = Microsoft.WorkItems.FilingClient.SourceControlProvider.AzureDevOps }); | ||
|
||
description.Should().BeEquivalentTo($"This work item contains {numOfResult} '' issue(s) detected in . Click the 'Scans' tab to review results."); | ||
} | ||
|
||
[Fact] | ||
public void SarifWorkItemExtensions_CreateWorkItemDescription_MultipleResultsMultipleLocations() | ||
{ | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,6 +13,7 @@ | |
using Microsoft.CodeAnalysis.Test.Utilities.Sarif; | ||
using Microsoft.CodeAnalysis.WorkItems; | ||
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models; | ||
using Microsoft.VisualStudio.Services.Common; | ||
using Microsoft.VisualStudio.Services.WebApi; | ||
using Microsoft.VisualStudio.Services.WebApi.Patch.Json; | ||
using Microsoft.WorkItems; | ||
|
@@ -105,6 +106,41 @@ public void WorkItemFiler_ValidatesSarifLogFileLocationArgument() | |
action.Should().Throw<ArgumentNullException>(); | ||
} | ||
|
||
[Fact] | ||
public void WorkItemFiler_SarifLogResultsShouldNotBeFiled() | ||
{ | ||
SarifLog sarifLog = TestData.CreateSimpleLog(); | ||
|
||
// Set all results baseline state to 'Unchanged' so no work item should be filed. | ||
sarifLog.Runs.SelectMany(run => run.Results).ForEach(result => result.BaselineState = BaselineState.Unchanged); | ||
|
||
SarifWorkItemContext context = CreateAzureDevOpsTestContext(); | ||
|
||
context.SplittingStrategy = SplittingStrategy.None; | ||
|
||
int numberOfResults = 0; | ||
context.SetProperty(ExpectedWorkItemsCount, numberOfResults); | ||
context.SetProperty(ExpectedFilingResult, FilingResult.None); | ||
|
||
TestWorkItemFiler(sarifLog, context, true); | ||
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. 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. Parameter adoClient controls which FilingClient mock object it generates, adoClient or githubClient. Added/updated test cases also covers githubClient. |
||
} | ||
|
||
[Fact] | ||
public void WorkItemFiler_SarifLogResultsAreEmpty() | ||
{ | ||
SarifLog sarifLog = TestData.CreateEmptyRun(); | ||
|
||
SarifWorkItemContext context = CreateAzureDevOpsTestContext(); | ||
|
||
context.SplittingStrategy = SplittingStrategy.None; | ||
|
||
int numberOfResults = 0; | ||
context.SetProperty(ExpectedWorkItemsCount, numberOfResults); | ||
context.SetProperty(ExpectedFilingResult, FilingResult.None); | ||
|
||
TestWorkItemFiler(sarifLog, context, true); | ||
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. |
||
} | ||
|
||
private void TestWorkItemFiler(SarifLog sarifLog, SarifWorkItemContext context, bool adoClient) | ||
{ | ||
// ONE. Create test data that the low-level ADO client mocks | ||
|
@@ -162,14 +198,17 @@ private void TestWorkItemFiler(SarifLog sarifLog, SarifWorkItemContext context, | |
CreateWorkItemCalled.Should().Be(expectedWorkItemsCount); | ||
CreateAttachmentCount.Should().Be(adoClient ? expectedWorkItemsCount : 0); | ||
|
||
// default value is FilingResult.Succeeded | ||
FilingResult expectedFilingResult = context.GetProperty(ExpectedFilingResult); | ||
|
||
// This property is a naive mechanism to ensure that the code | ||
// executed comprehensively (i.e., that execution was not limited | ||
// due to unhandled exceptions). This is required because we have | ||
// not really implemented a proper async API with appropriate handling | ||
// for exceptions and other negative conditions. I wouldn't expect this | ||
// little helper to survive but it closes the loop for the current | ||
// rudimentary in-flight implementation. | ||
filer.FilingResult.Should().Be(FilingResult.Succeeded); | ||
filer.FilingResult.Should().Be(expectedFilingResult); | ||
|
||
filer.FiledWorkItems.Count.Should().Be(expectedWorkItemsCount); | ||
|
||
|
@@ -188,20 +227,23 @@ private void TestWorkItemFiler(SarifLog sarifLog, SarifWorkItemContext context, | |
// | ||
updatedSarifLog.Should().NotBeEquivalentTo(sarifLog); | ||
|
||
foreach (Run run in updatedSarifLog.Runs) | ||
if (expectedWorkItemsCount > 0) | ||
{ | ||
foreach (Result result in run.Results) | ||
foreach (Run run in updatedSarifLog.Runs) | ||
{ | ||
result.WorkItemUris.Should().NotBeNull(); | ||
result.WorkItemUris.Count.Should().Be(1); | ||
result.WorkItemUris[0].Should().Be(bugHtmlUri); | ||
foreach (Result result in run.Results) | ||
{ | ||
result.WorkItemUris.Should().NotBeNull(); | ||
result.WorkItemUris.Count.Should().Be(1); | ||
result.WorkItemUris[0].Should().Be(bugHtmlUri); | ||
|
||
result.TryGetProperty(SarifWorkItemFiler.PROGRAMMABLE_URIS_PROPERTY_NAME, out List<Uri> programmableUris) | ||
.Should().BeTrue(); | ||
result.TryGetProperty(SarifWorkItemFiler.PROGRAMMABLE_URIS_PROPERTY_NAME, out List<Uri> programmableUris) | ||
.Should().BeTrue(); | ||
|
||
programmableUris.Should().NotBeNull(); | ||
programmableUris.Count.Should().Be(1); | ||
programmableUris[0].Should().Be(bugUri); | ||
programmableUris.Should().NotBeNull(); | ||
programmableUris.Count.Should().Be(1); | ||
programmableUris[0].Should().Be(bugUri); | ||
} | ||
} | ||
} | ||
} | ||
|
@@ -374,5 +416,10 @@ private static SarifWorkItemContext CreateAzureDevOpsTestContext() | |
new PerLanguageOption<int>( | ||
"Extensibility", nameof(ExpectedWorkItemsCount), | ||
defaultValue: () => { return 1; }); | ||
|
||
internal static PerLanguageOption<FilingResult> ExpectedFilingResult { get; } = | ||
new PerLanguageOption<FilingResult>( | ||
"Extensibility", nameof(ExpectedFilingResult), | ||
defaultValue: () => { return FilingResult.Succeeded; }); | ||
} | ||
} |
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.
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
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.
removed in latest change