On Thu, Mar 26, 2026 at 05:28:38PM +0000, Iain Sandoe wrote:
> This does also lead to an ICE on invalid, per the referenced PR.
>
> Tested on x86_64 darwin, x86_64, powerpc64le Linux,
> OK for trunk?
> thanks
> Iain
>
> --- 8< ---
>
> We were currently accepting invalid code by allowing contract assertions
> to trigger implicit lambda captures contrary to:
> [expr.prim.lambda.closure] / p10.
>
> The solution here is to mark captures that occur within contract
> assertion scopes and then clear that mark if we then see a normal
> capture for the same entity - we must defer the error handling since
> the following is valid:
>
> auto f5 = [=] {
> contract_assert (i > 0); // OK, i is referenced elsewhere.
> return i;
> };
>
> PR c++/124648
>
> gcc/cp/ChangeLog:
>
> * cp-tree.h (DECL_CONTRACT_CAPTURE_P): New.
> * parser.cc (cp_parser_lambda_body): Scan the captures for
> ones were only added in contract assertion scopes. Issue
> errors for those found.
> * semantics.cc (process_outer_var_ref): Mark implicit
> captures that occur in contract assertion scopes. Clear
> the mark if the entity is subsequently captured 'normally'.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C: New test.
>
> Signed-off-by: Iain Sandoe <[email protected]>
> ---
> gcc/cp/cp-tree.h | 3 ++
> gcc/cp/parser.cc | 20 +++++++
> gcc/cp/semantics.cc | 27 ++++++++--
> .../cpp26/expr.prim.lambda.closure.p10.C | 53 +++++++++++++++++++
> 4 files changed, 100 insertions(+), 3 deletions(-)
> create mode 100644
> gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C
>
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index ea3cb049785..7beaae6bbfd 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -5367,6 +5367,9 @@ get_vec_init_expr (tree t)
> #define DECL_NORMAL_CAPTURE_P(NODE) \
> DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))
>
> +#define DECL_CONTRACT_CAPTURE_P(NODE) \
> + DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))
All the #defines above have a comment so this one should have one too.
Please also update the "Usage of DECL_LANG_FLAG_?" comment above.
> /* Nonzero if TYPE is an anonymous union or struct type. We have to use a
> flag for this because "A union for which objects or pointers are
> declared is not an anonymous union" [class.union]. */
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 8d88dc9c312..e5157cd7386 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -13740,6 +13740,26 @@ cp_parser_lambda_body (cp_parser* parser, tree
> lambda_expr)
> finish_function (which will try to emit the contracts). */
> cp_parser_late_contracts (parser, fco);
>
> + /* Check that we have not caused captures that only relate to contracts.
> + [expr.prim.lambda.closure] / P10. */
> + if (flag_contracts)
> + {
> + tree le = LAMBDA_EXPR_CAPTURE_LIST (current_lambda_expr ());
Was this really meant to be current_lambda_expr() instead of lambda_expr?
> + for (; le; le = TREE_CHAIN (le))
Why not put the LAMBDA_EXPR_CAPTURE_LIST in the for and then lose the
{ } in the if (flag_contracts)?
> + {
> + tree cap_fld = TREE_PURPOSE (le);
> + if (TREE_CODE (cap_fld) == FIELD_DECL
> + && DECL_CONTRACT_CAPTURE_P (cap_fld))
> + {
> + auto_diagnostic_group d;
> + tree expr = TREE_VALUE (le);
> + location_t loc = DECL_SOURCE_LOCATION (cap_fld);
> + error_at (loc, "%qE is not implicitly captured by a contract"
> + " assertion", expr);
> + inform (DECL_SOURCE_LOCATION (expr), "%q#E declared here",
> expr);
> + }
> + }
> + }
> finish_lambda_function (body);
> }
>
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index ac08b598f3d..992ae29a4f4 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -4619,6 +4619,17 @@ process_outer_var_ref (tree decl, tsubst_flags_t
> complain, bool odr_use)
>
> if (d && d != decl && is_capture_proxy (d))
> {
> + if (flag_contracts && !processing_contract_condition)
> + {
> + /* We might have created a capture for a contract_assert ref. to
> + some var, if that is now captured 'normally' then this is OK.
> + Otherwise we leave the capture marked as incorrect. */
> + gcc_checking_assert (DECL_HAS_VALUE_EXPR_P (d));
> + tree proxy = DECL_VALUE_EXPR (d);
> + gcc_checking_assert (TREE_CODE (proxy) == COMPONENT_REF
> + && TREE_CODE (TREE_OPERAND (proxy, 1)) ==
> FIELD_DECL);
> + DECL_CONTRACT_CAPTURE_P (TREE_OPERAND (proxy, 1)) = false;
> + }
> if (DECL_CONTEXT (d) == containing_function)
> /* We already have an inner proxy. */
> return d;
> @@ -4667,10 +4678,20 @@ process_outer_var_ref (tree decl, tsubst_flags_t
> complain, bool odr_use)
> return error_mark_node;
> }
> /* Do lambda capture when processing the id-expression, not when
> - odr-using a variable. */
> + odr-using a variable, but uses in a contract must not cause a capture.
> */
> if (!odr_use && context == containing_function)
> - decl = add_default_capture (lambda_stack,
> - /*id=*/DECL_NAME (decl), initializer);
> + {
> + decl = add_default_capture (lambda_stack,
> + /*id=*/DECL_NAME (decl), initializer);
> + if (flag_contracts && processing_contract_condition)
> + {
> + gcc_checking_assert (DECL_HAS_VALUE_EXPR_P (decl));
> + tree proxy = DECL_VALUE_EXPR (decl);
> + gcc_checking_assert (TREE_CODE (proxy) == COMPONENT_REF
> + && TREE_CODE (TREE_OPERAND (proxy, 1)) ==
> FIELD_DECL);
> + DECL_CONTRACT_CAPTURE_P (TREE_OPERAND (proxy, 1)) = true;
> + }
> + }
> /* Only an odr-use of an outer automatic variable causes an
> error, and a constant variable can decay to a prvalue
> constant without odr-use. So don't complain yet. */
> diff --git
> a/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C
> b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C
> new file mode 100644
> index 00000000000..5a165c48166
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C
> @@ -0,0 +1,53 @@
> +// N5008 :
> +// [expr.prim.lambda.closure]/p10
> +// If all potential references to a local entity implicitly captured by a
> +// lambda-expression L occur within the function contract assertions of the
> +// call operator or operator template of L or within assertion-statements
> +// within the body of L, the program is ill-formed.
> +// [Note 4: Adding a contract assertion to an existing C++ program cannot
> +// cause additional captures. — end note]
> +// { dg-do compile { target c++26 } }
> +// { dg-additional-options "-fcontracts -fsyntax-only" }
> +
> +auto gl0 = [] (int x)
> + pre (x > 10) { return x; }; // OK
> +
> +static int i = 0;
> +
> +void
> +foo ()
> +{
> + auto f1 = [=]
> + pre (i > 0) {}; // OK, no local entities are captured.
> +
> + int i = 1;
> +
> + auto f2 = [=]
> + pre (i > 0) {}; // { dg-error {'i' is not implicitly captured by a
> contract assertion} }
> +
> + auto f3 = [i]
> + pre (i > 0) {}; // OK, i is captured explicitly.
> +
> + auto f4 = [=] {
> + contract_assert (i > 0); // { dg-error {'i' is not implicitly captured
> by a contract assertion} }
> + };
> +
> + auto f5 = [=] {
> + contract_assert (i > 0); // OK, i is referenced elsewhere.
> + return i;
> + };
> +
> + auto f6 = [=] pre ( // #1
> + []{
> + bool x = true;
> + return [=]{ return x; }(); // OK, #1 captures nothing.
> + }()) {};
> +
> +// TODO: lambda captures in function contract specifiers are not yet
> +// fully functional.
> +#if 0
> + bool y = true;
> + auto f7 = [=]
> + pre([=]{ return y; }()); // error: outer capture of y is invalid.
> +#endif
> +}
> --
> 2.50.1 (Apple Git-155)
>
Marek