Thanks for all the feedback and tips, I have hopefully addressed the comments 
from the first patch by:

- reorganised the patch so that it only uses the printf specifier expression 
checker,
- I have also removed the CheckSprintfHandler class and moved the functionality 
into CheckPrintfHandler,
- used a switch statement on ConversionSpecifier::Kind to check each specifier,
- used Expr functionality to evaluate constant integer expressions,
- added platform specific checks for pointer formatting,
- I have created a specific error message which reports the size needed.

cheers,


http://reviews.llvm.org/D9665

Files:
  include/clang/Basic/DiagnosticSemaKinds.td
  include/clang/Sema/Sema.h
  lib/Sema/SemaChecking.cpp
  test/Sema/sprintf-chk.cpp

EMAIL PREFERENCES
  http://reviews.llvm.org/settings/panel/emailpreferences/
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -482,6 +482,11 @@
   "%0 will always overflow destination buffer">,
   InGroup<DiagGroup<"builtin-memcpy-chk-size">>;
 
+def warn_sprintf_chk_overflow : Warning<
+  "call will always overflow destination buffer as it requires at least %0 "
+  "bytes whereas %1 are provided">,
+  InGroup<Format>;
+
 /// main()
 // static main() is not an error in C, just in C++.
 def warn_static_main : Warning<"'main' should not be declared static">,
Index: include/clang/Sema/Sema.h
===================================================================
--- include/clang/Sema/Sema.h
+++ include/clang/Sema/Sema.h
@@ -8740,6 +8740,7 @@
   enum FormatStringType {
     FST_Scanf,
     FST_Printf,
+    FST_Sprintf,    // extra check on top of Printf
     FST_NSString,
     FST_Strftime,
     FST_Strfmon,
@@ -8767,13 +8768,15 @@
                             bool IsCXXMember,
                             VariadicCallType CallType,
                             SourceLocation Loc, SourceRange Range,
-                            llvm::SmallBitVector &CheckedVarArgs);
+                            llvm::SmallBitVector &CheckedVarArgs,
+                            bool isSprintf);
   bool CheckFormatArguments(ArrayRef<const Expr *> Args,
                             bool HasVAListArg, unsigned format_idx,
                             unsigned firstDataArg, FormatStringType Type,
                             VariadicCallType CallType,
                             SourceLocation Loc, SourceRange range,
-                            llvm::SmallBitVector &CheckedVarArgs);
+                            llvm::SmallBitVector &CheckedVarArgs,
+                            bool isSprintf);
 
   void CheckAbsoluteValueFunction(const CallExpr *Call,
                                   const FunctionDecl *FDecl,
Index: lib/Sema/SemaChecking.cpp
===================================================================
--- lib/Sema/SemaChecking.cpp
+++ lib/Sema/SemaChecking.cpp
@@ -1313,12 +1313,16 @@
   // Printf and scanf checking.
   llvm::SmallBitVector CheckedVarArgs;
   if (FDecl) {
+    bool isSprintf = false;
+    if (auto *CastFDecl = dyn_cast<FunctionDecl>(FDecl))
+      if (CastFDecl->getBuiltinID() == Builtin::BI__builtin___sprintf_chk)
+        isSprintf = true;
     for (const auto *I : FDecl->specific_attrs<FormatAttr>()) {
       // Only create vector if there are format attributes.
       CheckedVarArgs.resize(Args.size());
 
       CheckFormatArguments(I, Args, IsMemberFunction, CallType, Loc, Range,
-                           CheckedVarArgs);
+                           CheckedVarArgs, isSprintf);
     }
   }
 
@@ -3050,27 +3054,33 @@
                                 bool IsCXXMember,
                                 VariadicCallType CallType,
                                 SourceLocation Loc, SourceRange Range,
-                                llvm::SmallBitVector &CheckedVarArgs) {
+                                llvm::SmallBitVector &CheckedVarArgs,
+                                bool isSprintf) {
   FormatStringInfo FSI;
   if (getFormatStringInfo(Format, IsCXXMember, &FSI))
     return CheckFormatArguments(Args, FSI.HasVAListArg, FSI.FormatIdx,
                                 FSI.FirstDataArg, GetFormatStringType(Format),
-                                CallType, Loc, Range, CheckedVarArgs);
+                                CallType, Loc, Range, CheckedVarArgs,
+                                isSprintf);
   return false;
 }
 
 bool Sema::CheckFormatArguments(ArrayRef<const Expr *> Args,
                                 bool HasVAListArg, unsigned format_idx,
                                 unsigned firstDataArg, FormatStringType Type,
                                 VariadicCallType CallType,
                                 SourceLocation Loc, SourceRange Range,
-                                llvm::SmallBitVector &CheckedVarArgs) {
+                                llvm::SmallBitVector &CheckedVarArgs,
+                                bool isSprintf) {
   // CHECK: printf/scanf-like function is called with no format string.
   if (format_idx >= Args.size()) {
     Diag(Loc, diag::warn_missing_format_string) << Range;
     return false;
   }
 
+  if (isSprintf)
+    Type = FST_Sprintf;
+
   const Expr *OrigFormatExpr = Args[format_idx]->IgnoreParenCasts();
 
   // CHECK: format string is not a string literal.
@@ -3523,7 +3533,7 @@
   }
 }
 
-//===--- CHECK: Printf format string checking ------------------------------===//
+//===--- CHECK: Printf format string checking -----------------------------===//
 
 namespace {
 class CheckPrintfHandler : public CheckFormatHandler {
@@ -3536,12 +3546,15 @@
                      ArrayRef<const Expr *> Args,
                      unsigned formatIdx, bool inFunctionCall,
                      Sema::VariadicCallType CallType,
-                     llvm::SmallBitVector &CheckedVarArgs)
+                     llvm::SmallBitVector &CheckedVarArgs,
+                     bool isSprintf)
     : CheckFormatHandler(s, fexpr, origFormatExpr, firstDataArg,
                          numDataArgs, beg, hasVAListArg, Args,
                          formatIdx, inFunctionCall, CallType, CheckedVarArgs),
-      ObjCContext(isObjC)
-  {}
+      ObjCContext(isObjC), isSprintf(isSprintf)
+  {
+    SprintfRequiredStorage = 1;
+  }
 
 
   bool HandleInvalidPrintfConversionSpecifier(
@@ -3557,6 +3570,10 @@
                        unsigned SpecifierLen,
                        const Expr *E);
 
+  bool HandleSprintfSpecifier(const analyze_printf::PrintfSpecifier &FS,
+                              const char *startSpecifier,
+                              unsigned specifierLen);
+
   bool HandleAmount(const analyze_format_string::OptionalAmount &Amt, unsigned k,
                     const char *startSpecifier, unsigned specifierLen);
   void HandleInvalidAmount(const analyze_printf::PrintfSpecifier &FS,
@@ -3572,8 +3589,10 @@
                          const char *startSpecifier, unsigned specifierLen);
   bool checkForCStrMembers(const analyze_printf::ArgType &AT,
                            const Expr *E);
-
-};  
+private:
+  bool isSprintf;
+  uint64_t SprintfRequiredStorage;
+};
 }
 
 bool CheckPrintfHandler::HandleInvalidPrintfConversionSpecifier(
@@ -3916,7 +3935,171 @@
   if (!Arg)
     return true;
 
-  return checkFormatExpr(FS, startSpecifier, specifierLen, Arg);
+
+  if (checkFormatExpr(FS, startSpecifier, specifierLen, Arg)) {
+    if (isSprintf)
+      return HandleSprintfSpecifier(FS, startSpecifier, specifierLen);
+    else
+      return true;
+  }
+  return false;
+}
+
+static const ConstantArrayType *getConstArrayType(Sema &S, const Expr *Array) {
+  return S.Context.getAsConstantArrayType(Array->getType());
+}
+
+// Analyses the data arguments to sprintf to warn the user of memory overflows.
+// Mainly handles integer and string literals with their specifiers and flags
+// with little support for floating point values. None specifier text is also
+// not considered towards the storage space required for the string.
+bool CheckPrintfHandler::HandleSprintfSpecifier(
+  const analyze_printf::PrintfSpecifier &FS,
+  const char *startSpecifier, unsigned specifierLen) {
+
+  using namespace analyze_format_string;
+  using namespace analyze_printf;
+
+  const PrintfConversionSpecifier &CS = FS.getConversionSpecifier();
+  ConversionSpecifier::Kind CSKind = CS.getKind();
+  OptionalAmount Precision = FS.getPrecision();
+  OptionalAmount Width = FS.getFieldWidth();
+  // Offset the argument index to the data items to be printed.
+  unsigned Idx = 4 + FS.getArgIndex();
+  const Expr* Data = Args[Idx]->IgnoreImplicit();
+  uint64_t StorageBufSize = 0;
+  uint64_t RequiredStorage = 0;
+
+  if (auto *StorageBufTy = getConstArrayType(S, Args[0]->IgnoreImplicit()))
+    StorageBufSize = StorageBufTy->getSize().getZExtValue();
+  else
+    return true;
+
+  switch (CSKind) {
+  default:
+    // We can't handle eArg, EArg, fArg, FArg, gArg, GArg, nArg
+    break;
+  case ConversionSpecifier::aArg:
+  case ConversionSpecifier::AArg: {
+    if (isa<FloatingLiteral>(Data)) {
+      llvm::APFloat Value = cast<FloatingLiteral>(Data)->getValue();
+      char TempBuf[32];
+      unsigned HexDigits = 0;
+
+      if (Precision.getHowSpecified() == OptionalAmount::Constant)
+        HexDigits = Precision.getConstantAmount();
+
+      // Get the number of digits used to print the value.
+      unsigned NumDigits =
+        Value.convertToHexString(TempBuf, HexDigits, false,
+                                 llvm::APFloat::rmNearestTiesToEven);
+      RequiredStorage += NumDigits;
+    }
+    break;
+  }
+  case ConversionSpecifier::cArg:
+  case ConversionSpecifier::CArg:
+   ++RequiredStorage;
+   break;
+  case ConversionSpecifier::dArg:
+  case ConversionSpecifier::DArg:
+  case ConversionSpecifier::iArg:
+  case ConversionSpecifier::uArg:
+  case ConversionSpecifier::UArg: {
+    if (Data->isEvaluatable(S.getASTContext())) {
+      llvm::APSInt Result = llvm::APSInt(64);
+      if (CSKind == ConversionSpecifier::uArg ||
+          CSKind == ConversionSpecifier::UArg)
+        Result = llvm::APSInt(64, false);
+      if (Data->EvaluateAsInt(Result, S.getASTContext())) {
+        std::string DataStr = Result.toString(10, true);
+        RequiredStorage += DataStr.length();
+      }
+    }
+    break;
+  }
+  case ConversionSpecifier::pArg: {
+    // When using the %p specifier, all platforms appear to use hex digits, this
+    // is prepended with '0x' on BSD, Linux and OSX.
+    const TargetInfo &TI = S.Context.getTargetInfo();
+    RequiredStorage += TI.getPointerWidth(0) / 4;
+    if (TI.getTriple().isOSDarwin() ||
+        TI.getTriple().isOSLinux() ||
+        TI.getTriple().isOSNetBSD() ||
+        TI.getTriple().isOSOpenBSD() ||
+        TI.getTriple().isOSFreeBSD() ||
+        TI.getTriple().isOSDragonFly())
+      RequiredStorage += 2;
+    break;
+  }
+  case ConversionSpecifier::sArg:
+  case ConversionSpecifier::SArg:
+  case ConversionSpecifier::ZArg:
+    if (auto SL = dyn_cast<StringLiteral>(Data))
+      RequiredStorage += SL->getByteLength();
+    else if (auto DataBuf = getConstArrayType(S, Data)) {
+      uint64_t BufSize = DataBuf->getSize().getZExtValue();
+      RequiredStorage += BufSize;
+    }
+    break;
+  case ConversionSpecifier::oArg:
+  case ConversionSpecifier::OArg:
+  case ConversionSpecifier::xArg:
+  case ConversionSpecifier::XArg: {
+    unsigned Radix = 16;
+    if (CSKind == ConversionSpecifier::oArg ||
+        CSKind == ConversionSpecifier::OArg)
+      Radix = 8;
+    if (isa<IntegerLiteral>(Data)) {
+      // Create a real string to use its size.
+      std::string DataStr =
+        cast<IntegerLiteral>(Data)->getValue().toString(Radix, true);
+      RequiredStorage += DataStr.length();
+      // 0x will can be prepended for oct and hex values.
+      if (FS.hasAlternativeForm())
+        RequiredStorage += 2;
+    }
+    break;
+  }
+  }
+
+  // An extra will be required for +/-
+  if (FS.hasPlusPrefix() || FS.hasSpacePrefix())
+    ++RequiredStorage;
+
+  // The calculated sizes could be smaller than sizes specified by the user
+  // in their format string.
+  if (CS.isAnyIntArg()) {
+    // For integers, the precision modifier specifies how many digits to be
+    // written so this may overwrite the calculated size.
+    if (Precision.getHowSpecified() == OptionalAmount::Constant) {
+      if (RequiredStorage <= Precision.getConstantAmount())
+        RequiredStorage = Precision.getConstantAmount();
+    }
+  }
+
+  // Width specifies the minimum number of characters to be printed.
+  if (Width.getHowSpecified() == OptionalAmount::Constant) {
+    if (RequiredStorage <= Width.getConstantAmount())
+      RequiredStorage = Width.getConstantAmount();
+  }
+
+  // Update the total required.
+  SprintfRequiredStorage += RequiredStorage;
+
+  if (StorageBufSize < SprintfRequiredStorage) {
+    std::string Limit = llvm::APInt(64, StorageBufSize).toString(10, false);
+    std::string Required =
+      llvm::APInt(64, SprintfRequiredStorage).toString(10, false);
+    S.Diag(Args[0]->getLocStart(), diag::warn_sprintf_chk_overflow)
+      << getSpecifierRange(startSpecifier, specifierLen)
+      << Data->getSourceRange()
+      << Required
+      << Limit;
+    return false;
+  }
+
+  return true;
 }
 
 static bool requiresParensToAddCast(const Expr *E) {
@@ -4493,11 +4676,14 @@
   }
   
   if (Type == FST_Printf || Type == FST_NSString ||
-      Type == FST_FreeBSDKPrintf || Type == FST_OSTrace) {
+      Type == FST_FreeBSDKPrintf || Type == FST_OSTrace ||
+      Type == FST_Sprintf) {
     CheckPrintfHandler H(*this, FExpr, OrigFormatExpr, firstDataArg,
-                         numDataArgs, (Type == FST_NSString || Type == FST_OSTrace),
+                         numDataArgs,
+                         (Type == FST_NSString || Type == FST_OSTrace),
                          Str, HasVAListArg, Args, format_idx,
-                         inFunctionCall, CallType, CheckedVarArgs);
+                         inFunctionCall, CallType, CheckedVarArgs,
+                         Type == FST_Sprintf);
   
     if (!analyze_format_string::ParsePrintfString(H, Str, Str + StrLen,
                                                   getLangOpts(),
Index: test/Sema/sprintf-chk.cpp
===================================================================
--- /dev/null
+++ test/Sema/sprintf-chk.cpp
@@ -0,0 +1,106 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fsyntax-only %s
+void test() {
+
+  char bufA[4];
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%d", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%i", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%u", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%x", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%X", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%o", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%s", "foo");
+  char addrBuf[19];
+  __builtin___sprintf_chk(addrBuf, 0, __builtin_object_size(addrBuf, 0), "%p",
+                          bufA);
+
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%d%d", 10, 10);
+  // expected-warning@-1{{call will always overflow destination buffer as it requires at least 5 bytes whereas 4 are provided}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%i%i", 10, 10);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%u%u", 10, 10);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%x%x", 100, 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%X%X", 100, 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%o%o", 100, 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%d", 1000);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%i", 1000);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%u", 1000);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%x", 10000);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%X", 10000);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%o", 1000);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%a", 10.0);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%A", 10.0);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%s", "foobar");
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%p", bufA);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%+d", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%+d", 10);
+
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%3d", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%3i", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%3u", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%3x", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%3X", 100);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%3o", 100);
+
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%4d", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%4i", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%4u", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%4x", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%4X", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%4o", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%#x", 15);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%#X", 15);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%#o", 7);
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%#x", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%#X", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufA, 0, __builtin_object_size(bufA, 0), "%#o", 100);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+
+
+  char bufB[7];
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%a", 0.5);
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%A", 0.5);
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%a", 10.0);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%A", 10.0);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%1.1a", 0.5);
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%1.1A", 0.5);
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%1.2a", 0.5);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+  __builtin___sprintf_chk(bufB, 0, __builtin_object_size(bufB, 0), "%1.2A", 0.5);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+
+  char bufC[5];
+  __builtin___sprintf_chk(bufC, 0, __builtin_object_size(bufC, 0), "%s%s",
+                          "foo", "bar");
+  // expected-warning@-2{{call will always overflow destination buffer}}
+  const char bar[] = "foobar";
+  __builtin___sprintf_chk(bufC, 0, __builtin_object_size(bufC, 0), "%s", bar);
+  // expected-warning@-1{{call will always overflow destination buffer}}
+}
_______________________________________________
cfe-commits mailing list
[email protected]
http://lists.cs.uiuc.edu/mailman/listinfo/cfe-commits

Reply via email to