Re: BUG #18722: Processing arrays with plpgsql raises errors - Mailing list pgsql-bugs

From Tom Lane
Subject Re: BUG #18722: Processing arrays with plpgsql raises errors
Date
Msg-id 1211277.1732488552@sss.pgh.pa.us
Whole thread Raw
In response to Re: BUG #18722: Processing arrays with plpgsql raises errors  (Tom Lane <tgl@sss.pgh.pa.us>)
Responses Re: BUG #18722: Processing arrays with plpgsql raises errors
List pgsql-bugs
I wrote:
> What EEOP_NULLIF needs to do is pass a read-only pointer to the
> equality function, so that the object is not modified and remains
> available to return if we want to do so.

> Attached is a quick WIP patch to handle that.  It is missing a test
> case, but the real omission is that llvm_compile_expr()'s EEOP_NULLIF
> handling also needs to be fixed, and I'm pretty unsure how to do that.

Here's a fleshed-out patch with a test case and JIT support.  This
is about the first time I've messed with LLVM, so I wouldn't mind
some review of what I did in llvmjit_expr.c.  In particular, do I
correctly understand that "l_funcvalue(b, v_fcinfo, 0)" produces
a reference to a copy of the initial value of args[0].value?
It seems to work in testing --- the original R/W pointer is returned
out of the expression --- but if we returned the R/O pointer instead
it'd be mighty difficult to detect the resulting inefficiency.

> I'm wondering now if any of our other conditional expressions have
> similar bugs ...

I was amused to discover that case.sql already had infrastructure
suitable for testing this, thanks to bug #14472.

            regards, tom lane

diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 8f7a534005..69d36f70b3 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -1191,6 +1191,14 @@ ExecInitExprRec(Expr *node, ExprState *state,
                              op->args, op->opfuncid, op->inputcollid,
                              state);

+                /*
+                 * If first argument is of varlena type, we'll need to ensure
+                 * that the value passed to the comparison function is a
+                 * read-only pointer.
+                 */
+                scratch.d.func.make_ro =
+                    (get_typlen(exprType((Node *) linitial(op->args))) == -1);
+
                 /*
                  * Change opcode of call instruction to EEOP_NULLIF.
                  *
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 30c5a19aad..42da962178 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -1308,12 +1308,24 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
              * The arguments are already evaluated into fcinfo->args.
              */
             FunctionCallInfo fcinfo = op->d.func.fcinfo_data;
+            Datum        save_arg0 = fcinfo->args[0].value;

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

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

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

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

             EEO_NEXT();
diff --git a/src/backend/jit/llvm/llvmjit_expr.c b/src/backend/jit/llvm/llvmjit_expr.c
index 9cec17544b..c533f55254 100644
--- a/src/backend/jit/llvm/llvmjit_expr.c
+++ b/src/backend/jit/llvm/llvmjit_expr.c
@@ -1559,6 +1559,9 @@ llvm_compile_expr(ExprState *state)

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

+                    /* save original arg[0] */
+                    v_arg0 = l_funcvalue(b, v_fcinfo, 0);
+
                     /* if either argument is NULL they can't be equal */
                     v_argnull0 = l_funcnull(b, v_fcinfo, 0);
                     v_argnull1 = l_funcnull(b, v_fcinfo, 1);
@@ -1575,7 +1578,6 @@ llvm_compile_expr(ExprState *state)

                     /* one (or both) of the arguments are null, return arg[0] */
                     LLVMPositionBuilderAtEnd(b, b_hasnull);
-                    v_arg0 = l_funcvalue(b, v_fcinfo, 0);
                     LLVMBuildStore(b, v_argnull0, v_resnullp);
                     LLVMBuildStore(b, v_arg0, v_resvaluep);
                     LLVMBuildBr(b, opblocks[opno + 1]);
@@ -1583,12 +1585,35 @@ llvm_compile_expr(ExprState *state)
                     /* build block to invoke function and check result */
                     LLVMPositionBuilderAtEnd(b, b_nonull);

+                    /*
+                     * If first argument is of varlena type, it might be an
+                     * expanded datum.  We need to ensure that the value
+                     * passed to the comparison function is a read-only
+                     * pointer.  However, if we end by returning the first
+                     * argument, that will be the original read-write pointer
+                     * if it was read-write.
+                     */
+                    if (op->d.func.make_ro)
+                    {
+                        LLVMValueRef v_params[1];
+                        LLVMValueRef v_arg0_ro;
+
+                        v_params[0] = v_arg0;
+                        v_arg0_ro =
+                            l_call(b,
+                                   llvm_pg_var_func_type("MakeExpandedObjectReadOnlyInternal"),
+                                   llvm_pg_func(mod, "MakeExpandedObjectReadOnlyInternal"),
+                                   v_params, lengthof(v_params), "");
+                        LLVMBuildStore(b, v_arg0_ro,
+                                       l_funcvaluep(b, v_fcinfo, 0));
+                    }
+
                     v_retval = BuildV1Call(context, b, mod, fcinfo, &v_fcinfo_isnull);

                     /*
-                     * If result not null, and arguments are equal return null
-                     * (same result as if there'd been NULLs, hence reuse
-                     * b_hasnull).
+                     * If result not null and arguments are equal return null,
+                     * else return arg[0] (same result as if there'd been
+                     * NULLs, hence reuse b_hasnull).
                      */
                     v_argsequal = LLVMBuildAnd(b,
                                                LLVMBuildICmp(b, LLVMIntEQ,
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index cd97dfa062..56fb0d0adb 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -365,6 +365,7 @@ typedef struct ExprEvalStep
             /* faster to access without additional indirection: */
             PGFunction    fn_addr;    /* actual call address */
             int            nargs;    /* number of arguments */
+            bool        make_ro;    /* make arg0 R/O (used only for NULLIF) */
         }            func;

         /* for EEOP_BOOL_*_STEP */
diff --git a/src/test/regress/expected/case.out b/src/test/regress/expected/case.out
index f5136c17ab..efee7fc431 100644
--- a/src/test/regress/expected/case.out
+++ b/src/test/regress/expected/case.out
@@ -397,6 +397,14 @@ SELECT CASE make_ad(1,2)
  right
 (1 row)

+-- While we're here, also test handling of a NULLIF arg that is a read/write
+-- object (bug #18722)
+SELECT NULLIF(make_ad(1,2), array[2,3]::arrdomain);
+ nullif
+--------
+ {1,2}
+(1 row)
+
 ROLLBACK;
 -- Test interaction of CASE with ArrayCoerceExpr (bug #15471)
 BEGIN;
diff --git a/src/test/regress/sql/case.sql b/src/test/regress/sql/case.sql
index 83fe43be6b..388d4c6f52 100644
--- a/src/test/regress/sql/case.sql
+++ b/src/test/regress/sql/case.sql
@@ -242,6 +242,11 @@ SELECT CASE make_ad(1,2)
   WHEN array[1,2]::arrdomain THEN 'right'
   END;

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

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

pgsql-bugs by date:

Previous
From: Tom Lane
Date:
Subject: Re: Detection of hadware feature => please do not use signal
Next
From: Thomas Munro
Date:
Subject: Re: Detection of hadware feature => please do not use signal