diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -952,7 +952,7 @@ unix.cstring.NullArg (C) """"""""""""""""""""""""" Check for null pointers being passed as arguments to C string functions: -``strlen, strnlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp, strcasecmp, strncasecmp``. +``strlen, strnlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp, strcasecmp, strncasecmp, wcslen, wcsnlen``. .. code-block:: c @@ -2699,7 +2699,7 @@ alpha.unix.cstring.BufferOverlap (C) """""""""""""""""""""""""""""""""""" -Checks for overlap in two buffer arguments. Applies to: ``memcpy, mempcpy``. +Checks for overlap in two buffer arguments. Applies to: ``memcpy, mempcpy, wmemcpy``. .. code-block:: c @@ -2712,7 +2712,7 @@ alpha.unix.cstring.NotNullTerminated (C) """""""""""""""""""""""""""""""""""""""" -Check for arguments which are not null-terminated strings; applies to: ``strlen, strnlen, strcpy, strncpy, strcat, strncat``. +Check for arguments which are not null-terminated strings; applies to: ``strlen, strnlen, strcpy, strncpy, strcat, strncat, wcslen, wcsnlen``. .. code-block:: c @@ -2724,15 +2724,24 @@ alpha.unix.cstring.OutOfBounds (C) """""""""""""""""""""""""""""""""" -Check for out-of-bounds access in string functions; applies to:`` strncopy, strncat``. +Check for out-of-bounds access in string functions, such as: +``memcpy, bcopy, strcpy, strncpy, strcat, strncat, memmove, memcmp, memset`` and more. -This check also applies to string literals, except there is a known bug in that -the analyzer cannot detect embedded NULL characters. +This check also works with string literals, except there is a known bug in that +the analyzer cannot detect embedded NULL characters when determining the string length. .. code-block:: c - void test() { - int y = strlen((char *)&test); // warn + void test1() { + const char str[] = "Hello world"; + char buffer[] = "Hello world"; + memcpy(buffer, str, sizeof(str) + 1); // warn + } + + void test2() { + const char str[] = "Hello world"; + char buffer[] = "Helloworld"; + memcpy(buffer, str, sizeof(str)); // warn } .. _alpha-unix-cstring-UninitializedRead: diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -26,9 +26,11 @@ #include "llvm/ADT/SmallString.h" #include "llvm/ADT/StringExtras.h" #include "llvm/Support/raw_ostream.h" +#include using namespace clang; using namespace ento; +using namespace std::placeholders; namespace { struct AnyArgExpr { @@ -118,10 +120,14 @@ const LocationContext *LCtx, const CallEvent *Call) const; - typedef void (CStringChecker::*FnCheck)(CheckerContext &, - const CallExpr *) const; + using FnCheck = std::function; + CallDescriptionMap Callbacks = { - {{CDF_MaybeBuiltin, "memcpy", 3}, &CStringChecker::evalMemcpy}, + {{CDF_MaybeBuiltin, "memcpy", 3}, + std::bind(&CStringChecker::evalMemcpy, _1, _2, _3, false)}, + {{CDF_MaybeBuiltin, "wmemcpy", 3}, + std::bind(&CStringChecker::evalMemcpy, _1, _2, _3, true)}, {{CDF_MaybeBuiltin, "mempcpy", 3}, &CStringChecker::evalMempcpy}, {{CDF_MaybeBuiltin, "memcmp", 3}, &CStringChecker::evalMemcmp}, {{CDF_MaybeBuiltin, "memmove", 3}, &CStringChecker::evalMemmove}, @@ -135,7 +141,9 @@ {{CDF_MaybeBuiltin, "strncat", 3}, &CStringChecker::evalStrncat}, {{CDF_MaybeBuiltin, "strlcat", 3}, &CStringChecker::evalStrlcat}, {{CDF_MaybeBuiltin, "strlen", 1}, &CStringChecker::evalstrLength}, + {{CDF_MaybeBuiltin, "wcslen", 1}, &CStringChecker::evalstrLength}, {{CDF_MaybeBuiltin, "strnlen", 2}, &CStringChecker::evalstrnLength}, + {{CDF_MaybeBuiltin, "wcsnlen", 2}, &CStringChecker::evalstrnLength}, {{CDF_MaybeBuiltin, "strcmp", 2}, &CStringChecker::evalStrcmp}, {{CDF_MaybeBuiltin, "strncmp", 3}, &CStringChecker::evalStrncmp}, {{CDF_MaybeBuiltin, "strcasecmp", 2}, &CStringChecker::evalStrcasecmp}, @@ -152,14 +160,14 @@ StdCopyBackward{{"std", "copy_backward"}, 3}; FnCheck identifyCall(const CallEvent &Call, CheckerContext &C) const; - void evalMemcpy(CheckerContext &C, const CallExpr *CE) const; + void evalMemcpy(CheckerContext &C, const CallExpr *CE, bool IsWide) const; void evalMempcpy(CheckerContext &C, const CallExpr *CE) const; void evalMemmove(CheckerContext &C, const CallExpr *CE) const; void evalBcopy(CheckerContext &C, const CallExpr *CE) const; void evalCopyCommon(CheckerContext &C, const CallExpr *CE, ProgramStateRef state, SizeArgExpr Size, DestinationArgExpr Dest, SourceArgExpr Source, - bool Restricted, bool IsMempcpy) const; + bool Restricted, bool IsMempcpy, bool IsWide) const; void evalMemcmp(CheckerContext &C, const CallExpr *CE) const; @@ -240,13 +248,14 @@ AnyArgExpr Arg, SVal l) const; ProgramStateRef CheckLocation(CheckerContext &C, ProgramStateRef state, AnyArgExpr Buffer, SVal Element, - AccessKind Access) const; + AccessKind Access, bool IsWide = false) const; ProgramStateRef CheckBufferAccess(CheckerContext &C, ProgramStateRef State, AnyArgExpr Buffer, SizeArgExpr Size, - AccessKind Access) const; + AccessKind Access, + bool IsWide = false) const; ProgramStateRef CheckOverlap(CheckerContext &C, ProgramStateRef state, SizeArgExpr Size, AnyArgExpr First, - AnyArgExpr Second) const; + AnyArgExpr Second, bool IsWide = false) const; void emitOverlapBug(CheckerContext &C, ProgramStateRef state, const Stmt *First, @@ -329,7 +338,8 @@ ProgramStateRef CStringChecker::CheckLocation(CheckerContext &C, ProgramStateRef state, AnyArgExpr Buffer, SVal Element, - AccessKind Access) const { + AccessKind Access, + bool IsWide) const { // If a previous check has failed, propagate the failure. if (!state) @@ -344,17 +354,36 @@ if (!ER) return state; - if (ER->getValueType() != C.getASTContext().CharTy) - return state; + SValBuilder &svalBuilder = C.getSValBuilder(); + ASTContext &Ctx = svalBuilder.getContext(); + + // Get the index of the accessed element. + NonLoc Idx = ER->getIndex(); + + if (!IsWide) { + if (ER->getValueType() != Ctx.CharTy) + return state; + } else { + if (ER->getValueType() != Ctx.WideCharTy) + return state; + + QualType SizeTy = Ctx.getSizeType(); + NonLoc WideSize = + svalBuilder + .makeIntVal(Ctx.getTypeSizeInChars(Ctx.WideCharTy).getQuantity(), + SizeTy) + .castAs(); + SVal Offset = svalBuilder.evalBinOpNN(state, BO_Mul, Idx, WideSize, SizeTy); + if (Offset.isUnknown()) + return state; + Idx = Offset.castAs(); + } // Get the size of the array. const auto *superReg = cast(ER->getSuperRegion()); DefinedOrUnknownSVal Size = getDynamicExtent(state, superReg, C.getSValBuilder()); - // Get the index of the accessed element. - DefinedOrUnknownSVal Idx = ER->getIndex().castAs(); - ProgramStateRef StInBound, StOutBound; std::tie(StInBound, StOutBound) = state->assumeInBoundDual(Idx, Size); if (StOutBound && !StInBound) { @@ -385,11 +414,10 @@ return StInBound; } -ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, - ProgramStateRef State, - AnyArgExpr Buffer, - SizeArgExpr Size, - AccessKind Access) const { +ProgramStateRef +CStringChecker::CheckBufferAccess(CheckerContext &C, ProgramStateRef State, + AnyArgExpr Buffer, SizeArgExpr Size, + AccessKind Access, bool IsWide) const { // If a previous check has failed, propagate the failure. if (!State) return nullptr; @@ -398,7 +426,7 @@ ASTContext &Ctx = svalBuilder.getContext(); QualType SizeTy = Size.Expression->getType(); - QualType PtrTy = Ctx.getPointerType(Ctx.CharTy); + QualType PtrTy = Ctx.getPointerType(IsWide ? Ctx.WideCharTy : Ctx.CharTy); // Check that the first buffer is non-null. SVal BufVal = C.getSVal(Buffer.Expression); @@ -432,7 +460,7 @@ SVal BufEnd = svalBuilder.evalBinOpLN(State, BO_Add, *BufLoc, LastOffset, PtrTy); - State = CheckLocation(C, State, Buffer, BufEnd, Access); + State = CheckLocation(C, State, Buffer, BufEnd, Access, IsWide); // If the buffer isn't large enough, abort. if (!State) @@ -446,7 +474,8 @@ ProgramStateRef CStringChecker::CheckOverlap(CheckerContext &C, ProgramStateRef state, SizeArgExpr Size, AnyArgExpr First, - AnyArgExpr Second) const { + AnyArgExpr Second, + bool IsWide) const { if (!Filter.CheckCStringBufferOverlap) return state; @@ -525,7 +554,7 @@ // Convert the first buffer's start address to char*. // Bail out if the cast fails. ASTContext &Ctx = svalBuilder.getContext(); - QualType CharPtrTy = Ctx.getPointerType(Ctx.CharTy); + QualType CharPtrTy = Ctx.getPointerType(IsWide ? Ctx.WideCharTy : Ctx.CharTy); SVal FirstStart = svalBuilder.evalCast(*firstLoc, CharPtrTy, First.Expression->getType()); Optional FirstStartLoc = FirstStart.getAs(); @@ -1161,7 +1190,7 @@ ProgramStateRef state, SizeArgExpr Size, DestinationArgExpr Dest, SourceArgExpr Source, bool Restricted, - bool IsMempcpy) const { + bool IsMempcpy, bool IsWide) const { CurrentFunctionDescription = "memory copy function"; // See if the size argument is zero. @@ -1204,11 +1233,11 @@ return; // Ensure the accesses are valid and that the buffers do not overlap. - state = CheckBufferAccess(C, state, Dest, Size, AccessKind::write); - state = CheckBufferAccess(C, state, Source, Size, AccessKind::read); + state = CheckBufferAccess(C, state, Dest, Size, AccessKind::write, IsWide); + state = CheckBufferAccess(C, state, Source, Size, AccessKind::read, IsWide); if (Restricted) - state = CheckOverlap(C, state, Size, Dest, Source); + state = CheckOverlap(C, state, Size, Dest, Source, IsWide); if (!state) return; @@ -1258,7 +1287,8 @@ } } -void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { +void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE, + bool IsWide) const { // void *memcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is the address of the destination buffer. DestinationArgExpr Dest = {CE->getArg(0), 0}; @@ -1269,7 +1299,8 @@ constexpr bool IsRestricted = true; constexpr bool IsMempcpy = false; - evalCopyCommon(C, CE, State, Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, CE, State, Size, Dest, Src, IsRestricted, IsMempcpy, + IsWide); } void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { @@ -1281,7 +1312,8 @@ constexpr bool IsRestricted = true; constexpr bool IsMempcpy = true; - evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy, + false); } void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { @@ -1293,7 +1325,8 @@ constexpr bool IsRestricted = false; constexpr bool IsMempcpy = false; - evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy, + false); } void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { @@ -1304,7 +1337,8 @@ constexpr bool IsRestricted = false; constexpr bool IsMempcpy = false; - evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy); + evalCopyCommon(C, CE, C.getState(), Size, Dest, Src, IsRestricted, IsMempcpy, + false); } void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { @@ -2336,7 +2370,7 @@ // Check and evaluate the call. const auto *CE = cast(Call.getOriginExpr()); - (this->*Callback)(C, CE); + Callback(this, C, CE); // If the evaluate call resulted in no change, chain to the next eval call // handler. diff --git a/clang/test/Analysis/wstring.c b/clang/test/Analysis/wstring.c new file mode 100644 --- /dev/null +++ b/clang/test/Analysis/wstring.c @@ -0,0 +1,389 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=unix.cstring \ +// 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: %clang_analyze_cc1 -verify %s -DUSE_BUILTINS \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=unix.cstring \ +// 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 + +//===----------------------------------------------------------------------=== +// Declarations +//===----------------------------------------------------------------------=== + +// Some functions are implemented as builtins. These should be #defined as +// BUILTIN(f), which will prepend "__builtin_" if USE_BUILTINS is defined. + +#ifdef USE_BUILTINS +# define BUILTIN(f) __builtin_ ## f +#else /* USE_BUILTINS */ +# define BUILTIN(f) f +#endif /* USE_BUILTINS */ + +typedef __SIZE_TYPE__ size_t; +typedef __WCHAR_TYPE__ wchar_t; + +void clang_analyzer_eval(int); + +//===----------------------------------------------------------------------=== +// wwmemcpy() +//===----------------------------------------------------------------------=== + +#define wmemcpy BUILTIN(wmemcpy) +wchar_t *wmemcpy(wchar_t *restrict s1, const wchar_t *restrict s2, size_t n); + +void wmemcpy0 (void) { + wchar_t src[] = {1, 2, 3, 4}; + wchar_t dst[4] = {0}; + + wmemcpy(dst, src, 4); // no-warning + + clang_analyzer_eval(wmemcpy(dst, src, 4) == dst); // expected-warning{{TRUE}} + + // If we actually model the copy, we can make this known. + // The important thing for now is that the old value has been invalidated. + clang_analyzer_eval(dst[0] != 0); // expected-warning{{UNKNOWN}} +} + +void wmemcpy1 (void) { + wchar_t src[] = {1, 2, 3, 4}; + wchar_t dst[10]; + + wmemcpy(dst, src, 5); // expected-warning{{Memory copy function accesses out-of-bound array element}} +} + +void wmemcpy2 (void) { + wchar_t src[] = {1, 2, 3, 4}; + wchar_t dst[1]; + + wmemcpy(dst, src, 4); // expected-warning {{Memory copy function overflows the destination buffer}} +} + +void wmemcpy3 (void) { + wchar_t src[] = {1, 2, 3, 4}; + wchar_t dst[3]; + + wmemcpy(dst+1, src+2, 2); // no-warning +} + +void wmemcpy4 (void) { + wchar_t src[] = {1, 2, 3, 4}; + wchar_t dst[10]; + + wmemcpy(dst+2, src+2, 3); // expected-warning{{Memory copy function accesses out-of-bound array element}} +} + +void wmemcpy5(void) { + wchar_t src[] = {1, 2, 3, 4}; + wchar_t dst[3]; + + wmemcpy(dst + 2, src + 2, 2); // expected-warning{{Memory copy function overflows the destination buffer}} +} + +void wmemcpy6(void) { + wchar_t a[4] = {0}; + wmemcpy(a, a, 2); // expected-warning{{overlapping}} +} + +void wmemcpy7(void) { + wchar_t a[4] = {0}; + wmemcpy(a+2, a+1, 2); // expected-warning{{overlapping}} +} + +void wmemcpy8(void) { + wchar_t a[4] = {0}; + wmemcpy(a+1, a+2, 2); // expected-warning{{overlapping}} +} + +void wmemcpy9(void) { + wchar_t a[4] = {0}; + wmemcpy(a+2, a+1, 1); // no-warning + wmemcpy(a+1, a+2, 1); // no-warning +} + +void wmemcpy10(void) { + wchar_t a[4] = {0}; + wmemcpy(0, a, 1); // expected-warning{{Null pointer passed as 1st argument to memory copy function}} +} + +void wmemcpy11(void) { + wchar_t a[4] = {0}; + wmemcpy(a, 0, 1); // expected-warning{{Null pointer passed as 2nd argument to memory copy function}} +} + +void wmemcpy12(void) { + wchar_t a[4] = {0}; + wmemcpy(0, a, 0); // no-warning +} + +void wmemcpy13(void) { + wchar_t a[4] = {0}; + wmemcpy(a, 0, 0); // no-warning +} + +void wmemcpy_unknown_size (size_t n) { + wchar_t a[4], b[4] = {1}; + clang_analyzer_eval(wmemcpy(a, b, n) == a); // expected-warning{{TRUE}} +} + +void wmemcpy_unknown_size_warn (size_t n) { + wchar_t a[4]; + void *result = wmemcpy(a, 0, n); // expected-warning{{Null pointer passed as 2nd argument to memory copy function}} + clang_analyzer_eval(result == a); // no-warning (above is fatal) +} + +//===----------------------------------------------------------------------=== +// wcslen() +//===----------------------------------------------------------------------=== + +#define wcslen BUILTIN(wcslen) +size_t wcslen(const wchar_t *s); + +void wcslen_constant0(void) { + clang_analyzer_eval(wcslen(L"123") == 3); // expected-warning{{TRUE}} +} + +void wcslen_constant1(void) { + const wchar_t *a = L"123"; + clang_analyzer_eval(wcslen(a) == 3); // expected-warning{{TRUE}} +} + +void wcslen_constant2(wchar_t x) { + wchar_t a[] = L"123"; + clang_analyzer_eval(wcslen(a) == 3); // expected-warning{{TRUE}} + + a[0] = x; + clang_analyzer_eval(wcslen(a) == 3); // expected-warning{{UNKNOWN}} +} + +size_t wcslen_null(void) { + return wcslen(0); // expected-warning{{Null pointer passed as 1st argument to string length function}} +} + +size_t wcslen_fn(void) { + return wcslen((wchar_t*)&wcslen_fn); // expected-warning{{Argument to string length function is the address of the function 'wcslen_fn', which is not a null-terminated string}} +} + +size_t wcslen_nonloc(void) { +label: + return wcslen((wchar_t*)&&label); // expected-warning{{Argument to string length function is the address of the label 'label', which is not a null-terminated string}} +} + +void wcslen_subregion(void) { + struct two_strings { wchar_t a[2], b[2]; }; + extern void use_two_strings(struct two_strings *); + + struct two_strings z; + use_two_strings(&z); + + size_t a = wcslen(z.a); + z.b[0] = 5; + size_t b = wcslen(z.a); + if (a == 0) + clang_analyzer_eval(b == 0); // expected-warning{{TRUE}} + + use_two_strings(&z); + + size_t c = wcslen(z.a); + if (a == 0) + clang_analyzer_eval(c == 0); // expected-warning{{UNKNOWN}} +} + +extern void use_string(wchar_t *); +void wcslen_argument(wchar_t *x) { + size_t a = wcslen(x); + size_t b = wcslen(x); + if (a == 0) + clang_analyzer_eval(b == 0); // expected-warning{{TRUE}} + + use_string(x); + + size_t c = wcslen(x); + if (a == 0) + clang_analyzer_eval(c == 0); // expected-warning{{UNKNOWN}} +} + +extern wchar_t global_str[]; +void wcslen_global(void) { + size_t a = wcslen(global_str); + size_t b = wcslen(global_str); + if (a == 0) { + clang_analyzer_eval(b == 0); // expected-warning{{TRUE}} + // Make sure clang_analyzer_eval does not invalidate globals. + clang_analyzer_eval(wcslen(global_str) == 0); // expected-warning{{TRUE}} + } + + // Call a function with unknown effects, which should invalidate globals. + use_string(0); + + size_t c = wcslen(global_str); + if (a == 0) + clang_analyzer_eval(c == 0); // expected-warning{{UNKNOWN}} +} + +void wcslen_indirect(wchar_t *x) { + size_t a = wcslen(x); + wchar_t *p = x; + wchar_t **p2 = &p; + size_t b = wcslen(x); + if (a == 0) + clang_analyzer_eval(b == 0); // expected-warning{{TRUE}} + + extern void use_string_ptr(wchar_t*const*); + use_string_ptr(p2); + + size_t c = wcslen(x); + if (a == 0) + clang_analyzer_eval(c == 0); // expected-warning{{UNKNOWN}} +} + +void wcslen_indirect2(wchar_t *x) { + size_t a = wcslen(x); + wchar_t *p = x; + wchar_t **p2 = &p; + extern void use_string_ptr2(wchar_t**); + use_string_ptr2(p2); + + size_t c = wcslen(x); + if (a == 0) + clang_analyzer_eval(c == 0); // expected-warning{{UNKNOWN}} +} + +void wcslen_liveness(const wchar_t *x) { + if (wcslen(x) < 5) + return; + clang_analyzer_eval(wcslen(x) < 5); // expected-warning{{FALSE}} +} + + +size_t wcslenWrapper(const wchar_t *str) { + return wcslen(str); +} + +extern void invalidate(wchar_t *s); + +void testwcslenCallee(void) { + wchar_t str[42]; + invalidate(str); + size_t lenBefore = wcslenWrapper(str); + invalidate(str); + size_t lenAfter = wcslenWrapper(str); + clang_analyzer_eval(lenBefore == lenAfter); // expected-warning{{UNKNOWN}} +} + +//===----------------------------------------------------------------------=== +// wcsnlen() +//===----------------------------------------------------------------------=== + +size_t wcsnlen(const wchar_t *s, size_t maxlen); + +void wcsnlen_constant0(void) { + clang_analyzer_eval(wcsnlen(L"123", 10) == 3); // expected-warning{{TRUE}} +} + +void wcsnlen_constant1(void) { + const wchar_t *a = L"123"; + clang_analyzer_eval(wcsnlen(a, 10) == 3); // expected-warning{{TRUE}} +} + +void wcsnlen_constant2(char x) { + wchar_t a[] = L"123"; + clang_analyzer_eval(wcsnlen(a, 10) == 3); // expected-warning{{TRUE}} + a[0] = x; + clang_analyzer_eval(wcsnlen(a, 10) == 3); // expected-warning{{UNKNOWN}} +} + +void wcsnlen_constant4(void) { + clang_analyzer_eval(wcsnlen(L"123456", 3) == 3); // expected-warning{{TRUE}} +} + +void wcsnlen_constant5(void) { + const wchar_t *a = L"123456"; + clang_analyzer_eval(wcsnlen(a, 3) == 3); // expected-warning{{TRUE}} +} + +void wcsnlen_constant6(char x) { + wchar_t a[] = L"123456"; + clang_analyzer_eval(wcsnlen(a, 3) == 3); // expected-warning{{TRUE}} + a[0] = x; + clang_analyzer_eval(wcsnlen(a, 3) == 3); // expected-warning{{UNKNOWN}} +} + +size_t wcsnlen_null(void) { + return wcsnlen(0, 3); // expected-warning{{Null pointer passed as 1st argument to string length function}} +} + +size_t wcsnlen_fn(void) { + return wcsnlen((wchar_t*)&wcsnlen_fn, 3); // expected-warning{{Argument to string length function is the address of the function 'wcsnlen_fn', which is not a null-terminated string}} +} + +size_t wcsnlen_nonloc(void) { +label: + return wcsnlen((wchar_t*)&&label, 3); // expected-warning{{Argument to string length function is the address of the label 'label', which is not a null-terminated string}} +} + +void wcsnlen_zero(void) { + clang_analyzer_eval(wcsnlen(L"abc", 0) == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(wcsnlen(0, 0) == 0); // expected-warning{{TRUE}} +} + +size_t wcsnlen_compound_literal(void) { + // This used to crash because we don't model the string lengths of + // compound literals. + return wcsnlen((wchar_t[]) { 'a', 'b', 0 }, 1); +} + +size_t wcsnlen_unknown_limit(float f) { + // This used to crash because we don't model the integer values of floats. + return wcsnlen(L"abc", (int)f); +} + +void wcsnlen_is_not_wcslen(wchar_t *x) { + clang_analyzer_eval(wcsnlen(x, 10) == wcslen(x)); // expected-warning{{UNKNOWN}} +} + +void wcsnlen_at_limit(wchar_t *x) { + size_t len = wcsnlen(x, 10); + clang_analyzer_eval(len <= 10); // expected-warning{{TRUE}} + clang_analyzer_eval(len == 10); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(len < 10); // expected-warning{{UNKNOWN}} +} + +void wcsnlen_at_actual(size_t limit) { + size_t len = wcsnlen(L"abc", limit); + clang_analyzer_eval(len <= 3); // expected-warning{{TRUE}} + // This is due to eager assertion in wcsnlen. + if (limit == 0) { + clang_analyzer_eval(len == 0); // expected-warning{{TRUE}} + } else { + clang_analyzer_eval(len == 3); // expected-warning{{UNKNOWN}} + clang_analyzer_eval(len < 3); // expected-warning{{UNKNOWN}} + } +} + +//===----------------------------------------------------------------------=== +// other tests +//===----------------------------------------------------------------------=== + +static const wchar_t w_str[] = L"Hello world"; + +void wmemcpy_sizeof(void) { + wchar_t a[32]; + wmemcpy(a, w_str, sizeof(w_str) / sizeof(w_str[0])); + wmemcpy(a, w_str, (sizeof(w_str) / sizeof(w_str[0])) + 1); // expected-warning {{Memory copy function accesses out-of-bound array element}} +} + +void wmemcpy_wcslen(void) { + wchar_t a[32]; + // FIXME: This should work with 'w_str' instead of 'w_str1' + const wchar_t w_str1[] = L"Hello world"; + 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}} +}