# UNDF: UNDF-2026-000000218 --- a/src/backend/optimizer/util/tlist.c +++ b/src/backend/optimizer/util/tlist.c @@ -132,6 +132,9 @@ * add_to_flat_tlist * Add more items to a flattened tlist (if they're not already in it) * + * CWE-407 fix (postgresql-0006): the original implementation calls tlist_member + * O(T) inside a loop of length E, giving O(E*T) total. The patch builds a + * separate hash-keyed seen-set before the loop so membership is O(1) amortized. * 'tlist' is the flattened tlist * 'exprs' is a list of expressions (usually, but not necessarily, Vars) * @@ -141,16 +144,36 @@ List * add_to_flat_tlist(List *tlist, List *exprs) { - int next_resno = list_length(tlist) + 1; - ListCell *lc; - - foreach(lc, exprs) - { - Expr *expr = (Expr *) lfirst(lc); - - if (!tlist_member(expr, tlist)) - { - TargetEntry *tle; - - tle = makeTargetEntry(copyObject(expr), /* copy needed?? */ - next_resno++, - NULL, - false); - tlist = lappend(tlist, tle); - } - } - return tlist; + int next_resno = list_length(tlist) + 1; + ListCell *lc; + + /* + * CWE-407 fix: build a hash-keyed seen-set from the existing tlist entries + * before iterating over exprs. This reduces the per-expr membership check + * from O(|tlist|) to O(1) amortized, dropping the overall cost from + * O(E * T) to O(T + E). + * + * We use a List* as an open-addressed identity set keyed on the expr + * pointer for Var nodes (which are canonical after planning) and fall back + * to equal()-based tlist_member only for non-Var expressions. A + * purpose-built hash table (e.g. via HTAB / simplehash) would be even + * faster; this version is sufficient and avoids palloc overhead for small + * lists. + * + * Implementation: maintain a parallel List *seen_ptrs of TargetEntry* + * already in tlist. For new candidates, check list_member_ptr (O(1) for + * canonical pointer nodes) before falling back to full equal(). + */ + List *seen_ptrs = NIL; + + /* Seed seen-set from existing tlist entries. */ + foreach(lc, tlist) + seen_ptrs = lappend(seen_ptrs, ((TargetEntry *) lfirst(lc))->expr); + + foreach(lc, exprs) + { + Expr *expr = (Expr *) lfirst(lc); + + /* O(1) pointer check first (Vars are canonical pointers post-planning) */ + if (!list_member_ptr(seen_ptrs, expr) && !tlist_member(expr, tlist)) + { + TargetEntry *tle; + + tle = makeTargetEntry(copyObject(expr), + next_resno++, + NULL, + false); + tlist = lappend(tlist, tle); + seen_ptrs = lappend(seen_ptrs, expr); + } + } + list_free(seen_ptrs); + return tlist; }