| 1 | #include "LifetimeModeling.h" |
| 2 | #include "clang/AST/Attr.h" |
| 3 | #include "clang/Analysis/Analyses/LifetimeSafety/LifetimeAnnotations.h" |
| 4 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
| 5 | #include "clang/StaticAnalyzer/Core/Checker.h" |
| 6 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h" |
| 7 | #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" |
| 8 | #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" |
| 9 | #include "llvm/Support/raw_ostream.h" |
| 10 | |
| 11 | using namespace clang; |
| 12 | using namespace ento; |
| 13 | |
| 14 | REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(LifetimeSourceSet, const MemRegion *) |
| 15 | REGISTER_MAP_WITH_PROGRAMSTATE(LifetimeBoundMap, SVal, LifetimeSourceSet) |
| 16 | |
| 17 | REGISTER_SET_WITH_PROGRAMSTATE(DeallocatedSourceSet, const MemRegion *) |
| 18 | REGISTER_SET_WITH_PROGRAMSTATE(ReportedDeadRegions, const MemRegion *) |
| 19 | |
| 20 | namespace { |
| 21 | |
| 22 | class LifetimeModeling |
| 23 | : public Checker<check::PostCall, check::DeadSymbols, |
| 24 | check::PreStmt<DeclStmt>, check::LifetimeEnd> { |
| 25 | public: |
| 26 | void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, |
| 27 | const char *Sep) const override; |
| 28 | void checkPostCall(const CallEvent &Call, CheckerContext &C) const; |
| 29 | void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; |
| 30 | void checkLifetimeEnd(const VarDecl *VD, CheckerContext &C) const; |
| 31 | void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; |
| 32 | }; |
| 33 | |
| 34 | } // namespace |
| 35 | |
| 36 | static bool isDanglingStackSource(const MemRegion *Source, |
| 37 | ProgramStateRef State, CheckerContext &C) { |
| 38 | // FIXME: The checker currently handles stack-region sources. Other |
| 39 | // region kinds require separate methodology. For example, heap |
| 40 | // regions do not go out of scope at the end of a stack frame, so |
| 41 | // in order to detect those type of dangling sources the function |
| 42 | // needs to be expanded to an event-driven approach as well. |
| 43 | if (const auto *StackSpace = |
| 44 | Source->getMemorySpaceAs<StackSpaceRegion>(State)) { |
| 45 | const StackFrame *SF = StackSpace->getStackFrame(); |
| 46 | const StackFrame *CurrentSF = C.getStackFrame(); |
| 47 | // If any frame on the current stack belongs to a destructor |
| 48 | // the warning should be suppressed. When a lifetimebound method |
| 49 | // is called from a destructor then its return value is not expected |
| 50 | // to outlive the object being destroyed. |
| 51 | if (llvm::any_of(Range: C.stackframes(), P: [&](const StackFrame &Frame) { |
| 52 | return isa<CXXDestructorDecl>(Val: Frame.getDecl()); |
| 53 | })) { |
| 54 | return false; |
| 55 | } |
| 56 | // Only a source whose frame is still live on the current stack can |
| 57 | // dangle. If that frame is not on the stack then the source outlives |
| 58 | // the returned value. The source is still alive when the returned value |
| 59 | // is used, so it does not dangle. |
| 60 | if (is_contained(Range: make_pointer_range(Range: C.stackframes()), Element: SF)) { |
| 61 | if (SF == CurrentSF || !SF->isParentOf(SF: CurrentSF)) |
| 62 | return true; |
| 63 | } |
| 64 | } |
| 65 | return false; |
| 66 | } |
| 67 | |
| 68 | std::vector<const MemRegion *> lifetime_modeling::getDanglingRegionsAfterReturn( |
| 69 | SVal Val, ProgramStateRef State, CheckerContext &C) { |
| 70 | std::vector<const MemRegion *> Regions; |
| 71 | if (auto *SourceSet = State->get<LifetimeBoundMap>(key: Val)) { |
| 72 | for (const MemRegion *Region : *SourceSet) { |
| 73 | if (isDanglingStackSource(Source: Region, State, C)) |
| 74 | Regions.push_back(x: Region); |
| 75 | } |
| 76 | } |
| 77 | return Regions; |
| 78 | } |
| 79 | |
| 80 | bool lifetime_modeling::isBoundToLifetimeSource(ProgramStateRef State, |
| 81 | SVal Val) { |
| 82 | return State->get<LifetimeBoundMap>(key: Val) != nullptr; |
| 83 | } |
| 84 | |
| 85 | bool lifetime_modeling::isDeallocated(ProgramStateRef State, |
| 86 | const MemRegion *Region) { |
| 87 | return State->contains<DeallocatedSourceSet>(key: Region->getBaseRegion()); |
| 88 | } |
| 89 | |
| 90 | ProgramStateRef lifetime_modeling::markAsReported(ProgramStateRef State, |
| 91 | const MemRegion *Region) { |
| 92 | ProgramStateRef NewState = |
| 93 | State->add<ReportedDeadRegions>(K: Region->getBaseRegion()); |
| 94 | |
| 95 | return (NewState != State) ? NewState : nullptr; |
| 96 | } |
| 97 | |
| 98 | static ProgramStateRef bindSource(ProgramStateRef State, SVal RetVal, |
| 99 | const MemRegion *Source) { |
| 100 | LifetimeSourceSet::Factory &F = State->get_context<LifetimeSourceSet>(); |
| 101 | const LifetimeSourceSet *LSet = State->get<LifetimeBoundMap>(key: RetVal); |
| 102 | |
| 103 | LifetimeSourceSet Set = LSet ? *LSet : F.getEmptySet(); |
| 104 | Set = F.add(Old: Set, V: Source); |
| 105 | State = State->set<LifetimeBoundMap>(K: RetVal, E: Set); |
| 106 | return State; |
| 107 | } |
| 108 | |
| 109 | std::string lifetime_modeling::getRegionName(const MemRegion *Reg) { |
| 110 | // FIXME: Once the checker supports heap allocation, more region kinds |
| 111 | // should be handled to produce the correct descriptive name. |
| 112 | if (const std::string RegName = Reg->getDescriptiveName(); !RegName.empty()) |
| 113 | return RegName; |
| 114 | return "the region" ; |
| 115 | } |
| 116 | |
| 117 | void LifetimeModeling::checkPostCall(const CallEvent &Call, |
| 118 | CheckerContext &C) const { |
| 119 | ProgramStateRef State = C.getState(); |
| 120 | |
| 121 | const auto *FC = dyn_cast<AnyFunctionCall>(Val: &Call); |
| 122 | if (!FC) |
| 123 | return; |
| 124 | |
| 125 | const FunctionDecl *FD = FC->getDecl(); |
| 126 | if (!FD) |
| 127 | return; |
| 128 | |
| 129 | SVal RetVal = Call.getReturnValue(); |
| 130 | |
| 131 | for (const ParmVarDecl *PVD : FD->parameters()) { |
| 132 | if (PVD->hasAttr<LifetimeBoundAttr>()) { |
| 133 | unsigned Idx = PVD->getFunctionScopeIndex(); |
| 134 | SVal Arg = Call.getArgSVal(Index: Idx); |
| 135 | if (const MemRegion *ArgValRegion = Arg.getAsRegion()) |
| 136 | State = bindSource(State, RetVal, Source: ArgValRegion); |
| 137 | } |
| 138 | } |
| 139 | |
| 140 | const auto *IC = dyn_cast<CXXInstanceCall>(Val: &Call); |
| 141 | if (IC && lifetimes::implicitObjectParamIsLifetimeBound(FD)) { |
| 142 | if (const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion()) |
| 143 | State = bindSource(State, RetVal, Source: ThisRegion); |
| 144 | } |
| 145 | C.addTransition(State); |
| 146 | } |
| 147 | |
| 148 | void LifetimeModeling::checkLifetimeEnd(const VarDecl *VD, |
| 149 | CheckerContext &C) const { |
| 150 | ProgramStateRef State = C.getState(); |
| 151 | |
| 152 | SVal SourceVal = State->getLValue(VD, SF: C.getStackFrame()); |
| 153 | if (const MemRegion *SourceValRegion = SourceVal.getAsRegion()) { |
| 154 | State = State->add<DeallocatedSourceSet>(K: SourceValRegion); |
| 155 | C.addTransition(State); |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | void LifetimeModeling::checkPreStmt(const DeclStmt *DS, |
| 160 | CheckerContext &C) const { |
| 161 | ProgramStateRef State = C.getState(); |
| 162 | for (const auto *I : DS->decls()) { |
| 163 | if (const VarDecl *VD = dyn_cast<VarDecl>(Val: I)) { |
| 164 | SVal Val = State->getLValue(VD, SF: C.getStackFrame()); |
| 165 | if (const MemRegion *ValRegion = Val.getAsRegion()) |
| 166 | State = State->remove<DeallocatedSourceSet>(K: ValRegion); |
| 167 | } |
| 168 | } |
| 169 | C.addTransition(State); |
| 170 | } |
| 171 | |
| 172 | void LifetimeModeling::checkDeadSymbols(SymbolReaper &SymReaper, |
| 173 | CheckerContext &C) const { |
| 174 | ProgramStateRef State = C.getState(); |
| 175 | LifetimeBoundMapTy LBMap = State->get<LifetimeBoundMap>(); |
| 176 | DeallocatedSourceSetTy Sources = State->get<DeallocatedSourceSet>(); |
| 177 | ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>(); |
| 178 | |
| 179 | for (SVal Val : llvm::make_first_range(c&: LBMap)) { |
| 180 | if (const auto *R = Val.getAsRegion(); R && SymReaper.isLiveRegion(region: R)) |
| 181 | continue; |
| 182 | |
| 183 | if (SymbolRef S = Val.getAsSymbol(/*IncludeBaseRegions=*/true); |
| 184 | S && SymReaper.isLive(sym: S)) |
| 185 | continue; |
| 186 | |
| 187 | State = State->remove<LifetimeBoundMap>(K: Val); |
| 188 | } |
| 189 | |
| 190 | for (const MemRegion *Region : Sources) { |
| 191 | if (!SymReaper.isLiveRegion(region: Region)) |
| 192 | State = State->remove<DeallocatedSourceSet>(K: Region); |
| 193 | } |
| 194 | |
| 195 | for (const MemRegion *Region : Reported) { |
| 196 | if (!SymReaper.isLiveRegion(region: Region)) |
| 197 | State = State->remove<ReportedDeadRegions>(K: Region); |
| 198 | } |
| 199 | C.addTransition(State); |
| 200 | } |
| 201 | |
| 202 | void LifetimeModeling::printState(raw_ostream &Out, ProgramStateRef State, |
| 203 | const char *NL, const char *Sep) const { |
| 204 | auto LBMap = State->get<LifetimeBoundMap>(); |
| 205 | ReportedDeadRegionsTy Reported = State->get<ReportedDeadRegions>(); |
| 206 | |
| 207 | if (!LBMap.isEmpty()) { |
| 208 | Out << Sep << "LifetimeBound bindings:" << NL; |
| 209 | for (auto &&[OriginSym, SourceSet] : LBMap) { |
| 210 | for (const auto *Region : SourceSet) |
| 211 | Out << " Origin " << OriginSym << " contains Loan " << Region << NL; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | if (!Reported.isEmpty()) { |
| 216 | Out << Sep << "Reported regions: " << NL; |
| 217 | for (const auto *Region : Reported) { |
| 218 | Out << " " << Region << NL; |
| 219 | } |
| 220 | } |
| 221 | } |
| 222 | |
| 223 | // FIXME: Eventually move the debug checker to its own source file once |
| 224 | // it has more functionality. |
| 225 | namespace { |
| 226 | class DebugLifetimeModeling : public Checker<eval::Call> { |
| 227 | public: |
| 228 | bool evalCall(const CallEvent &Call, CheckerContext &C) const; |
| 229 | void analyzerDumpLifetimeOriginsOf(const CallEvent &Call, |
| 230 | CheckerContext &C) const; |
| 231 | const BugType BugMsg{this, "DebugLifetimeModeling" , "DebugLifetimeModeling" }; |
| 232 | using FnCheck = void (DebugLifetimeModeling::*)(const CallEvent &Call, |
| 233 | CheckerContext &C) const; |
| 234 | |
| 235 | const CallDescriptionMap<FnCheck> Callbacks = { |
| 236 | {{CDM::SimpleFunc, {"clang_analyzer_dumpLifetimeOriginsOf" }}, |
| 237 | &DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf}, |
| 238 | }; |
| 239 | }; |
| 240 | |
| 241 | } // namespace |
| 242 | |
| 243 | bool DebugLifetimeModeling::evalCall(const CallEvent &Call, |
| 244 | CheckerContext &C) const { |
| 245 | if (!isa_and_nonnull<CallExpr>(Val: Call.getOriginExpr())) |
| 246 | return false; |
| 247 | |
| 248 | const FnCheck *Handler = Callbacks.lookup(Call); |
| 249 | if (!Handler) |
| 250 | return false; |
| 251 | |
| 252 | (this->*(*Handler))(Call, C); |
| 253 | return true; |
| 254 | } |
| 255 | |
| 256 | void DebugLifetimeModeling::analyzerDumpLifetimeOriginsOf( |
| 257 | const CallEvent &Call, CheckerContext &C) const { |
| 258 | ProgramStateRef State = C.getState(); |
| 259 | |
| 260 | if (Call.getNumArgs() != 1) { |
| 261 | if (ExplodedNode *N = C.generateNonFatalErrorNode()) { |
| 262 | auto BR = std::make_unique<PathSensitiveBugReport>( |
| 263 | args: BugMsg, |
| 264 | args: "clang_analyzer_dumpLifetimeOriginsOf requires exactly 1 argument" , |
| 265 | args&: N); |
| 266 | C.emitReport(R: std::move(BR)); |
| 267 | } |
| 268 | return; |
| 269 | } |
| 270 | |
| 271 | SVal ArgSVal = Call.getArgSVal(Index: 0); |
| 272 | const LifetimeSourceSet *SourceSet = State->get<LifetimeBoundMap>(key: ArgSVal); |
| 273 | |
| 274 | if (!SourceSet) |
| 275 | return; |
| 276 | |
| 277 | if (ExplodedNode *N = C.generateNonFatalErrorNode()) { |
| 278 | llvm::SmallVector<std::string> RegionNames = |
| 279 | to_vector(Range: map_range(C: llvm::make_pointee_range(Range: *SourceSet), |
| 280 | F: std::mem_fn(pm: &MemRegion::getString))); |
| 281 | llvm::sort(C&: RegionNames); |
| 282 | |
| 283 | llvm::SmallString<128> Str; |
| 284 | llvm::raw_svector_ostream OS(Str); |
| 285 | OS << " Origin '" << ArgSVal << "' bound to " ; |
| 286 | llvm::interleaveComma(c: RegionNames, os&: OS, |
| 287 | each_fn: [&](StringRef Name) { OS << "'" << Name << "'" ; }); |
| 288 | C.emitReport(R: std::make_unique<PathSensitiveBugReport>(args: BugMsg, args: OS.str(), args&: N)); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | void ento::registerLifetimeModeling(CheckerManager &Mgr) { |
| 293 | Mgr.registerChecker<LifetimeModeling>(); |
| 294 | } |
| 295 | |
| 296 | bool ento::shouldRegisterLifetimeModeling(const CheckerManager &Mgr) { |
| 297 | return true; |
| 298 | } |
| 299 | |
| 300 | void ento::registerDebugLifetimeModeling(CheckerManager &Mgr) { |
| 301 | Mgr.registerChecker<DebugLifetimeModeling>(); |
| 302 | } |
| 303 | |
| 304 | bool ento::shouldRegisterDebugLifetimeModeling(const CheckerManager &Mgr) { |
| 305 | return true; |
| 306 | } |
| 307 | |