Skip to content

Commit 13e1fc2

Browse files
iainsjicama
andcommitted
c++, contracts: implicit capture in lambda contract [PR124648]
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'. (set_contract_capture_flag): New. gcc/testsuite/ChangeLog: * g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C: New test. Signed-off-by: Iain Sandoe <iain@sandoe.co.uk> Co-authored-by: Jason Merrill <jason@redhat.com>
1 parent 11c0183 commit 13e1fc2

4 files changed

Lines changed: 106 additions & 3 deletions

File tree

gcc/cp/cp-tree.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -595,6 +595,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];
595595
DECL_DECLARED_CONSTINIT_P (in VAR_DECL)
596596
TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)
597597
8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)
598+
DECL_CONTRACT_CAPTURE_P (in FIELD_DECL)
598599
599600
Usage of language-independent fields in a language-dependent manner:
600601
@@ -5387,6 +5388,13 @@ get_vec_init_expr (tree t)
53875388
#define DECL_NORMAL_CAPTURE_P(NODE) \
53885389
DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))
53895390

5391+
/* True when a field decl relates to a lambda capture that has currently been
5392+
made to satisfy a use within a contract check. Reset to false when the
5393+
capture is required outside a contract check. Used to diagnose cases where
5394+
a capture is only made within contract checks. */
5395+
#define DECL_CONTRACT_CAPTURE_P(NODE) \
5396+
DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))
5397+
53905398
/* Nonzero if TYPE is an anonymous union or struct type. We have to use a
53915399
flag for this because "A union for which objects or pointers are
53925400
declared is not an anonymous union" [class.union]. */

gcc/cp/parser.cc

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13737,6 +13737,27 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr)
1373713737
finish_function (which will try to emit the contracts). */
1373813738
cp_parser_late_contracts (parser, fco);
1373913739

13740+
/* Check that we have not caused captures that only relate to contracts.
13741+
[expr.prim.lambda.closure] / P10. */
13742+
if (flag_contracts)
13743+
{
13744+
gcc_checking_assert (current_lambda_expr () == lambda_expr);
13745+
for (tree le = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); le;
13746+
le = TREE_CHAIN (le))
13747+
{
13748+
tree cap_fld = TREE_PURPOSE (le);
13749+
if (TREE_CODE (cap_fld) == FIELD_DECL
13750+
&& DECL_CONTRACT_CAPTURE_P (cap_fld))
13751+
{
13752+
auto_diagnostic_group d;
13753+
tree expr = TREE_VALUE (le);
13754+
location_t loc = DECL_SOURCE_LOCATION (cap_fld);
13755+
error_at (loc, "%qE is not implicitly captured by a contract"
13756+
" assertion", expr);
13757+
inform (DECL_SOURCE_LOCATION (expr), "%q#E declared here", expr);
13758+
}
13759+
}
13760+
}
1374013761
finish_lambda_function (body);
1374113762
}
1374213763

gcc/cp/semantics.cc

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4575,6 +4575,18 @@ outer_automatic_var_p (tree decl)
45754575
&& !TREE_STATIC (decl));
45764576
}
45774577

4578+
/* Note whether capture proxy D is only used in a contract condition. */
4579+
4580+
static void
4581+
set_contract_capture_flag (tree d, bool val)
4582+
{
4583+
gcc_checking_assert (DECL_HAS_VALUE_EXPR_P (d));
4584+
tree proxy = DECL_VALUE_EXPR (d);
4585+
gcc_checking_assert (TREE_CODE (proxy) == COMPONENT_REF
4586+
&& TREE_CODE (TREE_OPERAND (proxy, 1)) == FIELD_DECL);
4587+
DECL_CONTRACT_CAPTURE_P (TREE_OPERAND (proxy, 1)) = val;
4588+
}
4589+
45784590
/* DECL satisfies outer_automatic_var_p. Possibly complain about it or
45794591
rewrite it for lambda capture.
45804592
@@ -4619,6 +4631,11 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use)
46194631

46204632
if (d && d != decl && is_capture_proxy (d))
46214633
{
4634+
if (flag_contracts && !processing_contract_condition)
4635+
/* We might have created a capture for a contract_assert ref. to
4636+
some var, if that is now captured 'normally' then this is OK.
4637+
Otherwise we leave the capture marked as incorrect. */
4638+
set_contract_capture_flag (d, false);
46224639
if (DECL_CONTEXT (d) == containing_function)
46234640
/* We already have an inner proxy. */
46244641
return d;
@@ -4667,10 +4684,14 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use)
46674684
return error_mark_node;
46684685
}
46694686
/* Do lambda capture when processing the id-expression, not when
4670-
odr-using a variable. */
4687+
odr-using a variable, but uses in a contract must not cause a capture. */
46714688
if (!odr_use && context == containing_function)
4672-
decl = add_default_capture (lambda_stack,
4673-
/*id=*/DECL_NAME (decl), initializer);
4689+
{
4690+
decl = add_default_capture (lambda_stack,
4691+
/*id=*/DECL_NAME (decl), initializer);
4692+
if (flag_contracts && processing_contract_condition)
4693+
set_contract_capture_flag (decl, true);
4694+
}
46744695
/* Only an odr-use of an outer automatic variable causes an
46754696
error, and a constant variable can decay to a prvalue
46764697
constant without odr-use. So don't complain yet. */
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// N5008 :
2+
// [expr.prim.lambda.closure]/p10
3+
// If all potential references to a local entity implicitly captured by a
4+
// lambda-expression L occur within the function contract assertions of the
5+
// call operator or operator template of L or within assertion-statements
6+
// within the body of L, the program is ill-formed.
7+
// [Note 4: Adding a contract assertion to an existing C++ program cannot
8+
// cause additional captures. — end note]
9+
// { dg-do compile { target c++26 } }
10+
// { dg-additional-options "-fcontracts -fsyntax-only" }
11+
12+
auto gl0 = [] (int x)
13+
pre (x > 10) { return x; }; // OK
14+
15+
static int i = 0;
16+
17+
void
18+
foo ()
19+
{
20+
auto f1 = [=]
21+
pre (i > 0) {}; // OK, no local entities are captured.
22+
23+
int i = 1;
24+
25+
auto f2 = [=]
26+
pre (i > 0) {}; // { dg-error {'i' is not implicitly captured by a contract assertion} }
27+
28+
auto f3 = [i]
29+
pre (i > 0) {}; // OK, i is captured explicitly.
30+
31+
auto f4 = [=] {
32+
contract_assert (i > 0); // { dg-error {'i' is not implicitly captured by a contract assertion} }
33+
};
34+
35+
auto f5 = [=] {
36+
contract_assert (i > 0); // OK, i is referenced elsewhere.
37+
return i;
38+
};
39+
40+
auto f6 = [=] pre ( // #1
41+
[]{
42+
bool x = true;
43+
return [=]{ return x; }(); // OK, #1 captures nothing.
44+
}()) {};
45+
46+
// TODO: lambda captures in function contract specifiers are not yet
47+
// fully functional.
48+
#if 0
49+
bool y = true;
50+
auto f7 = [=]
51+
pre([=]{ return y; }()); // error: outer capture of y is invalid.
52+
#endif
53+
}

0 commit comments

Comments
 (0)