Skip to content

Commit 97be02a

Browse files
committed
Fix NULLIF()'s handling of read-write expanded objects.
If passed a read-write expanded object pointer, the EEOP_NULLIF code would hand that same pointer to the equality function and then (unless equality was reported) also return the same pointer as its value. This is no good, because a function that receives a read-write expanded object pointer is fully entitled to scribble on or even delete the object, thus corrupting the NULLIF output. (This problem is likely unobservable with the equality functions provided in core Postgres, but it's easy to demonstrate with one coded in plpgsql.) To fix, make sure the pointer passed to the equality function is read-only. We can still return the original read-write pointer as the NULLIF result, allowing optimization of later operations. Per bug #18722 from Alexander Lakhin. This has been wrong since we invented expanded objects, so back-patch to all supported branches. Discussion: https://postgr.es/m/18722-fd9e645448cc78b4@postgresql.org
1 parent 718af10 commit 97be02a

File tree

6 files changed

+64
-5
lines changed

6 files changed

+64
-5
lines changed

src/backend/executor/execExpr.c

+8
Original file line numberDiff line numberDiff line change
@@ -1188,6 +1188,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
11881188
op->args, op->opfuncid, op->inputcollid,
11891189
state);
11901190

1191+
/*
1192+
* If first argument is of varlena type, we'll need to ensure
1193+
* that the value passed to the comparison function is a
1194+
* read-only pointer.
1195+
*/
1196+
scratch.d.func.make_ro =
1197+
(get_typlen(exprType((Node *) linitial(op->args))) == -1);
1198+
11911199
/*
11921200
* Change opcode of call instruction to EEOP_NULLIF.
11931201
*

src/backend/executor/execExprInterp.c

+13-1
Original file line numberDiff line numberDiff line change
@@ -1295,12 +1295,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
12951295
* The arguments are already evaluated into fcinfo->args.
12961296
*/
12971297
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
1298+
Datum save_arg0 = fcinfo->args[0].value;
12981299

12991300
/* if either argument is NULL they can't be equal */
13001301
if (!fcinfo->args[0].isnull && !fcinfo->args[1].isnull)
13011302
{
13021303
Datum result;
13031304

1305+
/*
1306+
* If first argument is of varlena type, it might be an
1307+
* expanded datum. We need to ensure that the value passed to
1308+
* the comparison function is a read-only pointer. However,
1309+
* if we end by returning the first argument, that will be the
1310+
* original read-write pointer if it was read-write.
1311+
*/
1312+
if (op->d.func.make_ro)
1313+
fcinfo->args[0].value =
1314+
MakeExpandedObjectReadOnlyInternal(save_arg0);
1315+
13041316
fcinfo->isnull = false;
13051317
result = op->d.func.fn_addr(fcinfo);
13061318

@@ -1315,7 +1327,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
13151327
}
13161328

13171329
/* Arguments aren't equal, so return the first one */
1318-
*op->resvalue = fcinfo->args[0].value;
1330+
*op->resvalue = save_arg0;
13191331
*op->resnull = fcinfo->args[0].isnull;
13201332

13211333
EEO_NEXT();

src/backend/jit/llvm/llvmjit_expr.c

+29-4
Original file line numberDiff line numberDiff line change
@@ -1562,6 +1562,9 @@ llvm_compile_expr(ExprState *state)
15621562

15631563
v_fcinfo = l_ptr_const(fcinfo, l_ptr(StructFunctionCallInfoData));
15641564

1565+
/* save original arg[0] */
1566+
v_arg0 = l_funcvalue(b, v_fcinfo, 0);
1567+
15651568
/* if either argument is NULL they can't be equal */
15661569
v_argnull0 = l_funcnull(b, v_fcinfo, 0);
15671570
v_argnull1 = l_funcnull(b, v_fcinfo, 1);
@@ -1578,20 +1581,42 @@ llvm_compile_expr(ExprState *state)
15781581

15791582
/* one (or both) of the arguments are null, return arg[0] */
15801583
LLVMPositionBuilderAtEnd(b, b_hasnull);
1581-
v_arg0 = l_funcvalue(b, v_fcinfo, 0);
15821584
LLVMBuildStore(b, v_argnull0, v_resnullp);
15831585
LLVMBuildStore(b, v_arg0, v_resvaluep);
15841586
LLVMBuildBr(b, opblocks[opno + 1]);
15851587

15861588
/* build block to invoke function and check result */
15871589
LLVMPositionBuilderAtEnd(b, b_nonull);
15881590

1591+
/*
1592+
* If first argument is of varlena type, it might be an
1593+
* expanded datum. We need to ensure that the value
1594+
* passed to the comparison function is a read-only
1595+
* pointer. However, if we end by returning the first
1596+
* argument, that will be the original read-write pointer
1597+
* if it was read-write.
1598+
*/
1599+
if (op->d.func.make_ro)
1600+
{
1601+
LLVMValueRef v_params[1];
1602+
LLVMValueRef v_arg0_ro;
1603+
1604+
v_params[0] = v_arg0;
1605+
v_arg0_ro =
1606+
l_call(b,
1607+
llvm_pg_var_func_type("MakeExpandedObjectReadOnlyInternal"),
1608+
llvm_pg_func(mod, "MakeExpandedObjectReadOnlyInternal"),
1609+
v_params, lengthof(v_params), "");
1610+
LLVMBuildStore(b, v_arg0_ro,
1611+
l_funcvaluep(b, v_fcinfo, 0));
1612+
}
1613+
15891614
v_retval = BuildV1Call(context, b, mod, fcinfo, &v_fcinfo_isnull);
15901615

15911616
/*
1592-
* If result not null, and arguments are equal return null
1593-
* (same result as if there'd been NULLs, hence reuse
1594-
* b_hasnull).
1617+
* If result not null and arguments are equal return null,
1618+
* else return arg[0] (same result as if there'd been
1619+
* NULLs, hence reuse b_hasnull).
15951620
*/
15961621
v_argsequal = LLVMBuildAnd(b,
15971622
LLVMBuildICmp(b, LLVMIntEQ,

src/include/executor/execExpr.h

+1
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,7 @@ typedef struct ExprEvalStep
356356
/* faster to access without additional indirection: */
357357
PGFunction fn_addr; /* actual call address */
358358
int nargs; /* number of arguments */
359+
bool make_ro; /* make arg0 R/O (used only for NULLIF) */
359360
} func;
360361

361362
/* for EEOP_BOOL_*_STEP */

src/test/regress/expected/case.out

+8
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,14 @@ SELECT CASE make_ad(1,2)
397397
right
398398
(1 row)
399399

400+
-- While we're here, also test handling of a NULLIF arg that is a read/write
401+
-- object (bug #18722)
402+
SELECT NULLIF(make_ad(1,2), array[2,3]::arrdomain);
403+
nullif
404+
--------
405+
{1,2}
406+
(1 row)
407+
400408
ROLLBACK;
401409
-- Test interaction of CASE with ArrayCoerceExpr (bug #15471)
402410
BEGIN;

src/test/regress/sql/case.sql

+5
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,11 @@ SELECT CASE make_ad(1,2)
242242
WHEN array[1,2]::arrdomain THEN 'right'
243243
END;
244244

245+
-- While we're here, also test handling of a NULLIF arg that is a read/write
246+
-- object (bug #18722)
247+
248+
SELECT NULLIF(make_ad(1,2), array[2,3]::arrdomain);
249+
245250
ROLLBACK;
246251

247252
-- Test interaction of CASE with ArrayCoerceExpr (bug #15471)

0 commit comments

Comments
 (0)