Author: Balázs Benics
Date: 2026-10-27T14:49:39Z
New Revision: a068e44b95c53bf20b3371ab40483208fbda6d43

URL: 
https://github.com/llvm/llvm-project/commit/a068e44b95c53bf20b3371ab40483208fbda6d43
DIFF: 
https://github.com/llvm/llvm-project/commit/a068e44b95c53bf20b3371ab40483208fbda6d43.diff

LOG: [LifetimeSafety] Keep locs and params in TypeLoc of inferred lifetime 
bound 'this' (#234177)

Follow-up to #233558, addressing review comments:
- The mismatch comes from the deduced return type, not from template
  instantiation. Reword the comment, and add a non-template lit case.
- Build the trivial TypeLoc at the written type's begin loc instead of
  the method's name, so getReturnTypeSourceRange() points at the written
  'auto'.
- A trivial FunctionTypeLoc has no ParmVarDecls, which AST visitors rely
  on to reach the parameters. Set them from the method.

The locations and parameters are only observable through the C++ API, so
they are covered by a unittest.

Addresses:
- https://github.com/llvm/llvm-project/pull/233558#discussion_r4326341553
- https://github.com/llvm/llvm-project/pull/233558#discussion_r4326365012

Authored-By: claude

Added: 
    clang/unittests/Sema/LifetimeBoundImplicitThisTest.cpp

Modified: 
    clang/lib/Sema/SemaDecl.cpp
    clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
    clang/unittests/Sema/CMakeLists.txt

Removed: 
    


################################################################################
diff  --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index fba96fe7abb2bc..e46c14d1513467 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -15134,17 +15134,22 @@ void 
Sema::addLifetimeBoundToImplicitThis(CXXMethodDecl *MD) {
     TLB.pushFullCopy(TSI->getTypeLoc());
   } else {
     // The written type-source info can disagree with the method's type. For
-    // example, a deduced 'auto' return type in a template instantiation keeps
-    // the 'auto' placeholder in its TypeSourceInfo while the method's type has
-    // already been resolved to the deduced type. Pushing the stale TypeLoc
-    // would make the wrapping AttributedTypeLoc's inner type inconsistent with
-    // MethodType. Build a trivial TypeLoc that matches MethodType instead.
-    TLB.pushTrivial(Context, MethodType, MD->getLocation());
+    // example, a deduced 'auto' return type keeps the 'auto' placeholder in 
its
+    // TypeSourceInfo while the method's type has already been resolved to the
+    // deduced type. Pushing the stale TypeLoc would make the wrapping
+    // AttributedTypeLoc's inner type inconsistent with MethodType. Build a
+    // trivial TypeLoc that matches MethodType instead.
+    TLB.pushTrivial(Context, MethodType,
+                    TSI ? TSI->getTypeLoc().getBeginLoc() : MD->getLocation());
   }
   AttributedTypeLoc TyLoc = TLB.push<AttributedTypeLoc>(AttributedType);
   TyLoc.setAttr(Attr);
   MD->setType(AttributedType);
   MD->setTypeSourceInfo(TLB.getTypeSourceInfo(Context, AttributedType));
+  // A trivial TypeLoc has no parameter decls, but AST visitors rely on them.
+  if (FunctionTypeLoc FTL = MD->getFunctionTypeLoc())
+    for (unsigned I = 0, E = MD->getNumParams(); I != E; ++I)
+      FTL.setParam(I, MD->getParamDecl(I));
 }
 
 void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {

diff  --git a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp 
b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
index 517c3cf6b82a1b..5d451469f45f3b 100644
--- a/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
+++ b/clang/test/Sema/LifetimeSafety/annotation-suggestions.cpp
@@ -272,6 +272,22 @@ auto test_auto_return_implicit_this() {
   return b.make(); // expected-warning {{stack memory associated with local 
variable 'b' is returned}} expected-note {{returned here}}
 }
 
+struct AutoReturnSelfNonTemplate;
+
+struct SelfCapturingIterNonTemplate {
+  AutoReturnSelfNonTemplate *c; // expected-note {{escapes to this field}}
+  explicit SelfCapturingIterNonTemplate(AutoReturnSelfNonTemplate &x) : c(&x) 
{} // expected-warning {{parameter in intra-TU constructor should be marked 
[[clang::lifetimebound]]}}
+};
+
+struct AutoReturnSelfNonTemplate {
+  auto make() { return SelfCapturingIterNonTemplate(*this); } // 
expected-warning {{implicit this in intra-TU function should be marked 
[[clang::lifetimebound]]}} expected-note {{param returned here}}
+};
+
+auto test_auto_return_implicit_this_non_template() {
+  AutoReturnSelfNonTemplate b;
+  return b.make(); // expected-warning {{stack memory associated with local 
variable 'b' is returned}} expected-note {{returned here}}
+}
+
 struct ReturnThisAnnotated {
   const ReturnThisAnnotated& get() [[clang::lifetimebound]] { return *this; }
 };

diff  --git a/clang/unittests/Sema/CMakeLists.txt 
b/clang/unittests/Sema/CMakeLists.txt
index b203bc18eeac88..1dfea7d60c9b54 100644
--- a/clang/unittests/Sema/CMakeLists.txt
+++ b/clang/unittests/Sema/CMakeLists.txt
@@ -7,6 +7,7 @@ add_distinct_clang_unittest(SemaTests
   CodeCompleteTest.cpp
   HeuristicResolverTest.cpp
   GslOwnerPointerInference.cpp
+  LifetimeBoundImplicitThisTest.cpp
   SemaLookupTest.cpp
   SemaModuleTest.cpp
   SemaNoloadLookupTest.cpp

diff  --git a/clang/unittests/Sema/LifetimeBoundImplicitThisTest.cpp 
b/clang/unittests/Sema/LifetimeBoundImplicitThisTest.cpp
new file mode 100644
index 00000000000000..3f3aeac3907213
--- /dev/null
+++ b/clang/unittests/Sema/LifetimeBoundImplicitThisTest.cpp
@@ -0,0 +1,153 @@
+//== unittests/Sema/LifetimeBoundImplicitThisTest.cpp 
----------------------==//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM 
Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Basic/SourceManager.h"
+#include "clang/Frontend/ASTUnit.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+#include <string>
+
+using namespace clang;
+using namespace clang::ast_matchers;
+using namespace clang::tooling;
+
+namespace {
+
+// The lifetime-safety inference rewrites the method's TypeSourceInfo after
+// inferring [[clang::lifetimebound]] on the implicit 'this'.
+// The method's type is already the deduced one while the written
+// TypeSourceInfo still holds the 'auto' placeholder.
+struct TestCase {
+  const char *Name;
+  const char *Code;
+  bool IsInstantiation;
+  // The spelling of the written return type the TypeLoc must still point at.
+  StringRef WrittenReturnType;
+  unsigned NumParams;
+};
+
+const TestCase Cases[] = {
+    {"NonTemplate", R"cpp(
+struct S;
+struct Iter {
+  S *s;
+  explicit Iter(S &x) : s(&x) {}
+};
+struct S {
+  auto make() { return Iter(*this); }
+};
+auto test() { S b; return b.make(); }
+)cpp",
+     /*IsInstantiation=*/false, "auto", /*NumParams=*/0},
+    {"TemplateInstantiation", R"cpp(
+template <class C> struct Iter {
+  C *c;
+  explicit Iter(C &x) : c(&x) {}
+};
+template <class T> struct S {
+  auto make() { return Iter<S>(*this); }
+};
+auto test() { S<char> b; return b.make(); }
+)cpp",
+     /*IsInstantiation=*/true, "auto", /*NumParams=*/0},
+    // The parameters must survive the rewrite untouched.
+    {"WithParam", R"cpp(
+struct S;
+struct Iter {
+  S *s;
+  explicit Iter(S &x) : s(&x) {}
+};
+struct S {
+  auto make(int x) { return Iter(*this); }
+};
+auto test() { S b; return b.make(0); }
+)cpp",
+     /*IsInstantiation=*/false, "auto", /*NumParams=*/1},
+    // The 'const' qualifier lives on the function type that gets wrapped.
+    {"ConstQualified", R"cpp(
+struct S;
+struct Iter {
+  const S *s;
+  explicit Iter(const S &x) : s(&x) {}
+};
+struct S {
+  auto make() const { return Iter(*this); }
+};
+auto test() { S b; return b.make(); }
+)cpp",
+     /*IsInstantiation=*/false, "auto", /*NumParams=*/0},
+    {"DecltypeAuto", R"cpp(
+struct S;
+struct Iter {
+  S *s;
+  explicit Iter(S &x) : s(&x) {}
+};
+struct S {
+  decltype(auto) make() { return Iter(*this); }
+};
+auto test() { S b; return b.make(); }
+)cpp",
+     /*IsInstantiation=*/false, "decltype(auto)", /*NumParams=*/0},
+};
+
+class LifetimeBoundImplicitThisTest : public testing::TestWithParam<TestCase> {
+};
+
+// Checks that the inference annotated 'make' and that the return type's
+// location still points at the written return type.
+TEST_P(LifetimeBoundImplicitThisTest, DeducedReturnTypeTypeLoc) {
+  const TestCase &C = GetParam();
+  std::unique_ptr<ASTUnit> AST = buildASTFromCodeWithArgs(
+      C.Code,
+      {"-std=c++17", "-fsyntax-only", "-Xclang", "-flifetime-safety-inference",
+       "-Xclang", "-fexperimental-lifetime-safety-tu-analysis",
+       "-Wlifetime-safety-suggestions", "-Wlifetime-safety",
+       "-Wlifetime-safety-experimental-field-origin-escape", "-Wno-dangling"},
+      "input.cc");
+  ASSERT_TRUE(AST);
+  ASTContext &Ctx = AST->getASTContext();
+  const SourceManager &SM = Ctx.getSourceManager();
+
+  auto Matches =
+      match(cxxMethodDecl(hasName("make"), isDefinition(),
+                          C.IsInstantiation ? isInstantiated()
+                                            : unless(isInstantiated()))
+                .bind("m"),
+            Ctx);
+  ASSERT_EQ(Matches.size(), 1u);
+  const auto *MD = Matches[0].getNodeAs<CXXMethodDecl>("m");
+  ASSERT_TRUE(MD);
+
+  // Inference must have wrapped the function type in an AttributedType.
+  EXPECT_TRUE(isa<AttributedType>(MD->getType()));
+  
EXPECT_TRUE(MD->getTypeSourceInfo()->getTypeLoc().getAs<AttributedTypeLoc>());
+
+  SourceLocation RetBegin = MD->getReturnTypeSourceRange().getBegin();
+  ASSERT_TRUE(RetBegin.isValid());
+  EXPECT_EQ(
+      StringRef(SM.getCharacterData(RetBegin), C.WrittenReturnType.size()),
+      C.WrittenReturnType);
+
+  FunctionTypeLoc FTL = MD->getFunctionTypeLoc();
+  ASSERT_TRUE(FTL);
+  ASSERT_EQ(FTL.getNumParams(), C.NumParams);
+  for (unsigned I = 0; I != C.NumParams; ++I)
+    EXPECT_EQ(FTL.getParam(I), MD->getParamDecl(I));
+}
+
+INSTANTIATE_TEST_SUITE_P(DeducedReturnTypes, LifetimeBoundImplicitThisTest,
+                         testing::ValuesIn(Cases),
+                         [](const testing::TestParamInfo<TestCase> &Info) {
+                           return std::string(Info.param.Name);
+                         });
+
+} // namespace


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to