| #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 *) | |
| REGISTER_SET_WITH_PROGRAMSTATE(ReportedDeadRegions, 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; | |
| } | |
| // Only a source whose frame is still live on the current stack can | |
| // dangle. If that frame is not on the stack then the source outlives | |
| // the returned value. The source is still alive when the returned value | |
| // is used, so it does not dangle. | |
| if (is_contained(make_pointer_range(C.stackframes()), SF)) { | |
| 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::isBoundToLifetimeSource(ProgramStateRef State, | |
| SVal Val) { | |
| return State->get<LifetimeBoundMap>(Val) != nullptr; | |
| } | |
| bool lifetime_modeling::isDeallocated(ProgramStateRef State, | |
| const MemRegion *Region) { | |
| return State->contains<DeallocatedSourceSet>(Region->getBaseRegion()); | |
| } | |
| ProgramStateRef lifetime_modeling::markAsReported(ProgramStateRef State, | |
| const MemRegion *Region) { | |
| ProgramStateRef NewState = | |
| State->add<ReportedDeadRegions>(Region->getBaseRegion()); | |
| return (NewState != State) ? NewState : nullptr; | |
| } | |
| 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>(); | |
| ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>(); | |
| 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); | |
| } | |
| for (const MemRegion *Region : Reported) { | |
| if (!SymReaper.isLiveRegion(Region)) | |
| State = State->remove<ReportedDeadRegions>(Region); | |
| } | |
| C.addTransition(State); | |
| } | |
| void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State, | |
| const char *NL, const char *Sep) const { | |
| auto LBMap = State->get<LifetimeBoundMap>(); | |
| ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>(); | |
| if (!LBMap.isEmpty()) { | |
| Out << Sep << "LifetimeBound bindings:" << NL; | |
| for (auto &&[OriginSym, SourceSet] : LBMap) { | |
| for (const auto *Region : SourceSet) | |
| Out << " Origin " << OriginSym << " contains Loan " << Region << NL; | |
| } | |
| } | |
| if (!Reported.isEmpty()) { | |
| Out << Sep << "Reported regions: " << NL; | |
| for (const auto *Region : Reported) { | |
| Out << " " << 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; | |
| } |