Add new data flow paths view (empty) (#2172)
This commit is contained in:
@@ -56,6 +56,7 @@
|
||||
"onCommand:codeQL.restartQueryServer",
|
||||
"onWebviewPanel:resultsView",
|
||||
"onWebviewPanel:codeQL.variantAnalysis",
|
||||
"onWebviewPanel:codeQL.dataFlowPaths",
|
||||
"onFileSystem:codeql-zip-archive"
|
||||
],
|
||||
"main": "./out/extension",
|
||||
|
||||
@@ -109,7 +109,11 @@ export function tryResolveLocation(
|
||||
}
|
||||
}
|
||||
|
||||
export type WebviewView = "results" | "compare" | "variant-analysis";
|
||||
export type WebviewView =
|
||||
| "results"
|
||||
| "compare"
|
||||
| "variant-analysis"
|
||||
| "data-flow-paths";
|
||||
|
||||
export interface WebviewMessage {
|
||||
t: string;
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
} from "../variant-analysis/shared/variant-analysis";
|
||||
import { RepositoriesFilterSortStateWithIds } from "./variant-analysis-filter-sort";
|
||||
import { ErrorLike } from "./errors";
|
||||
import { DataFlowPaths } from "../variant-analysis/shared/data-flow-paths";
|
||||
|
||||
/**
|
||||
* This module contains types and code that are shared between
|
||||
@@ -462,3 +463,12 @@ export type FromVariantAnalysisMessage =
|
||||
| ExportResultsMessage
|
||||
| OpenLogsMessage
|
||||
| CancelVariantAnalysisMessage;
|
||||
|
||||
export interface SetDataFlowPathsMessage {
|
||||
t: "setDataFlowPaths";
|
||||
dataFlowPaths: DataFlowPaths;
|
||||
}
|
||||
|
||||
export type ToDataFlowPathsMessage = SetDataFlowPathsMessage;
|
||||
|
||||
export type FromDataFlowPathsMessage = CommonFromViewMessages;
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
import { ExtensionContext, ViewColumn } from "vscode";
|
||||
import { AbstractWebview, WebviewPanelConfig } from "../abstract-webview";
|
||||
import { assertNever } from "../pure/helpers-pure";
|
||||
import { telemetryListener } from "../telemetry";
|
||||
import {
|
||||
FromDataFlowPathsMessage,
|
||||
ToDataFlowPathsMessage,
|
||||
} from "../pure/interface-types";
|
||||
import { DataFlowPaths } from "./shared/data-flow-paths";
|
||||
import { showAndLogExceptionWithTelemetry } from "../helpers";
|
||||
import { redactableError } from "../pure/errors";
|
||||
|
||||
export class DataFlowPathsView extends AbstractWebview<
|
||||
ToDataFlowPathsMessage,
|
||||
FromDataFlowPathsMessage
|
||||
> {
|
||||
public static readonly viewType = "codeQL.dataFlowPaths";
|
||||
|
||||
public constructor(ctx: ExtensionContext) {
|
||||
super(ctx);
|
||||
}
|
||||
|
||||
public async showDataFlows(dataFlowPaths: DataFlowPaths) {
|
||||
const panel = await this.getPanel();
|
||||
panel.reveal(undefined, true);
|
||||
|
||||
await this.waitForPanelLoaded();
|
||||
|
||||
await this.postMessage({
|
||||
t: "setDataFlowPaths",
|
||||
dataFlowPaths,
|
||||
});
|
||||
}
|
||||
|
||||
protected async getPanelConfig(): Promise<WebviewPanelConfig> {
|
||||
return {
|
||||
viewId: DataFlowPathsView.viewType,
|
||||
title: "Data Flow Paths",
|
||||
viewColumn: ViewColumn.Active,
|
||||
preserveFocus: true,
|
||||
view: "data-flow-paths",
|
||||
};
|
||||
}
|
||||
|
||||
protected onPanelDispose(): void {
|
||||
// Nothing to dispose
|
||||
}
|
||||
|
||||
protected async onMessage(msg: FromDataFlowPathsMessage): Promise<void> {
|
||||
switch (msg.t) {
|
||||
case "viewLoaded":
|
||||
this.onWebViewLoaded();
|
||||
break;
|
||||
case "telemetry":
|
||||
telemetryListener?.sendUIInteraction(msg.action);
|
||||
break;
|
||||
case "unhandledError":
|
||||
void showAndLogExceptionWithTelemetry(
|
||||
redactableError(
|
||||
msg.error,
|
||||
)`Unhandled error in data flow paths view: ${msg.error.message}`,
|
||||
);
|
||||
break;
|
||||
default:
|
||||
assertNever(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { AnalysisMessage, CodeFlow, ResultSeverity } from "./analysis-result";
|
||||
|
||||
export interface DataFlowPaths {
|
||||
codeFlows: CodeFlow[];
|
||||
ruleDescription: string;
|
||||
message: AnalysisMessage;
|
||||
severity: ResultSeverity;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import * as React from "react";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ToDataFlowPathsMessage } from "../../pure/interface-types";
|
||||
import { DataFlowPaths } from "../../variant-analysis/shared/data-flow-paths";
|
||||
|
||||
export type DataFlowPathsViewProps = {
|
||||
dataFlowPaths?: DataFlowPaths;
|
||||
};
|
||||
|
||||
export function DataFlowPathsView({
|
||||
dataFlowPaths: initialDataFlowPaths,
|
||||
}: DataFlowPathsViewProps): JSX.Element {
|
||||
const [dataFlowPaths, setDataFlowPaths] = useState<DataFlowPaths | undefined>(
|
||||
initialDataFlowPaths,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const listener = (evt: MessageEvent) => {
|
||||
if (evt.origin === window.origin) {
|
||||
const msg: ToDataFlowPathsMessage = evt.data;
|
||||
if (msg.t === "setDataFlowPaths") {
|
||||
setDataFlowPaths(msg.dataFlowPaths);
|
||||
}
|
||||
} else {
|
||||
// sanitize origin
|
||||
const origin = evt.origin.replace(/\n|\r/g, "");
|
||||
console.error(`Invalid event origin ${origin}`);
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", listener);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("message", listener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
if (!dataFlowPaths) {
|
||||
return <>Loading data flow paths</>;
|
||||
}
|
||||
|
||||
// For now, just render the data flows as JSON.
|
||||
return (
|
||||
<>
|
||||
Loaded
|
||||
<pre>{JSON.stringify(dataFlowPaths)}</pre>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import * as React from "react";
|
||||
import { render as reactRender, screen } from "@testing-library/react";
|
||||
import {
|
||||
DataFlowPathsView,
|
||||
DataFlowPathsViewProps,
|
||||
} from "../DataFlowPathsView";
|
||||
import { createMockDataFlowPaths } from "../../../../test/factories/variant-analysis/shared/data-flow-paths";
|
||||
|
||||
describe(DataFlowPathsView.name, () => {
|
||||
const render = (props: Partial<DataFlowPathsViewProps>) =>
|
||||
reactRender(<DataFlowPathsView {...props} />);
|
||||
|
||||
it("renders a loading data flow paths view", () => {
|
||||
render({});
|
||||
|
||||
expect(screen.getByText("Loading data flow paths")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders a data flow paths view", () => {
|
||||
render({ dataFlowPaths: createMockDataFlowPaths() });
|
||||
|
||||
expect(screen.getByText("Loaded")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
9
extensions/ql-vscode/src/view/data-flow-paths/index.tsx
Normal file
9
extensions/ql-vscode/src/view/data-flow-paths/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import * as React from "react";
|
||||
import { WebviewDefinition } from "../webview-definition";
|
||||
import { DataFlowPathsView } from "./DataFlowPathsView";
|
||||
|
||||
const definition: WebviewDefinition = {
|
||||
component: <DataFlowPathsView />,
|
||||
};
|
||||
|
||||
export default definition;
|
||||
@@ -0,0 +1,106 @@
|
||||
import { CodeFlow } from "../../../../src/variant-analysis/shared/analysis-result";
|
||||
import { DataFlowPaths } from "../../../../src/variant-analysis/shared/data-flow-paths";
|
||||
|
||||
export function createMockDataFlowPaths(): DataFlowPaths {
|
||||
const codeFlows: CodeFlow[] = [
|
||||
{
|
||||
threadFlows: [
|
||||
{
|
||||
fileLink: {
|
||||
fileLinkPrefix:
|
||||
"https://github.com/PowerShell/PowerShell/blob/450d884668ca477c6581ce597958f021fac30bff",
|
||||
filePath:
|
||||
"src/System.Management.Automation/help/UpdatableHelpSystem.cs",
|
||||
},
|
||||
codeSnippet: {
|
||||
startLine: 1260,
|
||||
endLine: 1260,
|
||||
text: " string extractPath = Path.Combine(destination, entry.FullName);",
|
||||
},
|
||||
highlightedRegion: {
|
||||
startLine: 1260,
|
||||
startColumn: 72,
|
||||
endLine: 1260,
|
||||
endColumn: 86,
|
||||
},
|
||||
message: {
|
||||
tokens: [
|
||||
{
|
||||
t: "text",
|
||||
text: "access to property FullName : String",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
fileLink: {
|
||||
fileLinkPrefix:
|
||||
"https://github.com/PowerShell/PowerShell/blob/450d884668ca477c6581ce597958f021fac30bff",
|
||||
filePath:
|
||||
"src/System.Management.Automation/help/UpdatableHelpSystem.cs",
|
||||
},
|
||||
codeSnippet: {
|
||||
startLine: 1260,
|
||||
endLine: 1260,
|
||||
text: " string extractPath = Path.Combine(destination, entry.FullName);",
|
||||
},
|
||||
highlightedRegion: {
|
||||
startLine: 1260,
|
||||
startColumn: 46,
|
||||
endLine: 1260,
|
||||
endColumn: 87,
|
||||
},
|
||||
message: {
|
||||
tokens: [
|
||||
{
|
||||
t: "text",
|
||||
text: "call to method Combine : String",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
fileLink: {
|
||||
fileLinkPrefix:
|
||||
"https://github.com/PowerShell/PowerShell/blob/450d884668ca477c6581ce597958f021fac30bff",
|
||||
filePath:
|
||||
"src/System.Management.Automation/help/UpdatableHelpSystem.cs",
|
||||
},
|
||||
codeSnippet: {
|
||||
startLine: 1261,
|
||||
endLine: 1261,
|
||||
text: " entry.ExtractToFile(extractPath);",
|
||||
},
|
||||
highlightedRegion: {
|
||||
startLine: 1261,
|
||||
startColumn: 45,
|
||||
endLine: 1261,
|
||||
endColumn: 56,
|
||||
},
|
||||
message: {
|
||||
tokens: [
|
||||
{
|
||||
t: "text",
|
||||
text: "access to local variable extractPath",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
codeFlows,
|
||||
ruleDescription: "ZipSlip vulnerability",
|
||||
message: {
|
||||
tokens: [
|
||||
{
|
||||
t: "text",
|
||||
text: "This zip file may have a dangerous path",
|
||||
},
|
||||
],
|
||||
},
|
||||
severity: "Warning",
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user