From 740d490593e0de8732a697c9f77b90ddd463863b Mon Sep 17 00:00:00 2001 From: Jordan Rose Date: Mon, 2 Jul 2012 19:27:35 +0000 Subject: [PATCH] [analyzer] Add a new abstraction over all types of calls: CallEvent This is intended to replace CallOrObjCMessage, and is eventually intended to be used for anything that cares more about /what/ is being called than /how/ it's being called. For example, inlining destructors should be the same as inlining blocks, and checking __attribute__((nonnull)) should apply to the allocator calls generated by operator new. git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@159554 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/StaticAnalyzer/Core/Checker.h | 2 +- .../StaticAnalyzer/Core/CheckerManager.h | 4 +- .../StaticAnalyzer/Core/PathSensitive/Calls.h | 356 ++++++++++++++++++ .../Core/PathSensitive/ExprEngine.h | 8 +- .../Core/PathSensitive/ProgramState.h | 6 +- .../StaticAnalyzer/Core/PathSensitive/Store.h | 4 +- .../Core/PathSensitive/SubEngine.h | 2 +- .../Checkers/CStringChecker.cpp | 4 +- .../Checkers/CheckerDocumentation.cpp | 2 +- lib/StaticAnalyzer/Checkers/MallocChecker.cpp | 248 ++++++------ .../Checkers/RetainCountChecker.cpp | 4 +- lib/StaticAnalyzer/Core/CMakeLists.txt | 1 + lib/StaticAnalyzer/Core/Calls.cpp | 353 +++++++++++++++++ lib/StaticAnalyzer/Core/CheckerManager.cpp | 2 +- lib/StaticAnalyzer/Core/ExprEngine.cpp | 2 +- lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 6 +- .../Core/ExprEngineCallAndReturn.cpp | 188 +-------- lib/StaticAnalyzer/Core/ExprEngineObjC.cpp | 15 +- lib/StaticAnalyzer/Core/ProgramState.cpp | 4 +- lib/StaticAnalyzer/Core/RegionStore.cpp | 6 +- test/Analysis/blocks-no-inline.c | 13 + 21 files changed, 890 insertions(+), 340 deletions(-) create mode 100644 include/clang/StaticAnalyzer/Core/PathSensitive/Calls.h create mode 100644 lib/StaticAnalyzer/Core/Calls.cpp create mode 100644 test/Analysis/blocks-no-inline.c diff --git a/include/clang/StaticAnalyzer/Core/Checker.h b/include/clang/StaticAnalyzer/Core/Checker.h index 76d8c15f75..fb224ef158 100644 --- a/include/clang/StaticAnalyzer/Core/Checker.h +++ b/include/clang/StaticAnalyzer/Core/Checker.h @@ -266,7 +266,7 @@ class RegionChanges { const StoreManager::InvalidatedSymbols *invalidated, ArrayRef Explicits, ArrayRef Regions, - const CallOrObjCMessage *Call) { + const CallEvent *Call) { return ((const CHECKER *)checker)->checkRegionChanges(state, invalidated, Explicits, Regions, Call); } diff --git a/include/clang/StaticAnalyzer/Core/CheckerManager.h b/include/clang/StaticAnalyzer/Core/CheckerManager.h index b9d6a32918..3202fbe6b8 100644 --- a/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -294,7 +294,7 @@ public: const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call); + const CallEvent *Call); /// \brief Run checkers for handling assumptions on symbolic values. ProgramStateRef runCheckersForEvalAssume(ProgramStateRef state, @@ -373,7 +373,7 @@ public: const StoreManager::InvalidatedSymbols *symbols, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call)> + const CallEvent *Call)> CheckRegionChangesFunc; typedef CheckerFn WantsRegionChangeUpdateFunc; diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/Calls.h b/include/clang/StaticAnalyzer/Core/PathSensitive/Calls.h new file mode 100644 index 0000000000..45b5fc70a2 --- /dev/null +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/Calls.h @@ -0,0 +1,356 @@ +//===- Calls.h - Wrapper for all function and method calls --------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +/// \file This file defines CallEvent and its subclasses, which represent path- +/// sensitive instances of different kinds of function and method calls +/// (C, C++, and Objective-C). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_PATHSENSITIVE_CALL +#define LLVM_CLANG_STATICANALYZER_PATHSENSITIVE_CALL + +#include "clang/Basic/SourceManager.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/ExprCXX.h" +#include "clang/AST/ExprObjC.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" + +namespace clang { +namespace ento { + +enum CallEventKind { + CE_Function, + CE_CXXMember, + CE_Block, + CE_BEG_SIMPLE_CALLS = CE_Function, + CE_END_SIMPLE_CALLS = CE_Block, + CE_CXXConstructor, + CE_BEG_FUNCTION_CALLS = CE_Function, + CE_END_FUNCTION_CALLS = CE_CXXConstructor, + CE_ObjCMessage +}; + +/// \brief Represents an abstract call to a function or method along a +/// particular path. +class CallEvent { +public: + typedef CallEventKind Kind; + +protected: + ProgramStateRef State; + const LocationContext *LCtx; + const Kind K; + + CallEvent(ProgramStateRef state, const LocationContext *lctx, Kind k) + : State(state), LCtx(lctx), K(k) {} + virtual ~CallEvent() {} + + /// \brief Get the value of arbitrary expressions at this point in the path. + SVal getSVal(const Stmt *S) const { + return State->getSVal(S, LCtx); + } + + typedef SmallVectorImpl RegionList; + + /// \brief Used to specify non-argument regions that will be invalidated as a + /// result of this call. + virtual void addExtraInvalidatedRegions(RegionList &Regions) const {} + + typedef const ParmVarDecl * const *param_iterator; + virtual param_iterator param_begin() const = 0; + virtual param_iterator param_end() const = 0; + + virtual QualType getDeclaredResultType() const { return QualType(); } + +public: + /// \brief Returns the declaration of the function or method that will be + /// called. May be null. + virtual const Decl *getDecl() const = 0; + + /// \brief Returns the expression whose value will be the result of this call. + /// May be null. + virtual const Expr *getOriginExpr() const = 0; + + /// \brief Returns the number of arguments (explicit and implicit). + /// + /// Note that this may be greater than the number of parameters in the + /// callee's declaration, and that it may include arguments not written in + /// the source. + virtual unsigned getNumArgs() const = 0; + + /// \brief Returns true if the callee is known to be from a system header. + bool isInSystemHeader() const { + const Decl *D = getDecl(); + if (!D) + return false; + + SourceLocation Loc = D->getLocation(); + if (Loc.isValid()) { + const SourceManager &SM = + State->getStateManager().getContext().getSourceManager(); + return SM.isInSystemHeader(D->getLocation()); + } + + // Special case for implicitly-declared global operator new/delete. + // These should be considered system functions. + if (const FunctionDecl *FD = dyn_cast(D)) + return FD->isOverloadedOperator() && FD->isImplicit() && FD->isGlobal(); + + return false; + } + + /// \brief Returns the kind of call this is. + Kind getKind() const { return K; } + + /// \brief Returns the value of a given argument at the time of the call. + virtual SVal getArgSVal(unsigned Index) const; + + /// \brief Returns the expression associated with a given argument. + /// May be null if this expression does not appear in the source. + virtual const Expr *getArgExpr(unsigned Index) const { + return 0; + } + + /// \brief Returns the source range for errors associated with this argument. + /// May be invalid if the argument is not written in the source. + // FIXME: Is it better to return an invalid range or the range of the origin + // expression? + virtual SourceRange getArgSourceRange(unsigned Index) const; + + /// \brief Returns the result type, adjusted for references. + QualType getResultType() const; + + /// \brief Returns true if any of the arguments appear to represent callbacks. + bool hasNonZeroCallbackArg() const; + + /// \brief Returns a new state with all argument regions invalidated. + /// + /// This accepts an alternate state in case some processing has already + /// occurred. + ProgramStateRef invalidateRegions(unsigned BlockCount, + ProgramStateRef Orig = 0) const; + + // Iterator access to parameter types. +private: + typedef std::const_mem_fun_t get_type_fun; + +public: + typedef llvm::mapped_iterator + param_type_iterator; + + param_type_iterator param_type_begin() const { + return llvm::map_iterator(param_begin(), + get_type_fun(&ParmVarDecl::getType)); + } + param_type_iterator param_type_end() const { + return llvm::map_iterator(param_end(), get_type_fun(&ParmVarDecl::getType)); + } + + static bool classof(const CallEvent *) { return true; } +}; + +/// \brief Represents a call to any sort of function that might have a +/// FunctionDecl. +class AnyFunctionCall : public CallEvent { +protected: + AnyFunctionCall(ProgramStateRef St, const LocationContext *LCtx, Kind K) + : CallEvent(St, LCtx, K) {} + + param_iterator param_begin() const; + param_iterator param_end() const; + + QualType getDeclaredResultType() const; + +public: + virtual const FunctionDecl *getDecl() const = 0; + + static bool classof(const CallEvent *CA) { + return CA->getKind() >= CE_BEG_FUNCTION_CALLS && + CA->getKind() <= CE_END_FUNCTION_CALLS; + } +}; + +/// \brief Represents a call to a written as a CallExpr. +class SimpleCall : public AnyFunctionCall { + const CallExpr *CE; + +protected: + SimpleCall(const CallExpr *ce, ProgramStateRef St, + const LocationContext *LCtx, Kind K) + : AnyFunctionCall(St, LCtx, K), CE(ce) { + } + +public: + const CallExpr *getOriginExpr() const { return CE; } + + const FunctionDecl *getDecl() const; + + unsigned getNumArgs() const { return CE->getNumArgs(); } + + const Expr *getArgExpr(unsigned Index) const { + return CE->getArg(Index); + } + + static bool classof(const CallEvent *CA) { + return CA->getKind() >= CE_BEG_SIMPLE_CALLS && + CA->getKind() <= CE_END_SIMPLE_CALLS; + } +}; + +/// \brief Represents a C function or static C++ member function call. +/// +/// Example: \c fun() +class FunctionCall : public SimpleCall { +public: + FunctionCall(const CallExpr *CE, ProgramStateRef St, + const LocationContext *LCtx) + : SimpleCall(CE, St, LCtx, CE_Function) {} + + static bool classof(const CallEvent *CA) { + return CA->getKind() == CE_Function; + } +}; + +/// \brief Represents a non-static C++ member function call. +/// +/// Example: \c obj.fun() +class CXXMemberCall : public SimpleCall { +protected: + void addExtraInvalidatedRegions(RegionList &Regions) const; + +public: + CXXMemberCall(const CXXMemberCallExpr *CE, ProgramStateRef St, + const LocationContext *LCtx) + : SimpleCall(CE, St, LCtx, CE_CXXMember) {} + + const CXXMemberCallExpr *getOriginExpr() const { + return cast(SimpleCall::getOriginExpr()); + } + + static bool classof(const CallEvent *CA) { + return CA->getKind() == CE_CXXMember; + } +}; + +/// \brief Represents a call to a block. +/// +/// Example: \c ^{ /* ... */ }() +class BlockCall : public SimpleCall { +protected: + void addExtraInvalidatedRegions(RegionList &Regions) const; + + param_iterator param_begin() const; + param_iterator param_end() const; + + QualType getDeclaredResultType() const; + +public: + BlockCall(const CallExpr *CE, ProgramStateRef St, + const LocationContext *LCtx) + : SimpleCall(CE, St, LCtx, CE_Block) { + assert(isa(getSVal(CE->getCallee()).getAsRegion())); + } + + static bool classof(const CallEvent *CA) { + return CA->getKind() == CE_Block; + } + +private: + const BlockDataRegion *getBlockRegion() const; +}; + +/// \brief Represents a call to a C++ constructor. +/// +/// Example: \c T(1) +class CXXConstructorCall : public AnyFunctionCall { + const CXXConstructExpr *CE; + const MemRegion *Target; + +protected: + void addExtraInvalidatedRegions(RegionList &Regions) const; + +public: + CXXConstructorCall(const CXXConstructExpr *ce, ProgramStateRef St, + const LocationContext *LCtx) + : AnyFunctionCall(St, LCtx, CE_CXXConstructor), CE(ce), Target(0) {} + CXXConstructorCall(const CXXConstructExpr *ce, const MemRegion *target, + ProgramStateRef St, const LocationContext *LCtx) + : AnyFunctionCall(St, LCtx, CE_CXXConstructor), CE(ce), Target(target) {} + + const CXXConstructExpr *getOriginExpr() const { return CE; } + + const CXXConstructorDecl *getDecl() const { + return CE->getConstructor(); + } + + unsigned getNumArgs() const { return CE->getNumArgs(); } + + const Expr *getArgExpr(unsigned Index) const { + return CE->getArg(Index); + } + + static bool classof(const CallEvent *CA) { + return CA->getKind() == CE_CXXConstructor; + } +}; + +/// \brief Represents any expression that causes an Objective-C message send. +// +// This class is mostly passthrough to ObjCMessage, because /that/ class is the +// adapter for the different kinds of Objective-C messages in the system. The +// difference here is that like other CallActions this refers to a specific +// (path-sensitive) message send, while ObjCMessage is simply a wrapper for the +// various (path-insensitive) expressions that are implemented using messages. +class ObjCMessageInvocation : public CallEvent { + ObjCMessage Msg; + +protected: + void addExtraInvalidatedRegions(RegionList &Regions) const; + + param_iterator param_begin() const; + param_iterator param_end() const; + + QualType getDeclaredResultType() const; + +public: + ObjCMessageInvocation(const ObjCMessage &msg, ProgramStateRef St, + const LocationContext *LCtx) + : CallEvent(St, LCtx, CE_ObjCMessage), Msg(msg) {} + + Selector getSelector() const { return Msg.getSelector(); } + bool isInstanceMessage() const { return Msg.isInstanceMessage(); } + const ObjCMethodDecl *getDecl() const { return Msg.getMethodDecl(); } + unsigned getNumArgs() const { return Msg.getNumArgs(); } + const Expr *getArgExpr(unsigned Index) const { return Msg.getArgExpr(Index); } + + // FIXME: for emitting warnings and such this may not be the best idea. + const Expr *getOriginExpr() const { return Msg.getMessageExpr(); } + + SVal getReceiverSVal() const; + + SourceRange getReceiverSourceRange() const { + return Msg.getReceiverSourceRange(); + } + + const ObjCInterfaceDecl *getReceiverInterface() const { + return Msg.getReceiverInterface(); + } + + static bool classof(const CallEvent *CA) { + return CA->getKind() == CE_ObjCMessage; + } +}; + +} // end namespace ento +} // end namespace clang + +#endif diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 0cccd41d3c..46fb70deae 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -41,7 +41,7 @@ class ObjCForCollectionStmt; namespace ento { class AnalysisManager; -class CallOrObjCMessage; +class CallEvent; class ObjCMessage; class ExprEngine : public SubEngine { @@ -240,7 +240,7 @@ public: const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call); + const CallEvent *Call); /// printState - Called by ProgramStateManager to print checker-specific data. void printState(raw_ostream &Out, ProgramStateRef State, @@ -437,10 +437,6 @@ protected: ExplodedNode *Pred, ProgramStateRef state, bool GenSink); - ProgramStateRef invalidateArguments(ProgramStateRef State, - const CallOrObjCMessage &Call, - const LocationContext *LC); - ProgramStateRef MarkBranch(ProgramStateRef state, const Stmt *Terminator, const LocationContext *LCtx, diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h index 44190c6709..3dd2775aa4 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h @@ -35,7 +35,7 @@ class ASTContext; namespace ento { -class CallOrObjCMessage; +class CallEvent; typedef ConstraintManager* (*ConstraintManagerCreator)(ProgramStateManager&, SubEngine&); @@ -219,7 +219,7 @@ public: const Expr *E, unsigned BlockCount, const LocationContext *LCtx, StoreManager::InvalidatedSymbols *IS = 0, - const CallOrObjCMessage *Call = 0) const; + const CallEvent *Call = 0) const; /// enterStackFrame - Returns the state for entry to the given stack frame, /// preserving the current state. @@ -381,7 +381,7 @@ private: const Expr *E, unsigned BlockCount, const LocationContext *LCtx, StoreManager::InvalidatedSymbols &IS, - const CallOrObjCMessage *Call) const; + const CallEvent *Call) const; }; //===----------------------------------------------------------------------===// diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h b/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h index b8a745168e..35097aca6c 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/Store.h @@ -29,7 +29,7 @@ class StackFrameContext; namespace ento { -class CallOrObjCMessage; +class CallEvent; class ProgramState; class ProgramStateManager; class SubRegionMap; @@ -194,7 +194,7 @@ public: const Expr *E, unsigned Count, const LocationContext *LCtx, InvalidatedSymbols &IS, - const CallOrObjCMessage *Call, + const CallEvent *Call, InvalidatedRegions *Invalidated) = 0; /// enterStackFrame - Let the StoreManager to do something when execution diff --git a/include/clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h b/include/clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h index baf57d4906..68b81f19a4 100644 --- a/include/clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h +++ b/include/clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h @@ -105,7 +105,7 @@ public: const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) = 0; + const CallEvent *Call) = 0; inline ProgramStateRef diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 942280d03d..69373749a2 100644 --- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -66,7 +66,7 @@ public: const StoreManager::InvalidatedSymbols *, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const; + const CallEvent *Call) const; typedef void (CStringChecker::*FnCheck)(CheckerContext &, const CallExpr *) const; @@ -1895,7 +1895,7 @@ CStringChecker::checkRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const { + const CallEvent *Call) const { CStringLength::EntryMap Entries = state->get(); if (Entries.isEmpty()) return state; diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp index 8d46e5ec30..61582d028a 100644 --- a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp @@ -206,7 +206,7 @@ public: const StoreManager::InvalidatedSymbols *Invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const { + const CallEvent *Call) const { return State; } diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index 1aa9b824e3..0e13ddcd7e 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -18,7 +18,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" @@ -140,7 +140,7 @@ public: const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const; + const CallEvent *Call) const; bool wantsRegionChangeUpdate(ProgramStateRef state) const { return true; } @@ -200,7 +200,7 @@ private: /// Check if the function is not known to us. So, for example, we could /// conservatively assume it can free/reallocate it's pointer arguments. - bool doesNotFreeMemory(const CallOrObjCMessage *Call, + bool doesNotFreeMemory(const CallEvent *Call, ProgramStateRef State) const; static bool SummarizeValue(raw_ostream &os, SVal V); @@ -481,7 +481,8 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { C.addTransition(State); } -static bool isFreeWhenDoneSetToZero(CallOrObjCMessage Call, Selector &S) { +static bool isFreeWhenDoneSetToZero(const ObjCMessageInvocation &Call, + Selector &S) { for (unsigned i = 1; i < S.getNumArgs(); ++i) if (S.getNameForSlot(i).equals("freeWhenDone")) if (Call.getArgSVal(i).isConstant(0)) @@ -491,12 +492,12 @@ static bool isFreeWhenDoneSetToZero(CallOrObjCMessage Call, Selector &S) { } void MallocChecker::checkPreObjCMessage(const ObjCMessage &Msg, - CheckerContext &C) const { + CheckerContext &C) const { const ObjCMethodDecl *MD = Msg.getMethodDecl(); if (!MD) return; - CallOrObjCMessage Call(Msg, C.getState(), C.getLocationContext()); + ObjCMessageInvocation Call(Msg, C.getState(), C.getLocationContext()); Selector S = Msg.getSelector(); // If the first selector is dataWithBytesNoCopy, assume that the memory will @@ -509,7 +510,7 @@ void MallocChecker::checkPreObjCMessage(const ObjCMessage &Msg, S.getNameForSlot(0) == "initWithCharactersNoCopy") && !isFreeWhenDoneSetToZero(Call, S)){ unsigned int argIdx = 0; - C.addTransition(FreeMemAux(C, Call.getArg(argIdx), + C.addTransition(FreeMemAux(C, Call.getArgExpr(argIdx), Msg.getMessageExpr(), C.getState(), true)); } } @@ -1310,10 +1311,10 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, } // Check if the function is known to us. So, for example, we could -// conservatively assume it can free/reallocate it's pointer arguments. +// conservatively assume it can free/reallocate its pointer arguments. // (We assume that the pointers cannot escape through calls to system // functions not handled by this checker.) -bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call, +bool MallocChecker::doesNotFreeMemory(const CallEvent *Call, ProgramStateRef State) const { if (!Call) return false; @@ -1322,118 +1323,23 @@ bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call, // TODO: If we want to be more optimistic here, we'll need to make sure that // regions escape to C++ containers. They seem to do that even now, but for // mysterious reasons. - if (Call->isCXXCall()) + if (!(isa(Call) || isa(Call))) return false; - const Decl *D = Call->getDecl(); - if (!D) + // If the call has a callback as an argument, assume the memory + // can be freed. + if (Call->hasNonZeroCallbackArg()) return false; - ASTContext &ASTC = State->getStateManager().getContext(); - - // If it's one of the allocation functions we can reason about, we model - // its behavior explicitly. - if (isa(D) && isMemFunction(cast(D), ASTC)) { - return true; - } - - // If it's not a system call, assume it frees memory. - if (!Call->isInSystemHeader()) - return false; - - // Process C/ObjC functions. - if (const FunctionDecl *FD = dyn_cast(D)) { - // White list the system functions whose arguments escape. - const IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return true; - StringRef FName = II->getName(); - - // White list thread local storage. - if (FName.equals("pthread_setspecific")) - return false; - - // White list xpc connection context. - // TODO: Ensure that the deallocation actually happens, need to reason - // about "xpc_connection_set_finalizer_f". - if (FName.equals("xpc_connection_set_context")) - return false; - - // White list the 'XXXNoCopy' ObjC functions. - if (FName.endswith("NoCopy")) { - // Look for the deallocator argument. We know that the memory ownership - // is not transferred only if the deallocator argument is - // 'kCFAllocatorNull'. - for (unsigned i = 1; i < Call->getNumArgs(); ++i) { - const Expr *ArgE = Call->getArg(i)->IgnoreParenCasts(); - if (const DeclRefExpr *DE = dyn_cast(ArgE)) { - StringRef DeallocatorName = DE->getFoundDecl()->getName(); - if (DeallocatorName == "kCFAllocatorNull") - return true; - } - } - return false; - } - - // PR12101 - // Many CoreFoundation and CoreGraphics might allow a tracked object - // to escape. - if (Call->isCFCGAllowingEscape(FName)) - return false; - - // Associating streams with malloced buffers. The pointer can escape if - // 'closefn' is specified (and if that function does free memory). - // Currently, we do not inspect the 'closefn' function (PR12101). - if (FName == "funopen") - if (Call->getNumArgs() >= 4 && !Call->getArgSVal(4).isConstant(0)) - return false; - - // Do not warn on pointers passed to 'setbuf' when used with std streams, - // these leaks might be intentional when setting the buffer for stdio. - // http://stackoverflow.com/questions/2671151/who-frees-setvbuf-buffer - if (FName == "setbuf" || FName =="setbuffer" || - FName == "setlinebuf" || FName == "setvbuf") { - if (Call->getNumArgs() >= 1) - if (const DeclRefExpr *Arg = - dyn_cast(Call->getArg(0)->IgnoreParenCasts())) - if (const VarDecl *D = dyn_cast(Arg->getDecl())) - if (D->getCanonicalDecl()->getName().find("std") - != StringRef::npos) - return false; - } - - // A bunch of other functions which either take ownership of a pointer or - // wrap the result up in a struct or object, meaning it can be freed later. - // (See RetainCountChecker.) Not all the parameters here are invalidated, - // but the Malloc checker cannot differentiate between them. The right way - // of doing this would be to implement a pointer escapes callback. - if (FName == "CGBitmapContextCreate" || - FName == "CGBitmapContextCreateWithData" || - FName == "CVPixelBufferCreateWithBytes" || - FName == "CVPixelBufferCreateWithPlanarBytes" || - FName == "OSAtomicEnqueue") { - return false; - } - - // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can - // be deallocated by NSMapRemove. - if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos)) + // Check Objective-C messages by selector name. + if (const ObjCMessageInvocation *Msg = dyn_cast(Call)){ + // If it's not a framework call, assume it frees memory. + if (!Call->isInSystemHeader()) return false; - // If the call has a callback as an argument, assume the memory - // can be freed. - if (Call->hasNonZeroCallbackArg()) - return false; - - // Otherwise, assume that the function does not free memory. - // Most system calls, do not free the memory. - return true; + Selector S = Msg->getSelector(); - // Process ObjC functions. - } else if (const ObjCMethodDecl * ObjCD = dyn_cast(D)) { - Selector S = ObjCD->getSelector(); - - // White list the ObjC functions which do free memory. + // Whitelist the ObjC methods which do free memory. // - Anything containing 'freeWhenDone' param set to 1. // Ex: dataWithBytesNoCopy:length:freeWhenDone. for (unsigned i = 1; i < S.getNumArgs(); ++i) { @@ -1448,33 +1354,117 @@ bool MallocChecker::doesNotFreeMemory(const CallOrObjCMessage *Call, // If the first selector ends with NoCopy, assume that the ownership is // transferred as well. // Ex: [NSData dataWithBytesNoCopy:bytes length:10]; - if (S.getNameForSlot(0).endswith("NoCopy")) { + StringRef FirstSlot = S.getNameForSlot(0); + if (FirstSlot.endswith("NoCopy")) return false; - } // If the first selector starts with addPointer, insertPointer, // or replacePointer, assume we are dealing with NSPointerArray or similar. // This is similar to C++ containers (vector); we still might want to check - // that the pointers get freed, by following the container itself. - if (S.getNameForSlot(0).startswith("addPointer") || - S.getNameForSlot(0).startswith("insertPointer") || - S.getNameForSlot(0).startswith("replacePointer")) { + // that the pointers get freed by following the container itself. + if (FirstSlot.startswith("addPointer") || + FirstSlot.startswith("insertPointer") || + FirstSlot.startswith("replacePointer")) { return false; } - // If the call has a callback as an argument, assume the memory - // can be freed. - if (Call->hasNonZeroCallbackArg()) - return false; + // Otherwise, assume that the method does not free memory. + // Most framework methods do not free memory. + return true; + } - // Otherwise, assume that the function does not free memory. - // Most system calls, do not free the memory. + // At this point the only thing left to handle is straight function calls. + const FunctionDecl *FD = cast(Call)->getDecl(); + if (!FD) + return false; + + ASTContext &ASTC = State->getStateManager().getContext(); + + // If it's one of the allocation functions we can reason about, we model + // its behavior explicitly. + if (isMemFunction(FD, ASTC)) return true; + + // If it's not a system call, assume it frees memory. + if (!Call->isInSystemHeader()) + return false; + + // White list the system functions whose arguments escape. + const IdentifierInfo *II = FD->getIdentifier(); + if (!II) + return false; + StringRef FName = II->getName(); + + // White list thread local storage. + if (FName.equals("pthread_setspecific")) + return false; + if (FName.equals("xpc_connection_set_context")) + return false; + + // White list the 'XXXNoCopy' CoreFoundation functions. + if (FName.endswith("NoCopy")) { + // Look for the deallocator argument. We know that the memory ownership + // is not transferred only if the deallocator argument is + // 'kCFAllocatorNull'. + for (unsigned i = 1; i < Call->getNumArgs(); ++i) { + const Expr *ArgE = Call->getArgExpr(i)->IgnoreParenCasts(); + if (const DeclRefExpr *DE = dyn_cast(ArgE)) { + StringRef DeallocatorName = DE->getFoundDecl()->getName(); + if (DeallocatorName == "kCFAllocatorNull") + return true; + } + } + return false; } - // Otherwise, assume that the function can free memory. - return false; + // PR12101 + // Many CoreFoundation and CoreGraphics might allow a tracked object + // to escape. + if (CallOrObjCMessage::isCFCGAllowingEscape(FName)) + return false; + + // Associating streams with malloced buffers. The pointer can escape if + // 'closefn' is specified (and if that function does free memory). + // Currently, we do not inspect the 'closefn' function (PR12101). + if (FName == "funopen") + if (Call->getNumArgs() >= 4 && !Call->getArgSVal(4).isConstant(0)) + return false; + // Do not warn on pointers passed to 'setbuf' when used with std streams, + // these leaks might be intentional when setting the buffer for stdio. + // http://stackoverflow.com/questions/2671151/who-frees-setvbuf-buffer + if (FName == "setbuf" || FName =="setbuffer" || + FName == "setlinebuf" || FName == "setvbuf") { + if (Call->getNumArgs() >= 1) { + const Expr *ArgE = Call->getArgExpr(0)->IgnoreParenCasts(); + if (const DeclRefExpr *ArgDRE = dyn_cast(ArgE)) + if (const VarDecl *D = dyn_cast(ArgDRE->getDecl())) + if (D->getCanonicalDecl()->getName().find("std") != StringRef::npos) + return false; + } + } + + // A bunch of other functions which either take ownership of a pointer or + // wrap the result up in a struct or object, meaning it can be freed later. + // (See RetainCountChecker.) Not all the parameters here are invalidated, + // but the Malloc checker cannot differentiate between them. The right way + // of doing this would be to implement a pointer escapes callback. + if (FName == "CGBitmapContextCreate" || + FName == "CGBitmapContextCreateWithData" || + FName == "CVPixelBufferCreateWithBytes" || + FName == "CVPixelBufferCreateWithPlanarBytes" || + FName == "OSAtomicEnqueue") { + return false; + } + + // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can + // be deallocated by NSMapRemove. + if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos)) + return false; + + // Otherwise, assume that the function does not free memory. + // Most system calls do not free the memory. + return true; } // If the symbol we are tracking is invalidated, but not explicitly (ex: the &p @@ -1485,7 +1475,7 @@ MallocChecker::checkRegionChanges(ProgramStateRef State, const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const { + const CallEvent *Call) const { if (!invalidated || invalidated->empty()) return State; llvm::SmallPtrSet WhitelistedSymbols; diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp index 6da8833b33..098c464eef 100644 --- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp @@ -2500,7 +2500,7 @@ public: const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const; + const CallEvent *Call) const; bool wantsRegionChangeUpdate(ProgramStateRef state) const { return true; @@ -3454,7 +3454,7 @@ RetainCountChecker::checkRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) const { + const CallEvent *Call) const { if (!invalidated) return state; diff --git a/lib/StaticAnalyzer/Core/CMakeLists.txt b/lib/StaticAnalyzer/Core/CMakeLists.txt index 3da91788c6..0061ce1045 100644 --- a/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -8,6 +8,7 @@ add_clang_library(clangStaticAnalyzerCore BlockCounter.cpp BugReporter.cpp BugReporterVisitors.cpp + Calls.cpp Checker.cpp CheckerContext.cpp CheckerHelpers.cpp diff --git a/lib/StaticAnalyzer/Core/Calls.cpp b/lib/StaticAnalyzer/Core/Calls.cpp new file mode 100644 index 0000000000..ea28cfdf36 --- /dev/null +++ b/lib/StaticAnalyzer/Core/Calls.cpp @@ -0,0 +1,353 @@ +//===- Calls.cpp - Wrapper for all function and method calls ------*- C++ -*--// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +/// \file This file defines CallEvent and its subclasses, which represent path- +/// sensitive instances of different kinds of function and method calls +/// (C, C++, and Objective-C). +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "llvm/ADT/SmallSet.h" + +using namespace clang; +using namespace ento; + +SVal CallEvent::getArgSVal(unsigned Index) const { + const Expr *ArgE = getArgExpr(Index); + if (!ArgE) + return UnknownVal(); + return getSVal(ArgE); +} + +SourceRange CallEvent::getArgSourceRange(unsigned Index) const { + const Expr *ArgE = getArgExpr(Index); + if (!ArgE) + return SourceRange(); + return ArgE->getSourceRange(); +} + +QualType CallEvent::getResultType() const { + QualType ResultTy = getDeclaredResultType(); + + if (const Expr *E = getOriginExpr()) { + if (ResultTy.isNull()) + ResultTy = E->getType(); + + // FIXME: This is copied from CallOrObjCMessage, but it seems suspicious. + if (E->isGLValue()) { + ASTContext &Ctx = State->getStateManager().getContext(); + ResultTy = Ctx.getPointerType(ResultTy); + } + } + + return ResultTy; +} + +static bool isCallbackArg(SVal V, QualType T) { + // If the parameter is 0, it's harmless. + if (V.isZeroConstant()) + return false; + + // If a parameter is a block or a callback, assume it can modify pointer. + if (T->isBlockPointerType() || + T->isFunctionPointerType() || + T->isObjCSelType()) + return true; + + // Check if a callback is passed inside a struct (for both, struct passed by + // reference and by value). Dig just one level into the struct for now. + + if (isa(T) || isa(T)) + T = T->getPointeeType(); + + if (const RecordType *RT = T->getAsStructureType()) { + const RecordDecl *RD = RT->getDecl(); + for (RecordDecl::field_iterator I = RD->field_begin(), E = RD->field_end(); + I != E; ++I) { + QualType FieldT = I->getType(); + if (FieldT->isBlockPointerType() || FieldT->isFunctionPointerType()) + return true; + } + } + + return false; +} + +bool CallEvent::hasNonZeroCallbackArg() const { + unsigned NumOfArgs = getNumArgs(); + + // If calling using a function pointer, assume the function does not + // have a callback. TODO: We could check the types of the arguments here. + if (!getDecl()) + return false; + + unsigned Idx = 0; + for (CallEvent::param_type_iterator I = param_type_begin(), + E = param_type_end(); + I != E && Idx < NumOfArgs; ++I, ++Idx) { + if (NumOfArgs <= Idx) + break; + + if (isCallbackArg(getArgSVal(Idx), *I)) + return true; + } + + return false; +} + +/// \brief Returns true if a type is a pointer-to-const or reference-to-const +/// with no further indirection. +static bool isPointerToConst(QualType Ty) { + QualType PointeeTy = Ty->getPointeeType(); + if (PointeeTy == QualType()) + return false; + if (!PointeeTy.isConstQualified()) + return false; + if (PointeeTy->isAnyPointerType()) + return false; + return true; +} + +// Try to retrieve the function declaration and find the function parameter +// types which are pointers/references to a non-pointer const. +// We do not invalidate the corresponding argument regions. +static void findPtrToConstParams(llvm::SmallSet &PreserveArgs, + const CallEvent &Call) { + const Decl *CallDecl = Call.getDecl(); + if (!CallDecl) + return; + + if (Call.hasNonZeroCallbackArg()) + return; + + if (const FunctionDecl *FDecl = dyn_cast(CallDecl)) { + const IdentifierInfo *II = FDecl->getIdentifier(); + + // List the cases, where the region should be invalidated even if the + // argument is const. + // FIXME: This is conflating invalidating /contents/ and invalidating + // /metadata/. Now that we pass the CallEvent to the checkers, they + // should probably be doing this work themselves. + if (II) { + StringRef FName = II->getName(); + // - 'int pthread_setspecific(ptheread_key k, const void *)' stores a + // value into thread local storage. The value can later be retrieved with + // 'void *ptheread_getspecific(pthread_key)'. So even thought the + // parameter is 'const void *', the region escapes through the call. + // - funopen - sets a buffer for future IO calls. + // - ObjC functions that end with "NoCopy" can free memory, of the passed + // in buffer. + // - Many CF containers allow objects to escape through custom + // allocators/deallocators upon container construction. + // - NSXXInsertXX, for example NSMapInsertIfAbsent, since they can + // be deallocated by NSMapRemove. + // - Any call that has a callback as one of the arguments. + if (FName == "pthread_setspecific" || + FName == "funopen" || + FName.endswith("NoCopy") || + (FName.startswith("NS") && + (FName.find("Insert") != StringRef::npos)) || + CallOrObjCMessage::isCFCGAllowingEscape(FName)) + return; + } + } + + unsigned Idx = 0; + for (CallEvent::param_type_iterator I = Call.param_type_begin(), + E = Call.param_type_end(); + I != E; ++I, ++Idx) { + if (isPointerToConst(*I)) + PreserveArgs.insert(Idx); + } +} + +ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, + ProgramStateRef Orig) const { + ProgramStateRef Result = (Orig ? Orig : State); + + SmallVector RegionsToInvalidate; + addExtraInvalidatedRegions(RegionsToInvalidate); + + // Indexes of arguments whose values will be preserved by the call. + llvm::SmallSet PreserveArgs; + findPtrToConstParams(PreserveArgs, *this); + + for (unsigned Idx = 0, Count = getNumArgs(); Idx != Count; ++Idx) { + if (PreserveArgs.count(Idx)) + continue; + + SVal V = getArgSVal(Idx); + + // If we are passing a location wrapped as an integer, unwrap it and + // invalidate the values referred by the location. + if (nonloc::LocAsInteger *Wrapped = dyn_cast(&V)) + V = Wrapped->getLoc(); + else if (!isa(V)) + continue; + + if (const MemRegion *R = V.getAsRegion()) { + // Invalidate the value of the variable passed by reference. + + // Are we dealing with an ElementRegion? If the element type is + // a basic integer type (e.g., char, int) and the underlying region + // is a variable region then strip off the ElementRegion. + // FIXME: We really need to think about this for the general case + // as sometimes we are reasoning about arrays and other times + // about (char*), etc., is just a form of passing raw bytes. + // e.g., void *p = alloca(); foo((char*)p); + if (const ElementRegion *ER = dyn_cast(R)) { + // Checking for 'integral type' is probably too promiscuous, but + // we'll leave it in for now until we have a systematic way of + // handling all of these cases. Eventually we need to come up + // with an interface to StoreManager so that this logic can be + // appropriately delegated to the respective StoreManagers while + // still allowing us to do checker-specific logic (e.g., + // invalidating reference counts), probably via callbacks. + if (ER->getElementType()->isIntegralOrEnumerationType()) { + const MemRegion *superReg = ER->getSuperRegion(); + if (isa(superReg) || isa(superReg) || + isa(superReg)) + R = cast(superReg); + } + // FIXME: What about layers of ElementRegions? + } + + // Mark this region for invalidation. We batch invalidate regions + // below for efficiency. + RegionsToInvalidate.push_back(R); + } + } + + // Invalidate designated regions using the batch invalidation API. + // NOTE: Even if RegionsToInvalidate is empty, we may still invalidate + // global variables. + return Result->invalidateRegions(RegionsToInvalidate, getOriginExpr(), + BlockCount, LCtx, /*Symbols=*/0, this); +} + +CallEvent::param_iterator AnyFunctionCall::param_begin() const { + const FunctionDecl *D = getDecl(); + if (!D) + return 0; + + return D->param_begin(); +} + +CallEvent::param_iterator AnyFunctionCall::param_end() const { + const FunctionDecl *D = getDecl(); + if (!D) + return 0; + + return D->param_end(); +} + +QualType AnyFunctionCall::getDeclaredResultType() const { + const FunctionDecl *D = getDecl(); + if (!D) + return QualType(); + + return D->getResultType(); +} + +const FunctionDecl *SimpleCall::getDecl() const { + const FunctionDecl *D = CE->getDirectCallee(); + if (D) + return D; + + return getSVal(CE->getCallee()).getAsFunctionDecl(); +} + +void CXXMemberCall::addExtraInvalidatedRegions(RegionList &Regions) const { + const Expr *Base = getOriginExpr()->getImplicitObjectArgument(); + + // FIXME: Will eventually need to cope with member pointers. This is + // a limitation in getImplicitObjectArgument(). + if (!Base) + return; + + if (const MemRegion *R = getSVal(Base).getAsRegion()) + Regions.push_back(R); +} + +const BlockDataRegion *BlockCall::getBlockRegion() const { + const Expr *Callee = getOriginExpr()->getCallee(); + const MemRegion *DataReg = getSVal(Callee).getAsRegion(); + + return cast(DataReg); +} + +CallEvent::param_iterator BlockCall::param_begin() const { + return getBlockRegion()->getDecl()->param_begin(); +} + +CallEvent::param_iterator BlockCall::param_end() const { + return getBlockRegion()->getDecl()->param_end(); +} + +void BlockCall::addExtraInvalidatedRegions(RegionList &Regions) const { + Regions.push_back(getBlockRegion()); +} + +QualType BlockCall::getDeclaredResultType() const { + QualType BlockTy = getBlockRegion()->getCodeRegion()->getLocationType(); + return cast(BlockTy->getPointeeType())->getResultType(); +} + +void CXXConstructorCall::addExtraInvalidatedRegions(RegionList &Regions) const { + if (Target) + Regions.push_back(Target); +} + +CallEvent::param_iterator ObjCMessageInvocation::param_begin() const { + const ObjCMethodDecl *D = getDecl(); + if (!D) + return 0; + + return D->param_begin(); +} + +CallEvent::param_iterator ObjCMessageInvocation::param_end() const { + const ObjCMethodDecl *D = getDecl(); + if (!D) + return 0; + + return D->param_end(); +} + +void +ObjCMessageInvocation::addExtraInvalidatedRegions(RegionList &Regions) const { + if (const MemRegion *R = getReceiverSVal().getAsRegion()) + Regions.push_back(R); +} + +QualType ObjCMessageInvocation::getDeclaredResultType() const { + const ObjCMethodDecl *D = getDecl(); + if (!D) + return QualType(); + + return D->getResultType(); +} + +SVal ObjCMessageInvocation::getReceiverSVal() const { + // FIXME: Is this the best way to handle class receivers? + if (!isInstanceMessage()) + return UnknownVal(); + + const Expr *Base = Msg.getInstanceReceiver(); + if (Base) + return getSVal(Base); + + // An instance message with no expression means we are sending to super. + // In this case the object reference is the same as 'self'. + const ImplicitParamDecl *SelfDecl = LCtx->getSelfDecl(); + assert(SelfDecl && "No message receiver Expr, but not in an ObjC method"); + return loc::MemRegionVal(State->getRegion(SelfDecl, LCtx)); +} diff --git a/lib/StaticAnalyzer/Core/CheckerManager.cpp b/lib/StaticAnalyzer/Core/CheckerManager.cpp index 8a6ea1d0f8..b3d93cfe35 100644 --- a/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -431,7 +431,7 @@ CheckerManager::runCheckersForRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *invalidated, ArrayRef ExplicitRegions, ArrayRef Regions, - const CallOrObjCMessage *Call) { + const CallEvent *Call) { for (unsigned i = 0, e = RegionChangesCheckers.size(); i != e; ++i) { // If any checker declares the state infeasible (or if it starts that way), // bail out. diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp index a206a1a2fb..74c65c8be0 100644 --- a/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -185,7 +185,7 @@ ExprEngine::processRegionChanges(ProgramStateRef state, const StoreManager::InvalidatedSymbols *invalidated, ArrayRef Explicits, ArrayRef Regions, - const CallOrObjCMessage *Call) { + const CallEvent *Call) { return getCheckerManager().runCheckersForRegionChanges(state, invalidated, Explicits, Regions, Call); } diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index b462e010d2..b74d46f17c 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -14,7 +14,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/StmtCXX.h" @@ -125,7 +125,9 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *E, const LocationContext *LC = Pred->getLocationContext(); ProgramStateRef state = Pred->getState(); - state = invalidateArguments(state, CallOrObjCMessage(E, state, LC), LC); + CXXConstructorCall Call(E, state, LC); + unsigned BlockCount = currentBuilderContext->getCurrentBlockCount(); + state = Call.invalidateRegions(BlockCount); Bldr.generateNode(E, Pred, state); } } diff --git a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index d46e65c5e7..0cbf4829ab 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -13,7 +13,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h" #include "clang/AST/DeclCXX.h" #include "llvm/ADT/SmallSet.h" #include "llvm/Support/SaveAndRestore.h" @@ -307,179 +307,6 @@ bool ExprEngine::InlineCall(ExplodedNodeSet &Dst, return true; } -static bool isPointerToConst(const ParmVarDecl *ParamDecl) { - QualType PointeeTy = ParamDecl->getOriginalType()->getPointeeType(); - if (PointeeTy != QualType() && PointeeTy.isConstQualified() && - !PointeeTy->isAnyPointerType() && !PointeeTy->isReferenceType()) { - return true; - } - return false; -} - -// Try to retrieve the function declaration and find the function parameter -// types which are pointers/references to a non-pointer const. -// We do not invalidate the corresponding argument regions. -static void findPtrToConstParams(llvm::SmallSet &PreserveArgs, - const CallOrObjCMessage &Call) { - const Decl *CallDecl = Call.getDecl(); - if (!CallDecl) - return; - - if (const FunctionDecl *FDecl = dyn_cast(CallDecl)) { - const IdentifierInfo *II = FDecl->getIdentifier(); - - // List the cases, where the region should be invalidated even if the - // argument is const. - if (II) { - StringRef FName = II->getName(); - // - 'int pthread_setspecific(ptheread_key k, const void *)' stores a - // value into thread local storage. The value can later be retrieved with - // 'void *ptheread_getspecific(pthread_key)'. So even thought the - // parameter is 'const void *', the region escapes through the call. - // - funopen - sets a buffer for future IO calls. - // - ObjC functions that end with "NoCopy" can free memory, of the passed - // in buffer. - // - Many CF containers allow objects to escape through custom - // allocators/deallocators upon container construction. - // - NSXXInsertXX, for example NSMapInsertIfAbsent, since they can - // be deallocated by NSMapRemove. - // - Any call that has a callback as one of the arguments. - if (FName == "pthread_setspecific" || - FName == "funopen" || - FName.endswith("NoCopy") || - (FName.startswith("NS") && - (FName.find("Insert") != StringRef::npos)) || - Call.isCFCGAllowingEscape(FName) || - Call.hasNonZeroCallbackArg()) - return; - } - - for (unsigned Idx = 0, E = Call.getNumArgs(); Idx != E; ++Idx) { - if (FDecl && Idx < FDecl->getNumParams()) { - if (isPointerToConst(FDecl->getParamDecl(Idx))) - PreserveArgs.insert(Idx); - } - } - return; - } - - if (const ObjCMethodDecl *MDecl = dyn_cast(CallDecl)) { - assert(MDecl->param_size() <= Call.getNumArgs()); - unsigned Idx = 0; - - if (Call.hasNonZeroCallbackArg()) - return; - - for (clang::ObjCMethodDecl::param_const_iterator - I = MDecl->param_begin(), E = MDecl->param_end(); I != E; ++I, ++Idx) { - if (isPointerToConst(*I)) - PreserveArgs.insert(Idx); - } - return; - } -} - -ProgramStateRef -ExprEngine::invalidateArguments(ProgramStateRef State, - const CallOrObjCMessage &Call, - const LocationContext *LC) { - SmallVector RegionsToInvalidate; - - if (Call.isObjCMessage()) { - // Invalidate all instance variables of the receiver of an ObjC message. - // FIXME: We should be able to do better with inter-procedural analysis. - if (const MemRegion *MR = Call.getInstanceMessageReceiver(LC).getAsRegion()) - RegionsToInvalidate.push_back(MR); - - } else if (Call.isCXXCall()) { - // Invalidate all instance variables for the callee of a C++ method call. - // FIXME: We should be able to do better with inter-procedural analysis. - // FIXME: We can probably do better for const versus non-const methods. - if (const MemRegion *Callee = Call.getCXXCallee().getAsRegion()) - RegionsToInvalidate.push_back(Callee); - - } else if (Call.isFunctionCall()) { - // Block calls invalidate all captured-by-reference values. - SVal CalleeVal = Call.getFunctionCallee(); - if (const MemRegion *Callee = CalleeVal.getAsRegion()) { - if (isa(Callee)) - RegionsToInvalidate.push_back(Callee); - } - } - - // Indexes of arguments whose values will be preserved by the call. - llvm::SmallSet PreserveArgs; - findPtrToConstParams(PreserveArgs, Call); - - for (unsigned idx = 0, e = Call.getNumArgs(); idx != e; ++idx) { - if (PreserveArgs.count(idx)) - continue; - - SVal V = Call.getArgSVal(idx); - - // If we are passing a location wrapped as an integer, unwrap it and - // invalidate the values referred by the location. - if (nonloc::LocAsInteger *Wrapped = dyn_cast(&V)) - V = Wrapped->getLoc(); - else if (!isa(V)) - continue; - - if (const MemRegion *R = V.getAsRegion()) { - // Invalidate the value of the variable passed by reference. - - // Are we dealing with an ElementRegion? If the element type is - // a basic integer type (e.g., char, int) and the underlying region - // is a variable region then strip off the ElementRegion. - // FIXME: We really need to think about this for the general case - // as sometimes we are reasoning about arrays and other times - // about (char*), etc., is just a form of passing raw bytes. - // e.g., void *p = alloca(); foo((char*)p); - if (const ElementRegion *ER = dyn_cast(R)) { - // Checking for 'integral type' is probably too promiscuous, but - // we'll leave it in for now until we have a systematic way of - // handling all of these cases. Eventually we need to come up - // with an interface to StoreManager so that this logic can be - // appropriately delegated to the respective StoreManagers while - // still allowing us to do checker-specific logic (e.g., - // invalidating reference counts), probably via callbacks. - if (ER->getElementType()->isIntegralOrEnumerationType()) { - const MemRegion *superReg = ER->getSuperRegion(); - if (isa(superReg) || isa(superReg) || - isa(superReg)) - R = cast(superReg); - } - // FIXME: What about layers of ElementRegions? - } - - // Mark this region for invalidation. We batch invalidate regions - // below for efficiency. - RegionsToInvalidate.push_back(R); - } else { - // Nuke all other arguments passed by reference. - // FIXME: is this necessary or correct? This handles the non-Region - // cases. Is it ever valid to store to these? - State = State->unbindLoc(cast(V)); - } - } - - // Invalidate designated regions using the batch invalidation API. - - // FIXME: We can have collisions on the conjured symbol if the - // expression *I also creates conjured symbols. We probably want - // to identify conjured symbols by an expression pair: the enclosing - // expression (the context) and the expression itself. This should - // disambiguate conjured symbols. - unsigned Count = currentBuilderContext->getCurrentBlockCount(); - StoreManager::InvalidatedSymbols IS; - - // NOTE: Even if RegionsToInvalidate is empty, we may still invalidate - // global variables. - return State->invalidateRegions(RegionsToInvalidate, - Call.getOriginExpr(), Count, LC, - &IS, &Call); - -} - static ProgramStateRef getReplayWithoutInliningState(ExplodedNode *&N, const CallExpr *CE) { void *ReplayState = N->getState()->get(); @@ -524,6 +351,7 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred, SVal L = state->getSVal(Callee, Pred->getLocationContext()); // Figure out the result type. We do this dance to handle references. + // FIXME: This doesn't handle C++ methods, blocks, etc. QualType ResultTy; if (const FunctionDecl *FD = L.getAsFunctionDecl()) ResultTy = FD->getResultType(); @@ -543,8 +371,16 @@ void ExprEngine::VisitCallExpr(const CallExpr *CE, ExplodedNode *Pred, state = state->BindExpr(CE, LCtx, RetVal); // Invalidate the arguments. - state = Eng.invalidateArguments(state, CallOrObjCMessage(CE, state, LCtx), - LCtx); + if (const CXXMemberCallExpr *MemberCE = dyn_cast(CE)) { + CXXMemberCall Call(MemberCE, state, LCtx); + state = Call.invalidateRegions(Count); + } else if (isa(L.getAsRegion())) { + BlockCall Call(CE, state, LCtx); + state = Call.invalidateRegions(Count); + } else { + FunctionCall Call(CE, state, LCtx); + state = Call.invalidateRegions(Count); + } // And make the result node. Bldr.generateNode(CE, Pred, state); diff --git a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp index be9a26b1f5..d35d999b5e 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -13,6 +13,7 @@ #include "clang/AST/StmtObjC.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" @@ -246,6 +247,9 @@ void ExprEngine::evalObjCMessage(StmtNodeBuilder &Bldr, ExplodedNode *Pred, ProgramStateRef state, bool GenSink) { + const LocationContext *LCtx = Pred->getLocationContext(); + unsigned BlockCount = currentBuilderContext->getCurrentBlockCount(); + // First handle the return value. SVal ReturnValue = UnknownVal(); @@ -259,7 +263,7 @@ void ExprEngine::evalObjCMessage(StmtNodeBuilder &Bldr, // These methods return their receivers. const Expr *ReceiverE = msg.getInstanceReceiver(); if (ReceiverE) - ReturnValue = state->getSVal(ReceiverE, Pred->getLocationContext()); + ReturnValue = state->getSVal(ReceiverE, LCtx); break; } } @@ -268,18 +272,17 @@ void ExprEngine::evalObjCMessage(StmtNodeBuilder &Bldr, if (ReturnValue.isUnknown()) { SValBuilder &SVB = getSValBuilder(); QualType ResultTy = msg.getResultType(getContext()); - unsigned Count = currentBuilderContext->getCurrentBlockCount(); const Expr *CurrentE = cast(currentStmt); - const LocationContext *LCtx = Pred->getLocationContext(); - ReturnValue = SVB.getConjuredSymbolVal(NULL, CurrentE, LCtx, ResultTy, Count); + ReturnValue = SVB.getConjuredSymbolVal(NULL, CurrentE, LCtx, ResultTy, + BlockCount); } // Bind the return value. - const LocationContext *LCtx = Pred->getLocationContext(); state = state->BindExpr(currentStmt, LCtx, ReturnValue); // Invalidate the arguments (and the receiver) - state = invalidateArguments(state, CallOrObjCMessage(msg, state, LCtx), LCtx); + ObjCMessageInvocation Invocation(msg, state, LCtx); + state = Invocation.invalidateRegions(BlockCount); // And create the new node. Bldr.generateNode(msg.getMessageExpr(), Pred, state, GenSink); diff --git a/lib/StaticAnalyzer/Core/ProgramState.cpp b/lib/StaticAnalyzer/Core/ProgramState.cpp index a666de0c5c..d7668dec1a 100644 --- a/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -157,7 +157,7 @@ ProgramState::invalidateRegions(ArrayRef Regions, const Expr *E, unsigned Count, const LocationContext *LCtx, StoreManager::InvalidatedSymbols *IS, - const CallOrObjCMessage *Call) const { + const CallEvent *Call) const { if (!IS) { StoreManager::InvalidatedSymbols invalidated; return invalidateRegionsImpl(Regions, E, Count, LCtx, @@ -171,7 +171,7 @@ ProgramState::invalidateRegionsImpl(ArrayRef Regions, const Expr *E, unsigned Count, const LocationContext *LCtx, StoreManager::InvalidatedSymbols &IS, - const CallOrObjCMessage *Call) const { + const CallEvent *Call) const { ProgramStateManager &Mgr = getStateManager(); SubEngine* Eng = Mgr.getOwningEngine(); diff --git a/lib/StaticAnalyzer/Core/RegionStore.cpp b/lib/StaticAnalyzer/Core/RegionStore.cpp index 1698316d61..a8e47ff8e2 100644 --- a/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -20,7 +20,7 @@ #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/AnalysisContext.h" #include "clang/Basic/TargetInfo.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" @@ -252,7 +252,7 @@ public: const Expr *E, unsigned Count, const LocationContext *LCtx, InvalidatedSymbols &IS, - const CallOrObjCMessage *Call, + const CallEvent *Call, InvalidatedRegions *Invalidated); public: // Made public for helper classes. @@ -790,7 +790,7 @@ StoreRef RegionStoreManager::invalidateRegions(Store store, const Expr *Ex, unsigned Count, const LocationContext *LCtx, InvalidatedSymbols &IS, - const CallOrObjCMessage *Call, + const CallEvent *Call, InvalidatedRegions *Invalidated) { invalidateRegionsWorker W(*this, StateMgr, RegionStoreManager::GetRegionBindings(store), diff --git a/test/Analysis/blocks-no-inline.c b/test/Analysis/blocks-no-inline.c new file mode 100644 index 0000000000..1ec14e820b --- /dev/null +++ b/test/Analysis/blocks-no-inline.c @@ -0,0 +1,13 @@ +// RUN: %clang_cc1 -triple x86_64-apple-darwin10 -analyze -analyzer-checker=core,debug.ExprInspection -analyzer-ipa=none -fblocks -verify %s + +void clang_analyzer_eval(int); + +void testInvalidation() { + __block int i = 0; + ^{ + ++i; + }(); + + // Under inlining, we will know that i == 1. + clang_analyzer_eval(i == 0); // expected-warning{{UNKNOWN}} +} -- 2.40.0