Skip to content

Commit 3985b75

Browse files
committed
Save/restore SPI's global variables in SPI_connect() and SPI_finish().
This patch removes two sources of interference between nominally independent functions when one SPI-using function calls another, perhaps without knowing that it does so. Chapman Flack pointed out that xml.c's query_to_xml_internal() expects SPI_tuptable and SPI_processed to stay valid across datatype output function calls; but it's possible that such a call could involve re-entrant use of SPI. It seems likely that there are similar hazards elsewhere, if not in the core code then in third-party SPI users. Previously SPI_finish() reset SPI's API globals to zeroes/nulls, which would typically make for a crash in such a situation. Restoring them to the values they had at SPI_connect() seems like a considerably more useful behavior, and it still meets the design goal of not leaving any dangling pointers to tuple tables of the function being exited. Also, cause SPI_connect() to reset these variables to zeroes/nulls after saving them. This prevents interference in the opposite direction: it's possible that a SPI-using function that's only ever been tested standalone contains assumptions that these variables start out as zeroes. That was the case as long as you were the outermost SPI user, but not so much for an inner user. Now it's consistent. Report and fix suggestion by Chapman Flack, actual patch by me. Back-patch to all supported branches. Discussion: https://postgr.es/m/9fa25bef-2e4f-1c32-22a4-3ad0723c4a17@anastigmatix.net
1 parent adfc156 commit 3985b75

File tree

2 files changed

+39
-9
lines changed

2 files changed

+39
-9
lines changed

src/backend/executor/spi.c

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,16 @@
3636
#include "utils/typcache.h"
3737

3838

39+
/*
40+
* These global variables are part of the API for various SPI functions
41+
* (a horrible API choice, but it's too late now). To reduce the risk of
42+
* interference between different SPI callers, we save and restore them
43+
* when entering/exiting a SPI nesting level.
44+
*/
3945
uint64 SPI_processed = 0;
4046
Oid SPI_lastoid = InvalidOid;
4147
SPITupleTable *SPI_tuptable = NULL;
42-
int SPI_result;
48+
int SPI_result = 0;
4349

4450
static _SPI_connection *_SPI_stack = NULL;
4551
static _SPI_connection *_SPI_current = NULL;
@@ -124,6 +130,10 @@ SPI_connect(void)
124130
_SPI_current->execCxt = NULL;
125131
_SPI_current->connectSubid = GetCurrentSubTransactionId();
126132
_SPI_current->queryEnv = NULL;
133+
_SPI_current->outer_processed = SPI_processed;
134+
_SPI_current->outer_lastoid = SPI_lastoid;
135+
_SPI_current->outer_tuptable = SPI_tuptable;
136+
_SPI_current->outer_result = SPI_result;
127137

128138
/*
129139
* Create memory contexts for this procedure
@@ -142,6 +152,15 @@ SPI_connect(void)
142152
/* ... and switch to procedure's context */
143153
_SPI_current->savedcxt = MemoryContextSwitchTo(_SPI_current->procCxt);
144154

155+
/*
156+
* Reset API global variables so that current caller cannot accidentally
157+
* depend on state of an outer caller.
158+
*/
159+
SPI_processed = 0;
160+
SPI_lastoid = InvalidOid;
161+
SPI_tuptable = NULL;
162+
SPI_result = 0;
163+
145164
return SPI_OK_CONNECT;
146165
}
147166

@@ -164,12 +183,13 @@ SPI_finish(void)
164183
_SPI_current->procCxt = NULL;
165184

166185
/*
167-
* Reset result variables, especially SPI_tuptable which is probably
186+
* Restore outer API variables, especially SPI_tuptable which is probably
168187
* pointing at a just-deleted tuptable
169188
*/
170-
SPI_processed = 0;
171-
SPI_lastoid = InvalidOid;
172-
SPI_tuptable = NULL;
189+
SPI_processed = _SPI_current->outer_processed;
190+
SPI_lastoid = _SPI_current->outer_lastoid;
191+
SPI_tuptable = _SPI_current->outer_tuptable;
192+
SPI_result = _SPI_current->outer_result;
173193

174194
/* Exit stack level */
175195
_SPI_connected--;
@@ -201,9 +221,11 @@ AtEOXact_SPI(bool isCommit)
201221
_SPI_current = _SPI_stack = NULL;
202222
_SPI_stack_depth = 0;
203223
_SPI_connected = -1;
224+
/* Reset API global variables, too */
204225
SPI_processed = 0;
205226
SPI_lastoid = InvalidOid;
206227
SPI_tuptable = NULL;
228+
SPI_result = 0;
207229
}
208230

209231
/*
@@ -241,18 +263,20 @@ AtEOSubXact_SPI(bool isCommit, SubTransactionId mySubid)
241263
}
242264

243265
/*
244-
* Pop the stack entry and reset global variables. Unlike
266+
* Restore outer global variables and pop the stack entry. Unlike
245267
* SPI_finish(), we don't risk switching to memory contexts that might
246268
* be already gone.
247269
*/
270+
SPI_processed = connection->outer_processed;
271+
SPI_lastoid = connection->outer_lastoid;
272+
SPI_tuptable = connection->outer_tuptable;
273+
SPI_result = connection->outer_result;
274+
248275
_SPI_connected--;
249276
if (_SPI_connected < 0)
250277
_SPI_current = NULL;
251278
else
252279
_SPI_current = &(_SPI_stack[_SPI_connected]);
253-
SPI_processed = 0;
254-
SPI_lastoid = InvalidOid;
255-
SPI_tuptable = NULL;
256280
}
257281

258282
if (found && isCommit)

src/include/executor/spi_priv.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ typedef struct
3636

3737
/* subtransaction in which current Executor call was started */
3838
SubTransactionId execSubid;
39+
40+
/* saved values of API global variables for previous nesting level */
41+
uint64 outer_processed;
42+
Oid outer_lastoid;
43+
SPITupleTable *outer_tuptable;
44+
int outer_result;
3945
} _SPI_connection;
4046

4147
/*

0 commit comments

Comments
 (0)