Utkarsh Saxena 47e9c1db61
[LifetimeSafety] Detect dangling fields (#177363)
Detect dangling field references when stack memory escapes to class
fields. This change extends lifetime safety analysis to detect a common
class of temporal memory safety bugs where local variables or parameters
are stored in class fields but outlive their scope.

- Added a new `FieldEscapeFact` class to represent when an origin
escapes via assignment to a field
- Refactored `OriginEscapesFact` into a base class with specialized
subclasses for different escape scenarios
- Added detection for stack memory escaping to fields in constructors
and member functions
- Implemented new diagnostic for dangling field references with
appropriate warning messages

Importantly,
- Added `AddParameterDtors` option to CFG to add parameter dtors and
lifetime ends behind an option. In principle, parameters ctors and dtors
do not belong in the function context but in the caller context. This
becomes incorrect to include in function's CFG when we have inlined CFGs
like some analyses in the analyzer (produces double dtors for
arguments). Therefore this provides a way to opt-in to know about
destructed params on function exits.
2026-01-28 13:42:32 +00:00

190 lines
6.5 KiB
C++

//===- AdornedCFG.cpp ---------------------------------------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// This file defines an `AdornedCFG` class that is used by dataflow analyses
// that run over Control-Flow Graphs (CFGs).
//
//===----------------------------------------------------------------------===//
#include "clang/Analysis/FlowSensitive/AdornedCFG.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/Decl.h"
#include "clang/AST/Stmt.h"
#include "clang/Analysis/CFG.h"
#include "llvm/ADT/BitVector.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/Support/Error.h"
#include <utility>
namespace clang {
namespace dataflow {
/// Returns a map from statements to basic blocks that contain them.
static llvm::DenseMap<const Stmt *, const CFGBlock *>
buildStmtToBasicBlockMap(const CFG &Cfg) {
llvm::DenseMap<const Stmt *, const CFGBlock *> StmtToBlock;
for (const CFGBlock *Block : Cfg) {
if (Block == nullptr)
continue;
for (const CFGElement &Element : *Block) {
auto Stmt = Element.getAs<CFGStmt>();
if (!Stmt)
continue;
StmtToBlock[Stmt->getStmt()] = Block;
}
}
// Some terminator conditions don't appear as a `CFGElement` anywhere else -
// for example, this is true if the terminator condition is a `&&` or `||`
// operator.
// We associate these conditions with the block the terminator appears in,
// but only if the condition has not already appeared as a regular
// `CFGElement`. (The `insert()` below does nothing if the key already exists
// in the map.)
for (const CFGBlock *Block : Cfg) {
if (Block != nullptr)
if (const Stmt *TerminatorCond = Block->getTerminatorCondition())
StmtToBlock.insert({TerminatorCond, Block});
}
// Terminator statements typically don't appear as a `CFGElement` anywhere
// else, so we want to associate them with the block that they terminate.
// However, there are some important special cases:
// - The conditional operator is a type of terminator, but it also appears
// as a regular `CFGElement`, and we want to associate it with the block
// in which it appears as a `CFGElement`.
// - The `&&` and `||` operators are types of terminators, but like the
// conditional operator, they can appear as a regular `CFGElement` or
// as a terminator condition (see above).
// We process terminators last to make sure that we only associate them with
// the block they terminate if they haven't previously occurred as a regular
// `CFGElement` or as a terminator condition.
for (const CFGBlock *Block : Cfg) {
if (Block != nullptr)
if (const Stmt *TerminatorStmt = Block->getTerminatorStmt())
StmtToBlock.insert({TerminatorStmt, Block});
}
return StmtToBlock;
}
static llvm::BitVector findReachableBlocks(const CFG &Cfg) {
llvm::BitVector BlockReachable(Cfg.getNumBlockIDs(), false);
llvm::SmallVector<const CFGBlock *> BlocksToVisit;
BlocksToVisit.push_back(&Cfg.getEntry());
while (!BlocksToVisit.empty()) {
const CFGBlock *Block = BlocksToVisit.back();
BlocksToVisit.pop_back();
if (BlockReachable[Block->getBlockID()])
continue;
BlockReachable[Block->getBlockID()] = true;
for (const CFGBlock *Succ : Block->succs())
if (Succ)
BlocksToVisit.push_back(Succ);
}
return BlockReachable;
}
static llvm::DenseSet<const CFGBlock *>
buildContainsExprConsumedInDifferentBlock(
const CFG &Cfg, const internal::StmtToBlockMap &StmtToBlock) {
llvm::DenseSet<const CFGBlock *> Result;
auto CheckChildExprs = [&Result, &StmtToBlock](const Stmt *S,
const CFGBlock *Block) {
for (const Stmt *Child : S->children()) {
if (!isa_and_nonnull<Expr>(Child))
continue;
const CFGBlock *ChildBlock = StmtToBlock.lookup(*Child);
if (ChildBlock != Block)
Result.insert(ChildBlock);
}
};
for (const CFGBlock *Block : Cfg) {
if (Block == nullptr)
continue;
for (const CFGElement &Element : *Block)
if (auto S = Element.getAs<CFGStmt>())
CheckChildExprs(S->getStmt(), Block);
if (const Stmt *TerminatorCond = Block->getTerminatorCondition())
CheckChildExprs(TerminatorCond, Block);
}
return Result;
}
namespace internal {
StmtToBlockMap::StmtToBlockMap(const CFG &Cfg)
: StmtToBlock(buildStmtToBasicBlockMap(Cfg)) {}
} // namespace internal
llvm::Expected<AdornedCFG> AdornedCFG::build(const FunctionDecl &Func) {
if (!Func.doesThisDeclarationHaveABody())
return llvm::createStringError(
std::make_error_code(std::errc::invalid_argument),
"Cannot analyze function without a body");
return build(Func, *Func.getBody(), Func.getASTContext());
}
llvm::Expected<AdornedCFG> AdornedCFG::build(const Decl &D, Stmt &S,
ASTContext &C) {
if (D.isTemplated())
return llvm::createStringError(
std::make_error_code(std::errc::invalid_argument),
"Cannot analyze templated declarations");
// The shape of certain elements of the AST can vary depending on the
// language. We currently only support C++.
if (!C.getLangOpts().CPlusPlus || C.getLangOpts().ObjC)
return llvm::createStringError(
std::make_error_code(std::errc::invalid_argument),
"Can only analyze C++");
CFG::BuildOptions Options;
Options.PruneTriviallyFalseEdges = true;
Options.AddImplicitDtors = true;
Options.AddTemporaryDtors = true;
Options.AddInitializers = true;
Options.AddCXXDefaultInitExprInCtors = true;
Options.AddLifetime = true;
Options.AddParameterLifetimes = true;
// Ensure that all sub-expressions in basic blocks are evaluated.
Options.setAllAlwaysAdd();
auto Cfg = CFG::buildCFG(&D, &S, &C, Options);
if (Cfg == nullptr)
return llvm::createStringError(
std::make_error_code(std::errc::invalid_argument),
"CFG::buildCFG failed");
internal::StmtToBlockMap StmtToBlock(*Cfg);
llvm::BitVector BlockReachable = findReachableBlocks(*Cfg);
llvm::DenseSet<const CFGBlock *> ContainsExprConsumedInDifferentBlock =
buildContainsExprConsumedInDifferentBlock(*Cfg, StmtToBlock);
return AdornedCFG(D, std::move(Cfg), std::move(StmtToBlock),
std::move(BlockReachable),
std::move(ContainsExprConsumedInDifferentBlock));
}
} // namespace dataflow
} // namespace clang