Remove automodel v1
This commit is contained in:
@@ -704,7 +704,6 @@ export function showQueriesPanel(): boolean {
|
|||||||
|
|
||||||
const DATA_EXTENSIONS = new Setting("dataExtensions", ROOT_SETTING);
|
const DATA_EXTENSIONS = new Setting("dataExtensions", ROOT_SETTING);
|
||||||
const LLM_GENERATION = new Setting("llmGeneration", DATA_EXTENSIONS);
|
const LLM_GENERATION = new Setting("llmGeneration", DATA_EXTENSIONS);
|
||||||
const LLM_GENERATION_V2 = new Setting("llmGenerationV2", DATA_EXTENSIONS);
|
|
||||||
const FRAMEWORK_MODE = new Setting("frameworkMode", DATA_EXTENSIONS);
|
const FRAMEWORK_MODE = new Setting("frameworkMode", DATA_EXTENSIONS);
|
||||||
const DISABLE_AUTO_NAME_EXTENSION_PACK = new Setting(
|
const DISABLE_AUTO_NAME_EXTENSION_PACK = new Setting(
|
||||||
"disableAutoNameExtensionPack",
|
"disableAutoNameExtensionPack",
|
||||||
@@ -724,10 +723,6 @@ export function showLlmGeneration(): boolean {
|
|||||||
return !!LLM_GENERATION.getValue<boolean>();
|
return !!LLM_GENERATION.getValue<boolean>();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useLlmGenerationV2(): boolean {
|
|
||||||
return !!LLM_GENERATION_V2.getValue<boolean>();
|
|
||||||
}
|
|
||||||
|
|
||||||
export function enableFrameworkMode(): boolean {
|
export function enableFrameworkMode(): boolean {
|
||||||
return !!FRAMEWORK_MODE.getValue<boolean>();
|
return !!FRAMEWORK_MODE.getValue<boolean>();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
import { Credentials } from "../common/authentication";
|
|
||||||
import { OctokitResponse } from "@octokit/types";
|
|
||||||
|
|
||||||
export enum ClassificationType {
|
|
||||||
Unknown = "CLASSIFICATION_TYPE_UNKNOWN",
|
|
||||||
Neutral = "CLASSIFICATION_TYPE_NEUTRAL",
|
|
||||||
Source = "CLASSIFICATION_TYPE_SOURCE",
|
|
||||||
Sink = "CLASSIFICATION_TYPE_SINK",
|
|
||||||
Summary = "CLASSIFICATION_TYPE_SUMMARY",
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Classification {
|
|
||||||
type: ClassificationType;
|
|
||||||
kind: string;
|
|
||||||
explanation: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Method {
|
|
||||||
package: string;
|
|
||||||
type: string;
|
|
||||||
name: string;
|
|
||||||
signature: string;
|
|
||||||
usages: string[];
|
|
||||||
classification?: Classification;
|
|
||||||
input?: string;
|
|
||||||
output?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelRequest {
|
|
||||||
language: string;
|
|
||||||
candidates: Method[];
|
|
||||||
samples: Method[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ModelResponse {
|
|
||||||
language: string;
|
|
||||||
predicted?: Method[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function autoModel(
|
|
||||||
credentials: Credentials,
|
|
||||||
request: ModelRequest,
|
|
||||||
): Promise<ModelResponse> {
|
|
||||||
const octokit = await credentials.getOctokit();
|
|
||||||
|
|
||||||
const response: OctokitResponse<ModelResponse> = await octokit.request(
|
|
||||||
"POST /repos/github/codeql/code-scanning/codeql/auto-model",
|
|
||||||
{
|
|
||||||
data: request,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return response.data;
|
|
||||||
}
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
import { CancellationTokenSource } from "vscode";
|
|
||||||
import { join } from "path";
|
|
||||||
import { runQuery } from "./external-api-usage-query";
|
|
||||||
import { CodeQLCliServer } from "../codeql-cli/cli";
|
|
||||||
import { QueryRunner } from "../query-server";
|
|
||||||
import { DatabaseItem } from "../databases/local-databases";
|
|
||||||
import { interpretResultsSarif } from "../query-results";
|
|
||||||
import { ProgressCallback } from "../common/vscode/progress";
|
|
||||||
import { Mode } from "./shared/mode";
|
|
||||||
|
|
||||||
type Options = {
|
|
||||||
cliServer: CodeQLCliServer;
|
|
||||||
queryRunner: QueryRunner;
|
|
||||||
databaseItem: DatabaseItem;
|
|
||||||
queryStorageDir: string;
|
|
||||||
queryDir: string;
|
|
||||||
|
|
||||||
progress: ProgressCallback;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type UsageSnippetsBySignature = Record<string, string[]>;
|
|
||||||
|
|
||||||
export async function getAutoModelUsages({
|
|
||||||
cliServer,
|
|
||||||
queryRunner,
|
|
||||||
databaseItem,
|
|
||||||
queryStorageDir,
|
|
||||||
queryDir,
|
|
||||||
progress,
|
|
||||||
}: Options): Promise<UsageSnippetsBySignature> {
|
|
||||||
const maxStep = 1500;
|
|
||||||
|
|
||||||
const cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
// This will re-run the query that was already run when opening the data extensions editor. This
|
|
||||||
// might be unnecessary, but this makes it really easy to get the path to the BQRS file which we
|
|
||||||
// need to interpret the results.
|
|
||||||
const queryResult = await runQuery(Mode.Application, {
|
|
||||||
cliServer,
|
|
||||||
queryRunner,
|
|
||||||
queryStorageDir,
|
|
||||||
databaseItem,
|
|
||||||
queryDir,
|
|
||||||
progress: (update) =>
|
|
||||||
progress({
|
|
||||||
maxStep,
|
|
||||||
step: update.step,
|
|
||||||
message: update.message,
|
|
||||||
}),
|
|
||||||
token: cancellationTokenSource.token,
|
|
||||||
});
|
|
||||||
if (!queryResult) {
|
|
||||||
throw new Error("Query failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
progress({
|
|
||||||
maxStep,
|
|
||||||
step: 1100,
|
|
||||||
message: "Retrieving source location prefix",
|
|
||||||
});
|
|
||||||
|
|
||||||
// CodeQL needs to have access to the database to be able to retrieve the
|
|
||||||
// snippets from it. The source location prefix is used to determine the
|
|
||||||
// base path of the database.
|
|
||||||
const sourceLocationPrefix = await databaseItem.getSourceLocationPrefix(
|
|
||||||
cliServer,
|
|
||||||
);
|
|
||||||
const sourceArchiveUri = databaseItem.sourceArchive;
|
|
||||||
const sourceInfo =
|
|
||||||
sourceArchiveUri === undefined
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
sourceArchive: sourceArchiveUri.fsPath,
|
|
||||||
sourceLocationPrefix,
|
|
||||||
};
|
|
||||||
|
|
||||||
progress({
|
|
||||||
maxStep,
|
|
||||||
step: 1200,
|
|
||||||
message: "Interpreting results",
|
|
||||||
});
|
|
||||||
|
|
||||||
// Convert the results to SARIF so that Codeql will retrieve the snippets
|
|
||||||
// from the datababe. This means we don't need to do that in the extension
|
|
||||||
// and everything is handled by the CodeQL CLI.
|
|
||||||
const sarif = await interpretResultsSarif(
|
|
||||||
cliServer,
|
|
||||||
{
|
|
||||||
// To interpret the results we need to provide metadata about the query. We could do this using
|
|
||||||
// `resolveMetadata` but that would be an extra call to the CodeQL CLI server and would require
|
|
||||||
// us to know the path to the query on the filesystem. Since we know what the metadata should
|
|
||||||
// look like and the only metadata that the CodeQL CLI requires is an ID and the kind, we can
|
|
||||||
// simply use constants here.
|
|
||||||
kind: "problem",
|
|
||||||
id: "usage",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
resultsPath: queryResult.outputDir.bqrsPath,
|
|
||||||
interpretedResultsPath: join(
|
|
||||||
queryStorageDir,
|
|
||||||
"interpreted-results.sarif",
|
|
||||||
),
|
|
||||||
},
|
|
||||||
sourceInfo,
|
|
||||||
["--sarif-add-snippets"],
|
|
||||||
);
|
|
||||||
|
|
||||||
progress({
|
|
||||||
maxStep,
|
|
||||||
step: 1400,
|
|
||||||
message: "Parsing results",
|
|
||||||
});
|
|
||||||
|
|
||||||
const snippets: UsageSnippetsBySignature = {};
|
|
||||||
|
|
||||||
const results = sarif.runs[0]?.results;
|
|
||||||
if (!results) {
|
|
||||||
throw new Error("No results");
|
|
||||||
}
|
|
||||||
|
|
||||||
// This will group the snippets by the method signature.
|
|
||||||
for (const result of results) {
|
|
||||||
const signature = result.message.text;
|
|
||||||
|
|
||||||
const snippet =
|
|
||||||
result.locations?.[0]?.physicalLocation?.contextRegion?.snippet?.text;
|
|
||||||
|
|
||||||
if (!signature || !snippet) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(signature in snippets)) {
|
|
||||||
snippets[signature] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
snippets[signature].push(snippet);
|
|
||||||
}
|
|
||||||
|
|
||||||
return snippets;
|
|
||||||
}
|
|
||||||
@@ -1,262 +0,0 @@
|
|||||||
import { ExternalApiUsage } from "./external-api-usage";
|
|
||||||
import { ModeledMethod, ModeledMethodType } from "./modeled-method";
|
|
||||||
import {
|
|
||||||
Classification,
|
|
||||||
ClassificationType,
|
|
||||||
Method,
|
|
||||||
ModelRequest,
|
|
||||||
} from "./auto-model-api";
|
|
||||||
import type { UsageSnippetsBySignature } from "./auto-model-usages-query";
|
|
||||||
import { groupMethods, sortGroupNames, sortMethods } from "./shared/sorting";
|
|
||||||
import { Mode } from "./shared/mode";
|
|
||||||
|
|
||||||
// Soft limit on the number of candidates to send to the model.
|
|
||||||
// Note that the model may return fewer than this number of candidates.
|
|
||||||
const candidateLimit = 20;
|
|
||||||
// Soft limit on the number of samples to send to the model.
|
|
||||||
const sampleLimit = 100;
|
|
||||||
|
|
||||||
export function createAutoModelRequest(
|
|
||||||
language: string,
|
|
||||||
externalApiUsages: ExternalApiUsage[],
|
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
|
||||||
usages: UsageSnippetsBySignature,
|
|
||||||
mode: Mode,
|
|
||||||
): ModelRequest {
|
|
||||||
const request: ModelRequest = {
|
|
||||||
language,
|
|
||||||
samples: [],
|
|
||||||
candidates: [],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sort the same way as the UI so we send the first ones listed in the UI first
|
|
||||||
const grouped = groupMethods(externalApiUsages, mode);
|
|
||||||
const sortedGroupNames = sortGroupNames(grouped);
|
|
||||||
const sortedExternalApiUsages = sortedGroupNames.flatMap((name) =>
|
|
||||||
sortMethods(grouped[name]),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (const externalApiUsage of sortedExternalApiUsages) {
|
|
||||||
const modeledMethod: ModeledMethod = modeledMethods[
|
|
||||||
externalApiUsage.signature
|
|
||||||
] ?? {
|
|
||||||
type: "none",
|
|
||||||
};
|
|
||||||
|
|
||||||
const usagesForMethod =
|
|
||||||
usages[externalApiUsage.signature] ??
|
|
||||||
externalApiUsage.usages.map((usage) => usage.label);
|
|
||||||
|
|
||||||
const numberOfArguments =
|
|
||||||
externalApiUsage.methodParameters === "()"
|
|
||||||
? 0
|
|
||||||
: externalApiUsage.methodParameters.split(",").length;
|
|
||||||
|
|
||||||
const candidates: Method[] = [];
|
|
||||||
const samples: Method[] = [];
|
|
||||||
for (
|
|
||||||
let argumentIndex = -1; // Start at -1 which means `this` as in `this.method()`
|
|
||||||
argumentIndex < numberOfArguments;
|
|
||||||
argumentIndex++
|
|
||||||
) {
|
|
||||||
const argumentInput: string =
|
|
||||||
argumentIndex === -1 ? "Argument[this]" : `Argument[${argumentIndex}]`;
|
|
||||||
const method: Method = {
|
|
||||||
package: externalApiUsage.packageName,
|
|
||||||
type: externalApiUsage.typeName,
|
|
||||||
name: externalApiUsage.methodName,
|
|
||||||
signature: externalApiUsage.methodParameters,
|
|
||||||
classification:
|
|
||||||
modeledMethod.type === "none"
|
|
||||||
? undefined
|
|
||||||
: toMethodClassification(modeledMethod),
|
|
||||||
usages: usagesForMethod.slice(0, 6), // At most 6 usages per argument
|
|
||||||
input: argumentInput,
|
|
||||||
};
|
|
||||||
|
|
||||||
// A method that is supported is modeled outside of the model file, so it is not a candidate.
|
|
||||||
// We also do not want it as a sample because we do not know the classification.
|
|
||||||
if (modeledMethod.type === "none" && externalApiUsage.supported) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Candidates are methods that are not currently modeled
|
|
||||||
if (modeledMethod.type === "none") {
|
|
||||||
candidates.push(method);
|
|
||||||
} else {
|
|
||||||
samples.push(method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If there is room for at least one candidate, add all candidates.
|
|
||||||
// This ensures that we send all arguments for a method together.
|
|
||||||
// NOTE: this might go above the candidate limit, but that's okay.
|
|
||||||
if (request.candidates.length < candidateLimit) {
|
|
||||||
request.candidates.push(...candidates);
|
|
||||||
}
|
|
||||||
// Same for samples
|
|
||||||
if (request.samples.length < sampleLimit) {
|
|
||||||
request.samples.push(...samples);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return request;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For now, we have a simplified model that only models methods as sinks. It does not model methods as neutral,
|
|
||||||
* so we aren't actually able to correctly determine that a method is neutral; it could still be a source or summary.
|
|
||||||
* However, to keep this method simple and give output to the user, we will model any method for which none of its
|
|
||||||
* arguments are modeled as sinks as neutral.
|
|
||||||
*
|
|
||||||
* If there are multiple arguments which are modeled as sinks, we will only model the first one.
|
|
||||||
*/
|
|
||||||
export function parsePredictedClassifications(
|
|
||||||
predicted: Method[],
|
|
||||||
): Record<string, ModeledMethod> {
|
|
||||||
const predictedBySignature: Record<string, Method[]> = {};
|
|
||||||
for (const method of predicted) {
|
|
||||||
const signature = toFullMethodSignature(method);
|
|
||||||
|
|
||||||
if (!(signature in predictedBySignature)) {
|
|
||||||
predictedBySignature[signature] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
predictedBySignature[signature].push(method);
|
|
||||||
}
|
|
||||||
|
|
||||||
const modeledMethods: Record<string, ModeledMethod> = {};
|
|
||||||
|
|
||||||
for (const signature in predictedBySignature) {
|
|
||||||
const predictedMethods = predictedBySignature[signature];
|
|
||||||
|
|
||||||
const sinks = predictedMethods.filter(
|
|
||||||
(method) => method.classification?.type === ClassificationType.Sink,
|
|
||||||
);
|
|
||||||
if (sinks.length === 0) {
|
|
||||||
// For now, model any method for which none of its arguments are modeled as sinks as neutral
|
|
||||||
modeledMethods[signature] = {
|
|
||||||
type: "neutral",
|
|
||||||
kind: "summary",
|
|
||||||
input: "",
|
|
||||||
output: "",
|
|
||||||
provenance: "ai-generated",
|
|
||||||
signature,
|
|
||||||
// predictedBySignature[signature] always has at least element
|
|
||||||
packageName: predictedMethods[0].package,
|
|
||||||
typeName: predictedMethods[0].type,
|
|
||||||
methodName: predictedMethods[0].name,
|
|
||||||
methodParameters: predictedMethods[0].signature,
|
|
||||||
};
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Order the sinks by the input alphabetically. This will ensure that the first argument is always
|
|
||||||
// first in the list of sinks, the second argument is always second, etc.
|
|
||||||
// If we get back "Argument[1]" and "Argument[3]", "Argument[1]" should always be first
|
|
||||||
sinks.sort((a, b) => compareInputOutput(a.input ?? "", b.input ?? ""));
|
|
||||||
|
|
||||||
const sink = sinks[0];
|
|
||||||
|
|
||||||
modeledMethods[signature] = {
|
|
||||||
type: "sink",
|
|
||||||
kind: sink.classification?.kind ?? "",
|
|
||||||
input: sink.input ?? "",
|
|
||||||
output: sink.output ?? "",
|
|
||||||
provenance: "ai-generated",
|
|
||||||
signature,
|
|
||||||
packageName: sink.package,
|
|
||||||
typeName: sink.type,
|
|
||||||
methodName: sink.name,
|
|
||||||
methodParameters: sink.signature,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return modeledMethods;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toMethodClassificationType(
|
|
||||||
type: ModeledMethodType,
|
|
||||||
): ClassificationType {
|
|
||||||
switch (type) {
|
|
||||||
case "source":
|
|
||||||
return ClassificationType.Source;
|
|
||||||
case "sink":
|
|
||||||
return ClassificationType.Sink;
|
|
||||||
case "summary":
|
|
||||||
return ClassificationType.Summary;
|
|
||||||
case "neutral":
|
|
||||||
return ClassificationType.Neutral;
|
|
||||||
default:
|
|
||||||
return ClassificationType.Unknown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function toMethodClassification(modeledMethod: ModeledMethod): Classification {
|
|
||||||
return {
|
|
||||||
type: toMethodClassificationType(modeledMethod.type),
|
|
||||||
kind: modeledMethod.kind,
|
|
||||||
explanation: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toFullMethodSignature(method: Method): string {
|
|
||||||
return `${method.package}.${method.type}#${method.name}${method.signature}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const argumentRegex = /^Argument\[(\d+)]$/;
|
|
||||||
|
|
||||||
// Argument[this] is before ReturnValue
|
|
||||||
const nonNumericArgumentOrder = ["Argument[this]", "ReturnValue"];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compare two inputs or outputs matching `Argument[<number>]`, `Argument[this]`, or `ReturnValue`.
|
|
||||||
* If they are the same, return 0. If a is less than b, returns a negative number.
|
|
||||||
* If a is greater than b, returns a positive number.
|
|
||||||
*/
|
|
||||||
export function compareInputOutput(a: string, b: string): number {
|
|
||||||
if (a === b) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const aMatch = a.match(argumentRegex);
|
|
||||||
const bMatch = b.match(argumentRegex);
|
|
||||||
|
|
||||||
// Numeric arguments are always first
|
|
||||||
if (aMatch && !bMatch) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (!aMatch && bMatch) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Neither is an argument
|
|
||||||
if (!aMatch && !bMatch) {
|
|
||||||
const aIndex = nonNumericArgumentOrder.indexOf(a);
|
|
||||||
const bIndex = nonNumericArgumentOrder.indexOf(b);
|
|
||||||
|
|
||||||
// If either one is unknown, it is sorted last
|
|
||||||
if (aIndex === -1 && bIndex === -1) {
|
|
||||||
// Use en-US because these are well-known strings that are not localized
|
|
||||||
return a.localeCompare(b, "en-US");
|
|
||||||
}
|
|
||||||
if (aIndex === -1) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
if (bIndex === -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return aIndex - bIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This case shouldn't happen, but makes TypeScript happy
|
|
||||||
if (!aMatch || !bMatch) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Both are arguments
|
|
||||||
const aIndex = parseInt(aMatch[1]);
|
|
||||||
const bIndex = parseInt(bMatch[1]);
|
|
||||||
|
|
||||||
return aIndex - bIndex;
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
ViewColumn,
|
ViewColumn,
|
||||||
window,
|
window,
|
||||||
} from "vscode";
|
} from "vscode";
|
||||||
import { RequestError } from "@octokit/request-error";
|
|
||||||
import {
|
import {
|
||||||
AbstractWebview,
|
AbstractWebview,
|
||||||
WebviewPanelConfig,
|
WebviewPanelConfig,
|
||||||
@@ -34,18 +33,11 @@ import { readQueryResults, runQuery } from "./external-api-usage-query";
|
|||||||
import { ExternalApiUsage } from "./external-api-usage";
|
import { ExternalApiUsage } from "./external-api-usage";
|
||||||
import { ModeledMethod } from "./modeled-method";
|
import { ModeledMethod } from "./modeled-method";
|
||||||
import { ExtensionPack } from "./shared/extension-pack";
|
import { ExtensionPack } from "./shared/extension-pack";
|
||||||
import { autoModel, ModelRequest, ModelResponse } from "./auto-model-api";
|
|
||||||
import {
|
|
||||||
createAutoModelRequest,
|
|
||||||
parsePredictedClassifications,
|
|
||||||
} from "./auto-model";
|
|
||||||
import {
|
import {
|
||||||
enableFrameworkMode,
|
enableFrameworkMode,
|
||||||
showLlmGeneration,
|
showLlmGeneration,
|
||||||
showModelDetailsView,
|
showModelDetailsView,
|
||||||
useLlmGenerationV2,
|
|
||||||
} from "../config";
|
} from "../config";
|
||||||
import { getAutoModelUsages } from "./auto-model-usages-query";
|
|
||||||
import { Mode } from "./shared/mode";
|
import { Mode } from "./shared/mode";
|
||||||
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
import { loadModeledMethods, saveModeledMethods } from "./modeled-method-fs";
|
||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
@@ -176,18 +168,11 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case "generateExternalApiFromLlm":
|
case "generateExternalApiFromLlm":
|
||||||
if (useLlmGenerationV2()) {
|
await this.generateModeledMethodsFromLlmV2(
|
||||||
await this.generateModeledMethodsFromLlmV2(
|
msg.packageName,
|
||||||
msg.packageName,
|
msg.externalApiUsages,
|
||||||
msg.externalApiUsages,
|
msg.modeledMethods,
|
||||||
msg.modeledMethods,
|
);
|
||||||
);
|
|
||||||
} else {
|
|
||||||
await this.generateModeledMethodsFromLlmV1(
|
|
||||||
msg.externalApiUsages,
|
|
||||||
msg.modeledMethods,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case "stopGeneratingExternalApiFromLlm":
|
case "stopGeneratingExternalApiFromLlm":
|
||||||
await this.autoModeler.stopModeling(msg.packageName);
|
await this.autoModeler.stopModeling(msg.packageName);
|
||||||
@@ -389,76 +374,6 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateModeledMethodsFromLlmV1(
|
|
||||||
externalApiUsages: ExternalApiUsage[],
|
|
||||||
modeledMethods: Record<string, ModeledMethod>,
|
|
||||||
): Promise<void> {
|
|
||||||
await withProgress(async (progress) => {
|
|
||||||
const maxStep = 3000;
|
|
||||||
|
|
||||||
progress({
|
|
||||||
step: 0,
|
|
||||||
maxStep,
|
|
||||||
message: "Retrieving usages",
|
|
||||||
});
|
|
||||||
|
|
||||||
const usages = await getAutoModelUsages({
|
|
||||||
cliServer: this.cliServer,
|
|
||||||
queryRunner: this.queryRunner,
|
|
||||||
queryStorageDir: this.queryStorageDir,
|
|
||||||
queryDir: this.queryDir,
|
|
||||||
databaseItem: this.databaseItem,
|
|
||||||
progress: (update) => progress({ ...update, maxStep }),
|
|
||||||
});
|
|
||||||
|
|
||||||
progress({
|
|
||||||
step: 1800,
|
|
||||||
maxStep,
|
|
||||||
message: "Creating request",
|
|
||||||
});
|
|
||||||
|
|
||||||
const request = createAutoModelRequest(
|
|
||||||
this.databaseItem.language,
|
|
||||||
externalApiUsages,
|
|
||||||
modeledMethods,
|
|
||||||
usages,
|
|
||||||
this.mode,
|
|
||||||
);
|
|
||||||
|
|
||||||
progress({
|
|
||||||
step: 2000,
|
|
||||||
maxStep,
|
|
||||||
message: "Sending request",
|
|
||||||
});
|
|
||||||
|
|
||||||
const response = await this.callAutoModelApi(request);
|
|
||||||
if (!response) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress({
|
|
||||||
step: 2500,
|
|
||||||
maxStep,
|
|
||||||
message: "Parsing response",
|
|
||||||
});
|
|
||||||
|
|
||||||
const predictedModeledMethods = parsePredictedClassifications(
|
|
||||||
response.predicted || [],
|
|
||||||
);
|
|
||||||
|
|
||||||
progress({
|
|
||||||
step: 2800,
|
|
||||||
maxStep,
|
|
||||||
message: "Applying results",
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.postMessage({
|
|
||||||
t: "addModeledMethods",
|
|
||||||
modeledMethods: predictedModeledMethods,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private async generateModeledMethodsFromLlmV2(
|
private async generateModeledMethodsFromLlmV2(
|
||||||
packageName: string,
|
packageName: string,
|
||||||
externalApiUsages: ExternalApiUsage[],
|
externalApiUsages: ExternalApiUsage[],
|
||||||
@@ -580,23 +495,4 @@ export class DataExtensionsEditorView extends AbstractWebview<
|
|||||||
|
|
||||||
return addedDatabase;
|
return addedDatabase;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async callAutoModelApi(
|
|
||||||
request: ModelRequest,
|
|
||||||
): Promise<ModelResponse | null> {
|
|
||||||
try {
|
|
||||||
return await autoModel(this.app.credentials, request);
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof RequestError && e.status === 429) {
|
|
||||||
void showAndLogExceptionWithTelemetry(
|
|
||||||
this.app.logger,
|
|
||||||
this.app.telemetry,
|
|
||||||
redactableError(e)`Rate limit hit, please try again soon.`,
|
|
||||||
);
|
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,632 +0,0 @@
|
|||||||
import {
|
|
||||||
compareInputOutput,
|
|
||||||
createAutoModelRequest,
|
|
||||||
parsePredictedClassifications,
|
|
||||||
} from "../../../src/data-extensions-editor/auto-model";
|
|
||||||
import {
|
|
||||||
CallClassification,
|
|
||||||
ExternalApiUsage,
|
|
||||||
} from "../../../src/data-extensions-editor/external-api-usage";
|
|
||||||
import { ModeledMethod } from "../../../src/data-extensions-editor/modeled-method";
|
|
||||||
import {
|
|
||||||
ClassificationType,
|
|
||||||
Method,
|
|
||||||
} from "../../../src/data-extensions-editor/auto-model-api";
|
|
||||||
import { Mode } from "../../../src/data-extensions-editor/shared/mode";
|
|
||||||
|
|
||||||
describe("createAutoModelRequest", () => {
|
|
||||||
const externalApiUsages: ExternalApiUsage[] = [
|
|
||||||
{
|
|
||||||
library: "spring-boot-3.0.2.jar",
|
|
||||||
signature:
|
|
||||||
"org.springframework.boot.SpringApplication#run(Class,String[])",
|
|
||||||
packageName: "org.springframework.boot",
|
|
||||||
typeName: "SpringApplication",
|
|
||||||
methodName: "run",
|
|
||||||
methodParameters: "(Class,String[])",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "run(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/Sql2oExampleApplication.java",
|
|
||||||
startLine: 9,
|
|
||||||
startColumn: 9,
|
|
||||||
endLine: 9,
|
|
||||||
endColumn: 66,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "sql2o-1.6.0.jar",
|
|
||||||
signature: "org.sql2o.Connection#createQuery(String)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Connection",
|
|
||||||
methodName: "createQuery",
|
|
||||||
methodParameters: "(String)",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "createQuery(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 15,
|
|
||||||
startColumn: 13,
|
|
||||||
endLine: 15,
|
|
||||||
endColumn: 56,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "createQuery(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 26,
|
|
||||||
startColumn: 13,
|
|
||||||
endLine: 26,
|
|
||||||
endColumn: 39,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "sql2o-1.6.0.jar",
|
|
||||||
signature: "org.sql2o.Query#executeScalar(Class)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Query",
|
|
||||||
methodName: "executeScalar",
|
|
||||||
methodParameters: "(Class)",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "executeScalar(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 15,
|
|
||||||
startColumn: 13,
|
|
||||||
endLine: 15,
|
|
||||||
endColumn: 85,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "executeScalar(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 26,
|
|
||||||
startColumn: 13,
|
|
||||||
endLine: 26,
|
|
||||||
endColumn: 68,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "sql2o-1.6.0.jar",
|
|
||||||
signature: "org.sql2o.Sql2o#open()",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "open",
|
|
||||||
methodParameters: "()",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "open(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 14,
|
|
||||||
startColumn: 24,
|
|
||||||
endLine: 14,
|
|
||||||
endColumn: 35,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "open(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 25,
|
|
||||||
startColumn: 24,
|
|
||||||
endLine: 25,
|
|
||||||
endColumn: 35,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "rt.jar",
|
|
||||||
signature: "java.io.PrintStream#println(String)",
|
|
||||||
packageName: "java.io",
|
|
||||||
typeName: "PrintStream",
|
|
||||||
methodName: "println",
|
|
||||||
methodParameters: "(String)",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "println(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 29,
|
|
||||||
startColumn: 9,
|
|
||||||
endLine: 29,
|
|
||||||
endColumn: 49,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "sql2o-1.6.0.jar",
|
|
||||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "Sql2o",
|
|
||||||
methodParameters: "(String,String,String)",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "new Sql2o(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 10,
|
|
||||||
startColumn: 33,
|
|
||||||
endLine: 10,
|
|
||||||
endColumn: 88,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "sql2o-1.6.0.jar",
|
|
||||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "Sql2o",
|
|
||||||
methodParameters: "(String)",
|
|
||||||
supported: false,
|
|
||||||
supportedType: "none",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "new Sql2o(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/sql2o-example/sql2o-example/src/main/java/org/example/HelloController.java",
|
|
||||||
startLine: 23,
|
|
||||||
startColumn: 23,
|
|
||||||
endLine: 23,
|
|
||||||
endColumn: 36,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
library: "test.jar",
|
|
||||||
signature: "org.test.MyClass#test()",
|
|
||||||
packageName: "org.test",
|
|
||||||
typeName: "MyClass",
|
|
||||||
methodName: "test",
|
|
||||||
methodParameters: "()",
|
|
||||||
supported: true,
|
|
||||||
supportedType: "neutral",
|
|
||||||
usages: [
|
|
||||||
{
|
|
||||||
label: "abc.test(...)",
|
|
||||||
url: {
|
|
||||||
uri: "file:/home/runner/work/test/Test.java",
|
|
||||||
startLine: 23,
|
|
||||||
startColumn: 23,
|
|
||||||
endLine: 23,
|
|
||||||
endColumn: 36,
|
|
||||||
},
|
|
||||||
classification: CallClassification.Source,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const modeledMethods: Record<string, ModeledMethod> = {
|
|
||||||
"org.sql2o.Sql2o#open()": {
|
|
||||||
type: "neutral",
|
|
||||||
kind: "",
|
|
||||||
input: "",
|
|
||||||
output: "",
|
|
||||||
provenance: "manual",
|
|
||||||
signature: "org.sql2o.Sql2o#open()",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "open",
|
|
||||||
methodParameters: "()",
|
|
||||||
},
|
|
||||||
"org.sql2o.Sql2o#Sql2o(String)": {
|
|
||||||
type: "sink",
|
|
||||||
kind: "jndi-injection",
|
|
||||||
input: "Argument[0]",
|
|
||||||
output: "",
|
|
||||||
provenance: "manual",
|
|
||||||
signature: "org.sql2o.Sql2o#Sql2o(String)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "Sql2o",
|
|
||||||
methodParameters: "(String)",
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const usages: Record<string, string[]> = {
|
|
||||||
"org.springframework.boot.SpringApplication#run(Class,String[])": [
|
|
||||||
"public class Sql2oExampleApplication {\n public static void main(String[] args) {\n SpringApplication.run(Sql2oExampleApplication.class, args);\n }\n}",
|
|
||||||
],
|
|
||||||
"org.sql2o.Connection#createQuery(String)": [
|
|
||||||
' public String index(@RequestParam("id") String id) {\n try (var con = sql2o.open()) {\n con.createQuery("select 1 where id = " + id).executeScalar(Integer.class);\n }\n\n',
|
|
||||||
'\n try (var con = sql2o.open()) {\n con.createQuery("select 1").executeScalar(Integer.class);\n }\n\n',
|
|
||||||
],
|
|
||||||
"org.sql2o.Query#executeScalar(Class)": [
|
|
||||||
' public String index(@RequestParam("id") String id) {\n try (var con = sql2o.open()) {\n con.createQuery("select 1 where id = " + id).executeScalar(Integer.class);\n }\n\n',
|
|
||||||
'\n try (var con = sql2o.open()) {\n con.createQuery("select 1").executeScalar(Integer.class);\n }\n\n',
|
|
||||||
],
|
|
||||||
"org.sql2o.Sql2o#open()": [
|
|
||||||
' @GetMapping("/")\n public String index(@RequestParam("id") String id) {\n try (var con = sql2o.open()) {\n con.createQuery("select 1 where id = " + id).executeScalar(Integer.class);\n }\n',
|
|
||||||
' Sql2o sql2o = new Sql2o(url);\n\n try (var con = sql2o.open()) {\n con.createQuery("select 1").executeScalar(Integer.class);\n }\n',
|
|
||||||
],
|
|
||||||
"java.io.PrintStream#println(String)": [
|
|
||||||
' }\n\n System.out.println("Connected to " + url);\n\n return "Greetings from Spring Boot!";\n',
|
|
||||||
],
|
|
||||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": [
|
|
||||||
'@RestController\npublic class HelloController {\n private final Sql2o sql2o = new Sql2o("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1","sa", "");\n\n @GetMapping("/")\n',
|
|
||||||
],
|
|
||||||
"org.sql2o.Sql2o#Sql2o(String)": [
|
|
||||||
' @GetMapping("/connect")\n public String connect(@RequestParam("url") String url) {\n Sql2o sql2o = new Sql2o(url);\n\n try (var con = sql2o.open()) {\n',
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
it("creates a matching request", () => {
|
|
||||||
expect(
|
|
||||||
createAutoModelRequest(
|
|
||||||
"java",
|
|
||||||
externalApiUsages,
|
|
||||||
modeledMethods,
|
|
||||||
usages,
|
|
||||||
Mode.Application,
|
|
||||||
),
|
|
||||||
).toEqual({
|
|
||||||
language: "java",
|
|
||||||
samples: [
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "open",
|
|
||||||
signature: "()",
|
|
||||||
classification: {
|
|
||||||
type: "CLASSIFICATION_TYPE_NEUTRAL",
|
|
||||||
kind: "",
|
|
||||||
explanation: "",
|
|
||||||
},
|
|
||||||
usages: usages["org.sql2o.Sql2o#open()"],
|
|
||||||
input: "Argument[this]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String)",
|
|
||||||
classification: {
|
|
||||||
type: "CLASSIFICATION_TYPE_SINK",
|
|
||||||
kind: "jndi-injection",
|
|
||||||
explanation: "",
|
|
||||||
},
|
|
||||||
usages: usages["org.sql2o.Sql2o#Sql2o(String)"],
|
|
||||||
input: "Argument[this]",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String)",
|
|
||||||
classification: {
|
|
||||||
type: "CLASSIFICATION_TYPE_SINK",
|
|
||||||
kind: "jndi-injection",
|
|
||||||
explanation: "",
|
|
||||||
},
|
|
||||||
usages: usages["org.sql2o.Sql2o#Sql2o(String)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
candidates: [
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Connection",
|
|
||||||
name: "createQuery",
|
|
||||||
signature: "(String)",
|
|
||||||
usages: usages["org.sql2o.Connection#createQuery(String)"],
|
|
||||||
input: "Argument[this]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Connection",
|
|
||||||
name: "createQuery",
|
|
||||||
signature: "(String)",
|
|
||||||
usages: usages["org.sql2o.Connection#createQuery(String)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Query",
|
|
||||||
name: "executeScalar",
|
|
||||||
signature: "(Class)",
|
|
||||||
usages: usages["org.sql2o.Query#executeScalar(Class)"],
|
|
||||||
input: "Argument[this]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Query",
|
|
||||||
name: "executeScalar",
|
|
||||||
signature: "(Class)",
|
|
||||||
usages: usages["org.sql2o.Query#executeScalar(Class)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: usages["org.sql2o.Sql2o#Sql2o(String,String,String)"],
|
|
||||||
input: "Argument[this]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: usages["org.sql2o.Sql2o#Sql2o(String,String,String)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: usages["org.sql2o.Sql2o#Sql2o(String,String,String)"],
|
|
||||||
input: "Argument[1]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: usages["org.sql2o.Sql2o#Sql2o(String,String,String)"],
|
|
||||||
input: "Argument[2]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "java.io",
|
|
||||||
type: "PrintStream",
|
|
||||||
name: "println",
|
|
||||||
signature: "(String)",
|
|
||||||
usages: usages["java.io.PrintStream#println(String)"],
|
|
||||||
input: "Argument[this]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "java.io",
|
|
||||||
type: "PrintStream",
|
|
||||||
name: "println",
|
|
||||||
signature: "(String)",
|
|
||||||
usages: usages["java.io.PrintStream#println(String)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.springframework.boot",
|
|
||||||
type: "SpringApplication",
|
|
||||||
name: "run",
|
|
||||||
signature: "(Class,String[])",
|
|
||||||
usages:
|
|
||||||
usages[
|
|
||||||
"org.springframework.boot.SpringApplication#run(Class,String[])"
|
|
||||||
],
|
|
||||||
input: "Argument[this]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.springframework.boot",
|
|
||||||
type: "SpringApplication",
|
|
||||||
name: "run",
|
|
||||||
signature: "(Class,String[])",
|
|
||||||
usages:
|
|
||||||
usages[
|
|
||||||
"org.springframework.boot.SpringApplication#run(Class,String[])"
|
|
||||||
],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.springframework.boot",
|
|
||||||
type: "SpringApplication",
|
|
||||||
name: "run",
|
|
||||||
signature: "(Class,String[])",
|
|
||||||
usages:
|
|
||||||
usages[
|
|
||||||
"org.springframework.boot.SpringApplication#run(Class,String[])"
|
|
||||||
],
|
|
||||||
input: "Argument[1]",
|
|
||||||
classification: undefined,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("parsePredictedClassifications", () => {
|
|
||||||
const predictions: Method[] = [
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "createQuery",
|
|
||||||
signature: "(String)",
|
|
||||||
usages: ["createQuery(...)", "createQuery(...)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: {
|
|
||||||
type: ClassificationType.Sink,
|
|
||||||
kind: "sql injection sink",
|
|
||||||
explanation: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "executeScalar",
|
|
||||||
signature: "(Class)",
|
|
||||||
usages: ["executeScalar(...)", "executeScalar(...)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: {
|
|
||||||
type: ClassificationType.Neutral,
|
|
||||||
kind: "",
|
|
||||||
explanation: "not a sink",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: ["new Sql2o(...)"],
|
|
||||||
input: "Argument[0]",
|
|
||||||
classification: {
|
|
||||||
type: ClassificationType.Neutral,
|
|
||||||
kind: "",
|
|
||||||
explanation: "not a sink",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: ["new Sql2o(...)"],
|
|
||||||
input: "Argument[1]",
|
|
||||||
classification: {
|
|
||||||
type: ClassificationType.Sink,
|
|
||||||
kind: "sql injection sink",
|
|
||||||
explanation: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
package: "org.sql2o",
|
|
||||||
type: "Sql2o",
|
|
||||||
name: "Sql2o",
|
|
||||||
signature: "(String,String,String)",
|
|
||||||
usages: ["new Sql2o(...)"],
|
|
||||||
input: "Argument[2]",
|
|
||||||
classification: {
|
|
||||||
type: ClassificationType.Sink,
|
|
||||||
kind: "sql injection sink",
|
|
||||||
explanation: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
it("correctly parses the output", () => {
|
|
||||||
expect(parsePredictedClassifications(predictions)).toEqual({
|
|
||||||
"org.sql2o.Sql2o#createQuery(String)": {
|
|
||||||
type: "sink",
|
|
||||||
kind: "sql injection sink",
|
|
||||||
input: "Argument[0]",
|
|
||||||
output: "",
|
|
||||||
provenance: "ai-generated",
|
|
||||||
signature: "org.sql2o.Sql2o#createQuery(String)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "createQuery",
|
|
||||||
methodParameters: "(String)",
|
|
||||||
},
|
|
||||||
"org.sql2o.Sql2o#executeScalar(Class)": {
|
|
||||||
type: "neutral",
|
|
||||||
kind: "summary",
|
|
||||||
input: "",
|
|
||||||
output: "",
|
|
||||||
provenance: "ai-generated",
|
|
||||||
signature: "org.sql2o.Sql2o#executeScalar(Class)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "executeScalar",
|
|
||||||
methodParameters: "(Class)",
|
|
||||||
},
|
|
||||||
"org.sql2o.Sql2o#Sql2o(String,String,String)": {
|
|
||||||
type: "sink",
|
|
||||||
kind: "sql injection sink",
|
|
||||||
input: "Argument[1]",
|
|
||||||
output: "",
|
|
||||||
provenance: "ai-generated",
|
|
||||||
signature: "org.sql2o.Sql2o#Sql2o(String,String,String)",
|
|
||||||
packageName: "org.sql2o",
|
|
||||||
typeName: "Sql2o",
|
|
||||||
methodName: "Sql2o",
|
|
||||||
methodParameters: "(String,String,String)",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("compareInputOutput", () => {
|
|
||||||
it("with two small numeric arguments", () => {
|
|
||||||
expect(
|
|
||||||
compareInputOutput("Argument[0]", "Argument[1]"),
|
|
||||||
).toBeLessThanOrEqual(-1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with one larger non-alphabetic argument", () => {
|
|
||||||
expect(
|
|
||||||
compareInputOutput("Argument[10]", "Argument[2]"),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with one non-numeric arguments", () => {
|
|
||||||
expect(
|
|
||||||
compareInputOutput("Argument[5]", "Argument[this]"),
|
|
||||||
).toBeLessThanOrEqual(-1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with two non-numeric arguments", () => {
|
|
||||||
expect(
|
|
||||||
compareInputOutput("ReturnValue", "Argument[this]"),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with one unknown argument in the a position", () => {
|
|
||||||
expect(
|
|
||||||
compareInputOutput("FooBar", "Argument[this]"),
|
|
||||||
).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with one unknown argument in the b position", () => {
|
|
||||||
expect(compareInputOutput("Argument[this]", "FooBar")).toBeLessThanOrEqual(
|
|
||||||
-1,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with one empty string arguments", () => {
|
|
||||||
expect(compareInputOutput("Argument[5]", "")).toBeLessThanOrEqual(-1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("with two unknown arguments", () => {
|
|
||||||
expect(compareInputOutput("FooBar", "BarFoo")).toBeGreaterThanOrEqual(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Reference in New Issue
Block a user