Skip to content

Commit 13671b4

Browse files
committed
Code review for GUC serialization/deserialization code.
The serialization code dumped core for a string-valued GUC whose value is NULL, which is a legal state. The infrastructure isn't capable of transmitting that state exactly, but fortunately, transmitting an empty string instead should be close enough (compare, eg, commit e45e990). The code potentially underestimated the space required to format a real-valued variable, both because it made an unwarranted assumption that %g output would never be longer than %e output, and because it didn't count right even for %e format. In practice this would pretty much always be masked by overestimates for other variables, but it's still wrong. Also fix boundary-case error in read_gucstate, incorrect handling of the case where guc_sourcefile is non-NULL but zero length (not clear that can happen, but if it did, this code would get totally confused), and confusingly useless check for a NULL result from read_gucstate. Andreas Seltenreich discovered the core dump; other issues noted while reading nearby code. Back-patch to 9.5 where this code was introduced. Michael Paquier and Tom Lane Discussion: <871sy78wno.fsf@credativ.de>
1 parent 67dc4cc commit 13671b4

File tree

1 file changed

+31
-21
lines changed
  • src/backend/utils/misc

1 file changed

+31
-21
lines changed

src/backend/utils/misc/guc.c

+31-21
Original file line numberDiff line numberDiff line change
@@ -8903,7 +8903,9 @@ can_skip_gucvar(struct config_generic * gconf)
89038903

89048904
/*
89058905
* estimate_variable_size:
8906-
* Estimate max size for dumping the given GUC variable.
8906+
* Compute space needed for dumping the given GUC variable.
8907+
*
8908+
* It's OK to overestimate, but not to underestimate.
89078909
*/
89088910
static Size
89098911
estimate_variable_size(struct config_generic * gconf)
@@ -8914,9 +8916,8 @@ estimate_variable_size(struct config_generic * gconf)
89148916
if (can_skip_gucvar(gconf))
89158917
return 0;
89168918

8917-
size = 0;
8918-
8919-
size = add_size(size, strlen(gconf->name) + 1);
8919+
/* Name, plus trailing zero byte. */
8920+
size = strlen(gconf->name) + 1;
89208921

89218922
/* Get the maximum display length of the GUC value. */
89228923
switch (gconf->vartype)
@@ -8937,7 +8938,7 @@ estimate_variable_size(struct config_generic * gconf)
89378938
* small values. Maximum value is 2147483647, i.e. 10 chars.
89388939
* Include one byte for sign.
89398940
*/
8940-
if (abs(*conf->variable) < 1000)
8941+
if (Abs(*conf->variable) < 1000)
89418942
valsize = 3 + 1;
89428943
else
89438944
valsize = 10 + 1;
@@ -8947,19 +8948,28 @@ estimate_variable_size(struct config_generic * gconf)
89478948
case PGC_REAL:
89488949
{
89498950
/*
8950-
* We are going to print it with %.17g. Account for sign,
8951-
* decimal point, and e+nnn notation. E.g.
8952-
* -3.9932904234000002e+110
8951+
* We are going to print it with %e with REALTYPE_PRECISION
8952+
* fractional digits. Account for sign, leading digit,
8953+
* decimal point, and exponent with up to 3 digits. E.g.
8954+
* -3.99329042340000021e+110
89538955
*/
8954-
valsize = REALTYPE_PRECISION + 1 + 1 + 5;
8956+
valsize = 1 + 1 + 1 + REALTYPE_PRECISION + 5;
89558957
}
89568958
break;
89578959

89588960
case PGC_STRING:
89598961
{
89608962
struct config_string *conf = (struct config_string *) gconf;
89618963

8962-
valsize = strlen(*conf->variable);
8964+
/*
8965+
* If the value is NULL, we transmit it as an empty string.
8966+
* Although this is not physically the same value, GUC
8967+
* generally treats a NULL the same as empty string.
8968+
*/
8969+
if (*conf->variable)
8970+
valsize = strlen(*conf->variable);
8971+
else
8972+
valsize = 0;
89638973
}
89648974
break;
89658975

@@ -8972,17 +8982,17 @@ estimate_variable_size(struct config_generic * gconf)
89728982
break;
89738983
}
89748984

8975-
/* Allow space for terminating zero-byte */
8985+
/* Allow space for terminating zero-byte for value */
89768986
size = add_size(size, valsize + 1);
89778987

89788988
if (gconf->sourcefile)
89798989
size = add_size(size, strlen(gconf->sourcefile));
89808990

8981-
/* Allow space for terminating zero-byte */
8991+
/* Allow space for terminating zero-byte for sourcefile */
89828992
size = add_size(size, 1);
89838993

8984-
/* Include line whenever we include file. */
8985-
if (gconf->sourcefile)
8994+
/* Include line whenever file is nonempty. */
8995+
if (gconf->sourcefile && gconf->sourcefile[0])
89868996
size = add_size(size, sizeof(gconf->sourceline));
89878997

89888998
size = add_size(size, sizeof(gconf->source));
@@ -9100,7 +9110,7 @@ serialize_variable(char **destptr, Size *maxbytes,
91009110
{
91019111
struct config_real *conf = (struct config_real *) gconf;
91029112

9103-
do_serialize(destptr, maxbytes, "%.*g",
9113+
do_serialize(destptr, maxbytes, "%.*e",
91049114
REALTYPE_PRECISION, *conf->variable);
91059115
}
91069116
break;
@@ -9109,7 +9119,9 @@ serialize_variable(char **destptr, Size *maxbytes,
91099119
{
91109120
struct config_string *conf = (struct config_string *) gconf;
91119121

9112-
do_serialize(destptr, maxbytes, "%s", *conf->variable);
9122+
/* NULL becomes empty string, see estimate_variable_size() */
9123+
do_serialize(destptr, maxbytes, "%s",
9124+
*conf->variable ? *conf->variable : "");
91139125
}
91149126
break;
91159127

@@ -9126,7 +9138,7 @@ serialize_variable(char **destptr, Size *maxbytes,
91269138
do_serialize(destptr, maxbytes, "%s",
91279139
(gconf->sourcefile ? gconf->sourcefile : ""));
91289140

9129-
if (gconf->sourcefile)
9141+
if (gconf->sourcefile && gconf->sourcefile[0])
91309142
do_serialize_binary(destptr, maxbytes, &gconf->sourceline,
91319143
sizeof(gconf->sourceline));
91329144

@@ -9193,7 +9205,7 @@ read_gucstate(char **srcptr, char *srcend)
91939205
for (ptr = *srcptr; ptr < srcend && *ptr != '\0'; ptr++)
91949206
;
91959207

9196-
if (ptr > srcend)
9208+
if (ptr >= srcend)
91979209
elog(ERROR, "could not find null terminator in GUC state");
91989210

91999211
/* Set the new position to the byte following the terminating NUL */
@@ -9247,9 +9259,7 @@ RestoreGUCState(void *gucstate)
92479259
{
92489260
int result;
92499261

9250-
if ((varname = read_gucstate(&srcptr, srcend)) == NULL)
9251-
break;
9252-
9262+
varname = read_gucstate(&srcptr, srcend);
92539263
varvalue = read_gucstate(&srcptr, srcend);
92549264
varsourcefile = read_gucstate(&srcptr, srcend);
92559265
if (varsourcefile[0])

0 commit comments

Comments
 (0)