Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 78d5952

Browse files
committed
Ensure result of an aggregate's finalfunc is made read-only.
The finalfunc might return a read-write expanded object. If we de-duplicate multiple call sites for the aggregate, any function(s) receiving the aggregate result earlier could alter or destroy the value that reaches the ones called later. This is a brown-paper-bag bug in commit 42b746d, because we actually considered the need for read-only-ness but failed to realize that it applied to the case with a finalfunc as well as the case without. Per report from Justin Pryzby. New error in HEAD, no need for back-patch. Discussion: https://postgr.es/m/ZDm5TuKsh3tzoEjz@telsasoft.com
1 parent 1c54b93 commit 78d5952

File tree

4 files changed

+96
-5
lines changed

4 files changed

+96
-5
lines changed

src/backend/executor/nodeAgg.c

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1040,9 +1040,10 @@ process_ordered_aggregate_multi(AggState *aggstate,
10401040
* (But note that in some cases, such as when there is no finalfn, the
10411041
* result might be a pointer to or into the agg's transition value.)
10421042
*
1043-
* The finalfn uses the state as set in the transno. This also might be
1043+
* The finalfn uses the state as set in the transno. This also might be
10441044
* being used by another aggregate function, so it's important that we do
1045-
* nothing destructive here.
1045+
* nothing destructive here. Moreover, the aggregate's final value might
1046+
* get used in multiple places, so we mustn't return a R/W expanded datum.
10461047
*/
10471048
static void
10481049
finalize_aggregate(AggState *aggstate,
@@ -1116,8 +1117,13 @@ finalize_aggregate(AggState *aggstate,
11161117
}
11171118
else
11181119
{
1119-
*resultVal = FunctionCallInvoke(fcinfo);
1120+
Datum result;
1121+
1122+
result = FunctionCallInvoke(fcinfo);
11201123
*resultIsNull = fcinfo->isnull;
1124+
*resultVal = MakeExpandedObjectReadOnly(result,
1125+
fcinfo->isnull,
1126+
peragg->resulttypeLen);
11211127
}
11221128
aggstate->curperagg = NULL;
11231129
}
@@ -1165,6 +1171,7 @@ finalize_partialaggregate(AggState *aggstate,
11651171
else
11661172
{
11671173
FunctionCallInfo fcinfo = pertrans->serialfn_fcinfo;
1174+
Datum result;
11681175

11691176
fcinfo->args[0].value =
11701177
MakeExpandedObjectReadOnly(pergroupstate->transValue,
@@ -1173,8 +1180,11 @@ finalize_partialaggregate(AggState *aggstate,
11731180
fcinfo->args[0].isnull = pergroupstate->transValueIsNull;
11741181
fcinfo->isnull = false;
11751182

1176-
*resultVal = FunctionCallInvoke(fcinfo);
1183+
result = FunctionCallInvoke(fcinfo);
11771184
*resultIsNull = fcinfo->isnull;
1185+
*resultVal = MakeExpandedObjectReadOnly(result,
1186+
fcinfo->isnull,
1187+
peragg->resulttypeLen);
11781188
}
11791189
}
11801190
else

src/backend/executor/nodeWindowAgg.c

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,10 +623,15 @@ finalize_windowaggregate(WindowAggState *winstate,
623623
}
624624
else
625625
{
626+
Datum res;
627+
626628
winstate->curaggcontext = peraggstate->aggcontext;
627-
*result = FunctionCallInvoke(fcinfo);
629+
res = FunctionCallInvoke(fcinfo);
628630
winstate->curaggcontext = NULL;
629631
*isnull = fcinfo->isnull;
632+
*result = MakeExpandedObjectReadOnly(res,
633+
fcinfo->isnull,
634+
peraggstate->resulttypeLen);
630635
}
631636
}
632637
else

src/test/regress/expected/aggregates.out

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2758,6 +2758,43 @@ SELECT balk(hundred) FROM tenk1;
27582758

27592759
(1 row)
27602760

2761+
ROLLBACK;
2762+
-- test multiple usage of an aggregate whose finalfn returns a R/W datum
2763+
BEGIN;
2764+
CREATE FUNCTION rwagg_sfunc(x anyarray, y anyarray) RETURNS anyarray
2765+
LANGUAGE plpgsql IMMUTABLE AS $$
2766+
BEGIN
2767+
RETURN array_fill(y[1], ARRAY[4]);
2768+
END;
2769+
$$;
2770+
CREATE FUNCTION rwagg_finalfunc(x anyarray) RETURNS anyarray
2771+
LANGUAGE plpgsql STRICT IMMUTABLE AS $$
2772+
DECLARE
2773+
res x%TYPE;
2774+
BEGIN
2775+
-- assignment is essential for this test, it expands the array to R/W
2776+
res := array_fill(x[1], ARRAY[4]);
2777+
RETURN res;
2778+
END;
2779+
$$;
2780+
CREATE AGGREGATE rwagg(anyarray) (
2781+
STYPE = anyarray,
2782+
SFUNC = rwagg_sfunc,
2783+
FINALFUNC = rwagg_finalfunc
2784+
);
2785+
CREATE FUNCTION eatarray(x real[]) RETURNS real[]
2786+
LANGUAGE plpgsql STRICT IMMUTABLE AS $$
2787+
BEGIN
2788+
x[1] := x[1] + 1;
2789+
RETURN x;
2790+
END;
2791+
$$;
2792+
SELECT eatarray(rwagg(ARRAY[1.0::real])), eatarray(rwagg(ARRAY[1.0::real]));
2793+
eatarray | eatarray
2794+
-----------+-----------
2795+
{2,1,1,1} | {2,1,1,1}
2796+
(1 row)
2797+
27612798
ROLLBACK;
27622799
-- test coverage for aggregate combine/serial/deserial functions
27632800
BEGIN;

src/test/regress/sql/aggregates.sql

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,6 +1207,45 @@ SELECT balk(hundred) FROM tenk1;
12071207

12081208
ROLLBACK;
12091209

1210+
-- test multiple usage of an aggregate whose finalfn returns a R/W datum
1211+
BEGIN;
1212+
1213+
CREATE FUNCTION rwagg_sfunc(x anyarray, y anyarray) RETURNS anyarray
1214+
LANGUAGE plpgsql IMMUTABLE AS $$
1215+
BEGIN
1216+
RETURN array_fill(y[1], ARRAY[4]);
1217+
END;
1218+
$$;
1219+
1220+
CREATE FUNCTION rwagg_finalfunc(x anyarray) RETURNS anyarray
1221+
LANGUAGE plpgsql STRICT IMMUTABLE AS $$
1222+
DECLARE
1223+
res x%TYPE;
1224+
BEGIN
1225+
-- assignment is essential for this test, it expands the array to R/W
1226+
res := array_fill(x[1], ARRAY[4]);
1227+
RETURN res;
1228+
END;
1229+
$$;
1230+
1231+
CREATE AGGREGATE rwagg(anyarray) (
1232+
STYPE = anyarray,
1233+
SFUNC = rwagg_sfunc,
1234+
FINALFUNC = rwagg_finalfunc
1235+
);
1236+
1237+
CREATE FUNCTION eatarray(x real[]) RETURNS real[]
1238+
LANGUAGE plpgsql STRICT IMMUTABLE AS $$
1239+
BEGIN
1240+
x[1] := x[1] + 1;
1241+
RETURN x;
1242+
END;
1243+
$$;
1244+
1245+
SELECT eatarray(rwagg(ARRAY[1.0::real])), eatarray(rwagg(ARRAY[1.0::real]));
1246+
1247+
ROLLBACK;
1248+
12101249
-- test coverage for aggregate combine/serial/deserial functions
12111250
BEGIN;
12121251

0 commit comments

Comments
 (0)