82 lines
2.6 KiB
Diff
82 lines
2.6 KiB
Diff
# 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;
|
|
}
|