functions.c: copy trees from source_list before parse analysis etc.
authorTom Lane <tgl@sss.pgh.pa.us>
Fri, 4 Apr 2025 22:26:51 +0000 (18:26 -0400)
committerTom Lane <tgl@sss.pgh.pa.us>
Fri, 4 Apr 2025 22:26:51 +0000 (18:26 -0400)
This is yet another bit of fallout from the fact that backend/parser
(like other code) feels free to scribble on the parse tree it's
handed.  In this case that resulted in modifying the
relatively-short-lived copy in the cached function's source_list.
That would be fine since we only need each source_list tree once
... except that if the parser fails after making some changes,
the function cache entry remains as-is and will still be there
if the user tries to execute the function again.  Then we have
problems because we're feeding a non-pristine tree to the parser.

The most expedient fix is a quick copyObject().  I considered
other answers like somehow marking the cache entry invalid
temporarily, but that would add complexity and I'm not sure
it's worth it.  In typical scenarios we'd only do this once
per function query per session.

Reported-by: Alexander Lakhin <exclusion@gmail.com>
Author: Tom Lane <tgl@sss.pgh.pa.us>
Discussion: https://postgr.es/m/6d442183-102c-498a-81d1-eeeb086cdc5a@gmail.com

src/backend/executor/functions.c

index cff75b2f647cb036755d262becbbc1941ec1dfc1..53ff614d87b29cdce32458432ab038ae5bdbe4de 100644 (file)
@@ -871,13 +871,19 @@ prepare_next_query(SQLFunctionHashEntry *func)
 
    /*
     * Parse and/or rewrite the query, creating a CachedPlanSource that holds
-    * a copy of the original parsetree.
+    * a copy of the original parsetree.  Note fine point: we make a copy of
+    * each original parsetree to ensure that the source_list in pcontext
+    * remains unmodified during parse analysis and rewrite.  This is normally
+    * unnecessary, but we have to do it in case an error is raised during
+    * parse analysis.  Otherwise, a fresh attempt to execute the function
+    * will arrive back here and try to work from a corrupted source_list.
     */
    if (!func->raw_source)
    {
        /* Source queries are already parse-analyzed */
        Query      *parsetree = list_nth_node(Query, func->source_list, qindex);
 
+       parsetree = copyObject(parsetree);
        plansource = CreateCachedPlanForQuery(parsetree,
                                              func->src,
                                              CreateCommandTag((Node *) parsetree));
@@ -889,6 +895,7 @@ prepare_next_query(SQLFunctionHashEntry *func)
        /* Source queries are raw parsetrees */
        RawStmt    *parsetree = list_nth_node(RawStmt, func->source_list, qindex);
 
+       parsetree = copyObject(parsetree);
        plansource = CreateCachedPlan(parsetree,
                                      func->src,
                                      CreateCommandTag(parsetree->stmt));