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
