{"id":2220862,"url":"http://patchwork.ozlabs.org/api/1.1/patches/2220862/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/patch/20260408094730.49390-1-iain@sandoe.co.uk/","project":{"id":17,"url":"http://patchwork.ozlabs.org/api/1.1/projects/17/?format=json","name":"GNU Compiler Collection","link_name":"gcc","list_id":"gcc-patches.gcc.gnu.org","list_email":"gcc-patches@gcc.gnu.org","web_url":null,"scm_url":null,"webscm_url":null},"msgid":"<20260408094730.49390-1-iain@sandoe.co.uk>","date":"2026-04-08T09:47:16","name":"[v2] c++, contracts: No implicit capture in lambda contract assertions [PR124648].","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"7c04a340cc56f351c899a4fedd92099aded0e817","submitter":{"id":80672,"url":"http://patchwork.ozlabs.org/api/1.1/people/80672/?format=json","name":"Iain Sandoe","email":"iains.gcc@gmail.com"},"delegate":null,"mbox":"http://patchwork.ozlabs.org/project/gcc/patch/20260408094730.49390-1-iain@sandoe.co.uk/mbox/","series":[{"id":499110,"url":"http://patchwork.ozlabs.org/api/1.1/series/499110/?format=json","web_url":"http://patchwork.ozlabs.org/project/gcc/list/?series=499110","date":"2026-04-08T09:47:16","name":"[v2] c++, contracts: No implicit capture in lambda contract assertions [PR124648].","version":2,"mbox":"http://patchwork.ozlabs.org/series/499110/mbox/"}],"comments":"http://patchwork.ozlabs.org/api/patches/2220862/comments/","check":"pending","checks":"http://patchwork.ozlabs.org/api/patches/2220862/checks/","tags":{},"headers":{"Return-Path":"<gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org>","X-Original-To":["incoming@patchwork.ozlabs.org","gcc-patches@gcc.gnu.org"],"Delivered-To":["patchwork-incoming@legolas.ozlabs.org","gcc-patches@gcc.gnu.org"],"Authentication-Results":["legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=hr2REm4c;\n\tdkim-atps=neutral","legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=gcc.gnu.org\n (client-ip=38.145.34.32; helo=vm01.sourceware.org;\n envelope-from=gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org;\n receiver=patchwork.ozlabs.org)","sourceware.org;\n\tdkim=pass (2048-bit key,\n unprotected) header.d=gmail.com header.i=@gmail.com header.a=rsa-sha256\n header.s=20251104 header.b=hr2REm4c","sourceware.org;\n dmarc=pass (p=none dis=none) header.from=gmail.com","sourceware.org; spf=pass smtp.mailfrom=gmail.com","server2.sourceware.org;\n arc=none smtp.remote-ip=209.85.214.170"],"Received":["from vm01.sourceware.org (vm01.sourceware.org [38.145.34.32])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\t key-exchange x25519 server-signature ECDSA (secp384r1) server-digest SHA384)\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4frJCX4Vjxz1xv0\n\tfor <incoming@patchwork.ozlabs.org>; Wed, 08 Apr 2026 19:48:08 +1000 (AEST)","from vm01.sourceware.org (localhost [127.0.0.1])\n\tby sourceware.org (Postfix) with ESMTP id 992084BA2E31\n\tfor <incoming@patchwork.ozlabs.org>; Wed,  8 Apr 2026 09:48:06 +0000 (GMT)","from mail-pl1-f170.google.com (mail-pl1-f170.google.com\n [209.85.214.170])\n by sourceware.org (Postfix) with ESMTPS id 44F224BA2E09\n for <gcc-patches@gcc.gnu.org>; Wed,  8 Apr 2026 09:47:38 +0000 (GMT)","by mail-pl1-f170.google.com with SMTP id\n d9443c01a7336-2b23f90f53aso53138795ad.0\n for <gcc-patches@gcc.gnu.org>; Wed, 08 Apr 2026 02:47:38 -0700 (PDT)","from localhost.localdomain ([103.176.47.167])\n by smtp.gmail.com with ESMTPSA id\n d9443c01a7336-2b27497af26sm204051805ad.49.2026.04.08.02.47.34\n (version=TLS1_3 cipher=TLS_CHACHA20_POLY1305_SHA256 bits=256/256);\n Wed, 08 Apr 2026 02:47:36 -0700 (PDT)"],"DKIM-Filter":["OpenDKIM Filter v2.11.0 sourceware.org 992084BA2E31","OpenDKIM Filter v2.11.0 sourceware.org 44F224BA2E09"],"DMARC-Filter":"OpenDMARC Filter v1.4.2 sourceware.org 44F224BA2E09","ARC-Filter":"OpenARC Filter v1.0.0 sourceware.org 44F224BA2E09","ARC-Seal":"i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1775641658; cv=none;\n b=vlPDxii8JRnaQK7r/ZIKtsuU2ywJ5a0zjbxo3fDVH/2up6smHjOPaiguUzcpZrmy8YizJCskLbOJu1dxS2+yDdRNc9JJsULN9NVtkh3aqebdG6BwEQZIwP7EYaVpZi67VY9YYw1FFgLg1HDA/h/hb/8v910OQTEp+bOcckK8d14=","ARC-Message-Signature":"i=1; a=rsa-sha256; d=sourceware.org; s=key;\n t=1775641658; c=relaxed/simple;\n bh=mbC3pTaQESesTzqjhEInAwAA1cuUOODTSn1bLV1MOio=;\n h=DKIM-Signature:From:To:Subject:Date:Message-ID:MIME-Version;\n b=NtCjWfOymcFT/HWNC5YZ+tFkYYZ9BZwJqMf5jnlrhvoOQ0XYfadibsRG7MHmBLOXs3SiRWoTZ/y0DwPPlBQkrrugPu5YUeWOhvz5voK0U9ZhxUW3F1znLeZn0sAN5hR4t4Wz2Ar+47A3bGYjvSxxHxxwocEPsehGy+ByLScWIa0=","ARC-Authentication-Results":"i=1; server2.sourceware.org","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=gmail.com; s=20251104; t=1775641657; x=1776246457; darn=gcc.gnu.org;\n h=content-transfer-encoding:mime-version:reply-to:references\n :in-reply-to:message-id:date:subject:cc:to:from:from:to:cc:subject\n :date:message-id:reply-to;\n bh=5THQAwPMfVNyqQ0bUQKkWKqLUzIoAHbUSRG3K5r59DE=;\n b=hr2REm4c6hqo5wB4Pr7h2gAYjbAX/AazpjqNAqx6mUt9Vw0Pp8OHozAAmhM7Hkra/h\n 3h0XXzM5oXM3HaI4w2dlIVRU5aMsGRww4Ir6cc6fPnunUYG+maT3zUSGG48AAo/Lg+S8\n LoWRI2mXqXH5n0E2/9XFawvSirF87cDtoZewa1ZSOoV7TVBAKkG833DezWK7UxztWVy+\n /gEbnlfaT9pHPZmsQMfmyHQS3SWIaardaAqplOOt8GGau8xdz0e1OP4rJX0cHN1p4Lfz\n 5QXb8mXp/LoHj1p0xXdAVjzaROJqwnAqdRmu82z1BAU+CF2Ef6F2a1/zI2pkGSBNseFK\n FSAw==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n d=1e100.net; s=20251104; t=1775641657; x=1776246457;\n h=content-transfer-encoding:mime-version:reply-to:references\n :in-reply-to:message-id:date:subject:cc:to:from:x-gm-gg\n :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to;\n bh=5THQAwPMfVNyqQ0bUQKkWKqLUzIoAHbUSRG3K5r59DE=;\n b=f4TOKabFT8hDVLP/7O4snQiBZS0Jrk/ZMy15YxOxbnkCMVBh74fKDWb+WSonp1Aot4\n xJEjfvAsoXCfwvhlbgw7CDGe9EyPBOYsV+wgmfYg0TJL/BKsce0nJb2xJZlHOkopktsU\n LhuUTcEQSS4gE+v/RTlfe1cI92U0knS9E1O2wKe4VMtsRHC20kYMJYkNOWsHFKL0KG0S\n puMqBSOdAvaMf9BFOTQywtHjoycaBjEP8BIU37aUQSkdZrd+pSs0E9xFpCHcA0YfZaqE\n MwmL/11tAVacCe2DMndqqlWpeQQtZdq7nB3w97obuJPo3j1PMfnX/dQJbCCyX9r5rkGp\n jwFg==","X-Forwarded-Encrypted":"i=1;\n AJvYcCUirBW/wbkPLFTz7Gt6/9CWZZqctMjFlyCPyYFus5K/wyW97uv4WRkGLVvulnjUU4DI9xS35Y4gcNpB7A==@gcc.gnu.org","X-Gm-Message-State":"AOJu0YwPWQ/gBV2CxVMQYZMMadtjD0oTwkiSnsuuQ1K0PBmz80YP5++F\n Zg2urYd13ZNcwaraEnaknDKXM+ioOOrrM22cUzbjg2iH2+S36oLELEOIqto/Kg==","X-Gm-Gg":"AeBDievpwKp5ZP8V58rLeuv4rNQjdPtbRA14msCfHADHvVnEd6ZaOtP+uNxcnAPMcAd\n VoSESnhi2GZSH2qFJKoHq5zWcbCuEd6qLm4hvq6BAaIapyNauTKiEl0QLMG5mLOljVoI/fqfrwx\n iePTiSMpPA602/MiQ7CWsuZDc0fUirywnnWic9IliQ99OpWrE37Ke5iqaeMpknUOwmC616ekVmP\n oUlAvgcSMcSed5Bvjk8TkEOYIXtSl0jq2GPnCjfdJklW7Zcwn/f+X6UJc/Cj92HioSIJLi9wm20\n dklnVDeoZr5dXjupcMRJvBANMuMN4r35vgCkPIpd6v3yg7zdV8IYqC5PUCNHJ0HPOedweOsAwCz\n cCOtHR4tjeEvYiu/PGgJ2BbXaJmHjscPv+aNjXUdP0qeDNWdYyDY6VVXk1VkLiCf/VsXFes1aqP\n 9/jbCyFN9z1RtqZEZV7xx18f73BR17B9F5DRhsKCVwqYU=","X-Received":"by 2002:a17:903:37c4:b0:2b2:3dd9:c5f5 with SMTP id\n d9443c01a7336-2b2817e9b67mr210601065ad.36.1775641656990;\n Wed, 08 Apr 2026 02:47:36 -0700 (PDT)","From":"Iain Sandoe <iains.gcc@gmail.com>","X-Google-Original-From":"Iain Sandoe <iain@sandoe.co.uk>","To":"polacek@redhat.com,\n\tgcc-patches@gcc.gnu.org","Cc":"jason@redhat.com","Subject":"[PATCH v2] c++,\n contracts: No implicit capture in lambda contract assertions\n [PR124648].","Date":"Wed,  8 Apr 2026 15:17:16 +0530","Message-ID":"<20260408094730.49390-1-iain@sandoe.co.uk>","X-Mailer":"git-send-email 2.50.1","In-Reply-To":"<acV586ggm1HnlZAL@redhat.com>","References":"<acV586ggm1HnlZAL@redhat.com>","MIME-Version":"1.0","Content-Type":"text/plain; charset=UTF-8","Content-Transfer-Encoding":"8bit","X-BeenThere":"gcc-patches@gcc.gnu.org","X-Mailman-Version":"2.1.30","Precedence":"list","List-Id":"Gcc-patches mailing list <gcc-patches.gcc.gnu.org>","List-Unsubscribe":"<https://gcc.gnu.org/mailman/options/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=unsubscribe>","List-Archive":"<https://gcc.gnu.org/pipermail/gcc-patches/>","List-Post":"<mailto:gcc-patches@gcc.gnu.org>","List-Help":"<mailto:gcc-patches-request@gcc.gnu.org?subject=help>","List-Subscribe":"<https://gcc.gnu.org/mailman/listinfo/gcc-patches>,\n <mailto:gcc-patches-request@gcc.gnu.org?subject=subscribe>","Reply-To":"iain@sandoe.co.uk","Errors-To":"gcc-patches-bounces~incoming=patchwork.ozlabs.org@gcc.gnu.org"},"content":">>+#define DECL_CONTRACT_CAPTURE_P(NODE) \\\n>>+  DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))\n\n>All the #defines above have a comment so this one should have one too.\n>Please also update the \"Usage of DECL_LANG_FLAG_?\" comment above.\n\nOops... done,\n\n>>+      {\n>>+ tree le = LAMBDA_EXPR_CAPTURE_LIST (current_lambda_expr ());\n\n>Was this really meant to be current_lambda_expr() instead of lambda_expr?\n\nNo, changed.\n\n>>+ for (; le; le = TREE_CHAIN (le))\n\n>Why not put the LAMBDA_EXPR_CAPTURE_LIST in the for and then lose the\n>{ } in the if (flag_contracts)?\n\nI did not / do not particularly want to make every lambda walk the capture\nlist, but although we have a way to determine that there are pre/post\nconditions on a function we do not have an easy way to determine that there\nare any contract_asserts.  So for now the best I could think of doing was\nto avoid the walk unless contracts are enabled.\nHappy to go with any other flow that is preferred, of course,\n\nre-tested on x86_64-darwin / powerpc64le-linux, OK for trunk/when?\nIain\n\n--- 8< --- \n\nWe were currently accepting invalid code by allowing contract assertions\nto trigger implicit lambda captures contrary to:\n[expr.prim.lambda.closure] / p10.\n\nThe solution here is to mark captures that occur within contract\nassertion scopes and then clear that mark if we then see a normal\ncapture for the same entity - we must defer the error handling since\nthe following is valid:\n\n  auto f5 = [=] {\n    contract_assert (i > 0); // OK, i is referenced elsewhere.\n    return i;\n  };\n\n\tPR c++/124648\n\ngcc/cp/ChangeLog:\n\n\t* cp-tree.h (DECL_CONTRACT_CAPTURE_P): New.\n\t* parser.cc (cp_parser_lambda_body): Scan the captures for\n\tones were only added in contract assertion scopes.  Issue\n\terrors for those found.\n\t* semantics.cc (process_outer_var_ref): Mark implicit\n\tcaptures that occur in contract assertion scopes.  Clear\n\tthe mark if the entity is subsequently captured 'normally'.\n\ngcc/testsuite/ChangeLog:\n\n\t* g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C: New test.\n\nSigned-off-by: Iain Sandoe <iain@sandoe.co.uk>\n---\n gcc/cp/cp-tree.h                              |  8 +++\n gcc/cp/parser.cc                              | 21 ++++++++\n gcc/cp/semantics.cc                           | 27 ++++++++--\n .../cpp26/expr.prim.lambda.closure.p10.C      | 53 +++++++++++++++++++\n 4 files changed, 106 insertions(+), 3 deletions(-)\n create mode 100644 gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C","diff":"diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h\nindex c0822da8283..352988294c3 100644\n--- a/gcc/cp/cp-tree.h\n+++ b/gcc/cp/cp-tree.h\n@@ -593,6 +593,7 @@ extern GTY(()) tree cp_global_trees[CPTI_MAX];\n       DECL_DECLARED_CONSTINIT_P (in VAR_DECL)\n       TYPE_DECL_FOR_LINKAGE_PURPOSES_P (in TYPE_DECL)\n    8: DECL_DECLARED_CONSTEXPR_P (in VAR_DECL, FUNCTION_DECL)\n+      DECL_CONTRACT_CAPTURE_P (in FIELD_DECL)\n \n    Usage of language-independent fields in a language-dependent manner:\n \n@@ -5367,6 +5368,13 @@ get_vec_init_expr (tree t)\n #define DECL_NORMAL_CAPTURE_P(NODE) \\\n   DECL_LANG_FLAG_7 (FIELD_DECL_CHECK (NODE))\n \n+/* True when a field decl relates to a lambda capture that has currently been\n+   made to satisfy a use within a contract check.  Reset to false when the\n+   capture is required outside a contract check.  Used to diagnose cases where\n+   a capture is only made within contract checks.  */\n+#define DECL_CONTRACT_CAPTURE_P(NODE) \\\n+  DECL_LANG_FLAG_8 (FIELD_DECL_CHECK (NODE))\n+\n /* Nonzero if TYPE is an anonymous union or struct type.  We have to use a\n    flag for this because \"A union for which objects or pointers are\n    declared is not an anonymous union\" [class.union].  */\ndiff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc\nindex 518858fc776..8b26a1de72d 100644\n--- a/gcc/cp/parser.cc\n+++ b/gcc/cp/parser.cc\n@@ -13745,6 +13745,27 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr)\n        finish_function (which will try to emit the contracts).  */\n     cp_parser_late_contracts (parser, fco);\n \n+    /* Check that we have not caused captures that only relate to contracts.\n+       [expr.prim.lambda.closure] / P10.  */\n+    if (flag_contracts)\n+      {\n+\tgcc_checking_assert (current_lambda_expr () == lambda_expr);\n+\tfor (tree le = LAMBDA_EXPR_CAPTURE_LIST (lambda_expr); le;\n+\t     le = TREE_CHAIN (le))\n+\t  {\n+\t    tree cap_fld = TREE_PURPOSE (le);\n+\t    if (TREE_CODE (cap_fld) == FIELD_DECL\n+\t\t&& DECL_CONTRACT_CAPTURE_P (cap_fld))\n+\t      {\n+\t\tauto_diagnostic_group d;\n+\t\ttree expr = TREE_VALUE (le);\n+\t\tlocation_t loc = DECL_SOURCE_LOCATION (cap_fld);\n+\t\terror_at (loc, \"%qE is not implicitly captured by a contract\"\n+\t\t\t  \" assertion\", expr);\n+\t\tinform (DECL_SOURCE_LOCATION (expr), \"%q#E declared here\", expr);\n+\t      }\n+\t  }\n+      }\n     finish_lambda_function (body);\n   }\n \ndiff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc\nindex abd1156a2dc..f836ab47ab5 100644\n--- a/gcc/cp/semantics.cc\n+++ b/gcc/cp/semantics.cc\n@@ -4619,6 +4619,17 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use)\n \n       if (d && d != decl && is_capture_proxy (d))\n \t{\n+\t  if (flag_contracts && !processing_contract_condition)\n+\t    {\n+\t      /* We might have created a capture for a contract_assert ref. to\n+\t\t some var, if that is now captured 'normally' then this is OK.\n+\t\t Otherwise we leave the capture marked as incorrect.  */\n+\t      gcc_checking_assert (DECL_HAS_VALUE_EXPR_P (d));\n+\t      tree proxy = DECL_VALUE_EXPR (d);\n+\t      gcc_checking_assert (TREE_CODE (proxy) == COMPONENT_REF\n+\t\t\t\t&& TREE_CODE (TREE_OPERAND (proxy, 1)) == FIELD_DECL);\n+              DECL_CONTRACT_CAPTURE_P (TREE_OPERAND (proxy, 1)) = false;\n+\t    }\n \t  if (DECL_CONTEXT (d) == containing_function)\n \t    /* We already have an inner proxy.  */\n \t    return d;\n@@ -4667,10 +4678,20 @@ process_outer_var_ref (tree decl, tsubst_flags_t complain, bool odr_use)\n       return error_mark_node;\n     }\n   /* Do lambda capture when processing the id-expression, not when\n-     odr-using a variable.  */\n+     odr-using a variable, but uses in a contract must not cause a capture.  */\n   if (!odr_use && context == containing_function)\n-    decl = add_default_capture (lambda_stack,\n-\t\t\t\t/*id=*/DECL_NAME (decl), initializer);\n+    {\n+      decl = add_default_capture (lambda_stack,\n+\t\t\t\t  /*id=*/DECL_NAME (decl), initializer);\n+      if (flag_contracts && processing_contract_condition)\n+\t{\n+\t  gcc_checking_assert (DECL_HAS_VALUE_EXPR_P (decl));\n+\t  tree proxy = DECL_VALUE_EXPR (decl);\n+\t  gcc_checking_assert (TREE_CODE (proxy) == COMPONENT_REF\n+\t\t\t\t&& TREE_CODE (TREE_OPERAND (proxy, 1)) == FIELD_DECL);\n+          DECL_CONTRACT_CAPTURE_P (TREE_OPERAND (proxy, 1)) = true;\n+        }\n+    }\n   /* Only an odr-use of an outer automatic variable causes an\n      error, and a constant variable can decay to a prvalue\n      constant without odr-use.  So don't complain yet.  */\ndiff --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\nnew file mode 100644\nindex 00000000000..5a165c48166\n--- /dev/null\n+++ b/gcc/testsuite/g++.dg/contracts/cpp26/expr.prim.lambda.closure.p10.C\n@@ -0,0 +1,53 @@\n+// N5008 :\n+// [expr.prim.lambda.closure]/p10\n+// If all potential references to a local entity implicitly captured by a\n+// lambda-expression L occur within the function contract assertions of the\n+// call operator or operator template of L or within assertion-statements\n+// within the body of L, the program is ill-formed.\n+// [Note 4: Adding a contract assertion to an existing C++ program cannot\n+//  cause additional captures. — end note]\n+// { dg-do compile { target c++26 } }\n+// { dg-additional-options \"-fcontracts -fsyntax-only\" }\n+\n+auto gl0 = [] (int x) \n+  pre (x > 10) { return x; }; // OK\n+\n+static int i = 0;\n+\n+void\n+foo ()\n+{\n+  auto f1 = [=]\n+    pre (i > 0) {};  // OK, no local entities are captured.\n+\n+  int i = 1;\n+\n+  auto f2 = [=]\n+    pre (i > 0) {};  // { dg-error {'i' is not implicitly captured by a contract assertion} }\n+\n+  auto f3 = [i]\n+    pre (i > 0) {};  // OK, i is captured explicitly.\n+\n+  auto f4 = [=] {\n+    contract_assert (i > 0); // { dg-error {'i' is not implicitly captured by a contract assertion} }\n+  };\n+\n+  auto f5 = [=] {\n+    contract_assert (i > 0); // OK, i is referenced elsewhere.\n+    return i;\n+  };\n+\n+  auto f6 = [=] pre (                // #1\n+    []{\n+      bool x = true;\n+      return [=]{ return x; }();    // OK, #1 captures nothing.\n+    }()) {};\n+\n+// TODO: lambda captures in function contract specifiers are not yet\n+// fully functional.\n+#if 0\n+  bool y = true;\n+  auto f7 = [=]\n+    pre([=]{ return y; }()); // error: outer capture of y is invalid.\n+#endif\n+}\n","prefixes":["v2"]}