Avoid some other O(N^2) hazards in list manipulation.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 1 Nov 2021 20:24:39 +0000 (16:24 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Mon, 1 Nov 2021 20:24:39 +0000 (16:24 -0400)
In the same spirit as 6301c3ada, fix some more places where we were
using list_delete_first() in a loop and thereby risking O(N^2)
behavior.  It's not clear that the lists manipulated in these spots
can get long enough to be really problematic ... but it's not clear
that they can't, either, and the fixes are simple enough.

As before, back-patch to v13.

Discussion: https://postgr.es/m/CD2F0E7F-9822-45EC-A411-AE56F14DEA9F@amazon.com

contrib/pg_trgm/trgm_regexp.c
src/backend/executor/nodeAgg.c
src/backend/jit/llvm/llvmjit.c

index 64816dd3703969186fd1972c0560c7c5eb0077f2..1887a39161f85f8502d98b04646ff798b3376488 100644 (file)
@@ -907,6 +907,7 @@ transformGraph(TrgmNFA *trgmNFA)
    HASHCTL     hashCtl;
    TrgmStateKey initkey;
    TrgmState  *initstate;
+   ListCell   *lc;
 
    /* Initialize this stage's workspace in trgmNFA struct */
    trgmNFA->queue = NIL;
@@ -937,12 +938,13 @@ transformGraph(TrgmNFA *trgmNFA)
    /*
     * Recursively build the expanded graph by processing queue of states
     * (breadth-first search).  getState already put initstate in the queue.
+    * Note that getState will append new states to the queue within the loop,
+    * too; this works as long as we don't do repeat fetches using the "lc"
+    * pointer.
     */
-   while (trgmNFA->queue != NIL)
+   foreach(lc, trgmNFA->queue)
    {
-       TrgmState  *state = (TrgmState *) linitial(trgmNFA->queue);
-
-       trgmNFA->queue = list_delete_first(trgmNFA->queue);
+       TrgmState  *state = (TrgmState *) lfirst(lc);
 
        /*
         * If we overflowed then just mark state as final.  Otherwise do
@@ -966,22 +968,29 @@ transformGraph(TrgmNFA *trgmNFA)
 static void
 processState(TrgmNFA *trgmNFA, TrgmState *state)
 {
+   ListCell   *lc;
+
    /* keysQueue should be NIL already, but make sure */
    trgmNFA->keysQueue = NIL;
 
    /*
     * Add state's own key, and then process all keys added to keysQueue until
-    * queue is empty.  But we can quit if the state gets marked final.
+    * queue is finished.  But we can quit if the state gets marked final.
     */
    addKey(trgmNFA, state, &state->stateKey);
-   while (trgmNFA->keysQueue != NIL && !(state->flags & TSTATE_FIN))
+   foreach(lc, trgmNFA->keysQueue)
    {
-       TrgmStateKey *key = (TrgmStateKey *) linitial(trgmNFA->keysQueue);
+       TrgmStateKey *key = (TrgmStateKey *) lfirst(lc);
 
-       trgmNFA->keysQueue = list_delete_first(trgmNFA->keysQueue);
+       if (state->flags & TSTATE_FIN)
+           break;
        addKey(trgmNFA, state, key);
    }
 
+   /* Release keysQueue to clean up for next cycle */
+   list_free(trgmNFA->keysQueue);
+   trgmNFA->keysQueue = NIL;
+
    /*
     * Add outgoing arcs only if state isn't final (we have no interest in
     * outgoing arcs if we already match)
index c99a0de4ddb757a83a528dea6c32ee98bf37f722..f5a187cae3a485436ae6726a8fab25b7028a4e68 100644 (file)
@@ -2584,8 +2584,9 @@ agg_refill_hash_table(AggState *aggstate)
    if (aggstate->hash_batches == NIL)
        return false;
 
-   batch = linitial(aggstate->hash_batches);
-   aggstate->hash_batches = list_delete_first(aggstate->hash_batches);
+   /* hash_batches is a stack, with the top item at the end of the list */
+   batch = llast(aggstate->hash_batches);
+   aggstate->hash_batches = list_delete_last(aggstate->hash_batches);
 
    hash_agg_set_limits(aggstate->hashentrysize, batch->input_card,
                        batch->used_bits, &aggstate->hash_mem_limit,
@@ -3098,7 +3099,7 @@ hashagg_spill_finish(AggState *aggstate, HashAggSpill *spill, int setno)
        new_batch = hashagg_batch_new(tape, setno,
                                      spill->ntuples[i], cardinality,
                                      used_bits);
-       aggstate->hash_batches = lcons(new_batch, aggstate->hash_batches);
+       aggstate->hash_batches = lappend(aggstate->hash_batches, new_batch);
        aggstate->hash_batches_used++;
    }
 
@@ -3113,8 +3114,6 @@ hashagg_spill_finish(AggState *aggstate, HashAggSpill *spill, int setno)
 static void
 hashagg_reset_spill_state(AggState *aggstate)
 {
-   ListCell   *lc;
-
    /* free spills from initial pass */
    if (aggstate->hash_spills != NULL)
    {
@@ -3132,13 +3131,7 @@ hashagg_reset_spill_state(AggState *aggstate)
    }
 
    /* free batches */
-   foreach(lc, aggstate->hash_batches)
-   {
-       HashAggBatch *batch = (HashAggBatch *) lfirst(lc);
-
-       pfree(batch);
-   }
-   list_free(aggstate->hash_batches);
+   list_free_deep(aggstate->hash_batches);
    aggstate->hash_batches = NIL;
 
    /* close tape set */
index 169dad96d76bf8e00fe652dc998e274dfaa58f7b..fb294495737c4f9fb913764acb5d225a0a1e8b92 100644 (file)
@@ -171,6 +171,7 @@ static void
 llvm_release_context(JitContext *context)
 {
    LLVMJitContext *llvm_context = (LLVMJitContext *) context;
+   ListCell   *lc;
 
    /*
     * When this backend is exiting, don't clean up LLVM. As an error might
@@ -188,12 +189,9 @@ llvm_release_context(JitContext *context)
        llvm_context->module = NULL;
    }
 
-   while (llvm_context->handles != NIL)
+   foreach(lc, llvm_context->handles)
    {
-       LLVMJitHandle *jit_handle;
-
-       jit_handle = (LLVMJitHandle *) linitial(llvm_context->handles);
-       llvm_context->handles = list_delete_first(llvm_context->handles);
+       LLVMJitHandle *jit_handle = (LLVMJitHandle *) lfirst(lc);
 
 #if LLVM_VERSION_MAJOR > 11
        {
@@ -221,6 +219,8 @@ llvm_release_context(JitContext *context)
 
        pfree(jit_handle);
    }
+   list_free(llvm_context->handles);
+   llvm_context->handles = NIL;
 }
 
 /*