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 "" }