Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ class FactsGenerator : public ConstStmtVisitor<FactsGenerator> {
private:
void handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds);

void handleTemporaryDtor(const CFGTemporaryDtor &TemporaryDtor);

void handleGSLPointerConstruction(const CXXConstructExpr *CCE);

/// Checks if a call-like expression creates a borrow by passing a value to a
Expand Down
21 changes: 19 additions & 2 deletions clang/include/clang/Analysis/Analyses/LifetimeSafety/Loans.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#define LLVM_CLANG_ANALYSIS_ANALYSES_LIFETIMESAFETY_LOANS_H

#include "clang/AST/Decl.h"
#include "clang/AST/ExprCXX.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Utils.h"
#include "llvm/Support/raw_ostream.h"

Expand All @@ -29,9 +30,25 @@ inline llvm::raw_ostream &operator<<(llvm::raw_ostream &OS, LoanID ID) {
/// variable.
/// TODO: Model access paths of other types, e.g., s.field, heap and globals.
struct AccessPath {
const clang::ValueDecl *D;
// An access path can be:
// - ValueDecl * , to represent the storage location corresponding to the
// variable declared in ValueDecl.
// - MaterializeTemporaryExpr * , to represent the storage location of the
// temporary object materialized via this MaterializeTemporaryExpr.
const llvm::PointerUnion<const clang::ValueDecl *,
const clang::MaterializeTemporaryExpr *>
P;

AccessPath(const clang::ValueDecl *D) : P(D) {}
AccessPath(const clang::MaterializeTemporaryExpr *MTE) : P(MTE) {}

const clang::ValueDecl *getAsValueDecl() const {
return P.dyn_cast<const clang::ValueDecl *>();
}

AccessPath(const clang::ValueDecl *D) : D(D) {}
const clang::MaterializeTemporaryExpr *getAsMaterializeTemporaryExpr() const {
return P.dyn_cast<const clang::MaterializeTemporaryExpr *>();
}
};

/// An abstract base class for a single "Loan" which represents lending a
Expand Down
61 changes: 57 additions & 4 deletions clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,26 @@ static const PathLoan *createLoan(FactManager &FactMgr,
return nullptr;
}

/// Creates a loan for the storage location of a temporary object.
/// \param MTW The MaterializeTemporaryExpr that represents the temporary
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo MTE

/// binding. \return The new Loan.
static const PathLoan *createLoan(FactManager &FactMgr,
const MaterializeTemporaryExpr *MTE) {
AccessPath Path(MTE);
return FactMgr.getLoanMgr().createLoan<PathLoan>(Path, MTE);
}

/// Try to find a CXXBindTemporaryExpr that descends from MTE, stripping away
/// any implicit casts.
/// \param MTE MaterializeTemporaryExpr whose descendants we are interested in.
/// \return Pointer to descendant CXXBindTemporaryExpr or nullptr when not
/// found.
static const CXXBindTemporaryExpr *
getChildBinding(const MaterializeTemporaryExpr *MTE) {
const Expr *Child = MTE->getSubExpr()->IgnoreImpCasts();
return dyn_cast<CXXBindTemporaryExpr>(Child);
}

void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
Expand All @@ -60,6 +80,9 @@ void FactsGenerator::run() {
else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
Element.getAs<CFGLifetimeEnds>())
handleLifetimeEnds(*LifetimeEnds);
else if (std::optional<CFGTemporaryDtor> TemporaryDtor =
Element.getAs<CFGTemporaryDtor>())
handleTemporaryDtor(*TemporaryDtor);
}
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
EscapesInCurrentBlock.end());
Expand Down Expand Up @@ -218,9 +241,14 @@ void FactsGenerator::VisitMaterializeTemporaryExpr(
const MaterializeTemporaryExpr *MTE) {
if (!hasOrigin(MTE))
return;
// A temporary object's origin is the same as the origin of the
// expression that initializes it.
killAndFlowOrigin(*MTE, *MTE->getSubExpr());
if (getChildBinding(MTE)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would use !isPointerType(MTE->getType()) instead of this to correctly reflect the issue with l-valued pointer type expr. For other MTE, we would have a loan issued but no corresponding expire because of no BTE child.

Copy link
Collaborator

Choose a reason for hiding this comment

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

You mentioned lifetime extended temporaries offline. Is that reflected in the current iteration of the patch or do you need to update this?

Copy link
Author

Choose a reason for hiding this comment

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

That is not reflected here, it will be in future iterations. For the future iterations, I believe my next PR would be in handling the case of non lifetime-extended general temporaries. I believe @usx95 suggested that I should choose to not track the MTE if I detect it is lifetime extended?

Copy link
Collaborator

Choose a reason for hiding this comment

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

In case this is not too hard to do, I'd prefer that bit to be included in this PR.

Copy link
Author

Choose a reason for hiding this comment

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

Makes sense will include it!

// Issue a loan to MTE for the storage location represented by MTE.
const Loan *L = createLoan(FactMgr, MTE);
OriginID OID = FactMgr.getOriginMgr().getOrCreate(*MTE);
CurrentBlockFacts.push_back(FactMgr.createFact<IssueFact>(L->getID(), OID));
} else {
killAndFlowOrigin(*MTE, *MTE->getSubExpr());
}
}

void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
Expand All @@ -233,13 +261,38 @@ void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
// Check if the loan is for a stack variable and if that variable
// is the one being destructed.
if (BL->getAccessPath().D == LifetimeEndsVD)
const AccessPath AP = BL->getAccessPath();
const ValueDecl *Path = AP.getAsValueDecl();
if (Path == LifetimeEndsVD)
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
BL->getID(), LifetimeEnds.getTriggerStmt()->getEndLoc()));
}
}
}

void FactsGenerator::handleTemporaryDtor(
const CFGTemporaryDtor &TemporaryDtor) {
const CXXBindTemporaryExpr *BTE = TemporaryDtor.getBindTemporaryExpr();
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: rename to ExpiringBTE

if (!BTE) {
return;
}
Comment on lines +276 to +278
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove the curly braces in a single-statement if body

// Iterate through all loans to see if any expire.
for (const auto *Loan : FactMgr.getLoanMgr().getLoans()) {
if (const auto *BL = dyn_cast<PathLoan>(Loan)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: rename to PL to denote PathLoan

// Check if the loan is for a temporary materialization and if that storage
// location is the one being destructed.
const AccessPath &AP = BL->getAccessPath();
const MaterializeTemporaryExpr *Path = AP.getAsMaterializeTemporaryExpr();
if (!Path)
continue;
if (BTE == getChildBinding(Path)) {
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
BL->getID(), TemporaryDtor.getBindTemporaryExpr()->getEndLoc()));
}
}
}
}

void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
assert(isGslPointerType(CCE->getType()));
if (CCE->getNumArgs() != 1)
Expand Down
11 changes: 10 additions & 1 deletion clang/lib/Analysis/LifetimeSafety/Loans.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@ namespace clang::lifetimes::internal {

void PathLoan::dump(llvm::raw_ostream &OS) const {
OS << getID() << " (Path: ";
OS << Path.D->getNameAsString() << ")";
if (const clang::ValueDecl *VD = Path.getAsValueDecl()) {
OS << VD->getNameAsString();
} else if (const clang::MaterializeTemporaryExpr *MTE =
Path.getAsMaterializeTemporaryExpr()) {
// No nice "name" for the temporary, so deferring to LLVM default
OS << "MaterializeTemporaryExpr at " << MTE;
} else {
llvm_unreachable("access path is not one of any supported types");
}
OS << ")";
Comment on lines +15 to +24
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove the {} for single-stmt bodies

}

void PlaceholderLoan::dump(llvm::raw_ostream &OS) const {
Expand Down
10 changes: 10 additions & 0 deletions clang/test/Sema/warn-lifetime-safety.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ class TriviallyDestructedClass {
View a, b;
};

MyObj temporary();
void use(View);

//===----------------------------------------------------------------------===//
// Basic Definite Use-After-Free (-W...permissive)
// These are cases where the pointer is guaranteed to be dangling at the use site.
Expand Down Expand Up @@ -943,3 +946,10 @@ void parentheses(bool cond) {
} // expected-note 4 {{destroyed here}}
(void)*p; // expected-note 4 {{later used here}}
}

void use_temporary_after_destruction() {
View a;
Comment on lines +950 to +951
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you try adding more tests. Like passing a temporary to a function with lifetimebound arg. Please also add tests which do not work at the moment with a FIXME (trivial temporaries, nested BTE).

a = temporary(); // expected-warning {{object whose reference is captured does not live long enough}} \
expected-note {{destroyed here}}
use(a); // expected-note {{later used here}}
}
50 changes: 45 additions & 5 deletions clang/unittests/Analysis/LifetimeSafetyTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeSafety.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Loans.h"
#include "clang/Testing/TestAST.h"
#include "llvm/ADT/StringMap.h"
#include "gmock/gmock.h"
Expand Down Expand Up @@ -116,7 +117,7 @@ class LifetimeTestHelper {
std::vector<LoanID> LID;
for (const Loan *L : Analysis.getFactManager().getLoanMgr().getLoans())
if (const auto *BL = dyn_cast<PathLoan>(L))
if (BL->getAccessPath().D == VD)
if (BL->getAccessPath().getAsValueDecl() == VD)
LID.push_back(L->getID());
if (LID.empty()) {
ADD_FAILURE() << "Loan for '" << VarName << "' not found.";
Expand All @@ -125,6 +126,14 @@ class LifetimeTestHelper {
return LID;
}

bool isLoanToATemporary(LoanID LID) {
const Loan *L = Analysis.getFactManager().getLoanMgr().getLoan(LID);
if (const auto *BL = dyn_cast<PathLoan>(L)) {
return BL->getAccessPath().getAsMaterializeTemporaryExpr() != nullptr;
}
return false;
}

// Gets the set of loans that are live at the given program point. A loan is
// considered live at point P if there is a live origin which contains this
// loan.
Expand Down Expand Up @@ -402,6 +411,35 @@ MATCHER_P(AreLiveAt, Annotation, "") {
arg, result_listener);
}

MATCHER_P(HasLoanToATemporary, Annotation, "") {
const OriginInfo &Info = arg;
auto &Helper = Info.Helper;
std::optional<OriginID> OIDOpt = Helper.getOriginForDecl(Info.OriginVar);
if (!OIDOpt) {
*result_listener << "could not find origin for '" << Info.OriginVar.str()
<< "'";
return false;
}

std::optional<LoanSet> LoansSetOpt =
Helper.getLoansAtPoint(*OIDOpt, Annotation);
if (!LoansSetOpt) {
*result_listener << "could not get a valid loan set at point '"
<< Annotation << "'";
return false;
}

std::vector<LoanID> Loans(LoansSetOpt->begin(), LoansSetOpt->end());

for (LoanID LID : Loans) {
if (Helper.isLoanToATemporary(LID))
return true;
}
*result_listener << "could not find loan to a temporary for '"
<< Info.OriginVar.str() << "'";
return false;
}

// Base test fixture to manage the runner and helper.
class LifetimeAnalysisTest : public ::testing::Test {
protected:
Expand Down Expand Up @@ -803,16 +841,18 @@ TEST_F(LifetimeAnalysisTest, ExtraParenthesis) {
EXPECT_THAT(Origin("p"), HasLoansTo({"a"}, "p1"));
}

// FIXME: Handle temporaries.
TEST_F(LifetimeAnalysisTest, ViewFromTemporary) {
SetupTest(R"(
MyObj temporary();
void use(View);
void target() {
View v = temporary();
POINT(p1);
View a;
a = temporary();
POINT(p1);
use(a);
}
)");
EXPECT_THAT(Origin("v"), HasLoansTo({}, "p1"));
EXPECT_THAT(Origin("a"), HasLoanToATemporary("p1"));
}

TEST_F(LifetimeAnalysisTest, GslPointerWithConstAndAuto) {
Expand Down