From 8d31f2930968ddaeba91ffca392116e912f26fc3 Mon Sep 17 00:00:00 2001
From: Benjamin Kramer <benny.kra@googlemail.com>
Date: Thu, 20 Feb 2014 17:05:38 +0000
Subject: [PATCH] Sema: Emit a warning for non-null terminated format strings
 and other pathological cases.

PR18905.

git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@201795 91177308-0d34-0410-b5e6-96231b3b80d8
---
 include/clang/Basic/DiagnosticSemaKinds.td |  2 ++
 lib/Sema/SemaChecking.cpp                  | 20 ++++++++++++++++++--
 test/Sema/format-strings.c                 | 15 +++++++++++++++
 3 files changed, 35 insertions(+), 2 deletions(-)

diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td
index a2415f7a956..62ae84595cb 100644
--- a/include/clang/Basic/DiagnosticSemaKinds.td
+++ b/include/clang/Basic/DiagnosticSemaKinds.td
@@ -6223,6 +6223,8 @@ def warn_format_string_is_wide_literal : Warning<
   "format string should not be a wide string">, InGroup<Format>;
 def warn_printf_format_string_contains_null_char : Warning<
   "format string contains '\\0' within the string body">, InGroup<Format>;
+def warn_printf_format_string_not_null_terminated : Warning<
+  "format string is not null-terminated">, InGroup<Format>;
 def warn_printf_asterisk_missing_arg : Warning<
   "'%select{*|.*}0' specified field %select{width|precision}0 is missing a matching 'int' argument">,
   InGroup<Format>;
diff --git a/lib/Sema/SemaChecking.cpp b/lib/Sema/SemaChecking.cpp
index cfd042f9459..f3f08dec97f 100644
--- a/lib/Sema/SemaChecking.cpp
+++ b/lib/Sema/SemaChecking.cpp
@@ -3493,9 +3493,25 @@ void Sema::CheckFormatString(const StringLiteral *FExpr,
   // Str - The format string.  NOTE: this is NOT null-terminated!
   StringRef StrRef = FExpr->getString();
   const char *Str = StrRef.data();
-  unsigned StrLen = StrRef.size();
+  // Account for cases where the string literal is truncated in a declaration.
+  const ConstantArrayType *T = Context.getAsConstantArrayType(FExpr->getType());
+  assert(T && "String literal not of constant array type!");
+  size_t TypeSize = T->getSize().getZExtValue();
+  size_t StrLen = std::min(std::max(TypeSize, size_t(1)) - 1, StrRef.size());
   const unsigned numDataArgs = Args.size() - firstDataArg;
-  
+
+  // Emit a warning if the string literal is truncated and does not contain an
+  // embedded null character.
+  if (TypeSize <= StrRef.size() &&
+      StrRef.substr(0, TypeSize).find('\0') == StringRef::npos) {
+    CheckFormatHandler::EmitFormatDiagnostic(
+        *this, inFunctionCall, Args[format_idx],
+        PDiag(diag::warn_printf_format_string_not_null_terminated),
+        FExpr->getLocStart(),
+        /*IsStringLocation=*/true, OrigFormatExpr->getSourceRange());
+    return;
+  }
+
   // CHECK: empty format string?
   if (StrLen == 0 && numDataArgs > 0) {
     CheckFormatHandler::EmitFormatDiagnostic(
diff --git a/test/Sema/format-strings.c b/test/Sema/format-strings.c
index 6ed665c7003..ad7b37c3e11 100644
--- a/test/Sema/format-strings.c
+++ b/test/Sema/format-strings.c
@@ -535,6 +535,21 @@ void pr9751() {
          0.0); // expected-warning{{format specifies}}
 }
 
+void pr18905() {
+  const char s1[] = "s\0%s"; // expected-note{{format string is defined here}}
+  const char s2[1] = "s"; // expected-note{{format string is defined here}}
+  const char s3[2] = "s\0%s"; // expected-warning{{initializer-string for char array is too long}}
+  const char s4[10] = "s";
+  const char s5[0] = "%s"; // expected-warning{{initializer-string for char array is too long}}
+                           // expected-note@-1{{format string is defined here}}
+
+  printf(s1); // expected-warning{{format string contains '\0' within the string body}}
+  printf(s2); // expected-warning{{format string is not null-terminated}}
+  printf(s3); // no-warning
+  printf(s4); // no-warning
+  printf(s5); // expected-warning{{format string is not null-terminated}}
+}
+
 void __attribute__((format(strfmon,1,2))) monformat(const char *fmt, ...);
 void __attribute__((format(strftime,1,0))) dateformat(const char *fmt);
 
-- 
GitLab