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/CallDescription.h"
27 #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h"
28 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
29 #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h"
30 #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
31 #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h"
32 #include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h"
33 #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h"
34 #include "llvm/ADT/StringMap.h"
35 #include "llvm/Support/ErrorHandling.h"
36 #include <optional>
37 #include <string>
38
39 using namespace clang;
40 using namespace ento;
41
42 namespace {
43
44 class SmartPtrModeling
45 : public Checker<eval::Call, check::DeadSymbols, check::RegionChanges,
46 check::LiveSymbols> {
47
48 bool isBoolConversionMethod(const CallEvent &Call) const;
49
50 public:
51 // Whether the checker should model for null dereferences of smart pointers.
52 bool ModelSmartPtrDereference = false;
53 bool evalCall(const CallEvent &Call, CheckerContext &C) const;
54 void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const;
55 ProgramStateRef
56 checkRegionChanges(ProgramStateRef State,
57 const InvalidatedSymbols *Invalidated,
58 ArrayRef<const MemRegion *> ExplicitRegions,
59 ArrayRef<const MemRegion *> Regions,
60 const LocationContext *LCtx, const CallEvent *Call) const;
61 void printState(raw_ostream &Out, ProgramStateRef State, const char *NL,
62 const char *Sep) const override;
63 void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const;
64
65 private:
66 void handleReset(const CallEvent &Call, CheckerContext &C) const;
67 void handleRelease(const CallEvent &Call, CheckerContext &C) const;
68 void handleSwapMethod(const CallEvent &Call, CheckerContext &C) const;
69 void handleGet(const CallEvent &Call, CheckerContext &C) const;
70 bool handleAssignOp(const CallEvent &Call, CheckerContext &C) const;
71 bool handleMoveCtr(const CallEvent &Call, CheckerContext &C,
72 const MemRegion *ThisRegion) const;
73 bool updateMovedSmartPointers(CheckerContext &C, const MemRegion *ThisRegion,
74 const MemRegion *OtherSmartPtrRegion,
75 const CallEvent &Call) const;
76 void handleBoolConversion(const CallEvent &Call, CheckerContext &C) const;
77 bool handleComparisionOp(const CallEvent &Call, CheckerContext &C) const;
78 bool handleOstreamOperator(const CallEvent &Call, CheckerContext &C) const;
79 bool handleSwap(ProgramStateRef State, SVal First, SVal Second,
80 CheckerContext &C) const;
81 std::pair<SVal, ProgramStateRef>
82 retrieveOrConjureInnerPtrVal(ProgramStateRef State,
83 const MemRegion *ThisRegion, const Expr *E,
84 QualType Type, CheckerContext &C) const;
85
86 using SmartPtrMethodHandlerFn =
87 void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const;
88 CallDescriptionMap<SmartPtrMethodHandlerFn> SmartPtrMethodHandlers{
89 {{{"reset"}}, &SmartPtrModeling::handleReset},
90 {{{"release"}}, &SmartPtrModeling::handleRelease},
91 {{{"swap"}, 1}, &SmartPtrModeling::handleSwapMethod},
92 {{{"get"}}, &SmartPtrModeling::handleGet}};
93 const CallDescription StdSwapCall{{"std", "swap"}, 2};
94 const CallDescription StdMakeUniqueCall{{"std", "make_unique"}};
95 const CallDescription StdMakeUniqueForOverwriteCall{
96 {"std", "make_unique_for_overwrite"}};
97 };
98 } // end of anonymous namespace
99
REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap,const MemRegion *,SVal)100 REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal)
101
102 // Checks if RD has name in Names and is in std namespace
103 static bool hasStdClassWithName(const CXXRecordDecl *RD,
104 ArrayRef<llvm::StringLiteral> Names) {
105 if (!RD || !RD->getDeclContext()->isStdNamespace())
106 return false;
107 if (RD->getDeclName().isIdentifier())
108 return llvm::is_contained(Names, RD->getName());
109 return false;
110 }
111
112 constexpr llvm::StringLiteral STD_PTR_NAMES[] = {"shared_ptr", "unique_ptr",
113 "weak_ptr"};
114
isStdSmartPtr(const CXXRecordDecl * RD)115 static bool isStdSmartPtr(const CXXRecordDecl *RD) {
116 return hasStdClassWithName(RD, STD_PTR_NAMES);
117 }
118
isStdSmartPtr(const Expr * E)119 static bool isStdSmartPtr(const Expr *E) {
120 return isStdSmartPtr(E->getType()->getAsCXXRecordDecl());
121 }
122
123 // Define the inter-checker API.
124 namespace clang {
125 namespace ento {
126 namespace smartptr {
isStdSmartPtrCall(const CallEvent & Call)127 bool isStdSmartPtrCall(const CallEvent &Call) {
128 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
129 if (!MethodDecl || !MethodDecl->getParent())
130 return false;
131 return isStdSmartPtr(MethodDecl->getParent());
132 }
133
isStdSmartPtr(const CXXRecordDecl * RD)134 bool isStdSmartPtr(const CXXRecordDecl *RD) {
135 if (!RD || !RD->getDeclContext()->isStdNamespace())
136 return false;
137
138 if (RD->getDeclName().isIdentifier()) {
139 StringRef Name = RD->getName();
140 return Name == "shared_ptr" || Name == "unique_ptr" || Name == "weak_ptr";
141 }
142 return false;
143 }
144
isStdSmartPtr(const Expr * E)145 bool isStdSmartPtr(const Expr *E) {
146 return isStdSmartPtr(E->getType()->getAsCXXRecordDecl());
147 }
148
isNullSmartPtr(const ProgramStateRef State,const MemRegion * ThisRegion)149 bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) {
150 const auto *InnerPointVal = State->get<TrackedRegionMap>(ThisRegion);
151 return InnerPointVal &&
152 !State->assume(InnerPointVal->castAs<DefinedOrUnknownSVal>(), true);
153 }
154 } // namespace smartptr
155 } // namespace ento
156 } // namespace clang
157
158 // If a region is removed all of the subregions need to be removed too.
159 static TrackedRegionMapTy
removeTrackedSubregions(TrackedRegionMapTy RegionMap,TrackedRegionMapTy::Factory & RegionMapFactory,const MemRegion * Region)160 removeTrackedSubregions(TrackedRegionMapTy RegionMap,
161 TrackedRegionMapTy::Factory &RegionMapFactory,
162 const MemRegion *Region) {
163 if (!Region)
164 return RegionMap;
165 for (const auto &E : RegionMap) {
166 if (E.first->isSubRegionOf(Region))
167 RegionMap = RegionMapFactory.remove(RegionMap, E.first);
168 }
169 return RegionMap;
170 }
171
updateSwappedRegion(ProgramStateRef State,const MemRegion * Region,const SVal * RegionInnerPointerVal)172 static ProgramStateRef updateSwappedRegion(ProgramStateRef State,
173 const MemRegion *Region,
174 const SVal *RegionInnerPointerVal) {
175 if (RegionInnerPointerVal) {
176 State = State->set<TrackedRegionMap>(Region, *RegionInnerPointerVal);
177 } else {
178 State = State->remove<TrackedRegionMap>(Region);
179 }
180 return State;
181 }
182
getInnerPointerType(CheckerContext C,const CXXRecordDecl * RD)183 static QualType getInnerPointerType(CheckerContext C, const CXXRecordDecl *RD) {
184 if (!RD || !RD->isInStdNamespace())
185 return {};
186
187 const auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(RD);
188 if (!TSD)
189 return {};
190
191 auto TemplateArgs = TSD->getTemplateArgs().asArray();
192 if (TemplateArgs.empty())
193 return {};
194 auto InnerValueType = TemplateArgs[0].getAsType();
195 return C.getASTContext().getPointerType(InnerValueType.getCanonicalType());
196 }
197
198 // This is for use with standalone-functions like std::make_unique,
199 // std::make_unique_for_overwrite, etc. It reads the template parameter and
200 // returns the pointer type corresponding to it,
getPointerTypeFromTemplateArg(const CallEvent & Call,CheckerContext & C)201 static QualType getPointerTypeFromTemplateArg(const CallEvent &Call,
202 CheckerContext &C) {
203 const auto *FD = dyn_cast_or_null<FunctionDecl>(Call.getDecl());
204 if (!FD || !FD->isFunctionTemplateSpecialization())
205 return {};
206 const auto &TemplateArgs = FD->getTemplateSpecializationArgs()->asArray();
207 if (TemplateArgs.size() == 0)
208 return {};
209 auto ValueType = TemplateArgs[0].getAsType();
210 return C.getASTContext().getPointerType(ValueType.getCanonicalType());
211 }
212
213 // Helper method to get the inner pointer type of specialized smart pointer
214 // Returns empty type if not found valid inner pointer type.
getInnerPointerType(const CallEvent & Call,CheckerContext & C)215 static QualType getInnerPointerType(const CallEvent &Call, CheckerContext &C) {
216 const auto *MethodDecl = dyn_cast_or_null<CXXMethodDecl>(Call.getDecl());
217 if (!MethodDecl || !MethodDecl->getParent())
218 return {};
219
220 const auto *RecordDecl = MethodDecl->getParent();
221 return getInnerPointerType(C, RecordDecl);
222 }
223
224 // Helper method to pretty print region and avoid extra spacing.
checkAndPrettyPrintRegion(llvm::raw_ostream & OS,const MemRegion * Region)225 static void checkAndPrettyPrintRegion(llvm::raw_ostream &OS,
226 const MemRegion *Region) {
227 if (Region->canPrintPretty()) {
228 OS << " ";
229 Region->printPretty(OS);
230 }
231 }
232
isBoolConversionMethod(const CallEvent & Call) const233 bool SmartPtrModeling::isBoolConversionMethod(const CallEvent &Call) const {
234 // TODO: Update CallDescription to support anonymous calls?
235 // TODO: Handle other methods, such as .get() or .release().
236 // But once we do, we'd need a visitor to explain null dereferences
237 // that are found via such modeling.
238 const auto *CD = dyn_cast_or_null<CXXConversionDecl>(Call.getDecl());
239 return CD && CD->getConversionType()->isBooleanType();
240 }
241
242 constexpr llvm::StringLiteral BASIC_OSTREAM_NAMES[] = {"basic_ostream"};
243
isStdBasicOstream(const Expr * E)244 bool isStdBasicOstream(const Expr *E) {
245 const auto *RD = E->getType()->getAsCXXRecordDecl();
246 return hasStdClassWithName(RD, BASIC_OSTREAM_NAMES);
247 }
248
isStdFunctionCall(const CallEvent & Call)249 static bool isStdFunctionCall(const CallEvent &Call) {
250 return Call.getDecl() && Call.getDecl()->getDeclContext()->isStdNamespace();
251 }
252
isStdOstreamOperatorCall(const CallEvent & Call)253 bool isStdOstreamOperatorCall(const CallEvent &Call) {
254 if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call))
255 return false;
256 const auto *FC = dyn_cast<SimpleFunctionCall>(&Call);
257 if (!FC)
258 return false;
259 const FunctionDecl *FD = FC->getDecl();
260 if (!FD->isOverloadedOperator())
261 return false;
262 const OverloadedOperatorKind OOK = FD->getOverloadedOperator();
263 if (OOK != clang::OO_LessLess)
264 return false;
265 return isStdSmartPtr(Call.getArgExpr(1)) &&
266 isStdBasicOstream(Call.getArgExpr(0));
267 }
268
isPotentiallyComparisionOpCall(const CallEvent & Call)269 static bool isPotentiallyComparisionOpCall(const CallEvent &Call) {
270 if (Call.getNumArgs() != 2 || !isStdFunctionCall(Call))
271 return false;
272 return smartptr::isStdSmartPtr(Call.getArgExpr(0)) ||
273 smartptr::isStdSmartPtr(Call.getArgExpr(1));
274 }
275
evalCall(const CallEvent & Call,CheckerContext & C) const276 bool SmartPtrModeling::evalCall(const CallEvent &Call,
277 CheckerContext &C) const {
278
279 ProgramStateRef State = C.getState();
280
281 // If any one of the arg is a unique_ptr, then
282 // we can try this function
283 if (ModelSmartPtrDereference && isPotentiallyComparisionOpCall(Call))
284 if (handleComparisionOp(Call, C))
285 return true;
286
287 if (ModelSmartPtrDereference && isStdOstreamOperatorCall(Call))
288 return handleOstreamOperator(Call, C);
289
290 if (StdSwapCall.matches(Call)) {
291 // Check the first arg, if it is of std::unique_ptr type.
292 assert(Call.getNumArgs() == 2 && "std::swap should have two arguments");
293 const Expr *FirstArg = Call.getArgExpr(0);
294 if (!smartptr::isStdSmartPtr(FirstArg->getType()->getAsCXXRecordDecl()))
295 return false;
296 return handleSwap(State, Call.getArgSVal(0), Call.getArgSVal(1), C);
297 }
298
299 if (matchesAny(Call, StdMakeUniqueCall, StdMakeUniqueForOverwriteCall)) {
300 if (!ModelSmartPtrDereference)
301 return false;
302
303 const std::optional<SVal> ThisRegionOpt =
304 Call.getReturnValueUnderConstruction();
305 if (!ThisRegionOpt)
306 return false;
307
308 const auto PtrVal = C.getSValBuilder().getConjuredHeapSymbolVal(
309 Call.getOriginExpr(), C.getLocationContext(),
310 getPointerTypeFromTemplateArg(Call, C), C.blockCount());
311
312 const MemRegion *ThisRegion = ThisRegionOpt->getAsRegion();
313 State = State->set<TrackedRegionMap>(ThisRegion, PtrVal);
314 State = State->assume(PtrVal, true);
315
316 // TODO: ExprEngine should do this for us.
317 // For a bit more context:
318 // 1) Why do we need this? Since we are modelling a "function"
319 // that returns a constructed object we need to store this information in
320 // the program state.
321 //
322 // 2) Why does this work?
323 // `updateObjectsUnderConstruction` does exactly as it sounds.
324 //
325 // 3) How should it look like when moved to the Engine?
326 // It would be nice if we can just
327 // pretend we don't need to know about this - ie, completely automatic work.
328 // However, realistically speaking, I think we would need to "signal" the
329 // ExprEngine evalCall handler that we are constructing an object with this
330 // function call (constructors obviously construct, hence can be
331 // automatically deduced).
332 auto &Engine = State->getStateManager().getOwningEngine();
333 State = Engine.updateObjectsUnderConstruction(
334 *ThisRegionOpt, nullptr, State, C.getLocationContext(),
335 Call.getConstructionContext(), {});
336
337 // We don't leave a note here since it is guaranteed the
338 // unique_ptr from this call is non-null (hence is safe to de-reference).
339 C.addTransition(State);
340 return true;
341 }
342
343 if (!smartptr::isStdSmartPtrCall(Call))
344 return false;
345
346 if (isBoolConversionMethod(Call)) {
347 const MemRegion *ThisR =
348 cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
349
350 if (ModelSmartPtrDereference) {
351 // The check for the region is moved is duplicated in handleBoolOperation
352 // method.
353 // FIXME: Once we model std::move for smart pointers clean up this and use
354 // that modeling.
355 handleBoolConversion(Call, C);
356 return true;
357 } else {
358 if (!move::isMovedFrom(State, ThisR)) {
359 // TODO: Model this case as well. At least, avoid invalidation of
360 // globals.
361 return false;
362 }
363
364 // TODO: Add a note to bug reports describing this decision.
365 C.addTransition(State->BindExpr(
366 Call.getOriginExpr(), C.getLocationContext(),
367 C.getSValBuilder().makeZeroVal(Call.getResultType())));
368
369 return true;
370 }
371 }
372
373 if (!ModelSmartPtrDereference)
374 return false;
375
376 if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) {
377 if (CC->getDecl()->isCopyConstructor())
378 return false;
379
380 const MemRegion *ThisRegion = CC->getCXXThisVal().getAsRegion();
381 if (!ThisRegion)
382 return false;
383
384 QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType();
385
386 if (CC->getDecl()->isMoveConstructor())
387 return handleMoveCtr(Call, C, ThisRegion);
388
389 if (Call.getNumArgs() == 0) {
390 auto NullVal = C.getSValBuilder().makeNullWithType(ThisType);
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 QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType();
648 auto ValueToUpdate = C.getSValBuilder().makeNullWithType(ThisType);
649 State = State->set<TrackedRegionMap>(ThisRegion, ValueToUpdate);
650
651 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
652 llvm::raw_ostream &OS) {
653 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
654 !BR.isInteresting(ThisRegion))
655 return;
656
657 OS << "Smart pointer";
658 checkAndPrettyPrintRegion(OS, ThisRegion);
659 OS << " is released and set to null";
660 }));
661 // TODO: Add support to enable MallocChecker to start tracking the raw
662 // pointer.
663 }
664
handleSwapMethod(const CallEvent & Call,CheckerContext & C) const665 void SmartPtrModeling::handleSwapMethod(const CallEvent &Call,
666 CheckerContext &C) const {
667 // To model unique_ptr::swap() method.
668 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
669 if (!IC)
670 return;
671
672 auto State = C.getState();
673 handleSwap(State, IC->getCXXThisVal(), Call.getArgSVal(0), C);
674 }
675
handleSwap(ProgramStateRef State,SVal First,SVal Second,CheckerContext & C) const676 bool SmartPtrModeling::handleSwap(ProgramStateRef State, SVal First,
677 SVal Second, CheckerContext &C) const {
678 const MemRegion *FirstThisRegion = First.getAsRegion();
679 if (!FirstThisRegion)
680 return false;
681 const MemRegion *SecondThisRegion = Second.getAsRegion();
682 if (!SecondThisRegion)
683 return false;
684
685 const auto *FirstInnerPtrVal = State->get<TrackedRegionMap>(FirstThisRegion);
686 const auto *SecondInnerPtrVal =
687 State->get<TrackedRegionMap>(SecondThisRegion);
688
689 State = updateSwappedRegion(State, FirstThisRegion, SecondInnerPtrVal);
690 State = updateSwappedRegion(State, SecondThisRegion, FirstInnerPtrVal);
691
692 C.addTransition(State, C.getNoteTag([FirstThisRegion, SecondThisRegion](
693 PathSensitiveBugReport &BR,
694 llvm::raw_ostream &OS) {
695 if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
696 return;
697 if (BR.isInteresting(FirstThisRegion) &&
698 !BR.isInteresting(SecondThisRegion)) {
699 BR.markInteresting(SecondThisRegion);
700 BR.markNotInteresting(FirstThisRegion);
701 }
702 if (BR.isInteresting(SecondThisRegion) &&
703 !BR.isInteresting(FirstThisRegion)) {
704 BR.markInteresting(FirstThisRegion);
705 BR.markNotInteresting(SecondThisRegion);
706 }
707 // TODO: We need to emit some note here probably!!
708 }));
709
710 return true;
711 }
712
handleGet(const CallEvent & Call,CheckerContext & C) const713 void SmartPtrModeling::handleGet(const CallEvent &Call,
714 CheckerContext &C) const {
715 ProgramStateRef State = C.getState();
716 const auto *IC = dyn_cast<CXXInstanceCall>(&Call);
717 if (!IC)
718 return;
719
720 const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion();
721 if (!ThisRegion)
722 return;
723
724 SVal InnerPointerVal;
725 std::tie(InnerPointerVal, State) = retrieveOrConjureInnerPtrVal(
726 State, ThisRegion, Call.getOriginExpr(), Call.getResultType(), C);
727 State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(),
728 InnerPointerVal);
729 // TODO: Add NoteTag, for how the raw pointer got using 'get' method.
730 C.addTransition(State);
731 }
732
handleAssignOp(const CallEvent & Call,CheckerContext & C) const733 bool SmartPtrModeling::handleAssignOp(const CallEvent &Call,
734 CheckerContext &C) const {
735 ProgramStateRef State = C.getState();
736 const auto *OC = dyn_cast<CXXMemberOperatorCall>(&Call);
737 if (!OC)
738 return false;
739 OverloadedOperatorKind OOK = OC->getOverloadedOperator();
740 if (OOK != OO_Equal)
741 return false;
742 const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion();
743 if (!ThisRegion)
744 return false;
745
746 QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType();
747
748 const MemRegion *OtherSmartPtrRegion = OC->getArgSVal(0).getAsRegion();
749 // In case of 'nullptr' or '0' assigned
750 if (!OtherSmartPtrRegion) {
751 bool AssignedNull = Call.getArgSVal(0).isZeroConstant();
752 if (!AssignedNull)
753 return false;
754 auto NullVal = C.getSValBuilder().makeNullWithType(ThisType);
755 State = State->set<TrackedRegionMap>(ThisRegion, NullVal);
756 C.addTransition(State, C.getNoteTag([ThisRegion](PathSensitiveBugReport &BR,
757 llvm::raw_ostream &OS) {
758 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
759 !BR.isInteresting(ThisRegion))
760 return;
761 OS << "Smart pointer";
762 checkAndPrettyPrintRegion(OS, ThisRegion);
763 OS << " is assigned to null";
764 }));
765 return true;
766 }
767
768 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call);
769 }
770
handleMoveCtr(const CallEvent & Call,CheckerContext & C,const MemRegion * ThisRegion) const771 bool SmartPtrModeling::handleMoveCtr(const CallEvent &Call, CheckerContext &C,
772 const MemRegion *ThisRegion) const {
773 const auto *OtherSmartPtrRegion = Call.getArgSVal(0).getAsRegion();
774 if (!OtherSmartPtrRegion)
775 return false;
776
777 return updateMovedSmartPointers(C, ThisRegion, OtherSmartPtrRegion, Call);
778 }
779
updateMovedSmartPointers(CheckerContext & C,const MemRegion * ThisRegion,const MemRegion * OtherSmartPtrRegion,const CallEvent & Call) const780 bool SmartPtrModeling::updateMovedSmartPointers(
781 CheckerContext &C, const MemRegion *ThisRegion,
782 const MemRegion *OtherSmartPtrRegion, const CallEvent &Call) const {
783 ProgramStateRef State = C.getState();
784 QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType();
785 const auto *OtherInnerPtr = State->get<TrackedRegionMap>(OtherSmartPtrRegion);
786 if (OtherInnerPtr) {
787 State = State->set<TrackedRegionMap>(ThisRegion, *OtherInnerPtr);
788
789 auto NullVal = C.getSValBuilder().makeNullWithType(ThisType);
790 State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
791 bool IsArgValNull = OtherInnerPtr->isZeroConstant();
792
793 C.addTransition(
794 State,
795 C.getNoteTag([ThisRegion, OtherSmartPtrRegion, IsArgValNull](
796 PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
797 if (&BR.getBugType() != smartptr::getNullDereferenceBugType())
798 return;
799 if (BR.isInteresting(OtherSmartPtrRegion)) {
800 OS << "Smart pointer";
801 checkAndPrettyPrintRegion(OS, OtherSmartPtrRegion);
802 OS << " is null after being moved to";
803 checkAndPrettyPrintRegion(OS, ThisRegion);
804 }
805 if (BR.isInteresting(ThisRegion) && IsArgValNull) {
806 OS << "A null pointer value is moved to";
807 checkAndPrettyPrintRegion(OS, ThisRegion);
808 BR.markInteresting(OtherSmartPtrRegion);
809 }
810 }));
811 return true;
812 } else {
813 // In case we dont know anything about value we are moving from
814 // remove the entry from map for which smart pointer got moved to.
815 // For unique_ptr<A>, Ty will be 'A*'.
816 auto NullVal = C.getSValBuilder().makeNullWithType(ThisType);
817 State = State->remove<TrackedRegionMap>(ThisRegion);
818 State = State->set<TrackedRegionMap>(OtherSmartPtrRegion, NullVal);
819 C.addTransition(State, C.getNoteTag([OtherSmartPtrRegion,
820 ThisRegion](PathSensitiveBugReport &BR,
821 llvm::raw_ostream &OS) {
822 if (&BR.getBugType() != smartptr::getNullDereferenceBugType() ||
823 !BR.isInteresting(OtherSmartPtrRegion))
824 return;
825 OS << "Smart pointer";
826 checkAndPrettyPrintRegion(OS, OtherSmartPtrRegion);
827 OS << " is null after; previous value moved to";
828 checkAndPrettyPrintRegion(OS, ThisRegion);
829 }));
830 return true;
831 }
832 return false;
833 }
834
handleBoolConversion(const CallEvent & Call,CheckerContext & C) const835 void SmartPtrModeling::handleBoolConversion(const CallEvent &Call,
836 CheckerContext &C) const {
837 // To model unique_ptr::operator bool
838 ProgramStateRef State = C.getState();
839 const Expr *CallExpr = Call.getOriginExpr();
840 const MemRegion *ThisRegion =
841 cast<CXXInstanceCall>(&Call)->getCXXThisVal().getAsRegion();
842
843 QualType ThisType = cast<CXXMethodDecl>(Call.getDecl())->getThisType();
844
845 SVal InnerPointerVal;
846 if (const auto *InnerValPtr = State->get<TrackedRegionMap>(ThisRegion)) {
847 InnerPointerVal = *InnerValPtr;
848 } else {
849 // In case of inner pointer SVal is not available we create
850 // conjureSymbolVal for inner pointer value.
851 auto InnerPointerType = getInnerPointerType(Call, C);
852 if (InnerPointerType.isNull())
853 return;
854
855 const LocationContext *LC = C.getLocationContext();
856 InnerPointerVal = C.getSValBuilder().conjureSymbolVal(
857 CallExpr, LC, InnerPointerType, C.blockCount());
858 State = State->set<TrackedRegionMap>(ThisRegion, InnerPointerVal);
859 }
860
861 if (State->isNull(InnerPointerVal).isConstrainedTrue()) {
862 State = State->BindExpr(CallExpr, C.getLocationContext(),
863 C.getSValBuilder().makeTruthVal(false));
864
865 C.addTransition(State);
866 return;
867 } else if (State->isNonNull(InnerPointerVal).isConstrainedTrue()) {
868 State = State->BindExpr(CallExpr, C.getLocationContext(),
869 C.getSValBuilder().makeTruthVal(true));
870
871 C.addTransition(State);
872 return;
873 } else if (move::isMovedFrom(State, ThisRegion)) {
874 C.addTransition(
875 State->BindExpr(CallExpr, C.getLocationContext(),
876 C.getSValBuilder().makeZeroVal(Call.getResultType())));
877 return;
878 } else {
879 ProgramStateRef NotNullState, NullState;
880 std::tie(NotNullState, NullState) =
881 State->assume(InnerPointerVal.castAs<DefinedOrUnknownSVal>());
882
883 auto NullVal = C.getSValBuilder().makeNullWithType(ThisType);
884 // Explicitly tracking the region as null.
885 NullState = NullState->set<TrackedRegionMap>(ThisRegion, NullVal);
886
887 NullState = NullState->BindExpr(CallExpr, C.getLocationContext(),
888 C.getSValBuilder().makeTruthVal(false));
889 C.addTransition(NullState, C.getNoteTag(
890 [ThisRegion](PathSensitiveBugReport &BR,
891 llvm::raw_ostream &OS) {
892 OS << "Assuming smart pointer";
893 checkAndPrettyPrintRegion(OS, ThisRegion);
894 OS << " is null";
895 },
896 /*IsPrunable=*/true));
897 NotNullState =
898 NotNullState->BindExpr(CallExpr, C.getLocationContext(),
899 C.getSValBuilder().makeTruthVal(true));
900 C.addTransition(
901 NotNullState,
902 C.getNoteTag(
903 [ThisRegion](PathSensitiveBugReport &BR, llvm::raw_ostream &OS) {
904 OS << "Assuming smart pointer";
905 checkAndPrettyPrintRegion(OS, ThisRegion);
906 OS << " is non-null";
907 },
908 /*IsPrunable=*/true));
909 return;
910 }
911 }
912
registerSmartPtrModeling(CheckerManager & Mgr)913 void ento::registerSmartPtrModeling(CheckerManager &Mgr) {
914 auto *Checker = Mgr.registerChecker<SmartPtrModeling>();
915 Checker->ModelSmartPtrDereference =
916 Mgr.getAnalyzerOptions().getCheckerBooleanOption(
917 Checker, "ModelSmartPtrDereference");
918 }
919
shouldRegisterSmartPtrModeling(const CheckerManager & mgr)920 bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) {
921 const LangOptions &LO = mgr.getLangOpts();
922 return LO.CPlusPlus;
923 }
924