diff options
author | Dimitry Andric <dim@FreeBSD.org> | 2020-01-17 20:45:01 +0000 |
---|---|---|
committer | Dimitry Andric <dim@FreeBSD.org> | 2020-01-17 20:45:01 +0000 |
commit | 706b4fc47bbc608932d3b491ae19a3b9cde9497b (patch) | |
tree | 4adf86a776049cbf7f69a1929c4babcbbef925eb /clang/lib/StaticAnalyzer | |
parent | 7cc9cf2bf09f069cb2dd947ead05d0b54301fb71 (diff) |
Vendor import of llvm-project master e26a78e70, the last commit beforevendor/llvm-project/llvmorg-10-init-17466-ge26a78e7085
the llvmorg-11-init tag, from which release/10.x was branched.
Diffstat (limited to 'clang/lib/StaticAnalyzer')
42 files changed, 3093 insertions, 1616 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp index d0def6918932..2ef50a727ece 100644 --- a/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/AnalysisOrderChecker.cpp @@ -40,6 +40,7 @@ class AnalysisOrderChecker check::EndFunction, check::NewAllocator, check::Bind, + check::PointerEscape, check::RegionChanges, check::LiveSymbols> { @@ -165,6 +166,15 @@ public: llvm::errs() << "RegionChanges\n"; return State; } + + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const { + if (isCallbackEnabled(State, "PointerEscape")) + llvm::errs() << "PointerEscape\n"; + return State; + } }; } // end anonymous namespace diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 503c451670b8..21c4bbc60264 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -28,6 +28,7 @@ using namespace clang; using namespace ento; namespace { +enum class ConcatFnKind { none = 0, strcat = 1, strlcat = 2 }; class CStringChecker : public Checker< eval::Call, check::PreStmt<DeclStmt>, check::LiveSymbols, @@ -129,11 +130,8 @@ public: void evalStrncpy(CheckerContext &C, const CallExpr *CE) const; void evalStpcpy(CheckerContext &C, const CallExpr *CE) const; void evalStrlcpy(CheckerContext &C, const CallExpr *CE) const; - void evalStrcpyCommon(CheckerContext &C, - const CallExpr *CE, - bool returnEnd, - bool isBounded, - bool isAppending, + void evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, bool ReturnEnd, + bool IsBounded, ConcatFnKind appendK, bool returnPtr = true) const; void evalStrcat(CheckerContext &C, const CallExpr *CE) const; @@ -146,8 +144,8 @@ public: void evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const; void evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, - bool isBounded = false, - bool ignoreCase = false) const; + bool IsBounded = false, + bool IgnoreCase = false) const; void evalStrsep(CheckerContext &C, const CallExpr *CE) const; @@ -292,9 +290,9 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, SmallString<80> buf; llvm::raw_svector_ostream OS(buf); assert(CurrentFunctionDescription); - OS << "Null pointer argument in call to " << CurrentFunctionDescription - << ' ' << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) - << " parameter"; + OS << "Null pointer passed as " << IdxOfArg + << llvm::getOrdinalSuffix(IdxOfArg) << " argument to " + << CurrentFunctionDescription; emitNullArgBug(C, stateNull, S, OS.str()); } @@ -1008,12 +1006,9 @@ ProgramStateRef CStringChecker::InvalidateBuffer(CheckerContext &C, bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR) { - const TypedValueRegion *TVR = dyn_cast<TypedValueRegion>(MR); - switch (MR->getKind()) { case MemRegion::FunctionCodeRegionKind: { - const NamedDecl *FD = cast<FunctionCodeRegion>(MR)->getDecl(); - if (FD) + if (const auto *FD = cast<FunctionCodeRegion>(MR)->getDecl()) os << "the address of the function '" << *FD << '\''; else os << "the address of a function"; @@ -1027,16 +1022,20 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, return true; case MemRegion::CXXThisRegionKind: case MemRegion::CXXTempObjectRegionKind: - os << "a C++ temp object of type " << TVR->getValueType().getAsString(); + os << "a C++ temp object of type " + << cast<TypedValueRegion>(MR)->getValueType().getAsString(); return true; case MemRegion::VarRegionKind: - os << "a variable of type" << TVR->getValueType().getAsString(); + os << "a variable of type" + << cast<TypedValueRegion>(MR)->getValueType().getAsString(); return true; case MemRegion::FieldRegionKind: - os << "a field of type " << TVR->getValueType().getAsString(); + os << "a field of type " + << cast<TypedValueRegion>(MR)->getValueType().getAsString(); return true; case MemRegion::ObjCIvarRegionKind: - os << "an instance variable of type " << TVR->getValueType().getAsString(); + os << "an instance variable of type " + << cast<TypedValueRegion>(MR)->getValueType().getAsString(); return true; default: return false; @@ -1315,9 +1314,9 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { ProgramStateRef StSameBuf, StNotSameBuf; std::tie(StSameBuf, StNotSameBuf) = state->assume(SameBuf); - // If the two arguments might be the same buffer, we know the result is 0, + // If the two arguments are the same buffer, we know the result is 0, // and we only need to check one size. - if (StSameBuf) { + if (StSameBuf && !StNotSameBuf) { state = StSameBuf; state = CheckBufferAccess(C, state, Size, Left); if (state) { @@ -1325,20 +1324,19 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { svalBuilder.makeZeroVal(CE->getType())); C.addTransition(state); } + return; } - // If the two arguments might be different buffers, we have to check the - // size of both of them. - if (StNotSameBuf) { - state = StNotSameBuf; - state = CheckBufferAccess(C, state, Size, Left, Right); - if (state) { - // The return value is the comparison result, which we don't know. - SVal CmpV = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()); - state = state->BindExpr(CE, LCtx, CmpV); - C.addTransition(state); - } + // If the two arguments might be different buffers, we have to check + // the size of both of them. + assert(StNotSameBuf); + state = CheckBufferAccess(C, state, Size, Left, Right); + if (state) { + // The return value is the comparison result, which we don't know. + SVal CmpV = + svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); + state = state->BindExpr(CE, LCtx, CmpV); + C.addTransition(state); } } } @@ -1477,69 +1475,71 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { // char *strcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, - /* returnEnd = */ false, - /* isBounded = */ false, - /* isAppending = */ false); + /* ReturnEnd = */ false, + /* IsBounded = */ false, + /* appendK = */ ConcatFnKind::none); } void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { // char *strncpy(char *restrict dst, const char *restrict src, size_t n); evalStrcpyCommon(C, CE, - /* returnEnd = */ false, - /* isBounded = */ true, - /* isAppending = */ false); + /* ReturnEnd = */ false, + /* IsBounded = */ true, + /* appendK = */ ConcatFnKind::none); } void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { // char *stpcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, - /* returnEnd = */ true, - /* isBounded = */ false, - /* isAppending = */ false); + /* ReturnEnd = */ true, + /* IsBounded = */ false, + /* appendK = */ ConcatFnKind::none); } void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { - // char *strlcpy(char *dst, const char *src, size_t n); + // size_t strlcpy(char *dest, const char *src, size_t size); evalStrcpyCommon(C, CE, - /* returnEnd = */ true, - /* isBounded = */ true, - /* isAppending = */ false, + /* ReturnEnd = */ true, + /* IsBounded = */ true, + /* appendK = */ ConcatFnKind::none, /* returnPtr = */ false); } void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { - //char *strcat(char *restrict s1, const char *restrict s2); + // char *strcat(char *restrict s1, const char *restrict s2); evalStrcpyCommon(C, CE, - /* returnEnd = */ false, - /* isBounded = */ false, - /* isAppending = */ true); + /* ReturnEnd = */ false, + /* IsBounded = */ false, + /* appendK = */ ConcatFnKind::strcat); } void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { //char *strncat(char *restrict s1, const char *restrict s2, size_t n); evalStrcpyCommon(C, CE, - /* returnEnd = */ false, - /* isBounded = */ true, - /* isAppending = */ true); + /* ReturnEnd = */ false, + /* IsBounded = */ true, + /* appendK = */ ConcatFnKind::strcat); } void CStringChecker::evalStrlcat(CheckerContext &C, const CallExpr *CE) const { - // FIXME: strlcat() uses a different rule for bound checking, i.e. 'n' means - // a different thing as compared to strncat(). This currently causes - // false positives in the alpha string bound checker. - - //char *strlcat(char *s1, const char *s2, size_t n); + // size_t strlcat(char *dst, const char *src, size_t size); + // It will append at most size - strlen(dst) - 1 bytes, + // NULL-terminating the result. evalStrcpyCommon(C, CE, - /* returnEnd = */ false, - /* isBounded = */ true, - /* isAppending = */ true, + /* ReturnEnd = */ false, + /* IsBounded = */ true, + /* appendK = */ ConcatFnKind::strlcat, /* returnPtr = */ false); } void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, - bool returnEnd, bool isBounded, - bool isAppending, bool returnPtr) const { - CurrentFunctionDescription = "string copy function"; + bool ReturnEnd, bool IsBounded, + ConcatFnKind appendK, + bool returnPtr) const { + if (appendK == ConcatFnKind::none) + CurrentFunctionDescription = "string copy function"; + else + CurrentFunctionDescription = "string concatenation function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); @@ -1560,6 +1560,11 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // Get the string length of the source. SVal strLength = getCStringLength(C, state, srcExpr, srcVal); + Optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); + + // Get the string length of the destination buffer. + SVal dstStrLength = getCStringLength(C, state, Dst, DstVal); + Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>(); // If the source isn't a valid C string, give up. if (strLength.isUndef()) @@ -1576,13 +1581,14 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, SVal maxLastElementIndex = UnknownVal(); const char *boundWarning = nullptr; - state = CheckOverlap(C, state, isBounded ? CE->getArg(2) : CE->getArg(1), Dst, srcExpr); + state = CheckOverlap(C, state, IsBounded ? CE->getArg(2) : CE->getArg(1), Dst, + srcExpr); if (!state) return; // If the function is strncpy, strncat, etc... it is bounded. - if (isBounded) { + if (IsBounded) { // Get the max number of characters to copy. const Expr *lenExpr = CE->getArg(2); SVal lenVal = state->getSVal(lenExpr, LCtx); @@ -1590,57 +1596,100 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // Protect against misdeclared strncpy(). lenVal = svalBuilder.evalCast(lenVal, sizeTy, lenExpr->getType()); - Optional<NonLoc> strLengthNL = strLength.getAs<NonLoc>(); Optional<NonLoc> lenValNL = lenVal.getAs<NonLoc>(); // If we know both values, we might be able to figure out how much // we're copying. if (strLengthNL && lenValNL) { - ProgramStateRef stateSourceTooLong, stateSourceNotTooLong; + switch (appendK) { + case ConcatFnKind::none: + case ConcatFnKind::strcat: { + ProgramStateRef stateSourceTooLong, stateSourceNotTooLong; + // Check if the max number to copy is less than the length of the src. + // If the bound is equal to the source length, strncpy won't null- + // terminate the result! + std::tie(stateSourceTooLong, stateSourceNotTooLong) = state->assume( + svalBuilder + .evalBinOpNN(state, BO_GE, *strLengthNL, *lenValNL, cmpTy) + .castAs<DefinedOrUnknownSVal>()); + + if (stateSourceTooLong && !stateSourceNotTooLong) { + // Max number to copy is less than the length of the src, so the + // actual strLength copied is the max number arg. + state = stateSourceTooLong; + amountCopied = lenVal; + + } else if (!stateSourceTooLong && stateSourceNotTooLong) { + // The source buffer entirely fits in the bound. + state = stateSourceNotTooLong; + amountCopied = strLength; + } + break; + } + case ConcatFnKind::strlcat: + if (!dstStrLengthNL) + return; - // Check if the max number to copy is less than the length of the src. - // If the bound is equal to the source length, strncpy won't null- - // terminate the result! - std::tie(stateSourceTooLong, stateSourceNotTooLong) = state->assume( - svalBuilder.evalBinOpNN(state, BO_GE, *strLengthNL, *lenValNL, cmpTy) - .castAs<DefinedOrUnknownSVal>()); + // amountCopied = min (size - dstLen - 1 , srcLen) + SVal freeSpace = svalBuilder.evalBinOpNN(state, BO_Sub, *lenValNL, + *dstStrLengthNL, sizeTy); + if (!freeSpace.getAs<NonLoc>()) + return; + freeSpace = + svalBuilder.evalBinOp(state, BO_Sub, freeSpace, + svalBuilder.makeIntVal(1, sizeTy), sizeTy); + Optional<NonLoc> freeSpaceNL = freeSpace.getAs<NonLoc>(); + + // While unlikely, it is possible that the subtraction is + // too complex to compute, let's check whether it succeeded. + if (!freeSpaceNL) + return; + SVal hasEnoughSpace = svalBuilder.evalBinOpNN( + state, BO_LE, *strLengthNL, *freeSpaceNL, cmpTy); + + ProgramStateRef TrueState, FalseState; + std::tie(TrueState, FalseState) = + state->assume(hasEnoughSpace.castAs<DefinedOrUnknownSVal>()); + + // srcStrLength <= size - dstStrLength -1 + if (TrueState && !FalseState) { + amountCopied = strLength; + } - if (stateSourceTooLong && !stateSourceNotTooLong) { - // Max number to copy is less than the length of the src, so the actual - // strLength copied is the max number arg. - state = stateSourceTooLong; - amountCopied = lenVal; + // srcStrLength > size - dstStrLength -1 + if (!TrueState && FalseState) { + amountCopied = freeSpace; + } - } else if (!stateSourceTooLong && stateSourceNotTooLong) { - // The source buffer entirely fits in the bound. - state = stateSourceNotTooLong; - amountCopied = strLength; + if (TrueState && FalseState) + amountCopied = UnknownVal(); + break; } } - // We still want to know if the bound is known to be too large. if (lenValNL) { - if (isAppending) { + switch (appendK) { + case ConcatFnKind::strcat: // For strncat, the check is strlen(dst) + lenVal < sizeof(dst) // Get the string length of the destination. If the destination is // memory that can't have a string length, we shouldn't be copying // into it anyway. - SVal dstStrLength = getCStringLength(C, state, Dst, DstVal); if (dstStrLength.isUndef()) return; - if (Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>()) { - maxLastElementIndex = svalBuilder.evalBinOpNN(state, BO_Add, - *lenValNL, - *dstStrLengthNL, - sizeTy); + if (dstStrLengthNL) { + maxLastElementIndex = svalBuilder.evalBinOpNN( + state, BO_Add, *lenValNL, *dstStrLengthNL, sizeTy); + boundWarning = "Size argument is greater than the free space in the " "destination buffer"; } - - } else { - // For strncpy, this is just checking that lenVal <= sizeof(dst) + break; + case ConcatFnKind::none: + case ConcatFnKind::strlcat: + // For strncpy and strlcat, this is just checking + // that lenVal <= sizeof(dst). // (Yes, strncpy and strncat differ in how they treat termination. // strncat ALWAYS terminates, but strncpy doesn't.) @@ -1649,14 +1698,22 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // as the last element accessed, so n == 0 is problematic. ProgramStateRef StateZeroSize, StateNonZeroSize; std::tie(StateZeroSize, StateNonZeroSize) = - assumeZero(C, state, *lenValNL, sizeTy); + assumeZero(C, state, *lenValNL, sizeTy); // If the size is known to be zero, we're done. if (StateZeroSize && !StateNonZeroSize) { if (returnPtr) { StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, DstVal); } else { - StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, *lenValNL); + if (appendK == ConcatFnKind::none) { + // strlcpy returns strlen(src) + StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, strLength); + } else { + // strlcat returns strlen(src) + strlen(dst) + SVal retSize = svalBuilder.evalBinOp( + state, BO_Add, strLength, dstStrLength, sizeTy); + StateZeroSize = StateZeroSize->BindExpr(CE, LCtx, retSize); + } } C.addTransition(StateZeroSize); return; @@ -1666,50 +1723,13 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // We don't record the non-zero assumption here because we can't // be sure. We won't warn on a possible zero. NonLoc one = svalBuilder.makeIntVal(1, sizeTy).castAs<NonLoc>(); - maxLastElementIndex = svalBuilder.evalBinOpNN(state, BO_Sub, *lenValNL, - one, sizeTy); + maxLastElementIndex = + svalBuilder.evalBinOpNN(state, BO_Sub, *lenValNL, one, sizeTy); boundWarning = "Size argument is greater than the length of the " "destination buffer"; + break; } } - - // If we couldn't pin down the copy length, at least bound it. - // FIXME: We should actually run this code path for append as well, but - // right now it creates problems with constraints (since we can end up - // trying to pass constraints from symbol to symbol). - if (amountCopied.isUnknown() && !isAppending) { - // Try to get a "hypothetical" string length symbol, which we can later - // set as a real value if that turns out to be the case. - amountCopied = getCStringLength(C, state, lenExpr, srcVal, true); - assert(!amountCopied.isUndef()); - - if (Optional<NonLoc> amountCopiedNL = amountCopied.getAs<NonLoc>()) { - if (lenValNL) { - // amountCopied <= lenVal - SVal copiedLessThanBound = svalBuilder.evalBinOpNN(state, BO_LE, - *amountCopiedNL, - *lenValNL, - cmpTy); - state = state->assume( - copiedLessThanBound.castAs<DefinedOrUnknownSVal>(), true); - if (!state) - return; - } - - if (strLengthNL) { - // amountCopied <= strlen(source) - SVal copiedLessThanSrc = svalBuilder.evalBinOpNN(state, BO_LE, - *amountCopiedNL, - *strLengthNL, - cmpTy); - state = state->assume( - copiedLessThanSrc.castAs<DefinedOrUnknownSVal>(), true); - if (!state) - return; - } - } - } - } else { // The function isn't bounded. The amount copied should match the length // of the source buffer. @@ -1722,28 +1742,37 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, // buffer. (It may not actually be the strlen if the destination buffer // is not terminated.) SVal finalStrLength = UnknownVal(); + SVal strlRetVal = UnknownVal(); + + if (appendK == ConcatFnKind::none && !returnPtr) { + // strlcpy returns the sizeof(src) + strlRetVal = strLength; + } // If this is an appending function (strcat, strncat...) then set the // string length to strlen(src) + strlen(dst) since the buffer will // ultimately contain both. - if (isAppending) { + if (appendK != ConcatFnKind::none) { // Get the string length of the destination. If the destination is memory // that can't have a string length, we shouldn't be copying into it anyway. - SVal dstStrLength = getCStringLength(C, state, Dst, DstVal); if (dstStrLength.isUndef()) return; - Optional<NonLoc> srcStrLengthNL = amountCopied.getAs<NonLoc>(); - Optional<NonLoc> dstStrLengthNL = dstStrLength.getAs<NonLoc>(); + if (appendK == ConcatFnKind::strlcat && dstStrLengthNL && strLengthNL) { + strlRetVal = svalBuilder.evalBinOpNN(state, BO_Add, *strLengthNL, + *dstStrLengthNL, sizeTy); + } + + Optional<NonLoc> amountCopiedNL = amountCopied.getAs<NonLoc>(); // If we know both string lengths, we might know the final string length. - if (srcStrLengthNL && dstStrLengthNL) { + if (amountCopiedNL && dstStrLengthNL) { // Make sure the two lengths together don't overflow a size_t. - state = checkAdditionOverflow(C, state, *srcStrLengthNL, *dstStrLengthNL); + state = checkAdditionOverflow(C, state, *amountCopiedNL, *dstStrLengthNL); if (!state) return; - finalStrLength = svalBuilder.evalBinOpNN(state, BO_Add, *srcStrLengthNL, + finalStrLength = svalBuilder.evalBinOpNN(state, BO_Add, *amountCopiedNL, *dstStrLengthNL, sizeTy); } @@ -1756,19 +1785,19 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, assert(!finalStrLength.isUndef()); if (Optional<NonLoc> finalStrLengthNL = finalStrLength.getAs<NonLoc>()) { - if (srcStrLengthNL) { + if (amountCopiedNL && appendK == ConcatFnKind::none) { + // we overwrite dst string with the src // finalStrLength >= srcStrLength - SVal sourceInResult = svalBuilder.evalBinOpNN(state, BO_GE, - *finalStrLengthNL, - *srcStrLengthNL, - cmpTy); + SVal sourceInResult = svalBuilder.evalBinOpNN( + state, BO_GE, *finalStrLengthNL, *amountCopiedNL, cmpTy); state = state->assume(sourceInResult.castAs<DefinedOrUnknownSVal>(), true); if (!state) return; } - if (dstStrLengthNL) { + if (dstStrLengthNL && appendK != ConcatFnKind::none) { + // we extend the dst string with the src // finalStrLength >= dstStrLength SVal destInResult = svalBuilder.evalBinOpNN(state, BO_GE, *finalStrLengthNL, @@ -1793,9 +1822,13 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (returnPtr) { // The final result of the function will either be a pointer past the last // copied element, or a pointer to the start of the destination buffer. - Result = (returnEnd ? UnknownVal() : DstVal); + Result = (ReturnEnd ? UnknownVal() : DstVal); } else { - Result = finalStrLength; + if (appendK == ConcatFnKind::strlcat || appendK == ConcatFnKind::none) + //strlcpy, strlcat + Result = strlRetVal; + else + Result = finalStrLength; } assert(state); @@ -1834,7 +1867,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, } // If this is a stpcpy-style copy, the last element is the return value. - if (returnPtr && returnEnd) + if (returnPtr && ReturnEnd) Result = lastElement; } @@ -1854,7 +1887,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, nullptr); // Set the C string length of the destination, if we know it. - if (isBounded && !isAppending) { + if (IsBounded && (appendK == ConcatFnKind::none)) { // strncpy is annoying in that it doesn't guarantee to null-terminate // the result string. If the original string didn't fit entirely inside // the bound (including the null-terminator), we don't know how long the @@ -1870,7 +1903,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, if (returnPtr) { // If this is a stpcpy-style copy, but we were unable to check for a buffer // overflow, we still need a result. Conjure a return value. - if (returnEnd && Result.isUnknown()) { + if (ReturnEnd && Result.isUnknown()) { Result = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()); } } @@ -1881,28 +1914,28 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, void CStringChecker::evalStrcmp(CheckerContext &C, const CallExpr *CE) const { //int strcmp(const char *s1, const char *s2); - evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ false); + evalStrcmpCommon(C, CE, /* IsBounded = */ false, /* IgnoreCase = */ false); } void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { //int strncmp(const char *s1, const char *s2, size_t n); - evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ false); + evalStrcmpCommon(C, CE, /* IsBounded = */ true, /* IgnoreCase = */ false); } void CStringChecker::evalStrcasecmp(CheckerContext &C, const CallExpr *CE) const { //int strcasecmp(const char *s1, const char *s2); - evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ true); + evalStrcmpCommon(C, CE, /* IsBounded = */ false, /* IgnoreCase = */ true); } void CStringChecker::evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const { //int strncasecmp(const char *s1, const char *s2, size_t n); - evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ true); + evalStrcmpCommon(C, CE, /* IsBounded = */ true, /* IgnoreCase = */ true); } void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, - bool isBounded, bool ignoreCase) const { + bool IsBounded, bool IgnoreCase) const { CurrentFunctionDescription = "string comparison function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); @@ -1972,7 +2005,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, StringRef s1StrRef = s1StrLiteral->getString(); StringRef s2StrRef = s2StrLiteral->getString(); - if (isBounded) { + if (IsBounded) { // Get the max number of characters to compare. const Expr *lenExpr = CE->getArg(2); SVal lenVal = state->getSVal(lenExpr, LCtx); @@ -2000,7 +2033,7 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, s2StrRef = s2StrRef.substr(0, s2Term); // Use StringRef's comparison methods to compute the actual result. - int compareRes = ignoreCase ? s1StrRef.compare_lower(s2StrRef) + int compareRes = IgnoreCase ? s1StrRef.compare_lower(s2StrRef) : s1StrRef.compare(s2StrRef); // The strcmp function returns an integer greater than, equal to, or less @@ -2180,7 +2213,7 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().IntTy); ProgramStateRef State = C.getState(); - + // See if the size argument is zero. SVal SizeVal = C.getSVal(Size); QualType SizeTy = Size->getType(); diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp new file mode 100644 index 000000000000..48fee4a0ffb7 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp @@ -0,0 +1,121 @@ +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/Support/FormatVariadic.h" + +using namespace clang; +using namespace ento; + +namespace { +class PlacementNewChecker : public Checker<check::PreStmt<CXXNewExpr>> { +public: + void checkPreStmt(const CXXNewExpr *NE, CheckerContext &C) const; + +private: + // Returns the size of the target in a placement new expression. + // E.g. in "new (&s) long" it returns the size of `long`. + SVal getExtentSizeOfNewTarget(const CXXNewExpr *NE, ProgramStateRef State, + CheckerContext &C) const; + // Returns the size of the place in a placement new expression. + // E.g. in "new (&s) long" it returns the size of `s`. + SVal getExtentSizeOfPlace(const Expr *NE, ProgramStateRef State, + CheckerContext &C) const; + BugType BT{this, "Insufficient storage for placement new", + categories::MemoryError}; +}; +} // namespace + +SVal PlacementNewChecker::getExtentSizeOfPlace(const Expr *Place, + ProgramStateRef State, + CheckerContext &C) const { + const MemRegion *MRegion = C.getSVal(Place).getAsRegion(); + if (!MRegion) + return UnknownVal(); + RegionOffset Offset = MRegion->getAsOffset(); + if (Offset.hasSymbolicOffset()) + return UnknownVal(); + const MemRegion *BaseRegion = MRegion->getBaseRegion(); + if (!BaseRegion) + return UnknownVal(); + + SValBuilder &SvalBuilder = C.getSValBuilder(); + NonLoc OffsetInBytes = SvalBuilder.makeArrayIndex( + Offset.getOffset() / C.getASTContext().getCharWidth()); + DefinedOrUnknownSVal ExtentInBytes = + BaseRegion->castAs<SubRegion>()->getExtent(SvalBuilder); + + return SvalBuilder.evalBinOp(State, BinaryOperator::Opcode::BO_Sub, + ExtentInBytes, OffsetInBytes, + SvalBuilder.getArrayIndexType()); +} + +SVal PlacementNewChecker::getExtentSizeOfNewTarget(const CXXNewExpr *NE, + ProgramStateRef State, + CheckerContext &C) const { + SValBuilder &SvalBuilder = C.getSValBuilder(); + QualType ElementType = NE->getAllocatedType(); + ASTContext &AstContext = C.getASTContext(); + CharUnits TypeSize = AstContext.getTypeSizeInChars(ElementType); + if (NE->isArray()) { + const Expr *SizeExpr = *NE->getArraySize(); + SVal ElementCount = C.getSVal(SizeExpr); + if (auto ElementCountNL = ElementCount.getAs<NonLoc>()) { + // size in Bytes = ElementCountNL * TypeSize + return SvalBuilder.evalBinOp( + State, BO_Mul, *ElementCountNL, + SvalBuilder.makeArrayIndex(TypeSize.getQuantity()), + SvalBuilder.getArrayIndexType()); + } + } else { + // Create a concrete int whose size in bits and signedness is equal to + // ArrayIndexType. + llvm::APInt I(AstContext.getTypeSizeInChars(SvalBuilder.getArrayIndexType()) + .getQuantity() * + C.getASTContext().getCharWidth(), + TypeSize.getQuantity()); + return SvalBuilder.makeArrayIndex(I.getZExtValue()); + } + return UnknownVal(); +} + +void PlacementNewChecker::checkPreStmt(const CXXNewExpr *NE, + CheckerContext &C) const { + // Check only the default placement new. + if (!NE->getOperatorNew()->isReservedGlobalPlacementOperator()) + return; + if (NE->getNumPlacementArgs() == 0) + return; + + ProgramStateRef State = C.getState(); + SVal SizeOfTarget = getExtentSizeOfNewTarget(NE, State, C); + const Expr *Place = NE->getPlacementArg(0); + SVal SizeOfPlace = getExtentSizeOfPlace(Place, State, C); + const auto SizeOfTargetCI = SizeOfTarget.getAs<nonloc::ConcreteInt>(); + if (!SizeOfTargetCI) + return; + const auto SizeOfPlaceCI = SizeOfPlace.getAs<nonloc::ConcreteInt>(); + if (!SizeOfPlaceCI) + return; + + if (SizeOfPlaceCI->getValue() < SizeOfTargetCI->getValue()) { + if (ExplodedNode *N = C.generateErrorNode(State)) { + std::string Msg = + llvm::formatv("Storage provided to placement new is only {0} bytes, " + "whereas the allocated type requires {1} bytes", + SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()); + + auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N); + bugreporter::trackExpressionValue(N, Place, *R); + C.emitReport(std::move(R)); + return; + } + } +} + +void ento::registerPlacementNewChecker(CheckerManager &mgr) { + mgr.registerChecker<PlacementNewChecker>(); +} + +bool ento::shouldRegisterPlacementNewChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 260a2896e78c..d9ffa562c0aa 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -49,6 +49,7 @@ struct ChecksFilter { DefaultBool check_vfork; DefaultBool check_FloatLoopCounter; DefaultBool check_UncheckedReturn; + DefaultBool check_decodeValueOfObjCType; CheckerNameRef checkName_bcmp; CheckerNameRef checkName_bcopy; @@ -63,6 +64,7 @@ struct ChecksFilter { CheckerNameRef checkName_vfork; CheckerNameRef checkName_FloatLoopCounter; CheckerNameRef checkName_UncheckedReturn; + CheckerNameRef checkName_decodeValueOfObjCType; }; class WalkAST : public StmtVisitor<WalkAST> { @@ -83,6 +85,7 @@ public: // Statement visitor methods. void VisitCallExpr(CallExpr *CE); + void VisitObjCMessageExpr(ObjCMessageExpr *CE); void VisitForStmt(ForStmt *S); void VisitCompoundStmt (CompoundStmt *S); void VisitStmt(Stmt *S) { VisitChildren(S); } @@ -93,6 +96,7 @@ public: bool checkCall_strCommon(const CallExpr *CE, const FunctionDecl *FD); typedef void (WalkAST::*FnCheck)(const CallExpr *, const FunctionDecl *); + typedef void (WalkAST::*MsgCheck)(const ObjCMessageExpr *); // Checker-specific methods. void checkLoopConditionForFloat(const ForStmt *FS); @@ -110,6 +114,7 @@ public: void checkCall_rand(const CallExpr *CE, const FunctionDecl *FD); void checkCall_random(const CallExpr *CE, const FunctionDecl *FD); void checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD); + void checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME); void checkUncheckedReturnValue(CallExpr *CE); }; } // end anonymous namespace @@ -182,6 +187,20 @@ void WalkAST::VisitCallExpr(CallExpr *CE) { VisitChildren(CE); } +void WalkAST::VisitObjCMessageExpr(ObjCMessageExpr *ME) { + MsgCheck evalFunction = + llvm::StringSwitch<MsgCheck>(ME->getSelector().getAsString()) + .Case("decodeValueOfObjCType:at:", + &WalkAST::checkMsg_decodeValueOfObjCType) + .Default(nullptr); + + if (evalFunction) + (this->*evalFunction)(ME); + + // Recurse and check children. + VisitChildren(ME); +} + void WalkAST::VisitCompoundStmt(CompoundStmt *S) { for (Stmt *Child : S->children()) if (Child) { @@ -924,6 +943,54 @@ void WalkAST::checkCall_vfork(const CallExpr *CE, const FunctionDecl *FD) { } //===----------------------------------------------------------------------===// +// Check: '-decodeValueOfObjCType:at:' should not be used. +// It is deprecated in favor of '-decodeValueOfObjCType:at:size:' due to +// likelihood of buffer overflows. +//===----------------------------------------------------------------------===// + +void WalkAST::checkMsg_decodeValueOfObjCType(const ObjCMessageExpr *ME) { + if (!filter.check_decodeValueOfObjCType) + return; + + // Check availability of the secure alternative: + // iOS 11+, macOS 10.13+, tvOS 11+, and watchOS 4.0+ + // FIXME: We probably shouldn't register the check if it's not available. + const TargetInfo &TI = AC->getASTContext().getTargetInfo(); + const llvm::Triple &T = TI.getTriple(); + const VersionTuple &VT = TI.getPlatformMinVersion(); + switch (T.getOS()) { + case llvm::Triple::IOS: + if (VT < VersionTuple(11, 0)) + return; + break; + case llvm::Triple::MacOSX: + if (VT < VersionTuple(10, 13)) + return; + break; + case llvm::Triple::WatchOS: + if (VT < VersionTuple(4, 0)) + return; + break; + case llvm::Triple::TvOS: + if (VT < VersionTuple(11, 0)) + return; + break; + default: + return; + } + + PathDiagnosticLocation MELoc = + PathDiagnosticLocation::createBegin(ME, BR.getSourceManager(), AC); + BR.EmitBasicReport( + AC->getDecl(), filter.checkName_decodeValueOfObjCType, + "Potential buffer overflow in '-decodeValueOfObjCType:at:'", "Security", + "Deprecated method '-decodeValueOfObjCType:at:' is insecure " + "as it can lead to potential buffer overflows. Use the safer " + "'-decodeValueOfObjCType:at:size:' method.", + MELoc, ME->getSourceRange()); +} + +//===----------------------------------------------------------------------===// // Check: Should check whether privileges are dropped successfully. // Originally: <rdar://problem/6337132> //===----------------------------------------------------------------------===// @@ -1035,3 +1102,4 @@ REGISTER_CHECKER(vfork) REGISTER_CHECKER(FloatLoopCounter) REGISTER_CHECKER(UncheckedReturn) REGISTER_CHECKER(DeprecatedOrUnsafeBufferHandling) +REGISTER_CHECKER(decodeValueOfObjCType) diff --git a/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp new file mode 100644 index 000000000000..4717fef96341 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/DebugIteratorModeling.cpp @@ -0,0 +1,196 @@ +//===-- DebugIteratorModeling.cpp ---------------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for debugging iterator modeling. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + +#include "Iterator.h" + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class DebugIteratorModeling + : public Checker<eval::Call> { + + std::unique_ptr<BugType> DebugMsgBugType; + + template <typename Getter> + void analyzerContainerDataField(const CallExpr *CE, CheckerContext &C, + Getter get) const; + void analyzerContainerBegin(const CallExpr *CE, CheckerContext &C) const; + void analyzerContainerEnd(const CallExpr *CE, CheckerContext &C) const; + template <typename Getter> + void analyzerIteratorDataField(const CallExpr *CE, CheckerContext &C, + Getter get, SVal Default) const; + void analyzerIteratorPosition(const CallExpr *CE, CheckerContext &C) const; + void analyzerIteratorContainer(const CallExpr *CE, CheckerContext &C) const; + void analyzerIteratorValidity(const CallExpr *CE, CheckerContext &C) const; + ExplodedNode *reportDebugMsg(llvm::StringRef Msg, CheckerContext &C) const; + + typedef void (DebugIteratorModeling::*FnCheck)(const CallExpr *, + CheckerContext &) const; + + CallDescriptionMap<FnCheck> Callbacks = { + {{0, "clang_analyzer_container_begin", 1}, + &DebugIteratorModeling::analyzerContainerBegin}, + {{0, "clang_analyzer_container_end", 1}, + &DebugIteratorModeling::analyzerContainerEnd}, + {{0, "clang_analyzer_iterator_position", 1}, + &DebugIteratorModeling::analyzerIteratorPosition}, + {{0, "clang_analyzer_iterator_container", 1}, + &DebugIteratorModeling::analyzerIteratorContainer}, + {{0, "clang_analyzer_iterator_validity", 1}, + &DebugIteratorModeling::analyzerIteratorValidity}, + }; + +public: + DebugIteratorModeling(); + + bool evalCall(const CallEvent &Call, CheckerContext &C) const; +}; + +} //namespace + +DebugIteratorModeling::DebugIteratorModeling() { + DebugMsgBugType.reset( + new BugType(this, "Checking analyzer assumptions", "debug", + /*SuppressOnSink=*/true)); +} + +bool DebugIteratorModeling::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return false; + + const FnCheck *Handler = Callbacks.lookup(Call); + if (!Handler) + return false; + + (this->**Handler)(CE, C); + return true; +} + +template <typename Getter> +void DebugIteratorModeling::analyzerContainerDataField(const CallExpr *CE, + CheckerContext &C, + Getter get) const { + if (CE->getNumArgs() == 0) { + reportDebugMsg("Missing container argument", C); + return; + } + + auto State = C.getState(); + const MemRegion *Cont = C.getSVal(CE->getArg(0)).getAsRegion(); + if (Cont) { + const auto *Data = getContainerData(State, Cont); + if (Data) { + SymbolRef Field = get(Data); + if (Field) { + State = State->BindExpr(CE, C.getLocationContext(), + nonloc::SymbolVal(Field)); + C.addTransition(State); + return; + } + } + } + + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + State = State->BindExpr(CE, C.getLocationContext(), + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void DebugIteratorModeling::analyzerContainerBegin(const CallExpr *CE, + CheckerContext &C) const { + analyzerContainerDataField(CE, C, [](const ContainerData *D) { + return D->getBegin(); + }); +} + +void DebugIteratorModeling::analyzerContainerEnd(const CallExpr *CE, + CheckerContext &C) const { + analyzerContainerDataField(CE, C, [](const ContainerData *D) { + return D->getEnd(); + }); +} + +template <typename Getter> +void DebugIteratorModeling::analyzerIteratorDataField(const CallExpr *CE, + CheckerContext &C, + Getter get, + SVal Default) const { + if (CE->getNumArgs() == 0) { + reportDebugMsg("Missing iterator argument", C); + return; + } + + auto State = C.getState(); + SVal V = C.getSVal(CE->getArg(0)); + const auto *Pos = getIteratorPosition(State, V); + if (Pos) { + State = State->BindExpr(CE, C.getLocationContext(), get(Pos)); + } else { + State = State->BindExpr(CE, C.getLocationContext(), Default); + } + C.addTransition(State); +} + +void DebugIteratorModeling::analyzerIteratorPosition(const CallExpr *CE, + CheckerContext &C) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + analyzerIteratorDataField(CE, C, [](const IteratorPosition *P) { + return nonloc::SymbolVal(P->getOffset()); + }, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void DebugIteratorModeling::analyzerIteratorContainer(const CallExpr *CE, + CheckerContext &C) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + analyzerIteratorDataField(CE, C, [](const IteratorPosition *P) { + return loc::MemRegionVal(P->getContainer()); + }, loc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +void DebugIteratorModeling::analyzerIteratorValidity(const CallExpr *CE, + CheckerContext &C) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + analyzerIteratorDataField(CE, C, [&BVF](const IteratorPosition *P) { + return + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get((P->isValid())))); + }, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0)))); +} + +ExplodedNode *DebugIteratorModeling::reportDebugMsg(llvm::StringRef Msg, + CheckerContext &C) const { + ExplodedNode *N = C.generateNonFatalErrorNode(); + if (!N) + return nullptr; + + auto &BR = C.getBugReporter(); + BR.emitReport(std::make_unique<PathSensitiveBugReport>(*DebugMsgBugType, + Msg, N)); + return N; +} + +void ento::registerDebugIteratorModeling(CheckerManager &mgr) { + mgr.registerChecker<DebugIteratorModeling>(); +} + +bool ento::shouldRegisterDebugIteratorModeling(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index e3de0b4f4a7f..46100cd1dace 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -107,7 +107,7 @@ static const Expr *getDereferenceExpr(const Stmt *S, bool IsBind=false){ static bool suppressReport(const Expr *E) { // Do not report dereferences on memory in non-default address spaces. - return E->getType().getQualifiers().hasAddressSpace(); + return E->getType().hasAddressSpace(); } static bool isDeclRefExprToReference(const Expr *E) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp b/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp index 0058f3d3881f..0c46447e1985 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DirectIvarAssignment.cpp @@ -144,6 +144,8 @@ void DirectIvarAssignment::checkASTDecl(const ObjCImplementationDecl *D, continue; const Stmt *Body = M->getBody(); + if (M->isSynthesizedAccessorStub()) + continue; assert(Body); MethodCrawler MC(IvarToPropMap, M->getCanonicalDecl(), InterD, BR, this, diff --git a/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp new file mode 100644 index 000000000000..3c04983df443 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/FuchsiaHandleChecker.cpp @@ -0,0 +1,557 @@ +//=== FuchsiaHandleChecker.cpp - Find handle leaks/double closes -*- C++ -*--=// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This checker checks if the handle of Fuchsia is properly used according to +// following rules. +// - If a handle is acquired, it should be released before execution +// ends. +// - If a handle is released, it should not be released again. +// - If a handle is released, it should not be used for other purposes +// such as I/O. +// +// In this checker, each tracked handle is associated with a state. When the +// handle variable is passed to different function calls or syscalls, its state +// changes. The state changes can be generally represented by following ASCII +// Art: +// +// +// +-+---------v-+ +------------+ +// acquire_func succeeded | | Escape | | +// +-----------------> Allocated +---------> Escaped <--+ +// | | | | | | +// | +-----+------++ +------------+ | +// | | | | +// | release_func | +--+ | +// | | | handle +--------+ | +// | | | dies | | | +// | +----v-----+ +---------> Leaked | | +// | | | |(REPORT)| | +// +----------+--+ | Released | Escape +--------+ | +// | | | +---------------------------+ +// | Not tracked <--+ +----+---+-+ +// | | | | | As argument by value +// +------+------+ | release_func | +------+ in function call +// | | | | or by reference in +// | | | | use_func call +// +---------+ +----v-----+ | +-----------+ +// acquire_func failed | Double | +-----> Use after | +// | released | | released | +// | (REPORT) | | (REPORT) | +// +----------+ +-----------+ +// +// acquire_func represents the functions or syscalls that may acquire a handle. +// release_func represents the functions or syscalls that may release a handle. +// use_func represents the functions or syscall that requires an open handle. +// +// If a tracked handle dies in "Released" or "Not Tracked" state, we assume it +// is properly used. Otherwise a bug and will be reported. +// +// Note that, the analyzer does not always know for sure if a function failed +// or succeeded. In those cases we use the state MaybeAllocated. +// Thus, the diagramm above captures the intent, not implementation details. +// +// Due to the fact that the number of handle related syscalls in Fuchsia +// is large, we adopt the annotation attributes to descript syscalls' +// operations(acquire/release/use) on handles instead of hardcoding +// everything in the checker. +// +// We use following annotation attributes for handle related syscalls or +// functions: +// 1. __attribute__((acquire_handle("Fuchsia"))) |handle will be acquired +// 2. __attribute__((release_handle("Fuchsia"))) |handle will be released +// 3. __attribute__((use_handle("Fuchsia"))) |handle will not transit to +// escaped state, it also needs to be open. +// +// For example, an annotated syscall: +// zx_status_t zx_channel_create( +// uint32_t options, +// zx_handle_t* out0 __attribute__((acquire_handle("Fuchsia"))) , +// zx_handle_t* out1 __attribute__((acquire_handle("Fuchsia")))); +// denotes a syscall which will acquire two handles and save them to 'out0' and +// 'out1' when succeeded. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/Attr.h" +#include "clang/AST/Decl.h" +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ConstraintManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" + +using namespace clang; +using namespace ento; + +namespace { + +static const StringRef HandleTypeName = "zx_handle_t"; +static const StringRef ErrorTypeName = "zx_status_t"; + +class HandleState { +private: + enum class Kind { MaybeAllocated, Allocated, Released, Escaped } K; + SymbolRef ErrorSym; + HandleState(Kind K, SymbolRef ErrorSym) : K(K), ErrorSym(ErrorSym) {} + +public: + bool operator==(const HandleState &Other) const { + return K == Other.K && ErrorSym == Other.ErrorSym; + } + bool isAllocated() const { return K == Kind::Allocated; } + bool maybeAllocated() const { return K == Kind::MaybeAllocated; } + bool isReleased() const { return K == Kind::Released; } + bool isEscaped() const { return K == Kind::Escaped; } + + static HandleState getMaybeAllocated(SymbolRef ErrorSym) { + return HandleState(Kind::MaybeAllocated, ErrorSym); + } + static HandleState getAllocated(ProgramStateRef State, HandleState S) { + assert(S.maybeAllocated()); + assert(State->getConstraintManager() + .isNull(State, S.getErrorSym()) + .isConstrained()); + return HandleState(Kind::Allocated, nullptr); + } + static HandleState getReleased() { + return HandleState(Kind::Released, nullptr); + } + static HandleState getEscaped() { + return HandleState(Kind::Escaped, nullptr); + } + + SymbolRef getErrorSym() const { return ErrorSym; } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(static_cast<int>(K)); + ID.AddPointer(ErrorSym); + } + + LLVM_DUMP_METHOD void dump(raw_ostream &OS) const { + switch (K) { +#define CASE(ID) \ + case ID: \ + OS << #ID; \ + break; + CASE(Kind::MaybeAllocated) + CASE(Kind::Allocated) + CASE(Kind::Released) + CASE(Kind::Escaped) + } + } + + LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } +}; + +template <typename Attr> static bool hasFuchsiaAttr(const Decl *D) { + return D->hasAttr<Attr>() && D->getAttr<Attr>()->getHandleType() == "Fuchsia"; +} + +class FuchsiaHandleChecker + : public Checker<check::PostCall, check::PreCall, check::DeadSymbols, + check::PointerEscape, eval::Assume> { + BugType LeakBugType{this, "Fuchsia handle leak", "Fuchsia Handle Error", + /*SuppressOnSink=*/true}; + BugType DoubleReleaseBugType{this, "Fuchsia handle double release", + "Fuchsia Handle Error"}; + BugType UseAfterReleaseBugType{this, "Fuchsia handle use after release", + "Fuchsia Handle Error"}; + +public: + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; + ProgramStateRef evalAssume(ProgramStateRef State, SVal Cond, + bool Assumption) const; + ProgramStateRef checkPointerEscape(ProgramStateRef State, + const InvalidatedSymbols &Escaped, + const CallEvent *Call, + PointerEscapeKind Kind) const; + + ExplodedNode *reportLeaks(ArrayRef<SymbolRef> LeakedHandles, + CheckerContext &C, ExplodedNode *Pred) const; + + void reportDoubleRelease(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &C) const; + + void reportUseAfterFree(SymbolRef HandleSym, const SourceRange &Range, + CheckerContext &C) const; + + void reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, CheckerContext &C, + const SourceRange *Range, const BugType &Type, + StringRef Msg) const; + + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; +}; +} // end anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(HStateMap, SymbolRef, HandleState) + +static const ExplodedNode *getAcquireSite(const ExplodedNode *N, SymbolRef Sym, + CheckerContext &Ctx) { + ProgramStateRef State = N->getState(); + // When bug type is handle leak, exploded node N does not have state info for + // leaking handle. Get the predecessor of N instead. + if (!State->get<HStateMap>(Sym)) + N = N->getFirstPred(); + + const ExplodedNode *Pred = N; + while (N) { + State = N->getState(); + if (!State->get<HStateMap>(Sym)) { + const HandleState *HState = Pred->getState()->get<HStateMap>(Sym); + if (HState && (HState->isAllocated() || HState->maybeAllocated())) + return N; + } + Pred = N; + N = N->getFirstPred(); + } + return nullptr; +} + +/// Returns the symbols extracted from the argument or null if it cannot be +/// found. +static SymbolRef getFuchsiaHandleSymbol(QualType QT, SVal Arg, + ProgramStateRef State) { + int PtrToHandleLevel = 0; + while (QT->isAnyPointerType() || QT->isReferenceType()) { + ++PtrToHandleLevel; + QT = QT->getPointeeType(); + } + if (const auto *HandleType = QT->getAs<TypedefType>()) { + if (HandleType->getDecl()->getName() != HandleTypeName) + return nullptr; + if (PtrToHandleLevel > 1) { + // Not supported yet. + return nullptr; + } + + if (PtrToHandleLevel == 0) { + return Arg.getAsSymbol(); + } else { + assert(PtrToHandleLevel == 1); + if (Optional<Loc> ArgLoc = Arg.getAs<Loc>()) + return State->getSVal(*ArgLoc).getAsSymbol(); + } + } + return nullptr; +} + +void FuchsiaHandleChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FuncDecl) { + // Unknown call, escape by value handles. They are not covered by + // PointerEscape callback. + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (SymbolRef Handle = Call.getArgSVal(Arg).getAsSymbol()) + State = State->set<HStateMap>(Handle, HandleState::getEscaped()); + } + C.addTransition(State); + return; + } + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!Handle) + continue; + + // Handled in checkPostCall. + if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD) || + hasFuchsiaAttr<AcquireHandleAttr>(PVD)) + continue; + + const HandleState *HState = State->get<HStateMap>(Handle); + if (!HState || HState->isEscaped()) + continue; + + if (hasFuchsiaAttr<UseHandleAttr>(PVD) || PVD->getType()->isIntegerType()) { + if (HState->isReleased()) { + reportUseAfterFree(Handle, Call.getArgSourceRange(Arg), C); + return; + } + } + if (!hasFuchsiaAttr<UseHandleAttr>(PVD) && + PVD->getType()->isIntegerType()) { + // Working around integer by-value escapes. + State = State->set<HStateMap>(Handle, HandleState::getEscaped()); + } + } + C.addTransition(State); +} + +void FuchsiaHandleChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const FunctionDecl *FuncDecl = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!FuncDecl) + return; + + ProgramStateRef State = C.getState(); + + std::vector<std::function<std::string(BugReport & BR)>> Notes; + SymbolRef ResultSymbol = nullptr; + if (const auto *TypeDefTy = FuncDecl->getReturnType()->getAs<TypedefType>()) + if (TypeDefTy->getDecl()->getName() == ErrorTypeName) + ResultSymbol = Call.getReturnValue().getAsSymbol(); + + // Function returns an open handle. + if (hasFuchsiaAttr<AcquireHandleAttr>(FuncDecl)) { + SymbolRef RetSym = Call.getReturnValue().getAsSymbol(); + State = + State->set<HStateMap>(RetSym, HandleState::getMaybeAllocated(nullptr)); + } + + for (unsigned Arg = 0; Arg < Call.getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call.getArgSVal(Arg), State); + if (!Handle) + continue; + + const HandleState *HState = State->get<HStateMap>(Handle); + if (HState && HState->isEscaped()) + continue; + if (hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) { + if (HState && HState->isReleased()) { + reportDoubleRelease(Handle, Call.getArgSourceRange(Arg), C); + return; + } else { + Notes.push_back([Handle](BugReport &BR) { + auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); + if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { + return "Handle released here."; + } else + return ""; + }); + State = State->set<HStateMap>(Handle, HandleState::getReleased()); + } + } else if (hasFuchsiaAttr<AcquireHandleAttr>(PVD)) { + Notes.push_back([Handle](BugReport &BR) { + auto *PathBR = static_cast<PathSensitiveBugReport *>(&BR); + if (auto IsInteresting = PathBR->getInterestingnessKind(Handle)) { + return "Handle allocated here."; + } else + return ""; + }); + State = State->set<HStateMap>( + Handle, HandleState::getMaybeAllocated(ResultSymbol)); + } + } + const NoteTag *T = nullptr; + if (!Notes.empty()) { + T = C.getNoteTag( + [this, Notes{std::move(Notes)}](BugReport &BR) -> std::string { + if (&BR.getBugType() != &UseAfterReleaseBugType && + &BR.getBugType() != &LeakBugType && + &BR.getBugType() != &DoubleReleaseBugType) + return ""; + for (auto &Note : Notes) { + std::string Text = Note(BR); + if (!Text.empty()) + return Text; + } + return ""; + }); + } + C.addTransition(State, T); +} + +void FuchsiaHandleChecker::checkDeadSymbols(SymbolReaper &SymReaper, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + SmallVector<SymbolRef, 2> LeakedSyms; + HStateMapTy TrackedHandles = State->get<HStateMap>(); + for (auto &CurItem : TrackedHandles) { + if (!SymReaper.isDead(CurItem.first)) + continue; + if (CurItem.second.isAllocated() || CurItem.second.maybeAllocated()) + LeakedSyms.push_back(CurItem.first); + State = State->remove<HStateMap>(CurItem.first); + } + + ExplodedNode *N = C.getPredecessor(); + if (!LeakedSyms.empty()) + N = reportLeaks(LeakedSyms, C, N); + + C.addTransition(State, N); +} + +// Acquiring a handle is not always successful. In Fuchsia most functions +// return a status code that determines the status of the handle. +// When we split the path based on this status code we know that on one +// path we do have the handle and on the other path the acquire failed. +// This method helps avoiding false positive leak warnings on paths where +// the function failed. +// Moreover, when a handle is known to be zero (the invalid handle), +// we no longer can follow the symbol on the path, becaue the constant +// zero will be used instead of the symbol. We also do not need to release +// an invalid handle, so we remove the corresponding symbol from the state. +ProgramStateRef FuchsiaHandleChecker::evalAssume(ProgramStateRef State, + SVal Cond, + bool Assumption) const { + // TODO: add notes about successes/fails for APIs. + ConstraintManager &Cmr = State->getConstraintManager(); + HStateMapTy TrackedHandles = State->get<HStateMap>(); + for (auto &CurItem : TrackedHandles) { + ConditionTruthVal HandleVal = Cmr.isNull(State, CurItem.first); + if (HandleVal.isConstrainedTrue()) { + // The handle is invalid. We can no longer follow the symbol on this path. + State = State->remove<HStateMap>(CurItem.first); + } + SymbolRef ErrorSym = CurItem.second.getErrorSym(); + if (!ErrorSym) + continue; + ConditionTruthVal ErrorVal = Cmr.isNull(State, ErrorSym); + if (ErrorVal.isConstrainedTrue()) { + // Allocation succeeded. + if (CurItem.second.maybeAllocated()) + State = State->set<HStateMap>( + CurItem.first, HandleState::getAllocated(State, CurItem.second)); + } else if (ErrorVal.isConstrainedFalse()) { + // Allocation failed. + if (CurItem.second.maybeAllocated()) + State = State->remove<HStateMap>(CurItem.first); + } + } + return State; +} + +ProgramStateRef FuchsiaHandleChecker::checkPointerEscape( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind) const { + const FunctionDecl *FuncDecl = + Call ? dyn_cast_or_null<FunctionDecl>(Call->getDecl()) : nullptr; + + llvm::DenseSet<SymbolRef> UnEscaped; + // Not all calls should escape our symbols. + if (FuncDecl && + (Kind == PSK_DirectEscapeOnCall || Kind == PSK_IndirectEscapeOnCall || + Kind == PSK_EscapeOutParameters)) { + for (unsigned Arg = 0; Arg < Call->getNumArgs(); ++Arg) { + if (Arg >= FuncDecl->getNumParams()) + break; + const ParmVarDecl *PVD = FuncDecl->getParamDecl(Arg); + SymbolRef Handle = + getFuchsiaHandleSymbol(PVD->getType(), Call->getArgSVal(Arg), State); + if (!Handle) + continue; + if (hasFuchsiaAttr<UseHandleAttr>(PVD) || + hasFuchsiaAttr<ReleaseHandleAttr>(PVD)) + UnEscaped.insert(Handle); + } + } + + // For out params, we have to deal with derived symbols. See + // MacOSKeychainAPIChecker for details. + for (auto I : State->get<HStateMap>()) { + if (Escaped.count(I.first) && !UnEscaped.count(I.first)) + State = State->set<HStateMap>(I.first, HandleState::getEscaped()); + if (const auto *SD = dyn_cast<SymbolDerived>(I.first)) { + auto ParentSym = SD->getParentSymbol(); + if (Escaped.count(ParentSym)) + State = State->set<HStateMap>(I.first, HandleState::getEscaped()); + } + } + + return State; +} + +ExplodedNode * +FuchsiaHandleChecker::reportLeaks(ArrayRef<SymbolRef> LeakedHandles, + CheckerContext &C, ExplodedNode *Pred) const { + ExplodedNode *ErrNode = C.generateNonFatalErrorNode(C.getState(), Pred); + for (SymbolRef LeakedHandle : LeakedHandles) { + reportBug(LeakedHandle, ErrNode, C, nullptr, LeakBugType, + "Potential leak of handle"); + } + return ErrNode; +} + +void FuchsiaHandleChecker::reportDoubleRelease(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(C.getState()); + reportBug(HandleSym, ErrNode, C, &Range, DoubleReleaseBugType, + "Releasing a previously released handle"); +} + +void FuchsiaHandleChecker::reportUseAfterFree(SymbolRef HandleSym, + const SourceRange &Range, + CheckerContext &C) const { + ExplodedNode *ErrNode = C.generateErrorNode(C.getState()); + reportBug(HandleSym, ErrNode, C, &Range, UseAfterReleaseBugType, + "Using a previously released handle"); +} + +void FuchsiaHandleChecker::reportBug(SymbolRef Sym, ExplodedNode *ErrorNode, + CheckerContext &C, + const SourceRange *Range, + const BugType &Type, StringRef Msg) const { + if (!ErrorNode) + return; + + std::unique_ptr<PathSensitiveBugReport> R; + if (Type.isSuppressOnSink()) { + const ExplodedNode *AcquireNode = getAcquireSite(ErrorNode, Sym, C); + if (AcquireNode) { + PathDiagnosticLocation LocUsedForUniqueing = + PathDiagnosticLocation::createBegin( + AcquireNode->getStmtForDiagnostics(), C.getSourceManager(), + AcquireNode->getLocationContext()); + + R = std::make_unique<PathSensitiveBugReport>( + Type, Msg, ErrorNode, LocUsedForUniqueing, + AcquireNode->getLocationContext()->getDecl()); + } + } + if (!R) + R = std::make_unique<PathSensitiveBugReport>(Type, Msg, ErrorNode); + if (Range) + R->addRange(*Range); + R->markInteresting(Sym); + C.emitReport(std::move(R)); +} + +void ento::registerFuchsiaHandleChecker(CheckerManager &mgr) { + mgr.registerChecker<FuchsiaHandleChecker>(); +} + +bool ento::shouldRegisterFuchsiaHandleChecker(const LangOptions &LO) { + return true; +} + +void FuchsiaHandleChecker::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { + + HStateMapTy StateMap = State->get<HStateMap>(); + + if (!StateMap.isEmpty()) { + Out << Sep << "FuchsiaHandleChecker :" << NL; + for (HStateMapTy::iterator I = StateMap.begin(), E = StateMap.end(); I != E; + ++I) { + I.getKey()->dumpToStream(Out); + Out << " : "; + I.getData().dump(Out); + Out << NL; + } + } +} diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index d442b26b3959..302d5bb1bea8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -24,9 +24,10 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "llvm/ADT/StringMap.h" #include "llvm/Support/YAMLTraits.h" +#include <algorithm> #include <limits> +#include <unordered_map> #include <utility> using namespace clang; @@ -56,10 +57,11 @@ public: /// Used to parse the configuration file. struct TaintConfiguration { - using NameArgsPair = std::pair<std::string, ArgVector>; + using NameScopeArgs = std::tuple<std::string, std::string, ArgVector>; struct Propagation { std::string Name; + std::string Scope; ArgVector SrcArgs; SignedArgVector DstArgs; VariadicType VarType; @@ -67,8 +69,8 @@ public: }; std::vector<Propagation> Propagations; - std::vector<NameArgsPair> Filters; - std::vector<NameArgsPair> Sinks; + std::vector<NameScopeArgs> Filters; + std::vector<NameScopeArgs> Sinks; TaintConfiguration() = default; TaintConfiguration(const TaintConfiguration &) = default; @@ -97,14 +99,52 @@ private: BT.reset(new BugType(this, "Use of Untrusted Data", "Untrusted Data")); } + struct FunctionData { + FunctionData() = delete; + FunctionData(const FunctionData &) = default; + FunctionData(FunctionData &&) = default; + FunctionData &operator=(const FunctionData &) = delete; + FunctionData &operator=(FunctionData &&) = delete; + + static Optional<FunctionData> create(const CallExpr *CE, + const CheckerContext &C) { + const FunctionDecl *FDecl = C.getCalleeDecl(CE); + if (!FDecl || (FDecl->getKind() != Decl::Function && + FDecl->getKind() != Decl::CXXMethod)) + return None; + + StringRef Name = C.getCalleeName(FDecl); + std::string FullName = FDecl->getQualifiedNameAsString(); + if (Name.empty() || FullName.empty()) + return None; + + return FunctionData{FDecl, Name, FullName}; + } + + bool isInScope(StringRef Scope) const { + return StringRef(FullName).startswith(Scope); + } + + const FunctionDecl *const FDecl; + const StringRef Name; + const std::string FullName; + }; + /// Catch taint related bugs. Check if tainted data is passed to a - /// system call etc. - bool checkPre(const CallExpr *CE, CheckerContext &C) const; + /// system call etc. Returns true on matching. + bool checkPre(const CallExpr *CE, const FunctionData &FData, + CheckerContext &C) const; + + /// Add taint sources on a pre-visit. Returns true on matching. + bool addSourcesPre(const CallExpr *CE, const FunctionData &FData, + CheckerContext &C) const; - /// Add taint sources on a pre-visit. - void addSourcesPre(const CallExpr *CE, CheckerContext &C) const; + /// Mark filter's arguments not tainted on a pre-visit. Returns true on + /// matching. + bool addFiltersPre(const CallExpr *CE, const FunctionData &FData, + CheckerContext &C) const; - /// Propagate taint generated at pre-visit. + /// Propagate taint generated at pre-visit. Returns true on matching. bool propagateFromPre(const CallExpr *CE, CheckerContext &C) const; /// Check if the region the expression evaluates to is the standard input, @@ -142,7 +182,7 @@ private: /// Check if tainted data is used as a custom sink's parameter. static constexpr llvm::StringLiteral MsgCustomSink = "Untrusted data is passed to a user-defined sink"; - bool checkCustomSinks(const CallExpr *CE, StringRef Name, + bool checkCustomSinks(const CallExpr *CE, const FunctionData &FData, CheckerContext &C) const; /// Generate a report if the expression is tainted or points to tainted data. @@ -150,8 +190,17 @@ private: CheckerContext &C) const; struct TaintPropagationRule; - using NameRuleMap = llvm::StringMap<TaintPropagationRule>; - using NameArgMap = llvm::StringMap<ArgVector>; + template <typename T> + using ConfigDataMap = + std::unordered_multimap<std::string, std::pair<std::string, T>>; + using NameRuleMap = ConfigDataMap<TaintPropagationRule>; + using NameArgMap = ConfigDataMap<ArgVector>; + + /// Find a function with the given name and scope. Returns the first match + /// or the end of the map. + template <typename T> + static auto findFunctionInConfig(const ConfigDataMap<T> &Map, + const FunctionData &FData); /// A struct used to specify taint propagation rules for a function. /// @@ -193,8 +242,7 @@ private: /// Get the propagation rule for a given function. static TaintPropagationRule getTaintPropagationRule(const NameRuleMap &CustomPropagations, - const FunctionDecl *FDecl, StringRef Name, - CheckerContext &C); + const FunctionData &FData, CheckerContext &C); void addSrcArg(unsigned A) { SrcArgs.push_back(A); } void addDstArg(unsigned A) { DstArgs.push_back(A); } @@ -229,14 +277,15 @@ private: CheckerContext &C); }; - /// Defines a map between the propagation function's name and - /// TaintPropagationRule. + /// Defines a map between the propagation function's name, scope + /// and TaintPropagationRule. NameRuleMap CustomPropagations; - /// Defines a map between the filter function's name and filtering args. + /// Defines a map between the filter function's name, scope and filtering + /// args. NameArgMap CustomFilters; - /// Defines a map between the sink function's name and sinking args. + /// Defines a map between the sink function's name, scope and sinking args. NameArgMap CustomSinks; }; @@ -253,7 +302,7 @@ constexpr llvm::StringLiteral GenericTaintChecker::MsgCustomSink; using TaintConfig = GenericTaintChecker::TaintConfiguration; LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::Propagation) -LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::NameArgsPair) +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::NameScopeArgs) namespace llvm { namespace yaml { @@ -268,6 +317,7 @@ template <> struct MappingTraits<TaintConfig> { template <> struct MappingTraits<TaintConfig::Propagation> { static void mapping(IO &IO, TaintConfig::Propagation &Propagation) { IO.mapRequired("Name", Propagation.Name); + IO.mapOptional("Scope", Propagation.Scope); IO.mapOptional("SrcArgs", Propagation.SrcArgs); IO.mapOptional("DstArgs", Propagation.DstArgs); IO.mapOptional("VariadicType", Propagation.VarType, @@ -285,10 +335,11 @@ template <> struct ScalarEnumerationTraits<GenericTaintChecker::VariadicType> { } }; -template <> struct MappingTraits<TaintConfig::NameArgsPair> { - static void mapping(IO &IO, TaintConfig::NameArgsPair &NameArg) { - IO.mapRequired("Name", NameArg.first); - IO.mapRequired("Args", NameArg.second); +template <> struct MappingTraits<TaintConfig::NameScopeArgs> { + static void mapping(IO &IO, TaintConfig::NameScopeArgs &NSA) { + IO.mapRequired("Name", std::get<0>(NSA)); + IO.mapOptional("Scope", std::get<1>(NSA)); + IO.mapRequired("Args", std::get<2>(NSA)); } }; } // namespace yaml @@ -321,31 +372,51 @@ void GenericTaintChecker::parseConfiguration(CheckerManager &Mgr, const std::string &Option, TaintConfiguration &&Config) { for (auto &P : Config.Propagations) { - GenericTaintChecker::CustomPropagations.try_emplace( - P.Name, std::move(P.SrcArgs), - convertToArgVector(Mgr, Option, P.DstArgs), P.VarType, P.VarIndex); + GenericTaintChecker::CustomPropagations.emplace( + P.Name, + std::make_pair(P.Scope, TaintPropagationRule{ + std::move(P.SrcArgs), + convertToArgVector(Mgr, Option, P.DstArgs), + P.VarType, P.VarIndex})); } for (auto &F : Config.Filters) { - GenericTaintChecker::CustomFilters.try_emplace(F.first, - std::move(F.second)); + GenericTaintChecker::CustomFilters.emplace( + std::get<0>(F), + std::make_pair(std::move(std::get<1>(F)), std::move(std::get<2>(F)))); } for (auto &S : Config.Sinks) { - GenericTaintChecker::CustomSinks.try_emplace(S.first, std::move(S.second)); + GenericTaintChecker::CustomSinks.emplace( + std::get<0>(S), + std::make_pair(std::move(std::get<1>(S)), std::move(std::get<2>(S)))); } } +template <typename T> +auto GenericTaintChecker::findFunctionInConfig(const ConfigDataMap<T> &Map, + const FunctionData &FData) { + auto Range = Map.equal_range(FData.Name); + auto It = + std::find_if(Range.first, Range.second, [&FData](const auto &Entry) { + const auto &Value = Entry.second; + StringRef Scope = Value.first; + return Scope.empty() || FData.isInScope(Scope); + }); + return It != Range.second ? It : Map.end(); +} + GenericTaintChecker::TaintPropagationRule GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( - const NameRuleMap &CustomPropagations, const FunctionDecl *FDecl, - StringRef Name, CheckerContext &C) { + const NameRuleMap &CustomPropagations, const FunctionData &FData, + CheckerContext &C) { // TODO: Currently, we might lose precision here: we always mark a return // value as tainted even if it's just a pointer, pointing to tainted data. // Check for exact name match for functions without builtin substitutes. + // Use qualified name, because these are C functions without namespace. TaintPropagationRule Rule = - llvm::StringSwitch<TaintPropagationRule>(Name) + llvm::StringSwitch<TaintPropagationRule>(FData.FullName) // Source functions // TODO: Add support for vfscanf & family. .Case("fdopen", TaintPropagationRule({}, {ReturnValueIndex})) @@ -390,6 +461,7 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( // Check if it's one of the memory setting/copying functions. // This check is specialized but faster then calling isCLibraryFunction. + const FunctionDecl *FDecl = FData.FDecl; unsigned BId = 0; if ((BId = FDecl->getMemoryFunctionKind())) switch (BId) { @@ -433,23 +505,32 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( // or smart memory copy: // - memccpy - copying until hitting a special character. - auto It = CustomPropagations.find(Name); - if (It != CustomPropagations.end()) - return It->getValue(); + auto It = findFunctionInConfig(CustomPropagations, FData); + if (It != CustomPropagations.end()) { + const auto &Value = It->second; + return Value.second; + } return TaintPropagationRule(); } void GenericTaintChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { + Optional<FunctionData> FData = FunctionData::create(CE, C); + if (!FData) + return; + // Check for taintedness related errors first: system call, uncontrolled // format string, tainted buffer size. - if (checkPre(CE, C)) + if (checkPre(CE, *FData, C)) return; // Marks the function's arguments and/or return value tainted if it present in // the list. - addSourcesPre(CE, C); + if (addSourcesPre(CE, *FData, C)) + return; + + addFiltersPre(CE, *FData, C); } void GenericTaintChecker::checkPostStmt(const CallExpr *CE, @@ -464,31 +545,47 @@ void GenericTaintChecker::printState(raw_ostream &Out, ProgramStateRef State, printTaint(State, Out, NL, Sep); } -void GenericTaintChecker::addSourcesPre(const CallExpr *CE, +bool GenericTaintChecker::addSourcesPre(const CallExpr *CE, + const FunctionData &FData, CheckerContext &C) const { - ProgramStateRef State = nullptr; - const FunctionDecl *FDecl = C.getCalleeDecl(CE); - if (!FDecl || FDecl->getKind() != Decl::Function) - return; - - StringRef Name = C.getCalleeName(FDecl); - if (Name.empty()) - return; - // First, try generating a propagation rule for this function. TaintPropagationRule Rule = TaintPropagationRule::getTaintPropagationRule( - this->CustomPropagations, FDecl, Name, C); + this->CustomPropagations, FData, C); if (!Rule.isNull()) { - State = Rule.process(CE, C); - if (!State) - return; - C.addTransition(State); - return; + ProgramStateRef State = Rule.process(CE, C); + if (State) { + C.addTransition(State); + return true; + } + } + return false; +} + +bool GenericTaintChecker::addFiltersPre(const CallExpr *CE, + const FunctionData &FData, + CheckerContext &C) const { + auto It = findFunctionInConfig(CustomFilters, FData); + if (It == CustomFilters.end()) + return false; + + ProgramStateRef State = C.getState(); + const auto &Value = It->second; + const ArgVector &Args = Value.second; + for (unsigned ArgNum : Args) { + if (ArgNum >= CE->getNumArgs()) + continue; + + const Expr *Arg = CE->getArg(ArgNum); + Optional<SVal> V = getPointedToSVal(C, Arg); + if (V) + State = removeTaint(State, *V); } - if (!State) - return; - C.addTransition(State); + if (State != C.getState()) { + C.addTransition(State); + return true; + } + return false; } bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, @@ -530,26 +627,19 @@ bool GenericTaintChecker::propagateFromPre(const CallExpr *CE, } bool GenericTaintChecker::checkPre(const CallExpr *CE, + const FunctionData &FData, CheckerContext &C) const { if (checkUncontrolledFormatString(CE, C)) return true; - const FunctionDecl *FDecl = C.getCalleeDecl(CE); - if (!FDecl || FDecl->getKind() != Decl::Function) - return false; - - StringRef Name = C.getCalleeName(FDecl); - if (Name.empty()) - return false; - - if (checkSystemCall(CE, Name, C)) + if (checkSystemCall(CE, FData.Name, C)) return true; - if (checkTaintedBufferSize(CE, FDecl, C)) + if (checkTaintedBufferSize(CE, FData.FDecl, C)) return true; - if (checkCustomSinks(CE, Name, C)) + if (checkCustomSinks(CE, FData, C)) return true; return false; @@ -568,7 +658,7 @@ Optional<SVal> GenericTaintChecker::getPointedToSVal(CheckerContext &C, QualType ArgTy = Arg->getType().getCanonicalType(); if (!ArgTy->isPointerType()) - return None; + return State->getSVal(*AddrLoc); QualType ValTy = ArgTy->getPointeeType(); @@ -821,13 +911,15 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C); } -bool GenericTaintChecker::checkCustomSinks(const CallExpr *CE, StringRef Name, +bool GenericTaintChecker::checkCustomSinks(const CallExpr *CE, + const FunctionData &FData, CheckerContext &C) const { - auto It = CustomSinks.find(Name); + auto It = findFunctionInConfig(CustomSinks, FData); if (It == CustomSinks.end()) return false; - const GenericTaintChecker::ArgVector &Args = It->getValue(); + const auto &Value = It->second; + const GenericTaintChecker::ArgVector &Args = Value.second; for (unsigned ArgNum : Args) { if (ArgNum >= CE->getNumArgs()) continue; diff --git a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index b0d101c88517..dd89c53478e8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -62,7 +62,7 @@ public: // lookup by region. bool isSymbolTracked(ProgramStateRef State, SymbolRef Sym) { RawPtrMapTy Map = State->get<RawPtrMap>(); - for (const auto Entry : Map) { + for (const auto &Entry : Map) { if (Entry.second.contains(Sym)) return true; } @@ -236,7 +236,7 @@ void InnerPointerChecker::checkDeadSymbols(SymbolReaper &SymReaper, ProgramStateRef State = C.getState(); PtrSet::Factory &F = State->getStateManager().get_context<PtrSet>(); RawPtrMapTy RPM = State->get<RawPtrMap>(); - for (const auto Entry : RPM) { + for (const auto &Entry : RPM) { if (!SymReaper.isLiveRegion(Entry.first)) { // Due to incomplete destructor support, some dead regions might // remain in the program state map. Clean them up. @@ -266,7 +266,7 @@ std::unique_ptr<BugReporterVisitor> getInnerPointerBRVisitor(SymbolRef Sym) { const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { RawPtrMapTy Map = State->get<RawPtrMap>(); - for (const auto Entry : Map) { + for (const auto &Entry : Map) { if (Entry.second.contains(Sym)) { return Entry.first; } diff --git a/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp new file mode 100644 index 000000000000..d1a9a7df071d --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/InvalidatedIteratorChecker.cpp @@ -0,0 +1,95 @@ +//===-- InvalidatedIteratorChecker.cpp ----------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for access of invalidated iterators. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + + +#include "Iterator.h" + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class InvalidatedIteratorChecker + : public Checker<check::PreCall> { + + std::unique_ptr<BugType> InvalidatedBugType; + + void verifyAccess(CheckerContext &C, const SVal &Val) const; + void reportBug(const StringRef &Message, const SVal &Val, + CheckerContext &C, ExplodedNode *ErrNode) const; +public: + InvalidatedIteratorChecker(); + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +}; + +} //namespace + +InvalidatedIteratorChecker::InvalidatedIteratorChecker() { + InvalidatedBugType.reset( + new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); +} + +void InvalidatedIteratorChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + // Check for access of invalidated position + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!Func) + return; + + if (Func->isOverloadedOperator() && + isAccessOperator(Func->getOverloadedOperator())) { + // Check for any kind of access of invalidated iterator positions + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyAccess(C, InstCall->getCXXThisVal()); + } else { + verifyAccess(C, Call.getArgSVal(0)); + } + } +} + +void InvalidatedIteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos && !Pos->isValid()) { + auto *N = C.generateErrorNode(State); + if (!N) { + return; + } + reportBug("Invalidated iterator accessed.", Val, C, N); + } +} + +void InvalidatedIteratorChecker::reportBug(const StringRef &Message, + const SVal &Val, CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = std::make_unique<PathSensitiveBugReport>(*InvalidatedBugType, + Message, ErrNode); + R->markInteresting(Val); + C.emitReport(std::move(R)); +} + +void ento::registerInvalidatedIteratorChecker(CheckerManager &mgr) { + mgr.registerChecker<InvalidatedIteratorChecker>(); +} + +bool ento::shouldRegisterInvalidatedIteratorChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp b/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp new file mode 100644 index 000000000000..6bca5515724c --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/Iterator.cpp @@ -0,0 +1,227 @@ +//=== Iterator.cpp - Common functions for iterator checkers. -------*- C++ -*-// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines common functions to be used by the itertor checkers . +// +//===----------------------------------------------------------------------===// + +#include "Iterator.h" + +namespace clang { +namespace ento { +namespace iterator { + +bool isIteratorType(const QualType &Type) { + if (Type->isPointerType()) + return true; + + const auto *CRD = Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); + return isIterator(CRD); +} + +bool isIterator(const CXXRecordDecl *CRD) { + if (!CRD) + return false; + + const auto Name = CRD->getName(); + if (!(Name.endswith_lower("iterator") || Name.endswith_lower("iter") || + Name.endswith_lower("it"))) + return false; + + bool HasCopyCtor = false, HasCopyAssign = true, HasDtor = false, + HasPreIncrOp = false, HasPostIncrOp = false, HasDerefOp = false; + for (const auto *Method : CRD->methods()) { + if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Method)) { + if (Ctor->isCopyConstructor()) { + HasCopyCtor = !Ctor->isDeleted() && Ctor->getAccess() == AS_public; + } + continue; + } + if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(Method)) { + HasDtor = !Dtor->isDeleted() && Dtor->getAccess() == AS_public; + continue; + } + if (Method->isCopyAssignmentOperator()) { + HasCopyAssign = !Method->isDeleted() && Method->getAccess() == AS_public; + continue; + } + if (!Method->isOverloadedOperator()) + continue; + const auto OPK = Method->getOverloadedOperator(); + if (OPK == OO_PlusPlus) { + HasPreIncrOp = HasPreIncrOp || (Method->getNumParams() == 0); + HasPostIncrOp = HasPostIncrOp || (Method->getNumParams() == 1); + continue; + } + if (OPK == OO_Star) { + HasDerefOp = (Method->getNumParams() == 0); + continue; + } + } + + return HasCopyCtor && HasCopyAssign && HasDtor && HasPreIncrOp && + HasPostIncrOp && HasDerefOp; +} + +bool isComparisonOperator(OverloadedOperatorKind OK) { + return OK == OO_EqualEqual || OK == OO_ExclaimEqual || OK == OO_Less || + OK == OO_LessEqual || OK == OO_Greater || OK == OO_GreaterEqual; +} + +bool isInsertCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 2 || Func->getNumParams() > 3) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + return IdInfo->getName() == "insert"; +} + +bool isEmplaceCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 2) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + return IdInfo->getName() == "emplace"; +} + +bool isEraseCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 1 || Func->getNumParams() > 2) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + if (Func->getNumParams() == 2 && + !isIteratorType(Func->getParamDecl(1)->getType())) + return false; + return IdInfo->getName() == "erase"; +} + +bool isEraseAfterCall(const FunctionDecl *Func) { + const auto *IdInfo = Func->getIdentifier(); + if (!IdInfo) + return false; + if (Func->getNumParams() < 1 || Func->getNumParams() > 2) + return false; + if (!isIteratorType(Func->getParamDecl(0)->getType())) + return false; + if (Func->getNumParams() == 2 && + !isIteratorType(Func->getParamDecl(1)->getType())) + return false; + return IdInfo->getName() == "erase_after"; +} + +bool isAccessOperator(OverloadedOperatorKind OK) { + return isDereferenceOperator(OK) || isIncrementOperator(OK) || + isDecrementOperator(OK) || isRandomIncrOrDecrOperator(OK); +} + +bool isDereferenceOperator(OverloadedOperatorKind OK) { + return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || + OK == OO_Subscript; +} + +bool isIncrementOperator(OverloadedOperatorKind OK) { + return OK == OO_PlusPlus; +} + +bool isDecrementOperator(OverloadedOperatorKind OK) { + return OK == OO_MinusMinus; +} + +bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK) { + return OK == OO_Plus || OK == OO_PlusEqual || OK == OO_Minus || + OK == OO_MinusEqual; +} + +const ContainerData *getContainerData(ProgramStateRef State, + const MemRegion *Cont) { + return State->get<ContainerMap>(Cont); +} + +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + const SVal &Val) { + if (auto Reg = Val.getAsRegion()) { + Reg = Reg->getMostDerivedObjectRegion(); + return State->get<IteratorRegionMap>(Reg); + } else if (const auto Sym = Val.getAsSymbol()) { + return State->get<IteratorSymbolMap>(Sym); + } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { + return State->get<IteratorRegionMap>(LCVal->getRegion()); + } + return nullptr; +} + +ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, + const IteratorPosition &Pos) { + if (auto Reg = Val.getAsRegion()) { + Reg = Reg->getMostDerivedObjectRegion(); + return State->set<IteratorRegionMap>(Reg, Pos); + } else if (const auto Sym = Val.getAsSymbol()) { + return State->set<IteratorSymbolMap>(Sym, Pos); + } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { + return State->set<IteratorRegionMap>(LCVal->getRegion(), Pos); + } + return nullptr; +} + +ProgramStateRef advancePosition(ProgramStateRef State, const SVal &Iter, + OverloadedOperatorKind Op, + const SVal &Distance) { + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return nullptr; + + auto &SymMgr = State->getStateManager().getSymbolManager(); + auto &SVB = State->getStateManager().getSValBuilder(); + + assert ((Op == OO_Plus || Op == OO_PlusEqual || + Op == OO_Minus || Op == OO_MinusEqual) && + "Advance operator must be one of +, -, += and -=."); + auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; + if (const auto IntDist = Distance.getAs<nonloc::ConcreteInt>()) { + // For concrete integers we can calculate the new position + const auto NewPos = + Pos->setTo(SVB.evalBinOp(State, BinOp, + nonloc::SymbolVal(Pos->getOffset()), + *IntDist, SymMgr.getType(Pos->getOffset())) + .getAsSymbol()); + return setIteratorPosition(State, Iter, NewPos); + } + + return nullptr; +} + +bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, + BinaryOperator::Opcode Opc) { + return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc); +} + +bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, + BinaryOperator::Opcode Opc) { + auto &SVB = State->getStateManager().getSValBuilder(); + + const auto comparison = + SVB.evalBinOp(State, Opc, NL1, NL2, SVB.getConditionType()); + + assert(comparison.getAs<DefinedSVal>() && + "Symbol comparison must be a `DefinedSVal`"); + + return !State->assume(comparison.castAs<DefinedSVal>(), false); +} + +} // namespace iterator +} // namespace ento +} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Checkers/Iterator.h b/clang/lib/StaticAnalyzer/Checkers/Iterator.h new file mode 100644 index 000000000000..c10d86691693 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/Iterator.h @@ -0,0 +1,175 @@ +//=== Iterator.h - Common functions for iterator checkers. ---------*- C++ -*-// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines common functions to be used by the itertor checkers . +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ITERATOR_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_ITERATOR_H + +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" + +namespace clang { +namespace ento { +namespace iterator { + +// Abstract position of an iterator. This helps to handle all three kinds +// of operators in a common way by using a symbolic position. +struct IteratorPosition { +private: + + // Container the iterator belongs to + const MemRegion *Cont; + + // Whether iterator is valid + const bool Valid; + + // Abstract offset + const SymbolRef Offset; + + IteratorPosition(const MemRegion *C, bool V, SymbolRef Of) + : Cont(C), Valid(V), Offset(Of) {} + +public: + const MemRegion *getContainer() const { return Cont; } + bool isValid() const { return Valid; } + SymbolRef getOffset() const { return Offset; } + + IteratorPosition invalidate() const { + return IteratorPosition(Cont, false, Offset); + } + + static IteratorPosition getPosition(const MemRegion *C, SymbolRef Of) { + return IteratorPosition(C, true, Of); + } + + IteratorPosition setTo(SymbolRef NewOf) const { + return IteratorPosition(Cont, Valid, NewOf); + } + + IteratorPosition reAssign(const MemRegion *NewCont) const { + return IteratorPosition(NewCont, Valid, Offset); + } + + bool operator==(const IteratorPosition &X) const { + return Cont == X.Cont && Valid == X.Valid && Offset == X.Offset; + } + + bool operator!=(const IteratorPosition &X) const { + return Cont != X.Cont || Valid != X.Valid || Offset != X.Offset; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddPointer(Cont); + ID.AddInteger(Valid); + ID.Add(Offset); + } +}; + +// Structure to record the symbolic begin and end position of a container +struct ContainerData { +private: + const SymbolRef Begin, End; + + ContainerData(SymbolRef B, SymbolRef E) : Begin(B), End(E) {} + +public: + static ContainerData fromBegin(SymbolRef B) { + return ContainerData(B, nullptr); + } + + static ContainerData fromEnd(SymbolRef E) { + return ContainerData(nullptr, E); + } + + SymbolRef getBegin() const { return Begin; } + SymbolRef getEnd() const { return End; } + + ContainerData newBegin(SymbolRef B) const { return ContainerData(B, End); } + + ContainerData newEnd(SymbolRef E) const { return ContainerData(Begin, E); } + + bool operator==(const ContainerData &X) const { + return Begin == X.Begin && End == X.End; + } + + bool operator!=(const ContainerData &X) const { + return Begin != X.Begin || End != X.End; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.Add(Begin); + ID.Add(End); + } +}; + +class IteratorSymbolMap {}; +class IteratorRegionMap {}; +class ContainerMap {}; + +using IteratorSymbolMapTy = CLANG_ENTO_PROGRAMSTATE_MAP(SymbolRef, IteratorPosition); +using IteratorRegionMapTy = CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, IteratorPosition); +using ContainerMapTy = CLANG_ENTO_PROGRAMSTATE_MAP(const MemRegion *, ContainerData); + +} // namespace iterator + +template<> +struct ProgramStateTrait<iterator::IteratorSymbolMap> + : public ProgramStatePartialTrait<iterator::IteratorSymbolMapTy> { + static void *GDMIndex() { static int Index; return &Index; } +}; + +template<> +struct ProgramStateTrait<iterator::IteratorRegionMap> + : public ProgramStatePartialTrait<iterator::IteratorRegionMapTy> { + static void *GDMIndex() { static int Index; return &Index; } +}; + +template<> +struct ProgramStateTrait<iterator::ContainerMap> + : public ProgramStatePartialTrait<iterator::ContainerMapTy> { + static void *GDMIndex() { static int Index; return &Index; } +}; + +namespace iterator { + +bool isIteratorType(const QualType &Type); +bool isIterator(const CXXRecordDecl *CRD); +bool isComparisonOperator(OverloadedOperatorKind OK); +bool isInsertCall(const FunctionDecl *Func); +bool isEraseCall(const FunctionDecl *Func); +bool isEraseAfterCall(const FunctionDecl *Func); +bool isEmplaceCall(const FunctionDecl *Func); +bool isAccessOperator(OverloadedOperatorKind OK); +bool isDereferenceOperator(OverloadedOperatorKind OK); +bool isIncrementOperator(OverloadedOperatorKind OK); +bool isDecrementOperator(OverloadedOperatorKind OK); +bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK); +const ContainerData *getContainerData(ProgramStateRef State, + const MemRegion *Cont); +const IteratorPosition *getIteratorPosition(ProgramStateRef State, + const SVal &Val); +ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, + const IteratorPosition &Pos); +ProgramStateRef advancePosition(ProgramStateRef State, + const SVal &Iter, + OverloadedOperatorKind Op, + const SVal &Distance); +bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, + BinaryOperator::Opcode Opc); +bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, + BinaryOperator::Opcode Opc); + +} // namespace iterator +} // namespace ento +} // namespace clang + +#endif diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp index 97ace68569ef..eb962a2ffd9e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorModeling.cpp @@ -1,4 +1,4 @@ -//===-- IteratorChecker.cpp ---------------------------------------*- C++ -*--// +//===-- IteratorModeling.cpp --------------------------------------*- C++ -*--// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. @@ -73,127 +73,33 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "Iterator.h" + #include <utility> using namespace clang; using namespace ento; +using namespace iterator; namespace { -// Abstract position of an iterator. This helps to handle all three kinds -// of operators in a common way by using a symbolic position. -struct IteratorPosition { -private: - - // Container the iterator belongs to - const MemRegion *Cont; - - // Whether iterator is valid - const bool Valid; - - // Abstract offset - const SymbolRef Offset; - - IteratorPosition(const MemRegion *C, bool V, SymbolRef Of) - : Cont(C), Valid(V), Offset(Of) {} - -public: - const MemRegion *getContainer() const { return Cont; } - bool isValid() const { return Valid; } - SymbolRef getOffset() const { return Offset; } - - IteratorPosition invalidate() const { - return IteratorPosition(Cont, false, Offset); - } - - static IteratorPosition getPosition(const MemRegion *C, SymbolRef Of) { - return IteratorPosition(C, true, Of); - } - - IteratorPosition setTo(SymbolRef NewOf) const { - return IteratorPosition(Cont, Valid, NewOf); - } - - IteratorPosition reAssign(const MemRegion *NewCont) const { - return IteratorPosition(NewCont, Valid, Offset); - } - - bool operator==(const IteratorPosition &X) const { - return Cont == X.Cont && Valid == X.Valid && Offset == X.Offset; - } - - bool operator!=(const IteratorPosition &X) const { - return Cont != X.Cont || Valid != X.Valid || Offset != X.Offset; - } - - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddPointer(Cont); - ID.AddInteger(Valid); - ID.Add(Offset); - } -}; - -// Structure to record the symbolic begin and end position of a container -struct ContainerData { -private: - const SymbolRef Begin, End; - - ContainerData(SymbolRef B, SymbolRef E) : Begin(B), End(E) {} - -public: - static ContainerData fromBegin(SymbolRef B) { - return ContainerData(B, nullptr); - } - - static ContainerData fromEnd(SymbolRef E) { - return ContainerData(nullptr, E); - } - - SymbolRef getBegin() const { return Begin; } - SymbolRef getEnd() const { return End; } +class IteratorModeling + : public Checker<check::PostCall, check::PostStmt<MaterializeTemporaryExpr>, + check::Bind, check::LiveSymbols, check::DeadSymbols> { - ContainerData newBegin(SymbolRef B) const { return ContainerData(B, End); } - - ContainerData newEnd(SymbolRef E) const { return ContainerData(Begin, E); } - - bool operator==(const ContainerData &X) const { - return Begin == X.Begin && End == X.End; - } - - bool operator!=(const ContainerData &X) const { - return Begin != X.Begin || End != X.End; - } - - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.Add(Begin); - ID.Add(End); - } -}; - -class IteratorChecker - : public Checker<check::PreCall, check::PostCall, - check::PostStmt<MaterializeTemporaryExpr>, check::Bind, - check::LiveSymbols, check::DeadSymbols> { - - std::unique_ptr<BugType> OutOfRangeBugType; - std::unique_ptr<BugType> MismatchedBugType; - std::unique_ptr<BugType> InvalidatedBugType; - - void handleComparison(CheckerContext &C, const Expr *CE, const SVal &RetVal, + void handleComparison(CheckerContext &C, const Expr *CE, SVal RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const; void processComparison(CheckerContext &C, ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, const SVal &RetVal, OverloadedOperatorKind Op) const; - void verifyAccess(CheckerContext &C, const SVal &Val) const; - void verifyDereference(CheckerContext &C, const SVal &Val) const; void handleIncrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, bool Postfix) const; void handleDecrement(CheckerContext &C, const SVal &RetVal, const SVal &Iter, bool Postfix) const; - void handleRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, - const SVal &RetVal, const SVal &LHS, - const SVal &RHS) const; + void handleRandomIncrOrDecr(CheckerContext &C, const Expr *CE, + OverloadedOperatorKind Op, const SVal &RetVal, + const SVal &LHS, const SVal &RHS) const; void handleBegin(CheckerContext &C, const Expr *CE, const SVal &RetVal, const SVal &Cont) const; void handleEnd(CheckerContext &C, const Expr *CE, const SVal &RetVal, @@ -215,42 +121,12 @@ class IteratorChecker void handleEraseAfter(CheckerContext &C, const SVal &Iter) const; void handleEraseAfter(CheckerContext &C, const SVal &Iter1, const SVal &Iter2) const; - void verifyIncrement(CheckerContext &C, const SVal &Iter) const; - void verifyDecrement(CheckerContext &C, const SVal &Iter) const; - void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, - const SVal &LHS, const SVal &RHS) const; - void verifyMatch(CheckerContext &C, const SVal &Iter, - const MemRegion *Cont) const; - void verifyMatch(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const; - IteratorPosition advancePosition(CheckerContext &C, OverloadedOperatorKind Op, - const IteratorPosition &Pos, - const SVal &Distance) const; - void reportOutOfRangeBug(const StringRef &Message, const SVal &Val, - CheckerContext &C, ExplodedNode *ErrNode) const; - void reportMismatchedBug(const StringRef &Message, const SVal &Val1, - const SVal &Val2, CheckerContext &C, - ExplodedNode *ErrNode) const; - void reportMismatchedBug(const StringRef &Message, const SVal &Val, - const MemRegion *Reg, CheckerContext &C, - ExplodedNode *ErrNode) const; - void reportInvalidatedBug(const StringRef &Message, const SVal &Val, - CheckerContext &C, ExplodedNode *ErrNode) const; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; public: - IteratorChecker(); + IteratorModeling() {} - enum CheckKind { - CK_IteratorRangeChecker, - CK_MismatchedIteratorChecker, - CK_InvalidatedIteratorChecker, - CK_NumCheckKinds - }; - - DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckerNameRef CheckNames[CK_NumCheckKinds]; - - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &C) const; void checkPostStmt(const CXXConstructExpr *CCE, CheckerContext &C) const; @@ -260,19 +136,7 @@ public: void checkLiveSymbols(ProgramStateRef State, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; }; -} // namespace - -REGISTER_MAP_WITH_PROGRAMSTATE(IteratorSymbolMap, SymbolRef, IteratorPosition) -REGISTER_MAP_WITH_PROGRAMSTATE(IteratorRegionMap, const MemRegion *, - IteratorPosition) - -REGISTER_MAP_WITH_PROGRAMSTATE(ContainerMap, const MemRegion *, ContainerData) - -namespace { -bool isIteratorType(const QualType &Type); -bool isIterator(const CXXRecordDecl *CRD); -bool isComparisonOperator(OverloadedOperatorKind OK); bool isBeginCall(const FunctionDecl *Func); bool isEndCall(const FunctionDecl *Func); bool isAssignCall(const FunctionDecl *Func); @@ -283,17 +147,8 @@ bool isPopBackCall(const FunctionDecl *Func); bool isPushFrontCall(const FunctionDecl *Func); bool isEmplaceFrontCall(const FunctionDecl *Func); bool isPopFrontCall(const FunctionDecl *Func); -bool isInsertCall(const FunctionDecl *Func); -bool isEraseCall(const FunctionDecl *Func); -bool isEraseAfterCall(const FunctionDecl *Func); -bool isEmplaceCall(const FunctionDecl *Func); bool isAssignmentOperator(OverloadedOperatorKind OK); bool isSimpleComparisonOperator(OverloadedOperatorKind OK); -bool isAccessOperator(OverloadedOperatorKind OK); -bool isDereferenceOperator(OverloadedOperatorKind OK); -bool isIncrementOperator(OverloadedOperatorKind OK); -bool isDecrementOperator(OverloadedOperatorKind OK); -bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK); bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg); bool frontModifiable(ProgramStateRef State, const MemRegion *Reg); bool backModifiable(ProgramStateRef State, const MemRegion *Reg); @@ -307,10 +162,8 @@ ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, const Expr *E, QualType T, const LocationContext *LCtx, unsigned BlockCount); -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - const SVal &Val); -ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, - const IteratorPosition &Pos); +ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, + const ContainerData &CData); ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val); ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, long Scale); @@ -341,222 +194,16 @@ ProgramStateRef rebaseSymbolInIteratorPositionsIf( SymbolRef NewSym, SymbolRef CondSym, BinaryOperator::Opcode Opc); ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, bool Equal); -const ContainerData *getContainerData(ProgramStateRef State, - const MemRegion *Cont); -ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, - const ContainerData &CData); +SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef Expr, + SymbolRef OldSym, SymbolRef NewSym); bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont); bool isBoundThroughLazyCompoundVal(const Environment &Env, const MemRegion *Reg); -bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); -bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos); -bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); -bool isZero(ProgramStateRef State, const NonLoc &Val); -} // namespace -IteratorChecker::IteratorChecker() { - OutOfRangeBugType.reset( - new BugType(this, "Iterator out of range", "Misuse of STL APIs")); - MismatchedBugType.reset( - new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs", - /*SuppressOnSink=*/true)); - InvalidatedBugType.reset( - new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); -} - -void IteratorChecker::checkPreCall(const CallEvent &Call, - CheckerContext &C) const { - // Check for out of range access or access of invalidated position and - // iterator mismatches - const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); - if (!Func) - return; - - if (Func->isOverloadedOperator()) { - if (ChecksEnabled[CK_InvalidatedIteratorChecker] && - isAccessOperator(Func->getOverloadedOperator())) { - // Check for any kind of access of invalidated iterator positions - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - verifyAccess(C, InstCall->getCXXThisVal()); - } else { - verifyAccess(C, Call.getArgSVal(0)); - } - } - if (ChecksEnabled[CK_IteratorRangeChecker]) { - if (isIncrementOperator(Func->getOverloadedOperator())) { - // Check for out-of-range incrementions - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - verifyIncrement(C, InstCall->getCXXThisVal()); - } else { - if (Call.getNumArgs() >= 1) { - verifyIncrement(C, Call.getArgSVal(0)); - } - } - } else if (isDecrementOperator(Func->getOverloadedOperator())) { - // Check for out-of-range decrementions - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - verifyDecrement(C, InstCall->getCXXThisVal()); - } else { - if (Call.getNumArgs() >= 1) { - verifyDecrement(C, Call.getArgSVal(0)); - } - } - } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - // Check for out-of-range incrementions and decrementions - if (Call.getNumArgs() >= 1 && - Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { - verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), - InstCall->getCXXThisVal(), - Call.getArgSVal(0)); - } - } else { - if (Call.getNumArgs() >= 2 && - Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { - verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), - Call.getArgSVal(0), Call.getArgSVal(1)); - } - } - } else if (isDereferenceOperator(Func->getOverloadedOperator())) { - // Check for dereference of out-of-range iterators - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - verifyDereference(C, InstCall->getCXXThisVal()); - } else { - verifyDereference(C, Call.getArgSVal(0)); - } - } - } else if (ChecksEnabled[CK_MismatchedIteratorChecker] && - isComparisonOperator(Func->getOverloadedOperator())) { - // Check for comparisons of iterators of different containers - if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - if (Call.getNumArgs() < 1) - return; - - if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) || - !isIteratorType(Call.getArgExpr(0)->getType())) - return; - - verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0)); - } else { - if (Call.getNumArgs() < 2) - return; - - if (!isIteratorType(Call.getArgExpr(0)->getType()) || - !isIteratorType(Call.getArgExpr(1)->getType())) - return; - - verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); - } - } - } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { - if (!ChecksEnabled[CK_MismatchedIteratorChecker]) - return; - - const auto *ContReg = InstCall->getCXXThisVal().getAsRegion(); - if (!ContReg) - return; - // Check for erase, insert and emplace using iterator of another container - if (isEraseCall(Func) || isEraseAfterCall(Func)) { - verifyMatch(C, Call.getArgSVal(0), - InstCall->getCXXThisVal().getAsRegion()); - if (Call.getNumArgs() == 2) { - verifyMatch(C, Call.getArgSVal(1), - InstCall->getCXXThisVal().getAsRegion()); - } - } else if (isInsertCall(Func)) { - verifyMatch(C, Call.getArgSVal(0), - InstCall->getCXXThisVal().getAsRegion()); - if (Call.getNumArgs() == 3 && - isIteratorType(Call.getArgExpr(1)->getType()) && - isIteratorType(Call.getArgExpr(2)->getType())) { - verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2)); - } - } else if (isEmplaceCall(Func)) { - verifyMatch(C, Call.getArgSVal(0), - InstCall->getCXXThisVal().getAsRegion()); - } - } else if (isa<CXXConstructorCall>(&Call)) { - // Check match of first-last iterator pair in a constructor of a container - if (Call.getNumArgs() < 2) - return; - - const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl()); - if (Ctr->getNumParams() < 2) - return; - - if (Ctr->getParamDecl(0)->getName() != "first" || - Ctr->getParamDecl(1)->getName() != "last") - return; - - if (!isIteratorType(Call.getArgExpr(0)->getType()) || - !isIteratorType(Call.getArgExpr(1)->getType())) - return; - - verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); - } else { - // The main purpose of iterators is to abstract away from different - // containers and provide a (maybe limited) uniform access to them. - // This implies that any correctly written template function that - // works on multiple containers using iterators takes different - // template parameters for different containers. So we can safely - // assume that passing iterators of different containers as arguments - // whose type replaces the same template parameter is a bug. - // - // Example: - // template<typename I1, typename I2> - // void f(I1 first1, I1 last1, I2 first2, I2 last2); - // - // In this case the first two arguments to f() must be iterators must belong - // to the same container and the last to also to the same container but - // not necessarily to the same as the first two. - - if (!ChecksEnabled[CK_MismatchedIteratorChecker]) - return; - - const auto *Templ = Func->getPrimaryTemplate(); - if (!Templ) - return; - - const auto *TParams = Templ->getTemplateParameters(); - const auto *TArgs = Func->getTemplateSpecializationArgs(); - - // Iterate over all the template parameters - for (size_t I = 0; I < TParams->size(); ++I) { - const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I)); - if (!TPDecl) - continue; - - if (TPDecl->isParameterPack()) - continue; - - const auto TAType = TArgs->get(I).getAsType(); - if (!isIteratorType(TAType)) - continue; - - SVal LHS = UndefinedVal(); - - // For every template parameter which is an iterator type in the - // instantiation look for all functions' parameters' type by it and - // check whether they belong to the same container - for (auto J = 0U; J < Func->getNumParams(); ++J) { - const auto *Param = Func->getParamDecl(J); - const auto *ParamType = - Param->getType()->getAs<SubstTemplateTypeParmType>(); - if (!ParamType || - ParamType->getReplacedParameter()->getDecl() != TPDecl) - continue; - if (LHS.isUndef()) { - LHS = Call.getArgSVal(J); - } else { - verifyMatch(C, LHS, Call.getArgSVal(J)); - } - } - } - } -} +} // namespace -void IteratorChecker::checkPostCall(const CallEvent &Call, - CheckerContext &C) const { +void IteratorModeling::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { // Record new iterator positions and iterator position changes const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); if (!Func) @@ -590,10 +237,14 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, Call.getArgSVal(1), Op); return; } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { + const auto *OrigExpr = Call.getOriginExpr(); + if (!OrigExpr) + return; + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { if (Call.getNumArgs() >= 1 && Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { - handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), + handleRandomIncrOrDecr(C, OrigExpr, Func->getOverloadedOperator(), Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); return; @@ -601,7 +252,7 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, } else { if (Call.getNumArgs() >= 2 && Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { - handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), + handleRandomIncrOrDecr(C, OrigExpr, Func->getOverloadedOperator(), Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1)); return; @@ -746,8 +397,8 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, } } -void IteratorChecker::checkBind(SVal Loc, SVal Val, const Stmt *S, - CheckerContext &C) const { +void IteratorModeling::checkBind(SVal Loc, SVal Val, const Stmt *S, + CheckerContext &C) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos) { @@ -762,24 +413,23 @@ void IteratorChecker::checkBind(SVal Loc, SVal Val, const Stmt *S, } } -void IteratorChecker::checkPostStmt(const MaterializeTemporaryExpr *MTE, - CheckerContext &C) const { +void IteratorModeling::checkPostStmt(const MaterializeTemporaryExpr *MTE, + CheckerContext &C) const { /* Transfer iterator state to temporary objects */ auto State = C.getState(); - const auto *Pos = - getIteratorPosition(State, C.getSVal(MTE->GetTemporaryExpr())); + const auto *Pos = getIteratorPosition(State, C.getSVal(MTE->getSubExpr())); if (!Pos) return; State = setIteratorPosition(State, C.getSVal(MTE), *Pos); C.addTransition(State); } -void IteratorChecker::checkLiveSymbols(ProgramStateRef State, - SymbolReaper &SR) const { +void IteratorModeling::checkLiveSymbols(ProgramStateRef State, + SymbolReaper &SR) const { // Keep symbolic expressions of iterator positions, container begins and ends // alive auto RegionMap = State->get<IteratorRegionMap>(); - for (const auto Reg : RegionMap) { + for (const auto &Reg : RegionMap) { const auto Offset = Reg.second.getOffset(); for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) if (isa<SymbolData>(*i)) @@ -787,7 +437,7 @@ void IteratorChecker::checkLiveSymbols(ProgramStateRef State, } auto SymbolMap = State->get<IteratorSymbolMap>(); - for (const auto Sym : SymbolMap) { + for (const auto &Sym : SymbolMap) { const auto Offset = Sym.second.getOffset(); for (auto i = Offset->symbol_begin(); i != Offset->symbol_end(); ++i) if (isa<SymbolData>(*i)) @@ -795,7 +445,7 @@ void IteratorChecker::checkLiveSymbols(ProgramStateRef State, } auto ContMap = State->get<ContainerMap>(); - for (const auto Cont : ContMap) { + for (const auto &Cont : ContMap) { const auto CData = Cont.second; if (CData.getBegin()) { SR.markLive(CData.getBegin()); @@ -810,13 +460,13 @@ void IteratorChecker::checkLiveSymbols(ProgramStateRef State, } } -void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, - CheckerContext &C) const { +void IteratorModeling::checkDeadSymbols(SymbolReaper &SR, + CheckerContext &C) const { // Cleanup auto State = C.getState(); auto RegionMap = State->get<IteratorRegionMap>(); - for (const auto Reg : RegionMap) { + for (const auto &Reg : RegionMap) { if (!SR.isLiveRegion(Reg.first)) { // The region behind the `LazyCompoundVal` is often cleaned up before // the `LazyCompoundVal` itself. If there are iterator positions keyed @@ -828,14 +478,14 @@ void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, } auto SymbolMap = State->get<IteratorSymbolMap>(); - for (const auto Sym : SymbolMap) { + for (const auto &Sym : SymbolMap) { if (!SR.isLive(Sym.first)) { State = State->remove<IteratorSymbolMap>(Sym.first); } } auto ContMap = State->get<ContainerMap>(); - for (const auto Cont : ContMap) { + for (const auto &Cont : ContMap) { if (!SR.isLiveRegion(Cont.first)) { // We must keep the container data while it has live iterators to be able // to compare them to the begin and the end of the container. @@ -848,8 +498,8 @@ void IteratorChecker::checkDeadSymbols(SymbolReaper &SR, C.addTransition(State); } -void IteratorChecker::handleComparison(CheckerContext &C, const Expr *CE, - const SVal &RetVal, const SVal &LVal, +void IteratorModeling::handleComparison(CheckerContext &C, const Expr *CE, + SVal RetVal, const SVal &LVal, const SVal &RVal, OverloadedOperatorKind Op) const { // Record the operands and the operator of the comparison for the next @@ -888,13 +538,23 @@ void IteratorChecker::handleComparison(CheckerContext &C, const Expr *CE, RPos = getIteratorPosition(State, RVal); } + // We cannot make assumpotions on `UnknownVal`. Let us conjure a symbol + // instead. + if (RetVal.isUnknown()) { + auto &SymMgr = C.getSymbolManager(); + auto *LCtx = C.getLocationContext(); + RetVal = nonloc::SymbolVal(SymMgr.conjureSymbol( + CE, LCtx, C.getASTContext().BoolTy, C.blockCount())); + State = State->BindExpr(CE, LCtx, RetVal); + } + processComparison(C, State, LPos->getOffset(), RPos->getOffset(), RetVal, Op); } -void IteratorChecker::processComparison(CheckerContext &C, - ProgramStateRef State, SymbolRef Sym1, - SymbolRef Sym2, const SVal &RetVal, - OverloadedOperatorKind Op) const { +void IteratorModeling::processComparison(CheckerContext &C, + ProgramStateRef State, SymbolRef Sym1, + SymbolRef Sym2, const SVal &RetVal, + OverloadedOperatorKind Op) const { if (const auto TruthVal = RetVal.getAs<nonloc::ConcreteInt>()) { if ((State = relateSymbols(State, Sym1, Sym2, (Op == OO_EqualEqual) == @@ -909,7 +569,7 @@ void IteratorChecker::processComparison(CheckerContext &C, const auto ConditionVal = RetVal.getAs<DefinedSVal>(); if (!ConditionVal) return; - + if (auto StateTrue = relateSymbols(State, Sym1, Sym2, Op == OO_EqualEqual)) { StateTrue = StateTrue->assume(*ConditionVal, true); C.addTransition(StateTrue); @@ -921,75 +581,68 @@ void IteratorChecker::processComparison(CheckerContext &C, } } -void IteratorChecker::verifyDereference(CheckerContext &C, - const SVal &Val) const { - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Val); - if (Pos && isPastTheEnd(State, *Pos)) { - auto *N = C.generateErrorNode(State); - if (!N) - return; - reportOutOfRangeBug("Past-the-end iterator dereferenced.", Val, C, N); - return; - } -} - -void IteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Val); - if (Pos && !Pos->isValid()) { - auto *N = C.generateErrorNode(State); - if (!N) { - return; - } - reportInvalidatedBug("Invalidated iterator accessed.", Val, C, N); - } -} - -void IteratorChecker::handleIncrement(CheckerContext &C, const SVal &RetVal, - const SVal &Iter, bool Postfix) const { +void IteratorModeling::handleIncrement(CheckerContext &C, const SVal &RetVal, + const SVal &Iter, bool Postfix) const { // Increment the symbolic expressions which represents the position of the // iterator auto State = C.getState(); + auto &BVF = C.getSymbolManager().getBasicVals(); + const auto *Pos = getIteratorPosition(State, Iter); - if (Pos) { - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - const auto NewPos = - advancePosition(C, OO_Plus, *Pos, + if (!Pos) + return; + + auto NewState = + advancePosition(State, Iter, OO_Plus, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); - State = setIteratorPosition(State, Iter, NewPos); - State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); - C.addTransition(State); - } + assert(NewState && + "Advancing position by concrete int should always be successful"); + + const auto *NewPos = getIteratorPosition(NewState, Iter); + assert(NewPos && + "Iterator should have position after successful advancement"); + + State = setIteratorPosition(State, Iter, *NewPos); + State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos); + C.addTransition(State); } -void IteratorChecker::handleDecrement(CheckerContext &C, const SVal &RetVal, - const SVal &Iter, bool Postfix) const { +void IteratorModeling::handleDecrement(CheckerContext &C, const SVal &RetVal, + const SVal &Iter, bool Postfix) const { // Decrement the symbolic expressions which represents the position of the // iterator auto State = C.getState(); + auto &BVF = C.getSymbolManager().getBasicVals(); + const auto *Pos = getIteratorPosition(State, Iter); - if (Pos) { - auto &SymMgr = C.getSymbolManager(); - auto &BVF = SymMgr.getBasicVals(); - const auto NewPos = - advancePosition(C, OO_Minus, *Pos, + if (!Pos) + return; + + auto NewState = + advancePosition(State, Iter, OO_Minus, nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); - State = setIteratorPosition(State, Iter, NewPos); - State = setIteratorPosition(State, RetVal, Postfix ? *Pos : NewPos); - C.addTransition(State); - } + assert(NewState && + "Advancing position by concrete int should always be successful"); + + const auto *NewPos = getIteratorPosition(NewState, Iter); + assert(NewPos && + "Iterator should have position after successful advancement"); + + State = setIteratorPosition(State, Iter, *NewPos); + State = setIteratorPosition(State, RetVal, Postfix ? *Pos : *NewPos); + C.addTransition(State); } -void IteratorChecker::handleRandomIncrOrDecr(CheckerContext &C, - OverloadedOperatorKind Op, - const SVal &RetVal, - const SVal &LHS, - const SVal &RHS) const { +void IteratorModeling::handleRandomIncrOrDecr(CheckerContext &C, + const Expr *CE, + OverloadedOperatorKind Op, + const SVal &RetVal, + const SVal &LHS, + const SVal &RHS) const { // Increment or decrement the symbolic expressions which represents the // position of the iterator auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, LHS); if (!Pos) return; @@ -1001,142 +654,23 @@ void IteratorChecker::handleRandomIncrOrDecr(CheckerContext &C, } auto &TgtVal = (Op == OO_PlusEqual || Op == OO_MinusEqual) ? LHS : RetVal; - State = - setIteratorPosition(State, TgtVal, advancePosition(C, Op, *Pos, *value)); - C.addTransition(State); -} - -void IteratorChecker::verifyIncrement(CheckerContext &C, - const SVal &Iter) const { - auto &BVF = C.getSValBuilder().getBasicValueFactory(); - verifyRandomIncrOrDecr(C, OO_Plus, Iter, - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); -} - -void IteratorChecker::verifyDecrement(CheckerContext &C, - const SVal &Iter) const { - auto &BVF = C.getSValBuilder().getBasicValueFactory(); - verifyRandomIncrOrDecr(C, OO_Minus, Iter, - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); -} -void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C, - OverloadedOperatorKind Op, - const SVal &LHS, - const SVal &RHS) const { - auto State = C.getState(); - - // If the iterator is initially inside its range, then the operation is valid - const auto *Pos = getIteratorPosition(State, LHS); - if (!Pos) - return; - - auto Value = RHS; - if (auto ValAsLoc = RHS.getAs<Loc>()) { - Value = State->getRawSVal(*ValAsLoc); - } - - if (Value.isUnknown()) - return; - - // Incremention or decremention by 0 is never a bug. - if (isZero(State, Value.castAs<NonLoc>())) - return; - - // The result may be the past-end iterator of the container, but any other - // out of range position is undefined behaviour - if (isAheadOfRange(State, advancePosition(C, Op, *Pos, Value))) { - auto *N = C.generateErrorNode(State); - if (!N) - return; - reportOutOfRangeBug("Iterator decremented ahead of its valid range.", LHS, - C, N); - } - if (isBehindPastTheEnd(State, advancePosition(C, Op, *Pos, Value))) { - auto *N = C.generateErrorNode(State); - if (!N) - return; - reportOutOfRangeBug("Iterator incremented behind the past-the-end " - "iterator.", LHS, C, N); - } -} - -void IteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, - const MemRegion *Cont) const { - // Verify match between a container and the container of an iterator - Cont = Cont->getMostDerivedObjectRegion(); + auto NewState = + advancePosition(State, LHS, Op, *value); + if (NewState) { + const auto *NewPos = getIteratorPosition(NewState, LHS); + assert(NewPos && + "Iterator should have position after successful advancement"); - if (const auto *ContSym = Cont->getSymbolicBase()) { - if (isa<SymbolConjured>(ContSym->getSymbol())) - return; - } - - auto State = C.getState(); - const auto *Pos = getIteratorPosition(State, Iter); - if (!Pos) - return; - - const auto *IterCont = Pos->getContainer(); - - // Skip symbolic regions based on conjured symbols. Two conjured symbols - // may or may not be the same. For example, the same function can return - // the same or a different container but we get different conjured symbols - // for each call. This may cause false positives so omit them from the check. - if (const auto *ContSym = IterCont->getSymbolicBase()) { - if (isa<SymbolConjured>(ContSym->getSymbol())) - return; - } - - if (IterCont != Cont) { - auto *N = C.generateNonFatalErrorNode(State); - if (!N) { - return; - } - reportMismatchedBug("Container accessed using foreign iterator argument.", - Iter, Cont, C, N); - } -} - -void IteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const { - // Verify match between the containers of two iterators - auto State = C.getState(); - const auto *Pos1 = getIteratorPosition(State, Iter1); - if (!Pos1) - return; - - const auto *IterCont1 = Pos1->getContainer(); - - // Skip symbolic regions based on conjured symbols. Two conjured symbols - // may or may not be the same. For example, the same function can return - // the same or a different container but we get different conjured symbols - // for each call. This may cause false positives so omit them from the check. - if (const auto *ContSym = IterCont1->getSymbolicBase()) { - if (isa<SymbolConjured>(ContSym->getSymbol())) - return; - } - - const auto *Pos2 = getIteratorPosition(State, Iter2); - if (!Pos2) - return; - - const auto *IterCont2 = Pos2->getContainer(); - if (const auto *ContSym = IterCont2->getSymbolicBase()) { - if (isa<SymbolConjured>(ContSym->getSymbol())) - return; - } - - if (IterCont1 != IterCont2) { - auto *N = C.generateNonFatalErrorNode(State); - if (!N) - return; - reportMismatchedBug("Iterators of different containers used where the " - "same container is expected.", Iter1, Iter2, C, N); + State = setIteratorPosition(NewState, TgtVal, *NewPos); + C.addTransition(State); + } else { + assignToContainer(C, CE, TgtVal, Pos->getContainer()); } } -void IteratorChecker::handleBegin(CheckerContext &C, const Expr *CE, - const SVal &RetVal, const SVal &Cont) const { +void IteratorModeling::handleBegin(CheckerContext &C, const Expr *CE, + const SVal &RetVal, const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1157,8 +691,8 @@ void IteratorChecker::handleBegin(CheckerContext &C, const Expr *CE, C.addTransition(State); } -void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, - const SVal &RetVal, const SVal &Cont) const { +void IteratorModeling::handleEnd(CheckerContext &C, const Expr *CE, + const SVal &RetVal, const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1179,9 +713,9 @@ void IteratorChecker::handleEnd(CheckerContext &C, const Expr *CE, C.addTransition(State); } -void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, - const SVal &RetVal, - const MemRegion *Cont) const { +void IteratorModeling::assignToContainer(CheckerContext &C, const Expr *CE, + const SVal &RetVal, + const MemRegion *Cont) const { Cont = Cont->getMostDerivedObjectRegion(); auto State = C.getState(); @@ -1194,8 +728,8 @@ void IteratorChecker::assignToContainer(CheckerContext &C, const Expr *CE, C.addTransition(State); } -void IteratorChecker::handleAssign(CheckerContext &C, const SVal &Cont, - const Expr *CE, const SVal &OldCont) const { +void IteratorModeling::handleAssign(CheckerContext &C, const SVal &Cont, + const Expr *CE, const SVal &OldCont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1270,7 +804,7 @@ void IteratorChecker::handleAssign(CheckerContext &C, const SVal &Cont, C.addTransition(State); } -void IteratorChecker::handleClear(CheckerContext &C, const SVal &Cont) const { +void IteratorModeling::handleClear(CheckerContext &C, const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1296,8 +830,8 @@ void IteratorChecker::handleClear(CheckerContext &C, const SVal &Cont) const { C.addTransition(State); } -void IteratorChecker::handlePushBack(CheckerContext &C, - const SVal &Cont) const { +void IteratorModeling::handlePushBack(CheckerContext &C, + const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1334,7 +868,8 @@ void IteratorChecker::handlePushBack(CheckerContext &C, C.addTransition(State); } -void IteratorChecker::handlePopBack(CheckerContext &C, const SVal &Cont) const { +void IteratorModeling::handlePopBack(CheckerContext &C, + const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1371,8 +906,8 @@ void IteratorChecker::handlePopBack(CheckerContext &C, const SVal &Cont) const { } } -void IteratorChecker::handlePushFront(CheckerContext &C, - const SVal &Cont) const { +void IteratorModeling::handlePushFront(CheckerContext &C, + const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1404,8 +939,8 @@ void IteratorChecker::handlePushFront(CheckerContext &C, } } -void IteratorChecker::handlePopFront(CheckerContext &C, - const SVal &Cont) const { +void IteratorModeling::handlePopFront(CheckerContext &C, + const SVal &Cont) const { const auto *ContReg = Cont.getAsRegion(); if (!ContReg) return; @@ -1438,7 +973,7 @@ void IteratorChecker::handlePopFront(CheckerContext &C, } } -void IteratorChecker::handleInsert(CheckerContext &C, const SVal &Iter) const { +void IteratorModeling::handleInsert(CheckerContext &C, const SVal &Iter) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Iter); if (!Pos) @@ -1463,7 +998,7 @@ void IteratorChecker::handleInsert(CheckerContext &C, const SVal &Iter) const { } } -void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter) const { +void IteratorModeling::handleErase(CheckerContext &C, const SVal &Iter) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Iter); if (!Pos) @@ -1491,8 +1026,8 @@ void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter) const { C.addTransition(State); } -void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const { +void IteratorModeling::handleErase(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const { auto State = C.getState(); const auto *Pos1 = getIteratorPosition(State, Iter1); const auto *Pos2 = getIteratorPosition(State, Iter2); @@ -1523,8 +1058,8 @@ void IteratorChecker::handleErase(CheckerContext &C, const SVal &Iter1, C.addTransition(State); } -void IteratorChecker::handleEraseAfter(CheckerContext &C, - const SVal &Iter) const { +void IteratorModeling::handleEraseAfter(CheckerContext &C, + const SVal &Iter) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Iter); if (!Pos) @@ -1544,8 +1079,8 @@ void IteratorChecker::handleEraseAfter(CheckerContext &C, C.addTransition(State); } -void IteratorChecker::handleEraseAfter(CheckerContext &C, const SVal &Iter1, - const SVal &Iter2) const { +void IteratorModeling::handleEraseAfter(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const { auto State = C.getState(); const auto *Pos1 = getIteratorPosition(State, Iter1); const auto *Pos2 = getIteratorPosition(State, Iter2); @@ -1558,145 +1093,62 @@ void IteratorChecker::handleEraseAfter(CheckerContext &C, const SVal &Iter1, C.addTransition(State); } -IteratorPosition IteratorChecker::advancePosition(CheckerContext &C, - OverloadedOperatorKind Op, - const IteratorPosition &Pos, - const SVal &Distance) const { - auto State = C.getState(); - auto &SymMgr = C.getSymbolManager(); - auto &SVB = C.getSValBuilder(); +void IteratorModeling::printState(raw_ostream &Out, ProgramStateRef State, + const char *NL, const char *Sep) const { - assert ((Op == OO_Plus || Op == OO_PlusEqual || - Op == OO_Minus || Op == OO_MinusEqual) && - "Advance operator must be one of +, -, += and -=."); - auto BinOp = (Op == OO_Plus || Op == OO_PlusEqual) ? BO_Add : BO_Sub; - if (const auto IntDist = Distance.getAs<nonloc::ConcreteInt>()) { - // For concrete integers we can calculate the new position - return Pos.setTo(SVB.evalBinOp(State, BinOp, - nonloc::SymbolVal(Pos.getOffset()), *IntDist, - SymMgr.getType(Pos.getOffset())) - .getAsSymbol()); - } else { - // For other symbols create a new symbol to keep expressions simple - const auto &LCtx = C.getLocationContext(); - const auto NewPosSym = SymMgr.conjureSymbol(nullptr, LCtx, - SymMgr.getType(Pos.getOffset()), - C.blockCount()); - State = assumeNoOverflow(State, NewPosSym, 4); - return Pos.setTo(NewPosSym); + auto ContMap = State->get<ContainerMap>(); + + if (!ContMap.isEmpty()) { + Out << Sep << "Container Data :" << NL; + for (const auto &Cont : ContMap) { + Cont.first->dumpToStream(Out); + Out << " : [ "; + const auto CData = Cont.second; + if (CData.getBegin()) + CData.getBegin()->dumpToStream(Out); + else + Out << "<Unknown>"; + Out << " .. "; + if (CData.getEnd()) + CData.getEnd()->dumpToStream(Out); + else + Out << "<Unknown>"; + Out << " ]" << NL; + } } -} -void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, - const SVal &Val, CheckerContext &C, - ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*OutOfRangeBugType, Message, - ErrNode); - R->markInteresting(Val); - C.emitReport(std::move(R)); -} + auto SymbolMap = State->get<IteratorSymbolMap>(); + auto RegionMap = State->get<IteratorRegionMap>(); -void IteratorChecker::reportMismatchedBug(const StringRef &Message, - const SVal &Val1, const SVal &Val2, - CheckerContext &C, - ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message, - ErrNode); - R->markInteresting(Val1); - R->markInteresting(Val2); - C.emitReport(std::move(R)); -} + if (!SymbolMap.isEmpty() || !RegionMap.isEmpty()) { + Out << Sep << "Iterator Positions :" << NL; + for (const auto &Sym : SymbolMap) { + Sym.first->dumpToStream(Out); + Out << " : "; + const auto Pos = Sym.second; + Out << (Pos.isValid() ? "Valid" : "Invalid") << " ; Container == "; + Pos.getContainer()->dumpToStream(Out); + Out<<" ; Offset == "; + Pos.getOffset()->dumpToStream(Out); + } -void IteratorChecker::reportMismatchedBug(const StringRef &Message, - const SVal &Val, const MemRegion *Reg, - CheckerContext &C, - ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message, - ErrNode); - R->markInteresting(Val); - R->markInteresting(Reg); - C.emitReport(std::move(R)); + for (const auto &Reg : RegionMap) { + Reg.first->dumpToStream(Out); + Out << " : "; + const auto Pos = Reg.second; + Out << (Pos.isValid() ? "Valid" : "Invalid") << " ; Container == "; + Pos.getContainer()->dumpToStream(Out); + Out<<" ; Offset == "; + Pos.getOffset()->dumpToStream(Out); + } + } } -void IteratorChecker::reportInvalidatedBug(const StringRef &Message, - const SVal &Val, CheckerContext &C, - ExplodedNode *ErrNode) const { - auto R = std::make_unique<PathSensitiveBugReport>(*InvalidatedBugType, - Message, ErrNode); - R->markInteresting(Val); - C.emitReport(std::move(R)); -} namespace { -bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); -bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); -bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); -bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, - BinaryOperator::Opcode Opc); -bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, - BinaryOperator::Opcode Opc); const CXXRecordDecl *getCXXRecordDecl(ProgramStateRef State, const MemRegion *Reg); -SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymbolRef Expr, - SymbolRef OldSym, SymbolRef NewSym); - -bool isIteratorType(const QualType &Type) { - if (Type->isPointerType()) - return true; - - const auto *CRD = Type->getUnqualifiedDesugaredType()->getAsCXXRecordDecl(); - return isIterator(CRD); -} - -bool isIterator(const CXXRecordDecl *CRD) { - if (!CRD) - return false; - - const auto Name = CRD->getName(); - if (!(Name.endswith_lower("iterator") || Name.endswith_lower("iter") || - Name.endswith_lower("it"))) - return false; - - bool HasCopyCtor = false, HasCopyAssign = true, HasDtor = false, - HasPreIncrOp = false, HasPostIncrOp = false, HasDerefOp = false; - for (const auto *Method : CRD->methods()) { - if (const auto *Ctor = dyn_cast<CXXConstructorDecl>(Method)) { - if (Ctor->isCopyConstructor()) { - HasCopyCtor = !Ctor->isDeleted() && Ctor->getAccess() == AS_public; - } - continue; - } - if (const auto *Dtor = dyn_cast<CXXDestructorDecl>(Method)) { - HasDtor = !Dtor->isDeleted() && Dtor->getAccess() == AS_public; - continue; - } - if (Method->isCopyAssignmentOperator()) { - HasCopyAssign = !Method->isDeleted() && Method->getAccess() == AS_public; - continue; - } - if (!Method->isOverloadedOperator()) - continue; - const auto OPK = Method->getOverloadedOperator(); - if (OPK == OO_PlusPlus) { - HasPreIncrOp = HasPreIncrOp || (Method->getNumParams() == 0); - HasPostIncrOp = HasPostIncrOp || (Method->getNumParams() == 1); - continue; - } - if (OPK == OO_Star) { - HasDerefOp = (Method->getNumParams() == 0); - continue; - } - } - - return HasCopyCtor && HasCopyAssign && HasDtor && HasPreIncrOp && - HasPostIncrOp && HasDerefOp; -} - -bool isComparisonOperator(OverloadedOperatorKind OK) { - return OK == OO_EqualEqual || OK == OO_ExclaimEqual || OK == OO_Less || - OK == OO_LessEqual || OK == OO_Greater || OK == OO_GreaterEqual; -} bool isBeginCall(const FunctionDecl *Func) { const auto *IdInfo = Func->getIdentifier(); @@ -1784,85 +1236,12 @@ bool isPopFrontCall(const FunctionDecl *Func) { return IdInfo->getName() == "pop_front"; } -bool isInsertCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() < 2 || Func->getNumParams() > 3) - return false; - if (!isIteratorType(Func->getParamDecl(0)->getType())) - return false; - return IdInfo->getName() == "insert"; -} - -bool isEmplaceCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() < 2) - return false; - if (!isIteratorType(Func->getParamDecl(0)->getType())) - return false; - return IdInfo->getName() == "emplace"; -} - -bool isEraseCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() < 1 || Func->getNumParams() > 2) - return false; - if (!isIteratorType(Func->getParamDecl(0)->getType())) - return false; - if (Func->getNumParams() == 2 && - !isIteratorType(Func->getParamDecl(1)->getType())) - return false; - return IdInfo->getName() == "erase"; -} - -bool isEraseAfterCall(const FunctionDecl *Func) { - const auto *IdInfo = Func->getIdentifier(); - if (!IdInfo) - return false; - if (Func->getNumParams() < 1 || Func->getNumParams() > 2) - return false; - if (!isIteratorType(Func->getParamDecl(0)->getType())) - return false; - if (Func->getNumParams() == 2 && - !isIteratorType(Func->getParamDecl(1)->getType())) - return false; - return IdInfo->getName() == "erase_after"; -} - bool isAssignmentOperator(OverloadedOperatorKind OK) { return OK == OO_Equal; } bool isSimpleComparisonOperator(OverloadedOperatorKind OK) { return OK == OO_EqualEqual || OK == OO_ExclaimEqual; } -bool isAccessOperator(OverloadedOperatorKind OK) { - return isDereferenceOperator(OK) || isIncrementOperator(OK) || - isDecrementOperator(OK) || isRandomIncrOrDecrOperator(OK); -} - -bool isDereferenceOperator(OverloadedOperatorKind OK) { - return OK == OO_Star || OK == OO_Arrow || OK == OO_ArrowStar || - OK == OO_Subscript; -} - -bool isIncrementOperator(OverloadedOperatorKind OK) { - return OK == OO_PlusPlus; -} - -bool isDecrementOperator(OverloadedOperatorKind OK) { - return OK == OO_MinusMinus; -} - -bool isRandomIncrOrDecrOperator(OverloadedOperatorKind OK) { - return OK == OO_Plus || OK == OO_PlusEqual || OK == OO_Minus || - OK == OO_MinusEqual; -} - bool hasSubscriptOperator(ProgramStateRef State, const MemRegion *Reg) { const auto *CRD = getCXXRecordDecl(State, Reg); if (!CRD) @@ -1985,52 +1364,62 @@ ProgramStateRef createContainerEnd(ProgramStateRef State, const MemRegion *Cont, return setContainerData(State, Cont, CData); } -const ContainerData *getContainerData(ProgramStateRef State, - const MemRegion *Cont) { - return State->get<ContainerMap>(Cont); -} - ProgramStateRef setContainerData(ProgramStateRef State, const MemRegion *Cont, const ContainerData &CData) { return State->set<ContainerMap>(Cont, CData); } -const IteratorPosition *getIteratorPosition(ProgramStateRef State, - const SVal &Val) { +ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { if (auto Reg = Val.getAsRegion()) { Reg = Reg->getMostDerivedObjectRegion(); - return State->get<IteratorRegionMap>(Reg); + return State->remove<IteratorRegionMap>(Reg); } else if (const auto Sym = Val.getAsSymbol()) { - return State->get<IteratorSymbolMap>(Sym); + return State->remove<IteratorSymbolMap>(Sym); } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { - return State->get<IteratorRegionMap>(LCVal->getRegion()); + return State->remove<IteratorRegionMap>(LCVal->getRegion()); } return nullptr; } -ProgramStateRef setIteratorPosition(ProgramStateRef State, const SVal &Val, - const IteratorPosition &Pos) { - if (auto Reg = Val.getAsRegion()) { - Reg = Reg->getMostDerivedObjectRegion(); - return State->set<IteratorRegionMap>(Reg, Pos); - } else if (const auto Sym = Val.getAsSymbol()) { - return State->set<IteratorSymbolMap>(Sym, Pos); - } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { - return State->set<IteratorRegionMap>(LCVal->getRegion(), Pos); +// This function tells the analyzer's engine that symbols produced by our +// checker, most notably iterator positions, are relatively small. +// A distance between items in the container should not be very large. +// By assuming that it is within around 1/8 of the address space, +// we can help the analyzer perform operations on these symbols +// without being afraid of integer overflows. +// FIXME: Should we provide it as an API, so that all checkers could use it? +ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, + long Scale) { + SValBuilder &SVB = State->getStateManager().getSValBuilder(); + BasicValueFactory &BV = SVB.getBasicValueFactory(); + + QualType T = Sym->getType(); + assert(T->isSignedIntegerOrEnumerationType()); + APSIntType AT = BV.getAPSIntType(T); + + ProgramStateRef NewState = State; + + llvm::APSInt Max = AT.getMaxValue() / AT.getValue(Scale); + SVal IsCappedFromAbove = + SVB.evalBinOpNN(State, BO_LE, nonloc::SymbolVal(Sym), + nonloc::ConcreteInt(Max), SVB.getConditionType()); + if (auto DV = IsCappedFromAbove.getAs<DefinedSVal>()) { + NewState = NewState->assume(*DV, true); + if (!NewState) + return State; } - return nullptr; -} -ProgramStateRef removeIteratorPosition(ProgramStateRef State, const SVal &Val) { - if (auto Reg = Val.getAsRegion()) { - Reg = Reg->getMostDerivedObjectRegion(); - return State->remove<IteratorRegionMap>(Reg); - } else if (const auto Sym = Val.getAsSymbol()) { - return State->remove<IteratorSymbolMap>(Sym); - } else if (const auto LCVal = Val.getAs<nonloc::LazyCompoundVal>()) { - return State->remove<IteratorRegionMap>(LCVal->getRegion()); + llvm::APSInt Min = -Max; + SVal IsCappedFromBelow = + SVB.evalBinOpNN(State, BO_GE, nonloc::SymbolVal(Sym), + nonloc::ConcreteInt(Min), SVB.getConditionType()); + if (auto DV = IsCappedFromBelow.getAs<DefinedSVal>()) { + NewState = NewState->assume(*DV, true); + if (!NewState) + return State; } - return nullptr; + + return NewState; } ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, @@ -2067,13 +1456,13 @@ ProgramStateRef relateSymbols(ProgramStateRef State, SymbolRef Sym1, bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont) { auto RegionMap = State->get<IteratorRegionMap>(); - for (const auto Reg : RegionMap) { + for (const auto &Reg : RegionMap) { if (Reg.second.getContainer() == Cont) return true; } auto SymbolMap = State->get<IteratorSymbolMap>(); - for (const auto Sym : SymbolMap) { + for (const auto &Sym : SymbolMap) { if (Sym.second.getContainer() == Cont) return true; } @@ -2083,7 +1472,7 @@ bool hasLiveIterators(ProgramStateRef State, const MemRegion *Cont) { bool isBoundThroughLazyCompoundVal(const Environment &Env, const MemRegion *Reg) { - for (const auto Binding: Env) { + for (const auto &Binding : Env) { if (const auto LCVal = Binding.second.getAs<nonloc::LazyCompoundVal>()) { if (LCVal->getRegion() == Reg) return true; @@ -2093,54 +1482,13 @@ bool isBoundThroughLazyCompoundVal(const Environment &Env, return false; } -// This function tells the analyzer's engine that symbols produced by our -// checker, most notably iterator positions, are relatively small. -// A distance between items in the container should not be very large. -// By assuming that it is within around 1/8 of the address space, -// we can help the analyzer perform operations on these symbols -// without being afraid of integer overflows. -// FIXME: Should we provide it as an API, so that all checkers could use it? -ProgramStateRef assumeNoOverflow(ProgramStateRef State, SymbolRef Sym, - long Scale) { - SValBuilder &SVB = State->getStateManager().getSValBuilder(); - BasicValueFactory &BV = SVB.getBasicValueFactory(); - - QualType T = Sym->getType(); - assert(T->isSignedIntegerOrEnumerationType()); - APSIntType AT = BV.getAPSIntType(T); - - ProgramStateRef NewState = State; - - llvm::APSInt Max = AT.getMaxValue() / AT.getValue(Scale); - SVal IsCappedFromAbove = - SVB.evalBinOpNN(State, BO_LE, nonloc::SymbolVal(Sym), - nonloc::ConcreteInt(Max), SVB.getConditionType()); - if (auto DV = IsCappedFromAbove.getAs<DefinedSVal>()) { - NewState = NewState->assume(*DV, true); - if (!NewState) - return State; - } - - llvm::APSInt Min = -Max; - SVal IsCappedFromBelow = - SVB.evalBinOpNN(State, BO_GE, nonloc::SymbolVal(Sym), - nonloc::ConcreteInt(Min), SVB.getConditionType()); - if (auto DV = IsCappedFromBelow.getAs<DefinedSVal>()) { - NewState = NewState->assume(*DV, true); - if (!NewState) - return State; - } - - return NewState; -} - template <typename Condition, typename Process> ProgramStateRef processIteratorPositions(ProgramStateRef State, Condition Cond, Process Proc) { auto &RegionMapFactory = State->get_context<IteratorRegionMap>(); auto RegionMap = State->get<IteratorRegionMap>(); bool Changed = false; - for (const auto Reg : RegionMap) { + for (const auto &Reg : RegionMap) { if (Cond(Reg.second)) { RegionMap = RegionMapFactory.add(RegionMap, Reg.first, Proc(Reg.second)); Changed = true; @@ -2153,7 +1501,7 @@ ProgramStateRef processIteratorPositions(ProgramStateRef State, Condition Cond, auto &SymbolMapFactory = State->get_context<IteratorSymbolMap>(); auto SymbolMap = State->get<IteratorSymbolMap>(); Changed = false; - for (const auto Sym : SymbolMap) { + for (const auto &Sym : SymbolMap) { if (Cond(Sym.second)) { SymbolMap = SymbolMapFactory.add(SymbolMap, Sym.first, Proc(Sym.second)); Changed = true; @@ -2280,111 +1628,12 @@ SymbolRef rebaseSymbol(ProgramStateRef State, SValBuilder &SVB, SymMgr.getType(OrigExpr)).getAsSymbol(); } -bool isZero(ProgramStateRef State, const NonLoc &Val) { - auto &BVF = State->getBasicVals(); - return compare(State, Val, - nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0))), - BO_EQ); -} - -bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { - const auto *Cont = Pos.getContainer(); - const auto *CData = getContainerData(State, Cont); - if (!CData) - return false; - - const auto End = CData->getEnd(); - if (End) { - if (isEqual(State, Pos.getOffset(), End)) { - return true; - } - } - - return false; -} - -bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos) { - const auto *Cont = Pos.getContainer(); - const auto *CData = getContainerData(State, Cont); - if (!CData) - return false; - - const auto Beg = CData->getBegin(); - if (Beg) { - if (isLess(State, Pos.getOffset(), Beg)) { - return true; - } - } - - return false; -} - -bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { - const auto *Cont = Pos.getContainer(); - const auto *CData = getContainerData(State, Cont); - if (!CData) - return false; - - const auto End = CData->getEnd(); - if (End) { - if (isGreater(State, Pos.getOffset(), End)) { - return true; - } - } - - return false; -} - -bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { - return compare(State, Sym1, Sym2, BO_LT); -} - -bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { - return compare(State, Sym1, Sym2, BO_GT); -} - -bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { - return compare(State, Sym1, Sym2, BO_EQ); -} - -bool compare(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2, - BinaryOperator::Opcode Opc) { - return compare(State, nonloc::SymbolVal(Sym1), nonloc::SymbolVal(Sym2), Opc); -} - -bool compare(ProgramStateRef State, NonLoc NL1, NonLoc NL2, - BinaryOperator::Opcode Opc) { - auto &SVB = State->getStateManager().getSValBuilder(); - - const auto comparison = - SVB.evalBinOp(State, Opc, NL1, NL2, SVB.getConditionType()); - - assert(comparison.getAs<DefinedSVal>() && - "Symbol comparison must be a `DefinedSVal`"); - - return !State->assume(comparison.castAs<DefinedSVal>(), false); -} - } // namespace void ento::registerIteratorModeling(CheckerManager &mgr) { - mgr.registerChecker<IteratorChecker>(); + mgr.registerChecker<IteratorModeling>(); } bool ento::shouldRegisterIteratorModeling(const LangOptions &LO) { return true; } - -#define REGISTER_CHECKER(name) \ - void ento::register##name(CheckerManager &Mgr) { \ - auto *checker = Mgr.getChecker<IteratorChecker>(); \ - checker->ChecksEnabled[IteratorChecker::CK_##name] = true; \ - checker->CheckNames[IteratorChecker::CK_##name] = \ - Mgr.getCurrentCheckerName(); \ - } \ - \ - bool ento::shouldRegister##name(const LangOptions &LO) { return true; } - -REGISTER_CHECKER(IteratorRangeChecker) -REGISTER_CHECKER(MismatchedIteratorChecker) -REGISTER_CHECKER(InvalidatedIteratorChecker) diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp new file mode 100644 index 000000000000..bd8b84d464b6 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorRangeChecker.cpp @@ -0,0 +1,273 @@ +//===-- IteratorRangeChecker.cpp ----------------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for dereference of the past-the-end iterator and +// out-of-range increments and decrements. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + + +#include "Iterator.h" + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class IteratorRangeChecker + : public Checker<check::PreCall> { + + std::unique_ptr<BugType> OutOfRangeBugType; + + void verifyDereference(CheckerContext &C, const SVal &Val) const; + void verifyIncrement(CheckerContext &C, const SVal &Iter) const; + void verifyDecrement(CheckerContext &C, const SVal &Iter) const; + void verifyRandomIncrOrDecr(CheckerContext &C, OverloadedOperatorKind Op, + const SVal &LHS, const SVal &RHS) const; + void reportBug(const StringRef &Message, const SVal &Val, + CheckerContext &C, ExplodedNode *ErrNode) const; +public: + IteratorRangeChecker(); + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +}; + +bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); +bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos); +bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos); +bool isZero(ProgramStateRef State, const NonLoc &Val); + +} //namespace + +IteratorRangeChecker::IteratorRangeChecker() { + OutOfRangeBugType.reset( + new BugType(this, "Iterator out of range", "Misuse of STL APIs")); +} + +void IteratorRangeChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + // Check for out of range access + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!Func) + return; + + if (Func->isOverloadedOperator()) { + if (isIncrementOperator(Func->getOverloadedOperator())) { + // Check for out-of-range incrementions + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyIncrement(C, InstCall->getCXXThisVal()); + } else { + if (Call.getNumArgs() >= 1) { + verifyIncrement(C, Call.getArgSVal(0)); + } + } + } else if (isDecrementOperator(Func->getOverloadedOperator())) { + // Check for out-of-range decrementions + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyDecrement(C, InstCall->getCXXThisVal()); + } else { + if (Call.getNumArgs() >= 1) { + verifyDecrement(C, Call.getArgSVal(0)); + } + } + } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + // Check for out-of-range incrementions and decrementions + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { + verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), + InstCall->getCXXThisVal(), + Call.getArgSVal(0)); + } + } else { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { + verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), + Call.getArgSVal(0), Call.getArgSVal(1)); + } + } + } else if (isDereferenceOperator(Func->getOverloadedOperator())) { + // Check for dereference of out-of-range iterators + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + verifyDereference(C, InstCall->getCXXThisVal()); + } else { + verifyDereference(C, Call.getArgSVal(0)); + } + } + } +} + +void IteratorRangeChecker::verifyDereference(CheckerContext &C, + const SVal &Val) const { + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Val); + if (Pos && isPastTheEnd(State, *Pos)) { + auto *N = C.generateErrorNode(State); + if (!N) + return; + reportBug("Past-the-end iterator dereferenced.", Val, C, N); + return; + } +} + +void IteratorRangeChecker::verifyIncrement(CheckerContext &C, + const SVal &Iter) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + verifyRandomIncrOrDecr(C, OO_Plus, Iter, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); +} + +void IteratorRangeChecker::verifyDecrement(CheckerContext &C, + const SVal &Iter) const { + auto &BVF = C.getSValBuilder().getBasicValueFactory(); + verifyRandomIncrOrDecr(C, OO_Minus, Iter, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(1)))); +} + +void IteratorRangeChecker::verifyRandomIncrOrDecr(CheckerContext &C, + OverloadedOperatorKind Op, + const SVal &LHS, + const SVal &RHS) const { + auto State = C.getState(); + + auto Value = RHS; + if (auto ValAsLoc = RHS.getAs<Loc>()) { + Value = State->getRawSVal(*ValAsLoc); + } + + if (Value.isUnknown()) + return; + + // Incremention or decremention by 0 is never a bug. + if (isZero(State, Value.castAs<NonLoc>())) + return; + + // The result may be the past-end iterator of the container, but any other + // out of range position is undefined behaviour + auto StateAfter = advancePosition(State, LHS, Op, Value); + if (!StateAfter) + return; + + const auto *PosAfter = getIteratorPosition(StateAfter, LHS); + assert(PosAfter && + "Iterator should have position after successful advancement"); + if (isAheadOfRange(State, *PosAfter)) { + auto *N = C.generateErrorNode(State); + if (!N) + return; + reportBug("Iterator decremented ahead of its valid range.", LHS, + C, N); + } + if (isBehindPastTheEnd(State, *PosAfter)) { + auto *N = C.generateErrorNode(State); + if (!N) + return; + reportBug("Iterator incremented behind the past-the-end " + "iterator.", LHS, C, N); + } +} + +void IteratorRangeChecker::reportBug(const StringRef &Message, + const SVal &Val, CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = std::make_unique<PathSensitiveBugReport>(*OutOfRangeBugType, Message, + ErrNode); + R->markInteresting(Val); + C.emitReport(std::move(R)); +} + +namespace { + +bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); +bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); +bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2); + +bool isZero(ProgramStateRef State, const NonLoc &Val) { + auto &BVF = State->getBasicVals(); + return compare(State, Val, + nonloc::ConcreteInt(BVF.getValue(llvm::APSInt::get(0))), + BO_EQ); +} + +bool isPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { + const auto *Cont = Pos.getContainer(); + const auto *CData = getContainerData(State, Cont); + if (!CData) + return false; + + const auto End = CData->getEnd(); + if (End) { + if (isEqual(State, Pos.getOffset(), End)) { + return true; + } + } + + return false; +} + +bool isAheadOfRange(ProgramStateRef State, const IteratorPosition &Pos) { + const auto *Cont = Pos.getContainer(); + const auto *CData = getContainerData(State, Cont); + if (!CData) + return false; + + const auto Beg = CData->getBegin(); + if (Beg) { + if (isLess(State, Pos.getOffset(), Beg)) { + return true; + } + } + + return false; +} + +bool isBehindPastTheEnd(ProgramStateRef State, const IteratorPosition &Pos) { + const auto *Cont = Pos.getContainer(); + const auto *CData = getContainerData(State, Cont); + if (!CData) + return false; + + const auto End = CData->getEnd(); + if (End) { + if (isGreater(State, Pos.getOffset(), End)) { + return true; + } + } + + return false; +} + +bool isLess(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_LT); +} + +bool isGreater(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_GT); +} + +bool isEqual(ProgramStateRef State, SymbolRef Sym1, SymbolRef Sym2) { + return compare(State, Sym1, Sym2, BO_EQ); +} + +} // namespace + +void ento::registerIteratorRangeChecker(CheckerManager &mgr) { + mgr.registerChecker<IteratorRangeChecker>(); +} + +bool ento::shouldRegisterIteratorRangeChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index a81015b6e524..79de1844e745 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -1077,7 +1077,10 @@ void EmptyLocalizationContextChecker::checkASTDecl( AnalysisDeclContext *DCtx = Mgr.getAnalysisDeclContext(M); const Stmt *Body = M->getBody(); - assert(Body); + if (!Body) { + assert(M->isSynthesizedAccessorStub()); + continue; + } MethodCrawler MC(M->getCanonicalDecl(), BR, this, Mgr, DCtx); MC.VisitStmt(Body); diff --git a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp index d8fd125f4003..d73e2eb92d42 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -21,6 +21,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/Attr.h" #include "clang/Analysis/AnyCall.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index d964a1668eaa..410721d8b6ff 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -14,8 +14,9 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/Attr.h" #include "clang/Basic/TargetInfo.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index a82449951873..09306383d53f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -1469,6 +1469,9 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, if (!*FreeWhenDone) return; + if (Call.hasNonZeroCallbackArg()) + return; + bool IsKnownToBeAllocatedMemory; ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0), Call.getOriginExpr(), C.getState(), @@ -2525,19 +2528,18 @@ MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, // Find the most recent expression bound to the symbol in the current // context. - if (!ReferenceRegion) { - if (const MemRegion *MR = C.getLocationRegionIfPostStore(N)) { - SVal Val = State->getSVal(MR); - if (Val.getAsLocSymbol() == Sym) { - const VarRegion* VR = MR->getBaseRegion()->getAs<VarRegion>(); - // Do not show local variables belonging to a function other than - // where the error is reported. - if (!VR || - (VR->getStackFrame() == LeakContext->getStackFrame())) - ReferenceRegion = MR; - } + if (!ReferenceRegion) { + if (const MemRegion *MR = C.getLocationRegionIfPostStore(N)) { + SVal Val = State->getSVal(MR); + if (Val.getAsLocSymbol() == Sym) { + const VarRegion *VR = MR->getBaseRegion()->getAs<VarRegion>(); + // Do not show local variables belonging to a function other than + // where the error is reported. + if (!VR || (VR->getStackFrame() == LeakContext->getStackFrame())) + ReferenceRegion = MR; } } + } // Allocation node, is the last node in the current or parent context in // which the symbol was tracked. diff --git a/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp new file mode 100644 index 000000000000..143910588959 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/MismatchedIteratorChecker.cpp @@ -0,0 +1,295 @@ +//===-- MismatchedIteratorChecker.cpp -----------------------------*- C++ -*--// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// Defines a checker for mistakenly applying a foreign iterator on a container +// and for using iterators of two different containers in a context where +// iterators of the same container should be used. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" + + +#include "Iterator.h" + +using namespace clang; +using namespace ento; +using namespace iterator; + +namespace { + +class MismatchedIteratorChecker + : public Checker<check::PreCall> { + + std::unique_ptr<BugType> MismatchedBugType; + + void verifyMatch(CheckerContext &C, const SVal &Iter, + const MemRegion *Cont) const; + void verifyMatch(CheckerContext &C, const SVal &Iter1, + const SVal &Iter2) const; + void reportBug(const StringRef &Message, const SVal &Val1, + const SVal &Val2, CheckerContext &C, + ExplodedNode *ErrNode) const; + void reportBug(const StringRef &Message, const SVal &Val, + const MemRegion *Reg, CheckerContext &C, + ExplodedNode *ErrNode) const; + +public: + MismatchedIteratorChecker(); + + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + +}; + +} // namespace + +MismatchedIteratorChecker::MismatchedIteratorChecker() { + MismatchedBugType.reset( + new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs", + /*SuppressOnSink=*/true)); +} + +void MismatchedIteratorChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { + // Check for iterator mismatches + const auto *Func = dyn_cast_or_null<FunctionDecl>(Call.getDecl()); + if (!Func) + return; + + if (Func->isOverloadedOperator() && + isComparisonOperator(Func->getOverloadedOperator())) { + // Check for comparisons of iterators of different containers + if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + if (Call.getNumArgs() < 1) + return; + + if (!isIteratorType(InstCall->getCXXThisExpr()->getType()) || + !isIteratorType(Call.getArgExpr(0)->getType())) + return; + + verifyMatch(C, InstCall->getCXXThisVal(), Call.getArgSVal(0)); + } else { + if (Call.getNumArgs() < 2) + return; + + if (!isIteratorType(Call.getArgExpr(0)->getType()) || + !isIteratorType(Call.getArgExpr(1)->getType())) + return; + + verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } + } else if (const auto *InstCall = dyn_cast<CXXInstanceCall>(&Call)) { + const auto *ContReg = InstCall->getCXXThisVal().getAsRegion(); + if (!ContReg) + return; + // Check for erase, insert and emplace using iterator of another container + if (isEraseCall(Func) || isEraseAfterCall(Func)) { + verifyMatch(C, Call.getArgSVal(0), + InstCall->getCXXThisVal().getAsRegion()); + if (Call.getNumArgs() == 2) { + verifyMatch(C, Call.getArgSVal(1), + InstCall->getCXXThisVal().getAsRegion()); + } + } else if (isInsertCall(Func)) { + verifyMatch(C, Call.getArgSVal(0), + InstCall->getCXXThisVal().getAsRegion()); + if (Call.getNumArgs() == 3 && + isIteratorType(Call.getArgExpr(1)->getType()) && + isIteratorType(Call.getArgExpr(2)->getType())) { + verifyMatch(C, Call.getArgSVal(1), Call.getArgSVal(2)); + } + } else if (isEmplaceCall(Func)) { + verifyMatch(C, Call.getArgSVal(0), + InstCall->getCXXThisVal().getAsRegion()); + } + } else if (isa<CXXConstructorCall>(&Call)) { + // Check match of first-last iterator pair in a constructor of a container + if (Call.getNumArgs() < 2) + return; + + const auto *Ctr = cast<CXXConstructorDecl>(Call.getDecl()); + if (Ctr->getNumParams() < 2) + return; + + if (Ctr->getParamDecl(0)->getName() != "first" || + Ctr->getParamDecl(1)->getName() != "last") + return; + + if (!isIteratorType(Call.getArgExpr(0)->getType()) || + !isIteratorType(Call.getArgExpr(1)->getType())) + return; + + verifyMatch(C, Call.getArgSVal(0), Call.getArgSVal(1)); + } else { + // The main purpose of iterators is to abstract away from different + // containers and provide a (maybe limited) uniform access to them. + // This implies that any correctly written template function that + // works on multiple containers using iterators takes different + // template parameters for different containers. So we can safely + // assume that passing iterators of different containers as arguments + // whose type replaces the same template parameter is a bug. + // + // Example: + // template<typename I1, typename I2> + // void f(I1 first1, I1 last1, I2 first2, I2 last2); + // + // In this case the first two arguments to f() must be iterators must belong + // to the same container and the last to also to the same container but + // not necessarily to the same as the first two. + + const auto *Templ = Func->getPrimaryTemplate(); + if (!Templ) + return; + + const auto *TParams = Templ->getTemplateParameters(); + const auto *TArgs = Func->getTemplateSpecializationArgs(); + + // Iterate over all the template parameters + for (size_t I = 0; I < TParams->size(); ++I) { + const auto *TPDecl = dyn_cast<TemplateTypeParmDecl>(TParams->getParam(I)); + if (!TPDecl) + continue; + + if (TPDecl->isParameterPack()) + continue; + + const auto TAType = TArgs->get(I).getAsType(); + if (!isIteratorType(TAType)) + continue; + + SVal LHS = UndefinedVal(); + + // For every template parameter which is an iterator type in the + // instantiation look for all functions' parameters' type by it and + // check whether they belong to the same container + for (auto J = 0U; J < Func->getNumParams(); ++J) { + const auto *Param = Func->getParamDecl(J); + const auto *ParamType = + Param->getType()->getAs<SubstTemplateTypeParmType>(); + if (!ParamType || + ParamType->getReplacedParameter()->getDecl() != TPDecl) + continue; + if (LHS.isUndef()) { + LHS = Call.getArgSVal(J); + } else { + verifyMatch(C, LHS, Call.getArgSVal(J)); + } + } + } + } +} + +void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, const SVal &Iter, + const MemRegion *Cont) const { + // Verify match between a container and the container of an iterator + Cont = Cont->getMostDerivedObjectRegion(); + + if (const auto *ContSym = Cont->getSymbolicBase()) { + if (isa<SymbolConjured>(ContSym->getSymbol())) + return; + } + + auto State = C.getState(); + const auto *Pos = getIteratorPosition(State, Iter); + if (!Pos) + return; + + const auto *IterCont = Pos->getContainer(); + + // Skip symbolic regions based on conjured symbols. Two conjured symbols + // may or may not be the same. For example, the same function can return + // the same or a different container but we get different conjured symbols + // for each call. This may cause false positives so omit them from the check. + if (const auto *ContSym = IterCont->getSymbolicBase()) { + if (isa<SymbolConjured>(ContSym->getSymbol())) + return; + } + + if (IterCont != Cont) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) { + return; + } + reportBug("Container accessed using foreign iterator argument.", + Iter, Cont, C, N); + } +} + +void MismatchedIteratorChecker::verifyMatch(CheckerContext &C, + const SVal &Iter1, + const SVal &Iter2) const { + // Verify match between the containers of two iterators + auto State = C.getState(); + const auto *Pos1 = getIteratorPosition(State, Iter1); + if (!Pos1) + return; + + const auto *IterCont1 = Pos1->getContainer(); + + // Skip symbolic regions based on conjured symbols. Two conjured symbols + // may or may not be the same. For example, the same function can return + // the same or a different container but we get different conjured symbols + // for each call. This may cause false positives so omit them from the check. + if (const auto *ContSym = IterCont1->getSymbolicBase()) { + if (isa<SymbolConjured>(ContSym->getSymbol())) + return; + } + + const auto *Pos2 = getIteratorPosition(State, Iter2); + if (!Pos2) + return; + + const auto *IterCont2 = Pos2->getContainer(); + if (const auto *ContSym = IterCont2->getSymbolicBase()) { + if (isa<SymbolConjured>(ContSym->getSymbol())) + return; + } + + if (IterCont1 != IterCont2) { + auto *N = C.generateNonFatalErrorNode(State); + if (!N) + return; + reportBug("Iterators of different containers used where the " + "same container is expected.", Iter1, Iter2, C, N); + } +} + +void MismatchedIteratorChecker::reportBug(const StringRef &Message, + const SVal &Val1, + const SVal &Val2, + CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message, + ErrNode); + R->markInteresting(Val1); + R->markInteresting(Val2); + C.emitReport(std::move(R)); +} + +void MismatchedIteratorChecker::reportBug(const StringRef &Message, + const SVal &Val, const MemRegion *Reg, + CheckerContext &C, + ExplodedNode *ErrNode) const { + auto R = std::make_unique<PathSensitiveBugReport>(*MismatchedBugType, Message, + ErrNode); + R->markInteresting(Val); + R->markInteresting(Reg); + C.emitReport(std::move(R)); +} + +void ento::registerMismatchedIteratorChecker(CheckerManager &mgr) { + mgr.registerChecker<MismatchedIteratorChecker>(); +} + +bool ento::shouldRegisterMismatchedIteratorChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index 1473c05d7e3f..40eb113e3f8e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -12,6 +12,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/Attr.h" #include "clang/AST/ExprCXX.h" #include "clang/Driver/DriverDiagnostic.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" @@ -685,7 +686,7 @@ void MoveChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { ProgramStateRef State = C.getState(); TrackedRegionMapTy TrackedRegions = State->get<TrackedRegionMap>(); - for (TrackedRegionMapTy::value_type E : TrackedRegions) { + for (auto E : TrackedRegions) { const MemRegion *Region = E.first; bool IsRegDead = !SymReaper.isLiveRegion(Region); diff --git a/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index 43dbe57b8432..6efba433eed2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -36,6 +36,7 @@ class NonnullGlobalConstantsChecker : public Checker<check::Location> { mutable IdentifierInfo *NSStringII = nullptr; mutable IdentifierInfo *CFStringRefII = nullptr; mutable IdentifierInfo *CFBooleanRefII = nullptr; + mutable IdentifierInfo *CFNullRefII = nullptr; public: NonnullGlobalConstantsChecker() {} @@ -61,6 +62,7 @@ void NonnullGlobalConstantsChecker::initIdentifierInfo(ASTContext &Ctx) const { NSStringII = &Ctx.Idents.get("NSString"); CFStringRefII = &Ctx.Idents.get("CFStringRef"); CFBooleanRefII = &Ctx.Idents.get("CFBooleanRef"); + CFNullRefII = &Ctx.Idents.get("CFNullRef"); } /// Add an assumption that const string-like globals are non-null. @@ -136,7 +138,7 @@ bool NonnullGlobalConstantsChecker::isNonnullType(QualType Ty) const { T->getInterfaceDecl()->getIdentifier() == NSStringII; } else if (auto *T = dyn_cast<TypedefType>(Ty)) { IdentifierInfo* II = T->getDecl()->getIdentifier(); - return II == CFStringRefII || II == CFBooleanRefII; + return II == CFStringRefII || II == CFBooleanRefII || II == CFNullRefII; } return false; } diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index 4322ac207112..922048733c7c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -728,11 +728,6 @@ void NullabilityChecker::checkPreCall(const CallEvent &Call, } continue; } - // No tracked nullability yet. - if (ArgExprTypeLevelNullability != Nullability::Nullable) - continue; - State = State->set<NullabilityMap>( - Region, NullabilityState(ArgExprTypeLevelNullability, ArgExpr)); } if (State != OrigState) C.addTransition(State); diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index c254408351c8..47099f2afb6a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -19,84 +19,90 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include <functional> using namespace clang; using namespace ento; +using namespace std::placeholders; namespace { struct StreamState { enum Kind { Opened, Closed, OpenFailed, Escaped } K; - const Stmt *S; - StreamState(Kind k, const Stmt *s) : K(k), S(s) {} + StreamState(Kind k) : K(k) {} bool isOpened() const { return K == Opened; } bool isClosed() const { return K == Closed; } //bool isOpenFailed() const { return K == OpenFailed; } //bool isEscaped() const { return K == Escaped; } - bool operator==(const StreamState &X) const { - return K == X.K && S == X.S; - } + bool operator==(const StreamState &X) const { return K == X.K; } - 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 getEscaped(const Stmt *s) { - return StreamState(Escaped, s); - } + static StreamState getOpened() { return StreamState(Opened); } + static StreamState getClosed() { return StreamState(Closed); } + static StreamState getOpenFailed() { return StreamState(OpenFailed); } + static StreamState getEscaped() { return StreamState(Escaped); } void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(K); - ID.AddPointer(S); } }; 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_clearerr, *II_feof, *II_ferror, *II_fileno; mutable std::unique_ptr<BuiltinBug> BT_nullfp, BT_illegalwhence, BT_doubleclose, BT_ResourceLeak; public: - 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), - II_fsetpos(nullptr), II_clearerr(nullptr), II_feof(nullptr), - II_ferror(nullptr), II_fileno(nullptr) {} - bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const; private: - void Fopen(CheckerContext &C, const CallExpr *CE) const; - void Tmpfile(CheckerContext &C, const CallExpr *CE) const; - void Fclose(CheckerContext &C, const CallExpr *CE) const; - void Fread(CheckerContext &C, const CallExpr *CE) const; - void Fwrite(CheckerContext &C, const CallExpr *CE) const; - void Fseek(CheckerContext &C, const CallExpr *CE) const; - void Ftell(CheckerContext &C, const CallExpr *CE) const; - void Rewind(CheckerContext &C, const CallExpr *CE) const; - void Fgetpos(CheckerContext &C, const CallExpr *CE) const; - void Fsetpos(CheckerContext &C, const CallExpr *CE) const; - void Clearerr(CheckerContext &C, const CallExpr *CE) const; - void Feof(CheckerContext &C, const CallExpr *CE) const; - void Ferror(CheckerContext &C, const CallExpr *CE) const; - void Fileno(CheckerContext &C, const CallExpr *CE) const; - - void OpenFileAux(CheckerContext &C, const CallExpr *CE) const; - - ProgramStateRef CheckNullStream(SVal SV, ProgramStateRef state, - CheckerContext &C) const; - ProgramStateRef CheckDoubleClose(const CallExpr *CE, ProgramStateRef state, - CheckerContext &C) const; + using FnCheck = std::function<void(const StreamChecker *, const CallEvent &, + CheckerContext &)>; + + CallDescriptionMap<FnCheck> Callbacks = { + {{"fopen"}, &StreamChecker::evalFopen}, + {{"freopen", 3}, &StreamChecker::evalFreopen}, + {{"tmpfile"}, &StreamChecker::evalFopen}, + {{"fclose", 1}, &StreamChecker::evalFclose}, + {{"fread", 4}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, + {{"fwrite", 4}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 3)}, + {{"fseek", 3}, &StreamChecker::evalFseek}, + {{"ftell", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"rewind", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"fgetpos", 2}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"fsetpos", 2}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"clearerr", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"feof", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"ferror", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + {{"fileno", 1}, + std::bind(&StreamChecker::checkArgNullStream, _1, _2, _3, 0)}, + }; + + void evalFopen(const CallEvent &Call, CheckerContext &C) const; + void evalFreopen(const CallEvent &Call, CheckerContext &C) const; + void evalFclose(const CallEvent &Call, CheckerContext &C) const; + void evalFseek(const CallEvent &Call, CheckerContext &C) const; + + void checkArgNullStream(const CallEvent &Call, CheckerContext &C, + unsigned ArgI) const; + bool checkNullStream(SVal SV, CheckerContext &C, + ProgramStateRef &State) const; + void checkFseekWhence(SVal SV, CheckerContext &C, + ProgramStateRef &State) const; + bool checkDoubleClose(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const; }; } // end anonymous namespace @@ -109,115 +115,36 @@ bool StreamChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { if (!FD || FD->getKind() != Decl::Function) return false; - const auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); - if (!CE) + // Recognize "global C functions" with only integral or pointer arguments + // (and matching name) as stream functions. + if (!Call.isGlobalCFunction()) return false; - - ASTContext &Ctx = C.getASTContext(); - if (!II_fopen) - II_fopen = &Ctx.Idents.get("fopen"); - if (!II_tmpfile) - II_tmpfile = &Ctx.Idents.get("tmpfile"); - if (!II_fclose) - II_fclose = &Ctx.Idents.get("fclose"); - if (!II_fread) - II_fread = &Ctx.Idents.get("fread"); - if (!II_fwrite) - II_fwrite = &Ctx.Idents.get("fwrite"); - if (!II_fseek) - II_fseek = &Ctx.Idents.get("fseek"); - if (!II_ftell) - II_ftell = &Ctx.Idents.get("ftell"); - if (!II_rewind) - II_rewind = &Ctx.Idents.get("rewind"); - if (!II_fgetpos) - II_fgetpos = &Ctx.Idents.get("fgetpos"); - if (!II_fsetpos) - II_fsetpos = &Ctx.Idents.get("fsetpos"); - if (!II_clearerr) - II_clearerr = &Ctx.Idents.get("clearerr"); - if (!II_feof) - II_feof = &Ctx.Idents.get("feof"); - if (!II_ferror) - II_ferror = &Ctx.Idents.get("ferror"); - if (!II_fileno) - II_fileno = &Ctx.Idents.get("fileno"); - - if (FD->getIdentifier() == II_fopen) { - Fopen(C, CE); - return true; - } - if (FD->getIdentifier() == II_tmpfile) { - Tmpfile(C, CE); - return true; - } - if (FD->getIdentifier() == II_fclose) { - Fclose(C, CE); - return true; - } - if (FD->getIdentifier() == II_fread) { - Fread(C, CE); - return true; - } - if (FD->getIdentifier() == II_fwrite) { - Fwrite(C, CE); - return true; - } - if (FD->getIdentifier() == II_fseek) { - Fseek(C, CE); - return true; - } - if (FD->getIdentifier() == II_ftell) { - Ftell(C, CE); - return true; - } - if (FD->getIdentifier() == II_rewind) { - Rewind(C, CE); - return true; - } - if (FD->getIdentifier() == II_fgetpos) { - Fgetpos(C, CE); - return true; - } - if (FD->getIdentifier() == II_fsetpos) { - Fsetpos(C, CE); - return true; - } - if (FD->getIdentifier() == II_clearerr) { - Clearerr(C, CE); - return true; - } - if (FD->getIdentifier() == II_feof) { - Feof(C, CE); - return true; - } - if (FD->getIdentifier() == II_ferror) { - Ferror(C, CE); - return true; - } - if (FD->getIdentifier() == II_fileno) { - Fileno(C, CE); - return true; + for (auto P : Call.parameters()) { + QualType T = P->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + return false; } - return false; -} + const FnCheck *Callback = Callbacks.lookup(Call); + if (!Callback) + return false; -void StreamChecker::Fopen(CheckerContext &C, const CallExpr *CE) const { - OpenFileAux(C, CE); -} + (*Callback)(this, Call, C); -void StreamChecker::Tmpfile(CheckerContext &C, const CallExpr *CE) const { - OpenFileAux(C, CE); + return C.isDifferent(); } -void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { +void StreamChecker::evalFopen(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); SValBuilder &svalBuilder = C.getSValBuilder(); const LocationContext *LCtx = C.getPredecessor()->getLocationContext(); - DefinedSVal RetVal = svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, - C.blockCount()) - .castAs<DefinedSVal>(); + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) + return; + + DefinedSVal RetVal = + svalBuilder.conjureSymbolVal(nullptr, CE, LCtx, C.blockCount()) + .castAs<DefinedSVal>(); state = state->BindExpr(CE, C.getLocationContext(), RetVal); ConstraintManager &CM = C.getConstraintManager(); @@ -226,145 +153,153 @@ void StreamChecker::OpenFileAux(CheckerContext &C, const CallExpr *CE) const { 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 = - stateNotNull->set<StreamMap>(Sym,StreamState::getOpened(CE)); - stateNull = - stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed(CE)); - - C.addTransition(stateNotNull); - C.addTransition(stateNull); - } -} + SymbolRef Sym = RetVal.getAsSymbol(); + assert(Sym && "RetVal must be a symbol here."); + stateNotNull = stateNotNull->set<StreamMap>(Sym, StreamState::getOpened()); + stateNull = stateNull->set<StreamMap>(Sym, StreamState::getOpenFailed()); -void StreamChecker::Fclose(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = CheckDoubleClose(CE, C.getState(), C); - if (state) - C.addTransition(state); + C.addTransition(stateNotNull); + C.addTransition(stateNull); } -void StreamChecker::Fread(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) - return; -} +void StreamChecker::evalFreopen(const CallEvent &Call, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); -void StreamChecker::Fwrite(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(3)), state, C)) + auto *CE = dyn_cast_or_null<CallExpr>(Call.getOriginExpr()); + if (!CE) return; -} -void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!(state = CheckNullStream(C.getSVal(CE->getArg(0)), state, C))) + Optional<DefinedSVal> StreamVal = Call.getArgSVal(2).getAs<DefinedSVal>(); + if (!StreamVal) return; - // Check the legality of the 'whence' argument of 'fseek'. - SVal Whence = state->getSVal(CE->getArg(2), C.getLocationContext()); - Optional<nonloc::ConcreteInt> CI = Whence.getAs<nonloc::ConcreteInt>(); - - if (!CI) + // Do not allow NULL as passed stream pointer. + // This is not specified in the man page but may crash on some system. + checkNullStream(*StreamVal, C, State); + // Check if error was generated. + if (C.isDifferent()) return; - int64_t x = CI->getValue().getSExtValue(); - if (x >= 0 && x <= 2) + SymbolRef StreamSym = StreamVal->getAsSymbol(); + // Do not care about special values for stream ("(FILE *)0x12345"?). + if (!StreamSym) return; - if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { - if (!BT_illegalwhence) - BT_illegalwhence.reset( - new BuiltinBug(this, "Illegal whence argument", - "The whence argument to fseek() should be " - "SEEK_SET, SEEK_END, or SEEK_CUR.")); - C.emitReport(std::make_unique<PathSensitiveBugReport>( - *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); - } + // Generate state for non-failed case. + // Return value is the passed stream pointer. + // According to the documentations, the stream is closed first + // but any close error is ignored. The state changes to (or remains) opened. + ProgramStateRef StateRetNotNull = + State->BindExpr(CE, C.getLocationContext(), *StreamVal); + // Generate state for NULL return value. + // Stream switches to OpenFailed state. + ProgramStateRef StateRetNull = State->BindExpr(CE, C.getLocationContext(), + C.getSValBuilder().makeNull()); + + StateRetNotNull = + StateRetNotNull->set<StreamMap>(StreamSym, StreamState::getOpened()); + StateRetNull = + StateRetNull->set<StreamMap>(StreamSym, StreamState::getOpenFailed()); + + C.addTransition(StateRetNotNull); + C.addTransition(StateRetNull); } -void StreamChecker::Ftell(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; +void StreamChecker::evalFclose(const CallEvent &Call, CheckerContext &C) const { + ProgramStateRef State = C.getState(); + if (checkDoubleClose(Call, C, State)) + C.addTransition(State); } -void StreamChecker::Rewind(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) +void StreamChecker::evalFseek(const CallEvent &Call, CheckerContext &C) const { + const Expr *AE2 = Call.getArgExpr(2); + if (!AE2) return; -} -void StreamChecker::Fgetpos(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; -} + ProgramStateRef State = C.getState(); -void StreamChecker::Fsetpos(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) + bool StateChanged = checkNullStream(Call.getArgSVal(0), C, State); + // Check if error was generated. + if (C.isDifferent()) return; -} -void StreamChecker::Clearerr(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; -} + // Check the legality of the 'whence' argument of 'fseek'. + checkFseekWhence(State->getSVal(AE2, C.getLocationContext()), C, State); -void StreamChecker::Feof(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; -} + if (!C.isDifferent() && StateChanged) + C.addTransition(State); -void StreamChecker::Ferror(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; + return; } -void StreamChecker::Fileno(CheckerContext &C, const CallExpr *CE) const { - ProgramStateRef state = C.getState(); - if (!CheckNullStream(C.getSVal(CE->getArg(0)), state, C)) - return; +void StreamChecker::checkArgNullStream(const CallEvent &Call, CheckerContext &C, + unsigned ArgI) const { + ProgramStateRef State = C.getState(); + if (checkNullStream(Call.getArgSVal(ArgI), C, State)) + C.addTransition(State); } -ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, - CheckerContext &C) const { +bool StreamChecker::checkNullStream(SVal SV, CheckerContext &C, + ProgramStateRef &State) const { Optional<DefinedSVal> DV = SV.getAs<DefinedSVal>(); if (!DV) - return nullptr; + return false; ConstraintManager &CM = C.getConstraintManager(); - ProgramStateRef stateNotNull, stateNull; - std::tie(stateNotNull, stateNull) = CM.assumeDual(state, *DV); + ProgramStateRef StateNotNull, StateNull; + std::tie(StateNotNull, StateNull) = CM.assumeDual(C.getState(), *DV); - if (!stateNotNull && stateNull) { - if (ExplodedNode *N = C.generateErrorNode(stateNull)) { + if (!StateNotNull && StateNull) { + if (ExplodedNode *N = C.generateErrorNode(StateNull)) { if (!BT_nullfp) BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", "Stream pointer might be NULL.")); C.emitReport(std::make_unique<PathSensitiveBugReport>( *BT_nullfp, BT_nullfp->getDescription(), N)); } - return nullptr; + return false; } - return stateNotNull; + + if (StateNotNull) { + State = StateNotNull; + return true; + } + + return false; } -ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, - ProgramStateRef state, - CheckerContext &C) const { - SymbolRef Sym = C.getSVal(CE->getArg(0)).getAsSymbol(); +void StreamChecker::checkFseekWhence(SVal SV, CheckerContext &C, + ProgramStateRef &State) const { + Optional<nonloc::ConcreteInt> CI = SV.getAs<nonloc::ConcreteInt>(); + if (!CI) + return; + + int64_t X = CI->getValue().getSExtValue(); + if (X >= 0 && X <= 2) + return; + + if (ExplodedNode *N = C.generateNonFatalErrorNode(State)) { + if (!BT_illegalwhence) + BT_illegalwhence.reset( + new BuiltinBug(this, "Illegal whence argument", + "The whence argument to fseek() should be " + "SEEK_SET, SEEK_END, or SEEK_CUR.")); + C.emitReport(std::make_unique<PathSensitiveBugReport>( + *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); + } +} + +bool StreamChecker::checkDoubleClose(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const { + SymbolRef Sym = Call.getArgSVal(0).getAsSymbol(); if (!Sym) - return state; + return false; - const StreamState *SS = state->get<StreamMap>(Sym); + const StreamState *SS = State->get<StreamMap>(Sym); // If the file stream is not tracked, return. if (!SS) - return state; + return false; // Check: Double close a File Descriptor could cause undefined behaviour. // Conforming to man-pages @@ -378,19 +313,21 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, C.emitReport(std::make_unique<PathSensitiveBugReport>( *BT_doubleclose, BT_doubleclose->getDescription(), N)); } - return nullptr; + return false; } // Close the File Descriptor. - return state->set<StreamMap>(Sym, StreamState::getClosed(CE)); + State = State->set<StreamMap>(Sym, StreamState::getClosed()); + + return true; } void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, CheckerContext &C) const { - ProgramStateRef state = C.getState(); + ProgramStateRef State = C.getState(); // TODO: Clean up the state. - const StreamMapTy &Map = state->get<StreamMap>(); + const StreamMapTy &Map = State->get<StreamMap>(); for (const auto &I: Map) { SymbolRef Sym = I.first; const StreamState &SS = I.second; @@ -399,7 +336,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, ExplodedNode *N = C.generateErrorNode(); if (!N) - return; + continue; if (!BT_ResourceLeak) BT_ResourceLeak.reset( diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp index 574d4ed1e600..5b46ffb656cf 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp @@ -37,9 +37,7 @@ void taint::printTaint(ProgramStateRef State, raw_ostream &Out, const char *NL, Out << I.first << " : " << I.second << NL; } -void dumpTaint(ProgramStateRef State) { - printTaint(State, llvm::errs()); -} +void dumpTaint(ProgramStateRef State) { printTaint(State, llvm::errs()); } ProgramStateRef taint::addTaint(ProgramStateRef State, const Stmt *S, const LocationContext *LCtx, @@ -64,8 +62,8 @@ ProgramStateRef taint::addTaint(ProgramStateRef State, SVal V, // region of the parent region. if (auto LCV = V.getAs<nonloc::LazyCompoundVal>()) { if (Optional<SVal> binding = - State->getStateManager().getStoreManager() - .getDefaultBinding(*LCV)) { + State->getStateManager().getStoreManager().getDefaultBinding( + *LCV)) { if (SymbolRef Sym = binding->getAsSymbol()) return addPartialTaint(State, Sym, LCV->getRegion(), Kind); } @@ -94,6 +92,32 @@ ProgramStateRef taint::addTaint(ProgramStateRef State, SymbolRef Sym, return NewState; } +ProgramStateRef taint::removeTaint(ProgramStateRef State, SVal V) { + SymbolRef Sym = V.getAsSymbol(); + if (Sym) + return removeTaint(State, Sym); + + const MemRegion *R = V.getAsRegion(); + return removeTaint(State, R); +} + +ProgramStateRef taint::removeTaint(ProgramStateRef State, const MemRegion *R) { + if (const SymbolicRegion *SR = dyn_cast_or_null<SymbolicRegion>(R)) + return removeTaint(State, SR->getSymbol()); + return State; +} + +ProgramStateRef taint::removeTaint(ProgramStateRef State, SymbolRef Sym) { + // If this is a symbol cast, remove the cast before adding the taint. Taint + // is cast agnostic. + while (const SymbolCast *SC = dyn_cast<SymbolCast>(Sym)) + Sym = SC->getOperand(); + + ProgramStateRef NewState = State->remove<TaintMap>(Sym); + assert(NewState); + return NewState; +} + ProgramStateRef taint::addPartialTaint(ProgramStateRef State, SymbolRef ParentSym, const SubRegion *SubRegion, @@ -157,7 +181,8 @@ bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { // Traverse all the symbols this symbol depends on to see if any are tainted. for (SymExpr::symbol_iterator SI = Sym->symbol_begin(), - SE = Sym->symbol_end(); SI != SE; ++SI) { + SE = Sym->symbol_end(); + SI != SE; ++SI) { if (!isa<SymbolData>(*SI)) continue; diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.h b/clang/lib/StaticAnalyzer/Checkers/Taint.h index 8940916c1933..659a3c898d56 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.h +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.h @@ -27,34 +27,39 @@ using TaintTagType = unsigned; static constexpr TaintTagType TaintTagGeneric = 0; /// Create a new state in which the value of the statement is marked as tainted. -LLVM_NODISCARD ProgramStateRef -addTaint(ProgramStateRef State, const Stmt *S, const LocationContext *LCtx, - TaintTagType Kind = TaintTagGeneric); +LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, const Stmt *S, + const LocationContext *LCtx, + TaintTagType Kind = TaintTagGeneric); /// Create a new state in which the value is marked as tainted. -LLVM_NODISCARD ProgramStateRef -addTaint(ProgramStateRef State, SVal V, - TaintTagType Kind = TaintTagGeneric); +LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, SVal V, + TaintTagType Kind = TaintTagGeneric); /// Create a new state in which the symbol is marked as tainted. -LLVM_NODISCARD ProgramStateRef -addTaint(ProgramStateRef State, SymbolRef Sym, - TaintTagType Kind = TaintTagGeneric); +LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, SymbolRef Sym, + TaintTagType Kind = TaintTagGeneric); /// Create a new state in which the pointer represented by the region /// is marked as tainted. -LLVM_NODISCARD ProgramStateRef -addTaint(ProgramStateRef State, const MemRegion *R, - TaintTagType Kind = TaintTagGeneric); +LLVM_NODISCARD ProgramStateRef addTaint(ProgramStateRef State, + const MemRegion *R, + TaintTagType Kind = TaintTagGeneric); + +LLVM_NODISCARD ProgramStateRef removeTaint(ProgramStateRef State, SVal V); + +LLVM_NODISCARD ProgramStateRef removeTaint(ProgramStateRef State, + const MemRegion *R); + +LLVM_NODISCARD ProgramStateRef removeTaint(ProgramStateRef State, + SymbolRef Sym); /// Create a new state in a which a sub-region of a given symbol is tainted. /// This might be necessary when referring to regions that can not have an /// individual symbol, e.g. if they are represented by the default binding of /// a LazyCompoundVal. -LLVM_NODISCARD ProgramStateRef -addPartialTaint(ProgramStateRef State, - SymbolRef ParentSym, const SubRegion *SubRegion, - TaintTagType Kind = TaintTagGeneric); +LLVM_NODISCARD ProgramStateRef addPartialTaint( + ProgramStateRef State, SymbolRef ParentSym, const SubRegion *SubRegion, + TaintTagType Kind = TaintTagGeneric); /// Check if the statement has a tainted value in the given state. bool isTainted(ProgramStateRef State, const Stmt *S, @@ -99,4 +104,3 @@ public: } // namespace clang #endif - diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 12cee5f8d4f7..fd93fc33115f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -11,8 +11,9 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/Attr.h" #include "clang/AST/DeclCXX.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" diff --git a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp index 73e1a0d0000f..fdd03c75920d 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -43,6 +43,9 @@ AnalysisManager::AnalysisManager(ASTContext &ASTCtx, CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr), options(Options) { AnaCtxMgr.getCFGBuildOptions().setAllAlwaysAdd(); + AnaCtxMgr.getCFGBuildOptions().OmitImplicitValueInitializers = true; + AnaCtxMgr.getCFGBuildOptions().AddCXXDefaultInitExprInAggregates = + Options.ShouldIncludeDefaultInitForAggregates; } AnalysisManager::~AnalysisManager() { diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index 7ba93b858baf..0525b5c41e34 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -505,7 +505,7 @@ NoStoreFuncVisitor::findRegionOfInterestInRecord( // Recursively examine the base classes. // Note that following base classes does not increase the recursion depth. if (const auto *RDX = dyn_cast<CXXRecordDecl>(RD)) - for (const auto II : RDX->bases()) + for (const auto &II : RDX->bases()) if (const RecordDecl *RRD = II.getType()->getAsRecordDecl()) if (Optional<RegionVector> Out = findRegionOfInterestInRecord(RRD, State, R, Vec, depth)) @@ -1606,9 +1606,6 @@ SuppressInlineDefensiveChecksVisitor(DefinedSVal Value, const ExplodedNode *N) AnalyzerOptions &Options = N->getState()->getAnalysisManager().options; if (!Options.ShouldSuppressInlinedDefensiveChecks) IsSatisfied = true; - - assert(N->getState()->isNull(V).isConstrainedTrue() && - "The visitor only tracks the cases where V is constrained to 0"); } void SuppressInlineDefensiveChecksVisitor::Profile( @@ -1639,13 +1636,12 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, // Check if in the previous state it was feasible for this value // to *not* be null. - if (!Pred->getState()->isNull(V).isConstrainedTrue()) { + if (!Pred->getState()->isNull(V).isConstrainedTrue() && + Succ->getState()->isNull(V).isConstrainedTrue()) { IsSatisfied = true; - assert(Succ->getState()->isNull(V).isConstrainedTrue()); - // Check if this is inlined defensive checks. - const LocationContext *CurLC =Succ->getLocationContext(); + const LocationContext *CurLC = Succ->getLocationContext(); const LocationContext *ReportLC = BR.getErrorNode()->getLocationContext(); if (CurLC != ReportLC && !CurLC->isParentOf(ReportLC)) { BR.markInvalid("Suppress IDC", CurLC); @@ -2012,11 +2008,16 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, // Add visitor, which will suppress inline defensive checks. if (auto DV = V.getAs<DefinedSVal>()) - if (!DV->isZeroConstant() && LVState->isNull(*DV).isConstrainedTrue() && - EnableNullFPSuppression) + if (!DV->isZeroConstant() && EnableNullFPSuppression) { + // Note that LVNode may be too late (i.e., too far from the InputNode) + // because the lvalue may have been computed before the inlined call + // was evaluated. InputNode may as well be too early here, because + // the symbol is already dead; this, however, is fine because we can + // still find the node in which it collapsed to null previously. report.addVisitor( - std::make_unique<SuppressInlineDefensiveChecksVisitor>(*DV, - LVNode)); + std::make_unique<SuppressInlineDefensiveChecksVisitor>( + *DV, InputNode)); + } if (auto KV = V.getAs<KnownSVal>()) report.addVisitor(std::make_unique<FindLastStoreBRVisitor>( diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 5f04a59ba055..168d6fe6ec48 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/Attr.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" @@ -29,20 +30,20 @@ #include "clang/Analysis/CFGStmtMap.h" #include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" -#include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" +#include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SValBuilder.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "clang/StaticAnalyzer/Core/PathSensitive/Store.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/DenseMap.h" @@ -519,7 +520,7 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // TODO: Support allocator calls. if (Call.getKind() != CE_CXXAllocator) - if (Call.isArgumentConstructedDirectly(Idx)) + if (Call.isArgumentConstructedDirectly(Call.getASTArgumentIndex(Idx))) continue; // TODO: Allocators should receive the correct size and possibly alignment, @@ -1080,7 +1081,7 @@ ObjCMessageKind ObjCMethodCall::getMessageKind() const { const ObjCPropertyDecl *ObjCMethodCall::getAccessedProperty() const { // Look for properties accessed with property syntax (foo.bar = ...) - if ( getMessageKind() == OCM_PropertyAccess) { + if (getMessageKind() == OCM_PropertyAccess) { const PseudoObjectExpr *POE = getContainingPseudoObjectExpr(); assert(POE && "Property access without PseudoObjectExpr?"); @@ -1309,6 +1310,8 @@ RuntimeDefinition ObjCMethodCall::getRuntimeDefinition() const { } const ObjCMethodDecl *MD = Val.getValue(); + if (MD && !MD->hasBody()) + MD = MD->getCanonicalDecl(); if (CanBeSubClassed) return RuntimeDefinition(MD, Receiver); else diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index f676bd895283..a9361837cf68 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -91,7 +91,7 @@ void CheckerManager::runCheckersOnASTDecl(const Decl *D, AnalysisManager& mgr, } assert(checkers); - for (const auto checker : *checkers) + for (const auto &checker : *checkers) checker(D, mgr, BR); } @@ -99,7 +99,7 @@ void CheckerManager::runCheckersOnASTBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) { assert(D && D->hasBody()); - for (const auto BodyChecker : BodyCheckers) + for (const auto &BodyChecker : BodyCheckers) BodyChecker(D, mgr, BR); } @@ -402,7 +402,7 @@ void CheckerManager::runCheckersForBind(ExplodedNodeSet &Dst, void CheckerManager::runCheckersForEndAnalysis(ExplodedGraph &G, BugReporter &BR, ExprEngine &Eng) { - for (const auto EndAnalysisChecker : EndAnalysisCheckers) + for (const auto &EndAnalysisChecker : EndAnalysisCheckers) EndAnalysisChecker(G, BR, Eng); } @@ -455,7 +455,7 @@ void CheckerManager::runCheckersForEndFunction(NodeBuilderContext &BC, // creates a successor for Pred, we do not need to generate an // autotransition for it. NodeBuilder Bldr(Pred, Dst, BC); - for (const auto checkFn : EndFunctionCheckers) { + for (const auto &checkFn : EndFunctionCheckers) { const ProgramPoint &L = FunctionExitPoint(RS, Pred->getLocationContext(), checkFn.Checker); CheckerContext C(Bldr, Eng, Pred, L); @@ -542,7 +542,7 @@ void CheckerManager::runCheckersForNewAllocator( /// Run checkers for live symbols. void CheckerManager::runCheckersForLiveSymbols(ProgramStateRef state, SymbolReaper &SymReaper) { - for (const auto LiveSymbolsChecker : LiveSymbolsCheckers) + for (const auto &LiveSymbolsChecker : LiveSymbolsCheckers) LiveSymbolsChecker(state, SymReaper); } @@ -599,7 +599,7 @@ CheckerManager::runCheckersForRegionChanges(ProgramStateRef state, ArrayRef<const MemRegion *> Regions, const LocationContext *LCtx, const CallEvent *Call) { - for (const auto RegionChangesChecker : RegionChangesCheckers) { + for (const auto &RegionChangesChecker : RegionChangesCheckers) { // If any checker declares the state infeasible (or if it starts that way), // bail out. if (!state) @@ -621,7 +621,7 @@ CheckerManager::runCheckersForPointerEscape(ProgramStateRef State, (Kind != PSK_DirectEscapeOnCall && Kind != PSK_IndirectEscapeOnCall)) && "Call must not be NULL when escaping on call"); - for (const auto PointerEscapeChecker : PointerEscapeCheckers) { + for (const auto &PointerEscapeChecker : PointerEscapeCheckers) { // If any checker declares the state infeasible (or if it starts that // way), bail out. if (!State) @@ -635,7 +635,7 @@ CheckerManager::runCheckersForPointerEscape(ProgramStateRef State, ProgramStateRef CheckerManager::runCheckersForEvalAssume(ProgramStateRef state, SVal Cond, bool Assumption) { - for (const auto EvalAssumeChecker : EvalAssumeCheckers) { + for (const auto &EvalAssumeChecker : EvalAssumeCheckers) { // If any checker declares the state infeasible (or if it starts that way), // bail out. if (!state) @@ -658,7 +658,7 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, NodeBuilder B(Pred, checkDst, Eng.getBuilderContext()); // Check if any of the EvalCall callbacks can evaluate the call. - for (const auto EvalCallChecker : EvalCallCheckers) { + for (const auto &EvalCallChecker : EvalCallCheckers) { // TODO: Support the situation when the call doesn't correspond // to any Expr. ProgramPoint L = ProgramPoint::getProgramPoint( @@ -697,7 +697,7 @@ void CheckerManager::runCheckersOnEndOfTranslationUnit( const TranslationUnitDecl *TU, AnalysisManager &mgr, BugReporter &BR) { - for (const auto EndOfTranslationUnitChecker : EndOfTranslationUnitCheckers) + for (const auto &EndOfTranslationUnitChecker : EndOfTranslationUnitCheckers) EndOfTranslationUnitChecker(TU, mgr, BR); } @@ -904,6 +904,6 @@ CheckerManager::getCachedStmtCheckersFor(const Stmt *S, bool isPreVisit) { } CheckerManager::~CheckerManager() { - for (const auto CheckerDtor : CheckerDtors) + for (const auto &CheckerDtor : CheckerDtors) CheckerDtor(); } diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index e92e95354f5f..f917a4c8637b 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -728,7 +728,8 @@ void ExprEngine::removeDead(ExplodedNode *Pred, ExplodedNodeSet &Out, // Create a state in which dead bindings are removed from the environment // and the store. TODO: The function should just return new env and store, // not a new state. - CleanedState = StateMgr.removeDeadBindings(CleanedState, SFC, SymReaper); + CleanedState = StateMgr.removeDeadBindingsFromEnvironmentAndStore( + CleanedState, SFC, SymReaper); // Process any special transfer function for dead symbols. // A tag to track convenience transitions, which can be removed at cleanup. @@ -1171,13 +1172,16 @@ void ExprEngine::VisitCXXBindTemporaryExpr(const CXXBindTemporaryExpr *BTE, } } -ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V, - PointerEscapeKind K) const { +ProgramStateRef ExprEngine::escapeValues(ProgramStateRef State, + ArrayRef<SVal> Vs, + PointerEscapeKind K, + const CallEvent *Call) const { class CollectReachableSymbolsCallback final : public SymbolVisitor { - InvalidatedSymbols Symbols; + InvalidatedSymbols &Symbols; public: - explicit CollectReachableSymbolsCallback(ProgramStateRef) {} + explicit CollectReachableSymbolsCallback(InvalidatedSymbols &Symbols) + : Symbols(Symbols) {} const InvalidatedSymbols &getSymbols() const { return Symbols; } @@ -1186,11 +1190,13 @@ ProgramStateRef ExprEngine::escapeValue(ProgramStateRef State, SVal V, return true; } }; + InvalidatedSymbols Symbols; + CollectReachableSymbolsCallback CallBack(Symbols); + for (SVal V : Vs) + State->scanReachableSymbols(V, CallBack); - const CollectReachableSymbolsCallback &Scanner = - State->scanReachableSymbols<CollectReachableSymbolsCallback>(V); return getCheckerManager().runCheckersForPointerEscape( - State, Scanner.getSymbols(), /*CallEvent*/ nullptr, K, nullptr); + State, CallBack.getSymbols(), Call, K, nullptr); } void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, @@ -1245,6 +1251,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPParallelForDirectiveClass: case Stmt::OMPParallelForSimdDirectiveClass: case Stmt::OMPParallelSectionsDirectiveClass: + case Stmt::OMPParallelMasterDirectiveClass: case Stmt::OMPTaskDirectiveClass: case Stmt::OMPTaskyieldDirectiveClass: case Stmt::OMPBarrierDirectiveClass: @@ -1268,6 +1275,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::OMPMasterTaskLoopDirectiveClass: case Stmt::OMPMasterTaskLoopSimdDirectiveClass: case Stmt::OMPParallelMasterTaskLoopDirectiveClass: + case Stmt::OMPParallelMasterTaskLoopSimdDirectiveClass: case Stmt::OMPDistributeDirectiveClass: case Stmt::OMPDistributeParallelForDirectiveClass: case Stmt::OMPDistributeParallelForSimdDirectiveClass: @@ -1313,6 +1321,11 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::WhileStmtClass: case Expr::MSDependentExistsStmtClass: llvm_unreachable("Stmt should not be in analyzer evaluation loop"); + case Stmt::ImplicitValueInitExprClass: + // These nodes are shared in the CFG and would case caching out. + // Moreover, no additional evaluation required for them, the + // analyzer can reconstruct these values from the AST. + llvm_unreachable("Should be pruned from CFG"); case Stmt::ObjCSubscriptRefExprClass: case Stmt::ObjCPropertyRefExprClass: @@ -1383,7 +1396,6 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, case Stmt::IntegerLiteralClass: case Stmt::FixedPointLiteralClass: case Stmt::CharacterLiteralClass: - case Stmt::ImplicitValueInitExprClass: case Stmt::CXXScalarValueInitExprClass: case Stmt::CXXBoolLiteralExprClass: case Stmt::ObjCBoolLiteralExprClass: @@ -1426,7 +1438,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, bool IsTemporary = false; if (const auto *MTE = dyn_cast<MaterializeTemporaryExpr>(ArgE)) { - ArgE = MTE->GetTemporaryExpr(); + ArgE = MTE->getSubExpr(); IsTemporary = true; } @@ -1481,7 +1493,7 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred, for (auto Child : Ex->children()) { assert(Child); SVal Val = State->getSVal(Child, LCtx); - State = escapeValue(State, Val, PSK_EscapeOther); + State = escapeValues(State, Val, PSK_EscapeOther); } Bldr2.generateNode(S, N, State); @@ -2682,33 +2694,52 @@ void ExprEngine::VisitAtomicExpr(const AtomicExpr *AE, ExplodedNode *Pred, // destructor. We won't see the destructor during analysis, but it's there. // (4) We are binding to a MemRegion with stack storage that the store // does not understand. +ProgramStateRef ExprEngine::processPointerEscapedOnBind( + ProgramStateRef State, ArrayRef<std::pair<SVal, SVal>> LocAndVals, + const LocationContext *LCtx, PointerEscapeKind Kind, + const CallEvent *Call) { + SmallVector<SVal, 8> Escaped; + for (const std::pair<SVal, SVal> &LocAndVal : LocAndVals) { + // Cases (1) and (2). + const MemRegion *MR = LocAndVal.first.getAsRegion(); + if (!MR || !MR->hasStackStorage()) { + Escaped.push_back(LocAndVal.second); + continue; + } + + // Case (3). + if (const auto *VR = dyn_cast<VarRegion>(MR->getBaseRegion())) + if (VR->hasStackParametersStorage() && VR->getStackFrame()->inTopFrame()) + if (const auto *RD = VR->getValueType()->getAsCXXRecordDecl()) + if (!RD->hasTrivialDestructor()) { + Escaped.push_back(LocAndVal.second); + continue; + } + + // Case (4): in order to test that, generate a new state with the binding + // added. If it is the same state, then it escapes (since the store cannot + // represent the binding). + // Do this only if we know that the store is not supposed to generate the + // same state. + SVal StoredVal = State->getSVal(MR); + if (StoredVal != LocAndVal.second) + if (State == + (State->bindLoc(loc::MemRegionVal(MR), LocAndVal.second, LCtx))) + Escaped.push_back(LocAndVal.second); + } + + if (Escaped.empty()) + return State; + + return escapeValues(State, Escaped, Kind, Call); +} + ProgramStateRef ExprEngine::processPointerEscapedOnBind(ProgramStateRef State, SVal Loc, SVal Val, const LocationContext *LCtx) { - - // Cases (1) and (2). - const MemRegion *MR = Loc.getAsRegion(); - if (!MR || !MR->hasStackStorage()) - return escapeValue(State, Val, PSK_EscapeOnBind); - - // Case (3). - if (const auto *VR = dyn_cast<VarRegion>(MR->getBaseRegion())) - if (VR->hasStackParametersStorage() && VR->getStackFrame()->inTopFrame()) - if (const auto *RD = VR->getValueType()->getAsCXXRecordDecl()) - if (!RD->hasTrivialDestructor()) - return escapeValue(State, Val, PSK_EscapeOnBind); - - // Case (4): in order to test that, generate a new state with the binding - // added. If it is the same state, then it escapes (since the store cannot - // represent the binding). - // Do this only if we know that the store is not supposed to generate the - // same state. - SVal StoredVal = State->getSVal(MR); - if (StoredVal != Val) - if (State == (State->bindLoc(loc::MemRegionVal(MR), Val, LCtx))) - return escapeValue(State, Val, PSK_EscapeOnBind); - - return State; + std::pair<SVal, SVal> LocAndVal(Loc, Val); + return processPointerEscapedOnBind(State, LocAndVal, LCtx, PSK_EscapeOnBind, + nullptr); } ProgramStateRef diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index 02a398c77ac8..b17f26aa9c53 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -102,8 +102,8 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, state = state->BindExpr(B, LCtx, Result); } else { // If we cannot evaluate the operation escape the operands. - state = escapeValue(state, LeftV, PSK_EscapeOther); - state = escapeValue(state, RightV, PSK_EscapeOther); + state = escapeValues(state, LeftV, PSK_EscapeOther); + state = escapeValues(state, RightV, PSK_EscapeOther); } Bldr.generateNode(B, *it, state); @@ -275,7 +275,7 @@ ProgramStateRef ExprEngine::handleLValueBitCast( V = evalMinus(V); state = state->BindExpr(CastE, LCtx, V); if (V.isUnknown() && !OrigV.isUnknown()) { - state = escapeValue(state, OrigV, PSK_EscapeOther); + state = escapeValues(state, OrigV, PSK_EscapeOther); } Bldr.generateNode(CastE, Pred, state); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 058be985540d..b816aab7c18f 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -27,7 +27,7 @@ void ExprEngine::CreateCXXTemporaryObject(const MaterializeTemporaryExpr *ME, ExplodedNode *Pred, ExplodedNodeSet &Dst) { StmtNodeBuilder Bldr(Pred, Dst, *currBldrCtx); - const Expr *tempExpr = ME->GetTemporaryExpr()->IgnoreParens(); + const Expr *tempExpr = ME->getSubExpr()->IgnoreParens(); ProgramStateRef state = Pred->getState(); const LocationContext *LCtx = Pred->getLocationContext(); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 345d4d817dea..01a371e664b2 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/AST/Decl.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h" #include "PrettyStackTraceLocationContext.h" #include "clang/AST/CXXInheritance.h" @@ -592,9 +593,45 @@ void ExprEngine::evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, for (auto I : dstCallEvaluated) finishArgumentConstruction(dstArgumentCleanup, I, Call); - // Finally, run any post-call checks. - getCheckerManager().runCheckersForPostCall(Dst, dstArgumentCleanup, + ExplodedNodeSet dstPostCall; + getCheckerManager().runCheckersForPostCall(dstPostCall, dstArgumentCleanup, Call, *this); + + // Escaping symbols conjured during invalidating the regions above. + // Note that, for inlined calls the nodes were put back into the worklist, + // so we can assume that every node belongs to a conservative call at this + // point. + + // Run pointerEscape callback with the newly conjured symbols. + SmallVector<std::pair<SVal, SVal>, 8> Escaped; + for (auto I : dstPostCall) { + NodeBuilder B(I, Dst, *currBldrCtx); + ProgramStateRef State = I->getState(); + Escaped.clear(); + { + unsigned Arg = -1; + for (const ParmVarDecl *PVD : Call.parameters()) { + ++Arg; + QualType ParamTy = PVD->getType(); + if (ParamTy.isNull() || + (!ParamTy->isPointerType() && !ParamTy->isReferenceType())) + continue; + QualType Pointee = ParamTy->getPointeeType(); + if (Pointee.isConstQualified() || Pointee->isVoidType()) + continue; + if (const MemRegion *MR = Call.getArgSVal(Arg).getAsRegion()) + Escaped.emplace_back(loc::MemRegionVal(MR), State->getSVal(MR, Pointee)); + } + } + + State = processPointerEscapedOnBind(State, Escaped, I->getLocationContext(), + PSK_EscapeOutParameters, &Call); + + if (State == I->getState()) + Dst.insert(I); + else + B.generateNode(I->getLocation(), State, I); + } } ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, @@ -670,8 +707,7 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, // Conservatively evaluate call by invalidating regions and binding // a conjured return value. void ExprEngine::conservativeEvalCall(const CallEvent &Call, NodeBuilder &Bldr, - ExplodedNode *Pred, - ProgramStateRef State) { + ExplodedNode *Pred, ProgramStateRef State) { State = Call.invalidateRegions(currBldrCtx->blockCount(), State); State = bindReturnValue(Call, Pred->getLocationContext(), State); diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index f50d82de3b28..14006f79fd0f 100644 --- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -91,10 +91,9 @@ ProgramStateManager::~ProgramStateManager() { I->second.second(I->second.first); } -ProgramStateRef -ProgramStateManager::removeDeadBindings(ProgramStateRef state, - const StackFrameContext *LCtx, - SymbolReaper& SymReaper) { +ProgramStateRef ProgramStateManager::removeDeadBindingsFromEnvironmentAndStore( + ProgramStateRef state, const StackFrameContext *LCtx, + SymbolReaper &SymReaper) { // This code essentially performs a "mark-and-sweep" of the VariableBindings. // The roots are any Block-level exprs and Decls that our liveness algorithm @@ -112,8 +111,7 @@ ProgramStateManager::removeDeadBindings(ProgramStateRef state, NewState.setStore(newStore); SymReaper.setReapedStore(newStore); - ProgramStateRef Result = getPersistentState(NewState); - return ConstraintMgr->removeDeadBindings(Result, SymReaper); + return getPersistentState(NewState); } ProgramStateRef ProgramState::bindLoc(Loc LV, diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 5d2ef59e2d66..4797f564a837 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -1951,7 +1951,8 @@ RegionStoreManager::getBindingForFieldOrElementCommon(RegionBindingsConstRef B, if (hasSymbolicIndex) return UnknownVal(); - if (!hasPartialLazyBinding) + // Additionally allow introspection of a block's internal layout. + if (!hasPartialLazyBinding && !isa<BlockDataRegion>(R->getBaseRegion())) return UndefinedVal(); } diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 190ab7e21dbc..12332aaf936f 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringMap.h" +#include "llvm/Support/ConvertUTF.h" #include "llvm/Support/JSON.h" #include "llvm/Support/Path.h" @@ -27,10 +28,12 @@ using namespace ento; namespace { class SarifDiagnostics : public PathDiagnosticConsumer { std::string OutputFile; + const LangOptions &LO; public: - SarifDiagnostics(AnalyzerOptions &, const std::string &Output) - : OutputFile(Output) {} + SarifDiagnostics(AnalyzerOptions &, const std::string &Output, + const LangOptions &LO) + : OutputFile(Output), LO(LO) {} ~SarifDiagnostics() override = default; void FlushDiagnosticsImpl(std::vector<const PathDiagnostic *> &Diags, @@ -45,9 +48,9 @@ public: void ento::createSarifDiagnosticConsumer( AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, - const std::string &Output, const Preprocessor &, + const std::string &Output, const Preprocessor &PP, const cross_tu::CrossTranslationUnitContext &) { - C.push_back(new SarifDiagnostics(AnalyzerOpts, Output)); + C.push_back(new SarifDiagnostics(AnalyzerOpts, Output, PP.getLangOpts())); } static StringRef getFileName(const FileEntry &FE) { @@ -142,26 +145,56 @@ static json::Object createArtifactLocation(const FileEntry &FE, return json::Object{{"uri", FileURI}, {"index", Index}}; } -static json::Object createTextRegion(SourceRange R, const SourceManager &SM) { +static unsigned int adjustColumnPos(const SourceManager &SM, SourceLocation Loc, + unsigned int TokenLen = 0) { + assert(!Loc.isInvalid() && "invalid Loc when adjusting column position"); + + std::pair<FileID, unsigned> LocInfo = SM.getDecomposedExpansionLoc(Loc); + assert(LocInfo.second > SM.getExpansionColumnNumber(Loc) && + "position in file is before column number?"); + + bool InvalidBuffer = false; + const MemoryBuffer *Buf = SM.getBuffer(LocInfo.first, &InvalidBuffer); + assert(!InvalidBuffer && "got an invalid buffer for the location's file"); + assert(Buf->getBufferSize() >= (LocInfo.second + TokenLen) && + "token extends past end of buffer?"); + + // Adjust the offset to be the start of the line, since we'll be counting + // Unicode characters from there until our column offset. + unsigned int Off = LocInfo.second - (SM.getExpansionColumnNumber(Loc) - 1); + unsigned int Ret = 1; + while (Off < (LocInfo.second + TokenLen)) { + Off += getNumBytesForUTF8(Buf->getBuffer()[Off]); + Ret++; + } + + return Ret; +} + +static json::Object createTextRegion(const LangOptions &LO, SourceRange R, + const SourceManager &SM) { json::Object Region{ {"startLine", SM.getExpansionLineNumber(R.getBegin())}, - {"startColumn", SM.getExpansionColumnNumber(R.getBegin())}, + {"startColumn", adjustColumnPos(SM, R.getBegin())}, }; if (R.getBegin() == R.getEnd()) { - Region["endColumn"] = SM.getExpansionColumnNumber(R.getBegin()); + Region["endColumn"] = adjustColumnPos(SM, R.getBegin()); } else { Region["endLine"] = SM.getExpansionLineNumber(R.getEnd()); - Region["endColumn"] = SM.getExpansionColumnNumber(R.getEnd()) + 1; + Region["endColumn"] = adjustColumnPos( + SM, R.getEnd(), + Lexer::MeasureTokenLength(R.getEnd(), SM, LO)); } return Region; } -static json::Object createPhysicalLocation(SourceRange R, const FileEntry &FE, +static json::Object createPhysicalLocation(const LangOptions &LO, + SourceRange R, const FileEntry &FE, const SourceManager &SMgr, json::Array &Artifacts) { return json::Object{ {{"artifactLocation", createArtifactLocation(FE, Artifacts)}, - {"region", createTextRegion(R, SMgr)}}}; + {"region", createTextRegion(LO, R, SMgr)}}}; } enum class Importance { Important, Essential, Unimportant }; @@ -213,7 +246,8 @@ static Importance calculateImportance(const PathDiagnosticPiece &Piece) { return Importance::Unimportant; } -static json::Object createThreadFlow(const PathPieces &Pieces, +static json::Object createThreadFlow(const LangOptions &LO, + const PathPieces &Pieces, json::Array &Artifacts) { const SourceManager &SMgr = Pieces.front()->getLocation().getManager(); json::Array Locations; @@ -221,7 +255,7 @@ static json::Object createThreadFlow(const PathPieces &Pieces, const PathDiagnosticLocation &P = Piece->getLocation(); Locations.push_back(createThreadFlowLocation( createLocation(createPhysicalLocation( - P.asRange(), + LO, P.asRange(), *P.asLocation().getExpansionLoc().getFileEntry(), SMgr, Artifacts), Piece->getString()), @@ -230,13 +264,15 @@ static json::Object createThreadFlow(const PathPieces &Pieces, return json::Object{{"locations", std::move(Locations)}}; } -static json::Object createCodeFlow(const PathPieces &Pieces, +static json::Object createCodeFlow(const LangOptions &LO, + const PathPieces &Pieces, json::Array &Artifacts) { return json::Object{ - {"threadFlows", json::Array{createThreadFlow(Pieces, Artifacts)}}}; + {"threadFlows", json::Array{createThreadFlow(LO, Pieces, Artifacts)}}}; } -static json::Object createResult(const PathDiagnostic &Diag, +static json::Object createResult(const LangOptions &LO, + const PathDiagnostic &Diag, json::Array &Artifacts, const StringMap<unsigned> &RuleMapping) { const PathPieces &Path = Diag.path.flatten(false); @@ -247,10 +283,10 @@ static json::Object createResult(const PathDiagnostic &Diag, return json::Object{ {"message", createMessage(Diag.getVerboseDescription())}, - {"codeFlows", json::Array{createCodeFlow(Path, Artifacts)}}, + {"codeFlows", json::Array{createCodeFlow(LO, Path, Artifacts)}}, {"locations", json::Array{createLocation(createPhysicalLocation( - Diag.getLocation().asRange(), + LO, Diag.getLocation().asRange(), *Diag.getLocation().asLocation().getExpansionLoc().getFileEntry(), SMgr, Artifacts))}}, {"ruleIndex", Iter->getValue()}, @@ -320,18 +356,20 @@ static json::Object createTool(std::vector<const PathDiagnostic *> &Diags, {"rules", createRules(Diags, RuleMapping)}}}}; } -static json::Object createRun(std::vector<const PathDiagnostic *> &Diags) { +static json::Object createRun(const LangOptions &LO, + std::vector<const PathDiagnostic *> &Diags) { json::Array Results, Artifacts; StringMap<unsigned> RuleMapping; json::Object Tool = createTool(Diags, RuleMapping); llvm::for_each(Diags, [&](const PathDiagnostic *D) { - Results.push_back(createResult(*D, Artifacts, RuleMapping)); + Results.push_back(createResult(LO, *D, Artifacts, RuleMapping)); }); return json::Object{{"tool", std::move(Tool)}, {"results", std::move(Results)}, - {"artifacts", std::move(Artifacts)}}; + {"artifacts", std::move(Artifacts)}, + {"columnKind", "unicodeCodePoints"}}; } void SarifDiagnostics::FlushDiagnosticsImpl( @@ -351,6 +389,6 @@ void SarifDiagnostics::FlushDiagnosticsImpl( {"$schema", "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"}, {"version", "2.1.0"}, - {"runs", json::Array{createRun(Diags)}}}; + {"runs", json::Array{createRun(LO, Diags)}}}; OS << llvm::formatv("{0:2}\n", json::Value(std::move(Sarif))); } diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp index b4ab6877726c..b33129c88cea 100644 --- a/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -393,6 +393,11 @@ SVal StoreManager::attemptDownCast(SVal Base, QualType TargetType, return UnknownVal(); } +static bool hasSameUnqualifiedPointeeType(QualType ty1, QualType ty2) { + return ty1->getPointeeType().getCanonicalType().getTypePtr() == + ty2->getPointeeType().getCanonicalType().getTypePtr(); +} + /// CastRetrievedVal - Used by subclasses of StoreManager to implement /// implicit casts that arise from loads from regions that are reinterpreted /// as another region. @@ -421,10 +426,11 @@ SVal StoreManager::CastRetrievedVal(SVal V, const TypedValueRegion *R, // FIXME: We really need a single good function to perform casts for us // correctly every time we need it. if (castTy->isPointerType() && !castTy->isVoidPointerType()) - if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(V.getAsRegion())) - if (SR->getSymbol()->getType().getCanonicalType() != - castTy.getCanonicalType()) - return loc::MemRegionVal(castRegion(SR, castTy)); + if (const auto *SR = dyn_cast_or_null<SymbolicRegion>(V.getAsRegion())) { + QualType sr = SR->getSymbol()->getType(); + if (!hasSameUnqualifiedPointeeType(sr, castTy)) + return loc::MemRegionVal(castRegion(SR, castTy)); + } return svalBuilder.dispatchCast(V, castTy); } diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index 8236907ea773..fea8100c3b3b 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -722,13 +722,6 @@ std::string AnalysisConsumer::getFunctionName(const Decl *D) { } else if (const auto *OCD = dyn_cast<ObjCCategoryImplDecl>(DC)) { OS << OCD->getClassInterface()->getName() << '(' << OCD->getName() << ')'; - } else if (isa<ObjCProtocolDecl>(DC)) { - // We can extract the type of the class from the self pointer. - if (ImplicitParamDecl *SelfDecl = OMD->getSelfDecl()) { - QualType ClassTy = - cast<ObjCObjectPointerType>(SelfDecl->getType())->getPointeeType(); - ClassTy.print(OS, PrintingPolicy(LangOptions())); - } } OS << ' ' << OMD->getSelector().getAsString() << ']'; diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index e00fd976f6b8..f5c05281adab 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -591,7 +591,8 @@ void CheckerRegistry::printCheckerOptionList(raw_ostream &Out) const { /*MinLineWidth*/ 90); Out << "\n\n"; }; - for (const std::pair<StringRef, const CmdLineOption &> &Entry : OptionMap) { + for (const std::pair<const StringRef, const CmdLineOption &> &Entry : + OptionMap) { const CmdLineOption &Option = Entry.second; std::string FullOption = (Entry.first + ":" + Option.OptionName).str(); diff --git a/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp b/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp index 687fda75db48..7baae6778ebd 100644 --- a/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/ModelInjector.cpp @@ -11,6 +11,7 @@ #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LangStandard.h" #include "clang/Basic/Stack.h" +#include "clang/AST/DeclObjC.h" #include "clang/Frontend/ASTUnit.h" #include "clang/Frontend/CompilerInstance.h" #include "clang/Frontend/FrontendAction.h" |