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:
russell@unturf.com 2026-03-26 17:11:57 -04:00
commit 0a580b313d
70422 changed files with 17213626 additions and 0 deletions

View file

@ -0,0 +1,194 @@
package support;
import java.util.*;
/**
* PostgresqlJoinMergeAlgorithm models analyzejoins.c site postgresql-0004.
*
* DEFECT (CWE-407): When eliminating a redundant join (remove_useless_joins),
* PostgreSQL transfers target-list expressions from the eliminated relation
* (toRemove) to the surviving relation (toKeep). For each expression in
* toRemove's reltarget, it calls list_member() to check if an equivalent
* expression already exists in toKeep's reltarget. list_member() uses
* structural equal(), making the membership check O(|toKeep.exprs|) per node.
* With N expressions in toRemove and M in toKeep, the merge is O(N × M).
*
* Site in PostgreSQL source:
* analyzejoins.c:1914 remove_self_join_rel(), reltarget merge loop
*
* Defective C pattern:
*
* foreach(lc, toRemove->reltarget->exprs) {
* Node *node = lfirst(lc);
* ChangeVarNodesExtended(node, toRemove->relid, toKeep->relid, 0, ...);
* if (!list_member(toKeep->reltarget->exprs, node)) // O(M) DEFECT
* toKeep->reltarget->exprs = lappend(..., node);
* }
*
* Fix: Build a HashSet<Expr> from toKeep's exprs before the loop.
* Each list_member() call drops from O(M) to O(1). Total: O(N + M).
*
* C note: Requires structural equality (i.e., equal()) for correctness,
* because after ChangeVarNodesExtended both toRemove's and toKeep's Var
* nodes reference toKeep->relid they may be semantically identical but
* are distinct allocations. Pointer identity (list_member_ptr) would be
* wrong and would insert duplicates. Full fix requires nodeHash() for a
* C hash set, or a Bitmapset for the Var-only case.
*
* Comparison counts (N nodes in toRemove.exprs, M in toKeep.exprs):
* defective: N × M (each toRemove node scans all M toKeep nodes)
* fixed: M + N (M to build HashSet, then N O(1) lookups)
*
* Growth when N=M=k doubles: defective 4× (quadratic), fixed 2× (linear).
*/
public class PostgresqlJoinMergeAlgorithm {
// Expr: models a rewritten target-list expression
//
// After ChangeVarNodesExtended, an expression from toRemove has its
// relation reference updated to toKeep's relid. Two expressions are
// equal() if they reference the same column (same varattno after rewrite).
// We model this as a simple integer ID.
public static final class Expr {
/** Canonical ID after relid rewrite — models varattno post-rewrite. */
public final int id;
public Expr(int id) { this.id = id; }
/** Structural equality — models PostgreSQL equal() post-rewrite. */
@Override
public boolean equals(Object o) {
return o instanceof Expr e && id == e.id;
}
@Override
public int hashCode() { return Integer.hashCode(id); }
@Override
public String toString() { return "Expr(" + id + ")"; }
}
// RelTarget: models PathTarget (toRemove / toKeep reltarget)
public static final class RelTarget {
public final List<Expr> exprs;
public RelTarget(List<Expr> exprs) {
this.exprs = new ArrayList<>(exprs);
}
}
// Result
public static final class Result {
/** toKeep's exprs after merge. */
public final List<Expr> mergedExprs;
/** Total element-level equality checks performed. */
public final long comparisons;
public Result(List<Expr> mergedExprs, long comparisons) {
this.mergedExprs = mergedExprs;
this.comparisons = comparisons;
}
}
// Defective: list_member scan O(M) per node
//
// Models the BEFORE state. ChangeVarNodesExtended is modeled as a no-op
// (the rewrite is already baked into Expr.id both sides use the same
// canonical ID space post-rewrite).
public static Result defectiveMerge(RelTarget toRemove, RelTarget toKeep) {
List<Expr> keepExprs = new ArrayList<>(toKeep.exprs);
long comparisons = 0;
for (Expr node : toRemove.exprs) {
// list_member: scan keepExprs calling equal() on each element
boolean found = false;
for (Expr existing : keepExprs) {
comparisons++;
if (existing.equals(node)) {
found = true;
break;
}
}
if (!found)
keepExprs.add(node);
}
return new Result(keepExprs, comparisons);
}
// Fixed: HashSet<Expr> from toKeep O(1) per node
//
// Build a HashSet from toKeep's exprs before the loop.
// C equivalent: nodeHash() needed for general expressions; Bitmapset
// sufficient for pure Var nodes (no nodeHash() required for that subset).
public static Result fixedMerge(RelTarget toRemove, RelTarget toKeep) {
List<Expr> keepExprs = new ArrayList<>(toKeep.exprs);
Set<Expr> keepSet = new HashSet<>(toKeep.exprs);
long comparisons = toKeep.exprs.size(); // cost of building HashSet
for (Expr node : toRemove.exprs) {
comparisons++; // O(1) hash lookup
if (!keepSet.contains(node)) {
keepExprs.add(node);
keepSet.add(node);
}
}
return new Result(keepExprs, comparisons);
}
// Self-test
public static void main(String[] args) {
System.out.println("PostgresqlJoinMergeAlgorithm — postgresql-0004 (analyzejoins.c:1914)");
System.out.println("Defect: list_member O(M) scan when merging reltarget expr lists");
System.out.println("Fix: HashSet<Expr> built once from toKeep — O(1) per node");
System.out.println();
System.out.println("NOTE: C fix requires nodeHash() for full O(1) correctness.");
System.out.println(" Bitmapset covers the Var-only subset without nodeHash().");
System.out.println();
int[] sizes = {10, 25, 50, 100, 200, 500};
System.out.printf("%-12s %-16s %-12s %s%n",
"N=M exprs", "Defective ops", "Fixed ops", "Speedup");
System.out.println("".repeat(56));
Random rng = new Random(42);
for (int n : sizes) {
// toKeep has M exprs (IDs 0..M-1)
// toRemove has N exprs: half overlap with toKeep, half new
int overlap = n / 2;
List<Expr> keepExprs = new ArrayList<>();
for (int i = 0; i < n; i++) keepExprs.add(new Expr(i));
List<Expr> removeExprs = new ArrayList<>();
for (int i = 0; i < overlap; i++) removeExprs.add(new Expr(i)); // duplicates
for (int i = n; i < n + (n - overlap); i++) removeExprs.add(new Expr(i)); // new
Collections.shuffle(removeExprs, rng);
RelTarget toKeep = new RelTarget(keepExprs);
RelTarget toRemove = new RelTarget(removeExprs);
Result def = defectiveMerge(toRemove, toKeep);
Result fix = fixedMerge(toRemove, toKeep);
assert def.mergedExprs.size() == fix.mergedExprs.size()
: "merge size mismatch at N=" + n;
System.out.printf("%-12d %-16d %-12d %.1fx%n",
n,
def.comparisons,
fix.comparisons,
(double) def.comparisons / Math.max(1, fix.comparisons));
}
System.out.println();
System.out.println("Defective: O(N × M) — each toRemove node scans all toKeep nodes.");
System.out.println("Fixed: O(N + M) — build once, then O(1) lookups.");
}
}