Files
TheRock/compiler/amd-llvm/clang/lib/Analysis/LifetimeSafety/FactsGenerator.cpp
T
derek.holloway 7502bf64a4
CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
Flatten subprojects into a single base monorepo
2026-09-02 16:34:47 -07:00

1128 lines
43 KiB
C++

//===- FactsGenerator.cpp - Lifetime Facts Generation -----------*- C++ -*-===//
//
// 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 <cassert>
#include <string>
#include "clang/AST/Decl.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/OperationKinds.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Facts.h"
#include "clang/Analysis/Analyses/LifetimeSafety/FactsGenerator.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/Analysis/Analyses/LifetimeSafety/Origins.h"
#include "clang/Analysis/Analyses/PostOrderCFGView.h"
#include "clang/Analysis/CFG.h"
#include "clang/Basic/OperatorKinds.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/TimeProfiler.h"
namespace clang::lifetimes::internal {
using llvm::isa_and_present;
OriginList *FactsGenerator::getOriginsList(const ValueDecl &D) {
return FactMgr.getOriginMgr().getOrCreateList(&D);
}
OriginList *FactsGenerator::getOriginsList(const Expr &E) {
return FactMgr.getOriginMgr().getOrCreateList(&E);
}
bool FactsGenerator::hasOrigins(QualType QT) const {
return FactMgr.getOriginMgr().hasOrigins(QT);
}
bool FactsGenerator::hasOrigins(const Expr *E) const {
return FactMgr.getOriginMgr().hasOrigins(E);
}
/// Propagates origin information from Src to Dst through all levels of
/// indirection, creating OriginFlowFacts at each level.
///
/// This function enforces a critical type-safety invariant: both lists must
/// have the same shape (same depth/structure). This invariant ensures that
/// origins flow only between compatible types during expression evaluation.
///
/// Examples:
/// - `int* p = &x;` flows origins from `&x` (depth 1) to `p` (depth 1)
/// - `int** pp = &p;` flows origins from `&p` (depth 2) to `pp` (depth 2)
/// * Level 1: pp <- p's address
/// * Level 2: (*pp) <- what p points to (i.e., &x)
/// - `View v = obj;` flows origins from `obj` (depth 1) to `v` (depth 1)
void FactsGenerator::flow(OriginList *Dst, OriginList *Src, bool Kill) {
if (!Dst)
return;
assert(Src &&
"Dst is non-null but Src is null. List must have the same length");
assert(Dst->getLength() == Src->getLength() &&
"Lists must have the same length");
while (Dst && Src) {
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
Dst->getOuterOriginID(), Src->getOuterOriginID(), Kill));
Dst = Dst->peelOuterOrigin();
Src = Src->peelOuterOrigin();
}
}
/// Creates a loan for the storage path of a given declaration reference.
/// This function should be called whenever a DeclRefExpr represents a borrow.
/// \param DRE The declaration reference expression that initiates the borrow.
/// \return The new Loan on success, nullptr otherwise.
static const Loan *createLoan(FactManager &FactMgr, const DeclRefExpr *DRE) {
const ValueDecl *VD = DRE->getDecl();
AccessPath Path(VD);
// The loan is created at the location of the DeclRefExpr.
return FactMgr.getLoanMgr().createLoan(Path, DRE);
}
/// Creates a loan for the storage location of a temporary object.
/// \param MTE The MaterializeTemporaryExpr that represents the temporary
/// binding. \return The new Loan.
static const Loan *createLoan(FactManager &FactMgr,
const MaterializeTemporaryExpr *MTE) {
AccessPath Path(MTE);
return FactMgr.getLoanMgr().createLoan(Path, MTE);
}
/// Creates a loan for an allocation through 'new'
/// \param NE The CXXNewExpr that represents the allocation
/// \return The new Loan on success, nullptr otherwise
static const Loan *createLoan(FactManager &FactMgr, const CXXNewExpr *NE) {
AccessPath Path(NE);
return FactMgr.getLoanMgr().createLoan(Path, NE);
}
void FactsGenerator::run() {
llvm::TimeTraceScope TimeProfile("FactGenerator");
const CFG &Cfg = *AC.getCFG();
llvm::SmallVector<Fact *> PlaceholderLoanFacts = issuePlaceholderLoans();
// Iterate through the CFG blocks in reverse post-order to ensure that
// initializations and destructions are processed in the correct sequence.
for (const CFGBlock *Block : *AC.getAnalysis<PostOrderCFGView>()) {
CurrentBlockFacts.clear();
EscapesInCurrentBlock.clear();
CurrentBlock = Block;
if (Block == &Cfg.getEntry())
CurrentBlockFacts.append(PlaceholderLoanFacts.begin(),
PlaceholderLoanFacts.end());
for (unsigned I = 0; I < Block->size(); ++I) {
const CFGElement &Element = Block->Elements[I];
if (std::optional<CFGStmt> CS = Element.getAs<CFGStmt>())
Visit(CS->getStmt());
else if (std::optional<CFGInitializer> Initializer =
Element.getAs<CFGInitializer>())
handleCXXCtorInitializer(Initializer->getInitializer());
else if (std::optional<CFGLifetimeEnds> LifetimeEnds =
Element.getAs<CFGLifetimeEnds>())
handleLifetimeEnds(*LifetimeEnds);
else if (std::optional<CFGFullExprCleanup> FullExprCleanup =
Element.getAs<CFGFullExprCleanup>()) {
handleFullExprCleanup(*FullExprCleanup);
}
}
if (Block == &Cfg.getExit())
handleExitBlock();
CurrentBlockFacts.append(EscapesInCurrentBlock.begin(),
EscapesInCurrentBlock.end());
FactMgr.addBlockFacts(Block, CurrentBlockFacts);
}
}
/// Simulates LValueToRValue conversion by peeling the outer lvalue origin
/// if the expression is a GLValue. For pointer/view GLValues, this strips
/// the origin representing the storage location to get the origins of the
/// pointed-to value.
///
/// Example: For `View& v`, returns the origin of what v points to, not v's
/// storage.
static OriginList *getRValueOrigins(const Expr *E, OriginList *List) {
if (!List)
return nullptr;
return E->isGLValue() ? List->peelOuterOrigin() : List;
}
void FactsGenerator::VisitDeclStmt(const DeclStmt *DS) {
for (const Decl *D : DS->decls())
if (const auto *VD = dyn_cast<VarDecl>(D))
if (const Expr *InitExpr = VD->getInit()) {
OriginList *VDList = getOriginsList(*VD);
if (!VDList)
continue;
OriginList *InitList = getOriginsList(*InitExpr);
assert(InitList && "VarDecl had origins but InitExpr did not");
flow(VDList, InitList, /*Kill=*/true);
}
}
void FactsGenerator::VisitDeclRefExpr(const DeclRefExpr *DRE) {
// Skip function references as their lifetimes are not interesting. Skip non
// GLValues (like EnumConstants).
if (DRE->getFoundDecl()->isFunctionOrFunctionTemplate() || !DRE->isGLValue())
return;
handleUse(DRE);
// For all declarations with storage (non-references), we issue a loan
// representing the borrow of the variable's storage itself.
//
// Examples:
// - `int x; x` issues loan to x's storage
// - `int* p; p` issues loan to p's storage (the pointer variable)
// - `View v; v` issues loan to v's storage (the view object)
// - `int& r = x; r` issues no loan (r has no storage, it's an alias to x)
if (doesDeclHaveStorage(DRE->getDecl())) {
const Loan *L = createLoan(FactMgr, DRE);
assert(L);
OriginList *List = getOriginsList(*DRE);
assert(List &&
"gl-value DRE of non-pointer type should have an origin list");
// This loan specifically tracks borrowing the variable's storage location
// itself and is issued to outermost origin (List->OID).
CurrentBlockFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}
}
void FactsGenerator::VisitCXXConstructExpr(const CXXConstructExpr *CCE) {
if (isGslPointerType(CCE->getType())) {
handleGSLPointerConstruction(CCE);
return;
}
// For defaulted (implicit or `= default`) copy/move constructors, propagate
// origins directly. User-defined copy/move constructors are not handled here
// as they have opaque semantics.
if (CCE->getConstructor()->isCopyOrMoveConstructor() &&
CCE->getConstructor()->isDefaulted() && CCE->getNumArgs() == 1 &&
hasOrigins(CCE->getType())) {
const Expr *Arg = CCE->getArg(0);
if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
return;
}
}
// Standard library callable wrappers (e.g., std::function) propagate the
// stored lambda's origins.
if (const auto *RD = CCE->getType()->getAsCXXRecordDecl();
RD && isStdCallableWrapperType(RD) && CCE->getNumArgs() == 1) {
const Expr *Arg = CCE->getArg(0);
if (OriginList *ArgList = getRValueOrigins(Arg, getOriginsList(*Arg))) {
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
return;
}
}
handleFunctionCall(CCE, CCE->getConstructor(),
{CCE->getArgs(), CCE->getNumArgs()},
/*IsGslConstruction=*/false);
}
void FactsGenerator::VisitCXXDefaultInitExpr(const CXXDefaultInitExpr *DIE) {
if (const Expr *Init = DIE->getExpr())
killAndFlowOrigin(*DIE, *Init);
}
void FactsGenerator::handleCXXCtorInitializer(const CXXCtorInitializer *CII) {
// Flows origins from the initializer expression to the field.
// Example: `MyObj(std::string s) : view(s) {}`
if (const FieldDecl *FD = CII->getAnyMember())
killAndFlowOrigin(*FD, *CII->getInit());
}
void FactsGenerator::VisitCXXMemberCallExpr(const CXXMemberCallExpr *MCE) {
// Specifically for conversion operators,
// like `std::string_view p = std::string{};`
if (isGslPointerType(MCE->getType()) &&
isa_and_present<CXXConversionDecl>(MCE->getCalleeDecl()) &&
isGslOwnerType(MCE->getImplicitObjectArgument()->getType())) {
// The argument is the implicit object itself.
handleFunctionCall(MCE, MCE->getMethodDecl(),
{MCE->getImplicitObjectArgument()},
/*IsGslConstruction=*/true);
return;
}
if (const CXXMethodDecl *Method = MCE->getMethodDecl()) {
// Construct the argument list, with the implicit 'this' object as the
// first argument.
llvm::SmallVector<const Expr *, 4> Args;
Args.push_back(MCE->getImplicitObjectArgument());
Args.append(MCE->getArgs(), MCE->getArgs() + MCE->getNumArgs());
handleFunctionCall(MCE, Method, Args, /*IsGslConstruction=*/false);
}
}
void FactsGenerator::VisitMemberExpr(const MemberExpr *ME) {
auto *MD = ME->getMemberDecl();
if (isa<FieldDecl>(MD) && doesDeclHaveStorage(MD)) {
assert(ME->isGLValue() && "Field member should be GL value");
OriginList *Dst = getOriginsList(*ME);
assert(Dst && "Field member should have an origin list as it is GL value");
OriginList *Src = getOriginsList(*ME->getBase());
assert(Src && "Base expression should be a pointer/reference type");
// The field's glvalue (outermost origin) holds the same loans as the base
// expression.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
Dst->getOuterOriginID(), Src->getOuterOriginID(),
/*Kill=*/true));
}
}
void FactsGenerator::VisitCallExpr(const CallExpr *CE) {
handleFunctionCall(CE, CE->getDirectCallee(),
{CE->getArgs(), CE->getNumArgs()});
}
void FactsGenerator::VisitCXXNullPtrLiteralExpr(
const CXXNullPtrLiteralExpr *N) {
/// TODO: Handle nullptr expr as a special 'null' loan. Uninitialized
/// pointers can use the same type of loan.
getOriginsList(*N);
}
void FactsGenerator::VisitCastExpr(const CastExpr *CE) {
OriginList *Dest = getOriginsList(*CE);
if (!Dest)
return;
const Expr *SubExpr = CE->getSubExpr();
OriginList *Src = getOriginsList(*SubExpr);
switch (CE->getCastKind()) {
case CK_LValueToRValue:
if (!SubExpr->isGLValue())
return;
assert(Src && "LValue being cast to RValue has no origin list");
// The result of an LValue-to-RValue cast on a pointer lvalue (like `q` in
// `int *p, *q; p = q;`) should propagate the inner origin (what the pointer
// points to), not the outer origin (the pointer's storage location). Strip
// the outer lvalue origin.
flow(getOriginsList(*CE), getRValueOrigins(SubExpr, Src),
/*Kill=*/true);
return;
case CK_NullToPointer:
getOriginsList(*CE);
// TODO: Flow into them a null origin.
return;
case CK_NoOp:
case CK_ConstructorConversion:
case CK_UserDefinedConversion:
flow(Dest, Src, /*Kill=*/true);
return;
case CK_UncheckedDerivedToBase:
case CK_DerivedToBase:
// It is possible that the derived class and base class have different
// gsl::Pointer annotations. Skip if their origin shape differ.
if (Dest && Src && Dest->getLength() == Src->getLength())
flow(Dest, Src, /*Kill=*/true);
return;
case CK_ArrayToPointerDecay:
assert(Src && "Array expression should have origins as it is GL value");
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
Dest->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
return;
case CK_FunctionToPointerDecay:
case CK_BuiltinFnToFnPtr:
// Ignore function-to-pointer decays.
return;
case CK_BitCast:
// OriginLists for Src and Dst may differ here. For example when casting
// from int** to void*
if (Src && Dest && Dest->getLength() == Src->getLength())
flow(Dest, Src, /*Kill=*/true);
return;
default:
return;
}
}
void FactsGenerator::VisitUnaryOperator(const UnaryOperator *UO) {
switch (UO->getOpcode()) {
case UO_AddrOf: {
const Expr *SubExpr = UO->getSubExpr();
// The origin of an address-of expression (e.g., &x) is the origin of
// its sub-expression (x). This fact will cause the dataflow analysis
// to propagate any loans held by the sub-expression's origin to the
// origin of this UnaryOperator expression.
killAndFlowOrigin(*UO, *SubExpr);
return;
}
case UO_Deref: {
const Expr *SubExpr = UO->getSubExpr();
killAndFlowOrigin(*UO, *SubExpr);
return;
}
default:
return;
}
}
void FactsGenerator::VisitReturnStmt(const ReturnStmt *RS) {
if (const Expr *RetExpr = RS->getRetValue()) {
if (OriginList *List = getOriginsList(*RetExpr))
for (OriginList *L = List; L != nullptr; L = L->peelOuterOrigin())
EscapesInCurrentBlock.push_back(FactMgr.createFact<ReturnEscapeFact>(
L->getOuterOriginID(), RetExpr));
}
}
void FactsGenerator::handleAssignment(const Expr *TargetExpr,
const Expr *LHSExpr,
const Expr *RHSExpr) {
LHSExpr = LHSExpr->IgnoreParenImpCasts();
OriginList *LHSList = nullptr;
if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
LHSList = getOriginsList(*DRE_LHS);
assert(LHSList && "LHS is a DRE and should have an origin list");
}
// Handle assignment to member fields (e.g., `this->view = s` or `view = s`).
// This enables detection of dangling fields when local values escape to
// fields.
if (const auto *ME_LHS = dyn_cast<MemberExpr>(LHSExpr)) {
LHSList = getOriginsList(*ME_LHS);
assert(LHSList && "LHS is a MemberExpr and should have an origin list");
}
if (!LHSList)
return;
OriginList *RHSList = getOriginsList(*RHSExpr);
// For operator= with reference parameters (e.g.,
// `View& operator=(const View&)`), the RHS argument stays an lvalue,
// unlike built-in assignment where LValueToRValue cast strips the outer
// lvalue origin. Strip it manually to get the actual value origins being
// assigned.
RHSList = getRValueOrigins(RHSExpr, RHSList);
if (const auto *DRE_LHS = dyn_cast<DeclRefExpr>(LHSExpr)) {
QualType QT = DRE_LHS->getDecl()->getType();
if (QT->isReferenceType()) {
if (hasOrigins(QT->getPointeeType())) {
// Writing through a reference uses the binding but overwrites the
// pointee. Model this as a Read of the outer origin (keeping the
// binding live) and a Write of the inner origins (killing the pointee's
// liveness).
if (UseFact *UF = UseFacts.lookup(DRE_LHS)) {
const OriginList *FullList = UF->getUsedOrigins();
assert(FullList);
UF->setUsedOrigins(FactMgr.getOriginMgr().createSingleOriginList(
FullList->getOuterOriginID()));
if (const OriginList *InnerList = FullList->peelOuterOrigin()) {
UseFact *WriteUF = FactMgr.createFact<UseFact>(DRE_LHS, InnerList);
WriteUF->markAsWritten();
CurrentBlockFacts.push_back(WriteUF);
}
}
}
} else
markUseAsWrite(DRE_LHS);
}
if (!RHSList) {
// RHS has no tracked origins (e.g., assigning a callable without origins
// to std::function). Clear loans of the destination.
for (OriginList *LHSInner = LHSList->peelOuterOrigin(); LHSInner;
LHSInner = LHSInner->peelOuterOrigin())
CurrentBlockFacts.push_back(
FactMgr.createFact<KillOriginFact>(LHSInner->getOuterOriginID()));
return;
}
// Kill the old loans of the destination origin and flow the new loans
// from the source origin.
flow(LHSList->peelOuterOrigin(), RHSList, /*Kill=*/true);
killAndFlowOrigin(*TargetExpr, *LHSExpr);
}
void FactsGenerator::handlePointerArithmetic(const BinaryOperator *BO) {
if (Expr *RHS = BO->getRHS(); RHS->getType()->isPointerType()) {
killAndFlowOrigin(*BO, *RHS);
return;
}
Expr *LHS = BO->getLHS();
assert(LHS->getType()->isPointerType() &&
"Pointer arithmetic must have a pointer operand");
killAndFlowOrigin(*BO, *LHS);
}
void FactsGenerator::VisitBinaryOperator(const BinaryOperator *BO) {
if (BO->isCompoundAssignmentOp())
return;
if (BO->getType()->isPointerType() && BO->isAdditiveOp())
handlePointerArithmetic(BO);
handleUse(BO->getRHS());
if (BO->isAssignmentOp())
handleAssignment(BO, BO->getLHS(), BO->getRHS());
// TODO: Handle assignments involving dereference like `*p = q`.
}
void FactsGenerator::VisitConditionalOperator(const ConditionalOperator *CO) {
if (!hasOrigins(CO))
return;
const Expr *TrueExpr = CO->getTrueExpr();
const Expr *FalseExpr = CO->getFalseExpr();
const auto Preds = CurrentBlock->preds();
// Skip origin flow from conditional operator arms that cannot produce the
// result value: throw arms and calls to noreturn functions.
bool TBHasEdge = true;
bool FBHasEdge = true;
switch (CurrentBlock->pred_size()) {
case 0:
return;
case 1: {
TBHasEdge = llvm::any_of(**Preds.begin(),
[ExpectedStmt = TrueExpr->IgnoreParenImpCasts()](
const CFGElement &Elt) {
if (auto CS = Elt.getAs<CFGStmt>())
return CS->getStmt() == ExpectedStmt;
return false;
});
FBHasEdge = !TBHasEdge;
break;
}
case 2: {
const auto *It = Preds.begin();
TBHasEdge = It->isReachable();
FBHasEdge = (++It)->isReachable();
break;
}
default:
llvm_unreachable("expected at most 2 predecessors");
return;
}
bool FirstFlow = true;
auto HandleFlow = [&](const Expr *E) {
if (FirstFlow) {
killAndFlowOrigin(*CO, *E);
FirstFlow = false;
} else {
flowOrigin(*CO, *E);
}
};
if (TBHasEdge)
HandleFlow(TrueExpr);
if (FBHasEdge)
HandleFlow(FalseExpr);
}
void FactsGenerator::VisitCXXOperatorCallExpr(const CXXOperatorCallExpr *OCE) {
// Assignment operators have special "kill-then-propagate" semantics
// and are handled separately.
if (OCE->getOperator() == OO_Equal && OCE->getNumArgs() == 2 &&
hasOrigins(OCE->getArg(0)->getType())) {
// Pointer-like types: assignment inherently propagates origins.
QualType LHSTy = OCE->getArg(0)->getType();
if (LHSTy->isPointerOrReferenceType() || isGslPointerType(LHSTy) ||
isGslOwnerType(LHSTy)) {
handleAssignment(OCE, OCE->getArg(0), OCE->getArg(1));
return;
}
// Standard library callable wrappers (e.g., std::function) can propagate
// the stored lambda's origins.
if (const auto *RD = LHSTy->getAsCXXRecordDecl();
RD && isStdCallableWrapperType(RD)) {
handleAssignment(OCE, OCE->getArg(0), OCE->getArg(1));
return;
}
// Other tracked types: only defaulted operator= propagates origins.
// User-defined operator= has opaque semantics, so don't handle them now.
if (const auto *MD =
dyn_cast_or_null<CXXMethodDecl>(OCE->getDirectCallee());
MD && MD->isDefaulted()) {
handleAssignment(OCE, OCE->getArg(0), OCE->getArg(1));
return;
}
}
ArrayRef Args = {OCE->getArgs(), OCE->getNumArgs()};
// For `static operator()`, the first argument is the object argument,
// remove it from the argument list to avoid off-by-one errors.
if (OCE->getOperator() == OO_Call && OCE->getDirectCallee()->isStatic())
Args = Args.slice(1);
handleFunctionCall(OCE, OCE->getDirectCallee(), Args);
}
void FactsGenerator::VisitCXXFunctionalCastExpr(
const CXXFunctionalCastExpr *FCE) {
// Check if this is a test point marker. If so, we are done with this
// expression.
if (handleTestPoint(FCE))
return;
VisitCastExpr(FCE);
}
void FactsGenerator::VisitInitListExpr(const InitListExpr *ILE) {
if (!hasOrigins(ILE))
return;
// For list initialization with a single element, like `View{...}`, the
// origin of the list itself is the origin of its single element.
if (ILE->getNumInits() == 1)
killAndFlowOrigin(*ILE, *ILE->getInit(0));
}
void FactsGenerator::VisitCXXBindTemporaryExpr(
const CXXBindTemporaryExpr *BTE) {
killAndFlowOrigin(*BTE, *BTE->getSubExpr());
}
void FactsGenerator::VisitMaterializeTemporaryExpr(
const MaterializeTemporaryExpr *MTE) {
assert(MTE->isGLValue());
OriginList *MTEList = getOriginsList(*MTE);
if (!MTEList)
return;
OriginList *SubExprList = getOriginsList(*MTE->getSubExpr());
assert((!SubExprList ||
MTEList->getLength() == (SubExprList->getLength() + 1)) &&
"MTE top level origin should contain a loan to the MTE itself");
OriginList *RValMTEList = getRValueOrigins(MTE, MTEList);
flow(RValMTEList, SubExprList, /*Kill=*/true);
OriginID OuterMTEID = MTEList->getOuterOriginID();
if (MTE->getStorageDuration() == SD_FullExpression) {
// Issue a loan to MTE for the storage location represented by MTE.
const Loan *L = createLoan(FactMgr, MTE);
CurrentBlockFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), OuterMTEID));
}
}
void FactsGenerator::VisitLambdaExpr(const LambdaExpr *LE) {
// The lambda gets a single merged origin that aggregates all captured
// pointer-like origins. Currently we only need to detect whether the lambda
// outlives any capture.
OriginList *LambdaList = getOriginsList(*LE);
if (!LambdaList)
return;
bool Kill = true;
for (const Expr *Init : LE->capture_inits()) {
if (!Init)
continue;
OriginList *InitList = getOriginsList(*Init);
if (!InitList)
continue;
// FIXME: Consider flowing all origin levels once lambdas support more than
// one origin. Currently only the outermost origin is flowed, so by-ref
// captures like `[&p]` (where p is string_view) miss inner-level
// invalidation.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
LambdaList->getOuterOriginID(), InitList->getOuterOriginID(), Kill));
Kill = false;
}
}
void FactsGenerator::VisitArraySubscriptExpr(const ArraySubscriptExpr *ASE) {
assert(ASE->isGLValue() && "Array subscript should be a GL value");
OriginList *Dst = getOriginsList(*ASE);
assert(Dst && "Array subscript should have origins as it is a GL value");
OriginList *Src = getOriginsList(*ASE->getBase());
assert(Src && "Base of array subscript should have origins");
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
Dst->getOuterOriginID(), Src->getOuterOriginID(), /*Kill=*/true));
}
void FactsGenerator::handlePlacementNew(const CXXNewExpr *NE,
OriginList *NewList) {
// Model only the standard single-argument placement new form, where the
// placement argument corresponds to a void* allocation-function parameter.
// Other placement forms, such as std::nothrow, are not modeled as providing
// storage for the returned pointer.
if (NE->getNumPlacementArgs() != 1)
return;
const FunctionDecl *OperatorNew = NE->getOperatorNew();
if (OperatorNew->getNumParams() <= 1)
return;
const auto *Arg =
OperatorNew->getParamDecl(1)->getType()->getAs<PointerType>();
if (!Arg || !Arg->isVoidPointerType())
return;
// Use the placement argument before the implicit conversion to void*, so
// inner origins are still available.
const Expr *PlacementArg = NE->getPlacementArg(0);
if (const auto *ICE = dyn_cast<ImplicitCastExpr>(PlacementArg);
ICE && ICE->getCastKind() == CK_BitCast &&
PlacementArg->getType()->isVoidPointerType())
PlacementArg = ICE->getSubExpr();
OriginList *PlacementList = getOriginsList(*PlacementArg);
// FIXME: General placement arguments need separate handling to overwrite
// the right origins.
// The pointer returned by placement new comes from the placement
// argument.
if (PlacementList)
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
NewList->getOuterOriginID(), PlacementList->getOuterOriginID(), true));
}
void FactsGenerator::VisitCXXNewExpr(const CXXNewExpr *NE) {
OriginList *NewList = getOriginsList(*NE);
const Expr *Init = NE->getInitializer();
if (NE->getNumPlacementArgs() == 1) {
handlePlacementNew(NE, NewList);
} else {
const Loan *L = createLoan(FactMgr, NE);
CurrentBlockFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), NewList->getOuterOriginID()));
}
NewList = NewList->peelOuterOrigin();
if (!NewList || !Init)
return;
// FIXME: OriginList is null for `new[]` initializers. Remove this `Init`
// check once array origins are supported.
if (OriginList *InitList = getOriginsList(*Init); InitList)
flow(NewList, InitList, true);
}
void FactsGenerator::VisitCXXDeleteExpr(const CXXDeleteExpr *DE) {
OriginList *List = getOriginsList(*DE->getArgument());
CurrentBlockFacts.push_back(
FactMgr.createFact<InvalidateOriginFact>(List->getOuterOriginID(), DE));
}
bool FactsGenerator::escapesViaReturn(OriginID OID) const {
return llvm::any_of(EscapesInCurrentBlock, [OID](const Fact *F) {
if (const auto *EF = F->getAs<ReturnEscapeFact>())
return EF->getEscapedOriginID() == OID;
return false;
});
}
void FactsGenerator::handleLifetimeEnds(const CFGLifetimeEnds &LifetimeEnds) {
const VarDecl *LifetimeEndsVD = LifetimeEnds.getVarDecl();
if (!LifetimeEndsVD)
return;
// Expire the origin when its variable's lifetime ends to ensure liveness
// doesn't persist through loop back-edges.
std::optional<OriginID> ExpiredOID;
if (OriginList *List = getOriginsList(*LifetimeEndsVD)) {
OriginID OID = List->getOuterOriginID();
// Skip origins that escape via return; the escape checker needs their loans
// to remain until the return statement is processed.
if (!escapesViaReturn(OID))
ExpiredOID = OID;
}
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
AccessPath(LifetimeEndsVD), LifetimeEnds.getTriggerStmt()->getEndLoc(),
ExpiredOID));
}
void FactsGenerator::handleFullExprCleanup(
const CFGFullExprCleanup &FullExprCleanup) {
for (const auto *MTE : FullExprCleanup.getExpiringMTEs())
CurrentBlockFacts.push_back(FactMgr.createFact<ExpireFact>(
AccessPath(MTE), FullExprCleanup.getCleanupLoc()));
}
void FactsGenerator::handleExitBlock() {
for (const Origin &O : FactMgr.getOriginMgr().getOrigins())
if (auto *FD = dyn_cast_if_present<FieldDecl>(O.getDecl()))
// Create FieldEscapeFacts for all field origins that remain live at exit.
EscapesInCurrentBlock.push_back(
FactMgr.createFact<FieldEscapeFact>(O.ID, FD));
else if (auto *VD = dyn_cast_if_present<VarDecl>(O.getDecl())) {
// Create GlobalEscapeFacts for all origins with global-storage that
// remain live at exit.
if (VD->hasGlobalStorage()) {
EscapesInCurrentBlock.push_back(
FactMgr.createFact<GlobalEscapeFact>(O.ID, VD));
}
}
}
void FactsGenerator::handleGSLPointerConstruction(const CXXConstructExpr *CCE) {
assert(isGslPointerType(CCE->getType()));
if (CCE->getNumArgs() != 1)
return;
const Expr *Arg = CCE->getArg(0);
if (isGslPointerType(Arg->getType())) {
OriginList *ArgList = getOriginsList(*Arg);
assert(ArgList && "GSL pointer argument should have an origin list");
// GSL pointer is constructed from another gsl pointer.
// Example:
// View(View v);
// View(const View &v);
ArgList = getRValueOrigins(Arg, ArgList);
flow(getOriginsList(*CCE), ArgList, /*Kill=*/true);
} else if (Arg->getType()->isPointerType()) {
// GSL pointer is constructed from a raw pointer. Flow only the outermost
// raw pointer. Example:
// View(const char*);
// Span<int*>(const in**);
OriginList *ArgList = getOriginsList(*Arg);
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
getOriginsList(*CCE)->getOuterOriginID(), ArgList->getOuterOriginID(),
/*Kill=*/true));
} else {
// This could be a new borrow.
// TODO: Add code example here.
handleFunctionCall(CCE, CCE->getConstructor(),
{CCE->getArgs(), CCE->getNumArgs()},
/*IsGslConstruction=*/true);
}
}
void FactsGenerator::handleMovedArgsInCall(const FunctionDecl *FD,
ArrayRef<const Expr *> Args) {
unsigned IsInstance = 0;
if (const auto *MD = dyn_cast<CXXMethodDecl>(FD);
MD && MD->isInstance() && !isa<CXXConstructorDecl>(FD)) {
IsInstance = 1;
// std::unique_ptr::release() transfers ownership.
// Treat it as a move to prevent false-positive warnings when the unique_ptr
// destructor runs after ownership has been transferred.
if (isUniquePtrRelease(*MD)) {
const Expr *UniquePtrExpr = Args[0];
OriginList *MovedOrigins = getOriginsList(*UniquePtrExpr);
if (MovedOrigins)
CurrentBlockFacts.push_back(FactMgr.createFact<MovedOriginFact>(
UniquePtrExpr, MovedOrigins->getOuterOriginID()));
}
}
// Skip 'this' arg as it cannot be moved.
for (unsigned I = IsInstance;
I < Args.size() && I < FD->getNumParams() + IsInstance; ++I) {
const ParmVarDecl *PVD = FD->getParamDecl(I - IsInstance);
if (!PVD->getType()->isRValueReferenceType())
continue;
const Expr *Arg = Args[I];
OriginList *MovedOrigins = getOriginsList(*Arg);
assert(MovedOrigins->getLength() >= 1 &&
"unexpected length for r-value reference param");
// Arg is being moved to this parameter. Mark the origin as moved.
CurrentBlockFacts.push_back(FactMgr.createFact<MovedOriginFact>(
Arg, MovedOrigins->getOuterOriginID()));
}
}
void FactsGenerator::handleInvalidatingCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args) {
const auto *MD = dyn_cast<CXXMethodDecl>(FD);
if (!MD || !MD->isInstance())
return;
if (!isInvalidationMethod(*MD))
return;
// Heuristics to turn-down false positives. Skip member field expressions for
// now. This is not a perfect filter and will still surface some false
// positives (e.g. `auto& r = s.v`).
if (!isa<DeclRefExpr>(Args[0]->IgnoreImpCasts()))
return;
OriginList *ThisList = getOriginsList(*Args[0]);
if (ThisList)
CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
ThisList->getOuterOriginID(), Call));
}
void FactsGenerator::handleDestructiveCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args) {
if (!destructsFirstArg(*FD))
return;
OriginList *ArgList = getOriginsList(*Args[0]);
if (ArgList)
CurrentBlockFacts.push_back(FactMgr.createFact<InvalidateOriginFact>(
ArgList->getOuterOriginID(), Call));
}
void FactsGenerator::handleImplicitObjectFieldUses(const Expr *Call,
const FunctionDecl *FD) {
const auto *MemberCall = dyn_cast_or_null<CXXMemberCallExpr>(Call);
if (!MemberCall)
return;
if (!isa_and_present<CXXThisExpr>(
MemberCall->getImplicitObjectArgument()->IgnoreImpCasts()))
return;
const auto *MD = dyn_cast<CXXMethodDecl>(FD);
assert(MD && "Function must be a CXXMethodDecl for member calls");
const auto *ClassDecl = MD->getParent()->getDefinition();
if (!ClassDecl)
return;
const auto UseFields = [&](const CXXRecordDecl *RD) {
for (const auto *Field : RD->fields())
if (auto *FieldList = getOriginsList(*Field))
CurrentBlockFacts.push_back(
FactMgr.createFact<UseFact>(Call, FieldList));
};
UseFields(ClassDecl);
ClassDecl->forallBases([&](const CXXRecordDecl *Base) {
UseFields(Base);
return true;
});
}
void FactsGenerator::handleLifetimeCaptureBy(const FunctionDecl *FD,
ArrayRef<const Expr *> Args) {
if (Args.empty())
return;
// FIXME: Add support for capture_by on constructors.
if (isa<CXXConstructorDecl>(FD))
return;
const auto *Method = dyn_cast<CXXMethodDecl>(FD);
bool IsInstance =
Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD);
auto getArgCaptureBy = [FD,
IsInstance](unsigned I) -> LifetimeCaptureByAttr * {
const ParmVarDecl *PVD = nullptr;
if (IsInstance) {
// FIXME: Add support for I == 0 i.e. capture_by on function declarations
if (I > 0 && I - 1 < FD->getNumParams())
PVD = FD->getParamDecl(I - 1);
} else {
if (I < FD->getNumParams())
PVD = FD->getParamDecl(I);
}
return PVD ? PVD->getAttr<LifetimeCaptureByAttr>() : nullptr;
};
for (unsigned I = 0; I < Args.size(); ++I) {
const LifetimeCaptureByAttr *Attr = getArgCaptureBy(I);
if (!Attr)
continue;
OriginList *CapturedOriginList = getOriginsList(*Args[I]);
if (!CapturedOriginList)
continue;
if (!CapturedOriginList)
continue;
for (int CapturingArgIdx : Attr->params()) {
// FIXME: Add support for capturing to Global/unknown.
if (CapturingArgIdx == LifetimeCaptureByAttr::Global ||
CapturingArgIdx == LifetimeCaptureByAttr::Unknown ||
CapturingArgIdx == LifetimeCaptureByAttr::Invalid)
continue;
ArrayRef<const Expr *> CallArgs = IsInstance ? Args.drop_front() : Args;
const Expr *CapturedByArg =
(CapturingArgIdx == LifetimeCaptureByAttr::This)
? Args[0]
: CallArgs[CapturingArgIdx];
assert(CapturedByArg && "Capturer expression must be valid");
OriginList *CapturingOriginList = getOriginsList(*CapturedByArg);
OriginList *Dest = getRValueOrigins(CapturedByArg, CapturingOriginList);
if (!Dest)
continue;
// KillDest=false because we cannot know if previous captures are being
// replaced or accumulated. Multiple successive captures into the same
// destination must all be tracked, so captured lifetimes are always
// merged.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
Dest->getOuterOriginID(), CapturedOriginList->getOuterOriginID(),
/*KillDest=*/false));
}
}
}
void FactsGenerator::handleFunctionCall(const Expr *Call,
const FunctionDecl *FD,
ArrayRef<const Expr *> Args,
bool IsGslConstruction) {
OriginList *CallList = getOriginsList(*Call);
// Ignore functions returning values with no origin.
FD = getDeclWithMergedLifetimeBoundAttrs(FD);
if (!FD)
return;
// All arguments to a function are a use of the corresponding expressions.
for (const Expr *Arg : Args)
handleUse(Arg);
handleInvalidatingCall(Call, FD, Args);
handleDestructiveCall(Call, FD, Args);
handleMovedArgsInCall(FD, Args);
handleImplicitObjectFieldUses(Call, FD);
handleLifetimeCaptureBy(FD, Args);
if (!CallList)
return;
if (isStdReferenceCast(FD)) {
assert(Args.size() == 1 &&
"std reference cast builtins take exactly one argument");
// std reference-cast functions like std::move return a result that refers
// to the same object as the argument, so propagate the full origins.
flow(CallList, getOriginsList(*Args[0]), /*Kill=*/true);
return;
}
auto IsArgLifetimeBound = [FD, &Args](unsigned I) -> bool {
const ParmVarDecl *PVD = nullptr;
if (const auto *Method = dyn_cast<CXXMethodDecl>(FD);
Method && Method->isInstance() && !isa<CXXConstructorDecl>(FD)) {
if (I == 0)
// For the 'this' argument, the attribute is on the method itself.
return implicitObjectParamIsLifetimeBound(Method) ||
shouldTrackImplicitObjectArg(
*Args[0], Method, /*RunningUnderLifetimeSafety=*/true);
if ((I - 1) < Method->getNumParams())
// For explicit arguments, find the corresponding parameter
// declaration.
PVD = Method->getParamDecl(I - 1);
} else if (I == 0 && shouldTrackFirstArgument(FD)) {
return true;
} else if (I == 1 && shouldTrackSecondArgument(FD)) {
return true;
} else if (I < FD->getNumParams()) {
// For free functions or static methods.
PVD = FD->getParamDecl(I);
}
return PVD ? PVD->hasAttr<clang::LifetimeBoundAttr>() : false;
};
auto shouldTrackPointerImplicitObjectArg = [FD, &Args](unsigned I) -> bool {
const auto *Method = dyn_cast<CXXMethodDecl>(FD);
if (!Method || !Method->isInstance())
return false;
return I == 0 &&
isGslPointerType(Method->getFunctionObjectParameterType()) &&
shouldTrackImplicitObjectArg(*Args[0], Method,
/*RunningUnderLifetimeSafety=*/true);
};
if (Args.empty())
return;
bool KillSrc = true;
for (unsigned I = 0; I < Args.size(); ++I) {
OriginList *ArgList = getOriginsList(*Args[I]);
if (!ArgList)
continue;
if (IsGslConstruction) {
// TODO: document with code example.
// std::string_view(const std::string_view& from)
if (isGslPointerType(Args[I]->getType())) {
assert(!Args[I]->isGLValue() || ArgList->getLength() >= 2);
ArgList = getRValueOrigins(Args[I], ArgList);
}
if (isGslOwnerType(Args[I]->getType())) {
// The constructed gsl::Pointer borrows from the Owner's storage, not
// from what the Owner itself borrows, so only the outermost origin is
// needed.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
KillSrc));
KillSrc = false;
} else if (IsArgLifetimeBound(I)) {
// Only flow the outer origin here. For lifetimebound args in
// gsl::Pointer construction, we do not have enough information to
// safely match inner origins, so the source and
// destination origin lists may have different lengths.
// FIXME: Handle origin-shape mismatches gracefully so we can also flow
// inner origins.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(), ArgList->getOuterOriginID(),
KillSrc));
KillSrc = false;
}
} else if (shouldTrackPointerImplicitObjectArg(I)) {
assert(ArgList->getLength() >= 2 &&
"Object arg of pointer type should have at least two origins");
// See through the GSLPointer reference to see the pointer's value.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(),
ArgList->peelOuterOrigin()->getOuterOriginID(), KillSrc));
KillSrc = false;
} else if (IsArgLifetimeBound(I)) {
// Lifetimebound on a non-GSL-ctor function means the returned
// pointer/reference itself must not outlive the arguments. This
// only constrains the top-level origin.
CurrentBlockFacts.push_back(FactMgr.createFact<OriginFlowFact>(
CallList->getOuterOriginID(), ArgList->getOuterOriginID(), KillSrc));
KillSrc = false;
}
}
}
/// Checks if the expression is a `void("__lifetime_test_point_...")` cast.
/// If so, creates a `TestPointFact` and returns true.
bool FactsGenerator::handleTestPoint(const CXXFunctionalCastExpr *FCE) {
if (!FCE->getType()->isVoidType())
return false;
const auto *SubExpr = FCE->getSubExpr()->IgnoreParenImpCasts();
if (const auto *SL = dyn_cast<StringLiteral>(SubExpr)) {
llvm::StringRef LiteralValue = SL->getString();
const std::string Prefix = "__lifetime_test_point_";
if (LiteralValue.starts_with(Prefix)) {
StringRef Annotation = LiteralValue.drop_front(Prefix.length());
CurrentBlockFacts.push_back(
FactMgr.createFact<TestPointFact>(Annotation));
return true;
}
}
return false;
}
void FactsGenerator::handleUse(const Expr *E) {
OriginList *List = getOriginsList(*E);
if (!List)
return;
// For DeclRefExpr: Remove the outer layer of origin which borrows from the
// decl directly (e.g., when this is not a reference). This is a use of the
// underlying decl.
if (auto *DRE = dyn_cast<DeclRefExpr>(E);
DRE && !DRE->getDecl()->getType()->isReferenceType())
List = getRValueOrigins(DRE, List);
// Skip if there is no inner origin (e.g., when it is not a pointer type).
if (!List)
return;
if (!UseFacts.contains(E)) {
UseFact *UF = FactMgr.createFact<UseFact>(E, List);
CurrentBlockFacts.push_back(UF);
UseFacts[E] = UF;
}
}
void FactsGenerator::markUseAsWrite(const DeclRefExpr *DRE) {
if (UseFacts.contains(DRE))
UseFacts[DRE]->markAsWritten();
}
// Creates an IssueFact for a new placeholder loan for each pointer or reference
// parameter at the function's entry.
llvm::SmallVector<Fact *> FactsGenerator::issuePlaceholderLoans() {
const auto *FD = dyn_cast<FunctionDecl>(AC.getDecl());
if (!FD)
return {};
llvm::SmallVector<Fact *> PlaceholderLoanFacts;
if (auto ThisOrigins = FactMgr.getOriginMgr().getThisOrigins()) {
OriginList *List = *ThisOrigins;
const Loan *L = FactMgr.getLoanMgr().createLoan(
AccessPath::Placeholder(cast<CXXMethodDecl>(FD)),
/*IssuingExpr=*/nullptr);
PlaceholderLoanFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}
for (const ParmVarDecl *PVD : FD->parameters()) {
OriginList *List = getOriginsList(*PVD);
if (!List)
continue;
const Loan *L = FactMgr.getLoanMgr().createLoan(
AccessPath::Placeholder(PVD), /*IssuingExpr=*/nullptr);
PlaceholderLoanFacts.push_back(
FactMgr.createFact<IssueFact>(L->getID(), List->getOuterOriginID()));
}
return PlaceholderLoanFacts;
}
} // namespace clang::lifetimes::internal