[v2] c++, contracts: No implicit capture in lambda contract assertions [PR124648].
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_gcc_build--master-aarch64 |
success
|
Build passed
|
Commit Message
>>+#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.
Oops... done,
>>+ {
>>+ tree le = LAMBDA_EXPR_CAPTURE_LIST (current_lambda_expr ());
>Was this really meant to be current_lambda_expr() instead of lambda_expr?
No, changed.
>>+ 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)?
I did not / do not particularly want to make every lambda walk the capture
list, but although we have a way to determine that there are pre/post
conditions on a function we do not have an easy way to determine that there
are any contract_asserts. So for now the best I could think of doing was
to avoid the walk unless contracts are enabled.
Happy to go with any other flow that is preferred, of course,
re-tested on x86_64-darwin / powerpc64le-linux, OK for trunk/when?
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 <iain@sandoe.co.uk>
---
gcc/cp/cp-tree.h | 8 +++
gcc/cp/parser.cc | 21 ++++++++
gcc/cp/semantics.cc | 27 ++++++++--
.../cpp26/expr.prim.lambda.closure.p10.C | 53 +++++++++++++++++++
4 files changed, 106 insertions(+), 3 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C
Comments
On 4/8/26 5:47 AM, Iain Sandoe wrote:
>
>>> +#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.
>
> Oops... done,
>
>>> + {
>>> + tree le = LAMBDA_EXPR_CAPTURE_LIST (current_lambda_expr ());
>
>> Was this really meant to be current_lambda_expr() instead of lambda_expr?
>
> No, changed.
>
>>> + 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)?
>
> I did not / do not particularly want to make every lambda walk the capture
> list, but although we have a way to determine that there are pre/post
> conditions on a function we do not have an easy way to determine that there
> are any contract_asserts. So for now the best I could think of doing was
> to avoid the walk unless contracts are enabled.
> Happy to go with any other flow that is preferred, of course,
>
> re-tested on x86_64-darwin / powerpc64le-linux, OK for trunk/when?
> 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 <iain@sandoe.co.uk>
> ---
> gcc/cp/cp-tree.h | 8 +++
> gcc/cp/parser.cc | 21 ++++++++
> gcc/cp/semantics.cc | 27 ++++++++--
> .../cpp26/expr.prim.lambda.closure.p10.C | 53 +++++++++++++++++++
> 4 files changed, 106 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 c0822da8283..352988294c3 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -593,6 +593,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
> DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
> TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)
> 8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)
> + DECL_CONTRACT_CAPTURE_P (in FIELD_DECL)
>
> Usage of language-independent fields in a language-dependent manner:
>
> @@ -5367,6 +5368,13 @@ get_vec_init_expr (tree t)
> #define DECL_NORMAL_CAPTURE_P(NODE) \
> DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))
>
> +/* True when a field decl relates to a lambda capture that has currently been
> + made to satisfy a use within a contract check. Reset to false when the
> + capture is required outside a contract check. Used to diagnose cases where
> + a capture is only made within contract checks. */
> +#define DECL_CONTRACT_CAPTURE_P(NODE) \
> + DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))
> +
> /* 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 518858fc776..8b26a1de72d 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -13745,6 +13745,27 @@ 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)
> + {
> + gcc_checking_assert (current_lambda_expr () == lambda_expr);
> + for (tree le = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); le;
> + le = TREE_CHAIN (le))
> + {
> + tree cap_fld = TREE_PURPOSE (le);
You probably need to look through EXPR_PACK_EXPANSION here for a capture
pack.
> + 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 abd1156a2dc..f836ab47ab5 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;
Let's factor out this pattern that only differs in the value we're setting.
> + }
> 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
> +}
On 4/8/26 10:27 AM, Jason Merrill wrote:
> On 4/8/26 5:47 AM, Iain Sandoe wrote:
>>
>>>> +#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.
>>
>> Oops... done,
>>
>>>> + {
>>>> + tree le = LAMBDA_EXPR_CAPTURE_LIST (current_lambda_expr ());
>>
>>> Was this really meant to be current_lambda_expr() instead of
>>> lambda_expr?
>>
>> No, changed.
>>
>>>> + 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)?
>>
>> I did not / do not particularly want to make every lambda walk the
>> capture
>> list, but although we have a way to determine that there are pre/post
>> conditions on a function we do not have an easy way to determine that
>> there
>> are any contract_asserts. So for now the best I could think of doing was
>> to avoid the walk unless contracts are enabled.
>> Happy to go with any other flow that is preferred, of course,
>>
>> re-tested on x86_64-darwin / powerpc64le-linux, OK for trunk/when?
>> 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 <iain@sandoe.co.uk>
>> ---
>> gcc/cp/cp-tree.h | 8 +++
>> gcc/cp/parser.cc | 21 ++++++++
>> gcc/cp/semantics.cc | 27 ++++++++--
>> .../cpp26/expr.prim.lambda.closure.p10.C | 53 +++++++++++++++++++
>> 4 files changed, 106 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 c0822da8283..352988294c3 100644
>> --- a/gcc/cp/cp-tree.h
>> +++ b/gcc/cp/cp-tree.h
>> @@ -593,6 +593,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>> DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
>> TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)
>> 8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)
>> + DECL_CONTRACT_CAPTURE_P (in FIELD_DECL)
>> Usage of language-independent fields in a language-dependent manner:
>> @@ -5367,6 +5368,13 @@ get_vec_init_expr (tree t)
>> #define DECL_NORMAL_CAPTURE_P(NODE) \
>> DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))
>> +/* True when a field decl relates to a lambda capture that has
>> currently been
>> + made to satisfy a use within a contract check. Reset to false
>> when the
>> + capture is required outside a contract check. Used to diagnose
>> cases where
>> + a capture is only made within contract checks. */
>> +#define DECL_CONTRACT_CAPTURE_P(NODE) \
>> + DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))
>> +
>> /* 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 518858fc776..8b26a1de72d 100644
>> --- a/gcc/cp/parser.cc
>> +++ b/gcc/cp/parser.cc
>> @@ -13745,6 +13745,27 @@ 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)
>> + {
>> + gcc_checking_assert (current_lambda_expr () == lambda_expr);
>> + for (tree le = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); le;
>> + le = TREE_CHAIN (le))
>> + {
>> + tree cap_fld = TREE_PURPOSE (le);
>
> You probably need to look through EXPR_PACK_EXPANSION here for a capture
> pack.
Actually it's fine that we don't check this in a dependent lambda, but
I tried adding such a testcase, and it fails early; it seems that lambda
contracts aren't instantiated by tsubst_lambda_expr, so we try to
evaluate the unsubstituted expression.
template <class T, class...Ts>
void
bar (T t, Ts... ts)
{
auto f1 = [=]
pre ((ts + ...) == 0) {};
}
template void bar<int,short,char>(int, short, char);
In the meantime I'll test and push this with the refactoring I mentioned.
Jason
On 4/15/26 5:27 PM, Jason Merrill wrote:
> On 4/8/26 10:27 AM, Jason Merrill wrote:
>> On 4/8/26 5:47 AM, Iain Sandoe wrote:
>>>
>>>>> +#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.
>>>
>>> Oops... done,
>>>
>>>>> + {
>>>>> + tree le = LAMBDA_EXPR_CAPTURE_LIST (current_lambda_expr ());
>>>
>>>> Was this really meant to be current_lambda_expr() instead of
>>>> lambda_expr?
>>>
>>> No, changed.
>>>
>>>>> + 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)?
>>>
>>> I did not / do not particularly want to make every lambda walk the
>>> capture
>>> list, but although we have a way to determine that there are pre/post
>>> conditions on a function we do not have an easy way to determine that
>>> there
>>> are any contract_asserts. So for now the best I could think of doing
>>> was
>>> to avoid the walk unless contracts are enabled.
>>> Happy to go with any other flow that is preferred, of course,
>>>
>>> re-tested on x86_64-darwin / powerpc64le-linux, OK for trunk/when?
>>> 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 <iain@sandoe.co.uk>
>>> ---
>>> gcc/cp/cp-tree.h | 8 +++
>>> gcc/cp/parser.cc | 21 ++++++++
>>> gcc/cp/semantics.cc | 27 ++++++++--
>>> .../cpp26/expr.prim.lambda.closure.p10.C | 53 +++++++++++++++++++
>>> 4 files changed, 106 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 c0822da8283..352988294c3 100644
>>> --- a/gcc/cp/cp-tree.h
>>> +++ b/gcc/cp/cp-tree.h
>>> @@ -593,6 +593,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
>>> DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
>>> TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)
>>> 8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)
>>> + DECL_CONTRACT_CAPTURE_P (in FIELD_DECL)
>>> Usage of language-independent fields in a language-dependent
>>> manner:
>>> @@ -5367,6 +5368,13 @@ get_vec_init_expr (tree t)
>>> #define DECL_NORMAL_CAPTURE_P(NODE) \
>>> DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))
>>> +/* True when a field decl relates to a lambda capture that has
>>> currently been
>>> + made to satisfy a use within a contract check. Reset to false
>>> when the
>>> + capture is required outside a contract check. Used to diagnose
>>> cases where
>>> + a capture is only made within contract checks. */
>>> +#define DECL_CONTRACT_CAPTURE_P(NODE) \
>>> + DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))
>>> +
>>> /* 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 518858fc776..8b26a1de72d 100644
>>> --- a/gcc/cp/parser.cc
>>> +++ b/gcc/cp/parser.cc
>>> @@ -13745,6 +13745,27 @@ 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)
>>> + {
>>> + gcc_checking_assert (current_lambda_expr () == lambda_expr);
>>> + for (tree le = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); le;
>>> + le = TREE_CHAIN (le))
>>> + {
>>> + tree cap_fld = TREE_PURPOSE (le);
>>
>> You probably need to look through EXPR_PACK_EXPANSION here for a
>> capture pack.
>
> Actually it's fine that we don't check this in a dependent lambda, but
> I tried adding such a testcase, and it fails early; it seems that lambda
> contracts aren't instantiated by tsubst_lambda_expr, so we try to
> evaluate the unsubstituted expression.
>
> template <class T, class...Ts>
> void
> bar (T t, Ts... ts)
> {
> auto f1 = [=]
> pre ((ts + ...) == 0) {};
> }
>
> template void bar<int,short,char>(int, short, char);
>
> In the meantime I'll test and push this with the refactoring I mentioned.
Thus:
@@ -593,6 +593,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)
8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)
+ DECL_CONTRACT_CAPTURE_P (in FIELD_DECL)
Usage of language-independent fields in a language-dependent manner:
@@ -5367,6 +5368,13 @@ get_vec_init_expr (tree t)
#define DECL_NORMAL_CAPTURE_P(NODE) \
DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))
+/* True when a field decl relates to a lambda capture that has currently been
+ made to satisfy a use within a contract check. Reset to false when the
+ capture is required outside a contract check. Used to diagnose cases where
+ a capture is only made within contract checks. */
+#define DECL_CONTRACT_CAPTURE_P(NODE) \
+ DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))
+
/* 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]. */
@@ -13745,6 +13745,27 @@ 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)
+ {
+ gcc_checking_assert (current_lambda_expr () == lambda_expr);
+ for (tree le = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); le;
+ le = TREE_CHAIN (le))
+ {
+ 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);
}
@@ -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. */
new file mode 100644
@@ -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
+}