diff --git a/benchmarks/push/finding_dump.py b/benchmarks/push/finding_dump.py index f7da829..d05ea99 100644 --- a/benchmarks/push/finding_dump.py +++ b/benchmarks/push/finding_dump.py @@ -81,11 +81,17 @@ def main() -> int: "seconds": round(time.monotonic() - started, 1), "questions": len(outcomes), "completed": sum(1 for o in outcomes if o.status == "completed"), + # Which questions answered, so two runs can be compared only where BOTH answered: a finding that + # vanishes because its question went unasked is lost coverage, not gained precision. + "completed_regions": sorted({f"{o.identity.path}:{o.identity.function or ''}" for o in outcomes if o.status == "completed"}), "degradations": sorted({str(d.get("reason")) for d in result.degradations}), "findings": findings, } args.out.write_text(json.dumps(record, indent=2) + "\n") - print(json.dumps({k: v for k, v in record.items() if k != "findings"} | {"findings": len(findings)}), flush=True) + print( + json.dumps({k: v for k, v in record.items() if k not in ("findings", "completed_regions")} | {"findings": len(findings)}), + flush=True, + ) if not outcomes: raise SystemExit("no question was asked: the dump measured nothing") return 0 diff --git a/src/openultrasast/cpg/queries/taint.sc b/src/openultrasast/cpg/queries/taint.sc index 8461d0e..ea83845 100644 --- a/src/openultrasast/cpg/queries/taint.sc +++ b/src/openultrasast/cpg/queries/taint.sc @@ -306,10 +306,12 @@ val methods = methodsIn(fileName) val framework: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] = methods.flatMap(_.ast.isCall.filter(c => sourcePatterns.exists(p => c.code.contains(p))).l) + // Every parameter is a CANDIDATE start; whether its callers feed it is asked only of a parameter that + // actually begins an unsanitized flow into the field being read (`fieldIsTainted`). Asked of every + // parameter up front, the caller check ran a repository-wide flow query per parameter of every + // method in the file and cut PMPro's completed questions from 272 to 67 in the same budget. val params: List[io.shiftleft.codepropertygraph.generated.nodes.CfgNode] = - if (parameterSources == "true") - methods.flatMap(m => m.parameter.l.filter(p => p.name != "this" && fedByCaller(m, p))) - else Nil + if (parameterSources == "true") methods.flatMap(_.parameter.l.filter(_.name != "this")) else Nil framework ++ params } ) @@ -331,7 +333,13 @@ // queries that way: the fixed side of its pair went from 1 finding to 13 without this, // which is the pair no longer separating at all. val flows = args(1).start.reachableByFlows(seeds.iterator).l - flows.exists(f => !f.elements.map(_.code).l.exists(code => mentionsToken(code, sanitizerNames))) + flows.exists { f => + !f.elements.map(_.code).l.exists(code => mentionsToken(code, sanitizerNames)) && (f.elements.headOption match { + case Some(param: io.shiftleft.codepropertygraph.generated.nodes.MethodParameterIn) => + fedByCaller(param.method, param) + case _ => true + }) + } case _ => false }) }