undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com Patches, unit tests, benchmarks, whitepaper, and outreach briefs. Public domain — no copyright claimed. Use freely.
This commit is contained in:
commit
0a580b313d
70422 changed files with 17213626 additions and 0 deletions
220
tests/support/PostgresqlVarDedupAlgorithm.java
Normal file
220
tests/support/PostgresqlVarDedupAlgorithm.java
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package support;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* PostgresqlVarDedupAlgorithm — models preptlist.c sites postgresql-0002.
|
||||
*
|
||||
* DEFECT (CWE-407): tlist_member() walks the entire target list calling
|
||||
* structural equal() on each entry to check if a Var is already present.
|
||||
* As the list grows, each check takes longer. Building a tlist of N unique
|
||||
* Vars from a pool of M candidates costs O(M × N) — quadratic when M ≈ N.
|
||||
*
|
||||
* Sites in PostgreSQL source (preptlist.c):
|
||||
* :180 — MERGE action targetList/qual Var collection
|
||||
* :206 — mergeJoinCondition Var collection
|
||||
* :316 — RETURNING clause Var collection
|
||||
*
|
||||
* Defective C pattern (all three sites identical):
|
||||
*
|
||||
* vars = pull_var_clause(..., PVC_INCLUDE_PLACEHOLDERS);
|
||||
* foreach(l, vars) {
|
||||
* Var *var = (Var *) lfirst(l);
|
||||
* if (IsA(var, Var) && var->varno == result_relation)
|
||||
* continue; // skip target-rel vars
|
||||
* if (tlist_member((Expr *) var, tlist)) // O(n) — THE DEFECT
|
||||
* continue;
|
||||
* tlist = lappend(tlist, makeTargetEntry(...));
|
||||
* }
|
||||
*
|
||||
* Fix (Path B — no nodeHash() required):
|
||||
* Maintain a HashSet<Var> (C: Bitmapset keyed on encoded varno×varattno)
|
||||
* alongside the tlist. Check/update the set instead of scanning tlist.
|
||||
* Reduces per-var check from O(n) to O(1). Total: O(n) instead of O(n²).
|
||||
*
|
||||
* C encoding: bms_member(varno * 3200 + varattno + 1600)
|
||||
* Safe for varno ≤ 65001 (INNER_VAR) and varattno in [-1600, 1600].
|
||||
*
|
||||
* Comparison counts:
|
||||
* defective: 0+1+2+...+(N-1) = N*(N-1)/2 (N unique vars, each checked
|
||||
* against all previously added entries)
|
||||
* fixed: N (one O(1) hash check per var)
|
||||
*
|
||||
* Growth when N doubles: defective ≈4× (quadratic), fixed ≈2× (linear).
|
||||
*/
|
||||
public class PostgresqlVarDedupAlgorithm {
|
||||
|
||||
// ── Var: models PostgreSQL Var node ──────────────────────────────────────
|
||||
//
|
||||
// A Var represents a column reference: (varno=relation, varattno=column,
|
||||
// varlevelsup=query nesting depth). PostgreSQL equal() on Vars compares
|
||||
// all three fields plus vartype and several others. We model the minimum
|
||||
// set needed to reproduce the defect: varno + varattno + varlevelsup.
|
||||
|
||||
public static final class Var {
|
||||
public final int varno; // relation index (1-based)
|
||||
public final int varattno; // column number (negative = system col)
|
||||
public final int varlevelsup; // query nesting (0 = current query)
|
||||
|
||||
public Var(int varno, int varattno, int varlevelsup) {
|
||||
this.varno = varno;
|
||||
this.varattno = varattno;
|
||||
this.varlevelsup = varlevelsup;
|
||||
}
|
||||
|
||||
/** Structural equality — models PostgreSQL equal() for Var nodes. */
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Var v)) return false;
|
||||
return varno == v.varno && varattno == v.varattno
|
||||
&& varlevelsup == v.varlevelsup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() { return Objects.hash(varno, varattno, varlevelsup); }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Var(" + varno + "," + varattno + "," + varlevelsup + ")";
|
||||
}
|
||||
}
|
||||
|
||||
// ── TargetEntry: wraps an expression in the target list ──────────────────
|
||||
|
||||
public static final class TargetEntry {
|
||||
public final Var expr;
|
||||
public final int resno; // position in tlist (1-based)
|
||||
|
||||
public TargetEntry(Var expr, int resno) {
|
||||
this.expr = expr;
|
||||
this.resno = resno;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Result ────────────────────────────────────────────────────────────────
|
||||
|
||||
public static final class Result {
|
||||
public final List<TargetEntry> tlist;
|
||||
/** Number of element-level equality checks performed. */
|
||||
public final long comparisons;
|
||||
|
||||
public Result(List<TargetEntry> tlist, long comparisons) {
|
||||
this.tlist = tlist;
|
||||
this.comparisons = comparisons;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Defective: tlist_member scan — O(n) per var ──────────────────────────
|
||||
//
|
||||
// Exact port of the defective PostgreSQL pattern.
|
||||
// Each call to tlist_member() walks the existing tlist, calling equal()
|
||||
// on each entry's expr until it finds a match or exhausts the list.
|
||||
|
||||
public static Result defectiveBuildTlist(List<Var> vars, int resultRelation) {
|
||||
List<TargetEntry> tlist = new ArrayList<>();
|
||||
long comparisons = 0;
|
||||
|
||||
for (Var var : vars) {
|
||||
if (var.varno == resultRelation)
|
||||
continue; // skip: belongs to target relation
|
||||
|
||||
// tlist_member: scan tlist, call equal() on each expr
|
||||
boolean found = false;
|
||||
for (TargetEntry te : tlist) {
|
||||
comparisons++;
|
||||
if (te.expr.equals(var)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) continue;
|
||||
|
||||
tlist.add(new TargetEntry(var, tlist.size() + 1));
|
||||
}
|
||||
|
||||
return new Result(tlist, comparisons);
|
||||
}
|
||||
|
||||
// ── Fixed: HashSet on Var identity — O(1) per var ────────────────────────
|
||||
//
|
||||
// Maintain a HashSet<Var> alongside the tlist.
|
||||
// C equivalent: Bitmapset keyed on (varno * 3200 + varattno + 1600).
|
||||
// Does NOT require nodeHash() — Var identity is three plain integers.
|
||||
|
||||
public static Result fixedBuildTlist(List<Var> vars, int resultRelation) {
|
||||
List<TargetEntry> tlist = new ArrayList<>();
|
||||
Set<Var> seen = new HashSet<>();
|
||||
long comparisons = 0;
|
||||
|
||||
for (Var var : vars) {
|
||||
if (var.varno == resultRelation)
|
||||
continue;
|
||||
|
||||
comparisons++; // one O(1) hash table lookup
|
||||
if (seen.contains(var)) continue;
|
||||
|
||||
tlist.add(new TargetEntry(var, tlist.size() + 1));
|
||||
seen.add(var);
|
||||
}
|
||||
|
||||
return new Result(tlist, comparisons);
|
||||
}
|
||||
|
||||
// ── Self-test ─────────────────────────────────────────────────────────────
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("PostgresqlVarDedupAlgorithm — postgresql-0002 (preptlist.c)");
|
||||
System.out.println("Defect: tlist_member O(n) check per var while building junk tlist");
|
||||
System.out.println("Fix: HashSet<Var> / Bitmapset — O(1) per check");
|
||||
System.out.println();
|
||||
|
||||
// Simulate a MERGE with multiple actions referencing many columns.
|
||||
// resultRelation=1, source table=2, many columns referenced.
|
||||
int[] sizes = {10, 20, 50, 100, 200, 500};
|
||||
System.out.printf("%-8s %-14s %-10s %-10s %s%n",
|
||||
"N vars", "Defective ops", "Fixed ops", "Speedup", "Tlist size");
|
||||
System.out.println("─".repeat(62));
|
||||
|
||||
for (int n : sizes) {
|
||||
List<Var> vars = buildVarPool(n, /*resultRelation=*/1, /*duplicates=*/n / 2);
|
||||
Result def = defectiveBuildTlist(vars, 1);
|
||||
Result fix = fixedBuildTlist(vars, 1);
|
||||
|
||||
// Sanity: both versions must produce the same tlist
|
||||
assert def.tlist.size() == fix.tlist.size()
|
||||
: "tlist size mismatch at N=" + n;
|
||||
|
||||
System.out.printf("%-8d %-14d %-10d %-10.1fx %d%n",
|
||||
vars.size(),
|
||||
def.comparisons,
|
||||
fix.comparisons,
|
||||
(double) def.comparisons / Math.max(1, fix.comparisons),
|
||||
def.tlist.size());
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Exact defective count = N*(N-1)/2 (triangular number).");
|
||||
System.out.println("Fixed count = N (one hash lookup per unique var).");
|
||||
}
|
||||
|
||||
/** Builds a pool of vars mixing unique entries and duplicates. */
|
||||
public static List<Var> buildVarPool(int uniqueVars, int resultRelation, int extraDuplicates) {
|
||||
List<Var> pool = new ArrayList<>();
|
||||
// unique vars from relation 2
|
||||
for (int att = 1; att <= uniqueVars; att++) {
|
||||
pool.add(new Var(2, att, 0));
|
||||
}
|
||||
// duplicates (already in pool — trigger the O(n) scan)
|
||||
Random rng = new Random(42);
|
||||
for (int i = 0; i < extraDuplicates; i++) {
|
||||
int att = rng.nextInt(uniqueVars) + 1;
|
||||
pool.add(new Var(2, att, 0));
|
||||
}
|
||||
// result relation vars (filtered out by both versions)
|
||||
for (int att = 1; att <= 5; att++) {
|
||||
pool.add(new Var(resultRelation, att, 0));
|
||||
}
|
||||
Collections.shuffle(pool, rng);
|
||||
return pool;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue