Index: clang/docs/analyzer/checkers.rst =================================================================== --- clang/docs/analyzer/checkers.rst +++ clang/docs/analyzer/checkers.rst @@ -1836,6 +1836,19 @@ [x retain]; // warn } +.. _alpha-cplusplus-SmartPtr: + +alpha.cplusplus.SmartPtr (C++) +"""""""""""""""""""""""""""""" +Check for dereference of null smart pointers. + +.. code-block:: cpp + + void deref_smart_ptr() { + std::unique_ptr P; + *P; // warn: dereference of a default constructed smart unique_ptr + } + alpha.llvm ^^^^^^^^^^ Index: clang/include/clang/StaticAnalyzer/Checkers/Checkers.td =================================================================== --- clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -571,9 +571,17 @@ Documentation, Hidden; -def SmartPtrModeling: Checker<"SmartPtr">, +def SmartPtrModeling: Checker<"SmartPtrModeling">, HelpText<"Model behavior of C++ smart pointers">, Documentation, + CheckerOptions<[ + CmdLineOption, + ]>, Hidden; def MoveChecker: Checker<"Move">, @@ -730,6 +738,11 @@ Dependencies<[IteratorModeling]>, Documentation; +def SmartPtrChecker: Checker<"SmartPtr">, + HelpText<"Find the dereference of null SmrtPtr">, + Dependencies<[SmartPtrModeling]>, + Documentation; + } // end: "alpha.cplusplus" Index: clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h =================================================================== --- clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -788,6 +788,10 @@ // to implicit this-parameter on the declaration. return CallArgumentIndex + 1; } + + OverloadedOperatorKind getOverloadedOperator() const { + return getOriginExpr()->getOperator(); + } }; /// Represents an implicit call to a C++ destructor. Index: clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt =================================================================== --- clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -98,6 +98,7 @@ ReturnValueChecker.cpp RunLoopAutoreleaseLeakChecker.cpp SimpleStreamChecker.cpp + SmartPtrChecker.cpp SmartPtrModeling.cpp StackAddrEscapeChecker.cpp StdLibraryFunctionsChecker.cpp Index: clang/lib/StaticAnalyzer/Checkers/SmartPtr.h =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/SmartPtr.h @@ -0,0 +1,40 @@ +//=== SmartPtr.h - Tracking smart pointer state. -------------------*- C++ -*-// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines inter-checker API for the smart pointer modeling. It allows +// dependent checkers to figure out if an smart pointer is null or not. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SMARTPTR_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SMARTPTR_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" + +namespace clang { +namespace ento { +namespace smartptr { + +/// Set of STL smart pointer class which we are trying to model. +const llvm::StringSet<> StdSmartPtrs = { + "shared_ptr", + "unique_ptr", + "weak_ptr", +}; + +/// Returns true if the event call is on smart pointer. +bool isStdSmartPtrCall(const CallEvent &Call); + +/// Returns whether the smart pointer is null or not. +bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion); + +} // namespace smartptr +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SMARTPTR_H Index: clang/lib/StaticAnalyzer/Checkers/SmartPtrChecker.cpp =================================================================== --- /dev/null +++ clang/lib/StaticAnalyzer/Checkers/SmartPtrChecker.cpp @@ -0,0 +1,80 @@ +// SmartPtrChecker.cpp - Check for smart pointer dereference - C++ --------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This file defines a checker that check for null dereference of C++ smart +// pointer. +// +//===----------------------------------------------------------------------===// +#include "SmartPtr.h" + +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" + +using namespace clang; +using namespace ento; + +namespace { +class SmartPtrChecker : public Checker { + BugType NullDereferenceBugType{this, "Null SmartPtr dereference", + "C++ Smart Pointer"}; + +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +private: + void reportBug(CheckerContext &C, const CallEvent &Call) const; +}; +} // end of anonymous namespace + +void SmartPtrChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!smartptr::isStdSmartPtrCall(Call)) + return; + ProgramStateRef State = C.getState(); + const auto *OC = dyn_cast(&Call); + if (!OC) + return; + const MemRegion *ThisRegion = OC->getCXXThisVal().getAsRegion(); + if (!ThisRegion) + return; + + OverloadedOperatorKind OOK = OC->getOverloadedOperator(); + if (OOK == OO_Star || OOK == OO_Arrow) { + if (smartptr::isNullSmartPtr(State, ThisRegion)) + reportBug(C, Call); + } +} + +void SmartPtrChecker::reportBug(CheckerContext &C, + const CallEvent &Call) const { + ExplodedNode *ErrNode = C.generateErrorNode(); + if (!ErrNode) + return; + + auto R = std::make_unique( + NullDereferenceBugType, "Dereference of null smart pointer", ErrNode); + C.emitReport(std::move(R)); +} + +void ento::registerSmartPtrChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterSmartPtrChecker(const CheckerManager &mgr) { + const LangOptions &LO = mgr.getLangOpts(); + return LO.CPlusPlus; +} Index: clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp =================================================================== --- clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -12,27 +12,80 @@ //===----------------------------------------------------------------------===// #include "Move.h" +#include "SmartPtr.h" +#include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" +#include "clang/AST/Type.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" using namespace clang; using namespace ento; namespace { -class SmartPtrModeling : public Checker { +class SmartPtrModeling : public Checker { + bool isNullAfterMoveMethod(const CallEvent &Call) const; public: + // Whether the checker should model for null dereferences of smart pointers. + DefaultBool ModelSmartPtrDereference; bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + +private: + ProgramStateRef updateTrackedRegion(const CallEvent &Call, CheckerContext &C, + const MemRegion *ThisValRegion) const; + void handleReset(const CallEvent &Call, CheckerContext &C) const; + void handleRelease(const CallEvent &Call, CheckerContext &C) const; + void handleSwap(const CallEvent &Call, CheckerContext &C) const; + + using SmartPtrMethodHandlerFn = + void (SmartPtrModeling::*)(const CallEvent &Call, CheckerContext &) const; + CallDescriptionMap SmartPtrMethodHandlers{ + {{"reset"}, &SmartPtrModeling::handleReset}, + {{"release"}, &SmartPtrModeling::handleRelease}, + {{"swap", 1}, &SmartPtrModeling::handleSwap}}; }; } // end of anonymous namespace +REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, SVal) + +// Define the inter-checker API. +namespace clang { +namespace ento { +namespace smartptr { +bool isStdSmartPtrCall(const CallEvent &Call) { + const auto *MethodDecl = dyn_cast_or_null(Call.getDecl()); + if (!MethodDecl || !MethodDecl->getParent()) + return false; + + const auto *RecordDecl = MethodDecl->getParent(); + if (!RecordDecl || !RecordDecl->getDeclContext()->isStdNamespace()) + return false; + + if (RecordDecl->getDeclName().isIdentifier()) { + return smartptr::StdSmartPtrs.count(RecordDecl->getName().lower()); + } + return false; +} + +bool isNullSmartPtr(const ProgramStateRef State, const MemRegion *ThisRegion) { + const auto *InnerPointVal = State->get(ThisRegion); + return InnerPointVal && InnerPointVal->isZeroConstant(); +} +} // namespace smartptr +} // namespace ento +} // namespace clang + bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { // TODO: Update CallDescription to support anonymous calls? // TODO: Handle other methods, such as .get() or .release(). @@ -44,27 +97,132 @@ bool SmartPtrModeling::evalCall(const CallEvent &Call, CheckerContext &C) const { - if (!isNullAfterMoveMethod(Call)) + + if (!smartptr::isStdSmartPtrCall(Call)) return false; - ProgramStateRef State = C.getState(); - const MemRegion *ThisR = - cast(&Call)->getCXXThisVal().getAsRegion(); + if (isNullAfterMoveMethod(Call)) { + ProgramStateRef State = C.getState(); + const MemRegion *ThisR = + cast(&Call)->getCXXThisVal().getAsRegion(); + + if (!move::isMovedFrom(State, ThisR)) { + // TODO: Model this case as well. At least, avoid invalidation of globals. + return false; + } + + // TODO: Add a note to bug reports describing this decision. + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeZeroVal(Call.getResultType()))); + return true; + } - if (!move::isMovedFrom(State, ThisR)) { - // TODO: Model this case as well. At least, avoid invalidation of globals. + if (!ModelSmartPtrDereference) return false; + + if (const auto *CC = dyn_cast(&Call)) { + if (CC->getDecl()->isCopyOrMoveConstructor()) + return false; + + const MemRegion *ThisValRegion = CC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return false; + + auto State = updateTrackedRegion(Call, C, ThisValRegion); + C.addTransition(State); + return true; + } + + const SmartPtrMethodHandlerFn *Handler = SmartPtrMethodHandlers.lookup(Call); + if (!Handler) + return false; + (this->**Handler)(Call, C); + + return C.isDifferent(); +} + +void SmartPtrModeling::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + // Clean up dead regions from the region map. + TrackedRegionMapTy TrackedRegions = State->get(); + for (auto E : TrackedRegions) { + const MemRegion *Region = E.first; + bool IsRegDead = !SymReaper.isLiveRegion(Region); + + if (IsRegDead) + State = State->remove(Region); + } + C.addTransition(State); +} + +void SmartPtrModeling::handleReset(const CallEvent &Call, + CheckerContext &C) const { + const auto *IC = dyn_cast(&Call); + if (!IC) + return; + + const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return; + auto State = updateTrackedRegion(Call, C, ThisValRegion); + C.addTransition(State); + // TODO: Make sure to ivalidate the the region in the Store if we don't have + // time to model all methods. +} + +void SmartPtrModeling::handleRelease(const CallEvent &Call, + CheckerContext &C) const { + const auto *IC = dyn_cast(&Call); + if (!IC) + return; + + const MemRegion *ThisValRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisValRegion) + return; + + auto State = updateTrackedRegion(Call, C, ThisValRegion); + + const auto *InnerPointVal = State->get(ThisValRegion); + if (InnerPointVal) { + State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + *InnerPointVal); + } + C.addTransition(State); + // TODO: Add support to enable MallocChecker to start tracking the raw + // pointer. +} + +void SmartPtrModeling::handleSwap(const CallEvent &Call, + CheckerContext &C) const { + // TODO: Add support to handle swap method. +} + +ProgramStateRef +SmartPtrModeling::updateTrackedRegion(const CallEvent &Call, CheckerContext &C, + const MemRegion *ThisValRegion) const { + ProgramStateRef State = C.getState(); + auto NumArgs = Call.getNumArgs(); + + if (NumArgs == 0) { + auto NullSVal = C.getSValBuilder().makeNull(); + State = State->set(ThisValRegion, NullSVal); + } else if (NumArgs == 1) { + auto ArgVal = Call.getArgSVal(0); + assert(Call.getArgExpr(0)->getType()->isPointerType() && + "Adding a non pointer value to TrackedRegionMap"); + State = State->set(ThisValRegion, ArgVal); } - // TODO: Add a note to bug reports describing this decision. - C.addTransition( - State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), - C.getSValBuilder().makeZeroVal(Call.getResultType()))); - return true; + return State; } void ento::registerSmartPtrModeling(CheckerManager &Mgr) { - Mgr.registerChecker(); + auto *Checker = Mgr.registerChecker(); + Checker->ModelSmartPtrDereference = + Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Checker, "ModelSmartPtrDereference"); } bool ento::shouldRegisterSmartPtrModeling(const CheckerManager &mgr) { Index: clang/test/Analysis/Inputs/system-header-simulator-cxx.h =================================================================== --- clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -946,10 +946,15 @@ template // TODO: Implement the stub for deleter. class unique_ptr { public: + unique_ptr() {} + unique_ptr(T *) {} unique_ptr(const unique_ptr &) = delete; unique_ptr(unique_ptr &&); T *get() const; + T *release() const; + void reset(T *p = nullptr) const; + void swap(unique_ptr &p) const; typename std::add_lvalue_reference::type operator*() const; T *operator->() const; Index: clang/test/Analysis/analyzer-config.c =================================================================== --- clang/test/Analysis/analyzer-config.c +++ clang/test/Analysis/analyzer-config.c @@ -39,6 +39,7 @@ // CHECK-NEXT: core.CallAndMessage:ParameterCount = true // CHECK-NEXT: core.CallAndMessage:UndefReceiver = true // CHECK-NEXT: cplusplus.Move:WarnOn = KnownsAndLocals +// CHECK-NEXT: cplusplus.SmartPtrModeling:ModelSmartPtrDereference = false // CHECK-NEXT: crosscheck-with-z3 = false // CHECK-NEXT: ctu-dir = "" // CHECK-NEXT: ctu-import-threshold = 100 Index: clang/test/Analysis/smart-ptr.cpp =================================================================== --- clang/test/Analysis/smart-ptr.cpp +++ clang/test/Analysis/smart-ptr.cpp @@ -1,16 +1,18 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection\ -// RUN: -analyzer-checker cplusplus.Move,cplusplus.SmartPtr\ +// RUN: -analyzer-checker cplusplus.Move,alpha.cplusplus.SmartPtr\ +// RUN: -analyzer-config cplusplus.SmartPtrModeling:ModelSmartPtrDereference=true\ // RUN: -std=c++11 -verify %s #include "Inputs/system-header-simulator-cxx.h" void clang_analyzer_warnIfReached(); +void clang_analyzer_numTimesReached(); void derefAfterMove(std::unique_ptr P) { std::unique_ptr Q = std::move(P); if (Q) clang_analyzer_warnIfReached(); // expected-warning{{REACHABLE}} - *Q.get() = 1; // no-warning + *Q.get() = 1; // no-warning if (P) clang_analyzer_warnIfReached(); // no-warning // TODO: Report a null dereference (instead). @@ -26,3 +28,76 @@ (s->*func)(); // no-crash } } // namespace testUnknownCallee + +class A { +public: + A(){}; + void foo(); +}; + +A *return_null() { + return nullptr; +} + +void derefAfterValidCtr() { + std::unique_ptr P(new A()); + P->foo(); // No warning. +} + +void derefOfUnknown(std::unique_ptr P) { + P->foo(); // No warning. +} + +void derefAfterDefaultCtr() { + std::unique_ptr P; + P->foo(); // expected-warning {{Dereference of null smart pointer [alpha.cplusplus.SmartPtr]}} +} + +void derefAfterCtrWithNull() { + std::unique_ptr P(nullptr); + *P; // expected-warning {{Dereference of null smart pointer [alpha.cplusplus.SmartPtr]}} +} + +void derefAfterCtrWithNullReturnMethod() { + std::unique_ptr P(return_null()); + P->foo(); // expected-warning {{Dereference of null smart pointer [alpha.cplusplus.SmartPtr]}} +} + +void derefAfterRelease() { + std::unique_ptr P(new A()); + P.release(); + clang_analyzer_numTimesReached(); // expected-warning {{1}} + P->foo(); // expected-warning {{Dereference of null smart pointer [alpha.cplusplus.SmartPtr]}} +} + +void derefAfterReset() { + std::unique_ptr P(new A()); + P.reset(); + clang_analyzer_numTimesReached(); // expected-warning {{1}} + P->foo(); // expected-warning {{Dereference of null smart pointer [alpha.cplusplus.SmartPtr]}} +} + +void derefAfterResetWithNull() { + std::unique_ptr P(new A()); + P.reset(nullptr); + P->foo(); // expected-warning {{Dereference of null smart pointer [alpha.cplusplus.SmartPtr]}} +} + +void derefAfterResetWithNonNull() { + std::unique_ptr P; + P.reset(new A()); + P->foo(); // No warning. +} + +void derefAfterReleaseAndResetWithNonNull() { + std::unique_ptr P(new A()); + P.release(); + P.reset(new A()); + P->foo(); // No warning. +} + +void derefOnReleasedNullRawPtr() { + std::unique_ptr P; + A *AP = P.release(); + AP->foo(); // expected-warning {{Called C++ object pointer is null [core.CallAndMessage]}} +} Index: clang/test/Analysis/use-after-move.cpp =================================================================== --- clang/test/Analysis/use-after-move.cpp +++ clang/test/Analysis/use-after-move.cpp @@ -1,36 +1,36 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=unexplored_first_queue\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,peaceful,non-aggressive // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,peaceful,non-aggressive // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=unexplored_first_queue\ // RUN: -analyzer-config cplusplus.Move:WarnOn=KnownsOnly\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,non-aggressive // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move -verify %s\ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ // RUN: -analyzer-config cplusplus.Move:WarnOn=KnownsOnly\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,non-aggressive // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=unexplored_first_queue\ // RUN: -analyzer-config cplusplus.Move:WarnOn=All\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,peaceful,aggressive // RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.Move %s\ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ // RUN: -analyzer-config cplusplus.Move:WarnOn=All\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,peaceful,aggressive // RUN: not %clang_analyze_cc1 -verify %s \ @@ -49,7 +49,7 @@ // RUN: -std=c++11 -analyzer-output=text -analyzer-config eagerly-assume=false\ // RUN: -analyzer-config exploration_strategy=dfs -DDFS\ // RUN: -analyzer-config cplusplus.Move:WarnOn=All -DAGGRESSIVE_DFS\ -// RUN: -analyzer-checker core,cplusplus.SmartPtr,debug.ExprInspection\ +// RUN: -analyzer-checker core,cplusplus.SmartPtrModeling,debug.ExprInspection\ // RUN: -verify=expected,peaceful,aggressive %s 2>&1 | FileCheck %s #include "Inputs/system-header-simulator-cxx.h"