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

Commit 5b8728c

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 4ba84de commit 5b8728c

File tree

6 files changed

+64
-5
lines changed

6 files changed

+64
-5
lines changed

src/backend/executor/execExpr.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1191,6 +1191,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
11911191
op->args, op->opfuncid, op->inputcollid,
11921192
state);
11931193

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

src/backend/executor/execExprInterp.c

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1308,12 +1308,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
13081308
* The arguments are already evaluated into fcinfo->args.
13091309
*/
13101310
FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
1311+
Datum save_arg0 = fcinfo->args[0].value;
13111312

13121313
/* if either argument is NULL they can't be equal */
13131314
if (!fcinfo->args[0].isnull && !fcinfo->args[1].isnull)
13141315
{
13151316
Datum result;
13161317

1318+
/*
1319+
* If first argument is of varlena type, it might be an
1320+
* expanded datum. We need to ensure that the value passed to
1321+
* the comparison function is a read-only pointer. However,
1322+
* if we end by returning the first argument, that will be the
1323+
* original read-write pointer if it was read-write.
1324+
*/
1325+
if (op->d.func.make_ro)
1326+
fcinfo->args[0].value =
1327+
MakeExpandedObjectReadOnlyInternal(save_arg0);
1328+
13171329
fcinfo->isnull = false;
13181330
result = op->d.func.fn_addr(fcinfo);
13191331

@@ -1328,7 +1340,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
13281340
}
13291341

13301342
/* Arguments aren't equal, so return the first one */
1331-
*op->resvalue = fcinfo->args[0].value;
1343+
*op->resvalue = save_arg0;
13321344
*op->resnull = fcinfo->args[0].isnull;
13331345

13341346
EEO_NEXT();

src/backend/jit/llvm/llvmjit_expr.c

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,6 +1559,9 @@ llvm_compile_expr(ExprState *state)
15591559

15601560
v_fcinfo = l_ptr_const(fcinfo, l_ptr(StructFunctionCallInfoData));
15611561

1562+
/* save original arg[0] */
1563+
v_arg0 = l_funcvalue(b, v_fcinfo, 0);
1564+
15621565
/* if either argument is NULL they can't be equal */
15631566
v_argnull0 = l_funcnull(b, v_fcinfo, 0);
15641567
v_argnull1 = l_funcnull(b, v_fcinfo, 1);
@@ -1575,20 +1578,42 @@ llvm_compile_expr(ExprState *state)
15751578

15761579
/* one (or both) of the arguments are null, return arg[0] */
15771580
LLVMPositionBuilderAtEnd(b, b_hasnull);
1578-
v_arg0 = l_funcvalue(b, v_fcinfo, 0);
15791581
LLVMBuildStore(b, v_argnull0, v_resnullp);
15801582
LLVMBuildStore(b, v_arg0, v_resvaluep);
15811583
LLVMBuildBr(b, opblocks[opno + 1]);
15821584

15831585
/* build block to invoke function and check result */
15841586
LLVMPositionBuilderAtEnd(b, b_nonull);
15851587

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

15881613
/*
1589-
* If result not null, and arguments are equal return null
1590-
* (same result as if there'd been NULLs, hence reuse
1591-
* b_hasnull).
1614+
* If result not null and arguments are equal return null,
1615+
* else return arg[0] (same result as if there'd been
1616+
* NULLs, hence reuse b_hasnull).
15921617
*/
15931618
v_argsequal = LLVMBuildAnd(b,
15941619
LLVMBuildICmp(b, LLVMIntEQ,

src/include/executor/execExpr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ typedef struct ExprEvalStep
365365
/* faster to access without additional indirection: */
366366
PGFunction fn_addr; /* actual call address */
367367
int nargs; /* number of arguments */
368+
bool make_ro; /* make arg0 R/O (used only for NULLIF) */
368369
} func;
369370

370371
/* for EEOP_BOOL_*_STEP */

src/test/regress/expected/case.out

Lines changed: 8 additions & 0 deletions
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

Lines changed: 5 additions & 0 deletions
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)