diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2019-01-19 10:04:05 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2019-01-19 10:04:05 +0000 |
commit | 676fbe8105eeb6ff4bb2ed261cb212fcfdbe7b63 (patch) | |
tree | 02a1ac369cb734d0abfa5000dd86e5b7797e6a74 /lib/StaticAnalyzer | |
parent | c7e70c433efc6953dc3888b9fbf9f3512d7da2b0 (diff) | |
download | src-676fbe8105eeb6ff4bb2ed261cb212fcfdbe7b63.tar.gz src-676fbe8105eeb6ff4bb2ed261cb212fcfdbe7b63.zip |
Vendor import of clang trunk r351319 (just before the release_80 branchvendor/clang/clang-trunk-r351319
Notes
Notes:
svn path=/vendor/clang/dist/; revision=343173
svn path=/vendor/clang/clang-trunk-r351319/; revision=343174; tag=vendor/clang/clang-trunk-r351319
Diffstat (limited to 'lib/StaticAnalyzer')
159 files changed, 11696 insertions, 9038 deletions
diff --git a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp b/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp deleted file mode 100644 index 3dec8a58c929..000000000000 --- a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.cpp +++ /dev/null @@ -1,24 +0,0 @@ -//=- AllocationDiagnostics.cpp - Config options for allocation diags *- C++ -*-// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Declares the configuration functions for leaks/allocation diagnostics. -// -//===-------------------------- - -#include "AllocationDiagnostics.h" - -namespace clang { -namespace ento { - -bool shouldIncludeAllocationSiteInLeakDiagnostics(AnalyzerOptions &AOpts) { - return AOpts.getBooleanOption("leak-diagnostics-reference-allocation", - false); -} - -}} diff --git a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h b/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h deleted file mode 100644 index 62b7fab0739a..000000000000 --- a/lib/StaticAnalyzer/Checkers/AllocationDiagnostics.h +++ /dev/null @@ -1,31 +0,0 @@ -//=--- AllocationDiagnostics.h - Config options for allocation diags *- C++ -*-// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Declares the configuration functions for leaks/allocation diagnostics. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONDIAGNOSTICS_H -#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ALLOCATIONDIAGNOSTICS_H - -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" - -namespace clang { namespace ento { - -/// Returns true if leak diagnostics should directly reference -/// the allocatin site (where possible). -/// -/// The default is false. -/// -bool shouldIncludeAllocationSiteInLeakDiagnostics(AnalyzerOptions &AOpts); - -}} - -#endif - diff --git a/lib/StaticAnalyzer/Checkers/AllocationState.h b/lib/StaticAnalyzer/Checkers/AllocationState.h index a6908bd7a651..c8193f77f928 100644 --- a/lib/StaticAnalyzer/Checkers/AllocationState.h +++ b/lib/StaticAnalyzer/Checkers/AllocationState.h @@ -26,6 +26,11 @@ ProgramStateRef markReleased(ProgramStateRef State, SymbolRef Sym, /// AF_InnerBuffer symbols. std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym); +/// 'Sym' represents a pointer to the inner buffer of a container object. +/// This function looks up the memory region of that object in +/// DanglingInternalBufferChecker's program state map. +const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym); + } // end namespace allocation_state } // end namespace ento diff --git a/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp index e4cdc500de6a..b5d0f6620a1d 100644 --- a/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp @@ -14,8 +14,9 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ExprCXX.h" +#include "clang/Analysis/CFGStmtMap.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -37,14 +38,15 @@ class AnalysisOrderChecker check::PostStmt<OffsetOfExpr>, check::PreCall, check::PostCall, + check::EndFunction, check::NewAllocator, check::Bind, check::RegionChanges, check::LiveSymbols> { bool isCallbackEnabled(AnalyzerOptions &Opts, StringRef CallbackName) const { - return Opts.getBooleanOption("*", false, this) || - Opts.getBooleanOption(CallbackName, false, this); + return Opts.getCheckerBooleanOption("*", false, this) || + Opts.getCheckerBooleanOption(CallbackName, false, this); } bool isCallbackEnabled(CheckerContext &C, StringRef CallbackName) const { @@ -54,7 +56,7 @@ class AnalysisOrderChecker bool isCallbackEnabled(ProgramStateRef State, StringRef CallbackName) const { AnalyzerOptions &Opts = State->getStateManager().getOwningEngine() - ->getAnalysisManager().getAnalyzerOptions(); + .getAnalysisManager().getAnalyzerOptions(); return isCallbackEnabled(Opts, CallbackName); } @@ -121,6 +123,23 @@ public: } } + void checkEndFunction(const ReturnStmt *S, CheckerContext &C) const { + if (isCallbackEnabled(C, "EndFunction")) { + llvm::errs() << "EndFunction\nReturnStmt: " << (S ? "yes" : "no") << "\n"; + if (!S) + return; + + llvm::errs() << "CFGElement: "; + CFGStmtMap *Map = C.getCurrentAnalysisDeclContext()->getCFGStmtMap(); + CFGElement LastElement = Map->getBlock(S)->back(); + + if (LastElement.getAs<CFGStmt>()) + llvm::errs() << "CFGStmt\n"; + else if (LastElement.getAs<CFGAutomaticObjDtor>()) + llvm::errs() << "CFGAutomaticObjDtor\n"; + } + } + void checkNewAllocator(const CXXNewExpr *CNE, SVal Target, CheckerContext &C) const { if (isCallbackEnabled(C, "NewAllocator")) diff --git a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp index aadc6bac8d00..5e01012401b2 100644 --- a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// // This file reports various statistics about analyzer visitation. //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclObjC.h" #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index c092610afe2b..20f3092fdba4 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index 933380d494a4..26887be9f258 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/CharUnits.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index 7d6358acbbac..577b5349f62e 100644 --- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -13,14 +13,14 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" -#include "SelectorExtras.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/StmtObjC.h" #include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Analysis/SelectorExtras.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -36,6 +36,7 @@ using namespace clang; using namespace ento; +using namespace llvm; namespace { class APIMisuse : public BugType { @@ -156,6 +157,11 @@ void NilArgChecker::warnIfNilArg(CheckerContext &C, if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) return; + // NOTE: We cannot throw non-fatal errors from warnIfNilExpr, + // because it's called multiple times from some callers, so it'd cause + // an unwanted state split if two or more non-fatal errors are thrown + // within the same checker callback. For now we don't want to, but + // it'll need to be fixed if we ever want to. if (ExplodedNode *N = C.generateErrorNode()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); @@ -208,7 +214,7 @@ void NilArgChecker::generateBugReport(ExplodedNode *N, auto R = llvm::make_unique<BugReport>(*BT, Msg, N); R->addRange(Range); - bugreporter::trackNullOrUndefValue(N, E, *R); + bugreporter::trackExpressionValue(N, E, *R); C.emitReport(std::move(R)); } @@ -526,93 +532,59 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, //===----------------------------------------------------------------------===// namespace { -class CFRetainReleaseChecker : public Checker< check::PreStmt<CallExpr> > { - mutable std::unique_ptr<APIMisuse> BT; - mutable IdentifierInfo *Retain, *Release, *MakeCollectable, *Autorelease; +class CFRetainReleaseChecker : public Checker<check::PreCall> { + mutable APIMisuse BT{this, "null passed to CF memory management function"}; + CallDescription CFRetain{"CFRetain", 1}, + CFRelease{"CFRelease", 1}, + CFMakeCollectable{"CFMakeCollectable", 1}, + CFAutorelease{"CFAutorelease", 1}; public: - CFRetainReleaseChecker() - : Retain(nullptr), Release(nullptr), MakeCollectable(nullptr), - Autorelease(nullptr) {} - void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; }; } // end anonymous namespace -void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, +void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - // If the CallExpr doesn't have exactly 1 argument just give up checking. - if (CE->getNumArgs() != 1) + // TODO: Make this check part of CallDescription. + if (!Call.isGlobalCFunction()) return; - ProgramStateRef state = C.getState(); - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return; - - if (!BT) { - ASTContext &Ctx = C.getASTContext(); - Retain = &Ctx.Idents.get("CFRetain"); - Release = &Ctx.Idents.get("CFRelease"); - MakeCollectable = &Ctx.Idents.get("CFMakeCollectable"); - Autorelease = &Ctx.Idents.get("CFAutorelease"); - BT.reset(new APIMisuse( - this, "null passed to CF memory management function")); - } - // Check if we called CFRetain/CFRelease/CFMakeCollectable/CFAutorelease. - const IdentifierInfo *FuncII = FD->getIdentifier(); - if (!(FuncII == Retain || FuncII == Release || FuncII == MakeCollectable || - FuncII == Autorelease)) + if (!(Call.isCalled(CFRetain) || Call.isCalled(CFRelease) || + Call.isCalled(CFMakeCollectable) || Call.isCalled(CFAutorelease))) return; - // FIXME: The rest of this just checks that the argument is non-null. - // It should probably be refactored and combined with NonNullParamChecker. - // Get the argument's value. - const Expr *Arg = CE->getArg(0); - SVal ArgVal = C.getSVal(Arg); + SVal ArgVal = Call.getArgSVal(0); Optional<DefinedSVal> DefArgVal = ArgVal.getAs<DefinedSVal>(); if (!DefArgVal) return; - // Get a NULL value. - SValBuilder &svalBuilder = C.getSValBuilder(); - DefinedSVal zero = - svalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>(); - - // Make an expression asserting that they're equal. - DefinedOrUnknownSVal ArgIsNull = svalBuilder.evalEQ(state, zero, *DefArgVal); - - // Are they equal? - ProgramStateRef stateTrue, stateFalse; - std::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); + // Is it null? + ProgramStateRef state = C.getState(); + ProgramStateRef stateNonNull, stateNull; + std::tie(stateNonNull, stateNull) = state->assume(*DefArgVal); - if (stateTrue && !stateFalse) { - ExplodedNode *N = C.generateErrorNode(stateTrue); + if (!stateNonNull) { + ExplodedNode *N = C.generateErrorNode(stateNull); if (!N) return; - const char *description; - if (FuncII == Retain) - description = "Null pointer argument in call to CFRetain"; - else if (FuncII == Release) - description = "Null pointer argument in call to CFRelease"; - else if (FuncII == MakeCollectable) - description = "Null pointer argument in call to CFMakeCollectable"; - else if (FuncII == Autorelease) - description = "Null pointer argument in call to CFAutorelease"; - else - llvm_unreachable("impossible case"); + SmallString<64> Str; + raw_svector_ostream OS(Str); + OS << "Null pointer argument in call to " + << cast<FunctionDecl>(Call.getDecl())->getName(); - auto report = llvm::make_unique<BugReport>(*BT, description, N); - report->addRange(Arg->getSourceRange()); - bugreporter::trackNullOrUndefValue(N, Arg, *report); + auto report = llvm::make_unique<BugReport>(BT, OS.str(), N); + report->addRange(Call.getArgSourceRange(0)); + bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report); C.emitReport(std::move(report)); return; } // From here on, we know the argument is non-null. - C.addTransition(stateFalse); + C.addTransition(stateNonNull); } //===----------------------------------------------------------------------===// @@ -828,7 +800,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, //===----------------------------------------------------------------------===// // The map from container symbol to the container count symbol. -// We currently will remember the last countainer count symbol encountered. +// We currently will remember the last container count symbol encountered. REGISTER_MAP_WITH_PROGRAMSTATE(ContainerCountMap, SymbolRef, SymbolRef) REGISTER_MAP_WITH_PROGRAMSTATE(ContainerNonEmptyMap, SymbolRef, bool) diff --git a/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index c31f2794df6a..00d08b371f37 100644 --- a/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" diff --git a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp index f26f73129e78..3008eddd397e 100644 --- a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index 0e781d08e24c..f98027942e18 100644 --- a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Basic/Builtins.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -101,9 +101,10 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // This must be resolvable at compile time, so we defer to the constant // evaluator for a value. SVal V = UnknownVal(); - llvm::APSInt Result; - if (CE->EvaluateAsInt(Result, C.getASTContext(), Expr::SE_NoSideEffects)) { + Expr::EvalResult EVResult; + if (CE->EvaluateAsInt(EVResult, C.getASTContext(), Expr::SE_NoSideEffects)) { // Make sure the result has the correct type. + llvm::APSInt Result = EVResult.Val.getInt(); SValBuilder &SVB = C.getSValBuilder(); BasicValueFactory &BVF = SVB.getBasicValueFactory(); BVF.getAPSIntType(CE->getType()).apply(Result); diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 5bb4770b5675..10fb0bd3536c 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -3,7 +3,6 @@ set(LLVM_LINK_COMPONENTS ) add_clang_library(clangStaticAnalyzerCheckers - AllocationDiagnostics.cpp AnalysisOrderChecker.cpp AnalyzerStatsChecker.cpp ArrayBoundChecker.cpp @@ -23,7 +22,6 @@ add_clang_library(clangStaticAnalyzerCheckers CheckSizeofPointer.cpp CheckerDocumentation.cpp ChrootChecker.cpp - ClangCheckers.cpp CloneChecker.cpp ConversionChecker.cpp CXXSelfAssignmentChecker.cpp @@ -35,6 +33,7 @@ add_clang_library(clangStaticAnalyzerCheckers DivZeroChecker.cpp DynamicTypePropagation.cpp DynamicTypeChecker.cpp + EnumCastOutOfRangeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp GCDAntipatternChecker.cpp @@ -52,7 +51,7 @@ add_clang_library(clangStaticAnalyzerCheckers MallocOverflowSecurityChecker.cpp MallocSizeofChecker.cpp MmapWriteExecChecker.cpp - MisusedMovedObjectChecker.cpp + MoveChecker.cpp MPI-Checker/MPIBugReporter.cpp MPI-Checker/MPIChecker.cpp MPI-Checker/MPIFunctionClassifier.cpp @@ -76,7 +75,8 @@ add_clang_library(clangStaticAnalyzerCheckers PointerArithChecker.cpp PointerSubChecker.cpp PthreadLockChecker.cpp - RetainCountChecker.cpp + RetainCountChecker/RetainCountChecker.cpp + RetainCountChecker/RetainCountDiagnostics.cpp ReturnPointerRangeChecker.cpp ReturnUndefChecker.cpp RunLoopAutoreleaseLeakChecker.cpp @@ -93,7 +93,8 @@ add_clang_library(clangStaticAnalyzerCheckers UndefResultChecker.cpp UndefinedArraySubscriptChecker.cpp UndefinedAssignmentChecker.cpp - UninitializedObjectChecker.cpp + UninitializedObject/UninitializedObjectChecker.cpp + UninitializedObject/UninitializedPointee.cpp UnixAPIChecker.cpp UnreachableCodeChecker.cpp VforkChecker.cpp @@ -101,9 +102,6 @@ add_clang_library(clangStaticAnalyzerCheckers ValistChecker.cpp VirtualCallChecker.cpp - DEPENDS - ClangSACheckers - LINK_LIBS clangAST clangASTMatchers diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 12a576e5d80d..8bffada69b9b 100644 --- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "InterCheckerAPI.h" #include "clang/Basic/CharInfo.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -124,6 +124,7 @@ public: void evalStdCopyBackward(CheckerContext &C, const CallExpr *CE) const; void evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const; void evalMemset(CheckerContext &C, const CallExpr *CE) const; + void evalBzero(CheckerContext &C, const CallExpr *CE) const; // Utility methods std::pair<ProgramStateRef , ProgramStateRef > @@ -158,7 +159,7 @@ public: static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR); - static bool memsetAux(const Expr *DstBuffer, const Expr *CharE, + static bool memsetAux(const Expr *DstBuffer, SVal CharE, const Expr *Size, CheckerContext &C, ProgramStateRef &State); @@ -187,7 +188,7 @@ public: const Expr *Buf, const char *message = nullptr, bool WarnAboutSize = false) const { - // This is a convenience override. + // This is a convenience overload. return CheckBufferAccess(C, state, Size, Buf, nullptr, message, nullptr, WarnAboutSize); } @@ -553,7 +554,8 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State, BuiltinBug *BT = static_cast<BuiltinBug *>(BT_Null.get()); auto Report = llvm::make_unique<BugReport>(*BT, WarningMsg, N); Report->addRange(S->getSourceRange()); - bugreporter::trackNullOrUndefValue(N, S, *Report); + if (const auto *Ex = dyn_cast<Expr>(S)) + bugreporter::trackExpressionValue(N, Ex, *Report); C.emitReport(std::move(Report)); } } @@ -1004,11 +1006,10 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, } } -bool CStringChecker::memsetAux(const Expr *DstBuffer, const Expr *CharE, +bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, const Expr *Size, CheckerContext &C, ProgramStateRef &State) { SVal MemVal = C.getSVal(DstBuffer); - SVal CharVal = C.getSVal(CharE); SVal SizeVal = C.getSVal(Size); const MemRegion *MR = MemVal.getAsRegion(); if (!MR) @@ -2183,13 +2184,59 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { // According to the values of the arguments, bind the value of the second // argument to the destination buffer and set string length, or just // invalidate the destination buffer. - if (!memsetAux(Mem, CharE, Size, C, State)) + if (!memsetAux(Mem, C.getSVal(CharE), Size, C, State)) return; State = State->BindExpr(CE, LCtx, MemVal); C.addTransition(State); } +void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { + if (CE->getNumArgs() != 2) + return; + + CurrentFunctionDescription = "memory clearance function"; + + const Expr *Mem = CE->getArg(0); + const Expr *Size = CE->getArg(1); + SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().IntTy); + + ProgramStateRef State = C.getState(); + + // See if the size argument is zero. + SVal SizeVal = C.getSVal(Size); + QualType SizeTy = Size->getType(); + + ProgramStateRef StateZeroSize, StateNonZeroSize; + std::tie(StateZeroSize, StateNonZeroSize) = + assumeZero(C, State, SizeVal, SizeTy); + + // If the size is zero, there won't be any actual memory access, + // In this case we just return. + if (StateZeroSize && !StateNonZeroSize) { + C.addTransition(StateZeroSize); + return; + } + + // Get the value of the memory area. + SVal MemVal = C.getSVal(Mem); + + // Ensure the memory area is not null. + // If it is NULL there will be a NULL pointer dereference. + State = checkNonNull(C, StateNonZeroSize, Mem, MemVal); + if (!State) + return; + + State = CheckBufferAccess(C, State, Size, Mem); + if (!State) + return; + + if (!memsetAux(Mem, Zero, Size, C, State)) + return; + + C.addTransition(State); +} + static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) { IdentifierInfo *II = FD->getIdentifier(); if (!II) @@ -2207,60 +2254,86 @@ static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) { // The driver method, and other Checker callbacks. //===----------------------------------------------------------------------===// -bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { +static CStringChecker::FnCheck identifyCall(const CallExpr *CE, + CheckerContext &C) { const FunctionDecl *FDecl = C.getCalleeDecl(CE); - if (!FDecl) - return false; + return nullptr; + + // Pro-actively check that argument types are safe to do arithmetic upon. + // We do not want to crash if someone accidentally passes a structure + // into, say, a C++ overload of any of these functions. + if (isCPPStdLibraryFunction(FDecl, "copy")) { + if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType()) + return nullptr; + return &CStringChecker::evalStdCopy; + } else if (isCPPStdLibraryFunction(FDecl, "copy_backward")) { + if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType()) + return nullptr; + return &CStringChecker::evalStdCopyBackward; + } else { + // An umbrella check for all C library functions. + for (auto I: CE->arguments()) { + QualType T = I->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + return nullptr; + } + } // FIXME: Poorly-factored string switches are slow. - FnCheck evalFunction = nullptr; if (C.isCLibraryFunction(FDecl, "memcpy")) - evalFunction = &CStringChecker::evalMemcpy; + return &CStringChecker::evalMemcpy; else if (C.isCLibraryFunction(FDecl, "mempcpy")) - evalFunction = &CStringChecker::evalMempcpy; + return &CStringChecker::evalMempcpy; else if (C.isCLibraryFunction(FDecl, "memcmp")) - evalFunction = &CStringChecker::evalMemcmp; + return &CStringChecker::evalMemcmp; else if (C.isCLibraryFunction(FDecl, "memmove")) - evalFunction = &CStringChecker::evalMemmove; - else if (C.isCLibraryFunction(FDecl, "memset")) - evalFunction = &CStringChecker::evalMemset; + return &CStringChecker::evalMemmove; + else if (C.isCLibraryFunction(FDecl, "memset") || + C.isCLibraryFunction(FDecl, "explicit_memset")) + return &CStringChecker::evalMemset; else if (C.isCLibraryFunction(FDecl, "strcpy")) - evalFunction = &CStringChecker::evalStrcpy; + return &CStringChecker::evalStrcpy; else if (C.isCLibraryFunction(FDecl, "strncpy")) - evalFunction = &CStringChecker::evalStrncpy; + return &CStringChecker::evalStrncpy; else if (C.isCLibraryFunction(FDecl, "stpcpy")) - evalFunction = &CStringChecker::evalStpcpy; + return &CStringChecker::evalStpcpy; else if (C.isCLibraryFunction(FDecl, "strlcpy")) - evalFunction = &CStringChecker::evalStrlcpy; + return &CStringChecker::evalStrlcpy; else if (C.isCLibraryFunction(FDecl, "strcat")) - evalFunction = &CStringChecker::evalStrcat; + return &CStringChecker::evalStrcat; else if (C.isCLibraryFunction(FDecl, "strncat")) - evalFunction = &CStringChecker::evalStrncat; + return &CStringChecker::evalStrncat; else if (C.isCLibraryFunction(FDecl, "strlcat")) - evalFunction = &CStringChecker::evalStrlcat; + return &CStringChecker::evalStrlcat; else if (C.isCLibraryFunction(FDecl, "strlen")) - evalFunction = &CStringChecker::evalstrLength; + return &CStringChecker::evalstrLength; else if (C.isCLibraryFunction(FDecl, "strnlen")) - evalFunction = &CStringChecker::evalstrnLength; + return &CStringChecker::evalstrnLength; else if (C.isCLibraryFunction(FDecl, "strcmp")) - evalFunction = &CStringChecker::evalStrcmp; + return &CStringChecker::evalStrcmp; else if (C.isCLibraryFunction(FDecl, "strncmp")) - evalFunction = &CStringChecker::evalStrncmp; + return &CStringChecker::evalStrncmp; else if (C.isCLibraryFunction(FDecl, "strcasecmp")) - evalFunction = &CStringChecker::evalStrcasecmp; + return &CStringChecker::evalStrcasecmp; else if (C.isCLibraryFunction(FDecl, "strncasecmp")) - evalFunction = &CStringChecker::evalStrncasecmp; + return &CStringChecker::evalStrncasecmp; else if (C.isCLibraryFunction(FDecl, "strsep")) - evalFunction = &CStringChecker::evalStrsep; + return &CStringChecker::evalStrsep; else if (C.isCLibraryFunction(FDecl, "bcopy")) - evalFunction = &CStringChecker::evalBcopy; + return &CStringChecker::evalBcopy; else if (C.isCLibraryFunction(FDecl, "bcmp")) - evalFunction = &CStringChecker::evalMemcmp; - else if (isCPPStdLibraryFunction(FDecl, "copy")) - evalFunction = &CStringChecker::evalStdCopy; - else if (isCPPStdLibraryFunction(FDecl, "copy_backward")) - evalFunction = &CStringChecker::evalStdCopyBackward; + return &CStringChecker::evalMemcmp; + else if (C.isCLibraryFunction(FDecl, "bzero") || + C.isCLibraryFunction(FDecl, "explicit_bzero")) + return &CStringChecker::evalBzero; + + return nullptr; +} + +bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { + + FnCheck evalFunction = identifyCall(CE, C); // If the callee isn't a string function, let another checker handle it. if (!evalFunction) @@ -2384,9 +2457,6 @@ void CStringChecker::checkLiveSymbols(ProgramStateRef state, void CStringChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { - if (!SR.hasDeadSymbols()) - return; - ProgramStateRef state = C.getState(); CStringLengthTy Entries = state->get<CStringLength>(); if (Entries.isEmpty()) diff --git a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp index 8b4aa857e775..bbeb41c5f3cf 100644 --- a/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp @@ -12,7 +12,7 @@ // of bytes to copy. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Expr.h" #include "clang/AST/OperationKinds.h" #include "clang/AST/StmtVisitor.h" @@ -90,7 +90,16 @@ class WalkAST: public StmtVisitor<WalkAST> { /// strlcpy(dst, "abcd", 4); /// strlcpy(dst + 3, "abcd", 2); /// strlcpy(dst, "abcd", cpy); - bool containsBadStrlcpyPattern(const CallExpr *CE); + /// Identify erroneous patterns in the last argument to strlcat - the number + /// of bytes to copy. + /// The bad pattern checked is when the last argument is basically + /// pointing to the destination buffer size or argument larger or + /// equal to. + /// char dst[2]; + /// strlcat(dst, src2, sizeof(dst)); + /// strlcat(dst, src2, 2); + /// strlcat(dst, src2, 10); + bool containsBadStrlcpyStrlcatPattern(const CallExpr *CE); public: WalkAST(const CheckerBase *Checker, BugReporter &BR, AnalysisDeclContext *AC) @@ -142,15 +151,19 @@ bool WalkAST::containsBadStrncatPattern(const CallExpr *CE) { return false; } -bool WalkAST::containsBadStrlcpyPattern(const CallExpr *CE) { +bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { if (CE->getNumArgs() != 3) return false; + const FunctionDecl *FD = CE->getDirectCallee(); + bool Append = CheckerContext::isCLibraryFunction(FD, "strlcat"); const Expr *DstArg = CE->getArg(0); const Expr *LenArg = CE->getArg(2); const auto *DstArgDecl = dyn_cast<DeclRefExpr>(DstArg->IgnoreParenImpCasts()); const auto *LenArgDecl = dyn_cast<DeclRefExpr>(LenArg->IgnoreParenLValueCasts()); uint64_t DstOff = 0; + if (isSizeof(LenArg, DstArg)) + return false; // - size_t dstlen = sizeof(dst) if (LenArgDecl) { const auto *LenArgVal = dyn_cast<VarDecl>(LenArgDecl->getDecl()); @@ -181,8 +194,14 @@ bool WalkAST::containsBadStrlcpyPattern(const CallExpr *CE) { if (const auto *Buffer = dyn_cast<ConstantArrayType>(DstArgDecl->getType())) { ASTContext &C = BR.getContext(); uint64_t BufferLen = C.getTypeSize(Buffer) / 8; - if ((BufferLen - DstOff) < ILRawVal) - return true; + auto RemainingBufferLen = BufferLen - DstOff; + if (Append) { + if (RemainingBufferLen <= ILRawVal) + return true; + } else { + if (RemainingBufferLen < ILRawVal) + return true; + } } } } @@ -219,8 +238,9 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { "C String API", os.str(), Loc, LenArg->getSourceRange()); } - } else if (CheckerContext::isCLibraryFunction(FD, "strlcpy")) { - if (containsBadStrlcpyPattern(CE)) { + } else if (CheckerContext::isCLibraryFunction(FD, "strlcpy") || + CheckerContext::isCLibraryFunction(FD, "strlcat")) { + if (containsBadStrlcpyStrlcatPattern(CE)) { const Expr *DstArg = CE->getArg(0); const Expr *LenArg = CE->getArg(2); PathDiagnosticLocation Loc = @@ -230,13 +250,17 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { SmallString<256> S; llvm::raw_svector_ostream os(S); - os << "The third argument is larger than the size of the input buffer. "; + os << "The third argument allows to potentially copy more bytes than it should. "; + os << "Replace with the value "; if (!DstName.empty()) - os << "Replace with the value 'sizeof(" << DstName << ")` or lower"; + os << "sizeof(" << DstName << ")"; + else + os << "sizeof(<destination buffer>)"; + os << " or lower"; BR.EmitBasicReport(FD, Checker, "Anti-pattern in the argument", - "C String API", os.str(), Loc, - LenArg->getSourceRange()); + "C String API", os.str(), Loc, + LenArg->getSourceRange()); } } diff --git a/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp index d1d37c75dfcc..0b539e1188eb 100644 --- a/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CXXSelfAssignmentChecker.cpp @@ -18,7 +18,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" diff --git a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index 20a46843e23e..ef30dc74c39d 100644 --- a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -108,7 +108,7 @@ void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C, R->addRange(BadE->getSourceRange()); if (BadE->isGLValue()) BadE = bugreporter::getDerefExpr(BadE); - bugreporter::trackNullOrUndefValue(N, BadE, *R); + bugreporter::trackExpressionValue(N, BadE, *R); } C.emitReport(std::move(R)); } @@ -185,9 +185,9 @@ bool CallAndMessageChecker::uninitRefOrPointer( LazyInit_BT(BD, BT); auto R = llvm::make_unique<BugReport>(*BT, Os.str(), N); R->addRange(ArgRange); - if (ArgEx) { - bugreporter::trackNullOrUndefValue(N, ArgEx, *R); - } + if (ArgEx) + bugreporter::trackExpressionValue(N, ArgEx, *R); + C.emitReport(std::move(R)); } return true; @@ -196,6 +196,47 @@ bool CallAndMessageChecker::uninitRefOrPointer( return false; } +namespace { +class FindUninitializedField { +public: + SmallVector<const FieldDecl *, 10> FieldChain; + +private: + StoreManager &StoreMgr; + MemRegionManager &MrMgr; + Store store; + +public: + FindUninitializedField(StoreManager &storeMgr, MemRegionManager &mrMgr, + Store s) + : StoreMgr(storeMgr), MrMgr(mrMgr), store(s) {} + + bool Find(const TypedValueRegion *R) { + QualType T = R->getValueType(); + if (const RecordType *RT = T->getAsStructureType()) { + const RecordDecl *RD = RT->getDecl()->getDefinition(); + assert(RD && "Referred record has no definition"); + for (const auto *I : RD->fields()) { + const FieldRegion *FR = MrMgr.getFieldRegion(I, R); + FieldChain.push_back(I); + T = I->getType(); + if (T->getAsStructureType()) { + if (Find(FR)) + return true; + } else { + const SVal &V = StoreMgr.getBinding(store, loc::MemRegionVal(FR)); + if (V.isUndef()) + return true; + } + FieldChain.pop_back(); + } + } + + return false; + } +}; +} // namespace + bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, SVal V, SourceRange ArgRange, @@ -223,7 +264,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, R->addRange(ArgRange); if (ArgEx) - bugreporter::trackNullOrUndefValue(N, ArgEx, *R); + bugreporter::trackExpressionValue(N, ArgEx, *R); C.emitReport(std::move(R)); } return true; @@ -232,47 +273,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, if (!CheckUninitFields) return false; - if (Optional<nonloc::LazyCompoundVal> LV = - V.getAs<nonloc::LazyCompoundVal>()) { - - class FindUninitializedField { - public: - SmallVector<const FieldDecl *, 10> FieldChain; - private: - StoreManager &StoreMgr; - MemRegionManager &MrMgr; - Store store; - public: - FindUninitializedField(StoreManager &storeMgr, - MemRegionManager &mrMgr, Store s) - : StoreMgr(storeMgr), MrMgr(mrMgr), store(s) {} - - bool Find(const TypedValueRegion *R) { - QualType T = R->getValueType(); - if (const RecordType *RT = T->getAsStructureType()) { - const RecordDecl *RD = RT->getDecl()->getDefinition(); - assert(RD && "Referred record has no definition"); - for (const auto *I : RD->fields()) { - const FieldRegion *FR = MrMgr.getFieldRegion(I, R); - FieldChain.push_back(I); - T = I->getType(); - if (T->getAsStructureType()) { - if (Find(FR)) - return true; - } - else { - const SVal &V = StoreMgr.getBinding(store, loc::MemRegionVal(FR)); - if (V.isUndef()) - return true; - } - FieldChain.pop_back(); - } - } - - return false; - } - }; - + if (auto LV = V.getAs<nonloc::LazyCompoundVal>()) { const LazyCompoundValData *D = LV->getCVData(); FindUninitializedField F(C.getState()->getStateManager().getStoreManager(), C.getSValBuilder().getRegionManager(), @@ -305,6 +306,8 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, auto R = llvm::make_unique<BugReport>(*BT, os.str(), N); R->addRange(ArgRange); + if (ArgEx) + bugreporter::trackExpressionValue(N, ArgEx, *R); // FIXME: enhance track back for uninitialized value for arbitrary // memregions C.emitReport(std::move(R)); @@ -364,7 +367,7 @@ void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE, Desc = "Argument to 'delete' is uninitialized"; BugType *BT = BT_cxx_delete_undef.get(); auto R = llvm::make_unique<BugReport>(*BT, Desc, N); - bugreporter::trackNullOrUndefValue(N, DE, *R); + bugreporter::trackExpressionValue(N, DE, *R); C.emitReport(std::move(R)); return; } @@ -493,7 +496,7 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, // FIXME: getTrackNullOrUndefValueVisitor can't handle "super" yet. if (const Expr *ReceiverE = ME->getInstanceReceiver()) - bugreporter::trackNullOrUndefValue(N, ReceiverE, *R); + bugreporter::trackExpressionValue(N, ReceiverE, *R); C.emitReport(std::move(R)); } return; @@ -534,7 +537,7 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, report->addRange(ME->getReceiverRange()); // FIXME: This won't track "self" in messages to super. if (const Expr *receiver = ME->getInstanceReceiver()) { - bugreporter::trackNullOrUndefValue(N, receiver, *report); + bugreporter::trackExpressionValue(N, receiver, *report); } C.emitReport(std::move(report)); } diff --git a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 059553b21995..5deb62d32311 100644 --- a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -11,7 +11,7 @@ // whether the size of the symbolic region is a multiple of the size of T. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/CharUnits.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp index 00e903355720..2bd3879627cb 100644 --- a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index f4d2e32cef11..00a912f27a8d 100644 --- a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -28,7 +28,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -178,20 +178,12 @@ private: }; } // End anonymous namespace. -typedef llvm::ImmutableSet<SymbolRef> SymbolSet; /// Maps from the symbol for a class instance to the set of /// symbols remaining that must be released in -dealloc. +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(SymbolSet, SymbolRef) REGISTER_MAP_WITH_PROGRAMSTATE(UnreleasedIvarMap, SymbolRef, SymbolSet) -namespace clang { -namespace ento { -template<> struct ProgramStateTrait<SymbolSet> -: public ProgramStatePartialTrait<SymbolSet> { - static void *GDMIndex() { static int index = 0; return &index; } -}; -} -} /// An AST check that diagnose when the class requires a -dealloc method and /// is missing one. @@ -723,6 +715,10 @@ bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, const ObjCMethodCall &M, CheckerContext &C) const { + // TODO: Apart from unknown/undefined receivers, this may happen when + // dealloc is called as a class method. Should we warn? + if (!DeallocedValue) + return false; // Find the property backing the instance variable that M // is dealloc'ing. @@ -761,15 +757,15 @@ ObjCDeallocChecker::ObjCDeallocChecker() MissingReleaseBugType.reset( new BugType(this, "Missing ivar release (leak)", - categories::MemoryCoreFoundationObjectiveC)); + categories::MemoryRefCount)); ExtraReleaseBugType.reset( new BugType(this, "Extra ivar release", - categories::MemoryCoreFoundationObjectiveC)); + categories::MemoryRefCount)); MistakenDeallocBugType.reset( new BugType(this, "Mistaken dealloc", - categories::MemoryCoreFoundationObjectiveC)); + categories::MemoryRefCount)); } void ObjCDeallocChecker::initIdentifierInfoAndSelectors( diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp index cc4c0c3db846..fe6715595e6f 100644 --- a/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Type.h" diff --git a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 202233acffab..163ca9d8556f 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/TargetInfo.h" @@ -29,10 +29,10 @@ static bool isArc4RandomAvailable(const ASTContext &Ctx) { const llvm::Triple &T = Ctx.getTargetInfo().getTriple(); return T.getVendor() == llvm::Triple::Apple || T.getOS() == llvm::Triple::CloudABI || - T.getOS() == llvm::Triple::FreeBSD || - T.getOS() == llvm::Triple::NetBSD || - T.getOS() == llvm::Triple::OpenBSD || - T.getOS() == llvm::Triple::DragonFly; + T.isOSFreeBSD() || + T.isOSNetBSD() || + T.isOSOpenBSD() || + T.isOSDragonFly(); } namespace { @@ -188,7 +188,7 @@ void WalkAST::VisitForStmt(ForStmt *FS) { } //===----------------------------------------------------------------------===// -// Check: floating poing variable used as loop counter. +// Check: floating point variable used as loop counter. // Originally: <rdar://problem/6336718> // Implements: CERT security coding advisory FLP-30. //===----------------------------------------------------------------------===// @@ -597,9 +597,10 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) { unsigned suffix = 0; if (ArgSuffix.second >= 0) { const Expr *suffixEx = CE->getArg((unsigned)ArgSuffix.second); - llvm::APSInt Result; - if (!suffixEx->EvaluateAsInt(Result, BR.getContext())) + Expr::EvalResult EVResult; + if (!suffixEx->EvaluateAsInt(EVResult, BR.getContext())) return; + llvm::APSInt Result = EVResult.Val.getInt(); // FIXME: Issue a warning. if (Result.isNegative()) return; @@ -650,14 +651,14 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { const auto *Target = CE->getArg(0)->IgnoreImpCasts(), *Source = CE->getArg(1)->IgnoreImpCasts(); - if (const auto *DeclRef = dyn_cast<DeclRefExpr>(Target)) - if (const auto *Array = dyn_cast<ConstantArrayType>(DeclRef->getType())) { - uint64_t ArraySize = BR.getContext().getTypeSize(Array) / 8; - if (const auto *String = dyn_cast<StringLiteral>(Source)) { - if (ArraySize >= String->getLength() + 1) - return; - } + + if (const auto *Array = dyn_cast<ConstantArrayType>(Target->getType())) { + uint64_t ArraySize = BR.getContext().getTypeSize(Array) / 8; + if (const auto *String = dyn_cast<StringLiteral>(Source)) { + if (ArraySize >= String->getLength() + 1) + return; } + } // Issue a warning. PathDiagnosticLocation CELoc = diff --git a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp index e079a8cb12be..7688b713b06b 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp index 7862a4c25681..44fac0278bdd 100644 --- a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -169,7 +169,7 @@ public: /// This callback should be used by the checkers to aggressively clean /// up/reduce the checker state, which is important for reducing the overall /// memory usage. Specifically, if a checker keeps symbol specific information - /// in the sate, it can and should be dropped after the symbol becomes dead. + /// in the state, it can and should be dropped after the symbol becomes dead. /// In addition, reporting a bug as soon as the checker becomes dead leads to /// more precise diagnostics. (For example, one should report that a malloced /// variable is not freed right after it goes out of scope.) diff --git a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index b38992b0e030..673608db1a1d 100644 --- a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/ClangCheckers.cpp b/lib/StaticAnalyzer/Checkers/ClangCheckers.cpp deleted file mode 100644 index fb9e366c3de0..000000000000 --- a/lib/StaticAnalyzer/Checkers/ClangCheckers.cpp +++ /dev/null @@ -1,32 +0,0 @@ -//===--- ClangCheckers.h - Provides builtin checkers ------------*- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Checkers/ClangCheckers.h" -#include "clang/StaticAnalyzer/Core/CheckerRegistry.h" - -// FIXME: This is only necessary as long as there are checker registration -// functions that do additional work besides mgr.registerChecker<CLASS>(). -// The only checkers that currently do this are: -// - NSAutoreleasePoolChecker -// - NSErrorChecker -// - ObjCAtSyncChecker -// It's probably worth including this information in Checkers.td to minimize -// boilerplate code. -#include "ClangSACheckers.h" - -using namespace clang; -using namespace ento; - -void ento::registerBuiltinCheckers(CheckerRegistry ®istry) { -#define GET_CHECKERS -#define CHECKER(FULLNAME,CLASS,DESCFILE,HELPTEXT,GROUPINDEX,HIDDEN) \ - registry.addChecker(register##CLASS, FULLNAME, HELPTEXT); -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef GET_CHECKERS -} diff --git a/lib/StaticAnalyzer/Checkers/ClangSACheckers.h b/lib/StaticAnalyzer/Checkers/ClangSACheckers.h deleted file mode 100644 index d6e96f27a75e..000000000000 --- a/lib/StaticAnalyzer/Checkers/ClangSACheckers.h +++ /dev/null @@ -1,37 +0,0 @@ -//===--- ClangSACheckers.h - Registration functions for Checkers *- C++ -*-===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// Declares the registation functions for the checkers defined in -// libclangStaticAnalyzerCheckers. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CLANGSACHECKERS_H -#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_CLANGSACHECKERS_H - -#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" - -namespace clang { - -namespace ento { -class CheckerManager; -class CheckerRegistry; - -#define GET_CHECKERS -#define CHECKER(FULLNAME,CLASS,CXXFILE,HELPTEXT,GROUPINDEX,HIDDEN) \ - void register##CLASS(CheckerManager &mgr); -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef CHECKER -#undef GET_CHECKERS - -} // end ento namespace - -} // end clang namespace - -#endif diff --git a/lib/StaticAnalyzer/Checkers/CloneChecker.cpp b/lib/StaticAnalyzer/Checkers/CloneChecker.cpp index ee517ed97770..89354b866004 100644 --- a/lib/StaticAnalyzer/Checkers/CloneChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CloneChecker.cpp @@ -13,7 +13,7 @@ /// //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Analysis/CloneDetection.h" #include "clang/Basic/Diagnostic.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -42,7 +42,7 @@ public: void reportClones(BugReporter &BR, AnalysisManager &Mgr, std::vector<CloneDetector::CloneGroup> &CloneGroups) const; - /// Reports only suspicious clones to the user along with informaton + /// Reports only suspicious clones to the user along with information /// that explain why they are suspicious. void reportSuspiciousClones( BugReporter &BR, AnalysisManager &Mgr, @@ -63,18 +63,18 @@ void CloneChecker::checkEndOfTranslationUnit(const TranslationUnitDecl *TU, // At this point, every statement in the translation unit has been analyzed by // the CloneDetector. The only thing left to do is to report the found clones. - int MinComplexity = Mgr.getAnalyzerOptions().getOptionAsInteger( + int MinComplexity = Mgr.getAnalyzerOptions().getCheckerIntegerOption( "MinimumCloneComplexity", 50, this); assert(MinComplexity >= 0); - bool ReportSuspiciousClones = Mgr.getAnalyzerOptions().getBooleanOption( - "ReportSuspiciousClones", true, this); + bool ReportSuspiciousClones = Mgr.getAnalyzerOptions() + .getCheckerBooleanOption("ReportSuspiciousClones", true, this); - bool ReportNormalClones = Mgr.getAnalyzerOptions().getBooleanOption( + bool ReportNormalClones = Mgr.getAnalyzerOptions().getCheckerBooleanOption( "ReportNormalClones", true, this); - StringRef IgnoredFilesPattern = Mgr.getAnalyzerOptions().getOptionAsString( - "IgnoredFilesPattern", "", this); + StringRef IgnoredFilesPattern = Mgr.getAnalyzerOptions() + .getCheckerStringOption("IgnoredFilesPattern", "", this); // Let the CloneDetector create a list of clones from all the analyzed // statements. We don't filter for matching variable patterns at this point diff --git a/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp b/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp index 17ec2c288777..a5c67c2a5b45 100644 --- a/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp @@ -14,20 +14,25 @@ // of expressions. A warning is reported when: // * a negative value is implicitly converted to an unsigned value in an // assignment, comparison or multiplication. -// * assignment / initialization when source value is greater than the max -// value of target +// * assignment / initialization when the source value is greater than the max +// value of the target integer type +// * assignment / initialization when the source integer is above the range +// where the target floating point type can represent all integers // // Many compilers and tools have similar checks that are based on semantic // analysis. Those checks are sound but have poor precision. ConversionChecker // is an alternative to those checks. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/APFloat.h" + +#include <climits> using namespace clang; using namespace ento; @@ -40,11 +45,9 @@ public: private: mutable std::unique_ptr<BuiltinBug> BT; - // Is there loss of precision bool isLossOfPrecision(const ImplicitCastExpr *Cast, QualType DestType, CheckerContext &C) const; - // Is there loss of sign bool isLossOfSign(const ImplicitCastExpr *Cast, CheckerContext &C) const; void reportBug(ExplodedNode *N, CheckerContext &C, const char Msg[]) const; @@ -132,19 +135,51 @@ bool ConversionChecker::isLossOfPrecision(const ImplicitCastExpr *Cast, QualType SubType = Cast->IgnoreParenImpCasts()->getType(); - if (!DestType->isIntegerType() || !SubType->isIntegerType()) + if (!DestType->isRealType() || !SubType->isIntegerType()) return false; - if (C.getASTContext().getIntWidth(DestType) >= - C.getASTContext().getIntWidth(SubType)) + const bool isFloat = DestType->isFloatingType(); + + const auto &AC = C.getASTContext(); + + // We will find the largest RepresentsUntilExp value such that the DestType + // can exactly represent all nonnegative integers below 2^RepresentsUntilExp. + unsigned RepresentsUntilExp; + + if (isFloat) { + const llvm::fltSemantics &Sema = AC.getFloatTypeSemantics(DestType); + RepresentsUntilExp = llvm::APFloat::semanticsPrecision(Sema); + } else { + RepresentsUntilExp = AC.getIntWidth(DestType); + if (RepresentsUntilExp == 1) { + // This is just casting a number to bool, probably not a bug. + return false; + } + if (DestType->isSignedIntegerType()) + RepresentsUntilExp--; + } + + if (RepresentsUntilExp >= sizeof(unsigned long long) * CHAR_BIT) { + // Avoid overflow in our later calculations. return false; + } + + unsigned CorrectedSrcWidth = AC.getIntWidth(SubType); + if (SubType->isSignedIntegerType()) + CorrectedSrcWidth--; - unsigned W = C.getASTContext().getIntWidth(DestType); - if (W == 1 || W >= 64U) + if (RepresentsUntilExp >= CorrectedSrcWidth) { + // Simple case: the destination can store all values of the source type. return false; + } - unsigned long long MaxVal = 1ULL << W; + unsigned long long MaxVal = 1ULL << RepresentsUntilExp; + if (isFloat) { + // If this is a floating point type, it can also represent MaxVal exactly. + MaxVal++; + } return C.isGreaterOrEqual(Cast->getSubExpr(), MaxVal); + // TODO: maybe also check negative values with too large magnitude. } bool ConversionChecker::isLossOfSign(const ImplicitCastExpr *Cast, diff --git a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index f7b5f61cfb8a..4e0f6d3bedfd 100644 --- a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" #include "clang/AST/ParentMap.h" @@ -262,7 +262,7 @@ public: currentBlock = block; // Skip statements in macros. - if (S->getLocStart().isMacroID()) + if (S->getBeginLoc().isMacroID()) return; // Only cover dead stores from regular assignments. ++/-- dead stores @@ -329,9 +329,8 @@ public: return; if (const Expr *E = V->getInit()) { - while (const ExprWithCleanups *exprClean = - dyn_cast<ExprWithCleanups>(E)) - E = exprClean->getSubExpr(); + while (const FullExpr *FE = dyn_cast<FullExpr>(E)) + E = FE->getSubExpr(); // Look through transitive assignments, e.g.: // int x = y = 0; diff --git a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 810a33ed404d..90b1111aff0f 100644 --- a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Analysis/Analyses/Dominators.h" #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/CallGraph.h" @@ -69,6 +69,25 @@ void ento::registerLiveVariablesDumper(CheckerManager &mgr) { } //===----------------------------------------------------------------------===// +// LiveStatementsDumper +//===----------------------------------------------------------------------===// + +namespace { +class LiveStatementsDumper : public Checker<check::ASTCodeBody> { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& Mgr, + BugReporter &BR) const { + if (LiveVariables *L = Mgr.getAnalysis<RelaxedLiveVariables>(D)) + L->dumpStmtLiveness(Mgr.getSourceManager()); + } +}; +} + +void ento::registerLiveStatementsDumper(CheckerManager &mgr) { + mgr.registerChecker<LiveStatementsDumper>(); +} + +//===----------------------------------------------------------------------===// // CFGViewer //===----------------------------------------------------------------------===// @@ -182,7 +201,9 @@ public: llvm::errs() << "[config]\n"; for (unsigned I = 0, E = Keys.size(); I != E; ++I) - llvm::errs() << Keys[I]->getKey() << " = " << Keys[I]->second << '\n'; + llvm::errs() << Keys[I]->getKey() << " = " + << (Keys[I]->second.empty() ? "\"\"" : Keys[I]->second) + << '\n'; llvm::errs() << "[stats]\n" << "num-entries = " << Keys.size() << '\n'; } diff --git a/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index d3489282ab62..adf5a8e77a74 100644 --- a/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -21,7 +21,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -47,7 +47,6 @@ class DeleteWithNonVirtualDtorChecker ID.AddPointer(&X); } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; @@ -104,7 +103,7 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, std::shared_ptr<PathDiagnosticPiece> DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( - const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { // Stop traversal after the first conversion was found on a path. if (Satisfied) diff --git a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index 152b937bb03f..d01a889d256a 100644 --- a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ExprOpenMP.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -111,6 +111,12 @@ static bool suppressReport(const Expr *E) { return E->getType().getQualifiers().hasAddressSpace(); } +static bool isDeclRefExprToReference(const Expr *E) { + if (const auto *DRE = dyn_cast<DeclRefExpr>(E)) + return DRE->getDecl()->getType()->isReferenceType(); + return false; +} + void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, CheckerContext &C) const { // Generate an error node. @@ -154,7 +160,7 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, } case Stmt::MemberExprClass: { const MemberExpr *M = cast<MemberExpr>(S); - if (M->isArrow() || bugreporter::isDeclRefExprToReference(M->getBase())) { + if (M->isArrow() || isDeclRefExprToReference(M->getBase())) { os << "Access to field '" << M->getMemberNameInfo() << "' results in a dereference of a null pointer"; AddDerefSource(os, Ranges, M->getBase()->IgnoreParenCasts(), @@ -177,7 +183,7 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, auto report = llvm::make_unique<BugReport>( *BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N); - bugreporter::trackNullOrUndefValue(N, bugreporter::getDerefExpr(S), *report); + bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); for (SmallVectorImpl<SourceRange>::iterator I = Ranges.begin(), E = Ranges.end(); I!=E; ++I) @@ -197,8 +203,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, auto report = llvm::make_unique<BugReport>(*BT_undef, BT_undef->getDescription(), N); - bugreporter::trackNullOrUndefValue(N, bugreporter::getDerefExpr(S), - *report); + bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); C.emitReport(std::move(report)); } return; diff --git a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp index 5efb9096f2ff..2a559422df34 100644 --- a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp +++ b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp @@ -21,7 +21,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/StmtVisitor.h" diff --git a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index bc39c92ea970..a220a0513e28 100644 --- a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -32,6 +32,13 @@ public: }; } // end anonymous namespace +static const Expr *getDenomExpr(const ExplodedNode *N) { + const Stmt *S = N->getLocationAs<PreStmt>()->getStmt(); + if (const auto *BE = dyn_cast<BinaryOperator>(S)) + return BE->getRHS(); + return nullptr; +} + void DivZeroChecker::reportBug( const char *Msg, ProgramStateRef StateZero, CheckerContext &C, std::unique_ptr<BugReporterVisitor> Visitor) const { @@ -41,7 +48,7 @@ void DivZeroChecker::reportBug( auto R = llvm::make_unique<BugReport>(*BT, Msg, N); R->addVisitor(std::move(Visitor)); - bugreporter::trackNullOrUndefValue(N, bugreporter::GetDenomExpr(N), *R); + bugreporter::trackExpressionValue(N, getDenomExpr(N), *R); C.emitReport(std::move(R)); } } diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 4e4d81cd6714..803d7ae22a71 100644 --- a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -17,7 +17,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -49,7 +49,6 @@ class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> { } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; @@ -92,11 +91,10 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, std::shared_ptr<PathDiagnosticPiece> DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, - BugReport &BR) { + BugReport &) { ProgramStateRef State = N->getState(); - ProgramStateRef StatePrev = PrevN->getState(); + ProgramStateRef StatePrev = N->getFirstPred()->getState(); DynamicTypeInfo TrackedType = getDynamicTypeInfo(State, Reg); DynamicTypeInfo TrackedTypePrev = getDynamicTypeInfo(StatePrev, Reg); diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 126e57645a43..31d4eebe8968 100644 --- a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -21,7 +21,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/Builtins.h" @@ -85,7 +85,6 @@ class DynamicTypePropagation: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; @@ -124,11 +123,6 @@ void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, } } - if (!SR.hasDeadSymbols()) { - C.addTransition(State); - return; - } - MostSpecializedTypeArgsMapTy TyArgMap = State->get<MostSpecializedTypeArgsMap>(); for (MostSpecializedTypeArgsMapTy::iterator I = TyArgMap.begin(), @@ -937,11 +931,10 @@ void DynamicTypePropagation::reportGenericsBug( std::shared_ptr<PathDiagnosticPiece> DynamicTypePropagation::GenericsBugVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef state = N->getState(); - ProgramStateRef statePrev = PrevN->getState(); + ProgramStateRef statePrev = N->getFirstPred()->getState(); const ObjCObjectPointerType *const *TrackedType = state->get<MostSpecializedTypeArgsMap>(Sym); diff --git a/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp new file mode 100644 index 000000000000..4e51cffaa744 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp @@ -0,0 +1,128 @@ +//===- EnumCastOutOfRangeChecker.cpp ---------------------------*- C++ -*--===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// The EnumCastOutOfRangeChecker is responsible for checking integer to +// enumeration casts that could result in undefined values. This could happen +// if the value that we cast from is out of the value range of the enumeration. +// Reference: +// [ISO/IEC 14882-2014] ISO/IEC 14882-2014. +// Programming Languages — C++, Fourth Edition. 2014. +// C++ Standard, [dcl.enum], in paragraph 8, which defines the range of an enum +// C++ Standard, [expr.static.cast], paragraph 10, which defines the behaviour +// of casting an integer value that is out of range +// SEI CERT C++ Coding Standard, INT50-CPP. Do not cast to an out-of-range +// enumeration value +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +using namespace clang; +using namespace ento; + +namespace { +// This evaluator checks two SVals for equality. The first SVal is provided via +// the constructor, the second is the parameter of the overloaded () operator. +// It uses the in-built ConstraintManager to resolve the equlity to possible or +// not possible ProgramStates. +class ConstraintBasedEQEvaluator { + const DefinedOrUnknownSVal CompareValue; + const ProgramStateRef PS; + SValBuilder &SVB; + +public: + ConstraintBasedEQEvaluator(CheckerContext &C, + const DefinedOrUnknownSVal CompareValue) + : CompareValue(CompareValue), PS(C.getState()), SVB(C.getSValBuilder()) {} + + bool operator()(const llvm::APSInt &EnumDeclInitValue) { + DefinedOrUnknownSVal EnumDeclValue = SVB.makeIntVal(EnumDeclInitValue); + DefinedOrUnknownSVal ElemEqualsValueToCast = + SVB.evalEQ(PS, EnumDeclValue, CompareValue); + + return static_cast<bool>(PS->assume(ElemEqualsValueToCast, true)); + } +}; + +// This checker checks CastExpr statements. +// If the value provided to the cast is one of the values the enumeration can +// represent, the said value matches the enumeration. If the checker can +// establish the impossibility of matching it gives a warning. +// Being conservative, it does not warn if there is slight possibility the +// value can be matching. +class EnumCastOutOfRangeChecker : public Checker<check::PreStmt<CastExpr>> { + mutable std::unique_ptr<BuiltinBug> EnumValueCastOutOfRange; + void reportWarning(CheckerContext &C) const; + +public: + void checkPreStmt(const CastExpr *CE, CheckerContext &C) const; +}; + +using EnumValueVector = llvm::SmallVector<llvm::APSInt, 6>; + +// Collects all of the values an enum can represent (as SVals). +EnumValueVector getDeclValuesForEnum(const EnumDecl *ED) { + EnumValueVector DeclValues( + std::distance(ED->enumerator_begin(), ED->enumerator_end())); + llvm::transform(ED->enumerators(), DeclValues.begin(), + [](const EnumConstantDecl *D) { return D->getInitVal(); }); + return DeclValues; +} +} // namespace + +void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const { + if (const ExplodedNode *N = C.generateNonFatalErrorNode()) { + if (!EnumValueCastOutOfRange) + EnumValueCastOutOfRange.reset( + new BuiltinBug(this, "Enum cast out of range", + "The value provided to the cast expression is not in " + "the valid range of values for the enum")); + C.emitReport(llvm::make_unique<BugReport>( + *EnumValueCastOutOfRange, EnumValueCastOutOfRange->getDescription(), + N)); + } +} + +void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE, + CheckerContext &C) const { + // Get the value of the expression to cast. + const llvm::Optional<DefinedOrUnknownSVal> ValueToCast = + C.getSVal(CE->getSubExpr()).getAs<DefinedOrUnknownSVal>(); + + // If the value cannot be reasoned about (not even a DefinedOrUnknownSVal), + // don't analyze further. + if (!ValueToCast) + return; + + const QualType T = CE->getType(); + // Check whether the cast type is an enum. + if (!T->isEnumeralType()) + return; + + // If the cast is an enum, get its declaration. + // If the isEnumeralType() returned true, then the declaration must exist + // even if it is a stub declaration. It is up to the getDeclValuesForEnum() + // function to handle this. + const EnumDecl *ED = T->castAs<EnumType>()->getDecl(); + + EnumValueVector DeclValues = getDeclValuesForEnum(ED); + // Check if any of the enum values possibly match. + bool PossibleValueMatch = llvm::any_of( + DeclValues, ConstraintBasedEQEvaluator(C, *ValueToCast)); + + // If there is no value that can possibly match any of the enum values, then + // warn. + if (!PossibleValueMatch) + reportWarning(C); +} + +void ento::registerEnumCastOutOfRangeChecker(CheckerManager &mgr) { + mgr.registerChecker<EnumCastOutOfRangeChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 8de653c10f7e..2553f54bbcac 100644 --- a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Checkers/SValExplainer.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -43,6 +43,8 @@ class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols, void analyzerPrintState(const CallExpr *CE, CheckerContext &C) const; void analyzerGetExtent(const CallExpr *CE, CheckerContext &C) const; void analyzerHashDump(const CallExpr *CE, CheckerContext &C) const; + void analyzerDenote(const CallExpr *CE, CheckerContext &C) const; + void analyzerExpress(const CallExpr *CE, CheckerContext &C) const; typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *, CheckerContext &C) const; @@ -60,6 +62,7 @@ public: } REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, SymbolRef) +REGISTER_MAP_WITH_PROGRAMSTATE(DenotedSymbols, SymbolRef, const StringLiteral *) bool ExprInspectionChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { @@ -82,6 +85,8 @@ bool ExprInspectionChecker::evalCall(const CallExpr *CE, .Case("clang_analyzer_numTimesReached", &ExprInspectionChecker::analyzerNumTimesReached) .Case("clang_analyzer_hashDump", &ExprInspectionChecker::analyzerHashDump) + .Case("clang_analyzer_denote", &ExprInspectionChecker::analyzerDenote) + .Case("clang_analyzer_express", &ExprInspectionChecker::analyzerExpress) .Default(nullptr); if (!Handler) @@ -264,6 +269,13 @@ void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper, N = BugNode; State = State->remove<MarkedSymbols>(Sym); } + + for (auto I : State->get<DenotedSymbols>()) { + SymbolRef Sym = I.first; + if (!SymReaper.isLive(Sym)) + State = State->remove<DenotedSymbols>(Sym); + } + C.addTransition(State, N); } @@ -287,7 +299,7 @@ void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE, CheckerContext &C) const { const LangOptions &Opts = C.getLangOpts(); const SourceManager &SM = C.getSourceManager(); - FullSourceLoc FL(CE->getArg(0)->getLocStart(), SM); + FullSourceLoc FL(CE->getArg(0)->getBeginLoc(), SM); std::string HashContent = GetIssueString(SM, FL, getCheckName().getName(), "Category", C.getLocationContext()->getDecl(), Opts); @@ -295,6 +307,105 @@ void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE, reportBug(HashContent, C); } +void ExprInspectionChecker::analyzerDenote(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() < 2) { + reportBug("clang_analyzer_denote() requires a symbol and a string literal", + C); + return; + } + + SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); + if (!Sym) { + reportBug("Not a symbol", C); + return; + } + + const auto *E = dyn_cast<StringLiteral>(CE->getArg(1)->IgnoreParenCasts()); + if (!E) { + reportBug("Not a string literal", C); + return; + } + + ProgramStateRef State = C.getState(); + + C.addTransition(C.getState()->set<DenotedSymbols>(Sym, E)); +} + +namespace { +class SymbolExpressor + : public SymExprVisitor<SymbolExpressor, Optional<std::string>> { + ProgramStateRef State; + +public: + SymbolExpressor(ProgramStateRef State) : State(State) {} + + Optional<std::string> lookup(const SymExpr *S) { + if (const StringLiteral *const *SLPtr = State->get<DenotedSymbols>(S)) { + const StringLiteral *SL = *SLPtr; + return std::string(SL->getBytes()); + } + return None; + } + + Optional<std::string> VisitSymExpr(const SymExpr *S) { + return lookup(S); + } + + Optional<std::string> VisitSymIntExpr(const SymIntExpr *S) { + if (Optional<std::string> Str = lookup(S)) + return Str; + if (Optional<std::string> Str = Visit(S->getLHS())) + return (*Str + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + " " + + std::to_string(S->getRHS().getLimitedValue()) + + (S->getRHS().isUnsigned() ? "U" : "")) + .str(); + return None; + } + + Optional<std::string> VisitSymSymExpr(const SymSymExpr *S) { + if (Optional<std::string> Str = lookup(S)) + return Str; + if (Optional<std::string> Str1 = Visit(S->getLHS())) + if (Optional<std::string> Str2 = Visit(S->getRHS())) + return (*Str1 + " " + BinaryOperator::getOpcodeStr(S->getOpcode()) + + " " + *Str2).str(); + return None; + } + + Optional<std::string> VisitSymbolCast(const SymbolCast *S) { + if (Optional<std::string> Str = lookup(S)) + return Str; + if (Optional<std::string> Str = Visit(S->getOperand())) + return (Twine("(") + S->getType().getAsString() + ")" + *Str).str(); + return None; + } +}; +} // namespace + +void ExprInspectionChecker::analyzerExpress(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() == 0) { + reportBug("clang_analyzer_express() requires a symbol", C); + return; + } + + SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); + if (!Sym) { + reportBug("Not a symbol", C); + return; + } + + SymbolExpressor V(C.getState()); + auto Str = V.Visit(Sym); + if (!Str) { + reportBug("Unable to express", C); + return; + } + + reportBug(*Str, C); +} + void ento::registerExprInspectionChecker(CheckerManager &Mgr) { Mgr.registerChecker<ExprInspectionChecker>(); } diff --git a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 059203fca730..165a4e4490eb 100644 --- a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp b/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp index 5cb51b01f044..248b9c3f7693 100644 --- a/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp @@ -29,7 +29,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" diff --git a/lib/StaticAnalyzer/Checkers/GTestChecker.cpp b/lib/StaticAnalyzer/Checkers/GTestChecker.cpp index 3ef95e673b87..818716dd6070 100644 --- a/lib/StaticAnalyzer/Checkers/GTestChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GTestChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Expr.h" #include "clang/Basic/LangOptions.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 899586745a0b..32fed202d3ab 100644 --- a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -14,7 +14,7 @@ // aggressively, even if the involved symbols are under constrained. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/Basic/Builtins.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -28,10 +28,13 @@ using namespace clang; using namespace ento; namespace { -class GenericTaintChecker : public Checker< check::PostStmt<CallExpr>, - check::PreStmt<CallExpr> > { +class GenericTaintChecker + : public Checker<check::PostStmt<CallExpr>, check::PreStmt<CallExpr>> { public: - static void *getTag() { static int Tag; return &Tag; } + static void *getTag() { + static int Tag; + return &Tag; + } void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; @@ -69,8 +72,8 @@ private: static Optional<SVal> getPointedToSVal(CheckerContext &C, const Expr *Arg); /// Functions defining the attack surface. - typedef ProgramStateRef (GenericTaintChecker::*FnCheck)(const CallExpr *, - CheckerContext &C) const; + typedef ProgramStateRef (GenericTaintChecker::*FnCheck)( + const CallExpr *, CheckerContext &C) const; ProgramStateRef postScanf(const CallExpr *CE, CheckerContext &C) const; ProgramStateRef postSocket(const CallExpr *CE, CheckerContext &C) const; ProgramStateRef postRetTaint(const CallExpr *CE, CheckerContext &C) const; @@ -120,16 +123,15 @@ private: TaintPropagationRule() {} - TaintPropagationRule(unsigned SArg, - unsigned DArg, bool TaintRet = false) { + TaintPropagationRule(unsigned SArg, unsigned DArg, bool TaintRet = false) { SrcArgs.push_back(SArg); DstArgs.push_back(DArg); if (TaintRet) DstArgs.push_back(ReturnValueIndex); } - TaintPropagationRule(unsigned SArg1, unsigned SArg2, - unsigned DArg, bool TaintRet = false) { + TaintPropagationRule(unsigned SArg1, unsigned SArg2, unsigned DArg, + bool TaintRet = false) { SrcArgs.push_back(SArg1); SrcArgs.push_back(SArg2); DstArgs.push_back(DArg); @@ -139,18 +141,17 @@ private: /// Get the propagation rule for a given function. static TaintPropagationRule - getTaintPropagationRule(const FunctionDecl *FDecl, - StringRef Name, - CheckerContext &C); + getTaintPropagationRule(const FunctionDecl *FDecl, StringRef Name, + CheckerContext &C); inline void addSrcArg(unsigned A) { SrcArgs.push_back(A); } - inline void addDstArg(unsigned A) { DstArgs.push_back(A); } + inline void addDstArg(unsigned A) { DstArgs.push_back(A); } inline bool isNull() const { return SrcArgs.empty(); } inline bool isDestinationArgument(unsigned ArgNum) const { - return (std::find(DstArgs.begin(), - DstArgs.end(), ArgNum) != DstArgs.end()); + return (std::find(DstArgs.begin(), DstArgs.end(), ArgNum) != + DstArgs.end()); } static inline bool isTaintedOrPointsToTainted(const Expr *E, @@ -169,7 +170,6 @@ private: /// Pre-process a function which propagates taint according to the /// taint rule. ProgramStateRef process(const CallExpr *CE, CheckerContext &C) const; - }; }; @@ -177,17 +177,18 @@ const unsigned GenericTaintChecker::ReturnValueIndex; const unsigned GenericTaintChecker::InvalidArgIndex; const char GenericTaintChecker::MsgUncontrolledFormatString[] = - "Untrusted data is used as a format string " - "(CWE-134: Uncontrolled Format String)"; + "Untrusted data is used as a format string " + "(CWE-134: Uncontrolled Format String)"; const char GenericTaintChecker::MsgSanitizeSystemArgs[] = - "Untrusted data is passed to a system call " - "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; + "Untrusted data is passed to a system call " + "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; const char GenericTaintChecker::MsgTaintedBufferSize[] = - "Untrusted data is used to specify the buffer size " - "(CERT/STR31-C. Guarantee that storage for strings has sufficient space for " - "character data and the null terminator)"; + "Untrusted data is used to specify the buffer size " + "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " + "for " + "character data and the null terminator)"; } // end of anonymous namespace @@ -199,33 +200,32 @@ REGISTER_SET_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, unsigned) GenericTaintChecker::TaintPropagationRule GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( - const FunctionDecl *FDecl, - StringRef Name, - CheckerContext &C) { + const FunctionDecl *FDecl, StringRef Name, CheckerContext &C) { // TODO: Currently, we might lose precision here: we always mark a return // value as tainted even if it's just a pointer, pointing to tainted data. // Check for exact name match for functions without builtin substitutes. - TaintPropagationRule Rule = llvm::StringSwitch<TaintPropagationRule>(Name) - .Case("atoi", TaintPropagationRule(0, ReturnValueIndex)) - .Case("atol", TaintPropagationRule(0, ReturnValueIndex)) - .Case("atoll", TaintPropagationRule(0, ReturnValueIndex)) - .Case("getc", TaintPropagationRule(0, ReturnValueIndex)) - .Case("fgetc", TaintPropagationRule(0, ReturnValueIndex)) - .Case("getc_unlocked", TaintPropagationRule(0, ReturnValueIndex)) - .Case("getw", TaintPropagationRule(0, ReturnValueIndex)) - .Case("toupper", TaintPropagationRule(0, ReturnValueIndex)) - .Case("tolower", TaintPropagationRule(0, ReturnValueIndex)) - .Case("strchr", TaintPropagationRule(0, ReturnValueIndex)) - .Case("strrchr", TaintPropagationRule(0, ReturnValueIndex)) - .Case("read", TaintPropagationRule(0, 2, 1, true)) - .Case("pread", TaintPropagationRule(InvalidArgIndex, 1, true)) - .Case("gets", TaintPropagationRule(InvalidArgIndex, 0, true)) - .Case("fgets", TaintPropagationRule(2, 0, true)) - .Case("getline", TaintPropagationRule(2, 0)) - .Case("getdelim", TaintPropagationRule(3, 0)) - .Case("fgetln", TaintPropagationRule(0, ReturnValueIndex)) - .Default(TaintPropagationRule()); + TaintPropagationRule Rule = + llvm::StringSwitch<TaintPropagationRule>(Name) + .Case("atoi", TaintPropagationRule(0, ReturnValueIndex)) + .Case("atol", TaintPropagationRule(0, ReturnValueIndex)) + .Case("atoll", TaintPropagationRule(0, ReturnValueIndex)) + .Case("getc", TaintPropagationRule(0, ReturnValueIndex)) + .Case("fgetc", TaintPropagationRule(0, ReturnValueIndex)) + .Case("getc_unlocked", TaintPropagationRule(0, ReturnValueIndex)) + .Case("getw", TaintPropagationRule(0, ReturnValueIndex)) + .Case("toupper", TaintPropagationRule(0, ReturnValueIndex)) + .Case("tolower", TaintPropagationRule(0, ReturnValueIndex)) + .Case("strchr", TaintPropagationRule(0, ReturnValueIndex)) + .Case("strrchr", TaintPropagationRule(0, ReturnValueIndex)) + .Case("read", TaintPropagationRule(0, 2, 1, true)) + .Case("pread", TaintPropagationRule(InvalidArgIndex, 1, true)) + .Case("gets", TaintPropagationRule(InvalidArgIndex, 0, true)) + .Case("fgets", TaintPropagationRule(2, 0, true)) + .Case("getline", TaintPropagationRule(2, 0)) + .Case("getdelim", TaintPropagationRule(3, 0)) + .Case("fgetln", TaintPropagationRule(0, ReturnValueIndex)) + .Default(TaintPropagationRule()); if (!Rule.isNull()) return Rule; @@ -233,8 +233,8 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( // Check if it's one of the memory setting/copying functions. // This check is specialized but faster then calling isCLibraryFunction. unsigned BId = 0; - if ( (BId = FDecl->getMemoryFunctionKind()) ) - switch(BId) { + if ((BId = FDecl->getMemoryFunctionKind())) + switch (BId) { case Builtin::BImemcpy: case Builtin::BImemmove: case Builtin::BIstrncpy: @@ -305,7 +305,7 @@ void GenericTaintChecker::addSourcesPre(const CallExpr *CE, // First, try generating a propagation rule for this function. TaintPropagationRule Rule = - TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C); + TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C); if (!Rule.isNull()) { State = Rule.process(CE, C); if (!State) @@ -316,15 +316,14 @@ void GenericTaintChecker::addSourcesPre(const CallExpr *CE, // Otherwise, check if we have custom pre-processing implemented. FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) - .Case("fscanf", &GenericTaintChecker::preFscanf) - .Default(nullptr); + .Case("fscanf", &GenericTaintChecker::preFscanf) + .Default(nullptr); // Check and evaluate the call. if (evalFunction) State = (this->*evalFunction)(CE, C); if (!State) return; C.addTransition(State); - } bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, @@ -338,9 +337,10 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, if (TaintArgs.isEmpty()) return false; - for (llvm::ImmutableSet<unsigned>::iterator - I = TaintArgs.begin(), E = TaintArgs.end(); I != E; ++I) { - unsigned ArgNum = *I; + for (llvm::ImmutableSet<unsigned>::iterator I = TaintArgs.begin(), + E = TaintArgs.end(); + I != E; ++I) { + unsigned ArgNum = *I; // Special handling for the tainted return value. if (ArgNum == ReturnValueIndex) { @@ -352,7 +352,7 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, // tainted after the call. if (CE->getNumArgs() < (ArgNum + 1)) return false; - const Expr* Arg = CE->getArg(ArgNum); + const Expr *Arg = CE->getArg(ArgNum); Optional<SVal> V = getPointedToSVal(C, Arg); if (V) State = State->addTaint(*V); @@ -379,19 +379,20 @@ void GenericTaintChecker::addSourcesPost(const CallExpr *CE, StringRef Name = C.getCalleeName(FDecl); if (Name.empty()) return; - FnCheck evalFunction = llvm::StringSwitch<FnCheck>(Name) - .Case("scanf", &GenericTaintChecker::postScanf) - // TODO: Add support for vfscanf & family. - .Case("getchar", &GenericTaintChecker::postRetTaint) - .Case("getchar_unlocked", &GenericTaintChecker::postRetTaint) - .Case("getenv", &GenericTaintChecker::postRetTaint) - .Case("fopen", &GenericTaintChecker::postRetTaint) - .Case("fdopen", &GenericTaintChecker::postRetTaint) - .Case("freopen", &GenericTaintChecker::postRetTaint) - .Case("getch", &GenericTaintChecker::postRetTaint) - .Case("wgetch", &GenericTaintChecker::postRetTaint) - .Case("socket", &GenericTaintChecker::postSocket) - .Default(nullptr); + FnCheck evalFunction = + llvm::StringSwitch<FnCheck>(Name) + .Case("scanf", &GenericTaintChecker::postScanf) + // TODO: Add support for vfscanf & family. + .Case("getchar", &GenericTaintChecker::postRetTaint) + .Case("getchar_unlocked", &GenericTaintChecker::postRetTaint) + .Case("getenv", &GenericTaintChecker::postRetTaint) + .Case("fopen", &GenericTaintChecker::postRetTaint) + .Case("fdopen", &GenericTaintChecker::postRetTaint) + .Case("freopen", &GenericTaintChecker::postRetTaint) + .Case("getch", &GenericTaintChecker::postRetTaint) + .Case("wgetch", &GenericTaintChecker::postRetTaint) + .Case("socket", &GenericTaintChecker::postSocket) + .Default(nullptr); // If the callee isn't defined, it is not of security concern. // Check and evaluate the call. @@ -404,7 +405,8 @@ void GenericTaintChecker::addSourcesPost(const CallExpr *CE, C.addTransition(State); } -bool GenericTaintChecker::checkPre(const CallExpr *CE, CheckerContext &C) const{ +bool GenericTaintChecker::checkPre(const CallExpr *CE, + CheckerContext &C) const { if (checkUncontrolledFormatString(CE, C)) return true; @@ -458,8 +460,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // Check for taint in arguments. bool IsTainted = false; - for (ArgVector::const_iterator I = SrcArgs.begin(), - E = SrcArgs.end(); I != E; ++I) { + for (ArgVector::const_iterator I = SrcArgs.begin(), E = SrcArgs.end(); I != E; + ++I) { unsigned ArgNum = *I; if (ArgNum == InvalidArgIndex) { @@ -483,8 +485,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, return State; // Mark the arguments which should be tainted after the function returns. - for (ArgVector::const_iterator I = DstArgs.begin(), - E = DstArgs.end(); I != E; ++I) { + for (ArgVector::const_iterator I = DstArgs.begin(), E = DstArgs.end(); I != E; + ++I) { unsigned ArgNum = *I; // Should we mark all arguments as tainted? @@ -498,8 +500,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // Process pointer argument. const Type *ArgTy = Arg->getType().getTypePtr(); QualType PType = ArgTy->getPointeeType(); - if ((!PType.isNull() && !PType.isConstQualified()) - || (ArgTy->isReferenceType() && !Arg->getType().isConstQualified())) + if ((!PType.isNull() && !PType.isConstQualified()) || + (ArgTy->isReferenceType() && !Arg->getType().isConstQualified())) State = State->add<TaintArgsOnPostVisit>(i); } continue; @@ -519,11 +521,10 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, return State; } - // If argument 0 (file descriptor) is tainted, all arguments except for arg 0 // and arg 1 should get taint. ProgramStateRef GenericTaintChecker::preFscanf(const CallExpr *CE, - CheckerContext &C) const { + CheckerContext &C) const { assert(CE->getNumArgs() >= 2); ProgramStateRef State = C.getState(); @@ -532,14 +533,13 @@ ProgramStateRef GenericTaintChecker::preFscanf(const CallExpr *CE, isStdin(CE->getArg(0), C)) { // All arguments except for the first two should get taint. for (unsigned int i = 2; i < CE->getNumArgs(); ++i) - State = State->add<TaintArgsOnPostVisit>(i); + State = State->add<TaintArgsOnPostVisit>(i); return State; } return nullptr; } - // If argument 0(protocol domain) is network, the return value should get taint. ProgramStateRef GenericTaintChecker::postSocket(const CallExpr *CE, CheckerContext &C) const { @@ -558,7 +558,7 @@ ProgramStateRef GenericTaintChecker::postSocket(const CallExpr *CE, } ProgramStateRef GenericTaintChecker::postScanf(const CallExpr *CE, - CheckerContext &C) const { + CheckerContext &C) const { ProgramStateRef State = C.getState(); if (CE->getNumArgs() < 2) return State; @@ -567,7 +567,7 @@ ProgramStateRef GenericTaintChecker::postScanf(const CallExpr *CE, for (unsigned int i = 1; i < CE->getNumArgs(); ++i) { // The arguments are pointer arguments. The data they are pointing at is // tainted after the call. - const Expr* Arg = CE->getArg(i); + const Expr *Arg = CE->getArg(i); Optional<SVal> V = getPointedToSVal(C, Arg); if (V) State = State->addTaint(*V); @@ -593,7 +593,8 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { return false; // Get it's symbol and find the declaration region it's pointing to. - const SymbolRegionValue *Sm =dyn_cast<SymbolRegionValue>(SymReg->getSymbol()); + const SymbolRegionValue *Sm = + dyn_cast<SymbolRegionValue>(SymReg->getSymbol()); if (!Sm) return false; const DeclRegion *DeclReg = dyn_cast_or_null<DeclRegion>(Sm->getRegion()); @@ -605,11 +606,11 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { if (const VarDecl *D = dyn_cast_or_null<VarDecl>(DeclReg->getDecl())) { D = D->getCanonicalDecl(); if ((D->getName().find("stdin") != StringRef::npos) && D->isExternC()) - if (const PointerType * PtrTy = + if (const PointerType *PtrTy = dyn_cast<PointerType>(D->getType().getTypePtr())) - if (PtrTy->getPointeeType().getCanonicalType() == - C.getASTContext().getFILEType().getCanonicalType()) - return true; + if (PtrTy->getPointeeType().getCanonicalType() == + C.getASTContext().getFILEType().getCanonicalType()) + return true; } return false; } @@ -625,8 +626,7 @@ static bool getPrintfFormatArgumentNum(const CallExpr *CE, return false; for (const auto *Format : FDecl->specific_attrs<FormatAttr>()) { ArgNum = Format->getFormatIdx() - 1; - if ((Format->getType()->getName() == "printf") && - CE->getNumArgs() > ArgNum) + if ((Format->getType()->getName() == "printf") && CE->getNumArgs() > ArgNum) return true; } @@ -667,36 +667,36 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, return false; } -bool GenericTaintChecker::checkUncontrolledFormatString(const CallExpr *CE, - CheckerContext &C) const{ +bool GenericTaintChecker::checkUncontrolledFormatString( + const CallExpr *CE, CheckerContext &C) const { // Check if the function contains a format string argument. unsigned int ArgNum = 0; if (!getPrintfFormatArgumentNum(CE, C, ArgNum)) return false; - // If either the format string content or the pointer itself are tainted, warn. + // If either the format string content or the pointer itself are tainted, + // warn. return generateReportIfTainted(CE->getArg(ArgNum), MsgUncontrolledFormatString, C); } -bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, - StringRef Name, +bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, StringRef Name, CheckerContext &C) const { // TODO: It might make sense to run this check on demand. In some cases, // we should check if the environment has been cleansed here. We also might // need to know if the user was reset before these calls(seteuid). unsigned ArgNum = llvm::StringSwitch<unsigned>(Name) - .Case("system", 0) - .Case("popen", 0) - .Case("execl", 0) - .Case("execle", 0) - .Case("execlp", 0) - .Case("execv", 0) - .Case("execvp", 0) - .Case("execvP", 0) - .Case("execve", 0) - .Case("dlopen", 0) - .Default(UINT_MAX); + .Case("system", 0) + .Case("popen", 0) + .Case("execl", 0) + .Case("execle", 0) + .Case("execlp", 0) + .Case("execv", 0) + .Case("execvp", 0) + .Case("execvP", 0) + .Case("execve", 0) + .Case("dlopen", 0) + .Default(UINT_MAX); if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1)) return false; @@ -712,8 +712,8 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, // If the function has a buffer size argument, set ArgNum. unsigned ArgNum = InvalidArgIndex; unsigned BId = 0; - if ( (BId = FDecl->getMemoryFunctionKind()) ) - switch(BId) { + if ((BId = FDecl->getMemoryFunctionKind())) + switch (BId) { case Builtin::BImemcpy: case Builtin::BImemmove: case Builtin::BIstrncpy: diff --git a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp index f102ca96a5c1..4c2a229428d9 100644 --- a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp @@ -16,7 +16,7 @@ /// //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index 29677f737f5c..a4f47d727a8f 100644 --- a/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -14,7 +14,8 @@ //===----------------------------------------------------------------------===// #include "AllocationState.h" -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "InterCheckerAPI.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -24,23 +25,10 @@ using namespace clang; using namespace ento; -using PtrSet = llvm::ImmutableSet<SymbolRef>; - // Associate container objects with a set of raw pointer symbols. +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(PtrSet, SymbolRef) REGISTER_MAP_WITH_PROGRAMSTATE(RawPtrMap, const MemRegion *, PtrSet) -// This is a trick to gain access to PtrSet's Factory. -namespace clang { -namespace ento { -template <> -struct ProgramStateTrait<PtrSet> : public ProgramStatePartialTrait<PtrSet> { - static void *GDMIndex() { - static int Index = 0; - return &Index; - } -}; -} // end namespace ento -} // end namespace clang namespace { @@ -67,8 +55,7 @@ public: ID.AddPointer(getTag()); } - std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, + virtual std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) override; @@ -85,14 +72,20 @@ public: }; InnerPointerChecker() - : AppendFn("append"), AssignFn("assign"), ClearFn("clear"), - CStrFn("c_str"), DataFn("data"), EraseFn("erase"), InsertFn("insert"), - PopBackFn("pop_back"), PushBackFn("push_back"), ReplaceFn("replace"), - ReserveFn("reserve"), ResizeFn("resize"), - ShrinkToFitFn("shrink_to_fit"), SwapFn("swap") {} - - /// Check if the object of this member function call is a `basic_string`. - bool isCalledOnStringObject(const CXXInstanceCall *ICall) const; + : AppendFn({"std", "basic_string", "append"}), + AssignFn({"std", "basic_string", "assign"}), + ClearFn({"std", "basic_string", "clear"}), + CStrFn({"std", "basic_string", "c_str"}), + DataFn({"std", "basic_string", "data"}), + EraseFn({"std", "basic_string", "erase"}), + InsertFn({"std", "basic_string", "insert"}), + PopBackFn({"std", "basic_string", "pop_back"}), + PushBackFn({"std", "basic_string", "push_back"}), + ReplaceFn({"std", "basic_string", "replace"}), + ReserveFn({"std", "basic_string", "reserve"}), + ResizeFn({"std", "basic_string", "resize"}), + ShrinkToFitFn({"std", "basic_string", "shrink_to_fit"}), + SwapFn({"std", "basic_string", "swap"}) {} /// Check whether the called member function potentially invalidates /// pointers referring to the container object's inner buffer. @@ -121,21 +114,6 @@ public: } // end anonymous namespace -bool InnerPointerChecker::isCalledOnStringObject( - const CXXInstanceCall *ICall) const { - const auto *ObjRegion = - dyn_cast_or_null<TypedValueRegion>(ICall->getCXXThisVal().getAsRegion()); - if (!ObjRegion) - return false; - - QualType ObjTy = ObjRegion->getValueType(); - if (ObjTy.isNull() || - ObjTy->getAsCXXRecordDecl()->getName() != "basic_string") - return false; - - return true; -} - bool InnerPointerChecker::isInvalidatingMemberFunction( const CallEvent &Call) const { if (const auto *MemOpCall = dyn_cast<CXXMemberOperatorCall>(&Call)) { @@ -219,33 +197,34 @@ void InnerPointerChecker::checkPostCall(const CallEvent &Call, ProgramStateRef State = C.getState(); if (const auto *ICall = dyn_cast<CXXInstanceCall>(&Call)) { - if (isCalledOnStringObject(ICall)) { - const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>( - ICall->getCXXThisVal().getAsRegion()); - - if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) { - SVal RawPtr = Call.getReturnValue(); - if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) { - // Start tracking this raw pointer by adding it to the set of symbols - // associated with this container object in the program state map. - - PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); - const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion); - PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet(); - assert(C.wasInlined || !Set.contains(Sym)); - Set = F.add(Set, Sym); - - State = State->set<RawPtrMap>(ObjRegion, Set); - C.addTransition(State); - } - return; - } + // TODO: Do we need these to be typed? + const auto *ObjRegion = dyn_cast_or_null<TypedValueRegion>( + ICall->getCXXThisVal().getAsRegion()); + if (!ObjRegion) + return; + + if (Call.isCalled(CStrFn) || Call.isCalled(DataFn)) { + SVal RawPtr = Call.getReturnValue(); + if (SymbolRef Sym = RawPtr.getAsSymbol(/*IncludeBaseRegions=*/true)) { + // Start tracking this raw pointer by adding it to the set of symbols + // associated with this container object in the program state map. - // Check [string.require] / second point. - if (isInvalidatingMemberFunction(Call)) { - markPtrSymbolsReleased(Call, State, ObjRegion, C); - return; + PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); + const PtrSet *SetPtr = State->get<RawPtrMap>(ObjRegion); + PtrSet Set = SetPtr ? *SetPtr : F.getEmptySet(); + assert(C.wasInlined || !Set.contains(Sym)); + Set = F.add(Set, Sym); + + State = State->set<RawPtrMap>(ObjRegion, Set); + C.addTransition(State); } + return; + } + + // Check [string.require] / second point. + if (isInvalidatingMemberFunction(Call)) { + markPtrSymbolsReleased(Call, State, ObjRegion, C); + return; } } @@ -278,41 +257,56 @@ void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper, C.addTransition(State); } +namespace clang { +namespace ento { +namespace allocation_state { + +std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) { + return llvm::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym); +} + +const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { + RawPtrMapTy Map = State->get<RawPtrMap>(); + for (const auto Entry : Map) { + if (Entry.second.contains(Sym)) { + return Entry.first; + } + } + return nullptr; +} + +} // end namespace allocation_state +} // end namespace ento +} // end namespace clang + std::shared_ptr<PathDiagnosticPiece> InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, - BugReport &BR) { + BugReport &) { if (!isSymbolTracked(N->getState(), PtrToBuf) || - isSymbolTracked(PrevN->getState(), PtrToBuf)) + isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf)) return nullptr; const Stmt *S = PathDiagnosticLocation::getStmt(N); if (!S) return nullptr; + const MemRegion *ObjRegion = + allocation_state::getContainerObjRegion(N->getState(), PtrToBuf); + const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion); + QualType ObjTy = TypedRegion->getValueType(); + SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); - OS << "Dangling inner pointer obtained here"; + OS << "Pointer to inner buffer of '" << ObjTy.getAsString() + << "' obtained here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true, nullptr); } -namespace clang { -namespace ento { -namespace allocation_state { - -std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) { - return llvm::make_unique<InnerPointerChecker::InnerPointerBRVisitor>(Sym); -} - -} // end namespace allocation_state -} // end namespace ento -} // end namespace clang - void ento::registerInnerPointerChecker(CheckerManager &Mgr) { - registerNewDeleteChecker(Mgr); + registerInnerPointerCheckerAux(Mgr); Mgr.registerChecker<InnerPointerChecker>(); } diff --git a/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h b/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h index d38d63cd05ce..81c95a4813a6 100644 --- a/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h +++ b/lib/StaticAnalyzer/Checkers/InterCheckerAPI.h @@ -20,5 +20,8 @@ namespace ento { /// Register the checker which evaluates CString API calls. void registerCStringCheckerBasic(CheckerManager &Mgr); +/// Register the part of MallocChecker connected to InnerPointerChecker. +void registerInnerPointerCheckerAux(CheckerManager &Mgr); + }} #endif /* INTERCHECKERAPI_H_ */ diff --git a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 520c32e1c770..e719e19d68e9 100644 --- a/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -66,11 +66,15 @@ // making an assumption e.g. `S1 + n == S2 + m` we store `S1 - S2 == m - n` as // a constraint which we later retrieve when doing an actual comparison. -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/DeclTemplate.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" + +#include <utility> using namespace clang; using namespace ento; @@ -85,34 +89,47 @@ private: // Container the iterator belongs to const MemRegion *Cont; + // Whether iterator is valid + const bool Valid; + // Abstract offset const SymbolRef Offset; - IteratorPosition(const MemRegion *C, SymbolRef Of) - : Cont(C), Offset(Of) {} + IteratorPosition(const MemRegion *C, bool V, SymbolRef Of) + : Cont(C), Valid(V), Offset(Of) {} public: const MemRegion *getContainer() const { return Cont; } + bool isValid() const { return Valid; } SymbolRef getOffset() const { return Offset; } + IteratorPosition invalidate() const { + return IteratorPosition(Cont, false, Offset); + } + static IteratorPosition getPosition(const MemRegion *C, SymbolRef Of) { - return IteratorPosition(C, Of); + return IteratorPosition(C, true, Of); } IteratorPosition setTo(SymbolRef NewOf) const { - return IteratorPosition(Cont, NewOf); + return IteratorPosition(Cont, Valid, NewOf); + } + + IteratorPosition reAssign(const MemRegion *NewCont) const { + return IteratorPosition(NewCont, Valid, Offset); } bool operator==(const IteratorPosition &X) const { - return Cont == X.Cont && Offset == X.Offset; + return Cont == X.Cont && Valid == X.Valid && Offset == X.Offset; } bool operator!=(const IteratorPosition &X) const { - return Cont != X.Cont || Offset != X.Offset; + return Cont != X.Cont || Valid != X.Valid || Offset != X.Offset; } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(Cont); + ID.AddInteger(Valid); ID.Add(Offset); } }; @@ -181,15 +198,17 @@ public: class IteratorChecker : public Checker<check::PreCall, check::PostCall, - check::PreStmt<CXXOperatorCallExpr>, - check::PostStmt<MaterializeTemporaryExpr>, + check::PostStmt<MaterializeTemporaryExpr>, check::Bind, check::LiveSymbols, check::DeadSymbols, eval::Assume> { std::unique_ptr<BugType> OutOfRangeBugType; + std::unique_ptr<BugType> MismatchedBugType; + std::unique_ptr<BugType> InvalidatedBugType; void handleComparison(CheckerContext &C, const SVal &RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const; + void verifyAccess(CheckerContext &C, const SVal &Val) const; void verifyDereference(CheckerContext &C, const SVal &Val) const; void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, bool Postfix) const; @@ -204,17 +223,50 @@ class IteratorChecker const SVal &Cont) const; void assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, const MemRegion *Cont) const; + void handleAssign(CheckerContext &C, const SVal &Cont, + const Expr *CE = nullptr, + const SVal &OldCont = UndefinedVal()) const; + void handleClear(CheckerContext &C, const SVal &Cont) const; + void handlePushBack(CheckerContext &C, const SVal &Cont) const; + void handlePopBack(CheckerContext &C, const SVal &Cont) const; + void handlePushFront(CheckerContext &C, const SVal &Cont) const; + void handlePopFront(CheckerContext &C, const SVal &Cont) const; + void handleInsert(CheckerContext &C, const SVal &Iter) const; + void handleErase(CheckerContext &C, const SVal &Iter) const; + void handleErase(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const; + void handleEraseAfter(CheckerContext &C, const SVal &Iter) const; + void handleEraseAfter(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const; + void verifyIncrement(CheckerContext &C, const SVal &Iter) const; + void verifyDecrement(CheckerContext &C, const SVal &Iter) const; void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, - const SVal &RetVal, const SVal &LHS, - const SVal &RHS) const; + const SVal &LHS, const SVal &RHS) const; + void verifyMatch(CheckerContext &C, const SVal &Iter, + const MemRegion *Cont) const; + void verifyMatch(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const; + IteratorPosition advancePosition(CheckerContext &C, OverloadedOperatorKind Op, + const IteratorPosition &Pos, + const SVal &Distance) const; void reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const; + void reportMismatchedBug(const StringRef &Message, const SVal &Val1, + const SVal &Val2, CheckerContext &C, + ExplodedNode *ErrNode) const; + void reportMismatchedBug(const StringRef &Message, const SVal &Val, + const MemRegion *Reg, CheckerContext &C, + ExplodedNode *ErrNode) const; + void reportInvalidatedBug(const StringRef &Message, const SVal &Val, + CheckerContext &C, ExplodedNode *ErrNode) const; public: IteratorChecker(); enum CheckKind { CK_IteratorRangeChecker, + CK_MismatchedIteratorChecker, + CK_InvalidatedIteratorChecker, CK_NumCheckKinds }; @@ -223,7 +275,9 @@ public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - void checkPreStmt(const CXXOperatorCallExpr *COCE, CheckerContext &C) const; + void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const; + void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const; + void checkPostStmt(const DeclStmt *DS, CheckerContext &C) const; void checkPostStmt(const MaterializeTemporaryExpr *MTE, CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; @@ -246,13 +300,31 @@ namespace { bool isIteratorType(const QualType &Type); bool isIterator(const CXXRecordDecl *CRD); +bool isComparisonOperator(OverloadedOperatorKind OK); bool isBeginCall(const FunctionDecl *Func); bool isEndCall(const FunctionDecl *Func); +bool isAssignCall(const FunctionDecl *Func); +bool isClearCall(const FunctionDecl *Func); +bool isPushBackCall(const FunctionDecl *Func); +bool isEmplaceBackCall(const FunctionDecl *Func); +bool isPopBackCall(const FunctionDecl *Func); +bool isPushFrontCall(const FunctionDecl *Func); +bool isEmplaceFrontCall(const FunctionDecl *Func); +bool isPopFrontCall(const FunctionDecl *Func); +bool isInsertCall(const FunctionDecl *Func); +bool isEraseCall(const FunctionDecl *Func); +bool isEraseAfterCall(const FunctionDecl *Func); +bool isEmplaceCall(const FunctionDecl *Func); +bool isAssignmentOperator(OverloadedOperatorKind OK); bool isSimpleComparisonOperator(OverloadedOperatorKind OK); +bool isAccessOperator(OverloadedOperatorKind OK); bool isDereferenceOperator(OverloadedOperatorKind OK); bool isIncrementOperator(OverloadedOperatorKind OK); bool isDecrementOperator(OverloadedOperatorKind OK); bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK); +bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg); +bool frontModifiable(ProgramStateRef State, const MemRegion *Reg); +bool backModifiable(ProgramStateRef State, const MemRegion *Reg); BinaryOperator::Opcode getOpcode(const SymExpr *SE); const RegionOrSymbol getRegionOrSymbol(const SVal &Val); const ProgramStateRef processComparison(ProgramStateRef State, @@ -287,12 +359,41 @@ ProgramStateRef relateIteratorPositions(ProgramStateRef State, const IteratorPosition &Pos1, const IteratorPosition &Pos2, bool Equal); +ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont); +ProgramStateRef +invalidateAllIteratorPositionsExcept(ProgramStateRef State, + const MemRegion *Cont, SymbolRef Offset, + BinaryOperator::Opcode Opc); +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset, + BinaryOperator::Opcode Opc); +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset1, + BinaryOperator::Opcode Opc1, + SymbolRef Offset2, + BinaryOperator::Opcode Opc2); +ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont); +ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont, + SymbolRef Offset, + BinaryOperator::Opcode Opc); +ProgramStateRef rebaseSymbolInIteratorPositionsIf( + ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym, + SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc); const ContainerData *getContainerData(ProgramStateRef State, const MemRegion *Cont); ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, const ContainerData &CData); bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont); -bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos); +bool isBoundThroughLazyCompoundVal(const Environment &Env, + const MemRegion *Reg); +bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); +bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos); +bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); bool isZero(ProgramStateRef State, const NonLoc &Val); } // namespace @@ -300,39 +401,198 @@ IteratorChecker::IteratorChecker() { OutOfRangeBugType.reset( new BugType(this, "Iterator out of range", "Misuse of STL APIs")); OutOfRangeBugType->setSuppressOnSink(true); + MismatchedBugType.reset( + new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs")); + MismatchedBugType->setSuppressOnSink(true); + InvalidatedBugType.reset( + new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); + InvalidatedBugType->setSuppressOnSink(true); } void IteratorChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - // Check for out of range access + // Check for out of range access or access of invalidated position and + // iterator mismatches const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); if (!Func) return; if (Func->isOverloadedOperator()) { - if (ChecksEnabled[CK_IteratorRangeChecker] && - isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { + if (ChecksEnabled[CK_InvalidatedIteratorChecker] && + isAccessOperator(Func->getOverloadedOperator())) { + // Check for any kind of access of invalidated iterator positions if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - // Check for out-of-range incrementions and decrementions - if (Call.getNumArgs() >= 1) { - verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), - Call.getReturnValue(), - InstCall->getCXXThisVal(), Call.getArgSVal(0)); - } + verifyAccess(C, InstCall->getCXXThisVal()); } else { - if (Call.getNumArgs() >= 2) { - verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), - Call.getReturnValue(), Call.getArgSVal(0), - Call.getArgSVal(1)); + verifyAccess(C, Call.getArgSVal(0)); + } + } + if (ChecksEnabled[CK_IteratorRangeChecker]) { + if (isIncrementOperator(Func->getOverloadedOperator())) { + // Check for out-of-range incrementions + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyIncrement(C, InstCall->getCXXThisVal()); + } else { + if (Call.getNumArgs() >= 1) { + verifyIncrement(C, Call.getArgSVal(0)); + } + } + } else if (isDecrementOperator(Func->getOverloadedOperator())) { + // Check for out-of-range decrementions + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyDecrement(C, InstCall->getCXXThisVal()); + } else { + if (Call.getNumArgs() >= 1) { + verifyDecrement(C, Call.getArgSVal(0)); + } + } + } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + // Check for out-of-range incrementions and decrementions + if (Call.getNumArgs() >= 1) { + verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), + InstCall->getCXXThisVal(), + Call.getArgSVal(0)); + } + } else { + if (Call.getNumArgs() >= 2) { + verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), + Call.getArgSVal(0), Call.getArgSVal(1)); + } + } + } else if (isDereferenceOperator(Func->getOverloadedOperator())) { + // Check for dereference of out-of-range iterators + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyDereference(C, InstCall->getCXXThisVal()); + } else { + verifyDereference(C, Call.getArgSVal(0)); } } - } else if (ChecksEnabled[CK_IteratorRangeChecker] && - isDereferenceOperator(Func->getOverloadedOperator())) { - // Check for dereference of out-of-range iterators + } else if (ChecksEnabled[CK_MismatchedIteratorChecker] && + isComparisonOperator(Func->getOverloadedOperator())) { + // Check for comparisons of iterators of different containers if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - verifyDereference(C, InstCall->getCXXThisVal()); + if (Call.getNumArgs() < 1) + return; + + if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) || + !isIteratorType(Call.getArgExpr(0)->getType())) + return; + + verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0)); } else { - verifyDereference(C, Call.getArgSVal(0)); + if (Call.getNumArgs() < 2) + return; + + if (!isIteratorType(Call.getArgExpr(0)->getType()) || + !isIteratorType(Call.getArgExpr(1)->getType())) + return; + + verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } + } + } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (!ChecksEnabled[CK_MismatchedIteratorChecker]) + return; + + const auto *ContReg = InstCall->getCXXThisVal().getAsRegion(); + if (!ContReg) + return; + // Check for erase, insert and emplace using iterator of another container + if (isEraseCall(Func) || isEraseAfterCall(Func)) { + verifyMatch(C, Call.getArgSVal(0), + InstCall->getCXXThisVal().getAsRegion()); + if (Call.getNumArgs() == 2) { + verifyMatch(C, Call.getArgSVal(1), + InstCall->getCXXThisVal().getAsRegion()); + } + } else if (isInsertCall(Func)) { + verifyMatch(C, Call.getArgSVal(0), + InstCall->getCXXThisVal().getAsRegion()); + if (Call.getNumArgs() == 3 && + isIteratorType(Call.getArgExpr(1)->getType()) && + isIteratorType(Call.getArgExpr(2)->getType())) { + verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2)); + } + } else if (isEmplaceCall(Func)) { + verifyMatch(C, Call.getArgSVal(0), + InstCall->getCXXThisVal().getAsRegion()); + } + } else if (isa<CXXConstructorCall>(&Call)) { + // Check match of first-last iterator pair in a constructor of a container + if (Call.getNumArgs() < 2) + return; + + const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl()); + if (Ctr->getNumParams() < 2) + return; + + if (Ctr->getParamDecl(0)->getName() != "first" || + Ctr->getParamDecl(1)->getName() != "last") + return; + + if (!isIteratorType(Call.getArgExpr(0)->getType()) || + !isIteratorType(Call.getArgExpr(1)->getType())) + return; + + verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } else { + // The main purpose of iterators is to abstract away from different + // containers and provide a (maybe limited) uniform access to them. + // This implies that any correctly written template function that + // works on multiple containers using iterators takes different + // template parameters for different containers. So we can safely + // assume that passing iterators of different containers as arguments + // whose type replaces the same template parameter is a bug. + // + // Example: + // template<typename I1, typename I2> + // void f(I1 first1, I1 last1, I2 first2, I2 last2); + // + // In this case the first two arguments to f() must be iterators must belong + // to the same container and the last to also to the same container but + // not necessarily to the same as the first two. + + if (!ChecksEnabled[CK_MismatchedIteratorChecker]) + return; + + const auto *Templ = Func->getPrimaryTemplate(); + if (!Templ) + return; + + const auto *TParams = Templ->getTemplateParameters(); + const auto *TArgs = Func->getTemplateSpecializationArgs(); + + // Iterate over all the template parameters + for (size_t I = 0; I < TParams->size(); ++I) { + const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I)); + if (!TPDecl) + continue; + + if (TPDecl->isParameterPack()) + continue; + + const auto TAType = TArgs->get(I).getAsType(); + if (!isIteratorType(TAType)) + continue; + + SVal LHS = UndefinedVal(); + + // For every template parameter which is an iterator type in the + // instantiation look for all functions' parameters' type by it and + // check whether they belong to the same container + for (auto J = 0U; J < Func->getNumParams(); ++J) { + const auto *Param = Func->getParamDecl(J); + const auto *ParamType = + Param->getType()->getAs<SubstTemplateTypeParmType>(); + if (!ParamType || + ParamType->getReplacedParameter()->getDecl() != TPDecl) + continue; + if (LHS.isUndef()) { + LHS = Call.getArgSVal(J); + } else { + verifyMatch(C, LHS, Call.getArgSVal(J)); + } } } } @@ -347,7 +607,15 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, if (Func->isOverloadedOperator()) { const auto Op = Func->getOverloadedOperator(); - if (isSimpleComparisonOperator(Op)) { + if (isAssignmentOperator(Op)) { + const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call); + if (Func->getParamDecl(0)->getType()->isRValueReferenceType()) { + handleAssign(C, InstCall->getCXXThisVal(), Call.getOriginExpr(), + Call.getArgSVal(0)); + } else { + handleAssign(C, InstCall->getCXXThisVal()); + } + } else if (isSimpleComparisonOperator(Op)) { if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { handleComparison(C, Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0), Op); @@ -387,6 +655,36 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, } } } else { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (isAssignCall(Func)) { + handleAssign(C, InstCall->getCXXThisVal()); + } else if (isClearCall(Func)) { + handleClear(C, InstCall->getCXXThisVal()); + } else if (isPushBackCall(Func) || isEmplaceBackCall(Func)) { + handlePushBack(C, InstCall->getCXXThisVal()); + } else if (isPopBackCall(Func)) { + handlePopBack(C, InstCall->getCXXThisVal()); + } else if (isPushFrontCall(Func) || isEmplaceFrontCall(Func)) { + handlePushFront(C, InstCall->getCXXThisVal()); + } else if (isPopFrontCall(Func)) { + handlePopFront(C, InstCall->getCXXThisVal()); + } else if (isInsertCall(Func) || isEmplaceCall(Func)) { + handleInsert(C, Call.getArgSVal(0)); + } else if (isEraseCall(Func)) { + if (Call.getNumArgs() == 1) { + handleErase(C, Call.getArgSVal(0)); + } else if (Call.getNumArgs() == 2) { + handleErase(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } + } else if (isEraseAfterCall(Func)) { + if (Call.getNumArgs() == 1) { + handleEraseAfter(C, Call.getArgSVal(0)); + } else if (Call.getNumArgs() == 2) { + handleEraseAfter(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } + } + } + const auto *OrigExpr = Call.getOriginExpr(); if (!OrigExpr) return; @@ -395,9 +693,6 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, return; auto State = C.getState(); - // Already bound to container? - if (getIteratorPosition(State, Call.getReturnValue())) - return; if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { if (isBeginCall(Func)) { @@ -412,6 +707,10 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, } } + // Already bound to container? + if (getIteratorPosition(State, Call.getReturnValue())) + return; + // Copy-like and move constructors if (isa<CXXConstructorCall>(&Call) && Call.getNumArgs() == 1) { if (const auto *Pos = getIteratorPosition(State, Call.getArgSVal(0))) { @@ -441,33 +740,19 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, } } -void IteratorChecker::checkPreStmt(const CXXOperatorCallExpr *COCE, - CheckerContext &C) const { - const auto *ThisExpr = COCE->getArg(0); - +void IteratorChecker::checkBind(SVal Loc, SVal Val, const Stmt *S, + CheckerContext &C) const { auto State = C.getState(); - const auto *LCtx = C.getLocationContext(); - - const auto CurrentThis = State->getSVal(ThisExpr, LCtx); - if (const auto *Reg = CurrentThis.getAsRegion()) { - if (!Reg->getAs<CXXTempObjectRegion>()) - return; - const auto OldState = C.getPredecessor()->getFirstPred()->getState(); - const auto OldThis = OldState->getSVal(ThisExpr, LCtx); - // FIXME: This solution is unreliable. It may happen that another checker - // subscribes to the pre-statement check of `CXXOperatorCallExpr` - // and adds a transition before us. The proper fix is to make the - // CFG provide a `ConstructionContext` for the `CXXOperatorCallExpr`, - // which would turn the corresponding `CFGStmt` element into a - // `CFGCXXRecordTypedCall` element, which will allow `ExprEngine` to - // foresee that the `begin()`/`end()` call constructs the object - // directly in the temporary region that `CXXOperatorCallExpr` takes - // as its implicit object argument. - const auto *Pos = getIteratorPosition(OldState, OldThis); - if (!Pos) - return; - State = setIteratorPosition(State, CurrentThis, *Pos); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos) { + State = setIteratorPosition(State, Loc, *Pos); C.addTransition(State); + } else { + const auto *OldPos = getIteratorPosition(State, Loc); + if (OldPos) { + State = removeIteratorPosition(State, Loc); + C.addTransition(State); + } } } @@ -508,9 +793,13 @@ void IteratorChecker::checkLiveSymbols(ProgramStateRef State, const auto CData = Cont.second; if (CData.getBegin()) { SR.markLive(CData.getBegin()); + if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getBegin())) + SR.markLive(SIE->getLHS()); } if (CData.getEnd()) { SR.markLive(CData.getEnd()); + if(const auto *SIE = dyn_cast<SymIntExpr>(CData.getEnd())) + SR.markLive(SIE->getLHS()); } } } @@ -523,7 +812,12 @@ void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, auto RegionMap = State->get<IteratorRegionMap>(); for (const auto Reg : RegionMap) { if (!SR.isLiveRegion(Reg.first)) { - State = State->remove<IteratorRegionMap>(Reg.first); + // The region behind the `LazyCompoundVal` is often cleaned up before + // the `LazyCompoundVal` itself. If there are iterator positions keyed + // by these regions their cleanup must be deferred. + if (!isBoundThroughLazyCompoundVal(State->getEnvironment(), Reg.first)) { + State = State->remove<IteratorRegionMap>(Reg.first); + } } } @@ -623,14 +917,24 @@ void IteratorChecker::verifyDereference(CheckerContext &C, const SVal &Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); - if (Pos && isOutOfRange(State, *Pos)) { - // If I do not put a tag here, some range tests will fail - static CheckerProgramPointTag Tag("IteratorRangeChecker", - "IteratorOutOfRange"); - auto *N = C.generateNonFatalErrorNode(State, &Tag); + if (Pos && isPastTheEnd(State, *Pos)) { + auto *N = C.generateNonFatalErrorNode(State); if (!N) return; - reportOutOfRangeBug("Iterator accessed outside of its range.", Val, C, N); + reportOutOfRangeBug("Past-the-end iterator dereferenced.", Val, C, N); + return; + } +} + +void IteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos && !Pos->isValid()) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) { + return; + } + reportInvalidatedBug("Invalidated iterator accessed.", Val, C, N); } } @@ -643,14 +947,9 @@ void IteratorChecker::handleIncrement(CheckerContext &C, const SVal &RetVal, if (Pos) { auto &SymMgr = C.getSymbolManager(); auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto OldOffset = Pos->getOffset(); - auto NewOffset = - SVB.evalBinOp(State, BO_Add, - nonloc::SymbolVal(OldOffset), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(OldOffset)).getAsSymbol(); - auto NewPos = Pos->setTo(NewOffset); + const auto NewPos = + advancePosition(C, OO_Plus, *Pos, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); State = setIteratorPosition(State, Iter, NewPos); State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); C.addTransition(State); @@ -666,14 +965,9 @@ void IteratorChecker::handleDecrement(CheckerContext &C, const SVal &RetVal, if (Pos) { auto &SymMgr = C.getSymbolManager(); auto &BVF = SymMgr.getBasicVals(); - auto &SVB = C.getSValBuilder(); - const auto OldOffset = Pos->getOffset(); - auto NewOffset = - SVB.evalBinOp(State, BO_Sub, - nonloc::SymbolVal(OldOffset), - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), - SymMgr.getType(OldOffset)).getAsSymbol(); - auto NewPos = Pos->setTo(NewOffset); + const auto NewPos = + advancePosition(C, OO_Minus, *Pos, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); State = setIteratorPosition(State, Iter, NewPos); State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); C.addTransition(State); @@ -739,69 +1033,95 @@ void IteratorChecker::handleRandomIncrOrDecr(CheckerContext &C, value = &val; } - auto &SymMgr = C.getSymbolManager(); - auto &SVB = C.getSValBuilder(); - auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; - const auto OldOffset = Pos->getOffset(); - SymbolRef NewOffset; - if (const auto intValue = value->getAs<nonloc::ConcreteInt>()) { - // For concrete integers we can calculate the new position - NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset), - *intValue, - SymMgr.getType(OldOffset)).getAsSymbol(); - } else { - // For other symbols create a new symbol to keep expressions simple - const auto &LCtx = C.getLocationContext(); - NewOffset = SymMgr.conjureSymbol(nullptr, LCtx, SymMgr.getType(OldOffset), - C.blockCount()); - State = assumeNoOverflow(State, NewOffset, 4); - } - auto NewPos = Pos->setTo(NewOffset); auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal; - State = setIteratorPosition(State, TgtVal, NewPos); + State = + setIteratorPosition(State, TgtVal, advancePosition(C, Op, *Pos, *value)); C.addTransition(State); } +void IteratorChecker::verifyIncrement(CheckerContext &C, + const SVal &Iter) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + verifyRandomIncrOrDecr(C, OO_Plus, Iter, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); +} + +void IteratorChecker::verifyDecrement(CheckerContext &C, + const SVal &Iter) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + verifyRandomIncrOrDecr(C, OO_Minus, Iter, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); +} + void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, - const SVal &RetVal, const SVal &LHS, const SVal &RHS) const { auto State = C.getState(); // If the iterator is initially inside its range, then the operation is valid const auto *Pos = getIteratorPosition(State, LHS); - if (!Pos || !isOutOfRange(State, *Pos)) + if (!Pos) return; - auto value = RHS; - if (auto loc = RHS.getAs<Loc>()) { - value = State->getRawSVal(*loc); + auto Value = RHS; + if (auto ValAsLoc = RHS.getAs<Loc>()) { + Value = State->getRawSVal(*ValAsLoc); } - // Incremention or decremention by 0 is never bug - if (isZero(State, value.castAs<NonLoc>())) + if (Value.isUnknown()) return; - auto &SymMgr = C.getSymbolManager(); - auto &SVB = C.getSValBuilder(); - auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; - const auto OldOffset = Pos->getOffset(); - const auto intValue = value.getAs<nonloc::ConcreteInt>(); - if (!intValue) + // Incremention or decremention by 0 is never a bug. + if (isZero(State, Value.castAs<NonLoc>())) return; - auto NewOffset = SVB.evalBinOp(State, BinOp, nonloc::SymbolVal(OldOffset), - *intValue, - SymMgr.getType(OldOffset)).getAsSymbol(); - auto NewPos = Pos->setTo(NewOffset); + // The result may be the past-end iterator of the container, but any other + // out of range position is undefined behaviour + if (isAheadOfRange(State, advancePosition(C, Op, *Pos, Value))) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) + return; + reportOutOfRangeBug("Iterator decremented ahead of its valid range.", LHS, + C, N); + } + if (isBehindPastTheEnd(State, advancePosition(C, Op, *Pos, Value))) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) + return; + reportOutOfRangeBug("Iterator incremented behind the past-the-end " + "iterator.", LHS, C, N); + } +} + +void IteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, + const MemRegion *Cont) const { + // Verify match between a container and the container of an iterator + Cont = Cont->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (Pos && Pos->getContainer() != Cont) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) { + return; + } + reportMismatchedBug("Container accessed using foreign iterator argument.", Iter, Cont, C, N); + } +} - // If out of range, the only valid operation is to step into the range - if (isOutOfRange(State, NewPos)) { +void IteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const { + // Verify match between the containers of two iterators + auto State = C.getState(); + const auto *Pos1 = getIteratorPosition(State, Iter1); + const auto *Pos2 = getIteratorPosition(State, Iter2); + if (Pos1 && Pos2 && Pos1->getContainer() != Pos2->getContainer()) { auto *N = C.generateNonFatalErrorNode(State); if (!N) return; - reportOutOfRangeBug("Iterator accessed past its end.", LHS, C, N); + reportMismatchedBug("Iterators of different containers used where the " + "same container is expected.", Iter1, Iter2, C, N); } } @@ -811,9 +1131,7 @@ void IteratorChecker::handleBegin(CheckerContext &C, const Expr *CE, if (!ContReg) return; - while (const auto *CBOR = ContReg->getAs<CXXBaseObjectRegion>()) { - ContReg = CBOR->getSuperRegion(); - } + ContReg = ContReg->getMostDerivedObjectRegion(); // If the container already has a begin symbol then use it. Otherwise first // create a new one. @@ -837,9 +1155,7 @@ void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, if (!ContReg) return; - while (const auto *CBOR = ContReg->getAs<CXXBaseObjectRegion>()) { - ContReg = CBOR->getSuperRegion(); - } + ContReg = ContReg->getMostDerivedObjectRegion(); // If the container already has an end symbol then use it. Otherwise first // create a new one. @@ -860,9 +1176,7 @@ void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, const SVal &RetVal, const MemRegion *Cont) const { - while (const auto *CBOR = Cont->getAs<CXXBaseObjectRegion>()) { - Cont = CBOR->getSuperRegion(); - } + Cont = Cont->getMostDerivedObjectRegion(); auto State = C.getState(); auto &SymMgr = C.getSymbolManager(); @@ -874,6 +1188,399 @@ void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, C.addTransition(State); } +void IteratorChecker::handleAssign(CheckerContext &C, const SVal &Cont, + const Expr *CE, const SVal &OldCont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // Assignment of a new value to a container always invalidates all its + // iterators + auto State = C.getState(); + const auto CData = getContainerData(State, ContReg); + if (CData) { + State = invalidateAllIteratorPositions(State, ContReg); + } + + // In case of move, iterators of the old container (except the past-end + // iterators) remain valid but refer to the new container + if (!OldCont.isUndef()) { + const auto *OldContReg = OldCont.getAsRegion(); + if (OldContReg) { + OldContReg = OldContReg->getMostDerivedObjectRegion(); + const auto OldCData = getContainerData(State, OldContReg); + if (OldCData) { + if (const auto OldEndSym = OldCData->getEnd()) { + // If we already assigned an "end" symbol to the old container, then + // first reassign all iterator positions to the new container which + // are not past the container (thus not greater or equal to the + // current "end" symbol). + State = reassignAllIteratorPositionsUnless(State, OldContReg, ContReg, + OldEndSym, BO_GE); + auto &SymMgr = C.getSymbolManager(); + auto &SVB = C.getSValBuilder(); + // Then generate and assign a new "end" symbol for the new container. + auto NewEndSym = + SymMgr.conjureSymbol(CE, C.getLocationContext(), + C.getASTContext().LongTy, C.blockCount()); + State = assumeNoOverflow(State, NewEndSym, 4); + if (CData) { + State = setContainerData(State, ContReg, CData->newEnd(NewEndSym)); + } else { + State = setContainerData(State, ContReg, + ContainerData::fromEnd(NewEndSym)); + } + // Finally, replace the old "end" symbol in the already reassigned + // iterator positions with the new "end" symbol. + State = rebaseSymbolInIteratorPositionsIf( + State, SVB, OldEndSym, NewEndSym, OldEndSym, BO_LT); + } else { + // There was no "end" symbol assigned yet to the old container, + // so reassign all iterator positions to the new container. + State = reassignAllIteratorPositions(State, OldContReg, ContReg); + } + if (const auto OldBeginSym = OldCData->getBegin()) { + // If we already assigned a "begin" symbol to the old container, then + // assign it to the new container and remove it from the old one. + if (CData) { + State = + setContainerData(State, ContReg, CData->newBegin(OldBeginSym)); + } else { + State = setContainerData(State, ContReg, + ContainerData::fromBegin(OldBeginSym)); + } + State = + setContainerData(State, OldContReg, OldCData->newEnd(nullptr)); + } + } else { + // There was neither "begin" nor "end" symbol assigned yet to the old + // container, so reassign all iterator positions to the new container. + State = reassignAllIteratorPositions(State, OldContReg, ContReg); + } + } + } + C.addTransition(State); +} + +void IteratorChecker::handleClear(CheckerContext &C, const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // The clear() operation invalidates all the iterators, except the past-end + // iterators of list-like containers + auto State = C.getState(); + if (!hasSubscriptOperator(State, ContReg) || + !backModifiable(State, ContReg)) { + const auto CData = getContainerData(State, ContReg); + if (CData) { + if (const auto EndSym = CData->getEnd()) { + State = + invalidateAllIteratorPositionsExcept(State, ContReg, EndSym, BO_GE); + C.addTransition(State); + return; + } + } + } + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State); +} + +void IteratorChecker::handlePushBack(CheckerContext &C, + const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // For deque-like containers invalidate all iterator positions + auto State = C.getState(); + if (hasSubscriptOperator(State, ContReg) && frontModifiable(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State); + return; + } + + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + // For vector-like containers invalidate the past-end iterator positions + if (const auto EndSym = CData->getEnd()) { + if (hasSubscriptOperator(State, ContReg)) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + } + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto newEndSym = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(EndSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(EndSym)).getAsSymbol(); + State = setContainerData(State, ContReg, CData->newEnd(newEndSym)); + } + C.addTransition(State); +} + +void IteratorChecker::handlePopBack(CheckerContext &C, const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + if (const auto EndSym = CData->getEnd()) { + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto BackSym = + SVB.evalBinOp(State, BO_Sub, + nonloc::SymbolVal(EndSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(EndSym)).getAsSymbol(); + // For vector-like and deque-like containers invalidate the last and the + // past-end iterator positions. For list-like containers only invalidate + // the last position + if (hasSubscriptOperator(State, ContReg) && + backModifiable(State, ContReg)) { + State = invalidateIteratorPositions(State, BackSym, BO_GE); + State = setContainerData(State, ContReg, CData->newEnd(nullptr)); + } else { + State = invalidateIteratorPositions(State, BackSym, BO_EQ); + } + auto newEndSym = BackSym; + State = setContainerData(State, ContReg, CData->newEnd(newEndSym)); + C.addTransition(State); + } +} + +void IteratorChecker::handlePushFront(CheckerContext &C, + const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + // For deque-like containers invalidate all iterator positions + auto State = C.getState(); + if (hasSubscriptOperator(State, ContReg)) { + State = invalidateAllIteratorPositions(State, ContReg); + C.addTransition(State); + } else { + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + if (const auto BeginSym = CData->getBegin()) { + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto newBeginSym = + SVB.evalBinOp(State, BO_Sub, + nonloc::SymbolVal(BeginSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(BeginSym)).getAsSymbol(); + State = setContainerData(State, ContReg, CData->newBegin(newBeginSym)); + C.addTransition(State); + } + } +} + +void IteratorChecker::handlePopFront(CheckerContext &C, + const SVal &Cont) const { + const auto *ContReg = Cont.getAsRegion(); + if (!ContReg) + return; + + ContReg = ContReg->getMostDerivedObjectRegion(); + + auto State = C.getState(); + const auto CData = getContainerData(State, ContReg); + if (!CData) + return; + + // For deque-like containers invalidate all iterator positions. For list-like + // iterators only invalidate the first position + if (const auto BeginSym = CData->getBegin()) { + if (hasSubscriptOperator(State, ContReg)) { + State = invalidateIteratorPositions(State, BeginSym, BO_LE); + } else { + State = invalidateIteratorPositions(State, BeginSym, BO_EQ); + } + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto newBeginSym = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(BeginSym), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(BeginSym)).getAsSymbol(); + State = setContainerData(State, ContReg, CData->newBegin(newBeginSym)); + C.addTransition(State); + } +} + +void IteratorChecker::handleInsert(CheckerContext &C, const SVal &Iter) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + // For deque-like containers invalidate all iterator positions. For + // vector-like containers invalidate iterator positions after the insertion. + const auto *Cont = Pos->getContainer(); + if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) { + if (frontModifiable(State, Cont)) { + State = invalidateAllIteratorPositions(State, Cont); + } else { + State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE); + } + if (const auto *CData = getContainerData(State, Cont)) { + if (const auto EndSym = CData->getEnd()) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + State = setContainerData(State, Cont, CData->newEnd(nullptr)); + } + } + C.addTransition(State); + } +} + +void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + // For deque-like containers invalidate all iterator positions. For + // vector-like containers invalidate iterator positions at and after the + // deletion. For list-like containers only invalidate the deleted position. + const auto *Cont = Pos->getContainer(); + if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) { + if (frontModifiable(State, Cont)) { + State = invalidateAllIteratorPositions(State, Cont); + } else { + State = invalidateIteratorPositions(State, Pos->getOffset(), BO_GE); + } + if (const auto *CData = getContainerData(State, Cont)) { + if (const auto EndSym = CData->getEnd()) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + State = setContainerData(State, Cont, CData->newEnd(nullptr)); + } + } + } else { + State = invalidateIteratorPositions(State, Pos->getOffset(), BO_EQ); + } + C.addTransition(State); +} + +void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const { + auto State = C.getState(); + const auto *Pos1 = getIteratorPosition(State, Iter1); + const auto *Pos2 = getIteratorPosition(State, Iter2); + if (!Pos1 || !Pos2) + return; + + // For deque-like containers invalidate all iterator positions. For + // vector-like containers invalidate iterator positions at and after the + // deletion range. For list-like containers only invalidate the deleted + // position range [first..last]. + const auto *Cont = Pos1->getContainer(); + if (hasSubscriptOperator(State, Cont) && backModifiable(State, Cont)) { + if (frontModifiable(State, Cont)) { + State = invalidateAllIteratorPositions(State, Cont); + } else { + State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE); + } + if (const auto *CData = getContainerData(State, Cont)) { + if (const auto EndSym = CData->getEnd()) { + State = invalidateIteratorPositions(State, EndSym, BO_GE); + State = setContainerData(State, Cont, CData->newEnd(nullptr)); + } + } + } else { + State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GE, + Pos2->getOffset(), BO_LT); + } + C.addTransition(State); +} + +void IteratorChecker::handleEraseAfter(CheckerContext &C, + const SVal &Iter) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + // Invalidate the deleted iterator position, which is the position of the + // parameter plus one. + auto &SymMgr = C.getSymbolManager(); + auto &BVF = SymMgr.getBasicVals(); + auto &SVB = C.getSValBuilder(); + const auto NextSym = + SVB.evalBinOp(State, BO_Add, + nonloc::SymbolVal(Pos->getOffset()), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1))), + SymMgr.getType(Pos->getOffset())).getAsSymbol(); + State = invalidateIteratorPositions(State, NextSym, BO_EQ); + C.addTransition(State); +} + +void IteratorChecker::handleEraseAfter(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const { + auto State = C.getState(); + const auto *Pos1 = getIteratorPosition(State, Iter1); + const auto *Pos2 = getIteratorPosition(State, Iter2); + if (!Pos1 || !Pos2) + return; + + // Invalidate the deleted iterator position range (first..last) + State = invalidateIteratorPositions(State, Pos1->getOffset(), BO_GT, + Pos2->getOffset(), BO_LT); + C.addTransition(State); +} + +IteratorPosition IteratorChecker::advancePosition(CheckerContext &C, + OverloadedOperatorKind Op, + const IteratorPosition &Pos, + const SVal &Distance) const { + auto State = C.getState(); + auto &SymMgr = C.getSymbolManager(); + auto &SVB = C.getSValBuilder(); + + assert ((Op == OO_Plus || Op == OO_PlusEqual || + Op == OO_Minus || Op == OO_MinusEqual) && + "Advance operator must be one of +, -, += and -=."); + auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; + if (const auto IntDist = Distance.getAs<nonloc::ConcreteInt>()) { + // For concrete integers we can calculate the new position + return Pos.setTo(SVB.evalBinOp(State, BinOp, + nonloc::SymbolVal(Pos.getOffset()), *IntDist, + SymMgr.getType(Pos.getOffset())) + .getAsSymbol()); + } else { + // For other symbols create a new symbol to keep expressions simple + const auto &LCtx = C.getLocationContext(); + const auto NewPosSym = SymMgr.conjureSymbol(nullptr, LCtx, + SymMgr.getType(Pos.getOffset()), + C.blockCount()); + State = assumeNoOverflow(State, NewPosSym, 4); + return Pos.setTo(NewPosSym); + } +} + void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const { @@ -882,14 +1589,47 @@ void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, C.emitReport(std::move(R)); } +void IteratorChecker::reportMismatchedBug(const StringRef &Message, + const SVal &Val1, const SVal &Val2, + CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = llvm::make_unique<BugReport>(*MismatchedBugType, Message, ErrNode); + R->markInteresting(Val1); + R->markInteresting(Val2); + C.emitReport(std::move(R)); +} + +void IteratorChecker::reportMismatchedBug(const StringRef &Message, + const SVal &Val, const MemRegion *Reg, + CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = llvm::make_unique<BugReport>(*MismatchedBugType, Message, ErrNode); + R->markInteresting(Val); + R->markInteresting(Reg); + C.emitReport(std::move(R)); +} + +void IteratorChecker::reportInvalidatedBug(const StringRef &Message, + const SVal &Val, CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = llvm::make_unique<BugReport>(*InvalidatedBugType, Message, ErrNode); + R->markInteresting(Val); + C.emitReport(std::move(R)); +} + namespace { bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); -bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); +bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); +bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, BinaryOperator::Opcode Opc); bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, BinaryOperator::Opcode Opc); +const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, + const MemRegion *Reg); +SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef Expr, + SymbolRef OldSym, SymbolRef NewSym); bool isIteratorType(const QualType &Type) { if (Type->isPointerType()) @@ -943,6 +1683,11 @@ bool isIterator(const CXXRecordDecl *CRD) { HasPostIncrOp && HasDerefOp; } +bool isComparisonOperator(OverloadedOperatorKind OK) { + return OK == OO_EqualEqual || OK == OO_ExclaimEqual || OK == OO_Less || + OK == OO_LessEqual || OK == OO_Greater || OK == OO_GreaterEqual; +} + bool isBeginCall(const FunctionDecl *Func) { const auto *IdInfo = Func->getIdentifier(); if (!IdInfo) @@ -957,10 +1702,139 @@ bool isEndCall(const FunctionDecl *Func) { return IdInfo->getName().endswith_lower("end"); } +bool isAssignCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() > 2) + return false; + return IdInfo->getName() == "assign"; +} + +bool isClearCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() > 0) + return false; + return IdInfo->getName() == "clear"; +} + +bool isPushBackCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() != 1) + return false; + return IdInfo->getName() == "push_back"; +} + +bool isEmplaceBackCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 1) + return false; + return IdInfo->getName() == "emplace_back"; +} + +bool isPopBackCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() > 0) + return false; + return IdInfo->getName() == "pop_back"; +} + +bool isPushFrontCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() != 1) + return false; + return IdInfo->getName() == "push_front"; +} + +bool isEmplaceFrontCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 1) + return false; + return IdInfo->getName() == "emplace_front"; +} + +bool isPopFrontCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() > 0) + return false; + return IdInfo->getName() == "pop_front"; +} + +bool isInsertCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 2 || Func->getNumParams() > 3) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + return IdInfo->getName() == "insert"; +} + +bool isEmplaceCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 2) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + return IdInfo->getName() == "emplace"; +} + +bool isEraseCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 1 || Func->getNumParams() > 2) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + if (Func->getNumParams() == 2 && + !isIteratorType(Func->getParamDecl(1)->getType())) + return false; + return IdInfo->getName() == "erase"; +} + +bool isEraseAfterCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 1 || Func->getNumParams() > 2) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + if (Func->getNumParams() == 2 && + !isIteratorType(Func->getParamDecl(1)->getType())) + return false; + return IdInfo->getName() == "erase_after"; +} + +bool isAssignmentOperator(OverloadedOperatorKind OK) { return OK == OO_Equal; } + bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { return OK == OO_EqualEqual || OK == OO_ExclaimEqual; } +bool isAccessOperator(OverloadedOperatorKind OK) { + return isDereferenceOperator(OK) || isIncrementOperator(OK) || + isDecrementOperator(OK) || isRandomIncrOrDecrOperator(OK); +} + bool isDereferenceOperator(OverloadedOperatorKind OK) { return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || OK == OO_Subscript; @@ -996,6 +1870,66 @@ BinaryOperator::Opcode getOpcode(const SymExpr *SE) { return BO_Comma; // Extremal value, neither EQ nor NE } +bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg) { + const auto *CRD = getCXXRecordDecl(State, Reg); + if (!CRD) + return false; + + for (const auto *Method : CRD->methods()) { + if (!Method->isOverloadedOperator()) + continue; + const auto OPK = Method->getOverloadedOperator(); + if (OPK == OO_Subscript) { + return true; + } + } + return false; +} + +bool frontModifiable(ProgramStateRef State, const MemRegion *Reg) { + const auto *CRD = getCXXRecordDecl(State, Reg); + if (!CRD) + return false; + + for (const auto *Method : CRD->methods()) { + if (!Method->getDeclName().isIdentifier()) + continue; + if (Method->getName() == "push_front" || Method->getName() == "pop_front") { + return true; + } + } + return false; +} + +bool backModifiable(ProgramStateRef State, const MemRegion *Reg) { + const auto *CRD = getCXXRecordDecl(State, Reg); + if (!CRD) + return false; + + for (const auto *Method : CRD->methods()) { + if (!Method->getDeclName().isIdentifier()) + continue; + if (Method->getName() == "push_back" || Method->getName() == "pop_back") { + return true; + } + } + return false; +} + +const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, + const MemRegion *Reg) { + auto TI = getDynamicTypeInfo(State, Reg); + if (!TI.isValid()) + return nullptr; + + auto Type = TI.getType(); + if (const auto *RefT = Type->getAs<ReferenceType>()) { + Type = RefT->getPointeeType(); + } + + return Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); +} + const RegionOrSymbol getRegionOrSymbol(const SVal &Val) { if (const auto Reg = Val.getAsRegion()) { return Reg; @@ -1097,7 +2031,8 @@ ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, const IteratorPosition *getIteratorPosition(ProgramStateRef State, const SVal &Val) { - if (const auto Reg = Val.getAsRegion()) { + if (auto Reg = Val.getAsRegion()) { + Reg = Reg->getMostDerivedObjectRegion(); return State->get<IteratorRegionMap>(Reg); } else if (const auto Sym = Val.getAsSymbol()) { return State->get<IteratorSymbolMap>(Sym); @@ -1110,7 +2045,8 @@ const IteratorPosition *getIteratorPosition(ProgramStateRef State, const IteratorPosition *getIteratorPosition(ProgramStateRef State, RegionOrSymbol RegOrSym) { if (RegOrSym.is<const MemRegion *>()) { - return State->get<IteratorRegionMap>(RegOrSym.get<const MemRegion *>()); + auto Reg = RegOrSym.get<const MemRegion *>()->getMostDerivedObjectRegion(); + return State->get<IteratorRegionMap>(Reg); } else if (RegOrSym.is<SymbolRef>()) { return State->get<IteratorSymbolMap>(RegOrSym.get<SymbolRef>()); } @@ -1119,7 +2055,8 @@ const IteratorPosition *getIteratorPosition(ProgramStateRef State, ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, const IteratorPosition &Pos) { - if (const auto Reg = Val.getAsRegion()) { + if (auto Reg = Val.getAsRegion()) { + Reg = Reg->getMostDerivedObjectRegion(); return State->set<IteratorRegionMap>(Reg, Pos); } else if (const auto Sym = Val.getAsSymbol()) { return State->set<IteratorSymbolMap>(Sym, Pos); @@ -1133,8 +2070,8 @@ ProgramStateRef setIteratorPosition(ProgramStateRef State, RegionOrSymbol RegOrSym, const IteratorPosition &Pos) { if (RegOrSym.is<const MemRegion *>()) { - return State->set<IteratorRegionMap>(RegOrSym.get<const MemRegion *>(), - Pos); + auto Reg = RegOrSym.get<const MemRegion *>()->getMostDerivedObjectRegion(); + return State->set<IteratorRegionMap>(Reg, Pos); } else if (RegOrSym.is<SymbolRef>()) { return State->set<IteratorSymbolMap>(RegOrSym.get<SymbolRef>(), Pos); } @@ -1142,7 +2079,8 @@ ProgramStateRef setIteratorPosition(ProgramStateRef State, } ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { - if (const auto Reg = Val.getAsRegion()) { + if (auto Reg = Val.getAsRegion()) { + Reg = Reg->getMostDerivedObjectRegion(); return State->remove<IteratorRegionMap>(Reg); } else if (const auto Sym = Val.getAsSymbol()) { return State->remove<IteratorSymbolMap>(Sym); @@ -1211,6 +2149,164 @@ bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont) { return false; } +bool isBoundThroughLazyCompoundVal(const Environment &Env, + const MemRegion *Reg) { + for (const auto Binding: Env) { + if (const auto LCVal = Binding.second.getAs<nonloc::LazyCompoundVal>()) { + if (LCVal->getRegion() == Reg) + return true; + } + } + + return false; +} + +template <typename Condition, typename Process> +ProgramStateRef processIteratorPositions(ProgramStateRef State, Condition Cond, + Process Proc) { + auto &RegionMapFactory = State->get_context<IteratorRegionMap>(); + auto RegionMap = State->get<IteratorRegionMap>(); + bool Changed = false; + for (const auto Reg : RegionMap) { + if (Cond(Reg.second)) { + RegionMap = RegionMapFactory.add(RegionMap, Reg.first, Proc(Reg.second)); + Changed = true; + } + } + + if (Changed) + State = State->set<IteratorRegionMap>(RegionMap); + + auto &SymbolMapFactory = State->get_context<IteratorSymbolMap>(); + auto SymbolMap = State->get<IteratorSymbolMap>(); + Changed = false; + for (const auto Sym : SymbolMap) { + if (Cond(Sym.second)) { + SymbolMap = SymbolMapFactory.add(SymbolMap, Sym.first, Proc(Sym.second)); + Changed = true; + } + } + + if (Changed) + State = State->set<IteratorSymbolMap>(SymbolMap); + + return State; +} + +ProgramStateRef invalidateAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont) { + auto MatchCont = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont; + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, MatchCont, Invalidate); +} + +ProgramStateRef +invalidateAllIteratorPositionsExcept(ProgramStateRef State, + const MemRegion *Cont, SymbolRef Offset, + BinaryOperator::Opcode Opc) { + auto MatchContAndCompare = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont && + !compare(State, Pos.getOffset(), Offset, Opc); + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, MatchContAndCompare, Invalidate); +} + +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset, + BinaryOperator::Opcode Opc) { + auto Compare = [&](const IteratorPosition &Pos) { + return compare(State, Pos.getOffset(), Offset, Opc); + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, Compare, Invalidate); +} + +ProgramStateRef invalidateIteratorPositions(ProgramStateRef State, + SymbolRef Offset1, + BinaryOperator::Opcode Opc1, + SymbolRef Offset2, + BinaryOperator::Opcode Opc2) { + auto Compare = [&](const IteratorPosition &Pos) { + return compare(State, Pos.getOffset(), Offset1, Opc1) && + compare(State, Pos.getOffset(), Offset2, Opc2); + }; + auto Invalidate = [&](const IteratorPosition &Pos) { + return Pos.invalidate(); + }; + return processIteratorPositions(State, Compare, Invalidate); +} + +ProgramStateRef reassignAllIteratorPositions(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont) { + auto MatchCont = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont; + }; + auto ReAssign = [&](const IteratorPosition &Pos) { + return Pos.reAssign(NewCont); + }; + return processIteratorPositions(State, MatchCont, ReAssign); +} + +ProgramStateRef reassignAllIteratorPositionsUnless(ProgramStateRef State, + const MemRegion *Cont, + const MemRegion *NewCont, + SymbolRef Offset, + BinaryOperator::Opcode Opc) { + auto MatchContAndCompare = [&](const IteratorPosition &Pos) { + return Pos.getContainer() == Cont && + !compare(State, Pos.getOffset(), Offset, Opc); + }; + auto ReAssign = [&](const IteratorPosition &Pos) { + return Pos.reAssign(NewCont); + }; + return processIteratorPositions(State, MatchContAndCompare, ReAssign); +} + +// This function rebases symbolic expression `OldSym + Int` to `NewSym + Int`, +// `OldSym - Int` to `NewSym - Int` and `OldSym` to `NewSym` in any iterator +// position offsets where `CondSym` is true. +ProgramStateRef rebaseSymbolInIteratorPositionsIf( + ProgramStateRef State, SValBuilder &SVB, SymbolRef OldSym, + SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc) { + auto LessThanEnd = [&](const IteratorPosition &Pos) { + return compare(State, Pos.getOffset(), CondSym, Opc); + }; + auto RebaseSymbol = [&](const IteratorPosition &Pos) { + return Pos.setTo(rebaseSymbol(State, SVB, Pos.getOffset(), OldSym, + NewSym)); + }; + return processIteratorPositions(State, LessThanEnd, RebaseSymbol); +} + +// This function rebases symbolic expression `OldExpr + Int` to `NewExpr + Int`, +// `OldExpr - Int` to `NewExpr - Int` and `OldExpr` to `NewExpr` in expression +// `OrigExpr`. +SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, + SymbolRef OrigExpr, SymbolRef OldExpr, + SymbolRef NewSym) { + auto &SymMgr = SVB.getSymbolManager(); + auto Diff = SVB.evalBinOpNN(State, BO_Sub, nonloc::SymbolVal(OrigExpr), + nonloc::SymbolVal(OldExpr), + SymMgr.getType(OrigExpr)); + + const auto DiffInt = Diff.getAs<nonloc::ConcreteInt>(); + if (!DiffInt) + return OrigExpr; + + return SVB.evalBinOpNN(State, BO_Add, *DiffInt, nonloc::SymbolVal(NewSym), + SymMgr.getType(OrigExpr)).getAsSymbol(); +} + bool isZero(ProgramStateRef State, const NonLoc &Val) { auto &BVF = State->getBasicVals(); return compare(State, Val, @@ -1218,14 +2314,27 @@ bool isZero(ProgramStateRef State, const NonLoc &Val) { BO_EQ); } -bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { +bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { const auto *Cont = Pos.getContainer(); const auto *CData = getContainerData(State, Cont); if (!CData) return false; - // Out of range means less than the begin symbol or greater or equal to the - // end symbol. + const auto End = CData->getEnd(); + if (End) { + if (isEqual(State, Pos.getOffset(), End)) { + return true; + } + } + + return false; +} + +bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos) { + const auto *Cont = Pos.getContainer(); + const auto *CData = getContainerData(State, Cont); + if (!CData) + return false; const auto Beg = CData->getBegin(); if (Beg) { @@ -1234,9 +2343,18 @@ bool isOutOfRange(ProgramStateRef State, const IteratorPosition &Pos) { } } + return false; +} + +bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { + const auto *Cont = Pos.getContainer(); + const auto *CData = getContainerData(State, Cont); + if (!CData) + return false; + const auto End = CData->getEnd(); if (End) { - if (isGreaterOrEqual(State, Pos.getOffset(), End)) { + if (isGreater(State, Pos.getOffset(), End)) { return true; } } @@ -1248,8 +2366,12 @@ bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { return compare(State, Sym1, Sym2, BO_LT); } -bool isGreaterOrEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { - return compare(State, Sym1, Sym2, BO_GE); +bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_GT); +} + +bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_EQ); } bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, @@ -1257,6 +2379,7 @@ bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc); } + bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, BinaryOperator::Opcode Opc) { auto &SVB = State->getStateManager().getSValBuilder(); @@ -1281,4 +2404,5 @@ bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, } REGISTER_CHECKER(IteratorRangeChecker) - +REGISTER_CHECKER(MismatchedIteratorChecker) +REGISTER_CHECKER(InvalidatedIteratorChecker) diff --git a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 2fb627184eb9..aade62fd7491 100644 --- a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -28,7 +28,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/StmtVisitor.h" diff --git a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp index db4fbca36deb..df238f2b2e45 100644 --- a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/StmtVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" @@ -32,8 +32,7 @@ static bool IsLLVMStringRef(QualType T) { if (!RT) return false; - return StringRef(QualType(RT, 0).getAsString()) == - "class StringRef"; + return StringRef(QualType(RT, 0).getAsString()) == "class StringRef"; } /// Check whether the declaration is semantically inside the top-level diff --git a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index 849b1193c042..eda39efeca17 100644 --- a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" @@ -125,7 +125,6 @@ public: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) override; @@ -1003,7 +1002,6 @@ void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, std::shared_ptr<PathDiagnosticPiece> NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) { if (Satisfied) return nullptr; @@ -1400,7 +1398,8 @@ void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) { NonLocalizedStringChecker *checker = mgr.registerChecker<NonLocalizedStringChecker>(); checker->IsAggressive = - mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false); + mgr.getAnalyzerOptions().getCheckerBooleanOption("AggressiveReport", + false, checker); } void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp index e9ec7a0c4365..fb9bccebe465 100644 --- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp +++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp @@ -87,7 +87,6 @@ void MPIBugReporter::reportUnmatchedWait( std::shared_ptr<PathDiagnosticPiece> MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { @@ -96,13 +95,13 @@ MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N, const Request *const Req = N->getState()->get<RequestMap>(RequestRegion); const Request *const PrevReq = - PrevN->getState()->get<RequestMap>(RequestRegion); + N->getFirstPred()->getState()->get<RequestMap>(RequestRegion); // Check if request was previously unused or in a different state. if ((Req && !PrevReq) || (Req->CurrentState != PrevReq->CurrentState)) { IsNodeFound = true; - ProgramPoint P = PrevN->getLocation(); + ProgramPoint P = N->getFirstPred()->getLocation(); PathDiagnosticLocation L = PathDiagnosticLocation::create(P, BRC.getSourceManager()); diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h index 40eb0631d7c5..32fcb07e3371 100644 --- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h +++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h @@ -91,7 +91,6 @@ private: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; diff --git a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp index 696cf39473d5..28c6898f7947 100644 --- a/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIChecker.cpp @@ -16,7 +16,7 @@ //===----------------------------------------------------------------------===// #include "MPIChecker.h" -#include "../ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" namespace clang { namespace ento { @@ -100,9 +100,6 @@ void MPIChecker::checkUnmatchedWaits(const CallEvent &PreCallEvent, void MPIChecker::checkMissingWaits(SymbolReaper &SymReaper, CheckerContext &Ctx) const { - if (!SymReaper.hasDeadSymbols()) - return; - ProgramStateRef State = Ctx.getState(); const auto &Requests = State->get<RequestMap>(); if (Requests.isEmpty()) diff --git a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index b8ef6701c0df..06e27fc5718d 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -12,10 +12,11 @@ // to be freed using a call to SecKeychainItemFreeContent. //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -29,6 +30,7 @@ namespace { class MacOSKeychainAPIChecker : public Checker<check::PreStmt<CallExpr>, check::PostStmt<CallExpr>, check::DeadSymbols, + check::PointerEscape, eval::Assume> { mutable std::unique_ptr<BugType> BT; @@ -58,6 +60,10 @@ public: void checkPreStmt(const CallExpr *S, CheckerContext &C) const; void checkPostStmt(const CallExpr *S, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; void printState(raw_ostream &Out, ProgramStateRef State, @@ -135,7 +141,6 @@ private: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; }; @@ -571,14 +576,52 @@ void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, C.addTransition(State, N); } +ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind) const { + // FIXME: This branch doesn't make any sense at all, but it is an overfitted + // replacement for a previous overfitted code that was making even less sense. + if (!Call || Call->getDecl()) + return State; + + for (auto I : State->get<AllocatedData>()) { + SymbolRef Sym = I.first; + if (Escaped.count(Sym)) + State = State->remove<AllocatedData>(Sym); + + // This checker is special. Most checkers in fact only track symbols of + // SymbolConjured type, eg. symbols returned from functions such as + // malloc(). This checker tracks symbols returned as out-parameters. + // + // When a function is evaluated conservatively, the out-parameter's pointee + // base region gets invalidated with a SymbolConjured. If the base region is + // larger than the region we're interested in, the value we're interested in + // would be SymbolDerived based on that SymbolConjured. However, such + // SymbolDerived will never be listed in the Escaped set when the base + // region is invalidated because ExprEngine doesn't know which symbols + // were derived from a given symbol, while there can be infinitely many + // valid symbols derived from any given symbol. + // + // Hence the extra boilerplate: remove the derived symbol when its parent + // symbol escapes. + // + if (const auto *SD = dyn_cast<SymbolDerived>(Sym)) { + SymbolRef ParentSym = SD->getParentSymbol(); + if (Escaped.count(ParentSym)) + State = State->remove<AllocatedData>(Sym); + } + } + return State; +} + std::shared_ptr<PathDiagnosticPiece> MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( - const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, - BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { const AllocationState *AS = N->getState()->get<AllocatedData>(Sym); if (!AS) return nullptr; - const AllocationState *ASPrev = PrevN->getState()->get<AllocatedData>(Sym); + const AllocationState *ASPrev = + N->getFirstPred()->getState()->get<AllocatedData>(Sym); if (ASPrev) return nullptr; diff --git a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 437378e53daa..f5976d7da4c1 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -83,7 +83,7 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, // that dispatch_once is a macro that wraps a call to _dispatch_once. // _dispatch_once is then a function which then calls the real dispatch_once. // Users do not care; they just want the warning at the top-level call. - if (CE->getLocStart().isMacroID()) { + if (CE->getBeginLoc().isMacroID()) { StringRef TrimmedFName = FName.ltrim('_'); if (TrimmedFName != FName) FName = TrimmedFName; diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index ebaf79a780c0..ae1b1fc837be 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "InterCheckerAPI.h" #include "clang/AST/Attr.h" #include "clang/AST/ParentMap.h" @@ -161,6 +161,7 @@ class MallocChecker : public Checker<check::DeadSymbols, check::PointerEscape, check::ConstPointerEscape, check::PreStmt<ReturnStmt>, + check::EndFunction, check::PreCall, check::PostStmt<CallExpr>, check::PostStmt<CXXNewExpr>, @@ -193,6 +194,7 @@ public: CK_NewDeleteChecker, CK_NewDeleteLeaksChecker, CK_MismatchedDeallocatorChecker, + CK_InnerPointerChecker, CK_NumCheckKinds }; @@ -217,6 +219,7 @@ public: void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *S, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; void checkLocation(SVal l, bool isLoad, const Stmt *S, @@ -353,7 +356,7 @@ private: static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE, ProgramStateRef State); - ///Check if the memory associated with this symbol was released. + /// Check if the memory associated with this symbol was released. bool isReleased(SymbolRef Sym, CheckerContext &C) const; bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; @@ -377,13 +380,16 @@ private: ProgramStateRef State, SymbolRef &EscapingSymbol) const; - // Implementation of the checkPointerEscape callabcks. + // Implementation of the checkPointerEscape callbacks. ProgramStateRef checkPointerEscapeAux(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind, bool(*CheckRefState)(const RefState*)) const; + // Implementation of the checkPreStmt and checkEndFunction callbacks. + void checkEscapeOnReturn(const ReturnStmt *S, CheckerContext &C) const; + ///@{ /// Tells if a given family/call/symbol is tracked by the current checker. /// Sets CheckKind to the kind of the checker responsible for this @@ -511,7 +517,6 @@ private: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; @@ -707,10 +712,8 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, return false; } -// Tells if the callee is one of the following: -// 1) A global non-placement new/delete operator function. -// 2) A global placement operator function with the single placement argument -// of type std::nothrow_t. +// Tells if the callee is one of the builtin new/delete operators, including +// placement operators and other standard overloads. bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const { if (!FD) @@ -721,23 +724,11 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, Kind != OO_Delete && Kind != OO_Array_Delete) return false; - // Skip all operator new/delete methods. - if (isa<CXXMethodDecl>(FD)) - return false; - - // Return true if tested operator is a standard placement nothrow operator. - if (FD->getNumParams() == 2) { - QualType T = FD->getParamDecl(1)->getType(); - if (const IdentifierInfo *II = T.getBaseTypeIdentifier()) - return II->getName().equals("nothrow_t"); - } - - // Skip placement operators. - if (FD->getNumParams() != 1 || FD->isVariadic()) - return false; - - // One of the standard new/new[]/delete/delete[] non-placement operators. - return true; + // This is standard if and only if it's not defined in a user file. + SourceLocation L = FD->getLocation(); + // If the header for operator delete is not included, it's still defined + // in an invalid source location. Check to make sure we don't crash. + return !L.isValid() || C.getSourceManager().isInSystemHeader(L); } llvm::Optional<ProgramStateRef> MallocChecker::performKernelMalloc( @@ -1082,12 +1073,6 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { void MallocChecker::processNewAllocation(const CXXNewExpr *NE, CheckerContext &C, SVal Target) const { - if (NE->getNumPlacementArgs()) - for (CXXNewExpr::const_arg_iterator I = NE->placement_arg_begin(), - E = NE->placement_arg_end(); I != E; ++I) - if (SymbolRef Sym = C.getSVal(*I).getAsSymbol()) - checkUseAfterFree(Sym, C, *I); - if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext())) return; @@ -1098,7 +1083,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE, ProgramStateRef State = C.getState(); // The return value from operator new is bound to a specified initialization // value (if any) and we don't want to loose this value. So we call - // MallocUpdateRefState() instead of MallocMemAux() which breakes the + // MallocUpdateRefState() instead of MallocMemAux() which breaks the // existing binding. State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray : AF_CXXNew, Target); @@ -1109,7 +1094,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE, void MallocChecker::checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const { - if (!C.getAnalysisManager().getAnalyzerOptions().mayInlineCXXAllocator()) + if (!C.getAnalysisManager().getAnalyzerOptions().MayInlineCXXAllocator) processNewAllocation(NE, C, C.getSVal(NE)); } @@ -1657,13 +1642,10 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, case AF_IfNameIndex: { if (ChecksEnabled[CK_MallocChecker]) return CK_MallocChecker; - - return Optional<MallocChecker::CheckKind>(); + return None; } case AF_CXXNew: - case AF_CXXNewArray: - // FIXME: Add new CheckKind for AF_InnerBuffer. - case AF_InnerBuffer: { + case AF_CXXNewArray: { if (IsALeakCheck) { if (ChecksEnabled[CK_NewDeleteLeaksChecker]) return CK_NewDeleteLeaksChecker; @@ -1672,7 +1654,12 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, if (ChecksEnabled[CK_NewDeleteChecker]) return CK_NewDeleteChecker; } - return Optional<MallocChecker::CheckKind>(); + return None; + } + case AF_InnerBuffer: { + if (ChecksEnabled[CK_InnerPointerChecker]) + return CK_InnerPointerChecker; + return None; } case AF_None: { llvm_unreachable("no family"); @@ -1975,7 +1962,8 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const { if (!ChecksEnabled[CK_MallocChecker] && - !ChecksEnabled[CK_NewDeleteChecker]) + !ChecksEnabled[CK_NewDeleteChecker] && + !ChecksEnabled[CK_InnerPointerChecker]) return; Optional<MallocChecker::CheckKind> CheckKind = getCheckIfTracked(C, Sym); @@ -1987,15 +1975,20 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, BT_UseFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Use-after-free", categories::MemoryError)); + AllocationFamily AF = + C.getState()->get<RegionState>(Sym)->getAllocationFamily(); + auto R = llvm::make_unique<BugReport>(*BT_UseFree[*CheckKind], - "Use of memory after it is freed", N); + AF == AF_InnerBuffer + ? "Inner pointer of container used after re/deallocation" + : "Use of memory after it is freed", + N); R->markInteresting(Sym); R->addRange(Range); R->addVisitor(llvm::make_unique<MallocBugVisitor>(Sym)); - const RefState *RS = C.getState()->get<RegionState>(Sym); - if (RS->getAllocationFamily() == AF_InnerBuffer) + if (AF == AF_InnerBuffer) R->addVisitor(allocation_state::getInnerPointerBRVisitor(Sym)); C.emitReport(std::move(R)); @@ -2352,13 +2345,11 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { - if (!SymReaper.hasDeadSymbols()) - return; - ProgramStateRef state = C.getState(); - RegionStateTy RS = state->get<RegionState>(); + RegionStateTy OldRS = state->get<RegionState>(); RegionStateTy::Factory &F = state->get_context<RegionState>(); + RegionStateTy RS = OldRS; SmallVector<SymbolRef, 2> Errors; for (RegionStateTy::iterator I = RS.begin(), E = RS.end(); I != E; ++I) { if (SymReaper.isDead(I->first)) { @@ -2366,10 +2357,18 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, Errors.push_back(I->first); // Remove the dead symbol from the map. RS = F.remove(RS, I->first); - } } + if (RS == OldRS) { + // We shouldn't have touched other maps yet. + assert(state->get<ReallocPairs>() == + C.getState()->get<ReallocPairs>()); + assert(state->get<FreeReturnValue>() == + C.getState()->get<FreeReturnValue>()); + return; + } + // Cleanup the Realloc Pairs Map. ReallocPairsTy RP = state->get<ReallocPairs>(); for (ReallocPairsTy::iterator I = RP.begin(), E = RP.end(); I != E; ++I) { @@ -2425,10 +2424,6 @@ void MallocChecker::checkPreCall(const CallEvent &Call, isCMemFunction(FD, Ctx, AF_IfNameIndex, MemoryOperationKind::MOK_Free))) return; - - if (ChecksEnabled[CK_NewDeleteChecker] && - isStandardNewDelete(FD, Ctx)) - return; } // Check if the callee of a method is deleted. @@ -2451,7 +2446,24 @@ void MallocChecker::checkPreCall(const CallEvent &Call, } } -void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { +void MallocChecker::checkPreStmt(const ReturnStmt *S, + CheckerContext &C) const { + checkEscapeOnReturn(S, C); +} + +// In the CFG, automatic destructors come after the return statement. +// This callback checks for returning memory that is freed by automatic +// destructors, as those cannot be reached in checkPreStmt(). +void MallocChecker::checkEndFunction(const ReturnStmt *S, + CheckerContext &C) const { + checkEscapeOnReturn(S, C); +} + +void MallocChecker::checkEscapeOnReturn(const ReturnStmt *S, + CheckerContext &C) const { + if (!S) + return; + const Expr *E = S->getRetValue(); if (!E) return; @@ -2509,8 +2521,7 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE, } state = - state->scanReachableSymbols<StopTrackingCallback>(Regions.data(), - Regions.data() + Regions.size()).getState(); + state->scanReachableSymbols<StopTrackingCallback>(Regions).getState(); C.addTransition(state); } @@ -2858,11 +2869,10 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { } std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode( - const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, - BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef state = N->getState(); - ProgramStateRef statePrev = PrevN->getState(); + ProgramStateRef statePrev = N->getFirstPred()->getState(); const RefState *RS = state->get<RegionState>(Sym); const RefState *RSPrev = statePrev->get<RegionState>(Sym); @@ -2918,13 +2928,22 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode( case AF_CXXNewArray: case AF_IfNameIndex: Msg = "Memory is released"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returning; memory was released"); break; case AF_InnerBuffer: { - OS << "Inner pointer invalidated by call to "; + const MemRegion *ObjRegion = + allocation_state::getContainerObjRegion(statePrev, Sym); + const auto *TypedRegion = cast<TypedValueRegion>(ObjRegion); + QualType ObjTy = TypedRegion->getValueType(); + OS << "Inner buffer of '" << ObjTy.getAsString() << "' "; + if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) { - OS << "destructor"; + OS << "deallocated by call to destructor"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returning; inner buffer was deallocated"); } else { - OS << "'"; + OS << "reallocated by call to '"; const Stmt *S = RS->getStmt(); if (const auto *MemCallE = dyn_cast<CXXMemberCallExpr>(S)) { OS << MemCallE->getMethodDecl()->getNameAsString(); @@ -2937,6 +2956,8 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode( OS << (D ? D->getNameAsString() : "unknown"); } OS << "'"; + StackHint = new StackHintGeneratorForSymbol(Sym, + "Returning; inner buffer was reallocated"); } Msg = OS.str(); break; @@ -2944,8 +2965,6 @@ std::shared_ptr<PathDiagnosticPiece> MallocChecker::MallocBugVisitor::VisitNode( case AF_None: llvm_unreachable("Unhandled allocation family!"); } - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; memory was released"); // See if we're releasing memory while inlining a destructor // (or one of its callees). This turns on various common @@ -3071,7 +3090,7 @@ markReleased(ProgramStateRef State, SymbolRef Sym, const Expr *Origin) { void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) { registerCStringCheckerBasic(mgr); MallocChecker *checker = mgr.registerChecker<MallocChecker>(); - checker->IsOptimistic = mgr.getAnalyzerOptions().getBooleanOption( + checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption( "Optimistic", false, checker); checker->ChecksEnabled[MallocChecker::CK_NewDeleteLeaksChecker] = true; checker->CheckNames[MallocChecker::CK_NewDeleteLeaksChecker] = @@ -3087,11 +3106,23 @@ void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) { } } +// Intended to be used in InnerPointerChecker to register the part of +// MallocChecker connected to it. +void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) { + registerCStringCheckerBasic(mgr); + MallocChecker *checker = mgr.registerChecker<MallocChecker>(); + checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption( + "Optimistic", false, checker); + checker->ChecksEnabled[MallocChecker::CK_InnerPointerChecker] = true; + checker->CheckNames[MallocChecker::CK_InnerPointerChecker] = + mgr.getCurrentCheckName(); +} + #define REGISTER_CHECKER(name) \ void ento::register##name(CheckerManager &mgr) { \ registerCStringCheckerBasic(mgr); \ MallocChecker *checker = mgr.registerChecker<MallocChecker>(); \ - checker->IsOptimistic = mgr.getAnalyzerOptions().getBooleanOption( \ + checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption( \ "Optimistic", false, checker); \ checker->ChecksEnabled[MallocChecker::CK_##name] = true; \ checker->CheckNames[MallocChecker::CK_##name] = mgr.getCurrentCheckName(); \ diff --git a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp index fc2ab1d6e3f7..d02ed48bceaa 100644 --- a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp @@ -18,7 +18,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/EvaluatedExprVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -135,9 +135,9 @@ private: bool isIntZeroExpr(const Expr *E) const { if (!E->getType()->isIntegralOrEnumerationType()) return false; - llvm::APSInt Result; + Expr::EvalResult Result; if (E->EvaluateAsInt(Result, Context)) - return Result == 0; + return Result.Val.getInt() == 0; return false; } @@ -191,8 +191,11 @@ private: if (const BinaryOperator *BOp = dyn_cast<BinaryOperator>(rhse)) { if (BOp->getOpcode() == BO_Div) { const Expr *denom = BOp->getRHS()->IgnoreParenImpCasts(); - if (denom->EvaluateAsInt(denomVal, Context)) + Expr::EvalResult Result; + if (denom->EvaluateAsInt(Result, Context)) { + denomVal = Result.Val.getInt(); denomKnown = true; + } const Expr *numerator = BOp->getLHS()->IgnoreParenImpCasts(); if (numerator->isEvaluatable(Context)) numeratorKnown = true; diff --git a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp index 80a3fbe1a409..bb245d82bc2b 100644 --- a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/AST/TypeLoc.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" diff --git a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp b/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp deleted file mode 100644 index 19c1d077afa1..000000000000 --- a/lib/StaticAnalyzer/Checkers/MisusedMovedObjectChecker.cpp +++ /dev/null @@ -1,525 +0,0 @@ -// MisusedMovedObjectChecker.cpp - Check use of moved-from objects. - C++ -===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// This defines checker which checks for potential misuses of a moved-from -// object. That means method calls on the object or copying it in moved-from -// state. -// -//===----------------------------------------------------------------------===// - -#include "ClangSACheckers.h" -#include "clang/AST/ExprCXX.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" - -using namespace clang; -using namespace ento; - -namespace { - -struct RegionState { -private: - enum Kind { Moved, Reported } K; - RegionState(Kind InK) : K(InK) {} - -public: - bool isReported() const { return K == Reported; } - bool isMoved() const { return K == Moved; } - - static RegionState getReported() { return RegionState(Reported); } - static RegionState getMoved() { return RegionState(Moved); } - - bool operator==(const RegionState &X) const { return K == X.K; } - void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } -}; - -class MisusedMovedObjectChecker - : public Checker<check::PreCall, check::PostCall, check::EndFunction, - check::DeadSymbols, check::RegionChanges> { -public: - void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; - void checkPreCall(const CallEvent &MC, CheckerContext &C) const; - void checkPostCall(const CallEvent &MC, CheckerContext &C) const; - void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; - ProgramStateRef - checkRegionChanges(ProgramStateRef State, - const InvalidatedSymbols *Invalidated, - ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions, - const LocationContext *LCtx, const CallEvent *Call) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; - -private: - enum MisuseKind {MK_FunCall, MK_Copy, MK_Move}; - class MovedBugVisitor : public BugReporterVisitor { - public: - MovedBugVisitor(const MemRegion *R) : Region(R), Found(false) {} - - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - ID.AddPointer(Region); - } - - std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) override; - - private: - // The tracked region. - const MemRegion *Region; - bool Found; - }; - - mutable std::unique_ptr<BugType> BT; - ExplodedNode *reportBug(const MemRegion *Region, const CallEvent &Call, - CheckerContext &C, MisuseKind MK) const; - bool isInMoveSafeContext(const LocationContext *LC) const; - bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; - bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const; - const ExplodedNode *getMoveLocation(const ExplodedNode *N, - const MemRegion *Region, - CheckerContext &C) const; -}; -} // end anonymous namespace - -REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState) - -// If a region is removed all of the subregions needs to be removed too. -static ProgramStateRef removeFromState(ProgramStateRef State, - const MemRegion *Region) { - if (!Region) - return State; - for (auto &E : State->get<TrackedRegionMap>()) { - if (E.first->isSubRegionOf(Region)) - State = State->remove<TrackedRegionMap>(E.first); - } - return State; -} - -static bool isAnyBaseRegionReported(ProgramStateRef State, - const MemRegion *Region) { - for (auto &E : State->get<TrackedRegionMap>()) { - if (Region->isSubRegionOf(E.first) && E.second.isReported()) - return true; - } - return false; -} - -std::shared_ptr<PathDiagnosticPiece> -MisusedMovedObjectChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) { - // We need only the last move of the reported object's region. - // The visitor walks the ExplodedGraph backwards. - if (Found) - return nullptr; - ProgramStateRef State = N->getState(); - ProgramStateRef StatePrev = PrevN->getState(); - const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region); - const RegionState *TrackedObjectPrev = - StatePrev->get<TrackedRegionMap>(Region); - if (!TrackedObject) - return nullptr; - if (TrackedObjectPrev && TrackedObject) - return nullptr; - - // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); - if (!S) - return nullptr; - Found = true; - - std::string ObjectName; - if (const auto DecReg = Region->getAs<DeclRegion>()) { - const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl()); - ObjectName = RegionDecl->getNameAsString(); - } - std::string InfoText; - if (ObjectName != "") - InfoText = "'" + ObjectName + "' became 'moved-from' here"; - else - InfoText = "Became 'moved-from' here"; - - // Generate the extra diagnostic. - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared<PathDiagnosticEventPiece>(Pos, InfoText, true); -} - -const ExplodedNode *MisusedMovedObjectChecker::getMoveLocation( - const ExplodedNode *N, const MemRegion *Region, CheckerContext &C) const { - // Walk the ExplodedGraph backwards and find the first node that referred to - // the tracked region. - const ExplodedNode *MoveNode = N; - - while (N) { - ProgramStateRef State = N->getState(); - if (!State->get<TrackedRegionMap>(Region)) - break; - MoveNode = N; - N = N->pred_empty() ? nullptr : *(N->pred_begin()); - } - return MoveNode; -} - -ExplodedNode *MisusedMovedObjectChecker::reportBug(const MemRegion *Region, - const CallEvent &Call, - CheckerContext &C, - MisuseKind MK) const { - if (ExplodedNode *N = C.generateNonFatalErrorNode()) { - if (!BT) - BT.reset(new BugType(this, "Usage of a 'moved-from' object", - "C++ move semantics")); - - // Uniqueing report to the same object. - PathDiagnosticLocation LocUsedForUniqueing; - const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); - - if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) - LocUsedForUniqueing = PathDiagnosticLocation::createBegin( - MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); - - // Creating the error message. - std::string ErrorMessage; - switch(MK) { - case MK_FunCall: - ErrorMessage = "Method call on a 'moved-from' object"; - break; - case MK_Copy: - ErrorMessage = "Copying a 'moved-from' object"; - break; - case MK_Move: - ErrorMessage = "Moving a 'moved-from' object"; - break; - } - if (const auto DecReg = Region->getAs<DeclRegion>()) { - const auto *RegionDecl = dyn_cast<NamedDecl>(DecReg->getDecl()); - ErrorMessage += " '" + RegionDecl->getNameAsString() + "'"; - } - - auto R = - llvm::make_unique<BugReport>(*BT, ErrorMessage, N, LocUsedForUniqueing, - MoveNode->getLocationContext()->getDecl()); - R->addVisitor(llvm::make_unique<MovedBugVisitor>(Region)); - C.emitReport(std::move(R)); - return N; - } - return nullptr; -} - -// Removing the function parameters' MemRegion from the state. This is needed -// for PODs where the trivial destructor does not even created nor executed. -void MisusedMovedObjectChecker::checkEndFunction(const ReturnStmt *RS, - CheckerContext &C) const { - auto State = C.getState(); - TrackedRegionMapTy Objects = State->get<TrackedRegionMap>(); - if (Objects.isEmpty()) - return; - - auto LC = C.getLocationContext(); - - const auto LD = dyn_cast_or_null<FunctionDecl>(LC->getDecl()); - if (!LD) - return; - llvm::SmallSet<const MemRegion *, 8> InvalidRegions; - - for (auto Param : LD->parameters()) { - auto Type = Param->getType().getTypePtrOrNull(); - if (!Type) - continue; - if (!Type->isPointerType() && !Type->isReferenceType()) { - InvalidRegions.insert(State->getLValue(Param, LC).getAsRegion()); - } - } - - if (InvalidRegions.empty()) - return; - - for (const auto &E : State->get<TrackedRegionMap>()) { - if (InvalidRegions.count(E.first->getBaseRegion())) - State = State->remove<TrackedRegionMap>(E.first); - } - - C.addTransition(State); -} - -void MisusedMovedObjectChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - const auto *AFC = dyn_cast<AnyFunctionCall>(&Call); - if (!AFC) - return; - - ProgramStateRef State = C.getState(); - const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl()); - if (!MethodDecl) - return; - - const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl); - - const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call); - // Check if an object became moved-from. - // Object can become moved from after a call to move assignment operator or - // move constructor . - if (ConstructorDecl && !ConstructorDecl->isMoveConstructor()) - return; - - if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator()) - return; - - const auto ArgRegion = AFC->getArgSVal(0).getAsRegion(); - if (!ArgRegion) - return; - - // Skip moving the object to itself. - if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion) - return; - if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC)) - if (IC->getCXXThisVal().getAsRegion() == ArgRegion) - return; - - const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); - // Skip temp objects because of their short lifetime. - if (BaseRegion->getAs<CXXTempObjectRegion>() || - AFC->getArgExpr(0)->isRValue()) - return; - // If it has already been reported do not need to modify the state. - - if (State->get<TrackedRegionMap>(ArgRegion)) - return; - // Mark object as moved-from. - State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved()); - C.addTransition(State); -} - -bool MisusedMovedObjectChecker::isMoveSafeMethod( - const CXXMethodDecl *MethodDec) const { - // We abandon the cases where bool/void/void* conversion happens. - if (const auto *ConversionDec = - dyn_cast_or_null<CXXConversionDecl>(MethodDec)) { - const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull(); - if (!Tp) - return false; - if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType()) - return true; - } - // Function call `empty` can be skipped. - if (MethodDec && MethodDec->getDeclName().isIdentifier() && - (MethodDec->getName().lower() == "empty" || - MethodDec->getName().lower() == "isempty")) - return true; - - return false; -} - -bool MisusedMovedObjectChecker::isStateResetMethod( - const CXXMethodDecl *MethodDec) const { - if (MethodDec && MethodDec->getDeclName().isIdentifier()) { - std::string MethodName = MethodDec->getName().lower(); - if (MethodName == "reset" || MethodName == "clear" || - MethodName == "destroy") - return true; - } - return false; -} - -// Don't report an error inside a move related operation. -// We assume that the programmer knows what she does. -bool MisusedMovedObjectChecker::isInMoveSafeContext( - const LocationContext *LC) const { - do { - const auto *CtxDec = LC->getDecl(); - auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec); - auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec); - auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec); - if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || - (MethodDec && MethodDec->isOverloadedOperator() && - MethodDec->getOverloadedOperator() == OO_Equal) || - isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) - return true; - } while ((LC = LC->getParent())); - return false; -} - -void MisusedMovedObjectChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - const LocationContext *LC = C.getLocationContext(); - ExplodedNode *N = nullptr; - - // Remove the MemRegions from the map on which a ctor/dtor call or assignment - // happened. - - // Checking constructor calls. - if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { - State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); - auto CtorDec = CC->getDecl(); - // Check for copying a moved-from object and report the bug. - if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { - const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); - const RegionState *ArgState = State->get<TrackedRegionMap>(ArgRegion); - if (ArgState && ArgState->isMoved()) { - if (!isInMoveSafeContext(LC)) { - if(CtorDec->isMoveConstructor()) - N = reportBug(ArgRegion, Call, C, MK_Move); - else - N = reportBug(ArgRegion, Call, C, MK_Copy); - State = State->set<TrackedRegionMap>(ArgRegion, - RegionState::getReported()); - } - } - } - C.addTransition(State, N); - return; - } - - const auto IC = dyn_cast<CXXInstanceCall>(&Call); - if (!IC) - return; - // In case of destructor call we do not track the object anymore. - const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); - if (!ThisRegion) - return; - - if (dyn_cast_or_null<CXXDestructorDecl>(Call.getDecl())) { - State = removeFromState(State, ThisRegion); - C.addTransition(State); - return; - } - - const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl()); - if (!MethodDecl) - return; - // Checking assignment operators. - bool OperatorEq = MethodDecl->isOverloadedOperator() && - MethodDecl->getOverloadedOperator() == OO_Equal; - // Remove the tracked object for every assignment operator, but report bug - // only for move or copy assignment's argument. - if (OperatorEq) { - State = removeFromState(State, ThisRegion); - if (MethodDecl->isCopyAssignmentOperator() || - MethodDecl->isMoveAssignmentOperator()) { - const RegionState *ArgState = - State->get<TrackedRegionMap>(IC->getArgSVal(0).getAsRegion()); - if (ArgState && ArgState->isMoved() && !isInMoveSafeContext(LC)) { - const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); - if(MethodDecl->isMoveAssignmentOperator()) - N = reportBug(ArgRegion, Call, C, MK_Move); - else - N = reportBug(ArgRegion, Call, C, MK_Copy); - State = - State->set<TrackedRegionMap>(ArgRegion, RegionState::getReported()); - } - } - C.addTransition(State, N); - return; - } - - // The remaining part is check only for method call on a moved-from object. - - // We want to investigate the whole object, not only sub-object of a parent - // class in which the encountered method defined. - while (const CXXBaseObjectRegion *BR = - dyn_cast<CXXBaseObjectRegion>(ThisRegion)) - ThisRegion = BR->getSuperRegion(); - - if (isMoveSafeMethod(MethodDecl)) - return; - - if (isStateResetMethod(MethodDecl)) { - State = removeFromState(State, ThisRegion); - C.addTransition(State); - return; - } - - // If it is already reported then we don't report the bug again. - const RegionState *ThisState = State->get<TrackedRegionMap>(ThisRegion); - if (!(ThisState && ThisState->isMoved())) - return; - - // Don't report it in case if any base region is already reported - if (isAnyBaseRegionReported(State, ThisRegion)) - return; - - if (isInMoveSafeContext(LC)) - return; - - N = reportBug(ThisRegion, Call, C, MK_FunCall); - State = State->set<TrackedRegionMap>(ThisRegion, RegionState::getReported()); - C.addTransition(State, N); -} - -void MisusedMovedObjectChecker::checkDeadSymbols(SymbolReaper &SymReaper, - CheckerContext &C) const { - ProgramStateRef State = C.getState(); - TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); - for (TrackedRegionMapTy::value_type E : TrackedRegions) { - const MemRegion *Region = E.first; - bool IsRegDead = !SymReaper.isLiveRegion(Region); - - // Remove the dead regions from the region map. - if (IsRegDead) { - State = State->remove<TrackedRegionMap>(Region); - } - } - C.addTransition(State); -} - -ProgramStateRef MisusedMovedObjectChecker::checkRegionChanges( - ProgramStateRef State, const InvalidatedSymbols *Invalidated, - ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, - const CallEvent *Call) const { - // In case of an InstanceCall don't remove the ThisRegion from the GDM since - // it is handled in checkPreCall and checkPostCall. - const MemRegion *ThisRegion = nullptr; - if (const auto *IC = dyn_cast_or_null<CXXInstanceCall>(Call)) { - ThisRegion = IC->getCXXThisVal().getAsRegion(); - } - - for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), - E = ExplicitRegions.end(); - I != E; ++I) { - const auto *Region = *I; - if (ThisRegion != Region) { - State = removeFromState(State, Region); - } - } - - return State; -} - -void MisusedMovedObjectChecker::printState(raw_ostream &Out, - ProgramStateRef State, - const char *NL, - const char *Sep) const { - - TrackedRegionMapTy RS = State->get<TrackedRegionMap>(); - - if (!RS.isEmpty()) { - Out << Sep << "Moved-from objects :" << NL; - for (auto I: RS) { - I.first->dumpToStream(Out); - if (I.second.isMoved()) - Out << ": moved"; - else - Out << ": moved and reported"; - Out << NL; - } - } -} -void ento::registerMisusedMovedObjectChecker(CheckerManager &mgr) { - mgr.registerChecker<MisusedMovedObjectChecker>(); -} diff --git a/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp index 5060b0e0a6e0..e3b24f20b0f0 100644 --- a/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -82,7 +82,9 @@ void ento::registerMmapWriteExecChecker(CheckerManager &mgr) { MmapWriteExecChecker *Mwec = mgr.registerChecker<MmapWriteExecChecker>(); Mwec->ProtExecOv = - mgr.getAnalyzerOptions().getOptionAsInteger("MmapProtExec", 0x04, Mwec); + mgr.getAnalyzerOptions() + .getCheckerIntegerOption("MmapProtExec", 0x04, Mwec); Mwec->ProtReadOv = - mgr.getAnalyzerOptions().getOptionAsInteger("MmapProtRead", 0x01, Mwec); + mgr.getAnalyzerOptions() + .getCheckerIntegerOption("MmapProtRead", 0x01, Mwec); } diff --git a/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/lib/StaticAnalyzer/Checkers/MoveChecker.cpp new file mode 100644 index 000000000000..6efa2dfbe5b4 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -0,0 +1,740 @@ +// MoveChecker.cpp - Check use of moved-from objects. - C++ ---------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This defines checker which checks for potential misuses of a moved-from +// object. That means method calls on the object or copying it in moved-from +// state. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/ExprCXX.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/StringSet.h" + +using namespace clang; +using namespace ento; + +namespace { +struct RegionState { +private: + enum Kind { Moved, Reported } K; + RegionState(Kind InK) : K(InK) {} + +public: + bool isReported() const { return K == Reported; } + bool isMoved() const { return K == Moved; } + + static RegionState getReported() { return RegionState(Reported); } + static RegionState getMoved() { return RegionState(Moved); } + + bool operator==(const RegionState &X) const { return K == X.K; } + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } +}; +} // end of anonymous namespace + +namespace { +class MoveChecker + : public Checker<check::PreCall, check::PostCall, + check::DeadSymbols, check::RegionChanges> { +public: + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; + void checkPreCall(const CallEvent &MC, CheckerContext &C) const; + void checkPostCall(const CallEvent &MC, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + ProgramStateRef + checkRegionChanges(ProgramStateRef State, + const InvalidatedSymbols *Invalidated, + ArrayRef<const MemRegion *> RequestedRegions, + ArrayRef<const MemRegion *> InvalidatedRegions, + const LocationContext *LCtx, const CallEvent *Call) const; + void printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const override; + +private: + enum MisuseKind { MK_FunCall, MK_Copy, MK_Move, MK_Dereference }; + enum StdObjectKind { SK_NonStd, SK_Unsafe, SK_Safe, SK_SmartPtr }; + + enum AggressivenessKind { // In any case, don't warn after a reset. + AK_Invalid = -1, + AK_KnownsOnly = 0, // Warn only about known move-unsafe classes. + AK_KnownsAndLocals = 1, // Also warn about all local objects. + AK_All = 2, // Warn on any use-after-move. + AK_NumKinds = AK_All + }; + + static bool misuseCausesCrash(MisuseKind MK) { + return MK == MK_Dereference; + } + + struct ObjectKind { + // Is this a local variable or a local rvalue reference? + bool IsLocal; + // Is this an STL object? If so, of what kind? + StdObjectKind StdKind; + }; + + // STL smart pointers are automatically re-initialized to null when moved + // from. So we can't warn on many methods, but we can warn when it is + // dereferenced, which is UB even if the resulting lvalue never gets read. + const llvm::StringSet<> StdSmartPtrClasses = { + "shared_ptr", + "unique_ptr", + "weak_ptr", + }; + + // Not all of these are entirely move-safe, but they do provide *some* + // guarantees, and it means that somebody is using them after move + // in a valid manner. + // TODO: We can still try to identify *unsafe* use after move, + // like we did with smart pointers. + const llvm::StringSet<> StdSafeClasses = { + "basic_filebuf", + "basic_ios", + "future", + "optional", + "packaged_task" + "promise", + "shared_future", + "shared_lock", + "thread", + "unique_lock", + }; + + // Should we bother tracking the state of the object? + bool shouldBeTracked(ObjectKind OK) const { + // In non-aggressive mode, only warn on use-after-move of local variables + // (or local rvalue references) and of STL objects. The former is possible + // because local variables (or local rvalue references) are not tempting + // their user to re-use the storage. The latter is possible because STL + // objects are known to end up in a valid but unspecified state after the + // move and their state-reset methods are also known, which allows us to + // predict precisely when use-after-move is invalid. + // Some STL objects are known to conform to additional contracts after move, + // so they are not tracked. However, smart pointers specifically are tracked + // because we can perform extra checking over them. + // In aggressive mode, warn on any use-after-move because the user has + // intentionally asked us to completely eliminate use-after-move + // in his code. + return (Aggressiveness == AK_All) || + (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) || + OK.StdKind == SK_Unsafe || OK.StdKind == SK_SmartPtr; + } + + // Some objects only suffer from some kinds of misuses, but we need to track + // them anyway because we cannot know in advance what misuse will we find. + bool shouldWarnAbout(ObjectKind OK, MisuseKind MK) const { + // Additionally, only warn on smart pointers when they are dereferenced (or + // local or we are aggressive). + return shouldBeTracked(OK) && + ((Aggressiveness == AK_All) || + (Aggressiveness >= AK_KnownsAndLocals && OK.IsLocal) || + OK.StdKind != SK_SmartPtr || MK == MK_Dereference); + } + + // Obtains ObjectKind of an object. Because class declaration cannot always + // be easily obtained from the memory region, it is supplied separately. + ObjectKind classifyObject(const MemRegion *MR, const CXXRecordDecl *RD) const; + + // Classifies the object and dumps a user-friendly description string to + // the stream. + void explainObject(llvm::raw_ostream &OS, const MemRegion *MR, + const CXXRecordDecl *RD, MisuseKind MK) const; + + bool belongsTo(const CXXRecordDecl *RD, const llvm::StringSet<> &Set) const; + + class MovedBugVisitor : public BugReporterVisitor { + public: + MovedBugVisitor(const MoveChecker &Chk, const MemRegion *R, + const CXXRecordDecl *RD, MisuseKind MK) + : Chk(Chk), Region(R), RD(RD), MK(MK), Found(false) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Region); + // Don't add RD because it's, in theory, uniquely determined by + // the region. In practice though, it's not always possible to obtain + // the declaration directly from the region, that's why we store it + // in the first place. + } + + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + const MoveChecker &Chk; + // The tracked region. + const MemRegion *Region; + // The class of the tracked object. + const CXXRecordDecl *RD; + // How exactly the object was misused. + const MisuseKind MK; + bool Found; + }; + + AggressivenessKind Aggressiveness; + +public: + void setAggressiveness(StringRef Str) { + Aggressiveness = + llvm::StringSwitch<AggressivenessKind>(Str) + .Case("KnownsOnly", AK_KnownsOnly) + .Case("KnownsAndLocals", AK_KnownsAndLocals) + .Case("All", AK_All) + .Default(AK_KnownsAndLocals); // A sane default. + }; + +private: + mutable std::unique_ptr<BugType> BT; + + // Check if the given form of potential misuse of a given object + // should be reported. If so, get it reported. The callback from which + // this function was called should immediately return after the call + // because this function adds one or two transitions. + void modelUse(ProgramStateRef State, const MemRegion *Region, + const CXXRecordDecl *RD, MisuseKind MK, + CheckerContext &C) const; + + // Returns the exploded node against which the report was emitted. + // The caller *must* add any further transitions against this node. + ExplodedNode *reportBug(const MemRegion *Region, const CXXRecordDecl *RD, + CheckerContext &C, MisuseKind MK) const; + + bool isInMoveSafeContext(const LocationContext *LC) const; + bool isStateResetMethod(const CXXMethodDecl *MethodDec) const; + bool isMoveSafeMethod(const CXXMethodDecl *MethodDec) const; + const ExplodedNode *getMoveLocation(const ExplodedNode *N, + const MemRegion *Region, + CheckerContext &C) const; +}; +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(TrackedRegionMap, const MemRegion *, RegionState) + +// If a region is removed all of the subregions needs to be removed too. +static ProgramStateRef removeFromState(ProgramStateRef State, + const MemRegion *Region) { + if (!Region) + return State; + for (auto &E : State->get<TrackedRegionMap>()) { + if (E.first->isSubRegionOf(Region)) + State = State->remove<TrackedRegionMap>(E.first); + } + return State; +} + +static bool isAnyBaseRegionReported(ProgramStateRef State, + const MemRegion *Region) { + for (auto &E : State->get<TrackedRegionMap>()) { + if (Region->isSubRegionOf(E.first) && E.second.isReported()) + return true; + } + return false; +} + +static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) { + if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(MR)) { + SymbolRef Sym = SR->getSymbol(); + if (Sym->getType()->isRValueReferenceType()) + if (const MemRegion *OriginMR = Sym->getOriginRegion()) + return OriginMR; + } + return MR; +} + +std::shared_ptr<PathDiagnosticPiece> +MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, BugReport &BR) { + // We need only the last move of the reported object's region. + // The visitor walks the ExplodedGraph backwards. + if (Found) + return nullptr; + ProgramStateRef State = N->getState(); + ProgramStateRef StatePrev = N->getFirstPred()->getState(); + const RegionState *TrackedObject = State->get<TrackedRegionMap>(Region); + const RegionState *TrackedObjectPrev = + StatePrev->get<TrackedRegionMap>(Region); + if (!TrackedObject) + return nullptr; + if (TrackedObjectPrev && TrackedObject) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = PathDiagnosticLocation::getStmt(N); + if (!S) + return nullptr; + Found = true; + + SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + + ObjectKind OK = Chk.classifyObject(Region, RD); + switch (OK.StdKind) { + case SK_SmartPtr: + if (MK == MK_Dereference) { + OS << "Smart pointer"; + Chk.explainObject(OS, Region, RD, MK); + OS << " is reset to null when moved from"; + break; + } + + // If it's not a dereference, we don't care if it was reset to null + // or that it is even a smart pointer. + LLVM_FALLTHROUGH; + case SK_NonStd: + case SK_Safe: + OS << "Object"; + Chk.explainObject(OS, Region, RD, MK); + OS << " is moved"; + break; + case SK_Unsafe: + OS << "Object"; + Chk.explainObject(OS, Region, RD, MK); + OS << " is left in a valid but unspecified state after move"; + break; + } + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return std::make_shared<PathDiagnosticEventPiece>(Pos, OS.str(), true); +} + +const ExplodedNode *MoveChecker::getMoveLocation(const ExplodedNode *N, + const MemRegion *Region, + CheckerContext &C) const { + // Walk the ExplodedGraph backwards and find the first node that referred to + // the tracked region. + const ExplodedNode *MoveNode = N; + + while (N) { + ProgramStateRef State = N->getState(); + if (!State->get<TrackedRegionMap>(Region)) + break; + MoveNode = N; + N = N->pred_empty() ? nullptr : *(N->pred_begin()); + } + return MoveNode; +} + +void MoveChecker::modelUse(ProgramStateRef State, const MemRegion *Region, + const CXXRecordDecl *RD, MisuseKind MK, + CheckerContext &C) const { + assert(!C.isDifferent() && "No transitions should have been made by now"); + const RegionState *RS = State->get<TrackedRegionMap>(Region); + ObjectKind OK = classifyObject(Region, RD); + + // Just in case: if it's not a smart pointer but it does have operator *, + // we shouldn't call the bug a dereference. + if (MK == MK_Dereference && OK.StdKind != SK_SmartPtr) + MK = MK_FunCall; + + if (!RS || !shouldWarnAbout(OK, MK) + || isInMoveSafeContext(C.getLocationContext())) { + // Finalize changes made by the caller. + C.addTransition(State); + return; + } + + // Don't report it in case if any base region is already reported. + // But still generate a sink in case of UB. + // And still finalize changes made by the caller. + if (isAnyBaseRegionReported(State, Region)) { + if (misuseCausesCrash(MK)) { + C.generateSink(State, C.getPredecessor()); + } else { + C.addTransition(State); + } + return; + } + + ExplodedNode *N = reportBug(Region, RD, C, MK); + + // If the program has already crashed on this path, don't bother. + if (N->isSink()) + return; + + State = State->set<TrackedRegionMap>(Region, RegionState::getReported()); + C.addTransition(State, N); +} + +ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, + const CXXRecordDecl *RD, CheckerContext &C, + MisuseKind MK) const { + if (ExplodedNode *N = misuseCausesCrash(MK) ? C.generateErrorNode() + : C.generateNonFatalErrorNode()) { + + if (!BT) + BT.reset(new BugType(this, "Use-after-move", + "C++ move semantics")); + + // Uniqueing report to the same object. + PathDiagnosticLocation LocUsedForUniqueing; + const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); + + if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) + LocUsedForUniqueing = PathDiagnosticLocation::createBegin( + MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); + + // Creating the error message. + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + switch(MK) { + case MK_FunCall: + OS << "Method called on moved-from object"; + explainObject(OS, Region, RD, MK); + break; + case MK_Copy: + OS << "Moved-from object"; + explainObject(OS, Region, RD, MK); + OS << " is copied"; + break; + case MK_Move: + OS << "Moved-from object"; + explainObject(OS, Region, RD, MK); + OS << " is moved"; + break; + case MK_Dereference: + OS << "Dereference of null smart pointer"; + explainObject(OS, Region, RD, MK); + break; + } + + auto R = + llvm::make_unique<BugReport>(*BT, OS.str(), N, LocUsedForUniqueing, + MoveNode->getLocationContext()->getDecl()); + R->addVisitor(llvm::make_unique<MovedBugVisitor>(*this, Region, RD, MK)); + C.emitReport(std::move(R)); + return N; + } + return nullptr; +} + +void MoveChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *AFC = dyn_cast<AnyFunctionCall>(&Call); + if (!AFC) + return; + + ProgramStateRef State = C.getState(); + const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(AFC->getDecl()); + if (!MethodDecl) + return; + + // Check if an object became moved-from. + // Object can become moved from after a call to move assignment operator or + // move constructor . + const auto *ConstructorDecl = dyn_cast<CXXConstructorDecl>(MethodDecl); + if (ConstructorDecl && !ConstructorDecl->isMoveConstructor()) + return; + + if (!ConstructorDecl && !MethodDecl->isMoveAssignmentOperator()) + return; + + const auto ArgRegion = AFC->getArgSVal(0).getAsRegion(); + if (!ArgRegion) + return; + + // Skip moving the object to itself. + const auto *CC = dyn_cast_or_null<CXXConstructorCall>(&Call); + if (CC && CC->getCXXThisVal().getAsRegion() == ArgRegion) + return; + + if (const auto *IC = dyn_cast<CXXInstanceCall>(AFC)) + if (IC->getCXXThisVal().getAsRegion() == ArgRegion) + return; + + const MemRegion *BaseRegion = ArgRegion->getBaseRegion(); + // Skip temp objects because of their short lifetime. + if (BaseRegion->getAs<CXXTempObjectRegion>() || + AFC->getArgExpr(0)->isRValue()) + return; + // If it has already been reported do not need to modify the state. + + if (State->get<TrackedRegionMap>(ArgRegion)) + return; + + const CXXRecordDecl *RD = MethodDecl->getParent(); + ObjectKind OK = classifyObject(ArgRegion, RD); + if (shouldBeTracked(OK)) { + // Mark object as moved-from. + State = State->set<TrackedRegionMap>(ArgRegion, RegionState::getMoved()); + C.addTransition(State); + return; + } + assert(!C.isDifferent() && "Should not have made transitions on this path!"); +} + +bool MoveChecker::isMoveSafeMethod(const CXXMethodDecl *MethodDec) const { + // We abandon the cases where bool/void/void* conversion happens. + if (const auto *ConversionDec = + dyn_cast_or_null<CXXConversionDecl>(MethodDec)) { + const Type *Tp = ConversionDec->getConversionType().getTypePtrOrNull(); + if (!Tp) + return false; + if (Tp->isBooleanType() || Tp->isVoidType() || Tp->isVoidPointerType()) + return true; + } + // Function call `empty` can be skipped. + return (MethodDec && MethodDec->getDeclName().isIdentifier() && + (MethodDec->getName().lower() == "empty" || + MethodDec->getName().lower() == "isempty")); +} + +bool MoveChecker::isStateResetMethod(const CXXMethodDecl *MethodDec) const { + if (!MethodDec) + return false; + if (MethodDec->hasAttr<ReinitializesAttr>()) + return true; + if (MethodDec->getDeclName().isIdentifier()) { + std::string MethodName = MethodDec->getName().lower(); + // TODO: Some of these methods (eg., resize) are not always resetting + // the state, so we should consider looking at the arguments. + if (MethodName == "reset" || MethodName == "clear" || + MethodName == "destroy" || MethodName == "resize" || + MethodName == "shrink") + return true; + } + return false; +} + +// Don't report an error inside a move related operation. +// We assume that the programmer knows what she does. +bool MoveChecker::isInMoveSafeContext(const LocationContext *LC) const { + do { + const auto *CtxDec = LC->getDecl(); + auto *CtorDec = dyn_cast_or_null<CXXConstructorDecl>(CtxDec); + auto *DtorDec = dyn_cast_or_null<CXXDestructorDecl>(CtxDec); + auto *MethodDec = dyn_cast_or_null<CXXMethodDecl>(CtxDec); + if (DtorDec || (CtorDec && CtorDec->isCopyOrMoveConstructor()) || + (MethodDec && MethodDec->isOverloadedOperator() && + MethodDec->getOverloadedOperator() == OO_Equal) || + isStateResetMethod(MethodDec) || isMoveSafeMethod(MethodDec)) + return true; + } while ((LC = LC->getParent())); + return false; +} + +bool MoveChecker::belongsTo(const CXXRecordDecl *RD, + const llvm::StringSet<> &Set) const { + const IdentifierInfo *II = RD->getIdentifier(); + return II && Set.count(II->getName()); +} + +MoveChecker::ObjectKind +MoveChecker::classifyObject(const MemRegion *MR, + const CXXRecordDecl *RD) const { + // Local variables and local rvalue references are classified as "Local". + // For the purposes of this checker, we classify move-safe STL types + // as not-"STL" types, because that's how the checker treats them. + MR = unwrapRValueReferenceIndirection(MR); + bool IsLocal = + MR && isa<VarRegion>(MR) && isa<StackSpaceRegion>(MR->getMemorySpace()); + + if (!RD || !RD->getDeclContext()->isStdNamespace()) + return { IsLocal, SK_NonStd }; + + if (belongsTo(RD, StdSmartPtrClasses)) + return { IsLocal, SK_SmartPtr }; + + if (belongsTo(RD, StdSafeClasses)) + return { IsLocal, SK_Safe }; + + return { IsLocal, SK_Unsafe }; +} + +void MoveChecker::explainObject(llvm::raw_ostream &OS, const MemRegion *MR, + const CXXRecordDecl *RD, MisuseKind MK) const { + // We may need a leading space every time we actually explain anything, + // and we never know if we are to explain anything until we try. + if (const auto DR = + dyn_cast_or_null<DeclRegion>(unwrapRValueReferenceIndirection(MR))) { + const auto *RegionDecl = cast<NamedDecl>(DR->getDecl()); + OS << " '" << RegionDecl->getNameAsString() << "'"; + } + + ObjectKind OK = classifyObject(MR, RD); + switch (OK.StdKind) { + case SK_NonStd: + case SK_Safe: + break; + case SK_SmartPtr: + if (MK != MK_Dereference) + break; + + // We only care about the type if it's a dereference. + LLVM_FALLTHROUGH; + case SK_Unsafe: + OS << " of type '" << RD->getQualifiedNameAsString() << "'"; + break; + }; +} + +void MoveChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + // Remove the MemRegions from the map on which a ctor/dtor call or assignment + // happened. + + // Checking constructor calls. + if (const auto *CC = dyn_cast<CXXConstructorCall>(&Call)) { + State = removeFromState(State, CC->getCXXThisVal().getAsRegion()); + auto CtorDec = CC->getDecl(); + // Check for copying a moved-from object and report the bug. + if (CtorDec && CtorDec->isCopyOrMoveConstructor()) { + const MemRegion *ArgRegion = CC->getArgSVal(0).getAsRegion(); + const CXXRecordDecl *RD = CtorDec->getParent(); + MisuseKind MK = CtorDec->isMoveConstructor() ? MK_Move : MK_Copy; + modelUse(State, ArgRegion, RD, MK, C); + return; + } + } + + const auto IC = dyn_cast<CXXInstanceCall>(&Call); + if (!IC) + return; + + // Calling a destructor on a moved object is fine. + if (isa<CXXDestructorCall>(IC)) + return; + + const MemRegion *ThisRegion = IC->getCXXThisVal().getAsRegion(); + if (!ThisRegion) + return; + + // The remaining part is check only for method call on a moved-from object. + const auto MethodDecl = dyn_cast_or_null<CXXMethodDecl>(IC->getDecl()); + if (!MethodDecl) + return; + + // We want to investigate the whole object, not only sub-object of a parent + // class in which the encountered method defined. + ThisRegion = ThisRegion->getMostDerivedObjectRegion(); + + if (isStateResetMethod(MethodDecl)) { + State = removeFromState(State, ThisRegion); + C.addTransition(State); + return; + } + + if (isMoveSafeMethod(MethodDecl)) + return; + + // Store class declaration as well, for bug reporting purposes. + const CXXRecordDecl *RD = MethodDecl->getParent(); + + if (MethodDecl->isOverloadedOperator()) { + OverloadedOperatorKind OOK = MethodDecl->getOverloadedOperator(); + + if (OOK == OO_Equal) { + // Remove the tracked object for every assignment operator, but report bug + // only for move or copy assignment's argument. + State = removeFromState(State, ThisRegion); + + if (MethodDecl->isCopyAssignmentOperator() || + MethodDecl->isMoveAssignmentOperator()) { + const MemRegion *ArgRegion = IC->getArgSVal(0).getAsRegion(); + MisuseKind MK = + MethodDecl->isMoveAssignmentOperator() ? MK_Move : MK_Copy; + modelUse(State, ArgRegion, RD, MK, C); + return; + } + C.addTransition(State); + return; + } + + if (OOK == OO_Star || OOK == OO_Arrow) { + modelUse(State, ThisRegion, RD, MK_Dereference, C); + return; + } + } + + modelUse(State, ThisRegion, RD, MK_FunCall, C); +} + +void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); + for (TrackedRegionMapTy::value_type E : TrackedRegions) { + const MemRegion *Region = E.first; + bool IsRegDead = !SymReaper.isLiveRegion(Region); + + // Remove the dead regions from the region map. + if (IsRegDead) { + State = State->remove<TrackedRegionMap>(Region); + } + } + C.addTransition(State); +} + +ProgramStateRef MoveChecker::checkRegionChanges( + ProgramStateRef State, const InvalidatedSymbols *Invalidated, + ArrayRef<const MemRegion *> RequestedRegions, + ArrayRef<const MemRegion *> InvalidatedRegions, + const LocationContext *LCtx, const CallEvent *Call) const { + if (Call) { + // Relax invalidation upon function calls: only invalidate parameters + // that are passed directly via non-const pointers or non-const references + // or rvalue references. + // In case of an InstanceCall don't invalidate the this-region since + // it is fully handled in checkPreCall and checkPostCall. + const MemRegion *ThisRegion = nullptr; + if (const auto *IC = dyn_cast<CXXInstanceCall>(Call)) + ThisRegion = IC->getCXXThisVal().getAsRegion(); + + // Requested ("explicit") regions are the regions passed into the call + // directly, but not all of them end up being invalidated. + // But when they do, they appear in the InvalidatedRegions array as well. + for (const auto *Region : RequestedRegions) { + if (ThisRegion != Region) { + if (llvm::find(InvalidatedRegions, Region) != + std::end(InvalidatedRegions)) { + State = removeFromState(State, Region); + } + } + } + } else { + // For invalidations that aren't caused by calls, assume nothing. In + // particular, direct write into an object's field invalidates the status. + for (const auto *Region : InvalidatedRegions) + State = removeFromState(State, Region->getBaseRegion()); + } + + return State; +} + +void MoveChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + TrackedRegionMapTy RS = State->get<TrackedRegionMap>(); + + if (!RS.isEmpty()) { + Out << Sep << "Moved-from objects :" << NL; + for (auto I: RS) { + I.first->dumpToStream(Out); + if (I.second.isMoved()) + Out << ": moved"; + else + Out << ": moved and reported"; + Out << NL; + } + } +} +void ento::registerMoveChecker(CheckerManager &mgr) { + MoveChecker *chk = mgr.registerChecker<MoveChecker>(); + chk->setAggressiveness( + mgr.getAnalyzerOptions().getCheckerStringOption("WarnOn", "", chk)); +} diff --git a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index 0e7894788c87..4ed1b25cb09e 100644 --- a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" diff --git a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index 2bd68b625c1f..06c43c6b9470 100644 --- a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" diff --git a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp index 8a5c769b6b50..83d4b5b0758b 100644 --- a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp @@ -12,9 +12,9 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" -#include "SelectorExtras.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" +#include "clang/Analysis/SelectorExtras.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" diff --git a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index 01d2c0491b85..3c4363b6850e 100644 --- a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -192,7 +192,7 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, *BTAttrNonNull, "Null pointer passed as an argument to a 'nonnull' parameter", ErrorNode); if (ArgE) - bugreporter::trackNullOrUndefValue(ErrorNode, ArgE, *R); + bugreporter::trackExpressionValue(ErrorNode, ArgE, *R); return R; } @@ -208,9 +208,7 @@ std::unique_ptr<BugReport> NonNullParamChecker::genReportReferenceToNullPointer( const Expr *ArgEDeref = bugreporter::getDerefExpr(ArgE); if (!ArgEDeref) ArgEDeref = ArgE; - bugreporter::trackNullOrUndefValue(ErrorNode, - ArgEDeref, - *R); + bugreporter::trackExpressionValue(ErrorNode, ArgEDeref, *R); } return R; diff --git a/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index 6f3180eb839a..ce9e950aa9ba 100644 --- a/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -21,7 +21,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index 7d1ca61c97a9..e535d1ae27ac 100644 --- a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -25,7 +25,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -139,7 +139,6 @@ private: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) override; @@ -175,7 +174,8 @@ private: if (Error == ErrorKind::NilAssignedToNonnull || Error == ErrorKind::NilPassedToNonnull || Error == ErrorKind::NilReturnedToNonnull) - bugreporter::trackNullOrUndefValue(N, ValueExpr, *R); + if (const auto *Ex = dyn_cast<Expr>(ValueExpr)) + bugreporter::trackExpressionValue(N, Ex, *R); } BR.emitReport(std::move(R)); } @@ -185,7 +185,7 @@ private: const SymbolicRegion *getTrackRegion(SVal Val, bool CheckSuperRegion = false) const; - /// Returns true if the call is diagnosable in the currrent analyzer + /// Returns true if the call is diagnosable in the current analyzer /// configuration. bool isDiagnosableCall(const CallEvent &Call) const { if (NoDiagnoseCallsToSystemHeaders && Call.isInSystemHeader()) @@ -293,11 +293,10 @@ NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { std::shared_ptr<PathDiagnosticPiece> NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, BugReporterContext &BRC, BugReport &BR) { ProgramStateRef State = N->getState(); - ProgramStateRef StatePrev = PrevN->getState(); + ProgramStateRef StatePrev = N->getFirstPred()->getState(); const NullabilityState *TrackedNullab = State->get<NullabilityMap>(Region); const NullabilityState *TrackedNullabPrev = @@ -311,7 +310,7 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, // Retrieve the associated statement. const Stmt *S = TrackedNullab->getNullabilitySource(); - if (!S || S->getLocStart().isInvalid()) { + if (!S || S->getBeginLoc().isInvalid()) { S = PathDiagnosticLocation::getStmt(N); } @@ -330,8 +329,8 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, nullptr); } -/// Returns true when the value stored at the given location is null -/// and the passed in type is nonnnull. +/// Returns true when the value stored at the given location has been +/// constrained to null after being passed through an object of nonnnull type. static bool checkValueAtLValForInvariantViolation(ProgramStateRef State, SVal LV, QualType T) { if (getNullabilityAnnotation(T) != Nullability::Nonnull) @@ -341,9 +340,14 @@ static bool checkValueAtLValForInvariantViolation(ProgramStateRef State, if (!RegionVal) return false; - auto StoredVal = - State->getSVal(RegionVal->getRegion()).getAs<DefinedOrUnknownSVal>(); - if (!StoredVal) + // If the value was constrained to null *after* it was passed through that + // location, it could not have been a concrete pointer *when* it was passed. + // In that case we would have handled the situation when the value was + // bound to that location, by emitting (or not emitting) a report. + // Therefore we are only interested in symbolic regions that can be either + // null or non-null depending on the value of their respective symbol. + auto StoredVal = State->getSVal(*RegionVal).getAs<loc::MemRegionVal>(); + if (!StoredVal || !isa<SymbolicRegion>(StoredVal->getRegion())) return false; if (getNullConstraint(*StoredVal, State) == NullConstraint::IsNull) @@ -447,9 +451,6 @@ void NullabilityChecker::reportBugIfInvariantHolds(StringRef Msg, /// Cleaning up the program state. void NullabilityChecker::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { - if (!SR.hasDeadSymbols()) - return; - ProgramStateRef State = C.getState(); NullabilityMapTy Nullabilities = State->get<NullabilityMap>(); for (NullabilityMapTy::iterator I = Nullabilities.begin(), @@ -766,7 +767,7 @@ void NullabilityChecker::checkPostCall(const CallEvent &Call, // CG headers are misannotated. Do not warn for symbols that are the results // of CG calls. const SourceManager &SM = C.getSourceManager(); - StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getLocStart())); + StringRef FilePath = SM.getFilename(SM.getSpellingLoc(Decl->getBeginLoc())); if (llvm::sys::path::filename(FilePath).startswith("CG")) { State = State->set<NullabilityMap>(Region, Nullability::Contradicted); C.addTransition(State); @@ -1174,10 +1175,15 @@ void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State, NullabilityMapTy B = State->get<NullabilityMap>(); + if (State->get<InvariantViolated>()) + Out << Sep << NL + << "Nullability invariant was violated, warnings suppressed." << NL; + if (B.isEmpty()) return; - Out << Sep << NL; + if (!State->get<InvariantViolated>()) + Out << Sep << NL; for (NullabilityMapTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { Out << I->first << " : "; @@ -1194,7 +1200,7 @@ void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State, checker->NeedTracking = checker->NeedTracking || trackingRequired; \ checker->NoDiagnoseCallsToSystemHeaders = \ checker->NoDiagnoseCallsToSystemHeaders || \ - mgr.getAnalyzerOptions().getBooleanOption( \ + mgr.getAnalyzerOptions().getCheckerBooleanOption( \ "NoDiagnoseCallsToSystemHeaders", false, checker, true); \ } diff --git a/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp b/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp index d1749cfdbe27..4e3a7205f1f4 100644 --- a/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NumberObjectConversionChecker.cpp @@ -26,7 +26,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -77,7 +77,7 @@ void Callback::run(const MatchFinder::MatchResult &Result) { // to zero literals in non-pedantic mode. // FIXME: Introduce an AST matcher to implement the macro-related logic? bool MacroIndicatesWeShouldSkipTheCheck = false; - SourceLocation Loc = CheckIfNull->getLocStart(); + SourceLocation Loc = CheckIfNull->getBeginLoc(); if (Loc.isMacroID()) { StringRef MacroName = Lexer::getImmediateMacroName( Loc, ACtx.getSourceManager(), ACtx.getLangOpts()); @@ -87,9 +87,10 @@ void Callback::run(const MatchFinder::MatchResult &Result) { MacroIndicatesWeShouldSkipTheCheck = true; } if (!MacroIndicatesWeShouldSkipTheCheck) { - llvm::APSInt Result; + Expr::EvalResult EVResult; if (CheckIfNull->IgnoreParenCasts()->EvaluateAsInt( - Result, ACtx, Expr::SE_AllowSideEffects)) { + EVResult, ACtx, Expr::SE_AllowSideEffects)) { + llvm::APSInt Result = EVResult.Val.getInt(); if (Result == 0) { if (!C->Pedantic) return; @@ -346,5 +347,5 @@ void ento::registerNumberObjectConversionChecker(CheckerManager &Mgr) { NumberObjectConversionChecker *Chk = Mgr.registerChecker<NumberObjectConversionChecker>(); Chk->Pedantic = - Mgr.getAnalyzerOptions().getBooleanOption("Pedantic", false, Chk); + Mgr.getAnalyzerOptions().getCheckerBooleanOption("Pedantic", false, Chk); } diff --git a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index b7339fe79f69..185b57575cb0 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtObjC.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -49,7 +49,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, "for @synchronized")); auto report = llvm::make_unique<BugReport>(*BT_undef, BT_undef->getDescription(), N); - bugreporter::trackNullOrUndefValue(N, Ex, *report); + bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); } return; @@ -73,7 +73,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, "(no synchronization will occur)")); auto report = llvm::make_unique<BugReport>(*BT_null, BT_null->getDescription(), N); - bugreporter::trackNullOrUndefValue(N, Ex, *report); + bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); return; @@ -89,6 +89,6 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, } void ento::registerObjCAtSyncChecker(CheckerManager &mgr) { - if (mgr.getLangOpts().ObjC2) + if (mgr.getLangOpts().ObjC) mgr.registerChecker<ObjCAtSyncChecker>(); } diff --git a/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp index 81bcda51b8f8..0424958f8e65 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCAutoreleaseWriteChecker.cpp @@ -27,7 +27,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp index e4737fcee7fb..34ce47823d51 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp @@ -11,7 +11,7 @@ // 'CFDictionary', 'CFSet' APIs. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/StmtVisitor.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Basic/TargetInfo.h" diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index fb05ca630b45..1c8c0d8dedda 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -16,7 +16,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -57,6 +57,9 @@ public: const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const; + + void printState(raw_ostream &OS, ProgramStateRef State, + const char *NL, const char *Sep) const; }; } // end anonymous namespace @@ -144,6 +147,8 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, initBugType(); auto R = llvm::make_unique<BugReport>(*BT, "Index is out of bounds", N); R->addRange(IdxExpr->getSourceRange()); + bugreporter::trackExpressionValue(N, IdxExpr, *R, + /*EnableNullFPSuppression=*/false); C.emitReport(std::move(R)); return; } @@ -166,6 +171,18 @@ ObjCContainersChecker::checkPointerEscape(ProgramStateRef State, return State; } +void ObjCContainersChecker::printState(raw_ostream &OS, ProgramStateRef State, + const char *NL, const char *Sep) const { + ArraySizeMapTy Map = State->get<ArraySizeMap>(); + if (Map.isEmpty()) + return; + + OS << Sep << "ObjC container sizes :" << NL; + for (auto I : Map) { + OS << I.first << " : " << I.second << NL; + } +} + /// Register checker. void ento::registerObjCContainersChecker(CheckerManager &mgr) { mgr.registerChecker<ObjCContainersChecker>(); diff --git a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index d01c6ae6e093..d383302b2790 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" diff --git a/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp index dfd2c9afe7fb..018d3fcfceb9 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCPropertyChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 629520437369..efa804220765 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -36,7 +36,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index fcba3b33f3e0..9058784dd345 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -72,7 +72,6 @@ public: Satisfied(false) {} std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) override; @@ -247,8 +246,7 @@ ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { std::shared_ptr<PathDiagnosticPiece> SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, BugReport &) { if (Satisfied) return nullptr; @@ -257,7 +255,8 @@ SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, bool CalledNow = Succ->getState()->contains<CalledSuperDealloc>(ReceiverSymbol); bool CalledBefore = - Pred->getState()->contains<CalledSuperDealloc>(ReceiverSymbol); + Succ->getFirstPred()->getState()->contains<CalledSuperDealloc>( + ReceiverSymbol); // Is Succ the node on which the analyzer noted that [super dealloc] was // called on ReceiverSymbol? diff --git a/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index c6da37eac0c0..7f7b45316087 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -98,7 +98,7 @@ static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID, SourceManager &SM) { for (const auto *I : C->decls()) if (const auto *FD = dyn_cast<FunctionDecl>(I)) { - SourceLocation L = FD->getLocStart(); + SourceLocation L = FD->getBeginLoc(); if (SM.getFileID(L) == FID) Scan(M, FD->getBody()); } diff --git a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp index f69f3492edb1..211db392bf71 100644 --- a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/CharUnits.h" #include "clang/AST/DeclTemplate.h" #include "clang/AST/RecordLayout.h" @@ -41,7 +41,8 @@ public: BugReporter &BRArg) const { BR = &BRArg; AllowedPad = - MGR.getAnalyzerOptions().getOptionAsInteger("AllowedPad", 24, this); + MGR.getAnalyzerOptions() + .getCheckerIntegerOption("AllowedPad", 24, this); assert(AllowedPad >= 0 && "AllowedPad option should be non-negative"); // The calls to checkAST* from AnalysisConsumer don't @@ -75,6 +76,20 @@ public: if (shouldSkipDecl(RD)) return; + // TODO: Figure out why we are going through declarations and not only + // definitions. + if (!(RD = RD->getDefinition())) + return; + + // This is the simplest correct case: a class with no fields and one base + // class. Other cases are more complicated because of how the base classes + // & fields might interact, so we don't bother dealing with them. + // TODO: Support other combinations of base classes and fields. + if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) + if (CXXRD->field_empty() && CXXRD->getNumBases() == 1) + return visitRecord(CXXRD->bases().begin()->getType()->getAsRecordDecl(), + PadMultiplier); + auto &ASTContext = RD->getASTContext(); const ASTRecordLayout &RL = ASTContext.getASTRecordLayout(RD); assert(llvm::isPowerOf2_64(RL.getAlignment().getQuantity())); @@ -112,12 +127,15 @@ public: if (RT == nullptr) return; - // TODO: Recurse into the fields and base classes to see if any - // of those have excess padding. + // TODO: Recurse into the fields to see if they have excess padding. visitRecord(RT->getDecl(), Elts); } bool shouldSkipDecl(const RecordDecl *RD) const { + // TODO: Figure out why we are going through declarations and not only + // definitions. + if (!(RD = RD->getDefinition())) + return true; auto Location = RD->getLocation(); // If the construct doesn't have a source file, then it's not something // we want to diagnose. @@ -132,13 +150,14 @@ public: // Not going to attempt to optimize unions. if (RD->isUnion()) return true; - // How do you reorder fields if you haven't got any? - if (RD->field_empty()) - return true; if (auto *CXXRD = dyn_cast<CXXRecordDecl>(RD)) { // Tail padding with base classes ends up being very complicated. - // We will skip objects with base classes for now. - if (CXXRD->getNumBases() != 0) + // We will skip objects with base classes for now, unless they do not + // have fields. + // TODO: Handle more base class scenarios. + if (!CXXRD->field_empty() && CXXRD->getNumBases() != 0) + return true; + if (CXXRD->field_empty() && CXXRD->getNumBases() != 1) return true; // Virtual bases are complicated, skipping those for now. if (CXXRD->getNumVBases() != 0) @@ -150,6 +169,10 @@ public: if (CXXRD->getTypeForDecl()->isInstantiationDependentType()) return true; } + // How do you reorder fields if you haven't got any? + else if (RD->field_empty()) + return true; + auto IsTrickyField = [](const FieldDecl *FD) -> bool { // Bitfield layout is hard. if (FD->isBitField()) @@ -237,7 +260,7 @@ public: }; std::transform(RD->field_begin(), RD->field_end(), std::back_inserter(Fields), GatherSizesAndAlignments); - llvm::sort(Fields.begin(), Fields.end()); + llvm::sort(Fields); // This lets us skip over vptrs and non-virtual bases, // so that we can just worry about the fields in our object. // Note that this does cause us to miss some cases where we @@ -323,7 +346,7 @@ public: BR->emitReport(std::move(Report)); } }; -} +} // namespace void ento::registerPaddingChecker(CheckerManager &Mgr) { Mgr.registerChecker<PaddingChecker>(); diff --git a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 63f82b275ba2..de3a16ebc729 100644 --- a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/ExprCXX.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -112,7 +112,7 @@ PointerArithChecker::getPointedRegion(const MemRegion *Region, } /// Checks whether a region is the part of an array. -/// In case there is a dericed to base cast above the array element, the +/// In case there is a derived to base cast above the array element, the /// Polymorphic output value is set to true. AKind output value is set to the /// allocation kind of the inspected region. const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region, diff --git a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index 9aa5348e4c34..41490e45f241 100644 --- a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index 10ab952e069b..66cc37278809 100644 --- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp deleted file mode 100644 index 9c85c0983723..000000000000 --- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ /dev/null @@ -1,4156 +0,0 @@ -//==-- RetainCountChecker.cpp - Checks for leaks and other issues -*- 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 the methods for RetainCountChecker, which implements -// a reference count checker for Core Foundation and Cocoa on (Mac OS X). -// -//===----------------------------------------------------------------------===// - -#include "AllocationDiagnostics.h" -#include "ClangSACheckers.h" -#include "SelectorExtras.h" -#include "clang/AST/Attr.h" -#include "clang/AST/DeclCXX.h" -#include "clang/AST/DeclObjC.h" -#include "clang/AST/ParentMap.h" -#include "clang/Analysis/DomainSpecific/CocoaConventions.h" -#include "clang/Basic/LangOptions.h" -#include "clang/Basic/SourceManager.h" -#include "clang/StaticAnalyzer/Checkers/ObjCRetainCount.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" -#include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/CheckerManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/FoldingSet.h" -#include "llvm/ADT/ImmutableList.h" -#include "llvm/ADT/ImmutableMap.h" -#include "llvm/ADT/STLExtras.h" -#include "llvm/ADT/SmallString.h" -#include "llvm/ADT/StringExtras.h" -#include <cstdarg> -#include <utility> - -using namespace clang; -using namespace ento; -using namespace objc_retain; -using llvm::StrInStrNoCase; - -//===----------------------------------------------------------------------===// -// Adapters for FoldingSet. -//===----------------------------------------------------------------------===// - -namespace llvm { -template <> struct FoldingSetTrait<ArgEffect> { -static inline void Profile(const ArgEffect X, FoldingSetNodeID &ID) { - ID.AddInteger((unsigned) X); -} -}; -template <> struct FoldingSetTrait<RetEffect> { - static inline void Profile(const RetEffect &X, FoldingSetNodeID &ID) { - ID.AddInteger((unsigned) X.getKind()); - ID.AddInteger((unsigned) X.getObjKind()); -} -}; -} // end llvm namespace - -//===----------------------------------------------------------------------===// -// Reference-counting logic (typestate + counts). -//===----------------------------------------------------------------------===// - -/// ArgEffects summarizes the effects of a function/method call on all of -/// its arguments. -typedef llvm::ImmutableMap<unsigned,ArgEffect> ArgEffects; - -namespace { -class RefVal { -public: - enum Kind { - Owned = 0, // Owning reference. - NotOwned, // Reference is not owned by still valid (not freed). - Released, // Object has been released. - ReturnedOwned, // Returned object passes ownership to caller. - ReturnedNotOwned, // Return object does not pass ownership to caller. - ERROR_START, - ErrorDeallocNotOwned, // -dealloc called on non-owned object. - ErrorDeallocGC, // Calling -dealloc with GC enabled. - ErrorUseAfterRelease, // Object used after released. - ErrorReleaseNotOwned, // Release of an object that was not owned. - ERROR_LEAK_START, - ErrorLeak, // A memory leak due to excessive reference counts. - ErrorLeakReturned, // A memory leak due to the returning method not having - // the correct naming conventions. - ErrorGCLeakReturned, - ErrorOverAutorelease, - ErrorReturnedNotOwned - }; - - /// Tracks how an object referenced by an ivar has been used. - /// - /// This accounts for us not knowing if an arbitrary ivar is supposed to be - /// stored at +0 or +1. - enum class IvarAccessHistory { - None, - AccessedDirectly, - ReleasedAfterDirectAccess - }; - -private: - /// The number of outstanding retains. - unsigned Cnt; - /// The number of outstanding autoreleases. - unsigned ACnt; - /// The (static) type of the object at the time we started tracking it. - QualType T; - - /// The current state of the object. - /// - /// See the RefVal::Kind enum for possible values. - unsigned RawKind : 5; - - /// The kind of object being tracked (CF or ObjC), if known. - /// - /// See the RetEffect::ObjKind enum for possible values. - unsigned RawObjectKind : 2; - - /// True if the current state and/or retain count may turn out to not be the - /// best possible approximation of the reference counting state. - /// - /// If true, the checker may decide to throw away ("override") this state - /// in favor of something else when it sees the object being used in new ways. - /// - /// This setting should not be propagated to state derived from this state. - /// Once we start deriving new states, it would be inconsistent to override - /// them. - unsigned RawIvarAccessHistory : 2; - - RefVal(Kind k, RetEffect::ObjKind o, unsigned cnt, unsigned acnt, QualType t, - IvarAccessHistory IvarAccess) - : Cnt(cnt), ACnt(acnt), T(t), RawKind(static_cast<unsigned>(k)), - RawObjectKind(static_cast<unsigned>(o)), - RawIvarAccessHistory(static_cast<unsigned>(IvarAccess)) { - assert(getKind() == k && "not enough bits for the kind"); - assert(getObjKind() == o && "not enough bits for the object kind"); - assert(getIvarAccessHistory() == IvarAccess && "not enough bits"); - } - -public: - Kind getKind() const { return static_cast<Kind>(RawKind); } - - RetEffect::ObjKind getObjKind() const { - return static_cast<RetEffect::ObjKind>(RawObjectKind); - } - - unsigned getCount() const { return Cnt; } - unsigned getAutoreleaseCount() const { return ACnt; } - unsigned getCombinedCounts() const { return Cnt + ACnt; } - void clearCounts() { - Cnt = 0; - ACnt = 0; - } - void setCount(unsigned i) { - Cnt = i; - } - void setAutoreleaseCount(unsigned i) { - ACnt = i; - } - - QualType getType() const { return T; } - - /// Returns what the analyzer knows about direct accesses to a particular - /// instance variable. - /// - /// If the object with this refcount wasn't originally from an Objective-C - /// ivar region, this should always return IvarAccessHistory::None. - IvarAccessHistory getIvarAccessHistory() const { - return static_cast<IvarAccessHistory>(RawIvarAccessHistory); - } - - bool isOwned() const { - return getKind() == Owned; - } - - bool isNotOwned() const { - return getKind() == NotOwned; - } - - bool isReturnedOwned() const { - return getKind() == ReturnedOwned; - } - - bool isReturnedNotOwned() const { - return getKind() == ReturnedNotOwned; - } - - /// Create a state for an object whose lifetime is the responsibility of the - /// current function, at least partially. - /// - /// Most commonly, this is an owned object with a retain count of +1. - static RefVal makeOwned(RetEffect::ObjKind o, QualType t, - unsigned Count = 1) { - return RefVal(Owned, o, Count, 0, t, IvarAccessHistory::None); - } - - /// Create a state for an object whose lifetime is not the responsibility of - /// the current function. - /// - /// Most commonly, this is an unowned object with a retain count of +0. - static RefVal makeNotOwned(RetEffect::ObjKind o, QualType t, - unsigned Count = 0) { - return RefVal(NotOwned, o, Count, 0, t, IvarAccessHistory::None); - } - - RefVal operator-(size_t i) const { - return RefVal(getKind(), getObjKind(), getCount() - i, - getAutoreleaseCount(), getType(), getIvarAccessHistory()); - } - - RefVal operator+(size_t i) const { - return RefVal(getKind(), getObjKind(), getCount() + i, - getAutoreleaseCount(), getType(), getIvarAccessHistory()); - } - - RefVal operator^(Kind k) const { - return RefVal(k, getObjKind(), getCount(), getAutoreleaseCount(), - getType(), getIvarAccessHistory()); - } - - RefVal autorelease() const { - return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount()+1, - getType(), getIvarAccessHistory()); - } - - RefVal withIvarAccess() const { - assert(getIvarAccessHistory() == IvarAccessHistory::None); - return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(), - getType(), IvarAccessHistory::AccessedDirectly); - } - - RefVal releaseViaIvar() const { - assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly); - return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(), - getType(), IvarAccessHistory::ReleasedAfterDirectAccess); - } - - // Comparison, profiling, and pretty-printing. - - bool hasSameState(const RefVal &X) const { - return getKind() == X.getKind() && Cnt == X.Cnt && ACnt == X.ACnt && - getIvarAccessHistory() == X.getIvarAccessHistory(); - } - - bool operator==(const RefVal& X) const { - return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind(); - } - - void Profile(llvm::FoldingSetNodeID& ID) const { - ID.Add(T); - ID.AddInteger(RawKind); - ID.AddInteger(Cnt); - ID.AddInteger(ACnt); - ID.AddInteger(RawObjectKind); - ID.AddInteger(RawIvarAccessHistory); - } - - void print(raw_ostream &Out) const; -}; - -void RefVal::print(raw_ostream &Out) const { - if (!T.isNull()) - Out << "Tracked " << T.getAsString() << '/'; - - switch (getKind()) { - default: llvm_unreachable("Invalid RefVal kind"); - case Owned: { - Out << "Owned"; - unsigned cnt = getCount(); - if (cnt) Out << " (+ " << cnt << ")"; - break; - } - - case NotOwned: { - Out << "NotOwned"; - unsigned cnt = getCount(); - if (cnt) Out << " (+ " << cnt << ")"; - break; - } - - case ReturnedOwned: { - Out << "ReturnedOwned"; - unsigned cnt = getCount(); - if (cnt) Out << " (+ " << cnt << ")"; - break; - } - - case ReturnedNotOwned: { - Out << "ReturnedNotOwned"; - unsigned cnt = getCount(); - if (cnt) Out << " (+ " << cnt << ")"; - break; - } - - case Released: - Out << "Released"; - break; - - case ErrorDeallocGC: - Out << "-dealloc (GC)"; - break; - - case ErrorDeallocNotOwned: - Out << "-dealloc (not-owned)"; - break; - - case ErrorLeak: - Out << "Leaked"; - break; - - case ErrorLeakReturned: - Out << "Leaked (Bad naming)"; - break; - - case ErrorGCLeakReturned: - Out << "Leaked (GC-ed at return)"; - break; - - case ErrorUseAfterRelease: - Out << "Use-After-Release [ERROR]"; - break; - - case ErrorReleaseNotOwned: - Out << "Release of Not-Owned [ERROR]"; - break; - - case RefVal::ErrorOverAutorelease: - Out << "Over-autoreleased"; - break; - - case RefVal::ErrorReturnedNotOwned: - Out << "Non-owned object returned instead of owned"; - break; - } - - switch (getIvarAccessHistory()) { - case IvarAccessHistory::None: - break; - case IvarAccessHistory::AccessedDirectly: - Out << " [direct ivar access]"; - break; - case IvarAccessHistory::ReleasedAfterDirectAccess: - Out << " [released after direct ivar access]"; - } - - if (ACnt) { - Out << " [autorelease -" << ACnt << ']'; - } -} -} //end anonymous namespace - -//===----------------------------------------------------------------------===// -// RefBindings - State used to track object reference counts. -//===----------------------------------------------------------------------===// - -REGISTER_MAP_WITH_PROGRAMSTATE(RefBindings, SymbolRef, RefVal) - -static inline const RefVal *getRefBinding(ProgramStateRef State, - SymbolRef Sym) { - return State->get<RefBindings>(Sym); -} - -static inline ProgramStateRef setRefBinding(ProgramStateRef State, - SymbolRef Sym, RefVal Val) { - return State->set<RefBindings>(Sym, Val); -} - -static ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym) { - return State->remove<RefBindings>(Sym); -} - -//===----------------------------------------------------------------------===// -// Function/Method behavior summaries. -//===----------------------------------------------------------------------===// - -namespace { -class RetainSummary { - /// Args - a map of (index, ArgEffect) pairs, where index - /// specifies the argument (starting from 0). This can be sparsely - /// populated; arguments with no entry in Args use 'DefaultArgEffect'. - ArgEffects Args; - - /// DefaultArgEffect - The default ArgEffect to apply to arguments that - /// do not have an entry in Args. - ArgEffect DefaultArgEffect; - - /// Receiver - If this summary applies to an Objective-C message expression, - /// this is the effect applied to the state of the receiver. - ArgEffect Receiver; - - /// Ret - The effect on the return value. Used to indicate if the - /// function/method call returns a new tracked symbol. - RetEffect Ret; - -public: - RetainSummary(ArgEffects A, RetEffect R, ArgEffect defaultEff, - ArgEffect ReceiverEff) - : Args(A), DefaultArgEffect(defaultEff), Receiver(ReceiverEff), Ret(R) {} - - /// getArg - Return the argument effect on the argument specified by - /// idx (starting from 0). - ArgEffect getArg(unsigned idx) const { - if (const ArgEffect *AE = Args.lookup(idx)) - return *AE; - - return DefaultArgEffect; - } - - void addArg(ArgEffects::Factory &af, unsigned idx, ArgEffect e) { - Args = af.add(Args, idx, e); - } - - /// setDefaultArgEffect - Set the default argument effect. - void setDefaultArgEffect(ArgEffect E) { - DefaultArgEffect = E; - } - - /// getRetEffect - Returns the effect on the return value of the call. - RetEffect getRetEffect() const { return Ret; } - - /// setRetEffect - Set the effect of the return value of the call. - void setRetEffect(RetEffect E) { Ret = E; } - - - /// Sets the effect on the receiver of the message. - void setReceiverEffect(ArgEffect e) { Receiver = e; } - - /// getReceiverEffect - Returns the effect on the receiver of the call. - /// This is only meaningful if the summary applies to an ObjCMessageExpr*. - ArgEffect getReceiverEffect() const { return Receiver; } - - /// Test if two retain summaries are identical. Note that merely equivalent - /// summaries are not necessarily identical (for example, if an explicit - /// argument effect matches the default effect). - bool operator==(const RetainSummary &Other) const { - return Args == Other.Args && DefaultArgEffect == Other.DefaultArgEffect && - Receiver == Other.Receiver && Ret == Other.Ret; - } - - /// Profile this summary for inclusion in a FoldingSet. - void Profile(llvm::FoldingSetNodeID& ID) const { - ID.Add(Args); - ID.Add(DefaultArgEffect); - ID.Add(Receiver); - ID.Add(Ret); - } - - /// A retain summary is simple if it has no ArgEffects other than the default. - bool isSimple() const { - return Args.isEmpty(); - } - -private: - ArgEffects getArgEffects() const { return Args; } - ArgEffect getDefaultArgEffect() const { return DefaultArgEffect; } - - friend class RetainSummaryManager; - friend class RetainCountChecker; -}; -} // end anonymous namespace - -//===----------------------------------------------------------------------===// -// Data structures for constructing summaries. -//===----------------------------------------------------------------------===// - -namespace { -class ObjCSummaryKey { - IdentifierInfo* II; - Selector S; -public: - ObjCSummaryKey(IdentifierInfo* ii, Selector s) - : II(ii), S(s) {} - - ObjCSummaryKey(const ObjCInterfaceDecl *d, Selector s) - : II(d ? d->getIdentifier() : nullptr), S(s) {} - - ObjCSummaryKey(Selector s) - : II(nullptr), S(s) {} - - IdentifierInfo *getIdentifier() const { return II; } - Selector getSelector() const { return S; } -}; -} // end anonymous namespace - -namespace llvm { -template <> struct DenseMapInfo<ObjCSummaryKey> { - static inline ObjCSummaryKey getEmptyKey() { - return ObjCSummaryKey(DenseMapInfo<IdentifierInfo*>::getEmptyKey(), - DenseMapInfo<Selector>::getEmptyKey()); - } - - static inline ObjCSummaryKey getTombstoneKey() { - return ObjCSummaryKey(DenseMapInfo<IdentifierInfo*>::getTombstoneKey(), - DenseMapInfo<Selector>::getTombstoneKey()); - } - - static unsigned getHashValue(const ObjCSummaryKey &V) { - typedef std::pair<IdentifierInfo*, Selector> PairTy; - return DenseMapInfo<PairTy>::getHashValue(PairTy(V.getIdentifier(), - V.getSelector())); - } - - static bool isEqual(const ObjCSummaryKey& LHS, const ObjCSummaryKey& RHS) { - return LHS.getIdentifier() == RHS.getIdentifier() && - LHS.getSelector() == RHS.getSelector(); - } - -}; -} // end llvm namespace - -namespace { -class ObjCSummaryCache { - typedef llvm::DenseMap<ObjCSummaryKey, const RetainSummary *> MapTy; - MapTy M; -public: - ObjCSummaryCache() {} - - const RetainSummary * find(const ObjCInterfaceDecl *D, Selector S) { - // Do a lookup with the (D,S) pair. If we find a match return - // the iterator. - ObjCSummaryKey K(D, S); - MapTy::iterator I = M.find(K); - - if (I != M.end()) - return I->second; - if (!D) - return nullptr; - - // Walk the super chain. If we find a hit with a parent, we'll end - // up returning that summary. We actually allow that key (null,S), as - // we cache summaries for the null ObjCInterfaceDecl* to allow us to - // generate initial summaries without having to worry about NSObject - // being declared. - // FIXME: We may change this at some point. - for (ObjCInterfaceDecl *C=D->getSuperClass() ;; C=C->getSuperClass()) { - if ((I = M.find(ObjCSummaryKey(C, S))) != M.end()) - break; - - if (!C) - return nullptr; - } - - // Cache the summary with original key to make the next lookup faster - // and return the iterator. - const RetainSummary *Summ = I->second; - M[K] = Summ; - return Summ; - } - - const RetainSummary *find(IdentifierInfo* II, Selector S) { - // FIXME: Class method lookup. Right now we don't have a good way - // of going between IdentifierInfo* and the class hierarchy. - MapTy::iterator I = M.find(ObjCSummaryKey(II, S)); - - if (I == M.end()) - I = M.find(ObjCSummaryKey(S)); - - return I == M.end() ? nullptr : I->second; - } - - const RetainSummary *& operator[](ObjCSummaryKey K) { - return M[K]; - } - - const RetainSummary *& operator[](Selector S) { - return M[ ObjCSummaryKey(S) ]; - } -}; -} // end anonymous namespace - -//===----------------------------------------------------------------------===// -// Data structures for managing collections of summaries. -//===----------------------------------------------------------------------===// - -namespace { -class RetainSummaryManager { - - //==-----------------------------------------------------------------==// - // Typedefs. - //==-----------------------------------------------------------------==// - - typedef llvm::DenseMap<const FunctionDecl*, const RetainSummary *> - FuncSummariesTy; - - typedef ObjCSummaryCache ObjCMethodSummariesTy; - - typedef llvm::FoldingSetNodeWrapper<RetainSummary> CachedSummaryNode; - - //==-----------------------------------------------------------------==// - // Data. - //==-----------------------------------------------------------------==// - - /// Ctx - The ASTContext object for the analyzed ASTs. - ASTContext &Ctx; - - /// GCEnabled - Records whether or not the analyzed code runs in GC mode. - const bool GCEnabled; - - /// Records whether or not the analyzed code runs in ARC mode. - const bool ARCEnabled; - - /// FuncSummaries - A map from FunctionDecls to summaries. - FuncSummariesTy FuncSummaries; - - /// ObjCClassMethodSummaries - A map from selectors (for instance methods) - /// to summaries. - ObjCMethodSummariesTy ObjCClassMethodSummaries; - - /// ObjCMethodSummaries - A map from selectors to summaries. - ObjCMethodSummariesTy ObjCMethodSummaries; - - /// BPAlloc - A BumpPtrAllocator used for allocating summaries, ArgEffects, - /// and all other data used by the checker. - llvm::BumpPtrAllocator BPAlloc; - - /// AF - A factory for ArgEffects objects. - ArgEffects::Factory AF; - - /// ScratchArgs - A holding buffer for construct ArgEffects. - ArgEffects ScratchArgs; - - /// ObjCAllocRetE - Default return effect for methods returning Objective-C - /// objects. - RetEffect ObjCAllocRetE; - - /// ObjCInitRetE - Default return effect for init methods returning - /// Objective-C objects. - RetEffect ObjCInitRetE; - - /// SimpleSummaries - Used for uniquing summaries that don't have special - /// effects. - llvm::FoldingSet<CachedSummaryNode> SimpleSummaries; - - //==-----------------------------------------------------------------==// - // Methods. - //==-----------------------------------------------------------------==// - - /// getArgEffects - Returns a persistent ArgEffects object based on the - /// data in ScratchArgs. - ArgEffects getArgEffects(); - - enum UnaryFuncKind { cfretain, cfrelease, cfautorelease, cfmakecollectable }; - - const RetainSummary *getUnarySummary(const FunctionType* FT, - UnaryFuncKind func); - - const RetainSummary *getCFSummaryCreateRule(const FunctionDecl *FD); - const RetainSummary *getCFSummaryGetRule(const FunctionDecl *FD); - const RetainSummary *getCFCreateGetRuleSummary(const FunctionDecl *FD); - - const RetainSummary *getPersistentSummary(const RetainSummary &OldSumm); - - const RetainSummary *getPersistentSummary(RetEffect RetEff, - ArgEffect ReceiverEff = DoNothing, - ArgEffect DefaultEff = MayEscape) { - RetainSummary Summ(getArgEffects(), RetEff, DefaultEff, ReceiverEff); - return getPersistentSummary(Summ); - } - - const RetainSummary *getDoNothingSummary() { - return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } - - const RetainSummary *getDefaultSummary() { - return getPersistentSummary(RetEffect::MakeNoRet(), - DoNothing, MayEscape); - } - - const RetainSummary *getPersistentStopSummary() { - return getPersistentSummary(RetEffect::MakeNoRet(), - StopTracking, StopTracking); - } - - void InitializeClassMethodSummaries(); - void InitializeMethodSummaries(); -private: - void addNSObjectClsMethSummary(Selector S, const RetainSummary *Summ) { - ObjCClassMethodSummaries[S] = Summ; - } - - void addNSObjectMethSummary(Selector S, const RetainSummary *Summ) { - ObjCMethodSummaries[S] = Summ; - } - - void addClassMethSummary(const char* Cls, const char* name, - const RetainSummary *Summ, bool isNullary = true) { - IdentifierInfo* ClsII = &Ctx.Idents.get(Cls); - Selector S = isNullary ? GetNullarySelector(name, Ctx) - : GetUnarySelector(name, Ctx); - ObjCClassMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; - } - - void addInstMethSummary(const char* Cls, const char* nullaryName, - const RetainSummary *Summ) { - IdentifierInfo* ClsII = &Ctx.Idents.get(Cls); - Selector S = GetNullarySelector(nullaryName, Ctx); - ObjCMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; - } - - template <typename... Keywords> - void addMethodSummary(IdentifierInfo *ClsII, ObjCMethodSummariesTy &Summaries, - const RetainSummary *Summ, Keywords *... Kws) { - Selector S = getKeywordSelector(Ctx, Kws...); - Summaries[ObjCSummaryKey(ClsII, S)] = Summ; - } - - template <typename... Keywords> - void addInstMethSummary(const char *Cls, const RetainSummary *Summ, - Keywords *... Kws) { - addMethodSummary(&Ctx.Idents.get(Cls), ObjCMethodSummaries, Summ, Kws...); - } - - template <typename... Keywords> - void addClsMethSummary(const char *Cls, const RetainSummary *Summ, - Keywords *... Kws) { - addMethodSummary(&Ctx.Idents.get(Cls), ObjCClassMethodSummaries, Summ, - Kws...); - } - - template <typename... Keywords> - void addClsMethSummary(IdentifierInfo *II, const RetainSummary *Summ, - Keywords *... Kws) { - addMethodSummary(II, ObjCClassMethodSummaries, Summ, Kws...); - } - -public: - - RetainSummaryManager(ASTContext &ctx, bool gcenabled, bool usesARC) - : Ctx(ctx), - GCEnabled(gcenabled), - ARCEnabled(usesARC), - AF(BPAlloc), ScratchArgs(AF.getEmptyMap()), - ObjCAllocRetE(gcenabled - ? RetEffect::MakeGCNotOwned() - : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC) - : RetEffect::MakeOwned(RetEffect::ObjC))), - ObjCInitRetE(gcenabled - ? RetEffect::MakeGCNotOwned() - : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC) - : RetEffect::MakeOwnedWhenTrackedReceiver())) { - InitializeClassMethodSummaries(); - InitializeMethodSummaries(); - } - - const RetainSummary *getSummary(const CallEvent &Call, - ProgramStateRef State = nullptr); - - const RetainSummary *getFunctionSummary(const FunctionDecl *FD); - - const RetainSummary *getMethodSummary(Selector S, const ObjCInterfaceDecl *ID, - const ObjCMethodDecl *MD, - QualType RetTy, - ObjCMethodSummariesTy &CachedSummaries); - - const RetainSummary *getInstanceMethodSummary(const ObjCMethodCall &M, - ProgramStateRef State); - - const RetainSummary *getClassMethodSummary(const ObjCMethodCall &M) { - assert(!M.isInstanceMessage()); - const ObjCInterfaceDecl *Class = M.getReceiverInterface(); - - return getMethodSummary(M.getSelector(), Class, M.getDecl(), - M.getResultType(), ObjCClassMethodSummaries); - } - - /// getMethodSummary - This version of getMethodSummary is used to query - /// the summary for the current method being analyzed. - const RetainSummary *getMethodSummary(const ObjCMethodDecl *MD) { - const ObjCInterfaceDecl *ID = MD->getClassInterface(); - Selector S = MD->getSelector(); - QualType ResultTy = MD->getReturnType(); - - ObjCMethodSummariesTy *CachedSummaries; - if (MD->isInstanceMethod()) - CachedSummaries = &ObjCMethodSummaries; - else - CachedSummaries = &ObjCClassMethodSummaries; - - return getMethodSummary(S, ID, MD, ResultTy, *CachedSummaries); - } - - const RetainSummary *getStandardMethodSummary(const ObjCMethodDecl *MD, - Selector S, QualType RetTy); - - /// Determine if there is a special return effect for this function or method. - Optional<RetEffect> getRetEffectFromAnnotations(QualType RetTy, - const Decl *D); - - void updateSummaryFromAnnotations(const RetainSummary *&Summ, - const ObjCMethodDecl *MD); - - void updateSummaryFromAnnotations(const RetainSummary *&Summ, - const FunctionDecl *FD); - - void updateSummaryForCall(const RetainSummary *&Summ, - const CallEvent &Call); - - bool isGCEnabled() const { return GCEnabled; } - - bool isARCEnabled() const { return ARCEnabled; } - - bool isARCorGCEnabled() const { return GCEnabled || ARCEnabled; } - - RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; } - - friend class RetainSummaryTemplate; -}; - -// Used to avoid allocating long-term (BPAlloc'd) memory for default retain -// summaries. If a function or method looks like it has a default summary, but -// it has annotations, the annotations are added to the stack-based template -// and then copied into managed memory. -class RetainSummaryTemplate { - RetainSummaryManager &Manager; - const RetainSummary *&RealSummary; - RetainSummary ScratchSummary; - bool Accessed; -public: - RetainSummaryTemplate(const RetainSummary *&real, RetainSummaryManager &mgr) - : Manager(mgr), RealSummary(real), ScratchSummary(*real), Accessed(false) {} - - ~RetainSummaryTemplate() { - if (Accessed) - RealSummary = Manager.getPersistentSummary(ScratchSummary); - } - - RetainSummary &operator*() { - Accessed = true; - return ScratchSummary; - } - - RetainSummary *operator->() { - Accessed = true; - return &ScratchSummary; - } -}; - -} // end anonymous namespace - -//===----------------------------------------------------------------------===// -// Implementation of checker data structures. -//===----------------------------------------------------------------------===// - -ArgEffects RetainSummaryManager::getArgEffects() { - ArgEffects AE = ScratchArgs; - ScratchArgs = AF.getEmptyMap(); - return AE; -} - -const RetainSummary * -RetainSummaryManager::getPersistentSummary(const RetainSummary &OldSumm) { - // Unique "simple" summaries -- those without ArgEffects. - if (OldSumm.isSimple()) { - llvm::FoldingSetNodeID ID; - OldSumm.Profile(ID); - - void *Pos; - CachedSummaryNode *N = SimpleSummaries.FindNodeOrInsertPos(ID, Pos); - - if (!N) { - N = (CachedSummaryNode *) BPAlloc.Allocate<CachedSummaryNode>(); - new (N) CachedSummaryNode(OldSumm); - SimpleSummaries.InsertNode(N, Pos); - } - - return &N->getValue(); - } - - RetainSummary *Summ = (RetainSummary *) BPAlloc.Allocate<RetainSummary>(); - new (Summ) RetainSummary(OldSumm); - return Summ; -} - -//===----------------------------------------------------------------------===// -// Summary creation for functions (largely uses of Core Foundation). -//===----------------------------------------------------------------------===// - -static bool isRetain(const FunctionDecl *FD, StringRef FName) { - return FName.startswith_lower("retain") || FName.endswith_lower("retain"); -} - -static bool isRelease(const FunctionDecl *FD, StringRef FName) { - return FName.startswith_lower("release") || FName.endswith_lower("release"); -} - -static bool isAutorelease(const FunctionDecl *FD, StringRef FName) { - return FName.startswith_lower("autorelease") || - FName.endswith_lower("autorelease"); -} - -static bool isMakeCollectable(const FunctionDecl *FD, StringRef FName) { - // FIXME: Remove FunctionDecl parameter. - // FIXME: Is it really okay if MakeCollectable isn't a suffix? - return FName.find_lower("MakeCollectable") != StringRef::npos; -} - -static ArgEffect getStopTrackingHardEquivalent(ArgEffect E) { - switch (E) { - case DoNothing: - case Autorelease: - case DecRefBridgedTransferred: - case IncRef: - case IncRefMsg: - case MakeCollectable: - case UnretainedOutParameter: - case RetainedOutParameter: - case MayEscape: - case StopTracking: - case StopTrackingHard: - return StopTrackingHard; - case DecRef: - case DecRefAndStopTrackingHard: - return DecRefAndStopTrackingHard; - case DecRefMsg: - case DecRefMsgAndStopTrackingHard: - return DecRefMsgAndStopTrackingHard; - case Dealloc: - return Dealloc; - } - - llvm_unreachable("Unknown ArgEffect kind"); -} - -void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S, - const CallEvent &Call) { - if (Call.hasNonZeroCallbackArg()) { - ArgEffect RecEffect = - getStopTrackingHardEquivalent(S->getReceiverEffect()); - ArgEffect DefEffect = - getStopTrackingHardEquivalent(S->getDefaultArgEffect()); - - ArgEffects CustomArgEffects = S->getArgEffects(); - for (ArgEffects::iterator I = CustomArgEffects.begin(), - E = CustomArgEffects.end(); - I != E; ++I) { - ArgEffect Translated = getStopTrackingHardEquivalent(I->second); - if (Translated != DefEffect) - ScratchArgs = AF.add(ScratchArgs, I->first, Translated); - } - - RetEffect RE = RetEffect::MakeNoRetHard(); - - // Special cases where the callback argument CANNOT free the return value. - // This can generally only happen if we know that the callback will only be - // called when the return value is already being deallocated. - if (const SimpleFunctionCall *FC = dyn_cast<SimpleFunctionCall>(&Call)) { - if (IdentifierInfo *Name = FC->getDecl()->getIdentifier()) { - // When the CGBitmapContext is deallocated, the callback here will free - // the associated data buffer. - // The callback in dispatch_data_create frees the buffer, but not - // the data object. - if (Name->isStr("CGBitmapContextCreateWithData") || - Name->isStr("dispatch_data_create")) - RE = S->getRetEffect(); - } - } - - S = getPersistentSummary(RE, RecEffect, DefEffect); - } - - // Special case '[super init];' and '[self init];' - // - // Even though calling '[super init]' without assigning the result to self - // and checking if the parent returns 'nil' is a bad pattern, it is common. - // Additionally, our Self Init checker already warns about it. To avoid - // overwhelming the user with messages from both checkers, we model the case - // of '[super init]' in cases when it is not consumed by another expression - // as if the call preserves the value of 'self'; essentially, assuming it can - // never fail and return 'nil'. - // Note, we don't want to just stop tracking the value since we want the - // RetainCount checker to report leaks and use-after-free if SelfInit checker - // is turned off. - if (const ObjCMethodCall *MC = dyn_cast<ObjCMethodCall>(&Call)) { - if (MC->getMethodFamily() == OMF_init && MC->isReceiverSelfOrSuper()) { - - // Check if the message is not consumed, we know it will not be used in - // an assignment, ex: "self = [super init]". - const Expr *ME = MC->getOriginExpr(); - const LocationContext *LCtx = MC->getLocationContext(); - ParentMap &PM = LCtx->getAnalysisDeclContext()->getParentMap(); - if (!PM.isConsumedExpr(ME)) { - RetainSummaryTemplate ModifiableSummaryTemplate(S, *this); - ModifiableSummaryTemplate->setReceiverEffect(DoNothing); - ModifiableSummaryTemplate->setRetEffect(RetEffect::MakeNoRet()); - } - } - } -} - -const RetainSummary * -RetainSummaryManager::getSummary(const CallEvent &Call, - ProgramStateRef State) { - const RetainSummary *Summ; - switch (Call.getKind()) { - case CE_Function: - Summ = getFunctionSummary(cast<SimpleFunctionCall>(Call).getDecl()); - break; - case CE_CXXMember: - case CE_CXXMemberOperator: - case CE_Block: - case CE_CXXConstructor: - case CE_CXXDestructor: - case CE_CXXAllocator: - // FIXME: These calls are currently unsupported. - return getPersistentStopSummary(); - case CE_ObjCMessage: { - const ObjCMethodCall &Msg = cast<ObjCMethodCall>(Call); - if (Msg.isInstanceMessage()) - Summ = getInstanceMethodSummary(Msg, State); - else - Summ = getClassMethodSummary(Msg); - break; - } - } - - updateSummaryForCall(Summ, Call); - - assert(Summ && "Unknown call type?"); - return Summ; -} - -const RetainSummary * -RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) { - // If we don't know what function we're calling, use our default summary. - if (!FD) - return getDefaultSummary(); - - // Look up a summary in our cache of FunctionDecls -> Summaries. - FuncSummariesTy::iterator I = FuncSummaries.find(FD); - if (I != FuncSummaries.end()) - return I->second; - - // No summary? Generate one. - const RetainSummary *S = nullptr; - bool AllowAnnotations = true; - - do { - // We generate "stop" summaries for implicitly defined functions. - if (FD->isImplicit()) { - S = getPersistentStopSummary(); - break; - } - - // [PR 3337] Use 'getAs<FunctionType>' to strip away any typedefs on the - // function's type. - const FunctionType* FT = FD->getType()->getAs<FunctionType>(); - const IdentifierInfo *II = FD->getIdentifier(); - if (!II) - break; - - StringRef FName = II->getName(); - - // Strip away preceding '_'. Doing this here will effect all the checks - // down below. - FName = FName.substr(FName.find_first_not_of('_')); - - // Inspect the result type. - QualType RetTy = FT->getReturnType(); - std::string RetTyName = RetTy.getAsString(); - - // FIXME: This should all be refactored into a chain of "summary lookup" - // filters. - assert(ScratchArgs.isEmpty()); - - if (FName == "pthread_create" || FName == "pthread_setspecific") { - // Part of: <rdar://problem/7299394> and <rdar://problem/11282706>. - // This will be addressed better with IPA. - S = getPersistentStopSummary(); - } else if (FName == "NSMakeCollectable") { - // Handle: id NSMakeCollectable(CFTypeRef) - S = (RetTy->isObjCIdType()) - ? getUnarySummary(FT, cfmakecollectable) - : getPersistentStopSummary(); - // The headers on OS X 10.8 use cf_consumed/ns_returns_retained, - // but we can fully model NSMakeCollectable ourselves. - AllowAnnotations = false; - } else if (FName == "CFPlugInInstanceCreate") { - S = getPersistentSummary(RetEffect::MakeNoRet()); - } else if (FName == "IORegistryEntrySearchCFProperty" - || (RetTyName == "CFMutableDictionaryRef" && ( - FName == "IOBSDNameMatching" || - FName == "IOServiceMatching" || - FName == "IOServiceNameMatching" || - FName == "IORegistryEntryIDMatching" || - FName == "IOOpenFirmwarePathMatching" - ))) { - // Part of <rdar://problem/6961230>. (IOKit) - // This should be addressed using a API table. - S = getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF), - DoNothing, DoNothing); - } else if (FName == "IOServiceGetMatchingService" || - FName == "IOServiceGetMatchingServices") { - // FIXES: <rdar://problem/6326900> - // This should be addressed using a API table. This strcmp is also - // a little gross, but there is no need to super optimize here. - ScratchArgs = AF.add(ScratchArgs, 1, DecRef); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } else if (FName == "IOServiceAddNotification" || - FName == "IOServiceAddMatchingNotification") { - // Part of <rdar://problem/6961230>. (IOKit) - // This should be addressed using a API table. - ScratchArgs = AF.add(ScratchArgs, 2, DecRef); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } else if (FName == "CVPixelBufferCreateWithBytes") { - // FIXES: <rdar://problem/7283567> - // Eventually this can be improved by recognizing that the pixel - // buffer passed to CVPixelBufferCreateWithBytes is released via - // a callback and doing full IPA to make sure this is done correctly. - // FIXME: This function has an out parameter that returns an - // allocated object. - ScratchArgs = AF.add(ScratchArgs, 7, StopTracking); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } else if (FName == "CGBitmapContextCreateWithData") { - // FIXES: <rdar://problem/7358899> - // Eventually this can be improved by recognizing that 'releaseInfo' - // passed to CGBitmapContextCreateWithData is released via - // a callback and doing full IPA to make sure this is done correctly. - ScratchArgs = AF.add(ScratchArgs, 8, StopTracking); - S = getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF), - DoNothing, DoNothing); - } else if (FName == "CVPixelBufferCreateWithPlanarBytes") { - // FIXES: <rdar://problem/7283567> - // Eventually this can be improved by recognizing that the pixel - // buffer passed to CVPixelBufferCreateWithPlanarBytes is released - // via a callback and doing full IPA to make sure this is done - // correctly. - ScratchArgs = AF.add(ScratchArgs, 12, StopTracking); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } else if (FName == "VTCompressionSessionEncodeFrame") { - // The context argument passed to VTCompressionSessionEncodeFrame() - // is passed to the callback specified when creating the session - // (e.g. with VTCompressionSessionCreate()) which can release it. - // To account for this possibility, conservatively stop tracking - // the context. - ScratchArgs = AF.add(ScratchArgs, 5, StopTracking); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } else if (FName == "dispatch_set_context" || - FName == "xpc_connection_set_context") { - // <rdar://problem/11059275> - The analyzer currently doesn't have - // a good way to reason about the finalizer function for libdispatch. - // If we pass a context object that is memory managed, stop tracking it. - // <rdar://problem/13783514> - Same problem, but for XPC. - // FIXME: this hack should possibly go away once we can handle - // libdispatch and XPC finalizers. - ScratchArgs = AF.add(ScratchArgs, 1, StopTracking); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } else if (FName.startswith("NSLog")) { - S = getDoNothingSummary(); - } else if (FName.startswith("NS") && - (FName.find("Insert") != StringRef::npos)) { - // Whitelist NSXXInsertXX, for example NSMapInsertIfAbsent, since they can - // be deallocated by NSMapRemove. (radar://11152419) - ScratchArgs = AF.add(ScratchArgs, 1, StopTracking); - ScratchArgs = AF.add(ScratchArgs, 2, StopTracking); - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); - } - - // Did we get a summary? - if (S) - break; - - if (RetTy->isPointerType()) { - // For CoreFoundation ('CF') types. - if (cocoa::isRefType(RetTy, "CF", FName)) { - if (isRetain(FD, FName)) { - S = getUnarySummary(FT, cfretain); - // CFRetain isn't supposed to be annotated. However, this may as well - // be a user-made "safe" CFRetain function that is incorrectly - // annotated as cf_returns_retained due to lack of better options. - // We want to ignore such annotation. - AllowAnnotations = false; - } else if (isAutorelease(FD, FName)) { - S = getUnarySummary(FT, cfautorelease); - // The headers use cf_consumed, but we can fully model CFAutorelease - // ourselves. - AllowAnnotations = false; - } else if (isMakeCollectable(FD, FName)) { - S = getUnarySummary(FT, cfmakecollectable); - AllowAnnotations = false; - } else { - S = getCFCreateGetRuleSummary(FD); - } - - break; - } - - // For CoreGraphics ('CG') and CoreVideo ('CV') types. - if (cocoa::isRefType(RetTy, "CG", FName) || - cocoa::isRefType(RetTy, "CV", FName)) { - if (isRetain(FD, FName)) - S = getUnarySummary(FT, cfretain); - else - S = getCFCreateGetRuleSummary(FD); - - break; - } - - // For all other CF-style types, use the Create/Get - // rule for summaries but don't support Retain functions - // with framework-specific prefixes. - if (coreFoundation::isCFObjectRef(RetTy)) { - S = getCFCreateGetRuleSummary(FD); - break; - } - - if (FD->hasAttr<CFAuditedTransferAttr>()) { - S = getCFCreateGetRuleSummary(FD); - break; - } - - break; - } - - // Check for release functions, the only kind of functions that we care - // about that don't return a pointer type. - if (FName.size() >= 2 && - FName[0] == 'C' && (FName[1] == 'F' || FName[1] == 'G')) { - // Test for 'CGCF'. - FName = FName.substr(FName.startswith("CGCF") ? 4 : 2); - - if (isRelease(FD, FName)) - S = getUnarySummary(FT, cfrelease); - else { - assert (ScratchArgs.isEmpty()); - // Remaining CoreFoundation and CoreGraphics functions. - // We use to assume that they all strictly followed the ownership idiom - // and that ownership cannot be transferred. While this is technically - // correct, many methods allow a tracked object to escape. For example: - // - // CFMutableDictionaryRef x = CFDictionaryCreateMutable(...); - // CFDictionaryAddValue(y, key, x); - // CFRelease(x); - // ... it is okay to use 'x' since 'y' has a reference to it - // - // We handle this and similar cases with the follow heuristic. If the - // function name contains "InsertValue", "SetValue", "AddValue", - // "AppendValue", or "SetAttribute", then we assume that arguments may - // "escape." This means that something else holds on to the object, - // allowing it be used even after its local retain count drops to 0. - ArgEffect E = (StrInStrNoCase(FName, "InsertValue") != StringRef::npos|| - StrInStrNoCase(FName, "AddValue") != StringRef::npos || - StrInStrNoCase(FName, "SetValue") != StringRef::npos || - StrInStrNoCase(FName, "AppendValue") != StringRef::npos|| - StrInStrNoCase(FName, "SetAttribute") != StringRef::npos) - ? MayEscape : DoNothing; - - S = getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, E); - } - } - } - while (0); - - // If we got all the way here without any luck, use a default summary. - if (!S) - S = getDefaultSummary(); - - // Annotations override defaults. - if (AllowAnnotations) - updateSummaryFromAnnotations(S, FD); - - FuncSummaries[FD] = S; - return S; -} - -const RetainSummary * -RetainSummaryManager::getCFCreateGetRuleSummary(const FunctionDecl *FD) { - if (coreFoundation::followsCreateRule(FD)) - return getCFSummaryCreateRule(FD); - - return getCFSummaryGetRule(FD); -} - -const RetainSummary * -RetainSummaryManager::getUnarySummary(const FunctionType* FT, - UnaryFuncKind func) { - - // Sanity check that this is *really* a unary function. This can - // happen if people do weird things. - const FunctionProtoType* FTP = dyn_cast<FunctionProtoType>(FT); - if (!FTP || FTP->getNumParams() != 1) - return getPersistentStopSummary(); - - assert (ScratchArgs.isEmpty()); - - ArgEffect Effect; - switch (func) { - case cfretain: Effect = IncRef; break; - case cfrelease: Effect = DecRef; break; - case cfautorelease: Effect = Autorelease; break; - case cfmakecollectable: Effect = MakeCollectable; break; - } - - ScratchArgs = AF.add(ScratchArgs, 0, Effect); - return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); -} - -const RetainSummary * -RetainSummaryManager::getCFSummaryCreateRule(const FunctionDecl *FD) { - assert (ScratchArgs.isEmpty()); - - return getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF)); -} - -const RetainSummary * -RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) { - assert (ScratchArgs.isEmpty()); - return getPersistentSummary(RetEffect::MakeNotOwned(RetEffect::CF), - DoNothing, DoNothing); -} - -/// Returns true if the declaration 'D' is annotated with 'rcAnnotation'. -static bool hasRCAnnotation(const Decl *D, StringRef rcAnnotation) { - for (const auto *Ann : D->specific_attrs<AnnotateAttr>()) { - if (Ann->getAnnotation() == rcAnnotation) - return true; - } - return false; -} - -/// Returns true if the function declaration 'FD' contains -/// 'rc_ownership_trusted_implementation' annotate attribute. -static bool isTrustedReferenceCountImplementation(const FunctionDecl *FD) { - return hasRCAnnotation(FD, "rc_ownership_trusted_implementation"); -} - -static bool isGeneralizedObjectRef(QualType Ty) { - if (Ty.getAsString().substr(0, 4) == "isl_") - return true; - else - return false; -} - -//===----------------------------------------------------------------------===// -// Summary creation for Selectors. -//===----------------------------------------------------------------------===// - -Optional<RetEffect> -RetainSummaryManager::getRetEffectFromAnnotations(QualType RetTy, - const Decl *D) { - if (cocoa::isCocoaObjectRef(RetTy)) { - if (D->hasAttr<NSReturnsRetainedAttr>()) - return ObjCAllocRetE; - - if (D->hasAttr<NSReturnsNotRetainedAttr>() || - D->hasAttr<NSReturnsAutoreleasedAttr>()) - return RetEffect::MakeNotOwned(RetEffect::ObjC); - - } else if (!RetTy->isPointerType()) { - return None; - } - - if (D->hasAttr<CFReturnsRetainedAttr>()) - return RetEffect::MakeOwned(RetEffect::CF); - else if (hasRCAnnotation(D, "rc_ownership_returns_retained")) - return RetEffect::MakeOwned(RetEffect::Generalized); - - if (D->hasAttr<CFReturnsNotRetainedAttr>()) - return RetEffect::MakeNotOwned(RetEffect::CF); - - return None; -} - -void -RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, - const FunctionDecl *FD) { - if (!FD) - return; - - assert(Summ && "Must have a summary to add annotations to."); - RetainSummaryTemplate Template(Summ, *this); - - // Effects on the parameters. - unsigned parm_idx = 0; - for (FunctionDecl::param_const_iterator pi = FD->param_begin(), - pe = FD->param_end(); pi != pe; ++pi, ++parm_idx) { - const ParmVarDecl *pd = *pi; - if (pd->hasAttr<NSConsumedAttr>()) - Template->addArg(AF, parm_idx, DecRefMsg); - else if (pd->hasAttr<CFConsumedAttr>() || - hasRCAnnotation(pd, "rc_ownership_consumed")) - Template->addArg(AF, parm_idx, DecRef); - else if (pd->hasAttr<CFReturnsRetainedAttr>() || - hasRCAnnotation(pd, "rc_ownership_returns_retained")) { - QualType PointeeTy = pd->getType()->getPointeeType(); - if (!PointeeTy.isNull()) - if (coreFoundation::isCFObjectRef(PointeeTy)) - Template->addArg(AF, parm_idx, RetainedOutParameter); - } else if (pd->hasAttr<CFReturnsNotRetainedAttr>()) { - QualType PointeeTy = pd->getType()->getPointeeType(); - if (!PointeeTy.isNull()) - if (coreFoundation::isCFObjectRef(PointeeTy)) - Template->addArg(AF, parm_idx, UnretainedOutParameter); - } - } - - QualType RetTy = FD->getReturnType(); - if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, FD)) - Template->setRetEffect(*RetE); -} - -void -RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, - const ObjCMethodDecl *MD) { - if (!MD) - return; - - assert(Summ && "Must have a valid summary to add annotations to"); - RetainSummaryTemplate Template(Summ, *this); - - // Effects on the receiver. - if (MD->hasAttr<NSConsumesSelfAttr>()) - Template->setReceiverEffect(DecRefMsg); - - // Effects on the parameters. - unsigned parm_idx = 0; - for (ObjCMethodDecl::param_const_iterator - pi=MD->param_begin(), pe=MD->param_end(); - pi != pe; ++pi, ++parm_idx) { - const ParmVarDecl *pd = *pi; - if (pd->hasAttr<NSConsumedAttr>()) - Template->addArg(AF, parm_idx, DecRefMsg); - else if (pd->hasAttr<CFConsumedAttr>()) { - Template->addArg(AF, parm_idx, DecRef); - } else if (pd->hasAttr<CFReturnsRetainedAttr>()) { - QualType PointeeTy = pd->getType()->getPointeeType(); - if (!PointeeTy.isNull()) - if (coreFoundation::isCFObjectRef(PointeeTy)) - Template->addArg(AF, parm_idx, RetainedOutParameter); - } else if (pd->hasAttr<CFReturnsNotRetainedAttr>()) { - QualType PointeeTy = pd->getType()->getPointeeType(); - if (!PointeeTy.isNull()) - if (coreFoundation::isCFObjectRef(PointeeTy)) - Template->addArg(AF, parm_idx, UnretainedOutParameter); - } - } - - QualType RetTy = MD->getReturnType(); - if (Optional<RetEffect> RetE = getRetEffectFromAnnotations(RetTy, MD)) - Template->setRetEffect(*RetE); -} - -const RetainSummary * -RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD, - Selector S, QualType RetTy) { - // Any special effects? - ArgEffect ReceiverEff = DoNothing; - RetEffect ResultEff = RetEffect::MakeNoRet(); - - // Check the method family, and apply any default annotations. - switch (MD ? MD->getMethodFamily() : S.getMethodFamily()) { - case OMF_None: - case OMF_initialize: - case OMF_performSelector: - // Assume all Objective-C methods follow Cocoa Memory Management rules. - // FIXME: Does the non-threaded performSelector family really belong here? - // The selector could be, say, @selector(copy). - if (cocoa::isCocoaObjectRef(RetTy)) - ResultEff = RetEffect::MakeNotOwned(RetEffect::ObjC); - else if (coreFoundation::isCFObjectRef(RetTy)) { - // ObjCMethodDecl currently doesn't consider CF objects as valid return - // values for alloc, new, copy, or mutableCopy, so we have to - // double-check with the selector. This is ugly, but there aren't that - // many Objective-C methods that return CF objects, right? - if (MD) { - switch (S.getMethodFamily()) { - case OMF_alloc: - case OMF_new: - case OMF_copy: - case OMF_mutableCopy: - ResultEff = RetEffect::MakeOwned(RetEffect::CF); - break; - default: - ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); - break; - } - } else { - ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); - } - } - break; - case OMF_init: - ResultEff = ObjCInitRetE; - ReceiverEff = DecRefMsg; - break; - case OMF_alloc: - case OMF_new: - case OMF_copy: - case OMF_mutableCopy: - if (cocoa::isCocoaObjectRef(RetTy)) - ResultEff = ObjCAllocRetE; - else if (coreFoundation::isCFObjectRef(RetTy)) - ResultEff = RetEffect::MakeOwned(RetEffect::CF); - break; - case OMF_autorelease: - ReceiverEff = Autorelease; - break; - case OMF_retain: - ReceiverEff = IncRefMsg; - break; - case OMF_release: - ReceiverEff = DecRefMsg; - break; - case OMF_dealloc: - ReceiverEff = Dealloc; - break; - case OMF_self: - // -self is handled specially by the ExprEngine to propagate the receiver. - break; - case OMF_retainCount: - case OMF_finalize: - // These methods don't return objects. - break; - } - - // If one of the arguments in the selector has the keyword 'delegate' we - // should stop tracking the reference count for the receiver. This is - // because the reference count is quite possibly handled by a delegate - // method. - if (S.isKeywordSelector()) { - for (unsigned i = 0, e = S.getNumArgs(); i != e; ++i) { - StringRef Slot = S.getNameForSlot(i); - if (Slot.substr(Slot.size() - 8).equals_lower("delegate")) { - if (ResultEff == ObjCInitRetE) - ResultEff = RetEffect::MakeNoRetHard(); - else - ReceiverEff = StopTrackingHard; - } - } - } - - if (ScratchArgs.isEmpty() && ReceiverEff == DoNothing && - ResultEff.getKind() == RetEffect::NoRet) - return getDefaultSummary(); - - return getPersistentSummary(ResultEff, ReceiverEff, MayEscape); -} - -const RetainSummary * -RetainSummaryManager::getInstanceMethodSummary(const ObjCMethodCall &Msg, - ProgramStateRef State) { - const ObjCInterfaceDecl *ReceiverClass = nullptr; - - // We do better tracking of the type of the object than the core ExprEngine. - // See if we have its type in our private state. - // FIXME: Eventually replace the use of state->get<RefBindings> with - // a generic API for reasoning about the Objective-C types of symbolic - // objects. - SVal ReceiverV = Msg.getReceiverSVal(); - if (SymbolRef Sym = ReceiverV.getAsLocSymbol()) - if (const RefVal *T = getRefBinding(State, Sym)) - if (const ObjCObjectPointerType *PT = - T->getType()->getAs<ObjCObjectPointerType>()) - ReceiverClass = PT->getInterfaceDecl(); - - // If we don't know what kind of object this is, fall back to its static type. - if (!ReceiverClass) - ReceiverClass = Msg.getReceiverInterface(); - - // FIXME: The receiver could be a reference to a class, meaning that - // we should use the class method. - // id x = [NSObject class]; - // [x performSelector:... withObject:... afterDelay:...]; - Selector S = Msg.getSelector(); - const ObjCMethodDecl *Method = Msg.getDecl(); - if (!Method && ReceiverClass) - Method = ReceiverClass->getInstanceMethod(S); - - return getMethodSummary(S, ReceiverClass, Method, Msg.getResultType(), - ObjCMethodSummaries); -} - -const RetainSummary * -RetainSummaryManager::getMethodSummary(Selector S, const ObjCInterfaceDecl *ID, - const ObjCMethodDecl *MD, QualType RetTy, - ObjCMethodSummariesTy &CachedSummaries) { - - // Look up a summary in our summary cache. - const RetainSummary *Summ = CachedSummaries.find(ID, S); - - if (!Summ) { - Summ = getStandardMethodSummary(MD, S, RetTy); - - // Annotations override defaults. - updateSummaryFromAnnotations(Summ, MD); - - // Memoize the summary. - CachedSummaries[ObjCSummaryKey(ID, S)] = Summ; - } - - return Summ; -} - -void RetainSummaryManager::InitializeClassMethodSummaries() { - assert(ScratchArgs.isEmpty()); - // Create the [NSAssertionHandler currentHander] summary. - addClassMethSummary("NSAssertionHandler", "currentHandler", - getPersistentSummary(RetEffect::MakeNotOwned(RetEffect::ObjC))); - - // Create the [NSAutoreleasePool addObject:] summary. - ScratchArgs = AF.add(ScratchArgs, 0, Autorelease); - addClassMethSummary("NSAutoreleasePool", "addObject", - getPersistentSummary(RetEffect::MakeNoRet(), - DoNothing, Autorelease)); -} - -void RetainSummaryManager::InitializeMethodSummaries() { - - assert (ScratchArgs.isEmpty()); - - // Create the "init" selector. It just acts as a pass-through for the - // receiver. - const RetainSummary *InitSumm = getPersistentSummary(ObjCInitRetE, DecRefMsg); - addNSObjectMethSummary(GetNullarySelector("init", Ctx), InitSumm); - - // awakeAfterUsingCoder: behaves basically like an 'init' method. It - // claims the receiver and returns a retained object. - addNSObjectMethSummary(GetUnarySelector("awakeAfterUsingCoder", Ctx), - InitSumm); - - // The next methods are allocators. - const RetainSummary *AllocSumm = getPersistentSummary(ObjCAllocRetE); - const RetainSummary *CFAllocSumm = - getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF)); - - // Create the "retain" selector. - RetEffect NoRet = RetEffect::MakeNoRet(); - const RetainSummary *Summ = getPersistentSummary(NoRet, IncRefMsg); - addNSObjectMethSummary(GetNullarySelector("retain", Ctx), Summ); - - // Create the "release" selector. - Summ = getPersistentSummary(NoRet, DecRefMsg); - addNSObjectMethSummary(GetNullarySelector("release", Ctx), Summ); - - // Create the -dealloc summary. - Summ = getPersistentSummary(NoRet, Dealloc); - addNSObjectMethSummary(GetNullarySelector("dealloc", Ctx), Summ); - - // Create the "autorelease" selector. - Summ = getPersistentSummary(NoRet, Autorelease); - addNSObjectMethSummary(GetNullarySelector("autorelease", Ctx), Summ); - - // For NSWindow, allocated objects are (initially) self-owned. - // FIXME: For now we opt for false negatives with NSWindow, as these objects - // self-own themselves. However, they only do this once they are displayed. - // Thus, we need to track an NSWindow's display status. - // This is tracked in <rdar://problem/6062711>. - // See also http://llvm.org/bugs/show_bug.cgi?id=3714. - const RetainSummary *NoTrackYet = getPersistentSummary(RetEffect::MakeNoRet(), - StopTracking, - StopTracking); - - addClassMethSummary("NSWindow", "alloc", NoTrackYet); - - // For NSPanel (which subclasses NSWindow), allocated objects are not - // self-owned. - // FIXME: For now we don't track NSPanels. object for the same reason - // as for NSWindow objects. - addClassMethSummary("NSPanel", "alloc", NoTrackYet); - - // For NSNull, objects returned by +null are singletons that ignore - // retain/release semantics. Just don't track them. - // <rdar://problem/12858915> - addClassMethSummary("NSNull", "null", NoTrackYet); - - // Don't track allocated autorelease pools, as it is okay to prematurely - // exit a method. - addClassMethSummary("NSAutoreleasePool", "alloc", NoTrackYet); - addClassMethSummary("NSAutoreleasePool", "allocWithZone", NoTrackYet, false); - addClassMethSummary("NSAutoreleasePool", "new", NoTrackYet); - - // Create summaries QCRenderer/QCView -createSnapShotImageOfType: - addInstMethSummary("QCRenderer", AllocSumm, "createSnapshotImageOfType"); - addInstMethSummary("QCView", AllocSumm, "createSnapshotImageOfType"); - - // Create summaries for CIContext, 'createCGImage' and - // 'createCGLayerWithSize'. These objects are CF objects, and are not - // automatically garbage collected. - addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect"); - addInstMethSummary("CIContext", CFAllocSumm, "createCGImage", "fromRect", - "format", "colorSpace"); - addInstMethSummary("CIContext", CFAllocSumm, "createCGLayerWithSize", "info"); -} - -//===----------------------------------------------------------------------===// -// Error reporting. -//===----------------------------------------------------------------------===// -namespace { - typedef llvm::DenseMap<const ExplodedNode *, const RetainSummary *> - SummaryLogTy; - - //===-------------===// - // Bug Descriptions. // - //===-------------===// - - class CFRefBug : public BugType { - protected: - CFRefBug(const CheckerBase *checker, StringRef name) - : BugType(checker, name, categories::MemoryCoreFoundationObjectiveC) {} - - public: - - // FIXME: Eventually remove. - virtual const char *getDescription() const = 0; - - virtual bool isLeak() const { return false; } - }; - - class UseAfterRelease : public CFRefBug { - public: - UseAfterRelease(const CheckerBase *checker) - : CFRefBug(checker, "Use-after-release") {} - - const char *getDescription() const override { - return "Reference-counted object is used after it is released"; - } - }; - - class BadRelease : public CFRefBug { - public: - BadRelease(const CheckerBase *checker) : CFRefBug(checker, "Bad release") {} - - const char *getDescription() const override { - return "Incorrect decrement of the reference count of an object that is " - "not owned at this point by the caller"; - } - }; - - class DeallocGC : public CFRefBug { - public: - DeallocGC(const CheckerBase *checker) - : CFRefBug(checker, "-dealloc called while using garbage collection") {} - - const char *getDescription() const override { - return "-dealloc called while using garbage collection"; - } - }; - - class DeallocNotOwned : public CFRefBug { - public: - DeallocNotOwned(const CheckerBase *checker) - : CFRefBug(checker, "-dealloc sent to non-exclusively owned object") {} - - const char *getDescription() const override { - return "-dealloc sent to object that may be referenced elsewhere"; - } - }; - - class OverAutorelease : public CFRefBug { - public: - OverAutorelease(const CheckerBase *checker) - : CFRefBug(checker, "Object autoreleased too many times") {} - - const char *getDescription() const override { - return "Object autoreleased too many times"; - } - }; - - class ReturnedNotOwnedForOwned : public CFRefBug { - public: - ReturnedNotOwnedForOwned(const CheckerBase *checker) - : CFRefBug(checker, "Method should return an owned object") {} - - const char *getDescription() const override { - return "Object with a +0 retain count returned to caller where a +1 " - "(owning) retain count is expected"; - } - }; - - class Leak : public CFRefBug { - public: - Leak(const CheckerBase *checker, StringRef name) : CFRefBug(checker, name) { - // Leaks should not be reported if they are post-dominated by a sink. - setSuppressOnSink(true); - } - - const char *getDescription() const override { return ""; } - - bool isLeak() const override { return true; } - }; - - //===---------===// - // Bug Reports. // - //===---------===// - class CFRefReportVisitor : public BugReporterVisitor { - protected: - SymbolRef Sym; - const SummaryLogTy &SummaryLog; - bool GCEnabled; - - public: - CFRefReportVisitor(SymbolRef sym, bool gcEnabled, const SummaryLogTy &log) - : Sym(sym), SummaryLog(log), GCEnabled(gcEnabled) {} - - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int x = 0; - ID.AddPointer(&x); - ID.AddPointer(Sym); - } - - std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, - const ExplodedNode *PrevN, - BugReporterContext &BRC, - BugReport &BR) override; - - std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override; - }; - - class CFRefLeakReportVisitor : public CFRefReportVisitor { - public: - CFRefLeakReportVisitor(SymbolRef sym, bool GCEnabled, - const SummaryLogTy &log) - : CFRefReportVisitor(sym, GCEnabled, log) {} - - std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override; - }; - - class CFRefReport : public BugReport { - void addGCModeDescription(const LangOptions &LOpts, bool GCEnabled); - - public: - CFRefReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled, - const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym, - bool registerVisitor = true) - : BugReport(D, D.getDescription(), n) { - if (registerVisitor) - addVisitor(llvm::make_unique<CFRefReportVisitor>(sym, GCEnabled, Log)); - addGCModeDescription(LOpts, GCEnabled); - } - - CFRefReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled, - const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym, - StringRef endText) - : BugReport(D, D.getDescription(), endText, n) { - addVisitor(llvm::make_unique<CFRefReportVisitor>(sym, GCEnabled, Log)); - addGCModeDescription(LOpts, GCEnabled); - } - - llvm::iterator_range<ranges_iterator> getRanges() override { - const CFRefBug& BugTy = static_cast<CFRefBug&>(getBugType()); - if (!BugTy.isLeak()) - return BugReport::getRanges(); - return llvm::make_range(ranges_iterator(), ranges_iterator()); - } - }; - - class CFRefLeakReport : public CFRefReport { - const MemRegion* AllocBinding; - const Stmt *AllocStmt; - - // Finds the function declaration where a leak warning for the parameter 'sym' should be raised. - void deriveParamLocation(CheckerContext &Ctx, SymbolRef sym); - // Finds the location where a leak warning for 'sym' should be raised. - void deriveAllocLocation(CheckerContext &Ctx, SymbolRef sym); - // Produces description of a leak warning which is printed on the console. - void createDescription(CheckerContext &Ctx, bool GCEnabled, bool IncludeAllocationLine); - - public: - CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, bool GCEnabled, - const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym, - CheckerContext &Ctx, - bool IncludeAllocationLine); - - PathDiagnosticLocation getLocation(const SourceManager &SM) const override { - assert(Location.isValid()); - return Location; - } - }; -} // end anonymous namespace - -void CFRefReport::addGCModeDescription(const LangOptions &LOpts, - bool GCEnabled) { - const char *GCModeDescription = nullptr; - - switch (LOpts.getGC()) { - case LangOptions::GCOnly: - assert(GCEnabled); - GCModeDescription = "Code is compiled to only use garbage collection"; - break; - - case LangOptions::NonGC: - assert(!GCEnabled); - GCModeDescription = "Code is compiled to use reference counts"; - break; - - case LangOptions::HybridGC: - if (GCEnabled) { - GCModeDescription = "Code is compiled to use either garbage collection " - "(GC) or reference counts (non-GC). The bug occurs " - "with GC enabled"; - break; - } else { - GCModeDescription = "Code is compiled to use either garbage collection " - "(GC) or reference counts (non-GC). The bug occurs " - "in non-GC mode"; - break; - } - } - - assert(GCModeDescription && "invalid/unknown GC mode"); - addExtraText(GCModeDescription); -} - -static bool isNumericLiteralExpression(const Expr *E) { - // FIXME: This set of cases was copied from SemaExprObjC. - return isa<IntegerLiteral>(E) || - isa<CharacterLiteral>(E) || - isa<FloatingLiteral>(E) || - isa<ObjCBoolLiteralExpr>(E) || - isa<CXXBoolLiteralExpr>(E); -} - -static Optional<std::string> describeRegion(const MemRegion *MR) { - if (const auto *VR = dyn_cast_or_null<VarRegion>(MR)) - return std::string(VR->getDecl()->getName()); - // Once we support more storage locations for bindings, - // this would need to be improved. - return None; -} - -/// Returns true if this stack frame is for an Objective-C method that is a -/// property getter or setter whose body has been synthesized by the analyzer. -static bool isSynthesizedAccessor(const StackFrameContext *SFC) { - auto Method = dyn_cast_or_null<ObjCMethodDecl>(SFC->getDecl()); - if (!Method || !Method->isPropertyAccessor()) - return false; - - return SFC->getAnalysisDeclContext()->isBodyAutosynthesized(); -} - -std::shared_ptr<PathDiagnosticPiece> -CFRefReportVisitor::VisitNode(const ExplodedNode *N, const ExplodedNode *PrevN, - BugReporterContext &BRC, BugReport &BR) { - // FIXME: We will eventually need to handle non-statement-based events - // (__attribute__((cleanup))). - if (!N->getLocation().getAs<StmtPoint>()) - return nullptr; - - // Check if the type state has changed. - ProgramStateRef PrevSt = PrevN->getState(); - ProgramStateRef CurrSt = N->getState(); - const LocationContext *LCtx = N->getLocationContext(); - - const RefVal* CurrT = getRefBinding(CurrSt, Sym); - if (!CurrT) return nullptr; - - const RefVal &CurrV = *CurrT; - const RefVal *PrevT = getRefBinding(PrevSt, Sym); - - // Create a string buffer to constain all the useful things we want - // to tell the user. - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - - // This is the allocation site since the previous node had no bindings - // for this symbol. - if (!PrevT) { - const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); - - if (isa<ObjCIvarRefExpr>(S) && - isSynthesizedAccessor(LCtx->getStackFrame())) { - S = LCtx->getStackFrame()->getCallSite(); - } - - if (isa<ObjCArrayLiteral>(S)) { - os << "NSArray literal is an object with a +0 retain count"; - } - else if (isa<ObjCDictionaryLiteral>(S)) { - os << "NSDictionary literal is an object with a +0 retain count"; - } - else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) { - if (isNumericLiteralExpression(BL->getSubExpr())) - os << "NSNumber literal is an object with a +0 retain count"; - else { - const ObjCInterfaceDecl *BoxClass = nullptr; - if (const ObjCMethodDecl *Method = BL->getBoxingMethod()) - BoxClass = Method->getClassInterface(); - - // We should always be able to find the boxing class interface, - // but consider this future-proofing. - if (BoxClass) - os << *BoxClass << " b"; - else - os << "B"; - - os << "oxed expression produces an object with a +0 retain count"; - } - } - else if (isa<ObjCIvarRefExpr>(S)) { - os << "Object loaded from instance variable"; - } - else { - if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { - // Get the name of the callee (if it is available). - SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx); - if (const FunctionDecl *FD = X.getAsFunctionDecl()) - os << "Call to function '" << *FD << '\''; - else - os << "function call"; - } - else { - assert(isa<ObjCMessageExpr>(S)); - CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager(); - CallEventRef<ObjCMethodCall> Call - = Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx); - - switch (Call->getMessageKind()) { - case OCM_Message: - os << "Method"; - break; - case OCM_PropertyAccess: - os << "Property"; - break; - case OCM_Subscript: - os << "Subscript"; - break; - } - } - - if (CurrV.getObjKind() == RetEffect::CF) { - os << " returns a Core Foundation object of type " - << Sym->getType().getAsString() << " with a "; - } else if (CurrV.getObjKind() == RetEffect::Generalized) { - os << " returns an object of type " << Sym->getType().getAsString() - << " with a "; - } else { - assert (CurrV.getObjKind() == RetEffect::ObjC); - QualType T = Sym->getType(); - if (!isa<ObjCObjectPointerType>(T)) { - os << " returns an Objective-C object with a "; - } else { - const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T); - os << " returns an instance of " - << PT->getPointeeType().getAsString() << " with a "; - } - } - - if (CurrV.isOwned()) { - os << "+1 retain count"; - - if (GCEnabled) { - assert(CurrV.getObjKind() == RetEffect::CF); - os << ". " - "Core Foundation objects are not automatically garbage collected."; - } - } - else { - assert (CurrV.isNotOwned()); - os << "+0 retain count"; - } - } - - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); - } - - // Gather up the effects that were performed on the object at this - // program point - SmallVector<ArgEffect, 2> AEffects; - - const ExplodedNode *OrigNode = BRC.getNodeResolver().getOriginalNode(N); - if (const RetainSummary *Summ = SummaryLog.lookup(OrigNode)) { - // We only have summaries attached to nodes after evaluating CallExpr and - // ObjCMessageExprs. - const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); - - if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { - // Iterate through the parameter expressions and see if the symbol - // was ever passed as an argument. - unsigned i = 0; - - for (CallExpr::const_arg_iterator AI=CE->arg_begin(), AE=CE->arg_end(); - AI!=AE; ++AI, ++i) { - - // Retrieve the value of the argument. Is it the symbol - // we are interested in? - if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym) - continue; - - // We have an argument. Get the effect! - AEffects.push_back(Summ->getArg(i)); - } - } - else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) { - if (const Expr *receiver = ME->getInstanceReceiver()) - if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx) - .getAsLocSymbol() == Sym) { - // The symbol we are tracking is the receiver. - AEffects.push_back(Summ->getReceiverEffect()); - } - } - } - - do { - // Get the previous type state. - RefVal PrevV = *PrevT; - - // Specially handle -dealloc. - if (!GCEnabled && std::find(AEffects.begin(), AEffects.end(), Dealloc) != - AEffects.end()) { - // Determine if the object's reference count was pushed to zero. - assert(!PrevV.hasSameState(CurrV) && "The state should have changed."); - // We may not have transitioned to 'release' if we hit an error. - // This case is handled elsewhere. - if (CurrV.getKind() == RefVal::Released) { - assert(CurrV.getCombinedCounts() == 0); - os << "Object released by directly sending the '-dealloc' message"; - break; - } - } - - // Specially handle CFMakeCollectable and friends. - if (std::find(AEffects.begin(), AEffects.end(), MakeCollectable) != - AEffects.end()) { - // Get the name of the function. - const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); - SVal X = - CurrSt->getSValAsScalarOrLoc(cast<CallExpr>(S)->getCallee(), LCtx); - const FunctionDecl *FD = X.getAsFunctionDecl(); - - if (GCEnabled) { - // Determine if the object's reference count was pushed to zero. - assert(!PrevV.hasSameState(CurrV) && "The state should have changed."); - - os << "In GC mode a call to '" << *FD - << "' decrements an object's retain count and registers the " - "object with the garbage collector. "; - - if (CurrV.getKind() == RefVal::Released) { - assert(CurrV.getCount() == 0); - os << "Since it now has a 0 retain count the object can be " - "automatically collected by the garbage collector."; - } - else - os << "An object must have a 0 retain count to be garbage collected. " - "After this call its retain count is +" << CurrV.getCount() - << '.'; - } - else - os << "When GC is not enabled a call to '" << *FD - << "' has no effect on its argument."; - - // Nothing more to say. - break; - } - - // Determine if the typestate has changed. - if (!PrevV.hasSameState(CurrV)) - switch (CurrV.getKind()) { - case RefVal::Owned: - case RefVal::NotOwned: - if (PrevV.getCount() == CurrV.getCount()) { - // Did an autorelease message get sent? - if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount()) - return nullptr; - - assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount()); - os << "Object autoreleased"; - break; - } - - if (PrevV.getCount() > CurrV.getCount()) - os << "Reference count decremented."; - else - os << "Reference count incremented."; - - if (unsigned Count = CurrV.getCount()) - os << " The object now has a +" << Count << " retain count."; - - if (PrevV.getKind() == RefVal::Released) { - assert(GCEnabled && CurrV.getCount() > 0); - os << " The object is not eligible for garbage collection until " - "the retain count reaches 0 again."; - } - - break; - - case RefVal::Released: - if (CurrV.getIvarAccessHistory() == - RefVal::IvarAccessHistory::ReleasedAfterDirectAccess && - CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) { - os << "Strong instance variable relinquished. "; - } - os << "Object released."; - break; - - case RefVal::ReturnedOwned: - // Autoreleases can be applied after marking a node ReturnedOwned. - if (CurrV.getAutoreleaseCount()) - return nullptr; - - os << "Object returned to caller as an owning reference (single " - "retain count transferred to caller)"; - break; - - case RefVal::ReturnedNotOwned: - os << "Object returned to caller with a +0 retain count"; - break; - - default: - return nullptr; - } - - // Emit any remaining diagnostics for the argument effects (if any). - for (SmallVectorImpl<ArgEffect>::iterator I=AEffects.begin(), - E=AEffects.end(); I != E; ++I) { - - // A bunch of things have alternate behavior under GC. - if (GCEnabled) - switch (*I) { - default: break; - case Autorelease: - os << "In GC mode an 'autorelease' has no effect."; - continue; - case IncRefMsg: - os << "In GC mode the 'retain' message has no effect."; - continue; - case DecRefMsg: - os << "In GC mode the 'release' message has no effect."; - continue; - } - } - } while (0); - - if (os.str().empty()) - return nullptr; // We have nothing to say! - - const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); - - // Add the range by scanning the children of the statement for any bindings - // to Sym. - for (const Stmt *Child : S->children()) - if (const Expr *Exp = dyn_cast_or_null<Expr>(Child)) - if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) { - P->addRange(Exp->getSourceRange()); - break; - } - - return std::move(P); -} - -namespace { -// Find the first node in the current function context that referred to the -// tracked symbol and the memory location that value was stored to. Note, the -// value is only reported if the allocation occurred in the same function as -// the leak. The function can also return a location context, which should be -// treated as interesting. -struct AllocationInfo { - const ExplodedNode* N; - const MemRegion *R; - const LocationContext *InterestingMethodContext; - AllocationInfo(const ExplodedNode *InN, - const MemRegion *InR, - const LocationContext *InInterestingMethodContext) : - N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {} -}; -} // end anonymous namespace - -static AllocationInfo -GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, - SymbolRef Sym) { - const ExplodedNode *AllocationNode = N; - const ExplodedNode *AllocationNodeInCurrentOrParentContext = N; - const MemRegion *FirstBinding = nullptr; - const LocationContext *LeakContext = N->getLocationContext(); - - // The location context of the init method called on the leaked object, if - // available. - const LocationContext *InitMethodContext = nullptr; - - while (N) { - ProgramStateRef St = N->getState(); - const LocationContext *NContext = N->getLocationContext(); - - if (!getRefBinding(St, Sym)) - break; - - StoreManager::FindUniqueBinding FB(Sym); - StateMgr.iterBindings(St, FB); - - if (FB) { - const MemRegion *R = FB.getRegion(); - const VarRegion *VR = R->getBaseRegion()->getAs<VarRegion>(); - // Do not show local variables belonging to a function other than - // where the error is reported. - if (!VR || VR->getStackFrame() == LeakContext->getStackFrame()) - FirstBinding = R; - } - - // AllocationNode is the last node in which the symbol was tracked. - AllocationNode = N; - - // AllocationNodeInCurrentContext, is the last node in the current or - // parent context in which the symbol was tracked. - // - // Note that the allocation site might be in the parent conext. For example, - // the case where an allocation happens in a block that captures a reference - // to it and that reference is overwritten/dropped by another call to - // the block. - if (NContext == LeakContext || NContext->isParentOf(LeakContext)) - AllocationNodeInCurrentOrParentContext = N; - - // Find the last init that was called on the given symbol and store the - // init method's location context. - if (!InitMethodContext) - if (Optional<CallEnter> CEP = N->getLocation().getAs<CallEnter>()) { - const Stmt *CE = CEP->getCallExpr(); - if (const ObjCMessageExpr *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) { - const Stmt *RecExpr = ME->getInstanceReceiver(); - if (RecExpr) { - SVal RecV = St->getSVal(RecExpr, NContext); - if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym) - InitMethodContext = CEP->getCalleeContext(); - } - } - } - - N = N->pred_empty() ? nullptr : *(N->pred_begin()); - } - - // If we are reporting a leak of the object that was allocated with alloc, - // mark its init method as interesting. - const LocationContext *InterestingMethodContext = nullptr; - if (InitMethodContext) { - const ProgramPoint AllocPP = AllocationNode->getLocation(); - if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>()) - if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>()) - if (ME->getMethodFamily() == OMF_alloc) - InterestingMethodContext = InitMethodContext; - } - - // If allocation happened in a function different from the leak node context, - // do not report the binding. - assert(N && "Could not find allocation node"); - if (N->getLocationContext() != LeakContext) { - FirstBinding = nullptr; - } - - return AllocationInfo(AllocationNodeInCurrentOrParentContext, - FirstBinding, - InterestingMethodContext); -} - -std::shared_ptr<PathDiagnosticPiece> -CFRefReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { - BR.markInteresting(Sym); - return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); -} - -std::shared_ptr<PathDiagnosticPiece> -CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { - - // Tell the BugReporterContext to report cases when the tracked symbol is - // assigned to different variables, etc. - BR.markInteresting(Sym); - - // We are reporting a leak. Walk up the graph to get to the first node where - // the symbol appeared, and also get the first VarDecl that tracked object - // is stored to. - AllocationInfo AllocI = - GetAllocationSite(BRC.getStateManager(), EndN, Sym); - - const MemRegion* FirstBinding = AllocI.R; - BR.markInteresting(AllocI.InterestingMethodContext); - - SourceManager& SM = BRC.getSourceManager(); - - // Compute an actual location for the leak. Sometimes a leak doesn't - // occur at an actual statement (e.g., transition between blocks; end - // of function) so we need to walk the graph and compute a real location. - const ExplodedNode *LeakN = EndN; - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM); - - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - - os << "Object leaked: "; - - Optional<std::string> RegionDescription = describeRegion(FirstBinding); - if (RegionDescription) { - os << "object allocated and stored into '" << *RegionDescription << '\''; - } - else - os << "allocated object"; - - // Get the retain count. - const RefVal* RV = getRefBinding(EndN->getState(), Sym); - assert(RV); - - if (RV->getKind() == RefVal::ErrorLeakReturned) { - // FIXME: Per comments in rdar://6320065, "create" only applies to CF - // objects. Only "copy", "alloc", "retain" and "new" transfer ownership - // to the caller for NS objects. - const Decl *D = &EndN->getCodeDecl(); - - os << (isa<ObjCMethodDecl>(D) ? " is returned from a method " - : " is returned from a function "); - - if (D->hasAttr<CFReturnsNotRetainedAttr>()) - os << "that is annotated as CF_RETURNS_NOT_RETAINED"; - else if (D->hasAttr<NSReturnsNotRetainedAttr>()) - os << "that is annotated as NS_RETURNS_NOT_RETAINED"; - else { - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) { - if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) { - os << "managed by Automatic Reference Counting"; - } else { - os << "whose name ('" << MD->getSelector().getAsString() - << "') does not start with " - "'copy', 'mutableCopy', 'alloc' or 'new'." - " This violates the naming convention rules" - " given in the Memory Management Guide for Cocoa"; - } - } - else { - const FunctionDecl *FD = cast<FunctionDecl>(D); - os << "whose name ('" << *FD - << "') does not contain 'Copy' or 'Create'. This violates the naming" - " convention rules given in the Memory Management Guide for Core" - " Foundation"; - } - } - } - else if (RV->getKind() == RefVal::ErrorGCLeakReturned) { - const ObjCMethodDecl &MD = cast<ObjCMethodDecl>(EndN->getCodeDecl()); - os << " and returned from method '" << MD.getSelector().getAsString() - << "' is potentially leaked when using garbage collection. Callers " - "of this method do not expect a returned object with a +1 retain " - "count since they expect the object to be managed by the garbage " - "collector"; - } - else - os << " is not referenced later in this execution path and has a retain " - "count of +" << RV->getCount(); - - return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); -} - -void CFRefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) { - const SourceManager& SMgr = Ctx.getSourceManager(); - - if (!sym->getOriginRegion()) - return; - - auto *Region = dyn_cast<DeclRegion>(sym->getOriginRegion()); - if (Region) { - const Decl *PDecl = Region->getDecl(); - if (PDecl && isa<ParmVarDecl>(PDecl)) { - PathDiagnosticLocation ParamLocation = PathDiagnosticLocation::create(PDecl, SMgr); - Location = ParamLocation; - UniqueingLocation = ParamLocation; - UniqueingDecl = Ctx.getLocationContext()->getDecl(); - } - } -} - -void CFRefLeakReport::deriveAllocLocation(CheckerContext &Ctx,SymbolRef sym) { - // Most bug reports are cached at the location where they occurred. - // With leaks, we want to unique them by the location where they were - // allocated, and only report a single path. To do this, we need to find - // the allocation site of a piece of tracked memory, which we do via a - // call to GetAllocationSite. This will walk the ExplodedGraph backwards. - // Note that this is *not* the trimmed graph; we are guaranteed, however, - // that all ancestor nodes that represent the allocation site have the - // same SourceLocation. - const ExplodedNode *AllocNode = nullptr; - - const SourceManager& SMgr = Ctx.getSourceManager(); - - AllocationInfo AllocI = - GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym); - - AllocNode = AllocI.N; - AllocBinding = AllocI.R; - markInteresting(AllocI.InterestingMethodContext); - - // Get the SourceLocation for the allocation site. - // FIXME: This will crash the analyzer if an allocation comes from an - // implicit call (ex: a destructor call). - // (Currently there are no such allocations in Cocoa, though.) - AllocStmt = PathDiagnosticLocation::getStmt(AllocNode); - - if (!AllocStmt) { - AllocBinding = nullptr; - return; - } - - PathDiagnosticLocation AllocLocation = - PathDiagnosticLocation::createBegin(AllocStmt, SMgr, - AllocNode->getLocationContext()); - Location = AllocLocation; - - // Set uniqieing info, which will be used for unique the bug reports. The - // leaks should be uniqued on the allocation site. - UniqueingLocation = AllocLocation; - UniqueingDecl = AllocNode->getLocationContext()->getDecl(); -} - -void CFRefLeakReport::createDescription(CheckerContext &Ctx, bool GCEnabled, - bool IncludeAllocationLine) { - assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid()); - Description.clear(); - llvm::raw_string_ostream os(Description); - os << "Potential leak "; - if (GCEnabled) - os << "(when using garbage collection) "; - os << "of an object"; - - Optional<std::string> RegionDescription = describeRegion(AllocBinding); - if (RegionDescription) { - os << " stored into '" << *RegionDescription << '\''; - if (IncludeAllocationLine) { - FullSourceLoc SL(AllocStmt->getLocStart(), Ctx.getSourceManager()); - os << " (allocated on line " << SL.getSpellingLineNumber() << ")"; - } - } -} - -CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, - bool GCEnabled, const SummaryLogTy &Log, - ExplodedNode *n, SymbolRef sym, - CheckerContext &Ctx, - bool IncludeAllocationLine) - : CFRefReport(D, LOpts, GCEnabled, Log, n, sym, false) { - - deriveAllocLocation(Ctx, sym); - if (!AllocBinding) - deriveParamLocation(Ctx, sym); - - createDescription(Ctx, GCEnabled, IncludeAllocationLine); - - addVisitor(llvm::make_unique<CFRefLeakReportVisitor>(sym, GCEnabled, Log)); -} - -//===----------------------------------------------------------------------===// -// Main checker logic. -//===----------------------------------------------------------------------===// - -namespace { -class RetainCountChecker - : public Checker< check::Bind, - check::DeadSymbols, - check::EndAnalysis, - check::BeginFunction, - check::EndFunction, - check::PostStmt<BlockExpr>, - check::PostStmt<CastExpr>, - check::PostStmt<ObjCArrayLiteral>, - check::PostStmt<ObjCDictionaryLiteral>, - check::PostStmt<ObjCBoxedExpr>, - check::PostStmt<ObjCIvarRefExpr>, - check::PostCall, - check::PreStmt<ReturnStmt>, - check::RegionChanges, - eval::Assume, - eval::Call > { - mutable std::unique_ptr<CFRefBug> useAfterRelease, releaseNotOwned; - mutable std::unique_ptr<CFRefBug> deallocGC, deallocNotOwned; - mutable std::unique_ptr<CFRefBug> overAutorelease, returnNotOwnedForOwned; - mutable std::unique_ptr<CFRefBug> leakWithinFunction, leakAtReturn; - mutable std::unique_ptr<CFRefBug> leakWithinFunctionGC, leakAtReturnGC; - - typedef llvm::DenseMap<SymbolRef, const CheckerProgramPointTag *> SymbolTagMap; - - // This map is only used to ensure proper deletion of any allocated tags. - mutable SymbolTagMap DeadSymbolTags; - - mutable std::unique_ptr<RetainSummaryManager> Summaries; - mutable std::unique_ptr<RetainSummaryManager> SummariesGC; - mutable SummaryLogTy SummaryLog; - mutable bool ShouldResetSummaryLog; - - /// Optional setting to indicate if leak reports should include - /// the allocation line. - mutable bool IncludeAllocationLine; - -public: - RetainCountChecker(AnalyzerOptions &AO) - : ShouldResetSummaryLog(false), - IncludeAllocationLine(shouldIncludeAllocationSiteInLeakDiagnostics(AO)) {} - - ~RetainCountChecker() override { DeleteContainerSeconds(DeadSymbolTags); } - - void checkEndAnalysis(ExplodedGraph &G, BugReporter &BR, - ExprEngine &Eng) const { - // FIXME: This is a hack to make sure the summary log gets cleared between - // analyses of different code bodies. - // - // Why is this necessary? Because a checker's lifetime is tied to a - // translation unit, but an ExplodedGraph's lifetime is just a code body. - // Once in a blue moon, a new ExplodedNode will have the same address as an - // old one with an associated summary, and the bug report visitor gets very - // confused. (To make things worse, the summary lifetime is currently also - // tied to a code body, so we get a crash instead of incorrect results.) - // - // Why is this a bad solution? Because if the lifetime of the ExplodedGraph - // changes, things will start going wrong again. Really the lifetime of this - // log needs to be tied to either the specific nodes in it or the entire - // ExplodedGraph, not to a specific part of the code being analyzed. - // - // (Also, having stateful local data means that the same checker can't be - // used from multiple threads, but a lot of checkers have incorrect - // assumptions about that anyway. So that wasn't a priority at the time of - // this fix.) - // - // This happens at the end of analysis, but bug reports are emitted /after/ - // this point. So we can't just clear the summary log now. Instead, we mark - // that the next time we access the summary log, it should be cleared. - - // If we never reset the summary log during /this/ code body analysis, - // there were no new summaries. There might still have been summaries from - // the /last/ analysis, so clear them out to make sure the bug report - // visitors don't get confused. - if (ShouldResetSummaryLog) - SummaryLog.clear(); - - ShouldResetSummaryLog = !SummaryLog.empty(); - } - - CFRefBug *getLeakWithinFunctionBug(const LangOptions &LOpts, - bool GCEnabled) const { - if (GCEnabled) { - if (!leakWithinFunctionGC) - leakWithinFunctionGC.reset(new Leak(this, "Leak of object when using " - "garbage collection")); - return leakWithinFunctionGC.get(); - } else { - if (!leakWithinFunction) { - if (LOpts.getGC() == LangOptions::HybridGC) { - leakWithinFunction.reset(new Leak(this, - "Leak of object when not using " - "garbage collection (GC) in " - "dual GC/non-GC code")); - } else { - leakWithinFunction.reset(new Leak(this, "Leak")); - } - } - return leakWithinFunction.get(); - } - } - - CFRefBug *getLeakAtReturnBug(const LangOptions &LOpts, bool GCEnabled) const { - if (GCEnabled) { - if (!leakAtReturnGC) - leakAtReturnGC.reset(new Leak(this, - "Leak of returned object when using " - "garbage collection")); - return leakAtReturnGC.get(); - } else { - if (!leakAtReturn) { - if (LOpts.getGC() == LangOptions::HybridGC) { - leakAtReturn.reset(new Leak(this, - "Leak of returned object when not using " - "garbage collection (GC) in dual " - "GC/non-GC code")); - } else { - leakAtReturn.reset(new Leak(this, "Leak of returned object")); - } - } - return leakAtReturn.get(); - } - } - - RetainSummaryManager &getSummaryManager(ASTContext &Ctx, - bool GCEnabled) const { - // FIXME: We don't support ARC being turned on and off during one analysis. - // (nor, for that matter, do we support changing ASTContexts) - bool ARCEnabled = (bool)Ctx.getLangOpts().ObjCAutoRefCount; - if (GCEnabled) { - if (!SummariesGC) - SummariesGC.reset(new RetainSummaryManager(Ctx, true, ARCEnabled)); - else - assert(SummariesGC->isARCEnabled() == ARCEnabled); - return *SummariesGC; - } else { - if (!Summaries) - Summaries.reset(new RetainSummaryManager(Ctx, false, ARCEnabled)); - else - assert(Summaries->isARCEnabled() == ARCEnabled); - return *Summaries; - } - } - - RetainSummaryManager &getSummaryManager(CheckerContext &C) const { - return getSummaryManager(C.getASTContext(), C.isObjCGCEnabled()); - } - - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; - - void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; - void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; - void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; - - void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const; - void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; - void checkPostStmt(const ObjCBoxedExpr *BE, CheckerContext &C) const; - - void checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const; - - void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - - void checkSummary(const RetainSummary &Summ, const CallEvent &Call, - CheckerContext &C) const; - - void processSummaryOfInlined(const RetainSummary &Summ, - const CallEvent &Call, - CheckerContext &C) const; - - bool evalCall(const CallExpr *CE, CheckerContext &C) const; - - ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, - bool Assumption) const; - - ProgramStateRef - checkRegionChanges(ProgramStateRef state, - const InvalidatedSymbols *invalidated, - ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions, - const LocationContext* LCtx, - const CallEvent *Call) const; - - void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; - void checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C, - ExplodedNode *Pred, RetEffect RE, RefVal X, - SymbolRef Sym, ProgramStateRef state) const; - - void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; - void checkBeginFunction(CheckerContext &C) const; - void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; - - ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym, - RefVal V, ArgEffect E, RefVal::Kind &hasErr, - CheckerContext &C) const; - - void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange, - RefVal::Kind ErrorKind, SymbolRef Sym, - CheckerContext &C) const; - - void processObjCLiterals(CheckerContext &C, const Expr *Ex) const; - - const ProgramPointTag *getDeadSymbolTag(SymbolRef sym) const; - - ProgramStateRef handleSymbolDeath(ProgramStateRef state, - SymbolRef sid, RefVal V, - SmallVectorImpl<SymbolRef> &Leaked) const; - - ProgramStateRef - handleAutoreleaseCounts(ProgramStateRef state, ExplodedNode *Pred, - const ProgramPointTag *Tag, CheckerContext &Ctx, - SymbolRef Sym, RefVal V) const; - - ExplodedNode *processLeaks(ProgramStateRef state, - SmallVectorImpl<SymbolRef> &Leaked, - CheckerContext &Ctx, - ExplodedNode *Pred = nullptr) const; -}; -} // end anonymous namespace - -namespace { -class StopTrackingCallback final : public SymbolVisitor { - ProgramStateRef state; -public: - StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} - ProgramStateRef getState() const { return state; } - - bool VisitSymbol(SymbolRef sym) override { - state = state->remove<RefBindings>(sym); - return true; - } -}; -} // end anonymous namespace - -//===----------------------------------------------------------------------===// -// Handle statements that may have an effect on refcounts. -//===----------------------------------------------------------------------===// - -void RetainCountChecker::checkPostStmt(const BlockExpr *BE, - CheckerContext &C) const { - - // Scan the BlockDecRefExprs for any object the retain count checker - // may be tracking. - if (!BE->getBlockDecl()->hasCaptures()) - return; - - ProgramStateRef state = C.getState(); - auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); - - BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), - E = R->referenced_vars_end(); - - if (I == E) - return; - - // FIXME: For now we invalidate the tracking of all symbols passed to blocks - // via captured variables, even though captured variables result in a copy - // and in implicit increment/decrement of a retain count. - SmallVector<const MemRegion*, 10> Regions; - const LocationContext *LC = C.getLocationContext(); - MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); - - for ( ; I != E; ++I) { - const VarRegion *VR = I.getCapturedRegion(); - if (VR->getSuperRegion() == R) { - VR = MemMgr.getVarRegion(VR->getDecl(), LC); - } - Regions.push_back(VR); - } - - state = - state->scanReachableSymbols<StopTrackingCallback>(Regions.data(), - Regions.data() + Regions.size()).getState(); - C.addTransition(state); -} - -void RetainCountChecker::checkPostStmt(const CastExpr *CE, - CheckerContext &C) const { - const ObjCBridgedCastExpr *BE = dyn_cast<ObjCBridgedCastExpr>(CE); - if (!BE) - return; - - ArgEffect AE = IncRef; - - switch (BE->getBridgeKind()) { - case clang::OBC_Bridge: - // Do nothing. - return; - case clang::OBC_BridgeRetained: - AE = IncRef; - break; - case clang::OBC_BridgeTransfer: - AE = DecRefBridgedTransferred; - break; - } - - ProgramStateRef state = C.getState(); - SymbolRef Sym = C.getSVal(CE).getAsLocSymbol(); - if (!Sym) - return; - const RefVal* T = getRefBinding(state, Sym); - if (!T) - return; - - RefVal::Kind hasErr = (RefVal::Kind) 0; - state = updateSymbol(state, Sym, *T, AE, hasErr, C); - - if (hasErr) { - // FIXME: If we get an error during a bridge cast, should we report it? - return; - } - - C.addTransition(state); -} - -void RetainCountChecker::processObjCLiterals(CheckerContext &C, - const Expr *Ex) const { - ProgramStateRef state = C.getState(); - const ExplodedNode *pred = C.getPredecessor(); - for (const Stmt *Child : Ex->children()) { - SVal V = pred->getSVal(Child); - if (SymbolRef sym = V.getAsSymbol()) - if (const RefVal* T = getRefBinding(state, sym)) { - RefVal::Kind hasErr = (RefVal::Kind) 0; - state = updateSymbol(state, sym, *T, MayEscape, hasErr, C); - if (hasErr) { - processNonLeakError(state, Child->getSourceRange(), hasErr, sym, C); - return; - } - } - } - - // Return the object as autoreleased. - // RetEffect RE = RetEffect::MakeNotOwned(RetEffect::ObjC); - if (SymbolRef sym = - state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) { - QualType ResultTy = Ex->getType(); - state = setRefBinding(state, sym, - RefVal::makeNotOwned(RetEffect::ObjC, ResultTy)); - } - - C.addTransition(state); -} - -void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL, - CheckerContext &C) const { - // Apply the 'MayEscape' to all values. - processObjCLiterals(C, AL); -} - -void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, - CheckerContext &C) const { - // Apply the 'MayEscape' to all keys and values. - processObjCLiterals(C, DL); -} - -void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex, - CheckerContext &C) const { - const ExplodedNode *Pred = C.getPredecessor(); - ProgramStateRef State = Pred->getState(); - - if (SymbolRef Sym = Pred->getSVal(Ex).getAsSymbol()) { - QualType ResultTy = Ex->getType(); - State = setRefBinding(State, Sym, - RefVal::makeNotOwned(RetEffect::ObjC, ResultTy)); - } - - C.addTransition(State); -} - -void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE, - CheckerContext &C) const { - Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>(); - if (!IVarLoc) - return; - - ProgramStateRef State = C.getState(); - SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol(); - if (!Sym || !dyn_cast_or_null<ObjCIvarRegion>(Sym->getOriginRegion())) - return; - - // Accessing an ivar directly is unusual. If we've done that, be more - // forgiving about what the surrounding code is allowed to do. - - QualType Ty = Sym->getType(); - RetEffect::ObjKind Kind; - if (Ty->isObjCRetainableType()) - Kind = RetEffect::ObjC; - else if (coreFoundation::isCFObjectRef(Ty)) - Kind = RetEffect::CF; - else - return; - - // If the value is already known to be nil, don't bother tracking it. - ConstraintManager &CMgr = State->getConstraintManager(); - if (CMgr.isNull(State, Sym).isConstrainedTrue()) - return; - - if (const RefVal *RV = getRefBinding(State, Sym)) { - // If we've seen this symbol before, or we're only seeing it now because - // of something the analyzer has synthesized, don't do anything. - if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None || - isSynthesizedAccessor(C.getStackFrame())) { - return; - } - - // Note that this value has been loaded from an ivar. - C.addTransition(setRefBinding(State, Sym, RV->withIvarAccess())); - return; - } - - RefVal PlusZero = RefVal::makeNotOwned(Kind, Ty); - - // In a synthesized accessor, the effective retain count is +0. - if (isSynthesizedAccessor(C.getStackFrame())) { - C.addTransition(setRefBinding(State, Sym, PlusZero)); - return; - } - - State = setRefBinding(State, Sym, PlusZero.withIvarAccess()); - C.addTransition(State); -} - -void RetainCountChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { - RetainSummaryManager &Summaries = getSummaryManager(C); - const RetainSummary *Summ = Summaries.getSummary(Call, C.getState()); - - if (C.wasInlined) { - processSummaryOfInlined(*Summ, Call, C); - return; - } - checkSummary(*Summ, Call, C); -} - -/// GetReturnType - Used to get the return type of a message expression or -/// function call with the intention of affixing that type to a tracked symbol. -/// While the return type can be queried directly from RetEx, when -/// invoking class methods we augment to the return type to be that of -/// a pointer to the class (as opposed it just being id). -// FIXME: We may be able to do this with related result types instead. -// This function is probably overestimating. -static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) { - QualType RetTy = RetE->getType(); - // If RetE is not a message expression just return its type. - // If RetE is a message expression, return its types if it is something - /// more specific than id. - if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(RetE)) - if (const ObjCObjectPointerType *PT = RetTy->getAs<ObjCObjectPointerType>()) - if (PT->isObjCQualifiedIdType() || PT->isObjCIdType() || - PT->isObjCClassType()) { - // At this point we know the return type of the message expression is - // id, id<...>, or Class. If we have an ObjCInterfaceDecl, we know this - // is a call to a class method whose type we can resolve. In such - // cases, promote the return type to XXX* (where XXX is the class). - const ObjCInterfaceDecl *D = ME->getReceiverInterface(); - return !D ? RetTy : - Ctx.getObjCObjectPointerType(Ctx.getObjCInterfaceType(D)); - } - - return RetTy; -} - -// We don't always get the exact modeling of the function with regards to the -// retain count checker even when the function is inlined. For example, we need -// to stop tracking the symbols which were marked with StopTrackingHard. -void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ, - const CallEvent &CallOrMsg, - CheckerContext &C) const { - ProgramStateRef state = C.getState(); - - // Evaluate the effect of the arguments. - for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) { - if (Summ.getArg(idx) == StopTrackingHard) { - SVal V = CallOrMsg.getArgSVal(idx); - if (SymbolRef Sym = V.getAsLocSymbol()) { - state = removeRefBinding(state, Sym); - } - } - } - - // Evaluate the effect on the message receiver. - const ObjCMethodCall *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg); - if (MsgInvocation) { - if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) { - if (Summ.getReceiverEffect() == StopTrackingHard) { - state = removeRefBinding(state, Sym); - } - } - } - - // Consult the summary for the return value. - RetEffect RE = Summ.getRetEffect(); - if (RE.getKind() == RetEffect::NoRetHard) { - SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol(); - if (Sym) - state = removeRefBinding(state, Sym); - } - - C.addTransition(state); -} - -static ProgramStateRef updateOutParameter(ProgramStateRef State, - SVal ArgVal, - ArgEffect Effect) { - auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion()); - if (!ArgRegion) - return State; - - QualType PointeeTy = ArgRegion->getValueType(); - if (!coreFoundation::isCFObjectRef(PointeeTy)) - return State; - - SVal PointeeVal = State->getSVal(ArgRegion); - SymbolRef Pointee = PointeeVal.getAsLocSymbol(); - if (!Pointee) - return State; - - switch (Effect) { - case UnretainedOutParameter: - State = setRefBinding(State, Pointee, - RefVal::makeNotOwned(RetEffect::CF, PointeeTy)); - break; - case RetainedOutParameter: - // Do nothing. Retained out parameters will either point to a +1 reference - // or NULL, but the way you check for failure differs depending on the API. - // Consequently, we don't have a good way to track them yet. - break; - - default: - llvm_unreachable("only for out parameters"); - } - - return State; -} - -void RetainCountChecker::checkSummary(const RetainSummary &Summ, - const CallEvent &CallOrMsg, - CheckerContext &C) const { - ProgramStateRef state = C.getState(); - - // Evaluate the effect of the arguments. - RefVal::Kind hasErr = (RefVal::Kind) 0; - SourceRange ErrorRange; - SymbolRef ErrorSym = nullptr; - - for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) { - SVal V = CallOrMsg.getArgSVal(idx); - - ArgEffect Effect = Summ.getArg(idx); - if (Effect == RetainedOutParameter || Effect == UnretainedOutParameter) { - state = updateOutParameter(state, V, Effect); - } else if (SymbolRef Sym = V.getAsLocSymbol()) { - if (const RefVal *T = getRefBinding(state, Sym)) { - state = updateSymbol(state, Sym, *T, Effect, hasErr, C); - if (hasErr) { - ErrorRange = CallOrMsg.getArgSourceRange(idx); - ErrorSym = Sym; - break; - } - } - } - } - - // Evaluate the effect on the message receiver. - bool ReceiverIsTracked = false; - if (!hasErr) { - const ObjCMethodCall *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg); - if (MsgInvocation) { - if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) { - if (const RefVal *T = getRefBinding(state, Sym)) { - ReceiverIsTracked = true; - state = updateSymbol(state, Sym, *T, Summ.getReceiverEffect(), - hasErr, C); - if (hasErr) { - ErrorRange = MsgInvocation->getOriginExpr()->getReceiverRange(); - ErrorSym = Sym; - } - } - } - } - } - - // Process any errors. - if (hasErr) { - processNonLeakError(state, ErrorRange, hasErr, ErrorSym, C); - return; - } - - // Consult the summary for the return value. - RetEffect RE = Summ.getRetEffect(); - - if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) { - if (ReceiverIsTracked) - RE = getSummaryManager(C).getObjAllocRetEffect(); - else - RE = RetEffect::MakeNoRet(); - } - - switch (RE.getKind()) { - default: - llvm_unreachable("Unhandled RetEffect."); - - case RetEffect::NoRet: - case RetEffect::NoRetHard: - // No work necessary. - break; - - case RetEffect::OwnedSymbol: { - SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol(); - if (!Sym) - break; - - // Use the result type from the CallEvent as it automatically adjusts - // for methods/functions that return references. - QualType ResultTy = CallOrMsg.getResultType(); - state = setRefBinding(state, Sym, RefVal::makeOwned(RE.getObjKind(), - ResultTy)); - - // FIXME: Add a flag to the checker where allocations are assumed to - // *not* fail. - break; - } - - case RetEffect::GCNotOwnedSymbol: - case RetEffect::NotOwnedSymbol: { - const Expr *Ex = CallOrMsg.getOriginExpr(); - SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol(); - if (!Sym) - break; - assert(Ex); - // Use GetReturnType in order to give [NSFoo alloc] the type NSFoo *. - QualType ResultTy = GetReturnType(Ex, C.getASTContext()); - state = setRefBinding(state, Sym, RefVal::makeNotOwned(RE.getObjKind(), - ResultTy)); - break; - } - } - - // This check is actually necessary; otherwise the statement builder thinks - // we've hit a previously-found path. - // Normally addTransition takes care of this, but we want the node pointer. - ExplodedNode *NewNode; - if (state == C.getState()) { - NewNode = C.getPredecessor(); - } else { - NewNode = C.addTransition(state); - } - - // Annotate the node with summary we used. - if (NewNode) { - // FIXME: This is ugly. See checkEndAnalysis for why it's necessary. - if (ShouldResetSummaryLog) { - SummaryLog.clear(); - ShouldResetSummaryLog = false; - } - SummaryLog[NewNode] = &Summ; - } -} - -ProgramStateRef -RetainCountChecker::updateSymbol(ProgramStateRef state, SymbolRef sym, - RefVal V, ArgEffect E, RefVal::Kind &hasErr, - CheckerContext &C) const { - // In GC mode [... release] and [... retain] do nothing. - // In ARC mode they shouldn't exist at all, but we just ignore them. - bool IgnoreRetainMsg = C.isObjCGCEnabled(); - if (!IgnoreRetainMsg) - IgnoreRetainMsg = (bool)C.getASTContext().getLangOpts().ObjCAutoRefCount; - - switch (E) { - default: - break; - case IncRefMsg: - E = IgnoreRetainMsg ? DoNothing : IncRef; - break; - case DecRefMsg: - E = IgnoreRetainMsg ? DoNothing : DecRef; - break; - case DecRefMsgAndStopTrackingHard: - E = IgnoreRetainMsg ? StopTracking : DecRefAndStopTrackingHard; - break; - case MakeCollectable: - E = C.isObjCGCEnabled() ? DecRef : DoNothing; - break; - } - - // Handle all use-after-releases. - if (!C.isObjCGCEnabled() && V.getKind() == RefVal::Released) { - V = V ^ RefVal::ErrorUseAfterRelease; - hasErr = V.getKind(); - return setRefBinding(state, sym, V); - } - - switch (E) { - case DecRefMsg: - case IncRefMsg: - case MakeCollectable: - case DecRefMsgAndStopTrackingHard: - llvm_unreachable("DecRefMsg/IncRefMsg/MakeCollectable already converted"); - - case UnretainedOutParameter: - case RetainedOutParameter: - llvm_unreachable("Applies to pointer-to-pointer parameters, which should " - "not have ref state."); - - case Dealloc: - // Any use of -dealloc in GC is *bad*. - if (C.isObjCGCEnabled()) { - V = V ^ RefVal::ErrorDeallocGC; - hasErr = V.getKind(); - break; - } - - switch (V.getKind()) { - default: - llvm_unreachable("Invalid RefVal state for an explicit dealloc."); - case RefVal::Owned: - // The object immediately transitions to the released state. - V = V ^ RefVal::Released; - V.clearCounts(); - return setRefBinding(state, sym, V); - case RefVal::NotOwned: - V = V ^ RefVal::ErrorDeallocNotOwned; - hasErr = V.getKind(); - break; - } - break; - - case MayEscape: - if (V.getKind() == RefVal::Owned) { - V = V ^ RefVal::NotOwned; - break; - } - - // Fall-through. - - case DoNothing: - return state; - - case Autorelease: - if (C.isObjCGCEnabled()) - return state; - // Update the autorelease counts. - V = V.autorelease(); - break; - - case StopTracking: - case StopTrackingHard: - return removeRefBinding(state, sym); - - case IncRef: - switch (V.getKind()) { - default: - llvm_unreachable("Invalid RefVal state for a retain."); - case RefVal::Owned: - case RefVal::NotOwned: - V = V + 1; - break; - case RefVal::Released: - // Non-GC cases are handled above. - assert(C.isObjCGCEnabled()); - V = (V ^ RefVal::Owned) + 1; - break; - } - break; - - case DecRef: - case DecRefBridgedTransferred: - case DecRefAndStopTrackingHard: - switch (V.getKind()) { - default: - // case 'RefVal::Released' handled above. - llvm_unreachable("Invalid RefVal state for a release."); - - case RefVal::Owned: - assert(V.getCount() > 0); - if (V.getCount() == 1) { - if (E == DecRefBridgedTransferred || - V.getIvarAccessHistory() == - RefVal::IvarAccessHistory::AccessedDirectly) - V = V ^ RefVal::NotOwned; - else - V = V ^ RefVal::Released; - } else if (E == DecRefAndStopTrackingHard) { - return removeRefBinding(state, sym); - } - - V = V - 1; - break; - - case RefVal::NotOwned: - if (V.getCount() > 0) { - if (E == DecRefAndStopTrackingHard) - return removeRefBinding(state, sym); - V = V - 1; - } else if (V.getIvarAccessHistory() == - RefVal::IvarAccessHistory::AccessedDirectly) { - // Assume that the instance variable was holding on the object at - // +1, and we just didn't know. - if (E == DecRefAndStopTrackingHard) - return removeRefBinding(state, sym); - V = V.releaseViaIvar() ^ RefVal::Released; - } else { - V = V ^ RefVal::ErrorReleaseNotOwned; - hasErr = V.getKind(); - } - break; - - case RefVal::Released: - // Non-GC cases are handled above. - assert(C.isObjCGCEnabled()); - V = V ^ RefVal::ErrorUseAfterRelease; - hasErr = V.getKind(); - break; - } - break; - } - return setRefBinding(state, sym, V); -} - -void RetainCountChecker::processNonLeakError(ProgramStateRef St, - SourceRange ErrorRange, - RefVal::Kind ErrorKind, - SymbolRef Sym, - CheckerContext &C) const { - // HACK: Ignore retain-count issues on values accessed through ivars, - // because of cases like this: - // [_contentView retain]; - // [_contentView removeFromSuperview]; - // [self addSubview:_contentView]; // invalidates 'self' - // [_contentView release]; - if (const RefVal *RV = getRefBinding(St, Sym)) - if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None) - return; - - ExplodedNode *N = C.generateErrorNode(St); - if (!N) - return; - - CFRefBug *BT; - switch (ErrorKind) { - default: - llvm_unreachable("Unhandled error."); - case RefVal::ErrorUseAfterRelease: - if (!useAfterRelease) - useAfterRelease.reset(new UseAfterRelease(this)); - BT = useAfterRelease.get(); - break; - case RefVal::ErrorReleaseNotOwned: - if (!releaseNotOwned) - releaseNotOwned.reset(new BadRelease(this)); - BT = releaseNotOwned.get(); - break; - case RefVal::ErrorDeallocGC: - if (!deallocGC) - deallocGC.reset(new DeallocGC(this)); - BT = deallocGC.get(); - break; - case RefVal::ErrorDeallocNotOwned: - if (!deallocNotOwned) - deallocNotOwned.reset(new DeallocNotOwned(this)); - BT = deallocNotOwned.get(); - break; - } - - assert(BT); - auto report = std::unique_ptr<BugReport>( - new CFRefReport(*BT, C.getASTContext().getLangOpts(), C.isObjCGCEnabled(), - SummaryLog, N, Sym)); - report->addRange(ErrorRange); - C.emitReport(std::move(report)); -} - -//===----------------------------------------------------------------------===// -// Handle the return values of retain-count-related functions. -//===----------------------------------------------------------------------===// - -bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - // Get the callee. We're only interested in simple C functions. - ProgramStateRef state = C.getState(); - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return false; - - IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return false; - - // For now, we're only handling the functions that return aliases of their - // arguments: CFRetain and CFMakeCollectable (and their families). - // Eventually we should add other functions we can model entirely, - // such as CFRelease, which don't invalidate their arguments or globals. - if (CE->getNumArgs() != 1) - return false; - - // Get the name of the function. - StringRef FName = II->getName(); - FName = FName.substr(FName.find_first_not_of('_')); - - // See if it's one of the specific functions we know how to eval. - bool canEval = false; - // See if the function has 'rc_ownership_trusted_implementation' - // annotate attribute. If it does, we will not inline it. - bool hasTrustedImplementationAnnotation = false; - - QualType ResultTy = CE->getCallReturnType(C.getASTContext()); - if (ResultTy->isObjCIdType()) { - // Handle: id NSMakeCollectable(CFTypeRef) - canEval = II->isStr("NSMakeCollectable"); - } else if (ResultTy->isPointerType()) { - // Handle: (CF|CG|CV)Retain - // CFAutorelease - // CFMakeCollectable - // It's okay to be a little sloppy here (CGMakeCollectable doesn't exist). - if (cocoa::isRefType(ResultTy, "CF", FName) || - cocoa::isRefType(ResultTy, "CG", FName) || - cocoa::isRefType(ResultTy, "CV", FName)) { - canEval = isRetain(FD, FName) || isAutorelease(FD, FName) || - isMakeCollectable(FD, FName); - } else { - if (FD->getDefinition()) { - canEval = isTrustedReferenceCountImplementation(FD->getDefinition()); - hasTrustedImplementationAnnotation = canEval; - } - } - } - - if (!canEval) - return false; - - // Bind the return value. - const LocationContext *LCtx = C.getLocationContext(); - SVal RetVal = state->getSVal(CE->getArg(0), LCtx); - if (RetVal.isUnknown() || - (hasTrustedImplementationAnnotation && !ResultTy.isNull())) { - // If the receiver is unknown or the function has - // 'rc_ownership_trusted_implementation' annotate attribute, conjure a - // return value. - SValBuilder &SVB = C.getSValBuilder(); - RetVal = SVB.conjureSymbolVal(nullptr, CE, LCtx, ResultTy, C.blockCount()); - } - state = state->BindExpr(CE, LCtx, RetVal, false); - - // FIXME: This should not be necessary, but otherwise the argument seems to be - // considered alive during the next statement. - if (const MemRegion *ArgRegion = RetVal.getAsRegion()) { - // Save the refcount status of the argument. - SymbolRef Sym = RetVal.getAsLocSymbol(); - const RefVal *Binding = nullptr; - if (Sym) - Binding = getRefBinding(state, Sym); - - // Invalidate the argument region. - state = state->invalidateRegions( - ArgRegion, CE, C.blockCount(), LCtx, - /*CausesPointerEscape*/ hasTrustedImplementationAnnotation); - - // Restore the refcount status of the argument. - if (Binding) - state = setRefBinding(state, Sym, *Binding); - } - - C.addTransition(state); - return true; -} - -//===----------------------------------------------------------------------===// -// Handle return statements. -//===----------------------------------------------------------------------===// - -void RetainCountChecker::checkPreStmt(const ReturnStmt *S, - CheckerContext &C) const { - - // Only adjust the reference count if this is the top-level call frame, - // and not the result of inlining. In the future, we should do - // better checking even for inlined calls, and see if they match - // with their expected semantics (e.g., the method should return a retained - // object, etc.). - if (!C.inTopFrame()) - return; - - const Expr *RetE = S->getRetValue(); - if (!RetE) - return; - - ProgramStateRef state = C.getState(); - SymbolRef Sym = - state->getSValAsScalarOrLoc(RetE, C.getLocationContext()).getAsLocSymbol(); - if (!Sym) - return; - - // Get the reference count binding (if any). - const RefVal *T = getRefBinding(state, Sym); - if (!T) - return; - - // Change the reference count. - RefVal X = *T; - - switch (X.getKind()) { - case RefVal::Owned: { - unsigned cnt = X.getCount(); - assert(cnt > 0); - X.setCount(cnt - 1); - X = X ^ RefVal::ReturnedOwned; - break; - } - - case RefVal::NotOwned: { - unsigned cnt = X.getCount(); - if (cnt) { - X.setCount(cnt - 1); - X = X ^ RefVal::ReturnedOwned; - } - else { - X = X ^ RefVal::ReturnedNotOwned; - } - break; - } - - default: - return; - } - - // Update the binding. - state = setRefBinding(state, Sym, X); - ExplodedNode *Pred = C.addTransition(state); - - // At this point we have updated the state properly. - // Everything after this is merely checking to see if the return value has - // been over- or under-retained. - - // Did we cache out? - if (!Pred) - return; - - // Update the autorelease counts. - static CheckerProgramPointTag AutoreleaseTag(this, "Autorelease"); - state = handleAutoreleaseCounts(state, Pred, &AutoreleaseTag, C, Sym, X); - - // Did we cache out? - if (!state) - return; - - // Get the updated binding. - T = getRefBinding(state, Sym); - assert(T); - X = *T; - - // Consult the summary of the enclosing method. - RetainSummaryManager &Summaries = getSummaryManager(C); - const Decl *CD = &Pred->getCodeDecl(); - RetEffect RE = RetEffect::MakeNoRet(); - - // FIXME: What is the convention for blocks? Is there one? - if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(CD)) { - const RetainSummary *Summ = Summaries.getMethodSummary(MD); - RE = Summ->getRetEffect(); - } else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CD)) { - if (!isa<CXXMethodDecl>(FD)) { - const RetainSummary *Summ = Summaries.getFunctionSummary(FD); - RE = Summ->getRetEffect(); - } - } - - checkReturnWithRetEffect(S, C, Pred, RE, X, Sym, state); -} - -void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, - CheckerContext &C, - ExplodedNode *Pred, - RetEffect RE, RefVal X, - SymbolRef Sym, - ProgramStateRef state) const { - // HACK: Ignore retain-count issues on values accessed through ivars, - // because of cases like this: - // [_contentView retain]; - // [_contentView removeFromSuperview]; - // [self addSubview:_contentView]; // invalidates 'self' - // [_contentView release]; - if (X.getIvarAccessHistory() != RefVal::IvarAccessHistory::None) - return; - - // Any leaks or other errors? - if (X.isReturnedOwned() && X.getCount() == 0) { - if (RE.getKind() != RetEffect::NoRet) { - bool hasError = false; - if (C.isObjCGCEnabled() && RE.getObjKind() == RetEffect::ObjC) { - // Things are more complicated with garbage collection. If the - // returned object is suppose to be an Objective-C object, we have - // a leak (as the caller expects a GC'ed object) because no - // method should return ownership unless it returns a CF object. - hasError = true; - X = X ^ RefVal::ErrorGCLeakReturned; - } - else if (!RE.isOwned()) { - // Either we are using GC and the returned object is a CF type - // or we aren't using GC. In either case, we expect that the - // enclosing method is expected to return ownership. - hasError = true; - X = X ^ RefVal::ErrorLeakReturned; - } - - if (hasError) { - // Generate an error node. - state = setRefBinding(state, Sym, X); - - static CheckerProgramPointTag ReturnOwnLeakTag(this, "ReturnsOwnLeak"); - ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag); - if (N) { - const LangOptions &LOpts = C.getASTContext().getLangOpts(); - bool GCEnabled = C.isObjCGCEnabled(); - C.emitReport(std::unique_ptr<BugReport>(new CFRefLeakReport( - *getLeakAtReturnBug(LOpts, GCEnabled), LOpts, GCEnabled, - SummaryLog, N, Sym, C, IncludeAllocationLine))); - } - } - } - } else if (X.isReturnedNotOwned()) { - if (RE.isOwned()) { - if (X.getIvarAccessHistory() == - RefVal::IvarAccessHistory::AccessedDirectly) { - // Assume the method was trying to transfer a +1 reference from a - // strong ivar to the caller. - state = setRefBinding(state, Sym, - X.releaseViaIvar() ^ RefVal::ReturnedOwned); - } else { - // Trying to return a not owned object to a caller expecting an - // owned object. - state = setRefBinding(state, Sym, X ^ RefVal::ErrorReturnedNotOwned); - - static CheckerProgramPointTag - ReturnNotOwnedTag(this, "ReturnNotOwnedForOwned"); - - ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag); - if (N) { - if (!returnNotOwnedForOwned) - returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned(this)); - - C.emitReport(std::unique_ptr<BugReport>(new CFRefReport( - *returnNotOwnedForOwned, C.getASTContext().getLangOpts(), - C.isObjCGCEnabled(), SummaryLog, N, Sym))); - } - } - } - } -} - -//===----------------------------------------------------------------------===// -// Check various ways a symbol can be invalidated. -//===----------------------------------------------------------------------===// - -void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, - CheckerContext &C) const { - // Are we storing to something that causes the value to "escape"? - bool escapes = true; - - // A value escapes in three possible cases (this may change): - // - // (1) we are binding to something that is not a memory region. - // (2) we are binding to a memregion that does not have stack storage - // (3) we are binding to a memregion with stack storage that the store - // does not understand. - ProgramStateRef state = C.getState(); - - if (Optional<loc::MemRegionVal> regionLoc = loc.getAs<loc::MemRegionVal>()) { - escapes = !regionLoc->getRegion()->hasStackStorage(); - - if (!escapes) { - // To test (3), generate a new state with the binding added. If it is - // the same state, then it escapes (since the store cannot represent - // the binding). - // Do this only if we know that the store is not supposed to generate the - // same state. - SVal StoredVal = state->getSVal(regionLoc->getRegion()); - if (StoredVal != val) - escapes = (state == (state->bindLoc(*regionLoc, val, C.getLocationContext()))); - } - if (!escapes) { - // Case 4: We do not currently model what happens when a symbol is - // assigned to a struct field, so be conservative here and let the symbol - // go. TODO: This could definitely be improved upon. - escapes = !isa<VarRegion>(regionLoc->getRegion()); - } - } - - // If we are storing the value into an auto function scope variable annotated - // with (__attribute__((cleanup))), stop tracking the value to avoid leak - // false positives. - if (const VarRegion *LVR = dyn_cast_or_null<VarRegion>(loc.getAsRegion())) { - const VarDecl *VD = LVR->getDecl(); - if (VD->hasAttr<CleanupAttr>()) { - escapes = true; - } - } - - // If our store can represent the binding and we aren't storing to something - // that doesn't have local storage then just return and have the simulation - // state continue as is. - if (!escapes) - return; - - // Otherwise, find all symbols referenced by 'val' that we are tracking - // and stop tracking them. - state = state->scanReachableSymbols<StopTrackingCallback>(val).getState(); - C.addTransition(state); -} - -ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state, - SVal Cond, - bool Assumption) const { - // FIXME: We may add to the interface of evalAssume the list of symbols - // whose assumptions have changed. For now we just iterate through the - // bindings and check if any of the tracked symbols are NULL. This isn't - // too bad since the number of symbols we will track in practice are - // probably small and evalAssume is only called at branches and a few - // other places. - RefBindingsTy B = state->get<RefBindings>(); - - if (B.isEmpty()) - return state; - - bool changed = false; - RefBindingsTy::Factory &RefBFactory = state->get_context<RefBindings>(); - - for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { - // Check if the symbol is null stop tracking the symbol. - ConstraintManager &CMgr = state->getConstraintManager(); - ConditionTruthVal AllocFailed = CMgr.isNull(state, I.getKey()); - if (AllocFailed.isConstrainedTrue()) { - changed = true; - B = RefBFactory.remove(B, I.getKey()); - } - } - - if (changed) - state = state->set<RefBindings>(B); - - return state; -} - -ProgramStateRef -RetainCountChecker::checkRegionChanges(ProgramStateRef state, - const InvalidatedSymbols *invalidated, - ArrayRef<const MemRegion *> ExplicitRegions, - ArrayRef<const MemRegion *> Regions, - const LocationContext *LCtx, - const CallEvent *Call) const { - if (!invalidated) - return state; - - llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols; - for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), - E = ExplicitRegions.end(); I != E; ++I) { - if (const SymbolicRegion *SR = (*I)->StripCasts()->getAs<SymbolicRegion>()) - WhitelistedSymbols.insert(SR->getSymbol()); - } - - for (InvalidatedSymbols::const_iterator I=invalidated->begin(), - E = invalidated->end(); I!=E; ++I) { - SymbolRef sym = *I; - if (WhitelistedSymbols.count(sym)) - continue; - // Remove any existing reference-count binding. - state = removeRefBinding(state, sym); - } - return state; -} - -//===----------------------------------------------------------------------===// -// Handle dead symbols and end-of-path. -//===----------------------------------------------------------------------===// - -ProgramStateRef -RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, - ExplodedNode *Pred, - const ProgramPointTag *Tag, - CheckerContext &Ctx, - SymbolRef Sym, RefVal V) const { - unsigned ACnt = V.getAutoreleaseCount(); - - // No autorelease counts? Nothing to be done. - if (!ACnt) - return state; - - assert(!Ctx.isObjCGCEnabled() && "Autorelease counts in GC mode?"); - unsigned Cnt = V.getCount(); - - // FIXME: Handle sending 'autorelease' to already released object. - - if (V.getKind() == RefVal::ReturnedOwned) - ++Cnt; - - // If we would over-release here, but we know the value came from an ivar, - // assume it was a strong ivar that's just been relinquished. - if (ACnt > Cnt && - V.getIvarAccessHistory() == RefVal::IvarAccessHistory::AccessedDirectly) { - V = V.releaseViaIvar(); - --ACnt; - } - - if (ACnt <= Cnt) { - if (ACnt == Cnt) { - V.clearCounts(); - if (V.getKind() == RefVal::ReturnedOwned) - V = V ^ RefVal::ReturnedNotOwned; - else - V = V ^ RefVal::NotOwned; - } else { - V.setCount(V.getCount() - ACnt); - V.setAutoreleaseCount(0); - } - return setRefBinding(state, Sym, V); - } - - // HACK: Ignore retain-count issues on values accessed through ivars, - // because of cases like this: - // [_contentView retain]; - // [_contentView removeFromSuperview]; - // [self addSubview:_contentView]; // invalidates 'self' - // [_contentView release]; - if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None) - return state; - - // Woah! More autorelease counts then retain counts left. - // Emit hard error. - V = V ^ RefVal::ErrorOverAutorelease; - state = setRefBinding(state, Sym, V); - - ExplodedNode *N = Ctx.generateSink(state, Pred, Tag); - if (N) { - SmallString<128> sbuf; - llvm::raw_svector_ostream os(sbuf); - os << "Object was autoreleased "; - if (V.getAutoreleaseCount() > 1) - os << V.getAutoreleaseCount() << " times but the object "; - else - os << "but "; - os << "has a +" << V.getCount() << " retain count"; - - if (!overAutorelease) - overAutorelease.reset(new OverAutorelease(this)); - - const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); - Ctx.emitReport(std::unique_ptr<BugReport>( - new CFRefReport(*overAutorelease, LOpts, /* GCEnabled = */ false, - SummaryLog, N, Sym, os.str()))); - } - - return nullptr; -} - -ProgramStateRef -RetainCountChecker::handleSymbolDeath(ProgramStateRef state, - SymbolRef sid, RefVal V, - SmallVectorImpl<SymbolRef> &Leaked) const { - bool hasLeak; - - // HACK: Ignore retain-count issues on values accessed through ivars, - // because of cases like this: - // [_contentView retain]; - // [_contentView removeFromSuperview]; - // [self addSubview:_contentView]; // invalidates 'self' - // [_contentView release]; - if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None) - hasLeak = false; - else if (V.isOwned()) - hasLeak = true; - else if (V.isNotOwned() || V.isReturnedOwned()) - hasLeak = (V.getCount() > 0); - else - hasLeak = false; - - if (!hasLeak) - return removeRefBinding(state, sid); - - Leaked.push_back(sid); - return setRefBinding(state, sid, V ^ RefVal::ErrorLeak); -} - -ExplodedNode * -RetainCountChecker::processLeaks(ProgramStateRef state, - SmallVectorImpl<SymbolRef> &Leaked, - CheckerContext &Ctx, - ExplodedNode *Pred) const { - // Generate an intermediate node representing the leak point. - ExplodedNode *N = Ctx.addTransition(state, Pred); - - if (N) { - for (SmallVectorImpl<SymbolRef>::iterator - I = Leaked.begin(), E = Leaked.end(); I != E; ++I) { - - const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); - bool GCEnabled = Ctx.isObjCGCEnabled(); - CFRefBug *BT = Pred ? getLeakWithinFunctionBug(LOpts, GCEnabled) - : getLeakAtReturnBug(LOpts, GCEnabled); - assert(BT && "BugType not initialized."); - - Ctx.emitReport(std::unique_ptr<BugReport>( - new CFRefLeakReport(*BT, LOpts, GCEnabled, SummaryLog, N, *I, Ctx, - IncludeAllocationLine))); - } - } - - return N; -} - -void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const { - if (!Ctx.inTopFrame()) - return; - - const LocationContext *LCtx = Ctx.getLocationContext(); - const FunctionDecl *FD = dyn_cast<FunctionDecl>(LCtx->getDecl()); - - if (!FD || isTrustedReferenceCountImplementation(FD)) - return; - - ProgramStateRef state = Ctx.getState(); - - const RetainSummary *FunctionSummary = getSummaryManager(Ctx).getFunctionSummary(FD); - ArgEffects CalleeSideArgEffects = FunctionSummary->getArgEffects(); - - for (unsigned idx = 0, e = FD->getNumParams(); idx != e; ++idx) { - const ParmVarDecl *Param = FD->getParamDecl(idx); - SymbolRef Sym = state->getSVal(state->getRegion(Param, LCtx)).getAsSymbol(); - - QualType Ty = Param->getType(); - const ArgEffect *AE = CalleeSideArgEffects.lookup(idx); - if (AE && *AE == DecRef && isGeneralizedObjectRef(Ty)) - state = setRefBinding(state, Sym, RefVal::makeOwned(RetEffect::ObjKind::Generalized, Ty)); - else if (isGeneralizedObjectRef(Ty)) - state = setRefBinding(state, Sym, RefVal::makeNotOwned(RetEffect::ObjKind::Generalized, Ty)); - } - - Ctx.addTransition(state); -} - -void RetainCountChecker::checkEndFunction(const ReturnStmt *RS, - CheckerContext &Ctx) const { - ProgramStateRef state = Ctx.getState(); - RefBindingsTy B = state->get<RefBindings>(); - ExplodedNode *Pred = Ctx.getPredecessor(); - - // Don't process anything within synthesized bodies. - const LocationContext *LCtx = Pred->getLocationContext(); - if (LCtx->getAnalysisDeclContext()->isBodyAutosynthesized()) { - assert(!LCtx->inTopFrame()); - return; - } - - for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { - state = handleAutoreleaseCounts(state, Pred, /*Tag=*/nullptr, Ctx, - I->first, I->second); - if (!state) - return; - } - - // If the current LocationContext has a parent, don't check for leaks. - // We will do that later. - // FIXME: we should instead check for imbalances of the retain/releases, - // and suggest annotations. - if (LCtx->getParent()) - return; - - B = state->get<RefBindings>(); - SmallVector<SymbolRef, 10> Leaked; - - for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) - state = handleSymbolDeath(state, I->first, I->second, Leaked); - - processLeaks(state, Leaked, Ctx, Pred); -} - -const ProgramPointTag * -RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const { - const CheckerProgramPointTag *&tag = DeadSymbolTags[sym]; - if (!tag) { - SmallString<64> buf; - llvm::raw_svector_ostream out(buf); - out << "Dead Symbol : "; - sym->dumpToStream(out); - tag = new CheckerProgramPointTag(this, out.str()); - } - return tag; -} - -void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, - CheckerContext &C) const { - ExplodedNode *Pred = C.getPredecessor(); - - ProgramStateRef state = C.getState(); - RefBindingsTy B = state->get<RefBindings>(); - SmallVector<SymbolRef, 10> Leaked; - - // Update counts from autorelease pools - for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), - E = SymReaper.dead_end(); I != E; ++I) { - SymbolRef Sym = *I; - if (const RefVal *T = B.lookup(Sym)){ - // Use the symbol as the tag. - // FIXME: This might not be as unique as we would like. - const ProgramPointTag *Tag = getDeadSymbolTag(Sym); - state = handleAutoreleaseCounts(state, Pred, Tag, C, Sym, *T); - if (!state) - return; - - // Fetch the new reference count from the state, and use it to handle - // this symbol. - state = handleSymbolDeath(state, *I, *getRefBinding(state, Sym), Leaked); - } - } - - if (Leaked.empty()) { - C.addTransition(state); - return; - } - - Pred = processLeaks(state, Leaked, C, Pred); - - // Did we cache out? - if (!Pred) - return; - - // Now generate a new node that nukes the old bindings. - // The only bindings left at this point are the leaked symbols. - RefBindingsTy::Factory &F = state->get_context<RefBindings>(); - B = state->get<RefBindings>(); - - for (SmallVectorImpl<SymbolRef>::iterator I = Leaked.begin(), - E = Leaked.end(); - I != E; ++I) - B = F.remove(B, *I); - - state = state->set<RefBindings>(B); - C.addTransition(state, Pred); -} - -void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const { - - RefBindingsTy B = State->get<RefBindings>(); - - if (B.isEmpty()) - return; - - Out << Sep << NL; - - for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { - Out << I->first << " : "; - I->second.print(Out); - Out << NL; - } -} - -//===----------------------------------------------------------------------===// -// Checker registration. -//===----------------------------------------------------------------------===// - -void ento::registerRetainCountChecker(CheckerManager &Mgr) { - Mgr.registerChecker<RetainCountChecker>(Mgr.getAnalyzerOptions()); -} - -//===----------------------------------------------------------------------===// -// Implementation of the CallEffects API. -//===----------------------------------------------------------------------===// - -namespace clang { -namespace ento { -namespace objc_retain { - -// This is a bit gross, but it allows us to populate CallEffects without -// creating a bunch of accessors. This kind is very localized, so the -// damage of this macro is limited. -#define createCallEffect(D, KIND)\ - ASTContext &Ctx = D->getASTContext();\ - LangOptions L = Ctx.getLangOpts();\ - RetainSummaryManager M(Ctx, L.GCOnly, L.ObjCAutoRefCount);\ - const RetainSummary *S = M.get ## KIND ## Summary(D);\ - CallEffects CE(S->getRetEffect());\ - CE.Receiver = S->getReceiverEffect();\ - unsigned N = D->param_size();\ - for (unsigned i = 0; i < N; ++i) {\ - CE.Args.push_back(S->getArg(i));\ - } - -CallEffects CallEffects::getEffect(const ObjCMethodDecl *MD) { - createCallEffect(MD, Method); - return CE; -} - -CallEffects CallEffects::getEffect(const FunctionDecl *FD) { - createCallEffect(FD, Function); - return CE; -} - -#undef createCallEffect - -} // end namespace objc_retain -} // end namespace ento -} // end namespace clang diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp new file mode 100644 index 000000000000..0652af856643 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp @@ -0,0 +1,1547 @@ +//==-- RetainCountChecker.cpp - Checks for leaks and other issues -*- 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 the methods for RetainCountChecker, which implements +// a reference count checker for Core Foundation and Cocoa on (Mac OS X). +// +//===----------------------------------------------------------------------===// + +#include "RetainCountChecker.h" + +using namespace clang; +using namespace ento; +using namespace retaincountchecker; +using llvm::StrInStrNoCase; + +REGISTER_MAP_WITH_PROGRAMSTATE(RefBindings, SymbolRef, RefVal) + +namespace clang { +namespace ento { +namespace retaincountchecker { + +const RefVal *getRefBinding(ProgramStateRef State, SymbolRef Sym) { + return State->get<RefBindings>(Sym); +} + +ProgramStateRef setRefBinding(ProgramStateRef State, SymbolRef Sym, + RefVal Val) { + assert(Sym != nullptr); + return State->set<RefBindings>(Sym, Val); +} + +ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym) { + return State->remove<RefBindings>(Sym); +} + +class UseAfterRelease : public RefCountBug { +public: + UseAfterRelease(const CheckerBase *checker) + : RefCountBug(checker, "Use-after-release") {} + + const char *getDescription() const override { + return "Reference-counted object is used after it is released"; + } +}; + +class BadRelease : public RefCountBug { +public: + BadRelease(const CheckerBase *checker) : RefCountBug(checker, "Bad release") {} + + const char *getDescription() const override { + return "Incorrect decrement of the reference count of an object that is " + "not owned at this point by the caller"; + } +}; + +class DeallocNotOwned : public RefCountBug { +public: + DeallocNotOwned(const CheckerBase *checker) + : RefCountBug(checker, "-dealloc sent to non-exclusively owned object") {} + + const char *getDescription() const override { + return "-dealloc sent to object that may be referenced elsewhere"; + } +}; + +class OverAutorelease : public RefCountBug { +public: + OverAutorelease(const CheckerBase *checker) + : RefCountBug(checker, "Object autoreleased too many times") {} + + const char *getDescription() const override { + return "Object autoreleased too many times"; + } +}; + +class ReturnedNotOwnedForOwned : public RefCountBug { +public: + ReturnedNotOwnedForOwned(const CheckerBase *checker) + : RefCountBug(checker, "Method should return an owned object") {} + + const char *getDescription() const override { + return "Object with a +0 retain count returned to caller where a +1 " + "(owning) retain count is expected"; + } +}; + +class Leak : public RefCountBug { +public: + Leak(const CheckerBase *checker, StringRef name) : RefCountBug(checker, name) { + // Leaks should not be reported if they are post-dominated by a sink. + setSuppressOnSink(true); + } + + const char *getDescription() const override { return ""; } + + bool isLeak() const override { return true; } +}; + +} // end namespace retaincountchecker +} // end namespace ento +} // end namespace clang + +void RefVal::print(raw_ostream &Out) const { + if (!T.isNull()) + Out << "Tracked " << T.getAsString() << " | "; + + switch (getKind()) { + default: llvm_unreachable("Invalid RefVal kind"); + case Owned: { + Out << "Owned"; + unsigned cnt = getCount(); + if (cnt) Out << " (+ " << cnt << ")"; + break; + } + + case NotOwned: { + Out << "NotOwned"; + unsigned cnt = getCount(); + if (cnt) Out << " (+ " << cnt << ")"; + break; + } + + case ReturnedOwned: { + Out << "ReturnedOwned"; + unsigned cnt = getCount(); + if (cnt) Out << " (+ " << cnt << ")"; + break; + } + + case ReturnedNotOwned: { + Out << "ReturnedNotOwned"; + unsigned cnt = getCount(); + if (cnt) Out << " (+ " << cnt << ")"; + break; + } + + case Released: + Out << "Released"; + break; + + case ErrorDeallocNotOwned: + Out << "-dealloc (not-owned)"; + break; + + case ErrorLeak: + Out << "Leaked"; + break; + + case ErrorLeakReturned: + Out << "Leaked (Bad naming)"; + break; + + case ErrorUseAfterRelease: + Out << "Use-After-Release [ERROR]"; + break; + + case ErrorReleaseNotOwned: + Out << "Release of Not-Owned [ERROR]"; + break; + + case RefVal::ErrorOverAutorelease: + Out << "Over-autoreleased"; + break; + + case RefVal::ErrorReturnedNotOwned: + Out << "Non-owned object returned instead of owned"; + break; + } + + switch (getIvarAccessHistory()) { + case IvarAccessHistory::None: + break; + case IvarAccessHistory::AccessedDirectly: + Out << " [direct ivar access]"; + break; + case IvarAccessHistory::ReleasedAfterDirectAccess: + Out << " [released after direct ivar access]"; + } + + if (ACnt) { + Out << " [autorelease -" << ACnt << ']'; + } +} + +namespace { +class StopTrackingCallback final : public SymbolVisitor { + ProgramStateRef state; +public: + StopTrackingCallback(ProgramStateRef st) : state(std::move(st)) {} + ProgramStateRef getState() const { return state; } + + bool VisitSymbol(SymbolRef sym) override { + state = state->remove<RefBindings>(sym); + return true; + } +}; +} // end anonymous namespace + +//===----------------------------------------------------------------------===// +// Handle statements that may have an effect on refcounts. +//===----------------------------------------------------------------------===// + +void RetainCountChecker::checkPostStmt(const BlockExpr *BE, + CheckerContext &C) const { + + // Scan the BlockDecRefExprs for any object the retain count checker + // may be tracking. + if (!BE->getBlockDecl()->hasCaptures()) + return; + + ProgramStateRef state = C.getState(); + auto *R = cast<BlockDataRegion>(C.getSVal(BE).getAsRegion()); + + BlockDataRegion::referenced_vars_iterator I = R->referenced_vars_begin(), + E = R->referenced_vars_end(); + + if (I == E) + return; + + // FIXME: For now we invalidate the tracking of all symbols passed to blocks + // via captured variables, even though captured variables result in a copy + // and in implicit increment/decrement of a retain count. + SmallVector<const MemRegion*, 10> Regions; + const LocationContext *LC = C.getLocationContext(); + MemRegionManager &MemMgr = C.getSValBuilder().getRegionManager(); + + for ( ; I != E; ++I) { + const VarRegion *VR = I.getCapturedRegion(); + if (VR->getSuperRegion() == R) { + VR = MemMgr.getVarRegion(VR->getDecl(), LC); + } + Regions.push_back(VR); + } + + state = state->scanReachableSymbols<StopTrackingCallback>(Regions).getState(); + C.addTransition(state); +} + +void RetainCountChecker::checkPostStmt(const CastExpr *CE, + CheckerContext &C) const { + const ObjCBridgedCastExpr *BE = dyn_cast<ObjCBridgedCastExpr>(CE); + if (!BE) + return; + + ArgEffect AE = ArgEffect(IncRef, ObjKind::ObjC); + + switch (BE->getBridgeKind()) { + case OBC_Bridge: + // Do nothing. + return; + case OBC_BridgeRetained: + AE = AE.withKind(IncRef); + break; + case OBC_BridgeTransfer: + AE = AE.withKind(DecRefBridgedTransferred); + break; + } + + ProgramStateRef state = C.getState(); + SymbolRef Sym = C.getSVal(CE).getAsLocSymbol(); + if (!Sym) + return; + const RefVal* T = getRefBinding(state, Sym); + if (!T) + return; + + RefVal::Kind hasErr = (RefVal::Kind) 0; + state = updateSymbol(state, Sym, *T, AE, hasErr, C); + + if (hasErr) { + // FIXME: If we get an error during a bridge cast, should we report it? + return; + } + + C.addTransition(state); +} + +void RetainCountChecker::processObjCLiterals(CheckerContext &C, + const Expr *Ex) const { + ProgramStateRef state = C.getState(); + const ExplodedNode *pred = C.getPredecessor(); + for (const Stmt *Child : Ex->children()) { + SVal V = pred->getSVal(Child); + if (SymbolRef sym = V.getAsSymbol()) + if (const RefVal* T = getRefBinding(state, sym)) { + RefVal::Kind hasErr = (RefVal::Kind) 0; + state = updateSymbol(state, sym, *T, + ArgEffect(MayEscape, ObjKind::ObjC), hasErr, C); + if (hasErr) { + processNonLeakError(state, Child->getSourceRange(), hasErr, sym, C); + return; + } + } + } + + // Return the object as autoreleased. + // RetEffect RE = RetEffect::MakeNotOwned(ObjKind::ObjC); + if (SymbolRef sym = + state->getSVal(Ex, pred->getLocationContext()).getAsSymbol()) { + QualType ResultTy = Ex->getType(); + state = setRefBinding(state, sym, + RefVal::makeNotOwned(ObjKind::ObjC, ResultTy)); + } + + C.addTransition(state); +} + +void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL, + CheckerContext &C) const { + // Apply the 'MayEscape' to all values. + processObjCLiterals(C, AL); +} + +void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, + CheckerContext &C) const { + // Apply the 'MayEscape' to all keys and values. + processObjCLiterals(C, DL); +} + +void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex, + CheckerContext &C) const { + const ExplodedNode *Pred = C.getPredecessor(); + ProgramStateRef State = Pred->getState(); + + if (SymbolRef Sym = Pred->getSVal(Ex).getAsSymbol()) { + QualType ResultTy = Ex->getType(); + State = setRefBinding(State, Sym, + RefVal::makeNotOwned(ObjKind::ObjC, ResultTy)); + } + + C.addTransition(State); +} + +void RetainCountChecker::checkPostStmt(const ObjCIvarRefExpr *IRE, + CheckerContext &C) const { + Optional<Loc> IVarLoc = C.getSVal(IRE).getAs<Loc>(); + if (!IVarLoc) + return; + + ProgramStateRef State = C.getState(); + SymbolRef Sym = State->getSVal(*IVarLoc).getAsSymbol(); + if (!Sym || !dyn_cast_or_null<ObjCIvarRegion>(Sym->getOriginRegion())) + return; + + // Accessing an ivar directly is unusual. If we've done that, be more + // forgiving about what the surrounding code is allowed to do. + + QualType Ty = Sym->getType(); + ObjKind Kind; + if (Ty->isObjCRetainableType()) + Kind = ObjKind::ObjC; + else if (coreFoundation::isCFObjectRef(Ty)) + Kind = ObjKind::CF; + else + return; + + // If the value is already known to be nil, don't bother tracking it. + ConstraintManager &CMgr = State->getConstraintManager(); + if (CMgr.isNull(State, Sym).isConstrainedTrue()) + return; + + if (const RefVal *RV = getRefBinding(State, Sym)) { + // If we've seen this symbol before, or we're only seeing it now because + // of something the analyzer has synthesized, don't do anything. + if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None || + isSynthesizedAccessor(C.getStackFrame())) { + return; + } + + // Note that this value has been loaded from an ivar. + C.addTransition(setRefBinding(State, Sym, RV->withIvarAccess())); + return; + } + + RefVal PlusZero = RefVal::makeNotOwned(Kind, Ty); + + // In a synthesized accessor, the effective retain count is +0. + if (isSynthesizedAccessor(C.getStackFrame())) { + C.addTransition(setRefBinding(State, Sym, PlusZero)); + return; + } + + State = setRefBinding(State, Sym, PlusZero.withIvarAccess()); + C.addTransition(State); +} + +void RetainCountChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + RetainSummaryManager &Summaries = getSummaryManager(C); + + // Leave null if no receiver. + QualType ReceiverType; + if (const auto *MC = dyn_cast<ObjCMethodCall>(&Call)) { + if (MC->isInstanceMessage()) { + SVal ReceiverV = MC->getReceiverSVal(); + if (SymbolRef Sym = ReceiverV.getAsLocSymbol()) + if (const RefVal *T = getRefBinding(C.getState(), Sym)) + ReceiverType = T->getType(); + } + } + + const RetainSummary *Summ = Summaries.getSummary(Call, ReceiverType); + + if (C.wasInlined) { + processSummaryOfInlined(*Summ, Call, C); + return; + } + checkSummary(*Summ, Call, C); +} + +RefCountBug * +RetainCountChecker::getLeakWithinFunctionBug(const LangOptions &LOpts) const { + if (!leakWithinFunction) + leakWithinFunction.reset(new Leak(this, "Leak")); + return leakWithinFunction.get(); +} + +RefCountBug * +RetainCountChecker::getLeakAtReturnBug(const LangOptions &LOpts) const { + if (!leakAtReturn) + leakAtReturn.reset(new Leak(this, "Leak of returned object")); + return leakAtReturn.get(); +} + +/// GetReturnType - Used to get the return type of a message expression or +/// function call with the intention of affixing that type to a tracked symbol. +/// While the return type can be queried directly from RetEx, when +/// invoking class methods we augment to the return type to be that of +/// a pointer to the class (as opposed it just being id). +// FIXME: We may be able to do this with related result types instead. +// This function is probably overestimating. +static QualType GetReturnType(const Expr *RetE, ASTContext &Ctx) { + QualType RetTy = RetE->getType(); + // If RetE is not a message expression just return its type. + // If RetE is a message expression, return its types if it is something + /// more specific than id. + if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(RetE)) + if (const ObjCObjectPointerType *PT = RetTy->getAs<ObjCObjectPointerType>()) + if (PT->isObjCQualifiedIdType() || PT->isObjCIdType() || + PT->isObjCClassType()) { + // At this point we know the return type of the message expression is + // id, id<...>, or Class. If we have an ObjCInterfaceDecl, we know this + // is a call to a class method whose type we can resolve. In such + // cases, promote the return type to XXX* (where XXX is the class). + const ObjCInterfaceDecl *D = ME->getReceiverInterface(); + return !D ? RetTy : + Ctx.getObjCObjectPointerType(Ctx.getObjCInterfaceType(D)); + } + + return RetTy; +} + +static Optional<RefVal> refValFromRetEffect(RetEffect RE, + QualType ResultTy) { + if (RE.isOwned()) { + return RefVal::makeOwned(RE.getObjKind(), ResultTy); + } else if (RE.notOwned()) { + return RefVal::makeNotOwned(RE.getObjKind(), ResultTy); + } + + return None; +} + +static bool isPointerToObject(QualType QT) { + QualType PT = QT->getPointeeType(); + if (!PT.isNull()) + if (PT->getAsCXXRecordDecl()) + return true; + return false; +} + +/// Whether the tracked value should be escaped on a given call. +/// OSObjects are escaped when passed to void * / etc. +static bool shouldEscapeOSArgumentOnCall(const CallEvent &CE, unsigned ArgIdx, + const RefVal *TrackedValue) { + if (TrackedValue->getObjKind() != ObjKind::OS) + return false; + if (ArgIdx >= CE.parameters().size()) + return false; + return !isPointerToObject(CE.parameters()[ArgIdx]->getType()); +} + +// We don't always get the exact modeling of the function with regards to the +// retain count checker even when the function is inlined. For example, we need +// to stop tracking the symbols which were marked with StopTrackingHard. +void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ, + const CallEvent &CallOrMsg, + CheckerContext &C) const { + ProgramStateRef state = C.getState(); + + // Evaluate the effect of the arguments. + for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) { + SVal V = CallOrMsg.getArgSVal(idx); + + if (SymbolRef Sym = V.getAsLocSymbol()) { + bool ShouldRemoveBinding = Summ.getArg(idx).getKind() == StopTrackingHard; + if (const RefVal *T = getRefBinding(state, Sym)) + if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T)) + ShouldRemoveBinding = true; + + if (ShouldRemoveBinding) + state = removeRefBinding(state, Sym); + } + } + + // Evaluate the effect on the message receiver. + if (const auto *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg)) { + if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) { + if (Summ.getReceiverEffect().getKind() == StopTrackingHard) { + state = removeRefBinding(state, Sym); + } + } + } + + // Consult the summary for the return value. + RetEffect RE = Summ.getRetEffect(); + + if (SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol()) { + if (RE.getKind() == RetEffect::NoRetHard) + state = removeRefBinding(state, Sym); + } + + C.addTransition(state); +} + +static bool shouldEscapeRegion(const MemRegion *R) { + + // We do not currently model what happens when a symbol is + // assigned to a struct field, so be conservative here and let the symbol + // go. TODO: This could definitely be improved upon. + return !R->hasStackStorage() || !isa<VarRegion>(R); +} + +static SmallVector<ProgramStateRef, 2> +updateOutParameters(ProgramStateRef State, const RetainSummary &Summ, + const CallEvent &CE) { + + SVal L = CE.getReturnValue(); + + // Splitting is required to support out parameters, + // as out parameters might be created only on the "success" branch. + // We want to avoid eagerly splitting unless out parameters are actually + // needed. + bool SplitNecessary = false; + for (auto &P : Summ.getArgEffects()) + if (P.second.getKind() == RetainedOutParameterOnNonZero || + P.second.getKind() == RetainedOutParameterOnZero) + SplitNecessary = true; + + ProgramStateRef AssumeNonZeroReturn = State; + ProgramStateRef AssumeZeroReturn = State; + + if (SplitNecessary) { + if (auto DL = L.getAs<DefinedOrUnknownSVal>()) { + AssumeNonZeroReturn = AssumeNonZeroReturn->assume(*DL, true); + AssumeZeroReturn = AssumeZeroReturn->assume(*DL, false); + } + } + + for (unsigned idx = 0, e = CE.getNumArgs(); idx != e; ++idx) { + SVal ArgVal = CE.getArgSVal(idx); + ArgEffect AE = Summ.getArg(idx); + + auto *ArgRegion = dyn_cast_or_null<TypedValueRegion>(ArgVal.getAsRegion()); + if (!ArgRegion) + continue; + + QualType PointeeTy = ArgRegion->getValueType(); + SVal PointeeVal = State->getSVal(ArgRegion); + SymbolRef Pointee = PointeeVal.getAsLocSymbol(); + if (!Pointee) + continue; + + if (shouldEscapeRegion(ArgRegion)) + continue; + + auto makeNotOwnedParameter = [&](ProgramStateRef St) { + return setRefBinding(St, Pointee, + RefVal::makeNotOwned(AE.getObjKind(), PointeeTy)); + }; + auto makeOwnedParameter = [&](ProgramStateRef St) { + return setRefBinding(St, Pointee, + RefVal::makeOwned(ObjKind::OS, PointeeTy)); + }; + + switch (AE.getKind()) { + case UnretainedOutParameter: + AssumeNonZeroReturn = makeNotOwnedParameter(AssumeNonZeroReturn); + AssumeZeroReturn = makeNotOwnedParameter(AssumeZeroReturn); + break; + case RetainedOutParameter: + AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn); + AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn); + break; + case RetainedOutParameterOnNonZero: + AssumeNonZeroReturn = makeOwnedParameter(AssumeNonZeroReturn); + break; + case RetainedOutParameterOnZero: + AssumeZeroReturn = makeOwnedParameter(AssumeZeroReturn); + break; + default: + break; + } + } + + if (SplitNecessary) { + return {AssumeNonZeroReturn, AssumeZeroReturn}; + } else { + assert(AssumeZeroReturn == AssumeNonZeroReturn); + return {AssumeZeroReturn}; + } +} + +void RetainCountChecker::checkSummary(const RetainSummary &Summ, + const CallEvent &CallOrMsg, + CheckerContext &C) const { + ProgramStateRef state = C.getState(); + + // Evaluate the effect of the arguments. + RefVal::Kind hasErr = (RefVal::Kind) 0; + SourceRange ErrorRange; + SymbolRef ErrorSym = nullptr; + + // Helper tag for providing diagnostics: indicate whether dealloc was sent + // at this location. + static CheckerProgramPointTag DeallocSentTag(this, DeallocTagDescription); + bool DeallocSent = false; + + for (unsigned idx = 0, e = CallOrMsg.getNumArgs(); idx != e; ++idx) { + SVal V = CallOrMsg.getArgSVal(idx); + + ArgEffect Effect = Summ.getArg(idx); + if (SymbolRef Sym = V.getAsLocSymbol()) { + if (const RefVal *T = getRefBinding(state, Sym)) { + + if (shouldEscapeOSArgumentOnCall(CallOrMsg, idx, T)) + Effect = ArgEffect(StopTrackingHard, ObjKind::OS); + + state = updateSymbol(state, Sym, *T, Effect, hasErr, C); + if (hasErr) { + ErrorRange = CallOrMsg.getArgSourceRange(idx); + ErrorSym = Sym; + break; + } else if (Effect.getKind() == Dealloc) { + DeallocSent = true; + } + } + } + } + + // Evaluate the effect on the message receiver / `this` argument. + bool ReceiverIsTracked = false; + if (!hasErr) { + if (const auto *MsgInvocation = dyn_cast<ObjCMethodCall>(&CallOrMsg)) { + if (SymbolRef Sym = MsgInvocation->getReceiverSVal().getAsLocSymbol()) { + if (const RefVal *T = getRefBinding(state, Sym)) { + ReceiverIsTracked = true; + state = updateSymbol(state, Sym, *T, + Summ.getReceiverEffect(), hasErr, C); + if (hasErr) { + ErrorRange = MsgInvocation->getOriginExpr()->getReceiverRange(); + ErrorSym = Sym; + } else if (Summ.getReceiverEffect().getKind() == Dealloc) { + DeallocSent = true; + } + } + } + } else if (const auto *MCall = dyn_cast<CXXMemberCall>(&CallOrMsg)) { + if (SymbolRef Sym = MCall->getCXXThisVal().getAsLocSymbol()) { + if (const RefVal *T = getRefBinding(state, Sym)) { + state = updateSymbol(state, Sym, *T, Summ.getThisEffect(), + hasErr, C); + if (hasErr) { + ErrorRange = MCall->getOriginExpr()->getSourceRange(); + ErrorSym = Sym; + } + } + } + } + } + + // Process any errors. + if (hasErr) { + processNonLeakError(state, ErrorRange, hasErr, ErrorSym, C); + return; + } + + // Consult the summary for the return value. + RetEffect RE = Summ.getRetEffect(); + + if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) { + if (ReceiverIsTracked) + RE = getSummaryManager(C).getObjAllocRetEffect(); + else + RE = RetEffect::MakeNoRet(); + } + + if (SymbolRef Sym = CallOrMsg.getReturnValue().getAsSymbol()) { + QualType ResultTy = CallOrMsg.getResultType(); + if (RE.notOwned()) { + const Expr *Ex = CallOrMsg.getOriginExpr(); + assert(Ex); + ResultTy = GetReturnType(Ex, C.getASTContext()); + } + if (Optional<RefVal> updatedRefVal = refValFromRetEffect(RE, ResultTy)) + state = setRefBinding(state, Sym, *updatedRefVal); + } + + SmallVector<ProgramStateRef, 2> Out = + updateOutParameters(state, Summ, CallOrMsg); + + for (ProgramStateRef St : Out) { + if (DeallocSent) { + C.addTransition(St, C.getPredecessor(), &DeallocSentTag); + } else { + C.addTransition(St); + } + } +} + +ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state, + SymbolRef sym, RefVal V, + ArgEffect AE, + RefVal::Kind &hasErr, + CheckerContext &C) const { + bool IgnoreRetainMsg = (bool)C.getASTContext().getLangOpts().ObjCAutoRefCount; + if (AE.getObjKind() == ObjKind::ObjC && IgnoreRetainMsg) { + switch (AE.getKind()) { + default: + break; + case IncRef: + AE = AE.withKind(DoNothing); + break; + case DecRef: + AE = AE.withKind(DoNothing); + break; + case DecRefAndStopTrackingHard: + AE = AE.withKind(StopTracking); + break; + } + } + + // Handle all use-after-releases. + if (V.getKind() == RefVal::Released) { + V = V ^ RefVal::ErrorUseAfterRelease; + hasErr = V.getKind(); + return setRefBinding(state, sym, V); + } + + switch (AE.getKind()) { + case UnretainedOutParameter: + case RetainedOutParameter: + case RetainedOutParameterOnZero: + case RetainedOutParameterOnNonZero: + llvm_unreachable("Applies to pointer-to-pointer parameters, which should " + "not have ref state."); + + case Dealloc: // NB. we only need to add a note in a non-error case. + switch (V.getKind()) { + default: + llvm_unreachable("Invalid RefVal state for an explicit dealloc."); + case RefVal::Owned: + // The object immediately transitions to the released state. + V = V ^ RefVal::Released; + V.clearCounts(); + return setRefBinding(state, sym, V); + case RefVal::NotOwned: + V = V ^ RefVal::ErrorDeallocNotOwned; + hasErr = V.getKind(); + break; + } + break; + + case MayEscape: + if (V.getKind() == RefVal::Owned) { + V = V ^ RefVal::NotOwned; + break; + } + + LLVM_FALLTHROUGH; + + case DoNothing: + return state; + + case Autorelease: + // Update the autorelease counts. + V = V.autorelease(); + break; + + case StopTracking: + case StopTrackingHard: + return removeRefBinding(state, sym); + + case IncRef: + switch (V.getKind()) { + default: + llvm_unreachable("Invalid RefVal state for a retain."); + case RefVal::Owned: + case RefVal::NotOwned: + V = V + 1; + break; + } + break; + + case DecRef: + case DecRefBridgedTransferred: + case DecRefAndStopTrackingHard: + switch (V.getKind()) { + default: + // case 'RefVal::Released' handled above. + llvm_unreachable("Invalid RefVal state for a release."); + + case RefVal::Owned: + assert(V.getCount() > 0); + if (V.getCount() == 1) { + if (AE.getKind() == DecRefBridgedTransferred || + V.getIvarAccessHistory() == + RefVal::IvarAccessHistory::AccessedDirectly) + V = V ^ RefVal::NotOwned; + else + V = V ^ RefVal::Released; + } else if (AE.getKind() == DecRefAndStopTrackingHard) { + return removeRefBinding(state, sym); + } + + V = V - 1; + break; + + case RefVal::NotOwned: + if (V.getCount() > 0) { + if (AE.getKind() == DecRefAndStopTrackingHard) + return removeRefBinding(state, sym); + V = V - 1; + } else if (V.getIvarAccessHistory() == + RefVal::IvarAccessHistory::AccessedDirectly) { + // Assume that the instance variable was holding on the object at + // +1, and we just didn't know. + if (AE.getKind() == DecRefAndStopTrackingHard) + return removeRefBinding(state, sym); + V = V.releaseViaIvar() ^ RefVal::Released; + } else { + V = V ^ RefVal::ErrorReleaseNotOwned; + hasErr = V.getKind(); + } + break; + } + break; + } + return setRefBinding(state, sym, V); +} + +void RetainCountChecker::processNonLeakError(ProgramStateRef St, + SourceRange ErrorRange, + RefVal::Kind ErrorKind, + SymbolRef Sym, + CheckerContext &C) const { + // HACK: Ignore retain-count issues on values accessed through ivars, + // because of cases like this: + // [_contentView retain]; + // [_contentView removeFromSuperview]; + // [self addSubview:_contentView]; // invalidates 'self' + // [_contentView release]; + if (const RefVal *RV = getRefBinding(St, Sym)) + if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None) + return; + + ExplodedNode *N = C.generateErrorNode(St); + if (!N) + return; + + RefCountBug *BT; + switch (ErrorKind) { + default: + llvm_unreachable("Unhandled error."); + case RefVal::ErrorUseAfterRelease: + if (!useAfterRelease) + useAfterRelease.reset(new UseAfterRelease(this)); + BT = useAfterRelease.get(); + break; + case RefVal::ErrorReleaseNotOwned: + if (!releaseNotOwned) + releaseNotOwned.reset(new BadRelease(this)); + BT = releaseNotOwned.get(); + break; + case RefVal::ErrorDeallocNotOwned: + if (!deallocNotOwned) + deallocNotOwned.reset(new DeallocNotOwned(this)); + BT = deallocNotOwned.get(); + break; + } + + assert(BT); + auto report = llvm::make_unique<RefCountReport>( + *BT, C.getASTContext().getLangOpts(), N, Sym); + report->addRange(ErrorRange); + C.emitReport(std::move(report)); +} + +//===----------------------------------------------------------------------===// +// Handle the return values of retain-count-related functions. +//===----------------------------------------------------------------------===// + +bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { + // Get the callee. We're only interested in simple C functions. + ProgramStateRef state = C.getState(); + const FunctionDecl *FD = C.getCalleeDecl(CE); + if (!FD) + return false; + + RetainSummaryManager &SmrMgr = getSummaryManager(C); + QualType ResultTy = CE->getCallReturnType(C.getASTContext()); + + // See if the function has 'rc_ownership_trusted_implementation' + // annotate attribute. If it does, we will not inline it. + bool hasTrustedImplementationAnnotation = false; + + const LocationContext *LCtx = C.getLocationContext(); + + using BehaviorSummary = RetainSummaryManager::BehaviorSummary; + Optional<BehaviorSummary> BSmr = + SmrMgr.canEval(CE, FD, hasTrustedImplementationAnnotation); + + // See if it's one of the specific functions we know how to eval. + if (!BSmr) + return false; + + // Bind the return value. + if (BSmr == BehaviorSummary::Identity || + BSmr == BehaviorSummary::IdentityOrZero) { + SVal RetVal = state->getSVal(CE->getArg(0), LCtx); + + // If the receiver is unknown or the function has + // 'rc_ownership_trusted_implementation' annotate attribute, conjure a + // return value. + if (RetVal.isUnknown() || + (hasTrustedImplementationAnnotation && !ResultTy.isNull())) { + SValBuilder &SVB = C.getSValBuilder(); + RetVal = + SVB.conjureSymbolVal(nullptr, CE, LCtx, ResultTy, C.blockCount()); + } + state = state->BindExpr(CE, LCtx, RetVal, /*Invalidate=*/false); + + if (BSmr == BehaviorSummary::IdentityOrZero) { + // Add a branch where the output is zero. + ProgramStateRef NullOutputState = C.getState(); + + // Assume that output is zero on the other branch. + NullOutputState = NullOutputState->BindExpr( + CE, LCtx, C.getSValBuilder().makeNull(), /*Invalidate=*/false); + + C.addTransition(NullOutputState); + + // And on the original branch assume that both input and + // output are non-zero. + if (auto L = RetVal.getAs<DefinedOrUnknownSVal>()) + state = state->assume(*L, /*Assumption=*/true); + + } + } + + C.addTransition(state); + return true; +} + +ExplodedNode * RetainCountChecker::processReturn(const ReturnStmt *S, + CheckerContext &C) const { + ExplodedNode *Pred = C.getPredecessor(); + + // Only adjust the reference count if this is the top-level call frame, + // and not the result of inlining. In the future, we should do + // better checking even for inlined calls, and see if they match + // with their expected semantics (e.g., the method should return a retained + // object, etc.). + if (!C.inTopFrame()) + return Pred; + + if (!S) + return Pred; + + const Expr *RetE = S->getRetValue(); + if (!RetE) + return Pred; + + ProgramStateRef state = C.getState(); + SymbolRef Sym = + state->getSValAsScalarOrLoc(RetE, C.getLocationContext()).getAsLocSymbol(); + if (!Sym) + return Pred; + + // Get the reference count binding (if any). + const RefVal *T = getRefBinding(state, Sym); + if (!T) + return Pred; + + // Change the reference count. + RefVal X = *T; + + switch (X.getKind()) { + case RefVal::Owned: { + unsigned cnt = X.getCount(); + assert(cnt > 0); + X.setCount(cnt - 1); + X = X ^ RefVal::ReturnedOwned; + break; + } + + case RefVal::NotOwned: { + unsigned cnt = X.getCount(); + if (cnt) { + X.setCount(cnt - 1); + X = X ^ RefVal::ReturnedOwned; + } else { + X = X ^ RefVal::ReturnedNotOwned; + } + break; + } + + default: + return Pred; + } + + // Update the binding. + state = setRefBinding(state, Sym, X); + Pred = C.addTransition(state); + + // At this point we have updated the state properly. + // Everything after this is merely checking to see if the return value has + // been over- or under-retained. + + // Did we cache out? + if (!Pred) + return nullptr; + + // Update the autorelease counts. + static CheckerProgramPointTag AutoreleaseTag(this, "Autorelease"); + state = handleAutoreleaseCounts(state, Pred, &AutoreleaseTag, C, Sym, X, S); + + // Have we generated a sink node? + if (!state) + return nullptr; + + // Get the updated binding. + T = getRefBinding(state, Sym); + assert(T); + X = *T; + + // Consult the summary of the enclosing method. + RetainSummaryManager &Summaries = getSummaryManager(C); + const Decl *CD = &Pred->getCodeDecl(); + RetEffect RE = RetEffect::MakeNoRet(); + + // FIXME: What is the convention for blocks? Is there one? + if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(CD)) { + const RetainSummary *Summ = Summaries.getMethodSummary(MD); + RE = Summ->getRetEffect(); + } else if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CD)) { + if (!isa<CXXMethodDecl>(FD)) { + const RetainSummary *Summ = Summaries.getFunctionSummary(FD); + RE = Summ->getRetEffect(); + } + } + + return checkReturnWithRetEffect(S, C, Pred, RE, X, Sym, state); +} + +ExplodedNode * RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, + CheckerContext &C, + ExplodedNode *Pred, + RetEffect RE, RefVal X, + SymbolRef Sym, + ProgramStateRef state) const { + // HACK: Ignore retain-count issues on values accessed through ivars, + // because of cases like this: + // [_contentView retain]; + // [_contentView removeFromSuperview]; + // [self addSubview:_contentView]; // invalidates 'self' + // [_contentView release]; + if (X.getIvarAccessHistory() != RefVal::IvarAccessHistory::None) + return Pred; + + // Any leaks or other errors? + if (X.isReturnedOwned() && X.getCount() == 0) { + if (RE.getKind() != RetEffect::NoRet) { + if (!RE.isOwned()) { + + // The returning type is a CF, we expect the enclosing method should + // return ownership. + X = X ^ RefVal::ErrorLeakReturned; + + // Generate an error node. + state = setRefBinding(state, Sym, X); + + static CheckerProgramPointTag ReturnOwnLeakTag(this, "ReturnsOwnLeak"); + ExplodedNode *N = C.addTransition(state, Pred, &ReturnOwnLeakTag); + if (N) { + const LangOptions &LOpts = C.getASTContext().getLangOpts(); + auto R = llvm::make_unique<RefLeakReport>( + *getLeakAtReturnBug(LOpts), LOpts, N, Sym, C); + C.emitReport(std::move(R)); + } + return N; + } + } + } else if (X.isReturnedNotOwned()) { + if (RE.isOwned()) { + if (X.getIvarAccessHistory() == + RefVal::IvarAccessHistory::AccessedDirectly) { + // Assume the method was trying to transfer a +1 reference from a + // strong ivar to the caller. + state = setRefBinding(state, Sym, + X.releaseViaIvar() ^ RefVal::ReturnedOwned); + } else { + // Trying to return a not owned object to a caller expecting an + // owned object. + state = setRefBinding(state, Sym, X ^ RefVal::ErrorReturnedNotOwned); + + static CheckerProgramPointTag + ReturnNotOwnedTag(this, "ReturnNotOwnedForOwned"); + + ExplodedNode *N = C.addTransition(state, Pred, &ReturnNotOwnedTag); + if (N) { + if (!returnNotOwnedForOwned) + returnNotOwnedForOwned.reset(new ReturnedNotOwnedForOwned(this)); + + auto R = llvm::make_unique<RefCountReport>( + *returnNotOwnedForOwned, C.getASTContext().getLangOpts(), N, Sym); + C.emitReport(std::move(R)); + } + return N; + } + } + } + return Pred; +} + +//===----------------------------------------------------------------------===// +// Check various ways a symbol can be invalidated. +//===----------------------------------------------------------------------===// + +void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, + CheckerContext &C) const { + // Are we storing to something that causes the value to "escape"? + bool escapes = true; + + // A value escapes in three possible cases (this may change): + // + // (1) we are binding to something that is not a memory region. + // (2) we are binding to a memregion that does not have stack storage + ProgramStateRef state = C.getState(); + + if (auto regionLoc = loc.getAs<loc::MemRegionVal>()) { + escapes = shouldEscapeRegion(regionLoc->getRegion()); + } + + // If we are storing the value into an auto function scope variable annotated + // with (__attribute__((cleanup))), stop tracking the value to avoid leak + // false positives. + if (const auto *LVR = dyn_cast_or_null<VarRegion>(loc.getAsRegion())) { + const VarDecl *VD = LVR->getDecl(); + if (VD->hasAttr<CleanupAttr>()) { + escapes = true; + } + } + + // If our store can represent the binding and we aren't storing to something + // that doesn't have local storage then just return and have the simulation + // state continue as is. + if (!escapes) + return; + + // Otherwise, find all symbols referenced by 'val' that we are tracking + // and stop tracking them. + state = state->scanReachableSymbols<StopTrackingCallback>(val).getState(); + C.addTransition(state); +} + +ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state, + SVal Cond, + bool Assumption) const { + // FIXME: We may add to the interface of evalAssume the list of symbols + // whose assumptions have changed. For now we just iterate through the + // bindings and check if any of the tracked symbols are NULL. This isn't + // too bad since the number of symbols we will track in practice are + // probably small and evalAssume is only called at branches and a few + // other places. + RefBindingsTy B = state->get<RefBindings>(); + + if (B.isEmpty()) + return state; + + bool changed = false; + RefBindingsTy::Factory &RefBFactory = state->get_context<RefBindings>(); + + for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { + // Check if the symbol is null stop tracking the symbol. + ConstraintManager &CMgr = state->getConstraintManager(); + ConditionTruthVal AllocFailed = CMgr.isNull(state, I.getKey()); + if (AllocFailed.isConstrainedTrue()) { + changed = true; + B = RefBFactory.remove(B, I.getKey()); + } + } + + if (changed) + state = state->set<RefBindings>(B); + + return state; +} + +ProgramStateRef +RetainCountChecker::checkRegionChanges(ProgramStateRef state, + const InvalidatedSymbols *invalidated, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const LocationContext *LCtx, + const CallEvent *Call) const { + if (!invalidated) + return state; + + llvm::SmallPtrSet<SymbolRef, 8> WhitelistedSymbols; + for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), + E = ExplicitRegions.end(); I != E; ++I) { + if (const SymbolicRegion *SR = (*I)->StripCasts()->getAs<SymbolicRegion>()) + WhitelistedSymbols.insert(SR->getSymbol()); + } + + for (SymbolRef sym : + llvm::make_range(invalidated->begin(), invalidated->end())) { + if (WhitelistedSymbols.count(sym)) + continue; + // Remove any existing reference-count binding. + state = removeRefBinding(state, sym); + } + return state; +} + +ProgramStateRef +RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, + ExplodedNode *Pred, + const ProgramPointTag *Tag, + CheckerContext &Ctx, + SymbolRef Sym, + RefVal V, + const ReturnStmt *S) const { + unsigned ACnt = V.getAutoreleaseCount(); + + // No autorelease counts? Nothing to be done. + if (!ACnt) + return state; + + unsigned Cnt = V.getCount(); + + // FIXME: Handle sending 'autorelease' to already released object. + + if (V.getKind() == RefVal::ReturnedOwned) + ++Cnt; + + // If we would over-release here, but we know the value came from an ivar, + // assume it was a strong ivar that's just been relinquished. + if (ACnt > Cnt && + V.getIvarAccessHistory() == RefVal::IvarAccessHistory::AccessedDirectly) { + V = V.releaseViaIvar(); + --ACnt; + } + + if (ACnt <= Cnt) { + if (ACnt == Cnt) { + V.clearCounts(); + if (V.getKind() == RefVal::ReturnedOwned) { + V = V ^ RefVal::ReturnedNotOwned; + } else { + V = V ^ RefVal::NotOwned; + } + } else { + V.setCount(V.getCount() - ACnt); + V.setAutoreleaseCount(0); + } + return setRefBinding(state, Sym, V); + } + + // HACK: Ignore retain-count issues on values accessed through ivars, + // because of cases like this: + // [_contentView retain]; + // [_contentView removeFromSuperview]; + // [self addSubview:_contentView]; // invalidates 'self' + // [_contentView release]; + if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None) + return state; + + // Woah! More autorelease counts then retain counts left. + // Emit hard error. + V = V ^ RefVal::ErrorOverAutorelease; + state = setRefBinding(state, Sym, V); + + ExplodedNode *N = Ctx.generateSink(state, Pred, Tag); + if (N) { + SmallString<128> sbuf; + llvm::raw_svector_ostream os(sbuf); + os << "Object was autoreleased "; + if (V.getAutoreleaseCount() > 1) + os << V.getAutoreleaseCount() << " times but the object "; + else + os << "but "; + os << "has a +" << V.getCount() << " retain count"; + + if (!overAutorelease) + overAutorelease.reset(new OverAutorelease(this)); + + const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); + auto R = llvm::make_unique<RefCountReport>(*overAutorelease, LOpts, N, Sym, + os.str()); + Ctx.emitReport(std::move(R)); + } + + return nullptr; +} + +ProgramStateRef +RetainCountChecker::handleSymbolDeath(ProgramStateRef state, + SymbolRef sid, RefVal V, + SmallVectorImpl<SymbolRef> &Leaked) const { + bool hasLeak; + + // HACK: Ignore retain-count issues on values accessed through ivars, + // because of cases like this: + // [_contentView retain]; + // [_contentView removeFromSuperview]; + // [self addSubview:_contentView]; // invalidates 'self' + // [_contentView release]; + if (V.getIvarAccessHistory() != RefVal::IvarAccessHistory::None) + hasLeak = false; + else if (V.isOwned()) + hasLeak = true; + else if (V.isNotOwned() || V.isReturnedOwned()) + hasLeak = (V.getCount() > 0); + else + hasLeak = false; + + if (!hasLeak) + return removeRefBinding(state, sid); + + Leaked.push_back(sid); + return setRefBinding(state, sid, V ^ RefVal::ErrorLeak); +} + +ExplodedNode * +RetainCountChecker::processLeaks(ProgramStateRef state, + SmallVectorImpl<SymbolRef> &Leaked, + CheckerContext &Ctx, + ExplodedNode *Pred) const { + // Generate an intermediate node representing the leak point. + ExplodedNode *N = Ctx.addTransition(state, Pred); + + if (N) { + for (SmallVectorImpl<SymbolRef>::iterator + I = Leaked.begin(), E = Leaked.end(); I != E; ++I) { + + const LangOptions &LOpts = Ctx.getASTContext().getLangOpts(); + RefCountBug *BT = Pred ? getLeakWithinFunctionBug(LOpts) + : getLeakAtReturnBug(LOpts); + assert(BT && "BugType not initialized."); + + Ctx.emitReport( + llvm::make_unique<RefLeakReport>(*BT, LOpts, N, *I, Ctx)); + } + } + + return N; +} + +static bool isISLObjectRef(QualType Ty) { + return StringRef(Ty.getAsString()).startswith("isl_"); +} + +void RetainCountChecker::checkBeginFunction(CheckerContext &Ctx) const { + if (!Ctx.inTopFrame()) + return; + + RetainSummaryManager &SmrMgr = getSummaryManager(Ctx); + const LocationContext *LCtx = Ctx.getLocationContext(); + const FunctionDecl *FD = dyn_cast<FunctionDecl>(LCtx->getDecl()); + + if (!FD || SmrMgr.isTrustedReferenceCountImplementation(FD)) + return; + + ProgramStateRef state = Ctx.getState(); + const RetainSummary *FunctionSummary = SmrMgr.getFunctionSummary(FD); + ArgEffects CalleeSideArgEffects = FunctionSummary->getArgEffects(); + + for (unsigned idx = 0, e = FD->getNumParams(); idx != e; ++idx) { + const ParmVarDecl *Param = FD->getParamDecl(idx); + SymbolRef Sym = state->getSVal(state->getRegion(Param, LCtx)).getAsSymbol(); + + QualType Ty = Param->getType(); + const ArgEffect *AE = CalleeSideArgEffects.lookup(idx); + if (AE && AE->getKind() == DecRef && isISLObjectRef(Ty)) { + state = setRefBinding( + state, Sym, RefVal::makeOwned(ObjKind::Generalized, Ty)); + } else if (isISLObjectRef(Ty)) { + state = setRefBinding( + state, Sym, + RefVal::makeNotOwned(ObjKind::Generalized, Ty)); + } + } + + Ctx.addTransition(state); +} + +void RetainCountChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &Ctx) const { + ExplodedNode *Pred = processReturn(RS, Ctx); + + // Created state cached out. + if (!Pred) { + return; + } + + ProgramStateRef state = Pred->getState(); + RefBindingsTy B = state->get<RefBindings>(); + + // Don't process anything within synthesized bodies. + const LocationContext *LCtx = Pred->getLocationContext(); + if (LCtx->getAnalysisDeclContext()->isBodyAutosynthesized()) { + assert(!LCtx->inTopFrame()); + return; + } + + for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { + state = handleAutoreleaseCounts(state, Pred, /*Tag=*/nullptr, Ctx, + I->first, I->second); + if (!state) + return; + } + + // If the current LocationContext has a parent, don't check for leaks. + // We will do that later. + // FIXME: we should instead check for imbalances of the retain/releases, + // and suggest annotations. + if (LCtx->getParent()) + return; + + B = state->get<RefBindings>(); + SmallVector<SymbolRef, 10> Leaked; + + for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) + state = handleSymbolDeath(state, I->first, I->second, Leaked); + + processLeaks(state, Leaked, Ctx, Pred); +} + +void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ExplodedNode *Pred = C.getPredecessor(); + + ProgramStateRef state = C.getState(); + RefBindingsTy B = state->get<RefBindings>(); + SmallVector<SymbolRef, 10> Leaked; + + // Update counts from autorelease pools + for (const auto &I: state->get<RefBindings>()) { + SymbolRef Sym = I.first; + if (SymReaper.isDead(Sym)) { + static CheckerProgramPointTag Tag(this, "DeadSymbolAutorelease"); + const RefVal &V = I.second; + state = handleAutoreleaseCounts(state, Pred, &Tag, C, Sym, V); + if (!state) + return; + + // Fetch the new reference count from the state, and use it to handle + // this symbol. + state = handleSymbolDeath(state, Sym, *getRefBinding(state, Sym), Leaked); + } + } + + if (Leaked.empty()) { + C.addTransition(state); + return; + } + + Pred = processLeaks(state, Leaked, C, Pred); + + // Did we cache out? + if (!Pred) + return; + + // Now generate a new node that nukes the old bindings. + // The only bindings left at this point are the leaked symbols. + RefBindingsTy::Factory &F = state->get_context<RefBindings>(); + B = state->get<RefBindings>(); + + for (SmallVectorImpl<SymbolRef>::iterator I = Leaked.begin(), + E = Leaked.end(); + I != E; ++I) + B = F.remove(B, *I); + + state = state->set<RefBindings>(B); + C.addTransition(state, Pred); +} + +void RetainCountChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + RefBindingsTy B = State->get<RefBindings>(); + + if (B.isEmpty()) + return; + + Out << Sep << NL; + + for (RefBindingsTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { + Out << I->first << " : "; + I->second.print(Out); + Out << NL; + } +} + +//===----------------------------------------------------------------------===// +// Checker registration. +//===----------------------------------------------------------------------===// + +void ento::registerRetainCountChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.registerChecker<RetainCountChecker>(); + Chk->TrackObjCAndCFObjects = true; +} + +// FIXME: remove this, hack for backwards compatibility: +// it should be possible to enable the NS/CF retain count checker as +// osx.cocoa.RetainCount, and it should be possible to disable +// osx.OSObjectRetainCount using osx.cocoa.RetainCount:CheckOSObject=false. +static bool hasPrevCheckOSObjectOptionDisabled(AnalyzerOptions &Options) { + auto I = Options.Config.find("osx.cocoa.RetainCount:CheckOSObject"); + if (I != Options.Config.end()) + return I->getValue() == "false"; + return false; +} + +void ento::registerOSObjectRetainCountChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.registerChecker<RetainCountChecker>(); + if (!hasPrevCheckOSObjectOptionDisabled(Mgr.getAnalyzerOptions())) + Chk->TrackOSObjects = true; +} diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h new file mode 100644 index 000000000000..31e2d9ae4932 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h @@ -0,0 +1,393 @@ +//==--- RetainCountChecker.h - Checks for leaks and other issues -*- 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 the methods for RetainCountChecker, which implements +// a reference count checker for Core Foundation and Cocoa on (Mac OS X). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_H + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "RetainCountDiagnostics.h" +#include "clang/AST/Attr.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/DeclObjC.h" +#include "clang/AST/ParentMap.h" +#include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Basic/LangOptions.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Analysis/SelectorExtras.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "clang/StaticAnalyzer/Core/RetainSummaryManager.h" +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/FoldingSet.h" +#include "llvm/ADT/ImmutableList.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/ADT/StringExtras.h" +#include <cstdarg> +#include <utility> + +namespace clang { +namespace ento { +namespace retaincountchecker { + +/// Metadata on reference. +class RefVal { +public: + enum Kind { + Owned = 0, // Owning reference. + NotOwned, // Reference is not owned by still valid (not freed). + Released, // Object has been released. + ReturnedOwned, // Returned object passes ownership to caller. + ReturnedNotOwned, // Return object does not pass ownership to caller. + ERROR_START, + ErrorDeallocNotOwned, // -dealloc called on non-owned object. + ErrorUseAfterRelease, // Object used after released. + ErrorReleaseNotOwned, // Release of an object that was not owned. + ERROR_LEAK_START, + ErrorLeak, // A memory leak due to excessive reference counts. + ErrorLeakReturned, // A memory leak due to the returning method not having + // the correct naming conventions. + ErrorOverAutorelease, + ErrorReturnedNotOwned + }; + + /// Tracks how an object referenced by an ivar has been used. + /// + /// This accounts for us not knowing if an arbitrary ivar is supposed to be + /// stored at +0 or +1. + enum class IvarAccessHistory { + None, + AccessedDirectly, + ReleasedAfterDirectAccess + }; + +private: + /// The number of outstanding retains. + unsigned Cnt; + /// The number of outstanding autoreleases. + unsigned ACnt; + /// The (static) type of the object at the time we started tracking it. + QualType T; + + /// The current state of the object. + /// + /// See the RefVal::Kind enum for possible values. + unsigned RawKind : 5; + + /// The kind of object being tracked (CF or ObjC or OSObject), if known. + /// + /// See the ObjKind enum for possible values. + unsigned RawObjectKind : 3; + + /// True if the current state and/or retain count may turn out to not be the + /// best possible approximation of the reference counting state. + /// + /// If true, the checker may decide to throw away ("override") this state + /// in favor of something else when it sees the object being used in new ways. + /// + /// This setting should not be propagated to state derived from this state. + /// Once we start deriving new states, it would be inconsistent to override + /// them. + unsigned RawIvarAccessHistory : 2; + + RefVal(Kind k, ObjKind o, unsigned cnt, unsigned acnt, QualType t, + IvarAccessHistory IvarAccess) + : Cnt(cnt), ACnt(acnt), T(t), RawKind(static_cast<unsigned>(k)), + RawObjectKind(static_cast<unsigned>(o)), + RawIvarAccessHistory(static_cast<unsigned>(IvarAccess)) { + assert(getKind() == k && "not enough bits for the kind"); + assert(getObjKind() == o && "not enough bits for the object kind"); + assert(getIvarAccessHistory() == IvarAccess && "not enough bits"); + } + +public: + Kind getKind() const { return static_cast<Kind>(RawKind); } + + ObjKind getObjKind() const { + return static_cast<ObjKind>(RawObjectKind); + } + + unsigned getCount() const { return Cnt; } + unsigned getAutoreleaseCount() const { return ACnt; } + unsigned getCombinedCounts() const { return Cnt + ACnt; } + void clearCounts() { + Cnt = 0; + ACnt = 0; + } + void setCount(unsigned i) { + Cnt = i; + } + void setAutoreleaseCount(unsigned i) { + ACnt = i; + } + + QualType getType() const { return T; } + + /// Returns what the analyzer knows about direct accesses to a particular + /// instance variable. + /// + /// If the object with this refcount wasn't originally from an Objective-C + /// ivar region, this should always return IvarAccessHistory::None. + IvarAccessHistory getIvarAccessHistory() const { + return static_cast<IvarAccessHistory>(RawIvarAccessHistory); + } + + bool isOwned() const { + return getKind() == Owned; + } + + bool isNotOwned() const { + return getKind() == NotOwned; + } + + bool isReturnedOwned() const { + return getKind() == ReturnedOwned; + } + + bool isReturnedNotOwned() const { + return getKind() == ReturnedNotOwned; + } + + /// Create a state for an object whose lifetime is the responsibility of the + /// current function, at least partially. + /// + /// Most commonly, this is an owned object with a retain count of +1. + static RefVal makeOwned(ObjKind o, QualType t) { + return RefVal(Owned, o, /*Count=*/1, 0, t, IvarAccessHistory::None); + } + + /// Create a state for an object whose lifetime is not the responsibility of + /// the current function. + /// + /// Most commonly, this is an unowned object with a retain count of +0. + static RefVal makeNotOwned(ObjKind o, QualType t) { + return RefVal(NotOwned, o, /*Count=*/0, 0, t, IvarAccessHistory::None); + } + + RefVal operator-(size_t i) const { + return RefVal(getKind(), getObjKind(), getCount() - i, + getAutoreleaseCount(), getType(), getIvarAccessHistory()); + } + + RefVal operator+(size_t i) const { + return RefVal(getKind(), getObjKind(), getCount() + i, + getAutoreleaseCount(), getType(), getIvarAccessHistory()); + } + + RefVal operator^(Kind k) const { + return RefVal(k, getObjKind(), getCount(), getAutoreleaseCount(), + getType(), getIvarAccessHistory()); + } + + RefVal autorelease() const { + return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount()+1, + getType(), getIvarAccessHistory()); + } + + RefVal withIvarAccess() const { + assert(getIvarAccessHistory() == IvarAccessHistory::None); + return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(), + getType(), IvarAccessHistory::AccessedDirectly); + } + + RefVal releaseViaIvar() const { + assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly); + return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(), + getType(), IvarAccessHistory::ReleasedAfterDirectAccess); + } + + // Comparison, profiling, and pretty-printing. + bool hasSameState(const RefVal &X) const { + return getKind() == X.getKind() && Cnt == X.Cnt && ACnt == X.ACnt && + getIvarAccessHistory() == X.getIvarAccessHistory(); + } + + bool operator==(const RefVal& X) const { + return T == X.T && hasSameState(X) && getObjKind() == X.getObjKind(); + } + + void Profile(llvm::FoldingSetNodeID& ID) const { + ID.Add(T); + ID.AddInteger(RawKind); + ID.AddInteger(Cnt); + ID.AddInteger(ACnt); + ID.AddInteger(RawObjectKind); + ID.AddInteger(RawIvarAccessHistory); + } + + void print(raw_ostream &Out) const; +}; + +class RetainCountChecker + : public Checker< check::Bind, + check::DeadSymbols, + check::BeginFunction, + check::EndFunction, + check::PostStmt<BlockExpr>, + check::PostStmt<CastExpr>, + check::PostStmt<ObjCArrayLiteral>, + check::PostStmt<ObjCDictionaryLiteral>, + check::PostStmt<ObjCBoxedExpr>, + check::PostStmt<ObjCIvarRefExpr>, + check::PostCall, + check::RegionChanges, + eval::Assume, + eval::Call > { + mutable std::unique_ptr<RefCountBug> useAfterRelease, releaseNotOwned; + mutable std::unique_ptr<RefCountBug> deallocNotOwned; + mutable std::unique_ptr<RefCountBug> overAutorelease, returnNotOwnedForOwned; + mutable std::unique_ptr<RefCountBug> leakWithinFunction, leakAtReturn; + + mutable std::unique_ptr<RetainSummaryManager> Summaries; +public: + static constexpr const char *DeallocTagDescription = "DeallocSent"; + + /// Track Objective-C and CoreFoundation objects. + bool TrackObjCAndCFObjects = false; + + /// Track sublcasses of OSObject. + bool TrackOSObjects = false; + + RetainCountChecker() {} + + RefCountBug *getLeakWithinFunctionBug(const LangOptions &LOpts) const; + + RefCountBug *getLeakAtReturnBug(const LangOptions &LOpts) const; + + RetainSummaryManager &getSummaryManager(ASTContext &Ctx) const { + // FIXME: We don't support ARC being turned on and off during one analysis. + // (nor, for that matter, do we support changing ASTContexts) + bool ARCEnabled = (bool)Ctx.getLangOpts().ObjCAutoRefCount; + if (!Summaries) { + Summaries.reset(new RetainSummaryManager( + Ctx, ARCEnabled, TrackObjCAndCFObjects, TrackOSObjects)); + } else { + assert(Summaries->isARCEnabled() == ARCEnabled); + } + return *Summaries; + } + + RetainSummaryManager &getSummaryManager(CheckerContext &C) const { + return getSummaryManager(C.getASTContext()); + } + + void printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const override; + + void checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const; + void checkPostStmt(const BlockExpr *BE, CheckerContext &C) const; + void checkPostStmt(const CastExpr *CE, CheckerContext &C) const; + + void checkPostStmt(const ObjCArrayLiteral *AL, CheckerContext &C) const; + void checkPostStmt(const ObjCDictionaryLiteral *DL, CheckerContext &C) const; + void checkPostStmt(const ObjCBoxedExpr *BE, CheckerContext &C) const; + + void checkPostStmt(const ObjCIvarRefExpr *IRE, CheckerContext &C) const; + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + void checkSummary(const RetainSummary &Summ, const CallEvent &Call, + CheckerContext &C) const; + + void processSummaryOfInlined(const RetainSummary &Summ, + const CallEvent &Call, + CheckerContext &C) const; + + bool evalCall(const CallExpr *CE, CheckerContext &C) const; + + ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, + bool Assumption) const; + + ProgramStateRef + checkRegionChanges(ProgramStateRef state, + const InvalidatedSymbols *invalidated, + ArrayRef<const MemRegion *> ExplicitRegions, + ArrayRef<const MemRegion *> Regions, + const LocationContext* LCtx, + const CallEvent *Call) const; + + ExplodedNode* checkReturnWithRetEffect(const ReturnStmt *S, CheckerContext &C, + ExplodedNode *Pred, RetEffect RE, RefVal X, + SymbolRef Sym, ProgramStateRef state) const; + + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + void checkBeginFunction(CheckerContext &C) const; + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; + + ProgramStateRef updateSymbol(ProgramStateRef state, SymbolRef sym, + RefVal V, ArgEffect E, RefVal::Kind &hasErr, + CheckerContext &C) const; + + void processNonLeakError(ProgramStateRef St, SourceRange ErrorRange, + RefVal::Kind ErrorKind, SymbolRef Sym, + CheckerContext &C) const; + + void processObjCLiterals(CheckerContext &C, const Expr *Ex) const; + + ProgramStateRef handleSymbolDeath(ProgramStateRef state, + SymbolRef sid, RefVal V, + SmallVectorImpl<SymbolRef> &Leaked) const; + + ProgramStateRef + handleAutoreleaseCounts(ProgramStateRef state, ExplodedNode *Pred, + const ProgramPointTag *Tag, CheckerContext &Ctx, + SymbolRef Sym, + RefVal V, + const ReturnStmt *S=nullptr) const; + + ExplodedNode *processLeaks(ProgramStateRef state, + SmallVectorImpl<SymbolRef> &Leaked, + CheckerContext &Ctx, + ExplodedNode *Pred = nullptr) const; + +private: + /// Perform the necessary checks and state adjustments at the end of the + /// function. + /// \p S Return statement, may be null. + ExplodedNode * processReturn(const ReturnStmt *S, CheckerContext &C) const; +}; + +//===----------------------------------------------------------------------===// +// RefBindings - State used to track object reference counts. +//===----------------------------------------------------------------------===// + +const RefVal *getRefBinding(ProgramStateRef State, SymbolRef Sym); + +ProgramStateRef setRefBinding(ProgramStateRef State, SymbolRef Sym, + RefVal Val); + +ProgramStateRef removeRefBinding(ProgramStateRef State, SymbolRef Sym); + +/// Returns true if this stack frame is for an Objective-C method that is a +/// property getter or setter whose body has been synthesized by the analyzer. +inline bool isSynthesizedAccessor(const StackFrameContext *SFC) { + auto Method = dyn_cast_or_null<ObjCMethodDecl>(SFC->getDecl()); + if (!Method || !Method->isPropertyAccessor()) + return false; + + return SFC->getAnalysisDeclContext()->isBodyAutosynthesized(); +} + +} // end namespace retaincountchecker +} // end namespace ento +} // end namespace clang + +#endif diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp new file mode 100644 index 000000000000..cda1a928de13 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -0,0 +1,794 @@ +// RetainCountDiagnostics.cpp - Checks for leaks and other issues -*- 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 diagnostics for RetainCountChecker, which implements +// a reference count checker for Core Foundation and Cocoa on (Mac OS X). +// +//===----------------------------------------------------------------------===// + +#include "RetainCountDiagnostics.h" +#include "RetainCountChecker.h" + +using namespace clang; +using namespace ento; +using namespace retaincountchecker; + +static bool isNumericLiteralExpression(const Expr *E) { + // FIXME: This set of cases was copied from SemaExprObjC. + return isa<IntegerLiteral>(E) || + isa<CharacterLiteral>(E) || + isa<FloatingLiteral>(E) || + isa<ObjCBoolLiteralExpr>(E) || + isa<CXXBoolLiteralExpr>(E); +} + +/// If type represents a pointer to CXXRecordDecl, +/// and is not a typedef, return the decl name. +/// Otherwise, return the serialization of type. +static std::string getPrettyTypeName(QualType QT) { + QualType PT = QT->getPointeeType(); + if (!PT.isNull() && !QT->getAs<TypedefType>()) + if (const auto *RD = PT->getAsCXXRecordDecl()) + return RD->getName(); + return QT.getAsString(); +} + +/// Write information about the type state change to {@code os}, +/// return whether the note should be generated. +static bool shouldGenerateNote(llvm::raw_string_ostream &os, + const RefVal *PrevT, const RefVal &CurrV, + bool DeallocSent) { + // Get the previous type state. + RefVal PrevV = *PrevT; + + // Specially handle -dealloc. + if (DeallocSent) { + // Determine if the object's reference count was pushed to zero. + assert(!PrevV.hasSameState(CurrV) && "The state should have changed."); + // We may not have transitioned to 'release' if we hit an error. + // This case is handled elsewhere. + if (CurrV.getKind() == RefVal::Released) { + assert(CurrV.getCombinedCounts() == 0); + os << "Object released by directly sending the '-dealloc' message"; + return true; + } + } + + // Determine if the typestate has changed. + if (!PrevV.hasSameState(CurrV)) + switch (CurrV.getKind()) { + case RefVal::Owned: + case RefVal::NotOwned: + if (PrevV.getCount() == CurrV.getCount()) { + // Did an autorelease message get sent? + if (PrevV.getAutoreleaseCount() == CurrV.getAutoreleaseCount()) + return false; + + assert(PrevV.getAutoreleaseCount() < CurrV.getAutoreleaseCount()); + os << "Object autoreleased"; + return true; + } + + if (PrevV.getCount() > CurrV.getCount()) + os << "Reference count decremented."; + else + os << "Reference count incremented."; + + if (unsigned Count = CurrV.getCount()) + os << " The object now has a +" << Count << " retain count."; + + return true; + + case RefVal::Released: + if (CurrV.getIvarAccessHistory() == + RefVal::IvarAccessHistory::ReleasedAfterDirectAccess && + CurrV.getIvarAccessHistory() != PrevV.getIvarAccessHistory()) { + os << "Strong instance variable relinquished. "; + } + os << "Object released."; + return true; + + case RefVal::ReturnedOwned: + // Autoreleases can be applied after marking a node ReturnedOwned. + if (CurrV.getAutoreleaseCount()) + return false; + + os << "Object returned to caller as an owning reference (single " + "retain count transferred to caller)"; + return true; + + case RefVal::ReturnedNotOwned: + os << "Object returned to caller with a +0 retain count"; + return true; + + default: + return false; + } + return true; +} + +/// Finds argument index of the out paramter in the call {@code S} +/// corresponding to the symbol {@code Sym}. +/// If none found, returns None. +static Optional<unsigned> findArgIdxOfSymbol(ProgramStateRef CurrSt, + const LocationContext *LCtx, + SymbolRef &Sym, + Optional<CallEventRef<>> CE) { + if (!CE) + return None; + + for (unsigned Idx = 0; Idx < (*CE)->getNumArgs(); Idx++) + if (const MemRegion *MR = (*CE)->getArgSVal(Idx).getAsRegion()) + if (const auto *TR = dyn_cast<TypedValueRegion>(MR)) + if (CurrSt->getSVal(MR, TR->getValueType()).getAsSymExpr() == Sym) + return Idx; + + return None; +} + +static void generateDiagnosticsForCallLike(ProgramStateRef CurrSt, + const LocationContext *LCtx, + const RefVal &CurrV, SymbolRef &Sym, + const Stmt *S, + llvm::raw_string_ostream &os) { + CallEventManager &Mgr = CurrSt->getStateManager().getCallEventManager(); + if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { + // Get the name of the callee (if it is available) + // from the tracked SVal. + SVal X = CurrSt->getSValAsScalarOrLoc(CE->getCallee(), LCtx); + const FunctionDecl *FD = X.getAsFunctionDecl(); + + // If failed, try to get it from AST. + if (!FD) + FD = dyn_cast<FunctionDecl>(CE->getCalleeDecl()); + + if (const auto *MD = dyn_cast<CXXMethodDecl>(CE->getCalleeDecl())) { + os << "Call to method '" << MD->getQualifiedNameAsString() << '\''; + } else if (FD) { + os << "Call to function '" << FD->getQualifiedNameAsString() << '\''; + } else { + os << "function call"; + } + } else if (isa<CXXNewExpr>(S)) { + os << "Operator 'new'"; + } else { + assert(isa<ObjCMessageExpr>(S)); + CallEventRef<ObjCMethodCall> Call = + Mgr.getObjCMethodCall(cast<ObjCMessageExpr>(S), CurrSt, LCtx); + + switch (Call->getMessageKind()) { + case OCM_Message: + os << "Method"; + break; + case OCM_PropertyAccess: + os << "Property"; + break; + case OCM_Subscript: + os << "Subscript"; + break; + } + } + + Optional<CallEventRef<>> CE = Mgr.getCall(S, CurrSt, LCtx); + auto Idx = findArgIdxOfSymbol(CurrSt, LCtx, Sym, CE); + + // If index is not found, we assume that the symbol was returned. + if (!Idx) { + os << " returns "; + } else { + os << " writes "; + } + + if (CurrV.getObjKind() == ObjKind::CF) { + os << "a Core Foundation object of type '" + << Sym->getType().getAsString() << "' with a "; + } else if (CurrV.getObjKind() == ObjKind::OS) { + os << "an OSObject of type '" << getPrettyTypeName(Sym->getType()) + << "' with a "; + } else if (CurrV.getObjKind() == ObjKind::Generalized) { + os << "an object of type '" << Sym->getType().getAsString() + << "' with a "; + } else { + assert(CurrV.getObjKind() == ObjKind::ObjC); + QualType T = Sym->getType(); + if (!isa<ObjCObjectPointerType>(T)) { + os << "an Objective-C object with a "; + } else { + const ObjCObjectPointerType *PT = cast<ObjCObjectPointerType>(T); + os << "an instance of " << PT->getPointeeType().getAsString() + << " with a "; + } + } + + if (CurrV.isOwned()) { + os << "+1 retain count"; + } else { + assert(CurrV.isNotOwned()); + os << "+0 retain count"; + } + + if (Idx) { + os << " into an out parameter '"; + const ParmVarDecl *PVD = (*CE)->parameters()[*Idx]; + PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(), + /*Qualified=*/false); + os << "'"; + + QualType RT = (*CE)->getResultType(); + if (!RT.isNull() && !RT->isVoidType()) { + SVal RV = (*CE)->getReturnValue(); + if (CurrSt->isNull(RV).isConstrainedTrue()) { + os << " (assuming the call returns zero)"; + } else if (CurrSt->isNonNull(RV).isConstrainedTrue()) { + os << " (assuming the call returns non-zero)"; + } + + } + } +} + +namespace clang { +namespace ento { +namespace retaincountchecker { + +class RefCountReportVisitor : public BugReporterVisitor { +protected: + SymbolRef Sym; + +public: + RefCountReportVisitor(SymbolRef sym) : Sym(sym) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int x = 0; + ID.AddPointer(&x); + ID.AddPointer(Sym); + } + + std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + BugReport &BR) override; + + std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + BugReport &BR) override; +}; + +class RefLeakReportVisitor : public RefCountReportVisitor { +public: + RefLeakReportVisitor(SymbolRef sym) : RefCountReportVisitor(sym) {} + + std::shared_ptr<PathDiagnosticPiece> getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + BugReport &BR) override; +}; + +} // end namespace retaincountchecker +} // end namespace ento +} // end namespace clang + + +/// Find the first node with the parent stack frame. +static const ExplodedNode *getCalleeNode(const ExplodedNode *Pred) { + const StackFrameContext *SC = Pred->getStackFrame(); + if (SC->inTopFrame()) + return nullptr; + const StackFrameContext *PC = SC->getParent()->getStackFrame(); + if (!PC) + return nullptr; + + const ExplodedNode *N = Pred; + while (N && N->getStackFrame() != PC) { + N = N->getFirstPred(); + } + return N; +} + + +/// Insert a diagnostic piece at function exit +/// if a function parameter is annotated as "os_consumed", +/// but it does not actually consume the reference. +static std::shared_ptr<PathDiagnosticEventPiece> +annotateConsumedSummaryMismatch(const ExplodedNode *N, + CallExitBegin &CallExitLoc, + const SourceManager &SM, + CallEventManager &CEMgr) { + + const ExplodedNode *CN = getCalleeNode(N); + if (!CN) + return nullptr; + + CallEventRef<> Call = CEMgr.getCaller(N->getStackFrame(), N->getState()); + + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + ArrayRef<const ParmVarDecl *> Parameters = Call->parameters(); + for (unsigned I=0; I < Call->getNumArgs() && I < Parameters.size(); ++I) { + const ParmVarDecl *PVD = Parameters[I]; + + if (!PVD->hasAttr<OSConsumedAttr>()) + continue; + + if (SymbolRef SR = Call->getArgSVal(I).getAsLocSymbol()) { + const RefVal *CountBeforeCall = getRefBinding(CN->getState(), SR); + const RefVal *CountAtExit = getRefBinding(N->getState(), SR); + + if (!CountBeforeCall || !CountAtExit) + continue; + + unsigned CountBefore = CountBeforeCall->getCount(); + unsigned CountAfter = CountAtExit->getCount(); + + bool AsExpected = CountBefore > 0 && CountAfter == CountBefore - 1; + if (!AsExpected) { + os << "Parameter '"; + PVD->getNameForDiagnostic(os, PVD->getASTContext().getPrintingPolicy(), + /*Qualified=*/false); + os << "' is marked as consuming, but the function did not consume " + << "the reference\n"; + } + } + } + + if (os.str().empty()) + return nullptr; + + // FIXME: remove the code duplication with NoStoreFuncVisitor. + PathDiagnosticLocation L; + if (const ReturnStmt *RS = CallExitLoc.getReturnStmt()) { + L = PathDiagnosticLocation::createBegin(RS, SM, N->getLocationContext()); + } else { + L = PathDiagnosticLocation( + Call->getRuntimeDefinition().getDecl()->getSourceRange().getEnd(), SM); + } + + return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); +} + +std::shared_ptr<PathDiagnosticPiece> +RefCountReportVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, BugReport &BR) { + + const SourceManager &SM = BRC.getSourceManager(); + CallEventManager &CEMgr = BRC.getStateManager().getCallEventManager(); + if (auto CE = N->getLocationAs<CallExitBegin>()) + if (auto PD = annotateConsumedSummaryMismatch(N, *CE, SM, CEMgr)) + return PD; + + // FIXME: We will eventually need to handle non-statement-based events + // (__attribute__((cleanup))). + if (!N->getLocation().getAs<StmtPoint>()) + return nullptr; + + // Check if the type state has changed. + const ExplodedNode *PrevNode = N->getFirstPred(); + ProgramStateRef PrevSt = PrevNode->getState(); + ProgramStateRef CurrSt = N->getState(); + const LocationContext *LCtx = N->getLocationContext(); + + const RefVal* CurrT = getRefBinding(CurrSt, Sym); + if (!CurrT) return nullptr; + + const RefVal &CurrV = *CurrT; + const RefVal *PrevT = getRefBinding(PrevSt, Sym); + + // Create a string buffer to constain all the useful things we want + // to tell the user. + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + + // This is the allocation site since the previous node had no bindings + // for this symbol. + if (!PrevT) { + const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); + + if (isa<ObjCIvarRefExpr>(S) && + isSynthesizedAccessor(LCtx->getStackFrame())) { + S = LCtx->getStackFrame()->getCallSite(); + } + + if (isa<ObjCArrayLiteral>(S)) { + os << "NSArray literal is an object with a +0 retain count"; + } else if (isa<ObjCDictionaryLiteral>(S)) { + os << "NSDictionary literal is an object with a +0 retain count"; + } else if (const ObjCBoxedExpr *BL = dyn_cast<ObjCBoxedExpr>(S)) { + if (isNumericLiteralExpression(BL->getSubExpr())) + os << "NSNumber literal is an object with a +0 retain count"; + else { + const ObjCInterfaceDecl *BoxClass = nullptr; + if (const ObjCMethodDecl *Method = BL->getBoxingMethod()) + BoxClass = Method->getClassInterface(); + + // We should always be able to find the boxing class interface, + // but consider this future-proofing. + if (BoxClass) { + os << *BoxClass << " b"; + } else { + os << "B"; + } + + os << "oxed expression produces an object with a +0 retain count"; + } + } else if (isa<ObjCIvarRefExpr>(S)) { + os << "Object loaded from instance variable"; + } else { + generateDiagnosticsForCallLike(CurrSt, LCtx, CurrV, Sym, S, os); + } + + PathDiagnosticLocation Pos(S, SM, N->getLocationContext()); + return std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); + } + + // Gather up the effects that were performed on the object at this + // program point + bool DeallocSent = false; + + if (N->getLocation().getTag() && + N->getLocation().getTag()->getTagDescription().contains( + RetainCountChecker::DeallocTagDescription)) { + // We only have summaries attached to nodes after evaluating CallExpr and + // ObjCMessageExprs. + const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); + + if (const CallExpr *CE = dyn_cast<CallExpr>(S)) { + // Iterate through the parameter expressions and see if the symbol + // was ever passed as an argument. + unsigned i = 0; + + for (auto AI=CE->arg_begin(), AE=CE->arg_end(); AI!=AE; ++AI, ++i) { + + // Retrieve the value of the argument. Is it the symbol + // we are interested in? + if (CurrSt->getSValAsScalarOrLoc(*AI, LCtx).getAsLocSymbol() != Sym) + continue; + + // We have an argument. Get the effect! + DeallocSent = true; + } + } else if (const ObjCMessageExpr *ME = dyn_cast<ObjCMessageExpr>(S)) { + if (const Expr *receiver = ME->getInstanceReceiver()) { + if (CurrSt->getSValAsScalarOrLoc(receiver, LCtx) + .getAsLocSymbol() == Sym) { + // The symbol we are tracking is the receiver. + DeallocSent = true; + } + } + } + } + + if (!shouldGenerateNote(os, PrevT, CurrV, DeallocSent)) + return nullptr; + + if (os.str().empty()) + return nullptr; // We have nothing to say! + + const Stmt *S = N->getLocation().castAs<StmtPoint>().getStmt(); + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + auto P = std::make_shared<PathDiagnosticEventPiece>(Pos, os.str()); + + // Add the range by scanning the children of the statement for any bindings + // to Sym. + for (const Stmt *Child : S->children()) + if (const Expr *Exp = dyn_cast_or_null<Expr>(Child)) + if (CurrSt->getSValAsScalarOrLoc(Exp, LCtx).getAsLocSymbol() == Sym) { + P->addRange(Exp->getSourceRange()); + break; + } + + return std::move(P); +} + +static Optional<std::string> describeRegion(const MemRegion *MR) { + if (const auto *VR = dyn_cast_or_null<VarRegion>(MR)) + return std::string(VR->getDecl()->getName()); + // Once we support more storage locations for bindings, + // this would need to be improved. + return None; +} + +namespace { +// Find the first node in the current function context that referred to the +// tracked symbol and the memory location that value was stored to. Note, the +// value is only reported if the allocation occurred in the same function as +// the leak. The function can also return a location context, which should be +// treated as interesting. +struct AllocationInfo { + const ExplodedNode* N; + const MemRegion *R; + const LocationContext *InterestingMethodContext; + AllocationInfo(const ExplodedNode *InN, + const MemRegion *InR, + const LocationContext *InInterestingMethodContext) : + N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {} +}; +} // end anonymous namespace + +static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr, + const ExplodedNode *N, SymbolRef Sym) { + const ExplodedNode *AllocationNode = N; + const ExplodedNode *AllocationNodeInCurrentOrParentContext = N; + const MemRegion *FirstBinding = nullptr; + const LocationContext *LeakContext = N->getLocationContext(); + + // The location context of the init method called on the leaked object, if + // available. + const LocationContext *InitMethodContext = nullptr; + + while (N) { + ProgramStateRef St = N->getState(); + const LocationContext *NContext = N->getLocationContext(); + + if (!getRefBinding(St, Sym)) + break; + + StoreManager::FindUniqueBinding FB(Sym); + StateMgr.iterBindings(St, FB); + + if (FB) { + const MemRegion *R = FB.getRegion(); + // Do not show local variables belonging to a function other than + // where the error is reported. + if (auto MR = dyn_cast<StackSpaceRegion>(R->getMemorySpace())) + if (MR->getStackFrame() == LeakContext->getStackFrame()) + FirstBinding = R; + } + + // AllocationNode is the last node in which the symbol was tracked. + AllocationNode = N; + + // AllocationNodeInCurrentContext, is the last node in the current or + // parent context in which the symbol was tracked. + // + // Note that the allocation site might be in the parent context. For example, + // the case where an allocation happens in a block that captures a reference + // to it and that reference is overwritten/dropped by another call to + // the block. + if (NContext == LeakContext || NContext->isParentOf(LeakContext)) + AllocationNodeInCurrentOrParentContext = N; + + // Find the last init that was called on the given symbol and store the + // init method's location context. + if (!InitMethodContext) + if (auto CEP = N->getLocation().getAs<CallEnter>()) { + const Stmt *CE = CEP->getCallExpr(); + if (const auto *ME = dyn_cast_or_null<ObjCMessageExpr>(CE)) { + const Stmt *RecExpr = ME->getInstanceReceiver(); + if (RecExpr) { + SVal RecV = St->getSVal(RecExpr, NContext); + if (ME->getMethodFamily() == OMF_init && RecV.getAsSymbol() == Sym) + InitMethodContext = CEP->getCalleeContext(); + } + } + } + + N = N->getFirstPred(); + } + + // If we are reporting a leak of the object that was allocated with alloc, + // mark its init method as interesting. + const LocationContext *InterestingMethodContext = nullptr; + if (InitMethodContext) { + const ProgramPoint AllocPP = AllocationNode->getLocation(); + if (Optional<StmtPoint> SP = AllocPP.getAs<StmtPoint>()) + if (const ObjCMessageExpr *ME = SP->getStmtAs<ObjCMessageExpr>()) + if (ME->getMethodFamily() == OMF_alloc) + InterestingMethodContext = InitMethodContext; + } + + // If allocation happened in a function different from the leak node context, + // do not report the binding. + assert(N && "Could not find allocation node"); + + if (AllocationNodeInCurrentOrParentContext && + AllocationNodeInCurrentOrParentContext->getLocationContext() != + LeakContext) + FirstBinding = nullptr; + + return AllocationInfo(AllocationNodeInCurrentOrParentContext, + FirstBinding, + InterestingMethodContext); +} + +std::shared_ptr<PathDiagnosticPiece> +RefCountReportVisitor::getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndN, BugReport &BR) { + BR.markInteresting(Sym); + return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); +} + +std::shared_ptr<PathDiagnosticPiece> +RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndN, BugReport &BR) { + + // Tell the BugReporterContext to report cases when the tracked symbol is + // assigned to different variables, etc. + BR.markInteresting(Sym); + + // We are reporting a leak. Walk up the graph to get to the first node where + // the symbol appeared, and also get the first VarDecl that tracked object + // is stored to. + AllocationInfo AllocI = GetAllocationSite(BRC.getStateManager(), EndN, Sym); + + const MemRegion* FirstBinding = AllocI.R; + BR.markInteresting(AllocI.InterestingMethodContext); + + SourceManager& SM = BRC.getSourceManager(); + + // Compute an actual location for the leak. Sometimes a leak doesn't + // occur at an actual statement (e.g., transition between blocks; end + // of function) so we need to walk the graph and compute a real location. + const ExplodedNode *LeakN = EndN; + PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM); + + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + + os << "Object leaked: "; + + Optional<std::string> RegionDescription = describeRegion(FirstBinding); + if (RegionDescription) { + os << "object allocated and stored into '" << *RegionDescription << '\''; + } else { + os << "allocated object of type '" << getPrettyTypeName(Sym->getType()) + << "'"; + } + + // Get the retain count. + const RefVal* RV = getRefBinding(EndN->getState(), Sym); + assert(RV); + + if (RV->getKind() == RefVal::ErrorLeakReturned) { + // FIXME: Per comments in rdar://6320065, "create" only applies to CF + // objects. Only "copy", "alloc", "retain" and "new" transfer ownership + // to the caller for NS objects. + const Decl *D = &EndN->getCodeDecl(); + + os << (isa<ObjCMethodDecl>(D) ? " is returned from a method " + : " is returned from a function "); + + if (D->hasAttr<CFReturnsNotRetainedAttr>()) { + os << "that is annotated as CF_RETURNS_NOT_RETAINED"; + } else if (D->hasAttr<NSReturnsNotRetainedAttr>()) { + os << "that is annotated as NS_RETURNS_NOT_RETAINED"; + } else if (D->hasAttr<OSReturnsNotRetainedAttr>()) { + os << "that is annotated as OS_RETURNS_NOT_RETAINED"; + } else { + if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) { + if (BRC.getASTContext().getLangOpts().ObjCAutoRefCount) { + os << "managed by Automatic Reference Counting"; + } else { + os << "whose name ('" << MD->getSelector().getAsString() + << "') does not start with " + "'copy', 'mutableCopy', 'alloc' or 'new'." + " This violates the naming convention rules" + " given in the Memory Management Guide for Cocoa"; + } + } else { + const FunctionDecl *FD = cast<FunctionDecl>(D); + os << "whose name ('" << *FD + << "') does not contain 'Copy' or 'Create'. This violates the naming" + " convention rules given in the Memory Management Guide for Core" + " Foundation"; + } + } + } else { + os << " is not referenced later in this execution path and has a retain " + "count of +" << RV->getCount(); + } + + return std::make_shared<PathDiagnosticEventPiece>(L, os.str()); +} + +RefCountReport::RefCountReport(RefCountBug &D, const LangOptions &LOpts, + ExplodedNode *n, SymbolRef sym, + bool registerVisitor) + : BugReport(D, D.getDescription(), n), Sym(sym) { + if (registerVisitor) + addVisitor(llvm::make_unique<RefCountReportVisitor>(sym)); +} + +RefCountReport::RefCountReport(RefCountBug &D, const LangOptions &LOpts, + ExplodedNode *n, SymbolRef sym, + StringRef endText) + : BugReport(D, D.getDescription(), endText, n) { + + addVisitor(llvm::make_unique<RefCountReportVisitor>(sym)); +} + +void RefLeakReport::deriveParamLocation(CheckerContext &Ctx, SymbolRef sym) { + const SourceManager& SMgr = Ctx.getSourceManager(); + + if (!sym->getOriginRegion()) + return; + + auto *Region = dyn_cast<DeclRegion>(sym->getOriginRegion()); + if (Region) { + const Decl *PDecl = Region->getDecl(); + if (PDecl && isa<ParmVarDecl>(PDecl)) { + PathDiagnosticLocation ParamLocation = + PathDiagnosticLocation::create(PDecl, SMgr); + Location = ParamLocation; + UniqueingLocation = ParamLocation; + UniqueingDecl = Ctx.getLocationContext()->getDecl(); + } + } +} + +void RefLeakReport::deriveAllocLocation(CheckerContext &Ctx, + SymbolRef sym) { + // Most bug reports are cached at the location where they occurred. + // With leaks, we want to unique them by the location where they were + // allocated, and only report a single path. To do this, we need to find + // the allocation site of a piece of tracked memory, which we do via a + // call to GetAllocationSite. This will walk the ExplodedGraph backwards. + // Note that this is *not* the trimmed graph; we are guaranteed, however, + // that all ancestor nodes that represent the allocation site have the + // same SourceLocation. + const ExplodedNode *AllocNode = nullptr; + + const SourceManager& SMgr = Ctx.getSourceManager(); + + AllocationInfo AllocI = + GetAllocationSite(Ctx.getStateManager(), getErrorNode(), sym); + + AllocNode = AllocI.N; + AllocBinding = AllocI.R; + markInteresting(AllocI.InterestingMethodContext); + + // Get the SourceLocation for the allocation site. + // FIXME: This will crash the analyzer if an allocation comes from an + // implicit call (ex: a destructor call). + // (Currently there are no such allocations in Cocoa, though.) + AllocStmt = PathDiagnosticLocation::getStmt(AllocNode); + + if (!AllocStmt) { + AllocBinding = nullptr; + return; + } + + PathDiagnosticLocation AllocLocation = + PathDiagnosticLocation::createBegin(AllocStmt, SMgr, + AllocNode->getLocationContext()); + Location = AllocLocation; + + // Set uniqieing info, which will be used for unique the bug reports. The + // leaks should be uniqued on the allocation site. + UniqueingLocation = AllocLocation; + UniqueingDecl = AllocNode->getLocationContext()->getDecl(); +} + +void RefLeakReport::createDescription(CheckerContext &Ctx) { + assert(Location.isValid() && UniqueingDecl && UniqueingLocation.isValid()); + Description.clear(); + llvm::raw_string_ostream os(Description); + os << "Potential leak of an object"; + + Optional<std::string> RegionDescription = describeRegion(AllocBinding); + if (RegionDescription) { + os << " stored into '" << *RegionDescription << '\''; + } else { + + // If we can't figure out the name, just supply the type information. + os << " of type '" << getPrettyTypeName(Sym->getType()) << "'"; + } +} + +RefLeakReport::RefLeakReport(RefCountBug &D, const LangOptions &LOpts, + ExplodedNode *n, SymbolRef sym, + CheckerContext &Ctx) + : RefCountReport(D, LOpts, n, sym, false) { + + deriveAllocLocation(Ctx, sym); + if (!AllocBinding) + deriveParamLocation(Ctx, sym); + + createDescription(Ctx); + + addVisitor(llvm::make_unique<RefLeakReportVisitor>(sym)); +} diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h new file mode 100644 index 000000000000..9f796abe8eae --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -0,0 +1,85 @@ +//== RetainCountDiagnostics.h - Checks for leaks and other issues -*- 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 diagnostics for RetainCountChecker, which implements +// a reference count checker for Core Foundation and Cocoa on (Mac OS X). +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H + +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" +#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Core/RetainSummaryManager.h" + +namespace clang { +namespace ento { +namespace retaincountchecker { + +class RefCountBug : public BugType { +protected: + RefCountBug(const CheckerBase *checker, StringRef name) + : BugType(checker, name, categories::MemoryRefCount) {} + +public: + virtual const char *getDescription() const = 0; + + virtual bool isLeak() const { return false; } +}; + +class RefCountReport : public BugReport { +protected: + SymbolRef Sym; + +public: + RefCountReport(RefCountBug &D, const LangOptions &LOpts, + ExplodedNode *n, SymbolRef sym, + bool registerVisitor = true); + + RefCountReport(RefCountBug &D, const LangOptions &LOpts, + ExplodedNode *n, SymbolRef sym, + StringRef endText); + + llvm::iterator_range<ranges_iterator> getRanges() override { + const RefCountBug& BugTy = static_cast<RefCountBug&>(getBugType()); + if (!BugTy.isLeak()) + return BugReport::getRanges(); + return llvm::make_range(ranges_iterator(), ranges_iterator()); + } +}; + +class RefLeakReport : public RefCountReport { + const MemRegion* AllocBinding; + const Stmt *AllocStmt; + + // Finds the function declaration where a leak warning for the parameter + // 'sym' should be raised. + void deriveParamLocation(CheckerContext &Ctx, SymbolRef sym); + // Finds the location where a leak warning for 'sym' should be raised. + void deriveAllocLocation(CheckerContext &Ctx, SymbolRef sym); + // Produces description of a leak warning which is printed on the console. + void createDescription(CheckerContext &Ctx); + +public: + RefLeakReport(RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n, + SymbolRef sym, CheckerContext &Ctx); + + PathDiagnosticLocation getLocation(const SourceManager &SM) const override { + assert(Location.isValid()); + return Location; + } +}; + +} // end namespace retaincountchecker +} // end namespace ento +} // end namespace clang + +#endif diff --git a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index 1952715a9b7c..17ef39531628 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index c5e826a84b84..3e0613e8ba68 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -13,7 +13,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -87,7 +87,7 @@ static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE, auto Report = llvm::make_unique<BugReport>(BT, BT.getDescription(), N); Report->addRange(RetE->getSourceRange()); - bugreporter::trackNullOrUndefValue(N, TrackingE ? TrackingE : RetE, *Report); + bugreporter::trackExpressionValue(N, TrackingE ? TrackingE : RetE, *Report); C.emitReport(std::move(Report)); } diff --git a/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp b/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp index 55516a34d1a7..cf03b3c21132 100644 --- a/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/RunLoopAutoreleaseLeakChecker.cpp @@ -23,7 +23,7 @@ //===----------------------------------------------------------------------===// // -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclObjC.h" #include "clang/ASTMatchers/ASTMatchFinder.h" @@ -58,13 +58,12 @@ public: } // end anonymous namespace - -using TriBoolTy = Optional<bool>; -using MemoizationMapTy = llvm::DenseMap<const Stmt *, Optional<TriBoolTy>>; - -static TriBoolTy -seenBeforeRec(const Stmt *Parent, const Stmt *A, const Stmt *B, - MemoizationMapTy &Memoization) { +/// \return Whether {@code A} occurs before {@code B} in traversal of +/// {@code Parent}. +/// Conceptually a very incomplete/unsound approximation of happens-before +/// relationship (A is likely to be evaluated before B), +/// but useful enough in this case. +static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) { for (const Stmt *C : Parent->children()) { if (!C) continue; @@ -74,26 +73,9 @@ seenBeforeRec(const Stmt *Parent, const Stmt *A, const Stmt *B, if (C == B) return false; - Optional<TriBoolTy> &Cached = Memoization[C]; - if (!Cached) - Cached = seenBeforeRec(C, A, B, Memoization); - - if (Cached->hasValue()) - return Cached->getValue(); + return seenBefore(C, A, B); } - - return None; -} - -/// \return Whether {@code A} occurs before {@code B} in traversal of -/// {@code Parent}. -/// Conceptually a very incomplete/unsound approximation of happens-before -/// relationship (A is likely to be evaluated before B), -/// but useful enough in this case. -static bool seenBefore(const Stmt *Parent, const Stmt *A, const Stmt *B) { - MemoizationMapTy Memoization; - TriBoolTy Val = seenBeforeRec(Parent, A, B, Memoization); - return Val.getValue(); + return false; } static void emitDiagnostics(BoundNodes &Match, diff --git a/lib/StaticAnalyzer/Checkers/SelectorExtras.h b/lib/StaticAnalyzer/Checkers/SelectorExtras.h deleted file mode 100644 index b11d070c629b..000000000000 --- a/lib/StaticAnalyzer/Checkers/SelectorExtras.h +++ /dev/null @@ -1,46 +0,0 @@ -//=== SelectorExtras.h - Helpers for checkers using selectors -----*- C++ -*-=// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SELECTOREXTRAS_H -#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_SELECTOREXTRAS_H - -#include "clang/AST/ASTContext.h" - -namespace clang { -namespace ento { - -template <typename... IdentifierInfos> -static inline Selector getKeywordSelector(ASTContext &Ctx, - IdentifierInfos *... IIs) { - static_assert(sizeof...(IdentifierInfos), - "keyword selectors must have at least one argument"); - SmallVector<IdentifierInfo *, 10> II({&Ctx.Idents.get(IIs)...}); - - return Ctx.Selectors.getSelector(II.size(), &II[0]); -} - -template <typename... IdentifierInfos> -static inline void lazyInitKeywordSelector(Selector &Sel, ASTContext &Ctx, - IdentifierInfos *... IIs) { - if (!Sel.isNull()) - return; - Sel = getKeywordSelector(Ctx, IIs...); -} - -static inline void lazyInitNullarySelector(Selector &Sel, ASTContext &Ctx, - const char *Name) { - if (!Sel.isNull()) - return; - Sel = GetNullarySelector(Name, Ctx); -} - -} // end namespace ento -} // end namespace clang - -#endif diff --git a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp index ab4b4d3bd91b..819d437e6883 100644 --- a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -15,7 +15,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" diff --git a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index e7a20fa03a4a..0f53d826a5f6 100644 --- a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ExprCXX.h" #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" @@ -79,17 +79,17 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R, const CompoundLiteralExpr *CL = CR->getLiteralExpr(); os << "stack memory associated with a compound literal " "declared on line " - << SM.getExpansionLineNumber(CL->getLocStart()) << " returned to caller"; + << SM.getExpansionLineNumber(CL->getBeginLoc()) << " returned to caller"; range = CL->getSourceRange(); } else if (const auto *AR = dyn_cast<AllocaRegion>(R)) { const Expr *ARE = AR->getExpr(); - SourceLocation L = ARE->getLocStart(); + SourceLocation L = ARE->getBeginLoc(); range = ARE->getSourceRange(); os << "stack memory allocated by call to alloca() on line " << SM.getExpansionLineNumber(L); } else if (const auto *BR = dyn_cast<BlockDataRegion>(R)) { const BlockDecl *BD = BR->getCodeRegion()->getDecl(); - SourceLocation L = BD->getLocStart(); + SourceLocation L = BD->getBeginLoc(); range = BD->getSourceRange(); os << "stack-allocated block declared on line " << SM.getExpansionLineNumber(L); diff --git a/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 2f9f5d2d9cf8..6478128ce954 100644 --- a/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -51,7 +51,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" diff --git a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index d77975559e3f..92647f032730 100644 --- a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -383,26 +383,26 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { + ProgramStateRef state = C.getState(); + // TODO: Clean up the state. - for (SymbolReaper::dead_iterator I = SymReaper.dead_begin(), - E = SymReaper.dead_end(); I != E; ++I) { - SymbolRef Sym = *I; - ProgramStateRef state = C.getState(); - const StreamState *SS = state->get<StreamMap>(Sym); - if (!SS) + const StreamMapTy &Map = state->get<StreamMap>(); + for (const auto &I: Map) { + SymbolRef Sym = I.first; + const StreamState &SS = I.second; + if (!SymReaper.isDead(Sym) || !SS.isOpened()) continue; - if (SS->isOpened()) { - ExplodedNode *N = C.generateErrorNode(); - if (N) { - if (!BT_ResourceLeak) - BT_ResourceLeak.reset(new BuiltinBug( - this, "Resource Leak", - "Opened File never closed. Potential Resource leak.")); - C.emitReport(llvm::make_unique<BugReport>( - *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); - } - } + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return; + + if (!BT_ResourceLeak) + BT_ResourceLeak.reset( + new BuiltinBug(this, "Resource Leak", + "Opened File never closed. Potential Resource leak.")); + C.emitReport(llvm::make_unique<BugReport>( + *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); } } diff --git a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp index 2e0529015ca6..3aa8e95d0ad0 100644 --- a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -10,7 +10,7 @@ // This checker can be used for testing how taint data is propagated. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index f4c0edbab3f0..527e371571f1 100644 --- a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -71,7 +71,6 @@ public: } std::shared_ptr<PathDiagnosticPiece> VisitNode(const ExplodedNode *Succ, - const ExplodedNode *Pred, BugReporterContext &BRC, BugReport &BR) override; }; @@ -95,7 +94,7 @@ public: REGISTER_SET_WITH_PROGRAMSTATE(DivZeroMap, ZeroState) std::shared_ptr<PathDiagnosticPiece> -DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, const ExplodedNode *Pred, +DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, BugReport &BR) { if (Satisfied) return nullptr; @@ -180,7 +179,7 @@ void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { } } -void TestAfterDivZeroChecker::checkEndFunction(const ReturnStmt *RS, +void TestAfterDivZeroChecker::checkEndFunction(const ReturnStmt *, CheckerContext &C) const { ProgramStateRef State = C.getState(); diff --git a/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp b/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp index ee185b813611..2f06469bb209 100644 --- a/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TraversalChecker.cpp @@ -11,7 +11,7 @@ // as it builds the ExplodedGraph. // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/AST/StmtObjC.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -47,7 +47,7 @@ void TraversalDumper::checkBranchCondition(const Stmt *Condition, // It is mildly evil to print directly to llvm::outs() rather than emitting // warnings, but this ensures things do not get filtered out by the rest of // the static analyzer machinery. - SourceLocation Loc = Parent->getLocStart(); + SourceLocation Loc = Parent->getBeginLoc(); llvm::outs() << C.getSourceManager().getSpellingLineNumber(Loc) << " " << Parent->getStmtClassName() << "\n"; } diff --git a/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp b/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp index f3d68014224d..5e777803af00 100644 --- a/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TrustNonnullChecker.cpp @@ -1,4 +1,4 @@ -//== TrustNonnullChecker.cpp - Checker for trusting annotations -*- C++ -*--==// +//== TrustNonnullChecker.cpp --------- API nullability modeling -*- C++ -*--==// // // The LLVM Compiler Infrastructure // @@ -7,12 +7,20 @@ // //===----------------------------------------------------------------------===// // -// This checker adds an assumption that methods annotated with _Nonnull +// This checker adds nullability-related assumptions: +// +// 1. Methods annotated with _Nonnull // which come from system headers actually return a non-null pointer. // +// 2. NSDictionary key is non-null after the keyword subscript operation +// on read if and only if the resulting expression is non-null. +// +// 3. NSMutableDictionary index is non-null after a write operation. +// //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/SelectorExtras.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" @@ -22,10 +30,129 @@ using namespace clang; using namespace ento; +/// Records implications between symbols. +/// The semantics is: +/// (antecedent != 0) => (consequent != 0) +/// These implications are then read during the evaluation of the assumption, +/// and the appropriate antecedents are applied. +REGISTER_MAP_WITH_PROGRAMSTATE(NonNullImplicationMap, SymbolRef, SymbolRef) + +/// The semantics is: +/// (antecedent == 0) => (consequent == 0) +REGISTER_MAP_WITH_PROGRAMSTATE(NullImplicationMap, SymbolRef, SymbolRef) + namespace { -class TrustNonnullChecker : public Checker<check::PostCall> { +class TrustNonnullChecker : public Checker<check::PostCall, + check::PostObjCMessage, + check::DeadSymbols, + eval::Assume> { + // Do not try to iterate over symbols with higher complexity. + static unsigned constexpr ComplexityThreshold = 10; + Selector ObjectForKeyedSubscriptSel; + Selector ObjectForKeySel; + Selector SetObjectForKeyedSubscriptSel; + Selector SetObjectForKeySel; + +public: + TrustNonnullChecker(ASTContext &Ctx) + : ObjectForKeyedSubscriptSel( + getKeywordSelector(Ctx, "objectForKeyedSubscript")), + ObjectForKeySel(getKeywordSelector(Ctx, "objectForKey")), + SetObjectForKeyedSubscriptSel( + getKeywordSelector(Ctx, "setObject", "forKeyedSubscript")), + SetObjectForKeySel(getKeywordSelector(Ctx, "setObject", "forKey")) {} + + ProgramStateRef evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + const SymbolRef CondS = Cond.getAsSymbol(); + if (!CondS || CondS->computeComplexity() > ComplexityThreshold) + return State; + + for (auto B=CondS->symbol_begin(), E=CondS->symbol_end(); B != E; ++B) { + const SymbolRef Antecedent = *B; + State = addImplication(Antecedent, State, true); + State = addImplication(Antecedent, State, false); + } + + return State; + } + + void checkPostCall(const CallEvent &Call, CheckerContext &C) const { + // Only trust annotations for system headers for non-protocols. + if (!Call.isInSystemHeader()) + return; + + ProgramStateRef State = C.getState(); + + if (isNonNullPtr(Call, C)) + if (auto L = Call.getReturnValue().getAs<Loc>()) + State = State->assume(*L, /*Assumption=*/true); + + C.addTransition(State); + } + + void checkPostObjCMessage(const ObjCMethodCall &Msg, + CheckerContext &C) const { + const ObjCInterfaceDecl *ID = Msg.getReceiverInterface(); + if (!ID) + return; + + ProgramStateRef State = C.getState(); + + // Index to setter for NSMutableDictionary is assumed to be non-null, + // as an exception is thrown otherwise. + if (interfaceHasSuperclass(ID, "NSMutableDictionary") && + (Msg.getSelector() == SetObjectForKeyedSubscriptSel || + Msg.getSelector() == SetObjectForKeySel)) { + if (auto L = Msg.getArgSVal(1).getAs<Loc>()) + State = State->assume(*L, /*Assumption=*/true); + } + + // Record an implication: index is non-null if the output is non-null. + if (interfaceHasSuperclass(ID, "NSDictionary") && + (Msg.getSelector() == ObjectForKeyedSubscriptSel || + Msg.getSelector() == ObjectForKeySel)) { + SymbolRef ArgS = Msg.getArgSVal(0).getAsSymbol(); + SymbolRef RetS = Msg.getReturnValue().getAsSymbol(); + + if (ArgS && RetS) { + // Emulate an implication: the argument is non-null if + // the return value is non-null. + State = State->set<NonNullImplicationMap>(RetS, ArgS); + + // Conversely, when the argument is null, the return value + // is definitely null. + State = State->set<NullImplicationMap>(ArgS, RetS); + } + } + + C.addTransition(State); + } + + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + + State = dropDeadFromGDM<NullImplicationMap>(SymReaper, State); + State = dropDeadFromGDM<NonNullImplicationMap>(SymReaper, State); + + C.addTransition(State); + } + private: + + /// \returns State with GDM \p MapName where all dead symbols were + // removed. + template <typename MapName> + ProgramStateRef dropDeadFromGDM(SymbolReaper &SymReaper, + ProgramStateRef State) const { + for (const std::pair<SymbolRef, SymbolRef> &P : State->get<MapName>()) + if (!SymReaper.isLive(P.first) || !SymReaper.isLive(P.second)) + State = State->remove<MapName>(P.first); + return State; + } + /// \returns Whether we trust the result of the method call to be /// a non-null pointer. bool isNonNullPtr(const CallEvent &Call, CheckerContext &C) const { @@ -66,19 +193,57 @@ private: return false; } -public: - void checkPostCall(const CallEvent &Call, CheckerContext &C) const { - // Only trust annotations for system headers for non-protocols. - if (!Call.isInSystemHeader()) - return; + /// \return Whether \p ID has a superclass by the name \p ClassName. + bool interfaceHasSuperclass(const ObjCInterfaceDecl *ID, + StringRef ClassName) const { + if (ID->getIdentifier()->getName() == ClassName) + return true; - ProgramStateRef State = C.getState(); + if (const ObjCInterfaceDecl *Super = ID->getSuperClass()) + return interfaceHasSuperclass(Super, ClassName); - if (isNonNullPtr(Call, C)) - if (auto L = Call.getReturnValue().getAs<Loc>()) - State = State->assume(*L, /*Assumption=*/true); + return false; + } - C.addTransition(State); + + /// \return a state with an optional implication added (if exists) + /// from a map of recorded implications. + /// If \p Negated is true, checks NullImplicationMap, and assumes + /// the negation of \p Antecedent. + /// Checks NonNullImplicationMap and assumes \p Antecedent otherwise. + ProgramStateRef addImplication(SymbolRef Antecedent, + ProgramStateRef InputState, + bool Negated) const { + if (!InputState) + return nullptr; + SValBuilder &SVB = InputState->getStateManager().getSValBuilder(); + const SymbolRef *Consequent = + Negated ? InputState->get<NonNullImplicationMap>(Antecedent) + : InputState->get<NullImplicationMap>(Antecedent); + if (!Consequent) + return InputState; + + SVal AntecedentV = SVB.makeSymbolVal(Antecedent); + ProgramStateRef State = InputState; + + if ((Negated && InputState->isNonNull(AntecedentV).isConstrainedTrue()) + || (!Negated && InputState->isNull(AntecedentV).isConstrainedTrue())) { + SVal ConsequentS = SVB.makeSymbolVal(*Consequent); + State = InputState->assume(ConsequentS.castAs<DefinedSVal>(), Negated); + if (!State) + return nullptr; + + // Drop implications from the map. + if (Negated) { + State = State->remove<NonNullImplicationMap>(Antecedent); + State = State->remove<NullImplicationMap>(*Consequent); + } else { + State = State->remove<NullImplicationMap>(Antecedent); + State = State->remove<NonNullImplicationMap>(*Consequent); + } + } + + return State; } }; @@ -86,5 +251,5 @@ public: void ento::registerTrustNonnullChecker(CheckerManager &Mgr) { - Mgr.registerChecker<TrustNonnullChecker>(); + Mgr.registerChecker<TrustNonnullChecker>(Mgr.getASTContext()); } diff --git a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index 934ee63318fa..d7fad4e475ab 100644 --- a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -98,7 +98,7 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, // Emit the bug report. auto R = llvm::make_unique<BugReport>(*BT, BT->getDescription(), N); - bugreporter::trackNullOrUndefValue(N, Ex, *R); + bugreporter::trackExpressionValue(N, Ex, *R); R->addRange(Ex->getSourceRange()); Ctx.emitReport(std::move(R)); diff --git a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 6a93c10c7644..8a625227b81e 100644 --- a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -11,7 +11,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/Attr.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index b9a93bedca2e..624cff6048fd 100644 --- a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -12,7 +12,7 @@ // //===----------------------------------------------------------------------===// -#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" @@ -69,6 +69,7 @@ static bool isLeftShiftResultUnrepresentable(const BinaryOperator *B, |