Skip to content

Commit 4d2f84d

Browse files
author
Dave Bartolomeo
authored
Merge pull request #3669 from github/dbartol/provenance
Display path provenance information in results view
2 parents 68bfa00 + a858b39 commit 4d2f84d

22 files changed

+340
-52
lines changed

extensions/ql-vscode/src/common/interface-types.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,21 @@ interface SetStateMsg {
147147
parsedResultSets: ParsedResultSets;
148148
}
149149

150+
export interface UserSettings {
151+
/** Whether to display links to the dataflow models that generated particular nodes in a flow path. */
152+
shouldShowProvenance: boolean;
153+
}
154+
155+
export const DEFAULT_USER_SETTINGS: UserSettings = {
156+
shouldShowProvenance: false,
157+
};
158+
159+
/** Message indicating that the user's configuration settings have changed. */
160+
interface SetUserSettingsMsg {
161+
t: "setUserSettings";
162+
userSettings: UserSettings;
163+
}
164+
150165
/**
151166
* Message indicating that the results view should display interpreted
152167
* results.
@@ -191,6 +206,7 @@ interface UntoggleShowProblemsMsg {
191206
export type IntoResultsViewMsg =
192207
| ResultsUpdatingMsg
193208
| SetStateMsg
209+
| SetUserSettingsMsg
194210
| ShowInterpretedPageMsg
195211
| NavigateMsg
196212
| UntoggleShowProblemsMsg;
@@ -208,13 +224,15 @@ export type FromResultsViewMsg =
208224
| OpenFileMsg;
209225

210226
/**
211-
* Message from the results view to open a database source
227+
* Message from the results view to open a source
212228
* file at the provided location.
213229
*/
214230
interface ViewSourceFileMsg {
215231
t: "viewSourceFile";
216232
loc: UrlValueResolvable;
217-
databaseUri: string;
233+
/** URI of the database whose source archive contains the file, or `undefined` to open a file from
234+
* the local disk. The latter case is used for opening links to data extension model files. */
235+
databaseUri: string | undefined;
218236
}
219237

220238
/**
@@ -341,7 +359,8 @@ interface ChangeCompareMessage {
341359

342360
export type ToCompareViewMessage =
343361
| SetComparisonQueryInfoMessage
344-
| SetComparisonsMessage;
362+
| SetComparisonsMessage
363+
| SetUserSettingsMsg;
345364

346365
/**
347366
* Message to the compare view that sets the metadata of the compared queries.

extensions/ql-vscode/src/common/sarif-parser.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
1-
import type { Log, Tool } from "sarif";
1+
import type { Log } from "sarif";
22
import { createReadStream } from "fs-extra";
33
import { connectTo } from "stream-json/Assembler";
44
import { getErrorMessage } from "./helpers-pure";
5-
import { withParser } from "stream-json/filters/Pick";
6-
7-
const DUMMY_TOOL: Tool = { driver: { name: "" } };
5+
import { withParser } from "stream-json/filters/Ignore";
86

97
export async function sarifParser(
108
interpretedResultsPath: string,
119
): Promise<Log> {
1210
try {
13-
// Parse the SARIF file into token streams, filtering out only the results array.
11+
// Parse the SARIF file into token streams, filtering out some of the larger subtrees that we
12+
// don't need.
1413
const pipeline = createReadStream(interpretedResultsPath).pipe(
15-
withParser({ filter: "runs.0.results" }),
14+
withParser({
15+
// We don't need to run's `artifacts` property, nor the driver's `notifications` property.
16+
filter: /^runs\.\d+\.(artifacts|tool\.driver\.notifications)/,
17+
}),
1618
);
1719

1820
// Creates JavaScript objects from the token stream
@@ -38,15 +40,17 @@ export async function sarifParser(
3840
});
3941

4042
asm.on("done", (asm) => {
41-
const log: Log = {
42-
version: "2.1.0",
43-
runs: [
44-
{
45-
tool: DUMMY_TOOL,
46-
results: asm.current ?? [],
47-
},
48-
],
49-
};
43+
const log = asm.current;
44+
45+
// Do some trivial validation. This isn't a full validation of the SARIF file, but it's at
46+
// least enough to ensure that we're not trying to parse complete garbage later.
47+
if (log.runs === undefined || log.runs.length < 1) {
48+
reject(
49+
new Error(
50+
"Invalid SARIF file: expecting at least one run with result.",
51+
),
52+
);
53+
}
5054

5155
resolve(log);
5256
alreadyDone = true;

extensions/ql-vscode/src/compare/compare-view.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
getResultSetNames,
3434
} from "./result-set-names";
3535
import { compareInterpretedResults } from "./interpreted-results";
36+
import { isCanary } from "../config";
3637

3738
interface ComparePair {
3839
from: CompletedLocalQueryInfo;
@@ -116,6 +117,13 @@ export class CompareView extends AbstractWebview<
116117
panel.reveal(undefined, true);
117118
await this.waitForPanelLoaded();
118119

120+
await this.postMessage({
121+
t: "setUserSettings",
122+
userSettings: {
123+
shouldShowProvenance: isCanary(),
124+
},
125+
});
126+
119127
await this.postMessage({
120128
t: "setComparisonQueryInfo",
121129
stats: {

extensions/ql-vscode/src/databases/local-databases/locations.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,12 @@ export const shownLocationLineDecoration =
3737
/**
3838
* Resolves the specified CodeQL location to a URI into the source archive.
3939
* @param loc CodeQL location to resolve. Must have a non-empty value for `loc.file`.
40-
* @param databaseItem Database in which to resolve the file location.
40+
* @param databaseItem Database in which to resolve the file location, or `undefined` to resolve
41+
* from the local file system.
4142
*/
4243
function resolveFivePartLocation(
4344
loc: UrlValueLineColumnLocation,
44-
databaseItem: DatabaseItem,
45+
databaseItem: DatabaseItem | undefined,
4546
): Location {
4647
// `Range` is a half-open interval, and is zero-based. CodeQL locations are closed intervals, and
4748
// are one-based. Adjust accordingly.
@@ -52,7 +53,10 @@ function resolveFivePartLocation(
5253
Math.max(1, loc.endColumn),
5354
);
5455

55-
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
56+
return new Location(
57+
databaseItem?.resolveSourceFile(loc.uri) ?? Uri.parse(loc.uri),
58+
range,
59+
);
5660
}
5761

5862
/**
@@ -62,22 +66,26 @@ function resolveFivePartLocation(
6266
*/
6367
function resolveWholeFileLocation(
6468
loc: UrlValueWholeFileLocation,
65-
databaseItem: DatabaseItem,
69+
databaseItem: DatabaseItem | undefined,
6670
): Location {
6771
// A location corresponding to the start of the file.
6872
const range = new Range(0, 0, 0, 0);
69-
return new Location(databaseItem.resolveSourceFile(loc.uri), range);
73+
return new Location(
74+
databaseItem?.resolveSourceFile(loc.uri) ?? Uri.parse(loc.uri),
75+
range,
76+
);
7077
}
7178

7279
/**
7380
* Try to resolve the specified CodeQL location to a URI into the source archive. If no exact location
7481
* can be resolved, returns `undefined`.
7582
* @param loc CodeQL location to resolve
76-
* @param databaseItem Database in which to resolve the file location.
83+
* @param databaseItem Database in which to resolve the file location, or `undefined` to resolve
84+
* from the local file system.
7785
*/
7886
export function tryResolveLocation(
7987
loc: UrlValueResolvable | undefined,
80-
databaseItem: DatabaseItem,
88+
databaseItem: DatabaseItem | undefined,
8189
): Location | undefined {
8290
if (!loc) {
8391
return;
@@ -95,7 +103,7 @@ export function tryResolveLocation(
95103

96104
export async function showResolvableLocation(
97105
loc: UrlValueResolvable,
98-
databaseItem: DatabaseItem,
106+
databaseItem: DatabaseItem | undefined,
99107
logger: Logger,
100108
): Promise<void> {
101109
try {
@@ -151,13 +159,14 @@ export async function showLocation(location?: Location) {
151159
}
152160

153161
export async function jumpToLocation(
154-
databaseUri: string,
162+
databaseUri: string | undefined,
155163
loc: UrlValueResolvable,
156164
databaseManager: DatabaseManager,
157165
logger: Logger,
158166
) {
159-
const databaseItem = databaseManager.findDatabaseItem(Uri.parse(databaseUri));
160-
if (databaseItem !== undefined) {
161-
await showResolvableLocation(loc, databaseItem, logger);
162-
}
167+
const databaseItem =
168+
databaseUri !== undefined
169+
? databaseManager.findDatabaseItem(Uri.parse(databaseUri))
170+
: undefined;
171+
await showResolvableLocation(loc, databaseItem, logger);
163172
}

extensions/ql-vscode/src/local-queries/results-view.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,14 @@ export class ResultsView extends AbstractWebview<
537537
resultSetNames,
538538
};
539539

540+
await this.postMessage({
541+
t: "setUserSettings",
542+
userSettings: {
543+
// Only show provenance info in canary mode for now.
544+
shouldShowProvenance: isCanary(),
545+
},
546+
});
547+
540548
await this.postMessage({
541549
t: "setState",
542550
interpretation: interpretationPage,

extensions/ql-vscode/src/view/compare/Compare.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ import type {
55
ToCompareViewMessage,
66
SetComparisonsMessage,
77
SetComparisonQueryInfoMessage,
8+
UserSettings,
89
} from "../../common/interface-types";
10+
import { DEFAULT_USER_SETTINGS } from "../../common/interface-types";
911
import CompareSelector from "./CompareSelector";
1012
import { vscode } from "../vscode-api";
1113
import CompareTable from "./CompareTable";
@@ -31,6 +33,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
3133
const [comparison, setComparison] = useState<SetComparisonsMessage | null>(
3234
null,
3335
);
36+
const [userSettings, setUserSettings] = useState<UserSettings>(
37+
DEFAULT_USER_SETTINGS,
38+
);
3439

3540
const message = comparison?.message || "Empty comparison";
3641
const hasRows =
@@ -48,6 +53,9 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
4853
case "setComparisons":
4954
setComparison(msg);
5055
break;
56+
case "setUserSettings":
57+
setUserSettings(msg.userSettings);
58+
break;
5159
default:
5260
assertNever(msg);
5361
}
@@ -85,6 +93,7 @@ export function Compare(_: Record<string, never>): React.JSX.Element {
8593
<CompareTable
8694
queryInfo={queryInfo}
8795
comparison={comparison}
96+
userSettings={userSettings}
8897
></CompareTable>
8998
) : (
9099
<Message>{message}</Message>

extensions/ql-vscode/src/view/compare/CompareTable.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
SetComparisonQueryInfoMessage,
33
SetComparisonsMessage,
4+
UserSettings,
45
} from "../../common/interface-types";
56
import { className } from "../results/result-table-utils";
67
import { vscode } from "../vscode-api";
@@ -12,6 +13,7 @@ import { InterpretedCompareResultTable } from "./InterpretedCompareResultTable";
1213
interface Props {
1314
queryInfo: SetComparisonQueryInfoMessage;
1415
comparison: SetComparisonsMessage;
16+
userSettings: UserSettings;
1517
}
1618

1719
const OpenButton = styled(TextButton)`
@@ -29,7 +31,11 @@ const Table = styled.table`
2931
}
3032
`;
3133

32-
export default function CompareTable({ queryInfo, comparison }: Props) {
34+
export default function CompareTable({
35+
queryInfo,
36+
comparison,
37+
userSettings,
38+
}: Props) {
3339
const result = comparison.result!;
3440

3541
async function openQuery(kind: "from" | "to") {
@@ -78,6 +84,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
7884
{result.kind === "interpreted" && (
7985
<InterpretedCompareResultTable
8086
results={result.from}
87+
userSettings={userSettings}
8188
databaseUri={queryInfo.databaseUri}
8289
sourceLocationPrefix={result.sourceLocationPrefix}
8390
/>
@@ -96,6 +103,7 @@ export default function CompareTable({ queryInfo, comparison }: Props) {
96103
{result.kind === "interpreted" && (
97104
<InterpretedCompareResultTable
98105
results={result.to}
106+
userSettings={userSettings}
99107
databaseUri={queryInfo.databaseUri}
100108
sourceLocationPrefix={result.sourceLocationPrefix}
101109
/>

extensions/ql-vscode/src/view/compare/InterpretedCompareResultTable.tsx

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,32 @@
1-
import type { Result } from "sarif";
1+
import type { Result, Run } from "sarif";
22
import { AlertTable } from "../results/AlertTable";
3+
import type { UserSettings } from "../../common/interface-types";
34

45
type Props = {
56
results: Result[];
67
databaseUri: string;
78
sourceLocationPrefix: string;
9+
run?: Run;
10+
userSettings: UserSettings;
811
};
912

1013
export const InterpretedCompareResultTable = ({
1114
results,
1215
databaseUri,
1316
sourceLocationPrefix,
17+
userSettings,
1418
}: Props) => {
1519
return (
1620
<AlertTable
1721
results={results}
22+
userSettings={userSettings}
1823
databaseUri={databaseUri}
1924
sourceLocationPrefix={sourceLocationPrefix}
2025
header={
2126
<thead>
2227
<tr>
2328
<th colSpan={2}></th>
24-
<th className={`vscode-codeql__alert-message-cell`} colSpan={3}>
29+
<th className={`vscode-codeql__alert-message-cell`} colSpan={4}>
2530
Message
2631
</th>
2732
</tr>

extensions/ql-vscode/src/view/results/AlertTable.tsx

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Location, Result } from "sarif";
1+
import type { Location, Result, Run } from "sarif";
22
import type {
33
PathNode,
44
Result as ResultKeysResult,
@@ -7,7 +7,7 @@ import type {
77
import { getPath, getPathNode, getResult, keyToString } from "./result-keys";
88
import { className, jumpToLocation } from "./result-table-utils";
99
import { onNavigation } from "./navigation";
10-
import type { NavigateMsg } from "../../common/interface-types";
10+
import type { NavigateMsg, UserSettings } from "../../common/interface-types";
1111
import { NavigationDirection } from "../../common/interface-types";
1212
import { isNoLocation, parseSarifLocation } from "../../common/sarif-utils";
1313
import { sendTelemetry } from "../common/telemetry";
@@ -21,6 +21,8 @@ type Props = {
2121
results: Result[];
2222
databaseUri: string;
2323
sourceLocationPrefix: string;
24+
run?: Run;
25+
userSettings: UserSettings;
2426
numTruncatedResults?: number;
2527

2628
header: ReactNode;
@@ -31,6 +33,8 @@ export function AlertTable({
3133
results,
3234
databaseUri,
3335
sourceLocationPrefix,
36+
run,
37+
userSettings,
3438
numTruncatedResults,
3539
header,
3640
noResults,
@@ -202,6 +206,8 @@ export function AlertTable({
202206
selectedItem={selectedItem}
203207
selectedItemRef={selectedItemRef}
204208
databaseUri={databaseUri}
209+
run={run}
210+
userSettings={userSettings}
205211
sourceLocationPrefix={sourceLocationPrefix}
206212
updateSelectionCallback={updateSelectionCallback}
207213
toggleExpanded={toggle}

0 commit comments

Comments
 (0)