circle-ir 3.9.8 → 3.11.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/passes/broad-catch-pass.d.ts +29 -0
- package/dist/analysis/passes/broad-catch-pass.js +79 -0
- package/dist/analysis/passes/broad-catch-pass.js.map +1 -0
- package/dist/analysis/passes/cleanup-verify-pass.d.ts +28 -0
- package/dist/analysis/passes/cleanup-verify-pass.js +130 -0
- package/dist/analysis/passes/cleanup-verify-pass.js.map +1 -0
- package/dist/analysis/passes/double-close-pass.d.ts +33 -0
- package/dist/analysis/passes/double-close-pass.js +109 -0
- package/dist/analysis/passes/double-close-pass.js.map +1 -0
- package/dist/analysis/passes/missing-guard-dom-pass.d.ts +25 -0
- package/dist/analysis/passes/missing-guard-dom-pass.js +99 -0
- package/dist/analysis/passes/missing-guard-dom-pass.js.map +1 -0
- package/dist/analysis/passes/missing-override-pass.d.ts +27 -0
- package/dist/analysis/passes/missing-override-pass.js +110 -0
- package/dist/analysis/passes/missing-override-pass.js.map +1 -0
- package/dist/analysis/passes/sink-filter-pass.js +88 -9
- package/dist/analysis/passes/sink-filter-pass.js.map +1 -1
- package/dist/analysis/passes/swallowed-exception-pass.d.ts +35 -0
- package/dist/analysis/passes/swallowed-exception-pass.js +103 -0
- package/dist/analysis/passes/swallowed-exception-pass.js.map +1 -0
- package/dist/analysis/passes/taint-matcher-pass.js +6 -1
- package/dist/analysis/passes/taint-matcher-pass.js.map +1 -1
- package/dist/analysis/passes/taint-propagation-pass.js +2 -3
- package/dist/analysis/passes/taint-propagation-pass.js.map +1 -1
- package/dist/analysis/passes/unhandled-exception-pass.d.ts +34 -0
- package/dist/analysis/passes/unhandled-exception-pass.js +123 -0
- package/dist/analysis/passes/unhandled-exception-pass.js.map +1 -0
- package/dist/analysis/passes/unused-interface-method-pass.d.ts +27 -0
- package/dist/analysis/passes/unused-interface-method-pass.js +62 -0
- package/dist/analysis/passes/unused-interface-method-pass.js.map +1 -0
- package/dist/analysis/passes/use-after-close-pass.d.ts +30 -0
- package/dist/analysis/passes/use-after-close-pass.js +100 -0
- package/dist/analysis/passes/use-after-close-pass.js.map +1 -0
- package/dist/analysis/taint-matcher.d.ts +2 -1
- package/dist/analysis/taint-matcher.js +10 -5
- package/dist/analysis/taint-matcher.js.map +1 -1
- package/dist/analyzer.d.ts +12 -3
- package/dist/analyzer.js +30 -3
- package/dist/analyzer.js.map +1 -1
- package/dist/browser/circle-ir.js +1523 -18
- package/dist/core/circle-ir-core.cjs +10 -6
- package/dist/core/circle-ir-core.js +10 -6
- package/dist/graph/exception-flow-graph.d.ts +44 -0
- package/dist/graph/exception-flow-graph.js +75 -0
- package/dist/graph/exception-flow-graph.js.map +1 -0
- package/dist/graph/index.d.ts +1 -0
- package/dist/graph/index.js +1 -0
- package/dist/graph/index.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -10257,9 +10257,9 @@ var PYTHON_TAINTED_PATTERNS = [
|
|
|
10257
10257
|
{ pattern: /\brequest\.query_params\b/, sourceType: "http_param" },
|
|
10258
10258
|
{ pattern: /\brequest\.path_params\b/, sourceType: "http_param" }
|
|
10259
10259
|
];
|
|
10260
|
-
function analyzeTaint(calls, types, config = getDefaultConfig()) {
|
|
10260
|
+
function analyzeTaint(calls, types, config = getDefaultConfig(), typeHierarchy) {
|
|
10261
10261
|
const sources = findSources(calls, types, config.sources);
|
|
10262
|
-
const sinks = findSinks(calls, config.sinks);
|
|
10262
|
+
const sinks = findSinks(calls, config.sinks, typeHierarchy);
|
|
10263
10263
|
const sanitizers = findSanitizers(calls, types, config.sanitizers);
|
|
10264
10264
|
return { sources, sinks, sanitizers };
|
|
10265
10265
|
}
|
|
@@ -10465,11 +10465,11 @@ function isParameterizedQueryCall(call, pattern) {
|
|
|
10465
10465
|
}
|
|
10466
10466
|
return false;
|
|
10467
10467
|
}
|
|
10468
|
-
function findSinks(calls, patterns) {
|
|
10468
|
+
function findSinks(calls, patterns, typeHierarchy) {
|
|
10469
10469
|
const sinkMap = /* @__PURE__ */ new Map();
|
|
10470
10470
|
for (const call of calls) {
|
|
10471
10471
|
for (const pattern of patterns) {
|
|
10472
|
-
if (matchesSinkPattern(call, pattern)) {
|
|
10472
|
+
if (matchesSinkPattern(call, pattern, typeHierarchy)) {
|
|
10473
10473
|
if (isParameterizedQueryCall(call, pattern)) {
|
|
10474
10474
|
continue;
|
|
10475
10475
|
}
|
|
@@ -10483,7 +10483,8 @@ function findSinks(calls, patterns) {
|
|
|
10483
10483
|
cwe: pattern.cwe,
|
|
10484
10484
|
location,
|
|
10485
10485
|
line: call.location.line,
|
|
10486
|
-
confidence
|
|
10486
|
+
confidence,
|
|
10487
|
+
method: call.method_name
|
|
10487
10488
|
});
|
|
10488
10489
|
}
|
|
10489
10490
|
}
|
|
@@ -10560,7 +10561,7 @@ function isJavaScriptTaintedArgument(argExpression, sourcePatterns) {
|
|
|
10560
10561
|
}
|
|
10561
10562
|
return { isTainted: false, sourceType: null };
|
|
10562
10563
|
}
|
|
10563
|
-
function matchesSinkPattern(call, pattern) {
|
|
10564
|
+
function matchesSinkPattern(call, pattern, typeHierarchy) {
|
|
10564
10565
|
const callMethodName = call.method_name;
|
|
10565
10566
|
const patternMethod = pattern.method;
|
|
10566
10567
|
let methodMatches = callMethodName === patternMethod;
|
|
@@ -10576,6 +10577,9 @@ function matchesSinkPattern(call, pattern) {
|
|
|
10576
10577
|
return true;
|
|
10577
10578
|
}
|
|
10578
10579
|
if (call.receiver && !receiverMightBeClass(call.receiver, pattern.class)) {
|
|
10580
|
+
if (typeHierarchy && typeHierarchy.couldBeType(call.receiver, pattern.class)) {
|
|
10581
|
+
return true;
|
|
10582
|
+
}
|
|
10579
10583
|
return false;
|
|
10580
10584
|
}
|
|
10581
10585
|
if (!call.receiver) {
|
|
@@ -11278,6 +11282,698 @@ var CodeGraph = class {
|
|
|
11278
11282
|
}
|
|
11279
11283
|
};
|
|
11280
11284
|
|
|
11285
|
+
// src/resolution/type-hierarchy.ts
|
|
11286
|
+
var TypeHierarchyResolver = class {
|
|
11287
|
+
// All known types by FQN
|
|
11288
|
+
types = /* @__PURE__ */ new Map();
|
|
11289
|
+
// Simple name to FQN mapping (for resolution)
|
|
11290
|
+
nameToFqn = /* @__PURE__ */ new Map();
|
|
11291
|
+
// Subtype relationships: parent FQN -> child FQNs
|
|
11292
|
+
subtypes = /* @__PURE__ */ new Map();
|
|
11293
|
+
// Implementation relationships: interface FQN -> implementing class FQNs
|
|
11294
|
+
implementations = /* @__PURE__ */ new Map();
|
|
11295
|
+
/**
|
|
11296
|
+
* Add types from a CircleIR analysis result
|
|
11297
|
+
*/
|
|
11298
|
+
addFromIR(ir, filePath) {
|
|
11299
|
+
for (const type of ir.types) {
|
|
11300
|
+
this.addType(type, filePath, ir.meta.package || null);
|
|
11301
|
+
}
|
|
11302
|
+
}
|
|
11303
|
+
/**
|
|
11304
|
+
* Add a single type to the hierarchy
|
|
11305
|
+
*/
|
|
11306
|
+
addType(type, filePath, defaultPackage = null) {
|
|
11307
|
+
const pkg = type.package || defaultPackage || "";
|
|
11308
|
+
const fqn = pkg ? `${pkg}.${type.name}` : type.name;
|
|
11309
|
+
const node = {
|
|
11310
|
+
name: type.name,
|
|
11311
|
+
fqn,
|
|
11312
|
+
kind: type.kind,
|
|
11313
|
+
extends: type.extends,
|
|
11314
|
+
implements: type.implements,
|
|
11315
|
+
extendsInterfaces: type.kind === "interface" ? type.implements : [],
|
|
11316
|
+
file: filePath,
|
|
11317
|
+
line: type.start_line
|
|
11318
|
+
};
|
|
11319
|
+
this.types.set(fqn, node);
|
|
11320
|
+
if (!this.nameToFqn.has(type.name)) {
|
|
11321
|
+
this.nameToFqn.set(type.name, /* @__PURE__ */ new Set());
|
|
11322
|
+
}
|
|
11323
|
+
this.nameToFqn.get(type.name).add(fqn);
|
|
11324
|
+
if (type.kind === "class" || type.kind === "enum") {
|
|
11325
|
+
if (type.extends) {
|
|
11326
|
+
const parentFqn = this.resolveTypeName(type.extends, pkg);
|
|
11327
|
+
if (!this.subtypes.has(parentFqn)) {
|
|
11328
|
+
this.subtypes.set(parentFqn, /* @__PURE__ */ new Set());
|
|
11329
|
+
}
|
|
11330
|
+
this.subtypes.get(parentFqn).add(fqn);
|
|
11331
|
+
}
|
|
11332
|
+
for (const iface of type.implements) {
|
|
11333
|
+
const ifaceFqn = this.resolveTypeName(iface, pkg);
|
|
11334
|
+
if (!this.implementations.has(ifaceFqn)) {
|
|
11335
|
+
this.implementations.set(ifaceFqn, /* @__PURE__ */ new Set());
|
|
11336
|
+
}
|
|
11337
|
+
this.implementations.get(ifaceFqn).add(fqn);
|
|
11338
|
+
}
|
|
11339
|
+
} else if (type.kind === "interface") {
|
|
11340
|
+
for (const parentIface of type.implements) {
|
|
11341
|
+
const parentFqn = this.resolveTypeName(parentIface, pkg);
|
|
11342
|
+
if (!this.subtypes.has(parentFqn)) {
|
|
11343
|
+
this.subtypes.set(parentFqn, /* @__PURE__ */ new Set());
|
|
11344
|
+
}
|
|
11345
|
+
this.subtypes.get(parentFqn).add(fqn);
|
|
11346
|
+
}
|
|
11347
|
+
}
|
|
11348
|
+
}
|
|
11349
|
+
/**
|
|
11350
|
+
* Get all direct subtypes of a class
|
|
11351
|
+
*/
|
|
11352
|
+
getDirectSubtypes(className) {
|
|
11353
|
+
const fqn = this.resolveFqn(className);
|
|
11354
|
+
return Array.from(this.subtypes.get(fqn) || []);
|
|
11355
|
+
}
|
|
11356
|
+
/**
|
|
11357
|
+
* Get all subtypes (transitive) of a class
|
|
11358
|
+
*/
|
|
11359
|
+
getAllSubtypes(className) {
|
|
11360
|
+
const fqn = this.resolveFqn(className);
|
|
11361
|
+
const result = /* @__PURE__ */ new Set();
|
|
11362
|
+
const queue = [fqn];
|
|
11363
|
+
while (queue.length > 0) {
|
|
11364
|
+
const current = queue.shift();
|
|
11365
|
+
const children = this.subtypes.get(current);
|
|
11366
|
+
if (children) {
|
|
11367
|
+
for (const child of children) {
|
|
11368
|
+
if (!result.has(child)) {
|
|
11369
|
+
result.add(child);
|
|
11370
|
+
queue.push(child);
|
|
11371
|
+
}
|
|
11372
|
+
}
|
|
11373
|
+
}
|
|
11374
|
+
}
|
|
11375
|
+
return Array.from(result);
|
|
11376
|
+
}
|
|
11377
|
+
/**
|
|
11378
|
+
* Get all direct implementations of an interface
|
|
11379
|
+
*/
|
|
11380
|
+
getDirectImplementations(interfaceName) {
|
|
11381
|
+
const fqn = this.resolveFqn(interfaceName);
|
|
11382
|
+
return Array.from(this.implementations.get(fqn) || []);
|
|
11383
|
+
}
|
|
11384
|
+
/**
|
|
11385
|
+
* Get all implementations (including through subinterfaces) of an interface
|
|
11386
|
+
*/
|
|
11387
|
+
getAllImplementations(interfaceName) {
|
|
11388
|
+
const fqn = this.resolveFqn(interfaceName);
|
|
11389
|
+
const result = /* @__PURE__ */ new Set();
|
|
11390
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11391
|
+
const queue = [fqn];
|
|
11392
|
+
while (queue.length > 0) {
|
|
11393
|
+
const current = queue.shift();
|
|
11394
|
+
if (visited.has(current)) continue;
|
|
11395
|
+
visited.add(current);
|
|
11396
|
+
const impls = this.implementations.get(current);
|
|
11397
|
+
if (impls) {
|
|
11398
|
+
for (const impl of impls) {
|
|
11399
|
+
result.add(impl);
|
|
11400
|
+
const subtypes = this.getAllSubtypes(impl);
|
|
11401
|
+
for (const subtype of subtypes) {
|
|
11402
|
+
result.add(subtype);
|
|
11403
|
+
}
|
|
11404
|
+
}
|
|
11405
|
+
}
|
|
11406
|
+
const subInterfaces = this.subtypes.get(current);
|
|
11407
|
+
if (subInterfaces) {
|
|
11408
|
+
for (const sub of subInterfaces) {
|
|
11409
|
+
queue.push(sub);
|
|
11410
|
+
}
|
|
11411
|
+
}
|
|
11412
|
+
}
|
|
11413
|
+
return Array.from(result);
|
|
11414
|
+
}
|
|
11415
|
+
/**
|
|
11416
|
+
* Check if a type is a subtype of another (including transitive)
|
|
11417
|
+
*/
|
|
11418
|
+
isSubtypeOf(childName, parentName) {
|
|
11419
|
+
const childFqn = this.resolveFqn(childName);
|
|
11420
|
+
const parentFqn = this.resolveFqn(parentName);
|
|
11421
|
+
if (childFqn === parentFqn) return true;
|
|
11422
|
+
const allSubtypes = this.getAllSubtypes(parentFqn);
|
|
11423
|
+
if (allSubtypes.includes(childFqn)) return true;
|
|
11424
|
+
const allImpls = this.getAllImplementations(parentFqn);
|
|
11425
|
+
if (allImpls.includes(childFqn)) return true;
|
|
11426
|
+
return false;
|
|
11427
|
+
}
|
|
11428
|
+
/**
|
|
11429
|
+
* Check if a type implements an interface (directly or through inheritance)
|
|
11430
|
+
* Also handles interface-extends-interface relationships
|
|
11431
|
+
*/
|
|
11432
|
+
implementsInterface(typeName, interfaceName) {
|
|
11433
|
+
const typeFqn = this.resolveFqn(typeName);
|
|
11434
|
+
const ifaceFqn = this.resolveFqn(interfaceName);
|
|
11435
|
+
const allImpls = this.getAllImplementations(ifaceFqn);
|
|
11436
|
+
if (allImpls.includes(typeFqn)) return true;
|
|
11437
|
+
const allSubtypes = this.getAllSubtypes(ifaceFqn);
|
|
11438
|
+
if (allSubtypes.includes(typeFqn)) return true;
|
|
11439
|
+
return false;
|
|
11440
|
+
}
|
|
11441
|
+
/**
|
|
11442
|
+
* Get type info by name
|
|
11443
|
+
*/
|
|
11444
|
+
getType(name2) {
|
|
11445
|
+
const fqn = this.resolveFqn(name2);
|
|
11446
|
+
return this.types.get(fqn);
|
|
11447
|
+
}
|
|
11448
|
+
/**
|
|
11449
|
+
* Get all types matching a simple name
|
|
11450
|
+
*/
|
|
11451
|
+
getTypesByName(simpleName) {
|
|
11452
|
+
const fqns = this.nameToFqn.get(simpleName);
|
|
11453
|
+
if (!fqns) return [];
|
|
11454
|
+
return Array.from(fqns).map((fqn) => this.types.get(fqn)).filter((t) => t !== void 0);
|
|
11455
|
+
}
|
|
11456
|
+
/**
|
|
11457
|
+
* Get the file where a type is defined
|
|
11458
|
+
*/
|
|
11459
|
+
getTypeFile(name2) {
|
|
11460
|
+
const type = this.getType(name2);
|
|
11461
|
+
return type?.file;
|
|
11462
|
+
}
|
|
11463
|
+
/**
|
|
11464
|
+
* Check if a receiver type could match a target class
|
|
11465
|
+
* Handles: exact match, subtype, implementation, simple name match
|
|
11466
|
+
*/
|
|
11467
|
+
couldBeType(receiverType, targetClass) {
|
|
11468
|
+
if (receiverType === targetClass) return true;
|
|
11469
|
+
const receiverSimple = this.getSimpleName(receiverType);
|
|
11470
|
+
const targetSimple = this.getSimpleName(targetClass);
|
|
11471
|
+
if (receiverSimple === targetSimple) return true;
|
|
11472
|
+
if (this.isSubtypeOf(receiverType, targetClass)) return true;
|
|
11473
|
+
const allSubtypes = this.getAllSubtypes(targetClass);
|
|
11474
|
+
const allImpls = this.getAllImplementations(targetClass);
|
|
11475
|
+
for (const sub of [...allSubtypes, ...allImpls]) {
|
|
11476
|
+
const subSimple = this.getSimpleName(sub);
|
|
11477
|
+
if (subSimple === receiverSimple) return true;
|
|
11478
|
+
}
|
|
11479
|
+
return false;
|
|
11480
|
+
}
|
|
11481
|
+
/**
|
|
11482
|
+
* Export hierarchy data in the CircleIR format
|
|
11483
|
+
*/
|
|
11484
|
+
toTypeHierarchyData() {
|
|
11485
|
+
const classes = {};
|
|
11486
|
+
const interfaces = {};
|
|
11487
|
+
for (const [fqn, node] of this.types) {
|
|
11488
|
+
if (node.kind === "class" || node.kind === "enum") {
|
|
11489
|
+
classes[fqn] = {
|
|
11490
|
+
file: node.file,
|
|
11491
|
+
extends: node.extends ? this.resolveTypeName(node.extends, this.getPackage(fqn)) : null,
|
|
11492
|
+
implements: node.implements.map((i2) => this.resolveTypeName(i2, this.getPackage(fqn))),
|
|
11493
|
+
subclasses: this.getDirectSubtypes(fqn)
|
|
11494
|
+
};
|
|
11495
|
+
} else if (node.kind === "interface") {
|
|
11496
|
+
interfaces[fqn] = {
|
|
11497
|
+
file: node.file,
|
|
11498
|
+
extends: node.extendsInterfaces.map((i2) => this.resolveTypeName(i2, this.getPackage(fqn))),
|
|
11499
|
+
implementations: this.getDirectImplementations(fqn)
|
|
11500
|
+
};
|
|
11501
|
+
}
|
|
11502
|
+
}
|
|
11503
|
+
return { classes, interfaces };
|
|
11504
|
+
}
|
|
11505
|
+
/**
|
|
11506
|
+
* Get statistics about the hierarchy
|
|
11507
|
+
*/
|
|
11508
|
+
getStats() {
|
|
11509
|
+
let classes = 0, interfaces = 0, enums = 0;
|
|
11510
|
+
for (const node of this.types.values()) {
|
|
11511
|
+
if (node.kind === "class") classes++;
|
|
11512
|
+
else if (node.kind === "interface") interfaces++;
|
|
11513
|
+
else if (node.kind === "enum") enums++;
|
|
11514
|
+
}
|
|
11515
|
+
return { totalTypes: this.types.size, classes, interfaces, enums };
|
|
11516
|
+
}
|
|
11517
|
+
/**
|
|
11518
|
+
* Get all types in the hierarchy
|
|
11519
|
+
*/
|
|
11520
|
+
getAllTypes() {
|
|
11521
|
+
return Array.from(this.types.values());
|
|
11522
|
+
}
|
|
11523
|
+
/**
|
|
11524
|
+
* Clear all data
|
|
11525
|
+
*/
|
|
11526
|
+
clear() {
|
|
11527
|
+
this.types.clear();
|
|
11528
|
+
this.nameToFqn.clear();
|
|
11529
|
+
this.subtypes.clear();
|
|
11530
|
+
this.implementations.clear();
|
|
11531
|
+
}
|
|
11532
|
+
// --- Private helpers ---
|
|
11533
|
+
/**
|
|
11534
|
+
* Resolve a type name to its FQN
|
|
11535
|
+
*/
|
|
11536
|
+
resolveTypeName(name2, currentPackage) {
|
|
11537
|
+
if (name2.includes(".")) return name2;
|
|
11538
|
+
const fqns = this.nameToFqn.get(name2);
|
|
11539
|
+
if (fqns && fqns.size === 1) {
|
|
11540
|
+
return Array.from(fqns)[0];
|
|
11541
|
+
}
|
|
11542
|
+
return currentPackage ? `${currentPackage}.${name2}` : name2;
|
|
11543
|
+
}
|
|
11544
|
+
/**
|
|
11545
|
+
* Resolve a name (simple or FQN) to its FQN
|
|
11546
|
+
*/
|
|
11547
|
+
resolveFqn(name2) {
|
|
11548
|
+
if (this.types.has(name2)) return name2;
|
|
11549
|
+
const fqns = this.nameToFqn.get(name2);
|
|
11550
|
+
if (fqns && fqns.size > 0) {
|
|
11551
|
+
return Array.from(fqns)[0];
|
|
11552
|
+
}
|
|
11553
|
+
return name2;
|
|
11554
|
+
}
|
|
11555
|
+
/**
|
|
11556
|
+
* Get simple name from FQN
|
|
11557
|
+
*/
|
|
11558
|
+
getSimpleName(name2) {
|
|
11559
|
+
const lastDot = name2.lastIndexOf(".");
|
|
11560
|
+
return lastDot === -1 ? name2 : name2.substring(lastDot + 1);
|
|
11561
|
+
}
|
|
11562
|
+
/**
|
|
11563
|
+
* Get package from FQN
|
|
11564
|
+
*/
|
|
11565
|
+
getPackage(fqn) {
|
|
11566
|
+
const lastDot = fqn.lastIndexOf(".");
|
|
11567
|
+
return lastDot === -1 ? "" : fqn.substring(0, lastDot);
|
|
11568
|
+
}
|
|
11569
|
+
};
|
|
11570
|
+
function createWithJdkTypes() {
|
|
11571
|
+
const resolver = new TypeHierarchyResolver();
|
|
11572
|
+
const jdbcTypes = [
|
|
11573
|
+
{
|
|
11574
|
+
name: "Statement",
|
|
11575
|
+
kind: "interface",
|
|
11576
|
+
package: "java.sql",
|
|
11577
|
+
extends: null,
|
|
11578
|
+
implements: [],
|
|
11579
|
+
annotations: [],
|
|
11580
|
+
methods: [],
|
|
11581
|
+
fields: [],
|
|
11582
|
+
start_line: 0,
|
|
11583
|
+
end_line: 0
|
|
11584
|
+
},
|
|
11585
|
+
{
|
|
11586
|
+
name: "PreparedStatement",
|
|
11587
|
+
kind: "interface",
|
|
11588
|
+
package: "java.sql",
|
|
11589
|
+
extends: null,
|
|
11590
|
+
implements: ["Statement"],
|
|
11591
|
+
annotations: [],
|
|
11592
|
+
methods: [],
|
|
11593
|
+
fields: [],
|
|
11594
|
+
start_line: 0,
|
|
11595
|
+
end_line: 0
|
|
11596
|
+
},
|
|
11597
|
+
{
|
|
11598
|
+
name: "CallableStatement",
|
|
11599
|
+
kind: "interface",
|
|
11600
|
+
package: "java.sql",
|
|
11601
|
+
extends: null,
|
|
11602
|
+
implements: ["PreparedStatement"],
|
|
11603
|
+
annotations: [],
|
|
11604
|
+
methods: [],
|
|
11605
|
+
fields: [],
|
|
11606
|
+
start_line: 0,
|
|
11607
|
+
end_line: 0
|
|
11608
|
+
}
|
|
11609
|
+
];
|
|
11610
|
+
const ioTypes = [
|
|
11611
|
+
{
|
|
11612
|
+
name: "InputStream",
|
|
11613
|
+
kind: "class",
|
|
11614
|
+
package: "java.io",
|
|
11615
|
+
extends: null,
|
|
11616
|
+
implements: [],
|
|
11617
|
+
annotations: [],
|
|
11618
|
+
methods: [],
|
|
11619
|
+
fields: [],
|
|
11620
|
+
start_line: 0,
|
|
11621
|
+
end_line: 0
|
|
11622
|
+
},
|
|
11623
|
+
{
|
|
11624
|
+
name: "FileInputStream",
|
|
11625
|
+
kind: "class",
|
|
11626
|
+
package: "java.io",
|
|
11627
|
+
extends: "InputStream",
|
|
11628
|
+
implements: [],
|
|
11629
|
+
annotations: [],
|
|
11630
|
+
methods: [],
|
|
11631
|
+
fields: [],
|
|
11632
|
+
start_line: 0,
|
|
11633
|
+
end_line: 0
|
|
11634
|
+
},
|
|
11635
|
+
{
|
|
11636
|
+
name: "OutputStream",
|
|
11637
|
+
kind: "class",
|
|
11638
|
+
package: "java.io",
|
|
11639
|
+
extends: null,
|
|
11640
|
+
implements: [],
|
|
11641
|
+
annotations: [],
|
|
11642
|
+
methods: [],
|
|
11643
|
+
fields: [],
|
|
11644
|
+
start_line: 0,
|
|
11645
|
+
end_line: 0
|
|
11646
|
+
},
|
|
11647
|
+
{
|
|
11648
|
+
name: "FileOutputStream",
|
|
11649
|
+
kind: "class",
|
|
11650
|
+
package: "java.io",
|
|
11651
|
+
extends: "OutputStream",
|
|
11652
|
+
implements: [],
|
|
11653
|
+
annotations: [],
|
|
11654
|
+
methods: [],
|
|
11655
|
+
fields: [],
|
|
11656
|
+
start_line: 0,
|
|
11657
|
+
end_line: 0
|
|
11658
|
+
},
|
|
11659
|
+
{
|
|
11660
|
+
name: "Writer",
|
|
11661
|
+
kind: "class",
|
|
11662
|
+
package: "java.io",
|
|
11663
|
+
extends: null,
|
|
11664
|
+
implements: [],
|
|
11665
|
+
annotations: [],
|
|
11666
|
+
methods: [],
|
|
11667
|
+
fields: [],
|
|
11668
|
+
start_line: 0,
|
|
11669
|
+
end_line: 0
|
|
11670
|
+
},
|
|
11671
|
+
{
|
|
11672
|
+
name: "PrintWriter",
|
|
11673
|
+
kind: "class",
|
|
11674
|
+
package: "java.io",
|
|
11675
|
+
extends: "Writer",
|
|
11676
|
+
implements: [],
|
|
11677
|
+
annotations: [],
|
|
11678
|
+
methods: [],
|
|
11679
|
+
fields: [],
|
|
11680
|
+
start_line: 0,
|
|
11681
|
+
end_line: 0
|
|
11682
|
+
}
|
|
11683
|
+
];
|
|
11684
|
+
const servletTypes = [
|
|
11685
|
+
{
|
|
11686
|
+
name: "ServletRequest",
|
|
11687
|
+
kind: "interface",
|
|
11688
|
+
package: "javax.servlet",
|
|
11689
|
+
extends: null,
|
|
11690
|
+
implements: [],
|
|
11691
|
+
annotations: [],
|
|
11692
|
+
methods: [],
|
|
11693
|
+
fields: [],
|
|
11694
|
+
start_line: 0,
|
|
11695
|
+
end_line: 0
|
|
11696
|
+
},
|
|
11697
|
+
{
|
|
11698
|
+
name: "HttpServletRequest",
|
|
11699
|
+
kind: "interface",
|
|
11700
|
+
package: "javax.servlet.http",
|
|
11701
|
+
extends: null,
|
|
11702
|
+
implements: ["javax.servlet.ServletRequest"],
|
|
11703
|
+
// FQN for cross-package reference
|
|
11704
|
+
annotations: [],
|
|
11705
|
+
methods: [],
|
|
11706
|
+
fields: [],
|
|
11707
|
+
start_line: 0,
|
|
11708
|
+
end_line: 0
|
|
11709
|
+
},
|
|
11710
|
+
{
|
|
11711
|
+
name: "ServletResponse",
|
|
11712
|
+
kind: "interface",
|
|
11713
|
+
package: "javax.servlet",
|
|
11714
|
+
extends: null,
|
|
11715
|
+
implements: [],
|
|
11716
|
+
annotations: [],
|
|
11717
|
+
methods: [],
|
|
11718
|
+
fields: [],
|
|
11719
|
+
start_line: 0,
|
|
11720
|
+
end_line: 0
|
|
11721
|
+
},
|
|
11722
|
+
{
|
|
11723
|
+
name: "HttpServletResponse",
|
|
11724
|
+
kind: "interface",
|
|
11725
|
+
package: "javax.servlet.http",
|
|
11726
|
+
extends: null,
|
|
11727
|
+
implements: ["javax.servlet.ServletResponse"],
|
|
11728
|
+
// FQN for cross-package reference
|
|
11729
|
+
annotations: [],
|
|
11730
|
+
methods: [],
|
|
11731
|
+
fields: [],
|
|
11732
|
+
start_line: 0,
|
|
11733
|
+
end_line: 0
|
|
11734
|
+
}
|
|
11735
|
+
];
|
|
11736
|
+
for (const type of [...jdbcTypes, ...ioTypes, ...servletTypes]) {
|
|
11737
|
+
resolver.addType(type, "jdk", type.package);
|
|
11738
|
+
}
|
|
11739
|
+
return resolver;
|
|
11740
|
+
}
|
|
11741
|
+
|
|
11742
|
+
// src/graph/dominator-graph.ts
|
|
11743
|
+
function computeRPO(cfg, entryId) {
|
|
11744
|
+
const outgoing = /* @__PURE__ */ new Map();
|
|
11745
|
+
for (const edge of cfg.edges) {
|
|
11746
|
+
const list = outgoing.get(edge.from) ?? [];
|
|
11747
|
+
list.push(edge.to);
|
|
11748
|
+
outgoing.set(edge.from, list);
|
|
11749
|
+
}
|
|
11750
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11751
|
+
const postOrder = [];
|
|
11752
|
+
const stack = [{ id: entryId, childIndex: 0 }];
|
|
11753
|
+
visited.add(entryId);
|
|
11754
|
+
while (stack.length > 0) {
|
|
11755
|
+
const top = stack[stack.length - 1];
|
|
11756
|
+
const children = outgoing.get(top.id) ?? [];
|
|
11757
|
+
let pushed = false;
|
|
11758
|
+
while (top.childIndex < children.length) {
|
|
11759
|
+
const child = children[top.childIndex++];
|
|
11760
|
+
if (!visited.has(child)) {
|
|
11761
|
+
visited.add(child);
|
|
11762
|
+
stack.push({ id: child, childIndex: 0 });
|
|
11763
|
+
pushed = true;
|
|
11764
|
+
break;
|
|
11765
|
+
}
|
|
11766
|
+
}
|
|
11767
|
+
if (!pushed) {
|
|
11768
|
+
postOrder.push(top.id);
|
|
11769
|
+
stack.pop();
|
|
11770
|
+
}
|
|
11771
|
+
}
|
|
11772
|
+
const rpoOrder = postOrder.reverse();
|
|
11773
|
+
const rpoIndex = /* @__PURE__ */ new Map();
|
|
11774
|
+
for (let i2 = 0; i2 < rpoOrder.length; i2++) {
|
|
11775
|
+
rpoIndex.set(rpoOrder[i2], i2);
|
|
11776
|
+
}
|
|
11777
|
+
return { rpoOrder, rpoIndex };
|
|
11778
|
+
}
|
|
11779
|
+
function intersect(b1, b2, idom, rpoIndex) {
|
|
11780
|
+
let finger1 = b1;
|
|
11781
|
+
let finger2 = b2;
|
|
11782
|
+
while (finger1 !== finger2) {
|
|
11783
|
+
while ((rpoIndex.get(finger1) ?? Number.MAX_SAFE_INTEGER) > (rpoIndex.get(finger2) ?? Number.MAX_SAFE_INTEGER)) {
|
|
11784
|
+
const parent = idom.get(finger1);
|
|
11785
|
+
if (parent === void 0 || parent === finger1) break;
|
|
11786
|
+
finger1 = parent;
|
|
11787
|
+
}
|
|
11788
|
+
while ((rpoIndex.get(finger2) ?? Number.MAX_SAFE_INTEGER) > (rpoIndex.get(finger1) ?? Number.MAX_SAFE_INTEGER)) {
|
|
11789
|
+
const parent = idom.get(finger2);
|
|
11790
|
+
if (parent === void 0 || parent === finger2) break;
|
|
11791
|
+
finger2 = parent;
|
|
11792
|
+
}
|
|
11793
|
+
if (finger1 === finger2) break;
|
|
11794
|
+
const rpo1 = rpoIndex.get(finger1) ?? Number.MAX_SAFE_INTEGER;
|
|
11795
|
+
const rpo2 = rpoIndex.get(finger2) ?? Number.MAX_SAFE_INTEGER;
|
|
11796
|
+
if (rpo1 === rpo2 && finger1 !== finger2) break;
|
|
11797
|
+
}
|
|
11798
|
+
return finger1;
|
|
11799
|
+
}
|
|
11800
|
+
function computeIdom(cfg, rpoOrder, rpoIndex, entryId) {
|
|
11801
|
+
const incoming = /* @__PURE__ */ new Map();
|
|
11802
|
+
for (const edge of cfg.edges) {
|
|
11803
|
+
const list = incoming.get(edge.to) ?? [];
|
|
11804
|
+
list.push(edge.from);
|
|
11805
|
+
incoming.set(edge.to, list);
|
|
11806
|
+
}
|
|
11807
|
+
const idom = /* @__PURE__ */ new Map();
|
|
11808
|
+
idom.set(entryId, entryId);
|
|
11809
|
+
let changed = true;
|
|
11810
|
+
while (changed) {
|
|
11811
|
+
changed = false;
|
|
11812
|
+
for (let i2 = 1; i2 < rpoOrder.length; i2++) {
|
|
11813
|
+
const b = rpoOrder[i2];
|
|
11814
|
+
const preds = incoming.get(b) ?? [];
|
|
11815
|
+
let newIdom;
|
|
11816
|
+
for (const p of preds) {
|
|
11817
|
+
if (idom.has(p)) {
|
|
11818
|
+
newIdom = p;
|
|
11819
|
+
break;
|
|
11820
|
+
}
|
|
11821
|
+
}
|
|
11822
|
+
if (newIdom === void 0) continue;
|
|
11823
|
+
for (const p of preds) {
|
|
11824
|
+
if (p === newIdom) continue;
|
|
11825
|
+
if (idom.has(p)) {
|
|
11826
|
+
newIdom = intersect(p, newIdom, idom, rpoIndex);
|
|
11827
|
+
}
|
|
11828
|
+
}
|
|
11829
|
+
if (idom.get(b) !== newIdom) {
|
|
11830
|
+
idom.set(b, newIdom);
|
|
11831
|
+
changed = true;
|
|
11832
|
+
}
|
|
11833
|
+
}
|
|
11834
|
+
}
|
|
11835
|
+
return idom;
|
|
11836
|
+
}
|
|
11837
|
+
var DominatorGraph = class {
|
|
11838
|
+
idom;
|
|
11839
|
+
rpoIndex;
|
|
11840
|
+
entryId;
|
|
11841
|
+
/** Cached reverse map: blockId → all blockIds it strictly dominates. */
|
|
11842
|
+
_dominated = null;
|
|
11843
|
+
constructor(cfg, entryId) {
|
|
11844
|
+
if (cfg.blocks.length === 0) {
|
|
11845
|
+
this.entryId = entryId ?? 0;
|
|
11846
|
+
this.idom = /* @__PURE__ */ new Map();
|
|
11847
|
+
this.rpoIndex = /* @__PURE__ */ new Map();
|
|
11848
|
+
return;
|
|
11849
|
+
}
|
|
11850
|
+
this.entryId = entryId ?? cfg.blocks.find((b) => b.type === "entry")?.id ?? cfg.blocks.reduce((a, b) => a.id < b.id ? a : b).id;
|
|
11851
|
+
const { rpoOrder, rpoIndex } = computeRPO(cfg, this.entryId);
|
|
11852
|
+
this.rpoIndex = rpoIndex;
|
|
11853
|
+
this.idom = computeIdom(cfg, rpoOrder, rpoIndex, this.entryId);
|
|
11854
|
+
this.idom.delete(this.entryId);
|
|
11855
|
+
}
|
|
11856
|
+
/**
|
|
11857
|
+
* Returns true if block `a` dominates block `b`.
|
|
11858
|
+
* A block dominates itself (reflexive).
|
|
11859
|
+
*/
|
|
11860
|
+
dominates(a, b) {
|
|
11861
|
+
if (a === b) return true;
|
|
11862
|
+
return this.strictlyDominates(a, b);
|
|
11863
|
+
}
|
|
11864
|
+
/**
|
|
11865
|
+
* Returns true if block `a` strictly dominates block `b` (a ≠ b and a dom b).
|
|
11866
|
+
*/
|
|
11867
|
+
strictlyDominates(a, b) {
|
|
11868
|
+
if (a === b) return false;
|
|
11869
|
+
const visited = /* @__PURE__ */ new Set();
|
|
11870
|
+
let cur = this.idom.get(b);
|
|
11871
|
+
while (cur !== void 0 && !visited.has(cur)) {
|
|
11872
|
+
if (cur === a) return true;
|
|
11873
|
+
visited.add(cur);
|
|
11874
|
+
cur = this.idom.get(cur);
|
|
11875
|
+
}
|
|
11876
|
+
return false;
|
|
11877
|
+
}
|
|
11878
|
+
/**
|
|
11879
|
+
* Returns the immediate dominator of `blockId`, or null for the entry block
|
|
11880
|
+
* (or any block not in the dominator tree).
|
|
11881
|
+
*/
|
|
11882
|
+
immediateDominator(blockId) {
|
|
11883
|
+
return this.idom.get(blockId) ?? null;
|
|
11884
|
+
}
|
|
11885
|
+
/**
|
|
11886
|
+
* Returns all block IDs strictly dominated by `blockId`.
|
|
11887
|
+
* (Computed lazily and cached on first call.)
|
|
11888
|
+
*/
|
|
11889
|
+
dominated(blockId) {
|
|
11890
|
+
if (!this._dominated) {
|
|
11891
|
+
this._dominated = /* @__PURE__ */ new Map();
|
|
11892
|
+
for (const [child, parent] of this.idom.entries()) {
|
|
11893
|
+
const ancestors = [];
|
|
11894
|
+
const seen = /* @__PURE__ */ new Set();
|
|
11895
|
+
let cur = parent;
|
|
11896
|
+
while (cur !== void 0 && !seen.has(cur)) {
|
|
11897
|
+
seen.add(cur);
|
|
11898
|
+
ancestors.push(cur);
|
|
11899
|
+
cur = this.idom.get(cur);
|
|
11900
|
+
}
|
|
11901
|
+
for (const anc of ancestors) {
|
|
11902
|
+
const list = this._dominated.get(anc) ?? [];
|
|
11903
|
+
list.push(child);
|
|
11904
|
+
this._dominated.set(anc, list);
|
|
11905
|
+
}
|
|
11906
|
+
}
|
|
11907
|
+
}
|
|
11908
|
+
return this._dominated.get(blockId) ?? [];
|
|
11909
|
+
}
|
|
11910
|
+
};
|
|
11911
|
+
|
|
11912
|
+
// src/graph/exception-flow-graph.ts
|
|
11913
|
+
var ExceptionFlowGraph = class {
|
|
11914
|
+
/** All try/catch pairs found in the CFG. */
|
|
11915
|
+
pairs;
|
|
11916
|
+
/** Block IDs that are catch-handler entry blocks. */
|
|
11917
|
+
catchEntryIds;
|
|
11918
|
+
/** Block IDs that are try-body entry blocks. */
|
|
11919
|
+
tryEntryIds;
|
|
11920
|
+
tryCatchMap;
|
|
11921
|
+
// tryEntryId → [catchEntryId, …]
|
|
11922
|
+
catchTryMap;
|
|
11923
|
+
// catchEntryId → tryEntryId
|
|
11924
|
+
constructor(cfg, blockById) {
|
|
11925
|
+
this.pairs = [];
|
|
11926
|
+
this.catchEntryIds = /* @__PURE__ */ new Set();
|
|
11927
|
+
this.tryEntryIds = /* @__PURE__ */ new Set();
|
|
11928
|
+
this.tryCatchMap = /* @__PURE__ */ new Map();
|
|
11929
|
+
this.catchTryMap = /* @__PURE__ */ new Map();
|
|
11930
|
+
for (const edge of cfg.edges) {
|
|
11931
|
+
if (edge.type !== "exception") continue;
|
|
11932
|
+
const tryBlock = blockById.get(edge.from);
|
|
11933
|
+
const catchBlock = blockById.get(edge.to);
|
|
11934
|
+
if (!tryBlock || !catchBlock) continue;
|
|
11935
|
+
this.tryEntryIds.add(edge.from);
|
|
11936
|
+
this.catchEntryIds.add(edge.to);
|
|
11937
|
+
const catches = this.tryCatchMap.get(edge.from) ?? [];
|
|
11938
|
+
catches.push(edge.to);
|
|
11939
|
+
this.tryCatchMap.set(edge.from, catches);
|
|
11940
|
+
this.catchTryMap.set(edge.to, edge.from);
|
|
11941
|
+
this.pairs.push({
|
|
11942
|
+
tryEntryId: edge.from,
|
|
11943
|
+
catchEntryId: edge.to,
|
|
11944
|
+
tryBlock,
|
|
11945
|
+
catchBlock
|
|
11946
|
+
});
|
|
11947
|
+
}
|
|
11948
|
+
}
|
|
11949
|
+
/** True if at least one try/catch pair was found. */
|
|
11950
|
+
get hasTryCatch() {
|
|
11951
|
+
return this.pairs.length > 0;
|
|
11952
|
+
}
|
|
11953
|
+
/** True if the given block ID is a catch-handler entry block. */
|
|
11954
|
+
isCatchEntry(blockId) {
|
|
11955
|
+
return this.catchEntryIds.has(blockId);
|
|
11956
|
+
}
|
|
11957
|
+
/** True if the given block ID is a try-body entry block. */
|
|
11958
|
+
isTryEntry(blockId) {
|
|
11959
|
+
return this.tryEntryIds.has(blockId);
|
|
11960
|
+
}
|
|
11961
|
+
/**
|
|
11962
|
+
* Returns the catch-entry block IDs for the given try-entry block.
|
|
11963
|
+
* Multiple values mean multiple catch clauses for the same try.
|
|
11964
|
+
*/
|
|
11965
|
+
catchBlocksFor(tryEntryId) {
|
|
11966
|
+
return this.tryCatchMap.get(tryEntryId) ?? [];
|
|
11967
|
+
}
|
|
11968
|
+
/**
|
|
11969
|
+
* Returns the try-entry block ID corresponding to a catch-entry block,
|
|
11970
|
+
* or `undefined` if the block is not a catch entry.
|
|
11971
|
+
*/
|
|
11972
|
+
tryBlockFor(catchEntryId) {
|
|
11973
|
+
return this.catchTryMap.get(catchEntryId);
|
|
11974
|
+
}
|
|
11975
|
+
};
|
|
11976
|
+
|
|
11281
11977
|
// src/graph/analysis-pass.ts
|
|
11282
11978
|
var AnalysisPipeline = class {
|
|
11283
11979
|
passes = [];
|
|
@@ -16833,7 +17529,9 @@ var TaintMatcherPass = class {
|
|
|
16833
17529
|
};
|
|
16834
17530
|
}
|
|
16835
17531
|
}
|
|
16836
|
-
const
|
|
17532
|
+
const hierarchy = createWithJdkTypes();
|
|
17533
|
+
hierarchy.addFromIR(graph.ir, graph.ir.meta.file);
|
|
17534
|
+
const taint = analyzeTaint(calls, types, mergedConfig, hierarchy);
|
|
16837
17535
|
const sanitizerMethods = [];
|
|
16838
17536
|
for (const type of types) {
|
|
16839
17537
|
for (const method of type.methods) {
|
|
@@ -17358,6 +18056,73 @@ var SinkFilterPass = class {
|
|
|
17358
18056
|
return { sources, sinks: filtered, sanitizers };
|
|
17359
18057
|
}
|
|
17360
18058
|
};
|
|
18059
|
+
function evalArithmetic(input) {
|
|
18060
|
+
let pos = 0;
|
|
18061
|
+
function peek() {
|
|
18062
|
+
return input[pos] ?? "";
|
|
18063
|
+
}
|
|
18064
|
+
function consume() {
|
|
18065
|
+
return input[pos++] ?? "";
|
|
18066
|
+
}
|
|
18067
|
+
function skipWs() {
|
|
18068
|
+
while (pos < input.length && input[pos] === " ") pos++;
|
|
18069
|
+
}
|
|
18070
|
+
function parseNumber() {
|
|
18071
|
+
skipWs();
|
|
18072
|
+
let s = "";
|
|
18073
|
+
if (peek() === "-") {
|
|
18074
|
+
s += consume();
|
|
18075
|
+
}
|
|
18076
|
+
while (pos < input.length && /[\d.]/.test(input[pos])) s += consume();
|
|
18077
|
+
if (s === "" || s === "-") return null;
|
|
18078
|
+
const n = parseFloat(s);
|
|
18079
|
+
return isFinite(n) ? n : null;
|
|
18080
|
+
}
|
|
18081
|
+
function parseFactor() {
|
|
18082
|
+
skipWs();
|
|
18083
|
+
if (peek() === "(") {
|
|
18084
|
+
consume();
|
|
18085
|
+
const val = parseExpr();
|
|
18086
|
+
skipWs();
|
|
18087
|
+
if (peek() === ")") consume();
|
|
18088
|
+
return val;
|
|
18089
|
+
}
|
|
18090
|
+
return parseNumber();
|
|
18091
|
+
}
|
|
18092
|
+
function parseTerm() {
|
|
18093
|
+
let left = parseFactor();
|
|
18094
|
+
if (left === null) return null;
|
|
18095
|
+
while (true) {
|
|
18096
|
+
skipWs();
|
|
18097
|
+
const op = peek();
|
|
18098
|
+
if (op !== "*" && op !== "/") break;
|
|
18099
|
+
consume();
|
|
18100
|
+
const right = parseFactor();
|
|
18101
|
+
if (right === null) return null;
|
|
18102
|
+
left = op === "*" ? left * right : right === 0 ? null : left / right;
|
|
18103
|
+
if (left === null) return null;
|
|
18104
|
+
}
|
|
18105
|
+
return left;
|
|
18106
|
+
}
|
|
18107
|
+
function parseExpr() {
|
|
18108
|
+
let left = parseTerm();
|
|
18109
|
+
if (left === null) return null;
|
|
18110
|
+
while (true) {
|
|
18111
|
+
skipWs();
|
|
18112
|
+
const op = peek();
|
|
18113
|
+
if (op !== "+" && op !== "-") break;
|
|
18114
|
+
consume();
|
|
18115
|
+
const right = parseTerm();
|
|
18116
|
+
if (right === null) return null;
|
|
18117
|
+
left = op === "+" ? left + right : left - right;
|
|
18118
|
+
}
|
|
18119
|
+
return left;
|
|
18120
|
+
}
|
|
18121
|
+
if (!/^[\d\s+\-*/().]+$/.test(input)) return null;
|
|
18122
|
+
const result = parseExpr();
|
|
18123
|
+
skipWs();
|
|
18124
|
+
return pos === input.length ? result : null;
|
|
18125
|
+
}
|
|
17361
18126
|
function evaluateSimpleExpression(expr, symbols) {
|
|
17362
18127
|
let evaluated = expr;
|
|
17363
18128
|
for (const [name2, val] of symbols) {
|
|
@@ -17366,13 +18131,8 @@ function evaluateSimpleExpression(expr, symbols) {
|
|
|
17366
18131
|
evaluated = evaluated.replace(regex, String(val.value));
|
|
17367
18132
|
}
|
|
17368
18133
|
}
|
|
17369
|
-
|
|
17370
|
-
|
|
17371
|
-
const result = Function('"use strict"; return (' + evaluated + ")")();
|
|
17372
|
-
if (typeof result === "number" && !isNaN(result)) return String(Math.floor(result));
|
|
17373
|
-
}
|
|
17374
|
-
} catch {
|
|
17375
|
-
}
|
|
18134
|
+
const result = evalArithmetic(evaluated);
|
|
18135
|
+
if (result !== null && !isNaN(result)) return String(Math.floor(result));
|
|
17376
18136
|
return expr;
|
|
17377
18137
|
}
|
|
17378
18138
|
function isStringLiteralExpression(expr) {
|
|
@@ -17422,7 +18182,8 @@ function filterCleanVariableSinks(sinks, calls, taintedVars, symbols, dfg, sanit
|
|
|
17422
18182
|
return sinks.filter((sink) => {
|
|
17423
18183
|
const callsAtSink = callsByLine.get(sink.line) ?? [];
|
|
17424
18184
|
const isInSynchronizedBlock = synchronizedLines?.has(sink.line) ?? false;
|
|
17425
|
-
|
|
18185
|
+
const relevantCalls = sink.method ? callsAtSink.filter((c) => c.method_name === sink.method) : callsAtSink;
|
|
18186
|
+
for (const call of relevantCalls) {
|
|
17426
18187
|
let allArgsAreClean = true;
|
|
17427
18188
|
const methodName = call.in_method;
|
|
17428
18189
|
for (const arg of call.arguments) {
|
|
@@ -17539,8 +18300,8 @@ var TaintPropagationPass = class {
|
|
|
17539
18300
|
for (const f of collectionFlows) {
|
|
17540
18301
|
if (flows.some((x) => x.source_line === f.source_line && x.sink_line === f.sink_line)) continue;
|
|
17541
18302
|
const flowForCheck = {
|
|
17542
|
-
source: { line: f.source_line
|
|
17543
|
-
sink: { line: f.sink_line
|
|
18303
|
+
source: { line: f.source_line },
|
|
18304
|
+
sink: { line: f.sink_line },
|
|
17544
18305
|
path: f.path.map((p) => ({ variable: p.variable, line: p.line }))
|
|
17545
18306
|
};
|
|
17546
18307
|
if (isCorrelatedPredicateFP(constProp, flowForCheck)) continue;
|
|
@@ -19690,6 +20451,750 @@ var ReactInlineJsxPass = class {
|
|
|
19690
20451
|
}
|
|
19691
20452
|
};
|
|
19692
20453
|
|
|
20454
|
+
// src/analysis/passes/swallowed-exception-pass.ts
|
|
20455
|
+
var MEANINGFUL_ACTION_RE = /\b(throw|raise|log|logger|console\.(error|warn|log|debug|info)|System\.(out|err)\.|print(?:ln|f)?|warn|error|debug|info|fatal|LOGGER|LOG|logging\.(warning|error|debug|info|critical))\b|\breturn\s+\S/;
|
|
20456
|
+
var SwallowedExceptionPass = class {
|
|
20457
|
+
name = "swallowed-exception";
|
|
20458
|
+
category = "reliability";
|
|
20459
|
+
run(ctx) {
|
|
20460
|
+
const { graph, code, language } = ctx;
|
|
20461
|
+
if (language === "rust" || language === "bash") {
|
|
20462
|
+
return { swallowed: [] };
|
|
20463
|
+
}
|
|
20464
|
+
const { cfg } = graph.ir;
|
|
20465
|
+
if (cfg.blocks.length === 0) return { swallowed: [] };
|
|
20466
|
+
const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
|
|
20467
|
+
if (!exGraph.hasTryCatch) return { swallowed: [] };
|
|
20468
|
+
const file = graph.ir.meta.file;
|
|
20469
|
+
const codeLines = code.split("\n");
|
|
20470
|
+
const swallowed = [];
|
|
20471
|
+
const reported = /* @__PURE__ */ new Set();
|
|
20472
|
+
for (const pair of exGraph.pairs) {
|
|
20473
|
+
const catchLine = pair.catchBlock.start_line;
|
|
20474
|
+
if (reported.has(catchLine)) continue;
|
|
20475
|
+
const methodInfo = graph.methodAtLine(catchLine);
|
|
20476
|
+
const scanEnd = methodInfo ? methodInfo.method.end_line : codeLines.length;
|
|
20477
|
+
const catchBodyEnd = this.findCatchBodyEnd(codeLines, catchLine, scanEnd);
|
|
20478
|
+
let hasAction = false;
|
|
20479
|
+
for (let ln = catchLine; ln <= catchBodyEnd && ln <= codeLines.length; ln++) {
|
|
20480
|
+
if (MEANINGFUL_ACTION_RE.test(codeLines[ln - 1] ?? "")) {
|
|
20481
|
+
hasAction = true;
|
|
20482
|
+
break;
|
|
20483
|
+
}
|
|
20484
|
+
}
|
|
20485
|
+
if (!hasAction) {
|
|
20486
|
+
reported.add(catchLine);
|
|
20487
|
+
swallowed.push({ line: catchLine });
|
|
20488
|
+
const snippet = (codeLines[catchLine - 1] ?? "").trim();
|
|
20489
|
+
ctx.addFinding({
|
|
20490
|
+
id: `swallowed-exception-${file}-${catchLine}`,
|
|
20491
|
+
pass: this.name,
|
|
20492
|
+
category: this.category,
|
|
20493
|
+
rule_id: this.name,
|
|
20494
|
+
cwe: "CWE-390",
|
|
20495
|
+
severity: "medium",
|
|
20496
|
+
level: "warning",
|
|
20497
|
+
message: `Swallowed exception: catch block at line ${catchLine} has no throw, log, or return \u2014 the exception is silently discarded`,
|
|
20498
|
+
file,
|
|
20499
|
+
line: catchLine,
|
|
20500
|
+
snippet,
|
|
20501
|
+
fix: "At minimum log the exception, or re-throw it; never silently discard exceptions"
|
|
20502
|
+
});
|
|
20503
|
+
}
|
|
20504
|
+
}
|
|
20505
|
+
return { swallowed };
|
|
20506
|
+
}
|
|
20507
|
+
/**
|
|
20508
|
+
* Walks source lines starting at `startLine` counting brace depth.
|
|
20509
|
+
* Returns the line where the brace depth first returns to zero after
|
|
20510
|
+
* the opening brace (i.e., the closing brace of the catch block).
|
|
20511
|
+
* Capped at `maxLine`.
|
|
20512
|
+
*/
|
|
20513
|
+
findCatchBodyEnd(lines, startLine, maxLine) {
|
|
20514
|
+
let depth = 0;
|
|
20515
|
+
let started = false;
|
|
20516
|
+
for (let ln = startLine; ln <= maxLine && ln <= lines.length; ln++) {
|
|
20517
|
+
const text = lines[ln - 1] ?? "";
|
|
20518
|
+
for (const ch of text) {
|
|
20519
|
+
if (ch === "{") {
|
|
20520
|
+
depth++;
|
|
20521
|
+
started = true;
|
|
20522
|
+
} else if (ch === "}" && started) {
|
|
20523
|
+
depth--;
|
|
20524
|
+
}
|
|
20525
|
+
}
|
|
20526
|
+
if (started && depth <= 0) return ln;
|
|
20527
|
+
}
|
|
20528
|
+
return maxLine;
|
|
20529
|
+
}
|
|
20530
|
+
};
|
|
20531
|
+
|
|
20532
|
+
// src/analysis/passes/broad-catch-pass.ts
|
|
20533
|
+
var JAVA_BROAD_RE = /catch\s*\(\s*(Exception|Throwable|RuntimeException|Error)\s/;
|
|
20534
|
+
var PYTHON_BROAD_RE = /^\s*except\s*:|except\s+(Exception|BaseException)\b/;
|
|
20535
|
+
var BroadCatchPass = class {
|
|
20536
|
+
name = "broad-catch";
|
|
20537
|
+
category = "reliability";
|
|
20538
|
+
run(ctx) {
|
|
20539
|
+
const { graph, code, language } = ctx;
|
|
20540
|
+
if (language !== "java" && language !== "python") {
|
|
20541
|
+
return { broadCatches: [] };
|
|
20542
|
+
}
|
|
20543
|
+
const { cfg } = graph.ir;
|
|
20544
|
+
if (cfg.blocks.length === 0) return { broadCatches: [] };
|
|
20545
|
+
const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
|
|
20546
|
+
if (!exGraph.hasTryCatch) return { broadCatches: [] };
|
|
20547
|
+
const file = graph.ir.meta.file;
|
|
20548
|
+
const codeLines = code.split("\n");
|
|
20549
|
+
const broadCatches = [];
|
|
20550
|
+
const reported = /* @__PURE__ */ new Set();
|
|
20551
|
+
const pattern = language === "java" ? JAVA_BROAD_RE : PYTHON_BROAD_RE;
|
|
20552
|
+
for (const pair of exGraph.pairs) {
|
|
20553
|
+
const catchLine = pair.catchBlock.start_line;
|
|
20554
|
+
if (reported.has(catchLine)) continue;
|
|
20555
|
+
const lineText = codeLines[catchLine - 1] ?? "";
|
|
20556
|
+
const match = pattern.exec(lineText);
|
|
20557
|
+
if (!match) continue;
|
|
20558
|
+
const caughtType = match[1] ?? "Exception";
|
|
20559
|
+
reported.add(catchLine);
|
|
20560
|
+
broadCatches.push({ line: catchLine, type: caughtType });
|
|
20561
|
+
const snippet = lineText.trim();
|
|
20562
|
+
ctx.addFinding({
|
|
20563
|
+
id: `broad-catch-${file}-${catchLine}`,
|
|
20564
|
+
pass: this.name,
|
|
20565
|
+
category: this.category,
|
|
20566
|
+
rule_id: this.name,
|
|
20567
|
+
cwe: "CWE-396",
|
|
20568
|
+
severity: "low",
|
|
20569
|
+
level: "warning",
|
|
20570
|
+
message: `Broad catch: catching \`${caughtType}\` at line ${catchLine} suppresses unexpected errors and hides bugs`,
|
|
20571
|
+
file,
|
|
20572
|
+
line: catchLine,
|
|
20573
|
+
snippet,
|
|
20574
|
+
fix: language === "java" ? `Catch the specific exception types your code can handle (e.g., \`IOException\`, \`SQLException\`)` : `Catch the specific exception types your code can handle (e.g., \`ValueError\`, \`KeyError\`)`,
|
|
20575
|
+
evidence: { caughtType }
|
|
20576
|
+
});
|
|
20577
|
+
}
|
|
20578
|
+
return { broadCatches };
|
|
20579
|
+
}
|
|
20580
|
+
};
|
|
20581
|
+
|
|
20582
|
+
// src/analysis/passes/unhandled-exception-pass.ts
|
|
20583
|
+
var JS_THROW_RE = /^\s*throw\s+/;
|
|
20584
|
+
var PYTHON_RAISE_RE = /^\s*raise\b/;
|
|
20585
|
+
var UnhandledExceptionPass = class {
|
|
20586
|
+
name = "unhandled-exception";
|
|
20587
|
+
category = "reliability";
|
|
20588
|
+
run(ctx) {
|
|
20589
|
+
const { graph, code, language } = ctx;
|
|
20590
|
+
if (language !== "javascript" && language !== "typescript" && language !== "python") {
|
|
20591
|
+
return { unhandled: [] };
|
|
20592
|
+
}
|
|
20593
|
+
const { cfg } = graph.ir;
|
|
20594
|
+
const file = graph.ir.meta.file;
|
|
20595
|
+
const codeLines = code.split("\n");
|
|
20596
|
+
const exGraph = new ExceptionFlowGraph(cfg, graph.blockById);
|
|
20597
|
+
const coveredRanges = [];
|
|
20598
|
+
for (const pair of exGraph.pairs) {
|
|
20599
|
+
if (pair.catchBlock.start_line > pair.tryBlock.start_line) {
|
|
20600
|
+
coveredRanges.push({
|
|
20601
|
+
start: pair.tryBlock.start_line,
|
|
20602
|
+
end: pair.catchBlock.start_line - 1
|
|
20603
|
+
});
|
|
20604
|
+
}
|
|
20605
|
+
}
|
|
20606
|
+
const catchStarts = new Set(
|
|
20607
|
+
exGraph.pairs.map((p) => p.catchBlock.start_line)
|
|
20608
|
+
);
|
|
20609
|
+
const throwRe = language === "python" ? PYTHON_RAISE_RE : JS_THROW_RE;
|
|
20610
|
+
const unhandled = [];
|
|
20611
|
+
const reportedMethods = /* @__PURE__ */ new Set();
|
|
20612
|
+
for (let ln = 1; ln <= codeLines.length; ln++) {
|
|
20613
|
+
const lineText = codeLines[ln - 1] ?? "";
|
|
20614
|
+
if (!throwRe.test(lineText)) continue;
|
|
20615
|
+
let inCatch = false;
|
|
20616
|
+
for (const cs of catchStarts) {
|
|
20617
|
+
if (ln >= cs) {
|
|
20618
|
+
inCatch = true;
|
|
20619
|
+
break;
|
|
20620
|
+
}
|
|
20621
|
+
}
|
|
20622
|
+
inCatch = false;
|
|
20623
|
+
for (const pair of exGraph.pairs) {
|
|
20624
|
+
if (ln >= pair.catchBlock.start_line) {
|
|
20625
|
+
const mThrow = graph.methodAtLine(ln);
|
|
20626
|
+
const mCatch = graph.methodAtLine(pair.catchBlock.start_line);
|
|
20627
|
+
if (mThrow && mCatch && mThrow.method.start_line === mCatch.method.start_line) {
|
|
20628
|
+
inCatch = true;
|
|
20629
|
+
break;
|
|
20630
|
+
}
|
|
20631
|
+
}
|
|
20632
|
+
}
|
|
20633
|
+
if (inCatch) continue;
|
|
20634
|
+
const isCovered = coveredRanges.some((r) => ln >= r.start && ln <= r.end);
|
|
20635
|
+
if (isCovered) continue;
|
|
20636
|
+
const methodInfo = graph.methodAtLine(ln);
|
|
20637
|
+
const methodKey = methodInfo ? `${methodInfo.method.start_line}-${methodInfo.method.end_line}` : `global-${ln}`;
|
|
20638
|
+
if (reportedMethods.has(methodKey)) continue;
|
|
20639
|
+
reportedMethods.add(methodKey);
|
|
20640
|
+
const methodName = methodInfo?.method.name ?? "<anonymous>";
|
|
20641
|
+
unhandled.push({ line: ln, method: methodName });
|
|
20642
|
+
const snippet = lineText.trim();
|
|
20643
|
+
ctx.addFinding({
|
|
20644
|
+
id: `unhandled-exception-${file}-${ln}`,
|
|
20645
|
+
pass: this.name,
|
|
20646
|
+
category: this.category,
|
|
20647
|
+
rule_id: this.name,
|
|
20648
|
+
cwe: "CWE-390",
|
|
20649
|
+
severity: "medium",
|
|
20650
|
+
level: "warning",
|
|
20651
|
+
message: `Unhandled exception: \`throw\` at line ${ln} in \`${methodName}\` is not inside a try/catch \u2014 callers receive an unexpected exception`,
|
|
20652
|
+
file,
|
|
20653
|
+
line: ln,
|
|
20654
|
+
snippet,
|
|
20655
|
+
fix: "Wrap throwing code in a try/catch, or document the exception in the function signature",
|
|
20656
|
+
evidence: { method: methodName }
|
|
20657
|
+
});
|
|
20658
|
+
}
|
|
20659
|
+
return { unhandled };
|
|
20660
|
+
}
|
|
20661
|
+
};
|
|
20662
|
+
|
|
20663
|
+
// src/analysis/passes/double-close-pass.ts
|
|
20664
|
+
var RESOURCE_CTORS2 = /* @__PURE__ */ new Set([
|
|
20665
|
+
"FileInputStream",
|
|
20666
|
+
"FileOutputStream",
|
|
20667
|
+
"FileReader",
|
|
20668
|
+
"FileWriter",
|
|
20669
|
+
"BufferedReader",
|
|
20670
|
+
"BufferedWriter",
|
|
20671
|
+
"PrintWriter",
|
|
20672
|
+
"InputStreamReader",
|
|
20673
|
+
"OutputStreamWriter",
|
|
20674
|
+
"RandomAccessFile",
|
|
20675
|
+
"DataInputStream",
|
|
20676
|
+
"DataOutputStream",
|
|
20677
|
+
"ObjectInputStream",
|
|
20678
|
+
"ObjectOutputStream",
|
|
20679
|
+
"ZipInputStream",
|
|
20680
|
+
"ZipOutputStream",
|
|
20681
|
+
"JarInputStream",
|
|
20682
|
+
"JarOutputStream",
|
|
20683
|
+
"GZIPInputStream",
|
|
20684
|
+
"GZIPOutputStream",
|
|
20685
|
+
"FileChannel",
|
|
20686
|
+
"Socket",
|
|
20687
|
+
"ServerSocket",
|
|
20688
|
+
"DatagramSocket"
|
|
20689
|
+
]);
|
|
20690
|
+
var RESOURCE_FACTORY_METHODS2 = /* @__PURE__ */ new Set([
|
|
20691
|
+
"openConnection",
|
|
20692
|
+
"openStream",
|
|
20693
|
+
"newInputStream",
|
|
20694
|
+
"newOutputStream",
|
|
20695
|
+
"newBufferedReader",
|
|
20696
|
+
"newBufferedWriter",
|
|
20697
|
+
"newByteChannel",
|
|
20698
|
+
"open",
|
|
20699
|
+
"createReadStream",
|
|
20700
|
+
"createWriteStream",
|
|
20701
|
+
"createConnection"
|
|
20702
|
+
]);
|
|
20703
|
+
var CLOSE_METHODS2 = /* @__PURE__ */ new Set([
|
|
20704
|
+
"close",
|
|
20705
|
+
"dispose",
|
|
20706
|
+
"shutdown",
|
|
20707
|
+
"disconnect",
|
|
20708
|
+
"release",
|
|
20709
|
+
"destroy",
|
|
20710
|
+
"free",
|
|
20711
|
+
"shutdownNow",
|
|
20712
|
+
"terminate"
|
|
20713
|
+
]);
|
|
20714
|
+
var DoubleClosePass = class {
|
|
20715
|
+
name = "double-close";
|
|
20716
|
+
category = "reliability";
|
|
20717
|
+
run(ctx) {
|
|
20718
|
+
const { graph, code } = ctx;
|
|
20719
|
+
if (ctx.language === "bash") return { doubleCloses: [] };
|
|
20720
|
+
const file = graph.ir.meta.file;
|
|
20721
|
+
const codeLines = code.split("\n");
|
|
20722
|
+
const doubleCloses = [];
|
|
20723
|
+
for (const call of graph.ir.calls) {
|
|
20724
|
+
const name2 = call.method_name;
|
|
20725
|
+
const isConstructor = call.is_constructor === true && RESOURCE_CTORS2.has(name2);
|
|
20726
|
+
const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS2.has(name2);
|
|
20727
|
+
if (!isConstructor && !isFactory) continue;
|
|
20728
|
+
const openLine = call.location.line;
|
|
20729
|
+
const defs = graph.defsAtLine(openLine);
|
|
20730
|
+
if (defs.length === 0) continue;
|
|
20731
|
+
const resourceVar = defs[0].variable;
|
|
20732
|
+
const methodInfo = graph.methodAtLine(openLine);
|
|
20733
|
+
if (!methodInfo) continue;
|
|
20734
|
+
const { start_line: methodStart, end_line: methodEnd } = methodInfo.method;
|
|
20735
|
+
const closeCalls = graph.ir.calls.filter(
|
|
20736
|
+
(c) => CLOSE_METHODS2.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd
|
|
20737
|
+
);
|
|
20738
|
+
if (closeCalls.length < 2) continue;
|
|
20739
|
+
const closeLines = closeCalls.map((c) => c.location.line);
|
|
20740
|
+
const allInFinally = closeLines.every(
|
|
20741
|
+
(cl) => this.isInFinallyBlock(codeLines, cl, methodStart, methodEnd)
|
|
20742
|
+
);
|
|
20743
|
+
if (allInFinally) continue;
|
|
20744
|
+
doubleCloses.push({ openLine, closeLines, variable: resourceVar });
|
|
20745
|
+
const snippet = (codeLines[openLine - 1] ?? "").trim();
|
|
20746
|
+
const linesStr = closeLines.join(" and ");
|
|
20747
|
+
ctx.addFinding({
|
|
20748
|
+
id: `double-close-${file}-${openLine}`,
|
|
20749
|
+
pass: this.name,
|
|
20750
|
+
category: this.category,
|
|
20751
|
+
rule_id: this.name,
|
|
20752
|
+
cwe: "CWE-675",
|
|
20753
|
+
severity: "medium",
|
|
20754
|
+
level: "warning",
|
|
20755
|
+
message: `Double close: \`${resourceVar}\` is closed at lines ${linesStr} \u2014 closing an already-closed resource may throw`,
|
|
20756
|
+
file,
|
|
20757
|
+
line: openLine,
|
|
20758
|
+
snippet,
|
|
20759
|
+
fix: `Close the resource exactly once in a finally block; add a null/isClosed guard before the second close if closing on multiple paths`,
|
|
20760
|
+
evidence: { variable: resourceVar, close_lines: closeLines }
|
|
20761
|
+
});
|
|
20762
|
+
}
|
|
20763
|
+
return { doubleCloses };
|
|
20764
|
+
}
|
|
20765
|
+
/** True if the given line is inside a `finally` block in the method. */
|
|
20766
|
+
isInFinallyBlock(lines, targetLine, methodStart, methodEnd) {
|
|
20767
|
+
for (let ln = methodStart; ln <= targetLine && ln <= methodEnd && ln <= lines.length; ln++) {
|
|
20768
|
+
if (/\bfinally\b/.test(lines[ln - 1] ?? "")) return true;
|
|
20769
|
+
}
|
|
20770
|
+
return false;
|
|
20771
|
+
}
|
|
20772
|
+
};
|
|
20773
|
+
|
|
20774
|
+
// src/analysis/passes/use-after-close-pass.ts
|
|
20775
|
+
var RESOURCE_CTORS3 = /* @__PURE__ */ new Set([
|
|
20776
|
+
"FileInputStream",
|
|
20777
|
+
"FileOutputStream",
|
|
20778
|
+
"FileReader",
|
|
20779
|
+
"FileWriter",
|
|
20780
|
+
"BufferedReader",
|
|
20781
|
+
"BufferedWriter",
|
|
20782
|
+
"PrintWriter",
|
|
20783
|
+
"InputStreamReader",
|
|
20784
|
+
"OutputStreamWriter",
|
|
20785
|
+
"RandomAccessFile",
|
|
20786
|
+
"DataInputStream",
|
|
20787
|
+
"DataOutputStream",
|
|
20788
|
+
"ObjectInputStream",
|
|
20789
|
+
"ObjectOutputStream",
|
|
20790
|
+
"ZipInputStream",
|
|
20791
|
+
"ZipOutputStream",
|
|
20792
|
+
"JarInputStream",
|
|
20793
|
+
"JarOutputStream",
|
|
20794
|
+
"GZIPInputStream",
|
|
20795
|
+
"GZIPOutputStream",
|
|
20796
|
+
"FileChannel",
|
|
20797
|
+
"Socket",
|
|
20798
|
+
"ServerSocket",
|
|
20799
|
+
"DatagramSocket"
|
|
20800
|
+
]);
|
|
20801
|
+
var RESOURCE_FACTORY_METHODS3 = /* @__PURE__ */ new Set([
|
|
20802
|
+
"openConnection",
|
|
20803
|
+
"openStream",
|
|
20804
|
+
"newInputStream",
|
|
20805
|
+
"newOutputStream",
|
|
20806
|
+
"newBufferedReader",
|
|
20807
|
+
"newBufferedWriter",
|
|
20808
|
+
"newByteChannel",
|
|
20809
|
+
"open",
|
|
20810
|
+
"createReadStream",
|
|
20811
|
+
"createWriteStream",
|
|
20812
|
+
"createConnection"
|
|
20813
|
+
]);
|
|
20814
|
+
var CLOSE_METHODS3 = /* @__PURE__ */ new Set([
|
|
20815
|
+
"close",
|
|
20816
|
+
"dispose",
|
|
20817
|
+
"shutdown",
|
|
20818
|
+
"disconnect",
|
|
20819
|
+
"release",
|
|
20820
|
+
"destroy",
|
|
20821
|
+
"free",
|
|
20822
|
+
"shutdownNow",
|
|
20823
|
+
"terminate"
|
|
20824
|
+
]);
|
|
20825
|
+
var UseAfterClosePass = class {
|
|
20826
|
+
name = "use-after-close";
|
|
20827
|
+
category = "reliability";
|
|
20828
|
+
run(ctx) {
|
|
20829
|
+
const { graph, code } = ctx;
|
|
20830
|
+
if (ctx.language === "bash") return { useAfterCloses: [] };
|
|
20831
|
+
const file = graph.ir.meta.file;
|
|
20832
|
+
const codeLines = code.split("\n");
|
|
20833
|
+
const useAfterCloses = [];
|
|
20834
|
+
for (const call of graph.ir.calls) {
|
|
20835
|
+
const name2 = call.method_name;
|
|
20836
|
+
const isConstructor = call.is_constructor === true && RESOURCE_CTORS3.has(name2);
|
|
20837
|
+
const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS3.has(name2);
|
|
20838
|
+
if (!isConstructor && !isFactory) continue;
|
|
20839
|
+
const openLine = call.location.line;
|
|
20840
|
+
const defs = graph.defsAtLine(openLine);
|
|
20841
|
+
if (defs.length === 0) continue;
|
|
20842
|
+
const resourceVar = defs[0].variable;
|
|
20843
|
+
const methodInfo = graph.methodAtLine(openLine);
|
|
20844
|
+
if (!methodInfo) continue;
|
|
20845
|
+
const methodEnd = methodInfo.method.end_line;
|
|
20846
|
+
const firstClose = graph.ir.calls.filter(
|
|
20847
|
+
(c) => CLOSE_METHODS3.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd
|
|
20848
|
+
).sort((a, b) => a.location.line - b.location.line)[0];
|
|
20849
|
+
if (!firstClose) continue;
|
|
20850
|
+
const closeLine = firstClose.location.line;
|
|
20851
|
+
const usesAfterClose = graph.ir.calls.filter(
|
|
20852
|
+
(c) => c.receiver === resourceVar && c.location.line > closeLine && c.location.line <= methodEnd && !CLOSE_METHODS3.has(c.method_name)
|
|
20853
|
+
);
|
|
20854
|
+
for (const use of usesAfterClose) {
|
|
20855
|
+
const useLine = use.location.line;
|
|
20856
|
+
useAfterCloses.push({ openLine, closeLine, useLine, variable: resourceVar });
|
|
20857
|
+
const snippet = (codeLines[useLine - 1] ?? "").trim();
|
|
20858
|
+
ctx.addFinding({
|
|
20859
|
+
id: `use-after-close-${file}-${useLine}`,
|
|
20860
|
+
pass: this.name,
|
|
20861
|
+
category: this.category,
|
|
20862
|
+
rule_id: this.name,
|
|
20863
|
+
cwe: "CWE-672",
|
|
20864
|
+
severity: "high",
|
|
20865
|
+
level: "error",
|
|
20866
|
+
message: `Use after close: \`${resourceVar}.${use.method_name}()\` at line ${useLine} is called after \`${resourceVar}.close()\` at line ${closeLine}`,
|
|
20867
|
+
file,
|
|
20868
|
+
line: useLine,
|
|
20869
|
+
snippet,
|
|
20870
|
+
fix: `Do not use a resource after closing it; keep \`${resourceVar}\` open until all uses are complete`,
|
|
20871
|
+
evidence: { variable: resourceVar, close_line: closeLine, open_line: openLine }
|
|
20872
|
+
});
|
|
20873
|
+
}
|
|
20874
|
+
}
|
|
20875
|
+
return { useAfterCloses };
|
|
20876
|
+
}
|
|
20877
|
+
};
|
|
20878
|
+
|
|
20879
|
+
// src/analysis/passes/missing-guard-dom-pass.ts
|
|
20880
|
+
var AUTH_METHODS = /* @__PURE__ */ new Set([
|
|
20881
|
+
"authenticate",
|
|
20882
|
+
"isAuthenticated",
|
|
20883
|
+
"isAuthorized",
|
|
20884
|
+
"isAdmin",
|
|
20885
|
+
"checkAuth",
|
|
20886
|
+
"hasPermission",
|
|
20887
|
+
"requiresAuth",
|
|
20888
|
+
"verifyToken",
|
|
20889
|
+
"validateToken",
|
|
20890
|
+
"checkRole",
|
|
20891
|
+
"authorize",
|
|
20892
|
+
"isLoggedIn"
|
|
20893
|
+
]);
|
|
20894
|
+
var SENSITIVE_METHODS = /* @__PURE__ */ new Set([
|
|
20895
|
+
"delete",
|
|
20896
|
+
"deleteById",
|
|
20897
|
+
"drop",
|
|
20898
|
+
"truncate",
|
|
20899
|
+
"executeUpdate",
|
|
20900
|
+
"createUser",
|
|
20901
|
+
"createAdmin",
|
|
20902
|
+
"modifyPermission",
|
|
20903
|
+
"grantRole",
|
|
20904
|
+
"setAdmin",
|
|
20905
|
+
"elevatePrivilege"
|
|
20906
|
+
]);
|
|
20907
|
+
var MissingGuardDomPass = class {
|
|
20908
|
+
name = "missing-guard-dom";
|
|
20909
|
+
category = "security";
|
|
20910
|
+
run(ctx) {
|
|
20911
|
+
const { graph, language } = ctx;
|
|
20912
|
+
if (language !== "java") return { findings: 0 };
|
|
20913
|
+
const { cfg, calls } = graph.ir;
|
|
20914
|
+
if (cfg.blocks.length === 0 || cfg.edges.length === 0) return { findings: 0 };
|
|
20915
|
+
const dom = new DominatorGraph(cfg);
|
|
20916
|
+
const file = graph.ir.meta.file;
|
|
20917
|
+
const authCallLines = [];
|
|
20918
|
+
const sensitiveOps = [];
|
|
20919
|
+
for (const call of calls) {
|
|
20920
|
+
if (AUTH_METHODS.has(call.method_name)) {
|
|
20921
|
+
authCallLines.push(call.location.line);
|
|
20922
|
+
}
|
|
20923
|
+
if (SENSITIVE_METHODS.has(call.method_name)) {
|
|
20924
|
+
sensitiveOps.push({ line: call.location.line, method: call.method_name });
|
|
20925
|
+
}
|
|
20926
|
+
}
|
|
20927
|
+
if (sensitiveOps.length === 0) return { findings: 0 };
|
|
20928
|
+
const blockContainingLine = (line) => cfg.blocks.find((b) => b.start_line <= line && line <= b.end_line) ?? null;
|
|
20929
|
+
const reportedMethods = /* @__PURE__ */ new Set();
|
|
20930
|
+
let count = 0;
|
|
20931
|
+
for (const op of sensitiveOps) {
|
|
20932
|
+
const opBlock = blockContainingLine(op.line);
|
|
20933
|
+
if (!opBlock) continue;
|
|
20934
|
+
const methodInfo = graph.methodAtLine(op.line);
|
|
20935
|
+
if (!methodInfo) continue;
|
|
20936
|
+
const methodKey = `${methodInfo.type.name}::${methodInfo.method.name}`;
|
|
20937
|
+
if (reportedMethods.has(methodKey)) continue;
|
|
20938
|
+
const { start_line, end_line } = methodInfo.method;
|
|
20939
|
+
const authInMethod = authCallLines.filter((l) => l >= start_line && l <= end_line);
|
|
20940
|
+
const dominated = authInMethod.some((authLine) => {
|
|
20941
|
+
const authBlock = blockContainingLine(authLine);
|
|
20942
|
+
return authBlock !== null && dom.dominates(authBlock.id, opBlock.id);
|
|
20943
|
+
});
|
|
20944
|
+
if (!dominated) {
|
|
20945
|
+
reportedMethods.add(methodKey);
|
|
20946
|
+
count++;
|
|
20947
|
+
ctx.addFinding({
|
|
20948
|
+
id: `missing-guard-dom-${file}-${op.line}`,
|
|
20949
|
+
pass: this.name,
|
|
20950
|
+
category: this.category,
|
|
20951
|
+
rule_id: "missing-guard-dom",
|
|
20952
|
+
cwe: "CWE-285",
|
|
20953
|
+
severity: "high",
|
|
20954
|
+
level: "error",
|
|
20955
|
+
message: `Sensitive operation \`${op.method}()\` at line ${op.line} is not dominated by an authentication check`,
|
|
20956
|
+
file,
|
|
20957
|
+
line: op.line,
|
|
20958
|
+
fix: `Add authentication/authorization check on all paths leading to line ${op.line}`,
|
|
20959
|
+
evidence: { method: op.method }
|
|
20960
|
+
});
|
|
20961
|
+
}
|
|
20962
|
+
}
|
|
20963
|
+
return { findings: count };
|
|
20964
|
+
}
|
|
20965
|
+
};
|
|
20966
|
+
|
|
20967
|
+
// src/analysis/passes/cleanup-verify-pass.ts
|
|
20968
|
+
var RESOURCE_CTORS4 = /* @__PURE__ */ new Set([
|
|
20969
|
+
"FileInputStream",
|
|
20970
|
+
"FileOutputStream",
|
|
20971
|
+
"FileReader",
|
|
20972
|
+
"FileWriter",
|
|
20973
|
+
"BufferedReader",
|
|
20974
|
+
"BufferedWriter",
|
|
20975
|
+
"PrintWriter",
|
|
20976
|
+
"InputStreamReader",
|
|
20977
|
+
"OutputStreamWriter",
|
|
20978
|
+
"RandomAccessFile",
|
|
20979
|
+
"DataInputStream",
|
|
20980
|
+
"DataOutputStream",
|
|
20981
|
+
"ObjectInputStream",
|
|
20982
|
+
"ObjectOutputStream",
|
|
20983
|
+
"ZipInputStream",
|
|
20984
|
+
"ZipOutputStream",
|
|
20985
|
+
"JarInputStream",
|
|
20986
|
+
"JarOutputStream",
|
|
20987
|
+
"GZIPInputStream",
|
|
20988
|
+
"GZIPOutputStream",
|
|
20989
|
+
"FileChannel",
|
|
20990
|
+
"Socket",
|
|
20991
|
+
"ServerSocket",
|
|
20992
|
+
"DatagramSocket"
|
|
20993
|
+
]);
|
|
20994
|
+
var RESOURCE_FACTORY_METHODS4 = /* @__PURE__ */ new Set([
|
|
20995
|
+
"openConnection",
|
|
20996
|
+
"openStream",
|
|
20997
|
+
"newInputStream",
|
|
20998
|
+
"newOutputStream",
|
|
20999
|
+
"newBufferedReader",
|
|
21000
|
+
"newBufferedWriter",
|
|
21001
|
+
"newByteChannel",
|
|
21002
|
+
"open",
|
|
21003
|
+
"createReadStream",
|
|
21004
|
+
"createWriteStream",
|
|
21005
|
+
"createConnection"
|
|
21006
|
+
]);
|
|
21007
|
+
var CLOSE_METHODS4 = /* @__PURE__ */ new Set([
|
|
21008
|
+
"close",
|
|
21009
|
+
"dispose",
|
|
21010
|
+
"shutdown",
|
|
21011
|
+
"disconnect",
|
|
21012
|
+
"release",
|
|
21013
|
+
"destroy",
|
|
21014
|
+
"free",
|
|
21015
|
+
"shutdownNow",
|
|
21016
|
+
"terminate"
|
|
21017
|
+
]);
|
|
21018
|
+
function buildPostDomGraph(cfg) {
|
|
21019
|
+
const exitBlock = cfg.blocks.find((b) => b.type === "exit") ?? cfg.blocks.find((b) => !cfg.edges.some((e) => e.from === b.id));
|
|
21020
|
+
if (!exitBlock || cfg.blocks.length === 0) {
|
|
21021
|
+
return new DominatorGraph({ blocks: [], edges: [] });
|
|
21022
|
+
}
|
|
21023
|
+
const reversed = {
|
|
21024
|
+
blocks: cfg.blocks,
|
|
21025
|
+
edges: cfg.edges.map((e) => ({ from: e.to, to: e.from, type: e.type }))
|
|
21026
|
+
};
|
|
21027
|
+
return new DominatorGraph(reversed, exitBlock.id);
|
|
21028
|
+
}
|
|
21029
|
+
var CleanupVerifyPass = class {
|
|
21030
|
+
name = "cleanup-verify";
|
|
21031
|
+
category = "reliability";
|
|
21032
|
+
run(ctx) {
|
|
21033
|
+
const { graph, language } = ctx;
|
|
21034
|
+
if (language === "rust" || language === "bash") return { findings: 0 };
|
|
21035
|
+
const { cfg, calls } = graph.ir;
|
|
21036
|
+
const file = graph.ir.meta.file;
|
|
21037
|
+
if (cfg.blocks.length === 0) return { findings: 0 };
|
|
21038
|
+
const postDom = buildPostDomGraph(cfg);
|
|
21039
|
+
const blockContainingLine = (line) => cfg.blocks.find((b) => b.start_line <= line && line <= b.end_line) ?? null;
|
|
21040
|
+
let count = 0;
|
|
21041
|
+
for (const call of calls) {
|
|
21042
|
+
const name2 = call.method_name;
|
|
21043
|
+
const isConstructor = call.is_constructor === true && RESOURCE_CTORS4.has(name2);
|
|
21044
|
+
const isFactory = !call.is_constructor && RESOURCE_FACTORY_METHODS4.has(name2);
|
|
21045
|
+
if (!isConstructor && !isFactory) continue;
|
|
21046
|
+
const openLine = call.location.line;
|
|
21047
|
+
const defs = graph.defsAtLine(openLine);
|
|
21048
|
+
if (defs.length === 0) continue;
|
|
21049
|
+
const resourceVar = defs[0].variable;
|
|
21050
|
+
const methodInfo = graph.methodAtLine(openLine);
|
|
21051
|
+
if (!methodInfo) continue;
|
|
21052
|
+
const methodEnd = methodInfo.method.end_line;
|
|
21053
|
+
const closeCall = calls.find(
|
|
21054
|
+
(c) => CLOSE_METHODS4.has(c.method_name) && c.receiver === resourceVar && c.location.line > openLine && c.location.line <= methodEnd
|
|
21055
|
+
);
|
|
21056
|
+
if (!closeCall) continue;
|
|
21057
|
+
const openBlock = blockContainingLine(openLine);
|
|
21058
|
+
const closeBlock = blockContainingLine(closeCall.location.line);
|
|
21059
|
+
if (!openBlock || !closeBlock) continue;
|
|
21060
|
+
if (postDom.dominates(closeBlock.id, openBlock.id)) continue;
|
|
21061
|
+
count++;
|
|
21062
|
+
ctx.addFinding({
|
|
21063
|
+
id: `cleanup-verify-${file}-${openLine}`,
|
|
21064
|
+
pass: this.name,
|
|
21065
|
+
category: this.category,
|
|
21066
|
+
rule_id: "cleanup-verify",
|
|
21067
|
+
cwe: "CWE-772",
|
|
21068
|
+
severity: "medium",
|
|
21069
|
+
level: "warning",
|
|
21070
|
+
message: `Resource \`${resourceVar}\` opened at line ${openLine} may not close on all paths \u2014 close() at line ${closeCall.location.line} does not post-dominate the acquisition`,
|
|
21071
|
+
file,
|
|
21072
|
+
line: openLine,
|
|
21073
|
+
fix: "Use try-with-resources (Java) or a finally block to guarantee cleanup on all paths",
|
|
21074
|
+
evidence: {
|
|
21075
|
+
resource: name2,
|
|
21076
|
+
variable: resourceVar,
|
|
21077
|
+
close_line: closeCall.location.line
|
|
21078
|
+
}
|
|
21079
|
+
});
|
|
21080
|
+
}
|
|
21081
|
+
return { findings: count };
|
|
21082
|
+
}
|
|
21083
|
+
};
|
|
21084
|
+
|
|
21085
|
+
// src/analysis/passes/missing-override-pass.ts
|
|
21086
|
+
var MissingOverridePass = class {
|
|
21087
|
+
name = "missing-override";
|
|
21088
|
+
category = "maintainability";
|
|
21089
|
+
run(ctx) {
|
|
21090
|
+
const { graph, language } = ctx;
|
|
21091
|
+
if (language !== "java") return { findings: 0 };
|
|
21092
|
+
const { types } = graph.ir;
|
|
21093
|
+
const file = graph.ir.meta.file;
|
|
21094
|
+
if (types.length === 0) return { findings: 0 };
|
|
21095
|
+
const methodsByClass = /* @__PURE__ */ new Map();
|
|
21096
|
+
for (const type of types) {
|
|
21097
|
+
methodsByClass.set(type.name, new Set(type.methods.map((m) => m.name)));
|
|
21098
|
+
}
|
|
21099
|
+
const parentMap = /* @__PURE__ */ new Map();
|
|
21100
|
+
for (const type of types) {
|
|
21101
|
+
if (type.extends) {
|
|
21102
|
+
const parent = type.extends.replace(/<[^>]*>/g, "").trim();
|
|
21103
|
+
parentMap.set(type.name, parent);
|
|
21104
|
+
}
|
|
21105
|
+
}
|
|
21106
|
+
if (parentMap.size === 0) return { findings: 0 };
|
|
21107
|
+
const getAncestorMethods = (className) => {
|
|
21108
|
+
const methods = /* @__PURE__ */ new Set();
|
|
21109
|
+
const visited = /* @__PURE__ */ new Set();
|
|
21110
|
+
let current = parentMap.get(className);
|
|
21111
|
+
let hops = 0;
|
|
21112
|
+
while (current && !visited.has(current) && hops < 10) {
|
|
21113
|
+
visited.add(current);
|
|
21114
|
+
const parentMethods = methodsByClass.get(current);
|
|
21115
|
+
if (parentMethods) {
|
|
21116
|
+
for (const m of parentMethods) methods.add(m);
|
|
21117
|
+
}
|
|
21118
|
+
current = parentMap.get(current);
|
|
21119
|
+
hops++;
|
|
21120
|
+
}
|
|
21121
|
+
return methods;
|
|
21122
|
+
};
|
|
21123
|
+
const dedup = /* @__PURE__ */ new Set();
|
|
21124
|
+
let count = 0;
|
|
21125
|
+
for (const type of types) {
|
|
21126
|
+
if (!parentMap.has(type.name)) continue;
|
|
21127
|
+
const ancestorMethods = getAncestorMethods(type.name);
|
|
21128
|
+
if (ancestorMethods.size === 0) continue;
|
|
21129
|
+
for (const method of type.methods) {
|
|
21130
|
+
if (method.name === type.name) continue;
|
|
21131
|
+
if (method.modifiers.includes("private")) continue;
|
|
21132
|
+
if (method.modifiers.includes("static")) continue;
|
|
21133
|
+
if (method.modifiers.includes("abstract")) continue;
|
|
21134
|
+
if (!ancestorMethods.has(method.name)) continue;
|
|
21135
|
+
if (method.annotations.includes("Override")) continue;
|
|
21136
|
+
const key = `${type.name}:${method.name}`;
|
|
21137
|
+
if (dedup.has(key)) continue;
|
|
21138
|
+
dedup.add(key);
|
|
21139
|
+
count++;
|
|
21140
|
+
ctx.addFinding({
|
|
21141
|
+
id: `missing-override-${file}-${method.start_line}`,
|
|
21142
|
+
pass: this.name,
|
|
21143
|
+
category: this.category,
|
|
21144
|
+
rule_id: "missing-override",
|
|
21145
|
+
severity: "low",
|
|
21146
|
+
level: "warning",
|
|
21147
|
+
message: `Method \`${method.name}()\` in \`${type.name}\` overrides a parent method but lacks @Override`,
|
|
21148
|
+
file,
|
|
21149
|
+
line: method.start_line,
|
|
21150
|
+
fix: "Add @Override to make the intent explicit and catch signature mismatches at compile time",
|
|
21151
|
+
evidence: { className: type.name, methodName: method.name }
|
|
21152
|
+
});
|
|
21153
|
+
}
|
|
21154
|
+
}
|
|
21155
|
+
return { findings: count };
|
|
21156
|
+
}
|
|
21157
|
+
};
|
|
21158
|
+
|
|
21159
|
+
// src/analysis/passes/unused-interface-method-pass.ts
|
|
21160
|
+
var UnusedInterfaceMethodPass = class {
|
|
21161
|
+
name = "unused-interface-method";
|
|
21162
|
+
category = "maintainability";
|
|
21163
|
+
run(ctx) {
|
|
21164
|
+
const { graph, language } = ctx;
|
|
21165
|
+
if (language !== "java" && language !== "typescript") return { findings: 0 };
|
|
21166
|
+
const { types, calls } = graph.ir;
|
|
21167
|
+
const file = graph.ir.meta.file;
|
|
21168
|
+
const calledMethods = new Set(calls.map((c) => c.method_name));
|
|
21169
|
+
const dedup = /* @__PURE__ */ new Set();
|
|
21170
|
+
let count = 0;
|
|
21171
|
+
for (const type of types) {
|
|
21172
|
+
if (type.kind !== "interface") continue;
|
|
21173
|
+
for (const method of type.methods) {
|
|
21174
|
+
if (calledMethods.has(method.name)) continue;
|
|
21175
|
+
const key = `${type.name}:${method.name}`;
|
|
21176
|
+
if (dedup.has(key)) continue;
|
|
21177
|
+
dedup.add(key);
|
|
21178
|
+
count++;
|
|
21179
|
+
ctx.addFinding({
|
|
21180
|
+
id: `unused-interface-method-${file}-${method.start_line}`,
|
|
21181
|
+
pass: this.name,
|
|
21182
|
+
category: this.category,
|
|
21183
|
+
rule_id: "unused-interface-method",
|
|
21184
|
+
severity: "low",
|
|
21185
|
+
level: "note",
|
|
21186
|
+
message: `Interface method \`${method.name}()\` in \`${type.name}\` is never called in this file`,
|
|
21187
|
+
file,
|
|
21188
|
+
line: method.start_line,
|
|
21189
|
+
fix: "Remove this method or verify it is used from other files; unused interface methods inflate the public API",
|
|
21190
|
+
evidence: { interfaceName: type.name, methodName: method.name }
|
|
21191
|
+
});
|
|
21192
|
+
}
|
|
21193
|
+
}
|
|
21194
|
+
return { findings: count };
|
|
21195
|
+
}
|
|
21196
|
+
};
|
|
21197
|
+
|
|
19693
21198
|
// src/analysis/metrics/passes/size-metrics-pass.ts
|
|
19694
21199
|
var SizeMetricsPass = class {
|
|
19695
21200
|
name = "size-metrics";
|
|
@@ -20489,7 +21994,7 @@ async function analyze(code, filePath, language, options = {}) {
|
|
|
20489
21994
|
enriched: {}
|
|
20490
21995
|
});
|
|
20491
21996
|
const config = options.taintConfig ?? getDefaultConfig();
|
|
20492
|
-
const { results, findings } = new AnalysisPipeline().add(new TaintMatcherPass()).add(new ConstantPropagationPass(tree)).add(new LanguageSourcesPass()).add(new SinkFilterPass()).add(new TaintPropagationPass()).add(new InterproceduralPass()).add(new DeadCodePass()).add(new MissingAwaitPass()).add(new NPlusOnePass()).add(new MissingPublicDocPass()).add(new TodoInProdPass()).add(new StringConcatLoopPass()).add(new SyncIoAsyncPass()).add(new UncheckedReturnPass()).add(new NullDerefPass()).add(new ResourceLeakPass()).add(new VariableShadowingPass()).add(new LeakedGlobalPass()).add(new UnusedVariablePass()).add(new DependencyFanOutPass()).add(new StaleDocRefPass()).add(new InfiniteLoopPass()).add(new DeepInheritancePass()).add(new RedundantLoopPass()).add(new UnboundedCollectionPass()).add(new SerialAwaitPass()).add(new ReactInlineJsxPass()).run(graph, code, language, config);
|
|
21997
|
+
const { results, findings } = new AnalysisPipeline().add(new TaintMatcherPass()).add(new ConstantPropagationPass(tree)).add(new LanguageSourcesPass()).add(new SinkFilterPass()).add(new TaintPropagationPass()).add(new InterproceduralPass()).add(new DeadCodePass()).add(new MissingAwaitPass()).add(new NPlusOnePass()).add(new MissingPublicDocPass()).add(new TodoInProdPass()).add(new StringConcatLoopPass()).add(new SyncIoAsyncPass()).add(new UncheckedReturnPass()).add(new NullDerefPass()).add(new ResourceLeakPass()).add(new VariableShadowingPass()).add(new LeakedGlobalPass()).add(new UnusedVariablePass()).add(new DependencyFanOutPass()).add(new StaleDocRefPass()).add(new InfiniteLoopPass()).add(new DeepInheritancePass()).add(new RedundantLoopPass()).add(new UnboundedCollectionPass()).add(new SerialAwaitPass()).add(new ReactInlineJsxPass()).add(new SwallowedExceptionPass()).add(new BroadCatchPass()).add(new UnhandledExceptionPass()).add(new DoubleClosePass()).add(new UseAfterClosePass()).add(new MissingGuardDomPass()).add(new CleanupVerifyPass()).add(new MissingOverridePass()).add(new UnusedInterfaceMethodPass()).run(graph, code, language, config);
|
|
20493
21998
|
const sinkFilter = results.get("sink-filter");
|
|
20494
21999
|
const interProc = results.get("interprocedural");
|
|
20495
22000
|
const taint = {
|