https://github.com/necto created https://github.com/llvm/llvm-project/pull/113908
Model: - `wcscpy` - `wcsncpy` - `wcscat` - `wcsncat` - `swprintf` - `wmemset` - `wcscmp` (partially) - `wcsncmp` (partially) All models draw from their regular-char counterparts. Additionally, `sprintf`, `snprintf`, and `swprintf` now report `nullptr` passed as the destination buffer. CPP-5751 >From cb27ac689166f255104193052479a25598c9fa6b Mon Sep 17 00:00:00 2001 From: Arseniy Zaostrovnykh <arseniy.zaostrovn...@sonarsource.com> Date: Thu, 24 Oct 2024 09:54:27 +0200 Subject: [PATCH] Model some wchar_t versions of C string standard functions Model: - `wcscpy` - `wcsncpy` - `wcscat` - `wcsncat` - `swprintf` - `wmemset` - `wcscmp` (partially) - `wcsncmp` (partially) All models draw from their regular-char counterparts. Additionally, `sprintf`, `snprintf`, and `swprintf` now report `nullptr` passed as the destination buffer. CPP-5751 --- .../Checkers/CStringChecker.cpp | 394 +++++--- clang/test/Analysis/string.cpp | 4 + clang/test/Analysis/wstring-suppress-oob.c | 160 ++++ clang/test/Analysis/wstring.c | 885 +++++++++++++++++- 4 files changed, 1310 insertions(+), 133 deletions(-) create mode 100644 clang/test/Analysis/wstring-suppress-oob.c diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 21a2d8828249d1..fce958a3fd3698 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "InterCheckerAPI.h" +#include "clang/AST/CharUnits.h" #include "clang/AST/OperationKinds.h" #include "clang/Basic/Builtins.h" #include "clang/Basic/CharInfo.h" @@ -128,26 +129,39 @@ class CStringChecker : public Checker< eval::Call, using FnCheck = std::function<void(const CStringChecker *, CheckerContext &, const CallEvent &)>; + template <typename Handler> static FnCheck forRegularChars(Handler fn) { + return [fn](const CStringChecker *Checker, CheckerContext &C, + const CallEvent &Call) { (Checker->*fn)(C, Call, CK_Regular); }; + } + + template <typename Handler> static FnCheck forWideChars(Handler fn) { + return [fn](const CStringChecker *Checker, CheckerContext &C, + const CallEvent &Call) { (Checker->*fn)(C, Call, CK_Wide); }; + } + CallDescriptionMap<FnCheck> Callbacks = { {{CDM::CLibraryMaybeHardened, {"memcpy"}, 3}, - std::bind(&CStringChecker::evalMemcpy, _1, _2, _3, CK_Regular)}, + forRegularChars(&CStringChecker::evalMemcpy)}, {{CDM::CLibraryMaybeHardened, {"wmemcpy"}, 3}, - std::bind(&CStringChecker::evalMemcpy, _1, _2, _3, CK_Wide)}, + forWideChars(&CStringChecker::evalMemcpy)}, {{CDM::CLibraryMaybeHardened, {"mempcpy"}, 3}, - std::bind(&CStringChecker::evalMempcpy, _1, _2, _3, CK_Regular)}, + forRegularChars(&CStringChecker::evalMempcpy)}, {{CDM::CLibraryMaybeHardened, {"wmempcpy"}, 3}, - std::bind(&CStringChecker::evalMempcpy, _1, _2, _3, CK_Wide)}, + forWideChars(&CStringChecker::evalMempcpy)}, {{CDM::CLibrary, {"memcmp"}, 3}, - std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)}, + forRegularChars(&CStringChecker::evalMemcmp)}, {{CDM::CLibrary, {"wmemcmp"}, 3}, - std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Wide)}, + forWideChars(&CStringChecker::evalMemcmp)}, {{CDM::CLibraryMaybeHardened, {"memmove"}, 3}, - std::bind(&CStringChecker::evalMemmove, _1, _2, _3, CK_Regular)}, + forRegularChars(&CStringChecker::evalMemmove)}, {{CDM::CLibraryMaybeHardened, {"wmemmove"}, 3}, - std::bind(&CStringChecker::evalMemmove, _1, _2, _3, CK_Wide)}, + forWideChars(&CStringChecker::evalMemmove)}, {{CDM::CLibraryMaybeHardened, {"memset"}, 3}, - &CStringChecker::evalMemset}, - {{CDM::CLibrary, {"explicit_memset"}, 3}, &CStringChecker::evalMemset}, + forRegularChars(&CStringChecker::evalMemset)}, + {{CDM::CLibraryMaybeHardened, {"wmemset"}, 3}, + forWideChars(&CStringChecker::evalMemset)}, + {{CDM::CLibrary, {"explicit_memset"}, 3}, + forRegularChars(&CStringChecker::evalMemset)}, // FIXME: C23 introduces 'memset_explicit', maybe also model that {{CDM::CLibraryMaybeHardened, {"strcpy"}, 2}, &CStringChecker::evalStrcpy}, @@ -157,26 +171,40 @@ class CStringChecker : public Checker< eval::Call, &CStringChecker::evalStpcpy}, {{CDM::CLibraryMaybeHardened, {"strlcpy"}, 3}, &CStringChecker::evalStrlcpy}, + {{CDM::CLibraryMaybeHardened, {"wcscpy"}, 2}, + &CStringChecker::evalWcscpy}, + {{CDM::CLibraryMaybeHardened, {"wcsncpy"}, 3}, + &CStringChecker::evalWcsncpy}, {{CDM::CLibraryMaybeHardened, {"strcat"}, 2}, &CStringChecker::evalStrcat}, {{CDM::CLibraryMaybeHardened, {"strncat"}, 3}, &CStringChecker::evalStrncat}, {{CDM::CLibraryMaybeHardened, {"strlcat"}, 3}, &CStringChecker::evalStrlcat}, + {{CDM::CLibraryMaybeHardened, {"wcscat"}, 2}, + &CStringChecker::evalWcscat}, + {{CDM::CLibraryMaybeHardened, {"wcsncat"}, 3}, + &CStringChecker::evalWcsncat}, {{CDM::CLibraryMaybeHardened, {"strlen"}, 1}, &CStringChecker::evalstrLength}, {{CDM::CLibrary, {"wcslen"}, 1}, &CStringChecker::evalstrLength}, {{CDM::CLibraryMaybeHardened, {"strnlen"}, 2}, &CStringChecker::evalstrnLength}, {{CDM::CLibrary, {"wcsnlen"}, 2}, &CStringChecker::evalstrnLength}, - {{CDM::CLibrary, {"strcmp"}, 2}, &CStringChecker::evalStrcmp}, - {{CDM::CLibrary, {"strncmp"}, 3}, &CStringChecker::evalStrncmp}, + {{CDM::CLibrary, {"strcmp"}, 2}, + forRegularChars(&CStringChecker::evalStrcmp)}, + {{CDM::CLibrary, {"wcscmp"}, 2}, + forWideChars(&CStringChecker::evalStrcmp)}, + {{CDM::CLibrary, {"strncmp"}, 3}, + forRegularChars(&CStringChecker::evalStrncmp)}, + {{CDM::CLibrary, {"wcsncmp"}, 3}, + forWideChars(&CStringChecker::evalStrncmp)}, {{CDM::CLibrary, {"strcasecmp"}, 2}, &CStringChecker::evalStrcasecmp}, {{CDM::CLibrary, {"strncasecmp"}, 3}, &CStringChecker::evalStrncasecmp}, {{CDM::CLibrary, {"strsep"}, 2}, &CStringChecker::evalStrsep}, {{CDM::CLibrary, {"bcopy"}, 3}, &CStringChecker::evalBcopy}, {{CDM::CLibrary, {"bcmp"}, 3}, - std::bind(&CStringChecker::evalMemcmp, _1, _2, _3, CK_Regular)}, + forRegularChars(&CStringChecker::evalMemcmp)}, {{CDM::CLibrary, {"bzero"}, 2}, &CStringChecker::evalBzero}, {{CDM::CLibraryMaybeHardened, {"explicit_bzero"}, 2}, &CStringChecker::evalBzero}, @@ -191,6 +219,8 @@ class CStringChecker : public Checker< eval::Call, &CStringChecker::evalSprintf}, {{CDM::CLibraryMaybeHardened, {"snprintf"}, std::nullopt, 3}, &CStringChecker::evalSnprintf}, + {{CDM::CLibraryMaybeHardened, {"swprintf"}, std::nullopt, 3}, + &CStringChecker::evalSwprintf}, }; // These require a bit of special handling. @@ -218,19 +248,23 @@ class CStringChecker : public Checker< eval::Call, void evalStrncpy(CheckerContext &C, const CallEvent &Call) const; void evalStpcpy(CheckerContext &C, const CallEvent &Call) const; void evalStrlcpy(CheckerContext &C, const CallEvent &Call) const; + void evalWcscpy(CheckerContext &C, const CallEvent &Call) const; + void evalWcsncpy(CheckerContext &C, const CallEvent &Call) const; void evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, bool ReturnEnd, bool IsBounded, ConcatFnKind appendK, - bool returnPtr = true) const; + CharKind CK, bool returnPtr = true) const; void evalStrcat(CheckerContext &C, const CallEvent &Call) const; void evalStrncat(CheckerContext &C, const CallEvent &Call) const; void evalStrlcat(CheckerContext &C, const CallEvent &Call) const; + void evalWcscat(CheckerContext &C, const CallEvent &Call) const; + void evalWcsncat(CheckerContext &C, const CallEvent &Call) const; - void evalStrcmp(CheckerContext &C, const CallEvent &Call) const; - void evalStrncmp(CheckerContext &C, const CallEvent &Call) const; + void evalStrcmp(CheckerContext &C, const CallEvent &Call, CharKind CK) const; + void evalStrncmp(CheckerContext &C, const CallEvent &Call, CharKind CK) const; void evalStrcasecmp(CheckerContext &C, const CallEvent &Call) const; void evalStrncasecmp(CheckerContext &C, const CallEvent &Call) const; - void evalStrcmpCommon(CheckerContext &C, const CallEvent &Call, + void evalStrcmpCommon(CheckerContext &C, const CallEvent &Call, CharKind CK, bool IsBounded = false, bool IgnoreCase = false) const; void evalStrsep(CheckerContext &C, const CallEvent &Call) const; @@ -238,19 +272,23 @@ class CStringChecker : public Checker< eval::Call, void evalStdCopy(CheckerContext &C, const CallEvent &Call) const; void evalStdCopyBackward(CheckerContext &C, const CallEvent &Call) const; void evalStdCopyCommon(CheckerContext &C, const CallEvent &Call) const; - void evalMemset(CheckerContext &C, const CallEvent &Call) const; + void evalMemset(CheckerContext &C, const CallEvent &Call, CharKind CK) const; void evalBzero(CheckerContext &C, const CallEvent &Call) const; void evalSprintf(CheckerContext &C, const CallEvent &Call) const; void evalSnprintf(CheckerContext &C, const CallEvent &Call) const; + void evalSwprintf(CheckerContext &C, const CallEvent &Call) const; void evalSprintfCommon(CheckerContext &C, const CallEvent &Call, - bool IsBounded) const; + bool IsBounded, CharKind CK) const; // Utility methods std::pair<ProgramStateRef , ProgramStateRef > static assumeZero(CheckerContext &C, ProgramStateRef state, SVal V, QualType Ty); + static std::pair<ProgramStateRef, ProgramStateRef> + assumeSizeZero(CheckerContext &C, ProgramStateRef State, const Expr *Size); + static ProgramStateRef setCStringLength(ProgramStateRef state, const MemRegion *MR, SVal strLength); @@ -270,11 +308,12 @@ class CStringChecker : public Checker< eval::Call, const Expr *expr, SVal val) const; + /// SizeVBytes must be in bytes. /// Invalidate the destination buffer determined by characters copied. static ProgramStateRef invalidateDestinationBufferBySize(CheckerContext &C, ProgramStateRef S, - const Expr *BufE, SVal BufV, SVal SizeV, - QualType SizeTy); + const Expr *BufE, SVal BufV, + SVal SizeVBytes, QualType SizeTy); /// Operation never overflows, do not invalidate the super region. static ProgramStateRef invalidateDestinationBufferNeverOverflows( @@ -302,8 +341,8 @@ class CStringChecker : public Checker< eval::Call, static bool SummarizeRegion(raw_ostream &os, ASTContext &Ctx, const MemRegion *MR); - static bool memsetAux(const Expr *DstBuffer, SVal CharE, - const Expr *Size, CheckerContext &C, + static bool memsetAux(const Expr *DstBuffer, SVal CharValUnsigned, + const Expr *Size, CharUnits UnitSize, CheckerContext &C, ProgramStateRef &State); // Re-usable checks @@ -315,20 +354,15 @@ class CStringChecker : public Checker< eval::Call, AnyArgExpr Buffer, SVal Element, SVal Size) const; ProgramStateRef CheckLocation(CheckerContext &C, ProgramStateRef state, AnyArgExpr Buffer, SVal Element, - AccessKind Access, - CharKind CK = CharKind::Regular) const; + AccessKind Access, CharKind CK) const; ProgramStateRef CheckBufferAccess(CheckerContext &C, ProgramStateRef State, AnyArgExpr Buffer, SizeArgExpr Size, - AccessKind Access, - CharKind CK = CharKind::Regular) const; + AccessKind Access, CharKind CK) const; ProgramStateRef CheckOverlap(CheckerContext &C, ProgramStateRef state, SizeArgExpr Size, AnyArgExpr First, - AnyArgExpr Second, - CharKind CK = CharKind::Regular) const; - void emitOverlapBug(CheckerContext &C, - ProgramStateRef state, - const Stmt *First, - const Stmt *Second) const; + AnyArgExpr Second, CharKind CK) const; + void emitOverlapBug(CheckerContext &C, ProgramStateRef state, + const Stmt *First, const Stmt *Second) const; void emitNullArgBug(CheckerContext &C, ProgramStateRef State, const Stmt *S, StringRef WarningMsg) const; @@ -351,6 +385,12 @@ class CStringChecker : public Checker< eval::Call, static bool isFirstBufInBound(CheckerContext &C, ProgramStateRef State, SVal BufVal, QualType BufTy, SVal LengthVal, QualType LengthTy); + + /// For the sprintf family of functions, the "dest" argument is allowed + /// to be null if it is a bounded sprintf function and the bound is 0. + /// Check this condition. + bool shouldCheckDestForNull(bool IsBounded, const CallEvent &Call, + ProgramStateRef State, CheckerContext &C) const; }; } //end anonymous namespace @@ -373,6 +413,12 @@ CStringChecker::assumeZero(CheckerContext &C, ProgramStateRef State, SVal V, return State->assume(svalBuilder.evalEQ(State, *val, zero)); } +std::pair<ProgramStateRef, ProgramStateRef> +CStringChecker::assumeSizeZero(CheckerContext &C, ProgramStateRef State, + const Expr *Size) { + return assumeZero(C, State, C.getSVal(Size), Size->getType()); +} + ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, ProgramStateRef State, AnyArgExpr Arg, SVal l) const { @@ -1149,12 +1195,12 @@ const StringLiteral *CStringChecker::getCStringLiteral(CheckerContext &C, bool CStringChecker::isFirstBufInBound(CheckerContext &C, ProgramStateRef State, SVal BufVal, QualType BufTy, - SVal LengthVal, QualType LengthTy) { + SVal LengthValBytes, QualType LengthTy) { // If we do not know that the buffer is long enough we return 'true'. // Otherwise the parent region of this field region would also get // invalidated, which would lead to warnings based on an unknown state. - if (LengthVal.isUnknown()) + if (LengthValBytes.isUnknown()) return false; // Originally copied from CheckBufferAccess and CheckLocation. @@ -1163,7 +1209,7 @@ bool CStringChecker::isFirstBufInBound(CheckerContext &C, ProgramStateRef State, QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); - std::optional<NonLoc> Length = LengthVal.getAs<NonLoc>(); + std::optional<NonLoc> Length = LengthValBytes.getAs<NonLoc>(); if (!Length) return true; // cf top comment. @@ -1210,14 +1256,14 @@ bool CStringChecker::isFirstBufInBound(CheckerContext &C, ProgramStateRef State, ProgramStateRef CStringChecker::invalidateDestinationBufferBySize( CheckerContext &C, ProgramStateRef S, const Expr *BufE, SVal BufV, - SVal SizeV, QualType SizeTy) { + SVal SizeVBytes, QualType SizeTy) { auto InvalidationTraitOperations = - [&C, S, BufTy = BufE->getType(), BufV, SizeV, + [&C, S, BufTy = BufE->getType(), BufV, SizeVBytes, SizeTy](RegionAndSymbolInvalidationTraits &ITraits, const MemRegion *R) { // If destination buffer is a field region and access is in bound, do // not invalidate its super region. if (MemRegion::FieldRegionKind == R->getKind() && - isFirstBufInBound(C, S, BufV, BufTy, SizeV, SizeTy)) { + isFirstBufInBound(C, S, BufV, BufTy, SizeVBytes, SizeTy)) { ITraits.setTrait( R, RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); @@ -1347,9 +1393,10 @@ bool CStringChecker::SummarizeRegion(raw_ostream &os, ASTContext &Ctx, } } -bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, - const Expr *Size, CheckerContext &C, - ProgramStateRef &State) { +bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharValCast, + const Expr *Size, CharUnits UnitSize, + CheckerContext &C, ProgramStateRef &State) { + SVal MemVal = C.getSVal(DstBuffer); SVal SizeVal = C.getSVal(Size); const MemRegion *MR = MemVal.getAsRegion(); @@ -1368,26 +1415,30 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, return false; SValBuilder &svalBuilder = C.getSValBuilder(); + + auto SizeInChars = + svalBuilder + .evalBinOp(State, BO_Mul, *SizeNL, + svalBuilder.makeArrayIndex(UnitSize.getQuantity()), + svalBuilder.getArrayIndexType()) + .castAs<NonLoc>(); + ASTContext &Ctx = C.getASTContext(); // void *memset(void *dest, int ch, size_t count); + // wchar_t *wmemset(wchar_t *s, wchar_t c, size_t n); // For now we can only handle the case of offset is 0 and concrete char value. if (Offset.isValid() && !Offset.hasSymbolicOffset() && Offset.getOffset() == 0) { // Get the base region's size. DefinedOrUnknownSVal SizeDV = getDynamicExtent(State, BR, svalBuilder); - ProgramStateRef StateWholeReg, StateNotWholeReg; - std::tie(StateWholeReg, StateNotWholeReg) = - State->assume(svalBuilder.evalEQ(State, SizeDV, *SizeNL)); - - // With the semantic of 'memset()', we should convert the CharVal to - // unsigned char. - CharVal = svalBuilder.evalCast(CharVal, Ctx.UnsignedCharTy, Ctx.IntTy); + auto [StateWholeReg, StateNotWholeReg] = + State->assume(svalBuilder.evalEQ(State, SizeDV, SizeInChars)); ProgramStateRef StateNullChar, StateNonNullChar; std::tie(StateNullChar, StateNonNullChar) = - assumeZero(C, State, CharVal, Ctx.UnsignedCharTy); + assumeZero(C, State, CharValCast, Ctx.UnsignedCharTy); if (StateWholeReg && !StateNotWholeReg && StateNullChar && !StateNonNullChar) { @@ -1403,7 +1454,7 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, // If the destination buffer's extent is not equal to the value of // third argument, just invalidate buffer. State = invalidateDestinationBufferBySize(C, State, DstBuffer, MemVal, - SizeVal, Size->getType()); + SizeInChars, Size->getType()); } if (StateNullChar && !StateNonNullChar) { @@ -1418,8 +1469,10 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, // If the value of second argument is not zero, then the string length // is at least the size argument. + // Using SizeNL here and not SizeInBytes, because strlen and wcslen + // in respective units and not in bytes. SVal NewStrLenGESize = svalBuilder.evalBinOp( - State, BO_GE, NewStrLen, SizeVal, svalBuilder.getConditionType()); + State, BO_GE, NewStrLen, *SizeNL, svalBuilder.getConditionType()); State = setCStringLength( State->assume(NewStrLenGESize.castAs<DefinedOrUnknownSVal>(), true), @@ -1429,7 +1482,7 @@ bool CStringChecker::memsetAux(const Expr *DstBuffer, SVal CharVal, // If the offset is not zero and char value is not concrete, we can do // nothing but invalidate the buffer. State = invalidateDestinationBufferBySize(C, State, DstBuffer, MemVal, - SizeVal, Size->getType()); + SizeInChars, Size->getType()); } return true; } @@ -1448,11 +1501,8 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, const CallEvent &Call, // See if the size argument is zero. const LocationContext *LCtx = C.getLocationContext(); SVal sizeVal = state->getSVal(Size.Expression, LCtx); - QualType sizeTy = Size.Expression->getType(); - - ProgramStateRef stateZeroSize, stateNonZeroSize; - std::tie(stateZeroSize, stateNonZeroSize) = - assumeZero(C, state, sizeVal, sizeTy); + auto [stateZeroSize, stateNonZeroSize] = + assumeSizeZero(C, state, Size.Expression); // Get the value of the Dest. SVal destVal = state->getSVal(Dest.Expression, LCtx); @@ -1610,13 +1660,8 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallEvent &Call, SValBuilder &Builder = C.getSValBuilder(); const LocationContext *LCtx = C.getLocationContext(); - // See if the size argument is zero. - SVal sizeVal = State->getSVal(Size.Expression, LCtx); - QualType sizeTy = Size.Expression->getType(); - - ProgramStateRef stateZeroSize, stateNonZeroSize; - std::tie(stateZeroSize, stateNonZeroSize) = - assumeZero(C, State, sizeVal, sizeTy); + auto [stateZeroSize, stateNonZeroSize] = + assumeSizeZero(C, State, Size.Expression); // If the size can be zero, the result will be 0 in that case, and we don't // have to check either of the buffers. @@ -1647,7 +1692,7 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallEvent &Call, // and we only need to check one size. if (SameBuffer && !NotSameBuffer) { State = SameBuffer; - State = CheckBufferAccess(C, State, Left, Size, AccessKind::read); + State = CheckBufferAccess(C, State, Left, Size, AccessKind::read, CK); if (State) { State = SameBuffer->BindExpr(Call.getOriginExpr(), LCtx, Builder.makeZeroVal(Call.getResultType())); @@ -1691,12 +1736,8 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const LocationContext *LCtx = C.getLocationContext(); if (IsStrnlen) { - const Expr *maxlenExpr = Call.getArgExpr(1); - SVal maxlenVal = state->getSVal(maxlenExpr, LCtx); - - ProgramStateRef stateZeroSize, stateNonZeroSize; - std::tie(stateZeroSize, stateNonZeroSize) = - assumeZero(C, state, maxlenVal, maxlenExpr->getType()); + auto [stateZeroSize, stateNonZeroSize] = + assumeSizeZero(C, state, Call.getArgExpr(1)); // If the size can be zero, the result will be 0 in that case, and we don't // have to check the string itself. @@ -1808,7 +1849,8 @@ void CStringChecker::evalStrcpy(CheckerContext &C, evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ false, - /* appendK = */ ConcatFnKind::none); + /* appendK = */ ConcatFnKind::none, + /* CK = */ CK_Regular); } void CStringChecker::evalStrncpy(CheckerContext &C, @@ -1817,7 +1859,8 @@ void CStringChecker::evalStrncpy(CheckerContext &C, evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ true, - /* appendK = */ ConcatFnKind::none); + /* appendK = */ ConcatFnKind::none, + /* CK = */ CK_Regular); } void CStringChecker::evalStpcpy(CheckerContext &C, @@ -1826,7 +1869,8 @@ void CStringChecker::evalStpcpy(CheckerContext &C, evalStrcpyCommon(C, Call, /* ReturnEnd = */ true, /* IsBounded = */ false, - /* appendK = */ ConcatFnKind::none); + /* appendK = */ ConcatFnKind::none, + /* CK = */ CK_Regular); } void CStringChecker::evalStrlcpy(CheckerContext &C, @@ -1836,16 +1880,40 @@ void CStringChecker::evalStrlcpy(CheckerContext &C, /* ReturnEnd = */ true, /* IsBounded = */ true, /* appendK = */ ConcatFnKind::none, + /* CK = */ CK_Regular, /* returnPtr = */ false); } +void CStringChecker::evalWcscpy(CheckerContext &C, + const CallEvent &Call) const { + // wchar_t *wcscpy(wchar_t *dest, const wchar_t *src); + evalStrcpyCommon(C, Call, + /* ReturnEnd = */ false, + /* IsBounded = */ false, + /* appendK = */ ConcatFnKind::none, + /* CK = */ CK_Wide, + /* returnPtr = */ true); +} + +void CStringChecker::evalWcsncpy(CheckerContext &C, + const CallEvent &Call) const { + // wchar_t *wcsncpy(wchar_t *dest, const wchar_t *src, size_t size); + evalStrcpyCommon(C, Call, + /* ReturnEnd = */ false, + /* IsBounded = */ true, + /* appendK = */ ConcatFnKind::none, + /* CK = */ CK_Wide, + /* returnPtr = */ true); +} + void CStringChecker::evalStrcat(CheckerContext &C, const CallEvent &Call) const { // char *strcat(char *restrict s1, const char *restrict s2); evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ false, - /* appendK = */ ConcatFnKind::strcat); + /* appendK = */ ConcatFnKind::strcat, + /* CK = */ CK_Regular); } void CStringChecker::evalStrncat(CheckerContext &C, @@ -1854,7 +1922,8 @@ void CStringChecker::evalStrncat(CheckerContext &C, evalStrcpyCommon(C, Call, /* ReturnEnd = */ false, /* IsBounded = */ true, - /* appendK = */ ConcatFnKind::strcat); + /* appendK = */ ConcatFnKind::strcat, + /* CK = */ CK_Regular); } void CStringChecker::evalStrlcat(CheckerContext &C, @@ -1866,12 +1935,35 @@ void CStringChecker::evalStrlcat(CheckerContext &C, /* ReturnEnd = */ false, /* IsBounded = */ true, /* appendK = */ ConcatFnKind::strlcat, + /* CK = */ CK_Regular, /* returnPtr = */ false); } +void CStringChecker::evalWcscat(CheckerContext &C, + const CallEvent &Call) const { + // wchar_t *wcscat(wchar_t *dst, const wchar_t *src); + evalStrcpyCommon(C, Call, + /* ReturnEnd = */ false, + /* IsBounded = */ false, + /* appendK = */ ConcatFnKind::strcat, + /* CK = */ CK_Wide, + /* returnPtr = */ true); +} + +void CStringChecker::evalWcsncat(CheckerContext &C, + const CallEvent &Call) const { + // wchar_t *wcsncat(wchar_t *dst, const wchar_t *src); + evalStrcpyCommon(C, Call, + /* ReturnEnd = */ false, + /* IsBounded = */ true, + /* appendK = */ ConcatFnKind::strcat, + /* CK = */ CK_Wide, + /* returnPtr = */ true); +} + void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, bool ReturnEnd, bool IsBounded, - ConcatFnKind appendK, + ConcatFnKind appendK, CharKind CK, bool returnPtr) const { if (appendK == ConcatFnKind::none) CurrentFunctionDescription = "string copy function"; @@ -1925,7 +2017,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, state = CheckOverlap( C, state, (IsBounded ? SizeArgExpr{{Call.getArgExpr(2), 2}} : SrcExprAsSizeDummy), - Dst, srcExpr); + Dst, srcExpr, CK); if (!state) return; @@ -2040,8 +2132,7 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, // We need a special case for when the copy size is zero, in which // case strncpy will do no work at all. Our bounds check uses n-1 // as the last element accessed, so n == 0 is problematic. - ProgramStateRef StateZeroSize, StateNonZeroSize; - std::tie(StateZeroSize, StateNonZeroSize) = + auto [StateZeroSize, StateNonZeroSize] = assumeZero(C, state, *lenValNL, sizeTy); // If the size is known to be zero, we're done. @@ -2195,11 +2286,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, svalBuilder.evalBinOpLN(state, BO_Add, *dstRegVal, *maxLastNL, ptrTy); // Check if the first byte of the destination is writable. - state = CheckLocation(C, state, Dst, DstVal, AccessKind::write); + state = CheckLocation(C, state, Dst, DstVal, AccessKind::write, CK); if (!state) return; // Check if the last byte of the destination is writable. - state = CheckLocation(C, state, Dst, maxLastElement, AccessKind::write); + state = + CheckLocation(C, state, Dst, maxLastElement, AccessKind::write, CK); if (!state) return; } @@ -2212,11 +2304,12 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, // ...and we haven't checked the bound, we'll check the actual copy. if (!boundWarning) { // Check if the first byte of the destination is writable. - state = CheckLocation(C, state, Dst, DstVal, AccessKind::write); + state = CheckLocation(C, state, Dst, DstVal, AccessKind::write, CK); if (!state) return; // Check if the last byte of the destination is writable. - state = CheckLocation(C, state, Dst, lastElement, AccessKind::write); + state = + CheckLocation(C, state, Dst, lastElement, AccessKind::write, CK); if (!state) return; } @@ -2268,32 +2361,39 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallEvent &Call, C.addTransition(state); } -void CStringChecker::evalStrcmp(CheckerContext &C, - const CallEvent &Call) const { - //int strcmp(const char *s1, const char *s2); - evalStrcmpCommon(C, Call, /* IsBounded = */ false, /* IgnoreCase = */ false); +void CStringChecker::evalStrcmp(CheckerContext &C, const CallEvent &Call, + CharKind CK) const { + // int strcmp(const char *s1, const char *s2); + // int wcscmp(const wchar_t *s1, const wchar_t *s2); + evalStrcmpCommon(C, Call, CK, /* IsBounded = */ false, + /* IgnoreCase = */ false); } -void CStringChecker::evalStrncmp(CheckerContext &C, - const CallEvent &Call) const { - //int strncmp(const char *s1, const char *s2, size_t n); - evalStrcmpCommon(C, Call, /* IsBounded = */ true, /* IgnoreCase = */ false); +void CStringChecker::evalStrncmp(CheckerContext &C, const CallEvent &Call, + CharKind CK) const { + // int strncmp(const char *s1, const char *s2, size_t n); + // int wcsncmp(const wchar_t *s1, const wchar_t *s2, size_t n); + evalStrcmpCommon(C, Call, CK, /* IsBounded = */ true, + /* IgnoreCase = */ false); } void CStringChecker::evalStrcasecmp(CheckerContext &C, const CallEvent &Call) const { - //int strcasecmp(const char *s1, const char *s2); - evalStrcmpCommon(C, Call, /* IsBounded = */ false, /* IgnoreCase = */ true); + // int strcasecmp(const char *s1, const char *s2); + evalStrcmpCommon(C, Call, /* CK = */ CK_Regular, /* IsBounded = */ false, + /* IgnoreCase = */ true); } void CStringChecker::evalStrncasecmp(CheckerContext &C, const CallEvent &Call) const { - //int strncasecmp(const char *s1, const char *s2, size_t n); - evalStrcmpCommon(C, Call, /* IsBounded = */ true, /* IgnoreCase = */ true); + // int strncasecmp(const char *s1, const char *s2, size_t n); + evalStrcmpCommon(C, Call, /* CK = */ CK_Regular, /* IsBounded = */ true, + /* IgnoreCase = */ true); } void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallEvent &Call, - bool IsBounded, bool IgnoreCase) const { + CharKind CK, bool IsBounded, + bool IgnoreCase) const { CurrentFunctionDescription = "string comparison function"; ProgramStateRef state = C.getState(); const LocationContext *LCtx = C.getLocationContext(); @@ -2362,7 +2462,8 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallEvent &Call, SVal resultVal = svalBuilder.conjureSymbolVal(nullptr, Call.getOriginExpr(), LCtx, C.blockCount()); - if (LeftStrLiteral && RightStrLiteral) { + // Comparison of wide strings is not implemented + if (CK == CK_Regular && LeftStrLiteral && RightStrLiteral) { StringRef LeftStrRef = LeftStrLiteral->getString(); StringRef RightStrRef = RightStrLiteral->getString(); @@ -2524,8 +2625,33 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C, C.addTransition(State); } -void CStringChecker::evalMemset(CheckerContext &C, - const CallEvent &Call) const { +namespace { +CharUnits getSizeOfUnit(CharKind CK, CheckerContext &C) { + assert(CK == CK_Regular || CK == CK_Wide); + auto UnitType = + CK == CK_Regular ? C.getASTContext().CharTy : C.getASTContext().WCharTy; + + return C.getASTContext().getTypeSizeInChars(UnitType); +} + +SVal getCharValCast(CharKind CK, CheckerContext &C, ProgramStateRef State, + const Expr *CharE) { + const LocationContext *LCtx = C.getLocationContext(); + auto CharVal = State->getSVal(CharE, LCtx); + if (CK == CK_Regular) { + auto &svalBuilder = C.getSValBuilder(); + const auto &Ctx = C.getASTContext(); + // With the semantic of 'memset()', we should convert the CharVal to + // unsigned char. + return svalBuilder.evalCast(CharVal, Ctx.UnsignedCharTy, Ctx.IntTy); + } + return CharVal; +} + +} // namespace + +void CStringChecker::evalMemset(CheckerContext &C, const CallEvent &Call, + CharKind CK) const { // void *memset(void *s, int c, size_t n); CurrentFunctionDescription = "memory set function"; @@ -2536,12 +2662,8 @@ void CStringChecker::evalMemset(CheckerContext &C, ProgramStateRef State = C.getState(); // See if the size argument is zero. + auto [ZeroSize, NonZeroSize] = assumeSizeZero(C, State, Size.Expression); const LocationContext *LCtx = C.getLocationContext(); - SVal SizeVal = C.getSVal(Size.Expression); - QualType SizeTy = Size.Expression->getType(); - - ProgramStateRef ZeroSize, NonZeroSize; - std::tie(ZeroSize, NonZeroSize) = assumeZero(C, State, SizeVal, SizeTy); // Get the value of the memory area. SVal BufferPtrVal = C.getSVal(Buffer.Expression); @@ -2560,15 +2682,16 @@ void CStringChecker::evalMemset(CheckerContext &C, if (!State) return; - State = CheckBufferAccess(C, State, Buffer, Size, AccessKind::write); + State = CheckBufferAccess(C, State, Buffer, Size, AccessKind::write, CK); if (!State) return; // According to the values of the arguments, bind the value of the second // argument to the destination buffer and set string length, or just // invalidate the destination buffer. - if (!memsetAux(Buffer.Expression, C.getSVal(CharE.Expression), - Size.Expression, C, State)) + if (!memsetAux(Buffer.Expression, + getCharValCast(CK, C, State, CharE.Expression), + Size.Expression, getSizeOfUnit(CK, C), C, State)) return; State = State->BindExpr(Call.getOriginExpr(), LCtx, BufferPtrVal); @@ -2580,17 +2703,12 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallEvent &Call) const { DestinationArgExpr Buffer = {{Call.getArgExpr(0), 0}}; SizeArgExpr Size = {{Call.getArgExpr(1), 1}}; - SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().IntTy); + SVal Zero = C.getSValBuilder().makeZeroVal(C.getASTContext().UnsignedCharTy); ProgramStateRef State = C.getState(); - // See if the size argument is zero. - SVal SizeVal = C.getSVal(Size.Expression); - QualType SizeTy = Size.Expression->getType(); - - ProgramStateRef StateZeroSize, StateNonZeroSize; - std::tie(StateZeroSize, StateNonZeroSize) = - assumeZero(C, State, SizeVal, SizeTy); + auto [StateZeroSize, StateNonZeroSize] = + assumeSizeZero(C, State, Size.Expression); // If the size is zero, there won't be any actual memory access, // In this case we just return. @@ -2608,11 +2726,13 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallEvent &Call) const { if (!State) return; - State = CheckBufferAccess(C, State, Buffer, Size, AccessKind::write); + State = + CheckBufferAccess(C, State, Buffer, Size, AccessKind::write, CK_Regular); if (!State) return; - if (!memsetAux(Buffer.Expression, Zero, Size.Expression, C, State)) + if (!memsetAux(Buffer.Expression, Zero, Size.Expression, CharUnits::One(), C, + State)) return; C.addTransition(State); @@ -2621,17 +2741,37 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallEvent &Call) const { void CStringChecker::evalSprintf(CheckerContext &C, const CallEvent &Call) const { CurrentFunctionDescription = "'sprintf'"; - evalSprintfCommon(C, Call, /* IsBounded = */ false); + evalSprintfCommon(C, Call, /* IsBounded = */ false, + /* CharKind = */ CK_Regular); } void CStringChecker::evalSnprintf(CheckerContext &C, const CallEvent &Call) const { CurrentFunctionDescription = "'snprintf'"; - evalSprintfCommon(C, Call, /* IsBounded = */ true); + evalSprintfCommon(C, Call, /* IsBounded = */ true, + /* CharKind = */ CK_Regular); +} + +void CStringChecker::evalSwprintf(CheckerContext &C, + const CallEvent &Call) const { + CurrentFunctionDescription = "'swprintf'"; + evalSprintfCommon(C, Call, /* IsBounded */ true, CK_Wide); +} + +bool CStringChecker::shouldCheckDestForNull(bool IsBounded, + const CallEvent &Call, + ProgramStateRef State, + CheckerContext &C) const { + if (!IsBounded) + return true; + + auto [StateZeroSize, StateNonZeroSize] = + assumeSizeZero(C, State, Call.getArgExpr(1)); + return !StateZeroSize; } void CStringChecker::evalSprintfCommon(CheckerContext &C, const CallEvent &Call, - bool IsBounded) const { + bool IsBounded, CharKind CK) const { ProgramStateRef State = C.getState(); const auto *CE = cast<CallExpr>(Call.getOriginExpr()); DestinationArgExpr Dest = {{Call.getArgExpr(0), 0}}; @@ -2642,6 +2782,11 @@ void CStringChecker::evalSprintfCommon(CheckerContext &C, const CallEvent &Call, return; } + if (shouldCheckDestForNull(IsBounded, Call, State, C)) { + SVal BufVal = C.getSVal(Dest.Expression); + State = checkNonNull(C, State, Dest, BufVal); + } + const auto AllArguments = llvm::make_range(CE->getArgs(), CE->getArgs() + CE->getNumArgs()); const auto VariadicArguments = drop_begin(enumerate(AllArguments), NumParams); @@ -2660,7 +2805,7 @@ void CStringChecker::evalSprintfCommon(CheckerContext &C, const CallEvent &Call, State = CheckOverlap( C, State, (IsBounded ? SizeArgExpr{{Call.getArgExpr(1), 1}} : SrcExprAsSizeDummy), - Dest, Source); + Dest, Source, CK); if (!State) return; } @@ -2693,7 +2838,8 @@ CStringChecker::FnCheck CStringChecker::identifyCall(const CallEvent &Call, // that for std::copy because they may have arguments of other types. for (auto I : CE->arguments()) { QualType T = I->getType(); - if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) + if (!T->isIntegralOrEnumerationType() && !T->isPointerType() && + !T->isNullPtrType()) return nullptr; } diff --git a/clang/test/Analysis/string.cpp b/clang/test/Analysis/string.cpp index c09422d1922369..983d7b6ec4067a 100644 --- a/clang/test/Analysis/string.cpp +++ b/clang/test/Analysis/string.cpp @@ -53,3 +53,7 @@ struct TestNotNullTerm { strlen((char *)&x); // expected-warning{{Argument to string length function is not a null-terminated string}} } }; + +void snprintf_null_dest_nullptr_arg() { + snprintf(nullptr, 10, "%s", nullptr); // expected-warning {{Null pointer passed as 1st argument to 'snprintf'}} +} diff --git a/clang/test/Analysis/wstring-suppress-oob.c b/clang/test/Analysis/wstring-suppress-oob.c new file mode 100644 index 00000000000000..973f425f0450f0 --- /dev/null +++ b/clang/test/Analysis/wstring-suppress-oob.c @@ -0,0 +1,160 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=unix.cstring \ +// RUN: -analyzer-checker=unix.Malloc \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap \ +// RUN: -analyzer-checker=alpha.unix.cstring.NotNullTerminated \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false +// +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -triple x86_64-pc-windows-msvc19.11.0 \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=unix.cstring \ +// RUN: -analyzer-checker=unix.Malloc \ +// RUN: -analyzer-checker=alpha.unix.cstring.BufferOverlap \ +// RUN: -analyzer-checker=alpha.unix.cstring.NotNullTerminated \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -analyzer-config eagerly-assume=false + +typedef __SIZE_TYPE__ size_t; +typedef __WCHAR_TYPE__ wchar_t; + +void clang_analyzer_eval(int); + +void *malloc(size_t); +void free(void *); + +wchar_t *wmemset(wchar_t *s, wchar_t c, size_t n); + +size_t wcslen(const wchar_t *s); + +void wmemset_char_malloc_overflow_with_nullchr_gives_unknown(void) { + wchar_t *str = (wchar_t *)malloc(10 * sizeof(wchar_t)); + wmemset(str, '\0', 12); + // If the `wmemset` doesn't set the whole buffer exactly, + // then the buffer is invalidated by the checker. + clang_analyzer_eval(str[1] == 0); // expected-warning{{UNKNOWN}} + free(str); +} + +void wmemset_char_array_set_wcslen(void) { + wchar_t str[5] = L"abcd"; + clang_analyzer_eval(wcslen(str) == 4); // expected-warning{{TRUE}} + wmemset(str, L'Z', 10); + clang_analyzer_eval(str[0] != L'Z'); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(wcslen(str) < 10); // expected-warning{{FALSE}} +} + +struct POD_wmemset { + int num; + wchar_t c; +}; + +void wmemset_struct_complete(void) { + struct POD_wmemset pod; + pod.num = 1; + pod.c = L'A'; + wmemset((wchar_t*)&pod.num, 0, sizeof(struct POD_wmemset) / sizeof(wchar_t)); + + clang_analyzer_eval(pod.num == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(pod.c == '\0'); // expected-warning{{TRUE}} +} + +void wmemset_struct_complete_incorrect_size(void) { + struct POD_wmemset pod; + pod.num = 1; + pod.c = L'A'; + _Static_assert(sizeof(wchar_t) != sizeof(char), "Expected by this test case"); + wmemset((wchar_t*)&pod, 0, sizeof(struct POD_wmemset)); // count is off if wchar_t != char + + clang_analyzer_eval(pod.num == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(pod.c == '\0'); // expected-warning{{UNKNOWN}} +} + +void wmemset_struct_first_field_equivalent_to_complete(void) { + struct POD_wmemset pod; + pod.num = 1; + pod.c = L'A'; + wmemset((wchar_t*)&pod.num, 0, sizeof(struct POD_wmemset) / sizeof(wchar_t)); + clang_analyzer_eval(pod.num == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(pod.c == 0); // expected-warning{{TRUE}} +} + +void wmemset_struct_second_field(void) { + struct POD_wmemset pod; + pod.num = 1; + pod.c = L'A'; + wmemset((wchar_t*)&pod.c, 0, sizeof(struct POD_wmemset) / sizeof(wchar_t)); + // wmemset crosses the boundary of pod.c, so entire pod is invalidated. + clang_analyzer_eval(pod.num == 1); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(pod.c == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset_struct_second_field_no_oob(void) { + struct POD_wmemset pod; + pod.num = 1; + pod.c = L'A'; + wmemset((wchar_t*)&pod.c, 0, 1); + // wmemset stays within pod.c, so pod.num is unaffected. + clang_analyzer_eval(pod.num == 1); // expected-warning{{TRUE}} + // pod.c is invalidated, while it should be set to 0. + // limitation of current modeling. + clang_analyzer_eval(pod.c == 0); // expected-warning{{UNKNOWN}} +} + +union U_wmemset { + int i; + double d; + char c; +}; + +void wmemset_union_field(void) { + union U_wmemset u; + u.i = 5; + wmemset((wchar_t*)&u.i, L'\0', sizeof(union U_wmemset)); + // Note: This should be TRUE, analyzer can't handle union perfectly now. + clang_analyzer_eval(u.d == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset_len_nonexact_invalidate() { + struct S { + wchar_t array[10]; + int field; + } s; + s.array[0] = L'a'; + s.field = 1; + clang_analyzer_eval(s.array[0] == L'a'); // expected-warning{{TRUE}} + wmemset(s.array, L'\0', 5); + // Invalidating the whole buffer because len does not match its full length + clang_analyzer_eval(s.array[0] == L'\0'); // expected-warning{{UNKNOWN}} + // wmemset stays within the bounds of s.array, so s.field is unaffected + clang_analyzer_eval(s.field == 1); // expected-warning{{TRUE}} + + wmemset(s.array, L'\0', sizeof(s.array)); // length in bytes means it will actually overflow + // Invalidating the whole buffer because len does not match its full length + clang_analyzer_eval(s.array[0] == L'\0'); // expected-warning{{UNKNOWN}} + // wmemset overflows the s.array buffer, so s.field is also invalidated + clang_analyzer_eval(s.field == 1); // expected-warning{{UNKNOWN}} + + s.array[0] = L'a'; + s.field = 1; + + wmemset(s.array, L'\0', sizeof(s.array) / sizeof(wchar_t)); + // Modeling limitation: wmemset clears exactly s.array, + // but not entire s, so s.array is invalidated instead of being set to 0. + clang_analyzer_eval(s.array[0] == L'\0'); // expected-warning{{UNKNOWN}} + // wmemset stays within the bounds of s.array, so s.field is preserved + clang_analyzer_eval(s.field == 1); // expected-warning{{TRUE}} +} +wchar_t* wcsncpy(wchar_t *restrict s1, const wchar_t *restrict s2, size_t n); + +// Make sure the checker does not crash when the length argument is way beyond the +// extents of the source and dest arguments +void wcsncpy_cstringchecker_bounds_nocrash(void) { + wchar_t *p = malloc(2 * sizeof(wchar_t)); + // sizeof(L"AAA") returns 4*sizeof(wchar_t), e.g., 16, which is longer than + // the number of characters in L"AAA" - 4: + wcsncpy(p, L"AAA", sizeof(L"AAA")); + free(p); +} diff --git a/clang/test/Analysis/wstring.c b/clang/test/Analysis/wstring.c index 9c60d39ff502e9..c277f8b9b8c7a3 100644 --- a/clang/test/Analysis/wstring.c +++ b/clang/test/Analysis/wstring.c @@ -1,18 +1,23 @@ // RUN: %clang_analyze_cc1 -verify %s \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-checker=unix.cstring \ +// RUN: -analyzer-checker=unix.Malloc \ // RUN: -analyzer-checker=alpha.unix.cstring \ // RUN: -analyzer-disable-checker=alpha.unix.cstring.UninitializedRead \ // RUN: -analyzer-checker=debug.ExprInspection \ -// RUN: -analyzer-config eagerly-assume=false +// RUN: -analyzer-config eagerly-assume=false // // RUN: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \ // RUN: -analyzer-checker=core \ // RUN: -analyzer-checker=unix.cstring \ +// RUN: -analyzer-checker=unix.Malloc \ // RUN: -analyzer-checker=alpha.unix.cstring \ // RUN: -analyzer-disable-checker=alpha.unix.cstring.UninitializedRead \ // RUN: -analyzer-checker=debug.ExprInspection \ // RUN: -analyzer-config eagerly-assume=false +// +// Enabling the malloc checker enables some of the buffer-checking portions +// of the C-string checker. //===----------------------------------------------------------------------=== // Declarations @@ -27,11 +32,19 @@ # define BUILTIN(f) f #endif /* USE_BUILTINS */ +#define NULL (0) + typedef __SIZE_TYPE__ size_t; typedef __WCHAR_TYPE__ wchar_t; void clang_analyzer_eval(int); +void escape(wchar_t*); + +void *malloc(size_t); +int scanf(const char *restrict format, ...); +void free(void *); + //===----------------------------------------------------------------------=== // wmemcpy() //===----------------------------------------------------------------------=== @@ -317,6 +330,22 @@ void wmemmove2 (void) { wmemmove(dst, src, 4); // expected-warning{{Memory copy function overflows the destination buffer}} } +//===----------------------------------------------------------------------=== +// memcmp() +// Checking here, that the non-wide-char version still works as expected. +//===----------------------------------------------------------------------=== +int memcmp(const void *s1, const void *s2, size_t n); + +int memcmp_same_buffer_not_oob (void) { + char a[] = {1, 2, 3, 4}; + return memcmp(a, a, 4); // no-warning +} + +int memcmp_same_buffer_oob (void) { + char a[] = {1, 2, 3, 4}; + return memcmp(a, a, 5); // expected-warning{{Memory comparison function accesses out-of-bound array element}} +} + //===----------------------------------------------------------------------=== // wmemcmp() //===----------------------------------------------------------------------=== @@ -327,7 +356,6 @@ int wmemcmp(const wchar_t *s1, const wchar_t *s2, size_t n); void wmemcmp0 (void) { wchar_t a[] = {1, 2, 3, 4}; wchar_t b[4] = { 0 }; - wmemcmp(a, b, 4); // no-warning } @@ -345,7 +373,17 @@ void wmemcmp2 (void) { wmemcmp(a, b, 4); // expected-warning{{out-of-bound}} } -void wmemcmp3 (void) { +int wmemcmp_same_buffer_not_oob (void) { + wchar_t a[] = {1, 2, 3, 4}; + return wmemcmp(a, a, 4); // no-warning +} + +int wmemcmp_same_buffer_oob (void) { + wchar_t a[] = {1, 2, 3, 4}; + return wmemcmp(a, a, 5); // expected-warning{{Memory comparison function accesses out-of-bound array element}} +} + +void wmemcmp_same_buffer_value (void) { wchar_t a[] = {1, 2, 3, 4}; clang_analyzer_eval(wmemcmp(a, a, 4) == 0); // expected-warning{{TRUE}} @@ -369,11 +407,14 @@ void wmemcmp6 (wchar_t *a, wchar_t *b, size_t n) { int result = wmemcmp(a, b, n); if (result != 0) clang_analyzer_eval(n != 0); // expected-warning{{TRUE}} - // else - // analyzer_assert_unknown(n == 0); - - // We can't do the above comparison because n has already been constrained. - // On one path n == 0, on the other n != 0. + else { + // result can be 0 regardless of the value of n. + // However, in the model of wmemcmp, analyzer splits state on n being 0 and not. + // For that reason we get two results TRUE and FALSE instead of one UNKNOWN. + clang_analyzer_eval(n == 0); + // expected-warning@-1{{TRUE}} + // expected-warning@-2{{FALSE}} + } } int wmemcmp7 (wchar_t *a, size_t x, size_t y, size_t n) { @@ -384,7 +425,6 @@ int wmemcmp7 (wchar_t *a, size_t x, size_t y, size_t n) { int wmemcmp8(wchar_t *a, size_t n) { wchar_t *b = 0; - // Do not warn about the first argument! return wmemcmp(a, b, n); // expected-warning{{Null pointer passed as 2nd argument to memory comparison function}} } @@ -636,3 +676,830 @@ void wmemcpy_wcslen(void) { wmemcpy(a, w_str1, wcslen(w_str1) + 1); wmemcpy(a, w_str1, wcslen(w_str1) + 2); // expected-warning {{Memory copy function accesses out-of-bound array element}} } + +//===----------------------------------------------------------------------=== +// wcscpy() +//===----------------------------------------------------------------------=== + +wchar_t* wcscpy(wchar_t *restrict s1, const wchar_t *restrict s2); + +void wcscpy_null_dst(wchar_t *x) { + wcscpy(NULL, x); // expected-warning{{Null pointer passed as 1st argument to string copy function}} +} + +void wcscpy_null_src(wchar_t *x) { + wcscpy(x, NULL); // expected-warning{{Null pointer passed as 2nd argument to string copy function}} +} + +void wcscpy_fn(wchar_t *x) { + wcscpy(x, (wchar_t*)&wcscpy_fn); // expected-warning{{Argument to string copy function is the address of the function 'wcscpy_fn', which is not a null-terminated string}} +} + +void wcscpy_fn_const(wchar_t *x) { + wcscpy(x, (const wchar_t*)&wcscpy_fn); // expected-warning{{Argument to string copy function is the address of the function 'wcscpy_fn', which is not a null-terminated string}} +} + +void wcscpy_label(wchar_t *x) { +label: + wcscpy(x, (const wchar_t*)&&label); // expected-warning{{Argument to string copy function is the address of the label 'label', which is not a null-terminated string}} +} + +extern int globalInt; +void wcscpy_effects(wchar_t *x, wchar_t *y) { + wchar_t x0 = x[0]; + globalInt = 42; + + clang_analyzer_eval(wcscpy(x, y) == x); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen(x) == wcslen(y)); // expected-warning{{TRUE}} + clang_analyzer_eval(x0 == x[0]); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(globalInt == 42); // expected-warning{{TRUE}} +} + +void wcscpy_model_after_call() { + wchar_t src[] = L"AAA"; + wchar_t dst[10]; + + clang_analyzer_eval(wcslen(src) == 3); // expected-warning{{TRUE}} + wcscpy(dst, src); + clang_analyzer_eval(wcslen(dst) == 3); // expected-warning{{TRUE}} +} + +void wcscpy_overflow(wchar_t *y) { + wchar_t x[4]; + if (wcslen(y) == 4) + wcscpy(x, y); // expected-warning{{String copy function overflows the destination buffer}} +} + +void wcscpy_no_overflow(wchar_t *y) { + wchar_t x[4]; + if (wcslen(y) == 3) + wcscpy(x, y); // no-warning +} + +void wcscpy_overlapping_local_arr(void) { + wchar_t arr[10]; + escape(arr); + if (wcslen(arr) != 5 || wcslen(arr + 1) != 4) + return; + // Given that arr points to a non-empty string, + // arr and arr + 1 overlap, but we don't detect it. + wcscpy(arr, arr + 1); // no-warning false negative + // We can detect the exact match, however. + wcscpy(arr, arr); // expected-warning{{overlapping}} +} + +void wcscpy_overlapping_param(wchar_t* buf) { + if (10 < wcslen(buf)) { + wcscpy(buf, buf + 6); // no-warning false negative + } + wcscpy(buf, buf); // expected-warning{{overlapping}} +} + +//===----------------------------------------------------------------------=== +// wcsncpy() +//===----------------------------------------------------------------------=== + +wchar_t* wcsncpy(wchar_t *restrict s1, const wchar_t *restrict s2, size_t n); + +void wcsncpy_null_dst(wchar_t *x) { + wcsncpy(NULL, x, 5); // expected-warning{{Null pointer passed as 1st argument to string copy function}} +} + +void wcsncpy_null_src(wchar_t *x) { + wcsncpy(x, NULL, 5); // expected-warning{{Null pointer passed as 2nd argument to string copy function}} +} + +void wcsncpy_fn(wchar_t *x) { + wcsncpy(x, (wchar_t*)&wcsncpy_fn, 5); // expected-warning{{Argument to string copy function is the address of the function 'wcsncpy_fn', which is not a null-terminated string}} +} + +void wcsncpy_effects(wchar_t *x, wchar_t *y) { + wchar_t x0 = x[0]; + + clang_analyzer_eval(wcsncpy(x, y, 5) == x); // expected-warning{{TRUE}} + wcsncpy(x, y, 5); + clang_analyzer_eval(wcslen(x) == wcslen(y)); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(x0 == x[0]); // expected-warning{{UNKNOWN}} +} + +// Make sure the checker does not crash when the length argument is way beyond the +// extents of the source and dest arguments +void wcsncpy_cstringchecker_bounds_nocrash(void) { + wchar_t *p = malloc(2 * sizeof(wchar_t)); + // sizeof(L"AAA") returns 4*sizeof(wchar_t), e.g., 16, which is longer than + // the number of characters in L"AAA" - 4: + wcsncpy(p, L"AAA", sizeof(L"AAA")); // expected-warning {{String copy function overflows the destination buffer}} + free(p); +} + +void wcsncpy_overflow(wchar_t *y) { + wchar_t x[4]; + if (wcslen(y) == 4) + wcsncpy(x, y, 5); // expected-warning {{String copy function overflows the destination buffer}} +} + +void wcsncpy_overflow_from_sizearg_1() { + wchar_t dst[3]; + wchar_t src[] = L"1"; + wcsncpy(dst, src, 5); // expected-warning {{String copy function overflows the destination buffer}} +} + +void wcsncpy_overflow_from_sizearg_2(wchar_t *y) { + wchar_t x[4]; + // From man page: + // If the length wcslen(src) is smaller than n, the remaining wide characters + // in the array pointed to by dest are filled with null wide characters. + // + // So, Exactly 5 wchars will be written even if wcslen is 3. + // Hence, the following overflows even though y could fit into x. + if (wcslen(y) == 3) + wcsncpy(x, y, 5); // expected-warning {{String copy function overflows the destination buffer}} +} + +void wcsncpy_overflow_from_src_1() { + wchar_t dst[3]; + wchar_t src[] = L"1234"; + wcsncpy(dst, src, 5); // expected-warning {{String copy function overflows the destination buffer}} +} + +void wcsncpy_overflow_from_src_2() { + wchar_t dst[3]; + wchar_t src[] = L"123456"; + wcsncpy(dst, src, 5); // expected-warning {{String copy function overflows the destination buffer}} +} + +void wcsncpy_no_overflow_no_null_term(wchar_t *y) { + wchar_t x[4]; + if (wcslen(y) == 10) + wcsncpy(x, y, 4); // no-warning +} + +void wcsncpy_no_overflow_false_negative(wchar_t *y, int n) { + if (n <= 4) + return; + + // This generates no warning because + // the built-in range-based solver has weak support for multiplication. + // In particular it cannot see that + // { "symbol": "((reg_$0<int n>) - 1) * 4U", "range": "{ [0, 3] }" } + // { "symbol": "reg_$0<int n>", "range": "{ [41, 2147483647] }" } + // constraints are incompatible + wchar_t x[4]; + if (wcslen(y) == 3) + wcsncpy(x, y, n); // no-warning - false negative +} + +void wcsncpy_truncate(wchar_t *y) { + wchar_t x[4]; + if (wcslen(y) == 4) + wcsncpy(x, y, 3); // no-warning +} + +void wcsncpy_no_truncate(wchar_t *y) { + wchar_t x[4]; + if (wcslen(y) == 3) + wcsncpy(x, y, 3); // no-warning +} + +void wcsncpy_exactly_matching_buffer(wchar_t *y) { + wchar_t x[4]; + wcsncpy(x, y, 4); // no-warning + + // wcsncpy does not null-terminate, so we have no idea what the strlen is + // after this. + clang_analyzer_eval(wcslen(x) > 4); // expected-warning{{UNKNOWN}} +} + +void wcsncpy_zero(wchar_t *src) { + wchar_t dst[] = L"123"; + wcsncpy(dst, src, 0); // no-warning +} + +void wcsncpy_empty(void) { + wchar_t dst[] = L"123"; + wchar_t src[] = L""; + wcsncpy(dst, src, 4); // no-warning +} + +void wcsncpy_overlapping_local_arr(int way) { + wchar_t arr[10]; + escape(arr); + switch(way) { + case 0: + wcsncpy(arr, arr + way, 5); // expected-warning{{overlapping}} + case 1: + wcsncpy(arr, arr + way, 5); // expected-warning{{overlapping}} + case 4: + wcsncpy(arr, arr + way, 5); // expected-warning{{overlapping}} + case 5: + wcsncpy(arr, arr + way, 5); + } +} + +void wcsncpy_overlapping_param1(wchar_t* buf) { + wcsncpy(buf, buf + 6, 10); // expected-warning{{overlapping}} +} + +void wcsncpy_overlapping_param2(wchar_t* buf1, wchar_t* buf2) { + if (buf1 == buf2) + wcsncpy(buf1, buf2, 10); // expected-warning{{overlapping}} + + // False negatives: + if (buf1 + 6 == buf2) + wcsncpy(buf1, buf2, 10); // no-warning + if (buf2 - buf1 < 6) + wcsncpy(buf1, buf2, 10); // no-warning +} + +//===----------------------------------------------------------------------=== +// wcscat() +//===----------------------------------------------------------------------=== + +wchar_t *wcscat(wchar_t *restrict s1, const wchar_t *restrict s2); + +void wcscat_null_dst(wchar_t *x) { + wcscat(NULL, x); // expected-warning{{Null pointer passed as 1st argument to string concatenation function}} +} + +void wchar_t_null_src(wchar_t *x) { + wcscat(x, NULL); // expected-warning{{Null pointer passed as 2nd argument to string concatenation function}} +} + +void wcscat_fn(wchar_t *x) { + wcscat(x, (wchar_t*)&wcscat_fn); // expected-warning{{Argument to string concatenation function is the address of the function 'wcscat_fn', which is not a null-terminated string}} +} + +void wcscat_effects(wchar_t *y) { + wchar_t x[8] = L"123"; + size_t orig_len = wcslen(x); + wchar_t x0 = x[0]; + + if (wcslen(y) != 4) + return; + + clang_analyzer_eval(wcscat(x, y) == x); // expected-warning{{TRUE}} + + clang_analyzer_eval((int)wcslen(x) == (orig_len + wcslen(y))); // expected-warning{{TRUE}} +} + +void wcscat_overflow_0(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 4) + wcscat(x, y); // expected-warning{{String concatenation function overflows the destination buffer}} +} + +void wcscat_overflow_1(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 3) + wcscat(x, y); // expected-warning{{String concatenation function overflows the destination buffer}} +} + +void wcscat_overflow_2(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 2) + wcscat(x, y); // expected-warning{{String concatenation function overflows the destination buffer}} +} + +void wcscat_no_overflow(wchar_t *y) { + wchar_t x[5] = L"12"; + if (wcslen(y) == 2) + wcscat(x, y); // no-warning +} + +void wcscat_unknown_dst_length(wchar_t *dst) { + wcscat(dst, L"1234"); + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} +} + +void wcscat_unknown_src_length(wchar_t *src, int offset) { + wchar_t dst[8] = L"1234"; + wcscat(dst, &src[offset]); + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} +} + +void wcscat_too_big(wchar_t *dst, wchar_t *src) { + // We assume this can never actually happen, so we don't get a warning. + if (wcslen(dst) != (((size_t)0) - 2)) + return; + if (wcslen(src) != 2) + return; + wcscat(dst, src); +} + +void wcscat_overlapping_local_arr(void) { + wchar_t arr[10]; + escape(arr); + if (wcslen(arr) != 5 || wcslen(arr + 1) != 4) + return; + // Given that arr points to a non-empty string, + // arr and arr + 1 overlap, but we don't detect it. + wcscat(arr, arr + 1); // no-warning false negative + // We can detect the exact match, however. + wcscat(arr, arr); // expected-warning{{overlapping}} +} + +void wcscat_overlapping_param(wchar_t* buf) { + if (10 < wcslen(buf)) { + wcscat(buf, buf + 6); // no-warning false negative + } + wcscat(buf, buf); // expected-warning{{overlapping}} +} + +//===----------------------------------------------------------------------=== +// wcsncat() +//===----------------------------------------------------------------------=== + +wchar_t *wcsncat(wchar_t *restrict s1, const wchar_t *restrict s2, size_t n); + +void wcsncat_null_dst(wchar_t *x) { + wcsncat(NULL, x, 4); // expected-warning{{Null pointer passed as 1st argument to string concatenation function}} +} + +void wcsncat_null_src(wchar_t *x) { + wcsncat(x, NULL, 4); // expected-warning{{Null pointer passed as 2nd argument to string concatenation function}} +} + +void wcsncat_fn(wchar_t *x) { + wcsncat(x, (wchar_t*)&wcsncat_fn, 4); // expected-warning{{Argument to string concatenation function is the address of the function 'wcsncat_fn', which is not a null-terminated string}} +} + +void wcsncat_effects(wchar_t *y) { + wchar_t x[8] = L"123"; + size_t orig_len = wcslen(x); + wchar_t x0 = x[0]; + + if (wcslen(y) != 4) + return; + + clang_analyzer_eval(wcsncat(x, y, wcslen(y)) == x); // expected-warning{{TRUE}} + + clang_analyzer_eval(wcslen(x) == (orig_len + wcslen(y))); // expected-warning{{TRUE}} +} + +void wcsncat_overflow_0(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 4) + wcsncat(x, y, wcslen(y)); // expected-warning {{String concatenation function overflows the destination buffer}} +} + +void wcsncat_overflow_1(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 3) + wcsncat(x, y, wcslen(y)); // expected-warning {{String concatenation function overflows the destination buffer}} +} + +void wcsncat_overflow_2(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 2) + wcsncat(x, y, wcslen(y)); // expected-warning {{String concatenation function overflows the destination buffer}} +} + +void wcsncat_overflow_3(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 4) + wcsncat(x, y, 2); // expected-warning {{String concatenation function overflows the destination buffer}} +} + +void wcsncat_no_overflow_1(wchar_t *y) { + wchar_t x[5] = L"12"; + if (wcslen(y) == 2) + wcsncat(x, y, wcslen(y)); // no-warning +} + +void wcsncat_no_overflow_2(wchar_t *y) { + wchar_t x[4] = L"12"; + if (wcslen(y) == 4) + wcsncat(x, y, 1); // no-warning +} + +void wcsncat_no_overflow_fn(wchar_t *y, unsigned n) { + if (n < 2) { + return; + } + + // The analyzer does not take advantage of the known fact that + // length of x is 2 and length of y is 4 to conclude that it does + // not fit into x that has only 4 elements. + wchar_t x[4] = L"12"; + if (wcslen(y) == 4) + wcsncat(x, y, n); // no-warning +} + +void wcsncat_unknown_dst_length(wchar_t *dst) { + wcsncat(dst, L"1234", 5); + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} +} + +void wcsncat_unknown_src_length(wchar_t *src) { + wchar_t dst[8] = L"1234"; + wcsncat(dst, src, 3); + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} + // Limitation: No modeling for the upper bound. + clang_analyzer_eval(wcslen(dst) <= 10); // expected-warning{{UNKNOWN}} + + wchar_t dst2[8] = L"1234"; + wcsncat(dst2, src, 4); // expected-warning {{String concatenation function overflows the destination buffer}} +} + +void wcsncat_unknown_src_length_with_offset(wchar_t *src, int offset) { + wchar_t dst[8] = L"1234"; + wcsncat(dst, &src[offset], 3); + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} + + wchar_t dst2[8] = L"1234"; + wcsncat(dst2, &src[offset], 4); // expected-warning {{String concatenation function overflows the destination buffer}} +} + +void wcsncat_unknown_limit(unsigned limit) { + wchar_t dst[6] = L"1234"; + wchar_t src[] = L"567"; + wcsncat(dst, src, limit); // no-warning + + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen(dst) == 4); // expected-warning{{UNKNOWN}} + // Limitation: No modeling for the upper bound + clang_analyzer_eval(wcslen(dst) < 10); // expected-warning{{UNKNOWN}} +} + +void wcsncat_unknown_float_limit(float limit) { + wchar_t dst[6] = L"1234"; + wchar_t src[] = L"567"; + wcsncat(dst, src, (size_t)limit); // no-warning + + clang_analyzer_eval(wcslen(dst) >= 4); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen(dst) == 4); // expected-warning{{UNKNOWN}} +} + +void wcsncat_too_big(wchar_t *dst, wchar_t *src) { + // We assume this will never actually happen, so we don't get a warning. + if (wcslen(dst) != (((size_t)0) - 2)) + return; + if (wcslen(src) != 2) + return; + wcsncat(dst, src, 2); +} + +void wcsncat_zero(wchar_t *src) { + wchar_t dst[] = L"123"; + wcsncat(dst, src, 0); // no-warning +} + +void wcsncat_zero_unknown_dst(wchar_t *dst, wchar_t *src) { + wcsncat(dst, src, 0); // no-warning +} + +void wcsncat_empty(void) { + wchar_t dst[8] = L"123"; + wchar_t src[] = L""; + wcsncat(dst, src, 4); // no-warning +} + +void wcsncat_overlapping_local_arr(int way) { + wchar_t arr[10]; + escape(arr); + switch(way) { + case 0: + wcsncat(arr, arr + way, 5); // expected-warning{{overlapping}} + case 1: + wcsncat(arr, arr + way, 5); // expected-warning{{overlapping}} + case 4: + wcsncat(arr, arr + way, 5); // expected-warning{{overlapping}} + case 5: + wcsncat(arr, arr + way, 5); + } +} + +void wcsncat_overlapping_param1(wchar_t* buf) { + wcsncat(buf, buf + 6, 10); // expected-warning{{overlapping}} +} + +void wcsncat_overlapping_param2(wchar_t* buf1, wchar_t* buf2) { + if (buf1 == buf2) + wcsncat(buf1, buf2, 10); // expected-warning{{overlapping}} + + // False negatives: + if (buf1 + 6 == buf2) + wcsncat(buf1, buf2, 10); // no-warning + if (buf2 - buf1 < 6) + wcsncat(buf1, buf2, 10); // no-warning +} + +//===----------------------------------------------------------------------=== +// wcscmp() +//===----------------------------------------------------------------------=== + +#define wcscmp BUILTIN(wcscmp) +int wcscmp(const wchar_t *string1, const wchar_t *string2); + +void wcscmp_check_modeling(void) { + wchar_t x[] = L"aa"; + wchar_t y[] = L"a"; + clang_analyzer_eval(wcscmp(x, x) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(wcscmp(x, y) == 0); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(wcscmp(&x[1], x) == 0); // expected-warning{{UNKNOWN}} +} + +void wcscmp_null_0(void) { + wchar_t *x = NULL; + wchar_t y[] = L"123"; + (void)wcscmp(x, y); // expected-warning{{Null pointer passed as 1st argument to string comparison function}} +} + +void wcscmp_null_1(void) { + wchar_t x[] = L"123"; + wchar_t *y = NULL; + (void)wcscmp(x, y); // expected-warning{{Null pointer passed as 2nd argument to string comparison function}} +} + +union argument { + char *f; +}; + +void function_pointer_cast_helper(wchar_t **a) { + // Similar code used to crash for regular c-strings, see PR24951 + (void)wcscmp(L"Hi", *a); +} + +void wcscmp_union_function_pointer_cast(union argument a) { + void (*fPtr)(union argument *) = (void (*)(union argument *))function_pointer_cast_helper; + + fPtr(&a); +} + +int wcscmp_null_argument(wchar_t *a) { + wchar_t *b = 0; + return wcscmp(a, b); // expected-warning{{Null pointer passed as 2nd argument to string comparison function}} +} + +//===----------------------------------------------------------------------=== +// wcsncmp() +//===----------------------------------------------------------------------=== + +#define wcsncmp BUILTIN(wcsncmp) +int wcsncmp(const wchar_t *string1, const wchar_t *string2, size_t count); + +void wcsncmp_check_modeling(void) { + wchar_t x[] = L"aa"; + wchar_t y[] = L"a"; + clang_analyzer_eval(wcsncmp(x, x, 2) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(wcsncmp(x, y, 2) == 0); // expected-warning{{UNKNOWN}} +} + +void wcsncmp_null_0(void) { + wchar_t *x = NULL; + wchar_t y[] = L"123"; + (void)wcsncmp(x, y, 3); // expected-warning{{Null pointer passed as 1st argument to string comparison function}} +} + +void wcsncmp_null_1(void) { + wchar_t x[] = L"123"; + wchar_t *y = NULL; + (void)wcsncmp(x, y, 3); // expected-warning{{Null pointer passed as 2nd argument to string comparison function}} +} + +int wcsncmp_null_argument(wchar_t *a, size_t n) { + wchar_t *b = 0; + return wcsncmp(a, b, n); // expected-warning{{Null pointer passed as 2nd argument to string comparison function}} +} + +//===----------------------------------------------------------------------=== +// wmemset() +//===----------------------------------------------------------------------=== + +wchar_t *wmemset(wchar_t *s, wchar_t c, size_t n); + +void wmemset1_char_array_null(void) { + wchar_t str[] = L"abcd"; + clang_analyzer_eval(wcslen(str) == 4); // expected-warning{{TRUE}} + wmemset(str, L'\0', 2); + clang_analyzer_eval(wcslen(str) == 0); // expected-warning{{TRUE}} +} + +void wmemset2_char_array_null(void) { + wchar_t str[] = L"abcd"; + clang_analyzer_eval(wcslen(str) == 4); // expected-warning{{TRUE}} + wmemset(str, L'\0', wcslen(str) + 1); + clang_analyzer_eval(wcslen(str) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(str[2] == 0); // expected-warning{{TRUE}} +} + +void wmemset3_char_malloc_null(void) { + wchar_t *str = (wchar_t *)malloc(10 * sizeof(wchar_t)); + wmemset(str + 1, '\0', 8); + clang_analyzer_eval(str[1] == 0); // expected-warning{{UNKNOWN}} + free(str); +} + +void wmemset4_char_malloc_null(void) { + wchar_t *str = (wchar_t *)malloc(10 * sizeof(wchar_t)); + //void *str = malloc(10 * sizeof(char)); + wmemset(str, '\0', 10); + clang_analyzer_eval(str[1] == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen(str) == 0); // expected-warning{{TRUE}} + free(str); +} + +void wmemset6_char_array_nonnull(void) { + wchar_t str[] = L"abcd"; + clang_analyzer_eval(wcslen(str) == 4); // expected-warning{{TRUE}} + wmemset(str, L'Z', 2); + clang_analyzer_eval(str[0] == L'Z'); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(wcslen(str) == 4); // expected-warning{{UNKNOWN}} +} + +void wmemset_len_lowerbound(wchar_t *array) { + clang_analyzer_eval(10 <= wcslen(array)); // expected-warning{{UNKNOWN}} + wmemset(array, L'a', 10); + clang_analyzer_eval(10 <= wcslen(array)); // expected-warning{{TRUE}} +} + +void wmemset_zero_param(wchar_t *array) { + wmemset(array, 0, 10); + clang_analyzer_eval(wcslen(array) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(array[0] == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset_zero_local() { + wchar_t array[10]; + wmemset(array, 0, 10); + clang_analyzer_eval(wcslen(array) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(array[0] == 0); // expected-warning{{TRUE}} +} + + +struct POD_wmemset { + int num; + wchar_t c; +}; + +void wmemset10_struct(void) { + struct POD_wmemset pod; + wchar_t *str = (wchar_t *)&pod; + pod.num = 1; + pod.c = 1; + clang_analyzer_eval(pod.num == 0); // expected-warning{{FALSE}} + wmemset(str, 0, sizeof(struct POD_wmemset) / sizeof(wchar_t)); + clang_analyzer_eval(pod.num == 0); // expected-warning{{TRUE}} +} + +void wmemset14_region_cast(void) { + wchar_t *str = (wchar_t *)malloc(10 * sizeof(int)); + int *array = (int *)str; + wmemset((wchar_t *)array, 0, 10 * sizeof(int) / sizeof(wchar_t)); + clang_analyzer_eval(str[10] == L'\0'); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen((wchar_t *)array) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen(str) == 0); // expected-warning{{TRUE}} + free(str); +} + +void wmemset15_region_cast(void) { + wchar_t *str = (wchar_t *)malloc(10 * sizeof(int)); + int *array = (int *)str; + wmemset((wchar_t *)array, 0, 5 * sizeof(int) / sizeof(wchar_t)); + clang_analyzer_eval(str[10] == '\0'); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(wcslen((wchar_t *)array) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(wcslen(str) == 0); // expected-warning{{TRUE}} + free(str); +} + +int wmemset20_scalar(void) { + int *x = malloc(sizeof(int)); + *x = 10; + wmemset((wchar_t *)x, 0, sizeof(int) / sizeof(wchar_t)); + int num = 1 / *x; // expected-warning{{Division by zero}} + free(x); + return num; +} + +int wmemset21_scalar(void) { + int *x = malloc(sizeof(int)); + wmemset((wchar_t *)x, 0, sizeof(int) / sizeof(wchar_t)); + int num = 1 / *x; // expected-warning{{Division by zero}} + free(x); + return num; +} + +int wmemset211_long_scalar(void) { + long long *x = malloc(sizeof(long long)); + wmemset((wchar_t *)x, 0, 1); + // The memory region is wider than sizeof(wchar_t) * 1. + // Limited modelling of wmemset simply invalidates the memory region + // rather than writing it or part of it to 0. + // So no division by zero or uninitialized read are reported. + int num = 1 / *x; // no-warning + free(x); + return num; +} + +void wmemset22_array(void) { + int array[] = {1, 2, 3, 4, 5, 6, 7, 8, 9}; + clang_analyzer_eval(array[1] == 2); // expected-warning{{TRUE}} + wmemset((wchar_t *)array, 0, sizeof(array) / (sizeof(wchar_t))); + clang_analyzer_eval(array[1] == 0); // expected-warning{{TRUE}} +} + +void wmemset23_array_pod_object(void) { + struct POD_wmemset array[10]; + array[1].num = 10; + array[1].c = 'c'; + clang_analyzer_eval(array[1].num == 10); // expected-warning{{TRUE}} + wmemset((wchar_t *)&array[1], 0, sizeof(struct POD_wmemset)); + clang_analyzer_eval(array[1].num == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset24_array_pod_object(void) { + struct POD_wmemset array[10]; + array[1].num = 10; + array[1].c = 'c'; + clang_analyzer_eval(array[1].num == 10); // expected-warning{{TRUE}} + wmemset((wchar_t *)array, 0, sizeof(array) / (sizeof(wchar_t))); + clang_analyzer_eval(array[1].num == 0); // expected-warning{{TRUE}} +} + +void wmemset25_symbol(char c) { + wchar_t array[10] = {1}; + if (c != 0) + return; + + wmemset(array, c, 10); + + clang_analyzer_eval(wcslen(array) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(array[4] == 0); // expected-warning{{TRUE}} +} + +void wmemset26_upper_UCHAR_MAX(void) { + wchar_t array[10] = {1}; + + // If cast to unsigned char, 0x400 would give 0 + // This test ensures that it does not happen + wmemset(array, 0x400, 10); + + clang_analyzer_eval(wcslen(array) == 0); // expected-warning{{FALSE}} + clang_analyzer_eval(10 <= wcslen(array)); // expected-warning{{TRUE}} + // The actual value is 0x400, but any non-0 is not modeled + clang_analyzer_eval(array[4] == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset_pseudo_zero_val_param(wchar_t *array) { + wmemset(array, 0xff00, 10); // if cast to char, 0xff00 is '\0' + clang_analyzer_eval(wcslen(array) == 0); // expected-warning{{FALSE}} + clang_analyzer_eval(array[0] == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset_pseudo_zero_val_local() { + wchar_t array[10]; + wmemset(array, 0xff00, 10); // if cast to char, 0xff00 is '\0' + clang_analyzer_eval(wcslen(array) == 0); // expected-warning{{FALSE}} + clang_analyzer_eval(array[0] == 0); // expected-warning{{UNKNOWN}} +} + +void wmemset_almost_overflows() { + wchar_t array[10] = {1}; + + wmemset(array, 0, 10); // no-warning +} + +void wmemset_overflows() { + wchar_t array[10] = {1}; + + wmemset(array, 0, 11); // expected-warning{{Memory set function overflows the destination buffer}} +} + +//===----------------------------------------------------------------------=== +// swprintf() +//===----------------------------------------------------------------------=== + +int swprintf(wchar_t* restrict ws, size_t n, const wchar_t* restrict format, ...); + +void swprintf_null_dst(void) { + swprintf(NULL, 10, L"%s", L"Hello"); // expected-warning{{Null pointer passed as 1st argument to 'swprintf'}} +} + +void swprintf_null_dst_zero_size(void) { + swprintf(NULL, 0, L"%s", L"Hello"); // no-warning: 0 size means no write will be done +} + +void swprintf_null_dst_unknown_size(int size) { + // If size is not known to be non-0, no check for the destination + swprintf(NULL, size, L"%s", L"Hello"); // no-warning + if (size == 0) { + // If size is known to be 0, no check for dest buffer, no write access here + swprintf(NULL, size, L"%s", L"Hello"); // no-warning + } else { + // If size is known to be non-0 it will likely try to write, + // so warn on a null dest buffer. + // This is the same code as in the beginning of the function where + // it produced no report because there was no constraint on size. + swprintf(NULL, size, L"%s", L"Hello"); // expected-warning{{Null pointer passed as 1st argument to 'swprintf'}} + } +} + +void swprintf_overlapping_param(wchar_t* buf) { + swprintf(buf, 10, L"%s", buf); // no-warning false negative +} + +void swprintf_overlapping_local_buf(void) { + wchar_t buf[10]; + escape(buf); + swprintf(buf, 10, L"%s", buf); // no-warning false negative +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits