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 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 emVars; // pull_var_clause(em_expr) result public EquivalenceMember(Expr emExpr, List 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 exprvars, List 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 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 exprvars, List ecMembers) { // Build HashSet once — O(|exprvars|) Set 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 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 exprvars = buildExprList(exprSize, 0); List 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 buildExprList(int size, int offset) { List list = new ArrayList<>(size); for (int i = 0; i < size; i++) list.add(new Expr(offset + i)); return list; } public static List buildEcMembers( int count, int emVarsEach, int exprSize, Random rng) { List members = new ArrayList<>(); for (int i = 0; i < count; i++) { List 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; } }