circle-ir 3.167.0 → 3.173.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/analysis/config-loader.d.ts.map +1 -1
- package/dist/analysis/config-loader.js +79 -0
- package/dist/analysis/config-loader.js.map +1 -1
- package/dist/analysis/passes/cross-file-pass.d.ts.map +1 -1
- package/dist/analysis/passes/cross-file-pass.js +3 -2
- package/dist/analysis/passes/cross-file-pass.js.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.d.ts.map +1 -1
- package/dist/analysis/passes/interprocedural-pass.js +3 -2
- package/dist/analysis/passes/interprocedural-pass.js.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.d.ts.map +1 -1
- package/dist/analysis/passes/sink-filter-pass.js +23 -2
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.d.ts.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +29 -15
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/sanitizer-index.d.ts +34 -0
- package/dist/analysis/sanitizer-index.d.ts.map +1 -0
- package/dist/analysis/sanitizer-index.js +48 -0
- package/dist/analysis/sanitizer-index.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts.map +1 -1
- package/dist/analysis/taint-matcher.js +93 -45
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analysis/taint-propagation.d.ts.map +1 -1
- package/dist/analysis/taint-propagation.js +5 -2
- package/dist/analysis/taint-propagation.js.map +1 -1
- package/dist/analyzer.d.ts.map +1 -1
- package/dist/analyzer.js +51 -2
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +319 -95
- package/dist/core/circle-ir-core.cjs +193 -72
- package/dist/core/circle-ir-core.js +193 -72
- package/dist/core/extractors/cfg.d.ts +11 -1
- package/dist/core/extractors/cfg.d.ts.map +1 -1
- package/dist/core/extractors/cfg.js +18 -9
- package/dist/core/extractors/cfg.js.map +1 -1
- package/dist/core/extractors/imports.d.ts +9 -1
- package/dist/core/extractors/imports.d.ts.map +1 -1
- package/dist/core/extractors/imports.js +29 -22
- package/dist/core/extractors/imports.js.map +1 -1
- package/package.json +1 -1
|
@@ -7497,33 +7497,33 @@ function detectLanguage2(tree) {
|
|
|
7497
7497
|
if (pythonScore > jsScore && pythonScore > javaScore) return "python";
|
|
7498
7498
|
return jsScore > javaScore ? "javascript" : "java";
|
|
7499
7499
|
}
|
|
7500
|
-
function extractImports(tree, language) {
|
|
7500
|
+
function extractImports(tree, language, cache) {
|
|
7501
7501
|
const effectiveLanguage = language ?? detectLanguage2(tree);
|
|
7502
7502
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
7503
7503
|
const isPython = effectiveLanguage === "python";
|
|
7504
7504
|
const isRust = effectiveLanguage === "rust";
|
|
7505
7505
|
if (effectiveLanguage === "go") {
|
|
7506
|
-
return extractGoImports(tree);
|
|
7506
|
+
return extractGoImports(tree, cache);
|
|
7507
7507
|
}
|
|
7508
7508
|
if (isRust) {
|
|
7509
|
-
return extractRustImports(tree);
|
|
7509
|
+
return extractRustImports(tree, cache);
|
|
7510
7510
|
}
|
|
7511
7511
|
if (isPython) {
|
|
7512
|
-
return extractPythonImports(tree);
|
|
7512
|
+
return extractPythonImports(tree, cache);
|
|
7513
7513
|
}
|
|
7514
7514
|
if (isJavaScript) {
|
|
7515
|
-
return extractJavaScriptImports(tree);
|
|
7515
|
+
return extractJavaScriptImports(tree, cache);
|
|
7516
7516
|
}
|
|
7517
|
-
return extractJavaImports(tree);
|
|
7517
|
+
return extractJavaImports(tree, cache);
|
|
7518
7518
|
}
|
|
7519
|
-
function extractJavaScriptImports(tree) {
|
|
7519
|
+
function extractJavaScriptImports(tree, cache) {
|
|
7520
7520
|
const imports = [];
|
|
7521
|
-
const importStatements =
|
|
7521
|
+
const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
|
|
7522
7522
|
for (const importStmt of importStatements) {
|
|
7523
7523
|
const importInfos = extractJSImportInfo(importStmt);
|
|
7524
7524
|
imports.push(...importInfos);
|
|
7525
7525
|
}
|
|
7526
|
-
const exportStatements =
|
|
7526
|
+
const exportStatements = getNodesFromCache(tree.rootNode, "export_statement", cache);
|
|
7527
7527
|
for (const exportStmt of exportStatements) {
|
|
7528
7528
|
const sourceNode = exportStmt.childForFieldName("source");
|
|
7529
7529
|
if (!sourceNode) continue;
|
|
@@ -7537,13 +7537,13 @@ function extractJavaScriptImports(tree) {
|
|
|
7537
7537
|
line_number: exportStmt.startPosition.row + 1
|
|
7538
7538
|
});
|
|
7539
7539
|
}
|
|
7540
|
-
const requireCalls = findRequireCalls(tree);
|
|
7540
|
+
const requireCalls = findRequireCalls(tree, cache);
|
|
7541
7541
|
imports.push(...requireCalls);
|
|
7542
7542
|
return imports;
|
|
7543
7543
|
}
|
|
7544
|
-
function extractJavaImports(tree) {
|
|
7544
|
+
function extractJavaImports(tree, cache) {
|
|
7545
7545
|
const imports = [];
|
|
7546
|
-
const importDecls =
|
|
7546
|
+
const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
7547
7547
|
for (const importDecl of importDecls) {
|
|
7548
7548
|
const importInfo = extractJavaImportInfo(importDecl);
|
|
7549
7549
|
if (importInfo) {
|
|
@@ -7691,9 +7691,9 @@ function extractJSImportInfo(node) {
|
|
|
7691
7691
|
}
|
|
7692
7692
|
return imports;
|
|
7693
7693
|
}
|
|
7694
|
-
function findRequireCalls(tree) {
|
|
7694
|
+
function findRequireCalls(tree, cache) {
|
|
7695
7695
|
const imports = [];
|
|
7696
|
-
const callExpressions =
|
|
7696
|
+
const callExpressions = getNodesFromCache(tree.rootNode, "call_expression", cache);
|
|
7697
7697
|
for (const call of callExpressions) {
|
|
7698
7698
|
const funcNode = call.childForFieldName("function");
|
|
7699
7699
|
if (!funcNode || getNodeText(funcNode) !== "require") continue;
|
|
@@ -7807,14 +7807,14 @@ function parseImportPath(fullPath, isWildcard) {
|
|
|
7807
7807
|
fromPackage: fullPath.substring(0, lastDot)
|
|
7808
7808
|
};
|
|
7809
7809
|
}
|
|
7810
|
-
function extractPythonImports(tree) {
|
|
7810
|
+
function extractPythonImports(tree, cache) {
|
|
7811
7811
|
const imports = [];
|
|
7812
|
-
const importStatements =
|
|
7812
|
+
const importStatements = getNodesFromCache(tree.rootNode, "import_statement", cache);
|
|
7813
7813
|
for (const stmt of importStatements) {
|
|
7814
7814
|
const importInfos = extractPythonImportStatement(stmt);
|
|
7815
7815
|
imports.push(...importInfos);
|
|
7816
7816
|
}
|
|
7817
|
-
const importFromStatements =
|
|
7817
|
+
const importFromStatements = getNodesFromCache(tree.rootNode, "import_from_statement", cache);
|
|
7818
7818
|
for (const stmt of importFromStatements) {
|
|
7819
7819
|
const importInfos = extractPythonFromImportStatement(stmt);
|
|
7820
7820
|
imports.push(...importInfos);
|
|
@@ -7908,9 +7908,9 @@ function extractPythonFromImportStatement(node) {
|
|
|
7908
7908
|
}
|
|
7909
7909
|
return imports;
|
|
7910
7910
|
}
|
|
7911
|
-
function extractRustImports(tree) {
|
|
7911
|
+
function extractRustImports(tree, cache) {
|
|
7912
7912
|
const imports = [];
|
|
7913
|
-
const useDecls =
|
|
7913
|
+
const useDecls = getNodesFromCache(tree.rootNode, "use_declaration", cache);
|
|
7914
7914
|
for (const useDecl of useDecls) {
|
|
7915
7915
|
const useImports = extractRustUseDecl(useDecl);
|
|
7916
7916
|
imports.push(...useImports);
|
|
@@ -8052,9 +8052,9 @@ function extractRustScopedUseList(node, lineNumber) {
|
|
|
8052
8052
|
}
|
|
8053
8053
|
return imports;
|
|
8054
8054
|
}
|
|
8055
|
-
function extractGoImports(tree) {
|
|
8055
|
+
function extractGoImports(tree, cache) {
|
|
8056
8056
|
const imports = [];
|
|
8057
|
-
const importDecls =
|
|
8057
|
+
const importDecls = getNodesFromCache(tree.rootNode, "import_declaration", cache);
|
|
8058
8058
|
for (const decl of importDecls) {
|
|
8059
8059
|
const singleSpec = findGoChildByType(decl, "import_spec");
|
|
8060
8060
|
if (singleSpec) {
|
|
@@ -8189,7 +8189,7 @@ function detectLanguage3(tree) {
|
|
|
8189
8189
|
}
|
|
8190
8190
|
return jsScore > javaScore ? "javascript" : "java";
|
|
8191
8191
|
}
|
|
8192
|
-
function buildCFG(tree, language) {
|
|
8192
|
+
function buildCFG(tree, language, cache) {
|
|
8193
8193
|
const effectiveLanguage = language ?? detectLanguage3(tree);
|
|
8194
8194
|
const isJavaScript = effectiveLanguage === "javascript" || effectiveLanguage === "typescript" || effectiveLanguage === "tsx";
|
|
8195
8195
|
const allBlocks = [];
|
|
@@ -8203,11 +8203,11 @@ function buildCFG(tree, language) {
|
|
|
8203
8203
|
}
|
|
8204
8204
|
if (isJavaScript) {
|
|
8205
8205
|
const functions = [
|
|
8206
|
-
...
|
|
8207
|
-
...
|
|
8208
|
-
...
|
|
8209
|
-
...
|
|
8210
|
-
...
|
|
8206
|
+
...getNodesFromCache(tree.rootNode, "function_declaration", cache),
|
|
8207
|
+
...getNodesFromCache(tree.rootNode, "arrow_function", cache),
|
|
8208
|
+
...getNodesFromCache(tree.rootNode, "method_definition", cache),
|
|
8209
|
+
...getNodesFromCache(tree.rootNode, "function", cache),
|
|
8210
|
+
...getNodesFromCache(tree.rootNode, "function_expression", cache)
|
|
8211
8211
|
];
|
|
8212
8212
|
for (const func2 of functions) {
|
|
8213
8213
|
const body2 = func2.childForFieldName("body");
|
|
@@ -8229,8 +8229,8 @@ function buildCFG(tree, language) {
|
|
|
8229
8229
|
}
|
|
8230
8230
|
} else {
|
|
8231
8231
|
const methods = [
|
|
8232
|
-
...
|
|
8233
|
-
...
|
|
8232
|
+
...getNodesFromCache(tree.rootNode, "method_declaration", cache),
|
|
8233
|
+
...getNodesFromCache(tree.rootNode, "constructor_declaration", cache)
|
|
8234
8234
|
];
|
|
8235
8235
|
for (const method of methods) {
|
|
8236
8236
|
const body2 = method.childForFieldName("body");
|
|
@@ -11134,7 +11134,86 @@ var DEFAULT_SOURCES = [
|
|
|
11134
11134
|
{ method: "recv", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11135
11135
|
{ method: "read", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11136
11136
|
{ method: "read_to_end", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11137
|
-
{ method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true }
|
|
11137
|
+
{ method: "read_to_string", class: "TcpStream", type: "network_input", severity: "high", return_tainted: true },
|
|
11138
|
+
// =========================================================================
|
|
11139
|
+
// Modern taint sources (cognium-dev #242 — 3.170.0)
|
|
11140
|
+
// GraphQL resolver args, gRPC request metadata, cache reads (second-order
|
|
11141
|
+
// taint), and JWT claims (unverified decode). These attacker-influenced
|
|
11142
|
+
// surfaces were previously silent — a resolver that concatenated an
|
|
11143
|
+
// `Args`-annotated field into a SQL string produced zero flows.
|
|
11144
|
+
// =========================================================================
|
|
11145
|
+
// --- GraphQL resolver argument sources (JS / TS — Apollo, TypeGraphQL, NestJS) ---
|
|
11146
|
+
// TypeGraphQL / NestJS annotate query/mutation/subscription methods; every
|
|
11147
|
+
// parameter on the annotated method carries attacker input from the GraphQL
|
|
11148
|
+
// POST body's `variables` field.
|
|
11149
|
+
{ method_annotation: "Query", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11150
|
+
{ method_annotation: "Mutation", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11151
|
+
{ method_annotation: "Subscription", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11152
|
+
{ method_annotation: "FieldResolver", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11153
|
+
{ method_annotation: "ResolveField", type: "http_body", severity: "high", languages: ["javascript", "typescript"] },
|
|
11154
|
+
// Parameter-level: `@Arg('id') id: string`, `@Args('input') input: FooDto`.
|
|
11155
|
+
{ annotation: "Arg", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
|
|
11156
|
+
{ annotation: "Args", type: "http_param", severity: "high", param_tainted: true, languages: ["javascript", "typescript"] },
|
|
11157
|
+
// --- GraphQL resolver argument sources (Python — Strawberry, Graphene, Ariadne) ---
|
|
11158
|
+
{ method_annotation: "strawberry.field", type: "http_body", severity: "high", languages: ["python"] },
|
|
11159
|
+
{ method_annotation: "strawberry.mutation", type: "http_body", severity: "high", languages: ["python"] },
|
|
11160
|
+
{ method_annotation: "strawberry.subscription", type: "http_body", severity: "high", languages: ["python"] },
|
|
11161
|
+
// --- GraphQL resolver argument sources (Java — Netflix DGS, SPQR, graphql-java-annotations) ---
|
|
11162
|
+
{ method_annotation: "DgsQuery", type: "http_body", severity: "high", languages: ["java"] },
|
|
11163
|
+
{ method_annotation: "DgsMutation", type: "http_body", severity: "high", languages: ["java"] },
|
|
11164
|
+
{ method_annotation: "DgsSubscription", type: "http_body", severity: "high", languages: ["java"] },
|
|
11165
|
+
{ method_annotation: "DgsData", type: "http_body", severity: "high", languages: ["java"] },
|
|
11166
|
+
{ method_annotation: "GraphQLQuery", type: "http_body", severity: "high", languages: ["java"] },
|
|
11167
|
+
{ method_annotation: "GraphQLMutation", type: "http_body", severity: "high", languages: ["java"] },
|
|
11168
|
+
{ annotation: "InputArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11169
|
+
{ annotation: "GraphQLArgument", type: "http_param", severity: "high", param_tainted: true, languages: ["java"] },
|
|
11170
|
+
// --- gRPC request metadata (Python — grpcio) ---
|
|
11171
|
+
// `context.invocation_metadata()` returns the caller-supplied metadata tuple.
|
|
11172
|
+
// No `class` filter — receiver names vary (`context`, `ctx`, `servicer_context`).
|
|
11173
|
+
{ method: "invocation_metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11174
|
+
// --- gRPC request metadata (JS / TS — @grpc/grpc-js) ---
|
|
11175
|
+
// `call.metadata.get(key)` / `call.metadata.getMap()` inside a service handler.
|
|
11176
|
+
{ method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11177
|
+
{ method: "getMap", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11178
|
+
// --- gRPC request metadata (Go — google.golang.org/grpc/metadata) ---
|
|
11179
|
+
// `md, _ := metadata.FromIncomingContext(ctx)` pulls the caller's headers.
|
|
11180
|
+
{ method: "FromIncomingContext", class: "metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] },
|
|
11181
|
+
// --- gRPC request metadata (Java — io.grpc.Metadata) ---
|
|
11182
|
+
// Server-side interceptor receives `Metadata headers`; `headers.get(KEY)`
|
|
11183
|
+
// returns caller-supplied header values.
|
|
11184
|
+
{ method: "get", class: "Metadata", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
11185
|
+
// --- Cache reads (second-order taint — Redis / Memcached / Django cache) ---
|
|
11186
|
+
// The cache round-trip is a canonical second-order sink: whatever was
|
|
11187
|
+
// written previously (potentially attacker-controlled) resurfaces on read.
|
|
11188
|
+
// Severity 'medium' — cache contents are usually filtered by the writer but
|
|
11189
|
+
// the read side often forgets that guarantee. Class-scoped to `Redis`,
|
|
11190
|
+
// `Jedis`, `cache` to avoid colliding with generic `Map.get()`.
|
|
11191
|
+
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11192
|
+
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11193
|
+
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11194
|
+
{ method: "lrange", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11195
|
+
{ method: "get", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11196
|
+
{ method: "get_many", class: "cache", type: "db_input", severity: "medium", return_tainted: true, languages: ["python"] },
|
|
11197
|
+
{ method: "get", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11198
|
+
{ method: "hget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11199
|
+
{ method: "mget", class: "Redis", type: "db_input", severity: "medium", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11200
|
+
{ method: "get", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
11201
|
+
{ method: "hget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
11202
|
+
{ method: "mget", class: "Jedis", type: "db_input", severity: "medium", return_tainted: true, languages: ["java"] },
|
|
11203
|
+
// --- JWT claims (unverified decode — PyJWT / jose / jsonwebtoken / auth0 java-jwt / golang-jwt) ---
|
|
11204
|
+
// A JWT's payload is *always* attacker-authored. Even after verification
|
|
11205
|
+
// the *contents* of the claims (username, role, custom fields) are not
|
|
11206
|
+
// trusted for downstream flows into SQL, HTML, shell, etc. The `decode`
|
|
11207
|
+
// variants here return the parsed claims dictionary/object.
|
|
11208
|
+
{ method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11209
|
+
{ method: "get_unverified_claims", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11210
|
+
{ method: "get_unverified_header", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["python"] },
|
|
11211
|
+
{ method: "decode", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11212
|
+
{ method: "decode", class: "jsonwebtoken", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11213
|
+
{ method: "decodeJwt", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11214
|
+
{ method: "decodeProtectedHeader", class: "jose", type: "http_header", severity: "high", return_tainted: true, languages: ["javascript", "typescript"] },
|
|
11215
|
+
{ method: "decode", class: "JWT", type: "http_header", severity: "high", return_tainted: true, languages: ["java"] },
|
|
11216
|
+
{ method: "ParseUnverified", class: "jwt", type: "http_header", severity: "high", return_tainted: true, languages: ["go"] }
|
|
11138
11217
|
];
|
|
11139
11218
|
var DEFAULT_SINKS = [
|
|
11140
11219
|
// SQL Injection (CWE-89)
|
|
@@ -13274,6 +13353,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13274
13353
|
for (const param of method.parameters) {
|
|
13275
13354
|
for (const pattern of patterns) {
|
|
13276
13355
|
if (pattern.annotation && pattern.param_tainted) {
|
|
13356
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13357
|
+
continue;
|
|
13358
|
+
}
|
|
13277
13359
|
if (matchesAnnotation(param.annotations, pattern.annotation)) {
|
|
13278
13360
|
const paramLine = param.line ?? method.start_line;
|
|
13279
13361
|
sources.push({
|
|
@@ -13294,6 +13376,9 @@ function findSources(calls, types, patterns, sourceLines, language) {
|
|
|
13294
13376
|
for (const method of type.methods) {
|
|
13295
13377
|
for (const pattern of patterns) {
|
|
13296
13378
|
if (!pattern.method_annotation) continue;
|
|
13379
|
+
if (pattern.languages && pattern.languages.length > 0 && language !== void 0 && !pattern.languages.includes(language)) {
|
|
13380
|
+
continue;
|
|
13381
|
+
}
|
|
13297
13382
|
if (!matchesAnnotation(method.annotations, pattern.method_annotation)) continue;
|
|
13298
13383
|
for (const param of method.parameters) {
|
|
13299
13384
|
const paramLine = param.line ?? method.start_line;
|
|
@@ -13558,7 +13643,7 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
13558
13643
|
const secondArg = call.arguments.find((a) => a.position === 1);
|
|
13559
13644
|
if (secondArg?.expression) {
|
|
13560
13645
|
const expr = secondArg.expression.trim();
|
|
13561
|
-
if (expr.startsWith("[")) {
|
|
13646
|
+
if (expr.startsWith("[") && !/^\[\s*\]$/.test(expr)) {
|
|
13562
13647
|
return true;
|
|
13563
13648
|
}
|
|
13564
13649
|
}
|
|
@@ -13999,52 +14084,74 @@ function matchesSourcePattern(call, pattern) {
|
|
|
13999
14084
|
}
|
|
14000
14085
|
return false;
|
|
14001
14086
|
}
|
|
14087
|
+
var JS_SOURCE_PATTERN_CACHE = /* @__PURE__ */ new WeakMap();
|
|
14088
|
+
var JS_FALLBACK_COMPILED = (() => {
|
|
14089
|
+
const bases = [
|
|
14090
|
+
{ base: "req\\.params", sourceType: "http_param" },
|
|
14091
|
+
{ base: "req\\.query", sourceType: "http_param" },
|
|
14092
|
+
{ base: "req\\.body", sourceType: "http_body" },
|
|
14093
|
+
{ base: "req\\.headers", sourceType: "http_header" },
|
|
14094
|
+
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
14095
|
+
{ base: "req\\.url", sourceType: "http_path" },
|
|
14096
|
+
{ base: "req\\.path", sourceType: "http_path" },
|
|
14097
|
+
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
14098
|
+
{ base: "req\\.file", sourceType: "file_input" },
|
|
14099
|
+
{ base: "req\\.files", sourceType: "file_input" },
|
|
14100
|
+
{ base: "request\\.params", sourceType: "http_param" },
|
|
14101
|
+
{ base: "request\\.query", sourceType: "http_param" },
|
|
14102
|
+
{ base: "request\\.body", sourceType: "http_body" },
|
|
14103
|
+
{ base: "request\\.headers", sourceType: "http_header" },
|
|
14104
|
+
{ base: "process\\.env", sourceType: "env_input" },
|
|
14105
|
+
{ base: "process\\.argv", sourceType: "io_input" },
|
|
14106
|
+
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
14107
|
+
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
14108
|
+
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
14109
|
+
];
|
|
14110
|
+
const exact = [];
|
|
14111
|
+
const contained = [];
|
|
14112
|
+
for (const { base, sourceType } of bases) {
|
|
14113
|
+
exact.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
14114
|
+
contained.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
14115
|
+
}
|
|
14116
|
+
return { exact, contained };
|
|
14117
|
+
})();
|
|
14118
|
+
function compileJsSourcePatterns(sourcePatterns) {
|
|
14119
|
+
const cached = JS_SOURCE_PATTERN_CACHE.get(sourcePatterns);
|
|
14120
|
+
if (cached) return cached;
|
|
14121
|
+
const exact = [];
|
|
14122
|
+
const contained = [];
|
|
14123
|
+
for (const sp of sourcePatterns) {
|
|
14124
|
+
if (sp.property && sp.object && sp.property_tainted) {
|
|
14125
|
+
exact.push({
|
|
14126
|
+
pattern: new RegExp(`^${sp.object}\\.${sp.property}\\b`),
|
|
14127
|
+
sourceType: sp.type
|
|
14128
|
+
});
|
|
14129
|
+
contained.push({
|
|
14130
|
+
pattern: new RegExp(`\\b${sp.object}\\.${sp.property}\\b`),
|
|
14131
|
+
sourceType: sp.type
|
|
14132
|
+
});
|
|
14133
|
+
}
|
|
14134
|
+
}
|
|
14135
|
+
const compiled = { exact, contained };
|
|
14136
|
+
JS_SOURCE_PATTERN_CACHE.set(sourcePatterns, compiled);
|
|
14137
|
+
return compiled;
|
|
14138
|
+
}
|
|
14002
14139
|
function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
const exactRegex = new RegExp(`^${sp.object}\\.${sp.property}\\b`);
|
|
14009
|
-
exactPatterns.push({ pattern: exactRegex, sourceType: sp.type });
|
|
14010
|
-
const containedRegex = new RegExp(`\\b${sp.object}\\.${sp.property}\\b`);
|
|
14011
|
-
containedPatterns.push({ pattern: containedRegex, sourceType: sp.type });
|
|
14012
|
-
}
|
|
14013
|
-
}
|
|
14014
|
-
}
|
|
14015
|
-
if (exactPatterns.length === 0) {
|
|
14016
|
-
const basePatterns = [
|
|
14017
|
-
{ base: "req\\.params", sourceType: "http_param" },
|
|
14018
|
-
{ base: "req\\.query", sourceType: "http_param" },
|
|
14019
|
-
{ base: "req\\.body", sourceType: "http_body" },
|
|
14020
|
-
{ base: "req\\.headers", sourceType: "http_header" },
|
|
14021
|
-
{ base: "req\\.cookies", sourceType: "http_cookie" },
|
|
14022
|
-
{ base: "req\\.url", sourceType: "http_path" },
|
|
14023
|
-
{ base: "req\\.path", sourceType: "http_path" },
|
|
14024
|
-
{ base: "req\\.originalUrl", sourceType: "http_path" },
|
|
14025
|
-
{ base: "req\\.file", sourceType: "file_input" },
|
|
14026
|
-
{ base: "req\\.files", sourceType: "file_input" },
|
|
14027
|
-
{ base: "request\\.params", sourceType: "http_param" },
|
|
14028
|
-
{ base: "request\\.query", sourceType: "http_param" },
|
|
14029
|
-
{ base: "request\\.body", sourceType: "http_body" },
|
|
14030
|
-
{ base: "request\\.headers", sourceType: "http_header" },
|
|
14031
|
-
{ base: "process\\.env", sourceType: "env_input" },
|
|
14032
|
-
{ base: "process\\.argv", sourceType: "io_input" },
|
|
14033
|
-
{ base: "ctx\\.query", sourceType: "http_param" },
|
|
14034
|
-
{ base: "ctx\\.params", sourceType: "http_param" },
|
|
14035
|
-
{ base: "ctx\\.request", sourceType: "http_body" }
|
|
14036
|
-
];
|
|
14037
|
-
for (const { base, sourceType } of basePatterns) {
|
|
14038
|
-
exactPatterns.push({ pattern: new RegExp(`^${base}\\b`), sourceType });
|
|
14039
|
-
containedPatterns.push({ pattern: new RegExp(`\\b${base}\\b`), sourceType });
|
|
14140
|
+
let compiled;
|
|
14141
|
+
if (sourcePatterns && sourcePatterns.length > 0) {
|
|
14142
|
+
compiled = compileJsSourcePatterns(sourcePatterns);
|
|
14143
|
+
if (compiled.exact.length === 0) {
|
|
14144
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
14040
14145
|
}
|
|
14146
|
+
} else {
|
|
14147
|
+
compiled = JS_FALLBACK_COMPILED;
|
|
14041
14148
|
}
|
|
14042
|
-
for (const { pattern, sourceType } of
|
|
14149
|
+
for (const { pattern, sourceType } of compiled.exact) {
|
|
14043
14150
|
if (pattern.test(argExpression)) {
|
|
14044
14151
|
return { isTainted: true, sourceType };
|
|
14045
14152
|
}
|
|
14046
14153
|
}
|
|
14047
|
-
for (const { pattern, sourceType } of
|
|
14154
|
+
for (const { pattern, sourceType } of compiled.contained) {
|
|
14048
14155
|
if (pattern.test(argExpression)) {
|
|
14049
14156
|
return { isTainted: true, sourceType };
|
|
14050
14157
|
}
|
|
@@ -14899,6 +15006,64 @@ function formatCallCode(call) {
|
|
|
14899
15006
|
return `${call.method_name}(${args2})`;
|
|
14900
15007
|
}
|
|
14901
15008
|
|
|
15009
|
+
// src/analysis/non-executable-lines.ts
|
|
15010
|
+
function isNonExecutableSourceLine(sourceCode, line, language) {
|
|
15011
|
+
if (!sourceCode || line < 1) return false;
|
|
15012
|
+
const lines = sourceCode.split("\n");
|
|
15013
|
+
if (line > lines.length) return false;
|
|
15014
|
+
const raw = lines[line - 1] ?? "";
|
|
15015
|
+
const trimmed = raw.trim();
|
|
15016
|
+
if (trimmed === "") return true;
|
|
15017
|
+
const lang = language.toLowerCase();
|
|
15018
|
+
if (trimmed.startsWith("//") || trimmed.startsWith("/*") || trimmed.startsWith("*/") || trimmed.startsWith("*") || trimmed === "/**") {
|
|
15019
|
+
return true;
|
|
15020
|
+
}
|
|
15021
|
+
switch (lang) {
|
|
15022
|
+
case "java":
|
|
15023
|
+
case "javascript":
|
|
15024
|
+
case "typescript":
|
|
15025
|
+
case "go":
|
|
15026
|
+
case "rust":
|
|
15027
|
+
return isNonExecutableCurly(trimmed);
|
|
15028
|
+
case "python":
|
|
15029
|
+
return isNonExecutablePython(trimmed);
|
|
15030
|
+
default:
|
|
15031
|
+
return false;
|
|
15032
|
+
}
|
|
15033
|
+
}
|
|
15034
|
+
function isNonExecutableCurly(trimmed) {
|
|
15035
|
+
if (/^(import|package|use|from)\s/.test(trimmed)) return true;
|
|
15036
|
+
if (/^@[A-Za-z_][\w.]*(?:\s*\([^)]*\))?\s*$/.test(trimmed)) return true;
|
|
15037
|
+
if (/^(?:(?:public|private|protected|internal)\s+)?(?:static\s+)?(?:final\s+|readonly\s+|const\s+)(?:[A-Za-z_][\w<>.\[\]]*\s+)?[A-Za-z_]\w*(?:\s*:\s*[^=]+)?\s*=\s*(?:["'`][^"'`]*["'`]|-?\d+(?:\.\d+)?)\s*;?\s*$/.test(
|
|
15038
|
+
trimmed
|
|
15039
|
+
)) {
|
|
15040
|
+
return true;
|
|
15041
|
+
}
|
|
15042
|
+
if (/^(?:const|let|var|static)\s+[A-Za-z_]\w*(?:\s*:\s*[^=]+)?\s*=\s*(?:["'`][^"'`]*["'`]|-?\d+(?:\.\d+)?)\s*;?\s*$/.test(
|
|
15043
|
+
trimmed
|
|
15044
|
+
)) {
|
|
15045
|
+
return true;
|
|
15046
|
+
}
|
|
15047
|
+
if (/^(?:(?:public|private|protected)\s+)?(?:static\s+)?(?:final\s+)[A-Za-z_][\w<>.\[\]]*\s+[A-Za-z_]\w*\s*;\s*$/.test(
|
|
15048
|
+
trimmed
|
|
15049
|
+
)) {
|
|
15050
|
+
return true;
|
|
15051
|
+
}
|
|
15052
|
+
return false;
|
|
15053
|
+
}
|
|
15054
|
+
function isNonExecutablePython(trimmed) {
|
|
15055
|
+
if (/^(import|from)\s/.test(trimmed)) return true;
|
|
15056
|
+
if (trimmed.startsWith("#")) return true;
|
|
15057
|
+
if (trimmed === '"""' || trimmed === "'''") return true;
|
|
15058
|
+
if (/^@[A-Za-z_][\w.]*(?:\s*\([^)]*\))?\s*$/.test(trimmed)) return true;
|
|
15059
|
+
if (/^[A-Z_][A-Z0-9_]*\s*(?::\s*[^=]+)?\s*=\s*(?:["'][^"']*["']|-?\d+(?:\.\d+)?)\s*$/.test(
|
|
15060
|
+
trimmed
|
|
15061
|
+
)) {
|
|
15062
|
+
return true;
|
|
15063
|
+
}
|
|
15064
|
+
return false;
|
|
15065
|
+
}
|
|
15066
|
+
|
|
14902
15067
|
// src/analysis/findings.ts
|
|
14903
15068
|
function canSourceReachSink(sourceType, sinkType) {
|
|
14904
15069
|
const sourceToSinkMapping = {
|
|
@@ -16293,6 +16458,20 @@ function walkBackwardDefs(startDefId, chainsByToDef, defById, options = {}) {
|
|
|
16293
16458
|
return { visited, lines, hopCapReached };
|
|
16294
16459
|
}
|
|
16295
16460
|
|
|
16461
|
+
// src/analysis/sanitizer-index.ts
|
|
16462
|
+
var SANITIZER_SET_CACHE = /* @__PURE__ */ new WeakMap();
|
|
16463
|
+
function getSanitizesSet(san) {
|
|
16464
|
+
let s = SANITIZER_SET_CACHE.get(san);
|
|
16465
|
+
if (!s) {
|
|
16466
|
+
s = new Set(san.sanitizes);
|
|
16467
|
+
SANITIZER_SET_CACHE.set(san, s);
|
|
16468
|
+
}
|
|
16469
|
+
return s;
|
|
16470
|
+
}
|
|
16471
|
+
function sanitizerCoversSink(san, sinkType) {
|
|
16472
|
+
return getSanitizesSet(san).has(sinkType);
|
|
16473
|
+
}
|
|
16474
|
+
|
|
16296
16475
|
// src/analysis/taint-propagation.ts
|
|
16297
16476
|
function buildSanitizersByLine(sanitizers) {
|
|
16298
16477
|
const out2 = /* @__PURE__ */ new Map();
|
|
@@ -16529,7 +16708,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
16529
16708
|
if (sanitizersAtTarget && sanitizersAtTarget.length > 0) {
|
|
16530
16709
|
for (const san of sanitizersAtTarget) {
|
|
16531
16710
|
if (isKnownSinkType) {
|
|
16532
|
-
if (san
|
|
16711
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
16533
16712
|
return { sanitized: true, sanitizer: san };
|
|
16534
16713
|
}
|
|
16535
16714
|
} else if (san.sanitizes.length > 0) {
|
|
@@ -16551,7 +16730,7 @@ function checkSanitized(fromLine, toLine, sinkType, sanitizersByLine, ctx) {
|
|
|
16551
16730
|
if (!sansAtLine || sansAtLine.length === 0) continue;
|
|
16552
16731
|
for (const san of sansAtLine) {
|
|
16553
16732
|
if (isKnownSinkType) {
|
|
16554
|
-
if (san
|
|
16733
|
+
if (sanitizerCoversSink(san, sinkType)) {
|
|
16555
16734
|
return { sanitized: true, sanitizer: san };
|
|
16556
16735
|
}
|
|
16557
16736
|
} else if (san.sanitizes.length > 0) {
|
|
@@ -31158,7 +31337,10 @@ var SinkFilterPass = class {
|
|
|
31158
31337
|
const taintMatcher = ctx.getResult("taint-matcher");
|
|
31159
31338
|
const constProp = ctx.getResult("constant-propagation");
|
|
31160
31339
|
const langSources = ctx.getResult("language-sources");
|
|
31161
|
-
const
|
|
31340
|
+
const mergedSources = [...taintMatcher.sources, ...langSources.additionalSources];
|
|
31341
|
+
const sources = ctx.code && ctx.language ? mergedSources.filter(
|
|
31342
|
+
(s) => !isNonExecutableSourceLine(ctx.code, s.line, ctx.language)
|
|
31343
|
+
) : mergedSources;
|
|
31162
31344
|
const sinks = [...taintMatcher.sinks];
|
|
31163
31345
|
for (const s of langSources.additionalSinks) {
|
|
31164
31346
|
if (!sinks.some((x) => x.line === s.line && x.cwe === s.cwe && x.type === s.type)) {
|
|
@@ -32133,7 +32315,7 @@ function filterSanitizedSinks(sinks, sanitizers, calls) {
|
|
|
32133
32315
|
const lineSanitizers = sanitizersByLine.get(sink.line);
|
|
32134
32316
|
if (!lineSanitizers || lineSanitizers.length === 0) return true;
|
|
32135
32317
|
for (const san of lineSanitizers) {
|
|
32136
|
-
if (san
|
|
32318
|
+
if (sanitizerCoversSink(san, sink.type)) {
|
|
32137
32319
|
const lineCalls = callsByLine.get(sink.line) ?? [];
|
|
32138
32320
|
for (const call of lineCalls) {
|
|
32139
32321
|
for (const arg of call.arguments) {
|
|
@@ -32626,15 +32808,23 @@ var TaintPropagationPass = class {
|
|
|
32626
32808
|
confidence: flow.confidence,
|
|
32627
32809
|
sanitized: flow.sanitized
|
|
32628
32810
|
}));
|
|
32811
|
+
const flowKey = (x) => `${x.source_line}|${x.sink_line}|${x.sink_type}`;
|
|
32812
|
+
const flowKeys = /* @__PURE__ */ new Set();
|
|
32813
|
+
for (const x of flows) flowKeys.add(flowKey(x));
|
|
32814
|
+
const pushIfNew = (f) => {
|
|
32815
|
+
const k = flowKey(f);
|
|
32816
|
+
if (flowKeys.has(k)) return false;
|
|
32817
|
+
flowKeys.add(k);
|
|
32818
|
+
flows.push(f);
|
|
32819
|
+
return true;
|
|
32820
|
+
};
|
|
32629
32821
|
const arrayFlows = detectArrayElementFlows(calls, sources, sinks, constProp.taintedArrayElements, constProp.unreachableLines, types) ?? [];
|
|
32630
32822
|
for (const f of arrayFlows) {
|
|
32631
|
-
|
|
32632
|
-
flows.push(f);
|
|
32633
|
-
}
|
|
32823
|
+
pushIfNew(f);
|
|
32634
32824
|
}
|
|
32635
32825
|
const collectionFlows = detectCollectionFlows(calls, sources, sinks, constProp.tainted, constProp.unreachableLines, ctx.code, types) ?? [];
|
|
32636
32826
|
for (const f of collectionFlows) {
|
|
32637
|
-
if (
|
|
32827
|
+
if (flowKeys.has(flowKey(f))) continue;
|
|
32638
32828
|
const flowForCheck = {
|
|
32639
32829
|
source: { line: f.source_line },
|
|
32640
32830
|
sink: { line: f.sink_line },
|
|
@@ -32649,19 +32839,15 @@ var TaintPropagationPass = class {
|
|
|
32649
32839
|
}
|
|
32650
32840
|
}
|
|
32651
32841
|
if (isFP) continue;
|
|
32652
|
-
|
|
32842
|
+
pushIfNew(f);
|
|
32653
32843
|
}
|
|
32654
32844
|
const paramFlows = detectParameterSinkFlows(types, calls, sources, sinks, constProp.unreachableLines, constProp.tainted, ctx.code) ?? [];
|
|
32655
32845
|
for (const f of paramFlows) {
|
|
32656
|
-
|
|
32657
|
-
flows.push(f);
|
|
32658
|
-
}
|
|
32846
|
+
pushIfNew(f);
|
|
32659
32847
|
}
|
|
32660
32848
|
const exprScanFlows = detectExpressionScanFlows(calls, sources, sinks, sanitizers, constProp.unreachableLines, constProp.tainted, ctx.code, ctx.language) ?? [];
|
|
32661
32849
|
for (const f of exprScanFlows) {
|
|
32662
|
-
if (
|
|
32663
|
-
(x) => x.source_line === f.source_line && x.sink_line === f.sink_line && x.sink_type === f.sink_type
|
|
32664
|
-
)) continue;
|
|
32850
|
+
if (flowKeys.has(flowKey(f))) continue;
|
|
32665
32851
|
const flowForCheck = {
|
|
32666
32852
|
source: { line: f.source_line },
|
|
32667
32853
|
sink: { line: f.sink_line },
|
|
@@ -32676,7 +32862,7 @@ var TaintPropagationPass = class {
|
|
|
32676
32862
|
}
|
|
32677
32863
|
}
|
|
32678
32864
|
if (isFP) continue;
|
|
32679
|
-
|
|
32865
|
+
pushIfNew(f);
|
|
32680
32866
|
}
|
|
32681
32867
|
const sanitizedNames = constProp.sanitizedVars;
|
|
32682
32868
|
let finalFlows = sanitizedNames.size === 0 ? flows : flows.filter((f) => {
|
|
@@ -32704,7 +32890,7 @@ var TaintPropagationPass = class {
|
|
|
32704
32890
|
const sansAtLine = sanitizersByLine.get(line);
|
|
32705
32891
|
if (!sansAtLine) continue;
|
|
32706
32892
|
for (const san of sansAtLine) {
|
|
32707
|
-
if (san
|
|
32893
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
32708
32894
|
return false;
|
|
32709
32895
|
}
|
|
32710
32896
|
}
|
|
@@ -32714,7 +32900,7 @@ var TaintPropagationPass = class {
|
|
|
32714
32900
|
const sansAtSink = sanitizersByLine.get(f.sink_line);
|
|
32715
32901
|
if (sansAtSink && sansAtSink.length > 0) {
|
|
32716
32902
|
for (const san of sansAtSink) {
|
|
32717
|
-
if (san
|
|
32903
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
32718
32904
|
return false;
|
|
32719
32905
|
}
|
|
32720
32906
|
}
|
|
@@ -32738,7 +32924,7 @@ var TaintPropagationPass = class {
|
|
|
32738
32924
|
const sansAtLine = sanitizersByLine.get(line);
|
|
32739
32925
|
if (!sansAtLine || sansAtLine.length === 0) continue;
|
|
32740
32926
|
for (const san of sansAtLine) {
|
|
32741
|
-
if (san
|
|
32927
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
32742
32928
|
return false;
|
|
32743
32929
|
}
|
|
32744
32930
|
}
|
|
@@ -33703,7 +33889,7 @@ var InterproceduralPass = class {
|
|
|
33703
33889
|
const sansAtLine = sanitizersByLine.get(line);
|
|
33704
33890
|
if (!sansAtLine) continue;
|
|
33705
33891
|
for (const san of sansAtLine) {
|
|
33706
|
-
if (san
|
|
33892
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
33707
33893
|
sanitizedSinkKeys.add(`${f.sink_line}:${f.sink_type}`);
|
|
33708
33894
|
return false;
|
|
33709
33895
|
}
|
|
@@ -33714,7 +33900,7 @@ var InterproceduralPass = class {
|
|
|
33714
33900
|
const sansAtSink = sanitizersByLine.get(f.sink_line);
|
|
33715
33901
|
if (!sansAtSink || sansAtSink.length === 0) return true;
|
|
33716
33902
|
for (const san of sansAtSink) {
|
|
33717
|
-
if (san
|
|
33903
|
+
if (sanitizerCoversSink(san, f.sink_type)) {
|
|
33718
33904
|
return false;
|
|
33719
33905
|
}
|
|
33720
33906
|
}
|
|
@@ -42125,7 +42311,13 @@ function getNodeTypesForLanguage(language) {
|
|
|
42125
42311
|
"jsx_self_closing_element",
|
|
42126
42312
|
"jsx_opening_element",
|
|
42127
42313
|
"jsx_attribute",
|
|
42128
|
-
"jsx_expression"
|
|
42314
|
+
"jsx_expression",
|
|
42315
|
+
// buildCFG containers (3.172.0 T2-A) — the two below are legacy
|
|
42316
|
+
// tree-sitter-javascript node types for anonymous / expression
|
|
42317
|
+
// functions; declaring them here lets `collectAllNodes` populate
|
|
42318
|
+
// the cache in the same pass so `buildCFG` skips its per-type walk.
|
|
42319
|
+
"function",
|
|
42320
|
+
"function_expression"
|
|
42129
42321
|
]);
|
|
42130
42322
|
case "bash":
|
|
42131
42323
|
return /* @__PURE__ */ new Set([
|
|
@@ -42179,7 +42371,9 @@ function getNodeTypesForLanguage(language) {
|
|
|
42179
42371
|
"field_declaration",
|
|
42180
42372
|
"import_declaration",
|
|
42181
42373
|
"interface_declaration",
|
|
42182
|
-
"enum_declaration"
|
|
42374
|
+
"enum_declaration",
|
|
42375
|
+
"package_declaration",
|
|
42376
|
+
"local_variable_declaration"
|
|
42183
42377
|
]);
|
|
42184
42378
|
}
|
|
42185
42379
|
}
|
|
@@ -42203,10 +42397,20 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42203
42397
|
}
|
|
42204
42398
|
}
|
|
42205
42399
|
logger.debug("Analyzing file", { filePath, language, parseGrammar, codeLength: code.length });
|
|
42400
|
+
const phaseTimingEnabled = globalThis.__circleIrPassTiming === true;
|
|
42401
|
+
const phaseStart = () => phaseTimingEnabled ? Date.now() : 0;
|
|
42402
|
+
const phaseEnd = (label, t0) => {
|
|
42403
|
+
if (!phaseTimingEnabled) return;
|
|
42404
|
+
console.error(`[phase-timing] ${label} ${Date.now() - t0}ms file=${filePath}`);
|
|
42405
|
+
};
|
|
42406
|
+
const tParse = phaseStart();
|
|
42206
42407
|
const tree = await parse(code, parseGrammar);
|
|
42408
|
+
phaseEnd("parse", tParse);
|
|
42207
42409
|
try {
|
|
42208
42410
|
logger.trace("Parsed AST", { rootNodeType: tree.rootNode.type });
|
|
42411
|
+
const tParseStatus = phaseStart();
|
|
42209
42412
|
const parseStatus = extractParseStatus(tree);
|
|
42413
|
+
phaseEnd("extractParseStatus", tParseStatus);
|
|
42210
42414
|
if (parseStatus.has_errors) {
|
|
42211
42415
|
logger.warn("Partial parse \u2014 IR may be incomplete", {
|
|
42212
42416
|
filePath,
|
|
@@ -42215,18 +42419,37 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42215
42419
|
firstErrorLine: parseStatus.error_locations[0]?.line
|
|
42216
42420
|
});
|
|
42217
42421
|
}
|
|
42422
|
+
const tCollect = phaseStart();
|
|
42218
42423
|
const nodeCache = collectAllNodes(tree.rootNode, getNodeTypesForLanguage(language));
|
|
42424
|
+
phaseEnd("collectAllNodes", tCollect);
|
|
42425
|
+
const tMeta = phaseStart();
|
|
42219
42426
|
const meta = extractMeta(code, tree, filePath, language);
|
|
42220
42427
|
if (options.projectProfile !== void 0) {
|
|
42221
42428
|
meta.projectProfile = makeProfileResolver(options.projectProfile)(filePath);
|
|
42222
42429
|
}
|
|
42430
|
+
phaseEnd("extractMeta", tMeta);
|
|
42431
|
+
const tTypes = phaseStart();
|
|
42223
42432
|
const types = extractTypes(tree, nodeCache, language);
|
|
42433
|
+
phaseEnd("extractTypes", tTypes);
|
|
42434
|
+
const tCalls = phaseStart();
|
|
42224
42435
|
const calls = extractCalls(tree, nodeCache, language);
|
|
42225
|
-
|
|
42436
|
+
phaseEnd("extractCalls", tCalls);
|
|
42437
|
+
const tImports = phaseStart();
|
|
42438
|
+
const imports = extractImports(tree, language, nodeCache);
|
|
42439
|
+
phaseEnd("extractImports", tImports);
|
|
42440
|
+
const tExports = phaseStart();
|
|
42226
42441
|
const exports = extractExports(types);
|
|
42227
|
-
|
|
42442
|
+
phaseEnd("extractExports", tExports);
|
|
42443
|
+
const tCFG = phaseStart();
|
|
42444
|
+
const cfg = buildCFG(tree, language, nodeCache);
|
|
42445
|
+
phaseEnd("buildCFG", tCFG);
|
|
42446
|
+
const tDFG = phaseStart();
|
|
42228
42447
|
const dfg = buildDFG(tree, nodeCache, language);
|
|
42448
|
+
phaseEnd("buildDFG", tDFG);
|
|
42449
|
+
const tRuntime = phaseStart();
|
|
42229
42450
|
const runtimeRegistrations = extractRuntimeRegistrations(tree, nodeCache, language, imports);
|
|
42451
|
+
phaseEnd("extractRuntimeRegistrations", tRuntime);
|
|
42452
|
+
const tGraph = phaseStart();
|
|
42230
42453
|
const graph = new CodeGraph({
|
|
42231
42454
|
meta,
|
|
42232
42455
|
types,
|
|
@@ -42239,6 +42462,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
42239
42462
|
unresolved: [],
|
|
42240
42463
|
enriched: {}
|
|
42241
42464
|
});
|
|
42465
|
+
phaseEnd("buildCodeGraph", tGraph);
|
|
42242
42466
|
const config = options.taintConfig ?? getDefaultConfig();
|
|
42243
42467
|
const disabledPasses = new Set(options.disabledPasses ?? []);
|
|
42244
42468
|
const passOpts = options.passOptions ?? {};
|