[clang] [clang][analyzer] Model more wide-character versions of string functions (PR #113908)
Arseniy Zaostrovnykh via cfe-commits
cfe-commits at lists.llvm.org
Mon Oct 28 06:16:48 PDT 2024
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.zaostrovnykh at 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 at -1{{TRUE}}
+ // expected-warning at -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
+}
More information about the cfe-commits
mailing list