Skip to content

Commit c027d84

Browse files
committed
Fix memory leaks in record_out() and record_send().
record_out() leaks memory: it fails to free the strings returned by the per-column output functions, and also is careless about detoasted values. This results in a query-lifespan memory leakage when returning composite values to the client, because printtup() runs the output functions in the query-lifespan memory context. Fix it to handle these issues the same way printtup() does. Also fix a similar leakage in record_send(). (At some point we might want to try to run output functions in shorter-lived memory contexts, so that we don't need a zero-leakage policy for them. But that would be a significantly more invasive patch, which doesn't seem like material for back-patching.) In passing, use appendStringInfoCharMacro instead of appendStringInfoChar in the innermost data-copying loop of record_out, to try to shave a few cycles from this function's runtime. Per trouble report from Carlos Henrique Reimer. Back-patch to all supported versions.
1 parent 2faab1a commit c027d84

File tree

1 file changed

+40
-12
lines changed

1 file changed

+40
-12
lines changed

src/backend/utils/adt/rowtypes.c

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ typedef struct ColumnIOData
3232
Oid column_type;
3333
Oid typiofunc;
3434
Oid typioparam;
35+
bool typisvarlena;
3536
FmgrInfo proc;
3637
} ColumnIOData;
3738

@@ -340,6 +341,7 @@ record_out(PG_FUNCTION_ARGS)
340341
{
341342
ColumnIOData *column_info = &my_extra->columns[i];
342343
Oid column_type = tupdesc->attrs[i]->atttypid;
344+
Datum attr;
343345
char *value;
344346
char *tmp;
345347
bool nq;
@@ -363,17 +365,24 @@ record_out(PG_FUNCTION_ARGS)
363365
*/
364366
if (column_info->column_type != column_type)
365367
{
366-
bool typIsVarlena;
367-
368368
getTypeOutputInfo(column_type,
369369
&column_info->typiofunc,
370-
&typIsVarlena);
370+
&column_info->typisvarlena);
371371
fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
372372
fcinfo->flinfo->fn_mcxt);
373373
column_info->column_type = column_type;
374374
}
375375

376-
value = OutputFunctionCall(&column_info->proc, values[i]);
376+
/*
377+
* If we have a toasted datum, forcibly detoast it here to avoid
378+
* memory leakage inside the type's output routine.
379+
*/
380+
if (column_info->typisvarlena)
381+
attr = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
382+
else
383+
attr = values[i];
384+
385+
value = OutputFunctionCall(&column_info->proc, attr);
377386

378387
/* Detect whether we need double quotes for this value */
379388
nq = (value[0] == '\0'); /* force quotes for empty string */
@@ -392,17 +401,23 @@ record_out(PG_FUNCTION_ARGS)
392401

393402
/* And emit the string */
394403
if (nq)
395-
appendStringInfoChar(&buf, '"');
404+
appendStringInfoCharMacro(&buf, '"');
396405
for (tmp = value; *tmp; tmp++)
397406
{
398407
char ch = *tmp;
399408

400409
if (ch == '"' || ch == '\\')
401-
appendStringInfoChar(&buf, ch);
402-
appendStringInfoChar(&buf, ch);
410+
appendStringInfoCharMacro(&buf, ch);
411+
appendStringInfoCharMacro(&buf, ch);
403412
}
404413
if (nq)
405-
appendStringInfoChar(&buf, '"');
414+
appendStringInfoCharMacro(&buf, '"');
415+
416+
pfree(value);
417+
418+
/* Clean up detoasted copy, if any */
419+
if (DatumGetPointer(attr) != DatumGetPointer(values[i]))
420+
pfree(DatumGetPointer(attr));
406421
}
407422

408423
appendStringInfoChar(&buf, ')');
@@ -690,6 +705,7 @@ record_send(PG_FUNCTION_ARGS)
690705
{
691706
ColumnIOData *column_info = &my_extra->columns[i];
692707
Oid column_type = tupdesc->attrs[i]->atttypid;
708+
Datum attr;
693709
bytea *outputbytes;
694710

695711
/* Ignore dropped columns in datatype */
@@ -710,23 +726,35 @@ record_send(PG_FUNCTION_ARGS)
710726
*/
711727
if (column_info->column_type != column_type)
712728
{
713-
bool typIsVarlena;
714-
715729
getTypeBinaryOutputInfo(column_type,
716730
&column_info->typiofunc,
717-
&typIsVarlena);
731+
&column_info->typisvarlena);
718732
fmgr_info_cxt(column_info->typiofunc, &column_info->proc,
719733
fcinfo->flinfo->fn_mcxt);
720734
column_info->column_type = column_type;
721735
}
722736

723-
outputbytes = SendFunctionCall(&column_info->proc, values[i]);
737+
/*
738+
* If we have a toasted datum, forcibly detoast it here to avoid
739+
* memory leakage inside the type's output routine.
740+
*/
741+
if (column_info->typisvarlena)
742+
attr = PointerGetDatum(PG_DETOAST_DATUM(values[i]));
743+
else
744+
attr = values[i];
745+
746+
outputbytes = SendFunctionCall(&column_info->proc, attr);
724747

725748
/* We assume the result will not have been toasted */
726749
pq_sendint(&buf, VARSIZE(outputbytes) - VARHDRSZ, 4);
727750
pq_sendbytes(&buf, VARDATA(outputbytes),
728751
VARSIZE(outputbytes) - VARHDRSZ);
752+
729753
pfree(outputbytes);
754+
755+
/* Clean up detoasted copy, if any */
756+
if (DatumGetPointer(attr) != DatumGetPointer(values[i]))
757+
pfree(DatumGetPointer(attr));
730758
}
731759

732760
pfree(values);

0 commit comments

Comments
 (0)