[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