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
224
tests/support/PostgresqlExprMembershipAlgorithm.java
Normal file
224
tests/support/PostgresqlExprMembershipAlgorithm.java
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
package support;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* PostgresqlExprMembershipAlgorithm — models equivclass.c site postgresql-0003.
|
||||
*
|
||||
* DEFECT (CWE-407): list_member() is called inside a nested loop to check
|
||||
* whether each Var pulled from an equivalence-class member expression is
|
||||
* present in a pre-built list (exprvars). The outer list (exprvars) is fixed
|
||||
* for the duration of the scan, but rebuilt from scratch via list_member()
|
||||
* on every inner-loop iteration — O(|exprvars|) per emvar, O(|exprvars| ×
|
||||
* sum(|emvars|)) total.
|
||||
*
|
||||
* Site in PostgreSQL source:
|
||||
* equivclass.c:1041 — find_em_expr_for_rel(), inner membership check
|
||||
*
|
||||
* Defective C pattern:
|
||||
*
|
||||
* exprvars = pull_var_clause(some_expr, ...); // fixed for this call
|
||||
* foreach(lc, ec->ec_members) {
|
||||
* EquivalenceMember *em = ...;
|
||||
* emvars = pull_var_clause(em->em_expr, ...); // varies per member
|
||||
* foreach(lc2, emvars) {
|
||||
* if (!list_member(exprvars, lfirst(lc2))) // O(|exprvars|) — DEFECT
|
||||
* break;
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* Fix: Convert exprvars to a HashSet<Expr> once before the outer loop.
|
||||
* Each list_member() call becomes O(1). Total cost drops from
|
||||
* O(|exprvars| × sum(|emvars|)) to O(|exprvars| + sum(|emvars|)).
|
||||
*
|
||||
* C equivalent: If emvars are pure Var nodes (common case), use a
|
||||
* Bitmapset keyed on (varno * 3200 + varattno + 1600) — no nodeHash()
|
||||
* needed. For non-Var nodes, fall back to list_member().
|
||||
*
|
||||
* Comparison counts (M ec_members, each with K emvars, exprvars size E):
|
||||
* defective: M × K × E (K checks per member, each scanning E exprvars)
|
||||
* fixed: E + M × K (E to build HashSet, then 1 per emvar check)
|
||||
*
|
||||
* Growth when E doubles: defective scales linearly with E, fixed does not.
|
||||
*/
|
||||
public class PostgresqlExprMembershipAlgorithm {
|
||||
|
||||
// ── Expr: models a PostgreSQL expression node ─────────────────────────────
|
||||
//
|
||||
// In real PostgreSQL, expressions are tree nodes compared via equal().
|
||||
// We model them as integer IDs for clarity; equals()/hashCode() are
|
||||
// based on the ID, analogous to structural equal() on two Var nodes
|
||||
// with the same (varno, varattno, varlevelsup).
|
||||
|
||||
public static final class Expr {
|
||||
public final int id; // unique expression identity (encodes Var fields)
|
||||
|
||||
public Expr(int id) { this.id = id; }
|
||||
|
||||
/** Structural equality — models PostgreSQL equal() for Var nodes. */
|
||||
@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 + ")"; }
|
||||
}
|
||||
|
||||
// ── EquivalenceMember: models EquivalenceMember with em_expr ─────────────
|
||||
|
||||
public static final class EquivalenceMember {
|
||||
public final Expr emExpr;
|
||||
public final List<Expr> emVars; // pull_var_clause(em_expr) result
|
||||
|
||||
public EquivalenceMember(Expr emExpr, List<Expr> emVars) {
|
||||
this.emExpr = emExpr;
|
||||
this.emVars = emVars;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Result ────────────────────────────────────────────────────────────────
|
||||
|
||||
public static final class Result {
|
||||
/** The first EquivalenceMember whose emvars are all present in exprvars. */
|
||||
public final EquivalenceMember found;
|
||||
/** Total element-level equality checks performed. */
|
||||
public final long comparisons;
|
||||
|
||||
public Result(EquivalenceMember found, long comparisons) {
|
||||
this.found = found;
|
||||
this.comparisons = comparisons;
|
||||
}
|
||||
}
|
||||
|
||||
// ── Defective: list_member scan — O(|exprvars|) per emvar ────────────────
|
||||
|
||||
public static Result defectiveFindEm(
|
||||
List<Expr> exprvars,
|
||||
List<EquivalenceMember> ecMembers) {
|
||||
|
||||
long comparisons = 0;
|
||||
|
||||
for (EquivalenceMember em : ecMembers) {
|
||||
boolean allPresent = true;
|
||||
|
||||
for (Expr emvar : em.emVars) {
|
||||
// list_member: scan exprvars calling equal() on each element
|
||||
boolean found = false;
|
||||
for (Expr ev : exprvars) {
|
||||
comparisons++;
|
||||
if (ev.equals(emvar)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
allPresent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPresent)
|
||||
return new Result(em, comparisons);
|
||||
}
|
||||
|
||||
return new Result(null, comparisons);
|
||||
}
|
||||
|
||||
// ── Fixed: HashSet<Expr> from exprvars — O(1) per emvar ─────────────────
|
||||
//
|
||||
// Build a HashSet from exprvars once before the outer loop.
|
||||
// C equivalent: Bitmapset for Var nodes; fall back to list_member for
|
||||
// non-Var nodes (PlaceHolderVar, Aggref, etc.).
|
||||
|
||||
public static Result fixedFindEm(
|
||||
List<Expr> exprvars,
|
||||
List<EquivalenceMember> ecMembers) {
|
||||
|
||||
// Build HashSet once — O(|exprvars|)
|
||||
Set<Expr> exprSet = new HashSet<>(exprvars);
|
||||
long comparisons = exprvars.size(); // cost of building the set
|
||||
|
||||
for (EquivalenceMember em : ecMembers) {
|
||||
boolean allPresent = true;
|
||||
|
||||
for (Expr emvar : em.emVars) {
|
||||
comparisons++; // O(1) hash lookup
|
||||
if (!exprSet.contains(emvar)) {
|
||||
allPresent = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (allPresent)
|
||||
return new Result(em, comparisons);
|
||||
}
|
||||
|
||||
return new Result(null, comparisons);
|
||||
}
|
||||
|
||||
// ── Self-test ─────────────────────────────────────────────────────────────
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("PostgresqlExprMembershipAlgorithm — postgresql-0003 (equivclass.c:1041)");
|
||||
System.out.println("Defect: list_member O(n) scan inside nested EC member loop");
|
||||
System.out.println("Fix: HashSet<Expr> built once from exprvars — O(1) per check");
|
||||
System.out.println();
|
||||
|
||||
int[] exprvarSizes = {10, 50, 100, 500, 1000};
|
||||
int emMembers = 20, emVarsPerMember = 5;
|
||||
|
||||
System.out.printf("%-12s %-16s %-12s %s%n",
|
||||
"|exprvars|", "Defective ops", "Fixed ops", "Speedup");
|
||||
System.out.println("─".repeat(56));
|
||||
|
||||
Random rng = new Random(42);
|
||||
for (int exprSize : exprvarSizes) {
|
||||
List<Expr> exprvars = buildExprList(exprSize, 0);
|
||||
List<EquivalenceMember> members = buildEcMembers(
|
||||
emMembers, emVarsPerMember, exprSize, rng);
|
||||
|
||||
Result def = defectiveFindEm(exprvars, members);
|
||||
Result fix = fixedFindEm(exprvars, members);
|
||||
|
||||
assert Objects.equals(
|
||||
def.found != null ? def.found.emExpr : null,
|
||||
fix.found != null ? fix.found.emExpr : null)
|
||||
: "result mismatch at |exprvars|=" + exprSize;
|
||||
|
||||
System.out.printf("%-12d %-16d %-12d %.1fx%n",
|
||||
exprSize,
|
||||
def.comparisons,
|
||||
fix.comparisons,
|
||||
(double) def.comparisons / Math.max(1, fix.comparisons));
|
||||
}
|
||||
|
||||
System.out.println();
|
||||
System.out.println("Defective: O(|exprvars| × M × K) — scales with exprvars size.");
|
||||
System.out.println("Fixed: O(|exprvars| + M × K) — exprvars built once.");
|
||||
}
|
||||
|
||||
public static List<Expr> buildExprList(int size, int offset) {
|
||||
List<Expr> list = new ArrayList<>(size);
|
||||
for (int i = 0; i < size; i++) list.add(new Expr(offset + i));
|
||||
return list;
|
||||
}
|
||||
|
||||
public static List<EquivalenceMember> buildEcMembers(
|
||||
int count, int emVarsEach, int exprSize, Random rng) {
|
||||
List<EquivalenceMember> members = new ArrayList<>();
|
||||
for (int i = 0; i < count; i++) {
|
||||
List<Expr> emVars = new ArrayList<>();
|
||||
for (int j = 0; j < emVarsEach; j++) {
|
||||
// Half the time pick from exprvars (found), half outside (not found)
|
||||
int id = rng.nextBoolean() ? rng.nextInt(exprSize) : exprSize + rng.nextInt(10);
|
||||
emVars.add(new Expr(id));
|
||||
}
|
||||
members.add(new EquivalenceMember(new Expr(exprSize + 100 + i), emVars));
|
||||
}
|
||||
return members;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue