Skip to content

Conversation

@kashika0112
Copy link
Contributor

@kashika0112 kashika0112 commented Nov 20, 2025

Handling Trivially Destructed Types

This PR uses AddLifetime to handle expiry of loans to trivially destructed types.

Example:

int * trivial_uar(){ int *ptr; int x = 1; ptr = &x; return ptr; }

The CFG created now has an Expire Fact for trivially destructed types:

Function: trivial_uar Block B2: End of Block Block B1: Issue (0 (Path: x), ToOrigin: 0 (Expr: DeclRefExpr)) OriginFlow (Dest: 1 (Expr: UnaryOperator), Src: 0 (Expr: DeclRefExpr)) Use (2 (Decl: ptr), Write) OriginFlow (Dest: 2 (Decl: ptr), Src: 1 (Expr: UnaryOperator)) Use (2 (Decl: ptr), Read) OriginFlow (Dest: 3 (Expr: ImplicitCastExpr), Src: 2 (Decl: ptr)) Expire (0 (Path: x)) OriginEscapes (3 (Expr: ImplicitCastExpr)) End of Block Block B0: End of Block 

This Expire Fact issues UAR and UAF warnings.

Fixes #162862

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:analysis clang:temporal-safety Issue/FR relating to the lifetime analysis in Clang (-Wdangling, -Wreturn-local-addr) labels Nov 20, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 20, 2025

@llvm/pr-subscribers-clang-analysis
@llvm/pr-subscribers-clang

@llvm/pr-subscribers-clang-temporal-safety

Author: Kashika Akhouri (kashika0112)

Changes

Handling Trivially Destructed Types

This PR uses AddLifetime to handle expiry of loans to trivially destructed types.

Example:

int * trivial_uar(){ int *ptr; int x = 1; ptr = &x; return ptr; } 

The CFG created now has an Expire Fact for trivially destructed types:

Function: trivial_uar Block B2: End of Block Block B1: Issue (0 (Path: x), ToOrigin: 0 (Expr: DeclRefExpr)) OriginFlow (Dest: 1 (Expr: UnaryOperator), Src: 0 (Expr: DeclRefExpr)) Use (2 (Decl: ptr), Write) OriginFlow (Dest: 2 (Decl: ptr), Src: 1 (Expr: UnaryOperator)) Use (2 (Decl: ptr), Read) OriginFlow (Dest: 3 (Expr: ImplicitCastExpr), Src: 2 (Decl: ptr)) Expire (0 (Path: x)) OriginEscapes (3 (Expr: ImplicitCastExpr)) End of Block Block B0: End of Block 

This Expire Fact issues UAR and UAF warnings.


Full diff: https://github.com/llvm/llvm-project/pull/168855.diff

6 Files Affected:

  • (modified) clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h (+1)
  • (modified) clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp (+14-4)
  • (modified) clang/lib/Sema/AnalysisBasedWarnings.cpp (+1)
  • (modified) clang/test/Sema/warn-lifetime-safety-dataflow.cpp (+21-3)
  • (modified) clang/test/Sema/warn-lifetime-safety.cpp (+40-4)
  • (modified) clang/unittests/Analysis/LifetimeSafetyTest.cpp (+66)
diff --git a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h index 8ea37259c570b..85192fdff5067 100644 --- a/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h +++ b/clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h @@ -51,6 +51,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { private: void handleDestructor(const CFGAutomaticObjDtor &DtorOpt); + void handleTrivialDestructors(const CFGLifetimeEnds &LifetimeEnds); void handleGSLPointerConstruction(const CXXConstructExpr *CCE); diff --git a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp index cb9a202b08968..fc92aba773187 100644 --- a/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp +++ b/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp @@ -66,6 +66,9 @@ void FactsGenerator::run() { else if (std::optional<CFGAutomaticObjDtor> DtorOpt = Element.getAs<CFGAutomaticObjDtor>()) handleDestructor(*DtorOpt); + else if (std::optional<CFGLifetimeEnds> LifetimeEnds = + Element.getAs<CFGLifetimeEnds>()) + handleTrivialDestructors(*LifetimeEnds); } CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), EscapesInCurrentBlock.end()); @@ -230,11 +233,7 @@ void FactsGenerator::VisitMaterializeTemporaryExpr( } void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { - /// TODO: Also handle trivial destructors (e.g., for `int` - /// variables) which will never have a CFGAutomaticObjDtor node. /// TODO: Handle loans to temporaries. - /// TODO: Consider using clang::CFG::BuildOptions::AddLifetime to reuse the - /// lifetime ends. const VarDecl *DestructedVD = DtorOpt.getVarDecl(); if (!DestructedVD) return; @@ -251,6 +250,17 @@ void FactsGenerator::handleDestructor(const CFGAutomaticObjDtor &DtorOpt) { } } +void FactsGenerator::handleTrivialDestructors( + const CFGLifetimeEnds &LifetimeEnds) { + for (const auto &Loan : FactMgr.getLoanMgr().getLoans()) { + if (Loan.Path.D == LifetimeEnds.getVarDecl()) { + CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>( + Loan.ID, LifetimeEnds.getTriggerStmt()->getEndLoc())); + break; + } + } +} + void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) { assert(isGslPointerType(CCE->getType())); if (CCE->getNumArgs() != 1) diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 0fa75a24db4f3..fd6abc2874cb7 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -2987,6 +2987,7 @@ void clang::sema::AnalysisBasedWarnings::IssueWarnings( AC.getCFGBuildOptions().AddInitializers = true; AC.getCFGBuildOptions().AddImplicitDtors = true; AC.getCFGBuildOptions().AddTemporaryDtors = true; + AC.getCFGBuildOptions().AddLifetime = true; AC.getCFGBuildOptions().AddCXXNewAllocator = false; AC.getCFGBuildOptions().AddCXXDefaultInitExprInCtors = true; diff --git a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp index 11d3b836db3e7..4d9d88fbc294d 100644 --- a/clang/test/Sema/warn-lifetime-safety-dataflow.cpp +++ b/clang/test/Sema/warn-lifetime-safety-dataflow.cpp @@ -59,6 +59,7 @@ int return_int_val() { int x = 10; // CHECK: Block B{{[0-9]+}}: // CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: {{[0-9]+}} (Expr: DeclRefExpr)) +// CHECK: Expire (0 (Path: x)) return x; } // CHECK-NEXT: End of Block @@ -77,7 +78,6 @@ void loan_expires_cpp() { } -// FIXME: No expire for Trivial Destructors // CHECK-LABEL: Function: loan_expires_trivial void loan_expires_trivial() { int trivial_obj = 1; @@ -86,11 +86,29 @@ void loan_expires_trivial() { // CHECK: OriginFlow (Dest: [[O_ADDR_TRIVIAL_OBJ:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_TRIVIAL]] (Expr: DeclRefExpr)) int* pTrivialObj = &trivial_obj; // CHECK: OriginFlow (Dest: {{[0-9]+}} (Decl: pTrivialObj), Src: [[O_ADDR_TRIVIAL_OBJ]] (Expr: UnaryOperator)) -// CHECK-NOT: Expire +// CHECK: Expire (0 (Path: trivial_obj)) // CHECK-NEXT: End of Block - // FIXME: Add check for Expire once trivial destructors are handled for expiration. } +// Trivial Destructors +// CHECK-LABEL: Function: return_int_pointer +int* return_int_pointer() { + int* ptr; +// CHECK: Block B{{[0-9]+}}: + int x = 1; +// CHECK: Issue ([[L_X:[0-9]+]] (Path: x), ToOrigin: [[O_DRE_X:[0-9]+]] (Expr: DeclRefExpr)) +// CHECK: OriginFlow (Dest: [[O_ADDR_X:[0-9]+]] (Expr: UnaryOperator), Src: [[O_DRE_X]] (Expr: DeclRefExpr)) + ptr = &x; +// CHECK: Use ([[O_PTR:[0-9]+]] (Decl: ptr), Write) +// CHECK: OriginFlow (Dest: [[O_PTR]] (Decl: ptr), Src: [[O_ADDR_X]] (Expr: UnaryOperator)) +// CHECK: Use ([[O_PTR]] (Decl: ptr), Read) +// CHECK: OriginFlow (Dest: [[O_RET_VAL:[0-9]+]] (Expr: ImplicitCastExpr), Src: [[O_PTR]] (Decl: ptr)) +// CHECK: Expire ([[L_X]] (Path: x)) +// CHECK: OriginEscapes ([[O_RET_VAL]] (Expr: ImplicitCastExpr)) + return ptr; +} +// CHECK-NEXT: End of Block + // CHECK-LABEL: Function: conditional void conditional(bool condition) { int a = 5; diff --git a/clang/test/Sema/warn-lifetime-safety.cpp b/clang/test/Sema/warn-lifetime-safety.cpp index 2803e73b5aee2..88c8d5076e5b5 100644 --- a/clang/test/Sema/warn-lifetime-safety.cpp +++ b/clang/test/Sema/warn-lifetime-safety.cpp @@ -12,6 +12,10 @@ struct [[gsl::Pointer()]] View { void use() const; }; +class S { + View a, b; +}; + //===----------------------------------------------------------------------===// // Basic Definite Use-After-Free (-W...permissive) // These are cases where the pointer is guaranteed to be dangling at the use site. @@ -396,6 +400,24 @@ void loan_from_previous_iteration(MyObj safe, bool condition) { } // expected-note {{destroyed here}} } +void trivial_uaf(){ + int * a; + { + int b = 1; + a = &b; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)*a; // expected-note {{later used here}} +} + +void trivial_class_uaf() { + S* ptr; + { + S s; + ptr = &s; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)ptr; // expected-note {{later used here}} +} + //===----------------------------------------------------------------------===// // Basic Definite Use-After-Return (Return-Stack-Address) (-W...permissive) // These are cases where the pointer is guaranteed to be dangling at the use site. @@ -493,6 +515,20 @@ MyObj& reference_return_of_local() { // expected-note@-1 {{returned here}} } +int* trivial_uar() { + int *a; + int b = 1; + a = &b; // expected-warning {{address of stack memory is returned later}} + return a; // expected-note {{returned here}} +} + +S* trivial_class_uar () { + S *ptr; + S s; + ptr = &s; // expected-warning {{address of stack memory is returned later}} + return ptr; // expected-note {{returned here}} +} + //===----------------------------------------------------------------------===// // Use-After-Scope & Use-After-Return (Return-Stack-Address) Combined // These are cases where the diagnostic kind is determined by location @@ -696,10 +732,10 @@ void lifetimebound_return_reference() { { MyObj obj; View temp_v = obj; - const MyObj& ref = GetObject(temp_v); - ptr = &ref; - } - (void)*ptr; + const MyObj& ref = GetObject(temp_v);  + ptr = &ref; // expected-warning {{object whose reference is captured does not live long enough}} + } // expected-note {{destroyed here}} + (void)*ptr; // expected-note {{later used here}} } // FIXME: No warning for non gsl::Pointer types. Origin tracking is only supported for pointer types. diff --git a/clang/unittests/Analysis/LifetimeSafetyTest.cpp b/clang/unittests/Analysis/LifetimeSafetyTest.cpp index 558a22af72572..a895475013c98 100644 --- a/clang/unittests/Analysis/LifetimeSafetyTest.cpp +++ b/clang/unittests/Analysis/LifetimeSafetyTest.cpp @@ -59,6 +59,7 @@ class LifetimeTestRunner { BuildOptions.setAllAlwaysAdd(); BuildOptions.AddImplicitDtors = true; BuildOptions.AddTemporaryDtors = true; + BuildOptions.AddLifetime = true; // Run the main analysis. Analysis = std::make_unique<LifetimeSafetyAnalysis>(*AnalysisCtx, nullptr); @@ -1307,6 +1308,42 @@ TEST_F(LifetimeAnalysisTest, LivenessOutsideLoop) { EXPECT_THAT(Origins({"p"}), MaybeLiveAt("p1")); } +TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAF) { + SetupTest(R"( + void target() { + int *ptr; + { + int s = 1; + ptr = &s; + } + POINT(p1);  + (void)*ptr; + } + )"); + EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1")); + EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1")); +} + +TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAF) { + SetupTest(R"( + class S { + View a, b; + }; + + void target() { + S* ptr; + { + S s; + ptr = &s; + } + POINT(p1); + (void)ptr; + } + )"); + EXPECT_THAT(Origin("ptr"), HasLoansTo({"s"}, "p1")); + EXPECT_THAT(Origins({"ptr"}), MustBeLiveAt("p1")); +} + TEST_F(LifetimeAnalysisTest, SimpleReturnStackAddress) { SetupTest(R"( MyObj* target() { @@ -1506,5 +1543,34 @@ TEST_F(LifetimeAnalysisTest, ReturnBeforeUseAfterScope) { EXPECT_THAT(Origins({"p"}), MustBeLiveAt("p1")); } +TEST_F(LifetimeAnalysisTest, TrivialDestructorsUAR) { + SetupTest(R"( + int* target() { + int s = 10; + int* p = &s; + POINT(p1); + return p; + } + )"); + EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); +} + +TEST_F(LifetimeAnalysisTest, TrivialClassDestructorsUAR) { + SetupTest(R"( + class S { + View a, b; + }; + + S* target() { + S *ptr; + S s; + ptr = &s; + POINT(p1); + return ptr; + } + )"); + EXPECT_THAT("s", HasLiveLoanAtExpiry("p1")); +} + } // anonymous namespace } // namespace clang::lifetimes::internal 
@github-actions
Copy link

⚠️ We detected that you are using a GitHub private e-mail address to contribute to the repo.
Please turn off Keep my email addresses private setting in your account.
See LLVM Developer Policy and LLVM Discourse for more information.

@github-actions
Copy link

github-actions bot commented Nov 20, 2025

🐧 Linux x64 Test Results

  • 111353 tests passed
  • 4435 tests skipped
Copy link
Collaborator

@Xazax-hun Xazax-hun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall looks great, one comment inline.

@usx95 usx95 moved this to In Progress in Lifetime Safety in Clang Nov 20, 2025
@usx95 usx95 requested review from usx95 and ymand November 20, 2025 12:15
@usx95 usx95 changed the title Detect expiry of loans to trivially destructed types (issue id: 162862) [LifetimeSafety] Detect expiry of loans to trivially destructed types Nov 20, 2025
Copy link
Contributor

@usx95 usx95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for doing this. Looks precise and great overall.

I have one comment around duplicate expired loans facts from destructors and lifetimeends. If possible, I would get rid of destructor handling.

Copy link
Contributor

usx95 commented Nov 21, 2025

One more comment:
Does it fix #169014 ?
I was kind of hoping we would see lifetime ends for params. If not, we might want to change the CFG and add lifetimeends for these (in a separate PR)

@kashika0112 kashika0112 requested a review from usx95 November 21, 2025 11:51
Copy link
Contributor

@usx95 usx95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. LGTM.

@usx95 usx95 merged commit 4fca7b0 into llvm:main Nov 21, 2025
10 checks passed
@github-project-automation github-project-automation bot moved this from In Progress to Done in Lifetime Safety in Clang Nov 21, 2025
aadeshps-mcw pushed a commit to aadeshps-mcw/llvm-project that referenced this pull request Nov 26, 2025
…llvm#168855) Handling Trivially Destructed Types This PR uses `AddLifetime` to handle expiry of loans to trivially destructed types. Example: ```cpp int * trivial_uar(){ int *ptr; int x = 1; ptr = &x; return ptr; } ``` The CFG created now has an Expire Fact for trivially destructed types: ``` Function: trivial_uar Block B2: End of Block Block B1: Issue (0 (Path: x), ToOrigin: 0 (Expr: DeclRefExpr)) OriginFlow (Dest: 1 (Expr: UnaryOperator), Src: 0 (Expr: DeclRefExpr)) Use (2 (Decl: ptr), Write) OriginFlow (Dest: 2 (Decl: ptr), Src: 1 (Expr: UnaryOperator)) Use (2 (Decl: ptr), Read) OriginFlow (Dest: 3 (Expr: ImplicitCastExpr), Src: 2 (Decl: ptr)) Expire (0 (Path: x)) OriginEscapes (3 (Expr: ImplicitCastExpr)) End of Block Block B0: End of Block ``` This Expire Fact issues UAR and UAF warnings. Fixes llvm#162862
Priyanshu3820 pushed a commit to Priyanshu3820/llvm-project that referenced this pull request Nov 26, 2025
…llvm#168855) Handling Trivially Destructed Types This PR uses `AddLifetime` to handle expiry of loans to trivially destructed types. Example: ```cpp int * trivial_uar(){ int *ptr; int x = 1; ptr = &x; return ptr; } ``` The CFG created now has an Expire Fact for trivially destructed types: ``` Function: trivial_uar Block B2: End of Block Block B1: Issue (0 (Path: x), ToOrigin: 0 (Expr: DeclRefExpr)) OriginFlow (Dest: 1 (Expr: UnaryOperator), Src: 0 (Expr: DeclRefExpr)) Use (2 (Decl: ptr), Write) OriginFlow (Dest: 2 (Decl: ptr), Src: 1 (Expr: UnaryOperator)) Use (2 (Decl: ptr), Read) OriginFlow (Dest: 3 (Expr: ImplicitCastExpr), Src: 2 (Decl: ptr)) Expire (0 (Path: x)) OriginEscapes (3 (Expr: ImplicitCastExpr)) End of Block Block B0: End of Block ``` This Expire Fact issues UAR and UAF warnings. Fixes llvm#162862
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:analysis clang:frontend Language frontend issues, e.g. anything involving "Sema" clang:temporal-safety Issue/FR relating to the lifetime analysis in Clang (-Wdangling, -Wreturn-local-addr) clang Clang issues not falling into any other category

4 participants