blob: 4b6d76a09575ac23d4ca560c442eabadb5a6d64b [file] [edit]
#include "LifetimeModeling.h"
#include "clang/AST/Attr.h"
#include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h"
#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
#include "clang/StaticAnalyzer/Core/Checker.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
#include "llvm/Support/raw_ostream.h"
using namespace clang;
using namespace ento;
REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *)
REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet)
REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *)
namespace {
class LifetimeModeling
: public Checker<check::PostCall, check::DeadSymbols,
check::PreStmt<DeclStmt>, check::LifetimeEnd> {
public:
void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
const char *Sep) const override;
void checkPostCall(const CallEvent &Call, CheckerContext &C) const;
void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const;
void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const;
};
} // namespace
static bool isDanglingStackSource(const MemRegion *Source,
ProgramStateRef State, CheckerContext &C) {
// FIXME: The checker currently handles stack-region sources. Other
// region kinds require separate methodology. For example, heap
// regions do not go out of scope at the end of a stack frame, so
// in order to detect those type of dangling sources the function
// needs to be expanded to an event-driven approach as well.
if (const auto *StackSpace =
Source->getMemorySpaceAs<StackSpaceRegion>(State)) {
const StackFrame *SF = StackSpace->getStackFrame();
const StackFrame *CurrentSF = C.getStackFrame();
// If any frame on the current stack belongs to a destructor
// the warning should be suppressed. When a lifetimebound method
// is called from a destructor then its return value is not expected
// to outlive the object being destroyed.
if (llvm::any_of(C.stackframes(), [&](const StackFrame &Frame) {
return isa<CXXDestructorDecl>(Frame.getDecl());
})) {
return false;
}
if (SF == CurrentSF || !SF->isParentOf(CurrentSF))
return true;
}
return false;
}
std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn(
SVal Val, ProgramStateRef State, CheckerContext &C) {
std::vector<const MemRegion *> Regions;
if (auto *SourceSet = State->get<LifetimeBoundMap>(Val)) {
for (const MemRegion *Region : *SourceSet) {
if (isDanglingStackSource(Region, State, C))
Regions.push_back(Region);
}
}
return Regions;
}
bool lifetime_modeling::isDeallocated(ProgramStateRef State,
const MemRegion *Region) {
return State->contains<DeallocatedSourceSet>(Region->getBaseRegion());
}
static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal,
const MemRegion *Source) {
LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>();
const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(RetVal);
LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet();
Set = F.add(Set, Source);
State = State->set<LifetimeBoundMap>(RetVal, Set);
return State;
}
std::string lifetime_modeling::getRegionName(const MemRegion *Reg) {
// FIXME: Once the checker supports heap allocation, more region kinds
// should be handled to produce the correct descriptive name.
if (const std::string RegName = Reg->getDescriptiveName(); !RegName.empty())
return RegName;
return "the region";
}
void LifetimeModeling::checkPostCall(const CallEvent &Call,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
const auto *FC = dyn_cast<AnyFunctionCall>(&Call);
if (!FC)
return;
const FunctionDecl *FD = FC->getDecl();
if (!FD)
return;
SVal RetVal = Call.getReturnValue();
for (const ParmVarDecl *PVD : FD->parameters()) {
if (PVD->hasAttr<LifetimeBoundAttr>()) {
unsigned Idx = PVD->getFunctionScopeIndex();
SVal Arg = Call.getArgSVal(Idx);
if (const MemRegion *ArgValRegion = Arg.getAsRegion())
State = bindSource(State, RetVal, ArgValRegion);
}
}
const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) {
if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion())
State = bindSource(State, RetVal, ThisRegion);
}
C.addTransition(State);
}
void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
SVal SourceVal = State->getLValue(VD, C.getStackFrame());
if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) {
State = State->add<DeallocatedSourceSet>(SourceValRegion);
C.addTransition(State);
}
}
void LifetimeModeling::checkPreStmt(const DeclStmt *DS,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
for (const auto *I : DS->decls()) {
if (const VarDecl *VD = dyn_cast<VarDecl>(I)) {
SVal Val = State->getLValue(VD, C.getStackFrame());
if (const MemRegion *ValRegion = Val.getAsRegion())
State = State->remove<DeallocatedSourceSet>(ValRegion);
}
}
C.addTransition(State);
}
void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper,
CheckerContext &C) const {
ProgramStateRef State = C.getState();
LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>();
DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>();
for (SVal Val : llvm::make_first_range(LBMap)) {
if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(R))
continue;
if (SymbolRef S = Val.getAsSymbol(/*IncludeBaseRegions=*/true);
S && SymReaper.isLive(S))
continue;
State = State->remove<LifetimeBoundMap>(Val);
}
for (const MemRegion *Region : Sources) {
if (!SymReaper.isLiveRegion(Region))
State = State->remove<DeallocatedSourceSet>(Region);
}
C.addTransition(State);
}
void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State,
const char *NL, const char *Sep) const {
auto LBMap = State->get<LifetimeBoundMap>();
if (LBMap.isEmpty())
return;
Out << Sep << "LifetimeBound bindings:" << NL;
for (auto &&[OriginSym, SourceSet] : LBMap) {
for (const auto *Region : SourceSet)
Out << " Origin " << OriginSym << " contains Loan " << Region << NL;
}
}
// FIXME: Eventually move the debug checker to its own source file once
// it has more functionality.
namespace {
class DebugLifetimeModeling : public Checker<eval::Call> {
public:
bool evalCall(const CallEvent &Call, CheckerContext &C) const;
void analyzerDumpLifetimeOriginsOf(const CallEvent &Call,
CheckerContext &C) const;
const BugType BugMsg{this, "DebugLifetimeModeling", "DebugLifetimeModeling"};
using FnCheck = void (DebugLifetimeModeling::*)(const CallEvent &Call,
CheckerContext &C) const;
const CallDescriptionMap<FnCheck> Callbacks = {
{{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf"}},
&DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf},
};
};
} // namespace
bool DebugLifetimeModeling::evalCall(const CallEvent &Call,
CheckerContext &C) const {
if (!isa_and_nonnull<CallExpr>(Call.getOriginExpr()))
return false;
const FnCheck *Handler = Callbacks.lookup(Call);
if (!Handler)
return false;
(this->*(*Handler))(Call, C);
return true;
}
void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf(
const CallEvent &Call, CheckerContext &C) const {
ProgramStateRef State = C.getState();
if (Call.getNumArgs() != 1) {
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
auto BR = std::make_unique<PathSensitiveBugReport>(
BugMsg,
"clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument",
N);
C.emitReport(std::move(BR));
}
return;
}
SVal ArgSVal = Call.getArgSVal(0);
const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(ArgSVal);
if (!SourceSet)
return;
if (ExplodedNode *N = C.generateNonFatalErrorNode()) {
llvm::SmallVector<std::string> RegionNames =
to_vector(map_range(llvm::make_pointee_range(*SourceSet),
std::mem_fn(&MemRegion::getString)));
llvm::sort(RegionNames);
llvm::SmallString<128> Str;
llvm::raw_svector_ostream OS(Str);
OS << " Origin '" << ArgSVal << "' bound to ";
llvm::interleaveComma(RegionNames, OS,
[&](StringRef Name) { OS << "'" << Name << "'"; });
C.emitReport(std::make_unique<PathSensitiveBugReport>(BugMsg, OS.str(), N));
}
}
void ento::registerLifetimeModeling(CheckerManager &Mgr) {
Mgr.registerChecker<LifetimeModeling>();
}
bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) {
return true;
}
void ento::registerDebugLifetimeModeling(CheckerManager &Mgr) {
Mgr.registerChecker<DebugLifetimeModeling>();
}
bool ento::shouldRegisterDebugLifetimeModeling(const CheckerManager &Mgr) {
return true;
}