/// \brief Returns true if any of the arguments appear to represent callbacks.
bool hasNonZeroCallbackArg() const;
+ /// \brief Returns true if any of the arguments are known to escape to long-
+ /// term storage, even if this method will not modify them.
+ // NOTE: The exact semantics of this are still being defined!
+ // We don't really want a list of hardcoded exceptions in the long run,
+ // but we don't want duplicated lists of known APIs in the short term either.
+ virtual bool argumentsMayEscape() const {
+ return hasNonZeroCallbackArg();
+ }
+
/// \brief Returns a new state with all argument regions invalidated.
///
/// This accepts an alternate state in case some processing has already
ProgramStateRef invalidateRegions(unsigned BlockCount,
ProgramStateRef Orig = 0) const;
+ /// \brief Returns true if this is a statement that can be considered for
+ /// inlining.
+ static bool mayBeInlined(const Stmt *S);
+
// Iterator access to parameter types.
private:
typedef std::const_mem_fun_t<QualType, ParmVarDecl> get_type_fun;
public:
virtual const FunctionDecl *getDecl() const = 0;
+ bool argumentsMayEscape() const;
+
static bool classof(const CallEvent *CA) {
return CA->getKind() >= CE_BEG_FUNCTION_CALLS &&
CA->getKind() <= CE_END_FUNCTION_CALLS;
}
};
-/// \brief Common wrapper for a call expression, ObjC message, or C++
-/// constructor, mainly to provide a common interface for their arguments.
-class CallOrObjCMessage {
- llvm::PointerUnion<const CallExpr *, const CXXConstructExpr *> CallE;
- ObjCMessage Msg;
- ProgramStateRef State;
- const LocationContext *LCtx;
-
- bool isCallbackArg(unsigned Idx, const Type *T) const;
-
-public:
- CallOrObjCMessage(const CallExpr *callE, ProgramStateRef state,
- const LocationContext *lctx)
- : CallE(callE), State(state), LCtx(lctx) {}
- CallOrObjCMessage(const CXXConstructExpr *consE, ProgramStateRef state,
- const LocationContext *lctx)
- : CallE(consE), State(state), LCtx(lctx) {}
- CallOrObjCMessage(const ObjCMessage &msg, ProgramStateRef state,
- const LocationContext *lctx)
- : CallE((CallExpr *)0), Msg(msg), State(state), LCtx(lctx) {}
-
- QualType getResultType(ASTContext &ctx) const;
-
- bool isFunctionCall() const {
- return CallE && CallE.is<const CallExpr *>();
- }
-
- bool isCXXConstructExpr() const {
- return CallE && CallE.is<const CXXConstructExpr *>();
- }
-
- bool isObjCMessage() const {
- return !CallE;
- }
-
- bool isCXXCall() const {
- const CallExpr *ActualCallE = CallE.dyn_cast<const CallExpr *>();
- return ActualCallE && isa<CXXMemberCallExpr>(ActualCallE);
- }
-
- /// Check if the callee is declared in the system header.
- bool isInSystemHeader() const {
- if (const Decl *D = getDecl()) {
- const SourceManager &SM =
- State->getStateManager().getContext().getSourceManager();
- SourceLocation Loc = D->getLocation();
- // Be careful: the implicit declarations of operator new/delete have
- // invalid source locations but should still count as system files.
- if (Loc.isValid())
- return SM.isInSystemHeader(D->getLocation());
- else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(D))
- return FD->isOverloadedOperator() && FD->isImplicit() && FD->isGlobal();
- }
- return false;
- }
-
- const Expr *getOriginExpr() const {
- if (!CallE)
- return Msg.getMessageExpr();
- if (const CXXConstructExpr *Ctor =
- CallE.dyn_cast<const CXXConstructExpr *>())
- return Ctor;
- return CallE.get<const CallExpr *>();
- }
-
- SVal getFunctionCallee() const;
- SVal getCXXCallee() const;
- SVal getInstanceMessageReceiver(const LocationContext *LC) const;
-
- /// Get the declaration of the function or method.
- const Decl *getDecl() const;
-
- unsigned getNumArgs() const {
- if (!CallE)
- return Msg.getNumArgs();
- if (const CXXConstructExpr *Ctor =
- CallE.dyn_cast<const CXXConstructExpr *>())
- return Ctor->getNumArgs();
- return CallE.get<const CallExpr *>()->getNumArgs();
- }
-
- SVal getArgSVal(unsigned i) const {
- assert(i < getNumArgs());
- if (!CallE)
- return Msg.getArgSVal(i, LCtx, State);
- return State->getSVal(getArg(i), LCtx);
- }
-
- const Expr *getArg(unsigned i) const {
- assert(i < getNumArgs());
- if (!CallE)
- return Msg.getArgExpr(i);
- if (const CXXConstructExpr *Ctor =
- CallE.dyn_cast<const CXXConstructExpr *>())
- return Ctor->getArg(i);
- return CallE.get<const CallExpr *>()->getArg(i);
- }
-
- SourceRange getArgSourceRange(unsigned i) const {
- assert(i < getNumArgs());
- if (CallE)
- return getArg(i)->getSourceRange();
- return Msg.getArgSourceRange(i);
- }
-
- SourceRange getReceiverSourceRange() const {
- assert(isObjCMessage());
- return Msg.getReceiverSourceRange();
- }
-
- /// \brief Check if one of the arguments might be a callback.
- bool hasNonZeroCallbackArg() const;
-
-
- /// \brief Check if the name corresponds to a CoreFoundation or CoreGraphics
- /// function that allows objects to escape.
- ///
- /// Many methods allow a tracked object to escape. For example:
- ///
- /// CFMutableDictionaryRef x = CFDictionaryCreateMutable(..., customDeallocator);
- /// CFDictionaryAddValue(y, key, x);
- ///
- /// We handle this and similar cases with the following heuristic. If the
- /// function name contains "InsertValue", "SetValue", "AddValue",
- /// "AppendValue", or "SetAttribute", then we assume that arguments may
- /// escape.
- //
- // TODO: To reduce false negatives here, we should track the container
- // allocation site and check if a proper deallocator was set there.
- static bool isCFCGAllowingEscape(StringRef FName);
-
- // Check if this kind of expression can be inlined by the analyzer.
- static bool canBeInlined(const Stmt *S) {
- return isa<CallExpr>(S);
- }
-};
-
}
}
// functions not handled by this checker.)
bool MallocChecker::doesNotFreeMemory(const CallEvent *Call,
ProgramStateRef State) const {
- if (!Call)
- return false;
+ assert(Call);
// For now, assume that any C++ call can free memory.
// TODO: If we want to be more optimistic here, we'll need to make sure that
if (!(isa<FunctionCall>(Call) || isa<ObjCMessageInvocation>(Call)))
return false;
- // If the call has a callback as an argument, assume the memory
- // can be freed.
- if (Call->hasNonZeroCallbackArg())
- return false;
-
// Check Objective-C messages by selector name.
if (const ObjCMessageInvocation *Msg = dyn_cast<ObjCMessageInvocation>(Call)){
- // If it's not a framework call, assume it frees memory.
- if (!Call->isInSystemHeader())
+ // If it's not a framework call, or if it takes a callback, assume it
+ // can free memory.
+ if (!Call->isInSystemHeader() || Call->hasNonZeroCallbackArg())
return false;
Selector S = Msg->getSelector();
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.
+ // We specifically check these before
if (FName.endswith("NoCopy")) {
// Look for the deallocator argument. We know that the memory ownership
// is not transferred only if the deallocator argument is
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).
+ // 'closefn' is specified (and if that function does free memory),
+ // but it will not if closefn is not specified.
// Currently, we do not inspect the 'closefn' function (PR12101).
if (FName == "funopen")
- if (Call->getNumArgs() >= 4 && !Call->getArgSVal(4).isConstant(0))
- return false;
+ if (Call->getNumArgs() >= 4 && Call->getArgSVal(4).isConstant(0))
+ return true;
// Do not warn on pointers passed to 'setbuf' when used with std streams,
// these leaks might be intentional when setting the buffer for stdio.
return false;
}
- // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
- // be deallocated by NSMapRemove.
- if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos))
+ // Handle cases where we know a buffer's /address/ can escape.
+ // Note that the above checks handle some special cases where we know that
+ // even though the address escapes, it's still our responsibility to free the
+ // buffer.
+ if (Call->argumentsMayEscape())
return false;
// Otherwise, assume that the function does not free memory.
FunctionSummary.cpp
HTMLDiagnostics.cpp
MemRegion.cpp
- ObjCMessage.cpp
PathDiagnostic.cpp
PlistDiagnostics.cpp
ProgramState.cpp
// 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.
+// We will not invalidate the corresponding argument regions.
static void findPtrToConstParams(llvm::SmallSet<unsigned, 1> &PreserveArgs,
const CallEvent &Call) {
- const Decl *CallDecl = Call.getDecl();
- if (!CallDecl)
- return;
-
- if (Call.hasNonZeroCallbackArg())
- return;
-
- if (const FunctionDecl *FDecl = dyn_cast<FunctionDecl>(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();
+ E = Call.param_type_end();
I != E; ++I, ++Idx) {
if (isPointerToConst(*I))
PreserveArgs.insert(Idx);
// Indexes of arguments whose values will be preserved by the call.
llvm::SmallSet<unsigned, 1> PreserveArgs;
- findPtrToConstParams(PreserveArgs, *this);
+ if (!argumentsMayEscape())
+ findPtrToConstParams(PreserveArgs, *this);
for (unsigned Idx = 0, Count = getNumArgs(); Idx != Count; ++Idx) {
if (PreserveArgs.count(Idx))
BlockCount, LCtx, /*Symbols=*/0, this);
}
+bool CallEvent::mayBeInlined(const Stmt *S) {
+ return isa<CallExpr>(S);
+}
+
+
CallEvent::param_iterator AnyFunctionCall::param_begin() const {
const FunctionDecl *D = getDecl();
if (!D)
return D->getResultType();
}
+bool AnyFunctionCall::argumentsMayEscape() const {
+ if (CallEvent::argumentsMayEscape())
+ return true;
+
+ const FunctionDecl *D = getDecl();
+ if (!D)
+ return true;
+
+ const IdentifierInfo *II = D->getIdentifier();
+ if (!II)
+ return true;
+
+ // This set of "escaping" APIs is
+
+ // - '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.
+ if (II->isStr("pthread_setspecific"))
+ return true;
+
+ // - xpc_connection_set_context stores a value which can be retrieved later
+ // with xpc_connection_get_context.
+ if (II->isStr("xpc_connection_set_context"))
+ return true;
+
+ // - funopen - sets a buffer for future IO calls.
+ if (II->isStr("funopen"))
+ return true;
+
+ StringRef FName = II->getName();
+
+ // - CoreFoundation functions that end with "NoCopy" can free a passed-in
+ // buffer even if it is const.
+ if (FName.endswith("NoCopy"))
+ return true;
+
+ // - NSXXInsertXX, for example NSMapInsertIfAbsent, since they can
+ // be deallocated by NSMapRemove.
+ if (FName.startswith("NS") && (FName.find("Insert") != StringRef::npos))
+ return true;
+
+ // - Many CF containers allow objects to escape through custom
+ // allocators/deallocators upon container construction. (PR12101)
+ if (FName.startswith("CF") || FName.startswith("CG")) {
+ return StrInStrNoCase(FName, "InsertValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "WithData") != StringRef::npos ||
+ StrInStrNoCase(FName, "AppendValue") != StringRef::npos ||
+ StrInStrNoCase(FName, "SetAttribute") != StringRef::npos;
+ }
+
+ return false;
+}
+
+
const FunctionDecl *SimpleCall::getDecl() const {
const FunctionDecl *D = CE->getDirectCallee();
if (D)
return getSVal(CE->getCallee()).getAsFunctionDecl();
}
+
void CXXMemberCall::addExtraInvalidatedRegions(RegionList &Regions) const {
const Expr *Base = getOriginExpr()->getImplicitObjectArgument();
Regions.push_back(R);
}
+
const BlockDataRegion *BlockCall::getBlockRegion() const {
const Expr *Callee = getOriginExpr()->getCallee();
const MemRegion *DataReg = getSVal(Callee).getAsRegion();
return cast<FunctionType>(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)
//===----------------------------------------------------------------------===//
#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h"
-#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h"
#include "clang/AST/Stmt.h"
#include "clang/AST/ParentMap.h"
// (6) The 'GDM' is the same as the predecessor.
// (7) The LocationContext is the same as the predecessor.
// (8) The PostStmt is for a non-consumed Stmt or Expr.
- // (9) The successor is a CallExpr StmtPoint (so that we would be able to
+ // (9) The successor is not a CallExpr StmtPoint (so that we would be able to
// find it when retrying a call with no inlining).
// Conditions 1 and 2.
// Condition 9.
const ProgramPoint SuccLoc = succ->getLocation();
if (const StmtPoint *SP = dyn_cast<StmtPoint>(&SuccLoc))
- if (CallOrObjCMessage::canBeInlined(SP->getStmt()))
+ if (CallEvent::mayBeInlined(SP->getStmt()))
return false;
return true;
#include "clang/StaticAnalyzer/Core/CheckerManager.h"
#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/Calls.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h"
#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h"
#include "clang/AST/CharUnits.h"
return true;
// Run before processing a call.
- if (CallOrObjCMessage::canBeInlined(S.getStmt()))
+ if (CallEvent::mayBeInlined(S.getStmt()))
return true;
// Is this an expression that is consumed by another expression? If so,
+++ /dev/null
-//===- ObjCMessage.cpp - Wrapper for ObjC messages and dot syntax -*- C++ -*--//
-//
-// The LLVM Compiler Infrastructure
-//
-// This file is distributed under the University of Illinois Open Source
-// License. See LICENSE.TXT for details.
-//
-//===----------------------------------------------------------------------===//
-//
-// This file defines ObjCMessage which serves as a common wrapper for ObjC
-// message expressions or implicit messages for loading/storing ObjC properties.
-//
-//===----------------------------------------------------------------------===//
-
-#include "clang/StaticAnalyzer/Core/PathSensitive/ObjCMessage.h"
-#include "clang/AST/DeclCXX.h"
-
-using namespace clang;
-using namespace ento;
-
-QualType CallOrObjCMessage::getResultType(ASTContext &ctx) const {
- QualType resultTy;
- bool isLVal = false;
-
- if (isObjCMessage()) {
- resultTy = Msg.getResultType(ctx);
- } else if (const CXXConstructExpr *Ctor =
- CallE.dyn_cast<const CXXConstructExpr *>()) {
- resultTy = Ctor->getType();
- } else {
- const CallExpr *FunctionCall = CallE.get<const CallExpr *>();
-
- isLVal = FunctionCall->isGLValue();
- const Expr *Callee = FunctionCall->getCallee();
- if (const FunctionDecl *FD = State->getSVal(Callee, LCtx).getAsFunctionDecl())
- resultTy = FD->getResultType();
- else
- resultTy = FunctionCall->getType();
- }
-
- if (isLVal)
- resultTy = ctx.getPointerType(resultTy);
-
- return resultTy;
-}
-
-SVal CallOrObjCMessage::getFunctionCallee() const {
- assert(isFunctionCall());
- assert(!isCXXCall());
- const Expr *Fun = CallE.get<const CallExpr *>()->getCallee()->IgnoreParens();
- return State->getSVal(Fun, LCtx);
-}
-
-SVal CallOrObjCMessage::getCXXCallee() const {
- assert(isCXXCall());
- const CallExpr *ActualCall = CallE.get<const CallExpr *>();
- const Expr *callee =
- cast<CXXMemberCallExpr>(ActualCall)->getImplicitObjectArgument();
-
- // FIXME: Will eventually need to cope with member pointers. This is
- // a limitation in getImplicitObjectArgument().
- if (!callee)
- return UnknownVal();
-
- return State->getSVal(callee, LCtx);
-}
-
-SVal
-CallOrObjCMessage::getInstanceMessageReceiver(const LocationContext *LC) const {
- assert(isObjCMessage());
- return Msg.getInstanceReceiverSVal(State, LC);
-}
-
-const Decl *CallOrObjCMessage::getDecl() const {
- if (isCXXCall()) {
- const CXXMemberCallExpr *CE =
- cast<CXXMemberCallExpr>(CallE.dyn_cast<const CallExpr *>());
- assert(CE);
- return CE->getMethodDecl();
- } else if (isObjCMessage()) {
- return Msg.getMethodDecl();
- } else if (isFunctionCall()) {
- // In case of a C style call, use the path sensitive information to find
- // the function declaration.
- SVal CalleeVal = getFunctionCallee();
- return CalleeVal.getAsFunctionDecl();
- }
- return 0;
-}
-
-bool CallOrObjCMessage::isCallbackArg(unsigned Idx, const Type *T) const {
- // If the parameter is 0, it's harmless.
- if (getArgSVal(Idx).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 (const PointerType *PT = dyn_cast<PointerType>(T))
- T = PT->getPointeeType().getTypePtr();
-
- 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 ) {
- const Type *FieldT = I->getType().getTypePtr();
- if (FieldT->isBlockPointerType() || FieldT->isFunctionPointerType())
- return true;
- }
- }
- return false;
-}
-
-bool CallOrObjCMessage::hasNonZeroCallbackArg() const {
- unsigned NumOfArgs = getNumArgs();
-
- // Process ObjC message first.
- if (!CallE) {
- const ObjCMethodDecl *D = Msg.getMethodDecl();
- unsigned Idx = 0;
- for (ObjCMethodDecl::param_const_iterator I = D->param_begin(),
- E = D->param_end(); I != E; ++I, ++Idx) {
- if (NumOfArgs <= Idx)
- break;
-
- if (isCallbackArg(Idx, (*I)->getType().getTypePtr()))
- return true;
- }
- return false;
- }
-
- // Else, assume we are dealing with a Function call.
- const FunctionDecl *FD = 0;
- if (const CXXConstructExpr *Ctor =
- CallE.dyn_cast<const CXXConstructExpr *>())
- FD = Ctor->getConstructor();
-
- const CallExpr * CE = CallE.get<const CallExpr *>();
- FD = dyn_cast_or_null<FunctionDecl>(CE->getCalleeDecl());
-
- // 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 (!FD)
- return false;
-
- unsigned Idx = 0;
- for (FunctionDecl::param_const_iterator I = FD->param_begin(),
- E = FD->param_end(); I != E; ++I, ++Idx) {
- if (NumOfArgs <= Idx)
- break;
-
- if (isCallbackArg(Idx, (*I)->getType().getTypePtr()))
- return true;
- }
- return false;
-}
-
-bool CallOrObjCMessage::isCFCGAllowingEscape(StringRef FName) {
- if (!FName.startswith("CF") && !FName.startswith("CG"))
- return false;
-
- return StrInStrNoCase(FName, "InsertValue") != StringRef::npos ||
- StrInStrNoCase(FName, "AddValue") != StringRef::npos ||
- StrInStrNoCase(FName, "SetValue") != StringRef::npos ||
- StrInStrNoCase(FName, "WithData") != StringRef::npos ||
- StrInStrNoCase(FName, "AppendValue") != StringRef::npos ||
- StrInStrNoCase(FName, "SetAttribute") != StringRef::npos;
-}
-
-
return f; // expected-warning{{leak}}
}
+static int readNothing(void *_ctx, char *buf, int size) {
+ return 0;
+}
+FILE *useFunOpenReadNoRelease() {
+ void *ctx = malloc(sizeof(int));
+ FILE *f = funopen(ctx, readNothing, 0, 0, 0);
+ if (f == 0) {
+ free(ctx);
+ }
+ return f; // expected-warning{{leak}}
+}
+
// Test setbuf, setvbuf.
int my_main_no_warning() {
char *p = malloc(100);