-
Notifications
You must be signed in to change notification settings - Fork 15k
Adding use-after-return in Lifetime Analysis #165370
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -44,6 +44,8 @@ class Fact { | |
| Use, | ||
| /// A marker for a specific point in the code, for testing. | ||
| TestPoint, | ||
| OriginEscapes, | ||
| // An origin that stores a loan escapes the function. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Wording suggestion: /// Indicates that an origin escapes the function scope (e.g., via return). |
||
| }; | ||
|
|
||
| private: | ||
|
|
@@ -142,6 +144,23 @@ class ReturnOfOriginFact : public Fact { | |
| const OriginManager &OM) const override; | ||
| }; | ||
|
|
||
| class OriginEscapesFact : public Fact { | ||
| OriginID OID; | ||
| const Stmt *EscapeSource; | ||
|
|
||
| public: | ||
| static bool classof(const Fact *F) { | ||
| return F->getKind() == Kind::OriginEscapes; | ||
| } | ||
|
|
||
| OriginEscapesFact(OriginID OID, const Stmt *Source) | ||
| : Fact(Kind::OriginEscapes), OID(OID), EscapeSource(Source) {} | ||
| OriginID getEscapedOriginID() const { return OID; } | ||
| const Stmt *getEscapeSource() const { return EscapeSource; } | ||
| void dump(llvm::raw_ostream &OS, const LoanManager &, | ||
| const OriginManager &OM) const override; | ||
| }; | ||
|
|
||
| class UseFact : public Fact { | ||
| const Expr *UseExpr; | ||
| // True if this use is a write operation (e.g., left-hand side of assignment). | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -93,6 +93,7 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> { | |
| FactManager &FactMgr; | ||
| AnalysisDeclContext &AC; | ||
| llvm::SmallVector<Fact *> CurrentBlockFacts; | ||
| llvm::SmallVector<Fact *> EscapesInCurrentBlock; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a line or two to explain the purpose. |
||
| // To distinguish between reads and writes for use-after-free checks, this map | ||
| // stores the `UseFact` for each `DeclRefExpr`. We initially identify all | ||
| // `DeclRefExpr`s as "read" uses. When an assignment is processed, the use | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -42,6 +42,11 @@ class LifetimeSafetyReporter { | |
| virtual void reportUseAfterFree(const Expr *IssueExpr, const Expr *UseExpr, | ||
| SourceLocation FreeLoc, | ||
| Confidence Confidence) {} | ||
|
|
||
| virtual void reportUseAfterReturn(const Expr *IssueExpr, | ||
| const Stmt *EscapeStmt, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does it have to be a |
||
| SourceLocation ExpiryLoc, | ||
| Confidence Confidence) {} | ||
| }; | ||
|
|
||
| /// The main entry point for the analysis. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -10730,6 +10730,7 @@ def warn_lifetime_safety_loan_expires_strict : Warning< | |
| InGroup<LifetimeSafetyStrict>, DefaultIgnore; | ||
| def note_lifetime_safety_used_here : Note<"later used here">; | ||
| def note_lifetime_safety_destroyed_here : Note<"destroyed here">; | ||
| def note_lifetime_safety_returned_here : Note<"returned here">; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can have a separate warning for this. |
||
|
|
||
| // For non-floating point, expressions of the form x == x or x != x | ||
| // should result in a warning, since these always evaluate to a constant. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ | |
| #include "clang/Analysis/AnalysisDeclContext.h" | ||
| #include "clang/Basic/SourceLocation.h" | ||
| #include "llvm/ADT/DenseMap.h" | ||
| #include "llvm/ADT/SmallSet.h" | ||
| #include "llvm/Support/ErrorHandling.h" | ||
| #include "llvm/Support/TimeProfiler.h" | ||
|
|
||
|
|
@@ -61,10 +62,23 @@ class LifetimeChecker { | |
| AnalysisDeclContext &ADC, LifetimeSafetyReporter *Reporter) | ||
| : LoanPropagation(LoanPropagation), LiveOrigins(LiveOrigins), FactMgr(FM), | ||
| Reporter(Reporter) { | ||
| for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) | ||
| for (const Fact *F : FactMgr.getFacts(B)) | ||
| if (const auto *EF = F->getAs<ExpireFact>()) | ||
| for (const CFGBlock *B : *ADC.getAnalysis<PostOrderCFGView>()) { | ||
| llvm::SmallVector<const ExpireFact *> BlockExpires; | ||
| llvm::SmallVector<const OriginEscapesFact *> BlockEscapes; | ||
| for (const Fact *F : FactMgr.getFacts(B)) { | ||
| if (const auto *EF = F->getAs<ExpireFact>()) { | ||
| checkExpiry(EF); | ||
| BlockExpires.push_back(EF); | ||
| } else if (const auto *OEF = F->getAs<OriginEscapesFact>()) { | ||
| BlockEscapes.push_back(OEF); | ||
| } | ||
| } | ||
| if (Reporter) { | ||
| for (const OriginEscapesFact *OEF : BlockEscapes) { | ||
| checkEscape(OEF, BlockExpires); | ||
| } | ||
| } | ||
| } | ||
| issuePendingWarnings(); | ||
| } | ||
|
|
||
|
|
@@ -106,6 +120,63 @@ class LifetimeChecker { | |
| /*ConfidenceLevel=*/CurConfidence}; | ||
| } | ||
|
|
||
| void checkEscape(const OriginEscapesFact *OEF, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think if we correctly use the liveness information, |
||
| llvm::ArrayRef<const ExpireFact *> BlockExpires) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we catch the error if the returned loan expired in a different block? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems important to choose the diagnostic loc and even kind of diagnostic std::string_view foo() {
std::string_view res;
{
std::string small = "small scope";
res = small;
}
return res;
}I think for this we should have an existing use-after-scope (because Also there could be cases where the loan is bad due to both use-after-scope and return-stack-addr std::string_view foo() {
std::string_view res;
{
std::string small = "small scope";
res = small;
if (cond) {
return res;
}
}
std::cout << res;
}We would see two loan expiries here. One before On the other hand, following would be existing use-after-scope: std::string_view foo() {
std::string_view res;
{
std::string small = "small scope";
res = small; // does not live long enough.
}
std::cout << res; // later used here.
return res;
} |
||
|
|
||
| if (!Reporter) { | ||
| return; | ||
| } | ||
|
|
||
| OriginID returnedOID = OEF->getEscapedOriginID(); | ||
| ProgramPoint PP = OEF; | ||
|
|
||
| LoanSet HeldLoans = LoanPropagation.getLoans(returnedOID, PP); | ||
| if (HeldLoans.isEmpty()) { | ||
| return; | ||
| } | ||
|
|
||
| llvm::SmallSet<LoanID, 4> ExpiredLoansInBlock; | ||
| llvm::DenseMap<LoanID, SourceLocation> ExpiryLocs; | ||
|
|
||
| for (const ExpireFact *EF : BlockExpires) { | ||
| ExpiredLoansInBlock.insert(EF->getLoanID()); | ||
| ExpiryLocs[EF->getLoanID()] = EF->getExpiryLoc(); | ||
| } | ||
|
|
||
| bool hasExpiredDependency = false; | ||
| bool allHeldLoansExpired = true; | ||
| LoanID exampleExpiredLoan = LoanID(); | ||
|
|
||
| for (LoanID heldLoan : HeldLoans) { | ||
| if (ExpiredLoansInBlock.count(heldLoan)) { | ||
| hasExpiredDependency = true; | ||
| if (exampleExpiredLoan.Value == LoanID().Value) { | ||
| exampleExpiredLoan = heldLoan; | ||
| } | ||
| } else { | ||
| allHeldLoansExpired = false; | ||
| } | ||
| } | ||
|
|
||
| if (!hasExpiredDependency) { | ||
| return; | ||
| } | ||
|
|
||
| Confidence FinalConfidence; | ||
| if (allHeldLoansExpired) { | ||
| FinalConfidence = Confidence::Definite; | ||
| } else { | ||
| FinalConfidence = Confidence::Maybe; | ||
| } | ||
|
|
||
| const Loan &L = FactMgr.getLoanMgr().getLoan(exampleExpiredLoan); | ||
| SourceLocation ExpiryLoc = ExpiryLocs[exampleExpiredLoan]; | ||
| const Stmt *EscapeSource = OEF->getEscapeSource(); | ||
|
|
||
| Reporter->reportUseAfterReturn(L.IssueExpr, EscapeSource, ExpiryLoc, | ||
| FinalConfidence); | ||
| } | ||
|
|
||
| void issuePendingWarnings() { | ||
| if (!Reporter) | ||
| return; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -58,6 +58,7 @@ void FactsGenerator::run() { | |
| // initializations and destructions are processed in the correct sequence. | ||
| for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) { | ||
| CurrentBlockFacts.clear(); | ||
| EscapesInCurrentBlock.clear(); | ||
| for (unsigned I = 0; I < Block->size(); ++I) { | ||
| const CFGElement &Element = Block->Elements[I]; | ||
| if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>()) | ||
|
|
@@ -66,6 +67,8 @@ void FactsGenerator::run() { | |
| Element.getAs<CFGAutomaticObjDtor>()) | ||
| handleDestructor(*DtorOpt); | ||
| } | ||
| CurrentBlockFacts.append(EscapesInCurrentBlock.begin(), | ||
| EscapesInCurrentBlock.end()); | ||
| FactMgr.addBlockFacts(Block, CurrentBlockFacts); | ||
| } | ||
| } | ||
|
|
@@ -166,7 +169,8 @@ void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) { | |
| if (const Expr *RetExpr = RS->getRetValue()) { | ||
| if (hasOrigin(RetExpr)) { | ||
| OriginID OID = FactMgr.getOriginMgr().getOrCreate(*RetExpr); | ||
| CurrentBlockFacts.push_back(FactMgr.createFact<ReturnOfOriginFact>(OID)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just to make sure this is intentional: WDYT @Xazax-hun, I remember you had some thoughts about still keeping it around. |
||
| EscapesInCurrentBlock.push_back( | ||
| FactMgr.createFact<OriginEscapesFact>(OID, RS)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
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.
Nit: the comment should probably go before the enum constant and might need some word smithing.