Tested x86_64-pc-linux-gnu, applying to trunk.
-- 8< --
Playing with using contract_assert for libstdc++ asserts led me to rework
how they are handled in constant evaluation.
The main change is that instead of always diagnosing under
cxx_eval_outermost_constant_expr, we diagnose during evaluation if we're
diagnosing. This means that the diagnostics now get the cx_call_context,
and (if terminating) we stop evaluating at that point.
If not diagnosing, but we saw a violation in an otherwise-constant
evaluation, we evaluate again to get the diagnostic in its call context.
And to diagnose multiple observes.
This approach also fixes non-const1.C, where we were wrongly complaining
that the predicate is non-constant; since the whole initialization is
non-constant, under [expr.const.init]/4.1 we shouldn't consider assertions
yet.
This also properly gets a diagnostic that an assertion fails in an
initialization that is otherwise constant (p17-SMF*).
Also for better context, I changed check_for_failed_contracts to
1) use the location of the predicate, not the keyword.
2) use cxx_constant_value or diagnose_failing_condition to explain
what went wrong.
This also works around an issue with maybe_unwind_expanded_macro_loc firing
repeatedly in a single diagnostic group--which would be good to fix in
general, but that's not this patch.
PR c++/119497
gcc/cp/ChangeLog:
* constexpr.cc (check_for_failed_contracts): Add context.
(cxx_eval_constant_expression): Refactor contract handling.
Diagnose now if diagnosing.
(cxx_eval_outermost_constant_expr): Re-evaluate to diagnose
contract violations if otherwise constant.
gcc/testsuite/ChangeLog:
* g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-post.C
* g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-pre.C
* g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF2.C
* g++.dg/contracts/cpp26/basic.contract.eval.p7.3.C
* g++.dg/contracts/cpp26/callerside-checks/ctor.C: Add diagnostic.
* g++.dg/contracts/cpp26/non-const1.C: New test.
---
gcc/cp/constexpr.cc | 130 ++++++++++++------
.../cpp26/basic.contract.eval.p17-SMF-post.C | 2 +-
.../cpp26/basic.contract.eval.p17-SMF-pre.C | 2 +-
.../cpp26/basic.contract.eval.p17-SMF2.C | 2 +-
.../cpp26/basic.contract.eval.p7.3.C | 1 +
.../contracts/cpp26/callerside-checks/ctor.C | 2 +-
.../g++.dg/contracts/cpp26/non-const1.C | 20 +++
7 files changed, 115 insertions(+), 44 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/non-const1.C
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 3209d435029..3c92281cdd0 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -53,6 +53,7 @@ do {
\
static HOST_WIDE_INT find_array_ctor_elt (tree ary, tree dindex,
bool insert = false);
static int array_index_cmp (tree key, tree index);
+static bool check_for_failed_contracts (const constexpr_ctx *ctx);
/* Returns true iff FUN is an instantiation of a constexpr function
template or a defaulted constexpr function. */
@@ -11260,42 +11261,43 @@ cxx_eval_constant_expression (const constexpr_ctx
*ctx, tree t,
r = void_node;
/* Only record the first fail, and do not go further is the semantic
is 'ignore'. */
- if (*non_constant_p || ctx->global->contract_statement
+ if (*non_constant_p
+ || (ctx->global->contract_statement
+ && contract_terminating_p (ctx->global->contract_statement))
|| contract_ignored_p (t))
break;
tree cond = CONTRACT_CONDITION (t);
- if (!potential_rvalue_constant_expression (cond))
+ bool ctrct_non_const_p = !potential_rvalue_constant_expression (cond);
+ tree eval = boolean_true_node;
+ if (!ctrct_non_const_p)
{
- ctx->global->contract_statement = t;
- ctx->global->contract_condition_non_const = true;
- break;
+ /* We need to evaluate and stash the result of this here, since
+ whether it needs to be reported (and how) depends on whether the
+ containing expression is otherwise const. */
+ bool ctrct_overflow_p = false;
+ tree jmp_target = NULL_TREE;
+ constexpr_ctx new_ctx = *ctx;
+ new_ctx.quiet = true;
+ /* Avoid modification of existing values. */
+ modifiable_tracker ms (new_ctx.global);
+ eval = (cxx_eval_constant_expression
+ (&new_ctx, cond, vc_prvalue, &ctrct_non_const_p,
+ &ctrct_overflow_p, &jmp_target));
}
- /* We need to evaluate and stash the result of this here, since whether
- it needs to be reported (and how) depends on whether the containing
- expression is otherwise const. */
- bool ctrct_non_const_p = false;
- bool ctrct_overflow_p = false;
- tree jmp_target = NULL_TREE;
- constexpr_ctx new_ctx = *ctx;
- new_ctx.quiet = true;
- /* Avoid modification of existing values. */
- modifiable_tracker ms (new_ctx.global);
- tree eval =
- cxx_eval_constant_expression (&new_ctx, cond, vc_prvalue,
- &ctrct_non_const_p,
- &ctrct_overflow_p, &jmp_target);
- /* Not a constant. */
- if (ctrct_non_const_p)
+ /* Not a constant or check failed. */
+ if (ctrct_non_const_p || integer_zerop (eval))
{
ctx->global->contract_statement = t;
- ctx->global->contract_condition_non_const = true;
- break;
+ ctx->global->contract_condition_non_const = ctrct_non_const_p;
}
- /* Constant, but check failed. */
- if (integer_zerop (eval))
- ctx->global->contract_statement = t;
+
+ /* If diagnosing, do it while we have the cx_call_context. */
+ if (!ctx->quiet
+ && check_for_failed_contracts (ctx))
+ /* And for a terminating semantic, stop evaluating. */
+ *non_constant_p = true;
}
break;
@@ -11521,13 +11523,15 @@ mark_non_constant (tree t)
a failed or non-constant contract that would invalidate this. */
static bool
-check_for_failed_contracts (constexpr_ctx *ctx)
+check_for_failed_contracts (const constexpr_ctx *ctx)
{
constexpr_global_ctx *const global_ctx = ctx->global;
if (!flag_contracts || !global_ctx->contract_statement)
return false;
location_t loc = EXPR_LOCATION (global_ctx->contract_statement);
+ tree cond = CONTRACT_CONDITION (global_ctx->contract_statement);
+ loc = cp_expr_loc_or_loc (cond, loc);
enum diagnostics::kind kind;
bool error = false;
/* [intro.compliance.general]/2.3.4. */
@@ -11545,16 +11549,24 @@ check_for_failed_contracts (constexpr_ctx *ctx)
else
kind = diagnostics::kind::warning;
+ auto_diagnostic_group d;
+
/* [basic.contract.eval]/7.3 */
if (global_ctx->contract_condition_non_const)
{
emit_diagnostic (kind, loc, 0, "contract condition is not constant");
+ if (error)
+ cxx_constant_value (cond);
return error;
}
/* Otherwise, the evaluation was const, but determined to be false. */
emit_diagnostic (kind, loc, 0,
"contract predicate is false in constant expression");
+ /* Workaround: Avoid redundant "in expansion of macro". */
+ loc = linemap_resolve_location (line_table, loc,
+ LRK_SPELLING_LOCATION, NULL);
+ diagnose_failing_condition (cond, loc, /*show_expr*/true, ctx);
return error;
}
@@ -11873,6 +11885,57 @@ cxx_eval_outermost_constant_expr (tree t, bool
allow_non_constant,
if (!non_constant_p && overflow_p)
non_constant_p = true;
+ /* If allow_non_constant, we delay diagnosing contracts until we know
+ that the expression is otherwise constant.
+
+ Various constexpr contract violation scenarios:
+ * MCE, non-SFINAE, terminating: diagnose and mark non-constant
+ during evaluation
+ * MCE, non-SFINAE, non-terminating: diagnose during evaluation
+ * MCE, SFINAE, terminating: diagnose in repeated eval, return
+ error_mark_node for SFINAE
+ * MCE, SFINAE, non-terminating: diagnose in repeated eval
+ * non-MCE: diagnose in repeated eval, set non-constant so run-time
+ violation handler also sees the violation
+
+ For MCE+terminating, the necessary semantics are just a hard error; what
+ happens after that is error-recovery.
+
+ In the non-SFINAE (!allow_non_constant) case I think it's clearly
+ preferable to stop evaluation after the violation, which we do by marking
+ the evaluation non-constant. We could work harder to return
+ error_mark_node instead like the SFINAE case below, but that seems
+ unnecessary.
+
+ The allow_non_constant case is trickier, because that expresses both
+ actual SFINAE and cases like finish_static_assert where we do one trial
+ evaluation to see if it's constant, and then another to get the actual
+ error if needed. So if we set non_constant_p like for the
+ !allow_non_constant case, we get SFINAE consistent with
+ e.g. _GLIBCXX_ASSERT_FAIL, but we get redundant errors with static_assert.
+ We can finesse it a bit more by returning error_mark_node, which
+ finish_static_assert distinguishes from non-constant.
+
+ ??? cp_fold_immediate_r should change to follow the finish_static_assert
+ pattern (using maybe_constant_value instead of cxx_constant_value for the
+ trial evaluation) once the consteval-only values overhaul goes in. */
+
+ if (!non_constant_p && allow_non_constant
+ && global_ctx.contract_statement)
+ {
+ /* Repeat evaluation to get cx_call_context for the diagnostic. */
+ cxx_eval_outermost_constant_expr (t, /*allow*/false, strict,
+ manifestly_const_eval,
+ constexpr_dtor, object);
+ if (ctx.manifestly_const_eval != mce_true)
+ /* Treat it as non-constant so the violation is also detectable at
+ runtime (if the compile succeeds because we're observing). */
+ non_constant_p = true;
+ else if (contract_terminating_p (global_ctx.contract_statement))
+ /* Substitution failure (see discussion above). */
+ return error_mark_node;
+ }
+
/* Unshare the result. */
bool should_unshare = true;
if (r == t || (TREE_CODE (t) == TARGET_EXPR
@@ -11881,19 +11944,6 @@ cxx_eval_outermost_constant_expr (tree t, bool
allow_non_constant,
if (non_constant_p && !allow_non_constant)
return error_mark_node;
- else if (check_for_failed_contracts (&ctx))
- {
- if (manifestly_const_eval == mce_true)
- /* If MCE, we gave a hard error and return error_mark_node. */
- return error_mark_node;
- else
- {
- /* Otherwise treat it as non-constant so the violation is still
- detectable at run-time. */
- gcc_checking_assert (allow_non_constant);
- return t;
- }
- }
else if (non_constant_p && TREE_CONSTANT (r))
r = mark_non_constant (r);
else if (non_constant_p)
diff --git
a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-post.C
b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-post.C
index 2aaf13781f8..2010809618a 100644
--- a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-post.C
+++ b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-post.C
@@ -35,7 +35,7 @@ void handle_contract_violation(const
std::contracts::contract_violation& violati
struct X
{
- X(const int x) noexcept post(x>1) try
+ X(const int x) noexcept post(x>1) try // { dg-warning "false" "" { target
implicit_constexpr } }
{
int i = 1;
}
diff --git
a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-pre.C
b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-pre.C
index 378b93d2bbc..ef582236744 100644
--- a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-pre.C
+++ b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF-pre.C
@@ -35,7 +35,7 @@ void handle_contract_violation(const
std::contracts::contract_violation& violati
struct X
{
- X(int x) noexcept pre(x>1) try
+ X(int x) noexcept pre(x>1) try // { dg-warning "false" "" { target
implicit_constexpr } }
{
int i = 1;
}
diff --git
a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF2.C
b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF2.C
index eccef309f4b..9de275c04f3 100644
--- a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF2.C
+++ b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p17-SMF2.C
@@ -34,7 +34,7 @@ void handle_contract_violation(const
std::contracts::contract_violation& violati
struct X
{
- X(int x) noexcept pre(x>1)
+ X(int x) noexcept pre(x>1) // { dg-warning "false" "" { target
implicit_constexpr } }
{
try{
int i = 1;
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p7.3.C
b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p7.3.C
index 6371c586dba..ea322cc0bf2 100644
--- a/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p7.3.C
+++ b/gcc/testsuite/g++.dg/contracts/cpp26/basic.contract.eval.p7.3.C
@@ -9,6 +9,7 @@ extern bool runtime_check ();
consteval void foo( auto x ) pre ( runtime_check () ) {}
// { dg-error {contract condition is not constant} "" { target *-*-* } .-1 }
+// { dg-message {runtime_check} "" { target *-*-* } .-2 }
int main() {
foo( 42 ); // { dg-error {call to consteval function 'foo<int>\(42\)' is not
a constant expression} }
}
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C
b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C
index 3aff0276601..b2d3a6062cc 100644
--- a/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C
+++ b/gcc/testsuite/g++.dg/contracts/cpp26/callerside-checks/ctor.C
@@ -4,7 +4,7 @@
struct X
{
X (int x) noexcept
- pre (x>1)
+ pre (x>1) // { dg-error "false" "" { target
implicit_constexpr } }
{
try {
int i = 1;
diff --git a/gcc/testsuite/g++.dg/contracts/cpp26/non-const1.C
b/gcc/testsuite/g++.dg/contracts/cpp26/non-const1.C
new file mode 100644
index 00000000000..7aa453227bd
--- /dev/null
+++ b/gcc/testsuite/g++.dg/contracts/cpp26/non-const1.C
@@ -0,0 +1,20 @@
+// { dg-additional-options -fcontracts }
+
+struct A {
+ const char *p;
+ unsigned len;
+
+ constexpr const char&
+ operator[] (unsigned i) const
+ {
+ contract_assert (i < len);
+ return p[i];
+ }
+};
+
+void f(A a)
+{
+ /* This shouldn't be an error just because the contract predicate is
+ non-constant -- so is the trial evaluation. */
+ const char &c = a[0];
+}
base-commit: 8a2d0ea99609812c896a098b2f93298b0ca7e965
--
2.56.0