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. Does this approach seem sensible? Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk? gcc/cp/ChangeLog: PR c++/97230 * constexpr.c (eval_and_check_array_index): Forward declare. (cxx_eval_component_reference): Likewise. (cxx_eval_call_expression): Verify the 'this' pointer for non-static member functions. gcc/testsuite/ChangeLog: PR c++/97230 * g++.dg/cpp0x/constexpr-member-fn1.C: New test. --- gcc/cp/constexpr.c | 72 ++++++++++++++++++- .../g++.dg/cpp0x/constexpr-member-fn1.C | 44 ++++++++++++ 2 files changed, 115 insertions(+), 1 deletion(-) 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..f62f37ce384 100644 --- a/gcc/cp/constexpr.c +++ b/gcc/cp/constexpr.c @@ -2181,6 +2181,11 @@ cxx_eval_thunk_call (const constexpr_ctx *ctx, tree t, tree thunk_fndecl, non_constant_p, overflow_p); } +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 *); + /* Subroutine of cxx_eval_constant_expression. Evaluate the call expression tree T in the context of OLD_CALL expression evaluation. */ @@ -2467,6 +2472,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t, if (*non_constant_p) return t; + tree result = NULL_TREE; depth_ok = push_cx_call_context (t); /* Remember the object we are constructing. */ @@ -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))) + { + 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. */ + 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; + } + } + } + } constexpr_call *entry = NULL; if (depth_ok && !non_constant_args && ctx->strict) 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: 04b99da898a9817e72fedb4063589648b7961ac5 -- 2.26.2