From patchwork Wed Apr 8 09:32:32 2026 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Iain Sandoe X-Patchwork-Id: 132797 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from vm01.sourceware.org (localhost [127.0.0.1]) by sourceware.org (Postfix) with ESMTP id E81964BA2E17 for ; Wed, 8 Apr 2026 09:33:39 +0000 (GMT) DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org E81964BA2E17 Authentication-Results: sourceware.org; dkim=pass (2048-bit key, unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256 header.s=20251104 header.b=OEjEt7Az X-Original-To: gcc-patches@gcc.gnu.org Delivered-To: gcc-patches@gcc.gnu.org Received: from mail-pf1-f182.google.com (mail-pf1-f182.google.com [209.85.210.182]) by sourceware.org (Postfix) with ESMTPS id 6CB4D4BA2E08 for ; Wed, 8 Apr 2026 09:32:55 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 6CB4D4BA2E08 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=gmail.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 6CB4D4BA2E08 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=209.85.210.182 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775640775; cv=none; b=glZYr8nNlCP4bOxKAyOQCxIKHnrYvRFsFgklmP7S8yacTwMgkeJ+TTkiS7rW+xJRDOlhYsxxp1HtGNoB6o3tGAWF2ZGxftWk5EizlZQrzV76uPbFvksx7yMjRjk5dCpAp5rQ0SGhjR5ipw9ozAxEZvTEAx1iDy41zKCrdqNTwus= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775640775; c=relaxed/simple; bh=opdsJt3afncM9fEboiRKKf+Vvcanm36sE3yhCIApP0U=; h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version; b=xcr5WXsfbRniGAaRsWLnDdgRB+uA6rNMBUlx6saGBjkdzYkVbAxqJ6JOICFfmh2HWq752odKe7TyNleiyR1t+yR8fzvqUTtakKtg/ihTw1QZGBtQgXes2yEjkAY6VKtQwEBdKgH6op+4R45Gj8K0tncdZRvnyyuNs1sg2slwV7E= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Filter: OpenDKIM Filter v2.11.0 sourceware.org 6CB4D4BA2E08 Received: by mail-pf1-f182.google.com with SMTP id d2e1a72fcca58-82d0b68837aso2570545b3a.2 for ; Wed, 08 Apr 2026 02:32:55 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1775640774; x=1776245574; darn=gcc.gnu.org; h=content-transfer-encoding:mime-version:reply-to:references :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject :date:message-id:reply-to; bh=jGSiZeUx0BmledKsr7zggkRtDbSH4M6Rhi1yRN7C6a8=; b=OEjEt7AzTbDKwDB624dSXptSQv2uPRNymf95alfAYvq1HVE4zQPKBEaFZSvSYQLI+d dw8b2fghtUejv91z75uGRT9xJhoJ5VkaohdS/oYb8Gre/68XEr2Gg4mbG3D9dGjfO+zP NarvVZFBvreFfMCzklUfMFjiO+kHQ4HlxwS/maZeVSpFxxK/Z/xWRGXg8KEgaF+ZaS7G FZLJppRMCq6VLHm5oUCnMVb1lDoAtlhGEOCI2N3b9yhgB+45ijL2kigIb+iBHXyvAbyT XPdY68SH0M5cvBsKsibMlO40x6OrpWJUAYZyBw/WKhBTbZoxWxGwPIDJk2iEjjZagOG8 t8mQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1775640774; x=1776245574; h=content-transfer-encoding:mime-version:reply-to:references :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=jGSiZeUx0BmledKsr7zggkRtDbSH4M6Rhi1yRN7C6a8=; b=KF2rNHYbPz818SOWw2MJqRv7itAjvSnTrFo43Z2uBuTeAXTHJtZQEcy6hDJQ9npjZP J49Xln9sATFi8I8UE5s3Pdy2auXtP6XvOlp0TKBXDaRLNnKiwXUuLSi7ufvdm+ELYWO+ MH6wgfrsheAaAUanU5EcbOlPkKZpXniBXakcTaP9viz0dHNeygiMwIozDa7sn4RjAGVP r0K27jNWH0Gp9wnPCY9Z9kEYj81tNMGsUyfjXNtRrsC3WT8auqoZc5PXqqZ7AzY+FmXw UE2aDorkI1mL7wKObvmjfIEiAcqH1AZiE87/oMhOoCFTrjYYKUYkMZe0Xp3T9hmn3DBL h2Vg== X-Forwarded-Encrypted: i=1; AJvYcCV4fTXHJDSkg2XipuSPvsnwRRVLWfGh0sNAgWjoGTBbYkFkU9nrhQxncRWL3dijnFzgtNI+860pHBgpCA==@gcc.gnu.org X-Gm-Message-State: AOJu0YwAbt3NAiMHU5ay1pxmJ2txpaIobxvcvUVFO0M1e/HN9l35S/yG S11bwEePZOUfATCeNZYz3gkWz50DpC1mYQOJsENpshu3W01gzhtgnSkL X-Gm-Gg: AeBDievP5ltps9SoqYyi5ADAGnsM6UBcyy0lk6jbe9L4IOXkb0oJjQP5PKXAJVCQ1lD eUBWPMiEWYTRZdE9AEsYRtGC2LTp6anuAHnJOSAf7Wqg121HtHj2R62trzV5yl5XkDB4ib52NsS ls56ssc9szaaT43fEmZNGqE0T5h0raxB1VjwXKeszDAjTbLPfhtFKv/35j9WuUf8QOQjH2tGuJt HUbFuSOZ5xcZri30TX6wjaBsPMLLwV4cvXxUbHLGIPHYaBYi1ev4VO8PFS74nKMIc7ybr9pQ4iS pWJkbiYH26phpBduxmDbqZmDYqut2NcbQmo125IqK9+/fXeKHmxwdkHXPqrP9OOngSHcx39H79D goaSuooj170o45eWXvx+WgRvMm1CstuklaR52lcIq/j2U7yvQ6tVeB30v8tm1sWbBOSnV8LVXFS /sil4BPLpFP91oefCEbgAoL21i7FateB3WK4CR5FhlWuCSxpxzJ5D/Kg== X-Received: by 2002:a05:6a21:3391:b0:39f:23ed:897f with SMTP id adf61e73a8af0-39f2f2af5bdmr21214788637.32.1775640774169; Wed, 08 Apr 2026 02:32:54 -0700 (PDT) Received: from localhost.localdomain ([103.176.47.167]) by smtp.gmail.com with ESMTPSA id 41be03b00d2f7-c76c657e5f3sm17363735a12.23.2026.04.08.02.32.51 (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256); Wed, 08 Apr 2026 02:32:53 -0700 (PDT) From: Iain Sandoe X-Google-Original-From: Iain Sandoe To: polacek@redhat.com, gcc-patches@gcc.gnu.org Cc: jason@redhat.com Subject: [PATCH v2] c++, contracts: No implicit capture in lambda contract assertions [PR124648]. Date: Wed, 8 Apr 2026 15:02:32 +0530 Message-ID: <20260408093243.49042-1-iain@sandoe.co.uk> X-Mailer: git-send-email 2.50.1 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Status: No, score=-7.2 required=5.0 tests=BAYES_00, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, FREEMAIL_FROM, GIT_PATCH_0, RCVD_IN_ABUSEAT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, RCVD_IN_VALIDITY_RPBL_BLOCKED, RCVD_IN_VALIDITY_SAFE_BLOCKED, SPF_HELO_NONE, SPF_PASS, TXREP autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on sourceware.org X-BeenThere: gcc-patches@gcc.gnu.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Gcc-patches mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-To: iain@sandoe.co.uk Errors-To: gcc-patches-bounces~patchwork=sourceware.org@gcc.gnu.org Hi Marek, >>+ if (current_class_ref && !CP_TYPE_CONST_P (TREE_TYPE (current_class_ref))) >>+ current_class_ref = view_as_const (current_class_ref); >This change makes current_class_ref_copy unused. Looks like this function >is missing the > > /* Revert (any) constification of the current class object. */ > current_class_ref = current_class_ref_copy; > >but there's > > current_class_ref = saved_ccr; > >below so I guess current_class_ref_copy can be removed. Yes, done. > Can we move the !CP_TYPE_CONST_P check into view_as_const instead? Yeah that's a much better place for it, done as attached, Retested on x86_64-darwin and powerpc64le-linux, OK for trunk, when? 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 --- 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); + 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; + } 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 +}