diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp')
-rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp new file mode 100644 index 0000000..f826573 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -0,0 +1,334 @@ +//=- NSErrorChecker.cpp - Coding conventions for uses of NSError -*- 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 a CheckNSError, a flow-insenstive check +// that determines if an Objective-C class interface correctly returns +// a non-void return type. +// +// File under feature request PR 2600. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/Decl.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang; +using namespace ento; + +static bool IsNSError(QualType T, IdentifierInfo *II); +static bool IsCFError(QualType T, IdentifierInfo *II); + +//===----------------------------------------------------------------------===// +// NSErrorMethodChecker +//===----------------------------------------------------------------------===// + +namespace { +class NSErrorMethodChecker + : public Checker< check::ASTDecl<ObjCMethodDecl> > { + mutable IdentifierInfo *II; + +public: + NSErrorMethodChecker() : II(0) { } + + void checkASTDecl(const ObjCMethodDecl *D, + AnalysisManager &mgr, BugReporter &BR) const; +}; +} + +void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D, + AnalysisManager &mgr, + BugReporter &BR) const { + if (!D->isThisDeclarationADefinition()) + return; + if (!D->getResultType()->isVoidType()) + return; + + if (!II) + II = &D->getASTContext().Idents.get("NSError"); + + bool hasNSError = false; + for (ObjCMethodDecl::param_const_iterator + I = D->param_begin(), E = D->param_end(); I != E; ++I) { + if (IsNSError((*I)->getType(), II)) { + hasNSError = true; + break; + } + } + + if (hasNSError) { + const char *err = "Method accepting NSError** " + "should have a non-void return value to indicate whether or not an " + "error occurred"; + PathDiagnosticLocation L = + PathDiagnosticLocation::create(D, BR.getSourceManager()); + BR.EmitBasicReport(D, "Bad return type when passing NSError**", + "Coding conventions (Apple)", err, L); + } +} + +//===----------------------------------------------------------------------===// +// CFErrorFunctionChecker +//===----------------------------------------------------------------------===// + +namespace { +class CFErrorFunctionChecker + : public Checker< check::ASTDecl<FunctionDecl> > { + mutable IdentifierInfo *II; + +public: + CFErrorFunctionChecker() : II(0) { } + + void checkASTDecl(const FunctionDecl *D, + AnalysisManager &mgr, BugReporter &BR) const; +}; +} + +void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, + AnalysisManager &mgr, + BugReporter &BR) const { + if (!D->doesThisDeclarationHaveABody()) + return; + if (!D->getResultType()->isVoidType()) + return; + + if (!II) + II = &D->getASTContext().Idents.get("CFErrorRef"); + + bool hasCFError = false; + for (FunctionDecl::param_const_iterator + I = D->param_begin(), E = D->param_end(); I != E; ++I) { + if (IsCFError((*I)->getType(), II)) { + hasCFError = true; + break; + } + } + + if (hasCFError) { + const char *err = "Function accepting CFErrorRef* " + "should have a non-void return value to indicate whether or not an " + "error occurred"; + PathDiagnosticLocation L = + PathDiagnosticLocation::create(D, BR.getSourceManager()); + BR.EmitBasicReport(D, "Bad return type when passing CFErrorRef*", + "Coding conventions (Apple)", err, L); + } +} + +//===----------------------------------------------------------------------===// +// NSOrCFErrorDerefChecker +//===----------------------------------------------------------------------===// + +namespace { + +class NSErrorDerefBug : public BugType { +public: + NSErrorDerefBug() : BugType("NSError** null dereference", + "Coding conventions (Apple)") {} +}; + +class CFErrorDerefBug : public BugType { +public: + CFErrorDerefBug() : BugType("CFErrorRef* null dereference", + "Coding conventions (Apple)") {} +}; + +} + +namespace { +class NSOrCFErrorDerefChecker + : public Checker< check::Location, + check::Event<ImplicitNullDerefEvent> > { + mutable IdentifierInfo *NSErrorII, *CFErrorII; +public: + bool ShouldCheckNSError, ShouldCheckCFError; + NSOrCFErrorDerefChecker() : NSErrorII(0), CFErrorII(0), + ShouldCheckNSError(0), ShouldCheckCFError(0) { } + + void checkLocation(SVal loc, bool isLoad, const Stmt *S, + CheckerContext &C) const; + void checkEvent(ImplicitNullDerefEvent event) const; +}; +} + +namespace { struct NSErrorOut {}; } +namespace { struct CFErrorOut {}; } + +typedef llvm::ImmutableMap<SymbolRef, unsigned> ErrorOutFlag; + +namespace clang { +namespace ento { + template <> + struct ProgramStateTrait<NSErrorOut> : public ProgramStatePartialTrait<ErrorOutFlag> { + static void *GDMIndex() { static int index = 0; return &index; } + }; + template <> + struct ProgramStateTrait<CFErrorOut> : public ProgramStatePartialTrait<ErrorOutFlag> { + static void *GDMIndex() { static int index = 0; return &index; } + }; +} +} + +template <typename T> +static bool hasFlag(SVal val, ProgramStateRef state) { + if (SymbolRef sym = val.getAsSymbol()) + if (const unsigned *attachedFlags = state->get<T>(sym)) + return *attachedFlags; + return false; +} + +template <typename T> +static void setFlag(ProgramStateRef state, SVal val, CheckerContext &C) { + // We tag the symbol that the SVal wraps. + if (SymbolRef sym = val.getAsSymbol()) + C.addTransition(state->set<T>(sym, true)); +} + +static QualType parameterTypeFromSVal(SVal val, CheckerContext &C) { + const StackFrameContext * + SFC = C.getLocationContext()->getCurrentStackFrame(); + if (const loc::MemRegionVal* X = dyn_cast<loc::MemRegionVal>(&val)) { + const MemRegion* R = X->getRegion(); + if (const VarRegion *VR = R->getAs<VarRegion>()) + if (const StackArgumentsSpaceRegion * + stackReg = dyn_cast<StackArgumentsSpaceRegion>(VR->getMemorySpace())) + if (stackReg->getStackFrame() == SFC) + return VR->getValueType(); + } + + return QualType(); +} + +void NSOrCFErrorDerefChecker::checkLocation(SVal loc, bool isLoad, + const Stmt *S, + CheckerContext &C) const { + if (!isLoad) + return; + if (loc.isUndef() || !isa<Loc>(loc)) + return; + + ASTContext &Ctx = C.getASTContext(); + ProgramStateRef state = C.getState(); + + // If we are loading from NSError**/CFErrorRef* parameter, mark the resulting + // SVal so that we can later check it when handling the + // ImplicitNullDerefEvent event. + // FIXME: Cumbersome! Maybe add hook at construction of SVals at start of + // function ? + + QualType parmT = parameterTypeFromSVal(loc, C); + if (parmT.isNull()) + return; + + if (!NSErrorII) + NSErrorII = &Ctx.Idents.get("NSError"); + if (!CFErrorII) + CFErrorII = &Ctx.Idents.get("CFErrorRef"); + + if (ShouldCheckNSError && IsNSError(parmT, NSErrorII)) { + setFlag<NSErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); + return; + } + + if (ShouldCheckCFError && IsCFError(parmT, CFErrorII)) { + setFlag<CFErrorOut>(state, state->getSVal(cast<Loc>(loc)), C); + return; + } +} + +void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { + if (event.IsLoad) + return; + + SVal loc = event.Location; + ProgramStateRef state = event.SinkNode->getState(); + BugReporter &BR = *event.BR; + + bool isNSError = hasFlag<NSErrorOut>(loc, state); + bool isCFError = false; + if (!isNSError) + isCFError = hasFlag<CFErrorOut>(loc, state); + + if (!(isNSError || isCFError)) + return; + + // Storing to possible null NSError/CFErrorRef out parameter. + + // Emit an error. + std::string err; + llvm::raw_string_ostream os(err); + os << "Potential null dereference. According to coding standards "; + + if (isNSError) + os << "in 'Creating and Returning NSError Objects' the parameter '"; + else + os << "documented in CoreFoundation/CFError.h the parameter '"; + + os << "' may be null."; + + BugType *bug = 0; + if (isNSError) + bug = new NSErrorDerefBug(); + else + bug = new CFErrorDerefBug(); + BugReport *report = new BugReport(*bug, os.str(), + event.SinkNode); + BR.EmitReport(report); +} + +static bool IsNSError(QualType T, IdentifierInfo *II) { + + const PointerType* PPT = T->getAs<PointerType>(); + if (!PPT) + return false; + + const ObjCObjectPointerType* PT = + PPT->getPointeeType()->getAs<ObjCObjectPointerType>(); + + if (!PT) + return false; + + const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); + + // FIXME: Can ID ever be NULL? + if (ID) + return II == ID->getIdentifier(); + + return false; +} + +static bool IsCFError(QualType T, IdentifierInfo *II) { + const PointerType* PPT = T->getAs<PointerType>(); + if (!PPT) return false; + + const TypedefType* TT = PPT->getPointeeType()->getAs<TypedefType>(); + if (!TT) return false; + + return TT->getDecl()->getIdentifier() == II; +} + +void ento::registerNSErrorChecker(CheckerManager &mgr) { + mgr.registerChecker<NSErrorMethodChecker>(); + NSOrCFErrorDerefChecker * + checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); + checker->ShouldCheckNSError = true; +} + +void ento::registerCFErrorChecker(CheckerManager &mgr) { + mgr.registerChecker<CFErrorFunctionChecker>(); + NSOrCFErrorDerefChecker * + checker = mgr.registerChecker<NSOrCFErrorDerefChecker>(); + checker->ShouldCheckCFError = true; +} |