1 // SmartPtrModeling.cpp - Model behavior of C++ smart pointers - C++ ------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 //
9 // This file defines a checker that models various aspects of
10 // C++ smart pointer behavior.
11 //
12 //===----------------------------------------------------------------------===//
13
14 #include "Move.h"
15 #include "SmartPtr.h"
16
17 #include "clang/AST/DeclCXX.h"
18 #include "clang/AST/DeclarationName.h"
19 #include "clang/AST/ExprCXX.h"
20 #include "clang/AST/Type.h"
21 #include "clang/Basic/LLVM.h"
22 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
23 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
24 #include "clang/StaticAnalyzer/Core/Checker.h"
25 #include "clang/StaticAnalyzer/Core/CheckerManager.h"
26 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
27 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
28 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
29 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
30 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
31 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
32 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
33 #include "llvm/ADT/StringMap.h"
34 #include "llvm/Support/ErrorHandling.h"
35 #include <string>
36
37 using namespace clang;
38 using namespace ento;
39
40 namespace {
41
42 class SmartPtrModeling
43 : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges,
44 check::LiveSymbols> {
45
46 bool isBoolConversionMethod(const CallEvent &Call) const;
47
48 public:
49 // Whether the checker should model for null dereferences of smart pointers.
50 DefaultBool ModelSmartPtrDereference;
51 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
52 void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
53 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
54 ProgramStateRef
55 checkRegionChanges(ProgramStateRef State,
56 const InvalidatedSymbols *Invalidated,
57 ArrayRef<const MemRegion *> ExplicitRegions,
58 ArrayRef<const MemRegion *> Regions,
59 const LocationContext *LCtx, const CallEvent *Call) const;
60 void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
61 const char *Sep) const override;
62 void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const;
63
64 private:
65 void handleReset(const CallEvent &Call, CheckerContext &C) const;
66 void handleRelease(const CallEvent &Call, CheckerContext &C) const;
67 void handleSwapMethod(const CallEvent &Call, CheckerContext &C) const;
68 void handleGet(const CallEvent &Call, CheckerContext &C) const;
69 bool handleAssignOp(const CallEvent &Call, CheckerContext &C) const;
70 bool handleMoveCtr(const CallEvent &Call, CheckerContext &C,
71 const MemRegion *ThisRegion) const;
72 bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion,
73 const MemRegion *OtherSmartPtrRegion) const;
74 void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const;
75 bool handleComparisionOp(const CallEvent &Call, CheckerContext &C) const;
76 bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const;
77 bool handleSwap(ProgramStateRef State, SVal First, SVal Second,
78 CheckerContext &C) const;
79 std::pair<SVal, ProgramStateRef>
80 retrieveOrConjureInnerPtrVal(ProgramStateRef State,
81 const MemRegion *ThisRegion, const Expr *E,
82 QualType Type, CheckerContext &C) const;
83
84 using SmartPtrMethodHandlerFn =
85 void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
86 CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
87 {{"reset"}, &SmartPtrModeling::handleReset},
88 {{"release"}, &SmartPtrModeling::handleRelease},
89 {{"swap", 1}, &SmartPtrModeling::handleSwapMethod},
90 {{"get"}, &SmartPtrModeling::handleGet}};
91 const CallDescription StdSwapCall{{"std", "swap"}, 2};
92 const CallDescription StdMakeUniqueCall{{"std", "make_unique"}};
93 const CallDescription StdMakeUniqueForOverwriteCall{
94 {"std", "make_unique_for_overwrite"}};
95 };
96 } // end of anonymous namespace
97
REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap,const MemRegion *,SVal)98 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
99
100 // Checks if RD has name in Names and is in std namespace
101 static bool hasStdClassWithName(const CXXRecordDecl *RD,
102 ArrayRef<llvm::StringLiteral> Names) {
103 if (!RD || !RD->getDeclContext()->isStdNamespace())
104 return false;
105 if (RD->getDeclName().isIdentifier()) {
106 StringRef Name = RD->getName();
107 return llvm::any_of(Names, [&Name](StringRef GivenName) -> bool {
108 return Name == GivenName;
109 });
110 }
111 return false;
112 }
113
114 constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr", "unique_ptr",
115 "weak_ptr"};
116
isStdSmartPtr(const CXXRecordDecl * RD)117 static bool isStdSmartPtr(const CXXRecordDecl *RD) {
118 return hasStdClassWithName(RD, STD_PTR_NAMES);
119 }
120
isStdSmartPtr(const Expr * E)121 static bool isStdSmartPtr(const Expr *E) {
122 return isStdSmartPtr(E->getType()->getAsCXXRecordDecl());
123 }
124
125 // Define the inter-checker API.
126 namespace clang {
127 namespace ento {
128 namespace smartptr {
isStdSmartPtrCall(const CallEvent & Call)129 bool isStdSmartPtrCall(const CallEvent &Call) {
130 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
131 if (!MethodDecl || !MethodDecl->getParent())
132 return false;
133 return isStdSmartPtr(MethodDecl->getParent());
134 }
135
isStdSmartPtr(const CXXRecordDecl * RD)136 bool isStdSmartPtr(const CXXRecordDecl *RD) {
137 if (!RD || !RD->getDeclContext()->isStdNamespace())
138 return false;
139
140 if (RD->getDeclName().isIdentifier()) {
141 StringRef Name = RD->getName();
142 return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr";
143 }
144 return false;
145 }
146
isStdSmartPtr(const Expr * E)147 bool isStdSmartPtr(const Expr *E) {
148 return isStdSmartPtr(E->getType()->getAsCXXRecordDecl());
149 }
150
isNullSmartPtr(const ProgramStateRef State,const MemRegion * ThisRegion)151 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
152 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
153 return InnerPointVal &&
154 !State->assume(InnerPointVal->castAs<DefinedOrUnknownSVal>(), true);
155 }
156 } // namespace smartptr
157 } // namespace ento
158 } // namespace clang
159
160 // If a region is removed all of the subregions need to be removed too.
161 static TrackedRegionMapTy
removeTrackedSubregions(TrackedRegionMapTy RegionMap,TrackedRegionMapTy::Factory & RegionMapFactory,const MemRegion * Region)162 removeTrackedSubregions(TrackedRegionMapTy RegionMap,
163 TrackedRegionMapTy::Factory &RegionMapFactory,
164 const MemRegion *Region) {
165 if (!Region)
166 return RegionMap;
167 for (const auto &E : RegionMap) {
168 if (E.first->isSubRegionOf(Region))
169 RegionMap = RegionMapFactory.remove(RegionMap, E.first);
170 }
171 return RegionMap;
172 }
173
updateSwappedRegion(ProgramStateRef State,const MemRegion * Region,const SVal * RegionInnerPointerVal)174 static ProgramStateRef updateSwappedRegion(ProgramStateRef State,
175 const MemRegion *Region,
176 const SVal *RegionInnerPointerVal) {
177 if (RegionInnerPointerVal) {
178 State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal);
179 } else {
180 State = State->remove<TrackedRegionMap>(Region);
181 }
182 return State;
183 }
184
getInnerPointerType(CheckerContext C,const CXXRecordDecl * RD)185 static QualType getInnerPointerType(CheckerContext C, const CXXRecordDecl *RD) {
186 if (!RD || !RD->isInStdNamespace())
187 return {};
188
189 const auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(RD);
190 if (!TSD)
191 return {};
192
193 auto TemplateArgs = TSD->getTemplateArgs().asArray();
194 if (TemplateArgs.empty())
195 return {};
196 auto InnerValueType = TemplateArgs[0].getAsType();
197 return C.getASTContext().getPointerType(InnerValueType.getCanonicalType());
198 }
199
200 // This is for use with standalone-functions like std::make_unique,
201 // std::make_unique_for_overwrite, etc. It reads the template parameter and
202 // returns the pointer type corresponding to it,
getPointerTypeFromTemplateArg(const CallEvent & Call,CheckerContext & C)203 static QualType getPointerTypeFromTemplateArg(const CallEvent &Call,
204 CheckerContext &C) {
205 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
206 if (!FD || !FD->isFunctionTemplateSpecialization())
207 return {};
208 const auto &TemplateArgs = FD->getTemplateSpecializationArgs()->asArray();
209 if (TemplateArgs.size() == 0)
210 return {};
211 auto ValueType = TemplateArgs[0].getAsType();
212 return C.getASTContext().getPointerType(ValueType.getCanonicalType());
213 }
214
215 // Helper method to get the inner pointer type of specialized smart pointer
216 // Returns empty type if not found valid inner pointer type.
getInnerPointerType(const CallEvent & Call,CheckerContext & C)217 static QualType getInnerPointerType(const CallEvent &Call, CheckerContext &C) {
218 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
219 if (!MethodDecl || !MethodDecl->getParent())
220 return {};
221
222 const auto *RecordDecl = MethodDecl->getParent();
223 return getInnerPointerType(C, RecordDecl);
224 }
225
226 // Helper method to pretty print region and avoid extra spacing.
checkAndPrettyPrintRegion(llvm::raw_ostream & OS,const MemRegion * Region)227 static void checkAndPrettyPrintRegion(llvm::raw_ostream &OS,
228 const MemRegion *Region) {
229 if (Region->canPrintPretty()) {
230 OS << " ";
231 Region->printPretty(OS);
232 }
233 }
234
isBoolConversionMethod(const CallEvent & Call) const235 bool SmartPtrModeling::isBoolConversionMethod(const CallEvent &Call) const {
236 // TODO: Update CallDescription to support anonymous calls?
237 // TODO: Handle other methods, such as .get() or .release().
238 // But once we do, we'd need a visitor to explain null dereferences
239 // that are found via such modeling.
240 const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
241 return CD && CD->getConversionType()->isBooleanType();
242 }
243
244 constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream"};
245
isStdBasicOstream(const Expr * E)246 bool isStdBasicOstream(const Expr *E) {
247 const auto *RD = E->getType()->getAsCXXRecordDecl();
248 return hasStdClassWithName(RD, BASIC_OSTREAM_NAMES);
249 }
250
isStdFunctionCall(const CallEvent & Call)251 static bool isStdFunctionCall(const CallEvent &Call) {
252 return Call.getDecl() && Call.getDecl()->getDeclContext()->isStdNamespace();
253 }
254
isStdOstreamOperatorCall(const CallEvent & Call)255 bool isStdOstreamOperatorCall(const CallEvent &Call) {
256 if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call))
257 return false;
258 const auto *FC = dyn_cast<SimpleFunctionCall>(&Call);
259 if (!FC)
260 return false;
261 const FunctionDecl *FD = FC->getDecl();
262 if (!FD->isOverloadedOperator())
263 return false;
264 const OverloadedOperatorKind OOK = FD->getOverloadedOperator();
265 if (OOK != clang::OO_LessLess)
266 return false;
267 return isStdSmartPtr(Call.getArgExpr(1)) &&
268 isStdBasicOstream(Call.getArgExpr(0));
269 }
270
isPotentiallyComparisionOpCall(const CallEvent & Call)271 static bool isPotentiallyComparisionOpCall(const CallEvent &Call) {
272 if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call))
273 return false;
274 return smartptr::isStdSmartPtr(Call.getArgExpr(0)) ||
275 smartptr::isStdSmartPtr(Call.getArgExpr(1));
276 }
277
evalCall(const CallEvent & Call,CheckerContext & C) const278 bool SmartPtrModeling::evalCall(const CallEvent &Call,
279 CheckerContext &C) const {
280
281 ProgramStateRef State = C.getState();
282
283 // If any one of the arg is a unique_ptr, then
284 // we can try this function
285 if (ModelSmartPtrDereference && isPotentiallyComparisionOpCall(Call))
286 if (handleComparisionOp(Call, C))
287 return true;
288
289 if (ModelSmartPtrDereference && isStdOstreamOperatorCall(Call))
290 return handleOstreamOperator(Call, C);
291
292 if (Call.isCalled(StdSwapCall)) {
293 // Check the first arg, if it is of std::unique_ptr type.
294 assert(Call.getNumArgs() == 2 && "std::swap should have two arguments");
295 const Expr *FirstArg = Call.getArgExpr(0);
296 if (!smartptr::isStdSmartPtr(FirstArg->getType()->getAsCXXRecordDecl()))
297 return false;
298 return handleSwap(State, Call.getArgSVal(0), Call.getArgSVal(1), C);
299 }
300
301 if (Call.isCalled(StdMakeUniqueCall) ||
302 Call.isCalled(StdMakeUniqueForOverwriteCall)) {
303 if (!ModelSmartPtrDereference)
304 return false;
305
306 const Optional<SVal> ThisRegionOpt = Call.getReturnValueUnderConstruction();
307 if (!ThisRegionOpt)
308 return false;
309
310 const auto PtrVal = C.getSValBuilder().getConjuredHeapSymbolVal(
311 Call.getOriginExpr(), C.getLocationContext(),
312 getPointerTypeFromTemplateArg(Call, C), C.blockCount());
313
314 const MemRegion *ThisRegion = ThisRegionOpt->getAsRegion();
315 State = State->set<TrackedRegionMap>(ThisRegion, PtrVal);
316 State = State->assume(PtrVal, true);
317
318 // TODO: ExprEngine should do this for us.
319 // For a bit more context:
320 // 1) Why do we need this? Since we are modelling a "function"
321 // that returns a constructed object we need to store this information in
322 // the program state.
323 //
324 // 2) Why does this work?
325 // `updateObjectsUnderConstruction` does exactly as it sounds.
326 //
327 // 3) How should it look like when moved to the Engine?
328 // It would be nice if we can just
329 // pretend we don't need to know about this - ie, completely automatic work.
330 // However, realistically speaking, I think we would need to "signal" the
331 // ExprEngine evalCall handler that we are constructing an object with this
332 // function call (constructors obviously construct, hence can be
333 // automatically deduced).
334 auto &Engine = State->getStateManager().getOwningEngine();
335 State = Engine.updateObjectsUnderConstruction(
336 *ThisRegionOpt, nullptr, State, C.getLocationContext(),
337 Call.getConstructionContext(), {});
338
339 // We don't leave a note here since it is guaranteed the
340 // unique_ptr from this call is non-null (hence is safe to de-reference).
341 C.addTransition(State);
342 return true;
343 }
344
345 if (!smartptr::isStdSmartPtrCall(Call))
346 return false;
347
348 if (isBoolConversionMethod(Call)) {
349 const MemRegion *ThisR =
350 cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
351
352 if (ModelSmartPtrDereference) {
353 // The check for the region is moved is duplicated in handleBoolOperation
354 // method.
355 // FIXME: Once we model std::move for smart pointers clean up this and use
356 // that modeling.
357 handleBoolConversion(Call, C);
358 return true;
359 } else {
360 if (!move::isMovedFrom(State, ThisR)) {
361 // TODO: Model this case as well. At least, avoid invalidation of
362 // globals.
363 return false;
364 }
365
366 // TODO: Add a note to bug reports describing this decision.
367 C.addTransition(State->BindExpr(
368 Call.getOriginExpr(), C.getLocationContext(),
369 C.getSValBuilder().makeZeroVal(Call.getResultType())));
370
371 return true;
372 }
373 }
374
375 if (!ModelSmartPtrDereference)
376 return false;
377
378 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
379 if (CC->getDecl()->isCopyConstructor())
380 return false;
381
382 const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion();
383 if (!ThisRegion)
384 return false;
385
386 if (CC->getDecl()->isMoveConstructor())
387 return handleMoveCtr(Call, C, ThisRegion);
388
389 if (Call.getNumArgs() == 0) {
390 auto NullVal = C.getSValBuilder().makeNull();
391 State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
392
393 C.addTransition(
394 State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
395 llvm::raw_ostream &OS) {
396 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
397 !BR.isInteresting(ThisRegion))
398 return;
399 OS << "Default constructed smart pointer";
400 checkAndPrettyPrintRegion(OS, ThisRegion);
401 OS << " is null";
402 }));
403 } else {
404 const auto *TrackingExpr = Call.getArgExpr(0);
405 assert(TrackingExpr->getType()->isPointerType() &&
406 "Adding a non pointer value to TrackedRegionMap");
407 auto ArgVal = Call.getArgSVal(0);
408 State = State->set<TrackedRegionMap>(ThisRegion, ArgVal);
409
410 C.addTransition(State, C.getNoteTag([ThisRegion, TrackingExpr,
411 ArgVal](PathSensitiveBugReport &BR,
412 llvm::raw_ostream &OS) {
413 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
414 !BR.isInteresting(ThisRegion))
415 return;
416 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
417 OS << "Smart pointer";
418 checkAndPrettyPrintRegion(OS, ThisRegion);
419 if (ArgVal.isZeroConstant())
420 OS << " is constructed using a null value";
421 else
422 OS << " is constructed";
423 }));
424 }
425 return true;
426 }
427
428 if (handleAssignOp(Call, C))
429 return true;
430
431 const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call);
432 if (!Handler)
433 return false;
434 (this->**Handler)(Call, C);
435
436 return C.isDifferent();
437 }
438
retrieveOrConjureInnerPtrVal(ProgramStateRef State,const MemRegion * ThisRegion,const Expr * E,QualType Type,CheckerContext & C) const439 std::pair<SVal, ProgramStateRef> SmartPtrModeling::retrieveOrConjureInnerPtrVal(
440 ProgramStateRef State, const MemRegion *ThisRegion, const Expr *E,
441 QualType Type, CheckerContext &C) const {
442 const auto *Ptr = State->get<TrackedRegionMap>(ThisRegion);
443 if (Ptr)
444 return {*Ptr, State};
445 auto Val = C.getSValBuilder().conjureSymbolVal(E, C.getLocationContext(),
446 Type, C.blockCount());
447 State = State->set<TrackedRegionMap>(ThisRegion, Val);
448 return {Val, State};
449 }
450
handleComparisionOp(const CallEvent & Call,CheckerContext & C) const451 bool SmartPtrModeling::handleComparisionOp(const CallEvent &Call,
452 CheckerContext &C) const {
453 const auto *FC = dyn_cast<SimpleFunctionCall>(&Call);
454 if (!FC)
455 return false;
456 const FunctionDecl *FD = FC->getDecl();
457 if (!FD->isOverloadedOperator())
458 return false;
459 const OverloadedOperatorKind OOK = FD->getOverloadedOperator();
460 if (!(OOK == OO_EqualEqual || OOK == OO_ExclaimEqual || OOK == OO_Less ||
461 OOK == OO_LessEqual || OOK == OO_Greater || OOK == OO_GreaterEqual ||
462 OOK == OO_Spaceship))
463 return false;
464
465 // There are some special cases about which we can infer about
466 // the resulting answer.
467 // For reference, there is a discussion at https://reviews.llvm.org/D104616.
468 // Also, the cppreference page is good to look at
469 // https://en.cppreference.com/w/cpp/memory/unique_ptr/operator_cmp.
470
471 auto makeSValFor = [&C, this](ProgramStateRef State, const Expr *E,
472 SVal S) -> std::pair<SVal, ProgramStateRef> {
473 if (S.isZeroConstant()) {
474 return {S, State};
475 }
476 const MemRegion *Reg = S.getAsRegion();
477 assert(Reg &&
478 "this pointer of std::unique_ptr should be obtainable as MemRegion");
479 QualType Type = getInnerPointerType(C, E->getType()->getAsCXXRecordDecl());
480 return retrieveOrConjureInnerPtrVal(State, Reg, E, Type, C);
481 };
482
483 SVal First = Call.getArgSVal(0);
484 SVal Second = Call.getArgSVal(1);
485 const auto *FirstExpr = Call.getArgExpr(0);
486 const auto *SecondExpr = Call.getArgExpr(1);
487
488 const auto *ResultExpr = Call.getOriginExpr();
489 const auto *LCtx = C.getLocationContext();
490 auto &Bldr = C.getSValBuilder();
491 ProgramStateRef State = C.getState();
492
493 SVal FirstPtrVal, SecondPtrVal;
494 std::tie(FirstPtrVal, State) = makeSValFor(State, FirstExpr, First);
495 std::tie(SecondPtrVal, State) = makeSValFor(State, SecondExpr, Second);
496 BinaryOperatorKind BOK =
497 operationKindFromOverloadedOperator(OOK, true).GetBinaryOpUnsafe();
498 auto RetVal = Bldr.evalBinOp(State, BOK, FirstPtrVal, SecondPtrVal,
499 Call.getResultType());
500
501 if (OOK != OO_Spaceship) {
502 ProgramStateRef TrueState, FalseState;
503 std::tie(TrueState, FalseState) =
504 State->assume(*RetVal.getAs<DefinedOrUnknownSVal>());
505 if (TrueState)
506 C.addTransition(
507 TrueState->BindExpr(ResultExpr, LCtx, Bldr.makeTruthVal(true)));
508 if (FalseState)
509 C.addTransition(
510 FalseState->BindExpr(ResultExpr, LCtx, Bldr.makeTruthVal(false)));
511 } else {
512 C.addTransition(State->BindExpr(ResultExpr, LCtx, RetVal));
513 }
514 return true;
515 }
516
handleOstreamOperator(const CallEvent & Call,CheckerContext & C) const517 bool SmartPtrModeling::handleOstreamOperator(const CallEvent &Call,
518 CheckerContext &C) const {
519 // operator<< does not modify the smart pointer.
520 // And we don't really have much of modelling of basic_ostream.
521 // So, we are better off:
522 // 1) Invalidating the mem-region of the ostream object at hand.
523 // 2) Setting the SVal of the basic_ostream as the return value.
524 // Not very satisfying, but it gets the job done, and is better
525 // than the default handling. :)
526
527 ProgramStateRef State = C.getState();
528 const auto StreamVal = Call.getArgSVal(0);
529 const MemRegion *StreamThisRegion = StreamVal.getAsRegion();
530 if (!StreamThisRegion)
531 return false;
532 State =
533 State->invalidateRegions({StreamThisRegion}, Call.getOriginExpr(),
534 C.blockCount(), C.getLocationContext(), false);
535 State =
536 State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), StreamVal);
537 C.addTransition(State);
538 return true;
539 }
540
checkDeadSymbols(SymbolReaper & SymReaper,CheckerContext & C) const541 void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper,
542 CheckerContext &C) const {
543 ProgramStateRef State = C.getState();
544 // Clean up dead regions from the region map.
545 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
546 for (auto E : TrackedRegions) {
547 const MemRegion *Region = E.first;
548 bool IsRegDead = !SymReaper.isLiveRegion(Region);
549
550 if (IsRegDead)
551 State = State->remove<TrackedRegionMap>(Region);
552 }
553 C.addTransition(State);
554 }
555
printState(raw_ostream & Out,ProgramStateRef State,const char * NL,const char * Sep) const556 void SmartPtrModeling::printState(raw_ostream &Out, ProgramStateRef State,
557 const char *NL, const char *Sep) const {
558 TrackedRegionMapTy RS = State->get<TrackedRegionMap>();
559
560 if (!RS.isEmpty()) {
561 Out << Sep << "Smart ptr regions :" << NL;
562 for (auto I : RS) {
563 I.first->dumpToStream(Out);
564 if (smartptr::isNullSmartPtr(State, I.first))
565 Out << ": Null";
566 else
567 Out << ": Non Null";
568 Out << NL;
569 }
570 }
571 }
572
checkRegionChanges(ProgramStateRef State,const InvalidatedSymbols * Invalidated,ArrayRef<const MemRegion * > ExplicitRegions,ArrayRef<const MemRegion * > Regions,const LocationContext * LCtx,const CallEvent * Call) const573 ProgramStateRef SmartPtrModeling::checkRegionChanges(
574 ProgramStateRef State, const InvalidatedSymbols *Invalidated,
575 ArrayRef<const MemRegion *> ExplicitRegions,
576 ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx,
577 const CallEvent *Call) const {
578 TrackedRegionMapTy RegionMap = State->get<TrackedRegionMap>();
579 TrackedRegionMapTy::Factory &RegionMapFactory =
580 State->get_context<TrackedRegionMap>();
581 for (const auto *Region : Regions)
582 RegionMap = removeTrackedSubregions(RegionMap, RegionMapFactory,
583 Region->getBaseRegion());
584 return State->set<TrackedRegionMap>(RegionMap);
585 }
586
checkLiveSymbols(ProgramStateRef State,SymbolReaper & SR) const587 void SmartPtrModeling::checkLiveSymbols(ProgramStateRef State,
588 SymbolReaper &SR) const {
589 // Marking tracked symbols alive
590 TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>();
591 for (auto I = TrackedRegions.begin(), E = TrackedRegions.end(); I != E; ++I) {
592 SVal Val = I->second;
593 for (auto si = Val.symbol_begin(), se = Val.symbol_end(); si != se; ++si) {
594 SR.markLive(*si);
595 }
596 }
597 }
598
handleReset(const CallEvent & Call,CheckerContext & C) const599 void SmartPtrModeling::handleReset(const CallEvent &Call,
600 CheckerContext &C) const {
601 ProgramStateRef State = C.getState();
602 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
603 if (!IC)
604 return;
605
606 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
607 if (!ThisRegion)
608 return;
609
610 assert(Call.getArgExpr(0)->getType()->isPointerType() &&
611 "Adding a non pointer value to TrackedRegionMap");
612 State = State->set<TrackedRegionMap>(ThisRegion, Call.getArgSVal(0));
613 const auto *TrackingExpr = Call.getArgExpr(0);
614 C.addTransition(
615 State, C.getNoteTag([ThisRegion, TrackingExpr](PathSensitiveBugReport &BR,
616 llvm::raw_ostream &OS) {
617 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
618 !BR.isInteresting(ThisRegion))
619 return;
620 bugreporter::trackExpressionValue(BR.getErrorNode(), TrackingExpr, BR);
621 OS << "Smart pointer";
622 checkAndPrettyPrintRegion(OS, ThisRegion);
623 OS << " reset using a null value";
624 }));
625 // TODO: Make sure to ivalidate the region in the Store if we don't have
626 // time to model all methods.
627 }
628
handleRelease(const CallEvent & Call,CheckerContext & C) const629 void SmartPtrModeling::handleRelease(const CallEvent &Call,
630 CheckerContext &C) const {
631 ProgramStateRef State = C.getState();
632 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
633 if (!IC)
634 return;
635
636 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
637 if (!ThisRegion)
638 return;
639
640 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
641
642 if (InnerPointVal) {
643 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
644 *InnerPointVal);
645 }
646
647 auto ValueToUpdate = C.getSValBuilder().makeNull();
648 State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate);
649
650 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
651 llvm::raw_ostream &OS) {
652 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
653 !BR.isInteresting(ThisRegion))
654 return;
655
656 OS << "Smart pointer";
657 checkAndPrettyPrintRegion(OS, ThisRegion);
658 OS << " is released and set to null";
659 }));
660 // TODO: Add support to enable MallocChecker to start tracking the raw
661 // pointer.
662 }
663
handleSwapMethod(const CallEvent & Call,CheckerContext & C) const664 void SmartPtrModeling::handleSwapMethod(const CallEvent &Call,
665 CheckerContext &C) const {
666 // To model unique_ptr::swap() method.
667 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
668 if (!IC)
669 return;
670
671 auto State = C.getState();
672 handleSwap(State, IC->getCXXThisVal(), Call.getArgSVal(0), C);
673 }
674
handleSwap(ProgramStateRef State,SVal First,SVal Second,CheckerContext & C) const675 bool SmartPtrModeling::handleSwap(ProgramStateRef State, SVal First,
676 SVal Second, CheckerContext &C) const {
677 const MemRegion *FirstThisRegion = First.getAsRegion();
678 if (!FirstThisRegion)
679 return false;
680 const MemRegion *SecondThisRegion = Second.getAsRegion();
681 if (!SecondThisRegion)
682 return false;
683
684 const auto *FirstInnerPtrVal = State->get<TrackedRegionMap>(FirstThisRegion);
685 const auto *SecondInnerPtrVal =
686 State->get<TrackedRegionMap>(SecondThisRegion);
687
688 State = updateSwappedRegion(State, FirstThisRegion, SecondInnerPtrVal);
689 State = updateSwappedRegion(State, SecondThisRegion, FirstInnerPtrVal);
690
691 C.addTransition(State, C.getNoteTag([FirstThisRegion, SecondThisRegion](
692 PathSensitiveBugReport &BR,
693 llvm::raw_ostream &OS) {
694 if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
695 return;
696 if (BR.isInteresting(FirstThisRegion) &&
697 !BR.isInteresting(SecondThisRegion)) {
698 BR.markInteresting(SecondThisRegion);
699 BR.markNotInteresting(FirstThisRegion);
700 }
701 if (BR.isInteresting(SecondThisRegion) &&
702 !BR.isInteresting(FirstThisRegion)) {
703 BR.markInteresting(FirstThisRegion);
704 BR.markNotInteresting(SecondThisRegion);
705 }
706 // TODO: We need to emit some note here probably!!
707 }));
708
709 return true;
710 }
711
handleGet(const CallEvent & Call,CheckerContext & C) const712 void SmartPtrModeling::handleGet(const CallEvent &Call,
713 CheckerContext &C) const {
714 ProgramStateRef State = C.getState();
715 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
716 if (!IC)
717 return;
718
719 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
720 if (!ThisRegion)
721 return;
722
723 SVal InnerPointerVal;
724 std::tie(InnerPointerVal, State) = retrieveOrConjureInnerPtrVal(
725 State, ThisRegion, Call.getOriginExpr(), Call.getResultType(), C);
726 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
727 InnerPointerVal);
728 // TODO: Add NoteTag, for how the raw pointer got using 'get' method.
729 C.addTransition(State);
730 }
731
handleAssignOp(const CallEvent & Call,CheckerContext & C) const732 bool SmartPtrModeling::handleAssignOp(const CallEvent &Call,
733 CheckerContext &C) const {
734 ProgramStateRef State = C.getState();
735 const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call);
736 if (!OC)
737 return false;
738 OverloadedOperatorKind OOK = OC->getOverloadedOperator();
739 if (OOK != OO_Equal)
740 return false;
741 const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion();
742 if (!ThisRegion)
743 return false;
744
745 const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(0).getAsRegion();
746 // In case of 'nullptr' or '0' assigned
747 if (!OtherSmartPtrRegion) {
748 bool AssignedNull = Call.getArgSVal(0).isZeroConstant();
749 if (!AssignedNull)
750 return false;
751 auto NullVal = C.getSValBuilder().makeNull();
752 State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
753 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
754 llvm::raw_ostream &OS) {
755 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
756 !BR.isInteresting(ThisRegion))
757 return;
758 OS << "Smart pointer";
759 checkAndPrettyPrintRegion(OS, ThisRegion);
760 OS << " is assigned to null";
761 }));
762 return true;
763 }
764
765 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion);
766 }
767
handleMoveCtr(const CallEvent & Call,CheckerContext & C,const MemRegion * ThisRegion) const768 bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C,
769 const MemRegion *ThisRegion) const {
770 const auto *OtherSmartPtrRegion = Call.getArgSVal(0).getAsRegion();
771 if (!OtherSmartPtrRegion)
772 return false;
773
774 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion);
775 }
776
updateMovedSmartPointers(CheckerContext & C,const MemRegion * ThisRegion,const MemRegion * OtherSmartPtrRegion) const777 bool SmartPtrModeling::updateMovedSmartPointers(
778 CheckerContext &C, const MemRegion *ThisRegion,
779 const MemRegion *OtherSmartPtrRegion) const {
780 ProgramStateRef State = C.getState();
781 const auto *OtherInnerPtr = State->get<TrackedRegionMap>(OtherSmartPtrRegion);
782 if (OtherInnerPtr) {
783 State = State->set<TrackedRegionMap>(ThisRegion, *OtherInnerPtr);
784 auto NullVal = C.getSValBuilder().makeNull();
785 State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
786 bool IsArgValNull = OtherInnerPtr->isZeroConstant();
787
788 C.addTransition(
789 State,
790 C.getNoteTag([ThisRegion, OtherSmartPtrRegion, IsArgValNull](
791 PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
792 if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
793 return;
794 if (BR.isInteresting(OtherSmartPtrRegion)) {
795 OS << "Smart pointer";
796 checkAndPrettyPrintRegion(OS, OtherSmartPtrRegion);
797 OS << " is null after being moved to";
798 checkAndPrettyPrintRegion(OS, ThisRegion);
799 }
800 if (BR.isInteresting(ThisRegion) && IsArgValNull) {
801 OS << "A null pointer value is moved to";
802 checkAndPrettyPrintRegion(OS, ThisRegion);
803 BR.markInteresting(OtherSmartPtrRegion);
804 }
805 }));
806 return true;
807 } else {
808 // In case we dont know anything about value we are moving from
809 // remove the entry from map for which smart pointer got moved to.
810 auto NullVal = C.getSValBuilder().makeNull();
811 State = State->remove<TrackedRegionMap>(ThisRegion);
812 State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
813 C.addTransition(State, C.getNoteTag([OtherSmartPtrRegion,
814 ThisRegion](PathSensitiveBugReport &BR,
815 llvm::raw_ostream &OS) {
816 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
817 !BR.isInteresting(OtherSmartPtrRegion))
818 return;
819 OS << "Smart pointer";
820 checkAndPrettyPrintRegion(OS, OtherSmartPtrRegion);
821 OS << " is null after; previous value moved to";
822 checkAndPrettyPrintRegion(OS, ThisRegion);
823 }));
824 return true;
825 }
826 return false;
827 }
828
handleBoolConversion(const CallEvent & Call,CheckerContext & C) const829 void SmartPtrModeling::handleBoolConversion(const CallEvent &Call,
830 CheckerContext &C) const {
831 // To model unique_ptr::operator bool
832 ProgramStateRef State = C.getState();
833 const Expr *CallExpr = Call.getOriginExpr();
834 const MemRegion *ThisRegion =
835 cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
836
837 SVal InnerPointerVal;
838 if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) {
839 InnerPointerVal = *InnerValPtr;
840 } else {
841 // In case of inner pointer SVal is not available we create
842 // conjureSymbolVal for inner pointer value.
843 auto InnerPointerType = getInnerPointerType(Call, C);
844 if (InnerPointerType.isNull())
845 return;
846
847 const LocationContext *LC = C.getLocationContext();
848 InnerPointerVal = C.getSValBuilder().conjureSymbolVal(
849 CallExpr, LC, InnerPointerType, C.blockCount());
850 State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal);
851 }
852
853 if (State->isNull(InnerPointerVal).isConstrainedTrue()) {
854 State = State->BindExpr(CallExpr, C.getLocationContext(),
855 C.getSValBuilder().makeTruthVal(false));
856
857 C.addTransition(State);
858 return;
859 } else if (State->isNonNull(InnerPointerVal).isConstrainedTrue()) {
860 State = State->BindExpr(CallExpr, C.getLocationContext(),
861 C.getSValBuilder().makeTruthVal(true));
862
863 C.addTransition(State);
864 return;
865 } else if (move::isMovedFrom(State, ThisRegion)) {
866 C.addTransition(
867 State->BindExpr(CallExpr, C.getLocationContext(),
868 C.getSValBuilder().makeZeroVal(Call.getResultType())));
869 return;
870 } else {
871 ProgramStateRef NotNullState, NullState;
872 std::tie(NotNullState, NullState) =
873 State->assume(InnerPointerVal.castAs<DefinedOrUnknownSVal>());
874
875 auto NullVal = C.getSValBuilder().makeNull();
876 // Explicitly tracking the region as null.
877 NullState = NullState->set<TrackedRegionMap>(ThisRegion, NullVal);
878
879 NullState = NullState->BindExpr(CallExpr, C.getLocationContext(),
880 C.getSValBuilder().makeTruthVal(false));
881 C.addTransition(NullState, C.getNoteTag(
882 [ThisRegion](PathSensitiveBugReport &BR,
883 llvm::raw_ostream &OS) {
884 OS << "Assuming smart pointer";
885 checkAndPrettyPrintRegion(OS, ThisRegion);
886 OS << " is null";
887 },
888 /*IsPrunable=*/true));
889 NotNullState =
890 NotNullState->BindExpr(CallExpr, C.getLocationContext(),
891 C.getSValBuilder().makeTruthVal(true));
892 C.addTransition(
893 NotNullState,
894 C.getNoteTag(
895 [ThisRegion](PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
896 OS << "Assuming smart pointer";
897 checkAndPrettyPrintRegion(OS, ThisRegion);
898 OS << " is non-null";
899 },
900 /*IsPrunable=*/true));
901 return;
902 }
903 }
904
registerSmartPtrModeling(CheckerManager & Mgr)905 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
906 auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
907 Checker->ModelSmartPtrDereference =
908 Mgr.getAnalyzerOptions().getCheckerBooleanOption(
909 Checker, "ModelSmartPtrDereference");
910 }
911
shouldRegisterSmartPtrModeling(const CheckerManager & mgr)912 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
913 const LangOptions &LO = mgr.getLangOpts();
914 return LO.CPlusPlus;
915 }
916