On Tue, Oct 06, 2020 at 01:06:37AM -0400, Jason Merrill via Gcc-patches wrote:
> On 10/1/20 1:08 PM, Marek Polacek wrote:
> > @@ -2496,8 +2502,72 @@ cxx_eval_call_expression (const constexpr_ctx *ctx,
> > tree t,
> > new_obj = NULL_TREE;
> > }
> > }
> > + /* Verify that the object we're invoking the function on is sane. */
> > + else if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fun)
> > + /* maybe_add_lambda_conv_op creates a null 'this' pointer. */
> > + && !LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun)))
>
> Let's look at lambda_static_thunk_p of ctx->call->fundef->decl instead; we
> don't want to allow calling a lambda op() with a null object from other
> contexts.
OK, fixed.
> > + {
> > + tree thisarg = TREE_VEC_ELT (new_call.bindings, 0);
> > + if (integer_zerop (thisarg))
> > + {
> > + if (!ctx->quiet)
> > + error_at (loc, "member call on null pointer is not allowed "
> > + "in a constant expression");
> > + *non_constant_p = true;
> > + result = error_mark_node;
> > + }
> > + else
> > + {
> > + STRIP_NOPS (thisarg);
> > + if (TREE_CODE (thisarg) == ADDR_EXPR)
> > + thisarg = TREE_OPERAND (thisarg, 0);
> > + /* Detect out-of-bounds accesses. */
> > + if (TREE_CODE (thisarg) == ARRAY_REF)
> > + {
> > + eval_and_check_array_index (ctx, thisarg, /*allow_one_past*/false,
> > + non_constant_p, overflow_p);
> > + if (*non_constant_p)
> > + result = error_mark_node;
> > + }
> > + /* Detect using an inactive member of a union. */
> > + else if (TREE_CODE (thisarg) == COMPONENT_REF)
> > + {
> > + cxx_eval_component_reference (ctx, thisarg, /*lval*/false,
> > + non_constant_p, overflow_p);
> > + if (*non_constant_p)
> > + result = error_mark_node;
> > + }
> > + /* Detect other invalid accesses like
> > - tree result = NULL_TREE;
> > + X x;
> > + (&x)[1].foo();
> > +
> > + where we'll end up with &x p+ 1. */
>
> Is there any way to combine this POINTER_PLUS_EXPR handling with
> cxx_fold_pointer_plus_expression or cxx_fold_indirect_ref?
cxx_fold_pointer_plus_expression looked like a place where the new checking
could go but in the end it didn't work. E.g., it STRIP_NOPS the lhs of the
POINTER_PLUS_EXPR so any casts would get lost.
> > + else if (TREE_CODE (thisarg) == POINTER_PLUS_EXPR)
> > + {
> > + tree op0 = TREE_OPERAND (thisarg, 0);
> > + /* This shouldn't trigger if we're accessing a base class of
> > + the object in question. */
> > + if (TREE_CODE (op0) == ADDR_EXPR
> > + && DECL_P (TREE_OPERAND (op0, 0)))
> > + {
> > + if (!ctx->quiet)
> > + {
> > + tree op1 = TREE_OPERAND (thisarg, 1);
> > + if (integer_onep (op1))
> > + error_at (loc, "cannot dereference one-past-the-end "
> > + "pointer in a constant expression");
> > + else
> > + error_at (loc, "cannot access element %qE of a "
> > + "non-array object in a constant expression",
> > + op1);
> > + }
> > + *non_constant_p = true;
> > + result = error_mark_node;
> > + }
> > + }
> > + }
> > + }
>
> These checks seem like the requirement that "A reference shall be
> initialized to refer to a valid object or function.", which matches with the
> change to describe the object argument as a reference rather than a pointer.
> Let's split this out into a separate function that expresses this, and
> perhaps then also use it to check the initializer of a reference variable.
I've moved it to a new function called cxx_verify_object_argument.
But checking the initializers of reference variables is still not handled
in this patch, because I don't know how to do that. For something like
constexpr const int &r1 = (&x)[1].m;
store_init_value calls cxx_constant_init for the init, which is
(const int &) &(&x + 4)->m
It could also be something like
(const int &) &arr[3].m
Maybe cxx_eval_constant_expression/NOP_EXPR should set some flag so that
we perform some checking when we encounter a POINTER_PLUS_EXPR or an
ARRAY_REF when evaluating op0 of the NOP_EXPR?
I've attached constexpr-ref13.C which is a testcase I played with; we
diagnose no errors there.
> And let's call it from cxx_bind_parameters_in_call.
Done.
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
-- >8 --
This PR points out that when we're invoking a non-static member function
on a null instance during constant evaluation, we should reject.
cxx_eval_call_expression calls cxx_bind_parameters_in_call which
evaluates function arguments, but it won't detect problems like these.
Well, ok, so use integer_zerop to detect a null 'this'. This also
detects member calls on a variable whose lifetime has ended, because
check_return_expr creates an artificial nullptr:
10195 else if (!processing_template_decl
10196 && maybe_warn_about_returning_address_of_local (retval,
loc)
10197 && INDIRECT_TYPE_P (valtype))
10198 retval = build2 (COMPOUND_EXPR, TREE_TYPE (retval), retval,
10199 build_zero_cst (TREE_TYPE (retval)));
It would be great if we could somehow distinguish between those two
cases, but experiments with setting TREE_THIS_VOLATILE on the zero
didn't work, so I left it be.
But by the same token, we should detect out-of-bounds accesses. For
this I'm (ab)using eval_and_check_array_index so that I don't have
to reimplement bounds checking yet again. But this only works for
ARRAY_REFs, so won't detect
X x;
(&x)[0].foo(); // ok
(&x)[1].foo(); // bad
so I've added a special handling of POINTER_PLUS_EXPRs.
While here, we should also detect using an inactive union member. For
that, I'm using cxx_eval_component_reference.
gcc/cp/ChangeLog:
PR c++/97230
* constexpr.c (eval_and_check_array_index): Forward declare.
(cxx_eval_component_reference): Likewise.
(cxx_verify_object_argument): New function.
(cxx_bind_parameters_in_call): Call it.
gcc/testsuite/ChangeLog:
PR c++/97230
* g++.dg/cpp0x/constexpr-member-fn1.C: New test.
---
gcc/cp/constexpr.c | 103 +++++++++++++++---
.../g++.dg/cpp0x/constexpr-member-fn1.C | 44 ++++++++
2 files changed, 134 insertions(+), 13 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/cpp0x/constexpr-member-fn1.C
diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index a118f8a810b..2155ed1ca19 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -1548,6 +1548,69 @@ free_constructor (tree t)
}
}
+static tree eval_and_check_array_index (const constexpr_ctx *, tree, bool,
+ bool *, bool *);
+static tree cxx_eval_component_reference (const constexpr_ctx *, tree,
+ bool, bool *, bool *);
+
+/* Verify that the object THISARG we're invoking the function on is sane. */
+
+static void
+cxx_verify_object_argument (const constexpr_ctx *ctx, tree thisarg,
+ bool *non_constant_p, bool *overflow_p)
+{
+ location_t loc = cp_expr_loc_or_input_loc (thisarg);
+ if (integer_zerop (thisarg))
+ {
+ if (!ctx->quiet)
+ error_at (loc, "member call on null pointer is not allowed "
+ "in a constant expression");
+ *non_constant_p = true;
+ }
+ else
+ {
+ STRIP_NOPS (thisarg);
+ if (TREE_CODE (thisarg) == ADDR_EXPR)
+ thisarg = TREE_OPERAND (thisarg, 0);
+ /* Detect out-of-bounds accesses. */
+ if (TREE_CODE (thisarg) == ARRAY_REF)
+ eval_and_check_array_index (ctx, thisarg, /*allow_one_past*/false,
+ non_constant_p, overflow_p);
+ /* Detect using an inactive member of a union. */
+ else if (TREE_CODE (thisarg) == COMPONENT_REF)
+ cxx_eval_component_reference (ctx, thisarg, /*lval*/false,
+ non_constant_p, overflow_p);
+ /* Detect other invalid accesses like
+
+ X x;
+ (&x)[1].foo();
+
+ where we'll end up with &x p+ 1. */
+ else if (TREE_CODE (thisarg) == POINTER_PLUS_EXPR)
+ {
+ tree op0 = TREE_OPERAND (thisarg, 0);
+ /* This shouldn't trigger if we're accessing a base class of
+ the object in question. */
+ if (TREE_CODE (op0) == ADDR_EXPR
+ && DECL_P (TREE_OPERAND (op0, 0)))
+ {
+ if (!ctx->quiet)
+ {
+ tree op1 = TREE_OPERAND (thisarg, 1);
+ if (integer_onep (op1))
+ error_at (loc, "cannot dereference one-past-the-end "
+ "pointer in a constant expression");
+ else
+ error_at (loc, "cannot access element %qE of a "
+ "non-array object in a constant expression",
+ op1);
+ }
+ *non_constant_p = true;
+ }
+ }
+ }
+}
+
/* Subroutine of cxx_eval_call_expression.
We are processing a call expression (either CALL_EXPR or
AGGR_INIT_EXPR) in the context of CTX. Evaluate
@@ -1605,21 +1668,35 @@ cxx_bind_parameters_in_call (const constexpr_ctx *ctx,
tree t,
/* For virtual calls, adjust the this argument, so that it is
the object on which the method is called, rather than
one of its bases. */
- if (i == 0 && DECL_VIRTUAL_P (fun))
+ if (i == 0)
{
- tree addr = arg;
- STRIP_NOPS (addr);
- if (TREE_CODE (addr) == ADDR_EXPR)
+ if (DECL_VIRTUAL_P (fun))
+ {
+ tree addr = arg;
+ STRIP_NOPS (addr);
+ if (TREE_CODE (addr) == ADDR_EXPR)
+ {
+ tree obj = TREE_OPERAND (addr, 0);
+ while (TREE_CODE (obj) == COMPONENT_REF
+ && DECL_FIELD_IS_BASE (TREE_OPERAND (obj, 1))
+ && !same_type_ignoring_top_level_qualifiers_p
+ (TREE_TYPE (obj), DECL_CONTEXT (fun)))
+ obj = TREE_OPERAND (obj, 0);
+ if (obj != TREE_OPERAND (addr, 0))
+ arg = build_fold_addr_expr_with_type (obj,
+ TREE_TYPE (arg));
+ }
+ }
+ else if (DECL_NONSTATIC_MEMBER_FUNCTION_P (fun)
+ && !DECL_CONSTRUCTOR_P (fun)
+ && !(ctx->call
+ && ctx->call->fundef
+ && lambda_static_thunk_p (ctx->call->fundef->decl)))
{
- tree obj = TREE_OPERAND (addr, 0);
- while (TREE_CODE (obj) == COMPONENT_REF
- && DECL_FIELD_IS_BASE (TREE_OPERAND (obj, 1))
- && !same_type_ignoring_top_level_qualifiers_p
- (TREE_TYPE (obj), DECL_CONTEXT (fun)))
- obj = TREE_OPERAND (obj, 0);
- if (obj != TREE_OPERAND (addr, 0))
- arg = build_fold_addr_expr_with_type (obj,
- TREE_TYPE (arg));
+ cxx_verify_object_argument (ctx, arg, non_constant_p,
+ overflow_p);
+ if (*non_constant_p && ctx->quiet)
+ return;
}
}
TREE_VEC_ELT (binds, i) = arg;
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-member-fn1.C
b/gcc/testsuite/g++.dg/cpp0x/constexpr-member-fn1.C
new file mode 100644
index 00000000000..8ba5b87286f
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-member-fn1.C
@@ -0,0 +1,44 @@
+// PR c++/97230
+// { dg-do compile { target c++11 } }
+
+struct X {
+ constexpr int foo () const { return 1; }
+};
+
+constexpr X *eval () { return nullptr; }
+constexpr const X *dead (X tmp) { return &tmp; } // { dg-warning "address of
local variable" }
+
+static constexpr X x;
+static constexpr X arr[3];
+
+constexpr const X *gimme () { return &x; }
+
+union U {
+ int i;
+ X x;
+};
+constexpr U u{42};
+
+void
+fn ()
+{
+ constexpr auto x1 = ((X *) nullptr)->foo(); // { dg-error "member call on
null pointer" }
+ constexpr auto x2 = (eval())->foo(); // { dg-error "member call on null
pointer" }
+ constexpr auto x3 = dead ({})->foo(); // { dg-error "member call" }
+ constexpr auto x4 = (&x)[0].foo();
+ constexpr auto x5 = (&x)[1].foo(); // { dg-error "cannot dereference
one-past-the-end pointer" }
+ constexpr auto x6 = (&x)[2].foo(); // { dg-error "cannot access element .2.
of a non-array object" }
+ constexpr auto x7 = (&x)[3].foo(); // { dg-error "cannot access element .3.
of a non-array object" }
+ constexpr auto x8 = arr[2].foo();
+ constexpr auto x9 = arr[3].foo(); // { dg-error "outside the bounds" }
+ constexpr auto *p = &arr[0];
+ constexpr auto x10 = (*p).foo();
+ constexpr auto x11 = p->foo();
+ constexpr auto x12 = (*(p + 1)).foo();
+ constexpr auto x13 = (*(p + 3)).foo(); // { dg-error "outside the bounds" }
+ constexpr auto x14 = (p + 3)->foo(); // { dg-error "outside the bounds" }
+ constexpr auto x15 = gimme()->foo();
+ constexpr auto x16 = (gimme() + 1)->foo(); // { dg-error "cannot dereference
one-past-the-end pointer" }
+ constexpr auto x17 = (gimme() + 2)->foo(); // { dg-error "cannot access
element .2. of a non-array object" }
+ constexpr auto x18 = u.x.foo(); // { dg-error "accessing .U::x. member
instead of initialized .U::i. member" }
+}
base-commit: 1e247c60df52e93c9814a3a1789a63bc07aa4542
--
2.26.2
// PR c++/97230
// { dg-do compile { target c++11 } }
struct X { int m; };
constexpr X x{};
constexpr X arr[3]{};
constexpr const X *gimme () { return &x; }
constexpr const int &r0 = (&x)[0].m;
constexpr const int &r1 = (&x)[1].m; // { dg-error "" }
constexpr const int &r2 = (&x)[2].m; // { dg-error "" }
constexpr const int &r3 = gimme()->m;
constexpr const int &r4 = (gimme() + 1)->m; // { dg-error "" }
constexpr const int &r5 = (gimme() + 2)->m; // { dg-error "" }
constexpr const int &r6 = arr[2].m;
constexpr const int &r7 = arr[3].m; // { dg-error "" }
constexpr auto *p = &arr[0];
constexpr const int &r8 = (*p).m;
constexpr const int &r9 = p->m;
constexpr const int &r10 = (p + 2)->m;
constexpr const int &r11 = (p + 3)->m; // { dg-error "" }