From 12662a55ce5e940941f197ba2d39bfd87facc772 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Fri, 14 Aug 2026 16:01:54 +0200
Subject: [PATCH v2] Make stringToNode infrastructure thread-safe

Whilst stringToNode was _technically_ reentrant-safe, this was only for
single-threaded workloads; the string we're decoding was still stored
in a global variable.

This patch moves the global variables into a struct that's passed around
on the stack, making this part of the code completely thread-safe.
---
 src/backend/nodes/gen_node_support.pl |   8 +-
 src/backend/nodes/read.c              |  65 ++++-------
 src/backend/nodes/readfuncs.c         | 148 +++++++++++++-------------
 src/include/nodes/nodes.h             |  15 +--
 src/include/nodes/readfuncs.h         |  17 +--
 5 files changed, 118 insertions(+), 135 deletions(-)

diff --git a/src/backend/nodes/gen_node_support.pl b/src/backend/nodes/gen_node_support.pl
index f4b1317e99f..0b766272018 100644
--- a/src/backend/nodes/gen_node_support.pl
+++ b/src/backend/nodes/gen_node_support.pl
@@ -909,7 +909,7 @@ foreach my $n (@node_types)
 
 	print $rfs "\tif (MATCH(\"$N\", "
 	  . length($N) . "))\n"
-	  . "\t\treturn (Node *) _read${n}();\n"
+	  . "\t\treturn (Node *) _read${n}(ctx);\n"
 	  unless $no_read;
 
 	next if elem $n, @custom_read_write;
@@ -930,7 +930,7 @@ _out${n}(StringInfo str, const $n *node)
 		  : 'READ_LOCALS_NO_FIELDS';
 		print $rff "
 static $n *
-_read${n}(void)
+_read${n}(ReadNodeContext *ctx)
 {
 \t$macro($n);
 
@@ -1192,8 +1192,8 @@ _read${n}(void)
 		/* Lookup CustomScanMethods by CustomName */
 		char	   *custom_name;
 		const CustomScanMethods *methods;
-		token = pg_strtok(&length); /* skip methods: */
-		token = pg_strtok(&length); /* CustomName */
+		token = pg_strtok(ctx, &length); /* skip methods: */
+		token = pg_strtok(ctx, &length); /* CustomName */
 		custom_name = nullable_string(token, length);
 		methods = GetCustomScanMethods(custom_name, false);
 		local_node->methods = methods;
diff --git a/src/backend/nodes/read.c b/src/backend/nodes/read.c
index f85cf65ea48..bacfc9b4640 100644
--- a/src/backend/nodes/read.c
+++ b/src/backend/nodes/read.c
@@ -28,15 +28,6 @@
 #include "nodes/value.h"
 
 
-/* Static state for pg_strtok */
-static const char *pg_strtok_ptr = NULL;
-
-/* State flag that determines how readfuncs.c should treat location fields */
-#ifdef DEBUG_NODE_TESTS_ENABLED
-bool		restore_location_fields = false;
-#endif
-
-
 /*
  * stringToNode -
  *	  builds a Node tree from its string representation (assumed valid)
@@ -49,36 +40,22 @@ static void *
 stringToNodeInternal(const char *str, bool restore_loc_fields)
 {
 	void	   *retval;
-	const char *save_strtok;
-#ifdef DEBUG_NODE_TESTS_ENABLED
-	bool		save_restore_location_fields;
-#endif
+	ReadNodeContext ctx;
 
 	/*
-	 * We save and restore the pre-existing state of pg_strtok. This makes the
-	 * world safe for re-entrant invocation of stringToNode, without incurring
-	 * a lot of notational overhead by having to pass the next-character
-	 * pointer around through all the readfuncs.c code.
+	 * We don't have a separate type for const StringInfos, unconstify +
+	 * careful coding will have to suffice.
 	 */
-	save_strtok = pg_strtok_ptr;
-
-	pg_strtok_ptr = str;		/* point pg_strtok at the string to read */
+	ctx.str = str;
 
 	/*
 	 * If enabled, likewise save/restore the location field handling flag.
 	 */
 #ifdef DEBUG_NODE_TESTS_ENABLED
-	save_restore_location_fields = restore_location_fields;
-	restore_location_fields = restore_loc_fields;
+	ctx.restore_location_fields = restore_loc_fields;
 #endif
 
-	retval = nodeRead(NULL, 0); /* do the reading */
-
-	pg_strtok_ptr = save_strtok;
-
-#ifdef DEBUG_NODE_TESTS_ENABLED
-	restore_location_fields = save_restore_location_fields;
-#endif
+	retval = nodeRead(&ctx, NULL, 0); /* do the reading */
 
 	return retval;
 }
@@ -150,12 +127,12 @@ stringToNodeWithLocations(const char *str)
  * as a single token.
  */
 const char *
-pg_strtok(int *length)
+pg_strtok(ReadNodeContext *ctx, int *length)
 {
 	const char *local_str;		/* working pointer to string */
 	const char *ret_str;		/* start of token to return */
 
-	local_str = pg_strtok_ptr;
+	local_str = ctx->str;
 
 	while (*local_str == ' ' || *local_str == '\n' || *local_str == '\t')
 		local_str++;
@@ -163,7 +140,7 @@ pg_strtok(int *length)
 	if (*local_str == '\0')
 	{
 		*length = 0;
-		pg_strtok_ptr = local_str;
+		ctx->str = local_str;
 		return NULL;			/* no more tokens */
 	}
 
@@ -200,7 +177,7 @@ pg_strtok(int *length)
 	if (*length == 2 && ret_str[0] == '<' && ret_str[1] == '>')
 		*length = 0;
 
-	pg_strtok_ptr = local_str;
+	ctx->str = local_str;
 
 	return ret_str;
 }
@@ -317,14 +294,14 @@ nodeTokenType(const char *token, int length)
  * this should only be invoked from within a stringToNode operation).
  */
 void *
-nodeRead(const char *token, int tok_len)
+nodeRead(ReadNodeContext *ctx, const char *token, int tok_len)
 {
 	Node	   *result;
 	NodeTag		type;
 
 	if (token == NULL)			/* need to read a token? */
 	{
-		token = pg_strtok(&tok_len);
+		token = pg_strtok(ctx, &tok_len);
 
 		if (token == NULL)		/* end of input */
 			return NULL;
@@ -335,8 +312,8 @@ nodeRead(const char *token, int tok_len)
 	switch ((int) type)
 	{
 		case LEFT_BRACE:
-			result = parseNodeString();
-			token = pg_strtok(&tok_len);
+			result = parseNodeString(ctx);
+			token = pg_strtok(ctx, &tok_len);
 			if (token == NULL || token[0] != '}')
 				elog(ERROR, "did not find '}' at end of input node");
 			break;
@@ -352,7 +329,7 @@ nodeRead(const char *token, int tok_len)
 				 * or a list of nodes/values:	(node node ...)
 				 *----------
 				 */
-				token = pg_strtok(&tok_len);
+				token = pg_strtok(ctx, &tok_len);
 				if (token == NULL)
 					elog(ERROR, "unterminated List structure");
 				if (tok_len == 1 && token[0] == 'i')
@@ -363,7 +340,7 @@ nodeRead(const char *token, int tok_len)
 						int			val;
 						char	   *endptr;
 
-						token = pg_strtok(&tok_len);
+						token = pg_strtok(ctx, &tok_len);
 						if (token == NULL)
 							elog(ERROR, "unterminated List structure");
 						if (token[0] == ')')
@@ -384,7 +361,7 @@ nodeRead(const char *token, int tok_len)
 						Oid			val;
 						char	   *endptr;
 
-						token = pg_strtok(&tok_len);
+						token = pg_strtok(ctx, &tok_len);
 						if (token == NULL)
 							elog(ERROR, "unterminated List structure");
 						if (token[0] == ')')
@@ -405,7 +382,7 @@ nodeRead(const char *token, int tok_len)
 						TransactionId val;
 						char	   *endptr;
 
-						token = pg_strtok(&tok_len);
+						token = pg_strtok(ctx, &tok_len);
 						if (token == NULL)
 							elog(ERROR, "unterminated List structure");
 						if (token[0] == ')')
@@ -428,7 +405,7 @@ nodeRead(const char *token, int tok_len)
 						int			val;
 						char	   *endptr;
 
-						token = pg_strtok(&tok_len);
+						token = pg_strtok(ctx, &tok_len);
 						if (token == NULL)
 							elog(ERROR, "unterminated Bitmapset structure");
 						if (tok_len == 1 && token[0] == ')')
@@ -449,8 +426,8 @@ nodeRead(const char *token, int tok_len)
 						/* We have already scanned next token... */
 						if (token[0] == ')')
 							break;
-						l = lappend(l, nodeRead(token, tok_len));
-						token = pg_strtok(&tok_len);
+						l = lappend(l, nodeRead(ctx, token, tok_len));
+						token = pg_strtok(ctx, &tok_len);
 						if (token == NULL)
 							elog(ERROR, "unterminated List structure");
 					}
diff --git a/src/backend/nodes/readfuncs.c b/src/backend/nodes/readfuncs.c
index b6b2ce6c792..89d70e07349 100644
--- a/src/backend/nodes/readfuncs.c
+++ b/src/backend/nodes/readfuncs.c
@@ -56,116 +56,116 @@
 
 /* Read an integer field (anything written as ":fldname %d") */
 #define READ_INT_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = atoi(token)
 
 /* Read an unsigned integer field (anything written as ":fldname %u") */
 #define READ_UINT_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = atoui(token)
 
 /* Read a signed integer field (anything written using INT64_FORMAT) */
 #define READ_INT64_FIELD(fldname) \
-	token = pg_strtok(&length); /* skip :fldname */ \
-	token = pg_strtok(&length); /* get field value */ \
+	token = pg_strtok(ctx, &length); /* skip :fldname */ \
+	token = pg_strtok(ctx, &length); /* get field value */ \
 	local_node->fldname = strtoi64(token, NULL, 10)
 
 /* Read an unsigned integer field (anything written using UINT64_FORMAT) */
 #define READ_UINT64_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = strtou64(token, NULL, 10)
 
 /* Read a long integer field (anything written as ":fldname %ld") */
 #define READ_LONG_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = atol(token)
 
 /* Read an OID field (don't hard-wire assumption that OID is same as uint) */
 #define READ_OID_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = atooid(token)
 
 /* Read a char field (ie, one ascii character) */
 #define READ_CHAR_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	/* avoid overhead of calling debackslash() for one char */ \
 	local_node->fldname = (length == 0) ? '\0' : (token[0] == '\\' ? token[1] : token[0])
 
 /* Read an enumerated-type field that was written as an integer code */
 #define READ_ENUM_FIELD(fldname, enumtype) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = (enumtype) atoi(token)
 
 /* Read a float field */
 #define READ_FLOAT_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = atof(token)
 
 /* Read a boolean field */
 #define READ_BOOL_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = strtobool(token)
 
 /* Read a character-string field */
 #define READ_STRING_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	local_node->fldname = nullable_string(token, length)
 
 /* Read a parse location field (and possibly throw away the value) */
 #ifdef DEBUG_NODE_TESTS_ENABLED
 #define READ_LOCATION_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
-	local_node->fldname = restore_location_fields ? atoi(token) : -1
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
+	local_node->fldname = ctx->restore_location_fields ? atoi(token) : -1
 #else
 #define READ_LOCATION_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	token = pg_strtok(&length);		/* get field value */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* get field value */ \
 	(void) token;				/* in case not used elsewhere */ \
 	local_node->fldname = -1	/* set field to "unknown" */
 #endif
 
 /* Read a Node field */
 #define READ_NODE_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
 	(void) token;				/* in case not used elsewhere */ \
-	local_node->fldname = nodeRead(NULL, 0)
+	local_node->fldname = nodeRead(ctx, NULL, 0)
 
 /* Read a bitmapset field */
 #define READ_BITMAPSET_FIELD(fldname) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
 	(void) token;				/* in case not used elsewhere */ \
-	local_node->fldname = _readBitmapset()
+	local_node->fldname = _readBitmapset(ctx)
 
 /* Read an attribute number array */
 #define READ_ATTRNUMBER_ARRAY(fldname, len) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	local_node->fldname = readAttrNumberCols(len)
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	local_node->fldname = readAttrNumberCols(ctx, len)
 
 /* Read an oid array */
 #define READ_OID_ARRAY(fldname, len) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	local_node->fldname = readOidCols(len)
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	local_node->fldname = readOidCols(ctx, len)
 
 /* Read an int array */
 #define READ_INT_ARRAY(fldname, len) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	local_node->fldname = readIntCols(len)
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	local_node->fldname = readIntCols(ctx, len)
 
 /* Read a bool array */
 #define READ_BOOL_ARRAY(fldname, len) \
-	token = pg_strtok(&length);		/* skip :fldname */ \
-	local_node->fldname = readBoolCols(len)
+	token = pg_strtok(ctx, &length);		/* skip :fldname */ \
+	local_node->fldname = readBoolCols(ctx, len)
 
 /* Routine exit */
 #define READ_DONE() \
@@ -204,19 +204,19 @@ nullable_string(const char *token, int length)
  * Bitmapset when we come across one in other contexts.
  */
 static Bitmapset *
-_readBitmapset(void)
+_readBitmapset(ReadNodeContext *ctx)
 {
 	Bitmapset  *result = NULL;
 
 	READ_TEMP_LOCALS();
 
-	token = pg_strtok(&length);
+	token = pg_strtok(ctx, &length);
 	if (token == NULL)
 		elog(ERROR, "incomplete Bitmapset structure");
 	if (length != 1 || token[0] != '(')
 		elog(ERROR, "unrecognized token: \"%.*s\"", length, token);
 
-	token = pg_strtok(&length);
+	token = pg_strtok(ctx, &length);
 	if (token == NULL)
 		elog(ERROR, "incomplete Bitmapset structure");
 	if (length != 1 || token[0] != 'b')
@@ -227,7 +227,7 @@ _readBitmapset(void)
 		int			val;
 		char	   *endptr;
 
-		token = pg_strtok(&length);
+		token = pg_strtok(ctx, &length);
 		if (token == NULL)
 			elog(ERROR, "unterminated Bitmapset structure");
 		if (length == 1 && token[0] == ')')
@@ -246,9 +246,9 @@ _readBitmapset(void)
  * That's somewhat historical, though, because calling nodeRead() will work.
  */
 Bitmapset *
-readBitmapset(void)
+readBitmapset(ReadNodeContext *ctx)
 {
-	return _readBitmapset();
+	return _readBitmapset(ctx);
 }
 
 #include "readfuncs.funcs.c"
@@ -260,7 +260,7 @@ readBitmapset(void)
  */
 
 static Const *
-_readConst(void)
+_readConst(ReadNodeContext *ctx)
 {
 	READ_LOCALS(Const);
 
@@ -272,23 +272,23 @@ _readConst(void)
 	READ_BOOL_FIELD(constisnull);
 	READ_LOCATION_FIELD(location);
 
-	token = pg_strtok(&length); /* skip :constvalue */
+	token = pg_strtok(ctx, &length); /* skip :constvalue */
 	if (local_node->constisnull)
-		token = pg_strtok(&length); /* skip "<>" */
+		token = pg_strtok(ctx, &length); /* skip "<>" */
 	else
-		local_node->constvalue = readDatum(local_node->constbyval);
+		local_node->constvalue = readDatum(ctx, local_node->constbyval);
 
 	READ_DONE();
 }
 
 static BoolExpr *
-_readBoolExpr(void)
+_readBoolExpr(ReadNodeContext *ctx)
 {
 	READ_LOCALS(BoolExpr);
 
 	/* do-it-yourself enum representation */
-	token = pg_strtok(&length); /* skip :boolop */
-	token = pg_strtok(&length); /* get field value */
+	token = pg_strtok(ctx, &length); /* skip :boolop */
+	token = pg_strtok(ctx, &length); /* get field value */
 	if (length == 3 && strncmp(token, "and", 3) == 0)
 		local_node->boolop = AND_EXPR;
 	else if (length == 2 && strncmp(token, "or", 2) == 0)
@@ -305,17 +305,17 @@ _readBoolExpr(void)
 }
 
 static A_Const *
-_readA_Const(void)
+_readA_Const(ReadNodeContext *ctx)
 {
 	READ_LOCALS(A_Const);
 
 	/* We expect either NULL or :val here */
-	token = pg_strtok(&length);
+	token = pg_strtok(ctx, &length);
 	if (length == 4 && strncmp(token, "NULL", 4) == 0)
 		local_node->isnull = true;
 	else
 	{
-		union ValUnion *tmp = nodeRead(NULL, 0);
+		union ValUnion *tmp = nodeRead(ctx, NULL, 0);
 
 		/* To forestall valgrind complaints, copy only the valid data */
 		switch (nodeTag(tmp))
@@ -348,7 +348,7 @@ _readA_Const(void)
 }
 
 static RangeTblEntry *
-_readRangeTblEntry(void)
+_readRangeTblEntry(ReadNodeContext *ctx)
 {
 	READ_LOCALS(RangeTblEntry);
 
@@ -452,11 +452,11 @@ _readRangeTblEntry(void)
 }
 
 static A_Expr *
-_readA_Expr(void)
+_readA_Expr(ReadNodeContext *ctx)
 {
 	READ_LOCALS(A_Expr);
 
-	token = pg_strtok(&length);
+	token = pg_strtok(ctx, &length);
 
 	if (length == 3 && strncmp(token, "ANY", 3) == 0)
 	{
@@ -526,7 +526,7 @@ _readA_Expr(void)
 	else if (length == 5 && strncmp(token, ":name", 5) == 0)
 	{
 		local_node->kind = AEXPR_OP;
-		local_node->name = nodeRead(NULL, 0);
+		local_node->name = nodeRead(ctx, NULL, 0);
 	}
 	else
 		elog(ERROR, "unrecognized A_Expr kind: \"%.*s\"", length, token);
@@ -541,7 +541,7 @@ _readA_Expr(void)
 }
 
 static ExtensibleNode *
-_readExtensibleNode(void)
+_readExtensibleNode(ReadNodeContext *ctx)
 {
 	const ExtensibleNodeMethods *methods;
 	ExtensibleNode *local_node;
@@ -549,8 +549,8 @@ _readExtensibleNode(void)
 
 	READ_TEMP_LOCALS();
 
-	token = pg_strtok(&length); /* skip :extnodename */
-	token = pg_strtok(&length); /* get extnodename */
+	token = pg_strtok(ctx, &length); /* skip :extnodename */
+	token = pg_strtok(ctx, &length); /* get extnodename */
 
 	extnodename = nullable_string(token, length);
 	if (!extnodename)
@@ -577,14 +577,14 @@ _readExtensibleNode(void)
  * The string to be read must already have been loaded into pg_strtok().
  */
 Node *
-parseNodeString(void)
+parseNodeString(ReadNodeContext *ctx)
 {
 	READ_TEMP_LOCALS();
 
 	/* Guard against stack overflow due to overly complex expressions */
 	check_stack_depth();
 
-	token = pg_strtok(&length);
+	token = pg_strtok(ctx, &length);
 
 #define MATCH(tokname, namelen) \
 	(length == namelen && memcmp(token, tokname, namelen) == 0)
@@ -604,7 +604,7 @@ parseNodeString(void)
  * so we must be told that.
  */
 Datum
-readDatum(bool typbyval)
+readDatum(ReadNodeContext *ctx, bool typbyval)
 {
 	Size		length;
 	int			tokenLength;
@@ -615,10 +615,10 @@ readDatum(bool typbyval)
 	/*
 	 * read the actual length of the value
 	 */
-	token = pg_strtok(&tokenLength);
+	token = pg_strtok(ctx, &tokenLength);
 	length = atoui(token);
 
-	token = pg_strtok(&tokenLength);	/* read the '[' */
+	token = pg_strtok(ctx, &tokenLength);	/* read the '[' */
 	if (token == NULL || token[0] != '[')
 		elog(ERROR, "expected \"[\" to start datum, but got \"%s\"; length = %zu",
 			 token ? token : "[NULL]", length);
@@ -631,7 +631,7 @@ readDatum(bool typbyval)
 		s = (char *) (&res);
 		for (Size i = 0; i < (Size) sizeof(Datum); i++)
 		{
-			token = pg_strtok(&tokenLength);
+			token = pg_strtok(ctx, &tokenLength);
 			s[i] = (char) atoi(token);
 		}
 	}
@@ -642,13 +642,13 @@ readDatum(bool typbyval)
 		s = (char *) palloc(length);
 		for (Size i = 0; i < length; i++)
 		{
-			token = pg_strtok(&tokenLength);
+			token = pg_strtok(ctx, &tokenLength);
 			s[i] = (char) atoi(token);
 		}
 		res = PointerGetDatum(s);
 	}
 
-	token = pg_strtok(&tokenLength);	/* read the ']' */
+	token = pg_strtok(ctx, &tokenLength);	/* read the ']' */
 	if (token == NULL || token[0] != ']')
 		elog(ERROR, "expected \"]\" to end datum, but got \"%s\"; length = %zu",
 			 token ? token : "[NULL]", length);
@@ -666,11 +666,11 @@ readDatum(bool typbyval)
  */
 #define READ_SCALAR_ARRAY(fnname, datatype, convfunc) \
 datatype * \
-fnname(int numCols) \
+fnname(ReadNodeContext *ctx, int numCols) \
 { \
 	datatype   *vals; \
 	READ_TEMP_LOCALS(); \
-	token = pg_strtok(&length); \
+	token = pg_strtok(ctx, &length); \
 	if (token == NULL) \
 		elog(ERROR, "incomplete scalar array"); \
 	if (length == 0) \
@@ -680,12 +680,12 @@ fnname(int numCols) \
 	vals = (datatype *) palloc(numCols * sizeof(datatype)); \
 	for (int i = 0; i < numCols; i++) \
 	{ \
-		token = pg_strtok(&length); \
+		token = pg_strtok(ctx, &length); \
 		if (token == NULL || token[0] == ')') \
 			elog(ERROR, "incomplete scalar array"); \
 		vals[i] = convfunc(token); \
 	} \
-	token = pg_strtok(&length); \
+	token = pg_strtok(ctx, &length); \
 	if (token == NULL || length != 1 || token[0] != ')') \
 		elog(ERROR, "incomplete scalar array"); \
 	return vals; \
diff --git a/src/include/nodes/nodes.h b/src/include/nodes/nodes.h
index 372eee20680..be29644b431 100644
--- a/src/include/nodes/nodes.h
+++ b/src/include/nodes/nodes.h
@@ -207,16 +207,19 @@ extern char *bmsToString(const struct Bitmapset *bms);
 /*
  * nodes/{readfuncs.c,read.c}
  */
+
+typedef struct ReadNodeContext ReadNodeContext;
+
 extern void *stringToNode(const char *str);
 #ifdef DEBUG_NODE_TESTS_ENABLED
 extern void *stringToNodeWithLocations(const char *str);
 #endif
-extern struct Bitmapset *readBitmapset(void);
-extern Datum readDatum(bool typbyval);
-extern bool *readBoolCols(int numCols);
-extern int *readIntCols(int numCols);
-extern Oid *readOidCols(int numCols);
-extern int16 *readAttrNumberCols(int numCols);
+extern struct Bitmapset *readBitmapset(ReadNodeContext *ctx);
+extern Datum readDatum(ReadNodeContext *ctx, bool typbyval);
+extern bool *readBoolCols(ReadNodeContext *ctx, int numCols);
+extern int *readIntCols(ReadNodeContext *ctx, int numCols);
+extern Oid *readOidCols(ReadNodeContext *ctx, int numCols);
+extern int16 *readAttrNumberCols(ReadNodeContext *ctx, int numCols);
 
 /*
  * nodes/copyfuncs.c
diff --git a/src/include/nodes/readfuncs.h b/src/include/nodes/readfuncs.h
index 0a5abdd9691..92ca3a800e6 100644
--- a/src/include/nodes/readfuncs.h
+++ b/src/include/nodes/readfuncs.h
@@ -16,23 +16,26 @@
 
 #include "nodes/nodes.h"
 
-/*
- * variable in read.c that needs to be accessible to readfuncs.c
- */
+typedef struct ReadNodeContext
+{
+	/* the string that's being parsed */
+	const char *str;
 #ifdef DEBUG_NODE_TESTS_ENABLED
-extern PGDLLIMPORT bool restore_location_fields;
+	/* state flag determining how readfuncs.c should treat location fields */
+	bool		restore_location_fields;
 #endif
+} ReadNodeContext;
 
 /*
  * prototypes for functions in read.c (the lisp token parser)
  */
-extern const char *pg_strtok(int *length);
+extern const char *pg_strtok(ReadNodeContext *ctx, int *length);
 extern char *debackslash(const char *token, int length);
-extern void *nodeRead(const char *token, int tok_len);
+extern void *nodeRead(ReadNodeContext *ctx, const char *token, int tok_len);
 
 /*
  * prototypes for functions in readfuncs.c
  */
-extern Node *parseNodeString(void);
+extern Node *parseNodeString(ReadNodeContext *ctx);
 
 #endif							/* READFUNCS_H */
-- 
2.50.1 (Apple Git-155)

