- Notifications
You must be signed in to change notification settings - Fork 15.3k
[LifetimeSafety] Detect expiry of loans to trivially destructed types #168855
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
| @llvm/pr-subscribers-clang-analysis @llvm/pr-subscribers-clang-temporal-safety Author: Kashika Akhouri (kashika0112) ChangesHandling Trivially Destructed Types This PR uses Example: The CFG created now has an Expire Fact for trivially destructed types: This Expire Fact issues UAR and UAF warnings. Full diff: https://github.com/llvm/llvm-project/pull/168855.diff 6 Files Affected:
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 |
|
|
🐧 Linux x64 Test Results
|
Xazax-hun left a comment
There was a problem hiding this 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 left a comment
There was a problem hiding this 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.
clang/include/clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h Outdated Show resolved Hide resolved
| One more comment: |
usx95 left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. LGTM.
…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
…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
Handling Trivially Destructed Types
This PR uses
AddLifetimeto handle expiry of loans to trivially destructed types.Example:
The CFG created now has an Expire Fact for trivially destructed types:
This Expire Fact issues UAR and UAF warnings.
Fixes #162862