Skip to content

Commit a03863c

Browse files
authored
Merge pull request #3317 from github/koesie10/sarif-comparison
Fix bug in SARIF comparison
2 parents 19d85a7 + c5db597 commit a03863c

File tree

2 files changed

+223
-2
lines changed

2 files changed

+223
-2
lines changed

extensions/ql-vscode/src/compare/sarif-diff.ts

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,40 @@
11
import type { Result } from "sarif";
22

3+
function toCanonicalResult(result: Result): Result {
4+
const canonicalResult = {
5+
...result,
6+
};
7+
8+
if (canonicalResult.locations) {
9+
canonicalResult.locations = canonicalResult.locations.map((location) => {
10+
if (location.physicalLocation?.artifactLocation?.index !== undefined) {
11+
const canonicalLocation = {
12+
...location,
13+
};
14+
15+
canonicalLocation.physicalLocation = {
16+
...canonicalLocation.physicalLocation,
17+
};
18+
19+
canonicalLocation.physicalLocation.artifactLocation = {
20+
...canonicalLocation.physicalLocation.artifactLocation,
21+
};
22+
23+
// The index is dependent on the result of the SARIF file and usually doesn't really tell
24+
// us anything useful, so we remove it from the comparison.
25+
delete canonicalLocation.physicalLocation.artifactLocation.index;
26+
27+
return canonicalLocation;
28+
}
29+
30+
// Don't create a new object if we don't need to
31+
return location;
32+
});
33+
}
34+
35+
return canonicalResult;
36+
}
37+
338
/**
439
* Compare the alerts of two queries. Use deep equality to determine if
540
* results have been added or removed across two invocations of a query.
@@ -25,9 +60,12 @@ export function sarifDiff(fromResults: Result[], toResults: Result[]) {
2560
throw new Error("CodeQL Compare: Target query has no results.");
2661
}
2762

63+
const canonicalFromResults = fromResults.map(toCanonicalResult);
64+
const canonicalToResults = toResults.map(toCanonicalResult);
65+
2866
const results = {
29-
from: arrayDiff(fromResults, toResults),
30-
to: arrayDiff(toResults, fromResults),
67+
from: arrayDiff(canonicalFromResults, canonicalToResults),
68+
to: arrayDiff(canonicalToResults, canonicalFromResults),
3169
};
3270

3371
if (
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
import type { Result } from "sarif";
2+
import { sarifDiff } from "../../../src/compare/sarif-diff";
3+
4+
describe("sarifDiff", () => {
5+
const result1: Result = {
6+
message: {
7+
text: "result1",
8+
},
9+
};
10+
const result2: Result = {
11+
message: {
12+
text: "result2",
13+
},
14+
};
15+
const result3: Result = {
16+
message: {
17+
text: "result3",
18+
},
19+
};
20+
21+
it("throws an error when the source query has no results", () => {
22+
expect(() => sarifDiff([], [result1, result2])).toThrow(
23+
"CodeQL Compare: Source query has no results.",
24+
);
25+
});
26+
27+
it("throws an error when the target query has no results", () => {
28+
expect(() => sarifDiff([result1, result2], [])).toThrow(
29+
"CodeQL Compare: Target query has no results.",
30+
);
31+
});
32+
33+
it("throws an error when there is no overlap between the source and target queries", () => {
34+
expect(() => sarifDiff([result1], [result2])).toThrow(
35+
"CodeQL Compare: No overlap between the selected queries.",
36+
);
37+
});
38+
39+
it("return an empty comparison when the results are the same", () => {
40+
expect(sarifDiff([result1, result2], [result1, result2])).toEqual({
41+
from: [],
42+
to: [],
43+
});
44+
});
45+
46+
it("returns the added and removed results", () => {
47+
expect(sarifDiff([result1, result3], [result1, result2])).toEqual({
48+
from: [result3],
49+
to: [result2],
50+
});
51+
});
52+
53+
it("does not use reference equality to compare results", () => {
54+
const result = {
55+
message: {
56+
text: "result1",
57+
},
58+
};
59+
expect(sarifDiff([result], [result1])).toEqual({
60+
from: [],
61+
to: [],
62+
});
63+
});
64+
65+
it("does not take into account the index of the artifact location", () => {
66+
const result1: Result = {
67+
message: {
68+
text: "result1",
69+
},
70+
locations: [
71+
{
72+
physicalLocation: {
73+
artifactLocation: {
74+
uri: "file:///path/to/file1",
75+
uriBaseId: "%SRCROOT%",
76+
index: 1,
77+
},
78+
},
79+
},
80+
],
81+
};
82+
const result2: Result = {
83+
message: {
84+
text: "result1",
85+
},
86+
locations: [
87+
{
88+
physicalLocation: {
89+
artifactLocation: {
90+
uri: "file:///path/to/file1",
91+
uriBaseId: "%SRCROOT%",
92+
index: 2,
93+
},
94+
},
95+
},
96+
],
97+
};
98+
expect(sarifDiff([result1], [result2])).toEqual({
99+
from: [],
100+
to: [],
101+
});
102+
});
103+
104+
it("takes into account the other properties of the artifact location", () => {
105+
const result1: Result = {
106+
message: {
107+
text: "result1",
108+
},
109+
locations: [
110+
{
111+
physicalLocation: {
112+
artifactLocation: {
113+
uri: "file:///path/to/file1",
114+
uriBaseId: "%SRCROOT%",
115+
},
116+
},
117+
},
118+
],
119+
};
120+
const result2: Result = {
121+
message: {
122+
text: "result1",
123+
},
124+
locations: [
125+
{
126+
physicalLocation: {
127+
artifactLocation: {
128+
uri: "file:///path/to/file2",
129+
uriBaseId: "%SRCROOT%",
130+
},
131+
},
132+
},
133+
],
134+
};
135+
expect(sarifDiff([result1], [result1, result2])).toEqual({
136+
from: [],
137+
to: [result2],
138+
});
139+
});
140+
141+
it("does not modify the input", () => {
142+
const result1: Result = {
143+
message: {
144+
text: "result1",
145+
},
146+
locations: [
147+
{
148+
physicalLocation: {
149+
artifactLocation: {
150+
uri: "file:///path/to/file1",
151+
uriBaseId: "%SRCROOT%",
152+
index: 1,
153+
},
154+
},
155+
},
156+
],
157+
};
158+
const result1Copy = JSON.parse(JSON.stringify(result1));
159+
const result2: Result = {
160+
message: {
161+
text: "result1",
162+
},
163+
locations: [
164+
{
165+
physicalLocation: {
166+
artifactLocation: {
167+
uri: "file:///path/to/file1",
168+
uriBaseId: "%SRCROOT%",
169+
index: 2,
170+
},
171+
},
172+
},
173+
],
174+
};
175+
const result2Copy = JSON.parse(JSON.stringify(result2));
176+
expect(sarifDiff([result1], [result2])).toEqual({
177+
from: [],
178+
to: [],
179+
});
180+
expect(result1).toEqual(result1Copy);
181+
expect(result2).toEqual(result2Copy);
182+
});
183+
});

0 commit comments

Comments
 (0)