diff options
Diffstat (limited to 'lib/StaticAnalyzer')
110 files changed, 6577 insertions, 1478 deletions
diff --git a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp index 166471a..a052d83 100644 --- a/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/AnalyzerStatsChecker.cpp @@ -101,7 +101,7 @@ void AnalyzerStatsChecker::checkEndAnalysis(ExplodedGraph &G, else if (isa<BlockDecl>(D)) { output << "block(line:" << Loc.getLine() << ":col:" << Loc.getColumn(); } - + NumBlocksUnreachable += unreachable; NumBlocks += total; std::string NameOfRootFunction = output.str(); diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 557439b..c092610 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -23,7 +23,7 @@ using namespace clang; using namespace ento; namespace { -class ArrayBoundChecker : +class ArrayBoundChecker : public Checker<check::Location> { mutable std::unique_ptr<BuiltinBug> BT; @@ -55,17 +55,17 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, ProgramStateRef state = C.getState(); // Get the size of the array. - DefinedOrUnknownSVal NumElements - = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(), + DefinedOrUnknownSVal NumElements + = C.getStoreManager().getSizeInElements(state, ER->getSuperRegion(), ER->getValueType()); ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); if (StOutBound && !StInBound) { - ExplodedNode *N = C.generateSink(StOutBound); + ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return; - + if (!BT) BT.reset(new BuiltinBug( this, "Out-of-bound array access", @@ -82,7 +82,7 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, C.emitReport(std::move(report)); return; } - + // Array bound check succeeded. From this point forward the array bound // should always succeed. C.addTransition(StInBound); diff --git a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index d8dc2aa..f4de733 100644 --- a/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -26,15 +26,15 @@ using namespace clang; using namespace ento; namespace { -class ArrayBoundCheckerV2 : +class ArrayBoundCheckerV2 : public Checker<check::Location> { mutable std::unique_ptr<BuiltinBug> BT; enum OOB_Kind { OOB_Precedes, OOB_Excedes, OOB_Tainted }; - + void reportOOB(CheckerContext &C, ProgramStateRef errorState, OOB_Kind kind) const; - + public: void checkLocation(SVal l, bool isLoad, const Stmt*S, CheckerContext &C) const; @@ -55,7 +55,7 @@ public: NonLoc getByteOffset() const { return byteOffset.castAs<NonLoc>(); } const SubRegion *getRegion() const { return baseRegion; } - + static RegionRawOffsetV2 computeOffset(ProgramStateRef state, SValBuilder &svalBuilder, SVal location); @@ -65,12 +65,12 @@ public: }; } -static SVal computeExtentBegin(SValBuilder &svalBuilder, +static SVal computeExtentBegin(SValBuilder &svalBuilder, const MemRegion *region) { while (true) switch (region->getKind()) { default: - return svalBuilder.makeZeroArrayIndex(); + return svalBuilder.makeZeroArrayIndex(); case MemRegion::SymbolicRegionKind: // FIXME: improve this later by tracking symbolic lower bounds // for symbolic regions. @@ -94,22 +94,22 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, // memory access is within the extent of the base region. Since we // have some flexibility in defining the base region, we can achieve // various levels of conservatism in our buffer overflow checking. - ProgramStateRef state = checkerContext.getState(); + ProgramStateRef state = checkerContext.getState(); ProgramStateRef originalState = state; SValBuilder &svalBuilder = checkerContext.getSValBuilder(); - const RegionRawOffsetV2 &rawOffset = + const RegionRawOffsetV2 &rawOffset = RegionRawOffsetV2::computeOffset(state, svalBuilder, location); if (!rawOffset.getRegion()) return; - // CHECK LOWER BOUND: Is byteOffset < extent begin? + // CHECK LOWER BOUND: Is byteOffset < extent begin? // If so, we are doing a load/store // before the first valid offset in the memory region. SVal extentBegin = computeExtentBegin(svalBuilder, rawOffset.getRegion()); - + if (Optional<NonLoc> NV = extentBegin.getAs<NonLoc>()) { SVal lowerBound = svalBuilder.evalBinOpNN(state, BO_LT, rawOffset.getByteOffset(), *NV, @@ -118,7 +118,7 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, Optional<NonLoc> lowerBoundToCheck = lowerBound.getAs<NonLoc>(); if (!lowerBoundToCheck) return; - + ProgramStateRef state_precedesLowerBound, state_withinLowerBound; std::tie(state_precedesLowerBound, state_withinLowerBound) = state->assume(*lowerBoundToCheck); @@ -128,12 +128,12 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, reportOOB(checkerContext, state_precedesLowerBound, OOB_Precedes); return; } - + // Otherwise, assume the constraint of the lower bound. assert(state_withinLowerBound); state = state_withinLowerBound; } - + do { // CHECK UPPER BOUND: Is byteOffset >= extent(baseRegion)? If so, // we are doing a load/store after the last valid offset. @@ -146,11 +146,11 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, = svalBuilder.evalBinOpNN(state, BO_GE, rawOffset.getByteOffset(), extentVal.castAs<NonLoc>(), svalBuilder.getConditionType()); - + Optional<NonLoc> upperboundToCheck = upperbound.getAs<NonLoc>(); if (!upperboundToCheck) break; - + ProgramStateRef state_exceedsUpperBound, state_withinUpperBound; std::tie(state_exceedsUpperBound, state_withinUpperBound) = state->assume(*upperboundToCheck); @@ -161,19 +161,19 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, reportOOB(checkerContext, state_exceedsUpperBound, OOB_Tainted); return; } - + // If we are constrained enough to definitely exceed the upper bound, report. if (state_exceedsUpperBound) { assert(!state_withinUpperBound); reportOOB(checkerContext, state_exceedsUpperBound, OOB_Excedes); return; } - + assert(state_withinUpperBound); state = state_withinUpperBound; } while (false); - + if (state != originalState) checkerContext.addTransition(state); } @@ -181,8 +181,8 @@ void ArrayBoundCheckerV2::checkLocation(SVal location, bool isLoad, void ArrayBoundCheckerV2::reportOOB(CheckerContext &checkerContext, ProgramStateRef errorState, OOB_Kind kind) const { - - ExplodedNode *errorNode = checkerContext.generateSink(errorState); + + ExplodedNode *errorNode = checkerContext.generateErrorNode(errorState); if (!errorNode) return; @@ -259,7 +259,7 @@ RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, { const MemRegion *region = location.getAsRegion(); SVal offset = UndefinedVal(); - + while (region) { switch (region->getKind()) { default: { @@ -280,7 +280,7 @@ RegionRawOffsetV2 RegionRawOffsetV2::computeOffset(ProgramStateRef state, ASTContext &astContext = svalBuilder.getContext(); if (elemType->isIncompleteType()) return RegionRawOffsetV2(); - + // Update the offset. offset = addValue(state, getValue(offset, svalBuilder), diff --git a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index f763284..26d42ba 100644 --- a/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -140,10 +140,10 @@ void NilArgChecker::warnIfNilExpr(const Expr *E, ProgramStateRef State = C.getState(); if (State->isNull(C.getSVal(E)).isConstrainedTrue()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { generateBugReport(N, Msg, E->getSourceRange(), E, C); } - + } } @@ -156,8 +156,8 @@ void NilArgChecker::warnIfNilArg(CheckerContext &C, ProgramStateRef State = C.getState(); if (!State->isNull(msg.getArgSVal(Arg)).isConstrainedTrue()) return; - - if (ExplodedNode *N = C.generateSink()) { + + if (ExplodedNode *N = C.generateErrorNode()) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); @@ -193,7 +193,7 @@ void NilArgChecker::warnIfNilArg(CheckerContext &C, os << "' cannot be nil"; } } - + generateBugReport(N, os.str(), msg.getArgSourceRange(Arg), msg.getArgExpr(Arg), C); } @@ -224,7 +224,7 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, static const unsigned InvalidArgIndex = UINT_MAX; unsigned Arg = InvalidArgIndex; bool CanBeSubscript = false; - + if (Class == FC_NSString) { Selector S = msg.getSelector(); @@ -307,8 +307,7 @@ void NilArgChecker::checkPreObjCMessage(const ObjCMethodCall &msg, warnIfNilArg(C, msg, /* Arg */1, Class); } else if (S == SetObjectForKeyedSubscriptSel) { CanBeSubscript = true; - Arg = 0; - warnIfNilArg(C, msg, /* Arg */1, Class, CanBeSubscript); + Arg = 1; } else if (S == RemoveObjectForKeySel) { Arg = 0; } @@ -433,7 +432,7 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; - + ASTContext &Ctx = C.getASTContext(); if (!II) II = &Ctx.Idents.get("CFNumberCreate"); @@ -489,23 +488,24 @@ void CFNumberCreateChecker::checkPreStmt(const CallExpr *CE, if (SourceSize == TargetSize) return; - // Generate an error. Only generate a sink if 'SourceSize < TargetSize'; - // otherwise generate a regular node. + // Generate an error. Only generate a sink error node + // if 'SourceSize < TargetSize'; otherwise generate a non-fatal error node. // // FIXME: We can actually create an abstract "CFNumber" object that has // the bits initialized to the provided values. // - if (ExplodedNode *N = SourceSize < TargetSize ? C.generateSink() - : C.addTransition()) { + ExplodedNode *N = SourceSize < TargetSize ? C.generateErrorNode() + : C.generateNonFatalErrorNode(); + if (N) { SmallString<128> sbuf; llvm::raw_svector_ostream os(sbuf); - + os << (SourceSize == 8 ? "An " : "A ") << SourceSize << " bit integer is used to initialize a CFNumber " "object that represents " << (TargetSize == 8 ? "an " : "a ") << TargetSize << " bit integer. "; - + if (SourceSize < TargetSize) os << (TargetSize - SourceSize) << " bits of the CFNumber value will be garbage." ; @@ -549,7 +549,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD) return; - + if (!BT) { ASTContext &Ctx = C.getASTContext(); Retain = &Ctx.Idents.get("CFRetain"); @@ -589,7 +589,7 @@ void CFRetainReleaseChecker::checkPreStmt(const CallExpr *CE, std::tie(stateTrue, stateFalse) = state->assume(ArgIsNull); if (stateTrue && !stateFalse) { - ExplodedNode *N = C.generateSink(stateTrue); + ExplodedNode *N = C.generateErrorNode(stateTrue); if (!N) return; @@ -635,7 +635,7 @@ public: void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const { - + if (!BT) { BT.reset(new APIMisuse( this, "message incorrectly sent to class instead of class instance")); @@ -646,7 +646,7 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, autoreleaseS = GetNullarySelector("autorelease", Ctx); drainS = GetNullarySelector("drain", Ctx); } - + if (msg.isInstanceMessage()) return; const ObjCInterfaceDecl *Class = msg.getReceiverInterface(); @@ -655,8 +655,8 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, Selector S = msg.getSelector(); if (!(S == releaseS || S == retainS || S == autoreleaseS || S == drainS)) return; - - if (ExplodedNode *N = C.addTransition()) { + + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { SmallString<200> buf; llvm::raw_svector_ostream os(buf); @@ -665,7 +665,7 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, os << "' message should be sent to instances " "of class '" << Class->getName() << "' and not the class directly"; - + auto report = llvm::make_unique<BugReport>(*BT, os.str(), N); report->addRange(msg.getSourceRange()); C.emitReport(std::move(report)); @@ -699,12 +699,12 @@ public: bool VariadicMethodTypeChecker::isVariadicMessage(const ObjCMethodCall &msg) const { const ObjCMethodDecl *MD = msg.getDecl(); - + if (!MD || !MD->isVariadic() || isa<ObjCProtocolDecl>(MD->getDeclContext())) return false; - + Selector S = msg.getSelector(); - + if (msg.isInstanceMessage()) { // FIXME: Ideally we'd look at the receiver interface here, but that's not // useful for init, because alloc returns 'id'. In theory, this could lead @@ -751,7 +751,7 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, ASTContext &Ctx = C.getASTContext(); arrayWithObjectsS = GetUnarySelector("arrayWithObjects", Ctx); - dictionaryWithObjectsAndKeysS = + dictionaryWithObjectsAndKeysS = GetUnarySelector("dictionaryWithObjectsAndKeys", Ctx); setWithObjectsS = GetUnarySelector("setWithObjects", Ctx); orderedSetWithObjectsS = GetUnarySelector("orderedSetWithObjects", Ctx); @@ -789,18 +789,18 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, // Ignore pointer constants. if (msg.getArgSVal(I).getAs<loc::ConcreteInt>()) continue; - + // Ignore pointer types annotated with 'NSObject' attribute. if (C.getASTContext().isObjCNSObjectType(ArgTy)) continue; - + // Ignore CF references, which can be toll-free bridged. if (coreFoundation::isCFObjectRef(ArgTy)) continue; // Generate only one error node to use for all bug reports. if (!errorNode.hasValue()) - errorNode = C.addTransition(); + errorNode = C.generateNonFatalErrorNode(); if (!errorNode.getValue()) continue; @@ -861,7 +861,7 @@ static bool isKnownNonNilCollectionType(QualType T) { const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>(); if (!PT) return false; - + const ObjCInterfaceDecl *ID = PT->getInterfaceDecl(); if (!ID) return false; @@ -992,9 +992,7 @@ static bool alreadyExecutedAtLeastOneLoopIteration(const ExplodedNode *N, ProgramPoint P = N->getLocation(); if (Optional<BlockEdge> BE = P.getAs<BlockEdge>()) { - if (BE->getSrc()->getLoopTarget() == FCS) - return true; - return false; + return BE->getSrc()->getLoopTarget() == FCS; } // Keep looking for a block edge. @@ -1023,9 +1021,9 @@ void ObjCLoopChecker::checkPostStmt(const ObjCForCollectionStmt *FCS, State = checkElementNonNil(C, State, FCS); State = assumeCollectionNonEmpty(C, State, FCS, /*Assumption*/true); } - + if (!State) - C.generateSink(); + C.generateSink(C.getState(), C.getPredecessor()); else if (State != C.getState()) C.addTransition(State); } @@ -1038,11 +1036,8 @@ bool ObjCLoopChecker::isCollectionCountMethod(const ObjCMethodCall &M, CountSelectorII = &C.getASTContext().Idents.get("count"); // If the method returns collection count, record the value. - if (S.isUnarySelector() && - (S.getIdentifierInfoForSlot(0) == CountSelectorII)) - return true; - - return false; + return S.isUnarySelector() && + (S.getIdentifierInfoForSlot(0) == CountSelectorII); } void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M, @@ -1069,7 +1064,7 @@ void ObjCLoopChecker::checkPostObjCMessage(const ObjCMethodCall &M, // a call to "count" and add it to the map. if (!isCollectionCountMethod(M, C)) return; - + const Expr *MsgExpr = M.getOriginExpr(); SymbolRef CountS = C.getSVal(MsgExpr).getAsSymbol(); if (CountS) { diff --git a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp index e945c38..f26f731 100644 --- a/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -32,7 +32,7 @@ namespace { void BoolAssignmentChecker::emitReport(ProgramStateRef state, CheckerContext &C) const { - if (ExplodedNode *N = C.addTransition(state)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { if (!BT) BT.reset(new BuiltinBug(this, "Assignment of a non-Boolean value")); C.emitReport(llvm::make_unique<BugReport>(*BT, BT->getDescription(), N)); @@ -42,45 +42,45 @@ void BoolAssignmentChecker::emitReport(ProgramStateRef state, static bool isBooleanType(QualType Ty) { if (Ty->isBooleanType()) // C++ or C99 return true; - + if (const TypedefType *TT = Ty->getAs<TypedefType>()) return TT->getDecl()->getName() == "BOOL" || // Objective-C TT->getDecl()->getName() == "_Bool" || // stdbool.h < C99 TT->getDecl()->getName() == "Boolean"; // MacTypes.h - + return false; } void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const { - + // We are only interested in stores into Booleans. const TypedValueRegion *TR = dyn_cast_or_null<TypedValueRegion>(loc.getAsRegion()); - + if (!TR) return; - + QualType valTy = TR->getValueType(); - + if (!isBooleanType(valTy)) return; - + // Get the value of the right-hand side. We only care about values // that are defined (UnknownVals and UndefinedVals are handled by other // checkers). Optional<DefinedSVal> DV = val.getAs<DefinedSVal>(); if (!DV) return; - + // Check if the assigned value meets our criteria for correctness. It must // be a value that is either 0 or 1. One way to check this is to see if // the value is possibly < 0 (for a negative value) or greater than 1. - ProgramStateRef state = C.getState(); + ProgramStateRef state = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); ConstraintManager &CM = C.getConstraintManager(); - - // First, ensure that the value is >= 0. + + // First, ensure that the value is >= 0. DefinedSVal zeroVal = svalBuilder.makeIntVal(0, valTy); SVal greaterThanOrEqualToZeroVal = svalBuilder.evalBinOp(state, BO_GE, *DV, zeroVal, @@ -91,13 +91,13 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, if (!greaterThanEqualToZero) { // The SValBuilder cannot construct a valid SVal for this condition. - // This means we cannot properly reason about it. + // This means we cannot properly reason about it. return; } - + ProgramStateRef stateLT, stateGE; std::tie(stateGE, stateLT) = CM.assumeDual(state, *greaterThanEqualToZero); - + // Is it possible for the value to be less than zero? if (stateLT) { // It is possible for the value to be less than zero. We only @@ -106,15 +106,15 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, // value is underconstrained and there is nothing left to be done. if (!stateGE) emitReport(stateLT, C); - + // In either case, we are done. return; } - + // If we reach here, it must be the case that the value is constrained // to only be >= 0. assert(stateGE == state); - + // At this point we know that the value is >= 0. // Now check to ensure that the value is <= 1. DefinedSVal OneVal = svalBuilder.makeIntVal(1, valTy); @@ -127,13 +127,13 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, if (!lessThanEqToOne) { // The SValBuilder cannot construct a valid SVal for this condition. - // This means we cannot properly reason about it. + // This means we cannot properly reason about it. return; } - + ProgramStateRef stateGT, stateLE; std::tie(stateLE, stateGT) = CM.assumeDual(state, *lessThanEqToOne); - + // Is it possible for the value to be greater than one? if (stateGT) { // It is possible for the value to be greater than one. We only @@ -142,11 +142,11 @@ void BoolAssignmentChecker::checkBind(SVal loc, SVal val, const Stmt *S, // value is underconstrained and there is nothing left to be done. if (!stateLE) emitReport(stateGT, C); - + // In either case, we are done. return; } - + // If we reach here, it must be the case that the value is constrained // to only be <= 1. assert(stateLE == state); diff --git a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index 104a81e..dab2f61 100644 --- a/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -41,11 +41,12 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, default: return false; + case Builtin::BI__builtin_unpredictable: case Builtin::BI__builtin_expect: case Builtin::BI__builtin_assume_aligned: case Builtin::BI__builtin_addressof: { - // For __builtin_expect and __builtin_assume_aligned, just return the value - // of the subexpression. + // For __builtin_unpredictable, __builtin_expect, and + // __builtin_assume_aligned, just return the value of the subexpression. // __builtin_addressof is going from a reference to a pointer, but those // are represented the same way in the analyzer. assert (CE->arg_begin() != CE->arg_end()); diff --git a/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/lib/StaticAnalyzer/Checkers/CMakeLists.txt index 9fb22ec..58ff48d 100644 --- a/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -33,12 +33,14 @@ add_clang_library(clangStaticAnalyzerCheckers DirectIvarAssignment.cpp DivZeroChecker.cpp DynamicTypePropagation.cpp + DynamicTypeChecker.cpp ExprInspectionChecker.cpp FixedAddressChecker.cpp GenericTaintChecker.cpp IdenticalExprChecker.cpp IvarInvalidationChecker.cpp LLVMConventionsChecker.cpp + LocalizationChecker.cpp MacOSKeychainAPIChecker.cpp MacOSXAPIChecker.cpp MallocChecker.cpp @@ -48,12 +50,14 @@ add_clang_library(clangStaticAnalyzerCheckers NSErrorChecker.cpp NoReturnFunctionChecker.cpp NonNullParamChecker.cpp + NullabilityChecker.cpp ObjCAtSyncChecker.cpp ObjCContainersASTChecker.cpp ObjCContainersChecker.cpp ObjCMissingSuperCallChecker.cpp ObjCSelfInitChecker.cpp ObjCUnusedIVarsChecker.cpp + PaddingChecker.cpp PointerArithChecker.cpp PointerSubChecker.cpp PthreadLockChecker.cpp @@ -73,6 +77,7 @@ add_clang_library(clangStaticAnalyzerCheckers UndefinedAssignmentChecker.cpp UnixAPIChecker.cpp UnreachableCodeChecker.cpp + VforkChecker.cpp VLASizeChecker.cpp VirtualCallChecker.cpp @@ -83,5 +88,6 @@ add_clang_library(clangStaticAnalyzerCheckers clangAST clangAnalysis clangBasic + clangLex clangStaticAnalyzerCore ) diff --git a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 54b1241..5d78d9b 100644 --- a/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -65,7 +65,7 @@ public: void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; bool wantsRegionChangeUpdate(ProgramStateRef state) const; - ProgramStateRef + ProgramStateRef checkRegionChanges(ProgramStateRef state, const InvalidatedSymbols *, ArrayRef<const MemRegion *> ExplicitRegions, @@ -92,7 +92,7 @@ public: void evalstrLength(CheckerContext &C, const CallExpr *CE) const; void evalstrnLength(CheckerContext &C, const CallExpr *CE) const; void evalstrLengthCommon(CheckerContext &C, - const CallExpr *CE, + const CallExpr *CE, bool IsStrnlen = false) const; void evalStrcpy(CheckerContext &C, const CallExpr *CE) const; @@ -137,15 +137,16 @@ public: SVal Buf, bool hypothetical = false) const; - const StringLiteral *getCStringLiteral(CheckerContext &C, + const StringLiteral *getCStringLiteral(CheckerContext &C, ProgramStateRef &state, - const Expr *expr, + const Expr *expr, SVal val) const; static ProgramStateRef InvalidateBuffer(CheckerContext &C, ProgramStateRef state, const Expr *Ex, SVal V, - bool IsSourceBuffer); + bool IsSourceBuffer, + const Expr *Size); static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR); @@ -193,6 +194,14 @@ public: ProgramStateRef state, NonLoc left, NonLoc right) const; + + // Return true if the destination buffer of the copy function may be in bound. + // Expects SVal of Size to be positive and unsigned. + // Expects SVal of FirstBuf to be a FieldRegion. + static bool IsFirstBufInBound(CheckerContext &C, + ProgramStateRef state, + const Expr *FirstBuf, + const Expr *Size); }; } //end anonymous namespace @@ -229,7 +238,7 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, if (!Filter.CheckCStringNullArg) return nullptr; - ExplodedNode *N = C.generateSink(stateNull); + ExplodedNode *N = C.generateErrorNode(stateNull); if (!N) return nullptr; @@ -282,7 +291,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, // Get the size of the array. const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion()); SValBuilder &svalBuilder = C.getSValBuilder(); - SVal Extent = + SVal Extent = svalBuilder.convertToArrayIndex(superReg->getExtent(svalBuilder)); DefinedOrUnknownSVal Size = Extent.castAs<DefinedOrUnknownSVal>(); @@ -292,7 +301,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, ProgramStateRef StInBound = state->assumeInBound(Idx, Size, true); ProgramStateRef StOutBound = state->assumeInBound(Idx, Size, false); if (StOutBound && !StInBound) { - ExplodedNode *N = C.generateSink(StOutBound); + ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return nullptr; @@ -327,7 +336,7 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, C.emitReport(std::move(report)); return nullptr; } - + // Array bound check succeeded. From this point forward the array bound // should always succeed. return StInBound; @@ -442,7 +451,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, return state; // Are the two values the same? - SValBuilder &svalBuilder = C.getSValBuilder(); + SValBuilder &svalBuilder = C.getSValBuilder(); std::tie(stateTrue, stateFalse) = state->assume(svalBuilder.evalEQ(state, *firstLoc, *secondLoc)); @@ -489,7 +498,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, // Bail out if the cast fails. ASTContext &Ctx = svalBuilder.getContext(); QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); - SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy, + SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy, First->getType()); Optional<Loc> FirstStartLoc = FirstStart.getAs<Loc>(); if (!FirstStartLoc) @@ -525,7 +534,7 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state, const Stmt *First, const Stmt *Second) const { - ExplodedNode *N = C.generateSink(state); + ExplodedNode *N = C.generateErrorNode(state); if (!N) return; @@ -568,7 +577,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, } else { // Try switching the operands. (The order of these two assignments is // important!) - maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, left, + maxMinusRight = svalBuilder.evalBinOpNN(state, BO_Sub, maxVal, left, sizeTy); left = right; } @@ -585,7 +594,7 @@ ProgramStateRef CStringChecker::checkAdditionOverflow(CheckerContext &C, if (stateOverflow && !stateOkay) { // We have an overflow. Emit a bug report. - ExplodedNode *N = C.generateSink(stateOverflow); + ExplodedNode *N = C.generateErrorNode(stateOverflow); if (!N) return nullptr; @@ -706,7 +715,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, if (!Filter.CheckCStringNotNullTerm) return UndefinedVal(); - if (ExplodedNode *N = C.addTransition(state)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { if (!BT_NotCString) BT_NotCString.reset(new BuiltinBug( Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, @@ -723,7 +732,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, auto report = llvm::make_unique<BugReport>(*BT_NotCString, os.str(), N); report->addRange(Ex->getSourceRange()); - C.emitReport(std::move(report)); + C.emitReport(std::move(report)); } return UndefinedVal(); @@ -766,7 +775,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, if (!Filter.CheckCStringNotNullTerm) return UndefinedVal(); - if (ExplodedNode *N = C.addTransition(state)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { if (!BT_NotCString) BT_NotCString.reset(new BuiltinBug( Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, @@ -787,7 +796,7 @@ SVal CStringChecker::getCStringLength(CheckerContext &C, ProgramStateRef &state, auto report = llvm::make_unique<BugReport>(*BT_NotCString, os.str(), N); report->addRange(Ex->getSourceRange()); - C.emitReport(std::move(report)); + C.emitReport(std::move(report)); } return UndefinedVal(); @@ -814,10 +823,74 @@ const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C, return strRegion->getStringLiteral(); } +bool CStringChecker::IsFirstBufInBound(CheckerContext &C, + ProgramStateRef state, + const Expr *FirstBuf, + const Expr *Size) { + // If we do not know that the buffer is long enough we return 'true'. + // Otherwise the parent region of this field region would also get + // invalidated, which would lead to warnings based on an unknown state. + + // Originally copied from CheckBufferAccess and CheckLocation. + SValBuilder &svalBuilder = C.getSValBuilder(); + ASTContext &Ctx = svalBuilder.getContext(); + const LocationContext *LCtx = C.getLocationContext(); + + QualType sizeTy = Size->getType(); + QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); + SVal BufVal = state->getSVal(FirstBuf, LCtx); + + SVal LengthVal = state->getSVal(Size, LCtx); + Optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); + if (!Length) + return true; // cf top comment. + + // Compute the offset of the last element to be accessed: size-1. + NonLoc One = svalBuilder.makeIntVal(1, sizeTy).castAs<NonLoc>(); + NonLoc LastOffset = + svalBuilder.evalBinOpNN(state, BO_Sub, *Length, One, sizeTy) + .castAs<NonLoc>(); + + // Check that the first buffer is sufficiently long. + SVal BufStart = svalBuilder.evalCast(BufVal, PtrTy, FirstBuf->getType()); + Optional<Loc> BufLoc = BufStart.getAs<Loc>(); + if (!BufLoc) + return true; // cf top comment. + + SVal BufEnd = + svalBuilder.evalBinOpLN(state, BO_Add, *BufLoc, LastOffset, PtrTy); + + // Check for out of bound array element access. + const MemRegion *R = BufEnd.getAsRegion(); + if (!R) + return true; // cf top comment. + + const ElementRegion *ER = dyn_cast<ElementRegion>(R); + if (!ER) + return true; // cf top comment. + + assert(ER->getValueType() == C.getASTContext().CharTy && + "IsFirstBufInBound should only be called with char* ElementRegions"); + + // Get the size of the array. + const SubRegion *superReg = cast<SubRegion>(ER->getSuperRegion()); + SVal Extent = + svalBuilder.convertToArrayIndex(superReg->getExtent(svalBuilder)); + DefinedOrUnknownSVal ExtentSize = Extent.castAs<DefinedOrUnknownSVal>(); + + // Get the index of the accessed element. + DefinedOrUnknownSVal Idx = ER->getIndex().castAs<DefinedOrUnknownSVal>(); + + ProgramStateRef StInBound = state->assumeInBound(Idx, ExtentSize, true); + + return static_cast<bool>(StInBound); +} + ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, ProgramStateRef state, const Expr *E, SVal V, - bool IsSourceBuffer) { + bool IsSourceBuffer, + const Expr *Size) { Optional<Loc> L = V.getAs<Loc>(); if (!L) return state; @@ -843,13 +916,23 @@ ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, // Invalidate and escape only indirect regions accessible through the source // buffer. if (IsSourceBuffer) { - ITraits.setTrait(R, + ITraits.setTrait(R, RegionAndSymbolInvalidationTraits::TK_PreserveContents); ITraits.setTrait(R, RegionAndSymbolInvalidationTraits::TK_SuppressEscape); CausesPointerEscape = true; + } else { + const MemRegion::Kind& K = R->getKind(); + if (K == MemRegion::FieldRegionKind) + if (Size && IsFirstBufInBound(C, state, E, Size)) { + // If destination buffer is a field region and access is in bound, + // do not invalidate its super region. + ITraits.setTrait( + R, + RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + } } - return state->invalidateRegions(R, E, C.blockCount(), LCtx, + return state->invalidateRegions(R, E, C.blockCount(), LCtx, CausesPointerEscape, nullptr, nullptr, &ITraits); } @@ -901,7 +984,7 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, // evaluation of individual function calls. //===----------------------------------------------------------------------===// -void CStringChecker::evalCopyCommon(CheckerContext &C, +void CStringChecker::evalCopyCommon(CheckerContext &C, const CallExpr *CE, ProgramStateRef state, const Expr *Size, const Expr *Dest, @@ -941,7 +1024,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // Get the value of the Src. SVal srcVal = state->getSVal(Source, LCtx); - + // Ensure the source is not null. If it is NULL there will be a // NULL pointer dereference. state = checkNonNull(C, state, Source, srcVal); @@ -959,11 +1042,11 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, if (!state) return; - // If this is mempcpy, get the byte after the last byte copied and + // If this is mempcpy, get the byte after the last byte copied and // bind the expr. if (IsMempcpy) { loc::MemRegionVal destRegVal = destVal.castAs<loc::MemRegionVal>(); - + // Get the length to copy. if (Optional<NonLoc> lenValNonLoc = sizeVal.getAs<NonLoc>()) { // Get the byte after the last byte copied. @@ -972,11 +1055,11 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); loc::MemRegionVal DestRegCharVal = SvalBuilder.evalCast(destRegVal, CharPtrTy, Dest->getType()).castAs<loc::MemRegionVal>(); - SVal lastElement = C.getSValBuilder().evalBinOpLN(state, BO_Add, + SVal lastElement = C.getSValBuilder().evalBinOpLN(state, BO_Add, DestRegCharVal, - *lenValNonLoc, + *lenValNonLoc, Dest->getType()); - + // The byte after the last byte copied is the return value. state = state->BindExpr(CE, LCtx, lastElement); } else { @@ -999,13 +1082,13 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // can use LazyCompoundVals to copy the source values into the destination. // This would probably remove any existing bindings past the end of the // copied region, but that's still an improvement over blank invalidation. - state = InvalidateBuffer(C, state, Dest, C.getSVal(Dest), - /*IsSourceBuffer*/false); + state = InvalidateBuffer(C, state, Dest, C.getSVal(Dest), + /*IsSourceBuffer*/false, Size); // Invalidate the source (const-invalidation without const-pointer-escaping // the address of the top-level region). - state = InvalidateBuffer(C, state, Source, C.getSVal(Source), - /*IsSourceBuffer*/true); + state = InvalidateBuffer(C, state, Source, C.getSVal(Source), + /*IsSourceBuffer*/true, nullptr); C.addTransition(state); } @@ -1032,7 +1115,7 @@ void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { // The return value is a pointer to the byte following the last written byte. const Expr *Dest = CE->getArg(0); ProgramStateRef state = C.getState(); - + evalCopyCommon(C, CE, state, CE->getArg(2), Dest, CE->getArg(1), true, true); } @@ -1053,7 +1136,7 @@ void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { return; // void bcopy(const void *src, void *dst, size_t n); - evalCopyCommon(C, CE, C.getState(), + evalCopyCommon(C, CE, C.getState(), CE->getArg(2), CE->getArg(1), CE->getArg(0)); } @@ -1244,7 +1327,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, state, BO_LE, resultNL, *strLengthNL, cmpTy) .castAs<DefinedOrUnknownSVal>(), true); } - + if (maxlenValNL) { state = state->assume(C.getSValBuilder().evalBinOpNN( state, BO_LE, resultNL, *maxlenValNL, cmpTy) @@ -1275,8 +1358,8 @@ void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { return; // char *strcpy(char *restrict dst, const char *restrict src); - evalStrcpyCommon(C, CE, - /* returnEnd = */ false, + evalStrcpyCommon(C, CE, + /* returnEnd = */ false, /* isBounded = */ false, /* isAppending = */ false); } @@ -1286,8 +1369,8 @@ void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { return; // char *strncpy(char *restrict dst, const char *restrict src, size_t n); - evalStrcpyCommon(C, CE, - /* returnEnd = */ false, + evalStrcpyCommon(C, CE, + /* returnEnd = */ false, /* isBounded = */ true, /* isAppending = */ false); } @@ -1297,8 +1380,8 @@ void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { return; // char *stpcpy(char *restrict dst, const char *restrict src); - evalStrcpyCommon(C, CE, - /* returnEnd = */ true, + evalStrcpyCommon(C, CE, + /* returnEnd = */ true, /* isBounded = */ false, /* isAppending = */ false); } @@ -1308,8 +1391,8 @@ void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { return; //char *strcat(char *restrict s1, const char *restrict s2); - evalStrcpyCommon(C, CE, - /* returnEnd = */ false, + evalStrcpyCommon(C, CE, + /* returnEnd = */ false, /* isBounded = */ false, /* isAppending = */ true); } @@ -1319,8 +1402,8 @@ void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { return; //char *strncat(char *restrict s1, const char *restrict s2, size_t n); - evalStrcpyCommon(C, CE, - /* returnEnd = */ false, + evalStrcpyCommon(C, CE, + /* returnEnd = */ false, /* isBounded = */ true, /* isAppending = */ true); } @@ -1515,7 +1598,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, Optional<NonLoc> srcStrLengthNL = amountCopied.getAs<NonLoc>(); Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>(); - + // If we know both string lengths, we might know the final string length. if (srcStrLengthNL && dstStrLengthNL) { // Make sure the two lengths together don't overflow a size_t. @@ -1523,7 +1606,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (!state) return; - finalStrLength = svalBuilder.evalBinOpNN(state, BO_Add, *srcStrLengthNL, + finalStrLength = svalBuilder.evalBinOpNN(state, BO_Add, *srcStrLengthNL, *dstStrLengthNL, sizeTy); } @@ -1586,7 +1669,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (Optional<NonLoc> maxLastNL = maxLastElementIndex.getAs<NonLoc>()) { SVal maxLastElement = svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, *maxLastNL, ptrTy); - state = CheckLocation(C, state, CE->getArg(2), maxLastElement, + state = CheckLocation(C, state, CE->getArg(2), maxLastElement, boundWarning); if (!state) return; @@ -1620,11 +1703,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // This would probably remove any existing bindings past the end of the // string, but that's still an improvement over blank invalidation. state = InvalidateBuffer(C, state, Dst, *dstRegVal, - /*IsSourceBuffer*/false); + /*IsSourceBuffer*/false, nullptr); // Invalidate the source (const-invalidation without const-pointer-escaping // the address of the top-level region). - state = InvalidateBuffer(C, state, srcExpr, srcVal, /*IsSourceBuffer*/true); + state = InvalidateBuffer(C, state, srcExpr, srcVal, /*IsSourceBuffer*/true, + nullptr); // Set the C string length of the destination, if we know it. if (isBounded && !isAppending) { @@ -1667,7 +1751,7 @@ void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ false); } -void CStringChecker::evalStrcasecmp(CheckerContext &C, +void CStringChecker::evalStrcasecmp(CheckerContext &C, const CallExpr *CE) const { if (CE->getNumArgs() < 2) return; @@ -1676,7 +1760,7 @@ void CStringChecker::evalStrcasecmp(CheckerContext &C, evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ true); } -void CStringChecker::evalStrncasecmp(CheckerContext &C, +void CStringChecker::evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const { if (CE->getNumArgs() < 3) return; @@ -1848,7 +1932,7 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { // Invalidate the search string, representing the change of one delimiter // character to NUL. State = InvalidateBuffer(C, State, SearchStrPtr, Result, - /*IsSourceBuffer*/false); + /*IsSourceBuffer*/false, nullptr); // Overwrite the search string pointer. The new value is either an address // further along in the same string, or NULL if there are no more tokens. @@ -1915,7 +1999,7 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { evalFunction = &CStringChecker::evalBcopy; else if (C.isCLibraryFunction(FDecl, "bcmp")) evalFunction = &CStringChecker::evalMemcmp; - + // If the callee isn't a string function, let another checker handle it. if (!evalFunction) return false; @@ -1929,10 +2013,7 @@ bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // properties are held. However, if the user chooses to turn off some of these // checks, we ignore the issues and leave the call evaluation to a generic // handler. - if (!C.isDifferent()) - return false; - - return true; + return C.isDifferent(); } void CStringChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { @@ -1975,7 +2056,7 @@ bool CStringChecker::wantsRegionChangeUpdate(ProgramStateRef state) const { return !Entries.isEmpty(); } -ProgramStateRef +ProgramStateRef CStringChecker::checkRegionChanges(ProgramStateRef state, const InvalidatedSymbols *, ArrayRef<const MemRegion *> ExplicitRegions, diff --git a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index 26423b7..1459083 100644 --- a/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -40,6 +40,7 @@ class CallAndMessageChecker : public Checker< check::PreStmt<CallExpr>, check::PreStmt<CXXDeleteExpr>, check::PreObjCMessage, + check::ObjCMessageNil, check::PreCall > { mutable std::unique_ptr<BugType> BT_call_null; mutable std::unique_ptr<BugType> BT_call_undef; @@ -60,6 +61,12 @@ public: void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; + + /// Fill in the return value that results from messaging nil based on the + /// return type and architecture and diagnose if the return value will be + /// garbage. + void checkObjCMessageNil(const ObjCMethodCall &msg, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; private: @@ -82,7 +89,7 @@ private: BT.reset(new BuiltinBug(this, desc)); } bool uninitRefOrPointer(CheckerContext &C, const SVal &V, - const SourceRange &ArgRange, + SourceRange ArgRange, const Expr *ArgEx, std::unique_ptr<BugType> &BT, const ParmVarDecl *ParamDecl, const char *BD) const; }; @@ -90,7 +97,7 @@ private: void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C, const Expr *BadE) { - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; @@ -131,7 +138,7 @@ static StringRef describeUninitializedArgumentInCall(const CallEvent &Call, bool CallAndMessageChecker::uninitRefOrPointer(CheckerContext &C, const SVal &V, - const SourceRange &ArgRange, + SourceRange ArgRange, const Expr *ArgEx, std::unique_ptr<BugType> &BT, const ParmVarDecl *ParamDecl, @@ -162,7 +169,7 @@ bool CallAndMessageChecker::uninitRefOrPointer(CheckerContext &C, const ProgramStateRef State = C.getState(); const SVal PSV = State->getSVal(SValMemRegion); if (PSV.isUndef()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); auto R = llvm::make_unique<BugReport>(*BT, Message, N); R->addRange(ArgRange); @@ -193,7 +200,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, return true; if (V.isUndef()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); // Generate a report for this bug. @@ -258,7 +265,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, D->getStore()); if (F.Find(D->getRegion())) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); SmallString<512> Str; llvm::raw_svector_ostream os(Str); @@ -331,7 +338,7 @@ void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE, SVal Arg = C.getSVal(DE->getArgument()); if (Arg.isUndef()) { StringRef Desc; - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; if (!BT_cxx_delete_undef) @@ -388,7 +395,7 @@ void CallAndMessageChecker::checkPreCall(const CallEvent &Call, // the function. unsigned Params = FD->getNumParams(); if (Call.getNumArgs() < Params) { - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; @@ -436,7 +443,7 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const { SVal recVal = msg.getReceiverSVal(); if (recVal.isUndef()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { BugType *BT = nullptr; switch (msg.getMessageKind()) { case OCM_Message: @@ -471,22 +478,14 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, C.emitReport(std::move(R)); } return; - } else { - // Bifurcate the state into nil and non-nil ones. - DefinedOrUnknownSVal receiverVal = recVal.castAs<DefinedOrUnknownSVal>(); - - ProgramStateRef state = C.getState(); - ProgramStateRef notNilState, nilState; - std::tie(notNilState, nilState) = state->assume(receiverVal); - - // Handle receiver must be nil. - if (nilState && !notNilState) { - HandleNilReceiver(C, state, msg); - return; - } } } +void CallAndMessageChecker::checkObjCMessageNil(const ObjCMethodCall &msg, + CheckerContext &C) const { + HandleNilReceiver(C, C.getState(), msg); +} + void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, const ObjCMethodCall &msg, ExplodedNode *N) const { @@ -523,7 +522,8 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, static bool supportsNilWithFloatRet(const llvm::Triple &triple) { return (triple.getVendor() == llvm::Triple::Apple && - (triple.isiOS() || !triple.isMacOSXVersionLT(10,5))); + (triple.isiOS() || triple.isWatchOS() || + !triple.isMacOSXVersionLT(10,5))); } void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C, @@ -560,7 +560,7 @@ void CallAndMessageChecker::HandleNilReceiver(CheckerContext &C, Ctx.LongDoubleTy == CanRetTy || Ctx.LongLongTy == CanRetTy || Ctx.UnsignedLongLongTy == CanRetTy)))) { - if (ExplodedNode *N = C.generateSink(state, nullptr, &Tag)) + if (ExplodedNode *N = C.generateErrorNode(state, &Tag)) emitNilReceiverBug(C, Msg, N); return; } diff --git a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 0d683f9..2337400 100644 --- a/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -82,10 +82,7 @@ static bool evenFlexibleArraySize(ASTContext &Ctx, CharUnits RegionSize, if (Left.isNegative()) return false; - if (Left % FlexSize == 0) - return true; - - return false; + return Left % FlexSize == 0; } void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { @@ -131,7 +128,7 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { if (evenFlexibleArraySize(Ctx, regionSize, typeSize, ToPointeeTy)) return; - if (ExplodedNode *errorNode = C.generateSink()) { + if (ExplodedNode *errorNode = C.generateErrorNode()) { if (!BT) BT.reset(new BuiltinBug(this, "Cast region with wrong size.", "Cast a region whose size is not a multiple" diff --git a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp index ba3024d..fa78413 100644 --- a/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/CastToStructChecker.cpp @@ -56,7 +56,7 @@ void CastToStructChecker::checkPreStmt(const CastExpr *CE, // Now the cast-to-type is struct pointer, the original type is not void*. if (!OrigPointeeTy->isRecordType()) { - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { if (!BT) BT.reset( new BuiltinBug(this, "Cast from non-struct type to struct type", diff --git a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index 12eb0bd..25caa00 100644 --- a/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -51,7 +51,7 @@ static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID, if (E->getDecl()->getIdentifier() == SelfII) if (ME->getMethodDecl() == PD->getSetterMethodDecl() && ME->getNumArgs() == 1 && - ME->getArg(0)->isNullPointerConstant(Ctx, + ME->getArg(0)->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull)) return true; @@ -61,7 +61,7 @@ static bool scan_ivar_release(Stmt *S, ObjCIvarDecl *ID, if (ObjCPropertyRefExpr *PRE = dyn_cast<ObjCPropertyRefExpr>(BO->getLHS()->IgnoreParenCasts())) if (PRE->isExplicitProperty() && PRE->getExplicitProperty() == PD) - if (BO->getRHS()->isNullPointerConstant(Ctx, + if (BO->getRHS()->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull)) { // This is only a 'release' if the property kind is not // 'assign'. diff --git a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index e0c113c..60f1618 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -86,8 +86,7 @@ public: // Helpers. bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD); - typedef void (WalkAST::*FnCheck)(const CallExpr *, - const FunctionDecl *); + typedef void (WalkAST::*FnCheck)(const CallExpr *, const FunctionDecl *); // Checker-specific methods. void checkLoopConditionForFloat(const ForStmt *FS); @@ -115,7 +114,7 @@ void WalkAST::VisitChildren(Stmt *S) { } void WalkAST::VisitCallExpr(CallExpr *CE) { - // Get the callee. + // Get the callee. const FunctionDecl *FD = CE->getDirectCallee(); if (!FD) @@ -307,7 +306,7 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { void WalkAST::checkCall_gets(const CallExpr *CE, const FunctionDecl *FD) { if (!filter.check_gets) return; - + const FunctionProtoType *FPT = FD->getType()->getAs<FunctionProtoType>(); if (!FPT) return; @@ -434,18 +433,18 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) { .Case("mkdtemp", std::make_pair(0,-1)) .Case("mkstemps", std::make_pair(0,1)) .Default(std::make_pair(-1, -1)); - + assert(ArgSuffix.first >= 0 && "Unsupported function"); // Check if the number of arguments is consistent with out expectations. unsigned numArgs = CE->getNumArgs(); if ((signed) numArgs <= ArgSuffix.first) return; - + const StringLiteral *strArg = dyn_cast<StringLiteral>(CE->getArg((unsigned)ArgSuffix.first) ->IgnoreParenImpCasts()); - + // Currently we only handle string literals. It is possible to do better, // either by looking at references to const variables, or by doing real // flow analysis. @@ -470,13 +469,13 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) { suffix = (unsigned) Result.getZExtValue(); n = (n > suffix) ? n - suffix : 0; } - + for (unsigned i = 0; i < n; ++i) if (str[i] == 'X') ++numX; - + if (numX >= 6) return; - + // Issue a warning. PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); @@ -502,13 +501,13 @@ void WalkAST::checkCall_mkstemp(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// // Check: Any use of 'strcpy' is insecure. // -// CWE-119: Improper Restriction of Operations within -// the Bounds of a Memory Buffer +// CWE-119: Improper Restriction of Operations within +// the Bounds of a Memory Buffer //===----------------------------------------------------------------------===// void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { if (!filter.check_strcpy) return; - + if (!checkCall_strCommon(CE, FD)) return; @@ -529,8 +528,8 @@ void WalkAST::checkCall_strcpy(const CallExpr *CE, const FunctionDecl *FD) { //===----------------------------------------------------------------------===// // Check: Any use of 'strcat' is insecure. // -// CWE-119: Improper Restriction of Operations within -// the Bounds of a Memory Buffer +// CWE-119: Improper Restriction of Operations within +// the Bounds of a Memory Buffer //===----------------------------------------------------------------------===// void WalkAST::checkCall_strcat(const CallExpr *CE, const FunctionDecl *FD) { if (!filter.check_strcpy) @@ -684,7 +683,7 @@ void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) { void WalkAST::checkUncheckedReturnValue(CallExpr *CE) { if (!filter.check_UncheckedReturn) return; - + const FunctionDecl *FD = CE->getDirectCallee(); if (!FD) return; @@ -749,7 +748,7 @@ namespace { class SecuritySyntaxChecker : public Checker<check::ASTCodeBody> { public: ChecksFilter filter; - + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { WalkAST walker(BR, mgr.getAnalysisDeclContext(D), filter); diff --git a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp index 81a2006..e079a8c 100644 --- a/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckSizeofPointer.cpp @@ -55,8 +55,8 @@ void WalkAST::VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr *E) { QualType T = E->getTypeOfArgument(); if (T->isPointerType()) { - // Many false positives have the form 'sizeof *p'. This is reasonable - // because people know what they are doing when they intentionally + // Many false positives have the form 'sizeof *p'. This is reasonable + // because people know what they are doing when they intentionally // dereference the pointer. Expr *ArgEx = E->getArgumentExpr(); if (!isa<DeclRefExpr>(ArgEx->IgnoreParens())) diff --git a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp index 956dca7..37b8448 100644 --- a/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp +++ b/lib/StaticAnalyzer/Checkers/CheckerDocumentation.cpp @@ -38,6 +38,7 @@ class CheckerDocumentation : public Checker< check::PreStmt<ReturnStmt>, check::PostStmt<DeclStmt>, check::PreObjCMessage, check::PostObjCMessage, + check::ObjCMessageNil, check::PreCall, check::PostCall, check::BranchCondition, @@ -95,6 +96,15 @@ public: /// check::PostObjCMessage void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const {} + /// \brief Visit an Objective-C message whose receiver is nil. + /// + /// This will be called when the analyzer core processes a method call whose + /// receiver is definitely nil. In this case, check{Pre/Post}ObjCMessage and + /// check{Pre/Post}Call will not be called. + /// + /// check::ObjCMessageNil + void checkObjCMessageNil(const ObjCMethodCall &M, CheckerContext &C) const {} + /// \brief Pre-visit an abstract "call" event. /// /// This is used for checkers that want to check arguments or attributed @@ -222,7 +232,7 @@ public: /// changed, this allows the analyzer core to skip the more expensive /// #checkRegionChanges when no checkers are tracking any state. bool wantsRegionChangeUpdate(ProgramStateRef St) const { return true; } - + /// \brief Called when the contents of one or more regions change. /// /// This can occur in many different ways: an explicit bind, a blanket @@ -246,7 +256,7 @@ public: /// #wantsRegionChangeUpdate returns \c true. /// /// check::RegionChanges - ProgramStateRef + ProgramStateRef checkRegionChanges(ProgramStateRef State, const InvalidatedSymbols *Invalidated, ArrayRef<const MemRegion *> ExplicitRegions, @@ -259,12 +269,12 @@ public: /// /// This notifies the checkers about pointer escape, which occurs whenever /// the analyzer cannot track the symbol any more. For example, as a - /// result of assigning a pointer into a global or when it's passed to a + /// result of assigning a pointer into a global or when it's passed to a /// function call the analyzer cannot model. - /// + /// /// \param State The state at the point of escape. /// \param Escaped The list of escaped symbols. - /// \param Call The corresponding CallEvent, if the symbols escape as + /// \param Call The corresponding CallEvent, if the symbols escape as /// parameters to the given call. /// \param Kind How the symbols have escaped. /// \returns Checkers can modify the state by returning a new state. @@ -285,7 +295,7 @@ public: PointerEscapeKind Kind) const { return State; } - + /// check::Event<ImplicitNullDerefEvent> void checkEvent(ImplicitNullDerefEvent Event) const {} diff --git a/lib/StaticAnalyzer/Checkers/Checkers.td b/lib/StaticAnalyzer/Checkers/Checkers.td index d1d6ac2..8133d29 100644 --- a/lib/StaticAnalyzer/Checkers/Checkers.td +++ b/lib/StaticAnalyzer/Checkers/Checkers.td @@ -13,6 +13,11 @@ include "clang/StaticAnalyzer/Checkers/CheckerBase.td" // Packages. //===----------------------------------------------------------------------===// +// The Alpha package is for checkers that have too many false positives to be +// turned on by default. The hierarchy under Alpha should be organized in the +// hierarchy checkers would have had if they were truly at the top level. +// (For example, a Cocoa-specific checker that is alpha should be in +// alpha.osx.cocoa). def Alpha : Package<"alpha">; def Core : Package<"core">; @@ -20,16 +25,33 @@ def CoreBuiltin : Package<"builtin">, InPackage<Core>; def CoreUninitialized : Package<"uninitialized">, InPackage<Core>; def CoreAlpha : Package<"core">, InPackage<Alpha>, Hidden; +// The OptIn package is for checkers that are not alpha and that would normally +// be on by default but where the driver does not have enough information to +// determine when they are applicable. For example, localizability checkers fit +// this criterion because the driver cannot determine whether a project is +// localized or not -- this is best determined at the IDE or build-system level. +// +// The checker hierarchy under OptIn should mirror that in Alpha: checkers +// should be organized as if they were at the top level. +// +// Note: OptIn is *not* intended for checkers that are too noisy to be on by +// default. Such checkers belong in the alpha package. +def OptIn : Package<"optin">; + +def Nullability : Package<"nullability">; + def Cplusplus : Package<"cplusplus">; def CplusplusAlpha : Package<"cplusplus">, InPackage<Alpha>, Hidden; def DeadCode : Package<"deadcode">; def DeadCodeAlpha : Package<"deadcode">, InPackage<Alpha>, Hidden; +def Performance : Package<"performance">, InPackage<OptIn>; + def Security : Package <"security">; def InsecureAPI : Package<"insecureAPI">, InPackage<Security>; def SecurityAlpha : Package<"security">, InPackage<Alpha>, Hidden; -def Taint : Package<"taint">, InPackage<SecurityAlpha>, Hidden; +def Taint : Package<"taint">, InPackage<SecurityAlpha>, Hidden; def Unix : Package<"unix">; def UnixAlpha : Package<"unix">, InPackage<Alpha>, Hidden; @@ -38,11 +60,18 @@ def CStringAlpha : Package<"cstring">, InPackage<UnixAlpha>, Hidden; def OSX : Package<"osx">; def OSXAlpha : Package<"osx">, InPackage<Alpha>, Hidden; +def OSXOptIn : Package<"osx">, InPackage<OptIn>; + def Cocoa : Package<"cocoa">, InPackage<OSX>; def CocoaAlpha : Package<"cocoa">, InPackage<OSXAlpha>, Hidden; +def CocoaOptIn : Package<"cocoa">, InPackage<OSXOptIn>; + def CoreFoundation : Package<"coreFoundation">, InPackage<OSX>; def Containers : Package<"containers">, InPackage<CoreFoundation>; +def LocalizabilityAlpha : Package<"localizability">, InPackage<CocoaAlpha>; +def LocalizabilityOptIn : Package<"localizability">, InPackage<CocoaOptIn>; + def LLVM : Package<"llvm">; def Debug : Package<"debug">; @@ -128,8 +157,36 @@ def TestAfterDivZeroChecker : Checker<"TestAfterDivZero">, HelpText<"Check for division by variable that is later compared against 0. Either the comparison is useless or there is division by zero.">, DescFile<"TestAfterDivZeroChecker.cpp">; +def DynamicTypeChecker : Checker<"DynamicTypeChecker">, + HelpText<"Check for cases where the dynamic and the static type of an object are unrelated.">, + DescFile<"DynamicTypeChecker.cpp">; + } // end "alpha.core" +let ParentPackage = Nullability in { + +def NullPassedToNonnullChecker : Checker<"NullPassedToNonnull">, + HelpText<"Warns when a null pointer is passed to a pointer which has a _Nonnull type.">, + DescFile<"NullabilityChecker.cpp">; + +def NullReturnedFromNonnullChecker : Checker<"NullReturnedFromNonnull">, + HelpText<"Warns when a null pointer is returned from a function that has _Nonnull return type.">, + DescFile<"NullabilityChecker.cpp">; + +def NullableDereferencedChecker : Checker<"NullableDereferenced">, + HelpText<"Warns when a nullable pointer is dereferenced.">, + DescFile<"NullabilityChecker.cpp">; + +def NullablePassedToNonnullChecker : Checker<"NullablePassedToNonnull">, + HelpText<"Warns when a nullable pointer is passed to a pointer which has a _Nonnull type.">, + DescFile<"NullabilityChecker.cpp">; + +def NullableReturnedFromNonnullChecker : Checker<"NullablePassedToNonnull">, + HelpText<"Warns when a nullable pointer is returned from a function that has _Nonnull return type.">, + DescFile<"NullabilityChecker.cpp">; + +} // end "nullability" + //===----------------------------------------------------------------------===// // Evaluate "builtin" functions. //===----------------------------------------------------------------------===// @@ -167,7 +224,7 @@ def UndefBranchChecker : Checker<"Branch">, def UndefCapturedBlockVarChecker : Checker<"CapturedBlockVariable">, HelpText<"Check for blocks that capture uninitialized values">, DescFile<"UndefCapturedBlockVarChecker.cpp">; - + def ReturnUndefChecker : Checker<"UndefReturn">, HelpText<"Check for uninitialized values being returned to the caller">, DescFile<"ReturnUndefChecker.cpp">; @@ -181,11 +238,11 @@ def ReturnUndefChecker : Checker<"UndefReturn">, let ParentPackage = Cplusplus in { def NewDeleteChecker : Checker<"NewDelete">, - HelpText<"Check for double-free and use-after-free problems. Traces memory managed by new/delete.">, + HelpText<"Check for double-free and use-after-free problems. Traces memory managed by new/delete.">, DescFile<"MallocChecker.cpp">; def NewDeleteLeaksChecker : Checker<"NewDeleteLeaks">, - HelpText<"Check for memory leaks. Traces memory managed by new/delete.">, + HelpText<"Check for memory leaks. Traces memory managed by new/delete.">, DescFile<"MallocChecker.cpp">; } // end: "cplusplus" @@ -193,7 +250,7 @@ def NewDeleteLeaksChecker : Checker<"NewDeleteLeaks">, let ParentPackage = CplusplusAlpha in { def VirtualCallChecker : Checker<"VirtualCall">, - HelpText<"Check virtual function calls during construction or destruction">, + HelpText<"Check virtual function calls during construction or destruction">, DescFile<"VirtualCallChecker.cpp">; } // end: "alpha.cplusplus" @@ -218,6 +275,18 @@ def UnreachableCodeChecker : Checker<"UnreachableCode">, } // end "alpha.deadcode" //===----------------------------------------------------------------------===// +// Performance checkers. +//===----------------------------------------------------------------------===// + +let ParentPackage = Performance in { + +def PaddingChecker : Checker<"Padding">, + HelpText<"Check for excessively padded structs.">, + DescFile<"PaddingChecker.cpp">; + +} // end: "padding" + +//===----------------------------------------------------------------------===// // Security checkers. //===----------------------------------------------------------------------===// @@ -257,7 +326,7 @@ let ParentPackage = SecurityAlpha in { def ArrayBoundChecker : Checker<"ArrayBound">, HelpText<"Warn about buffer overflows (older checker)">, - DescFile<"ArrayBoundChecker.cpp">; + DescFile<"ArrayBoundChecker.cpp">; def ArrayBoundCheckerV2 : Checker<"ArrayBoundV2">, HelpText<"Warn about buffer overflows (newer checker)">, @@ -298,7 +367,7 @@ def UnixAPIChecker : Checker<"API">, def MallocChecker: Checker<"Malloc">, HelpText<"Check for memory leaks, double free, and use-after-free problems. Traces memory managed by malloc()/free().">, DescFile<"MallocChecker.cpp">; - + def MallocSizeofChecker : Checker<"MallocSizeof">, HelpText<"Check for dubious malloc arguments involving sizeof">, DescFile<"MallocSizeofChecker.cpp">; @@ -306,7 +375,11 @@ def MallocSizeofChecker : Checker<"MallocSizeof">, def MismatchedDeallocatorChecker : Checker<"MismatchedDeallocator">, HelpText<"Check for mismatched deallocators.">, DescFile<"MallocChecker.cpp">; - + +def VforkChecker : Checker<"Vfork">, + HelpText<"Check for proper usage of vfork">, + DescFile<"VforkChecker.cpp">; + } // end "unix" let ParentPackage = UnixAlpha in { @@ -337,7 +410,7 @@ def CStringNullArg : Checker<"NullArg">, def CStringSyntaxChecker : Checker<"BadSizeArg">, HelpText<"Check the size argument passed into C string functions for common erroneous patterns">, - DescFile<"CStringSyntaxChecker.cpp">; + DescFile<"CStringSyntaxChecker.cpp">; } let ParentPackage = CStringAlpha in { @@ -428,6 +501,10 @@ def RetainCountChecker : Checker<"RetainCount">, HelpText<"Check for leaks and improper reference count management">, DescFile<"RetainCountChecker.cpp">; +def ObjCGenericsChecker : Checker<"ObjCGenerics">, + HelpText<"Check for type errors when using Objective-C generics">, + DescFile<"DynamicTypePropagation.cpp">; + } // end "osx.cocoa" let ParentPackage = CocoaAlpha in { @@ -477,8 +554,25 @@ def ObjCContainersASTChecker : Checker<"PointerSizedValues">, def ObjCContainersChecker : Checker<"OutOfBounds">, HelpText<"Checks for index out-of-bounds when using 'CFArray' API">, DescFile<"ObjCContainersChecker.cpp">; - + } + +let ParentPackage = LocalizabilityOptIn in { +def NonLocalizedStringChecker : Checker<"NonLocalizedStringChecker">, + HelpText<"Warns about uses of non-localized NSStrings passed to UI methods expecting localized NSStrings">, + DescFile<"LocalizationChecker.cpp">; + +def EmptyLocalizationContextChecker : Checker<"EmptyLocalizationContextChecker">, + HelpText<"Check that NSLocalizedString macros include a comment for context">, + DescFile<"LocalizationChecker.cpp">; +} + +let ParentPackage = LocalizabilityAlpha in { +def PluralMisuseChecker : Checker<"PluralMisuseChecker">, + HelpText<"Warns against using one vs. many plural pattern in code when generating localized strings.">, + DescFile<"LocalizationChecker.cpp">; +} + //===----------------------------------------------------------------------===// // Checkers for LLVM development. //===----------------------------------------------------------------------===// @@ -546,4 +640,8 @@ def ExplodedGraphViewer : Checker<"ViewExplodedGraph">, HelpText<"View Exploded Graphs using GraphViz">, DescFile<"DebugCheckers.cpp">; +def BugHashDumper : Checker<"DumpBugHash">, + HelpText<"Dump the bug hash for all statements.">, + DescFile<"DebugCheckers.cpp">; + } // end "debug" diff --git a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index 804e83c..3ad1996 100644 --- a/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -27,7 +27,7 @@ namespace { // enum value that represent the jail state enum Kind { NO_CHROOT, ROOT_CHANGED, JAIL_ENTERED }; - + bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } //bool isJailEntered(intptr_t k) { return k == JAIL_ENTERED; } @@ -50,7 +50,7 @@ public: static int x; return &x; } - + bool evalCall(const CallExpr *CE, CheckerContext &C) const; void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; @@ -87,8 +87,8 @@ bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); - - // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in + + // Once encouter a chroot(), set the enum value ROOT_CHANGED directly in // the GDM. state = Mgr.addGDM(state, ChrootChecker::getTag(), (void*) ROOT_CHANGED); C.addTransition(state); @@ -106,7 +106,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. const Expr *ArgExpr = CE->getArg(0); SVal ArgVal = state->getSVal(ArgExpr, C.getLocationContext()); - + if (const MemRegion *R = ArgVal.getAsRegion()) { R = R->StripCasts(); if (const StringRegion* StrRegion= dyn_cast<StringRegion>(R)) { @@ -135,12 +135,12 @@ void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { // Ingnore chroot and chdir. if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir) return; - + // If jail state is ROOT_CHANGED, generate BugReport. void *const* k = C.getState()->FindGDM(ChrootChecker::getTag()); if (k) if (isRootChanged((intptr_t) *k)) - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { if (!BT_BreakJail) BT_BreakJail.reset(new BuiltinBug( this, "Break out of jail", "No call of chdir(\"/\") immediately " diff --git a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index f4be5b3..f2a269a 100644 --- a/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -28,36 +28,36 @@ using namespace clang; using namespace ento; -namespace { - +namespace { + /// A simple visitor to record what VarDecls occur in EH-handling code. class EHCodeVisitor : public RecursiveASTVisitor<EHCodeVisitor> { public: bool inEH; llvm::DenseSet<const VarDecl *> &S; - + bool TraverseObjCAtFinallyStmt(ObjCAtFinallyStmt *S) { SaveAndRestore<bool> inFinally(inEH, true); return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtFinallyStmt(S); } - + bool TraverseObjCAtCatchStmt(ObjCAtCatchStmt *S) { SaveAndRestore<bool> inCatch(inEH, true); return ::RecursiveASTVisitor<EHCodeVisitor>::TraverseObjCAtCatchStmt(S); } - + bool TraverseCXXCatchStmt(CXXCatchStmt *S) { SaveAndRestore<bool> inCatch(inEH, true); return TraverseStmt(S->getHandlerBlock()); } - + bool VisitDeclRefExpr(DeclRefExpr *DR) { if (inEH) if (const VarDecl *D = dyn_cast<VarDecl>(DR->getDecl())) S.insert(D); return true; } - + EHCodeVisitor(llvm::DenseSet<const VarDecl *> &S) : inEH(false), S(S) {} }; @@ -70,9 +70,9 @@ class ReachableCode { public: ReachableCode(const CFG &cfg) : cfg(cfg), reachable(cfg.getNumBlockIDs(), false) {} - + void computeReachableBlocks(); - + bool isReachable(const CFGBlock *block) const { return reachable[block->getBlockID()]; } @@ -82,7 +82,7 @@ public: void ReachableCode::computeReachableBlocks() { if (!cfg.getNumBlockIDs()) return; - + SmallVector<const CFGBlock*, 10> worklist; worklist.push_back(&cfg.getEntry()); @@ -160,19 +160,19 @@ public: // to analyze that yet. return InEH->count(D); } - + void Report(const VarDecl *V, DeadStoreKind dsk, PathDiagnosticLocation L, SourceRange R) { if (Escaped.count(V)) return; - + // Compute reachable blocks within the CFG for trivial cases // where a bogus dead store can be reported because itself is unreachable. if (!reachableCode.get()) { reachableCode.reset(new ReachableCode(cfg)); reachableCode->computeReachableBlocks(); } - + if (!reachableCode->isReachable(currentBlock)) return; @@ -196,7 +196,7 @@ public: case Enclosing: // Don't report issues in this case, e.g.: "if (x = foo())", - // where 'x' is unused later. We have yet to see a case where + // where 'x' is unused later. We have yet to see a case where // this is a real bug. return; } @@ -259,7 +259,7 @@ public: const LiveVariables::LivenessValues &Live) override { currentBlock = block; - + // Skip statements in macros. if (S->getLocStart().isMacroID()) return; @@ -276,7 +276,7 @@ public: const Expr *RHS = LookThroughTransitiveAssignmentsAndCommaOperators(B->getRHS()); RHS = RHS->IgnoreParenCasts(); - + QualType T = VD->getType(); if (T->isPointerType() || T->isObjCObjectPointerType()) { if (RHS->isNullPointerConstant(Ctx, Expr::NPC_ValueDependentIsNull)) @@ -318,27 +318,27 @@ public: if (!V) continue; - - if (V->hasLocalStorage()) { + + if (V->hasLocalStorage()) { // Reference types confuse the dead stores checker. Skip them // for now. if (V->getType()->getAs<ReferenceType>()) return; - + if (const Expr *E = V->getInit()) { while (const ExprWithCleanups *exprClean = dyn_cast<ExprWithCleanups>(E)) E = exprClean->getSubExpr(); - + // Look through transitive assignments, e.g.: // int x = y = 0; E = LookThroughTransitiveAssignmentsAndCommaOperators(E); - + // Don't warn on C++ objects (yet) until we can show that their // constructors/destructors don't have side effects. if (isa<CXXConstructExpr>(E)) return; - + // A dead initialization is a variable that is dead after it // is initialized. We don't flag warnings for those variables // marked 'unused' or 'objc_precise_lifetime'. @@ -401,6 +401,11 @@ public: // Check for '&'. Any VarDecl whose address has been taken we treat as // escaped. // FIXME: What about references? + if (auto *LE = dyn_cast<LambdaExpr>(S)) { + findLambdaReferenceCaptures(LE); + return; + } + const UnaryOperator *U = dyn_cast<UnaryOperator>(S); if (!U) return; @@ -412,6 +417,28 @@ public: if (const VarDecl *VD = dyn_cast<VarDecl>(DR->getDecl())) Escaped.insert(VD); } + + // Treat local variables captured by reference in C++ lambdas as escaped. + void findLambdaReferenceCaptures(const LambdaExpr *LE) { + const CXXRecordDecl *LambdaClass = LE->getLambdaClass(); + llvm::DenseMap<const VarDecl *, FieldDecl *> CaptureFields; + FieldDecl *ThisCaptureField; + LambdaClass->getCaptureFields(CaptureFields, ThisCaptureField); + + for (const LambdaCapture &C : LE->captures()) { + if (!C.capturesVariable()) + continue; + + VarDecl *VD = C.getCapturedVar(); + const FieldDecl *FD = CaptureFields[VD]; + if (!FD) + continue; + + // If the capture field is a reference type, it is capture-by-reference. + if (FD->getType()->isReferenceType()) + Escaped.insert(VD); + } + } }; } // end anonymous namespace diff --git a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 51e7a3d..2eef168 100644 --- a/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -16,7 +16,10 @@ #include "clang/Analysis/Analyses/LiveVariables.h" #include "clang/Analysis/CallGraph.h" #include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/IssueHash.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "llvm/Support/Process.h" @@ -209,3 +212,36 @@ public: void ento::registerExplodedGraphViewer(CheckerManager &mgr) { mgr.registerChecker<ExplodedGraphViewer>(); } + +//===----------------------------------------------------------------------===// +// DumpBugHash +//===----------------------------------------------------------------------===// + +namespace { +class BugHashDumper : public Checker<check::PostStmt<Stmt>> { +public: + mutable std::unique_ptr<BugType> BT; + + void checkPostStmt(const Stmt *S, CheckerContext &C) const { + if (!BT) + BT.reset(new BugType(this, "Dump hash components", "debug")); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + const LangOptions &Opts = C.getLangOpts(); + const SourceManager &SM = C.getSourceManager(); + FullSourceLoc FL(S->getLocStart(), SM); + std::string HashContent = + GetIssueString(SM, FL, getCheckName().getName(), BT->getCategory(), + C.getLocationContext()->getDecl(), Opts); + + C.emitReport(llvm::make_unique<BugReport>(*BT, HashContent, N)); + } +}; +} + +void ento::registerBugHashDumper(CheckerManager &mgr) { + mgr.registerChecker<BugHashDumper>(); +} diff --git a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index 2ba7ea4..5dd2832 100644 --- a/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -14,10 +14,12 @@ #include "ClangSACheckers.h" #include "clang/AST/ExprObjC.h" +#include "clang/AST/ExprOpenMP.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 "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" #include "llvm/ADT/SmallString.h" #include "llvm/Support/raw_ostream.h" @@ -83,14 +85,14 @@ DereferenceChecker::AddDerefSource(raw_ostream &os, SourceLocation L = IV->getLocation(); Ranges.push_back(SourceRange(L, L)); break; - } + } } } void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, CheckerContext &C, bool IsBind) const { // Generate an error node. - ExplodedNode *N = C.generateSink(State); + ExplodedNode *N = C.generateErrorNode(State); if (!N) return; @@ -110,15 +112,11 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, S = expr->IgnoreParenLValueCasts(); if (IsBind) { - if (const BinaryOperator *BO = dyn_cast<BinaryOperator>(S)) { - if (BO->isAssignmentOp()) - S = BO->getRHS(); - } else if (const DeclStmt *DS = dyn_cast<DeclStmt>(S)) { - assert(DS->isSingleDecl() && "We process decls one by one"); - if (const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl())) - if (const Expr *Init = VD->getAnyInitializer()) - S = Init; - } + const VarDecl *VD; + const Expr *Init; + std::tie(VD, Init) = parseAssignment(S); + if (VD && Init) + S = Init; } switch (S->getStmtClass()) { @@ -130,6 +128,14 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, os << " results in a null pointer dereference"; break; } + case Stmt::OMPArraySectionExprClass: { + os << "Array access"; + const OMPArraySectionExpr *AE = cast<OMPArraySectionExpr>(S); + AddDerefSource(os, Ranges, AE->getBase()->IgnoreParenCasts(), + State.get(), N->getLocationContext()); + os << " results in a null pointer dereference"; + break; + } case Stmt::UnaryOperatorClass: { os << "Dereference of null pointer"; const UnaryOperator *U = cast<UnaryOperator>(S); @@ -159,7 +165,6 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, break; } - os.flush(); auto report = llvm::make_unique<BugReport>( *BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N); @@ -176,7 +181,7 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, CheckerContext &C) const { // Check for dereference of an undefined value. if (l.isUndef()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_undef) BT_undef.reset( new BuiltinBug(this, "Dereference of undefined pointer value")); @@ -211,8 +216,9 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, // Otherwise, we have the case where the location could either be // null or not-null. Record the error node as an "implicit" null // dereference. - if (ExplodedNode *N = C.generateSink(nullState)) { - ImplicitNullDerefEvent event = { l, isLoad, N, &C.getBugReporter() }; + if (ExplodedNode *N = C.generateSink(nullState, C.getPredecessor())) { + ImplicitNullDerefEvent event = {l, isLoad, N, &C.getBugReporter(), + /*IsDirectDereference=*/false}; dispatchEvent(event); } } @@ -248,9 +254,10 @@ void DereferenceChecker::checkBind(SVal L, SVal V, const Stmt *S, // At this point the value could be either null or non-null. // Record this as an "implicit" null dereference. - if (ExplodedNode *N = C.generateSink(StNull)) { - ImplicitNullDerefEvent event = { V, /*isLoad=*/true, N, - &C.getBugReporter() }; + if (ExplodedNode *N = C.generateSink(StNull, C.getPredecessor())) { + ImplicitNullDerefEvent event = {V, /*isLoad=*/true, N, + &C.getBugReporter(), + /*IsDirectDereference=*/false}; dispatchEvent(event); } } diff --git a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp index a71def2..ad478cb 100644 --- a/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp +++ b/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp @@ -41,13 +41,12 @@ namespace { /// Checks for the init, dealloc, and any other functions that might be allowed /// to perform direct instance variable assignment based on their name. static bool DefaultMethodFilter(const ObjCMethodDecl *M) { - if (M->getMethodFamily() == OMF_init || M->getMethodFamily() == OMF_dealloc || - M->getMethodFamily() == OMF_copy || - M->getMethodFamily() == OMF_mutableCopy || - M->getSelector().getNameForSlot(0).find("init") != StringRef::npos || - M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos) - return true; - return false; + return M->getMethodFamily() == OMF_init || + M->getMethodFamily() == OMF_dealloc || + M->getMethodFamily() == OMF_copy || + M->getMethodFamily() == OMF_mutableCopy || + M->getSelector().getNameForSlot(0).find("init") != StringRef::npos || + M->getSelector().getNameForSlot(0).find("Init") != StringRef::npos; } class DirectIvarAssignment : diff --git a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 79f9479b..5985023 100644 --- a/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -29,13 +29,13 @@ class DivZeroChecker : public Checker< check::PreStmt<BinaryOperator> > { CheckerContext &C) const ; public: void checkPreStmt(const BinaryOperator *B, CheckerContext &C) const; -}; +}; } // end anonymous namespace void DivZeroChecker::reportBug(const char *Msg, ProgramStateRef StateZero, CheckerContext &C) const { - if (ExplodedNode *N = C.generateSink(StateZero)) { + if (ExplodedNode *N = C.generateErrorNode(StateZero)) { if (!BT) BT.reset(new BuiltinBug(this, "Division by zero")); diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp new file mode 100644 index 0000000..7e0cb8e --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -0,0 +1,213 @@ +//== DynamicTypeChecker.cpp ------------------------------------ -*- C++ -*--=// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker looks for cases where the dynamic type of an object is unrelated +// to its static type. The type information utilized by this check is collected +// by the DynamicTypePropagation checker. This check does not report any type +// error for ObjC Generic types, in order to avoid duplicate erros from the +// ObjC Generics checker. This checker is not supposed to modify the program +// state, it is just the observer of the type information provided by other +// checkers. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.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/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" + +using namespace clang; +using namespace ento; + +namespace { +class DynamicTypeChecker : public Checker<check::PostStmt<ImplicitCastExpr>> { + mutable std::unique_ptr<BugType> BT; + void initBugType() const { + if (!BT) + BT.reset( + new BugType(this, "Dynamic and static type mismatch", "Type Error")); + } + + class DynamicTypeBugVisitor + : public BugReporterVisitorImpl<DynamicTypeBugVisitor> { + public: + DynamicTypeBugVisitor(const MemRegion *Reg) : Reg(Reg) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Reg); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + // The tracked region. + const MemRegion *Reg; + }; + + void reportTypeError(QualType DynamicType, QualType StaticType, + const MemRegion *Reg, const Stmt *ReportedNode, + CheckerContext &C) const; + +public: + void checkPostStmt(const ImplicitCastExpr *CE, CheckerContext &C) const; +}; +} + +void DynamicTypeChecker::reportTypeError(QualType DynamicType, + QualType StaticType, + const MemRegion *Reg, + const Stmt *ReportedNode, + CheckerContext &C) const { + initBugType(); + SmallString<192> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Object has a dynamic type '"; + QualType::print(DynamicType.getTypePtr(), Qualifiers(), OS, C.getLangOpts(), + llvm::Twine()); + OS << "' which is incompatible with static type '"; + QualType::print(StaticType.getTypePtr(), Qualifiers(), OS, C.getLangOpts(), + llvm::Twine()); + OS << "'"; + std::unique_ptr<BugReport> R( + new BugReport(*BT, OS.str(), C.generateNonFatalErrorNode())); + R->markInteresting(Reg); + R->addVisitor(llvm::make_unique<DynamicTypeBugVisitor>(Reg)); + R->addRange(ReportedNode->getSourceRange()); + C.emitReport(std::move(R)); +} + +PathDiagnosticPiece *DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( + const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef State = N->getState(); + ProgramStateRef StatePrev = PrevN->getState(); + + DynamicTypeInfo TrackedType = getDynamicTypeInfo(State, Reg); + DynamicTypeInfo TrackedTypePrev = getDynamicTypeInfo(StatePrev, Reg); + if (!TrackedType.isValid()) + return nullptr; + + if (TrackedTypePrev.isValid() && + TrackedTypePrev.getType() == TrackedType.getType()) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = nullptr; + ProgramPoint ProgLoc = N->getLocation(); + if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) { + S = SP->getStmt(); + } + + if (!S) + return nullptr; + + const LangOptions &LangOpts = BRC.getASTContext().getLangOpts(); + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Type '"; + QualType::print(TrackedType.getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "' is inferred from "; + + if (const auto *ExplicitCast = dyn_cast<ExplicitCastExpr>(S)) { + OS << "explicit cast (from '"; + QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(), + Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' to '"; + QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "')"; + } else if (const auto *ImplicitCast = dyn_cast<ImplicitCastExpr>(S)) { + OS << "implicit cast (from '"; + QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(), + Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' to '"; + QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "')"; + } else { + OS << "this context"; + } + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr); +} + +static bool hasDefinition(const ObjCObjectPointerType *ObjPtr) { + const ObjCInterfaceDecl *Decl = ObjPtr->getInterfaceDecl(); + if (!Decl) + return false; + + return Decl->getDefinition(); +} + +// TODO: consider checking explicit casts? +void DynamicTypeChecker::checkPostStmt(const ImplicitCastExpr *CE, + CheckerContext &C) const { + // TODO: C++ support. + if (CE->getCastKind() != CK_BitCast) + return; + + const MemRegion *Region = C.getSVal(CE).getAsRegion(); + if (!Region) + return; + + ProgramStateRef State = C.getState(); + DynamicTypeInfo DynTypeInfo = getDynamicTypeInfo(State, Region); + + if (!DynTypeInfo.isValid()) + return; + + QualType DynType = DynTypeInfo.getType(); + QualType StaticType = CE->getType(); + + const auto *DynObjCType = DynType->getAs<ObjCObjectPointerType>(); + const auto *StaticObjCType = StaticType->getAs<ObjCObjectPointerType>(); + + if (!DynObjCType || !StaticObjCType) + return; + + if (!hasDefinition(DynObjCType) || !hasDefinition(StaticObjCType)) + return; + + ASTContext &ASTCtxt = C.getASTContext(); + + // Strip kindeofness to correctly detect subtyping relationships. + DynObjCType = DynObjCType->stripObjCKindOfTypeAndQuals(ASTCtxt); + StaticObjCType = StaticObjCType->stripObjCKindOfTypeAndQuals(ASTCtxt); + + // Specialized objects are handled by the generics checker. + if (StaticObjCType->isSpecialized()) + return; + + if (ASTCtxt.canAssignObjCInterfaces(StaticObjCType, DynObjCType)) + return; + + if (DynTypeInfo.canBeASubClass() && + ASTCtxt.canAssignObjCInterfaces(DynObjCType, StaticObjCType)) + return; + + reportTypeError(DynType, StaticType, Region, CE, C); +} + +void ento::registerDynamicTypeChecker(CheckerManager &mgr) { + mgr.registerChecker<DynamicTypeChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index 43a2812..30f6298 100644 --- a/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -7,42 +7,139 @@ // //===----------------------------------------------------------------------===// // +// This file contains two checkers. One helps the static analyzer core to track +// types, the other does type inference on Obj-C generics and report type +// errors. +// +// Dynamic Type Propagation: // This checker defines the rules for dynamic type gathering and propagation. // +// Generics Checker for Objective-C: +// This checker tries to find type errors that the compiler is not able to catch +// due to the implicit conversions that were introduced for backward +// compatibility. +// //===----------------------------------------------------------------------===// #include "ClangSACheckers.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/Builtins.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/DynamicTypeMap.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; using namespace ento; +// ProgramState trait - The type inflation is tracked by DynamicTypeMap. This is +// an auxiliary map that tracks more information about generic types, because in +// some cases the most derived type is not the most informative one about the +// type parameters. This types that are stored for each symbol in this map must +// be specialized. +// TODO: In some case the type stored in this map is exactly the same that is +// stored in DynamicTypeMap. We should no store duplicated information in those +// cases. +REGISTER_MAP_WITH_PROGRAMSTATE(MostSpecializedTypeArgsMap, SymbolRef, + const ObjCObjectPointerType *) + namespace { class DynamicTypePropagation: public Checker< check::PreCall, check::PostCall, - check::PostStmt<ImplicitCastExpr>, - check::PostStmt<CXXNewExpr> > { + check::DeadSymbols, + check::PostStmt<CastExpr>, + check::PostStmt<CXXNewExpr>, + check::PreObjCMessage, + check::PostObjCMessage > { const ObjCObjectType *getObjectTypeForAllocAndNew(const ObjCMessageExpr *MsgE, CheckerContext &C) const; /// \brief Return a better dynamic type if one can be derived from the cast. const ObjCObjectPointerType *getBetterObjCType(const Expr *CastE, CheckerContext &C) const; + + ExplodedNode *dynamicTypePropagationOnCasts(const CastExpr *CE, + ProgramStateRef &State, + CheckerContext &C) const; + + mutable std::unique_ptr<BugType> ObjCGenericsBugType; + void initBugType() const { + if (!ObjCGenericsBugType) + ObjCGenericsBugType.reset( + new BugType(this, "Generics", categories::CoreFoundationObjectiveC)); + } + + class GenericsBugVisitor : public BugReporterVisitorImpl<GenericsBugVisitor> { + public: + GenericsBugVisitor(SymbolRef S) : Sym(S) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + // The tracked symbol. + SymbolRef Sym; + }; + + void reportGenericsBug(const ObjCObjectPointerType *From, + const ObjCObjectPointerType *To, ExplodedNode *N, + SymbolRef Sym, CheckerContext &C, + const Stmt *ReportedNode = nullptr) const; public: void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - void checkPostStmt(const ImplicitCastExpr *CastE, CheckerContext &C) const; + void checkPostStmt(const CastExpr *CastE, CheckerContext &C) const; void checkPostStmt(const CXXNewExpr *NewE, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + void checkPreObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + + /// This value is set to true, when the Generics checker is turned on. + DefaultBool CheckGenerics; }; } +void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + DynamicTypeMapImpl TypeMap = State->get<DynamicTypeMap>(); + for (DynamicTypeMapImpl::iterator I = TypeMap.begin(), E = TypeMap.end(); + I != E; ++I) { + if (!SR.isLiveRegion(I->first)) { + State = State->remove<DynamicTypeMap>(I->first); + } + } + + if (!SR.hasDeadSymbols()) { + C.addTransition(State); + return; + } + + MostSpecializedTypeArgsMapTy TyArgMap = + State->get<MostSpecializedTypeArgsMap>(); + for (MostSpecializedTypeArgsMapTy::iterator I = TyArgMap.begin(), + E = TyArgMap.end(); + I != E; ++I) { + if (SR.isDead(I->first)) { + State = State->remove<MostSpecializedTypeArgsMap>(I->first); + } + } + + C.addTransition(State); +} + static void recordFixedType(const MemRegion *Region, const CXXMethodDecl *MD, CheckerContext &C) { assert(Region); @@ -52,7 +149,7 @@ static void recordFixedType(const MemRegion *Region, const CXXMethodDecl *MD, QualType Ty = Ctx.getPointerType(Ctx.getRecordType(MD->getParent())); ProgramStateRef State = C.getState(); - State = State->setDynamicTypeInfo(Region, Ty, /*CanBeSubclass=*/false); + State = setDynamicTypeInfo(State, Region, Ty, /*CanBeSubclass=*/false); C.addTransition(State); return; } @@ -113,7 +210,7 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, ProgramStateRef State = C.getState(); const ObjCMethodDecl *D = Msg->getDecl(); - + if (D && D->hasRelatedResultType()) { switch (Msg->getMethodFamily()) { default: @@ -131,7 +228,7 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, return; QualType DynResTy = C.getASTContext().getObjCObjectPointerType(QualType(ObjTy, 0)); - C.addTransition(State->setDynamicTypeInfo(RetReg, DynResTy, false)); + C.addTransition(setDynamicTypeInfo(State, RetReg, DynResTy, false)); break; } case OMF_init: { @@ -140,8 +237,8 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, const MemRegion *RecReg = Msg->getReceiverSVal().getAsRegion(); if (!RecReg) return; - DynamicTypeInfo RecDynType = State->getDynamicTypeInfo(RecReg); - C.addTransition(State->setDynamicTypeInfo(RetReg, RecDynType)); + DynamicTypeInfo RecDynType = getDynamicTypeInfo(State, RecReg); + C.addTransition(setDynamicTypeInfo(State, RetReg, RecDynType)); break; } } @@ -173,23 +270,25 @@ void DynamicTypePropagation::checkPostCall(const CallEvent &Call, } } -void DynamicTypePropagation::checkPostStmt(const ImplicitCastExpr *CastE, - CheckerContext &C) const { - // We only track dynamic type info for regions. - const MemRegion *ToR = C.getSVal(CastE).getAsRegion(); +/// TODO: Handle explicit casts. +/// Handle C++ casts. +/// +/// Precondition: the cast is between ObjCObjectPointers. +ExplodedNode *DynamicTypePropagation::dynamicTypePropagationOnCasts( + const CastExpr *CE, ProgramStateRef &State, CheckerContext &C) const { + // We only track type info for regions. + const MemRegion *ToR = C.getSVal(CE).getAsRegion(); if (!ToR) - return; + return C.getPredecessor(); + + if (isa<ExplicitCastExpr>(CE)) + return C.getPredecessor(); - switch (CastE->getCastKind()) { - default: - break; - case CK_BitCast: - // Only handle ObjCObjects for now. - if (const Type *NewTy = getBetterObjCType(CastE, C)) - C.addTransition(C.getState()->setDynamicTypeInfo(ToR, QualType(NewTy,0))); - break; + if (const Type *NewTy = getBetterObjCType(CE, C)) { + State = setDynamicTypeInfo(State, ToR, QualType(NewTy, 0)); + return C.addTransition(State); } - return; + return C.getPredecessor(); } void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE, @@ -201,9 +300,9 @@ void DynamicTypePropagation::checkPostStmt(const CXXNewExpr *NewE, const MemRegion *MR = C.getSVal(NewE).getAsRegion(); if (!MR) return; - - C.addTransition(C.getState()->setDynamicTypeInfo(MR, NewE->getType(), - /*CanBeSubclass=*/false)); + + C.addTransition(setDynamicTypeInfo(C.getState(), MR, NewE->getType(), + /*CanBeSubclass=*/false)); } const ObjCObjectType * @@ -254,7 +353,7 @@ DynamicTypePropagation::getBetterObjCType(const Expr *CastE, CastE->getType()->getAs<ObjCObjectPointerType>(); if (!NewTy) return nullptr; - QualType OldDTy = C.getState()->getDynamicTypeInfo(ToR).getType(); + QualType OldDTy = getDynamicTypeInfo(C.getState(), ToR).getType(); if (OldDTy.isNull()) { return NewTy; } @@ -276,6 +375,566 @@ DynamicTypePropagation::getBetterObjCType(const Expr *CastE, return nullptr; } +static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl( + const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, + const ObjCObjectPointerType *MostInformativeCandidate, ASTContext &C) { + // Checking if from and to are the same classes modulo specialization. + if (From->getInterfaceDecl()->getCanonicalDecl() == + To->getInterfaceDecl()->getCanonicalDecl()) { + if (To->isSpecialized()) { + assert(MostInformativeCandidate->isSpecialized()); + return MostInformativeCandidate; + } + return From; + } + const auto *SuperOfTo = + To->getObjectType()->getSuperClassType()->getAs<ObjCObjectType>(); + assert(SuperOfTo); + QualType SuperPtrOfToQual = + C.getObjCObjectPointerType(QualType(SuperOfTo, 0)); + const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs<ObjCObjectPointerType>(); + if (To->isUnspecialized()) + return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo, + C); + else + return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, + MostInformativeCandidate, C); +} + +/// A downcast may loose specialization information. E. g.: +/// MutableMap<T, U> : Map +/// The downcast to MutableMap looses the information about the types of the +/// Map (due to the type parameters are not being forwarded to Map), and in +/// general there is no way to recover that information from the +/// declaration. In order to have to most information, lets find the most +/// derived type that has all the type parameters forwarded. +/// +/// Get the a subclass of \p From (which has a lower bound \p To) that do not +/// loose information about type parameters. \p To has to be a subclass of +/// \p From. From has to be specialized. +static const ObjCObjectPointerType * +getMostInformativeDerivedClass(const ObjCObjectPointerType *From, + const ObjCObjectPointerType *To, ASTContext &C) { + return getMostInformativeDerivedClassImpl(From, To, To, C); +} + +/// Inputs: +/// \param StaticLowerBound Static lower bound for a symbol. The dynamic lower +/// bound might be the subclass of this type. +/// \param StaticUpperBound A static upper bound for a symbol. +/// \p StaticLowerBound expected to be the subclass of \p StaticUpperBound. +/// \param Current The type that was inferred for a symbol in a previous +/// context. Might be null when this is the first time that inference happens. +/// Precondition: +/// \p StaticLowerBound or \p StaticUpperBound is specialized. If \p Current +/// is not null, it is specialized. +/// Possible cases: +/// (1) The \p Current is null and \p StaticLowerBound <: \p StaticUpperBound +/// (2) \p StaticLowerBound <: \p Current <: \p StaticUpperBound +/// (3) \p Current <: \p StaticLowerBound <: \p StaticUpperBound +/// (4) \p StaticLowerBound <: \p StaticUpperBound <: \p Current +/// Effect: +/// Use getMostInformativeDerivedClass with the upper and lower bound of the +/// set {\p StaticLowerBound, \p Current, \p StaticUpperBound}. The computed +/// lower bound must be specialized. If the result differs from \p Current or +/// \p Current is null, store the result. +static bool +storeWhenMoreInformative(ProgramStateRef &State, SymbolRef Sym, + const ObjCObjectPointerType *const *Current, + const ObjCObjectPointerType *StaticLowerBound, + const ObjCObjectPointerType *StaticUpperBound, + ASTContext &C) { + // Precondition + assert(StaticUpperBound->isSpecialized() || + StaticLowerBound->isSpecialized()); + assert(!Current || (*Current)->isSpecialized()); + + // Case (1) + if (!Current) { + if (StaticUpperBound->isUnspecialized()) { + State = State->set<MostSpecializedTypeArgsMap>(Sym, StaticLowerBound); + return true; + } + // Upper bound is specialized. + const ObjCObjectPointerType *WithMostInfo = + getMostInformativeDerivedClass(StaticUpperBound, StaticLowerBound, C); + State = State->set<MostSpecializedTypeArgsMap>(Sym, WithMostInfo); + return true; + } + + // Case (3) + if (C.canAssignObjCInterfaces(StaticLowerBound, *Current)) { + return false; + } + + // Case (4) + if (C.canAssignObjCInterfaces(*Current, StaticUpperBound)) { + // The type arguments might not be forwarded at any point of inheritance. + const ObjCObjectPointerType *WithMostInfo = + getMostInformativeDerivedClass(*Current, StaticUpperBound, C); + WithMostInfo = + getMostInformativeDerivedClass(WithMostInfo, StaticLowerBound, C); + if (WithMostInfo == *Current) + return false; + State = State->set<MostSpecializedTypeArgsMap>(Sym, WithMostInfo); + return true; + } + + // Case (2) + const ObjCObjectPointerType *WithMostInfo = + getMostInformativeDerivedClass(*Current, StaticLowerBound, C); + if (WithMostInfo != *Current) { + State = State->set<MostSpecializedTypeArgsMap>(Sym, WithMostInfo); + return true; + } + + return false; +} + +/// Type inference based on static type information that is available for the +/// cast and the tracked type information for the given symbol. When the tracked +/// symbol and the destination type of the cast are unrelated, report an error. +void DynamicTypePropagation::checkPostStmt(const CastExpr *CE, + CheckerContext &C) const { + if (CE->getCastKind() != CK_BitCast) + return; + + QualType OriginType = CE->getSubExpr()->getType(); + QualType DestType = CE->getType(); + + const auto *OrigObjectPtrType = OriginType->getAs<ObjCObjectPointerType>(); + const auto *DestObjectPtrType = DestType->getAs<ObjCObjectPointerType>(); + + if (!OrigObjectPtrType || !DestObjectPtrType) + return; + + ProgramStateRef State = C.getState(); + ExplodedNode *AfterTypeProp = dynamicTypePropagationOnCasts(CE, State, C); + + ASTContext &ASTCtxt = C.getASTContext(); + + // This checker detects the subtyping relationships using the assignment + // rules. In order to be able to do this the kindofness must be stripped + // first. The checker treats every type as kindof type anyways: when the + // tracked type is the subtype of the static type it tries to look up the + // methods in the tracked type first. + OrigObjectPtrType = OrigObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt); + DestObjectPtrType = DestObjectPtrType->stripObjCKindOfTypeAndQuals(ASTCtxt); + + // TODO: erase tracked information when there is a cast to unrelated type + // and everything is unspecialized statically. + if (OrigObjectPtrType->isUnspecialized() && + DestObjectPtrType->isUnspecialized()) + return; + + SymbolRef Sym = State->getSVal(CE, C.getLocationContext()).getAsSymbol(); + if (!Sym) + return; + + // Check which assignments are legal. + bool OrigToDest = + ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, OrigObjectPtrType); + bool DestToOrig = + ASTCtxt.canAssignObjCInterfaces(OrigObjectPtrType, DestObjectPtrType); + const ObjCObjectPointerType *const *TrackedType = + State->get<MostSpecializedTypeArgsMap>(Sym); + + // Downcasts and upcasts handled in an uniform way regardless of being + // explicit. Explicit casts however can happen between mismatched types. + if (isa<ExplicitCastExpr>(CE) && !OrigToDest && !DestToOrig) { + // Mismatched types. If the DestType specialized, store it. Forget the + // tracked type otherwise. + if (DestObjectPtrType->isSpecialized()) { + State = State->set<MostSpecializedTypeArgsMap>(Sym, DestObjectPtrType); + C.addTransition(State, AfterTypeProp); + } else if (TrackedType) { + State = State->remove<MostSpecializedTypeArgsMap>(Sym); + C.addTransition(State, AfterTypeProp); + } + return; + } + + // The tracked type should be the sub or super class of the static destination + // type. When an (implicit) upcast or a downcast happens according to static + // types, and there is no subtyping relationship between the tracked and the + // static destination types, it indicates an error. + if (TrackedType && + !ASTCtxt.canAssignObjCInterfaces(DestObjectPtrType, *TrackedType) && + !ASTCtxt.canAssignObjCInterfaces(*TrackedType, DestObjectPtrType)) { + static CheckerProgramPointTag IllegalConv(this, "IllegalConversion"); + ExplodedNode *N = C.addTransition(State, AfterTypeProp, &IllegalConv); + reportGenericsBug(*TrackedType, DestObjectPtrType, N, Sym, C); + return; + } + + // Handle downcasts and upcasts. + + const ObjCObjectPointerType *LowerBound = DestObjectPtrType; + const ObjCObjectPointerType *UpperBound = OrigObjectPtrType; + if (OrigToDest && !DestToOrig) + std::swap(LowerBound, UpperBound); + + // The id type is not a real bound. Eliminate it. + LowerBound = LowerBound->isObjCIdType() ? UpperBound : LowerBound; + UpperBound = UpperBound->isObjCIdType() ? LowerBound : UpperBound; + + if (storeWhenMoreInformative(State, Sym, TrackedType, LowerBound, UpperBound, + ASTCtxt)) { + C.addTransition(State, AfterTypeProp); + } +} + +static const Expr *stripCastsAndSugar(const Expr *E) { + E = E->IgnoreParenImpCasts(); + if (const PseudoObjectExpr *POE = dyn_cast<PseudoObjectExpr>(E)) + E = POE->getSyntacticForm()->IgnoreParenImpCasts(); + if (const OpaqueValueExpr *OVE = dyn_cast<OpaqueValueExpr>(E)) + E = OVE->getSourceExpr()->IgnoreParenImpCasts(); + return E; +} + +static bool isObjCTypeParamDependent(QualType Type) { + // It is illegal to typedef parameterized types inside an interface. Therfore + // an Objective-C type can only be dependent on a type parameter when the type + // parameter structurally present in the type itself. + class IsObjCTypeParamDependentTypeVisitor + : public RecursiveASTVisitor<IsObjCTypeParamDependentTypeVisitor> { + public: + IsObjCTypeParamDependentTypeVisitor() : Result(false) {} + bool VisitTypedefType(const TypedefType *Type) { + if (isa<ObjCTypeParamDecl>(Type->getDecl())) { + Result = true; + return false; + } + return true; + } + + bool Result; + }; + + IsObjCTypeParamDependentTypeVisitor Visitor; + Visitor.TraverseType(Type); + return Visitor.Result; +} + +/// A method might not be available in the interface indicated by the static +/// type. However it might be available in the tracked type. In order to +/// properly substitute the type parameters we need the declaration context of +/// the method. The more specialized the enclosing class of the method is, the +/// more likely that the parameter substitution will be successful. +static const ObjCMethodDecl * +findMethodDecl(const ObjCMessageExpr *MessageExpr, + const ObjCObjectPointerType *TrackedType, ASTContext &ASTCtxt) { + const ObjCMethodDecl *Method = nullptr; + + QualType ReceiverType = MessageExpr->getReceiverType(); + const auto *ReceiverObjectPtrType = + ReceiverType->getAs<ObjCObjectPointerType>(); + + // Do this "devirtualization" on instance and class methods only. Trust the + // static type on super and super class calls. + if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Instance || + MessageExpr->getReceiverKind() == ObjCMessageExpr::Class) { + // When the receiver type is id, Class, or some super class of the tracked + // type, look up the method in the tracked type, not in the receiver type. + // This way we preserve more information. + if (ReceiverType->isObjCIdType() || ReceiverType->isObjCClassType() || + ASTCtxt.canAssignObjCInterfaces(ReceiverObjectPtrType, TrackedType)) { + const ObjCInterfaceDecl *InterfaceDecl = TrackedType->getInterfaceDecl(); + // The method might not be found. + Selector Sel = MessageExpr->getSelector(); + Method = InterfaceDecl->lookupInstanceMethod(Sel); + if (!Method) + Method = InterfaceDecl->lookupClassMethod(Sel); + } + } + + // Fallback to statick method lookup when the one based on the tracked type + // failed. + return Method ? Method : MessageExpr->getMethodDecl(); +} + +/// Get the returned ObjCObjectPointerType by a method based on the tracked type +/// information, or null pointer when the returned type is not an +/// ObjCObjectPointerType. +static QualType getReturnTypeForMethod( + const ObjCMethodDecl *Method, ArrayRef<QualType> TypeArgs, + const ObjCObjectPointerType *SelfType, ASTContext &C) { + QualType StaticResultType = Method->getReturnType(); + + // Is the return type declared as instance type? + if (StaticResultType == C.getObjCInstanceType()) + return QualType(SelfType, 0); + + // Check whether the result type depends on a type parameter. + if (!isObjCTypeParamDependent(StaticResultType)) + return QualType(); + + QualType ResultType = StaticResultType.substObjCTypeArgs( + C, TypeArgs, ObjCSubstitutionContext::Result); + + return ResultType; +} + +/// When the receiver has a tracked type, use that type to validate the +/// argumments of the message expression and the return value. +void DynamicTypePropagation::checkPreObjCMessage(const ObjCMethodCall &M, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SymbolRef Sym = M.getReceiverSVal().getAsSymbol(); + if (!Sym) + return; + + const ObjCObjectPointerType *const *TrackedType = + State->get<MostSpecializedTypeArgsMap>(Sym); + if (!TrackedType) + return; + + // Get the type arguments from tracked type and substitute type arguments + // before do the semantic check. + + ASTContext &ASTCtxt = C.getASTContext(); + const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); + const ObjCMethodDecl *Method = + findMethodDecl(MessageExpr, *TrackedType, ASTCtxt); + + // It is possible to call non-existent methods in Obj-C. + if (!Method) + return; + + Optional<ArrayRef<QualType>> TypeArgs = + (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); + // This case might happen when there is an unspecialized override of a + // specialized method. + if (!TypeArgs) + return; + + for (unsigned i = 0; i < Method->param_size(); i++) { + const Expr *Arg = MessageExpr->getArg(i); + const ParmVarDecl *Param = Method->parameters()[i]; + + QualType OrigParamType = Param->getType(); + if (!isObjCTypeParamDependent(OrigParamType)) + continue; + + QualType ParamType = OrigParamType.substObjCTypeArgs( + ASTCtxt, *TypeArgs, ObjCSubstitutionContext::Parameter); + // Check if it can be assigned + const auto *ParamObjectPtrType = ParamType->getAs<ObjCObjectPointerType>(); + const auto *ArgObjectPtrType = + stripCastsAndSugar(Arg)->getType()->getAs<ObjCObjectPointerType>(); + if (!ParamObjectPtrType || !ArgObjectPtrType) + continue; + + // Check if we have more concrete tracked type that is not a super type of + // the static argument type. + SVal ArgSVal = M.getArgSVal(i); + SymbolRef ArgSym = ArgSVal.getAsSymbol(); + if (ArgSym) { + const ObjCObjectPointerType *const *TrackedArgType = + State->get<MostSpecializedTypeArgsMap>(ArgSym); + if (TrackedArgType && + ASTCtxt.canAssignObjCInterfaces(ArgObjectPtrType, *TrackedArgType)) { + ArgObjectPtrType = *TrackedArgType; + } + } + + // Warn when argument is incompatible with the parameter. + if (!ASTCtxt.canAssignObjCInterfaces(ParamObjectPtrType, + ArgObjectPtrType)) { + static CheckerProgramPointTag Tag(this, "ArgTypeMismatch"); + ExplodedNode *N = C.addTransition(State, &Tag); + reportGenericsBug(ArgObjectPtrType, ParamObjectPtrType, N, Sym, C, Arg); + return; + } + } +} + +/// This callback is used to infer the types for Class variables. This info is +/// used later to validate messages that sent to classes. Class variables are +/// initialized with by invoking the 'class' method on a class. +/// This method is also used to infer the type information for the return +/// types. +// TODO: right now it only tracks generic types. Extend this to track every +// type in the DynamicTypeMap and diagnose type errors! +void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, + CheckerContext &C) const { + const ObjCMessageExpr *MessageExpr = M.getOriginExpr(); + + SymbolRef RetSym = M.getReturnValue().getAsSymbol(); + if (!RetSym) + return; + + Selector Sel = MessageExpr->getSelector(); + ProgramStateRef State = C.getState(); + // Inference for class variables. + // We are only interested in cases where the class method is invoked on a + // class. This method is provided by the runtime and available on all classes. + if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && + Sel.getAsString() == "class") { + + QualType ReceiverType = MessageExpr->getClassReceiver(); + const auto *ReceiverClassType = ReceiverType->getAs<ObjCObjectType>(); + QualType ReceiverClassPointerType = + C.getASTContext().getObjCObjectPointerType( + QualType(ReceiverClassType, 0)); + + if (!ReceiverClassType->isSpecialized()) + return; + const auto *InferredType = + ReceiverClassPointerType->getAs<ObjCObjectPointerType>(); + assert(InferredType); + + State = State->set<MostSpecializedTypeArgsMap>(RetSym, InferredType); + C.addTransition(State); + return; + } + + // Tracking for return types. + SymbolRef RecSym = M.getReceiverSVal().getAsSymbol(); + if (!RecSym) + return; + + const ObjCObjectPointerType *const *TrackedType = + State->get<MostSpecializedTypeArgsMap>(RecSym); + if (!TrackedType) + return; + + ASTContext &ASTCtxt = C.getASTContext(); + const ObjCMethodDecl *Method = + findMethodDecl(MessageExpr, *TrackedType, ASTCtxt); + if (!Method) + return; + + Optional<ArrayRef<QualType>> TypeArgs = + (*TrackedType)->getObjCSubstitutions(Method->getDeclContext()); + if (!TypeArgs) + return; + + QualType ResultType = + getReturnTypeForMethod(Method, *TypeArgs, *TrackedType, ASTCtxt); + // The static type is the same as the deduced type. + if (ResultType.isNull()) + return; + + const MemRegion *RetRegion = M.getReturnValue().getAsRegion(); + ExplodedNode *Pred = C.getPredecessor(); + // When there is an entry available for the return symbol in DynamicTypeMap, + // the call was inlined, and the information in the DynamicTypeMap is should + // be precise. + if (RetRegion && !State->get<DynamicTypeMap>(RetRegion)) { + // TODO: we have duplicated information in DynamicTypeMap and + // MostSpecializedTypeArgsMap. We should only store anything in the later if + // the stored data differs from the one stored in the former. + State = setDynamicTypeInfo(State, RetRegion, ResultType, + /*CanBeSubclass=*/true); + Pred = C.addTransition(State); + } + + const auto *ResultPtrType = ResultType->getAs<ObjCObjectPointerType>(); + + if (!ResultPtrType || ResultPtrType->isUnspecialized()) + return; + + // When the result is a specialized type and it is not tracked yet, track it + // for the result symbol. + if (!State->get<MostSpecializedTypeArgsMap>(RetSym)) { + State = State->set<MostSpecializedTypeArgsMap>(RetSym, ResultPtrType); + C.addTransition(State, Pred); + } +} + +void DynamicTypePropagation::reportGenericsBug( + const ObjCObjectPointerType *From, const ObjCObjectPointerType *To, + ExplodedNode *N, SymbolRef Sym, CheckerContext &C, + const Stmt *ReportedNode) const { + if (!CheckGenerics) + return; + + initBugType(); + SmallString<192> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Conversion from value of type '"; + QualType::print(From, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); + OS << "' to incompatible type '"; + QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); + OS << "'"; + std::unique_ptr<BugReport> R( + new BugReport(*ObjCGenericsBugType, OS.str(), N)); + R->markInteresting(Sym); + R->addVisitor(llvm::make_unique<GenericsBugVisitor>(Sym)); + if (ReportedNode) + R->addRange(ReportedNode->getSourceRange()); + C.emitReport(std::move(R)); +} + +PathDiagnosticPiece *DynamicTypePropagation::GenericsBugVisitor::VisitNode( + const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef state = N->getState(); + ProgramStateRef statePrev = PrevN->getState(); + + const ObjCObjectPointerType *const *TrackedType = + state->get<MostSpecializedTypeArgsMap>(Sym); + const ObjCObjectPointerType *const *TrackedTypePrev = + statePrev->get<MostSpecializedTypeArgsMap>(Sym); + if (!TrackedType) + return nullptr; + + if (TrackedTypePrev && *TrackedTypePrev == *TrackedType) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = nullptr; + ProgramPoint ProgLoc = N->getLocation(); + if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) { + S = SP->getStmt(); + } + + if (!S) + return nullptr; + + const LangOptions &LangOpts = BRC.getASTContext().getLangOpts(); + + SmallString<256> Buf; + llvm::raw_svector_ostream OS(Buf); + OS << "Type '"; + QualType::print(*TrackedType, Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' is inferred from "; + + if (const auto *ExplicitCast = dyn_cast<ExplicitCastExpr>(S)) { + OS << "explicit cast (from '"; + QualType::print(ExplicitCast->getSubExpr()->getType().getTypePtr(), + Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' to '"; + QualType::print(ExplicitCast->getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "')"; + } else if (const auto *ImplicitCast = dyn_cast<ImplicitCastExpr>(S)) { + OS << "implicit cast (from '"; + QualType::print(ImplicitCast->getSubExpr()->getType().getTypePtr(), + Qualifiers(), OS, LangOpts, llvm::Twine()); + OS << "' to '"; + QualType::print(ImplicitCast->getType().getTypePtr(), Qualifiers(), OS, + LangOpts, llvm::Twine()); + OS << "')"; + } else { + OS << "this context"; + } + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, OS.str(), true, nullptr); +} + +/// Register checkers. +void ento::registerObjCGenericsChecker(CheckerManager &mgr) { + DynamicTypePropagation *checker = + mgr.registerChecker<DynamicTypePropagation>(); + checker->CheckGenerics = true; +} + void ento::registerDynamicTypePropagation(CheckerManager &mgr) { mgr.registerChecker<DynamicTypePropagation>(); } diff --git a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 7dc0a87..8f6c20a 100644 --- a/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -17,22 +17,26 @@ using namespace clang; using namespace ento; namespace { -class ExprInspectionChecker : public Checker< eval::Call > { +class ExprInspectionChecker : public Checker<eval::Call, check::DeadSymbols> { mutable std::unique_ptr<BugType> BT; void analyzerEval(const CallExpr *CE, CheckerContext &C) const; void analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const; void analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const; void analyzerCrash(const CallExpr *CE, CheckerContext &C) const; + void analyzerWarnOnDeadSymbol(const CallExpr *CE, CheckerContext &C) const; typedef void (ExprInspectionChecker::*FnCheck)(const CallExpr *, CheckerContext &C) const; public: bool evalCall(const CallExpr *CE, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; }; } +REGISTER_SET_WITH_PROGRAMSTATE(MarkedSymbols, const void *) + bool ExprInspectionChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { // These checks should have no effect on the surrounding environment @@ -42,7 +46,10 @@ bool ExprInspectionChecker::evalCall(const CallExpr *CE, .Case("clang_analyzer_checkInlined", &ExprInspectionChecker::analyzerCheckInlined) .Case("clang_analyzer_crash", &ExprInspectionChecker::analyzerCrash) - .Case("clang_analyzer_warnIfReached", &ExprInspectionChecker::analyzerWarnIfReached) + .Case("clang_analyzer_warnIfReached", + &ExprInspectionChecker::analyzerWarnIfReached) + .Case("clang_analyzer_warnOnDeadSymbol", + &ExprInspectionChecker::analyzerWarnOnDeadSymbol) .Default(nullptr); if (!Handler) @@ -86,8 +93,7 @@ static const char *getArgumentValueString(const CallExpr *CE, void ExprInspectionChecker::analyzerEval(const CallExpr *CE, CheckerContext &C) const { - ExplodedNode *N = C.getPredecessor(); - const LocationContext *LC = N->getLocationContext(); + const LocationContext *LC = C.getPredecessor()->getLocationContext(); // A specific instantiation of an inlined function may have more constrained // values than can generally be assumed. Skip the check. @@ -97,24 +103,28 @@ void ExprInspectionChecker::analyzerEval(const CallExpr *CE, if (!BT) BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; C.emitReport( llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N)); } void ExprInspectionChecker::analyzerWarnIfReached(const CallExpr *CE, CheckerContext &C) const { - ExplodedNode *N = C.getPredecessor(); if (!BT) BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; C.emitReport(llvm::make_unique<BugReport>(*BT, "REACHABLE", N)); } void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE, CheckerContext &C) const { - ExplodedNode *N = C.getPredecessor(); - const LocationContext *LC = N->getLocationContext(); + const LocationContext *LC = C.getPredecessor()->getLocationContext(); // An inlined function could conceivably also be analyzed as a top-level // function. We ignore this case and only emit a message (TRUE or FALSE) @@ -127,10 +137,48 @@ void ExprInspectionChecker::analyzerCheckInlined(const CallExpr *CE, if (!BT) BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; C.emitReport( llvm::make_unique<BugReport>(*BT, getArgumentValueString(CE, C), N)); } +void ExprInspectionChecker::analyzerWarnOnDeadSymbol(const CallExpr *CE, + CheckerContext &C) const { + if (CE->getNumArgs() == 0) + return; + SVal Val = C.getSVal(CE->getArg(0)); + SymbolRef Sym = Val.getAsSymbol(); + if (!Sym) + return; + + ProgramStateRef State = C.getState(); + State = State->add<MarkedSymbols>(Sym); + C.addTransition(State); +} + +void ExprInspectionChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const MarkedSymbolsTy &Syms = State->get<MarkedSymbols>(); + for (auto I = Syms.begin(), E = Syms.end(); I != E; ++I) { + SymbolRef Sym = static_cast<SymbolRef>(*I); + if (!SymReaper.isDead(Sym)) + continue; + + if (!BT) + BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); + + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return; + + C.emitReport(llvm::make_unique<BugReport>(*BT, "SYMBOL DEAD", N)); + C.addTransition(State->remove<MarkedSymbols>(Sym), N); + } +} + void ExprInspectionChecker::analyzerCrash(const CallExpr *CE, CheckerContext &C) const { LLVM_BUILTIN_TRAP; diff --git a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 48d6bd4..3fe89f9 100644 --- a/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -23,7 +23,7 @@ using namespace clang; using namespace ento; namespace { -class FixedAddressChecker +class FixedAddressChecker : public Checker< check::PreStmt<BinaryOperator> > { mutable std::unique_ptr<BuiltinBug> BT; @@ -50,7 +50,7 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B, if (!RV.isConstant() || RV.isZeroConstant()) return; - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { if (!BT) BT.reset( new BuiltinBug(this, "Use fixed address", diff --git a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index 2cf508f..8c8acc6 100644 --- a/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -100,8 +100,8 @@ private: /// Generate a report if the expression is tainted or points to tainted data. bool generateReportIfTainted(const Expr *E, const char Msg[], CheckerContext &C) const; - - + + typedef SmallVector<unsigned, 2> ArgVector; /// \brief A struct used to specify taint propagation rules for a function. @@ -441,7 +441,7 @@ SymbolRef GenericTaintChecker::getPointedToSymbol(CheckerContext &C, return Val.getAsSymbol(); } -ProgramStateRef +ProgramStateRef GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, CheckerContext &C) const { ProgramStateRef State = C.getState(); @@ -640,7 +640,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, return false; // Generate diagnostic. - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { initBugType(); auto report = llvm::make_unique<BugReport>(*BT, Msg, N); report->addRange(E->getSourceRange()); @@ -658,17 +658,15 @@ bool GenericTaintChecker::checkUncontrolledFormatString(const CallExpr *CE, return false; // If either the format string content or the pointer itself are tainted, warn. - if (generateReportIfTainted(CE->getArg(ArgNum), - MsgUncontrolledFormatString, C)) - return true; - return false; + return generateReportIfTainted(CE->getArg(ArgNum), + MsgUncontrolledFormatString, C); } 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 + // 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) @@ -686,11 +684,7 @@ bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1)) return false; - if (generateReportIfTainted(CE->getArg(ArgNum), - MsgSanitizeSystemArgs, C)) - return true; - - return false; + return generateReportIfTainted(CE->getArg(ArgNum), MsgSanitizeSystemArgs, C); } // TODO: Should this check be a part of the CString checker? @@ -728,11 +722,8 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, ArgNum = 2; } - if (ArgNum != InvalidArgIndex && CE->getNumArgs() > ArgNum && - generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C)) - return true; - - return false; + return ArgNum != InvalidArgIndex && CE->getNumArgs() > ArgNum && + generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C); } void ento::registerGenericTaintChecker(CheckerManager &mgr) { diff --git a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp index 58d0783..0c3bff5 100644 --- a/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IdenticalExprChecker.cpp @@ -96,7 +96,7 @@ void FindIdenticalExprVisitor::checkBitwiseOrLogicalOp(const BinaryOperator *B, } LHS = B2->getLHS(); } - + if (isIdenticalStmt(AC->getASTContext(), RHS, LHS)) { Sr[0] = RHS->getSourceRange(); Sr[1] = LHS->getSourceRange(); @@ -108,6 +108,24 @@ bool FindIdenticalExprVisitor::VisitIfStmt(const IfStmt *I) { const Stmt *Stmt1 = I->getThen(); const Stmt *Stmt2 = I->getElse(); + // Check for identical inner condition: + // + // if (x<10) { + // if (x<10) { + // .. + if (const CompoundStmt *CS = dyn_cast<CompoundStmt>(Stmt1)) { + if (!CS->body_empty()) { + const IfStmt *InnerIf = dyn_cast<IfStmt>(*CS->body_begin()); + if (InnerIf && isIdenticalStmt(AC->getASTContext(), I->getCond(), InnerIf->getCond(), /*ignoreSideEffects=*/ false)) { + PathDiagnosticLocation ELoc(InnerIf->getCond(), BR.getSourceManager(), AC); + BR.EmitBasicReport(AC->getDecl(), Checker, "Identical conditions", + categories::LogicError, + "conditions of the inner and outer statements are identical", + ELoc); + } + } + } + // Check for identical conditions: // // if (b) { @@ -287,9 +305,7 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, const Stmt *Stmt2, bool IgnoreSideEffects) { if (!Stmt1 || !Stmt2) { - if (!Stmt1 && !Stmt2) - return true; - return false; + return !Stmt1 && !Stmt2; } // If Stmt1 & Stmt2 are of different class then they are not @@ -332,6 +348,7 @@ static bool isIdenticalStmt(const ASTContext &Ctx, const Stmt *Stmt1, return false; case Stmt::CallExprClass: case Stmt::ArraySubscriptExprClass: + case Stmt::OMPArraySectionExprClass: case Stmt::ImplicitCastExprClass: case Stmt::ParenExprClass: case Stmt::BreakStmtClass: diff --git a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 3df5fa0..dffff38 100644 --- a/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -20,8 +20,8 @@ // been called on them. An invalidation method should either invalidate all // the ivars or call another invalidation method (on self). // -// Partial invalidor annotation allows to addess cases when ivars are -// invalidated by other methods, which might or might not be called from +// Partial invalidor annotation allows to addess cases when ivars are +// invalidated by other methods, which might or might not be called from // the invalidation method. The checker checks that each invalidation // method and all the partial methods cumulatively invalidate all ivars. // __attribute__((annotate("objc_instance_variable_invalidator_partial"))); @@ -310,7 +310,7 @@ const ObjCIvarDecl *IvarInvalidationCheckerImpl::findPropertyBackingIvar( // Lookup for the synthesized case. IvarD = Prop->getPropertyIvarDecl(); - // We only track the ivars/properties that are defined in the current + // We only track the ivars/properties that are defined in the current // class (not the parent). if (IvarD && IvarD->getContainingInterface() == InterfaceD) { if (TrackedIvars.count(IvarD)) { diff --git a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp index 4e3f9b7..db4fbca 100644 --- a/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp @@ -276,7 +276,6 @@ void ASTFieldVisitor::ReportError(QualType T) { } } os << " (type " << FieldChain.back()->getType().getAsString() << ")"; - os.flush(); // Note that this will fire for every translation unit that uses this // class. This is suboptimal, but at least scan-build will merge diff --git a/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp new file mode 100644 index 0000000..56346cd --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -0,0 +1,1201 @@ +//=- LocalizationChecker.cpp -------------------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a set of checks for localizability including: +// 1) A checker that warns about uses of non-localized NSStrings passed to +// UI methods expecting localized strings +// 2) A syntactic checker that warns against the bad practice of +// not including a comment in NSLocalizedString macros. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclObjC.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.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/ExprEngine.h" +#include "clang/Lex/Lexer.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/AST/StmtVisitor.h" +#include "llvm/Support/Unicode.h" +#include "llvm/ADT/StringSet.h" + +using namespace clang; +using namespace ento; + +namespace { +struct LocalizedState { +private: + enum Kind { NonLocalized, Localized } K; + LocalizedState(Kind InK) : K(InK) {} + +public: + bool isLocalized() const { return K == Localized; } + bool isNonLocalized() const { return K == NonLocalized; } + + static LocalizedState getLocalized() { return LocalizedState(Localized); } + static LocalizedState getNonLocalized() { + return LocalizedState(NonLocalized); + } + + // Overload the == operator + bool operator==(const LocalizedState &X) const { return K == X.K; } + + // LLVMs equivalent of a hash function + void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); } +}; + +class NonLocalizedStringChecker + : public Checker<check::PostCall, check::PreObjCMessage, + check::PostObjCMessage, + check::PostStmt<ObjCStringLiteral>> { + + mutable std::unique_ptr<BugType> BT; + + // Methods that require a localized string + mutable llvm::DenseMap<const IdentifierInfo *, + llvm::DenseMap<Selector, uint8_t>> UIMethods; + // Methods that return a localized string + mutable llvm::SmallSet<std::pair<const IdentifierInfo *, Selector>, 12> LSM; + // C Functions that return a localized string + mutable llvm::SmallSet<const IdentifierInfo *, 5> LSF; + + void initUIMethods(ASTContext &Ctx) const; + void initLocStringsMethods(ASTContext &Ctx) const; + + bool hasNonLocalizedState(SVal S, CheckerContext &C) const; + bool hasLocalizedState(SVal S, CheckerContext &C) const; + void setNonLocalizedState(SVal S, CheckerContext &C) const; + void setLocalizedState(SVal S, CheckerContext &C) const; + + bool isAnnotatedAsLocalized(const Decl *D) const; + void reportLocalizationError(SVal S, const ObjCMethodCall &M, + CheckerContext &C, int argumentNumber = 0) const; + + int getLocalizedArgumentForSelector(const IdentifierInfo *Receiver, + Selector S) const; + +public: + NonLocalizedStringChecker(); + + // When this parameter is set to true, the checker assumes all + // methods that return NSStrings are unlocalized. Thus, more false + // positives will be reported. + DefaultBool IsAggressive; + + void checkPreObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; + void checkPostObjCMessage(const ObjCMethodCall &msg, CheckerContext &C) const; + void checkPostStmt(const ObjCStringLiteral *SL, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; +}; + +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(LocalizedMemMap, const MemRegion *, + LocalizedState) + +NonLocalizedStringChecker::NonLocalizedStringChecker() { + BT.reset(new BugType(this, "Unlocalizable string", + "Localizability Issue (Apple)")); +} + +#define NEW_RECEIVER(receiver) \ + llvm::DenseMap<Selector, uint8_t> &receiver##M = \ + UIMethods.insert({&Ctx.Idents.get(#receiver), \ + llvm::DenseMap<Selector, uint8_t>()}) \ + .first->second; +#define ADD_NULLARY_METHOD(receiver, method, argument) \ + receiver##M.insert( \ + {Ctx.Selectors.getNullarySelector(&Ctx.Idents.get(#method)), argument}); +#define ADD_UNARY_METHOD(receiver, method, argument) \ + receiver##M.insert( \ + {Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(#method)), argument}); +#define ADD_METHOD(receiver, method_list, count, argument) \ + receiver##M.insert({Ctx.Selectors.getSelector(count, method_list), argument}); + +/// Initializes a list of methods that require a localized string +/// Format: {"ClassName", {{"selectorName:", LocStringArg#}, ...}, ...} +void NonLocalizedStringChecker::initUIMethods(ASTContext &Ctx) const { + if (!UIMethods.empty()) + return; + + // UI Methods + NEW_RECEIVER(UISearchDisplayController) + ADD_UNARY_METHOD(UISearchDisplayController, setSearchResultsTitle, 0) + + NEW_RECEIVER(UITabBarItem) + IdentifierInfo *initWithTitleUITabBarItemTag[] = { + &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"), + &Ctx.Idents.get("tag")}; + ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemTag, 3, 0) + IdentifierInfo *initWithTitleUITabBarItemImage[] = { + &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("image"), + &Ctx.Idents.get("selectedImage")}; + ADD_METHOD(UITabBarItem, initWithTitleUITabBarItemImage, 3, 0) + + NEW_RECEIVER(NSDockTile) + ADD_UNARY_METHOD(NSDockTile, setBadgeLabel, 0) + + NEW_RECEIVER(NSStatusItem) + ADD_UNARY_METHOD(NSStatusItem, setTitle, 0) + ADD_UNARY_METHOD(NSStatusItem, setToolTip, 0) + + NEW_RECEIVER(UITableViewRowAction) + IdentifierInfo *rowActionWithStyleUITableViewRowAction[] = { + &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"), + &Ctx.Idents.get("handler")}; + ADD_METHOD(UITableViewRowAction, rowActionWithStyleUITableViewRowAction, 3, 1) + ADD_UNARY_METHOD(UITableViewRowAction, setTitle, 0) + + NEW_RECEIVER(NSBox) + ADD_UNARY_METHOD(NSBox, setTitle, 0) + + NEW_RECEIVER(NSButton) + ADD_UNARY_METHOD(NSButton, setTitle, 0) + ADD_UNARY_METHOD(NSButton, setAlternateTitle, 0) + + NEW_RECEIVER(NSSavePanel) + ADD_UNARY_METHOD(NSSavePanel, setPrompt, 0) + ADD_UNARY_METHOD(NSSavePanel, setTitle, 0) + ADD_UNARY_METHOD(NSSavePanel, setNameFieldLabel, 0) + ADD_UNARY_METHOD(NSSavePanel, setNameFieldStringValue, 0) + ADD_UNARY_METHOD(NSSavePanel, setMessage, 0) + + NEW_RECEIVER(UIPrintInfo) + ADD_UNARY_METHOD(UIPrintInfo, setJobName, 0) + + NEW_RECEIVER(NSTabViewItem) + ADD_UNARY_METHOD(NSTabViewItem, setLabel, 0) + ADD_UNARY_METHOD(NSTabViewItem, setToolTip, 0) + + NEW_RECEIVER(NSBrowser) + IdentifierInfo *setTitleNSBrowser[] = {&Ctx.Idents.get("setTitle"), + &Ctx.Idents.get("ofColumn")}; + ADD_METHOD(NSBrowser, setTitleNSBrowser, 2, 0) + + NEW_RECEIVER(UIAccessibilityElement) + ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityLabel, 0) + ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityHint, 0) + ADD_UNARY_METHOD(UIAccessibilityElement, setAccessibilityValue, 0) + + NEW_RECEIVER(UIAlertAction) + IdentifierInfo *actionWithTitleUIAlertAction[] = { + &Ctx.Idents.get("actionWithTitle"), &Ctx.Idents.get("style"), + &Ctx.Idents.get("handler")}; + ADD_METHOD(UIAlertAction, actionWithTitleUIAlertAction, 3, 0) + + NEW_RECEIVER(NSPopUpButton) + ADD_UNARY_METHOD(NSPopUpButton, addItemWithTitle, 0) + IdentifierInfo *insertItemWithTitleNSPopUpButton[] = { + &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")}; + ADD_METHOD(NSPopUpButton, insertItemWithTitleNSPopUpButton, 2, 0) + ADD_UNARY_METHOD(NSPopUpButton, removeItemWithTitle, 0) + ADD_UNARY_METHOD(NSPopUpButton, selectItemWithTitle, 0) + ADD_UNARY_METHOD(NSPopUpButton, setTitle, 0) + + NEW_RECEIVER(NSTableViewRowAction) + IdentifierInfo *rowActionWithStyleNSTableViewRowAction[] = { + &Ctx.Idents.get("rowActionWithStyle"), &Ctx.Idents.get("title"), + &Ctx.Idents.get("handler")}; + ADD_METHOD(NSTableViewRowAction, rowActionWithStyleNSTableViewRowAction, 3, 1) + ADD_UNARY_METHOD(NSTableViewRowAction, setTitle, 0) + + NEW_RECEIVER(NSImage) + ADD_UNARY_METHOD(NSImage, setAccessibilityDescription, 0) + + NEW_RECEIVER(NSUserActivity) + ADD_UNARY_METHOD(NSUserActivity, setTitle, 0) + + NEW_RECEIVER(NSPathControlItem) + ADD_UNARY_METHOD(NSPathControlItem, setTitle, 0) + + NEW_RECEIVER(NSCell) + ADD_UNARY_METHOD(NSCell, initTextCell, 0) + ADD_UNARY_METHOD(NSCell, setTitle, 0) + ADD_UNARY_METHOD(NSCell, setStringValue, 0) + + NEW_RECEIVER(NSPathControl) + ADD_UNARY_METHOD(NSPathControl, setPlaceholderString, 0) + + NEW_RECEIVER(UIAccessibility) + ADD_UNARY_METHOD(UIAccessibility, setAccessibilityLabel, 0) + ADD_UNARY_METHOD(UIAccessibility, setAccessibilityHint, 0) + ADD_UNARY_METHOD(UIAccessibility, setAccessibilityValue, 0) + + NEW_RECEIVER(NSTableColumn) + ADD_UNARY_METHOD(NSTableColumn, setTitle, 0) + ADD_UNARY_METHOD(NSTableColumn, setHeaderToolTip, 0) + + NEW_RECEIVER(NSSegmentedControl) + IdentifierInfo *setLabelNSSegmentedControl[] = { + &Ctx.Idents.get("setLabel"), &Ctx.Idents.get("forSegment")}; + ADD_METHOD(NSSegmentedControl, setLabelNSSegmentedControl, 2, 0) + + NEW_RECEIVER(NSButtonCell) + ADD_UNARY_METHOD(NSButtonCell, setTitle, 0) + ADD_UNARY_METHOD(NSButtonCell, setAlternateTitle, 0) + + NEW_RECEIVER(NSSliderCell) + ADD_UNARY_METHOD(NSSliderCell, setTitle, 0) + + NEW_RECEIVER(NSControl) + ADD_UNARY_METHOD(NSControl, setStringValue, 0) + + NEW_RECEIVER(NSAccessibility) + ADD_UNARY_METHOD(NSAccessibility, setAccessibilityValueDescription, 0) + ADD_UNARY_METHOD(NSAccessibility, setAccessibilityLabel, 0) + ADD_UNARY_METHOD(NSAccessibility, setAccessibilityTitle, 0) + ADD_UNARY_METHOD(NSAccessibility, setAccessibilityPlaceholderValue, 0) + ADD_UNARY_METHOD(NSAccessibility, setAccessibilityHelp, 0) + + NEW_RECEIVER(NSMatrix) + IdentifierInfo *setToolTipNSMatrix[] = {&Ctx.Idents.get("setToolTip"), + &Ctx.Idents.get("forCell")}; + ADD_METHOD(NSMatrix, setToolTipNSMatrix, 2, 0) + + NEW_RECEIVER(NSPrintPanel) + ADD_UNARY_METHOD(NSPrintPanel, setDefaultButtonTitle, 0) + + NEW_RECEIVER(UILocalNotification) + ADD_UNARY_METHOD(UILocalNotification, setAlertBody, 0) + ADD_UNARY_METHOD(UILocalNotification, setAlertAction, 0) + ADD_UNARY_METHOD(UILocalNotification, setAlertTitle, 0) + + NEW_RECEIVER(NSSlider) + ADD_UNARY_METHOD(NSSlider, setTitle, 0) + + NEW_RECEIVER(UIMenuItem) + IdentifierInfo *initWithTitleUIMenuItem[] = {&Ctx.Idents.get("initWithTitle"), + &Ctx.Idents.get("action")}; + ADD_METHOD(UIMenuItem, initWithTitleUIMenuItem, 2, 0) + ADD_UNARY_METHOD(UIMenuItem, setTitle, 0) + + NEW_RECEIVER(UIAlertController) + IdentifierInfo *alertControllerWithTitleUIAlertController[] = { + &Ctx.Idents.get("alertControllerWithTitle"), &Ctx.Idents.get("message"), + &Ctx.Idents.get("preferredStyle")}; + ADD_METHOD(UIAlertController, alertControllerWithTitleUIAlertController, 3, 1) + ADD_UNARY_METHOD(UIAlertController, setTitle, 0) + ADD_UNARY_METHOD(UIAlertController, setMessage, 0) + + NEW_RECEIVER(UIApplicationShortcutItem) + IdentifierInfo *initWithTypeUIApplicationShortcutItemIcon[] = { + &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle"), + &Ctx.Idents.get("localizedSubtitle"), &Ctx.Idents.get("icon"), + &Ctx.Idents.get("userInfo")}; + ADD_METHOD(UIApplicationShortcutItem, + initWithTypeUIApplicationShortcutItemIcon, 5, 1) + IdentifierInfo *initWithTypeUIApplicationShortcutItem[] = { + &Ctx.Idents.get("initWithType"), &Ctx.Idents.get("localizedTitle")}; + ADD_METHOD(UIApplicationShortcutItem, initWithTypeUIApplicationShortcutItem, + 2, 1) + + NEW_RECEIVER(UIActionSheet) + IdentifierInfo *initWithTitleUIActionSheet[] = { + &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("delegate"), + &Ctx.Idents.get("cancelButtonTitle"), + &Ctx.Idents.get("destructiveButtonTitle"), + &Ctx.Idents.get("otherButtonTitles")}; + ADD_METHOD(UIActionSheet, initWithTitleUIActionSheet, 5, 0) + ADD_UNARY_METHOD(UIActionSheet, addButtonWithTitle, 0) + ADD_UNARY_METHOD(UIActionSheet, setTitle, 0) + + NEW_RECEIVER(NSURLSessionTask) + ADD_UNARY_METHOD(NSURLSessionTask, setTaskDescription, 0) + + NEW_RECEIVER(UIAccessibilityCustomAction) + IdentifierInfo *initWithNameUIAccessibilityCustomAction[] = { + &Ctx.Idents.get("initWithName"), &Ctx.Idents.get("target"), + &Ctx.Idents.get("selector")}; + ADD_METHOD(UIAccessibilityCustomAction, + initWithNameUIAccessibilityCustomAction, 3, 0) + ADD_UNARY_METHOD(UIAccessibilityCustomAction, setName, 0) + + NEW_RECEIVER(UISearchBar) + ADD_UNARY_METHOD(UISearchBar, setText, 0) + ADD_UNARY_METHOD(UISearchBar, setPrompt, 0) + ADD_UNARY_METHOD(UISearchBar, setPlaceholder, 0) + + NEW_RECEIVER(UIBarItem) + ADD_UNARY_METHOD(UIBarItem, setTitle, 0) + + NEW_RECEIVER(UITextView) + ADD_UNARY_METHOD(UITextView, setText, 0) + + NEW_RECEIVER(NSView) + ADD_UNARY_METHOD(NSView, setToolTip, 0) + + NEW_RECEIVER(NSTextField) + ADD_UNARY_METHOD(NSTextField, setPlaceholderString, 0) + + NEW_RECEIVER(NSAttributedString) + ADD_UNARY_METHOD(NSAttributedString, initWithString, 0) + IdentifierInfo *initWithStringNSAttributedString[] = { + &Ctx.Idents.get("initWithString"), &Ctx.Idents.get("attributes")}; + ADD_METHOD(NSAttributedString, initWithStringNSAttributedString, 2, 0) + + NEW_RECEIVER(NSText) + ADD_UNARY_METHOD(NSText, setString, 0) + + NEW_RECEIVER(UIKeyCommand) + IdentifierInfo *keyCommandWithInputUIKeyCommand[] = { + &Ctx.Idents.get("keyCommandWithInput"), &Ctx.Idents.get("modifierFlags"), + &Ctx.Idents.get("action"), &Ctx.Idents.get("discoverabilityTitle")}; + ADD_METHOD(UIKeyCommand, keyCommandWithInputUIKeyCommand, 4, 3) + ADD_UNARY_METHOD(UIKeyCommand, setDiscoverabilityTitle, 0) + + NEW_RECEIVER(UILabel) + ADD_UNARY_METHOD(UILabel, setText, 0) + + NEW_RECEIVER(NSAlert) + IdentifierInfo *alertWithMessageTextNSAlert[] = { + &Ctx.Idents.get("alertWithMessageText"), &Ctx.Idents.get("defaultButton"), + &Ctx.Idents.get("alternateButton"), &Ctx.Idents.get("otherButton"), + &Ctx.Idents.get("informativeTextWithFormat")}; + ADD_METHOD(NSAlert, alertWithMessageTextNSAlert, 5, 0) + ADD_UNARY_METHOD(NSAlert, addButtonWithTitle, 0) + ADD_UNARY_METHOD(NSAlert, setMessageText, 0) + ADD_UNARY_METHOD(NSAlert, setInformativeText, 0) + ADD_UNARY_METHOD(NSAlert, setHelpAnchor, 0) + + NEW_RECEIVER(UIMutableApplicationShortcutItem) + ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedTitle, 0) + ADD_UNARY_METHOD(UIMutableApplicationShortcutItem, setLocalizedSubtitle, 0) + + NEW_RECEIVER(UIButton) + IdentifierInfo *setTitleUIButton[] = {&Ctx.Idents.get("setTitle"), + &Ctx.Idents.get("forState")}; + ADD_METHOD(UIButton, setTitleUIButton, 2, 0) + + NEW_RECEIVER(NSWindow) + ADD_UNARY_METHOD(NSWindow, setTitle, 0) + IdentifierInfo *minFrameWidthWithTitleNSWindow[] = { + &Ctx.Idents.get("minFrameWidthWithTitle"), &Ctx.Idents.get("styleMask")}; + ADD_METHOD(NSWindow, minFrameWidthWithTitleNSWindow, 2, 0) + ADD_UNARY_METHOD(NSWindow, setMiniwindowTitle, 0) + + NEW_RECEIVER(NSPathCell) + ADD_UNARY_METHOD(NSPathCell, setPlaceholderString, 0) + + NEW_RECEIVER(UIDocumentMenuViewController) + IdentifierInfo *addOptionWithTitleUIDocumentMenuViewController[] = { + &Ctx.Idents.get("addOptionWithTitle"), &Ctx.Idents.get("image"), + &Ctx.Idents.get("order"), &Ctx.Idents.get("handler")}; + ADD_METHOD(UIDocumentMenuViewController, + addOptionWithTitleUIDocumentMenuViewController, 4, 0) + + NEW_RECEIVER(UINavigationItem) + ADD_UNARY_METHOD(UINavigationItem, initWithTitle, 0) + ADD_UNARY_METHOD(UINavigationItem, setTitle, 0) + ADD_UNARY_METHOD(UINavigationItem, setPrompt, 0) + + NEW_RECEIVER(UIAlertView) + IdentifierInfo *initWithTitleUIAlertView[] = { + &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("message"), + &Ctx.Idents.get("delegate"), &Ctx.Idents.get("cancelButtonTitle"), + &Ctx.Idents.get("otherButtonTitles")}; + ADD_METHOD(UIAlertView, initWithTitleUIAlertView, 5, 0) + ADD_UNARY_METHOD(UIAlertView, addButtonWithTitle, 0) + ADD_UNARY_METHOD(UIAlertView, setTitle, 0) + ADD_UNARY_METHOD(UIAlertView, setMessage, 0) + + NEW_RECEIVER(NSFormCell) + ADD_UNARY_METHOD(NSFormCell, initTextCell, 0) + ADD_UNARY_METHOD(NSFormCell, setTitle, 0) + ADD_UNARY_METHOD(NSFormCell, setPlaceholderString, 0) + + NEW_RECEIVER(NSUserNotification) + ADD_UNARY_METHOD(NSUserNotification, setTitle, 0) + ADD_UNARY_METHOD(NSUserNotification, setSubtitle, 0) + ADD_UNARY_METHOD(NSUserNotification, setInformativeText, 0) + ADD_UNARY_METHOD(NSUserNotification, setActionButtonTitle, 0) + ADD_UNARY_METHOD(NSUserNotification, setOtherButtonTitle, 0) + ADD_UNARY_METHOD(NSUserNotification, setResponsePlaceholder, 0) + + NEW_RECEIVER(NSToolbarItem) + ADD_UNARY_METHOD(NSToolbarItem, setLabel, 0) + ADD_UNARY_METHOD(NSToolbarItem, setPaletteLabel, 0) + ADD_UNARY_METHOD(NSToolbarItem, setToolTip, 0) + + NEW_RECEIVER(NSProgress) + ADD_UNARY_METHOD(NSProgress, setLocalizedDescription, 0) + ADD_UNARY_METHOD(NSProgress, setLocalizedAdditionalDescription, 0) + + NEW_RECEIVER(NSSegmentedCell) + IdentifierInfo *setLabelNSSegmentedCell[] = {&Ctx.Idents.get("setLabel"), + &Ctx.Idents.get("forSegment")}; + ADD_METHOD(NSSegmentedCell, setLabelNSSegmentedCell, 2, 0) + IdentifierInfo *setToolTipNSSegmentedCell[] = {&Ctx.Idents.get("setToolTip"), + &Ctx.Idents.get("forSegment")}; + ADD_METHOD(NSSegmentedCell, setToolTipNSSegmentedCell, 2, 0) + + NEW_RECEIVER(NSUndoManager) + ADD_UNARY_METHOD(NSUndoManager, setActionName, 0) + ADD_UNARY_METHOD(NSUndoManager, undoMenuTitleForUndoActionName, 0) + ADD_UNARY_METHOD(NSUndoManager, redoMenuTitleForUndoActionName, 0) + + NEW_RECEIVER(NSMenuItem) + IdentifierInfo *initWithTitleNSMenuItem[] = { + &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("action"), + &Ctx.Idents.get("keyEquivalent")}; + ADD_METHOD(NSMenuItem, initWithTitleNSMenuItem, 3, 0) + ADD_UNARY_METHOD(NSMenuItem, setTitle, 0) + ADD_UNARY_METHOD(NSMenuItem, setToolTip, 0) + + NEW_RECEIVER(NSPopUpButtonCell) + IdentifierInfo *initTextCellNSPopUpButtonCell[] = { + &Ctx.Idents.get("initTextCell"), &Ctx.Idents.get("pullsDown")}; + ADD_METHOD(NSPopUpButtonCell, initTextCellNSPopUpButtonCell, 2, 0) + ADD_UNARY_METHOD(NSPopUpButtonCell, addItemWithTitle, 0) + IdentifierInfo *insertItemWithTitleNSPopUpButtonCell[] = { + &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("atIndex")}; + ADD_METHOD(NSPopUpButtonCell, insertItemWithTitleNSPopUpButtonCell, 2, 0) + ADD_UNARY_METHOD(NSPopUpButtonCell, removeItemWithTitle, 0) + ADD_UNARY_METHOD(NSPopUpButtonCell, selectItemWithTitle, 0) + ADD_UNARY_METHOD(NSPopUpButtonCell, setTitle, 0) + + NEW_RECEIVER(NSViewController) + ADD_UNARY_METHOD(NSViewController, setTitle, 0) + + NEW_RECEIVER(NSMenu) + ADD_UNARY_METHOD(NSMenu, initWithTitle, 0) + IdentifierInfo *insertItemWithTitleNSMenu[] = { + &Ctx.Idents.get("insertItemWithTitle"), &Ctx.Idents.get("action"), + &Ctx.Idents.get("keyEquivalent"), &Ctx.Idents.get("atIndex")}; + ADD_METHOD(NSMenu, insertItemWithTitleNSMenu, 4, 0) + IdentifierInfo *addItemWithTitleNSMenu[] = { + &Ctx.Idents.get("addItemWithTitle"), &Ctx.Idents.get("action"), + &Ctx.Idents.get("keyEquivalent")}; + ADD_METHOD(NSMenu, addItemWithTitleNSMenu, 3, 0) + ADD_UNARY_METHOD(NSMenu, setTitle, 0) + + NEW_RECEIVER(UIMutableUserNotificationAction) + ADD_UNARY_METHOD(UIMutableUserNotificationAction, setTitle, 0) + + NEW_RECEIVER(NSForm) + ADD_UNARY_METHOD(NSForm, addEntry, 0) + IdentifierInfo *insertEntryNSForm[] = {&Ctx.Idents.get("insertEntry"), + &Ctx.Idents.get("atIndex")}; + ADD_METHOD(NSForm, insertEntryNSForm, 2, 0) + + NEW_RECEIVER(NSTextFieldCell) + ADD_UNARY_METHOD(NSTextFieldCell, setPlaceholderString, 0) + + NEW_RECEIVER(NSUserNotificationAction) + IdentifierInfo *actionWithIdentifierNSUserNotificationAction[] = { + &Ctx.Idents.get("actionWithIdentifier"), &Ctx.Idents.get("title")}; + ADD_METHOD(NSUserNotificationAction, + actionWithIdentifierNSUserNotificationAction, 2, 1) + + NEW_RECEIVER(NSURLSession) + ADD_UNARY_METHOD(NSURLSession, setSessionDescription, 0) + + NEW_RECEIVER(UITextField) + ADD_UNARY_METHOD(UITextField, setText, 0) + ADD_UNARY_METHOD(UITextField, setPlaceholder, 0) + + NEW_RECEIVER(UIBarButtonItem) + IdentifierInfo *initWithTitleUIBarButtonItem[] = { + &Ctx.Idents.get("initWithTitle"), &Ctx.Idents.get("style"), + &Ctx.Idents.get("target"), &Ctx.Idents.get("action")}; + ADD_METHOD(UIBarButtonItem, initWithTitleUIBarButtonItem, 4, 0) + + NEW_RECEIVER(UIViewController) + ADD_UNARY_METHOD(UIViewController, setTitle, 0) + + NEW_RECEIVER(UISegmentedControl) + IdentifierInfo *insertSegmentWithTitleUISegmentedControl[] = { + &Ctx.Idents.get("insertSegmentWithTitle"), &Ctx.Idents.get("atIndex"), + &Ctx.Idents.get("animated")}; + ADD_METHOD(UISegmentedControl, insertSegmentWithTitleUISegmentedControl, 3, 0) + IdentifierInfo *setTitleUISegmentedControl[] = { + &Ctx.Idents.get("setTitle"), &Ctx.Idents.get("forSegmentAtIndex")}; + ADD_METHOD(UISegmentedControl, setTitleUISegmentedControl, 2, 0) +} + +#define LSF_INSERT(function_name) LSF.insert(&Ctx.Idents.get(function_name)); +#define LSM_INSERT_NULLARY(receiver, method_name) \ + LSM.insert({&Ctx.Idents.get(receiver), Ctx.Selectors.getNullarySelector( \ + &Ctx.Idents.get(method_name))}); +#define LSM_INSERT_UNARY(receiver, method_name) \ + LSM.insert({&Ctx.Idents.get(receiver), \ + Ctx.Selectors.getUnarySelector(&Ctx.Idents.get(method_name))}); +#define LSM_INSERT_SELECTOR(receiver, method_list, arguments) \ + LSM.insert({&Ctx.Idents.get(receiver), \ + Ctx.Selectors.getSelector(arguments, method_list)}); + +/// Initializes a list of methods and C functions that return a localized string +void NonLocalizedStringChecker::initLocStringsMethods(ASTContext &Ctx) const { + if (!LSM.empty()) + return; + + IdentifierInfo *LocalizedStringMacro[] = { + &Ctx.Idents.get("localizedStringForKey"), &Ctx.Idents.get("value"), + &Ctx.Idents.get("table")}; + LSM_INSERT_SELECTOR("NSBundle", LocalizedStringMacro, 3) + LSM_INSERT_UNARY("NSDateFormatter", "stringFromDate") + IdentifierInfo *LocalizedStringFromDate[] = { + &Ctx.Idents.get("localizedStringFromDate"), &Ctx.Idents.get("dateStyle"), + &Ctx.Idents.get("timeStyle")}; + LSM_INSERT_SELECTOR("NSDateFormatter", LocalizedStringFromDate, 3) + LSM_INSERT_UNARY("NSNumberFormatter", "stringFromNumber") + LSM_INSERT_NULLARY("UITextField", "text") + LSM_INSERT_NULLARY("UITextView", "text") + LSM_INSERT_NULLARY("UILabel", "text") + + LSF_INSERT("CFDateFormatterCreateStringWithDate"); + LSF_INSERT("CFDateFormatterCreateStringWithAbsoluteTime"); + LSF_INSERT("CFNumberFormatterCreateStringWithNumber"); +} + +/// Checks to see if the method / function declaration includes +/// __attribute__((annotate("returns_localized_nsstring"))) +bool NonLocalizedStringChecker::isAnnotatedAsLocalized(const Decl *D) const { + if (!D) + return false; + return std::any_of( + D->specific_attr_begin<AnnotateAttr>(), + D->specific_attr_end<AnnotateAttr>(), [](const AnnotateAttr *Ann) { + return Ann->getAnnotation() == "returns_localized_nsstring"; + }); +} + +/// Returns true if the given SVal is marked as Localized in the program state +bool NonLocalizedStringChecker::hasLocalizedState(SVal S, + CheckerContext &C) const { + const MemRegion *mt = S.getAsRegion(); + if (mt) { + const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt); + if (LS && LS->isLocalized()) + return true; + } + return false; +} + +/// Returns true if the given SVal is marked as NonLocalized in the program +/// state +bool NonLocalizedStringChecker::hasNonLocalizedState(SVal S, + CheckerContext &C) const { + const MemRegion *mt = S.getAsRegion(); + if (mt) { + const LocalizedState *LS = C.getState()->get<LocalizedMemMap>(mt); + if (LS && LS->isNonLocalized()) + return true; + } + return false; +} + +/// Marks the given SVal as Localized in the program state +void NonLocalizedStringChecker::setLocalizedState(const SVal S, + CheckerContext &C) const { + const MemRegion *mt = S.getAsRegion(); + if (mt) { + ProgramStateRef State = + C.getState()->set<LocalizedMemMap>(mt, LocalizedState::getLocalized()); + C.addTransition(State); + } +} + +/// Marks the given SVal as NonLocalized in the program state +void NonLocalizedStringChecker::setNonLocalizedState(const SVal S, + CheckerContext &C) const { + const MemRegion *mt = S.getAsRegion(); + if (mt) { + ProgramStateRef State = C.getState()->set<LocalizedMemMap>( + mt, LocalizedState::getNonLocalized()); + C.addTransition(State); + } +} + +/// Reports a localization error for the passed in method call and SVal +void NonLocalizedStringChecker::reportLocalizationError( + SVal S, const ObjCMethodCall &M, CheckerContext &C, + int argumentNumber) const { + + ExplodedNode *ErrNode = C.getPredecessor(); + static CheckerProgramPointTag Tag("NonLocalizedStringChecker", + "UnlocalizedString"); + ErrNode = C.addTransition(C.getState(), C.getPredecessor(), &Tag); + + if (!ErrNode) + return; + + // Generate the bug report. + std::unique_ptr<BugReport> R(new BugReport( + *BT, "User-facing text should use localized string macro", ErrNode)); + if (argumentNumber) { + R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange()); + } else { + R->addRange(M.getSourceRange()); + } + R->markInteresting(S); + C.emitReport(std::move(R)); +} + +/// Returns the argument number requiring localized string if it exists +/// otherwise, returns -1 +int NonLocalizedStringChecker::getLocalizedArgumentForSelector( + const IdentifierInfo *Receiver, Selector S) const { + auto method = UIMethods.find(Receiver); + + if (method == UIMethods.end()) + return -1; + + auto argumentIterator = method->getSecond().find(S); + + if (argumentIterator == method->getSecond().end()) + return -1; + + int argumentNumber = argumentIterator->getSecond(); + return argumentNumber; +} + +/// Check if the string being passed in has NonLocalized state +void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg, + CheckerContext &C) const { + initUIMethods(C.getASTContext()); + + const ObjCInterfaceDecl *OD = msg.getReceiverInterface(); + if (!OD) + return; + const IdentifierInfo *odInfo = OD->getIdentifier(); + + Selector S = msg.getSelector(); + + std::string SelectorString = S.getAsString(); + StringRef SelectorName = SelectorString; + assert(!SelectorName.empty()); + + if (odInfo->isStr("NSString")) { + // Handle the case where the receiver is an NSString + // These special NSString methods draw to the screen + + if (!(SelectorName.startswith("drawAtPoint") || + SelectorName.startswith("drawInRect") || + SelectorName.startswith("drawWithRect"))) + return; + + SVal svTitle = msg.getReceiverSVal(); + + bool isNonLocalized = hasNonLocalizedState(svTitle, C); + + if (isNonLocalized) { + reportLocalizationError(svTitle, msg, C); + } + } + + int argumentNumber = getLocalizedArgumentForSelector(odInfo, S); + // Go up each hierarchy of superclasses and their protocols + while (argumentNumber < 0 && OD->getSuperClass() != nullptr) { + for (const auto *P : OD->all_referenced_protocols()) { + argumentNumber = getLocalizedArgumentForSelector(P->getIdentifier(), S); + if (argumentNumber >= 0) + break; + } + if (argumentNumber < 0) { + OD = OD->getSuperClass(); + argumentNumber = getLocalizedArgumentForSelector(OD->getIdentifier(), S); + } + } + + if (argumentNumber < 0) // There was no match in UIMethods + return; + + SVal svTitle = msg.getArgSVal(argumentNumber); + + if (const ObjCStringRegion *SR = + dyn_cast_or_null<ObjCStringRegion>(svTitle.getAsRegion())) { + StringRef stringValue = + SR->getObjCStringLiteral()->getString()->getString(); + if ((stringValue.trim().size() == 0 && stringValue.size() > 0) || + stringValue.empty()) + return; + if (!IsAggressive && llvm::sys::unicode::columnWidthUTF8(stringValue) < 2) + return; + } + + bool isNonLocalized = hasNonLocalizedState(svTitle, C); + + if (isNonLocalized) { + reportLocalizationError(svTitle, msg, C, argumentNumber + 1); + } +} + +static inline bool isNSStringType(QualType T, ASTContext &Ctx) { + + const ObjCObjectPointerType *PT = T->getAs<ObjCObjectPointerType>(); + if (!PT) + return false; + + ObjCInterfaceDecl *Cls = PT->getObjectType()->getInterface(); + if (!Cls) + return false; + + IdentifierInfo *ClsName = Cls->getIdentifier(); + + // FIXME: Should we walk the chain of classes? + return ClsName == &Ctx.Idents.get("NSString") || + ClsName == &Ctx.Idents.get("NSMutableString"); +} + +/// Marks a string being returned by any call as localized +/// if it is in LocStringFunctions (LSF) or the function is annotated. +/// Otherwise, we mark it as NonLocalized (Aggressive) or +/// NonLocalized only if it is not backed by a SymRegion (Non-Aggressive), +/// basically leaving only string literals as NonLocalized. +void NonLocalizedStringChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + initLocStringsMethods(C.getASTContext()); + + if (!Call.getOriginExpr()) + return; + + // Anything that takes in a localized NSString as an argument + // and returns an NSString will be assumed to be returning a + // localized NSString. (Counter: Incorrectly combining two LocalizedStrings) + const QualType RT = Call.getResultType(); + if (isNSStringType(RT, C.getASTContext())) { + for (unsigned i = 0; i < Call.getNumArgs(); ++i) { + SVal argValue = Call.getArgSVal(i); + if (hasLocalizedState(argValue, C)) { + SVal sv = Call.getReturnValue(); + setLocalizedState(sv, C); + return; + } + } + } + + const Decl *D = Call.getDecl(); + if (!D) + return; + + const IdentifierInfo *Identifier = Call.getCalleeIdentifier(); + + SVal sv = Call.getReturnValue(); + if (isAnnotatedAsLocalized(D) || LSF.count(Identifier) != 0) { + setLocalizedState(sv, C); + } else if (isNSStringType(RT, C.getASTContext()) && + !hasLocalizedState(sv, C)) { + if (IsAggressive) { + setNonLocalizedState(sv, C); + } else { + const SymbolicRegion *SymReg = + dyn_cast_or_null<SymbolicRegion>(sv.getAsRegion()); + if (!SymReg) + setNonLocalizedState(sv, C); + } + } +} + +/// Marks a string being returned by an ObjC method as localized +/// if it is in LocStringMethods or the method is annotated +void NonLocalizedStringChecker::checkPostObjCMessage(const ObjCMethodCall &msg, + CheckerContext &C) const { + initLocStringsMethods(C.getASTContext()); + + if (!msg.isInstanceMessage()) + return; + + const ObjCInterfaceDecl *OD = msg.getReceiverInterface(); + if (!OD) + return; + const IdentifierInfo *odInfo = OD->getIdentifier(); + + Selector S = msg.getSelector(); + std::string SelectorName = S.getAsString(); + + std::pair<const IdentifierInfo *, Selector> MethodDescription = {odInfo, S}; + + if (LSM.count(MethodDescription) || isAnnotatedAsLocalized(msg.getDecl())) { + SVal sv = msg.getReturnValue(); + setLocalizedState(sv, C); + } +} + +/// Marks all empty string literals as localized +void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, + CheckerContext &C) const { + SVal sv = C.getSVal(SL); + setNonLocalizedState(sv, C); +} + +namespace { +class EmptyLocalizationContextChecker + : public Checker<check::ASTDecl<ObjCImplementationDecl>> { + + // A helper class, which walks the AST + class MethodCrawler : public ConstStmtVisitor<MethodCrawler> { + const ObjCMethodDecl *MD; + BugReporter &BR; + AnalysisManager &Mgr; + const CheckerBase *Checker; + LocationOrAnalysisDeclContext DCtx; + + public: + MethodCrawler(const ObjCMethodDecl *InMD, BugReporter &InBR, + const CheckerBase *Checker, AnalysisManager &InMgr, + AnalysisDeclContext *InDCtx) + : MD(InMD), BR(InBR), Mgr(InMgr), Checker(Checker), DCtx(InDCtx) {} + + void VisitStmt(const Stmt *S) { VisitChildren(S); } + + void VisitObjCMessageExpr(const ObjCMessageExpr *ME); + + void reportEmptyContextError(const ObjCMessageExpr *M) const; + + void VisitChildren(const Stmt *S) { + for (const Stmt *Child : S->children()) { + if (Child) + this->Visit(Child); + } + } + }; + +public: + void checkASTDecl(const ObjCImplementationDecl *D, AnalysisManager &Mgr, + BugReporter &BR) const; +}; +} // end anonymous namespace + +void EmptyLocalizationContextChecker::checkASTDecl( + const ObjCImplementationDecl *D, AnalysisManager &Mgr, + BugReporter &BR) const { + + for (const ObjCMethodDecl *M : D->methods()) { + AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M); + + const Stmt *Body = M->getBody(); + assert(Body); + + MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx); + MC.VisitStmt(Body); + } +} + +/// This check attempts to match these macros, assuming they are defined as +/// follows: +/// +/// #define NSLocalizedString(key, comment) \ +/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] +/// #define NSLocalizedStringFromTable(key, tbl, comment) \ +/// [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:(tbl)] +/// #define NSLocalizedStringFromTableInBundle(key, tbl, bundle, comment) \ +/// [bundle localizedStringForKey:(key) value:@"" table:(tbl)] +/// #define NSLocalizedStringWithDefaultValue(key, tbl, bundle, val, comment) +/// +/// We cannot use the path sensitive check because the macro argument we are +/// checking for (comment) is not used and thus not present in the AST, +/// so we use Lexer on the original macro call and retrieve the value of +/// the comment. If it's empty or nil, we raise a warning. +void EmptyLocalizationContextChecker::MethodCrawler::VisitObjCMessageExpr( + const ObjCMessageExpr *ME) { + + const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); + if (!OD) + return; + + const IdentifierInfo *odInfo = OD->getIdentifier(); + + if (!(odInfo->isStr("NSBundle") && + ME->getSelector().getAsString() == + "localizedStringForKey:value:table:")) { + return; + } + + SourceRange R = ME->getSourceRange(); + if (!R.getBegin().isMacroID()) + return; + + // getImmediateMacroCallerLoc gets the location of the immediate macro + // caller, one level up the stack toward the initial macro typed into the + // source, so SL should point to the NSLocalizedString macro. + SourceLocation SL = + Mgr.getSourceManager().getImmediateMacroCallerLoc(R.getBegin()); + std::pair<FileID, unsigned> SLInfo = + Mgr.getSourceManager().getDecomposedLoc(SL); + + SrcMgr::SLocEntry SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first); + + // If NSLocalizedString macro is wrapped in another macro, we need to + // unwrap the expansion until we get to the NSLocalizedStringMacro. + while (SE.isExpansion()) { + SL = SE.getExpansion().getSpellingLoc(); + SLInfo = Mgr.getSourceManager().getDecomposedLoc(SL); + SE = Mgr.getSourceManager().getSLocEntry(SLInfo.first); + } + + llvm::MemoryBuffer *BF = SE.getFile().getContentCache()->getRawBuffer(); + Lexer TheLexer(SL, LangOptions(), BF->getBufferStart(), + BF->getBufferStart() + SLInfo.second, BF->getBufferEnd()); + + Token I; + Token Result; // This will hold the token just before the last ')' + int p_count = 0; // This is for parenthesis matching + while (!TheLexer.LexFromRawLexer(I)) { + if (I.getKind() == tok::l_paren) + ++p_count; + if (I.getKind() == tok::r_paren) { + if (p_count == 1) + break; + --p_count; + } + Result = I; + } + + if (isAnyIdentifier(Result.getKind())) { + if (Result.getRawIdentifier().equals("nil")) { + reportEmptyContextError(ME); + return; + } + } + + if (!isStringLiteral(Result.getKind())) + return; + + StringRef Comment = + StringRef(Result.getLiteralData(), Result.getLength()).trim("\""); + + if ((Comment.trim().size() == 0 && Comment.size() > 0) || // Is Whitespace + Comment.empty()) { + reportEmptyContextError(ME); + } +} + +void EmptyLocalizationContextChecker::MethodCrawler::reportEmptyContextError( + const ObjCMessageExpr *ME) const { + // Generate the bug report. + BR.EmitBasicReport(MD, Checker, "Context Missing", + "Localizability Issue (Apple)", + "Localized string macro should include a non-empty " + "comment for translators", + PathDiagnosticLocation(ME, BR.getSourceManager(), DCtx)); +} + +namespace { +class PluralMisuseChecker : public Checker<check::ASTCodeBody> { + + // A helper class, which walks the AST + class MethodCrawler : public RecursiveASTVisitor<MethodCrawler> { + BugReporter &BR; + const CheckerBase *Checker; + AnalysisDeclContext *AC; + + // This functions like a stack. We push on any IfStmt or + // ConditionalOperator that matches the condition + // and pop it off when we leave that statement + llvm::SmallVector<const clang::Stmt *, 8> MatchingStatements; + // This is true when we are the direct-child of a + // matching statement + bool InMatchingStatement = false; + + public: + explicit MethodCrawler(BugReporter &InBR, const CheckerBase *Checker, + AnalysisDeclContext *InAC) + : BR(InBR), Checker(Checker), AC(InAC) {} + + bool VisitIfStmt(const IfStmt *I); + bool EndVisitIfStmt(IfStmt *I); + bool TraverseIfStmt(IfStmt *x); + bool VisitConditionalOperator(const ConditionalOperator *C); + bool TraverseConditionalOperator(ConditionalOperator *C); + bool VisitCallExpr(const CallExpr *CE); + bool VisitObjCMessageExpr(const ObjCMessageExpr *ME); + + private: + void reportPluralMisuseError(const Stmt *S) const; + bool isCheckingPlurality(const Expr *E) const; + }; + +public: + void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, + BugReporter &BR) const { + MethodCrawler Visitor(BR, this, Mgr.getAnalysisDeclContext(D)); + Visitor.TraverseDecl(const_cast<Decl *>(D)); + } +}; +} // end anonymous namespace + +// Checks the condition of the IfStmt and returns true if one +// of the following heuristics are met: +// 1) The conidtion is a variable with "singular" or "plural" in the name +// 2) The condition is a binary operator with 1 or 2 on the right-hand side +bool PluralMisuseChecker::MethodCrawler::isCheckingPlurality( + const Expr *Condition) const { + const BinaryOperator *BO = nullptr; + // Accounts for when a VarDecl represents a BinaryOperator + if (const DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(Condition)) { + if (const VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + const Expr *InitExpr = VD->getInit(); + if (InitExpr) { + if (const BinaryOperator *B = + dyn_cast<BinaryOperator>(InitExpr->IgnoreParenImpCasts())) { + BO = B; + } + } + if (VD->getName().lower().find("plural") != StringRef::npos || + VD->getName().lower().find("singular") != StringRef::npos) { + return true; + } + } + } else if (const BinaryOperator *B = dyn_cast<BinaryOperator>(Condition)) { + BO = B; + } + + if (BO == nullptr) + return false; + + if (IntegerLiteral *IL = dyn_cast_or_null<IntegerLiteral>( + BO->getRHS()->IgnoreParenImpCasts())) { + llvm::APInt Value = IL->getValue(); + if (Value == 1 || Value == 2) { + return true; + } + } + return false; +} + +// A CallExpr with "LOC" in its identifier that takes in a string literal +// has been shown to almost always be a function that returns a localized +// string. Raise a diagnostic when this is in a statement that matches +// the condition. +bool PluralMisuseChecker::MethodCrawler::VisitCallExpr(const CallExpr *CE) { + if (InMatchingStatement) { + if (const FunctionDecl *FD = CE->getDirectCallee()) { + std::string NormalizedName = + StringRef(FD->getNameInfo().getAsString()).lower(); + if (NormalizedName.find("loc") != std::string::npos) { + for (const Expr *Arg : CE->arguments()) { + if (isa<ObjCStringLiteral>(Arg)) + reportPluralMisuseError(CE); + } + } + } + } + return true; +} + +// The other case is for NSLocalizedString which also returns +// a localized string. It's a macro for the ObjCMessageExpr +// [NSBundle localizedStringForKey:value:table:] Raise a +// diagnostic when this is in a statement that matches +// the condition. +bool PluralMisuseChecker::MethodCrawler::VisitObjCMessageExpr( + const ObjCMessageExpr *ME) { + const ObjCInterfaceDecl *OD = ME->getReceiverInterface(); + if (!OD) + return true; + + const IdentifierInfo *odInfo = OD->getIdentifier(); + + if (odInfo->isStr("NSBundle") && + ME->getSelector().getAsString() == "localizedStringForKey:value:table:") { + if (InMatchingStatement) { + reportPluralMisuseError(ME); + } + } + return true; +} + +/// Override TraverseIfStmt so we know when we are done traversing an IfStmt +bool PluralMisuseChecker::MethodCrawler::TraverseIfStmt(IfStmt *I) { + RecursiveASTVisitor<MethodCrawler>::TraverseIfStmt(I); + return EndVisitIfStmt(I); +} + +// EndVisit callbacks are not provided by the RecursiveASTVisitor +// so we override TraverseIfStmt and make a call to EndVisitIfStmt +// after traversing the IfStmt +bool PluralMisuseChecker::MethodCrawler::EndVisitIfStmt(IfStmt *I) { + MatchingStatements.pop_back(); + if (!MatchingStatements.empty()) { + if (MatchingStatements.back() != nullptr) { + InMatchingStatement = true; + return true; + } + } + InMatchingStatement = false; + return true; +} + +bool PluralMisuseChecker::MethodCrawler::VisitIfStmt(const IfStmt *I) { + const Expr *Condition = I->getCond()->IgnoreParenImpCasts(); + if (isCheckingPlurality(Condition)) { + MatchingStatements.push_back(I); + InMatchingStatement = true; + } else { + MatchingStatements.push_back(nullptr); + InMatchingStatement = false; + } + + return true; +} + +// Preliminary support for conditional operators. +bool PluralMisuseChecker::MethodCrawler::TraverseConditionalOperator( + ConditionalOperator *C) { + RecursiveASTVisitor<MethodCrawler>::TraverseConditionalOperator(C); + MatchingStatements.pop_back(); + if (!MatchingStatements.empty()) { + if (MatchingStatements.back() != nullptr) + InMatchingStatement = true; + else + InMatchingStatement = false; + } else { + InMatchingStatement = false; + } + return true; +} + +bool PluralMisuseChecker::MethodCrawler::VisitConditionalOperator( + const ConditionalOperator *C) { + const Expr *Condition = C->getCond()->IgnoreParenImpCasts(); + if (isCheckingPlurality(Condition)) { + MatchingStatements.push_back(C); + InMatchingStatement = true; + } else { + MatchingStatements.push_back(nullptr); + InMatchingStatement = false; + } + return true; +} + +void PluralMisuseChecker::MethodCrawler::reportPluralMisuseError( + const Stmt *S) const { + // Generate the bug report. + BR.EmitBasicReport(AC->getDecl(), Checker, "Plural Misuse", + "Localizability Issue (Apple)", + "Plural cases are not supported accross all languages. " + "Use a .stringsdict file instead", + PathDiagnosticLocation(S, BR.getSourceManager(), AC)); +} + +//===----------------------------------------------------------------------===// +// Checker registration. +//===----------------------------------------------------------------------===// + +void ento::registerNonLocalizedStringChecker(CheckerManager &mgr) { + NonLocalizedStringChecker *checker = + mgr.registerChecker<NonLocalizedStringChecker>(); + checker->IsAggressive = + mgr.getAnalyzerOptions().getBooleanOption("AggressiveReport", false); +} + +void ento::registerEmptyLocalizationContextChecker(CheckerManager &mgr) { + mgr.registerChecker<EmptyLocalizationContextChecker>(); +} + +void ento::registerPluralMisuseChecker(CheckerManager &mgr) { + mgr.registerChecker<PluralMisuseChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index 7838901..1e56d70 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -118,7 +118,7 @@ private: SValBuilder &Builder) const { return definitelyReturnedError(RetSym, State, Builder, true); } - + /// Mark an AllocationPair interesting for diagnostic reporting. void markInteresting(BugReport *R, const AllocationPair &AP) const { R->markInteresting(AP.first); @@ -136,7 +136,6 @@ private: public: SecKeychainBugVisitor(SymbolRef S) : Sym(S) {} - ~SecKeychainBugVisitor() override {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; @@ -202,12 +201,8 @@ unsigned MacOSKeychainAPIChecker::getTrackedFunctionIndex(StringRef Name, static bool isBadDeallocationArgument(const MemRegion *Arg) { if (!Arg) return false; - if (isa<AllocaRegion>(Arg) || - isa<BlockDataRegion>(Arg) || - isa<TypedRegion>(Arg)) { - return true; - } - return false; + return isa<AllocaRegion>(Arg) || isa<BlockDataRegion>(Arg) || + isa<TypedRegion>(Arg); } /// Given the address expression, retrieve the value it's pointing to. Assume @@ -241,11 +236,7 @@ bool MacOSKeychainAPIChecker::definitelyReturnedError(SymbolRef RetSym, DefinedOrUnknownSVal NoErr = Builder.evalEQ(State, NoErrVal, nonloc::SymbolVal(RetSym)); ProgramStateRef ErrState = State->assume(NoErr, noError); - if (ErrState == State) { - return true; - } - - return false; + return ErrState == State; } // Report deallocator mismatch. Remove the region from tracking - reporting a @@ -256,7 +247,7 @@ void MacOSKeychainAPIChecker:: CheckerContext &C) const { ProgramStateRef State = C.getState(); State = State->remove<AllocatedData>(AP.first); - ExplodedNode *N = C.addTransition(State); + ExplodedNode *N = C.generateNonFatalErrorNode(State); if (!N) return; @@ -283,7 +274,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, const FunctionDecl *FD = C.getCalleeDecl(CE); if (!FD || FD->getKind() != Decl::Function) return; - + StringRef funName = C.getCalleeName(FD); if (funName.empty()) return; @@ -302,7 +293,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, // Remove the value from the state. The new symbol will be added for // tracking when the second allocator is processed in checkPostStmt(). State = State->remove<AllocatedData>(V); - ExplodedNode *N = C.addTransition(State); + ExplodedNode *N = C.generateNonFatalErrorNode(State); if (!N) return; initBugType(); @@ -365,7 +356,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, if (isEnclosingFunctionParam(ArgExpr)) return; - ExplodedNode *N = C.addTransition(State); + ExplodedNode *N = C.generateNonFatalErrorNode(State); if (!N) return; initBugType(); @@ -431,7 +422,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, // report a bad call to free. if (State->assume(ArgSVal.castAs<DefinedSVal>(), false) && !definitelyDidnotReturnError(AS->Region, State, C.getSValBuilder())) { - ExplodedNode *N = C.addTransition(State); + ExplodedNode *N = C.generateNonFatalErrorNode(State); if (!N) return; initBugType(); @@ -585,10 +576,12 @@ void MacOSKeychainAPIChecker::checkDeadSymbols(SymbolReaper &SR, } static CheckerProgramPointTag Tag(this, "DeadSymbolsLeak"); - ExplodedNode *N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); + ExplodedNode *N = C.generateNonFatalErrorNode(C.getState(), &Tag); + if (!N) + return; // Generate the error reports. - for (const auto P : Errors) + for (const auto &P : Errors) C.emitReport(generateAllocatedDataNotReleasedReport(P, N, C)); // Generate the new, cleaned up state. diff --git a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 11ba609..4cbe97b 100644 --- a/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -62,7 +62,7 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, if (!R || !isa<StackSpaceRegion>(R->getMemorySpace())) return; - ExplodedNode *N = C.generateSink(state); + ExplodedNode *N = C.generateErrorNode(state); if (!N) return; @@ -79,7 +79,7 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, if (TrimmedFName != FName) FName = TrimmedFName; } - + SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to '" << FName << "' uses"; diff --git a/lib/StaticAnalyzer/Checkers/Makefile b/lib/StaticAnalyzer/Checkers/Makefile index 2582908..7c8f7bf 100644 --- a/lib/StaticAnalyzer/Checkers/Makefile +++ b/lib/StaticAnalyzer/Checkers/Makefile @@ -1,13 +1,13 @@ ##===- clang/lib/Checker/Makefile --------------------------*- Makefile -*-===## -# +# # The LLVM Compiler Infrastructure # # This file is distributed under the University of Illinois Open Source # License. See LICENSE.TXT for details. -# +# ##===----------------------------------------------------------------------===## # -# This implements analyses built on top of source-level CFGs. +# This implements analyses built on top of source-level CFGs. # ##===----------------------------------------------------------------------===## diff --git a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index a9e0865..713d9fe 100644 --- a/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -65,10 +65,10 @@ class RefState { const Stmt *S; unsigned K : 3; // Kind enum, but stored as a bitfield. - unsigned Family : 29; // Rest of 32-bit word, currently just an allocation + unsigned Family : 29; // Rest of 32-bit word, currently just an allocation // family. - RefState(Kind k, const Stmt *s, unsigned family) + RefState(Kind k, const Stmt *s, unsigned family) : S(s), K(k), Family(family) { assert(family != AF_None); } @@ -94,7 +94,7 @@ public: return RefState(AllocatedOfSizeZero, RS->getStmt(), RS->getAllocationFamily()); } - static RefState getReleased(unsigned family, const Stmt *s) { + static RefState getReleased(unsigned family, const Stmt *s) { return RefState(Released, s, family); } static RefState getRelinquished(unsigned family, const Stmt *s) { @@ -169,9 +169,9 @@ class MallocChecker : public Checker<check::DeadSymbols, { public: MallocChecker() - : II_alloca(nullptr), II_malloc(nullptr), II_free(nullptr), + : II_alloca(nullptr), II_malloc(nullptr), II_free(nullptr), II_realloc(nullptr), II_calloc(nullptr), II_valloc(nullptr), - II_reallocf(nullptr), II_strndup(nullptr), II_strdup(nullptr), + II_reallocf(nullptr), II_strndup(nullptr), II_strdup(nullptr), II_kmalloc(nullptr), II_if_nameindex(nullptr), II_if_freenameindex(nullptr) {} @@ -185,7 +185,7 @@ public: CK_NumCheckKinds }; - enum class MemoryOperationKind { + enum class MemoryOperationKind { MOK_Allocate, MOK_Free, MOK_Any @@ -245,19 +245,19 @@ private: /// \brief Print names of allocators and deallocators. /// /// \returns true on success. - bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, + bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, const Expr *E) const; /// \brief Print expected name of an allocator based on the deallocator's /// family derived from the DeallocExpr. - void printExpectedAllocName(raw_ostream &os, CheckerContext &C, + void printExpectedAllocName(raw_ostream &os, CheckerContext &C, const Expr *DeallocExpr) const; - /// \brief Print expected name of a deallocator based on the allocator's + /// \brief Print expected name of a deallocator based on the allocator's /// family. void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const; ///@{ - /// Check if this is one of the functions which can allocate/reallocate memory + /// Check if this is one of the functions which can allocate/reallocate memory /// pointed to by one of its arguments. bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; bool isCMemFunction(const FunctionDecl *FD, @@ -292,7 +292,7 @@ private: const ProgramStateRef &State) const; /// Update the RefState to reflect the new memory allocation. - static ProgramStateRef + static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, ProgramStateRef State, AllocationFamily Family = AF_Malloc); @@ -312,17 +312,17 @@ private: bool ReturnsNullOnFailure = false) const; ProgramStateRef ReallocMem(CheckerContext &C, const CallExpr *CE, - bool FreesMemOnFailure, + bool FreesMemOnFailure, ProgramStateRef State) const; static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE, ProgramStateRef State); - + ///\brief 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; - void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, + void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; bool checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const; @@ -330,7 +330,7 @@ private: /// Check if the function is known free memory, or if it is /// "interesting" and should be modeled explicitly. /// - /// \param [out] EscapingSymbol A function might not free memory in general, + /// \param [out] EscapingSymbol A function might not free memory in general, /// but could be known to free a particular symbol. In this case, false is /// returned and the single escaping symbol is returned through the out /// parameter. @@ -357,20 +357,20 @@ private: Optional<CheckKind> getCheckIfTracked(CheckerContext &C, const Stmt *AllocDeallocStmt, bool IsALeakCheck = false) const; - Optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym, + Optional<CheckKind> getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck = false) const; ///@} static bool SummarizeValue(raw_ostream &os, SVal V); static bool SummarizeRegion(raw_ostream &os, const MemRegion *MR); - void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange Range, + void ReportBadFree(CheckerContext &C, SVal ArgVal, SourceRange Range, const Expr *DeallocExpr) const; void ReportFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const; void ReportMismatchedDealloc(CheckerContext &C, SourceRange Range, const Expr *DeallocExpr, const RefState *RS, SymbolRef Sym, bool OwnershipTransferred) const; - void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, - const Expr *DeallocExpr, + void ReportOffsetFree(CheckerContext &C, SVal ArgVal, SourceRange Range, + const Expr *DeallocExpr, const Expr *AllocExpr = nullptr) const; void ReportUseAfterFree(CheckerContext &C, SourceRange Range, SymbolRef Sym) const; @@ -392,7 +392,8 @@ private: /// The bug visitor which allows us to print extra diagnostics along the /// BugReport path. For example, showing the allocation site of the leaked /// region. - class MallocBugVisitor : public BugReporterVisitorImpl<MallocBugVisitor> { + class MallocBugVisitor final + : public BugReporterVisitorImpl<MallocBugVisitor> { protected: enum NotificationMode { Normal, @@ -414,8 +415,6 @@ private: MallocBugVisitor(SymbolRef S, bool isLeak = false) : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), IsLeak(isLeak) {} - ~MallocBugVisitor() override {} - void Profile(llvm::FoldingSetNodeID &ID) const override { static int X = 0; ID.AddPointer(&X); @@ -426,8 +425,8 @@ private: const Stmt *Stmt) { // Did not track -> allocated. Other state (released) -> allocated. return (Stmt && (isa<CallExpr>(Stmt) || isa<CXXNewExpr>(Stmt)) && - (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && - (!SPrev || !(SPrev->isAllocated() || + (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && + (!SPrev || !(SPrev->isAllocated() || SPrev->isAllocatedOfSizeZero()))); } @@ -509,13 +508,14 @@ private: REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) +REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef) -// A map from the freed symbol to the symbol representing the return value of +// A map from the freed symbol to the symbol representing the return value of // the free function. REGISTER_MAP_WITH_PROGRAMSTATE(FreeReturnValue, SymbolRef, SymbolRef) namespace { -class StopTrackingCallback : public SymbolVisitor { +class StopTrackingCallback final : public SymbolVisitor { ProgramStateRef state; public: StopTrackingCallback(ProgramStateRef st) : state(st) {} @@ -634,7 +634,7 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, return false; OverloadedOperatorKind Kind = FD->getOverloadedOperator(); - if (Kind != OO_New && Kind != OO_Array_New && + if (Kind != OO_New && Kind != OO_Array_New && Kind != OO_Delete && Kind != OO_Array_Delete) return false; @@ -799,8 +799,8 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { State = ProcessZeroAllocation(C, CE, 0, State); } else if (isStandardNewDelete(FD, C.getASTContext())) { // Process direct calls to operator new/new[]/delete/delete[] functions - // as distinct from new/new[]/delete/delete[] expressions that are - // processed by the checkPostStmt callbacks for CXXNewExpr and + // as distinct from new/new[]/delete/delete[] expressions that are + // processed by the checkPostStmt callbacks for CXXNewExpr and // CXXDeleteExpr. OverloadedOperatorKind K = FD->getOverloadedOperator(); if (K == OO_New) { @@ -870,7 +870,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C, assert(Arg); - Optional<DefinedSVal> DefArgVal = + Optional<DefinedSVal> DefArgVal = State->getSVal(Arg, C.getLocationContext()).getAs<DefinedSVal>(); if (!DefArgVal) @@ -882,7 +882,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C, DefinedSVal Zero = SvalBuilder.makeZeroVal(Arg->getType()).castAs<DefinedSVal>(); - std::tie(TrueState, FalseState) = + std::tie(TrueState, FalseState) = State->assume(SvalBuilder.evalEQ(State, *DefArgVal, Zero)); if (TrueState && !FalseState) { @@ -892,15 +892,19 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation(CheckerContext &C, return State; const RefState *RS = State->get<RegionState>(Sym); - if (!RS) - return State; // TODO: change to assert(RS); after realloc() will - // guarantee have a RegionState attached. - - if (!RS->isAllocated()) - return State; - - return TrueState->set<RegionState>(Sym, - RefState::getAllocatedOfSizeZero(RS)); + if (RS) { + if (RS->isAllocated()) + return TrueState->set<RegionState>(Sym, + RefState::getAllocatedOfSizeZero(RS)); + else + return State; + } else { + // Case of zero-size realloc. Historically 'realloc(ptr, 0)' is treated as + // 'free(ptr)' and the returned value from 'realloc(ptr, 0)' is not + // tracked. Add zero-reallocated Sym to the state to catch references + // to zero-allocated memory. + return TrueState->add<ReallocSizeZeroSymbols>(Sym); + } } // Assume the value is non-zero going forward. @@ -944,7 +948,7 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { return false; } -void MallocChecker::checkPostStmt(const CXXNewExpr *NE, +void MallocChecker::checkPostStmt(const CXXNewExpr *NE, CheckerContext &C) const { if (NE->getNumPlacementArgs()) @@ -961,17 +965,17 @@ void MallocChecker::checkPostStmt(const CXXNewExpr *NE, return; 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 + // 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 // existing binding. - State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray + State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray : AF_CXXNew); State = ProcessZeroAllocation(C, NE, 0, State); C.addTransition(State); } -void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE, +void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const { if (!ChecksEnabled[CK_NewDeleteChecker]) @@ -996,12 +1000,9 @@ static bool isKnownDeallocObjCMethodName(const ObjCMethodCall &Call) { // Ex: [NSData dataWithBytesNoCopy:bytes length:10]; // (...unless a 'freeWhenDone' parameter is false, but that's checked later.) StringRef FirstSlot = Call.getSelector().getNameForSlot(0); - if (FirstSlot == "dataWithBytesNoCopy" || - FirstSlot == "initWithBytesNoCopy" || - FirstSlot == "initWithCharactersNoCopy") - return true; - - return false; + return FirstSlot == "dataWithBytesNoCopy" || + FirstSlot == "initWithBytesNoCopy" || + FirstSlot == "initWithCharactersNoCopy"; } static Optional<bool> getFreeWhenDoneArg(const ObjCMethodCall &Call) { @@ -1038,7 +1039,7 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, ProgramStateRef MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr *Att, + const OwnershipAttr *Att, ProgramStateRef State) const { if (!State) return nullptr; @@ -1105,7 +1106,7 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, State = State->assume(extentMatchesSize, true); assert(State); } - + return MallocUpdateRefState(C, CE, State, Family); } @@ -1132,7 +1133,7 @@ ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C, ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, const CallExpr *CE, - const OwnershipAttr *Att, + const OwnershipAttr *Att, ProgramStateRef State) const { if (!State) return nullptr; @@ -1184,7 +1185,7 @@ static bool didPreviousFreeFail(ProgramStateRef State, return false; } -AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, +AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, const Stmt *S) const { if (!S) return AF_None; @@ -1229,14 +1230,14 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, return AF_None; } -bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, +bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, const Expr *E) const { if (const CallExpr *CE = dyn_cast<CallExpr>(E)) { // FIXME: This doesn't handle indirect calls. const FunctionDecl *FD = CE->getDirectCallee(); if (!FD) return false; - + os << *FD; if (!FD->isOverloadedOperator()) os << "()"; @@ -1253,14 +1254,14 @@ bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, } if (const CXXNewExpr *NE = dyn_cast<CXXNewExpr>(E)) { - os << "'" + os << "'" << getOperatorSpelling(NE->getOperatorNew()->getOverloadedOperator()) << "'"; return true; } if (const CXXDeleteExpr *DE = dyn_cast<CXXDeleteExpr>(E)) { - os << "'" + os << "'" << getOperatorSpelling(DE->getOperatorDelete()->getOverloadedOperator()) << "'"; return true; @@ -1283,7 +1284,7 @@ void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C, } } -void MallocChecker::printExpectedDeallocName(raw_ostream &os, +void MallocChecker::printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const { switch(Family) { case AF_Malloc: os << "free()"; return; @@ -1327,25 +1328,25 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, return nullptr; const MemRegion *R = ArgVal.getAsRegion(); - + // Nonlocs can't be freed, of course. // Non-region locations (labels and fixed addresses) also shouldn't be freed. if (!R) { ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr); return nullptr; } - + R = R->StripCasts(); - + // Blocks might show up as heap data, but should not be free()d if (isa<BlockDataRegion>(R)) { ReportBadFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr); return nullptr; } - + const MemSpaceRegion *MS = R->getMemorySpace(); - - // Parameters, locals, statics, globals, and memory returned by + + // Parameters, locals, statics, globals, and memory returned by // __builtin_alloca() shouldn't be freed. if (!(isa<UnknownSpaceRegion>(MS) || isa<HeapSpaceRegion>(MS))) { // FIXME: at the time this code was written, malloc() regions were @@ -1391,7 +1392,7 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, // If the pointer is allocated or escaped, but we are now trying to free it, // check that the call to free is proper. - } else if (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero() || + } else if (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero() || RsBase->isEscaped()) { // Check if an expected deallocation function matches the real one. @@ -1410,20 +1411,20 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, !Offset.hasSymbolicOffset() && Offset.getOffset() != 0) { const Expr *AllocExpr = cast<Expr>(RsBase->getStmt()); - ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, + ReportOffsetFree(C, ArgVal, ArgExpr->getSourceRange(), ParentExpr, AllocExpr); return nullptr; } } } - ReleasedAllocated = (RsBase != nullptr) && (RsBase->isAllocated() || + ReleasedAllocated = (RsBase != nullptr) && (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero()); // Clean out the info on previous call to free return info. State = State->remove<FreeReturnValue>(SymBase); - // Keep track of the return value. If it is NULL, we will know that free + // Keep track of the return value. If it is NULL, we will know that free // failed. if (ReturnsNullOnFailure) { SVal RetVal = C.getSVal(ParentExpr); @@ -1463,7 +1464,7 @@ MallocChecker::getCheckIfTracked(AllocationFamily Family, if (IsALeakCheck) { if (ChecksEnabled[CK_NewDeleteLeaksChecker]) return CK_NewDeleteLeaksChecker; - } + } else { if (ChecksEnabled[CK_NewDeleteChecker]) return CK_NewDeleteChecker; @@ -1488,6 +1489,9 @@ MallocChecker::getCheckIfTracked(CheckerContext &C, Optional<MallocChecker::CheckKind> MallocChecker::getCheckIfTracked(CheckerContext &C, SymbolRef Sym, bool IsALeakCheck) const { + if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) + return CK_MallocChecker; + const RefState *RS = C.getState()->get<RegionState>(Sym); assert(RS); return getCheckIfTracked(RS->getAllocationFamily(), IsALeakCheck); @@ -1502,7 +1506,7 @@ bool MallocChecker::SummarizeValue(raw_ostream &os, SVal V) { os << "the address of the label '" << Label->getLabel()->getName() << "'"; else return false; - + return true; } @@ -1526,7 +1530,7 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, return true; default: { const MemSpaceRegion *MS = MR->getMemorySpace(); - + if (isa<StackLocalsSpaceRegion>(MS)) { const VarRegion *VR = dyn_cast<VarRegion>(MR); const VarDecl *VD; @@ -1580,8 +1584,8 @@ bool MallocChecker::SummarizeRegion(raw_ostream &os, } } -void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, - SourceRange Range, +void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, + SourceRange Range, const Expr *DeallocExpr) const { if (!ChecksEnabled[CK_MallocChecker] && @@ -1593,7 +1597,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, if (!CheckKind.hasValue()) return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_BadFree[*CheckKind]) BT_BadFree[*CheckKind].reset( new BugType(CheckNames[*CheckKind], "Bad free", "Memory Error")); @@ -1610,7 +1614,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, os << "deallocator"; os << " is "; - bool Summarized = MR ? SummarizeRegion(os, MR) + bool Summarized = MR ? SummarizeRegion(os, MR) : SummarizeValue(os, ArgVal); if (Summarized) os << ", which is not memory allocated by "; @@ -1626,7 +1630,7 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, } } -void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, +void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, SourceRange Range) const { Optional<MallocChecker::CheckKind> CheckKind; @@ -1638,7 +1642,7 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, else return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_FreeAlloca[*CheckKind]) BT_FreeAlloca[*CheckKind].reset( new BugType(CheckNames[*CheckKind], "Free alloca()", "Memory Error")); @@ -1652,17 +1656,17 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, } } -void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, +void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, SourceRange Range, - const Expr *DeallocExpr, + const Expr *DeallocExpr, const RefState *RS, - SymbolRef Sym, + SymbolRef Sym, bool OwnershipTransferred) const { if (!ChecksEnabled[CK_MismatchedDeallocatorChecker]) return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_MismatchedDealloc) BT_MismatchedDealloc.reset( new BugType(CheckNames[CK_MismatchedDeallocatorChecker], @@ -1680,7 +1684,7 @@ void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, if (OwnershipTransferred) { if (printAllocDeallocName(DeallocOs, C, DeallocExpr)) os << DeallocOs.str() << " cannot"; - else + else os << "Cannot"; os << " take ownership of memory"; @@ -1721,7 +1725,7 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal, if (!CheckKind.hasValue()) return; - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; @@ -1775,7 +1779,7 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, if (!CheckKind.hasValue()) return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_UseFree[*CheckKind]) BT_UseFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Use-after-free", "Memory Error")); @@ -1791,7 +1795,7 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, } void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range, - bool Released, SymbolRef Sym, + bool Released, SymbolRef Sym, SymbolRef PrevSym) const { if (!ChecksEnabled[CK_MallocChecker] && @@ -1802,7 +1806,7 @@ void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range, if (!CheckKind.hasValue()) return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_DoubleFree[*CheckKind]) BT_DoubleFree[*CheckKind].reset( new BugType(CheckNames[*CheckKind], "Double free", "Memory Error")); @@ -1830,7 +1834,7 @@ void MallocChecker::ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const { if (!CheckKind.hasValue()) return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_DoubleDelete) BT_DoubleDelete.reset(new BugType(CheckNames[CK_NewDeleteChecker], "Double delete", "Memory Error")); @@ -1857,7 +1861,7 @@ void MallocChecker::ReportUseZeroAllocated(CheckerContext &C, if (!CheckKind.hasValue()) return; - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_UseZerroAllocated[*CheckKind]) BT_UseZerroAllocated[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Use of zero allocated", "Memory Error")); @@ -1921,7 +1925,7 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C, bool PrtIsNull = StatePtrIsNull && !StatePtrNotNull; bool SizeIsZero = StateSizeIsZero && !StateSizeNotZero; - // If the ptr is NULL and the size is not 0, the call is equivalent to + // If the ptr is NULL and the size is not 0, the call is equivalent to // malloc(size). if ( PrtIsNull && !SizeIsZero) { ProgramStateRef stateMalloc = MallocMemAux(C, CE, CE->getArg(1), @@ -1930,7 +1934,7 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C, } if (PrtIsNull && SizeIsZero) - return nullptr; + return State; // Get the from and to pointer symbols as in toPtr = realloc(fromPtr, size). assert(!PrtIsNull); @@ -1979,7 +1983,7 @@ ProgramStateRef MallocChecker::ReallocMem(CheckerContext &C, return nullptr; } -ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE, +ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE, ProgramStateRef State) { if (!State) return nullptr; @@ -1992,7 +1996,7 @@ ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE, SVal count = State->getSVal(CE->getArg(0), LCtx); SVal elementSize = State->getSVal(CE->getArg(1), LCtx); SVal TotalSize = svalBuilder.evalBinOp(State, BO_Mul, count, elementSize, - svalBuilder.getContext().getSizeType()); + svalBuilder.getContext().getSizeType()); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); return MallocMemAux(C, CE, TotalSize, zeroVal, State); @@ -2079,7 +2083,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, const ExplodedNode *AllocNode = nullptr; const MemRegion *Region = nullptr; std::tie(AllocNode, Region) = getAllocationSite(N, Sym, C); - + ProgramPoint P = AllocNode->getLocation(); const Stmt *AllocationStmt = nullptr; if (Optional<CallExitEnd> Exit = P.getAs<CallExitEnd>()) @@ -2128,7 +2132,7 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, } } - + // Cleanup the Realloc Pairs Map. ReallocPairsTy RP = state->get<ReallocPairs>(); for (ReallocPairsTy::iterator I = RP.begin(), E = RP.end(); I != E; ++I) { @@ -2151,10 +2155,12 @@ void MallocChecker::checkDeadSymbols(SymbolReaper &SymReaper, ExplodedNode *N = C.getPredecessor(); if (!Errors.empty()) { static CheckerProgramPointTag Tag("MallocChecker", "DeadSymbolsLeak"); - N = C.addTransition(C.getState(), C.getPredecessor(), &Tag); - for (SmallVectorImpl<SymbolRef>::iterator + N = C.generateNonFatalErrorNode(C.getState(), &Tag); + if (N) { + for (SmallVectorImpl<SymbolRef>::iterator I = Errors.begin(), E = Errors.end(); I != E; ++I) { - reportLeak(*I, N, C); + reportLeak(*I, N, C); + } } } @@ -2233,7 +2239,7 @@ void MallocChecker::checkPreStmt(const ReturnStmt *S, CheckerContext &C) const { } // TODO: Blocks should be either inlined or should call invalidate regions -// upon invocation. After that's in place, special casing here will not be +// upon invocation. After that's in place, special casing here will not be // needed. void MallocChecker::checkPostStmt(const BlockExpr *BE, CheckerContext &C) const { @@ -2292,10 +2298,14 @@ bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, void MallocChecker::checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { assert(Sym); - const RefState *RS = C.getState()->get<RegionState>(Sym); - if (RS && RS->isAllocatedOfSizeZero()) - ReportUseZeroAllocated(C, RS->getStmt()->getSourceRange(), Sym); + if (const RefState *RS = C.getState()->get<RegionState>(Sym)) { + if (RS->isAllocatedOfSizeZero()) + ReportUseZeroAllocated(C, RS->getStmt()->getSourceRange(), Sym); + } + else if (C.getState()->contains<ReallocSizeZeroSymbols>(Sym)) { + ReportUseZeroAllocated(C, S->getSourceRange(), Sym); + } } bool MallocChecker::checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const { @@ -2377,7 +2387,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( if (const ObjCMethodCall *Msg = dyn_cast<ObjCMethodCall>(Call)) { // If it's not a framework call, or if it takes a callback, assume it // can free memory. - if (!Call->isInSystemHeader() || Call->hasNonZeroCallbackArg()) + if (!Call->isInSystemHeader() || Call->argumentsMayEscape()) return true; // If it's a method we know about, handle it explicitly post-call. @@ -2447,7 +2457,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( StringRef FName = II->getName(); // White list the 'XXXNoCopy' CoreFoundation functions. - // We specifically check these before + // We specifically check these before if (FName.endswith("NoCopy")) { // Look for the deallocator argument. We know that the memory ownership // is not transferred only if the deallocator argument is @@ -2556,7 +2566,7 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State, if (EscapingSymbol && EscapingSymbol != sym) continue; - + if (const RefState *RS = State->get<RegionState>(sym)) { if ((RS->isAllocated() || RS->isAllocatedOfSizeZero()) && CheckRefState(RS)) { @@ -2703,7 +2713,7 @@ void ento::registerNewDeleteLeaksChecker(CheckerManager &mgr) { checker->ChecksEnabled[MallocChecker::CK_NewDeleteLeaksChecker] = true; checker->CheckNames[MallocChecker::CK_NewDeleteLeaksChecker] = mgr.getCurrentCheckName(); - // We currently treat NewDeleteLeaks checker as a subchecker of NewDelete + // We currently treat NewDeleteLeaks checker as a subchecker of NewDelete // checker. if (!checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker]) checker->ChecksEnabled[MallocChecker::CK_NewDeleteChecker] = true; diff --git a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp index e913479..99ba90d 100644 --- a/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocOverflowSecurityChecker.cpp @@ -23,19 +23,22 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/APSInt.h" #include "llvm/ADT/SmallVector.h" using namespace clang; using namespace ento; +using llvm::APInt; +using llvm::APSInt; namespace { struct MallocOverflowCheck { const BinaryOperator *mulop; const Expr *variable; + APSInt maxVal; - MallocOverflowCheck (const BinaryOperator *m, const Expr *v) - : mulop(m), variable (v) - {} + MallocOverflowCheck(const BinaryOperator *m, const Expr *v, APSInt val) + : mulop(m), variable(v), maxVal(val) {} }; class MallocOverflowSecurityChecker : public Checker<check::ASTCodeBody> { @@ -54,6 +57,11 @@ public: }; } // end anonymous namespace +// Return true for computations which evaluate to zero: e.g., mult by 0. +static inline bool EvaluatesToZero(APSInt &Val, BinaryOperatorKind op) { + return (op == BO_Mul) && (Val == 0); +} + void MallocOverflowSecurityChecker::CheckMallocArgument( SmallVectorImpl<MallocOverflowCheck> &PossibleMallocOverflows, const Expr *TheArgument, @@ -64,13 +72,14 @@ void MallocOverflowSecurityChecker::CheckMallocArgument( Reject anything that applies to the variable: an explicit cast, conditional expression, an operation that could reduce the range of the result, or anything too complicated :-). */ - const Expr * e = TheArgument; + const Expr *e = TheArgument; const BinaryOperator * mulop = nullptr; + APSInt maxVal; for (;;) { + maxVal = 0; e = e->IgnoreParenImpCasts(); - if (isa<BinaryOperator>(e)) { - const BinaryOperator * binop = dyn_cast<BinaryOperator>(e); + if (const BinaryOperator *binop = dyn_cast<BinaryOperator>(e)) { BinaryOperatorKind opc = binop->getOpcode(); // TODO: ignore multiplications by 1, reject if multiplied by 0. if (mulop == nullptr && opc == BO_Mul) @@ -80,12 +89,18 @@ void MallocOverflowSecurityChecker::CheckMallocArgument( const Expr *lhs = binop->getLHS(); const Expr *rhs = binop->getRHS(); - if (rhs->isEvaluatable(Context)) + if (rhs->isEvaluatable(Context)) { e = lhs; - else if ((opc == BO_Add || opc == BO_Mul) - && lhs->isEvaluatable(Context)) + maxVal = rhs->EvaluateKnownConstInt(Context); + if (EvaluatesToZero(maxVal, opc)) + return; + } else if ((opc == BO_Add || opc == BO_Mul) && + lhs->isEvaluatable(Context)) { + maxVal = lhs->EvaluateKnownConstInt(Context); + if (EvaluatesToZero(maxVal, opc)) + return; e = rhs; - else + } else return; } else if (isa<DeclRefExpr>(e) || isa<MemberExpr>(e)) @@ -103,7 +118,7 @@ void MallocOverflowSecurityChecker::CheckMallocArgument( // TODO: Could push this into the innermost scope where 'e' is // defined, rather than the whole function. - PossibleMallocOverflows.push_back(MallocOverflowCheck(mulop, e)); + PossibleMallocOverflows.push_back(MallocOverflowCheck(mulop, e, maxVal)); } namespace { @@ -126,33 +141,84 @@ private: return false; } - void CheckExpr(const Expr *E_p) { - const Expr *E = E_p->IgnoreParenImpCasts(); + const Decl *getDecl(const DeclRefExpr *DR) { return DR->getDecl(); } + + const Decl *getDecl(const MemberExpr *ME) { return ME->getMemberDecl(); } + template <typename T1> + void Erase(const T1 *DR, std::function<bool(theVecType::iterator)> pred) { theVecType::iterator i = toScanFor.end(); theVecType::iterator e = toScanFor.begin(); - - if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) { - const Decl * EdreD = DR->getDecl(); - while (i != e) { - --i; - if (const DeclRefExpr *DR_i = dyn_cast<DeclRefExpr>(i->variable)) { - if (DR_i->getDecl() == EdreD) - i = toScanFor.erase(i); - } + while (i != e) { + --i; + if (const T1 *DR_i = dyn_cast<T1>(i->variable)) { + if ((getDecl(DR_i) == getDecl(DR)) && pred(i)) + i = toScanFor.erase(i); } } + } + + void CheckExpr(const Expr *E_p) { + auto PredTrue = [](theVecType::iterator) -> bool { return true; }; + const Expr *E = E_p->IgnoreParenImpCasts(); + if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) + Erase<DeclRefExpr>(DR, PredTrue); else if (const auto *ME = dyn_cast<MemberExpr>(E)) { - // No points-to analysis, just look at the member - const Decl *EmeMD = ME->getMemberDecl(); - while (i != e) { - --i; - if (const auto *ME_i = dyn_cast<MemberExpr>(i->variable)) { - if (ME_i->getMemberDecl() == EmeMD) - i = toScanFor.erase (i); - } + Erase<MemberExpr>(ME, PredTrue); + } + } + + // Check if the argument to malloc is assigned a value + // which cannot cause an overflow. + // e.g., malloc (mul * x) and, + // case 1: mul = <constant value> + // case 2: mul = a/b, where b > x + void CheckAssignmentExpr(BinaryOperator *AssignEx) { + bool assignKnown = false; + bool numeratorKnown = false, denomKnown = false; + APSInt denomVal; + denomVal = 0; + + // Erase if the multiplicand was assigned a constant value. + const Expr *rhs = AssignEx->getRHS(); + if (rhs->isEvaluatable(Context)) + assignKnown = true; + + // Discard the report if the multiplicand was assigned a value, + // that can never overflow after multiplication. e.g., the assignment + // is a division operator and the denominator is > other multiplicand. + const Expr *rhse = rhs->IgnoreParenImpCasts(); + if (const BinaryOperator *BOp = dyn_cast<BinaryOperator>(rhse)) { + if (BOp->getOpcode() == BO_Div) { + const Expr *denom = BOp->getRHS()->IgnoreParenImpCasts(); + if (denom->EvaluateAsInt(denomVal, Context)) + denomKnown = true; + const Expr *numerator = BOp->getLHS()->IgnoreParenImpCasts(); + if (numerator->isEvaluatable(Context)) + numeratorKnown = true; } } + if (!assignKnown && !denomKnown) + return; + auto denomExtVal = denomVal.getExtValue(); + + // Ignore negative denominator. + if (denomExtVal < 0) + return; + + const Expr *lhs = AssignEx->getLHS(); + const Expr *E = lhs->IgnoreParenImpCasts(); + + auto pred = [assignKnown, numeratorKnown, + denomExtVal](theVecType::iterator i) { + return assignKnown || + (numeratorKnown && (denomExtVal >= i->maxVal.getExtValue())); + }; + + if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(E)) + Erase<DeclRefExpr>(DR, pred); + else if (const auto *ME = dyn_cast<MemberExpr>(E)) + Erase<MemberExpr>(ME, pred); } public: @@ -162,11 +228,13 @@ private: const Expr * rhs = E->getRHS(); // Ignore comparisons against zero, since they generally don't // protect against an overflow. - if (!isIntZeroExpr(lhs) && ! isIntZeroExpr(rhs)) { + if (!isIntZeroExpr(lhs) && !isIntZeroExpr(rhs)) { CheckExpr(lhs); CheckExpr(rhs); } } + if (E->isAssignmentOp()) + CheckAssignmentExpr(E); EvaluatedExprVisitor<CheckOverflowOps>::VisitBinaryOperator(E); } @@ -243,12 +311,12 @@ void MallocOverflowSecurityChecker::checkASTCodeBody(const Decl *D, const FunctionDecl *FD = TheCall->getDirectCallee(); if (!FD) - return; + continue; // Get the name of the callee. If it's a builtin, strip off the prefix. IdentifierInfo *FnInfo = FD->getIdentifier(); if (!FnInfo) - return; + continue; if (FnInfo->isStr ("malloc") || FnInfo->isStr ("_MALLOC")) { if (TheCall->getNumArgs() == 1) diff --git a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp index fb07484..80a3fbe 100644 --- a/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -143,20 +143,20 @@ static bool typesCompatible(ASTContext &C, QualType A, QualType B) { while (true) { A = A.getCanonicalType(); B = B.getCanonicalType(); - + if (A.getTypePtr() == B.getTypePtr()) return true; - + if (const PointerType *ptrA = A->getAs<PointerType>()) if (const PointerType *ptrB = B->getAs<PointerType>()) { A = ptrA->getPointeeType(); B = ptrB->getPointeeType(); continue; } - + break; } - + return false; } diff --git a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index d23708e..0e78947 100644 --- a/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -11,7 +11,7 @@ // about subpar uses of NSAutoreleasePool. Note that while the check itself // (in its current form) could be written as a flow-insensitive check, in // can be potentially enhanced in the future with flow-sensitive information. -// It is also a good example of the CheckerVisitor interface. +// It is also a good example of the CheckerVisitor interface. // //===----------------------------------------------------------------------===// @@ -48,7 +48,7 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg, const ObjCInterfaceDecl *OD = msg.getReceiverInterface(); if (!OD) - return; + return; if (!OD->getIdentifier()->isStr("NSAutoreleasePool")) return; @@ -62,7 +62,7 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg, BT.reset(new BugType(this, "Use -drain instead of -release", "API Upgrade (Apple)")); - ExplodedNode *N = C.addTransition(); + ExplodedNode *N = C.generateNonFatalErrorNode(); if (!N) { assert(0); return; diff --git a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index c351c6e..dab068b 100644 --- a/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -58,7 +58,7 @@ void NSErrorMethodChecker::checkASTDecl(const ObjCMethodDecl *D, return; if (!II) - II = &D->getASTContext().Idents.get("NSError"); + II = &D->getASTContext().Idents.get("NSError"); bool hasNSError = false; for (const auto *I : D->params()) { @@ -105,7 +105,7 @@ void CFErrorFunctionChecker::checkASTDecl(const FunctionDecl *D, return; if (!II) - II = &D->getASTContext().Idents.get("CFErrorRef"); + II = &D->getASTContext().Idents.get("CFErrorRef"); bool hasCFError = false; for (auto I : D->params()) { diff --git a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp index ba82d1d..c1deade 100644 --- a/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NoReturnFunctionChecker.cpp @@ -66,6 +66,7 @@ void NoReturnFunctionChecker::checkPostCall(const CallEvent &CE, .Case("assfail", true) .Case("db_error", true) .Case("__assert", true) + .Case("__assert2", true) // For the purpose of static analysis, we do not care that // this MSVC function will return if the user decides to continue. .Case("_wassert", true) @@ -81,7 +82,7 @@ void NoReturnFunctionChecker::checkPostCall(const CallEvent &CE, } if (BuildSinks) - C.generateSink(); + C.generateSink(C.getState(), C.getPredecessor()); } void NoReturnFunctionChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, @@ -90,7 +91,7 @@ void NoReturnFunctionChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, if (const ObjCMethodDecl *MD = Msg.getDecl()) { MD = MD->getCanonicalDecl(); if (MD->hasAttr<AnalyzerNoReturnAttr>()) { - C.generateSink(); + C.generateSink(C.getState(), C.getPredecessor()); return; } } @@ -136,7 +137,7 @@ void NoReturnFunctionChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, } // If we got here, it's one of the messages we care about. - C.generateSink(); + C.generateSink(C.getState(), C.getPredecessor()); } void ento::registerNoReturnFunctionChecker(CheckerManager &mgr) { diff --git a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index 73f8087..1f82ab9 100644 --- a/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -28,7 +28,7 @@ using namespace ento; namespace { class NonNullParamChecker - : public Checker< check::PreCall > { + : public Checker< check::PreCall, EventDispatcher<ImplicitNullDerefEvent> > { mutable std::unique_ptr<BugType> BTAttrNonNull; mutable std::unique_ptr<BugType> BTNullRefArg; @@ -139,26 +139,34 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, ProgramStateRef stateNotNull, stateNull; std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); - if (stateNull && !stateNotNull) { - // Generate an error node. Check for a null node in case - // we cache out. - if (ExplodedNode *errorNode = C.generateSink(stateNull)) { + if (stateNull) { + if (!stateNotNull) { + // Generate an error node. Check for a null node in case + // we cache out. + if (ExplodedNode *errorNode = C.generateErrorNode(stateNull)) { - std::unique_ptr<BugReport> R; - if (haveAttrNonNull) - R = genReportNullAttrNonNull(errorNode, ArgE); - else if (haveRefTypeParam) - R = genReportReferenceToNullPointer(errorNode, ArgE); + std::unique_ptr<BugReport> R; + if (haveAttrNonNull) + R = genReportNullAttrNonNull(errorNode, ArgE); + else if (haveRefTypeParam) + R = genReportReferenceToNullPointer(errorNode, ArgE); - // Highlight the range of the argument that was null. - R->addRange(Call.getArgSourceRange(idx)); + // Highlight the range of the argument that was null. + R->addRange(Call.getArgSourceRange(idx)); - // Emit the bug report. - C.emitReport(std::move(R)); - } + // Emit the bug report. + C.emitReport(std::move(R)); + } - // Always return. Either we cached out or we just emitted an error. - return; + // Always return. Either we cached out or we just emitted an error. + return; + } + if (ExplodedNode *N = C.generateSink(stateNull, C.getPredecessor())) { + ImplicitNullDerefEvent event = { + V, false, N, &C.getBugReporter(), + /*IsDirectDereference=*/haveRefTypeParam}; + dispatchEvent(event); + } } // If a pointer value passed the check we should assume that it is diff --git a/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp new file mode 100644 index 0000000..bb86ea4 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -0,0 +1,1066 @@ +//== Nullabilityhecker.cpp - Nullability checker ----------------*- C++ -*--==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This checker tries to find nullability violations. There are several kinds of +// possible violations: +// * Null pointer is passed to a pointer which has a _Nonnull type. +// * Null pointer is returned from a function which has a _Nonnull return type. +// * Nullable pointer is passed to a pointer which has a _Nonnull type. +// * Nullable pointer is returned from a function which has a _Nonnull return +// type. +// * Nullable pointer is dereferenced. +// +// This checker propagates the nullability information of the pointers and looks +// for the patterns that are described above. Explicit casts are trusted and are +// considered a way to suppress false positives for this checker. The other way +// to suppress warnings would be to add asserts or guarding if statements to the +// code. In addition to the nullability propagation this checker also uses some +// heuristics to suppress potential false positives. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "llvm/Support/Path.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 "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" + +using namespace clang; +using namespace ento; + +namespace { +// Do not reorder! The getMostNullable method relies on the order. +// Optimization: Most pointers expected to be unspecified. When a symbol has an +// unspecified or nonnull type non of the rules would indicate any problem for +// that symbol. For this reason only nullable and contradicted nullability are +// stored for a symbol. When a symbol is already contradicted, it can not be +// casted back to nullable. +enum class Nullability : char { + Contradicted, // Tracked nullability is contradicted by an explicit cast. Do + // not report any nullability related issue for this symbol. + // This nullability is propagated agressively to avoid false + // positive results. See the comment on getMostNullable method. + Nullable, + Unspecified, + Nonnull +}; + +/// Returns the most nullable nullability. This is used for message expressions +/// like [reciever method], where the nullability of this expression is either +/// the nullability of the receiver or the nullability of the return type of the +/// method, depending on which is more nullable. Contradicted is considered to +/// be the most nullable, to avoid false positive results. +Nullability getMostNullable(Nullability Lhs, Nullability Rhs) { + return static_cast<Nullability>( + std::min(static_cast<char>(Lhs), static_cast<char>(Rhs))); +} + +const char *getNullabilityString(Nullability Nullab) { + switch (Nullab) { + case Nullability::Contradicted: + return "contradicted"; + case Nullability::Nullable: + return "nullable"; + case Nullability::Unspecified: + return "unspecified"; + case Nullability::Nonnull: + return "nonnull"; + } + llvm_unreachable("Unexpected enumeration."); + return ""; +} + +// These enums are used as an index to ErrorMessages array. +enum class ErrorKind : int { + NilAssignedToNonnull, + NilPassedToNonnull, + NilReturnedToNonnull, + NullableAssignedToNonnull, + NullableReturnedToNonnull, + NullableDereferenced, + NullablePassedToNonnull +}; + +const char *const ErrorMessages[] = { + "Null is assigned to a pointer which is expected to have non-null value", + "Null passed to a callee that requires a non-null argument", + "Null is returned from a function that is expected to return a non-null " + "value", + "Nullable pointer is assigned to a pointer which is expected to have " + "non-null value", + "Nullable pointer is returned from a function that is expected to return a " + "non-null value", + "Nullable pointer is dereferenced", + "Nullable pointer is passed to a callee that requires a non-null argument"}; + +class NullabilityChecker + : public Checker<check::Bind, check::PreCall, check::PreStmt<ReturnStmt>, + check::PostCall, check::PostStmt<ExplicitCastExpr>, + check::PostObjCMessage, check::DeadSymbols, + check::Event<ImplicitNullDerefEvent>> { + mutable std::unique_ptr<BugType> BT; + +public: + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + void checkPostStmt(const ExplicitCastExpr *CE, CheckerContext &C) const; + void checkPreStmt(const ReturnStmt *S, CheckerContext &C) const; + void checkPostObjCMessage(const ObjCMethodCall &M, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; + void checkEvent(ImplicitNullDerefEvent Event) const; + + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; + + struct NullabilityChecksFilter { + DefaultBool CheckNullPassedToNonnull; + DefaultBool CheckNullReturnedFromNonnull; + DefaultBool CheckNullableDereferenced; + DefaultBool CheckNullablePassedToNonnull; + DefaultBool CheckNullableReturnedFromNonnull; + + CheckName CheckNameNullPassedToNonnull; + CheckName CheckNameNullReturnedFromNonnull; + CheckName CheckNameNullableDereferenced; + CheckName CheckNameNullablePassedToNonnull; + CheckName CheckNameNullableReturnedFromNonnull; + }; + + NullabilityChecksFilter Filter; + // When set to false no nullability information will be tracked in + // NullabilityMap. It is possible to catch errors like passing a null pointer + // to a callee that expects nonnull argument without the information that is + // stroed in the NullabilityMap. This is an optimization. + DefaultBool NeedTracking; + +private: + class NullabilityBugVisitor + : public BugReporterVisitorImpl<NullabilityBugVisitor> { + public: + NullabilityBugVisitor(const MemRegion *M) : Region(M) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Region); + } + + PathDiagnosticPiece *VisitNode(const ExplodedNode *N, + const ExplodedNode *PrevN, + BugReporterContext &BRC, + BugReport &BR) override; + + private: + // The tracked region. + const MemRegion *Region; + }; + + /// When any of the nonnull arguments of the analyzed function is null, do not + /// report anything and turn off the check. + /// + /// When \p SuppressPath is set to true, no more bugs will be reported on this + /// path by this checker. + void reportBugIfPreconditionHolds(ErrorKind Error, ExplodedNode *N, + const MemRegion *Region, CheckerContext &C, + const Stmt *ValueExpr = nullptr, + bool SuppressPath = false) const; + + void reportBug(ErrorKind Error, ExplodedNode *N, const MemRegion *Region, + BugReporter &BR, const Stmt *ValueExpr = nullptr) const { + if (!BT) + BT.reset(new BugType(this, "Nullability", "Memory error")); + const char *Msg = ErrorMessages[static_cast<int>(Error)]; + std::unique_ptr<BugReport> R(new BugReport(*BT, Msg, N)); + if (Region) { + R->markInteresting(Region); + R->addVisitor(llvm::make_unique<NullabilityBugVisitor>(Region)); + } + if (ValueExpr) { + R->addRange(ValueExpr->getSourceRange()); + if (Error == ErrorKind::NilAssignedToNonnull || + Error == ErrorKind::NilPassedToNonnull || + Error == ErrorKind::NilReturnedToNonnull) + bugreporter::trackNullOrUndefValue(N, ValueExpr, *R); + } + BR.emitReport(std::move(R)); + } + + /// If an SVal wraps a region that should be tracked, it will return a pointer + /// to the wrapped region. Otherwise it will return a nullptr. + const SymbolicRegion *getTrackRegion(SVal Val, + bool CheckSuperRegion = false) const; +}; + +class NullabilityState { +public: + NullabilityState(Nullability Nullab, const Stmt *Source = nullptr) + : Nullab(Nullab), Source(Source) {} + + const Stmt *getNullabilitySource() const { return Source; } + + Nullability getValue() const { return Nullab; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(static_cast<char>(Nullab)); + ID.AddPointer(Source); + } + + void print(raw_ostream &Out) const { + Out << getNullabilityString(Nullab) << "\n"; + } + +private: + Nullability Nullab; + // Source is the expression which determined the nullability. For example in a + // message like [nullable nonnull_returning] has nullable nullability, because + // the receiver is nullable. Here the receiver will be the source of the + // nullability. This is useful information when the diagnostics are generated. + const Stmt *Source; +}; + +bool operator==(NullabilityState Lhs, NullabilityState Rhs) { + return Lhs.getValue() == Rhs.getValue() && + Lhs.getNullabilitySource() == Rhs.getNullabilitySource(); +} + +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(NullabilityMap, const MemRegion *, + NullabilityState) + +// If the nullability precondition of a function is violated, we should not +// report nullability related issues on that path. For this reason once a +// precondition is not met on a path, this checker will be esentially turned off +// for the rest of the analysis. We do not want to generate a sink node however, +// so this checker would not lead to reduced coverage. +REGISTER_TRAIT_WITH_PROGRAMSTATE(PreconditionViolated, bool) + +enum class NullConstraint { IsNull, IsNotNull, Unknown }; + +static NullConstraint getNullConstraint(DefinedOrUnknownSVal Val, + ProgramStateRef State) { + ConditionTruthVal Nullness = State->isNull(Val); + if (Nullness.isConstrainedFalse()) + return NullConstraint::IsNotNull; + if (Nullness.isConstrainedTrue()) + return NullConstraint::IsNull; + return NullConstraint::Unknown; +} + +const SymbolicRegion * +NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { + if (!NeedTracking) + return nullptr; + + auto RegionSVal = Val.getAs<loc::MemRegionVal>(); + if (!RegionSVal) + return nullptr; + + const MemRegion *Region = RegionSVal->getRegion(); + + if (CheckSuperRegion) { + if (auto FieldReg = Region->getAs<FieldRegion>()) + return dyn_cast<SymbolicRegion>(FieldReg->getSuperRegion()); + if (auto ElementReg = Region->getAs<ElementRegion>()) + return dyn_cast<SymbolicRegion>(ElementReg->getSuperRegion()); + } + + return dyn_cast<SymbolicRegion>(Region); +} + +PathDiagnosticPiece *NullabilityChecker::NullabilityBugVisitor::VisitNode( + const ExplodedNode *N, const ExplodedNode *PrevN, BugReporterContext &BRC, + BugReport &BR) { + ProgramStateRef State = N->getState(); + ProgramStateRef StatePrev = PrevN->getState(); + + const NullabilityState *TrackedNullab = State->get<NullabilityMap>(Region); + const NullabilityState *TrackedNullabPrev = + StatePrev->get<NullabilityMap>(Region); + if (!TrackedNullab) + return nullptr; + + if (TrackedNullabPrev && + TrackedNullabPrev->getValue() == TrackedNullab->getValue()) + return nullptr; + + // Retrieve the associated statement. + const Stmt *S = TrackedNullab->getNullabilitySource(); + if (!S) { + ProgramPoint ProgLoc = N->getLocation(); + if (Optional<StmtPoint> SP = ProgLoc.getAs<StmtPoint>()) { + S = SP->getStmt(); + } + } + + if (!S) + return nullptr; + + std::string InfoText = + (llvm::Twine("Nullability '") + + getNullabilityString(TrackedNullab->getValue()) + "' is infered") + .str(); + + // Generate the extra diagnostic. + PathDiagnosticLocation Pos(S, BRC.getSourceManager(), + N->getLocationContext()); + return new PathDiagnosticEventPiece(Pos, InfoText, true, nullptr); +} + +static Nullability getNullabilityAnnotation(QualType Type) { + const auto *AttrType = Type->getAs<AttributedType>(); + if (!AttrType) + return Nullability::Unspecified; + if (AttrType->getAttrKind() == AttributedType::attr_nullable) + return Nullability::Nullable; + else if (AttrType->getAttrKind() == AttributedType::attr_nonnull) + return Nullability::Nonnull; + return Nullability::Unspecified; +} + +template <typename ParamVarDeclRange> +static bool +checkParamsForPreconditionViolation(const ParamVarDeclRange &Params, + ProgramStateRef State, + const LocationContext *LocCtxt) { + for (const auto *ParamDecl : Params) { + if (ParamDecl->isParameterPack()) + break; + + if (getNullabilityAnnotation(ParamDecl->getType()) != Nullability::Nonnull) + continue; + + auto RegVal = State->getLValue(ParamDecl, LocCtxt) + .template getAs<loc::MemRegionVal>(); + if (!RegVal) + continue; + + auto ParamValue = State->getSVal(RegVal->getRegion()) + .template getAs<DefinedOrUnknownSVal>(); + if (!ParamValue) + continue; + + if (getNullConstraint(*ParamValue, State) == NullConstraint::IsNull) { + return true; + } + } + return false; +} + +static bool checkPreconditionViolation(ProgramStateRef State, ExplodedNode *N, + CheckerContext &C) { + if (State->get<PreconditionViolated>()) + return true; + + const LocationContext *LocCtxt = C.getLocationContext(); + const Decl *D = LocCtxt->getDecl(); + if (!D) + return false; + + if (const auto *BlockD = dyn_cast<BlockDecl>(D)) { + if (checkParamsForPreconditionViolation(BlockD->parameters(), State, + LocCtxt)) { + if (!N->isSink()) + C.addTransition(State->set<PreconditionViolated>(true), N); + return true; + } + return false; + } + + if (const auto *FuncDecl = dyn_cast<FunctionDecl>(D)) { + if (checkParamsForPreconditionViolation(FuncDecl->parameters(), State, + LocCtxt)) { + if (!N->isSink()) + C.addTransition(State->set<PreconditionViolated>(true), N); + return true; + } + return false; + } + return false; +} + +void NullabilityChecker::reportBugIfPreconditionHolds( + ErrorKind Error, ExplodedNode *N, const MemRegion *Region, + CheckerContext &C, const Stmt *ValueExpr, bool SuppressPath) const { + ProgramStateRef OriginalState = N->getState(); + + if (checkPreconditionViolation(OriginalState, N, C)) + return; + if (SuppressPath) { + OriginalState = OriginalState->set<PreconditionViolated>(true); + N = C.addTransition(OriginalState, N); + } + + reportBug(Error, N, Region, C.getBugReporter(), ValueExpr); +} + +/// 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(), + E = Nullabilities.end(); + I != E; ++I) { + const auto *Region = I->first->getAs<SymbolicRegion>(); + assert(Region && "Non-symbolic region is tracked."); + if (SR.isDead(Region->getSymbol())) { + State = State->remove<NullabilityMap>(I->first); + } + } + // When one of the nonnull arguments are constrained to be null, nullability + // preconditions are violated. It is not enough to check this only when we + // actually report an error, because at that time interesting symbols might be + // reaped. + if (checkPreconditionViolation(State, C.getPredecessor(), C)) + return; + C.addTransition(State); +} + +/// This callback triggers when a pointer is dereferenced and the analyzer does +/// not know anything about the value of that pointer. When that pointer is +/// nullable, this code emits a warning. +void NullabilityChecker::checkEvent(ImplicitNullDerefEvent Event) const { + if (Event.SinkNode->getState()->get<PreconditionViolated>()) + return; + + const MemRegion *Region = + getTrackRegion(Event.Location, /*CheckSuperregion=*/true); + if (!Region) + return; + + ProgramStateRef State = Event.SinkNode->getState(); + const NullabilityState *TrackedNullability = + State->get<NullabilityMap>(Region); + + if (!TrackedNullability) + return; + + if (Filter.CheckNullableDereferenced && + TrackedNullability->getValue() == Nullability::Nullable) { + BugReporter &BR = *Event.BR; + // Do not suppress errors on defensive code paths, because dereferencing + // a nullable pointer is always an error. + if (Event.IsDirectDereference) + reportBug(ErrorKind::NullableDereferenced, Event.SinkNode, Region, BR); + else + reportBug(ErrorKind::NullablePassedToNonnull, Event.SinkNode, Region, BR); + } +} + +/// This method check when nullable pointer or null value is returned from a +/// function that has nonnull return type. +/// +/// TODO: when nullability preconditons are violated, it is ok to violate the +/// nullability postconditons (i.e.: when one of the nonnull parameters are null +/// this check should not report any nullability related issue). +void NullabilityChecker::checkPreStmt(const ReturnStmt *S, + CheckerContext &C) const { + auto RetExpr = S->getRetValue(); + if (!RetExpr) + return; + + if (!RetExpr->getType()->isAnyPointerType()) + return; + + ProgramStateRef State = C.getState(); + if (State->get<PreconditionViolated>()) + return; + + auto RetSVal = + State->getSVal(S, C.getLocationContext()).getAs<DefinedOrUnknownSVal>(); + if (!RetSVal) + return; + + AnalysisDeclContext *DeclCtxt = + C.getLocationContext()->getAnalysisDeclContext(); + const FunctionType *FuncType = DeclCtxt->getDecl()->getFunctionType(); + if (!FuncType) + return; + + NullConstraint Nullness = getNullConstraint(*RetSVal, State); + + Nullability RequiredNullability = + getNullabilityAnnotation(FuncType->getReturnType()); + + // If the returned value is null but the type of the expression + // generating it is nonnull then we will suppress the diagnostic. + // This enables explicit suppression when returning a nil literal in a + // function with a _Nonnull return type: + // return (NSString * _Nonnull)0; + Nullability RetExprTypeLevelNullability = + getNullabilityAnnotation(RetExpr->getType()); + + if (Filter.CheckNullReturnedFromNonnull && + Nullness == NullConstraint::IsNull && + RetExprTypeLevelNullability != Nullability::Nonnull && + RequiredNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullReturnedFromNonnull"); + ExplodedNode *N = C.generateErrorNode(State, &Tag); + if (!N) + return; + reportBugIfPreconditionHolds(ErrorKind::NilReturnedToNonnull, N, nullptr, C, + RetExpr); + return; + } + + const MemRegion *Region = getTrackRegion(*RetSVal); + if (!Region) + return; + + const NullabilityState *TrackedNullability = + State->get<NullabilityMap>(Region); + if (TrackedNullability) { + Nullability TrackedNullabValue = TrackedNullability->getValue(); + if (Filter.CheckNullableReturnedFromNonnull && + Nullness != NullConstraint::IsNotNull && + TrackedNullabValue == Nullability::Nullable && + RequiredNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullableReturnedFromNonnull"); + ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); + reportBugIfPreconditionHolds(ErrorKind::NullableReturnedToNonnull, N, + Region, C); + } + return; + } + if (RequiredNullability == Nullability::Nullable) { + State = State->set<NullabilityMap>(Region, + NullabilityState(RequiredNullability, + S)); + C.addTransition(State); + } +} + +/// This callback warns when a nullable pointer or a null value is passed to a +/// function that expects its argument to be nonnull. +void NullabilityChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + if (!Call.getDecl()) + return; + + ProgramStateRef State = C.getState(); + if (State->get<PreconditionViolated>()) + return; + + ProgramStateRef OrigState = State; + + unsigned Idx = 0; + for (const ParmVarDecl *Param : Call.parameters()) { + if (Param->isParameterPack()) + break; + + const Expr *ArgExpr = nullptr; + if (Idx < Call.getNumArgs()) + ArgExpr = Call.getArgExpr(Idx); + auto ArgSVal = Call.getArgSVal(Idx++).getAs<DefinedOrUnknownSVal>(); + if (!ArgSVal) + continue; + + if (!Param->getType()->isAnyPointerType() && + !Param->getType()->isReferenceType()) + continue; + + NullConstraint Nullness = getNullConstraint(*ArgSVal, State); + + Nullability RequiredNullability = + getNullabilityAnnotation(Param->getType()); + Nullability ArgExprTypeLevelNullability = + getNullabilityAnnotation(ArgExpr->getType()); + + if (Filter.CheckNullPassedToNonnull && Nullness == NullConstraint::IsNull && + ArgExprTypeLevelNullability != Nullability::Nonnull && + RequiredNullability == Nullability::Nonnull) { + ExplodedNode *N = C.generateErrorNode(State); + if (!N) + return; + reportBugIfPreconditionHolds(ErrorKind::NilPassedToNonnull, N, nullptr, C, + ArgExpr); + return; + } + + const MemRegion *Region = getTrackRegion(*ArgSVal); + if (!Region) + continue; + + const NullabilityState *TrackedNullability = + State->get<NullabilityMap>(Region); + + if (TrackedNullability) { + if (Nullness == NullConstraint::IsNotNull || + TrackedNullability->getValue() != Nullability::Nullable) + continue; + + if (Filter.CheckNullablePassedToNonnull && + RequiredNullability == Nullability::Nonnull) { + ExplodedNode *N = C.addTransition(State); + reportBugIfPreconditionHolds(ErrorKind::NullablePassedToNonnull, N, + Region, C, ArgExpr, /*SuppressPath=*/true); + return; + } + if (Filter.CheckNullableDereferenced && + Param->getType()->isReferenceType()) { + ExplodedNode *N = C.addTransition(State); + reportBugIfPreconditionHolds(ErrorKind::NullableDereferenced, N, Region, + C, ArgExpr, /*SuppressPath=*/true); + return; + } + continue; + } + // No tracked nullability yet. + if (ArgExprTypeLevelNullability != Nullability::Nullable) + continue; + State = State->set<NullabilityMap>( + Region, NullabilityState(ArgExprTypeLevelNullability, ArgExpr)); + } + if (State != OrigState) + C.addTransition(State); +} + +/// Suppress the nullability warnings for some functions. +void NullabilityChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + auto Decl = Call.getDecl(); + if (!Decl) + return; + // ObjC Messages handles in a different callback. + if (Call.getKind() == CE_ObjCMessage) + return; + const FunctionType *FuncType = Decl->getFunctionType(); + if (!FuncType) + return; + QualType ReturnType = FuncType->getReturnType(); + if (!ReturnType->isAnyPointerType()) + return; + ProgramStateRef State = C.getState(); + if (State->get<PreconditionViolated>()) + return; + + const MemRegion *Region = getTrackRegion(Call.getReturnValue()); + if (!Region) + return; + + // 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())); + if (llvm::sys::path::filename(FilePath).startswith("CG")) { + State = State->set<NullabilityMap>(Region, Nullability::Contradicted); + C.addTransition(State); + return; + } + + const NullabilityState *TrackedNullability = + State->get<NullabilityMap>(Region); + + if (!TrackedNullability && + getNullabilityAnnotation(ReturnType) == Nullability::Nullable) { + State = State->set<NullabilityMap>(Region, Nullability::Nullable); + C.addTransition(State); + } +} + +static Nullability getReceiverNullability(const ObjCMethodCall &M, + ProgramStateRef State) { + if (M.isReceiverSelfOrSuper()) { + // For super and super class receivers we assume that the receiver is + // nonnull. + return Nullability::Nonnull; + } + // Otherwise look up nullability in the state. + SVal Receiver = M.getReceiverSVal(); + if (auto DefOrUnknown = Receiver.getAs<DefinedOrUnknownSVal>()) { + // If the receiver is constrained to be nonnull, assume that it is nonnull + // regardless of its type. + NullConstraint Nullness = getNullConstraint(*DefOrUnknown, State); + if (Nullness == NullConstraint::IsNotNull) + return Nullability::Nonnull; + } + auto ValueRegionSVal = Receiver.getAs<loc::MemRegionVal>(); + if (ValueRegionSVal) { + const MemRegion *SelfRegion = ValueRegionSVal->getRegion(); + assert(SelfRegion); + + const NullabilityState *TrackedSelfNullability = + State->get<NullabilityMap>(SelfRegion); + if (TrackedSelfNullability) + return TrackedSelfNullability->getValue(); + } + return Nullability::Unspecified; +} + +/// Calculate the nullability of the result of a message expr based on the +/// nullability of the receiver, the nullability of the return value, and the +/// constraints. +void NullabilityChecker::checkPostObjCMessage(const ObjCMethodCall &M, + CheckerContext &C) const { + auto Decl = M.getDecl(); + if (!Decl) + return; + QualType RetType = Decl->getReturnType(); + if (!RetType->isAnyPointerType()) + return; + + ProgramStateRef State = C.getState(); + if (State->get<PreconditionViolated>()) + return; + + const MemRegion *ReturnRegion = getTrackRegion(M.getReturnValue()); + if (!ReturnRegion) + return; + + auto Interface = Decl->getClassInterface(); + auto Name = Interface ? Interface->getName() : ""; + // In order to reduce the noise in the diagnostics generated by this checker, + // some framework and programming style based heuristics are used. These + // heuristics are for Cocoa APIs which have NS prefix. + if (Name.startswith("NS")) { + // Developers rely on dynamic invariants such as an item should be available + // in a collection, or a collection is not empty often. Those invariants can + // not be inferred by any static analysis tool. To not to bother the users + // with too many false positives, every item retrieval function should be + // ignored for collections. The instance methods of dictionaries in Cocoa + // are either item retrieval related or not interesting nullability wise. + // Using this fact, to keep the code easier to read just ignore the return + // value of every instance method of dictionaries. + if (M.isInstanceMessage() && Name.find("Dictionary") != StringRef::npos) { + State = + State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); + C.addTransition(State); + return; + } + // For similar reasons ignore some methods of Cocoa arrays. + StringRef FirstSelectorSlot = M.getSelector().getNameForSlot(0); + if (Name.find("Array") != StringRef::npos && + (FirstSelectorSlot == "firstObject" || + FirstSelectorSlot == "lastObject")) { + State = + State->set<NullabilityMap>(ReturnRegion, Nullability::Contradicted); + C.addTransition(State); + return; + } + + // Encoding related methods of string should not fail when lossless + // encodings are used. Using lossless encodings is so frequent that ignoring + // this class of methods reduced the emitted diagnostics by about 30% on + // some projects (and all of that was false positives). + if (Name.find("String") != StringRef::npos) { + for (auto Param : M.parameters()) { + if (Param->getName() == "encoding") { + State = State->set<NullabilityMap>(ReturnRegion, + Nullability::Contradicted); + C.addTransition(State); + return; + } + } + } + } + + const ObjCMessageExpr *Message = M.getOriginExpr(); + Nullability SelfNullability = getReceiverNullability(M, State); + + const NullabilityState *NullabilityOfReturn = + State->get<NullabilityMap>(ReturnRegion); + + if (NullabilityOfReturn) { + // When we have a nullability tracked for the return value, the nullability + // of the expression will be the most nullable of the receiver and the + // return value. + Nullability RetValTracked = NullabilityOfReturn->getValue(); + Nullability ComputedNullab = + getMostNullable(RetValTracked, SelfNullability); + if (ComputedNullab != RetValTracked && + ComputedNullab != Nullability::Unspecified) { + const Stmt *NullabilitySource = + ComputedNullab == RetValTracked + ? NullabilityOfReturn->getNullabilitySource() + : Message->getInstanceReceiver(); + State = State->set<NullabilityMap>( + ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource)); + C.addTransition(State); + } + return; + } + + // No tracked information. Use static type information for return value. + Nullability RetNullability = getNullabilityAnnotation(RetType); + + // Properties might be computed. For this reason the static analyzer creates a + // new symbol each time an unknown property is read. To avoid false pozitives + // do not treat unknown properties as nullable, even when they explicitly + // marked nullable. + if (M.getMessageKind() == OCM_PropertyAccess && !C.wasInlined) + RetNullability = Nullability::Nonnull; + + Nullability ComputedNullab = getMostNullable(RetNullability, SelfNullability); + if (ComputedNullab == Nullability::Nullable) { + const Stmt *NullabilitySource = ComputedNullab == RetNullability + ? Message + : Message->getInstanceReceiver(); + State = State->set<NullabilityMap>( + ReturnRegion, NullabilityState(ComputedNullab, NullabilitySource)); + C.addTransition(State); + } +} + +/// Explicit casts are trusted. If there is a disagreement in the nullability +/// annotations in the destination and the source or '0' is casted to nonnull +/// track the value as having contraditory nullability. This will allow users to +/// suppress warnings. +void NullabilityChecker::checkPostStmt(const ExplicitCastExpr *CE, + CheckerContext &C) const { + QualType OriginType = CE->getSubExpr()->getType(); + QualType DestType = CE->getType(); + if (!OriginType->isAnyPointerType()) + return; + if (!DestType->isAnyPointerType()) + return; + + ProgramStateRef State = C.getState(); + if (State->get<PreconditionViolated>()) + return; + + Nullability DestNullability = getNullabilityAnnotation(DestType); + + // No explicit nullability in the destination type, so this cast does not + // change the nullability. + if (DestNullability == Nullability::Unspecified) + return; + + auto RegionSVal = + State->getSVal(CE, C.getLocationContext()).getAs<DefinedOrUnknownSVal>(); + const MemRegion *Region = getTrackRegion(*RegionSVal); + if (!Region) + return; + + // When 0 is converted to nonnull mark it as contradicted. + if (DestNullability == Nullability::Nonnull) { + NullConstraint Nullness = getNullConstraint(*RegionSVal, State); + if (Nullness == NullConstraint::IsNull) { + State = State->set<NullabilityMap>(Region, Nullability::Contradicted); + C.addTransition(State); + return; + } + } + + const NullabilityState *TrackedNullability = + State->get<NullabilityMap>(Region); + + if (!TrackedNullability) { + if (DestNullability != Nullability::Nullable) + return; + State = State->set<NullabilityMap>(Region, + NullabilityState(DestNullability, CE)); + C.addTransition(State); + return; + } + + if (TrackedNullability->getValue() != DestNullability && + TrackedNullability->getValue() != Nullability::Contradicted) { + State = State->set<NullabilityMap>(Region, Nullability::Contradicted); + C.addTransition(State); + } +} + +/// For a given statement performing a bind, attempt to syntactically +/// match the expression resulting in the bound value. +static const Expr * matchValueExprForBind(const Stmt *S) { + // For `x = e` the value expression is the right-hand side. + if (auto *BinOp = dyn_cast<BinaryOperator>(S)) { + if (BinOp->getOpcode() == BO_Assign) + return BinOp->getRHS(); + } + + // For `int x = e` the value expression is the initializer. + if (auto *DS = dyn_cast<DeclStmt>(S)) { + if (DS->isSingleDecl()) { + auto *VD = dyn_cast<VarDecl>(DS->getSingleDecl()); + if (!VD) + return nullptr; + + if (const Expr *Init = VD->getInit()) + return Init; + } + } + + return nullptr; +} + +/// Returns true if \param S is a DeclStmt for a local variable that +/// ObjC automated reference counting initialized with zero. +static bool isARCNilInitializedLocal(CheckerContext &C, const Stmt *S) { + // We suppress diagnostics for ARC zero-initialized _Nonnull locals. This + // prevents false positives when a _Nonnull local variable cannot be + // initialized with an initialization expression: + // NSString * _Nonnull s; // no-warning + // @autoreleasepool { + // s = ... + // } + // + // FIXME: We should treat implicitly zero-initialized _Nonnull locals as + // uninitialized in Sema's UninitializedValues analysis to warn when a use of + // the zero-initialized definition will unexpectedly yield nil. + + // Locals are only zero-initialized when automated reference counting + // is turned on. + if (!C.getASTContext().getLangOpts().ObjCAutoRefCount) + return false; + + auto *DS = dyn_cast<DeclStmt>(S); + if (!DS || !DS->isSingleDecl()) + return false; + + auto *VD = dyn_cast<VarDecl>(DS->getSingleDecl()); + if (!VD) + return false; + + // Sema only zero-initializes locals with ObjCLifetimes. + if(!VD->getType().getQualifiers().hasObjCLifetime()) + return false; + + const Expr *Init = VD->getInit(); + assert(Init && "ObjC local under ARC without initializer"); + + // Return false if the local is explicitly initialized (e.g., with '= nil'). + if (!isa<ImplicitValueInitExpr>(Init)) + return false; + + return true; +} + +/// Propagate the nullability information through binds and warn when nullable +/// pointer or null symbol is assigned to a pointer with a nonnull type. +void NullabilityChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + const TypedValueRegion *TVR = + dyn_cast_or_null<TypedValueRegion>(L.getAsRegion()); + if (!TVR) + return; + + QualType LocType = TVR->getValueType(); + if (!LocType->isAnyPointerType()) + return; + + ProgramStateRef State = C.getState(); + if (State->get<PreconditionViolated>()) + return; + + auto ValDefOrUnknown = V.getAs<DefinedOrUnknownSVal>(); + if (!ValDefOrUnknown) + return; + + NullConstraint RhsNullness = getNullConstraint(*ValDefOrUnknown, State); + + Nullability ValNullability = Nullability::Unspecified; + if (SymbolRef Sym = ValDefOrUnknown->getAsSymbol()) + ValNullability = getNullabilityAnnotation(Sym->getType()); + + Nullability LocNullability = getNullabilityAnnotation(LocType); + if (Filter.CheckNullPassedToNonnull && + RhsNullness == NullConstraint::IsNull && + ValNullability != Nullability::Nonnull && + LocNullability == Nullability::Nonnull && + !isARCNilInitializedLocal(C, S)) { + static CheckerProgramPointTag Tag(this, "NullPassedToNonnull"); + ExplodedNode *N = C.generateErrorNode(State, &Tag); + if (!N) + return; + + const Stmt *ValueExpr = matchValueExprForBind(S); + if (!ValueExpr) + ValueExpr = S; + + reportBugIfPreconditionHolds(ErrorKind::NilAssignedToNonnull, N, nullptr, C, + ValueExpr); + return; + } + // Intentionally missing case: '0' is bound to a reference. It is handled by + // the DereferenceChecker. + + const MemRegion *ValueRegion = getTrackRegion(*ValDefOrUnknown); + if (!ValueRegion) + return; + + const NullabilityState *TrackedNullability = + State->get<NullabilityMap>(ValueRegion); + + if (TrackedNullability) { + if (RhsNullness == NullConstraint::IsNotNull || + TrackedNullability->getValue() != Nullability::Nullable) + return; + if (Filter.CheckNullablePassedToNonnull && + LocNullability == Nullability::Nonnull) { + static CheckerProgramPointTag Tag(this, "NullablePassedToNonnull"); + ExplodedNode *N = C.addTransition(State, C.getPredecessor(), &Tag); + reportBugIfPreconditionHolds(ErrorKind::NullableAssignedToNonnull, N, + ValueRegion, C); + } + return; + } + + const auto *BinOp = dyn_cast<BinaryOperator>(S); + + if (ValNullability == Nullability::Nullable) { + // Trust the static information of the value more than the static + // information on the location. + const Stmt *NullabilitySource = BinOp ? BinOp->getRHS() : S; + State = State->set<NullabilityMap>( + ValueRegion, NullabilityState(ValNullability, NullabilitySource)); + C.addTransition(State); + return; + } + + if (LocNullability == Nullability::Nullable) { + const Stmt *NullabilitySource = BinOp ? BinOp->getLHS() : S; + State = State->set<NullabilityMap>( + ValueRegion, NullabilityState(LocNullability, NullabilitySource)); + C.addTransition(State); + } +} + +void NullabilityChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + NullabilityMapTy B = State->get<NullabilityMap>(); + + if (B.isEmpty()) + return; + + Out << Sep << NL; + + for (NullabilityMapTy::iterator I = B.begin(), E = B.end(); I != E; ++I) { + Out << I->first << " : "; + I->second.print(Out); + Out << NL; + } +} + +#define REGISTER_CHECKER(name, trackingRequired) \ + void ento::register##name##Checker(CheckerManager &mgr) { \ + NullabilityChecker *checker = mgr.registerChecker<NullabilityChecker>(); \ + checker->Filter.Check##name = true; \ + checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ + checker->NeedTracking = checker->NeedTracking || trackingRequired; \ + } + +// The checks are likely to be turned on by default and it is possible to do +// them without tracking any nullability related information. As an optimization +// no nullability information will be tracked when only these two checks are +// enables. +REGISTER_CHECKER(NullPassedToNonnull, false) +REGISTER_CHECKER(NullReturnedFromNonnull, false) + +REGISTER_CHECKER(NullableDereferenced, true) +REGISTER_CHECKER(NullablePassedToNonnull, true) +REGISTER_CHECKER(NullableReturnedFromNonnull, true) diff --git a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index a7b92b4..cbaa5c2 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -43,7 +43,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, // Uninitialized value used for the mutex? if (V.getAs<UndefinedVal>()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT_undef) BT_undef.reset(new BuiltinBug(this, "Uninitialized value used as mutex " "for @synchronized")); @@ -66,7 +66,7 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, if (!notNullState) { // Generate an error node. This isn't a sink since // a null mutex just means no synchronization occurs. - if (ExplodedNode *N = C.addTransition(nullState)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode(nullState)) { if (!BT_null) BT_null.reset(new BuiltinBug( this, "Nil value used as mutex for @synchronized() " diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp index 224251b..b10ec84 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersASTChecker.cpp @@ -66,9 +66,8 @@ class WalkAST : public StmtVisitor<WalkAST> { // The type must be an array/pointer type. // This could be a null constant, which is allowed. - if (E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull)) - return true; - return false; + return static_cast<bool>( + E->isNullPointerConstant(ASTC, Expr::NPC_ValueDependentIsNull)); } public: diff --git a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index 53e1598..0203d79 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -133,13 +133,13 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, if (IdxVal.isUnknownOrUndef()) return; DefinedSVal Idx = IdxVal.castAs<DefinedSVal>(); - + // Now, check if 'Idx in [0, Size-1]'. const QualType T = IdxExpr->getType(); ProgramStateRef StInBound = State->assumeInBound(Idx, *Size, true, T); ProgramStateRef StOutBound = State->assumeInBound(Idx, *Size, false, T); if (StOutBound && !StInBound) { - ExplodedNode *N = C.generateSink(StOutBound); + ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return; initBugType(); diff --git a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index 016cb14..32a1adb 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -49,7 +49,7 @@ public: DoesCallSuper = true; // Recurse if we didn't find the super call yet. - return !DoesCallSuper; + return !DoesCallSuper; } bool DoesCallSuper; @@ -59,7 +59,7 @@ private: }; //===----------------------------------------------------------------------===// -// ObjCSuperCallChecker +// ObjCSuperCallChecker //===----------------------------------------------------------------------===// class ObjCSuperCallChecker : public Checker< @@ -88,7 +88,7 @@ private: /// \param[out] SuperclassName On return, the found superclass name. bool ObjCSuperCallChecker::isCheckableClass(const ObjCImplementationDecl *D, StringRef &SuperclassName) const { - const ObjCInterfaceDecl *ID = D->getClassInterface(); + const ObjCInterfaceDecl *ID = D->getClassInterface()->getSuperClass(); for ( ; ID ; ID = ID->getSuperClass()) { SuperclassName = ID->getIdentifier()->getName(); @@ -202,7 +202,7 @@ void ObjCSuperCallChecker::checkASTDecl(const ObjCImplementationDecl *D, SmallString<320> Buf; llvm::raw_svector_ostream os(Buf); - os << "The '" << S.getAsString() + os << "The '" << S.getAsString() << "' instance method in " << SuperclassName.str() << " subclass '" << *D << "' is missing a [super " << S.getAsString() << "] call"; diff --git a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 93b0553..ffa3a27 100644 --- a/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -145,15 +145,15 @@ void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C, const char *errorStr) const { if (!E) return; - + if (!C.getState()->get<CalledInit>()) return; - + if (!isInvalidSelf(E, C)) return; - + // Generate an error node. - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; @@ -177,12 +177,12 @@ void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, if (isInitMessage(Msg)) { // Tag the return value as the result of an initializer. ProgramStateRef state = C.getState(); - + // FIXME this really should be context sensitive, where we record // the current stack frame (for IPA). Also, we need to clean this // value out when we return from this method. state = state->set<CalledInit>(true); - + SVal V = state->getSVal(Msg.getOriginExpr(), C.getLocationContext()); addSelfFlag(state, V, SelfFlag_InitRes, C); return; @@ -318,7 +318,7 @@ void ObjCSelfInitChecker::checkBind(SVal loc, SVal val, const Stmt *S, CheckerContext &C) const { // Allow assignment of anything to self. Self is a local variable in the // initializer, so it is legal to assign anything to it, like results of - // static functions/method calls. After self is assigned something we cannot + // static functions/method calls. After self is assigned something we cannot // reason about, stop enforcing the rules. // (Only continue checking if the assigned value should be treated as self.) if ((isSelfVar(loc, C)) && @@ -404,15 +404,12 @@ static bool shouldRunOnFunctionOrMethod(const NamedDecl *ND) { if (II == NSObjectII) break; } - if (!ID) - return false; - - return true; + return ID != nullptr; } /// \brief Returns true if the location is 'self'. static bool isSelfVar(SVal location, CheckerContext &C) { - AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext(); + AnalysisDeclContext *analCtx = C.getCurrentAnalysisDeclContext(); if (!analCtx->getSelfDecl()) return false; if (!location.getAs<loc::MemRegionVal>()) diff --git a/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp new file mode 100644 index 0000000..8ce3735 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -0,0 +1,314 @@ +//=======- PaddingChecker.cpp ------------------------------------*- C++ -*-==// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file defines a checker that checks for padding that could be +// removed by re-ordering members. +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/AST/CharUnits.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/RecordLayout.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/MathExtras.h" +#include "llvm/Support/raw_ostream.h" +#include <numeric> + +using namespace clang; +using namespace ento; + +namespace { +class PaddingChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> { +private: + mutable std::unique_ptr<BugType> PaddingBug; + mutable int64_t AllowedPad; + mutable BugReporter *BR; + +public: + void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, + BugReporter &BRArg) const { + BR = &BRArg; + AllowedPad = + MGR.getAnalyzerOptions().getOptionAsInteger("AllowedPad", 24, this); + assert(AllowedPad >= 0 && "AllowedPad option should be non-negative"); + + // The calls to checkAST* from AnalysisConsumer don't + // visit template instantiations or lambda classes. We + // want to visit those, so we make our own RecursiveASTVisitor. + struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> { + const PaddingChecker *Checker; + bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } + explicit LocalVisitor(const PaddingChecker *Checker) : Checker(Checker) {} + bool VisitRecordDecl(const RecordDecl *RD) { + Checker->visitRecord(RD); + return true; + } + bool VisitVarDecl(const VarDecl *VD) { + Checker->visitVariable(VD); + return true; + } + // TODO: Visit array new and mallocs for arrays. + }; + + LocalVisitor visitor(this); + visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); + } + + /// \brief Look for records of overly padded types. If padding * + /// PadMultiplier exceeds AllowedPad, then generate a report. + /// PadMultiplier is used to share code with the array padding + /// checker. + void visitRecord(const RecordDecl *RD, uint64_t PadMultiplier = 1) const { + if (shouldSkipDecl(RD)) + return; + + auto &ASTContext = RD->getASTContext(); + const ASTRecordLayout &RL = ASTContext.getASTRecordLayout(RD); + assert(llvm::isPowerOf2_64(RL.getAlignment().getQuantity())); + + CharUnits BaselinePad = calculateBaselinePad(RD, ASTContext, RL); + if (BaselinePad.isZero()) + return; + CharUnits OptimalPad = calculateOptimalPad(RD, ASTContext, RL); + + CharUnits DiffPad = PadMultiplier * (BaselinePad - OptimalPad); + if (DiffPad.getQuantity() <= AllowedPad) { + assert(!DiffPad.isNegative() && "DiffPad should not be negative"); + // There is not enough excess padding to trigger a warning. + return; + } + reportRecord(RD, BaselinePad, OptimalPad); + } + + /// \brief Look for arrays of overly padded types. If the padding of the + /// array type exceeds AllowedPad, then generate a report. + void visitVariable(const VarDecl *VD) const { + const ArrayType *ArrTy = VD->getType()->getAsArrayTypeUnsafe(); + if (ArrTy == nullptr) + return; + uint64_t Elts = 0; + if (const ConstantArrayType *CArrTy = dyn_cast<ConstantArrayType>(ArrTy)) + Elts = CArrTy->getSize().getZExtValue(); + if (Elts == 0) + return; + const RecordType *RT = ArrTy->getElementType()->getAs<RecordType>(); + if (RT == nullptr) + return; + + // TODO: Recurse into the fields and base classes to see if any + // of those have excess padding. + visitRecord(RT->getDecl(), Elts); + } + + bool shouldSkipDecl(const RecordDecl *RD) const { + auto Location = RD->getLocation(); + // If the construct doesn't have a source file, then it's not something + // we want to diagnose. + if (!Location.isValid()) + return true; + SrcMgr::CharacteristicKind Kind = + BR->getSourceManager().getFileCharacteristic(Location); + // Throw out all records that come from system headers. + if (Kind != SrcMgr::C_User) + return true; + + // 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) + return true; + // Virtual bases are complicated, skipping those for now. + if (CXXRD->getNumVBases() != 0) + return true; + // Can't layout a template, so skip it. We do still layout the + // instantiations though. + if (CXXRD->getTypeForDecl()->isDependentType()) + return true; + if (CXXRD->getTypeForDecl()->isInstantiationDependentType()) + return true; + } + auto IsTrickyField = [](const FieldDecl *FD) -> bool { + // Bitfield layout is hard. + if (FD->isBitField()) + return true; + + // Variable length arrays are tricky too. + QualType Ty = FD->getType(); + if (Ty->isIncompleteArrayType()) + return true; + return false; + }; + + if (std::any_of(RD->field_begin(), RD->field_end(), IsTrickyField)) + return true; + return false; + } + + static CharUnits calculateBaselinePad(const RecordDecl *RD, + const ASTContext &ASTContext, + const ASTRecordLayout &RL) { + CharUnits PaddingSum; + CharUnits Offset = ASTContext.toCharUnitsFromBits(RL.getFieldOffset(0)); + for (const auto &FD : RD->fields()) { + // This checker only cares about the padded size of the + // field, and not the data size. If the field is a record + // with tail padding, then we won't put that number in our + // total because reordering fields won't fix that problem. + CharUnits FieldSize = ASTContext.getTypeSizeInChars(FD->getType()); + auto FieldOffsetBits = RL.getFieldOffset(FD->getFieldIndex()); + CharUnits FieldOffset = ASTContext.toCharUnitsFromBits(FieldOffsetBits); + PaddingSum += (FieldOffset - Offset); + Offset = FieldOffset + FieldSize; + } + PaddingSum += RL.getSize() - Offset; + return PaddingSum; + } + + /// Optimal padding overview: + /// 1. Find a close approximation to where we can place our first field. + /// This will usually be at offset 0. + /// 2. Try to find the best field that can legally be placed at the current + /// offset. + /// a. "Best" is the largest alignment that is legal, but smallest size. + /// This is to account for overly aligned types. + /// 3. If no fields can fit, pad by rounding the current offset up to the + /// smallest alignment requirement of our fields. Measure and track the + // amount of padding added. Go back to 2. + /// 4. Increment the current offset by the size of the chosen field. + /// 5. Remove the chosen field from the set of future possibilities. + /// 6. Go back to 2 if there are still unplaced fields. + /// 7. Add tail padding by rounding the current offset up to the structure + /// alignment. Track the amount of padding added. + + static CharUnits calculateOptimalPad(const RecordDecl *RD, + const ASTContext &ASTContext, + const ASTRecordLayout &RL) { + struct CharUnitPair { + CharUnits Align; + CharUnits Size; + bool operator<(const CharUnitPair &RHS) const { + // Order from small alignments to large alignments, + // then large sizes to small sizes. + return std::make_pair(Align, -Size) < + std::make_pair(RHS.Align, -RHS.Size); + } + }; + SmallVector<CharUnitPair, 20> Fields; + auto GatherSizesAndAlignments = [](const FieldDecl *FD) { + CharUnitPair RetVal; + auto &Ctx = FD->getASTContext(); + std::tie(RetVal.Size, RetVal.Align) = + Ctx.getTypeInfoInChars(FD->getType()); + assert(llvm::isPowerOf2_64(RetVal.Align.getQuantity())); + if (auto Max = FD->getMaxAlignment()) + RetVal.Align = std::max(Ctx.toCharUnitsFromBits(Max), RetVal.Align); + return RetVal; + }; + std::transform(RD->field_begin(), RD->field_end(), + std::back_inserter(Fields), GatherSizesAndAlignments); + std::sort(Fields.begin(), Fields.end()); + + // 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 + // could pack more bytes in to a base class's tail padding. + CharUnits NewOffset = ASTContext.toCharUnitsFromBits(RL.getFieldOffset(0)); + CharUnits NewPad; + + while (!Fields.empty()) { + unsigned TrailingZeros = + llvm::countTrailingZeros((unsigned long long)NewOffset.getQuantity()); + // If NewOffset is zero, then countTrailingZeros will be 64. Shifting + // 64 will overflow our unsigned long long. Shifting 63 will turn + // our long long (and CharUnits internal type) negative. So shift 62. + long long CurAlignmentBits = 1ull << (std::min)(TrailingZeros, 62u); + CharUnits CurAlignment = CharUnits::fromQuantity(CurAlignmentBits); + CharUnitPair InsertPoint = {CurAlignment, CharUnits::Zero()}; + auto CurBegin = Fields.begin(); + auto CurEnd = Fields.end(); + + // In the typical case, this will find the last element + // of the vector. We won't find a middle element unless + // we started on a poorly aligned address or have an overly + // aligned field. + auto Iter = std::upper_bound(CurBegin, CurEnd, InsertPoint); + if (Iter != CurBegin) { + // We found a field that we can layout with the current alignment. + --Iter; + NewOffset += Iter->Size; + Fields.erase(Iter); + } else { + // We are poorly aligned, and we need to pad in order to layout another + // field. Round up to at least the smallest field alignment that we + // currently have. + CharUnits NextOffset = NewOffset.RoundUpToAlignment(Fields[0].Align); + NewPad += NextOffset - NewOffset; + NewOffset = NextOffset; + } + } + // Calculate tail padding. + CharUnits NewSize = NewOffset.RoundUpToAlignment(RL.getAlignment()); + NewPad += NewSize - NewOffset; + return NewPad; + } + + void reportRecord(const RecordDecl *RD, CharUnits BaselinePad, + CharUnits TargetPad) const { + if (!PaddingBug) + PaddingBug = + llvm::make_unique<BugType>(this, "Excessive Padding", "Performance"); + + SmallString<100> Buf; + llvm::raw_svector_ostream Os(Buf); + + Os << "Excessive padding in '"; + Os << QualType::getAsString(RD->getTypeForDecl(), Qualifiers()) << "'"; + + if (auto *TSD = dyn_cast<ClassTemplateSpecializationDecl>(RD)) { + // TODO: make this show up better in the console output and in + // the HTML. Maybe just make it show up in HTML like the path + // diagnostics show. + SourceLocation ILoc = TSD->getPointOfInstantiation(); + if (ILoc.isValid()) + Os << " instantiated here: " + << ILoc.printToString(BR->getSourceManager()); + } + + Os << " (" << BaselinePad.getQuantity() << " padding bytes, where " + << TargetPad.getQuantity() << " is optimal). Consider reordering " + << "the fields or adding explicit padding members."; + + PathDiagnosticLocation CELoc = + PathDiagnosticLocation::create(RD, BR->getSourceManager()); + + auto Report = llvm::make_unique<BugReport>(*PaddingBug, Os.str(), CELoc); + Report->setDeclWithIssue(RD); + Report->addRange(RD->getSourceRange()); + + BR->emitReport(std::move(Report)); + } +}; +} + +void ento::registerPaddingChecker(CheckerManager &Mgr) { + Mgr.registerChecker<PaddingChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 8063124..e336967 100644 --- a/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -22,7 +22,7 @@ using namespace clang; using namespace ento; namespace { -class PointerArithChecker +class PointerArithChecker : public Checker< check::PreStmt<BinaryOperator> > { mutable std::unique_ptr<BuiltinBug> BT; @@ -48,10 +48,10 @@ void PointerArithChecker::checkPreStmt(const BinaryOperator *B, // If pointer arithmetic is done on variables of non-array type, this often // means behavior rely on memory organization, which is dangerous. - if (isa<VarRegion>(LR) || isa<CodeTextRegion>(LR) || + if (isa<VarRegion>(LR) || isa<CodeTextRegion>(LR) || isa<CompoundLiteralRegion>(LR)) { - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { if (!BT) BT.reset( new BuiltinBug(this, "Dangerous pointer arithmetic", diff --git a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index cf1f88a..2d33ebc 100644 --- a/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -8,7 +8,7 @@ //===----------------------------------------------------------------------===// // // This files defines PointerSubChecker, a builtin checker that checks for -// pointer subtractions on two pointers pointing to different memory chunks. +// pointer subtractions on two pointers pointing to different memory chunks. // This check corresponds to CWE-469. // //===----------------------------------------------------------------------===// @@ -23,7 +23,7 @@ using namespace clang; using namespace ento; namespace { -class PointerSubChecker +class PointerSubChecker : public Checker< check::PreStmt<BinaryOperator> > { mutable std::unique_ptr<BuiltinBug> BT; @@ -60,7 +60,7 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, if (isa<SymbolicRegion>(BaseLR) || isa<SymbolicRegion>(BaseRR)) return; - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { if (!BT) BT.reset( new BuiltinBug(this, "Pointer subtraction", diff --git a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index 4209017..28a4a08 100644 --- a/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -32,9 +32,9 @@ private: LockState(Kind K) : K(K) {} public: - static LockState getLocked(void) { return LockState(Locked); } - static LockState getUnlocked(void) { return LockState(Unlocked); } - static LockState getDestroyed(void) { return LockState(Destroyed); } + static LockState getLocked() { return LockState(Locked); } + static LockState getUnlocked() { return LockState(Unlocked); } + static LockState getDestroyed() { return LockState(Destroyed); } bool operator==(const LockState &X) const { return K == X.K; @@ -62,10 +62,10 @@ class PthreadLockChecker : public Checker< check::PostStmt<CallExpr> > { }; public: void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; - + void AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, bool isTryLock, enum LockingSemantics semantics) const; - + void ReleaseLock(CheckerContext &C, const CallExpr *CE, SVal lock) const; void DestroyLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const; void InitLock(CheckerContext &C, const CallExpr *CE, SVal Lock) const; @@ -96,7 +96,7 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE, false, PthreadSemantics); else if (FName == "lck_mtx_lock" || FName == "lck_rw_lock_exclusive" || - FName == "lck_rw_lock_shared") + FName == "lck_rw_lock_shared") AcquireLock(C, CE, state->getSVal(CE->getArg(0), LCtx), false, XNUSemantics); else if (FName == "pthread_mutex_trylock" || @@ -124,17 +124,17 @@ void PthreadLockChecker::checkPostStmt(const CallExpr *CE, void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, SVal lock, bool isTryLock, enum LockingSemantics semantics) const { - + const MemRegion *lockR = lock.getAsRegion(); if (!lockR) return; - + ProgramStateRef state = C.getState(); - + SVal X = state->getSVal(CE, C.getLocationContext()); if (X.isUnknownOrUndef()) return; - + DefinedSVal retVal = X.castAs<DefinedSVal>(); if (const LockState *LState = state->get<LockMap>(lockR)) { @@ -142,7 +142,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, if (!BT_doublelock) BT_doublelock.reset(new BugType(this, "Double locking", "Lock checker")); - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto report = llvm::make_unique<BugReport>( @@ -183,8 +183,8 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, assert((semantics == XNUSemantics) && "Unknown locking semantics"); lockSucc = state; } - - // Record that the lock was acquired. + + // Record that the lock was acquired. lockSucc = lockSucc->add<LockSet>(lockR); lockSucc = lockSucc->set<LockMap>(lockR, LockState::getLocked()); C.addTransition(lockSucc); @@ -196,7 +196,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, const MemRegion *lockR = lock.getAsRegion(); if (!lockR) return; - + ProgramStateRef state = C.getState(); if (const LockState *LState = state->get<LockMap>(lockR)) { @@ -204,7 +204,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, if (!BT_doubleunlock) BT_doubleunlock.reset(new BugType(this, "Double unlocking", "Lock checker")); - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto Report = llvm::make_unique<BugReport>( @@ -227,7 +227,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, if (firstLockR != lockR) { if (!BT_lor) BT_lor.reset(new BugType(this, "Lock order reversal", "Lock checker")); - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto report = llvm::make_unique<BugReport>( @@ -272,7 +272,7 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, if (!BT_destroylock) BT_destroylock.reset(new BugType(this, "Destroy invalid lock", "Lock checker")); - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto Report = llvm::make_unique<BugReport>(*BT_destroylock, Message, N); @@ -307,7 +307,7 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, if (!BT_initlock) BT_initlock.reset(new BugType(this, "Init invalid lock", "Lock checker")); - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto Report = llvm::make_unique<BugReport>(*BT_initlock, Message, N); @@ -320,7 +320,7 @@ void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, if (!BT_destroylock) BT_destroylock.reset(new BugType(this, "Use destroyed lock", "Lock checker")); - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; auto Report = llvm::make_unique<BugReport>( diff --git a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp index 6ee87a5..f983c30 100644 --- a/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/RetainCountChecker.cpp @@ -234,6 +234,7 @@ public: return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(), getType(), IvarAccessHistory::AccessedDirectly); } + RefVal releaseViaIvar() const { assert(getIvarAccessHistory() == IvarAccessHistory::AccessedDirectly); return RefVal(getKind(), getObjKind(), getCount(), getAutoreleaseCount(), @@ -250,7 +251,7 @@ public: 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); @@ -426,16 +427,16 @@ public: /// 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 + /// 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 && @@ -484,7 +485,7 @@ public: IdentifierInfo *getIdentifier() const { return II; } Selector getSelector() const { return S; } }; -} +} // end anonymous namespace namespace llvm { template <> struct DenseMapInfo<ObjCSummaryKey> { @@ -621,7 +622,7 @@ class RetainSummaryManager { ArgEffects::Factory AF; /// ScratchArgs - A holding buffer for construct ArgEffects. - ArgEffects ScratchArgs; + ArgEffects ScratchArgs; /// ObjCAllocRetE - Default return effect for methods returning Objective-C /// objects. @@ -644,7 +645,7 @@ class RetainSummaryManager { ArgEffects getArgEffects(); enum UnaryFuncKind { cfretain, cfrelease, cfautorelease, cfmakecollectable }; - + const RetainSummary *getUnarySummary(const FunctionType* FT, UnaryFuncKind func); @@ -664,7 +665,7 @@ class RetainSummaryManager { const RetainSummary *getDoNothingSummary() { return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); } - + const RetainSummary *getDefaultSummary() { return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, MayEscape); @@ -689,7 +690,7 @@ private: 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) + Selector S = isNullary ? GetNullarySelector(name, Ctx) : GetUnarySelector(name, Ctx); ObjCClassMethodSummaries[ObjCSummaryKey(ClsII, S)] = Summ; } @@ -739,7 +740,7 @@ public: ? RetEffect::MakeGCNotOwned() : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC) : RetEffect::MakeOwned(RetEffect::ObjC, true))), - ObjCInitRetE(gcenabled + ObjCInitRetE(gcenabled ? RetEffect::MakeGCNotOwned() : (usesARC ? RetEffect::MakeNotOwned(RetEffect::ObjC) : RetEffect::MakeOwnedWhenTrackedReceiver())) { @@ -803,7 +804,7 @@ public: bool isGCEnabled() const { return GCEnabled; } bool isARCEnabled() const { return ARCEnabled; } - + bool isARCorGCEnabled() const { return GCEnabled || ARCEnabled; } RetEffect getObjAllocRetEffect() const { return ObjCAllocRetE; } @@ -966,7 +967,7 @@ void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S, // 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 + // 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 @@ -985,7 +986,6 @@ void RetainSummaryManager::updateSummaryForCall(const RetainSummary *&S, ModifiableSummaryTemplate->setRetEffect(RetEffect::MakeNoRet()); } } - } } @@ -1150,7 +1150,7 @@ RetainSummaryManager::getFunctionSummary(const FunctionDecl *FD) { if (S) break; - if (RetTy->isPointerType()) { + if (RetTy->isPointerType()) { // For CoreFoundation ('CF') types. if (cocoa::isRefType(RetTy, "CF", FName)) { if (isRetain(FD, FName)) { @@ -1278,14 +1278,14 @@ RetainSummaryManager::getUnarySummary(const FunctionType* FT, return getPersistentSummary(RetEffect::MakeNoRet(), DoNothing, DoNothing); } -const RetainSummary * +const RetainSummary * RetainSummaryManager::getCFSummaryCreateRule(const FunctionDecl *FD) { assert (ScratchArgs.isEmpty()); return getPersistentSummary(RetEffect::MakeOwned(RetEffect::CF, true)); } -const RetainSummary * +const RetainSummary * RetainSummaryManager::getCFSummaryGetRule(const FunctionDecl *FD) { assert (ScratchArgs.isEmpty()); return getPersistentSummary(RetEffect::MakeNotOwned(RetEffect::CF), @@ -1331,7 +1331,7 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, // Effects on the parameters. unsigned parm_idx = 0; - for (FunctionDecl::param_const_iterator pi = FD->param_begin(), + 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>()) @@ -1367,8 +1367,8 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, // Effects on the receiver. if (MD->hasAttr<NSConsumesSelfAttr>()) - Template->setReceiverEffect(DecRefMsg); - + Template->setReceiverEffect(DecRefMsg); + // Effects on the parameters. unsigned parm_idx = 0; for (ObjCMethodDecl::param_const_iterator @@ -1376,9 +1376,9 @@ RetainSummaryManager::updateSummaryFromAnnotations(const RetainSummary *&Summ, pi != pe; ++pi, ++parm_idx) { const ParmVarDecl *pd = *pi; if (pd->hasAttr<NSConsumedAttr>()) - Template->addArg(AF, parm_idx, DecRefMsg); + Template->addArg(AF, parm_idx, DecRefMsg); else if (pd->hasAttr<CFConsumedAttr>()) { - Template->addArg(AF, parm_idx, DecRef); + Template->addArg(AF, parm_idx, DecRef); } else if (pd->hasAttr<CFReturnsRetainedAttr>()) { QualType PointeeTy = pd->getType()->getPointeeType(); if (!PointeeTy.isNull()) @@ -1415,7 +1415,7 @@ RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD, if (cocoa::isCocoaObjectRef(RetTy)) ResultEff = RetEffect::MakeNotOwned(RetEffect::ObjC); else if (coreFoundation::isCFObjectRef(RetTy)) { - // ObjCMethodDecl currently doesn't consider CF objects as valid return + // 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? @@ -1428,11 +1428,11 @@ RetainSummaryManager::getStandardMethodSummary(const ObjCMethodDecl *MD, ResultEff = RetEffect::MakeOwned(RetEffect::CF, true); break; default: - ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); + ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); break; } } else { - ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); + ResultEff = RetEffect::MakeNotOwned(RetEffect::CF); } } break; @@ -1749,7 +1749,7 @@ namespace { SymbolRef Sym; const SummaryLogTy &SummaryLog; bool GCEnabled; - + public: CFRefReportVisitor(SymbolRef sym, bool gcEnabled, const SummaryLogTy &log) : Sym(sym), SummaryLog(log), GCEnabled(gcEnabled) {} @@ -1869,7 +1869,7 @@ void CFRefReport::addGCModeDescription(const LangOptions &LOpts, static bool isNumericLiteralExpression(const Expr *E) { // FIXME: This set of cases was copied from SemaExprObjC. - return isa<IntegerLiteral>(E) || + return isa<IntegerLiteral>(E) || isa<CharacterLiteral>(E) || isa<FloatingLiteral>(E) || isa<ObjCBoolLiteralExpr>(E) || @@ -1948,7 +1948,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, else if (isa<ObjCIvarRefExpr>(S)) { os << "Object loaded from instance variable"; } - else { + 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); @@ -2192,6 +2192,7 @@ PathDiagnosticPiece *CFRefReportVisitor::VisitNode(const ExplodedNode *N, return 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 @@ -2206,6 +2207,7 @@ struct AllocationInfo { const LocationContext *InInterestingMethodContext) : N(InN), R(InR), InterestingMethodContext(InInterestingMethodContext) {} }; +} // end anonymous namespace static AllocationInfo GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, @@ -2228,7 +2230,7 @@ GetAllocationSite(ProgramStateManager& StateMgr, const ExplodedNode *N, StoreManager::FindUniqueBinding FB(Sym); StateMgr.iterBindings(St, FB); - + if (FB) { const MemRegion *R = FB.getRegion(); const VarRegion *VR = R->getBaseRegion()->getAs<VarRegion>(); @@ -2345,10 +2347,10 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, // 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>()) @@ -2385,7 +2387,7 @@ CFRefLeakReportVisitor::getEndPath(BugReporterContext &BRC, } CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, - bool GCEnabled, const SummaryLogTy &Log, + bool GCEnabled, const SummaryLogTy &Log, ExplodedNode *n, SymbolRef sym, CheckerContext &Ctx, bool IncludeAllocationLine) @@ -2414,7 +2416,7 @@ CFRefLeakReport::CFRefLeakReport(CFRefBug &D, const LangOptions &LOpts, // 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.) - const Stmt *AllocStmt = 0; + const Stmt *AllocStmt = nullptr; ProgramPoint P = AllocNode->getLocation(); if (Optional<CallExitEnd> Exit = P.getAs<CallExitEnd>()) AllocStmt = Exit->getCalleeContext()->getCallSite(); @@ -2492,7 +2494,7 @@ class RetainCountChecker /// the allocation line. mutable bool IncludeAllocationLine; -public: +public: RetainCountChecker(AnalyzerOptions &AO) : ShouldResetSummaryLog(false), IncludeAllocationLine(shouldIncludeAllocationSiteInLeakDiagnostics(AO)) {} @@ -2617,7 +2619,7 @@ public: 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; @@ -2630,13 +2632,13 @@ public: ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; - ProgramStateRef + ProgramStateRef checkRegionChanges(ProgramStateRef state, const InvalidatedSymbols *invalidated, ArrayRef<const MemRegion *> ExplicitRegions, ArrayRef<const MemRegion *> Regions, const CallEvent *Call) const; - + bool wantsRegionChangeUpdate(ProgramStateRef state) const { return true; } @@ -2645,7 +2647,7 @@ public: 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 checkEndFunction(CheckerContext &C) const; @@ -2656,7 +2658,7 @@ public: 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; @@ -2678,7 +2680,7 @@ public: } // end anonymous namespace namespace { -class StopTrackingCallback : public SymbolVisitor { +class StopTrackingCallback final : public SymbolVisitor { ProgramStateRef state; public: StopTrackingCallback(ProgramStateRef st) : state(st) {} @@ -2740,21 +2742,21 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE, 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; + break; case clang::OBC_BridgeTransfer: AE = DecRefBridgedTransferred; break; } - + ProgramStateRef state = C.getState(); SymbolRef Sym = state->getSVal(CE, C.getLocationContext()).getAsLocSymbol(); if (!Sym) @@ -2765,7 +2767,7 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE, 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; @@ -2777,7 +2779,7 @@ void RetainCountChecker::checkPostStmt(const CastExpr *CE, void RetainCountChecker::processObjCLiterals(CheckerContext &C, const Expr *Ex) const { ProgramStateRef state = C.getState(); - const ExplodedNode *pred = C.getPredecessor(); + const ExplodedNode *pred = C.getPredecessor(); for (const Stmt *Child : Ex->children()) { SVal V = state->getSVal(Child, pred->getLocationContext()); if (SymbolRef sym = V.getAsSymbol()) @@ -2790,17 +2792,17 @@ void RetainCountChecker::processObjCLiterals(CheckerContext &C, } } } - + // Return the object as autoreleased. // RetEffect RE = RetEffect::MakeNotOwned(RetEffect::ObjC); - if (SymbolRef sym = + 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); + + C.addTransition(state); } void RetainCountChecker::checkPostStmt(const ObjCArrayLiteral *AL, @@ -2817,7 +2819,7 @@ void RetainCountChecker::checkPostStmt(const ObjCDictionaryLiteral *DL, void RetainCountChecker::checkPostStmt(const ObjCBoxedExpr *Ex, CheckerContext &C) const { - const ExplodedNode *Pred = C.getPredecessor(); + const ExplodedNode *Pred = C.getPredecessor(); const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); @@ -2966,7 +2968,7 @@ void RetainCountChecker::processSummaryOfInlined(const RetainSummary &Summ, if (Sym) state = removeRefBinding(state, Sym); } - + C.addTransition(state); } @@ -3062,7 +3064,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, if (RE.getKind() == RetEffect::OwnedWhenTrackedReceiver) { if (ReceiverIsTracked) - RE = getSummaryManager(C).getObjAllocRetEffect(); + RE = getSummaryManager(C).getObjAllocRetEffect(); else RE = RetEffect::MakeNoRet(); } @@ -3129,8 +3131,7 @@ void RetainCountChecker::checkSummary(const RetainSummary &Summ, } } - -ProgramStateRef +ProgramStateRef RetainCountChecker::updateSymbol(ProgramStateRef state, SymbolRef sym, RefVal V, ArgEffect E, RefVal::Kind &hasErr, CheckerContext &C) const { @@ -3306,7 +3307,7 @@ void RetainCountChecker::processNonLeakError(ProgramStateRef St, if (RV->getIvarAccessHistory() != RefVal::IvarAccessHistory::None) return; - ExplodedNode *N = C.generateSink(St); + ExplodedNode *N = C.generateErrorNode(St); if (!N) return; @@ -3388,7 +3389,7 @@ bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { isMakeCollectable(FD, FName); } } - + if (!canEval) return false; @@ -3531,7 +3532,7 @@ void RetainCountChecker::checkReturnWithRetEffect(const ReturnStmt *S, ExplodedNode *Pred, RetEffect RE, RefVal X, SymbolRef Sym, - ProgramStateRef state) const { + ProgramStateRef state) const { // HACK: Ignore retain-count issues on values accessed through ivars, // because of cases like this: // [_contentView retain]; @@ -3669,7 +3670,6 @@ void RetainCountChecker::checkBind(SVal loc, SVal val, const Stmt *S, 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 @@ -3700,7 +3700,7 @@ ProgramStateRef RetainCountChecker::evalAssume(ProgramStateRef state, return state; } -ProgramStateRef +ProgramStateRef RetainCountChecker::checkRegionChanges(ProgramStateRef state, const InvalidatedSymbols *invalidated, ArrayRef<const MemRegion *> ExplicitRegions, @@ -3810,7 +3810,7 @@ RetainCountChecker::handleAutoreleaseCounts(ProgramStateRef state, return nullptr; } -ProgramStateRef +ProgramStateRef RetainCountChecker::handleSymbolDeath(ProgramStateRef state, SymbolRef sid, RefVal V, SmallVectorImpl<SymbolRef> &Leaked) const { @@ -3890,7 +3890,7 @@ void RetainCountChecker::checkEndFunction(CheckerContext &Ctx) const { // and suggest annotations. if (LCtx->getParent()) return; - + B = state->get<RefBindings>(); SmallVector<SymbolRef, 10> Leaked; @@ -3910,7 +3910,7 @@ RetainCountChecker::getDeadSymbolTag(SymbolRef sym) const { sym->dumpToStream(out); tag = new CheckerProgramPointTag(this, out.str()); } - return tag; + return tag; } void RetainCountChecker::checkDeadSymbols(SymbolReaper &SymReaper, @@ -3993,7 +3993,9 @@ void ento::registerRetainCountChecker(CheckerManager &Mgr) { // Implementation of the CallEffects API. //===----------------------------------------------------------------------===// -namespace clang { namespace ento { namespace objc_retain { +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 @@ -4022,4 +4024,6 @@ CallEffects CallEffects::getEffect(const FunctionDecl *FD) { #undef createCallEffect -}}} +} // end namespace objc_retain +} // end namespace ento +} // end namespace clang diff --git a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index acbd0d9..19fa0fb 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -23,7 +23,7 @@ using namespace clang; using namespace ento; namespace { -class ReturnPointerRangeChecker : +class ReturnPointerRangeChecker : public Checker< check::PreStmt<ReturnStmt> > { mutable std::unique_ptr<BuiltinBug> BT; @@ -39,7 +39,7 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, const Expr *RetE = RS->getRetValue(); if (!RetE) return; - + SVal V = state->getSVal(RetE, C.getLocationContext()); const MemRegion *R = V.getAsRegion(); @@ -62,11 +62,11 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, ProgramStateRef StInBound = state->assumeInBound(Idx, NumElements, true); ProgramStateRef StOutBound = state->assumeInBound(Idx, NumElements, false); if (StOutBound && !StInBound) { - ExplodedNode *N = C.generateSink(StOutBound); + ExplodedNode *N = C.generateErrorNode(StOutBound); if (!N) return; - + // FIXME: This bug correspond to CWE-466. Eventually we should have bug // types explicitly reference such exploit categories (when applicable). if (!BT) diff --git a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index 2668ac1..c5e826a 100644 --- a/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -80,7 +80,7 @@ void ReturnUndefChecker::checkPreStmt(const ReturnStmt *RS, static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE, const Expr *TrackingE = nullptr) { - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; diff --git a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp index c22e78b..7026a2e 100644 --- a/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -92,7 +92,7 @@ public: REGISTER_MAP_WITH_PROGRAMSTATE(StreamMap, SymbolRef, StreamState) namespace { -class StopTrackingCallback : public SymbolVisitor { +class StopTrackingCallback final : public SymbolVisitor { ProgramStateRef state; public: StopTrackingCallback(ProgramStateRef st) : state(st) {} @@ -200,7 +200,9 @@ void SimpleStreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, State = State->remove<StreamMap>(Sym); } - ExplodedNode *N = C.addTransition(State); + ExplodedNode *N = C.generateNonFatalErrorNode(State); + if (!N) + return; reportLeaks(LeakedStreams, C, N); } @@ -208,7 +210,7 @@ void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, const CallEvent &Call, CheckerContext &C) const { // We reached a bug, stop exploring the path here by generating a sink. - ExplodedNode *ErrNode = C.generateSink(); + ExplodedNode *ErrNode = C.generateErrorNode(); // If we've already reached this node on another path, return. if (!ErrNode) return; diff --git a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index 813c811..79fc701 100644 --- a/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines stack address leak checker, which checks if an invalid +// This file defines stack address leak checker, which checks if an invalid // stack address is stored into a global or heap location. See CERT DCL30-C. // //===----------------------------------------------------------------------===// @@ -49,20 +49,20 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R, SourceManager &SM = Ctx.getSourceManager(); SourceRange range; os << "Address of "; - + // Check if the region is a compound literal. - if (const CompoundLiteralRegion* CR = dyn_cast<CompoundLiteralRegion>(R)) { + if (const CompoundLiteralRegion* CR = dyn_cast<CompoundLiteralRegion>(R)) { const CompoundLiteralExpr *CL = CR->getLiteralExpr(); os << "stack memory associated with a compound literal " "declared on line " << SM.getExpansionLineNumber(CL->getLocStart()) - << " returned to caller"; + << " returned to caller"; range = CL->getSourceRange(); } else if (const AllocaRegion* AR = dyn_cast<AllocaRegion>(R)) { const Expr *ARE = AR->getExpr(); SourceLocation L = ARE->getLocStart(); - range = ARE->getSourceRange(); + range = ARE->getSourceRange(); os << "stack memory allocated by call to alloca() on line " << SM.getExpansionLineNumber(L); } @@ -87,14 +87,14 @@ SourceRange StackAddrEscapeChecker::genName(raw_ostream &os, const MemRegion *R, } else { llvm_unreachable("Invalid region in ReturnStackAddressChecker."); - } - + } + return range; } void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion *R, const Expr *RetE) const { - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; @@ -118,7 +118,7 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, const MemRegion * void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const { - + const Expr *RetE = RS->getRetValue(); if (!RetE) return; @@ -130,10 +130,10 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, if (!R) return; - + const StackSpaceRegion *SS = dyn_cast_or_null<StackSpaceRegion>(R->getMemorySpace()); - + if (!SS) return; @@ -156,6 +156,15 @@ void StackAddrEscapeChecker::checkPreStmt(const ReturnStmt *RS, if (isa<CXXConstructExpr>(RetE) && RetE->getType()->isRecordType()) return; + // The CK_CopyAndAutoreleaseBlockObject cast causes the block to be copied + // so the stack address is not escaping here. + if (auto *ICE = dyn_cast<ImplicitCastExpr>(RetE)) { + if (isa<BlockDataRegion>(R) && + ICE->getCastKind() == CK_CopyAndAutoreleaseBlockObject) { + return; + } + } + EmitStackError(C, R, RetE); } @@ -175,35 +184,35 @@ void StackAddrEscapeChecker::checkEndFunction(CheckerContext &Ctx) const { Ctx(CC), CurSFC(CC.getLocationContext()->getCurrentStackFrame()) {} - + bool HandleBinding(StoreManager &SMgr, Store store, const MemRegion *region, SVal val) override { if (!isa<GlobalsSpaceRegion>(region->getMemorySpace())) return true; - + const MemRegion *vR = val.getAsRegion(); if (!vR) return true; - + // Under automated retain release, it is okay to assign a block // directly to a global variable. if (Ctx.getASTContext().getLangOpts().ObjCAutoRefCount && isa<BlockDataRegion>(vR)) return true; - if (const StackSpaceRegion *SSR = + if (const StackSpaceRegion *SSR = dyn_cast<StackSpaceRegion>(vR->getMemorySpace())) { // If the global variable holds a location in the current stack frame, // record the binding to emit a warning. if (SSR->getStackFrame() == CurSFC) V.push_back(std::make_pair(region, vR)); } - + return true; } }; - + CallBack cb(Ctx); state->getStateManager().getStoreManager().iterBindings(state->getStore(),cb); @@ -211,7 +220,7 @@ void StackAddrEscapeChecker::checkEndFunction(CheckerContext &Ctx) const { return; // Generate an error node. - ExplodedNode *N = Ctx.addTransition(state); + ExplodedNode *N = Ctx.generateNonFatalErrorNode(state); if (!N) return; diff --git a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 2109a75..82b01fe 100644 --- a/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -43,8 +43,8 @@ struct StreamState { static StreamState getOpened(const Stmt *s) { return StreamState(Opened, s); } static StreamState getClosed(const Stmt *s) { return StreamState(Closed, s); } - static StreamState getOpenFailed(const Stmt *s) { - return StreamState(OpenFailed, s); + static StreamState getOpenFailed(const Stmt *s) { + return StreamState(OpenFailed, s); } static StreamState getEscaped(const Stmt *s) { return StreamState(Escaped, s); @@ -59,14 +59,14 @@ struct StreamState { class StreamChecker : public Checker<eval::Call, check::DeadSymbols > { mutable IdentifierInfo *II_fopen, *II_tmpfile, *II_fclose, *II_fread, - *II_fwrite, - *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, + *II_fwrite, + *II_fseek, *II_ftell, *II_rewind, *II_fgetpos, *II_fsetpos, *II_clearerr, *II_feof, *II_ferror, *II_fileno; mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence, BT_doubleclose, BT_ResourceLeak; public: - StreamChecker() + StreamChecker() : II_fopen(nullptr), II_tmpfile(nullptr), II_fclose(nullptr), II_fread(nullptr), II_fwrite(nullptr), II_fseek(nullptr), II_ftell(nullptr), II_rewind(nullptr), II_fgetpos(nullptr), @@ -93,10 +93,10 @@ private: void Fileno(CheckerContext &C, const CallExpr *CE) const; void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; - - ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, + + ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, CheckerContext &C) const; - ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, + ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, CheckerContext &C) const; }; @@ -216,13 +216,13 @@ void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { C.blockCount()) .castAs<DefinedSVal>(); state = state->BindExpr(CE, C.getLocationContext(), RetVal); - + ConstraintManager &CM = C.getConstraintManager(); // Bifurcate the state into two: one with a valid FILE* pointer, the other // with a NULL. ProgramStateRef stateNotNull, stateNull; std::tie(stateNotNull, stateNull) = CM.assumeDual(state, RetVal); - + if (SymbolRef Sym = RetVal.getAsSymbol()) { // if RetVal is not NULL, set the symbol's state to Opened. stateNotNull = @@ -271,7 +271,7 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { if (x >= 0 && x <= 2) return; - if (ExplodedNode *N = C.addTransition(state)) { + if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { if (!BT_illegalwhence) BT_illegalwhence.reset( new BuiltinBug(this, "Illegal whence argument", @@ -349,7 +349,7 @@ ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); if (!stateNotNull && stateNull) { - if (ExplodedNode *N = C.generateSink(stateNull)) { + if (ExplodedNode *N = C.generateErrorNode(stateNull)) { if (!BT_nullfp) BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", "Stream pointer might be NULL.")); @@ -368,17 +368,17 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, state->getSVal(CE->getArg(0), C.getLocationContext()).getAsSymbol(); if (!Sym) return state; - + const StreamState *SS = state->get<StreamMap>(Sym); // If the file stream is not tracked, return. if (!SS) return state; - + // Check: Double close a File Descriptor could cause undefined behaviour. // Conforming to man-pages if (SS->isClosed()) { - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (N) { if (!BT_doubleclose) BT_doubleclose.reset(new BuiltinBug( @@ -389,7 +389,7 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, } return nullptr; } - + // Close the File Descriptor. return state->set<StreamMap>(Sym, StreamState::getClosed(CE)); } @@ -406,7 +406,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, continue; if (SS->isOpened()) { - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (N) { if (!BT_ResourceLeak) BT_ResourceLeak.reset(new BuiltinBug( diff --git a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp index 6e24775..2e05290 100644 --- a/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -48,7 +48,7 @@ void TaintTesterChecker::checkPostStmt(const Expr *E, return; if (State->isTainted(E, C.getLocationContext())) { - if (ExplodedNode *N = C.addTransition()) { + if (ExplodedNode *N = C.generateNonFatalErrorNode()) { initBugType(); auto report = llvm::make_unique<BugReport>(*BT, "tainted",N); report->addRange(E->getSourceRange()); diff --git a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index 638701d..b794d2f 100644 --- a/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -167,7 +167,7 @@ bool TestAfterDivZeroChecker::hasDivZeroMap(SVal Var, } void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { - if (ExplodedNode *N = C.generateSink(C.getState())) { + if (ExplodedNode *N = C.generateErrorNode(C.getState())) { if (!DivZeroBug) DivZeroBug.reset(new BuiltinBug(this, "Division by zero")); diff --git a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index 1d8ef99..ed17610 100644 --- a/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -30,7 +30,7 @@ class UndefBranchChecker : public Checker<check::BranchCondition> { ProgramStateRef St; const LocationContext *LCtx; - FindUndefExpr(ProgramStateRef S, const LocationContext *L) + FindUndefExpr(ProgramStateRef S, const LocationContext *L) : St(S), LCtx(L) {} const Expr *FindExpr(const Expr *Ex) { @@ -45,7 +45,7 @@ class UndefBranchChecker : public Checker<check::BranchCondition> { return Ex; } - bool MatchesCriteria(const Expr *Ex) { + bool MatchesCriteria(const Expr *Ex) { return St->getSVal(Ex, LCtx).isUndef(); } }; @@ -62,7 +62,7 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, if (X.isUndef()) { // Generate a sink node, which implicitly marks both outgoing branches as // infeasible. - ExplodedNode *N = Ctx.generateSink(); + ExplodedNode *N = Ctx.generateErrorNode(); if (N) { if (!BT) BT.reset(new BuiltinBug( diff --git a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index 53fd069..17fe861 100644 --- a/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -74,7 +74,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, // Get the VarRegion associated with VD in the local stack frame. if (Optional<UndefinedVal> V = state->getSVal(I.getOriginalRegion()).getAs<UndefinedVal>()) { - if (ExplodedNode *N = C.generateSink()) { + if (ExplodedNode *N = C.generateErrorNode()) { if (!BT) BT.reset( new BuiltinBug(this, "uninitialized variable captured by block")); @@ -83,7 +83,7 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, SmallString<128> buf; llvm::raw_svector_ostream os(buf); - os << "Variable '" << VD->getName() + os << "Variable '" << VD->getName() << "' is uninitialized when captured by block"; auto R = llvm::make_unique<BugReport>(*BT, os.str(), N); diff --git a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index 5353310..38d2aa6 100644 --- a/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// // -// This defines UndefResultChecker, a builtin check in ExprEngine that +// This defines UndefResultChecker, a builtin check in ExprEngine that // performs checks for undefined results of non-assignment binary operators. // //===----------------------------------------------------------------------===// @@ -25,7 +25,7 @@ using namespace clang; using namespace ento; namespace { -class UndefResultChecker +class UndefResultChecker : public Checker< check::PostStmt<BinaryOperator> > { mutable std::unique_ptr<BugType> BT; @@ -50,10 +50,10 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, return; // Generate an error node. - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; - + if (!BT) BT.reset( new BuiltinBug(this, "Result of operation is garbage or undefined")); @@ -62,7 +62,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, llvm::raw_svector_ostream OS(sbuf); const Expr *Ex = nullptr; bool isLeft = true; - + if (state->getSVal(B->getLHS(), LCtx).isUndef()) { Ex = B->getLHS()->IgnoreParenCasts(); isLeft = true; @@ -71,13 +71,13 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, Ex = B->getRHS()->IgnoreParenCasts(); isLeft = false; } - + if (Ex) { OS << "The " << (isLeft ? "left" : "right") << " operand of '" << BinaryOperator::getOpcodeStr(B->getOpcode()) << "' is a garbage value"; - } + } else { // Neither operand was undefined, but the result is undefined. OS << "The result of the '" @@ -91,7 +91,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, } else bugreporter::trackNullOrUndefValue(N, B, *report); - + C.emitReport(std::move(report)); } } diff --git a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index ba4daf8..fe07eaf 100644 --- a/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -32,7 +32,7 @@ public: }; } // end anonymous namespace -void +void UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, CheckerContext &C) const { const Expr *Index = A->getIdx(); @@ -46,7 +46,7 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, if (Ctor->isDefaulted()) return; - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; if (!BT) diff --git a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index 81c96c4..7a31efc 100644 --- a/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -46,7 +46,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (C.getCalleeName(EnclosingFunctionDecl) == "swap") return; - ExplodedNode *N = C.generateSink(); + ExplodedNode *N = C.generateErrorNode(); if (!N) return; diff --git a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index a799b4c..4b78c20 100644 --- a/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -77,7 +77,7 @@ void UnixAPIChecker::ReportOpenBug(CheckerContext &C, ProgramStateRef State, const char *Msg, SourceRange SR) const { - ExplodedNode *N = C.generateSink(State); + ExplodedNode *N = C.generateErrorNode(State); if (!N) return; @@ -114,7 +114,7 @@ void UnixAPIChecker::CheckOpen(CheckerContext &C, const CallExpr *CE) const { // The definition of O_CREAT is platform specific. We need a better way // of querying this information from the checking environment. if (!Val_O_CREAT.hasValue()) { - if (C.getASTContext().getTargetInfo().getTriple().getVendor() + if (C.getASTContext().getTargetInfo().getTriple().getVendor() == llvm::Triple::Apple) Val_O_CREAT = 0x0200; else { @@ -182,7 +182,7 @@ void UnixAPIChecker::CheckPthreadOnce(CheckerContext &C, if (!R || !isa<StackSpaceRegion>(R->getMemorySpace())) return; - ExplodedNode *N = C.generateSink(state); + ExplodedNode *N = C.generateErrorNode(state); if (!N) return; @@ -220,7 +220,7 @@ static bool IsZeroByteAllocation(ProgramStateRef state, ProgramStateRef *falseState) { std::tie(*trueState, *falseState) = state->assume(argVal.castAs<DefinedSVal>()); - + return (*falseState && !*trueState); } @@ -231,7 +231,7 @@ bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C, ProgramStateRef falseState, const Expr *arg, const char *fn_name) const { - ExplodedNode *N = C.generateSink(falseState); + ExplodedNode *N = C.generateErrorNode(falseState); if (!N) return false; @@ -239,7 +239,7 @@ bool UnixAPIChecker::ReportZeroByteAllocation(CheckerContext &C, "Undefined allocation of 0 bytes (CERT MEM04-C; CWE-131)"); SmallString<256> S; - llvm::raw_svector_ostream os(S); + llvm::raw_svector_ostream os(S); os << "Call to '" << fn_name << "' has an allocation size of 0 bytes"; auto report = llvm::make_unique<BugReport>(*BT_mallocZero, os.str(), N); @@ -272,13 +272,13 @@ void UnixAPIChecker::BasicAllocationCheck(CheckerContext &C, // Is the value perfectly constrained to zero? if (IsZeroByteAllocation(state, argVal, &trueState, &falseState)) { - (void) ReportZeroByteAllocation(C, falseState, arg, fn); + (void) ReportZeroByteAllocation(C, falseState, arg, fn); return; } // Assume the value is non-zero going forward. assert(trueState); if (trueState != state) - C.addTransition(trueState); + C.addTransition(trueState); } void UnixAPIChecker::CheckCallocZero(CheckerContext &C, diff --git a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp index d78de3c..a03abce 100644 --- a/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -54,7 +54,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, BugReporter &B, ExprEngine &Eng) const { CFGBlocksSet reachable, visited; - + if (Eng.hasWorkRemaining()) return; @@ -88,7 +88,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, // Bail out if we didn't get the CFG or the ParentMap. if (!D || !C || !PM) return; - + // Don't do anything for template instantiations. Proving that code // in a template instantiation is unreachable means proving that it is // unreachable in all instantiations. @@ -235,12 +235,9 @@ bool UnreachableCodeChecker::isInvalidPath(const CFGBlock *CB, return false; // Run each of the checks on the conditions - if (containsMacro(cond) || containsEnum(cond) - || containsStaticLocal(cond) || containsBuiltinOffsetOf(cond) - || containsStmt<UnaryExprOrTypeTraitExpr>(cond)) - return true; - - return false; + return containsMacro(cond) || containsEnum(cond) || + containsStaticLocal(cond) || containsBuiltinOffsetOf(cond) || + containsStmt<UnaryExprOrTypeTraitExpr>(cond); } // Returns true if the given CFGBlock is empty diff --git a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index 80384bb..e3b2ed2 100644 --- a/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// // -// This defines VLASizeChecker, a builtin check in ExprEngine that +// This defines VLASizeChecker, a builtin check in ExprEngine that // performs checks for declaration of VLA of undefined or zero size. // In addition, VLASizeChecker is responsible for defining the extent // of the MemRegion that represents a VLA. @@ -46,7 +46,7 @@ void VLASizeChecker::reportBug(VLASize_Kind Kind, ProgramStateRef State, CheckerContext &C) const { // Generate an error node. - ExplodedNode *N = C.generateSink(State); + ExplodedNode *N = C.generateErrorNode(State); if (!N) return; @@ -82,7 +82,7 @@ void VLASizeChecker::reportBug(VLASize_Kind Kind, void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { if (!DS->isSingleDecl()) return; - + const VarDecl *VD = dyn_cast<VarDecl>(DS->getSingleDecl()); if (!VD) return; @@ -106,7 +106,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { // warned about that already. if (sizeV.isUnknown()) return; - + // Check if the size is tainted. if (state->isTainted(sizeV)) { reportBug(VLA_Tainted, SE, nullptr, C); @@ -123,7 +123,7 @@ void VLASizeChecker::checkPreStmt(const DeclStmt *DS, CheckerContext &C) const { reportBug(VLA_Zero, SE, stateZero, C); return; } - + // From this point on, assume that the size is not zero. state = stateNotZero; diff --git a/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/lib/StaticAnalyzer/Checkers/VforkChecker.cpp new file mode 100644 index 0000000..26ffee8 --- /dev/null +++ b/lib/StaticAnalyzer/Checkers/VforkChecker.cpp @@ -0,0 +1,218 @@ +//===- VforkChecker.cpp -------- Vfork usage checks --------------*- 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 vfork checker which checks for dangerous uses of vfork. +// Vforked process shares memory (including stack) with parent so it's +// range of actions is significantly limited: can't write variables, +// can't call functions not in whitelist, etc. For more details, see +// http://man7.org/linux/man-pages/man2/vfork.2.html +// +// This checker checks for prohibited constructs in vforked process. +// The state transition diagram: +// PARENT ---(vfork() == 0)--> CHILD +// | +// --(*p = ...)--> bug +// | +// --foo()--> bug +// | +// --return--> bug +// +//===----------------------------------------------------------------------===// + +#include "ClangSACheckers.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/AST/ParentMap.h" + +using namespace clang; +using namespace ento; + +namespace { + +class VforkChecker : public Checker<check::PreCall, check::PostCall, + check::Bind, check::PreStmt<ReturnStmt>> { + mutable std::unique_ptr<BuiltinBug> BT; + mutable llvm::SmallSet<const IdentifierInfo *, 10> VforkWhitelist; + mutable const IdentifierInfo *II_vfork; + + static bool isChildProcess(const ProgramStateRef State); + + bool isVforkCall(const Decl *D, CheckerContext &C) const; + bool isCallWhitelisted(const IdentifierInfo *II, CheckerContext &C) const; + + void reportBug(const char *What, CheckerContext &C, + const char *Details = 0) const; + +public: + VforkChecker() : II_vfork(0) {} + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const; + void checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const; +}; + +} // end anonymous namespace + +// This trait holds region of variable that is assigned with vfork's +// return value (this is the only region child is allowed to write). +// VFORK_RESULT_INVALID means that we are in parent process. +// VFORK_RESULT_NONE means that vfork's return value hasn't been assigned. +// Other values point to valid regions. +REGISTER_TRAIT_WITH_PROGRAMSTATE(VforkResultRegion, const void *) +#define VFORK_RESULT_INVALID 0 +#define VFORK_RESULT_NONE ((void *)(uintptr_t)1) + +bool VforkChecker::isChildProcess(const ProgramStateRef State) { + return State->get<VforkResultRegion>() != VFORK_RESULT_INVALID; +} + +bool VforkChecker::isVforkCall(const Decl *D, CheckerContext &C) const { + auto FD = dyn_cast_or_null<FunctionDecl>(D); + if (!FD || !C.isCLibraryFunction(FD)) + return false; + + if (!II_vfork) { + ASTContext &AC = C.getASTContext(); + II_vfork = &AC.Idents.get("vfork"); + } + + return FD->getIdentifier() == II_vfork; +} + +// Returns true iff ok to call function after successful vfork. +bool VforkChecker::isCallWhitelisted(const IdentifierInfo *II, + CheckerContext &C) const { + if (VforkWhitelist.empty()) { + // According to manpage. + const char *ids[] = { + "_exit", + "_Exit", + "execl", + "execlp", + "execle", + "execv", + "execvp", + "execvpe", + 0, + }; + + ASTContext &AC = C.getASTContext(); + for (const char **id = ids; *id; ++id) + VforkWhitelist.insert(&AC.Idents.get(*id)); + } + + return VforkWhitelist.count(II); +} + +void VforkChecker::reportBug(const char *What, CheckerContext &C, + const char *Details) const { + if (ExplodedNode *N = C.generateErrorNode(C.getState())) { + if (!BT) + BT.reset(new BuiltinBug(this, + "Dangerous construct in a vforked process")); + + SmallString<256> buf; + llvm::raw_svector_ostream os(buf); + + os << What << " is prohibited after a successful vfork"; + + if (Details) + os << "; " << Details; + + auto Report = llvm::make_unique<BugReport>(*BT, os.str(), N); + // TODO: mark vfork call in BugReportVisitor + C.emitReport(std::move(Report)); + } +} + +// Detect calls to vfork and split execution appropriately. +void VforkChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + // We can't call vfork in child so don't bother + // (corresponding warning has already been emitted in checkPreCall). + ProgramStateRef State = C.getState(); + if (isChildProcess(State)) + return; + + if (!isVforkCall(Call.getDecl(), C)) + return; + + // Get return value of vfork. + SVal VforkRetVal = Call.getReturnValue(); + Optional<DefinedOrUnknownSVal> DVal = + VforkRetVal.getAs<DefinedOrUnknownSVal>(); + if (!DVal) + return; + + // Get assigned variable. + const ParentMap &PM = C.getLocationContext()->getParentMap(); + const Stmt *P = PM.getParentIgnoreParenCasts(Call.getOriginExpr()); + const VarDecl *LhsDecl; + std::tie(LhsDecl, std::ignore) = parseAssignment(P); + + // Get assigned memory region. + MemRegionManager &M = C.getStoreManager().getRegionManager(); + const MemRegion *LhsDeclReg = + LhsDecl + ? M.getVarRegion(LhsDecl, C.getLocationContext()) + : (const MemRegion *)VFORK_RESULT_NONE; + + // Parent branch gets nonzero return value (according to manpage). + ProgramStateRef ParentState, ChildState; + std::tie(ParentState, ChildState) = C.getState()->assume(*DVal); + C.addTransition(ParentState); + ChildState = ChildState->set<VforkResultRegion>(LhsDeclReg); + C.addTransition(ChildState); +} + +// Prohibit calls to non-whitelist functions in child process. +void VforkChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (isChildProcess(State) + && !isCallWhitelisted(Call.getCalleeIdentifier(), C)) + reportBug("This function call", C); +} + +// Prohibit writes in child process (except for vfork's lhs). +void VforkChecker::checkBind(SVal L, SVal V, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (!isChildProcess(State)) + return; + + const MemRegion *VforkLhs = + static_cast<const MemRegion *>(State->get<VforkResultRegion>()); + const MemRegion *MR = L.getAsRegion(); + + // Child is allowed to modify only vfork's lhs. + if (!MR || MR == VforkLhs) + return; + + reportBug("This assignment", C); +} + +// Prohibit return from function in child process. +void VforkChecker::checkPreStmt(const ReturnStmt *RS, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (isChildProcess(State)) + reportBug("Return", C, "call _exit() instead"); +} + +void ento::registerVforkChecker(CheckerManager &mgr) { + mgr.registerChecker<VforkChecker>(); +} diff --git a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index f6ef4ae..55025030 100644 --- a/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -7,7 +7,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines a checker that checks virtual function calls during +// This file defines a checker that checks virtual function calls during // construction or destruction of C++ objects. // //===----------------------------------------------------------------------===// @@ -37,13 +37,13 @@ class WalkAST : public StmtVisitor<WalkAST> { /// A vector representing the worklist which has a chain of CallExprs. DFSWorkList WList; - + // PreVisited : A CallExpr to this FunctionDecl is in the worklist, but the // body has not been visited yet. // PostVisited : A CallExpr to this FunctionDecl is in the worklist, and the // body has been visited. enum Kind { NotVisited, - PreVisited, /**< A CallExpr to this FunctionDecl is in the + PreVisited, /**< A CallExpr to this FunctionDecl is in the worklist, but the body has not yet been visited. */ PostVisited /**< A CallExpr to this FunctionDecl is in the @@ -57,7 +57,7 @@ class WalkAST : public StmtVisitor<WalkAST> { /// generating bug reports. This is null while visiting the body of a /// constructor or destructor. const CallExpr *visitingCallExpr; - + public: WalkAST(const CheckerBase *checker, BugReporter &br, AnalysisDeclContext *ac) @@ -70,7 +70,7 @@ public: void Enqueue(WorkListUnit WLUnit) { const FunctionDecl *FD = WLUnit->getDirectCallee(); if (!FD || !FD->getBody()) - return; + return; Kind &K = VisitedFunctions[FD]; if (K != NotVisited) return; @@ -81,9 +81,9 @@ public: /// This method returns an item from the worklist without removing it. WorkListUnit Dequeue() { assert(!WList.empty()); - return WList.back(); + return WList.back(); } - + void Execute() { while (hasWork()) { WorkListUnit WLUnit = Dequeue(); @@ -95,7 +95,7 @@ public: // Visit the body. SaveAndRestore<const CallExpr *> SaveCall(visitingCallExpr, WLUnit); Visit(FD->getBody()); - + // Mark the function as being PostVisited to indicate we have // scanned the body. VisitedFunctions[FD] = PostVisited; @@ -114,7 +114,7 @@ public: void VisitCXXMemberCallExpr(CallExpr *CE); void VisitStmt(Stmt *S) { VisitChildren(S); } void VisitChildren(Stmt *S); - + void ReportVirtualCall(const CallExpr *CE, bool isPure); }; @@ -138,7 +138,7 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) { VisitChildren(CE); bool callIsNonVirtual = false; - + // Several situations to elide for checking. if (MemberExpr *CME = dyn_cast<MemberExpr>(CE->getCallee())) { // If the member access is fully qualified (i.e., X::F), then treat @@ -170,7 +170,7 @@ void WalkAST::VisitCXXMemberCallExpr(CallExpr *CE) { void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) { SmallString<100> buf; llvm::raw_svector_ostream os(buf); - + os << "Call Path : "; // Name of current visiting CallExpr. os << *CE->getDirectCallee(); @@ -190,7 +190,7 @@ void WalkAST::ReportVirtualCall(const CallExpr *CE, bool isPure) { PathDiagnosticLocation CELoc = PathDiagnosticLocation::createBegin(CE, BR.getSourceManager(), AC); SourceRange R = CE->getCallee()->getSourceRange(); - + if (isPure) { os << "\n" << "Call pure virtual functions during construction or " << "destruction may leads undefined behaviour"; diff --git a/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/lib/StaticAnalyzer/Core/AnalysisManager.cpp index 5798f01..54634fd 100644 --- a/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -18,7 +18,7 @@ AnalysisManager::AnalysisManager(ASTContext &ctx, DiagnosticsEngine &diags, const LangOptions &lang, const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr, - ConstraintManagerCreator constraintmgr, + ConstraintManagerCreator constraintmgr, CheckerManager *checkerMgr, AnalyzerOptions &Options, CodeInjector *injector) diff --git a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 1696bcf..54c668c 100644 --- a/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -65,7 +65,7 @@ IPAKind AnalyzerOptions::getIPAMode() { // Set the member variable. IPAMode = IPAConfig; } - + return IPAMode; } @@ -295,6 +295,13 @@ unsigned AnalyzerOptions::getMaxTimesInlineLarge() { return MaxTimesInlineLarge.getValue(); } +unsigned AnalyzerOptions::getMinCFGSizeTreatFunctionsAsLarge() { + if (!MinCFGSizeTreatFunctionsAsLarge.hasValue()) + MinCFGSizeTreatFunctionsAsLarge = getOptionAsInteger( + "min-cfg-size-treat-functions-as-large", 14); + return MinCFGSizeTreatFunctionsAsLarge.getValue(); +} + unsigned AnalyzerOptions::getMaxNodesPerTopLevelFunction() { if (!MaxNodesPerTopLevelFunction.hasValue()) { int DefaultValue = 0; @@ -325,3 +332,15 @@ bool AnalyzerOptions::shouldPrunePaths() { bool AnalyzerOptions::shouldConditionalizeStaticInitializers() { return getBooleanOption("cfg-conditional-static-initializers", true); } + +bool AnalyzerOptions::shouldInlineLambdas() { + if (!InlineLambdas.hasValue()) + InlineLambdas = getBooleanOption("inline-lambdas", /*Default=*/true); + return InlineLambdas.getValue(); +} + +bool AnalyzerOptions::shouldWidenLoops() { + if (!WidenLoops.hasValue()) + WidenLoops = getBooleanOption("widen-loops", /*Default=*/false); + return WidenLoops.getValue(); +} diff --git a/lib/StaticAnalyzer/Core/BlockCounter.cpp b/lib/StaticAnalyzer/Core/BlockCounter.cpp index c1ac03d..8c99bd8 100644 --- a/lib/StaticAnalyzer/Core/BlockCounter.cpp +++ b/lib/StaticAnalyzer/Core/BlockCounter.cpp @@ -26,7 +26,7 @@ class CountKey { unsigned BlockID; public: - CountKey(const StackFrameContext *CS, unsigned ID) + CountKey(const StackFrameContext *CS, unsigned ID) : CallSite(CS), BlockID(ID) {} bool operator==(const CountKey &RHS) const { @@ -55,7 +55,7 @@ static inline CountMap::Factory& GetFactory(void *F) { return *static_cast<CountMap::Factory*>(F); } -unsigned BlockCounter::getNumVisited(const StackFrameContext *CallSite, +unsigned BlockCounter::getNumVisited(const StackFrameContext *CallSite, unsigned BlockID) const { CountMap M = GetMap(Data); CountMap::data_type* T = M.lookup(CountKey(CallSite, BlockID)); @@ -71,10 +71,10 @@ BlockCounter::Factory::~Factory() { } BlockCounter -BlockCounter::Factory::IncrementCount(BlockCounter BC, +BlockCounter::Factory::IncrementCount(BlockCounter BC, const StackFrameContext *CallSite, unsigned BlockID) { - return BlockCounter(GetFactory(F).add(GetMap(BC.Data), + return BlockCounter(GetFactory(F).add(GetMap(BC.Data), CountKey(CallSite, BlockID), BC.getNumVisited(CallSite, BlockID)+1).getRoot()); } diff --git a/lib/StaticAnalyzer/Core/BugReporter.cpp b/lib/StaticAnalyzer/Core/BugReporter.cpp index e4db64f..11be764 100644 --- a/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -81,13 +81,13 @@ eventsDescribeSameCondition(PathDiagnosticEventPiece *X, // those that came from TrackConstraintBRVisitor. const void *tagPreferred = ConditionBRVisitor::getTag(); const void *tagLesser = TrackConstraintBRVisitor::getTag(); - + if (X->getLocation() != Y->getLocation()) return nullptr; if (X->getTag() == tagPreferred && Y->getTag() == tagLesser) return X; - + if (Y->getTag() == tagPreferred && X->getTag() == tagLesser) return Y; @@ -110,7 +110,7 @@ static void removeRedundantMsgs(PathPieces &path) { for (unsigned i = 0; i < N; ++i) { IntrusiveRefCntPtr<PathDiagnosticPiece> piece(path.front()); path.pop_front(); - + switch (piece->getKind()) { case clang::ento::PathDiagnosticPiece::Call: removeRedundantMsgs(cast<PathDiagnosticCallPiece>(piece)->path); @@ -123,7 +123,7 @@ static void removeRedundantMsgs(PathPieces &path) { case clang::ento::PathDiagnosticPiece::Event: { if (i == N-1) break; - + if (PathDiagnosticEventPiece *nextEvent = dyn_cast<PathDiagnosticEventPiece>(path.front().get())) { PathDiagnosticEventPiece *event = @@ -157,13 +157,13 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, LocationContextMap &LCM) { bool containsSomethingInteresting = false; const unsigned N = pieces.size(); - + for (unsigned i = 0 ; i < N ; ++i) { // Remove the front piece from the path. If it is still something we // want to keep once we are done, we will push it back on the end. IntrusiveRefCntPtr<PathDiagnosticPiece> piece(pieces.front()); pieces.pop_front(); - + switch (piece->getKind()) { case PathDiagnosticPiece::Call: { PathDiagnosticCallPiece *call = cast<PathDiagnosticCallPiece>(piece); @@ -176,7 +176,7 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, if (!removeUnneededCalls(call->path, R, LCM)) continue; - + containsSomethingInteresting = true; break; } @@ -189,7 +189,7 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, } case PathDiagnosticPiece::Event: { PathDiagnosticEventPiece *event = cast<PathDiagnosticEventPiece>(piece); - + // We never throw away an event, but we do throw it away wholesale // as part of a path if we throw the entire path away. containsSomethingInteresting |= !event->isPrunable(); @@ -198,10 +198,10 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, case PathDiagnosticPiece::ControlFlow: break; } - + pieces.push_back(piece); } - + return containsSomethingInteresting; } @@ -213,7 +213,7 @@ static bool hasImplicitBody(const Decl *D) { } /// Recursively scan through a path and make sure that all call pieces have -/// valid locations. +/// valid locations. static void adjustCallLocations(PathPieces &Pieces, PathDiagnosticLocation *LastCallLocation = nullptr) { @@ -323,7 +323,7 @@ class PathDiagnosticBuilder : public BugReporterContext { NodeMapClosure NMC; public: const LocationContext *LC; - + PathDiagnosticBuilder(GRBugReporter &br, BugReport *r, InterExplodedGraphMap &Backmap, PathDiagnosticConsumer *pdc) @@ -339,7 +339,7 @@ public: BugReport *getBugReport() { return R; } Decl const &getCodeDecl() { return R->getErrorNode()->getCodeDecl(); } - + ParentMap& getParentMap() { return LC->getParentMap(); } const Stmt *getParent(const Stmt *S) { @@ -957,7 +957,7 @@ static PathDiagnosticLocation cleanUpLocation(PathDiagnosticLocation L, if (firstCharOnly) L = PathDiagnosticLocation::createSingleLocation(L); - + return L; } @@ -1001,7 +1001,7 @@ public: ~EdgeBuilder() { while (!CLocs.empty()) popLocation(); - + // Finally, add an initial edge from the start location of the first // statement (if it doesn't already exist). PathDiagnosticLocation L = PathDiagnosticLocation::createDeclBegin( @@ -1016,7 +1016,7 @@ public: popLocation(); PrevLoc = PathDiagnosticLocation(); } - + void addEdge(PathDiagnosticLocation NewLoc, bool alwaysAdd = false, bool IsPostJump = false); @@ -1101,7 +1101,7 @@ void EdgeBuilder::rawAddEdge(PathDiagnosticLocation NewLoc) { PrevLoc = NewLoc; return; } - + if (NewLocClean.asLocation() == PrevLocClean.asLocation()) return; @@ -1242,7 +1242,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R, SVal V = State->getSVal(Ex, LCtx); if (!(R.isInteresting(V) || IE.count(Ex))) return; - + switch (Ex->getStmtClass()) { default: if (!isa<CastExpr>(Ex)) @@ -1260,7 +1260,7 @@ static void reversePropagateIntererstingSymbols(BugReport &R, break; } } - + R.markInteresting(V); } @@ -1275,7 +1275,7 @@ static void reversePropagateInterestingSymbols(BugReport &R, const Stmt *CallSite = Callee->getCallSite(); if (const CallExpr *CE = dyn_cast_or_null<CallExpr>(CallSite)) { if (const FunctionDecl *FD = dyn_cast<FunctionDecl>(CalleeCtx->getDecl())) { - FunctionDecl::param_const_iterator PI = FD->param_begin(), + FunctionDecl::param_const_iterator PI = FD->param_begin(), PE = FD->param_end(); CallExpr::const_arg_iterator AI = CE->arg_begin(), AE = CE->arg_end(); for (; AI != AE && PI != PE; ++AI, ++PI) { @@ -1406,7 +1406,7 @@ static bool GenerateExtensivePathDiagnostic( N->getState().get(), Ex, N->getLocationContext()); } - + if (Optional<CallExitEnd> CE = P.getAs<CallExitEnd>()) { const Stmt *S = CE->getCalleeContext()->getCallSite(); if (const Expr *Ex = dyn_cast_or_null<Expr>(S)) { @@ -1414,7 +1414,7 @@ static bool GenerateExtensivePathDiagnostic( N->getState().get(), Ex, N->getLocationContext()); } - + PathDiagnosticCallPiece *C = PathDiagnosticCallPiece::construct(N, *CE, SM); LCM[&C->path] = CE->getCalleeContext(); @@ -1427,7 +1427,7 @@ static bool GenerateExtensivePathDiagnostic( CallStack.push_back(StackDiagPair(C, N)); break; } - + // Pop the call hierarchy if we are done walking the contents // of a function call. if (Optional<CallEnter> CE = P.getAs<CallEnter>()) { @@ -1436,7 +1436,7 @@ static bool GenerateExtensivePathDiagnostic( PathDiagnosticLocation pos = PathDiagnosticLocation::createBegin(D, SM); EB.addEdge(pos); - + // Flush all locations, and pop the active path. bool VisitedEntireCall = PD.isWithinCall(); EB.flushLocations(); @@ -1466,7 +1466,7 @@ static bool GenerateExtensivePathDiagnostic( } break; } - + // Note that is important that we update the LocationContext // after looking at CallExits. CallExit basically adds an // edge in the *caller*, so we don't want to update the LocationContext @@ -1486,7 +1486,7 @@ static bool GenerateExtensivePathDiagnostic( CalleeCtx, CallerCtx); } } - + // Are we jumping to the head of a loop? Add a special diagnostic. if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { PathDiagnosticLocation L(Loop, SM, PDB.LC); @@ -1552,11 +1552,11 @@ static bool GenerateExtensivePathDiagnostic( else EB.addExtendedContext(PDB.getEnclosingStmtLocation(stmt).asStmt()); } - + break; } - - + + } while (0); if (!NextNode) @@ -2410,7 +2410,7 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, // Trim edges on expressions that are consumed by // the parent expression. if (isa<Expr>(s1End) && PM.isConsumedExpr(cast<Expr>(s1End))) { - removeEdge = true; + removeEdge = true; } // Trim edges where a lexical containment doesn't exist. // For example: @@ -2557,7 +2557,7 @@ BugReport::~BugReport() { const Decl *BugReport::getDeclWithIssue() const { if (DeclWithIssue) return DeclWithIssue; - + const ExplodedNode *N = getErrorNode(); if (!N) return nullptr; @@ -2579,9 +2579,7 @@ void BugReport::Profile(llvm::FoldingSetNodeID& hash) const { hash.AddPointer(GetCurrentOrPreviousStmt(ErrorNode)); } - for (SmallVectorImpl<SourceRange>::const_iterator I = - Ranges.begin(), E = Ranges.end(); I != E; ++I) { - const SourceRange range = *I; + for (SourceRange range : Ranges) { if (!range.isValid()) continue; hash.AddInteger(range.getBegin().getRawEncoding()); @@ -2714,8 +2712,7 @@ llvm::iterator_range<BugReport::ranges_iterator> BugReport::getRanges() { if (Ranges.size() == 1 && !Ranges.begin()->isValid()) return llvm::make_range(ranges_iterator(), ranges_iterator()); - return llvm::iterator_range<BugReport::ranges_iterator>(Ranges.begin(), - Ranges.end()); + return llvm::make_range(Ranges.begin(), Ranges.end()); } PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { @@ -2973,14 +2970,14 @@ static void CompactPathDiagnostic(PathPieces &path, const SourceManager& SM) { for (PathPieces::const_iterator I = path.begin(), E = path.end(); I!=E; ++I) { - + PathDiagnosticPiece *piece = I->get(); // Recursively compact calls. if (PathDiagnosticCallPiece *call=dyn_cast<PathDiagnosticCallPiece>(piece)){ CompactPathDiagnostic(call->path, SM); } - + // Get the location of the PathDiagnosticPiece. const FullSourceLoc Loc = piece->getLocation().asLocation(); @@ -3126,7 +3123,7 @@ bool GRBugReporter::generatePathDiagnostic(PathDiagnostic& PD, PD.resetPath(); origReportConfigToken = R->getConfigurationChangeToken(); - // Generate the very last diagnostic piece - the piece is visible before + // Generate the very last diagnostic piece - the piece is visible before // the trace is expanded. std::unique_ptr<PathDiagnosticPiece> LastPiece; for (BugReport::visitor_iterator I = visitors.begin(), E = visitors.end(); @@ -3224,6 +3221,11 @@ void BugReporter::Register(BugType *BT) { void BugReporter::emitReport(std::unique_ptr<BugReport> R) { if (const ExplodedNode *E = R->getErrorNode()) { + // An error node must either be a sink or have a tag, otherwise + // it could get reclaimed before the path diagnostic is created. + assert((E->isSink() || E->getLocation().getTag()) && + "Error node must either be a sink or have a tag"); + const AnalysisDeclContext *DeclCtx = E->getLocationContext()->getAnalysisDeclContext(); // The source of autosynthesized body can be handcrafted AST or a model @@ -3234,7 +3236,7 @@ void BugReporter::emitReport(std::unique_ptr<BugReport> R) { !DeclCtx->isBodyAutosynthesizedFromModelFile()) return; } - + bool ValidSourceLoc = R->getLocation(getSourceManager()).isValid(); assert(ValidSourceLoc); // If we mess up in a release build, we'd still prefer to just drop the bug @@ -3269,10 +3271,10 @@ namespace { struct FRIEC_WLItem { const ExplodedNode *N; ExplodedNode::const_succ_iterator I, E; - + FRIEC_WLItem(const ExplodedNode *n) : N(n), I(N->succ_begin()), E(N->succ_end()) {} -}; +}; } static BugReport * @@ -3287,11 +3289,11 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, // post-dominated by a sink, simply add all the nodes in the equivalence class // to 'Nodes'. Any of the reports will serve as a "representative" report. if (!BT.isSuppressOnSink()) { - BugReport *R = I; + BugReport *R = &*I; for (BugReportEquivClass::iterator I=EQ.begin(), E=EQ.end(); I!=E; ++I) { const ExplodedNode *N = I->getErrorNode(); if (N) { - R = I; + R = &*I; bugReports.push_back(R); } } @@ -3317,35 +3319,35 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, } // No successors? By definition this nodes isn't post-dominated by a sink. if (errorNode->succ_empty()) { - bugReports.push_back(I); + bugReports.push_back(&*I); if (!exampleReport) - exampleReport = I; + exampleReport = &*I; continue; } // At this point we know that 'N' is not a sink and it has at least one - // successor. Use a DFS worklist to find a non-sink end-of-path node. + // successor. Use a DFS worklist to find a non-sink end-of-path node. typedef FRIEC_WLItem WLItem; typedef SmallVector<WLItem, 10> DFSWorkList; llvm::DenseMap<const ExplodedNode *, unsigned> Visited; - + DFSWorkList WL; WL.push_back(errorNode); Visited[errorNode] = 1; - + while (!WL.empty()) { WLItem &WI = WL.back(); assert(!WI.N->succ_empty()); - + for (; WI.I != WI.E; ++WI.I) { - const ExplodedNode *Succ = *WI.I; + const ExplodedNode *Succ = *WI.I; // End-of-path node? if (Succ->succ_empty()) { // If we found an end-of-path node that is not a sink. if (!Succ->isSink()) { - bugReports.push_back(I); + bugReports.push_back(&*I); if (!exampleReport) - exampleReport = I; + exampleReport = &*I; WL.clear(); break; } @@ -3426,7 +3428,7 @@ void BugReporter::FlushReport(BugReport *exampleReport, PathDiagnosticLocation L = exampleReport->getLocation(getSourceManager()); auto piece = llvm::make_unique<PathDiagnosticEventPiece>( L, exampleReport->getDescription()); - for (const SourceRange &Range : exampleReport->getRanges()) + for (SourceRange Range : exampleReport->getRanges()) piece->addRange(Range); D->setEndOfPath(std::move(piece)); } diff --git a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index f2915ed..ec1310d 100644 --- a/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -117,7 +117,7 @@ std::unique_ptr<PathDiagnosticPiece> BugReporterVisitor::getDefaultEndPath( // special ranges for this report. auto P = llvm::make_unique<PathDiagnosticEventPiece>( L, BR.getDescription(), Ranges.begin() == Ranges.end()); - for (const SourceRange &Range : Ranges) + for (SourceRange Range : Ranges) P->addRange(Range); return std::move(P); @@ -169,7 +169,7 @@ public: bool InEnableNullFPSuppression) { if (!CallEvent::isCallStmt(S)) return; - + // First, find when we processed the statement. do { if (Optional<CallExitEnd> CEE = Node->getLocationAs<CallExitEnd>()) @@ -192,11 +192,11 @@ public: Optional<CallExitEnd> CEE = Node->getLocationAs<CallExitEnd>(); if (!CEE) return; - + const StackFrameContext *CalleeContext = CEE->getCalleeContext(); if (CalleeContext->getCallSite() != S) return; - + // Check the return value. ProgramStateRef State = Node->getState(); SVal RetVal = State->getSVal(S, Node->getLocationContext()); @@ -281,7 +281,7 @@ public: EnableNullFPSuppression); return nullptr; } - + // If we're returning 0, we should track where that 0 came from. bugreporter::trackNullOrUndefValue(N, RetE, BR, /*IsArg*/ false, EnableNullFPSuppression); @@ -472,7 +472,7 @@ PathDiagnosticPiece *FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, InitE = PIP->getInitializer()->getInit(); } } - + // Otherwise, see if this is the store site: // (1) Succ has this binding and Pred does not, i.e. this is // where the binding first occurred. @@ -504,7 +504,7 @@ PathDiagnosticPiece *FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (Optional<CallEnter> CE = Succ->getLocationAs<CallEnter>()) { if (const VarRegion *VR = dyn_cast<VarRegion>(R)) { const ParmVarDecl *Param = cast<ParmVarDecl>(VR->getDecl()); - + ProgramStateManager &StateMgr = BRC.getStateManager(); CallEventManager &CallMgr = StateMgr.getCallEventManager(); @@ -681,7 +681,7 @@ PathDiagnosticPiece *FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, else os << "Assigning value"; } - + if (R->canPrintPretty()) { os << " to "; R->printPretty(os); @@ -931,7 +931,7 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, if (!N) return false; } - + ProgramStateRef state = N->getState(); // The message send could be nil due to the receiver being nil. @@ -959,7 +959,7 @@ bool bugreporter::trackNullOrUndefValue(const ExplodedNode *N, assert(LVNode && "Unable to find the lvalue node."); ProgramStateRef LVState = LVNode->getState(); SVal LVal = LVState->getSVal(Inner, LVNode->getLocationContext()); - + if (LVState->isNull(LVal).isConstrainedTrue()) { // In case of C++ references, we want to differentiate between a null // reference and reference to null pointer. @@ -1162,11 +1162,11 @@ PathDiagnosticPiece *ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, const ExplodedNode *Prev, BugReporterContext &BRC, BugReport &BR) { - + ProgramPoint progPoint = N->getLocation(); ProgramStateRef CurrentState = N->getState(); ProgramStateRef PrevState = Prev->getState(); - + // Compare the GDMs of the state, because that is where constraints // are managed. Note that ensure that we only look at nodes that // were generated by the analyzer engine proper, not checkers. @@ -1177,16 +1177,16 @@ PathDiagnosticPiece *ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, // If an assumption was made on a branch, it should be caught // here by looking at the state transition. if (Optional<BlockEdge> BE = progPoint.getAs<BlockEdge>()) { - const CFGBlock *srcBlk = BE->getSrc(); + const CFGBlock *srcBlk = BE->getSrc(); if (const Stmt *term = srcBlk->getTerminator()) return VisitTerminator(term, N, srcBlk, BE->getDst(), BR, BRC); return nullptr; } - + if (Optional<PostStmt> PS = progPoint.getAs<PostStmt>()) { // FIXME: Assuming that BugReporter is a GRBugReporter is a layering // violation. - const std::pair<const ProgramPointTag *, const ProgramPointTag *> &tags = + const std::pair<const ProgramPointTag *, const ProgramPointTag *> &tags = cast<GRBugReporter>(BRC.getBugReporter()). getEngine().geteagerlyAssumeBinOpBifurcationTags(); @@ -1222,7 +1222,7 @@ ConditionBRVisitor::VisitTerminator(const Stmt *Term, case Stmt::ConditionalOperatorClass: Cond = cast<ConditionalOperator>(Term)->getCond(); break; - } + } assert(Cond); assert(srcBlk->succ_size() == 2); @@ -1236,9 +1236,9 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, BugReport &R, const ExplodedNode *N) { - + const Expr *Ex = Cond; - + while (true) { Ex = Ex->IgnoreParenCasts(); switch (Ex->getStmtClass()) { @@ -1294,7 +1294,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, raw_ostream &Out, Out << '\''; return quotes; } - + if (const IntegerLiteral *IL = dyn_cast<IntegerLiteral>(Ex)) { QualType OriginalTy = OriginalExpr->getType(); if (OriginalTy->isPointerType()) { @@ -1309,11 +1309,11 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, raw_ostream &Out, return false; } } - + Out << IL->getValue(); return false; } - + return false; } @@ -1324,10 +1324,10 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, BugReport &R, const ExplodedNode *N) { - + bool shouldInvert = false; Optional<bool> shouldPrune; - + SmallString<128> LhsString, RhsString; { llvm::raw_svector_ostream OutLHS(LhsString), OutRHS(RhsString); @@ -1335,10 +1335,10 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, shouldPrune); const bool isVarRHS = patternMatch(BExpr->getRHS(), OutRHS, BRC, R, N, shouldPrune); - - shouldInvert = !isVarLHS && isVarRHS; + + shouldInvert = !isVarLHS && isVarRHS; } - + BinaryOperator::Opcode Op = BExpr->getOpcode(); if (BinaryOperator::isAssignmentOp(Op)) { @@ -1380,7 +1380,7 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, default: return nullptr; } - + switch (Op) { case BO_EQ: Out << "equal to "; @@ -1392,7 +1392,7 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, Out << BinaryOperator::getOpcodeStr(Op) << ' '; break; } - + Out << (shouldInvert ? LhsString : RhsString); const LocationContext *LCtx = N->getLocationContext(); PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); @@ -1416,7 +1416,7 @@ ConditionBRVisitor::VisitConditionVariable(StringRef LhsString, SmallString<256> buf; llvm::raw_svector_ostream Out(buf); Out << "Assuming " << LhsString << " is "; - + QualType Ty = CondVarExpr->getType(); if (Ty->isPointerType()) @@ -1444,10 +1444,10 @@ ConditionBRVisitor::VisitConditionVariable(StringRef LhsString, } } } - + return event; } - + PathDiagnosticPiece * ConditionBRVisitor::VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, @@ -1462,11 +1462,11 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, SmallString<256> Buf; llvm::raw_svector_ostream Out(Buf); - + Out << "Assuming '" << VD->getDeclName() << "' is "; - + QualType VDTy = VD->getType(); - + if (VDTy->isPointerType()) Out << (tookTrue ? "non-null" : "null"); else if (VDTy->isObjCObjectPointerType()) @@ -1480,7 +1480,7 @@ ConditionBRVisitor::VisitTrueTest(const Expr *Cond, PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); PathDiagnosticEventPiece *event = new PathDiagnosticEventPiece(Loc, Out.str()); - + const ProgramState *state = N->getState().get(); if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { if (report.isInteresting(R)) @@ -1615,13 +1615,13 @@ UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, // Function can only change the value passed in by address. continue; } - + // If it is a const pointer value, the function does not intend to // change the value. if (T->getPointeeType().isConstQualified()) continue; - // Mark the call site (LocationContext) as interesting if the value of the + // Mark the call site (LocationContext) as interesting if the value of the // argument is undefined or '0'/'NULL'. SVal BoundVal = State->getSVal(R); if (BoundVal.isUndef() || BoundVal.isZeroConstant()) { diff --git a/lib/StaticAnalyzer/Core/CMakeLists.txt b/lib/StaticAnalyzer/Core/CMakeLists.txt index 59a6b6f..aaffb0b 100644 --- a/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -6,6 +6,7 @@ add_clang_library(clangStaticAnalyzerCore AnalyzerOptions.cpp BasicValueFactory.cpp BlockCounter.cpp + IssueHash.cpp BugReporter.cpp BugReporterVisitors.cpp CallEvent.cpp @@ -17,6 +18,7 @@ add_clang_library(clangStaticAnalyzerCore CommonBugCategories.cpp ConstraintManager.cpp CoreEngine.cpp + DynamicTypeMap.cpp Environment.cpp ExplodedGraph.cpp ExprEngine.cpp @@ -26,6 +28,7 @@ add_clang_library(clangStaticAnalyzerCore ExprEngineObjC.cpp FunctionSummary.cpp HTMLDiagnostics.cpp + LoopWidening.cpp MemRegion.cpp PathDiagnostic.cpp PlistDiagnostics.cpp diff --git a/lib/StaticAnalyzer/Core/CallEvent.cpp b/lib/StaticAnalyzer/Core/CallEvent.cpp index 8542f7d..69af09b 100644 --- a/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -17,6 +17,7 @@ #include "clang/AST/ParentMap.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" @@ -49,11 +50,7 @@ QualType CallEvent::getResultType() const { return ResultTy; } -static bool isCallbackArg(SVal V, QualType T) { - // If the parameter is 0, it's harmless. - if (V.isZeroConstant()) - return false; - +static bool isCallback(QualType T) { // If a parameter is a block or a callback, assume it can modify pointer. if (T->isBlockPointerType() || T->isFunctionPointerType() || @@ -74,32 +71,53 @@ static bool isCallbackArg(SVal V, QualType T) { return true; } } - return false; } -bool CallEvent::hasNonZeroCallbackArg() const { +static bool isVoidPointerToNonConst(QualType T) { + if (const PointerType *PT = T->getAs<PointerType>()) { + QualType PointeeTy = PT->getPointeeType(); + if (PointeeTy.isConstQualified()) + return false; + return PointeeTy->isVoidType(); + } else + return false; +} + +bool CallEvent::hasNonNullArgumentsWithType(bool (*Condition)(QualType)) const { unsigned NumOfArgs = getNumArgs(); // If calling using a function pointer, assume the function does not - // have a callback. TODO: We could check the types of the arguments here. + // satisfy the callback. + // TODO: We could check the types of the arguments here. if (!getDecl()) return false; unsigned Idx = 0; for (CallEvent::param_type_iterator I = param_type_begin(), - E = param_type_end(); + E = param_type_end(); I != E && Idx < NumOfArgs; ++I, ++Idx) { if (NumOfArgs <= Idx) break; - if (isCallbackArg(getArgSVal(Idx), *I)) + // If the parameter is 0, it's harmless. + if (getArgSVal(Idx).isZeroConstant()) + continue; + + if (Condition(*I)) return true; } - return false; } +bool CallEvent::hasNonZeroCallbackArg() const { + return hasNonNullArgumentsWithType(isCallback); +} + +bool CallEvent::hasVoidPointerToNonConstArg() const { + return hasNonNullArgumentsWithType(isVoidPointerToNonConst); +} + bool CallEvent::isGlobalCFunction(StringRef FunctionName) const { const FunctionDecl *FD = dyn_cast_or_null<FunctionDecl>(getDecl()); if (!FD) @@ -147,7 +165,7 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, SmallVector<SVal, 8> ValuesToInvalidate; RegionAndSymbolInvalidationTraits ETraits; - getExtraInvalidatedValues(ValuesToInvalidate); + getExtraInvalidatedValues(ValuesToInvalidate, &ETraits); // Indexes of arguments whose values will be preserved by the call. llvm::SmallSet<unsigned, 4> PreserveArgs; @@ -159,7 +177,7 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, // below for efficiency. if (PreserveArgs.count(Idx)) if (const MemRegion *MR = getArgSVal(Idx).getAsRegion()) - ETraits.setTrait(MR->StripCasts(), + ETraits.setTrait(MR->StripCasts(), RegionAndSymbolInvalidationTraits::TK_PreserveContents); // TODO: Factor this out + handle the lower level const pointers. @@ -184,7 +202,7 @@ ProgramPoint CallEvent::getProgramPoint(bool IsPreVisit, } const Decl *D = getDecl(); - assert(D && "Cannot get a program point without a statement or decl"); + assert(D && "Cannot get a program point without a statement or decl"); SourceLocation Loc = getSourceRange().getBegin(); if (IsPreVisit) @@ -265,7 +283,7 @@ QualType CallEvent::getDeclaredResultType(const Decl *D) { return QualType(); } - + llvm_unreachable("unknown callable kind"); } @@ -325,7 +343,7 @@ void AnyFunctionCall::getInitialStackFrameContents( } bool AnyFunctionCall::argumentsMayEscape() const { - if (hasNonZeroCallbackArg()) + if (CallEvent::argumentsMayEscape() || hasVoidPointerToNonConstArg()) return true; const FunctionDecl *D = getDecl(); @@ -336,7 +354,7 @@ bool AnyFunctionCall::argumentsMayEscape() const { if (!II) return false; - // This set of "escaping" APIs is + // This set of "escaping" APIs is // - 'int pthread_setspecific(ptheread_key k, const void *)' stores a // value into thread local storage. The value can later be retrieved with @@ -402,8 +420,30 @@ const FunctionDecl *CXXInstanceCall::getDecl() const { return getSVal(CE->getCallee()).getAsFunctionDecl(); } -void CXXInstanceCall::getExtraInvalidatedValues(ValueList &Values) const { - Values.push_back(getCXXThisVal()); +void CXXInstanceCall::getExtraInvalidatedValues( + ValueList &Values, RegionAndSymbolInvalidationTraits *ETraits) const { + SVal ThisVal = getCXXThisVal(); + Values.push_back(ThisVal); + + // Don't invalidate if the method is const and there are no mutable fields. + if (const CXXMethodDecl *D = cast_or_null<CXXMethodDecl>(getDecl())) { + if (!D->isConst()) + return; + // Get the record decl for the class of 'This'. D->getParent() may return a + // base class decl, rather than the class of the instance which needs to be + // checked for mutable fields. + const Expr *Ex = getCXXThisExpr()->ignoreParenBaseCasts(); + const CXXRecordDecl *ParentRecord = Ex->getType()->getAsCXXRecordDecl(); + if (!ParentRecord || ParentRecord->hasMutableFields()) + return; + // Preserve CXXThis. + const MemRegion *ThisRegion = ThisVal.getAsRegion(); + if (!ThisRegion) + return; + + ETraits->setTrait(ThisRegion->getBaseRegion(), + RegionAndSymbolInvalidationTraits::TK_PreserveContents); + } } SVal CXXInstanceCall::getCXXThisVal() const { @@ -435,7 +475,7 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { return RuntimeDefinition(); // Do we know anything about the type of 'this'? - DynamicTypeInfo DynType = getState()->getDynamicTypeInfo(R); + DynamicTypeInfo DynType = getDynamicTypeInfo(getState(), R); if (!DynType.isValid()) return RuntimeDefinition(); @@ -455,7 +495,7 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // However, we should at least be able to search up and down our own class // hierarchy, and some real bugs have been caught by checking this. assert(!RD->isDerivedFrom(MD->getParent()) && "Couldn't find known method"); - + // FIXME: This is checking that our DynamicTypeInfo is at least as good as // the static type. However, because we currently don't update // DynamicTypeInfo when an object is cast, we can't actually be sure the @@ -525,7 +565,7 @@ RuntimeDefinition CXXMemberCall::getRuntimeDefinition() const { if (const MemberExpr *ME = dyn_cast<MemberExpr>(getOriginExpr()->getCallee())) if (ME->hasQualifier()) return AnyFunctionCall::getRuntimeDefinition(); - + return CXXInstanceCall::getRuntimeDefinition(); } @@ -549,7 +589,8 @@ ArrayRef<ParmVarDecl*> BlockCall::parameters() const { return D->parameters(); } -void BlockCall::getExtraInvalidatedValues(ValueList &Values) const { +void BlockCall::getExtraInvalidatedValues(ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const { // FIXME: This also needs to invalidate captured globals. if (const MemRegion *R = getBlockRegion()) Values.push_back(loc::MemRegionVal(R)); @@ -557,10 +598,25 @@ void BlockCall::getExtraInvalidatedValues(ValueList &Values) const { void BlockCall::getInitialStackFrameContents(const StackFrameContext *CalleeCtx, BindingsTy &Bindings) const { - const BlockDecl *D = cast<BlockDecl>(CalleeCtx->getDecl()); SValBuilder &SVB = getState()->getStateManager().getSValBuilder(); + ArrayRef<ParmVarDecl*> Params; + if (isConversionFromLambda()) { + auto *LambdaOperatorDecl = cast<CXXMethodDecl>(CalleeCtx->getDecl()); + Params = LambdaOperatorDecl->parameters(); + + // For blocks converted from a C++ lambda, the callee declaration is the + // operator() method on the lambda so we bind "this" to + // the lambda captured by the block. + const VarRegion *CapturedLambdaRegion = getRegionStoringCapturedLambda(); + SVal ThisVal = loc::MemRegionVal(CapturedLambdaRegion); + Loc ThisLoc = SVB.getCXXThis(LambdaOperatorDecl, CalleeCtx); + Bindings.push_back(std::make_pair(ThisLoc, ThisVal)); + } else { + Params = cast<BlockDecl>(CalleeCtx->getDecl())->parameters(); + } + addParameterValuesToBindings(CalleeCtx, Bindings, SVB, *this, - D->parameters()); + Params); } @@ -570,7 +626,8 @@ SVal CXXConstructorCall::getCXXThisVal() const { return UnknownVal(); } -void CXXConstructorCall::getExtraInvalidatedValues(ValueList &Values) const { +void CXXConstructorCall::getExtraInvalidatedValues(ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const { if (Data) Values.push_back(loc::MemRegionVal(static_cast<const MemRegion *>(Data))); } @@ -612,7 +669,8 @@ ArrayRef<ParmVarDecl*> ObjCMethodCall::parameters() const { } void -ObjCMethodCall::getExtraInvalidatedValues(ValueList &Values) const { +ObjCMethodCall::getExtraInvalidatedValues(ValueList &Values, + RegionAndSymbolInvalidationTraits *ETraits) const { Values.push_back(getReceiverSVal()); } @@ -628,7 +686,7 @@ SVal ObjCMethodCall::getReceiverSVal() const { // FIXME: Is this the best way to handle class receivers? if (!isInstanceMessage()) return UnknownVal(); - + if (const Expr *RecE = getOriginExpr()->getInstanceReceiver()) return getSVal(RecE); @@ -709,7 +767,7 @@ ObjCMessageKind ObjCMethodCall::getMessageKind() const { return K; } } - + const_cast<ObjCMethodCall *>(this)->Data = ObjCMessageDataTy(nullptr, 1).getOpaqueValue(); assert(getMessageKind() == OCM_Message); @@ -730,7 +788,7 @@ bool ObjCMethodCall::canBeOverridenInSubclass(ObjCInterfaceDecl *IDecl, getState()->getStateManager().getContext().getSourceManager(); // If the class interface is declared inside the main file, assume it is not - // subcassed. + // subcassed. // TODO: It could actually be subclassed if the subclass is private as well. // This is probably very rare. SourceLocation InterfLoc = IDecl->getEndOfDefinitionLoc(); @@ -800,7 +858,7 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { if (!Receiver) return RuntimeDefinition(); - DynamicTypeInfo DTI = getState()->getDynamicTypeInfo(Receiver); + DynamicTypeInfo DTI = getDynamicTypeInfo(getState(), Receiver); QualType DynType = DTI.getType(); CanBeSubClassed = DTI.canBeASubClass(); ReceiverT = dyn_cast<ObjCObjectPointerType>(DynType); diff --git a/lib/StaticAnalyzer/Core/Checker.cpp b/lib/StaticAnalyzer/Core/Checker.cpp index 2235211..b422a88 100644 --- a/lib/StaticAnalyzer/Core/Checker.cpp +++ b/lib/StaticAnalyzer/Core/Checker.cpp @@ -23,7 +23,7 @@ StringRef CheckerBase::getTagDescription() const { CheckName CheckerBase::getCheckName() const { return Name; } -CheckerProgramPointTag::CheckerProgramPointTag(StringRef CheckerName, +CheckerProgramPointTag::CheckerProgramPointTag(StringRef CheckerName, StringRef Msg) : SimpleProgramPointTag(CheckerName, Msg) {} diff --git a/lib/StaticAnalyzer/Core/CheckerContext.cpp b/lib/StaticAnalyzer/Core/CheckerContext.cpp index 6b22bf4..5ec8bfa 100644 --- a/lib/StaticAnalyzer/Core/CheckerContext.cpp +++ b/lib/StaticAnalyzer/Core/CheckerContext.cpp @@ -45,7 +45,7 @@ bool CheckerContext::isCLibraryFunction(const FunctionDecl *FD, if (BId != 0) { if (Name.empty()) return true; - StringRef BName = FD->getASTContext().BuiltinInfo.GetName(BId); + StringRef BName = FD->getASTContext().BuiltinInfo.getName(BId); if (BName.find(Name) != StringRef::npos) return true; } @@ -57,12 +57,8 @@ bool CheckerContext::isCLibraryFunction(const FunctionDecl *FD, return false; // Look through 'extern "C"' and anything similar invented in the future. - const DeclContext *DC = FD->getDeclContext(); - while (DC->isTransparentContext()) - DC = DC->getParent(); - - // If this function is in a namespace, it is not a C library function. - if (!DC->isTranslationUnit()) + // If this function is not in TU directly, it is not a C library function. + if (!FD->getDeclContext()->getRedeclContext()->isTranslationUnit()) return false; // If this function is not externally visible, it is not a C library function. diff --git a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 3d9a815..d6aeceb 100644 --- a/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerHelpers.h" +#include "clang/AST/Decl.h" #include "clang/AST/Expr.h" // Recursively find any substatements containing macros @@ -70,3 +71,26 @@ bool clang::ento::containsBuiltinOffsetOf(const Stmt *S) { return false; } + +// Extract lhs and rhs from assignment statement +std::pair<const clang::VarDecl *, const clang::Expr *> +clang::ento::parseAssignment(const Stmt *S) { + const VarDecl *VD = 0; + const Expr *RHS = 0; + + if (auto Assign = dyn_cast_or_null<BinaryOperator>(S)) { + if (Assign->isAssignmentOp()) { + // Ordinary assignment + RHS = Assign->getRHS(); + if (auto DE = dyn_cast_or_null<DeclRefExpr>(Assign->getLHS())) + VD = dyn_cast_or_null<VarDecl>(DE->getDecl()); + } + } else if (auto PD = dyn_cast_or_null<DeclStmt>(S)) { + // Initialization + assert(PD->isSingleDecl() && "We process decls one by one"); + VD = dyn_cast_or_null<VarDecl>(PD->getSingleDecl()); + RHS = VD->getAnyInitializer(); + } + + return std::make_pair(VD, RHS); +} diff --git a/lib/StaticAnalyzer/Core/CheckerManager.cpp b/lib/StaticAnalyzer/Core/CheckerManager.cpp index 2684cc7..008e8ef 100644 --- a/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -177,7 +177,9 @@ void CheckerManager::runCheckersForStmt(bool isPreVisit, namespace { struct CheckObjCMessageContext { typedef std::vector<CheckerManager::CheckObjCMessageFunc> CheckersTy; - bool IsPreVisit, WasInlined; + + ObjCMessageVisitKind Kind; + bool WasInlined; const CheckersTy &Checkers; const ObjCMethodCall &Msg; ExprEngine &Eng; @@ -185,14 +187,28 @@ namespace { CheckersTy::const_iterator checkers_begin() { return Checkers.begin(); } CheckersTy::const_iterator checkers_end() { return Checkers.end(); } - CheckObjCMessageContext(bool isPreVisit, const CheckersTy &checkers, + CheckObjCMessageContext(ObjCMessageVisitKind visitKind, + const CheckersTy &checkers, const ObjCMethodCall &msg, ExprEngine &eng, bool wasInlined) - : IsPreVisit(isPreVisit), WasInlined(wasInlined), Checkers(checkers), + : Kind(visitKind), WasInlined(wasInlined), Checkers(checkers), Msg(msg), Eng(eng) { } void runChecker(CheckerManager::CheckObjCMessageFunc checkFn, NodeBuilder &Bldr, ExplodedNode *Pred) { + + bool IsPreVisit; + + switch (Kind) { + case ObjCMessageVisitKind::Pre: + IsPreVisit = true; + break; + case ObjCMessageVisitKind::MessageNil: + case ObjCMessageVisitKind::Post: + IsPreVisit = false; + break; + } + const ProgramPoint &L = Msg.getProgramPoint(IsPreVisit,checkFn.Checker); CheckerContext C(Bldr, Eng, Pred, L, WasInlined); @@ -202,19 +218,30 @@ namespace { } /// \brief Run checkers for visiting obj-c messages. -void CheckerManager::runCheckersForObjCMessage(bool isPreVisit, +void CheckerManager::runCheckersForObjCMessage(ObjCMessageVisitKind visitKind, ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const ObjCMethodCall &msg, ExprEngine &Eng, bool WasInlined) { - CheckObjCMessageContext C(isPreVisit, - isPreVisit ? PreObjCMessageCheckers - : PostObjCMessageCheckers, - msg, Eng, WasInlined); + auto &checkers = getObjCMessageCheckers(visitKind); + CheckObjCMessageContext C(visitKind, checkers, msg, Eng, WasInlined); expandGraphWithCheckers(C, Dst, Src); } +const std::vector<CheckerManager::CheckObjCMessageFunc> & +CheckerManager::getObjCMessageCheckers(ObjCMessageVisitKind Kind) { + switch (Kind) { + case ObjCMessageVisitKind::Pre: + return PreObjCMessageCheckers; + break; + case ObjCMessageVisitKind::Post: + return PostObjCMessageCheckers; + case ObjCMessageVisitKind::MessageNil: + return ObjCMessageNilCheckers; + } + llvm_unreachable("Unknown Kind"); +} namespace { // FIXME: This has all the same signatures as CheckObjCMessageContext. // Is there a way we can merge the two? @@ -357,9 +384,9 @@ void CheckerManager::runCheckersForEndFunction(NodeBuilderContext &BC, ExplodedNodeSet &Dst, ExplodedNode *Pred, ExprEngine &Eng) { - + // We define the builder outside of the loop bacause if at least one checkers - // creates a sucsessor for Pred, we do not need to generate an + // creates a sucsessor for Pred, we do not need to generate an // autotransition for it. NodeBuilder Bldr(Pred, Dst, BC); for (unsigned i = 0, e = EndFunctionCheckers.size(); i != e; ++i) { @@ -467,7 +494,7 @@ bool CheckerManager::wantsRegionChangeUpdate(ProgramStateRef state) { } /// \brief Run checkers for region changes. -ProgramStateRef +ProgramStateRef CheckerManager::runCheckersForRegionChanges(ProgramStateRef state, const InvalidatedSymbols *invalidated, ArrayRef<const MemRegion *> ExplicitRegions, @@ -478,7 +505,7 @@ CheckerManager::runCheckersForRegionChanges(ProgramStateRef state, // bail out. if (!state) return nullptr; - state = RegionChangesCheckers[i].CheckFn(state, invalidated, + state = RegionChangesCheckers[i].CheckFn(state, invalidated, ExplicitRegions, Regions, Call); } return state; @@ -506,7 +533,7 @@ CheckerManager::runCheckersForPointerEscape(ProgramStateRef State, } /// \brief Run checkers for handling assumptions on symbolic values. -ProgramStateRef +ProgramStateRef CheckerManager::runCheckersForEvalAssume(ProgramStateRef state, SVal Cond, bool Assumption) { for (unsigned i = 0, e = EvalAssumeCheckers.size(); i != e; ++i) { @@ -558,7 +585,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, #endif } } - + // If none of the checkers evaluated the call, ask ExprEngine to handle it. if (!anyEvaluated) { NodeBuilder B(Pred, Dst, Eng.getBuilderContext()); @@ -616,6 +643,11 @@ void CheckerManager::_registerForPostStmt(CheckStmtFunc checkfn, void CheckerManager::_registerForPreObjCMessage(CheckObjCMessageFunc checkfn) { PreObjCMessageCheckers.push_back(checkfn); } + +void CheckerManager::_registerForObjCMessageNil(CheckObjCMessageFunc checkfn) { + ObjCMessageNilCheckers.push_back(checkfn); +} + void CheckerManager::_registerForPostObjCMessage(CheckObjCMessageFunc checkfn) { PostObjCMessageCheckers.push_back(checkfn); } diff --git a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp b/lib/StaticAnalyzer/Core/CheckerRegistry.cpp index 6ba64f5..a15e157 100644 --- a/lib/StaticAnalyzer/Core/CheckerRegistry.cpp +++ b/lib/StaticAnalyzer/Core/CheckerRegistry.cpp @@ -94,7 +94,7 @@ void CheckerRegistry::addChecker(InitializationFunction fn, StringRef name, } } -void CheckerRegistry::initializeManager(CheckerManager &checkerMgr, +void CheckerRegistry::initializeManager(CheckerManager &checkerMgr, SmallVectorImpl<CheckerOptInfo> &opts) const { // Sort checkers for efficient collection. std::sort(Checkers.begin(), Checkers.end(), checkerNameLT); diff --git a/lib/StaticAnalyzer/Core/ConstraintManager.cpp b/lib/StaticAnalyzer/Core/ConstraintManager.cpp index 4dec526..b7db833 100644 --- a/lib/StaticAnalyzer/Core/ConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/ConstraintManager.cpp @@ -26,7 +26,7 @@ static DefinedSVal getLocFromSymbol(const ProgramStateRef &State, } ConditionTruthVal ConstraintManager::checkNull(ProgramStateRef State, - SymbolRef Sym) { + SymbolRef Sym) { QualType Ty = Sym->getType(); DefinedSVal V = Loc::isLocType(Ty) ? getLocFromSymbol(State, Sym) : nonloc::SymbolVal(Sym); diff --git a/lib/StaticAnalyzer/Core/CoreEngine.cpp b/lib/StaticAnalyzer/Core/CoreEngine.cpp index 7844ad4..39cf7e7 100644 --- a/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -271,7 +271,7 @@ void CoreEngine::dispatchWorkItem(ExplodedNode* Pred, ProgramPoint Loc, bool CoreEngine::ExecuteWorkListWithInitialState(const LocationContext *L, unsigned Steps, - ProgramStateRef InitState, + ProgramStateRef InitState, ExplodedNodeSet &Dst) { bool DidNotFinish = ExecuteWorkList(L, Steps, InitState); for (ExplodedGraph::eop_iterator I = G.eop_begin(), E = G.eop_end(); I != E; @@ -386,7 +386,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { } return; } - + case Stmt::DoStmtClass: HandleBranch(cast<DoStmt>(Term)->getCond(), Term, B, Pred); return; @@ -456,7 +456,7 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { Pred->State, Pred); } -void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, +void CoreEngine::HandleBranch(const Stmt *Cond, const Stmt *Term, const CFGBlock * B, ExplodedNode *Pred) { assert(B->succ_size() == 2); NodeBuilderContext Ctx(*this, B, Pred); @@ -491,7 +491,7 @@ void CoreEngine::HandleStaticInit(const DeclStmt *DS, const CFGBlock *B, } -void CoreEngine::HandlePostStmt(const CFGBlock *B, unsigned StmtIdx, +void CoreEngine::HandlePostStmt(const CFGBlock *B, unsigned StmtIdx, ExplodedNode *Pred) { assert(B); assert(!B->empty()); diff --git a/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp new file mode 100644 index 0000000..fd35b66 --- /dev/null +++ b/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp @@ -0,0 +1,51 @@ +//==- DynamicTypeMap.cpp - Dynamic Type Info related APIs ----------*- 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 APIs that track and query dynamic type information. This +// information can be used to devirtualize calls during the symbolic exection +// or do type checking. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" + +namespace clang { +namespace ento { + +DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, + const MemRegion *Reg) { + Reg = Reg->StripCasts(); + + // Look up the dynamic type in the GDM. + const DynamicTypeInfo *GDMType = State->get<DynamicTypeMap>(Reg); + if (GDMType) + return *GDMType; + + // Otherwise, fall back to what we know about the region. + if (const TypedRegion *TR = dyn_cast<TypedRegion>(Reg)) + return DynamicTypeInfo(TR->getLocationType(), /*CanBeSubclass=*/false); + + if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(Reg)) { + SymbolRef Sym = SR->getSymbol(); + return DynamicTypeInfo(Sym->getType()); + } + + return DynamicTypeInfo(); +} + +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, + DynamicTypeInfo NewTy) { + Reg = Reg->StripCasts(); + ProgramStateRef NewState = State->set<DynamicTypeMap>(Reg, NewTy); + assert(NewState); + return NewState; +} + +} // namespace ento +} // namespace clang diff --git a/lib/StaticAnalyzer/Core/Environment.cpp b/lib/StaticAnalyzer/Core/Environment.cpp index ae5a4cc..e2cb52cb 100644 --- a/lib/StaticAnalyzer/Core/Environment.cpp +++ b/lib/StaticAnalyzer/Core/Environment.cpp @@ -90,6 +90,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::CXXNullPtrLiteralExprClass: case Stmt::ObjCStringLiteralClass: case Stmt::StringLiteralClass: + case Stmt::TypeTraitExprClass: // Known constants; defer to SValBuilder. return svalBuilder.getConstantVal(cast<Expr>(S)).getValue(); @@ -97,9 +98,9 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, const ReturnStmt *RS = cast<ReturnStmt>(S); if (const Expr *RE = RS->getRetValue()) return getSVal(EnvironmentEntry(RE, LCtx), svalBuilder); - return UndefinedVal(); + return UndefinedVal(); } - + // Handle all other Stmt* using a lookup. default: return lookupExpr(EnvironmentEntry(S, LCtx)); @@ -120,7 +121,7 @@ Environment EnvironmentManager::bindExpr(Environment Env, } namespace { -class MarkLiveCallback : public SymbolVisitor { +class MarkLiveCallback final : public SymbolVisitor { SymbolReaper &SymReaper; public: MarkLiveCallback(SymbolReaper &symreaper) : SymReaper(symreaper) {} @@ -170,10 +171,6 @@ EnvironmentManager::removeDeadBindings(Environment Env, // Copy the binding to the new map. EBMapRef = EBMapRef.add(BlkExpr, X); - // If the block expr's value is a memory region, then mark that region. - if (Optional<loc::MemRegionVal> R = X.getAs<loc::MemRegionVal>()) - SymReaper.markLive(R->getRegion()); - // Mark all symbols in the block expr's value live. RSScaner.scan(X); continue; @@ -194,16 +191,16 @@ void Environment::print(raw_ostream &Out, const char *NL, for (Environment::iterator I = begin(), E = end(); I != E; ++I) { const EnvironmentEntry &En = I.getKey(); - + if (isFirst) { Out << NL << NL << "Expressions:" - << NL; + << NL; isFirst = false; } else { Out << NL; } - + const Stmt *S = En.getStmt(); assert(S != nullptr && "Expected non-null Stmt"); diff --git a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index 010d26e..8a09720 100644 --- a/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -90,8 +90,8 @@ bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { // (7) The LocationContext is the same as the predecessor. // (8) Expressions that are *not* lvalue expressions. // (9) The PostStmt isn't for a non-consumed Stmt or Expr. - // (10) The successor is neither a CallExpr StmtPoint nor a CallEnter or - // PreImplicitCall (so that we would be able to find it when retrying a + // (10) The successor is neither a CallExpr StmtPoint nor a CallEnter or + // PreImplicitCall (so that we would be able to find it when retrying a // call with no inlining). // FIXME: It may be safe to reclaim PreCall and PostCall nodes as well. @@ -102,7 +102,7 @@ bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { const ExplodedNode *pred = *(node->pred_begin()); if (pred->succ_size() != 1) return false; - + const ExplodedNode *succ = *(node->succ_begin()); if (succ->pred_size() != 1) return false; @@ -123,7 +123,7 @@ bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { // Conditions 5, 6, and 7. ProgramStateRef state = node->getState(); - ProgramStateRef pred_state = pred->getState(); + ProgramStateRef pred_state = pred->getState(); if (state->store != pred_state->store || state->GDM != pred_state->GDM || progPoint.getLocationContext() != pred->getLocationContext()) return false; @@ -174,7 +174,7 @@ void ExplodedGraph::collectNode(ExplodedNode *node) { FreeNodes.push_back(node); Nodes.RemoveNode(node); --NumNodes; - node->~ExplodedNode(); + node->~ExplodedNode(); } void ExplodedGraph::reclaimRecentlyAllocatedNodes() { diff --git a/lib/StaticAnalyzer/Core/ExprEngine.cpp b/lib/StaticAnalyzer/Core/ExprEngine.cpp index a3239f5..662b0a2 100644 --- a/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -26,6 +26,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" #include "llvm/ADT/ImmutableList.h" #include "llvm/ADT/Statistic.h" #include "llvm/Support/raw_ostream.h" @@ -174,7 +175,7 @@ ProgramStateRef ExprEngine::getInitialState(const LocationContext *InitLoc) { } } } - + return state; } @@ -265,7 +266,7 @@ bool ExprEngine::wantsRegionChangeUpdate(ProgramStateRef state) { return getCheckerManager().wantsRegionChangeUpdate(state); } -ProgramStateRef +ProgramStateRef ExprEngine::processRegionChanges(ProgramStateRef state, const InvalidatedSymbols *invalidated, ArrayRef<const MemRegion *> Explicits, @@ -315,7 +316,7 @@ static bool shouldRemoveDeadBindings(AnalysisManager &AMgr, const CFGStmt S, const ExplodedNode *Pred, const LocationContext *LC) { - + // Are we never purging state values? if (AMgr.options.AnalysisPurgeOpt == PurgeNone) return false; @@ -327,7 +328,7 @@ static bool shouldRemoveDeadBindings(AnalysisManager &AMgr, // Is this on a non-expression? if (!isa<Expr>(S.getStmt())) return true; - + // Run before processing a call. if (CallEvent::isCallStmt(S.getStmt())) return true; @@ -475,8 +476,12 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init, if (BMI->isAnyMemberInitializer()) { // Constructors build the object directly in the field, // but non-objects must be copied in from the initializer. - const Expr *Init = BMI->getInit()->IgnoreImplicit(); - if (!isa<CXXConstructExpr>(Init)) { + if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) { + assert(BMI->getInit()->IgnoreImplicit() == CtorExpr); + (void)CtorExpr; + // The field was directly constructed, so there is no need to bind. + } else { + const Expr *Init = BMI->getInit()->IgnoreImplicit(); const ValueDecl *Field; if (BMI->isIndirectMemberInitializer()) { Field = BMI->getIndirectMember(); @@ -512,7 +517,7 @@ void ExprEngine::ProcessInitializer(const CFGInitializer Init, assert(Tmp.size() == 1 && "have not generated any new nodes yet"); assert(*Tmp.begin() == Pred && "have not generated any new nodes yet"); Tmp.clear(); - + PostInitializer PP(BMI, FieldLoc.getAsRegion(), stackFrame); evalBind(Tmp, Init, Pred, FieldLoc, InitVal, /*isInit=*/true, &PP); } @@ -754,9 +759,9 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXUuidofExprClass: case Stmt::CXXFoldExprClass: case Stmt::MSPropertyRefExprClass: + case Stmt::MSPropertySubscriptExprClass: case Stmt::CXXUnresolvedConstructExprClass: case Stmt::DependentScopeDeclRefExprClass: - case Stmt::TypeTraitExprClass: case Stmt::ArrayTypeTraitExprClass: case Stmt::ExpressionTraitExprClass: case Stmt::UnresolvedLookupExprClass: @@ -766,16 +771,19 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::PackExpansionExprClass: case Stmt::SubstNonTypeTemplateParmPackExprClass: case Stmt::FunctionParmPackExprClass: + case Stmt::CoroutineBodyStmtClass: + case Stmt::CoawaitExprClass: + case Stmt::CoreturnStmtClass: + case Stmt::CoyieldExprClass: case Stmt::SEHTryStmtClass: case Stmt::SEHExceptStmtClass: case Stmt::SEHLeaveStmtClass: - case Stmt::LambdaExprClass: case Stmt::SEHFinallyStmtClass: { const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState()); Engine.addAbortedBlock(node, currBldrCtx->getBlock()); break; } - + case Stmt::ParenExprClass: llvm_unreachable("ParenExprs already handled."); case Stmt::GenericSelectionExprClass: @@ -821,9 +829,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPOrderedDirectiveClass: case Stmt::OMPAtomicDirectiveClass: case Stmt::OMPTargetDirectiveClass: + case Stmt::OMPTargetDataDirectiveClass: case Stmt::OMPTeamsDirectiveClass: case Stmt::OMPCancellationPointDirectiveClass: case Stmt::OMPCancelDirectiveClass: + case Stmt::OMPTaskLoopDirectiveClass: + case Stmt::OMPTaskLoopSimdDirectiveClass: + case Stmt::OMPDistributeDirectiveClass: llvm_unreachable("Stmt should not be in analyzer evaluation loop"); case Stmt::ObjCSubscriptRefExprClass: @@ -901,7 +913,9 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::ObjCStringLiteralClass: case Stmt::CXXPseudoDestructorExprClass: case Stmt::SubstNonTypeTemplateParmExprClass: - case Stmt::CXXNullPtrLiteralExprClass: { + case Stmt::CXXNullPtrLiteralExprClass: + case Stmt::OMPArraySectionExprClass: + case Stmt::TypeTraitExprClass: { Bldr.takeNodes(Pred); ExplodedNodeSet preVisit; getCheckerManager().runCheckersForPreStmt(preVisit, Pred, S, *this); @@ -964,7 +978,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ExplodedNodeSet preVisit; getCheckerManager().runCheckersForPreStmt(preVisit, Pred, S, *this); - + ExplodedNodeSet Tmp; StmtNodeBuilder Bldr2(preVisit, Tmp, *currBldrCtx); @@ -972,7 +986,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, QualType resultType = Ex->getType(); for (ExplodedNodeSet::iterator it = preVisit.begin(), et = preVisit.end(); - it != et; ++it) { + it != et; ++it) { ExplodedNode *N = *it; const LocationContext *LCtx = N->getLocationContext(); SVal result = svalBuilder.conjureSymbolVal(nullptr, Ex, LCtx, @@ -981,10 +995,10 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, ProgramStateRef state = N->getState()->BindExpr(Ex, LCtx, result); Bldr2.generateNode(S, N, state); } - + getCheckerManager().runCheckersForPostStmt(Dst, Tmp, S, *this); Bldr.addNodes(Dst); - break; + break; } case Stmt::ArraySubscriptExprClass: @@ -1011,6 +1025,17 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; + case Stmt::LambdaExprClass: + if (AMgr.options.shouldInlineLambdas()) { + Bldr.takeNodes(Pred); + VisitLambdaExpr(cast<LambdaExpr>(S), Pred, Dst); + Bldr.addNodes(Dst); + } else { + const ExplodedNode *node = Bldr.generateSink(S, Pred, Pred->getState()); + Engine.addAbortedBlock(node, currBldrCtx->getBlock()); + } + break; + case Stmt::BinaryOperatorClass: { const BinaryOperator* B = cast<BinaryOperator>(S); if (B->isLogicalOp()) { @@ -1029,7 +1054,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, } Bldr.takeNodes(Pred); - + if (AMgr.options.eagerlyAssumeBinOpBifurcation && (B->isRelationalOp() || B->isEqualityOp())) { ExplodedNodeSet Tmp; @@ -1074,7 +1099,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; } - + case Stmt::CXXCatchStmtClass: { Bldr.takeNodes(Pred); VisitCXXCatchStmt(cast<CXXCatchStmt>(S), Pred, Dst); @@ -1083,7 +1108,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, } case Stmt::CXXTemporaryObjectExprClass: - case Stmt::CXXConstructExprClass: { + case Stmt::CXXConstructExprClass: { Bldr.takeNodes(Pred); VisitCXXConstructExpr(cast<CXXConstructExpr>(S), Pred, Dst); Bldr.addNodes(Dst); @@ -1105,7 +1130,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, const CXXDeleteExpr *CDE = cast<CXXDeleteExpr>(S); getCheckerManager().runCheckersForPreStmt(PreVisit, Pred, S, *this); - for (ExplodedNodeSet::iterator i = PreVisit.begin(), + for (ExplodedNodeSet::iterator i = PreVisit.begin(), e = PreVisit.end(); i != e ; ++i) VisitCXXDeleteExpr(CDE, *i, Dst); @@ -1171,18 +1196,18 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::CXXDynamicCastExprClass: case Stmt::CXXReinterpretCastExprClass: case Stmt::CXXConstCastExprClass: - case Stmt::CXXFunctionalCastExprClass: + case Stmt::CXXFunctionalCastExprClass: case Stmt::ObjCBridgedCastExprClass: { Bldr.takeNodes(Pred); const CastExpr *C = cast<CastExpr>(S); // Handle the previsit checks. ExplodedNodeSet dstPrevisit; getCheckerManager().runCheckersForPreStmt(dstPrevisit, Pred, C, *this); - + // Handle the expression itself. ExplodedNodeSet dstExpr; for (ExplodedNodeSet::iterator i = dstPrevisit.begin(), - e = dstPrevisit.end(); i != e ; ++i) { + e = dstPrevisit.end(); i != e ; ++i) { VisitCast(C, C->getSubExpr(), *i, dstExpr); } @@ -1199,7 +1224,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.addNodes(Dst); break; } - + case Stmt::InitListExprClass: Bldr.takeNodes(Pred); VisitInitListExpr(cast<InitListExpr>(S), Pred, Dst); @@ -1294,7 +1319,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, Bldr.takeNodes(Pred); ProgramStateRef state = Pred->getState(); const PseudoObjectExpr *PE = cast<PseudoObjectExpr>(S); - if (const Expr *Result = PE->getResultExpr()) { + if (const Expr *Result = PE->getResultExpr()) { SVal V = state->getSVal(Result, Pred->getLocationContext()); Bldr.generateNode(S, Pred, state->BindExpr(S, Pred->getLocationContext(), V)); @@ -1375,12 +1400,29 @@ bool ExprEngine::replayWithoutInlining(ExplodedNode *N, /// Block entrance. (Update counters). void ExprEngine::processCFGBlockEntrance(const BlockEdge &L, - NodeBuilderWithSinks &nodeBuilder, + NodeBuilderWithSinks &nodeBuilder, ExplodedNode *Pred) { PrettyStackTraceLocationContext CrashInfo(Pred->getLocationContext()); + // If this block is terminated by a loop and it has already been visited the + // maximum number of times, widen the loop. + unsigned int BlockCount = nodeBuilder.getContext().blockCount(); + if (BlockCount == AMgr.options.maxBlockVisitOnPath - 1 && + AMgr.options.shouldWidenLoops()) { + const Stmt *Term = nodeBuilder.getContext().getBlock()->getTerminator(); + if (!(Term && + (isa<ForStmt>(Term) || isa<WhileStmt>(Term) || isa<DoStmt>(Term)))) + return; + // Widen. + const LocationContext *LCtx = Pred->getLocationContext(); + ProgramStateRef WidenedState = + getWidenedLoopState(Pred->getState(), LCtx, BlockCount, Term); + nodeBuilder.generateNode(WidenedState, Pred); + return; + } + // FIXME: Refactor this into a checker. - if (nodeBuilder.getContext().blockCount() >= AMgr.options.maxBlockVisitOnPath) { + if (BlockCount >= AMgr.options.maxBlockVisitOnPath) { static SimpleProgramPointTag tag(TagProviderName, "Block count exceeded"); const ExplodedNode *Sink = nodeBuilder.generateSink(Pred->getState(), Pred, &tag); @@ -1537,7 +1579,6 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, return; } - if (const Expr *Ex = dyn_cast<Expr>(Condition)) Condition = Ex->IgnoreParens(); @@ -1583,7 +1624,7 @@ void ExprEngine::processBranch(const Stmt *Condition, const Stmt *Term, } } } - + // If the condition is still unknown, give up. if (X.isUnknownOrUndef()) { builder.generateNode(PrevState, true, PredI); @@ -1750,7 +1791,7 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { DefinedOrUnknownSVal CondV = CondV_untested.castAs<DefinedOrUnknownSVal>(); ProgramStateRef DefaultSt = state; - + iterator I = builder.begin(), EI = builder.end(); bool defaultIsFeasible = I == EI; @@ -1758,7 +1799,7 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { // Successor may be pruned out during CFG construction. if (!I.getBlock()) continue; - + const CaseStmt *Case = I.getCase(); // Evaluate the LHS of the case value. @@ -1772,47 +1813,24 @@ void ExprEngine::processSwitch(SwitchNodeBuilder& builder) { else V2 = V1; - // FIXME: Eventually we should replace the logic below with a range - // comparison, rather than concretize the values within the range. - // This should be easy once we have "ranges" for NonLVals. - - do { - nonloc::ConcreteInt CaseVal(getBasicVals().getValue(V1)); - DefinedOrUnknownSVal Res = svalBuilder.evalEQ(DefaultSt ? DefaultSt : state, - CondV, CaseVal); - - // Now "assume" that the case matches. - if (ProgramStateRef stateNew = state->assume(Res, true)) { - builder.generateCaseStmtNode(I, stateNew); - - // If CondV evaluates to a constant, then we know that this - // is the *only* case that we can take, so stop evaluating the - // others. - if (CondV.getAs<nonloc::ConcreteInt>()) - return; - } - - // Now "assume" that the case doesn't match. Add this state - // to the default state (if it is feasible). - if (DefaultSt) { - if (ProgramStateRef stateNew = DefaultSt->assume(Res, false)) { - defaultIsFeasible = true; - DefaultSt = stateNew; - } - else { - defaultIsFeasible = false; - DefaultSt = nullptr; - } - } - - // Concretize the next value in the range. - if (V1 == V2) - break; - - ++V1; - assert (V1 <= V2); - - } while (true); + ProgramStateRef StateCase; + if (Optional<NonLoc> NL = CondV.getAs<NonLoc>()) + std::tie(StateCase, DefaultSt) = + DefaultSt->assumeWithinInclusiveRange(*NL, V1, V2); + else // UnknownVal + StateCase = DefaultSt; + + if (StateCase) + builder.generateCaseStmtNode(I, StateCase); + + // Now "assume" that the case doesn't match. Add this state + // to the default state (if it is feasible). + if (DefaultSt) + defaultIsFeasible = true; + else { + defaultIsFeasible = false; + break; + } } if (!defaultIsFeasible) @@ -1849,13 +1867,44 @@ void ExprEngine::VisitCommonDeclRefExpr(const Expr *Ex, const NamedDecl *D, if (const VarDecl *VD = dyn_cast<VarDecl>(D)) { // C permits "extern void v", and if you cast the address to a valid type, - // you can even do things with it. We simply pretend + // you can even do things with it. We simply pretend assert(Ex->isGLValue() || VD->getType()->isVoidType()); - SVal V = state->getLValue(VD, Pred->getLocationContext()); + const LocationContext *LocCtxt = Pred->getLocationContext(); + const Decl *D = LocCtxt->getDecl(); + const auto *MD = D ? dyn_cast<CXXMethodDecl>(D) : nullptr; + const auto *DeclRefEx = dyn_cast<DeclRefExpr>(Ex); + SVal V; + bool IsReference; + if (AMgr.options.shouldInlineLambdas() && DeclRefEx && + DeclRefEx->refersToEnclosingVariableOrCapture() && MD && + MD->getParent()->isLambda()) { + // Lookup the field of the lambda. + const CXXRecordDecl *CXXRec = MD->getParent(); + llvm::DenseMap<const VarDecl *, FieldDecl *> LambdaCaptureFields; + FieldDecl *LambdaThisCaptureField; + CXXRec->getCaptureFields(LambdaCaptureFields, LambdaThisCaptureField); + const FieldDecl *FD = LambdaCaptureFields[VD]; + if (!FD) { + // When a constant is captured, sometimes no corresponding field is + // created in the lambda object. + assert(VD->getType().isConstQualified()); + V = state->getLValue(VD, LocCtxt); + IsReference = false; + } else { + Loc CXXThis = + svalBuilder.getCXXThis(MD, LocCtxt->getCurrentStackFrame()); + SVal CXXThisVal = state->getSVal(CXXThis); + V = state->getLValue(FD, CXXThisVal); + IsReference = FD->getType()->isReferenceType(); + } + } else { + V = state->getLValue(VD, LocCtxt); + IsReference = VD->getType()->isReferenceType(); + } // For references, the 'lvalue' is the pointer address stored in the // reference region. - if (VD->getType()->isReferenceType()) { + if (IsReference) { if (const MemRegion *R = V.getAsRegion()) V = state->getSVal(R); else @@ -1900,7 +1949,6 @@ void ExprEngine::VisitLvalArraySubscriptExpr(const ArraySubscriptExpr *A, const Expr *Base = A->getBase()->IgnoreParens(); const Expr *Idx = A->getIdx()->IgnoreParens(); - ExplodedNodeSet checkerPreStmt; getCheckerManager().runCheckersForPreStmt(checkerPreStmt, Pred, A, *this); @@ -2005,8 +2053,9 @@ void ExprEngine::VisitMemberExpr(const MemberExpr *M, ExplodedNode *Pred, } namespace { -class CollectReachableSymbolsCallback : public SymbolVisitor { +class CollectReachableSymbolsCallback final : public SymbolVisitor { InvalidatedSymbols Symbols; + public: CollectReachableSymbolsCallback(ProgramStateRef State) {} const InvalidatedSymbols &getSymbols() const { return Symbols; } @@ -2064,14 +2113,14 @@ ProgramStateRef ExprEngine::processPointerEscapedOnBind(ProgramStateRef State, return State; } -ProgramStateRef +ProgramStateRef ExprEngine::notifyCheckersOfPointerEscape(ProgramStateRef State, const InvalidatedSymbols *Invalidated, ArrayRef<const MemRegion *> ExplicitRegions, ArrayRef<const MemRegion *> Regions, const CallEvent *Call, RegionAndSymbolInvalidationTraits &ITraits) { - + if (!Invalidated || Invalidated->empty()) return State; @@ -2082,7 +2131,7 @@ ExprEngine::notifyCheckersOfPointerEscape(ProgramStateRef State, PSK_EscapeOther, &ITraits); - // If the symbols were invalidated by a call, we want to find out which ones + // If the symbols were invalidated by a call, we want to find out which ones // were invalidated directly due to being arguments to the call. InvalidatedSymbols SymbolsDirectlyInvalidated; for (ArrayRef<const MemRegion *>::iterator I = ExplicitRegions.begin(), @@ -2129,7 +2178,6 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE, getCheckerManager().runCheckersForBind(CheckedSet, Pred, location, Val, StoreE, *this, *PP); - StmtNodeBuilder Bldr(CheckedSet, Dst, *currBldrCtx); // If the location is not a 'Loc', it will already be handled by @@ -2142,13 +2190,12 @@ void ExprEngine::evalBind(ExplodedNodeSet &Dst, const Stmt *StoreE, Bldr.generateNode(L, state, Pred); return; } - for (ExplodedNodeSet::iterator I = CheckedSet.begin(), E = CheckedSet.end(); I!=E; ++I) { ExplodedNode *PredI = *I; ProgramStateRef state = PredI->getState(); - + state = processPointerEscapedOnBind(state, location, Val); // When binding the value, pass on the hint that this is a initialization. @@ -2301,7 +2348,7 @@ void ExprEngine::evalLocation(ExplodedNodeSet &Dst, // "p = 0" is not noted as "Null pointer value stored to 'p'" but // instead "int *p" is noted as // "Variable 'p' initialized to a null pointer value" - + static SimpleProgramPointTag tag(TagProviderName, "Location"); Bldr.generateNode(NodeEx, Pred, state, &tag); } @@ -2326,7 +2373,7 @@ void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, ExplodedNodeSet &Src, const Expr *Ex) { StmtNodeBuilder Bldr(Src, Dst, *currBldrCtx); - + for (ExplodedNodeSet::iterator I=Src.begin(), E=Src.end(); I!=E; ++I) { ExplodedNode *Pred = *I; // Test if the previous node was as the same expression. This can happen @@ -2349,7 +2396,7 @@ void ExprEngine::evalEagerlyAssumeBinOpBifurcation(ExplodedNodeSet &Dst, // First assume that the condition is true. if (StateTrue) { - SVal Val = svalBuilder.makeIntVal(1U, Ex->getType()); + SVal Val = svalBuilder.makeIntVal(1U, Ex->getType()); StateTrue = StateTrue->BindExpr(Ex, Pred->getLocationContext(), Val); Bldr.generateNode(Ex, Pred, StateTrue, tags.first); } @@ -2641,10 +2688,10 @@ struct DOTGraphTraits<ExplodedNode*> : << " NodeID: " << (const void*) N << "\\|"; state->printDOT(Out); - Out << "\\l"; + Out << "\\l"; if (const ProgramPointTag *tag = Loc.getTag()) { - Out << "\\|Tag: " << tag->getTagDescription(); + Out << "\\|Tag: " << tag->getTagDescription(); Out << "\\l"; } return Out.str(); diff --git a/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 1777ea9..a5b5871 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -25,23 +25,23 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, Expr *LHS = B->getLHS()->IgnoreParens(); Expr *RHS = B->getRHS()->IgnoreParens(); - + // FIXME: Prechecks eventually go in ::Visit(). ExplodedNodeSet CheckedSet; ExplodedNodeSet Tmp2; getCheckerManager().runCheckersForPreStmt(CheckedSet, Pred, B, *this); - + // With both the LHS and RHS evaluated, process the operation itself. for (ExplodedNodeSet::iterator it=CheckedSet.begin(), ei=CheckedSet.end(); it != ei; ++it) { - + ProgramStateRef state = (*it)->getState(); const LocationContext *LCtx = (*it)->getLocationContext(); SVal LeftV = state->getSVal(LHS, LCtx); SVal RightV = state->getSVal(RHS, LCtx); - + BinaryOperator::Opcode Op = B->getOpcode(); - + if (Op == BO_Assign) { // EXPERIMENTAL: "Conjured" symbols. // FIXME: Handle structs. @@ -57,7 +57,7 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, LeftV, RightV); continue; } - + if (!B->isAssignmentOp()) { StmtNodeBuilder Bldr(*it, Tmp2, *currBldrCtx); @@ -90,19 +90,19 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, // Process non-assignments except commas or short-circuited // logical expressions (LAnd and LOr). - SVal Result = evalBinOp(state, Op, LeftV, RightV, B->getType()); + SVal Result = evalBinOp(state, Op, LeftV, RightV, B->getType()); if (Result.isUnknown()) { Bldr.generateNode(B, *it, state); continue; - } + } - state = state->BindExpr(B, LCtx, Result); + state = state->BindExpr(B, LCtx, Result); Bldr.generateNode(B, *it, state); continue; } - + assert (B->isCompoundAssignmentOp()); - + switch (Op) { default: llvm_unreachable("Invalid opcode for compound assignment."); @@ -117,43 +117,43 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, case BO_XorAssign: Op = BO_Xor; break; case BO_OrAssign: Op = BO_Or; break; } - + // Perform a load (the LHS). This performs the checks for // null dereferences, and so on. ExplodedNodeSet Tmp; SVal location = LeftV; evalLoad(Tmp, B, LHS, *it, state, location); - + for (ExplodedNodeSet::iterator I = Tmp.begin(), E = Tmp.end(); I != E; ++I) { state = (*I)->getState(); const LocationContext *LCtx = (*I)->getLocationContext(); SVal V = state->getSVal(LHS, LCtx); - + // Get the computation type. QualType CTy = cast<CompoundAssignOperator>(B)->getComputationResultType(); CTy = getContext().getCanonicalType(CTy); - + QualType CLHSTy = cast<CompoundAssignOperator>(B)->getComputationLHSType(); CLHSTy = getContext().getCanonicalType(CLHSTy); - + QualType LTy = getContext().getCanonicalType(LHS->getType()); - + // Promote LHS. V = svalBuilder.evalCast(V, CLHSTy, LTy); - + // Compute the result of the operation. SVal Result = svalBuilder.evalCast(evalBinOp(state, Op, V, RightV, CTy), B->getType(), CTy); - + // EXPERIMENTAL: "Conjured" symbols. // FIXME: Handle structs. - + SVal LHSVal; - + if (Result.isUnknown()) { // The symbolic value is actually for the type of the left-hand side // expression, not the computation type, as this is the value the @@ -168,52 +168,74 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, // computation type. LHSVal = svalBuilder.evalCast(Result, LTy, CTy); } - - // In C++, assignment and compound assignment operators return an + + // In C++, assignment and compound assignment operators return an // lvalue. if (B->isGLValue()) state = state->BindExpr(B, LCtx, location); else state = state->BindExpr(B, LCtx, Result); - + evalStore(Tmp2, B, LHS, *I, state, location, LHSVal); } } - + // FIXME: postvisits eventually go in ::Visit() getCheckerManager().runCheckersForPostStmt(Dst, Tmp2, B, *this); } void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - + CanQualType T = getContext().getCanonicalType(BE->getType()); + const BlockDecl *BD = BE->getBlockDecl(); // Get the value of the block itself. - SVal V = svalBuilder.getBlockPointer(BE->getBlockDecl(), T, + SVal V = svalBuilder.getBlockPointer(BD, T, Pred->getLocationContext(), currBldrCtx->blockCount()); - + ProgramStateRef State = Pred->getState(); - + // If we created a new MemRegion for the block, we should explicitly bind // the captured variables. if (const BlockDataRegion *BDR = dyn_cast_or_null<BlockDataRegion>(V.getAsRegion())) { - + BlockDataRegion::referenced_vars_iterator I = BDR->referenced_vars_begin(), E = BDR->referenced_vars_end(); - + + auto CI = BD->capture_begin(); + auto CE = BD->capture_end(); for (; I != E; ++I) { - const MemRegion *capturedR = I.getCapturedRegion(); - const MemRegion *originalR = I.getOriginalRegion(); + const VarRegion *capturedR = I.getCapturedRegion(); + const VarRegion *originalR = I.getOriginalRegion(); + + // If the capture had a copy expression, use the result of evaluating + // that expression, otherwise use the original value. + // We rely on the invariant that the block declaration's capture variables + // are a prefix of the BlockDataRegion's referenced vars (which may include + // referenced globals, etc.) to enable fast lookup of the capture for a + // given referenced var. + const Expr *copyExpr = nullptr; + if (CI != CE) { + assert(CI->getVariable() == capturedR->getDecl()); + copyExpr = CI->getCopyExpr(); + CI++; + } + if (capturedR != originalR) { - SVal originalV = State->getSVal(loc::MemRegionVal(originalR)); + SVal originalV; + if (copyExpr) { + originalV = State->getSVal(copyExpr, Pred->getLocationContext()); + } else { + originalV = State->getSVal(loc::MemRegionVal(originalR)); + } State = State->bindLoc(loc::MemRegionVal(capturedR), originalV); } } } - + ExplodedNodeSet Tmp; StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx); Bldr.generateNode(BE, Pred, @@ -224,12 +246,12 @@ void ExprEngine::VisitBlockExpr(const BlockExpr *BE, ExplodedNode *Pred, getCheckerManager().runCheckersForPostStmt(Dst, Tmp, BE, *this); } -void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, +void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - + ExplodedNodeSet dstPreStmt; getCheckerManager().runCheckersForPreStmt(dstPreStmt, Pred, CastE, *this); - + if (CastE->getCastKind() == CK_LValueToRValue) { for (ExplodedNodeSet::iterator I = dstPreStmt.begin(), E = dstPreStmt.end(); I!=E; ++I) { @@ -240,18 +262,18 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, } return; } - - // All other casts. + + // All other casts. QualType T = CastE->getType(); QualType ExTy = Ex->getType(); - + if (const ExplicitCastExpr *ExCast=dyn_cast_or_null<ExplicitCastExpr>(CastE)) T = ExCast->getTypeAsWritten(); - + StmtNodeBuilder Bldr(dstPreStmt, Dst, *currBldrCtx); for (ExplodedNodeSet::iterator I = dstPreStmt.begin(), E = dstPreStmt.end(); I != E; ++I) { - + Pred = *I; ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); @@ -316,8 +338,8 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, case CK_IntegralComplexToFloatingComplex: case CK_CPointerToObjCPointerCast: case CK_BlockPointerToObjCPointerCast: - case CK_AnyPointerToBlockPointerCast: - case CK_ObjCObjectLValueCast: + case CK_AnyPointerToBlockPointerCast: + case CK_ObjCObjectLValueCast: case CK_ZeroToOCLEvent: case CK_LValueBitCast: { // Delegate to SValBuilder to process. @@ -371,7 +393,7 @@ void ExprEngine::VisitCast(const CastExpr *CastE, const Expr *Ex, svalBuilder.conjureSymbolVal(nullptr, CastE, LCtx, resultType, currBldrCtx->blockCount()); state = state->BindExpr(CastE, LCtx, NewSym); - } else + } else // Else, bind to the derived region value. state = state->BindExpr(CastE, LCtx, val); } @@ -417,7 +439,7 @@ void ExprEngine::VisitCompoundLiteralExpr(const CompoundLiteralExpr *CL, const Expr *Init = CL->getInitializer(); SVal V = State->getSVal(CL->getInitializer(), LCtx); - + if (isa<CXXConstructExpr>(Init)) { // No work needed. Just pass the value up to this expression. } else { @@ -450,11 +472,11 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred, Dst.insert(Pred); return; } - + // FIXME: all pre/post visits should eventually be handled by ::Visit(). ExplodedNodeSet dstPreVisit; getCheckerManager().runCheckersForPreStmt(dstPreVisit, Pred, DS, *this); - + ExplodedNodeSet dstEvaluated; StmtNodeBuilder B(dstPreVisit, dstEvaluated, *currBldrCtx); for (ExplodedNodeSet::iterator I = dstPreVisit.begin(), E = dstPreVisit.end(); @@ -470,7 +492,10 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred, ExplodedNode *UpdatedN = N; SVal InitVal = state->getSVal(InitEx, LC); - if (isa<CXXConstructExpr>(InitEx->IgnoreImplicit())) { + assert(DS->isSingleDecl()); + if (auto *CtorExpr = findDirectConstructorForCurrentCFGElement()) { + assert(InitEx->IgnoreImplicit() == CtorExpr); + (void)CtorExpr; // We constructed the object directly in the variable. // No need to bind anything. B.generateNode(DS, UpdatedN, state); @@ -485,7 +510,7 @@ void ExprEngine::VisitDeclStmt(const DeclStmt *DS, ExplodedNode *Pred, assert(InitVal.getAs<nonloc::LazyCompoundVal>()); } } - + // Recover some path-sensitivity if a scalar value evaluated to // UnknownVal. if (InitVal.isUnknown()) { @@ -596,7 +621,7 @@ void ExprEngine::VisitInitListExpr(const InitListExpr *IE, (T->isArrayType() || T->isRecordType() || T->isVectorType() || T->isAnyComplexType())) { llvm::ImmutableList<SVal> vals = getBasicVals().getEmptySValList(); - + // Handle base case where the initializer has no elements. // e.g: static int* myArray[] = {}; if (NumInitElements == 0) { @@ -604,13 +629,13 @@ void ExprEngine::VisitInitListExpr(const InitListExpr *IE, B.generateNode(IE, Pred, state->BindExpr(IE, LCtx, V)); return; } - + for (InitListExpr::const_reverse_iterator it = IE->rbegin(), ei = IE->rend(); it != ei; ++it) { SVal V = state->getSVal(cast<Expr>(*it), LCtx); vals = getBasicVals().consVals(V, vals); } - + B.generateNode(IE, Pred, state->BindExpr(IE, LCtx, svalBuilder.makeCompoundVal(T, vals))); @@ -632,7 +657,7 @@ void ExprEngine::VisitInitListExpr(const InitListExpr *IE, } void ExprEngine::VisitGuardedExpr(const Expr *Ex, - const Expr *L, + const Expr *L, const Expr *R, ExplodedNode *Pred, ExplodedNodeSet &Dst) { @@ -663,9 +688,7 @@ void ExprEngine::VisitGuardedExpr(const Expr *Ex, bool hasValue = false; SVal V; - for (CFGBlock::const_reverse_iterator I = SrcBlock->rbegin(), - E = SrcBlock->rend(); I != E; ++I) { - CFGElement CE = *I; + for (CFGElement CE : llvm::reverse(*SrcBlock)) { if (Optional<CFGStmt> CS = CE.getAs<CFGStmt>()) { const Expr *ValEx = cast<Expr>(CS->getStmt()); ValEx = ValEx->IgnoreParens(); @@ -694,7 +717,7 @@ void ExprEngine::VisitGuardedExpr(const Expr *Ex, } void ExprEngine:: -VisitOffsetOfExpr(const OffsetOfExpr *OOE, +VisitOffsetOfExpr(const OffsetOfExpr *OOE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { StmtNodeBuilder B(Pred, Dst, *currBldrCtx); APSInt IV; @@ -730,7 +753,7 @@ VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Ex, if (Ex->getKind() == UETT_SizeOf) { if (!T->isIncompleteType() && !T->isConstantSizeType()) { assert(T->isVariableArrayType() && "Unknown non-constant-sized type."); - + // FIXME: Add support for VLA type arguments and VLA expressions. // When that happens, we should probably refactor VLASizeChecker's code. continue; @@ -741,10 +764,10 @@ VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Ex, continue; } } - + APSInt Value = Ex->EvaluateKnownConstInt(getContext()); CharUnits amt = CharUnits::fromQuantity(Value.getZExtValue()); - + ProgramStateRef state = (*I)->getState(); state = state->BindExpr(Ex, (*I)->getLocationContext(), svalBuilder.makeIntVal(amt.getQuantity(), @@ -755,7 +778,7 @@ VisitUnaryExprOrTypeTraitExpr(const UnaryExprOrTypeTraitExpr *Ex, getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, Ex, *this); } -void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, +void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, ExplodedNode *Pred, ExplodedNodeSet &Dst) { // FIXME: Prechecks eventually go in ::Visit(). @@ -777,13 +800,13 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, } case UO_Real: { const Expr *Ex = U->getSubExpr()->IgnoreParens(); - + // FIXME: We don't have complex SValues yet. if (Ex->getType()->isAnyComplexType()) { // Just report "Unknown." break; } - + // For all other types, UO_Real is an identity operation. assert (U->getType() == Ex->getType()); ProgramStateRef state = (*I)->getState(); @@ -792,8 +815,8 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, state->getSVal(Ex, LCtx))); break; } - - case UO_Imag: { + + case UO_Imag: { const Expr *Ex = U->getSubExpr()->IgnoreParens(); // FIXME: We don't have complex SValues yet. if (Ex->getType()->isAnyComplexType()) { @@ -807,7 +830,7 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, X)); break; } - + case UO_Plus: assert(!U->isGLValue()); // FALL-THROUGH. @@ -820,7 +843,7 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, // Unary "+" is a no-op, similar to a parentheses. We still have places // where it may be a block-level expression, so we need to // generate an extra node that just propagates the value of the - // subexpression. + // subexpression. const Expr *Ex = U->getSubExpr()->IgnoreParens(); ProgramStateRef state = (*I)->getState(); const LocationContext *LCtx = (*I)->getLocationContext(); @@ -828,7 +851,7 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, state->getSVal(Ex, LCtx))); break; } - + case UO_LNot: case UO_Minus: case UO_Not: { @@ -836,15 +859,15 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, const Expr *Ex = U->getSubExpr()->IgnoreParens(); ProgramStateRef state = (*I)->getState(); const LocationContext *LCtx = (*I)->getLocationContext(); - + // Get the value of the subexpression. SVal V = state->getSVal(Ex, LCtx); - + if (V.isUnknownOrUndef()) { Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, V)); break; } - + switch (U->getOpcode()) { default: llvm_unreachable("Invalid Opcode."); @@ -861,7 +884,7 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, // // Note: technically we do "E == 0", but this is the same in the // transfer functions as "0 == E". - SVal Result; + SVal Result; if (Optional<Loc> LV = V.getAs<Loc>()) { Loc X = svalBuilder.makeNull(); Result = evalBinOp(state, BO_EQ, *LV, X, U->getType()); @@ -874,8 +897,8 @@ void ExprEngine::VisitUnaryOperator(const UnaryOperator* U, Result = evalBinOp(state, BO_EQ, V.castAs<NonLoc>(), X, U->getType()); } - - state = state->BindExpr(U, LCtx, Result); + + state = state->BindExpr(U, LCtx, Result); break; } Bldr.generateNode(U, *I, state); @@ -893,81 +916,81 @@ void ExprEngine::VisitIncrementDecrementOperator(const UnaryOperator* U, // Handle ++ and -- (both pre- and post-increment). assert (U->isIncrementDecrementOp()); const Expr *Ex = U->getSubExpr()->IgnoreParens(); - + const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef state = Pred->getState(); SVal loc = state->getSVal(Ex, LCtx); - + // Perform a load. ExplodedNodeSet Tmp; evalLoad(Tmp, U, Ex, Pred, state, loc); - + ExplodedNodeSet Dst2; StmtNodeBuilder Bldr(Tmp, Dst2, *currBldrCtx); for (ExplodedNodeSet::iterator I=Tmp.begin(), E=Tmp.end();I!=E;++I) { - + state = (*I)->getState(); assert(LCtx == (*I)->getLocationContext()); SVal V2_untested = state->getSVal(Ex, LCtx); - + // Propagate unknown and undefined values. if (V2_untested.isUnknownOrUndef()) { Bldr.generateNode(U, *I, state->BindExpr(U, LCtx, V2_untested)); continue; } DefinedSVal V2 = V2_untested.castAs<DefinedSVal>(); - + // Handle all other values. BinaryOperator::Opcode Op = U->isIncrementOp() ? BO_Add : BO_Sub; - + // If the UnaryOperator has non-location type, use its type to create the // constant value. If the UnaryOperator has location type, create the // constant with int type and pointer width. SVal RHS; - + if (U->getType()->isAnyPointerType()) RHS = svalBuilder.makeArrayIndex(1); else if (U->getType()->isIntegralOrEnumerationType()) RHS = svalBuilder.makeIntVal(1, U->getType()); else RHS = UnknownVal(); - + SVal Result = evalBinOp(state, Op, V2, RHS, U->getType()); - + // Conjure a new symbol if necessary to recover precision. if (Result.isUnknown()){ DefinedOrUnknownSVal SymVal = svalBuilder.conjureSymbolVal(nullptr, Ex, LCtx, currBldrCtx->blockCount()); Result = SymVal; - + // If the value is a location, ++/-- should always preserve // non-nullness. Check if the original value was non-null, and if so // propagate that constraint. if (Loc::isLocType(U->getType())) { DefinedOrUnknownSVal Constraint = svalBuilder.evalEQ(state, V2,svalBuilder.makeZeroVal(U->getType())); - + if (!state->assume(Constraint, true)) { // It isn't feasible for the original value to be null. // Propagate this constraint. Constraint = svalBuilder.evalEQ(state, SymVal, svalBuilder.makeZeroVal(U->getType())); - - + + state = state->assume(Constraint, false); assert(state); } } } - + // Since the lvalue-to-rvalue conversion is explicit in the AST, // we bind an l-value if the operator is prefix and an lvalue (in C++). if (U->isGLValue()) state = state->BindExpr(U, LCtx, loc); else state = state->BindExpr(U, LCtx, U->isPostfix() ? V2 : Result); - + // Perform the store. Bldr.takeNodes(*I); ExplodedNodeSet Dst3; diff --git a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 2a76621..556e223 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -103,49 +103,32 @@ static SVal makeZeroElementRegion(ProgramStateRef State, SVal LValue, } -static const MemRegion *getRegionForConstructedObject( - const CXXConstructExpr *CE, ExplodedNode *Pred, ExprEngine &Eng, - unsigned int CurrStmtIdx) { +const MemRegion * +ExprEngine::getRegionForConstructedObject(const CXXConstructExpr *CE, + ExplodedNode *Pred) { const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); - const NodeBuilderContext &CurrBldrCtx = Eng.getBuilderContext(); // See if we're constructing an existing region by looking at the next // element in the CFG. - const CFGBlock *B = CurrBldrCtx.getBlock(); - unsigned int NextStmtIdx = CurrStmtIdx + 1; - if (NextStmtIdx < B->size()) { - CFGElement Next = (*B)[NextStmtIdx]; - - // Is this a destructor? If so, we might be in the middle of an assignment - // to a local or member: look ahead one more element to see what we find. - while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) { - ++NextStmtIdx; - Next = (*B)[NextStmtIdx]; - } - // Is this a constructor for a local variable? - if (Optional<CFGStmt> StmtElem = Next.getAs<CFGStmt>()) { - if (const DeclStmt *DS = dyn_cast<DeclStmt>(StmtElem->getStmt())) { - if (const VarDecl *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) { - if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) { - SVal LValue = State->getLValue(Var, LCtx); - QualType Ty = Var->getType(); - LValue = makeZeroElementRegion(State, LValue, Ty); - return LValue.getAsRegion(); - } + if (auto Elem = findElementDirectlyInitializedByCurrentConstructor()) { + if (Optional<CFGStmt> StmtElem = Elem->getAs<CFGStmt>()) { + auto *DS = cast<DeclStmt>(StmtElem->getStmt()); + if (const auto *Var = dyn_cast<VarDecl>(DS->getSingleDecl())) { + if (Var->getInit() && Var->getInit()->IgnoreImplicit() == CE) { + SVal LValue = State->getLValue(Var, LCtx); + QualType Ty = Var->getType(); + LValue = makeZeroElementRegion(State, LValue, Ty); + return LValue.getAsRegion(); } } - } - - // Is this a constructor for a member? - if (Optional<CFGInitializer> InitElem = Next.getAs<CFGInitializer>()) { + } else if (Optional<CFGInitializer> InitElem = Elem->getAs<CFGInitializer>()) { const CXXCtorInitializer *Init = InitElem->getInitializer(); assert(Init->isAnyMemberInitializer()); - const CXXMethodDecl *CurCtor = cast<CXXMethodDecl>(LCtx->getDecl()); - Loc ThisPtr = Eng.getSValBuilder().getCXXThis(CurCtor, - LCtx->getCurrentStackFrame()); + Loc ThisPtr = + getSValBuilder().getCXXThis(CurCtor, LCtx->getCurrentStackFrame()); SVal ThisVal = State->getSVal(ThisPtr); const ValueDecl *Field; @@ -167,13 +150,86 @@ static const MemRegion *getRegionForConstructedObject( // Don't forget to update the pre-constructor initialization code in // ExprEngine::VisitCXXConstructExpr. } - // If we couldn't find an existing region to construct into, assume we're // constructing a temporary. - MemRegionManager &MRMgr = Eng.getSValBuilder().getRegionManager(); + MemRegionManager &MRMgr = getSValBuilder().getRegionManager(); return MRMgr.getCXXTempObjectRegion(CE, LCtx); } +/// Returns true if the initializer for \Elem can be a direct +/// constructor. +static bool canHaveDirectConstructor(CFGElement Elem){ + // DeclStmts and CXXCtorInitializers for fields can be directly constructed. + + if (Optional<CFGStmt> StmtElem = Elem.getAs<CFGStmt>()) { + if (isa<DeclStmt>(StmtElem->getStmt())) { + return true; + } + } + + if (Elem.getKind() == CFGElement::Initializer) { + return true; + } + + return false; +} + +Optional<CFGElement> +ExprEngine::findElementDirectlyInitializedByCurrentConstructor() { + const NodeBuilderContext &CurrBldrCtx = getBuilderContext(); + // See if we're constructing an existing region by looking at the next + // element in the CFG. + const CFGBlock *B = CurrBldrCtx.getBlock(); + assert(isa<CXXConstructExpr>(((*B)[currStmtIdx]).castAs<CFGStmt>().getStmt())); + unsigned int NextStmtIdx = currStmtIdx + 1; + if (NextStmtIdx >= B->size()) + return None; + + CFGElement Next = (*B)[NextStmtIdx]; + + // Is this a destructor? If so, we might be in the middle of an assignment + // to a local or member: look ahead one more element to see what we find. + while (Next.getAs<CFGImplicitDtor>() && NextStmtIdx + 1 < B->size()) { + ++NextStmtIdx; + Next = (*B)[NextStmtIdx]; + } + + if (canHaveDirectConstructor(Next)) + return Next; + + return None; +} + +const CXXConstructExpr * +ExprEngine::findDirectConstructorForCurrentCFGElement() { + // Go backward in the CFG to see if the previous element (ignoring + // destructors) was a CXXConstructExpr. If so, that constructor + // was constructed directly into an existing region. + // This process is essentially the inverse of that performed in + // findElementDirectlyInitializedByCurrentConstructor(). + if (currStmtIdx == 0) + return nullptr; + + const CFGBlock *B = getBuilderContext().getBlock(); + assert(canHaveDirectConstructor((*B)[currStmtIdx])); + + unsigned int PreviousStmtIdx = currStmtIdx - 1; + CFGElement Previous = (*B)[PreviousStmtIdx]; + + while (Previous.getAs<CFGImplicitDtor>() && PreviousStmtIdx > 0) { + --PreviousStmtIdx; + Previous = (*B)[PreviousStmtIdx]; + } + + if (Optional<CFGStmt> PrevStmtElem = Previous.getAs<CFGStmt>()) { + if (auto *CtorExpr = dyn_cast<CXXConstructExpr>(PrevStmtElem->getStmt())) { + return CtorExpr; + } + } + + return nullptr; +} + void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, ExplodedNode *Pred, ExplodedNodeSet &destNodes) { @@ -188,7 +244,7 @@ void ExprEngine::VisitCXXConstructExpr(const CXXConstructExpr *CE, switch (CE->getConstructionKind()) { case CXXConstructExpr::CK_Complete: { - Target = getRegionForConstructedObject(CE, Pred, *this, currStmtIdx); + Target = getRegionForConstructedObject(CE, Pred); break; } case CXXConstructExpr::CK_VirtualBase: @@ -300,7 +356,7 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, const MemRegion *Dest, const Stmt *S, bool IsBaseDtor, - ExplodedNode *Pred, + ExplodedNode *Pred, ExplodedNodeSet &Dst) { const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); @@ -373,7 +429,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, // Also, we need to decide how allocators actually work -- they're not // really part of the CXXNewExpr because they happen BEFORE the // CXXConstructExpr subexpression. See PR12014 for some discussion. - + unsigned blockCount = currBldrCtx->blockCount(); const LocationContext *LCtx = Pred->getLocationContext(); DefinedOrUnknownSVal symVal = UnknownVal(); @@ -392,8 +448,8 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, IsStandardGlobalOpNewFunction = (FD->getNumParams() == 1); } - // We assume all standard global 'operator new' functions allocate memory in - // heap. We realize this is an approximation that might not correctly model + // We assume all standard global 'operator new' functions allocate memory in + // heap. We realize this is an approximation that might not correctly model // a custom global allocator. if (IsStandardGlobalOpNewFunction) symVal = svalBuilder.getConjuredHeapSymbolVal(CNE, LCtx, blockCount); @@ -472,7 +528,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, } } -void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE, +void ExprEngine::VisitCXXDeleteExpr(const CXXDeleteExpr *CDE, ExplodedNode *Pred, ExplodedNodeSet &Dst) { StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); ProgramStateRef state = Pred->getState(); @@ -513,3 +569,55 @@ void ExprEngine::VisitCXXThisExpr(const CXXThisExpr *TE, ExplodedNode *Pred, SVal V = state->getSVal(loc::MemRegionVal(R)); Bldr.generateNode(TE, Pred, state->BindExpr(TE, LCtx, V)); } + +void ExprEngine::VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred, + ExplodedNodeSet &Dst) { + const LocationContext *LocCtxt = Pred->getLocationContext(); + + // Get the region of the lambda itself. + const MemRegion *R = svalBuilder.getRegionManager().getCXXTempObjectRegion( + LE, LocCtxt); + SVal V = loc::MemRegionVal(R); + + ProgramStateRef State = Pred->getState(); + + // If we created a new MemRegion for the lambda, we should explicitly bind + // the captures. + CXXRecordDecl::field_iterator CurField = LE->getLambdaClass()->field_begin(); + for (LambdaExpr::const_capture_init_iterator i = LE->capture_init_begin(), + e = LE->capture_init_end(); + i != e; ++i, ++CurField) { + FieldDecl *FieldForCapture = *CurField; + SVal FieldLoc = State->getLValue(FieldForCapture, V); + + SVal InitVal; + if (!FieldForCapture->hasCapturedVLAType()) { + Expr *InitExpr = *i; + assert(InitExpr && "Capture missing initialization expression"); + InitVal = State->getSVal(InitExpr, LocCtxt); + } else { + // The field stores the length of a captured variable-length array. + // These captures don't have initialization expressions; instead we + // get the length from the VLAType size expression. + Expr *SizeExpr = FieldForCapture->getCapturedVLAType()->getSizeExpr(); + InitVal = State->getSVal(SizeExpr, LocCtxt); + } + + State = State->bindLoc(FieldLoc, InitVal); + } + + // Decay the Loc into an RValue, because there might be a + // MaterializeTemporaryExpr node above this one which expects the bound value + // to be an RValue. + SVal LambdaRVal = State->getSVal(R); + + ExplodedNodeSet Tmp; + StmtNodeBuilder Bldr(Pred, Tmp, *currBldrCtx); + // FIXME: is this the right program point kind? + Bldr.generateNode(LE, Pred, + State->BindExpr(LE, LocCtxt, LambdaRVal), + nullptr, ProgramPoint::PostLValueKind); + + // FIXME: Move all post/pre visits to ::Visit(). + getCheckerManager().runCheckersForPostStmt(Dst, Tmp, LE, *this); +} diff --git a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 3f608ba..74cc8d2 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -44,19 +44,19 @@ void ExprEngine::processCallEnter(CallEnter CE, ExplodedNode *Pred) { const CFG *CalleeCFG = calleeCtx->getCFG(); const CFGBlock *Entry = &(CalleeCFG->getEntry()); - + // Validate the CFG. assert(Entry->empty()); assert(Entry->succ_size() == 1); - + // Get the solitary successor. const CFGBlock *Succ = *(Entry->succ_begin()); - + // Construct an edge representing the starting location in the callee. BlockEdge Loc(Entry, Succ, calleeCtx); ProgramStateRef state = Pred->getState(); - + // Construct a new node and add it to the worklist. bool isNew; ExplodedNode *Node = G.getNode(Loc, state, false, &isNew); @@ -207,8 +207,8 @@ static bool isTemporaryPRValue(const CXXConstructExpr *E, SVal V) { return isa<CXXTempObjectRegion>(MR); } -/// The call exit is simulated with a sequence of nodes, which occur between -/// CallExitBegin and CallExitEnd. The following operations occur between the +/// The call exit is simulated with a sequence of nodes, which occur between +/// CallExitBegin and CallExitEnd. The following operations occur between the /// two program points: /// 1. CallExitBegin (triggers the start of call exit sequence) /// 2. Bind the return value @@ -220,12 +220,12 @@ void ExprEngine::processCallExit(ExplodedNode *CEBNode) { PrettyStackTraceLocationContext CrashInfo(CEBNode->getLocationContext()); const StackFrameContext *calleeCtx = CEBNode->getLocationContext()->getCurrentStackFrame(); - + // The parent context might not be a stack frame, so make sure we // look up the first enclosing stack frame. const StackFrameContext *callerCtx = calleeCtx->getParent()->getCurrentStackFrame(); - + const Stmt *CE = calleeCtx->getCallSite(); ProgramStateRef state = CEBNode->getState(); // Find the last statement in the function and the corresponding basic block. @@ -421,7 +421,8 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, const LocationContext *CurLC = Pred->getLocationContext(); const StackFrameContext *CallerSFC = CurLC->getCurrentStackFrame(); const LocationContext *ParentOfCallee = CallerSFC; - if (Call.getKind() == CE_Block) { + if (Call.getKind() == CE_Block && + !cast<BlockCall>(Call).isConversionFromLambda()) { const BlockDataRegion *BR = cast<BlockCall>(Call).getBlockRegion(); assert(BR && "If we have the block definition we should have its region"); AnalysisDeclContext *BlockCtx = AMgr.getAnalysisDeclContext(D); @@ -429,7 +430,7 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, cast<BlockDecl>(D), BR); } - + // This may be NULL, but that's fine. const Expr *CallE = Call.getOriginExpr(); @@ -439,8 +440,8 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, CalleeADC->getStackFrame(ParentOfCallee, CallE, currBldrCtx->getBlock(), currStmtIdx); - - + + CallEnter Loc(CallE, CalleeSFC, CurLC); // Construct a new state which contains the mapping from actual to @@ -690,9 +691,11 @@ static bool hasMember(const ASTContext &Ctx, const CXXRecordDecl *RD, return true; CXXBasePaths Paths(false, false, false); - if (RD->lookupInBases(&CXXRecordDecl::FindOrdinaryMember, - DeclName.getAsOpaquePtr(), - Paths)) + if (RD->lookupInBases( + [DeclName](const CXXBaseSpecifier *Specifier, CXXBasePath &Path) { + return CXXRecordDecl::FindOrdinaryMember(Specifier, Path, DeclName); + }, + Paths)) return true; return false; @@ -767,7 +770,7 @@ static bool mayInlineDecl(AnalysisDeclContext *CalleeADC, if (!Ctx.getSourceManager().isInMainFile(FD->getLocation())) if (isContainerMethod(Ctx, FD)) return false; - + // Conditionally control the inlining of the destructor of C++ shared_ptr. // We don't currently do a good job modeling shared_ptr because we can't // see the reference count, so treating as opaque is probably the best @@ -868,7 +871,8 @@ bool ExprEngine::shouldInlineCall(const CallEvent &Call, const Decl *D, // Do not inline large functions too many times. if ((Engine.FunctionSummaries->getNumTimesInlined(D) > Opts.getMaxTimesInlineLarge()) && - CalleeCFG->getNumBlockIDs() > 13) { + CalleeCFG->getNumBlockIDs() >= + Opts.getMinCFGSizeTreatFunctionsAsLarge()) { NumReachedInlineCountMax++; return false; } @@ -990,12 +994,12 @@ void ExprEngine::BifurcateCall(const MemRegion *BifurReg, void ExprEngine::VisitReturnStmt(const ReturnStmt *RS, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - + ExplodedNodeSet dstPreVisit; getCheckerManager().runCheckersForPreStmt(dstPreVisit, Pred, RS, *this); StmtNodeBuilder B(dstPreVisit, Dst, *currBldrCtx); - + if (RS->getRetValue()) { for (ExplodedNodeSet::iterator it = dstPreVisit.begin(), ei = dstPreVisit.end(); it != ei; ++it) { diff --git a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp index a6611e0..92c5fe6 100644 --- a/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp +++ b/lib/StaticAnalyzer/Core/ExprEngineObjC.cpp @@ -19,18 +19,18 @@ using namespace clang; using namespace ento; -void ExprEngine::VisitLvalObjCIvarRefExpr(const ObjCIvarRefExpr *Ex, +void ExprEngine::VisitLvalObjCIvarRefExpr(const ObjCIvarRefExpr *Ex, ExplodedNode *Pred, ExplodedNodeSet &Dst) { ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); SVal baseVal = state->getSVal(Ex->getBase(), LCtx); SVal location = state->getLValue(Ex->getDecl(), baseVal); - + ExplodedNodeSet dstIvar; StmtNodeBuilder Bldr(Pred, dstIvar, *currBldrCtx); Bldr.generateNode(Ex, Pred, state->BindExpr(Ex, LCtx, location)); - + // Perform the post-condition check of the ObjCIvarRefExpr and store // the created nodes in 'Dst'. getCheckerManager().runCheckersForPostStmt(Dst, dstIvar, Ex, *this); @@ -45,7 +45,7 @@ void ExprEngine::VisitObjCAtSynchronizedStmt(const ObjCAtSynchronizedStmt *S, void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, ExplodedNode *Pred, ExplodedNodeSet &Dst) { - + // ObjCForCollectionStmts are processed in two places. This method // handles the case where an ObjCForCollectionStmt* occurs as one of the // statements within a basic block. This transfer function does two things: @@ -74,7 +74,7 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, const Stmt *elem = S->getElement(); ProgramStateRef state = Pred->getState(); SVal elementV; - + if (const DeclStmt *DS = dyn_cast<DeclStmt>(elem)) { const VarDecl *elemD = cast<VarDecl>(DS->getSingleDecl()); assert(elemD->getInit() == nullptr); @@ -83,7 +83,7 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, else { elementV = state->getSVal(elem, Pred->getLocationContext()); } - + ExplodedNodeSet dstLocation; evalLocation(dstLocation, S, elem, Pred, state, elementV, nullptr, false); @@ -95,17 +95,17 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, Pred = *NI; ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); - + // Handle the case where the container still has elements. SVal TrueV = svalBuilder.makeTruthVal(1); ProgramStateRef hasElems = state->BindExpr(S, LCtx, TrueV); - + // Handle the case where the container has no elements. SVal FalseV = svalBuilder.makeTruthVal(0); ProgramStateRef noElems = state->BindExpr(S, LCtx, FalseV); if (Optional<loc::MemRegionVal> MV = elementV.getAs<loc::MemRegionVal>()) - if (const TypedValueRegion *R = + if (const TypedValueRegion *R = dyn_cast<TypedValueRegion>(MV->getRegion())) { // FIXME: The proper thing to do is to really iterate over the // container. We will do this with dispatch logic to the store. @@ -116,12 +116,12 @@ void ExprEngine::VisitObjCForCollectionStmt(const ObjCForCollectionStmt *S, currBldrCtx->blockCount()); SVal V = svalBuilder.makeLoc(Sym); hasElems = hasElems->bindLoc(elementV, V); - + // Bind the location to 'nil' on the false branch. SVal nilV = svalBuilder.makeIntVal(0, T); noElems = noElems->bindLoc(elementV, nilV); } - + // Create the new nodes. Bldr.generateNode(S, Pred, hasElems); Bldr.generateNode(S, Pred, noElems); @@ -139,6 +139,76 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, CallEventRef<ObjCMethodCall> Msg = CEMgr.getObjCMethodCall(ME, Pred->getState(), Pred->getLocationContext()); + // There are three cases for the receiver: + // (1) it is definitely nil, + // (2) it is definitely non-nil, and + // (3) we don't know. + // + // If the receiver is definitely nil, we skip the pre/post callbacks and + // instead call the ObjCMessageNil callbacks and return. + // + // If the receiver is definitely non-nil, we call the pre- callbacks, + // evaluate the call, and call the post- callbacks. + // + // If we don't know, we drop the potential nil flow and instead + // continue from the assumed non-nil state as in (2). This approach + // intentionally drops coverage in order to prevent false alarms + // in the following scenario: + // + // id result = [o someMethod] + // if (result) { + // if (!o) { + // // <-- This program point should be unreachable because if o is nil + // // it must the case that result is nil as well. + // } + // } + // + // We could avoid dropping coverage by performing an explicit case split + // on each method call -- but this would get very expensive. An alternative + // would be to introduce lazy constraints. + // FIXME: This ignores many potential bugs (<rdar://problem/11733396>). + // Revisit once we have lazier constraints. + if (Msg->isInstanceMessage()) { + SVal recVal = Msg->getReceiverSVal(); + if (!recVal.isUndef()) { + // Bifurcate the state into nil and non-nil ones. + DefinedOrUnknownSVal receiverVal = + recVal.castAs<DefinedOrUnknownSVal>(); + ProgramStateRef State = Pred->getState(); + + ProgramStateRef notNilState, nilState; + std::tie(notNilState, nilState) = State->assume(receiverVal); + + // Receiver is definitely nil, so run ObjCMessageNil callbacks and return. + if (nilState && !notNilState) { + StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); + bool HasTag = Pred->getLocation().getTag(); + Pred = Bldr.generateNode(ME, Pred, nilState, nullptr, + ProgramPoint::PreStmtKind); + assert((Pred || HasTag) && "Should have cached out already!"); + (void)HasTag; + if (!Pred) + return; + getCheckerManager().runCheckersForObjCMessageNil(Dst, Pred, + *Msg, *this); + return; + } + + ExplodedNodeSet dstNonNil; + StmtNodeBuilder Bldr(Pred, dstNonNil, *currBldrCtx); + // Generate a transition to the non-nil state, dropping any potential + // nil flow. + if (notNilState != State) { + bool HasTag = Pred->getLocation().getTag(); + Pred = Bldr.generateNode(ME, Pred, notNilState); + assert((Pred || HasTag) && "Should have cached out already!"); + (void)HasTag; + if (!Pred) + return; + } + } + } + // Handle the previsits checks. ExplodedNodeSet dstPrevisit; getCheckerManager().runCheckersForPreObjCMessage(dstPrevisit, Pred, @@ -156,39 +226,16 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, ExplodedNode *Pred = *DI; ProgramStateRef State = Pred->getState(); CallEventRef<ObjCMethodCall> UpdatedMsg = Msg.cloneWithState(State); - + if (UpdatedMsg->isInstanceMessage()) { SVal recVal = UpdatedMsg->getReceiverSVal(); if (!recVal.isUndef()) { - // Bifurcate the state into nil and non-nil ones. - DefinedOrUnknownSVal receiverVal = - recVal.castAs<DefinedOrUnknownSVal>(); - - ProgramStateRef notNilState, nilState; - std::tie(notNilState, nilState) = State->assume(receiverVal); - - // There are three cases: can be nil or non-nil, must be nil, must be - // non-nil. We ignore must be nil, and merge the rest two into non-nil. - // FIXME: This ignores many potential bugs (<rdar://problem/11733396>). - // Revisit once we have lazier constraints. - if (nilState && !notNilState) { - continue; - } - - // Check if the "raise" message was sent. - assert(notNilState); if (ObjCNoRet.isImplicitNoReturn(ME)) { // If we raise an exception, for now treat it as a sink. // Eventually we will want to handle exceptions properly. Bldr.generateSink(ME, Pred, State); continue; } - - // Generate a transition to non-Nil state. - if (notNilState != State) { - Pred = Bldr.generateNode(ME, Pred, notNilState); - assert(Pred && "Should have cached out already!"); - } } } else { // Check for special class methods that are known to not return @@ -203,7 +250,7 @@ void ExprEngine::VisitObjCMessage(const ObjCMessageExpr *ME, defaultEvalCall(Bldr, Pred, *UpdatedMsg); } - + ExplodedNodeSet dstPostvisit; getCheckerManager().runCheckersForPostCall(dstPostvisit, dstEval, *Msg, *this); diff --git a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index cfcf7c6..b3edb85 100644 --- a/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -11,7 +11,6 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Decl.h" #include "clang/Basic/FileManager.h" @@ -22,6 +21,8 @@ #include "clang/Rewrite/Core/Rewriter.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/IssueHash.h" +#include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/Support/Errc.h" #include "llvm/Support/FileSystem.h" #include "llvm/Support/MemoryBuffer.h" @@ -122,11 +123,11 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, // The path as already been prechecked that all parts of the path are // from the same file and that it is non-empty. - const SourceManager &SMgr = (*path.begin())->getLocation().getManager(); + const SourceManager &SMgr = path.front()->getLocation().getManager(); assert(!path.empty()); FileID FID = - (*path.begin())->getLocation().asLocation().getExpansionLoc().getFileID(); - assert(!FID.isInvalid()); + path.front()->getLocation().asLocation().getExpansionLoc().getFileID(); + assert(FID.isValid()); // Create a new rewriter to generate HTML. Rewriter R(const_cast<SourceManager&>(SMgr), PP.getLangOpts()); @@ -143,7 +144,7 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, // Retrieve the relative position of the declaration which will be used // for the file name FullSourceLoc L( - SMgr.getExpansionLoc((*path.rbegin())->getLocation().asLocation()), + SMgr.getExpansionLoc(path.back()->getLocation().asLocation()), SMgr); FullSourceLoc FunL(SMgr.getExpansionLoc(Body->getLocStart()), SMgr); offsetDecl = L.getExpansionLineNumber() - FunL.getExpansionLineNumber(); @@ -187,8 +188,8 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, DirName += '/'; } - int LineNumber = (*path.rbegin())->getLocation().asLocation().getExpansionLineNumber(); - int ColumnNumber = (*path.rbegin())->getLocation().asLocation().getExpansionColumnNumber(); + int LineNumber = path.back()->getLocation().asLocation().getExpansionLineNumber(); + int ColumnNumber = path.back()->getLocation().asLocation().getExpansionColumnNumber(); // Add the name of the file as an <h1> tag. @@ -236,6 +237,13 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, if (!BugType.empty()) os << "\n<!-- BUGTYPE " << BugType << " -->\n"; + PathDiagnosticLocation UPDLoc = D.getUniqueingLoc(); + FullSourceLoc L(SMgr.getExpansionLoc(UPDLoc.isValid() + ? UPDLoc.asLocation() + : D.getLocation().asLocation()), + SMgr); + const Decl *DeclWithIssue = D.getDeclWithIssue(); + StringRef BugCategory = D.getCategory(); if (!BugCategory.empty()) os << "\n<!-- BUGCATEGORY " << BugCategory << " -->\n"; @@ -246,6 +254,10 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, os << "\n<!-- FUNCTIONNAME " << declName << " -->\n"; + os << "\n<!-- ISSUEHASHCONTENTOFLINEINCONTEXT " + << GetIssueHash(SMgr, L, D.getCheckName(), D.getBugType(), DeclWithIssue, + PP.getLangOpts()) << " -->\n"; + os << "\n<!-- BUGLINE " << LineNumber << " -->\n"; @@ -281,7 +293,12 @@ void HTMLDiagnostics::ReportDiag(const PathDiagnostic& D, if (!AnalyzerOpts.shouldWriteStableReportFilename()) { llvm::sys::path::append(Model, Directory, "report-%%%%%%.html"); - + if (std::error_code EC = + llvm::sys::fs::make_absolute(Model)) { + llvm::errs() << "warning: could not make '" << Model + << "' absolute: " << EC.message() << '\n'; + return; + } if (std::error_code EC = llvm::sys::fs::createUniqueFile(Model, FD, ResultPath)) { llvm::errs() << "warning: could not create file in '" << Directory diff --git a/lib/StaticAnalyzer/Core/IssueHash.cpp b/lib/StaticAnalyzer/Core/IssueHash.cpp new file mode 100644 index 0000000..0a3af3d --- /dev/null +++ b/lib/StaticAnalyzer/Core/IssueHash.cpp @@ -0,0 +1,196 @@ +//===---------- IssueHash.cpp - Generate identification hashes --*- 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/Core/IssueHash.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/Decl.h" +#include "clang/AST/DeclCXX.h" +#include "clang/Basic/SourceManager.h" +#include "clang/Basic/Specifiers.h" +#include "clang/Lex/Lexer.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringExtras.h" +#include "llvm/ADT/StringRef.h" +#include "llvm/ADT/Twine.h" +#include "llvm/Support/LineIterator.h" +#include "llvm/Support/MD5.h" +#include "llvm/Support/Path.h" + +#include <functional> +#include <sstream> +#include <string> + +using namespace clang; + +// Get a string representation of the parts of the signature that can be +// overloaded on. +static std::string GetSignature(const FunctionDecl *Target) { + if (!Target) + return ""; + std::string Signature; + + if (!isa<CXXConstructorDecl>(Target) && !isa<CXXDestructorDecl>(Target) && + !isa<CXXConversionDecl>(Target)) + Signature.append(Target->getReturnType().getAsString()).append(" "); + Signature.append(Target->getQualifiedNameAsString()).append("("); + + for (int i = 0, paramsCount = Target->getNumParams(); i < paramsCount; ++i) { + if (i) + Signature.append(", "); + Signature.append(Target->getParamDecl(i)->getType().getAsString()); + } + + if (Target->isVariadic()) + Signature.append(", ..."); + Signature.append(")"); + + const auto *TargetT = + llvm::dyn_cast_or_null<FunctionType>(Target->getType().getTypePtr()); + + if (!TargetT || !isa<CXXMethodDecl>(Target)) + return Signature; + + if (TargetT->isConst()) + Signature.append(" const"); + if (TargetT->isVolatile()) + Signature.append(" volatile"); + if (TargetT->isRestrict()) + Signature.append(" restrict"); + + if (const auto *TargetPT = + dyn_cast_or_null<FunctionProtoType>(Target->getType().getTypePtr())) { + switch (TargetPT->getRefQualifier()) { + case RQ_LValue: + Signature.append(" &"); + break; + case RQ_RValue: + Signature.append(" &&"); + break; + default: + break; + } + } + + return Signature; +} + +static std::string GetEnclosingDeclContextSignature(const Decl *D) { + if (!D) + return ""; + + if (const auto *ND = dyn_cast<NamedDecl>(D)) { + std::string DeclName; + + switch (ND->getKind()) { + case Decl::Namespace: + case Decl::Record: + case Decl::CXXRecord: + case Decl::Enum: + DeclName = ND->getQualifiedNameAsString(); + break; + case Decl::CXXConstructor: + case Decl::CXXDestructor: + case Decl::CXXConversion: + case Decl::CXXMethod: + case Decl::Function: + DeclName = GetSignature(dyn_cast_or_null<FunctionDecl>(ND)); + break; + case Decl::ObjCMethod: + // ObjC Methods can not be overloaded, qualified name uniquely identifies + // the method. + DeclName = ND->getQualifiedNameAsString(); + break; + default: + break; + } + + return DeclName; + } + + return ""; +} + +static StringRef GetNthLineOfFile(llvm::MemoryBuffer *Buffer, int Line) { + if (!Buffer) + return ""; + + llvm::line_iterator LI(*Buffer, false); + for (; !LI.is_at_eof() && LI.line_number() != Line; ++LI) + ; + + return *LI; +} + +static std::string NormalizeLine(const SourceManager &SM, FullSourceLoc &L, + const LangOptions &LangOpts) { + static StringRef Whitespaces = " \t\n"; + + StringRef Str = GetNthLineOfFile(SM.getBuffer(L.getFileID(), L), + L.getExpansionLineNumber()); + unsigned col = Str.find_first_not_of(Whitespaces); + col++; + SourceLocation StartOfLine = + SM.translateLineCol(SM.getFileID(L), L.getExpansionLineNumber(), col); + llvm::MemoryBuffer *Buffer = + SM.getBuffer(SM.getFileID(StartOfLine), StartOfLine); + if (!Buffer) + return {}; + + const char *BufferPos = SM.getCharacterData(StartOfLine); + + Token Token; + Lexer Lexer(SM.getLocForStartOfFile(SM.getFileID(StartOfLine)), LangOpts, + Buffer->getBufferStart(), BufferPos, Buffer->getBufferEnd()); + + size_t NextStart = 0; + std::ostringstream LineBuff; + while (!Lexer.LexFromRawLexer(Token) && NextStart < 2) { + if (Token.isAtStartOfLine() && NextStart++ > 0) + continue; + LineBuff << std::string(SM.getCharacterData(Token.getLocation()), + Token.getLength()); + } + + return LineBuff.str(); +} + +static llvm::SmallString<32> GetHashOfContent(StringRef Content) { + llvm::MD5 Hash; + llvm::MD5::MD5Result MD5Res; + SmallString<32> Res; + + Hash.update(Content); + Hash.final(MD5Res); + llvm::MD5::stringifyResult(MD5Res, Res); + + return Res; +} + +std::string clang::GetIssueString(const SourceManager &SM, + FullSourceLoc &IssueLoc, + StringRef CheckerName, StringRef BugType, + const Decl *D, + const LangOptions &LangOpts) { + static StringRef Delimiter = "$"; + + return (llvm::Twine(CheckerName) + Delimiter + + GetEnclosingDeclContextSignature(D) + Delimiter + + llvm::utostr(IssueLoc.getExpansionColumnNumber()) + Delimiter + + NormalizeLine(SM, IssueLoc, LangOpts) + Delimiter + BugType) + .str(); +} + +SmallString<32> clang::GetIssueHash(const SourceManager &SM, + FullSourceLoc &IssueLoc, + StringRef CheckerName, StringRef BugType, + const Decl *D, + const LangOptions &LangOpts) { + + return GetHashOfContent( + GetIssueString(SM, IssueLoc, CheckerName, BugType, D, LangOpts)); +} diff --git a/lib/StaticAnalyzer/Core/LoopWidening.cpp b/lib/StaticAnalyzer/Core/LoopWidening.cpp new file mode 100644 index 0000000..05865c2 --- /dev/null +++ b/lib/StaticAnalyzer/Core/LoopWidening.cpp @@ -0,0 +1,68 @@ +//===--- LoopWidening.cpp - Widen loops -------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +/// +/// This file contains functions which are used to widen loops. A loop may be +/// widened to approximate the exit state(s), without analyzing every +/// iteration. The widening is done by invalidating anything which might be +/// modified by the body of the loop. +/// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/LoopWidening.h" + +using namespace clang; +using namespace ento; + +/// Return the loops condition Stmt or NULL if LoopStmt is not a loop +static const Expr *getLoopCondition(const Stmt *LoopStmt) { + switch (LoopStmt->getStmtClass()) { + default: + return nullptr; + case Stmt::ForStmtClass: + return cast<ForStmt>(LoopStmt)->getCond(); + case Stmt::WhileStmtClass: + return cast<WhileStmt>(LoopStmt)->getCond(); + case Stmt::DoStmtClass: + return cast<DoStmt>(LoopStmt)->getCond(); + } +} + +namespace clang { +namespace ento { + +ProgramStateRef getWidenedLoopState(ProgramStateRef PrevState, + const LocationContext *LCtx, + unsigned BlockCount, const Stmt *LoopStmt) { + + assert(isa<ForStmt>(LoopStmt) || isa<WhileStmt>(LoopStmt) || + isa<DoStmt>(LoopStmt)); + + // Invalidate values in the current state. + // TODO Make this more conservative by only invalidating values that might + // be modified by the body of the loop. + // TODO Nested loops are currently widened as a result of the invalidation + // being so inprecise. When the invalidation is improved, the handling + // of nested loops will also need to be improved. + const StackFrameContext *STC = LCtx->getCurrentStackFrame(); + MemRegionManager &MRMgr = PrevState->getStateManager().getRegionManager(); + const MemRegion *Regions[] = {MRMgr.getStackLocalsRegion(STC), + MRMgr.getStackArgumentsRegion(STC), + MRMgr.getGlobalsRegion()}; + RegionAndSymbolInvalidationTraits ITraits; + for (auto *Region : Regions) { + ITraits.setTrait(Region, + RegionAndSymbolInvalidationTraits::TK_EntireMemSpace); + } + return PrevState->invalidateRegions(Regions, getLoopCondition(LoopStmt), + BlockCount, LCtx, true, nullptr, nullptr, + &ITraits); +} + +} // end namespace ento +} // end namespace clang diff --git a/lib/StaticAnalyzer/Core/Makefile b/lib/StaticAnalyzer/Core/Makefile index 4aebc16..c3e00fa 100644 --- a/lib/StaticAnalyzer/Core/Makefile +++ b/lib/StaticAnalyzer/Core/Makefile @@ -1,13 +1,13 @@ ##===- clang/lib/StaticAnalyzer/Core/Makefile --------------*- Makefile -*-===## -# +# # The LLVM Compiler Infrastructure # # This file is distributed under the University of Illinois Open Source # License. See LICENSE.TXT for details. -# +# ##===----------------------------------------------------------------------===## # -# This implements analyses built on top of source-level CFGs. +# This implements analyses built on top of source-level CFGs. # ##===----------------------------------------------------------------------===## diff --git a/lib/StaticAnalyzer/Core/MemRegion.cpp b/lib/StaticAnalyzer/Core/MemRegion.cpp index 5ac8458..ad3f396 100644 --- a/lib/StaticAnalyzer/Core/MemRegion.cpp +++ b/lib/StaticAnalyzer/Core/MemRegion.cpp @@ -756,7 +756,7 @@ getStackOrCaptureRegionForDeclContext(const LocationContext *LC, return cast<VarRegion>(I.getCapturedRegion()); } } - + LC = LC->getParent(); } return (const StackFrameContext *)nullptr; @@ -788,18 +788,18 @@ const VarRegion* MemRegionManager::getVarRegion(const VarDecl *D, else sReg = getGlobalsRegion(); } - - // Finally handle static locals. + + // Finally handle static locals. } else { // FIXME: Once we implement scope handling, we will need to properly lookup // 'D' to the proper LocationContext. const DeclContext *DC = D->getDeclContext(); llvm::PointerUnion<const StackFrameContext *, const VarRegion *> V = getStackOrCaptureRegionForDeclContext(LC, DC, D); - + if (V.is<const VarRegion*>()) return V.get<const VarRegion*>(); - + const StackFrameContext *STC = V.get<const StackFrameContext*>(); if (!STC) @@ -1013,10 +1013,22 @@ MemRegionManager::getCXXBaseObjectRegion(const CXXRecordDecl *RD, const CXXThisRegion* MemRegionManager::getCXXThisRegion(QualType thisPointerTy, const LocationContext *LC) { - const StackFrameContext *STC = LC->getCurrentStackFrame(); - assert(STC); const PointerType *PT = thisPointerTy->getAs<PointerType>(); assert(PT); + // Inside the body of the operator() of a lambda a this expr might refer to an + // object in one of the parent location contexts. + const auto *D = dyn_cast<CXXMethodDecl>(LC->getDecl()); + // FIXME: when operator() of lambda is analyzed as a top level function and + // 'this' refers to a this to the enclosing scope, there is no right region to + // return. + while (!LC->inTopFrame() && + (!D || D->isStatic() || + PT != D->getThisType(getContext())->getAs<PointerType>())) { + LC = LC->getParent(); + D = dyn_cast<CXXMethodDecl>(LC->getDecl()); + } + const StackFrameContext *STC = LC->getCurrentStackFrame(); + assert(STC); return getSubRegion<CXXThisRegion>(PT, getStackArgumentsRegion(STC)); } @@ -1165,6 +1177,7 @@ RegionRawOffset ElementRegion::getAsArrayOffset() const { /// Returns true if \p Base is an immediate base class of \p Child static bool isImmediateBase(const CXXRecordDecl *Child, const CXXRecordDecl *Base) { + assert(Child && "Child must not be null"); // Note that we do NOT canonicalize the base class here, because // ASTRecordLayout doesn't either. If that leads us down the wrong path, // so be it; at least we won't crash. @@ -1239,23 +1252,23 @@ RegionOffset MemRegion::getAsOffset() const { Ty = SR->getSymbol()->getType()->getPointeeType(); RootIsSymbolic = true; } - + const CXXRecordDecl *Child = Ty->getAsCXXRecordDecl(); if (!Child) { // We cannot compute the offset of the base class. SymbolicOffsetBase = R; - } - - if (RootIsSymbolic) { - // Base layers on symbolic regions may not be type-correct. - // Double-check the inheritance here, and revert to a symbolic offset - // if it's invalid (e.g. due to a reinterpret_cast). - if (BOR->isVirtual()) { - if (!Child->isVirtuallyDerivedFrom(BOR->getDecl())) - SymbolicOffsetBase = R; - } else { - if (!isImmediateBase(Child, BOR->getDecl())) - SymbolicOffsetBase = R; + } else { + if (RootIsSymbolic) { + // Base layers on symbolic regions may not be type-correct. + // Double-check the inheritance here, and revert to a symbolic offset + // if it's invalid (e.g. due to a reinterpret_cast). + if (BOR->isVirtual()) { + if (!Child->isVirtuallyDerivedFrom(BOR->getDecl())) + SymbolicOffsetBase = R; + } else { + if (!isImmediateBase(Child, BOR->getDecl())) + SymbolicOffsetBase = R; + } } } @@ -1290,7 +1303,7 @@ RegionOffset MemRegion::getAsOffset() const { if (Optional<nonloc::ConcreteInt> CI = Index.getAs<nonloc::ConcreteInt>()) { // Don't bother calculating precise offsets if we already have a - // symbolic offset somewhere in the chain. + // symbolic offset somewhere in the chain. if (SymbolicOffsetBase) continue; @@ -1324,7 +1337,7 @@ RegionOffset MemRegion::getAsOffset() const { // Get the field number. unsigned idx = 0; - for (RecordDecl::field_iterator FI = RD->field_begin(), + for (RecordDecl::field_iterator FI = RD->field_begin(), FE = RD->field_end(); FI != FE; ++FI, ++idx) if (FR->getDecl() == *FI) break; @@ -1420,7 +1433,7 @@ BlockDataRegion::referenced_vars_begin() const { BumpVector<const MemRegion*> *VecOriginal = static_cast<BumpVector<const MemRegion*>*>(OriginalVars); - + return BlockDataRegion::referenced_vars_iterator(Vec->begin(), VecOriginal->begin()); } @@ -1456,12 +1469,12 @@ const VarRegion *BlockDataRegion::getOriginalRegion(const VarRegion *R) const { // RegionAndSymbolInvalidationTraits //===----------------------------------------------------------------------===// -void RegionAndSymbolInvalidationTraits::setTrait(SymbolRef Sym, +void RegionAndSymbolInvalidationTraits::setTrait(SymbolRef Sym, InvalidationKinds IK) { SymTraitsMap[Sym] |= IK; } -void RegionAndSymbolInvalidationTraits::setTrait(const MemRegion *MR, +void RegionAndSymbolInvalidationTraits::setTrait(const MemRegion *MR, InvalidationKinds IK) { assert(MR); if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) @@ -1470,13 +1483,13 @@ void RegionAndSymbolInvalidationTraits::setTrait(const MemRegion *MR, MRTraitsMap[MR] |= IK; } -bool RegionAndSymbolInvalidationTraits::hasTrait(SymbolRef Sym, +bool RegionAndSymbolInvalidationTraits::hasTrait(SymbolRef Sym, InvalidationKinds IK) { const_symbol_iterator I = SymTraitsMap.find(Sym); if (I != SymTraitsMap.end()) return I->second & IK; - return false; + return false; } bool RegionAndSymbolInvalidationTraits::hasTrait(const MemRegion *MR, diff --git a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp index c490031..504df30 100644 --- a/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/lib/StaticAnalyzer/Core/PathDiagnostic.cpp @@ -62,8 +62,6 @@ PathDiagnosticControlFlowPiece::~PathDiagnosticControlFlowPiece() {} PathDiagnosticMacroPiece::~PathDiagnosticMacroPiece() {} -PathPieces::~PathPieces() {} - void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current, bool ShouldFlattenMacros) const { for (PathPieces::const_iterator I = begin(), E = end(); I != E; ++I) { @@ -181,7 +179,7 @@ void PathDiagnostic::resetDiagnosticLocationToMainFile() { // Reset the report containing declaration and location. DeclWithIssue = CP->getCaller(); Loc = CP->getLocation(); - + return; } } @@ -201,7 +199,7 @@ void PathDiagnosticConsumer::HandlePathDiagnostic( std::unique_ptr<PathDiagnostic> D) { if (!D || D->path.empty()) return; - + // We need to flatten the locations (convert Stmt* to locations) because // the referenced statements may be freed by the time the diagnostics // are emitted. @@ -223,12 +221,12 @@ void PathDiagnosticConsumer::HandlePathDiagnostic( ++I) { const PathDiagnosticPiece *piece = I->get(); FullSourceLoc L = piece->getLocation().asLocation().getExpansionLoc(); - + if (FID.isInvalid()) { FID = SMgr.getFileID(L); } else if (SMgr.getFileID(L) != FID) return; // FIXME: Emit a warning? - + // Check the source ranges. ArrayRef<SourceRange> Ranges = piece->getRanges(); for (ArrayRef<SourceRange>::iterator I = Ranges.begin(), @@ -240,7 +238,7 @@ void PathDiagnosticConsumer::HandlePathDiagnostic( if (!L.isFileID() || SMgr.getFileID(L) != FID) return; // FIXME: Emit a warning? } - + if (const PathDiagnosticCallPiece *call = dyn_cast<PathDiagnosticCallPiece>(piece)) { WorkList.push_back(&call->path); @@ -251,10 +249,10 @@ void PathDiagnosticConsumer::HandlePathDiagnostic( } } } - + if (FID.isInvalid()) return; // FIXME: Emit a warning? - } + } // Profile the node to see if we already have something matching it llvm::FoldingSetNodeID profile; @@ -320,7 +318,7 @@ static Optional<bool> comparePiece(const PathDiagnosticPiece &X, const PathDiagnosticPiece &Y) { if (X.getKind() != Y.getKind()) return X.getKind() < Y.getKind(); - + FullSourceLoc XL = X.getLocation().asLocation(); FullSourceLoc YL = Y.getLocation().asLocation(); if (XL != YL) @@ -333,7 +331,7 @@ static Optional<bool> comparePiece(const PathDiagnosticPiece &X, return X.getRanges().size() < Y.getRanges().size(); const SourceManager &SM = XL.getManager(); - + for (unsigned i = 0, n = X.getRanges().size(); i < n; ++i) { SourceRange XR = X.getRanges()[i]; SourceRange YR = Y.getRanges()[i]; @@ -343,7 +341,7 @@ static Optional<bool> comparePiece(const PathDiagnosticPiece &X, return SM.isBeforeInTranslationUnit(XR.getEnd(), YR.getEnd()); } } - + switch (X.getKind()) { case clang::ento::PathDiagnosticPiece::ControlFlow: return compareControlFlow(cast<PathDiagnosticControlFlowPiece>(X), @@ -420,9 +418,9 @@ void PathDiagnosticConsumer::FlushDiagnostics( PathDiagnosticConsumer::FilesMade *Files) { if (flushed) return; - + flushed = true; - + std::vector<const PathDiagnostic *> BatchDiags; for (llvm::FoldingSet<PathDiagnostic>::iterator it = Diags.begin(), et = Diags.end(); it != et; ++it) { @@ -450,7 +448,7 @@ void PathDiagnosticConsumer::FlushDiagnostics( const PathDiagnostic *D = *it; delete D; } - + // Clear out the FoldingSet. Diags.clear(); } @@ -472,7 +470,7 @@ void PathDiagnosticConsumer::FilesMade::addDiagnostic(const PathDiagnostic &PD, Entry = new (Entry) PDFileEntry(NodeID); Set.InsertNode(Entry, InsertPos); } - + // Allocate persistent storage for the file name. char *FileName_cstr = (char*) Alloc.Allocate(FileName.size(), 1); memcpy(FileName_cstr, FileName.data(), FileName.size()); @@ -847,7 +845,7 @@ PathDiagnosticRange SourceRange R = S->getSourceRange(); if (R.isValid()) return R; - break; + break; } case DeclK: if (const ObjCMethodDecl *MD = dyn_cast<ObjCMethodDecl>(D)) @@ -949,7 +947,7 @@ static bool describeCodeDecl(raw_ostream &Out, const Decl *D, Out << "constructor"; describeClass(Out, MD->getParent(), " for "); - + } else if (isa<CXXDestructorDecl>(MD)) { if (!MD->isUserProvided()) { Out << "destructor"; @@ -1041,7 +1039,7 @@ static void compute_path_size(const PathPieces &pieces, unsigned &size) { for (PathPieces::const_iterator it = pieces.begin(), et = pieces.end(); it != et; ++it) { const PathDiagnosticPiece *piece = it->get(); - if (const PathDiagnosticCallPiece *cp = + if (const PathDiagnosticCallPiece *cp = dyn_cast<PathDiagnosticCallPiece>(piece)) { compute_path_size(cp->path, size); } @@ -1077,12 +1075,12 @@ void PathDiagnosticPiece::Profile(llvm::FoldingSetNodeID &ID) const { I != E; ++I) { ID.AddInteger(I->getBegin().getRawEncoding()); ID.AddInteger(I->getEnd().getRawEncoding()); - } + } } void PathDiagnosticCallPiece::Profile(llvm::FoldingSetNodeID &ID) const { PathDiagnosticPiece::Profile(ID); - for (PathPieces::const_iterator it = path.begin(), + for (PathPieces::const_iterator it = path.begin(), et = path.end(); it != et; ++it) { ID.Add(**it); } diff --git a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp index e0aff58..55e1222 100644 --- a/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp +++ b/lib/StaticAnalyzer/Core/PlistDiagnostics.cpp @@ -11,13 +11,13 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/Basic/FileManager.h" #include "clang/Basic/PlistSupport.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/SmallVector.h" @@ -171,7 +171,7 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, --indent; Indent(o, indent) << "</array>\n"; } - + // Output the call depth. Indent(o, indent) << "<key>depth</key>"; EmitInteger(o, depth) << '\n'; @@ -187,7 +187,7 @@ static void ReportEvent(raw_ostream &o, const PathDiagnosticPiece& P, Indent(o, indent) << "<key>message</key>\n"; Indent(o, indent); EmitString(o, P.getString()) << '\n'; - + // Finish up. --indent; Indent(o, indent); o << "</dict>\n"; @@ -208,9 +208,9 @@ static void ReportCall(raw_ostream &o, const LangOptions &LangOpts, unsigned indent, unsigned depth) { - + IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnter = - P.getCallEnterEvent(); + P.getCallEnterEvent(); if (callEnter) ReportPiece(o, *callEnter, FM, SM, LangOpts, indent, depth, true, @@ -218,18 +218,18 @@ static void ReportCall(raw_ostream &o, IntrusiveRefCntPtr<PathDiagnosticEventPiece> callEnterWithinCaller = P.getCallEnterWithinCallerEvent(); - + ++depth; - + if (callEnterWithinCaller) ReportPiece(o, *callEnterWithinCaller, FM, SM, LangOpts, indent, depth, true); - + for (PathPieces::const_iterator I = P.path.begin(), E = P.path.end();I!=E;++I) ReportPiece(o, **I, FM, SM, LangOpts, indent, depth, true); --depth; - + IntrusiveRefCntPtr<PathDiagnosticEventPiece> callExit = P.getCallExitEvent(); @@ -295,9 +295,9 @@ void PlistDiagnostics::FlushDiagnosticsImpl( const SourceManager* SM = nullptr; if (!Diags.empty()) - SM = &(*(*Diags.begin())->path.begin())->getLocation().getManager(); + SM = &Diags.front()->path.front()->getLocation().getManager(); + - for (std::vector<const PathDiagnostic*>::iterator DI = Diags.begin(), DE = Diags.end(); DI != DE; ++DI) { @@ -374,7 +374,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " <array>\n"; - for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end(); + for (PathPieces::const_iterator I = D->path.begin(), E = D->path.end(); I != E; ++I) ReportDiag(o, **I, FM, *SM, LangOpts); @@ -389,7 +389,19 @@ void PlistDiagnostics::FlushDiagnosticsImpl( EmitString(o, D->getBugType()) << '\n'; o << " <key>check_name</key>"; EmitString(o, D->getCheckName()) << '\n'; - + + o << " <!-- This hash is experimental and going to change! -->\n"; + o << " <key>issue_hash_content_of_line_in_context</key>"; + PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); + FullSourceLoc L(SM->getExpansionLoc(UPDLoc.isValid() + ? UPDLoc.asLocation() + : D->getLocation().asLocation()), + *SM); + const Decl *DeclWithIssue = D->getDeclWithIssue(); + EmitString(o, GetIssueHash(*SM, L, D->getCheckName(), D->getBugType(), + DeclWithIssue, LangOpts)) + << '\n'; + // Output information about the semantic context where // the issue occurred. if (const Decl *DeclWithIssue = D->getDeclWithIssue()) { @@ -423,28 +435,23 @@ void PlistDiagnostics::FlushDiagnosticsImpl( // Output the bug hash for issue unique-ing. Currently, it's just an // offset from the beginning of the function. if (const Stmt *Body = DeclWithIssue->getBody()) { - + // If the bug uniqueing location exists, use it for the hash. // For example, this ensures that two leaks reported on the same line // will have different issue_hashes and that the hash will identify // the leak location even after code is added between the allocation // site and the end of scope (leak report location). - PathDiagnosticLocation UPDLoc = D->getUniqueingLoc(); if (UPDLoc.isValid()) { - FullSourceLoc UL(SM->getExpansionLoc(UPDLoc.asLocation()), - *SM); FullSourceLoc UFunL(SM->getExpansionLoc( D->getUniqueingDecl()->getBody()->getLocStart()), *SM); - o << " <key>issue_hash</key><string>" - << UL.getExpansionLineNumber() - UFunL.getExpansionLineNumber() + o << " <key>issue_hash_function_offset</key><string>" + << L.getExpansionLineNumber() - UFunL.getExpansionLineNumber() << "</string>\n"; // Otherwise, use the location on which the bug is reported. } else { - FullSourceLoc L(SM->getExpansionLoc(D->getLocation().asLocation()), - *SM); FullSourceLoc FunL(SM->getExpansionLoc(Body->getLocStart()), *SM); - o << " <key>issue_hash</key><string>" + o << " <key>issue_hash_function_offset</key><string>" << L.getExpansionLineNumber() - FunL.getExpansionLineNumber() << "</string>\n"; } @@ -486,5 +493,5 @@ void PlistDiagnostics::FlushDiagnosticsImpl( o << " </array>\n"; // Finish. - o << "</dict>\n</plist>"; + o << "</dict>\n</plist>"; } diff --git a/lib/StaticAnalyzer/Core/ProgramState.cpp b/lib/StaticAnalyzer/Core/ProgramState.cpp index 60b32c7..4f9ad9e 100644 --- a/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -36,7 +36,7 @@ void ProgramStateRelease(const ProgramState *state) { if (--s->refCount == 0) { ProgramStateManager &Mgr = s->getStateManager(); Mgr.StateSet.RemoveNode(s); - s->~ProgramState(); + s->~ProgramState(); Mgr.freeStates.push_back(s); } } @@ -86,7 +86,7 @@ ProgramStateManager::~ProgramStateManager() { I->second.second(I->second.first); } -ProgramStateRef +ProgramStateRef ProgramStateManager::removeDeadBindings(ProgramStateRef state, const StackFrameContext *LCtx, SymbolReaper& SymReaper) { @@ -113,7 +113,7 @@ ProgramStateManager::removeDeadBindings(ProgramStateRef state, ProgramStateRef ProgramState::bindLoc(Loc LV, SVal V, bool notifyChanges) const { ProgramStateManager &Mgr = getStateManager(); - ProgramStateRef newState = makeWithStore(Mgr.StoreMgr->Bind(getStore(), + ProgramStateRef newState = makeWithStore(Mgr.StoreMgr->Bind(getStore(), LV, V)); const MemRegion *MR = LV.getAsRegion(); if (MR && Mgr.getOwningEngine() && notifyChanges) @@ -127,15 +127,15 @@ ProgramStateRef ProgramState::bindDefault(SVal loc, SVal V) const { const MemRegion *R = loc.castAs<loc::MemRegionVal>().getRegion(); const StoreRef &newStore = Mgr.StoreMgr->BindDefault(getStore(), R, V); ProgramStateRef new_state = makeWithStore(newStore); - return Mgr.getOwningEngine() ? - Mgr.getOwningEngine()->processRegionChange(new_state, R) : + return Mgr.getOwningEngine() ? + Mgr.getOwningEngine()->processRegionChange(new_state, R) : new_state; } typedef ArrayRef<const MemRegion *> RegionList; typedef ArrayRef<SVal> ValueList; -ProgramStateRef +ProgramStateRef ProgramState::invalidateRegions(RegionList Regions, const Expr *E, unsigned Count, const LocationContext *LCtx, @@ -197,11 +197,11 @@ ProgramState::invalidateRegionsImpl(ValueList Values, if (CausedByPointerEscape) { newState = Eng->notifyCheckersOfPointerEscape(newState, IS, TopLevelInvalidated, - Invalidated, Call, + Invalidated, Call, *ITraits); } - return Eng->processRegionChanges(newState, IS, TopLevelInvalidated, + return Eng->processRegionChanges(newState, IS, TopLevelInvalidated, Invalidated, Call); } @@ -224,7 +224,7 @@ ProgramStateRef ProgramState::killBinding(Loc LV) const { return makeWithStore(newStore); } -ProgramStateRef +ProgramStateRef ProgramState::enterStackFrame(const CallEvent &Call, const StackFrameContext *CalleeCtx) const { const StoreRef &NewStore = @@ -275,7 +275,7 @@ SVal ProgramState::getSVal(Loc location, QualType T) const { // symbol for the call to foo(); the type of that symbol is 'char', // not unsigned. const llvm::APSInt &NewV = getBasicVals().Convert(T, *Int); - + if (V.getAs<Loc>()) return loc::ConcreteInt(NewV); else @@ -283,7 +283,7 @@ SVal ProgramState::getSVal(Loc location, QualType T) const { } } } - + return V; } @@ -353,11 +353,11 @@ ConditionTruthVal ProgramState::isNull(SVal V) const { if (V.isConstant()) return false; - + SymbolRef Sym = V.getAsSymbol(/* IncludeBaseRegion */ true); if (!Sym) return ConditionTruthVal(); - + return getStateManager().ConstraintMgr->isNull(this, Sym); } @@ -390,7 +390,7 @@ ProgramStateRef ProgramStateManager::getPersistentState(ProgramState &State) { ProgramState *newState = nullptr; if (!freeStates.empty()) { newState = freeStates.back(); - freeStates.pop_back(); + freeStates.pop_back(); } else { newState = (ProgramState*) Alloc.Allocate<ProgramState>(); @@ -530,10 +530,10 @@ bool ScanReachableSymbols::scan(const SymExpr *sym) { bool wasVisited = !visited.insert(sym).second; if (wasVisited) return true; - + if (!visitor.VisitSymbol(sym)) return false; - + // TODO: should be rewritten using SymExpr::symbol_iterator. switch (sym->getKind()) { case SymExpr::RegionValueKind: @@ -582,11 +582,11 @@ bool ScanReachableSymbols::scan(SVal val) { bool ScanReachableSymbols::scan(const MemRegion *R) { if (isa<MemSpaceRegion>(R)) return true; - + bool wasVisited = !visited.insert(R).second; if (wasVisited) return true; - + if (!visitor.VisitMemRegion(R)) return false; @@ -722,14 +722,14 @@ bool ProgramState::isTainted(const MemRegion *Reg, TaintTagType K) const { bool ProgramState::isTainted(SymbolRef Sym, TaintTagType Kind) const { if (!Sym) return false; - + // Traverse all the symbols this symbol depends on to see if any are tainted. bool Tainted = false; for (SymExpr::symbol_iterator SI = Sym->symbol_begin(), SE =Sym->symbol_end(); SI != SE; ++SI) { if (!isa<SymbolData>(*SI)) continue; - + const TaintTagType *Tag = get<TaintMap>(*SI); Tainted = (Tag && *Tag == Kind); @@ -748,40 +748,7 @@ bool ProgramState::isTainted(SymbolRef Sym, TaintTagType Kind) const { if (Tainted) return true; } - - return Tainted; -} - -/// The GDM component containing the dynamic type info. This is a map from a -/// symbol to its most likely type. -REGISTER_TRAIT_WITH_PROGRAMSTATE(DynamicTypeMap, - CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, - DynamicTypeInfo)) - -DynamicTypeInfo ProgramState::getDynamicTypeInfo(const MemRegion *Reg) const { - Reg = Reg->StripCasts(); - - // Look up the dynamic type in the GDM. - const DynamicTypeInfo *GDMType = get<DynamicTypeMap>(Reg); - if (GDMType) - return *GDMType; - - // Otherwise, fall back to what we know about the region. - if (const TypedRegion *TR = dyn_cast<TypedRegion>(Reg)) - return DynamicTypeInfo(TR->getLocationType(), /*CanBeSubclass=*/false); - if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(Reg)) { - SymbolRef Sym = SR->getSymbol(); - return DynamicTypeInfo(Sym->getType()); - } - - return DynamicTypeInfo(); + return Tainted; } -ProgramStateRef ProgramState::setDynamicTypeInfo(const MemRegion *Reg, - DynamicTypeInfo NewTy) const { - Reg = Reg->StripCasts(); - ProgramStateRef NewState = set<DynamicTypeMap>(Reg, NewTy); - assert(NewState); - return NewState; -} diff --git a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp index 170f7c0..0a2b2e6 100644 --- a/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/RangeConstraintManager.cpp @@ -81,6 +81,15 @@ public: RangeSet(PrimRangeSet RS) : ranges(RS) {} + /// Create a new set with all ranges of this set and RS. + /// Possible intersections are not checked here. + RangeSet addRange(Factory &F, const RangeSet &RS) { + PrimRangeSet Ranges(RS.ranges); + for (const auto &range : ranges) + Ranges = F.add(Ranges, range); + return RangeSet(Ranges); + } + iterator begin() const { return ranges.begin(); } iterator end() const { return ranges.end(); } @@ -312,6 +321,14 @@ public: const llvm::APSInt& Int, const llvm::APSInt& Adjustment) override; + ProgramStateRef assumeSymbolWithinInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) override; + + ProgramStateRef assumeSymbolOutOfInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) override; + const llvm::APSInt* getSymVal(ProgramStateRef St, SymbolRef sym) const override; ConditionTruthVal checkNull(ProgramStateRef State, SymbolRef Sym) override; @@ -324,6 +341,20 @@ public: private: RangeSet::Factory F; + RangeSet getSymLTRange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymGTRange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymLERange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymLERange(const RangeSet &RS, const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); + RangeSet getSymGERange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment); }; } // end anonymous namespace @@ -365,7 +396,7 @@ ConditionTruthVal RangeConstraintManager::checkNull(ProgramStateRef State, /// Scan all symbols referenced by the constraints. If the symbol is not alive /// as marked in LSymbols, mark it as dead in DSymbols. -ProgramStateRef +ProgramStateRef RangeConstraintManager::removeDeadBindings(ProgramStateRef state, SymbolReaper& SymReaper) { @@ -415,7 +446,7 @@ RangeConstraintManager::GetRange(ProgramStateRef state, SymbolRef sym) { // As an example, the range [UINT_MAX-1, 3) contains five values: UINT_MAX-1, // UINT_MAX, 0, 1, and 2. -ProgramStateRef +ProgramStateRef RangeConstraintManager::assumeSymNE(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { @@ -435,7 +466,7 @@ RangeConstraintManager::assumeSymNE(ProgramStateRef St, SymbolRef Sym, return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); } -ProgramStateRef +ProgramStateRef RangeConstraintManager::assumeSymEQ(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { @@ -450,122 +481,199 @@ RangeConstraintManager::assumeSymEQ(ProgramStateRef St, SymbolRef Sym, return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); } -ProgramStateRef -RangeConstraintManager::assumeSymLT(ProgramStateRef St, SymbolRef Sym, - const llvm::APSInt &Int, - const llvm::APSInt &Adjustment) { +RangeSet RangeConstraintManager::getSymLTRange(ProgramStateRef St, + SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { // Before we do any real work, see if the value can even show up. APSIntType AdjustmentType(Adjustment); switch (AdjustmentType.testInRange(Int, true)) { case APSIntType::RTR_Below: - return nullptr; + return F.getEmptySet(); case APSIntType::RTR_Within: break; case APSIntType::RTR_Above: - return St; + return GetRange(St, Sym); } // Special case for Int == Min. This is always false. llvm::APSInt ComparisonVal = AdjustmentType.convert(Int); llvm::APSInt Min = AdjustmentType.getMinValue(); if (ComparisonVal == Min) - return nullptr; + return F.getEmptySet(); - llvm::APSInt Lower = Min-Adjustment; - llvm::APSInt Upper = ComparisonVal-Adjustment; + llvm::APSInt Lower = Min - Adjustment; + llvm::APSInt Upper = ComparisonVal - Adjustment; --Upper; - RangeSet New = GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); } -ProgramStateRef -RangeConstraintManager::assumeSymGT(ProgramStateRef St, SymbolRef Sym, +ProgramStateRef +RangeConstraintManager::assumeSymLT(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { + RangeSet New = getSymLTRange(St, Sym, Int, Adjustment); + return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); +} + +RangeSet +RangeConstraintManager::getSymGTRange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { // Before we do any real work, see if the value can even show up. APSIntType AdjustmentType(Adjustment); switch (AdjustmentType.testInRange(Int, true)) { case APSIntType::RTR_Below: - return St; + return GetRange(St, Sym); case APSIntType::RTR_Within: break; case APSIntType::RTR_Above: - return nullptr; + return F.getEmptySet(); } // Special case for Int == Max. This is always false. llvm::APSInt ComparisonVal = AdjustmentType.convert(Int); llvm::APSInt Max = AdjustmentType.getMaxValue(); if (ComparisonVal == Max) - return nullptr; + return F.getEmptySet(); - llvm::APSInt Lower = ComparisonVal-Adjustment; - llvm::APSInt Upper = Max-Adjustment; + llvm::APSInt Lower = ComparisonVal - Adjustment; + llvm::APSInt Upper = Max - Adjustment; ++Lower; - RangeSet New = GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); } -ProgramStateRef -RangeConstraintManager::assumeSymGE(ProgramStateRef St, SymbolRef Sym, +ProgramStateRef +RangeConstraintManager::assumeSymGT(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { + RangeSet New = getSymGTRange(St, Sym, Int, Adjustment); + return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); +} + +RangeSet +RangeConstraintManager::getSymGERange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { // Before we do any real work, see if the value can even show up. APSIntType AdjustmentType(Adjustment); switch (AdjustmentType.testInRange(Int, true)) { case APSIntType::RTR_Below: - return St; + return GetRange(St, Sym); case APSIntType::RTR_Within: break; case APSIntType::RTR_Above: - return nullptr; + return F.getEmptySet(); } // Special case for Int == Min. This is always feasible. llvm::APSInt ComparisonVal = AdjustmentType.convert(Int); llvm::APSInt Min = AdjustmentType.getMinValue(); if (ComparisonVal == Min) - return St; + return GetRange(St, Sym); llvm::APSInt Max = AdjustmentType.getMaxValue(); - llvm::APSInt Lower = ComparisonVal-Adjustment; - llvm::APSInt Upper = Max-Adjustment; + llvm::APSInt Lower = ComparisonVal - Adjustment; + llvm::APSInt Upper = Max - Adjustment; - RangeSet New = GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); - return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); + return GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); } -ProgramStateRef -RangeConstraintManager::assumeSymLE(ProgramStateRef St, SymbolRef Sym, +ProgramStateRef +RangeConstraintManager::assumeSymGE(ProgramStateRef St, SymbolRef Sym, const llvm::APSInt &Int, const llvm::APSInt &Adjustment) { + RangeSet New = getSymGERange(St, Sym, Int, Adjustment); + return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); +} + +RangeSet +RangeConstraintManager::getSymLERange(const RangeSet &RS, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { // Before we do any real work, see if the value can even show up. APSIntType AdjustmentType(Adjustment); switch (AdjustmentType.testInRange(Int, true)) { case APSIntType::RTR_Below: - return nullptr; + return F.getEmptySet(); case APSIntType::RTR_Within: break; case APSIntType::RTR_Above: - return St; + return RS; } // Special case for Int == Max. This is always feasible. llvm::APSInt ComparisonVal = AdjustmentType.convert(Int); llvm::APSInt Max = AdjustmentType.getMaxValue(); if (ComparisonVal == Max) - return St; + return RS; + + llvm::APSInt Min = AdjustmentType.getMinValue(); + llvm::APSInt Lower = Min - Adjustment; + llvm::APSInt Upper = ComparisonVal - Adjustment; + + return RS.Intersect(getBasicVals(), F, Lower, Upper); +} + +RangeSet +RangeConstraintManager::getSymLERange(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { + // Before we do any real work, see if the value can even show up. + APSIntType AdjustmentType(Adjustment); + switch (AdjustmentType.testInRange(Int, true)) { + case APSIntType::RTR_Below: + return F.getEmptySet(); + case APSIntType::RTR_Within: + break; + case APSIntType::RTR_Above: + return GetRange(St, Sym); + } + + // Special case for Int == Max. This is always feasible. + llvm::APSInt ComparisonVal = AdjustmentType.convert(Int); + llvm::APSInt Max = AdjustmentType.getMaxValue(); + if (ComparisonVal == Max) + return GetRange(St, Sym); llvm::APSInt Min = AdjustmentType.getMinValue(); - llvm::APSInt Lower = Min-Adjustment; - llvm::APSInt Upper = ComparisonVal-Adjustment; + llvm::APSInt Lower = Min - Adjustment; + llvm::APSInt Upper = ComparisonVal - Adjustment; + + return GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); +} - RangeSet New = GetRange(St, Sym).Intersect(getBasicVals(), F, Lower, Upper); +ProgramStateRef +RangeConstraintManager::assumeSymLE(ProgramStateRef St, SymbolRef Sym, + const llvm::APSInt &Int, + const llvm::APSInt &Adjustment) { + RangeSet New = getSymLERange(St, Sym, Int, Adjustment); return New.isEmpty() ? nullptr : St->set<ConstraintRange>(Sym, New); } +ProgramStateRef +RangeConstraintManager::assumeSymbolWithinInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) { + RangeSet New = getSymGERange(State, Sym, From, Adjustment); + if (New.isEmpty()) + return nullptr; + New = getSymLERange(New, To, Adjustment); + return New.isEmpty() ? nullptr : State->set<ConstraintRange>(Sym, New); +} + +ProgramStateRef +RangeConstraintManager::assumeSymbolOutOfInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) { + RangeSet RangeLT = getSymLTRange(State, Sym, From, Adjustment); + RangeSet RangeGT = getSymGTRange(State, Sym, To, Adjustment); + RangeSet New(RangeLT.addRange(F, RangeGT)); + return New.isEmpty() ? nullptr : State->set<ConstraintRange>(Sym, New); +} + //===------------------------------------------------------------------------=== // Pretty-printing. //===------------------------------------------------------------------------===/ diff --git a/lib/StaticAnalyzer/Core/RegionStore.cpp b/lib/StaticAnalyzer/Core/RegionStore.cpp index 6d41fc2..a63f6e4 100644 --- a/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -149,7 +149,8 @@ typedef llvm::ImmutableMap<const MemRegion *, ClusterBindings> namespace { class RegionBindingsRef : public llvm::ImmutableMapRef<const MemRegion *, ClusterBindings> { - ClusterBindings::Factory &CBFactory; + ClusterBindings::Factory *CBFactory; + public: typedef llvm::ImmutableMapRef<const MemRegion *, ClusterBindings> ParentTy; @@ -157,21 +158,21 @@ public: RegionBindingsRef(ClusterBindings::Factory &CBFactory, const RegionBindings::TreeTy *T, RegionBindings::TreeTy::Factory *F) - : llvm::ImmutableMapRef<const MemRegion *, ClusterBindings>(T, F), - CBFactory(CBFactory) {} + : llvm::ImmutableMapRef<const MemRegion *, ClusterBindings>(T, F), + CBFactory(&CBFactory) {} RegionBindingsRef(const ParentTy &P, ClusterBindings::Factory &CBFactory) - : llvm::ImmutableMapRef<const MemRegion *, ClusterBindings>(P), - CBFactory(CBFactory) {} + : llvm::ImmutableMapRef<const MemRegion *, ClusterBindings>(P), + CBFactory(&CBFactory) {} RegionBindingsRef add(key_type_ref K, data_type_ref D) const { - return RegionBindingsRef(static_cast<const ParentTy*>(this)->add(K, D), - CBFactory); + return RegionBindingsRef(static_cast<const ParentTy *>(this)->add(K, D), + *CBFactory); } RegionBindingsRef remove(key_type_ref K) const { - return RegionBindingsRef(static_cast<const ParentTy*>(this)->remove(K), - CBFactory); + return RegionBindingsRef(static_cast<const ParentTy *>(this)->remove(K), + *CBFactory); } RegionBindingsRef addBinding(BindingKey K, SVal V) const; @@ -179,16 +180,9 @@ public: RegionBindingsRef addBinding(const MemRegion *R, BindingKey::Kind k, SVal V) const; - RegionBindingsRef &operator=(const RegionBindingsRef &X) { - *static_cast<ParentTy*>(this) = X; - return *this; - } - const SVal *lookup(BindingKey K) const; const SVal *lookup(const MemRegion *R, BindingKey::Kind k) const; - const ClusterBindings *lookup(const MemRegion *R) const { - return static_cast<const ParentTy*>(this)->lookup(R); - } + using llvm::ImmutableMapRef<const MemRegion *, ClusterBindings>::lookup; RegionBindingsRef removeBinding(BindingKey K); @@ -245,10 +239,10 @@ RegionBindingsRef RegionBindingsRef::addBinding(BindingKey K, SVal V) const { const MemRegion *Base = K.getBaseRegion(); const ClusterBindings *ExistingCluster = lookup(Base); - ClusterBindings Cluster = (ExistingCluster ? *ExistingCluster - : CBFactory.getEmptyMap()); + ClusterBindings Cluster = + (ExistingCluster ? *ExistingCluster : CBFactory->getEmptyMap()); - ClusterBindings NewCluster = CBFactory.add(Cluster, K, V); + ClusterBindings NewCluster = CBFactory->add(Cluster, K, V); return add(Base, NewCluster); } @@ -277,7 +271,7 @@ RegionBindingsRef RegionBindingsRef::removeBinding(BindingKey K) { if (!Cluster) return *this; - ClusterBindings NewCluster = CBFactory.remove(*Cluster, K); + ClusterBindings NewCluster = CBFactory->remove(*Cluster, K); if (NewCluster.isEmpty()) return remove(Base); return add(Base, NewCluster); @@ -470,9 +464,9 @@ public: // Part of public interface to class. StoreRef killBinding(Store ST, Loc L) override; void incrementReferenceCount(Store store) override { - getRegionBindings(store).manualRetain(); + getRegionBindings(store).manualRetain(); } - + /// If the StoreManager supports it, decrement the reference count of /// the specified Store object. If the reference count hits 0, the memory /// associated with the object is recycled. @@ -514,7 +508,7 @@ public: // Part of public interface to class. SVal getBindingForFieldOrElementCommon(RegionBindingsConstRef B, const TypedValueRegion *R, QualType Ty); - + SVal getLazyBinding(const SubRegion *LazyBindingRegion, RegionBindingsRef LazyBinding); @@ -656,35 +650,25 @@ protected: RegionBindingsRef B; -private: - GlobalsFilterKind GlobalsFilter; protected: const ClusterBindings *getCluster(const MemRegion *R) { return B.lookup(R); } - /// Returns true if the memory space of the given region is one of the global - /// regions specially included at the start of analysis. - bool isInitiallyIncludedGlobalRegion(const MemRegion *R) { - switch (GlobalsFilter) { - case GFK_None: - return false; - case GFK_SystemOnly: - return isa<GlobalSystemSpaceRegion>(R->getMemorySpace()); - case GFK_All: - return isa<NonStaticGlobalSpaceRegion>(R->getMemorySpace()); - } - - llvm_unreachable("unknown globals filter"); + /// Returns true if all clusters in the given memspace should be initially + /// included in the cluster analysis. Subclasses may provide their + /// own implementation. + bool includeEntireMemorySpace(const MemRegion *Base) { + return false; } public: ClusterAnalysis(RegionStoreManager &rm, ProgramStateManager &StateMgr, - RegionBindingsRef b, GlobalsFilterKind GFK) + RegionBindingsRef b ) : RM(rm), Ctx(StateMgr.getContext()), svalBuilder(StateMgr.getSValBuilder()), - B(b), GlobalsFilter(GFK) {} + B(b) {} RegionBindingsRef getRegionBindings() const { return B; } @@ -702,8 +686,9 @@ public: assert(!Cluster.isEmpty() && "Empty clusters should be removed"); static_cast<DERIVED*>(this)->VisitAddedToCluster(Base, Cluster); - // If this is an interesting global region, add it the work list up front. - if (isInitiallyIncludedGlobalRegion(Base)) + // If the base's memspace should be entirely invalidated, add the cluster + // to the workspace up front. + if (static_cast<DERIVED*>(this)->includeEntireMemorySpace(Base)) AddToWorkList(WorkListElement(Base), &Cluster); } } @@ -716,8 +701,7 @@ public: } bool AddToWorkList(const MemRegion *R) { - const MemRegion *BaseR = R->getBaseRegion(); - return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR)); + return static_cast<DERIVED*>(this)->AddToWorkList(R); } void RunWorkList() { @@ -947,6 +931,7 @@ class invalidateRegionsWorker : public ClusterAnalysis<invalidateRegionsWorker> InvalidatedSymbols &IS; RegionAndSymbolInvalidationTraits &ITraits; StoreManager::InvalidatedRegions *Regions; + GlobalsFilterKind GlobalsFilter; public: invalidateRegionsWorker(RegionStoreManager &rm, ProgramStateManager &stateMgr, @@ -957,14 +942,34 @@ public: RegionAndSymbolInvalidationTraits &ITraitsIn, StoreManager::InvalidatedRegions *r, GlobalsFilterKind GFK) - : ClusterAnalysis<invalidateRegionsWorker>(rm, stateMgr, b, GFK), - Ex(ex), Count(count), LCtx(lctx), IS(is), ITraits(ITraitsIn), Regions(r){} + : ClusterAnalysis<invalidateRegionsWorker>(rm, stateMgr, b), + Ex(ex), Count(count), LCtx(lctx), IS(is), ITraits(ITraitsIn), Regions(r), + GlobalsFilter(GFK) {} void VisitCluster(const MemRegion *baseR, const ClusterBindings *C); void VisitBinding(SVal V); + + using ClusterAnalysis::AddToWorkList; + + bool AddToWorkList(const MemRegion *R); + + /// Returns true if all clusters in the memory space for \p Base should be + /// be invalidated. + bool includeEntireMemorySpace(const MemRegion *Base); + + /// Returns true if the memory space of the given region is one of the global + /// regions specially included at the start of invalidation. + bool isInitiallyIncludedGlobalRegion(const MemRegion *R); }; } +bool invalidateRegionsWorker::AddToWorkList(const MemRegion *R) { + bool doNotInvalidateSuperRegion = ITraits.hasTrait( + R, RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + const MemRegion *BaseR = doNotInvalidateSuperRegion ? R : R->getBaseRegion(); + return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR)); +} + void invalidateRegionsWorker::VisitBinding(SVal V) { // A symbol? Mark it touched by the invalidation. if (SymbolRef Sym = V.getAsSymbol()) @@ -993,8 +998,8 @@ void invalidateRegionsWorker::VisitBinding(SVal V) { void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, const ClusterBindings *C) { - bool PreserveRegionsContents = - ITraits.hasTrait(baseR, + bool PreserveRegionsContents = + ITraits.hasTrait(baseR, RegionAndSymbolInvalidationTraits::TK_PreserveContents); if (C) { @@ -1077,6 +1082,70 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, } if (const ArrayType *AT = Ctx.getAsArrayType(T)) { + bool doNotInvalidateSuperRegion = ITraits.hasTrait( + baseR, + RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + + if (doNotInvalidateSuperRegion) { + // We are not doing blank invalidation of the whole array region so we + // have to manually invalidate each elements. + Optional<uint64_t> NumElements; + + // Compute lower and upper offsets for region within array. + if (const ConstantArrayType *CAT = dyn_cast<ConstantArrayType>(AT)) + NumElements = CAT->getSize().getZExtValue(); + if (!NumElements) // We are not dealing with a constant size array + goto conjure_default; + QualType ElementTy = AT->getElementType(); + uint64_t ElemSize = Ctx.getTypeSize(ElementTy); + const RegionOffset &RO = baseR->getAsOffset(); + const MemRegion *SuperR = baseR->getBaseRegion(); + if (RO.hasSymbolicOffset()) { + // If base region has a symbolic offset, + // we revert to invalidating the super region. + if (SuperR) + AddToWorkList(SuperR); + goto conjure_default; + } + + uint64_t LowerOffset = RO.getOffset(); + uint64_t UpperOffset = LowerOffset + *NumElements * ElemSize; + bool UpperOverflow = UpperOffset < LowerOffset; + + // Invalidate regions which are within array boundaries, + // or have a symbolic offset. + if (!SuperR) + goto conjure_default; + + const ClusterBindings *C = B.lookup(SuperR); + if (!C) + goto conjure_default; + + for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; + ++I) { + const BindingKey &BK = I.getKey(); + Optional<uint64_t> ROffset = + BK.hasSymbolicOffset() ? Optional<uint64_t>() : BK.getOffset(); + + // Check offset is not symbolic and within array's boundaries. + // Handles arrays of 0 elements and of 0-sized elements as well. + if (!ROffset || + (ROffset && + ((*ROffset >= LowerOffset && *ROffset < UpperOffset) || + (UpperOverflow && + (*ROffset >= LowerOffset || *ROffset < UpperOffset)) || + (LowerOffset == UpperOffset && *ROffset == LowerOffset)))) { + B = B.removeBinding(I.getKey()); + // Bound symbolic regions need to be invalidated for dead symbol + // detection. + SVal V = I.getData(); + const MemRegion *R = V.getAsRegion(); + if (R && isa<SymbolicRegion>(R)) + VisitBinding(V); + } + } + } + conjure_default: // Set the default value of the array to conjured symbol. DefinedOrUnknownSVal V = svalBuilder.conjureSymbolVal(baseR, Ex, LCtx, @@ -1091,6 +1160,29 @@ void invalidateRegionsWorker::VisitCluster(const MemRegion *baseR, B = B.addBinding(baseR, BindingKey::Direct, V); } +bool invalidateRegionsWorker::isInitiallyIncludedGlobalRegion( + const MemRegion *R) { + switch (GlobalsFilter) { + case GFK_None: + return false; + case GFK_SystemOnly: + return isa<GlobalSystemSpaceRegion>(R->getMemorySpace()); + case GFK_All: + return isa<NonStaticGlobalSpaceRegion>(R->getMemorySpace()); + } + + llvm_unreachable("unknown globals filter"); +} + +bool invalidateRegionsWorker::includeEntireMemorySpace(const MemRegion *Base) { + if (isInitiallyIncludedGlobalRegion(Base)) + return true; + + const MemSpaceRegion *MemSpace = Base->getMemorySpace(); + return ITraits.hasTrait(MemSpace, + RegionAndSymbolInvalidationTraits::TK_EntireMemSpace); +} + RegionBindingsRef RegionStoreManager::invalidateGlobalRegion(MemRegion::Kind K, const Expr *Ex, @@ -1273,6 +1365,10 @@ SVal RegionStoreManager::getBinding(RegionBindingsConstRef B, Loc L, QualType T) const MemRegion *MR = L.castAs<loc::MemRegionVal>().getRegion(); + if (isa<BlockDataRegion>(MR)) { + return UnknownVal(); + } + if (isa<AllocaRegion>(MR) || isa<SymbolicRegion>(MR) || isa<CodeTextRegion>(MR)) { @@ -1462,7 +1558,7 @@ RegionStoreManager::findLazyBinding(RegionBindingsConstRef B, // through to look for lazy compound value. It is like a field region. Result = findLazyBinding(B, cast<SubRegion>(BaseReg->getSuperRegion()), originalRegion); - + if (Result.second) Result.second = MRMgr.getCXXBaseObjectRegionWithSuper(BaseReg, Result.second); @@ -1508,7 +1604,7 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, return svalBuilder.makeIntVal(c, T); } } - + // Check for loads from a code text region. For such loads, just give up. if (isa<CodeTextRegion>(superR)) return UnknownVal(); @@ -1520,12 +1616,12 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, // return *y; // FIXME: This is a hack, and doesn't do anything really intelligent yet. const RegionRawOffset &O = R->getAsArrayOffset(); - + // If we cannot reason about the offset, return an unknown value. if (!O.getRegion()) return UnknownVal(); - - if (const TypedValueRegion *baseR = + + if (const TypedValueRegion *baseR = dyn_cast_or_null<TypedValueRegion>(O.getRegion())) { QualType baseT = baseR->getValueType(); if (baseT->isScalarType()) { @@ -1616,7 +1712,7 @@ SVal RegionStoreManager::getLazyBinding(const SubRegion *LazyBindingRegion, return Result; } - + SVal RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, const TypedValueRegion *R, @@ -1670,7 +1766,7 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, if (!index.isConstant()) hasSymbolicIndex = true; } - + // If our super region is a field or element itself, walk up the region // hierarchy to see if there is a default value installed in an ancestor. SR = dyn_cast<SubRegion>(Base); @@ -1680,7 +1776,7 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, if (isa<ElementRegion>(R)) { // Currently we don't reason specially about Clang-style vectors. Check // if superR is a vector and if so return Unknown. - if (const TypedValueRegion *typedSuperR = + if (const TypedValueRegion *typedSuperR = dyn_cast<TypedValueRegion>(R->getSuperRegion())) { if (typedSuperR->getValueType()->isVectorType()) return UnknownVal(); @@ -1807,7 +1903,7 @@ RegionStoreManager::getInterestingValues(nonloc::LazyCompoundVal LCV) { List.insert(List.end(), InnerList.begin(), InnerList.end()); continue; } - + List.push_back(V); } @@ -1844,7 +1940,7 @@ SVal RegionStoreManager::getBindingForArray(RegionBindingsConstRef B, const TypedValueRegion *R) { assert(Ctx.getAsConstantArrayType(R->getValueType()) && "Only constant array types can have compound bindings."); - + return createLazyBinding(B, R); } @@ -2018,11 +2114,11 @@ RegionBindingsRef RegionStoreManager::bindVector(RegionBindingsConstRef B, QualType T = R->getValueType(); assert(T->isVectorType()); const VectorType *VT = T->getAs<VectorType>(); // Use getAs for typedefs. - + // Handle lazy compound values and symbolic values. if (V.getAs<nonloc::LazyCompoundVal>() || V.getAs<nonloc::SymbolVal>()) return bindAggregate(B, R, V); - + // We may get non-CompoundVal accidentally due to imprecise cast logic or // that we are binding symbolic struct value. Kill the field values, and if // the value is symbolic go and bind it as a "default" binding. @@ -2039,7 +2135,7 @@ RegionBindingsRef RegionStoreManager::bindVector(RegionBindingsConstRef B, for ( ; index != numElements ; ++index) { if (VI == VE) break; - + NonLoc Idx = svalBuilder.makeArrayIndex(index); const ElementRegion *ER = MRMgr.getElementRegion(ElemType, Idx, R, Ctx); @@ -2081,7 +2177,7 @@ RegionStoreManager::tryBindSmallStruct(RegionBindingsConstRef B, } RegionBindingsRef NewB = B; - + for (FieldVector::iterator I = Fields.begin(), E = Fields.end(); I != E; ++I){ const FieldRegion *SourceFR = MRMgr.getFieldRegion(*I, LCV.getRegion()); SVal V = getBindingForField(getRegionBindings(LCV.getStore()), SourceFR); @@ -2185,7 +2281,7 @@ public: ProgramStateManager &stateMgr, RegionBindingsRef b, SymbolReaper &symReaper, const StackFrameContext *LCtx) - : ClusterAnalysis<removeDeadBindingsWorker>(rm, stateMgr, b, GFK_None), + : ClusterAnalysis<removeDeadBindingsWorker>(rm, stateMgr, b), SymReaper(symReaper), CurrentLCtx(LCtx) {} // Called by ClusterAnalysis. @@ -2193,11 +2289,20 @@ public: void VisitCluster(const MemRegion *baseR, const ClusterBindings *C); using ClusterAnalysis<removeDeadBindingsWorker>::VisitCluster; + using ClusterAnalysis::AddToWorkList; + + bool AddToWorkList(const MemRegion *R); + bool UpdatePostponed(); void VisitBinding(SVal V); }; } +bool removeDeadBindingsWorker::AddToWorkList(const MemRegion *R) { + const MemRegion *BaseR = R->getBaseRegion(); + return AddToWorkList(WorkListElement(BaseR), getCluster(BaseR)); +} + void removeDeadBindingsWorker::VisitAddedToCluster(const MemRegion *baseR, const ClusterBindings &C) { @@ -2243,8 +2348,12 @@ void removeDeadBindingsWorker::VisitCluster(const MemRegion *baseR, if (const SymbolicRegion *SymR = dyn_cast<SymbolicRegion>(baseR)) SymReaper.markLive(SymR->getSymbol()); - for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; ++I) + for (ClusterBindings::iterator I = C->begin(), E = C->end(); I != E; ++I) { + // Element index of a binding key is live. + SymReaper.markElementIndicesLive(I.getKey().getRegion()); + VisitBinding(I.getData()); + } } void removeDeadBindingsWorker::VisitBinding(SVal V) { @@ -2265,7 +2374,8 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) { // If V is a region, then add it to the worklist. if (const MemRegion *R = V.getAsRegion()) { AddToWorkList(R); - + SymReaper.markLive(R); + // All regions captured by a block are also live. if (const BlockDataRegion *BR = dyn_cast<BlockDataRegion>(R)) { BlockDataRegion::referenced_vars_iterator I = BR->referenced_vars_begin(), @@ -2274,7 +2384,7 @@ void removeDeadBindingsWorker::VisitBinding(SVal V) { AddToWorkList(I.getCapturedRegion()); } } - + // Update the set of live symbols. for (SymExpr::symbol_iterator SI = V.symbol_begin(), SE = V.symbol_end(); diff --git a/lib/StaticAnalyzer/Core/SValBuilder.cpp b/lib/StaticAnalyzer/Core/SValBuilder.cpp index 3ed2bde..cdae040 100644 --- a/lib/StaticAnalyzer/Core/SValBuilder.cpp +++ b/lib/StaticAnalyzer/Core/SValBuilder.cpp @@ -91,10 +91,13 @@ nonloc::ConcreteInt SValBuilder::makeBoolVal(const CXXBoolLiteralExpr *boolean){ return makeTruthVal(boolean->getValue()); } -DefinedOrUnknownSVal +DefinedOrUnknownSVal SValBuilder::getRegionValueSymbolVal(const TypedValueRegion* region) { QualType T = region->getValueType(); + if (T->isNullPtrType()) + return makeZeroVal(T); + if (!SymbolManager::canSymbolicate(T)) return UnknownVal(); @@ -112,6 +115,9 @@ DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const void *SymbolTag, unsigned Count) { QualType T = Ex->getType(); + if (T->isNullPtrType()) + return makeZeroVal(T); + // Compute the type of the result. If the expression is not an R-value, the // result should be a location. QualType ExType = Ex->getType(); @@ -126,6 +132,9 @@ DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const void *symbolTag, const LocationContext *LCtx, QualType type, unsigned count) { + if (type->isNullPtrType()) + return makeZeroVal(type); + if (!SymbolManager::canSymbolicate(type)) return UnknownVal(); @@ -142,14 +151,17 @@ DefinedOrUnknownSVal SValBuilder::conjureSymbolVal(const Stmt *stmt, const LocationContext *LCtx, QualType type, unsigned visitCount) { + if (type->isNullPtrType()) + return makeZeroVal(type); + if (!SymbolManager::canSymbolicate(type)) return UnknownVal(); SymbolRef sym = SymMgr.conjureSymbol(stmt, LCtx, type, visitCount); - + if (Loc::isLocType(type)) return loc::MemRegionVal(MemMgr.getSymbolicRegion(sym)); - + return nonloc::SymbolVal(sym); } @@ -160,6 +172,8 @@ SValBuilder::getConjuredHeapSymbolVal(const Expr *E, QualType T = E->getType(); assert(Loc::isLocType(T)); assert(SymbolManager::canSymbolicate(T)); + if (T->isNullPtrType()) + return makeZeroVal(T); SymbolRef sym = SymMgr.conjureSymbol(E, LCtx, T, VisitCount); return loc::MemRegionVal(MemMgr.getSymbolicHeapRegion(sym)); @@ -185,6 +199,9 @@ SValBuilder::getDerivedRegionValueSymbolVal(SymbolRef parentSymbol, const TypedValueRegion *region) { QualType T = region->getValueType(); + if (T->isNullPtrType()) + return makeZeroVal(T); + if (!SymbolManager::canSymbolicate(T)) return UnknownVal(); @@ -259,6 +276,11 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case Stmt::CXXBoolLiteralExprClass: return makeBoolVal(cast<CXXBoolLiteralExpr>(E)); + case Stmt::TypeTraitExprClass: { + const TypeTraitExpr *TE = cast<TypeTraitExpr>(E); + return makeTruthVal(TE->getValue(), TE->getType()); + } + case Stmt::IntegerLiteralClass: return makeIntVal(cast<IntegerLiteral>(E)); @@ -270,11 +292,17 @@ Optional<SVal> SValBuilder::getConstantVal(const Expr *E) { case Stmt::ImplicitCastExprClass: { const CastExpr *CE = cast<CastExpr>(E); - if (CE->getCastKind() == CK_ArrayToPointerDecay) { - Optional<SVal> ArrayVal = getConstantVal(CE->getSubExpr()); - if (!ArrayVal) + switch (CE->getCastKind()) { + default: + break; + case CK_ArrayToPointerDecay: + case CK_BitCast: { + const Expr *SE = CE->getSubExpr(); + Optional<SVal> Val = getConstantVal(SE); + if (!Val) return None; - return evalCast(*ArrayVal, CE->getType(), CE->getSubExpr()->getType()); + return evalCast(*Val, CE->getType(), SE->getType()); + } } // FALLTHROUGH } @@ -307,7 +335,7 @@ SVal SValBuilder::makeSymExprValNN(ProgramStateRef State, QualType ResultTy) { if (!State->isTainted(RHS) && !State->isTainted(LHS)) return UnknownVal(); - + const SymExpr *symLHS = LHS.getAsSymExpr(); const SymExpr *symRHS = RHS.getAsSymExpr(); // TODO: When the Max Complexity is reached, we should conjure a symbol @@ -430,7 +458,7 @@ SVal SValBuilder::evalCast(SVal val, QualType castTy, QualType originalTy) { if (shouldBeModeledWithNoOp(Context, Context.getPointerType(castTy), Context.getPointerType(originalTy))) return val; - + // Check for casts from pointers to integers. if (castTy->isIntegralOrEnumerationType() && Loc::isLocType(originalTy)) return evalCastFromLoc(val.castAs<Loc>(), castTy); diff --git a/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp b/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp index 35930e4..4051242 100644 --- a/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp +++ b/lib/StaticAnalyzer/Core/SimpleConstraintManager.cpp @@ -190,6 +190,42 @@ ProgramStateRef SimpleConstraintManager::assumeAux(ProgramStateRef state, } // end switch } +ProgramStateRef SimpleConstraintManager::assumeWithinInclusiveRange( + ProgramStateRef State, NonLoc Value, const llvm::APSInt &From, + const llvm::APSInt &To, bool InRange) { + + assert(From.isUnsigned() == To.isUnsigned() && + From.getBitWidth() == To.getBitWidth() && + "Values should have same types!"); + + if (!canReasonAbout(Value)) { + // Just add the constraint to the expression without trying to simplify. + SymbolRef Sym = Value.getAsSymExpr(); + assert(Sym); + return assumeSymWithinInclusiveRange(State, Sym, From, To, InRange); + } + + switch (Value.getSubKind()) { + default: + llvm_unreachable("'assumeWithinInclusiveRange' is not implemented" + "for this NonLoc"); + + case nonloc::LocAsIntegerKind: + case nonloc::SymbolValKind: { + if (SymbolRef Sym = Value.getAsSymbol()) + return assumeSymWithinInclusiveRange(State, Sym, From, To, InRange); + return State; + } // end switch + + case nonloc::ConcreteIntKind: { + const llvm::APSInt &IntVal = Value.castAs<nonloc::ConcreteInt>().getValue(); + bool IsInRange = IntVal >= From && IntVal <= To; + bool isFeasible = (IsInRange == InRange); + return isFeasible ? State : nullptr; + } + } // end switch +} + static void computeAdjustment(SymbolRef &Sym, llvm::APSInt &Adjustment) { // Is it a "($sym+constant1)" expression? if (const SymIntExpr *SE = dyn_cast<SymIntExpr>(Sym)) { @@ -262,6 +298,37 @@ ProgramStateRef SimpleConstraintManager::assumeSymRel(ProgramStateRef state, } // end switch } +ProgramStateRef +SimpleConstraintManager::assumeSymWithinInclusiveRange(ProgramStateRef State, + SymbolRef Sym, + const llvm::APSInt &From, + const llvm::APSInt &To, + bool InRange) { + // Get the type used for calculating wraparound. + BasicValueFactory &BVF = getBasicVals(); + APSIntType WraparoundType = BVF.getAPSIntType(Sym->getType()); + + llvm::APSInt Adjustment = WraparoundType.getZeroValue(); + SymbolRef AdjustedSym = Sym; + computeAdjustment(AdjustedSym, Adjustment); + + // Convert the right-hand side integer as necessary. + APSIntType ComparisonType = std::max(WraparoundType, APSIntType(From)); + llvm::APSInt ConvertedFrom = ComparisonType.convert(From); + llvm::APSInt ConvertedTo = ComparisonType.convert(To); + + // Prefer unsigned comparisons. + if (ComparisonType.getBitWidth() == WraparoundType.getBitWidth() && + ComparisonType.isUnsigned() && !WraparoundType.isUnsigned()) + Adjustment.setIsSigned(false); + + if (InRange) + return assumeSymbolWithinInclusiveRange(State, AdjustedSym, ConvertedFrom, + ConvertedTo, Adjustment); + return assumeSymbolOutOfInclusiveRange(State, AdjustedSym, ConvertedFrom, + ConvertedTo, Adjustment); +} + } // end of namespace ento } // end of namespace clang diff --git a/lib/StaticAnalyzer/Core/SimpleConstraintManager.h b/lib/StaticAnalyzer/Core/SimpleConstraintManager.h index 135cd4e..b26bc94 100644 --- a/lib/StaticAnalyzer/Core/SimpleConstraintManager.h +++ b/lib/StaticAnalyzer/Core/SimpleConstraintManager.h @@ -38,11 +38,24 @@ public: ProgramStateRef assume(ProgramStateRef state, NonLoc Cond, bool Assumption); + ProgramStateRef assumeWithinInclusiveRange(ProgramStateRef State, + NonLoc Value, + const llvm::APSInt &From, + const llvm::APSInt &To, + bool InRange) override; + ProgramStateRef assumeSymRel(ProgramStateRef state, const SymExpr *LHS, BinaryOperator::Opcode op, const llvm::APSInt& Int); + ProgramStateRef assumeSymWithinInclusiveRange(ProgramStateRef State, + SymbolRef Sym, + const llvm::APSInt &From, + const llvm::APSInt &To, + bool InRange); + + protected: //===------------------------------------------------------------------===// @@ -75,6 +88,14 @@ protected: const llvm::APSInt& V, const llvm::APSInt& Adjustment) = 0; + + virtual ProgramStateRef assumeSymbolWithinInclusiveRange( + ProgramStateRef State, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) = 0; + + virtual ProgramStateRef assumeSymbolOutOfInclusiveRange( + ProgramStateRef state, SymbolRef Sym, const llvm::APSInt &From, + const llvm::APSInt &To, const llvm::APSInt &Adjustment) = 0; //===------------------------------------------------------------------===// // Internal implementation. //===------------------------------------------------------------------===// diff --git a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp index b3cab87c..a704ce2 100644 --- a/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp +++ b/lib/StaticAnalyzer/Core/SimpleSValBuilder.cpp @@ -638,7 +638,7 @@ SVal SimpleSValBuilder::evalBinOpLL(ProgramStateRef state, // on the ABI). // FIXME: we can probably do a comparison against other MemRegions, though. // FIXME: is there a way to tell if two labels refer to the same location? - return UnknownVal(); + return UnknownVal(); case loc::ConcreteIntKind: { // If one of the operands is a symbol and the other is a constant, @@ -863,7 +863,7 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, // Special case: rhs is a zero constant. if (rhs.isZeroConstant()) return lhs; - + // We are dealing with pointer arithmetic. // Handle pointer arithmetic on constant values. @@ -880,7 +880,7 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, // Offset the increment by the pointer size. llvm::APSInt Multiplicand(rightI.getBitWidth(), /* isUnsigned */ true); rightI *= Multiplicand; - + // Compute the adjusted pointer. switch (op) { case BO_Add: @@ -911,8 +911,9 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, elementType = elemReg->getElementType(); } else if (isa<SubRegion>(region)) { + assert(op == BO_Add || op == BO_Sub); + index = (op == BO_Add) ? rhs : evalMinus(rhs); superR = region; - index = rhs; if (resultTy->isAnyPointerType()) elementType = resultTy->getPointeeType(); } @@ -922,7 +923,7 @@ SVal SimpleSValBuilder::evalBinOpLN(ProgramStateRef state, superR, getContext())); } } - return UnknownVal(); + return UnknownVal(); } const llvm::APSInt *SimpleSValBuilder::getKnownValue(ProgramStateRef state, diff --git a/lib/StaticAnalyzer/Core/Store.cpp b/lib/StaticAnalyzer/Core/Store.cpp index 99ec1e7..7cdb55a 100644 --- a/lib/StaticAnalyzer/Core/Store.cpp +++ b/lib/StaticAnalyzer/Core/Store.cpp @@ -52,7 +52,7 @@ StoreRef StoreManager::BindDefault(Store store, const MemRegion *R, SVal V) { return StoreRef(store, *this); } -const ElementRegion *StoreManager::GetElementZeroRegion(const MemRegion *R, +const ElementRegion *StoreManager::GetElementZeroRegion(const MemRegion *R, QualType T) { NonLoc idx = svalBuilder.makeZeroArrayIndex(); assert(!T.isNull()); @@ -366,22 +366,22 @@ SVal StoreManager::evalDynamicCast(SVal Base, QualType TargetType, /// as another region. SVal StoreManager::CastRetrievedVal(SVal V, const TypedValueRegion *R, QualType castTy, bool performTestOnly) { - + if (castTy.isNull() || V.isUnknownOrUndef()) return V; - + ASTContext &Ctx = svalBuilder.getContext(); - if (performTestOnly) { + if (performTestOnly) { // Automatically translate references to pointers. QualType T = R->getValueType(); if (const ReferenceType *RT = T->getAs<ReferenceType>()) T = Ctx.getPointerType(RT->getPointeeType()); - + assert(svalBuilder.getContext().hasSameUnqualifiedType(castTy, T)); return V; } - + return svalBuilder.dispatchCast(V, castTy); } @@ -424,7 +424,7 @@ SVal StoreManager::getLValueIvar(const ObjCIvarDecl *decl, SVal base) { return getLValueFieldOrIvar(decl, base); } -SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, +SVal StoreManager::getLValueElement(QualType elementType, NonLoc Offset, SVal Base) { // If the base is an unknown or undefined value, just return it back. diff --git a/lib/StaticAnalyzer/Core/SymbolManager.cpp b/lib/StaticAnalyzer/Core/SymbolManager.cpp index cca0461..99b2e14 100644 --- a/lib/StaticAnalyzer/Core/SymbolManager.cpp +++ b/lib/StaticAnalyzer/Core/SymbolManager.cpp @@ -391,6 +391,18 @@ void SymbolReaper::markLive(SymbolRef sym) { void SymbolReaper::markLive(const MemRegion *region) { RegionRoots.insert(region); + markElementIndicesLive(region); +} + +void SymbolReaper::markElementIndicesLive(const MemRegion *region) { + for (auto SR = dyn_cast<SubRegion>(region); SR; + SR = dyn_cast<SubRegion>(SR->getSuperRegion())) { + if (auto ER = dyn_cast<ElementRegion>(SR)) { + SVal Idx = ER->getIndex(); + for (auto SI = Idx.symbol_begin(), SE = Idx.symbol_end(); SI != SE; ++SI) + markLive(*SI); + } + } } void SymbolReaper::markInUse(SymbolRef sym) { @@ -409,7 +421,7 @@ bool SymbolReaper::maybeDead(SymbolRef sym) { bool SymbolReaper::isLiveRegion(const MemRegion *MR) { if (RegionRoots.count(MR)) return true; - + MR = MR->getBaseRegion(); if (const SymbolicRegion *SR = dyn_cast<SymbolicRegion>(MR)) @@ -442,9 +454,9 @@ bool SymbolReaper::isLive(SymbolRef sym) { markDependentsLive(sym); return true; } - + bool KnownLive; - + switch (sym->getKind()) { case SymExpr::RegionValueKind: KnownLive = isLiveRegion(cast<SymbolRegionValue>(sym)->getRegion()); @@ -525,7 +537,7 @@ bool SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) const{ if (!includeStoreBindings) return false; - + unsigned &cachedQuery = const_cast<SymbolReaper*>(this)->includedRegionCache[VR]; @@ -535,16 +547,14 @@ bool SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) const{ // Query the store to see if the region occurs in any live bindings. if (Store store = reapedStore.getStore()) { - bool hasRegion = + bool hasRegion = reapedStore.getStoreManager().includedInBindings(store, VR); cachedQuery = hasRegion ? 1 : 2; return hasRegion; } - + return false; } return VarContext->isParentOf(CurrentContext); } - -SymbolVisitor::~SymbolVisitor() {} diff --git a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index c957a65..bf85c4c 100644 --- a/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -14,7 +14,7 @@ #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" #include "ModelInjector.h" #include "clang/AST/ASTConsumer.h" -#include "clang/AST/DataRecursiveASTVisitor.h" +#include "clang/AST/RecursiveASTVisitor.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -141,7 +141,7 @@ public: namespace { class AnalysisConsumer : public AnalysisASTConsumer, - public DataRecursiveASTVisitor<AnalysisConsumer> { + public RecursiveASTVisitor<AnalysisConsumer> { enum { AM_None = 0, AM_Syntax = 0x1, @@ -168,7 +168,7 @@ public: /// The local declaration to all declarations ratio might be very small when /// working with a PCH file. SetOfDecls LocalTUDecls; - + // Set of PathDiagnosticConsumers. Owned by AnalysisManager. PathDiagnosticConsumers PathConsumers; @@ -364,11 +364,15 @@ public: } return true; } - + bool VisitBlockDecl(BlockDecl *BD) { if (BD->hasBody()) { assert(RecVisitorMode == AM_Syntax || Mgr->shouldInlineCall() == false); - HandleCode(BD, RecVisitorMode); + // Since we skip function template definitions, we should skip blocks + // declared in those functions as well. + if (!BD->isDependentContext()) { + HandleCode(BD, RecVisitorMode); + } } return true; } @@ -475,7 +479,7 @@ void AnalysisConsumer::HandleDeclsCallGraph(const unsigned LocalTUDeclsSize) { CallGraphNode *N = *I; Decl *D = N->getDecl(); - + // Skip the abstract root node. if (!D) continue; @@ -588,8 +592,8 @@ AnalysisConsumer::getModeForDecl(Decl *D, AnalysisMode Mode) { // - Header files: run non-path-sensitive checks only. // - System headers: don't run any checks. SourceManager &SM = Ctx->getSourceManager(); - SourceLocation SL = D->hasBody() ? D->getBody()->getLocStart() - : D->getLocation(); + const Stmt *Body = D->getBody(); + SourceLocation SL = Body ? Body->getLocStart() : D->getLocation(); SL = SM.getExpansionLoc(SL); if (!Opts->AnalyzeAll && !SM.isWrittenInMainFile(SL)) { @@ -679,11 +683,11 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, case LangOptions::NonGC: ActionExprEngine(D, false, IMode, Visited); break; - + case LangOptions::GCOnly: ActionExprEngine(D, true, IMode, Visited); break; - + case LangOptions::HybridGC: ActionExprEngine(D, false, IMode, Visited); ActionExprEngine(D, true, IMode, Visited); @@ -778,8 +782,9 @@ void UbigraphViz::AddEdge(ExplodedNode *Src, ExplodedNode *Dst) { << ", ('arrow','true'), ('oriented', 'true'))\n"; } -UbigraphViz::UbigraphViz(std::unique_ptr<raw_ostream> Out, StringRef Filename) - : Out(std::move(Out)), Filename(Filename), Cntr(0) { +UbigraphViz::UbigraphViz(std::unique_ptr<raw_ostream> OutStream, + StringRef Filename) + : Out(std::move(OutStream)), Filename(Filename), Cntr(0) { *Out << "('vertex_style_attribute', 0, ('shape', 'icosahedron'))\n"; *Out << "('vertex_style', 1, 0, ('shape', 'sphere'), ('color', '#ffcc66')," diff --git a/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp index 7fced1e..75fa4c6 100644 --- a/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ b/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp @@ -42,7 +42,7 @@ public: ClangCheckerRegistry(ArrayRef<std::string> plugins, DiagnosticsEngine *diags = nullptr); }; - + } // end anonymous namespace ClangCheckerRegistry::ClangCheckerRegistry(ArrayRef<std::string> plugins, @@ -52,7 +52,12 @@ ClangCheckerRegistry::ClangCheckerRegistry(ArrayRef<std::string> plugins, for (ArrayRef<std::string>::iterator i = plugins.begin(), e = plugins.end(); i != e; ++i) { // Get access to the plugin. - DynamicLibrary lib = DynamicLibrary::getPermanentLibrary(i->c_str()); + std::string err; + DynamicLibrary lib = DynamicLibrary::getPermanentLibrary(i->c_str(), &err); + if (!lib.isValid()) { + diags->Report(diag::err_fe_unable_to_load_plugin) << *i << err; + continue; + } // See if it's compatible with this build of clang. const char *pluginAPIVersion = @@ -78,10 +83,7 @@ bool ClangCheckerRegistry::isCompatibleAPIVersion(const char *versionString) { // For now, none of the static analyzer API is considered stable. // Versions must match exactly. - if (strcmp(versionString, CLANG_ANALYZER_API_VERSION_STRING) == 0) - return true; - - return false; + return strcmp(versionString, CLANG_ANALYZER_API_VERSION_STRING) == 0; } void ClangCheckerRegistry::warnIncompatible(DiagnosticsEngine *diags, diff --git a/lib/StaticAnalyzer/Frontend/Makefile b/lib/StaticAnalyzer/Frontend/Makefile index 2698120..3f15988 100644 --- a/lib/StaticAnalyzer/Frontend/Makefile +++ b/lib/StaticAnalyzer/Frontend/Makefile @@ -1,10 +1,10 @@ ##===- clang/lib/StaticAnalyzer/Frontend/Makefile ----------*- Makefile -*-===## -# +# # The LLVM Compiler Infrastructure # # This file is distributed under the University of Illinois Open Source # License. See LICENSE.TXT for details. -# +# ##===----------------------------------------------------------------------===## # # Starting point into the static analyzer land for the driver. |