]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c: do not warn about truncating NUL char when initializing nonstring arrays [PR117178]
authorJakub Jelinek <jakub@redhat.com>
Fri, 7 Mar 2025 22:59:34 +0000 (23:59 +0100)
committerJakub Jelinek <jakub@gcc.gnu.org>
Fri, 7 Mar 2025 22:59:34 +0000 (23:59 +0100)
When initializing a nonstring char array when compiled with
-Wunterminated-string-initialization the warning trips even when
truncating the trailing NUL character from the string constant.  Only
warn about this when running under -Wc++-compat since under C++ we should
not initialize nonstrings from C strings.

This patch separates the -Wunterminated-string-initialization and
-Wc++-compat warnings, they are now independent option, the former implied
by -Wextra, the latter not implied by anything.  If -Wc++-compat is in effect,
it takes precedence over -Wunterminated-string-initialization and warns regardless
of nonstring attribute, otherwise if -Wunterminated-string-initialization is
enabled, it warns only if there isn't nonstring attribute.
In all cases, the warnings and also pedwarn_init for even larger sizes now
provide details on the lengths.

2025-03-07  Kees Cook  <kees@kernel.org>
    Jakub Jelinek  <jakub@redhat.com>

PR c/117178
gcc/
* doc/invoke.texi (Wunterminated-string-initialization): Document
the new interaction between this warning and -Wc++-compat and that
initialization of decls with nonstring attribute aren't warned about.
gcc/c-family/
* c.opt (Wunterminated-string-initialization): Don't depend on
-Wc++-compat.
gcc/c/
* c-typeck.cc (digest_init): Add DECL argument.  Adjust wording of
pedwarn_init for too long strings and provide details on the lengths,
for string literals where just the trailing NULL doesn't fit warn for
warn_cxx_compat with OPT_Wc___compat, wording which mentions "for C++"
and provides details on lengths, otherwise for
warn_unterminated_string_initialization adjust the warning, provide
details on lengths and don't warn if get_attr_nonstring_decl (decl).
(build_c_cast, store_init_value, output_init_element): Adjust
digest_init callers.
gcc/testsuite/
* gcc.dg/Wunterminated-string-initialization.c: Add additional test
coverage.
* gcc.dg/Wcxx-compat-14.c: Check in dg-warning for "for C++" part of
the diagnostics.
* gcc.dg/Wcxx-compat-23.c: New test.
* gcc.dg/Wcxx-compat-24.c: New test.

Signed-off-by: Kees Cook <kees@kernel.org>
gcc/c-family/c.opt
gcc/c/c-typeck.cc
gcc/doc/invoke.texi
gcc/testsuite/gcc.dg/Wcxx-compat-14.c
gcc/testsuite/gcc.dg/Wcxx-compat-23.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/Wcxx-compat-24.c [new file with mode: 0644]
gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c

index f432203b2ebc2d7e71324fc78ce0e6fe991d19af..6c6922ab2018f65997faeb8e34db2428529a9039 100644 (file)
@@ -1550,7 +1550,7 @@ C ObjC Var(warn_unsuffixed_float_constants) Warning
 Warn about unsuffixed float constants.
 
 Wunterminated-string-initialization
-C ObjC Var(warn_unterminated_string_initialization) Warning LangEnabledBy(C ObjC,Wextra || Wc++-compat)
+C ObjC Var(warn_unterminated_string_initialization) Warning LangEnabledBy(C ObjC,Wextra)
 Warn about character arrays initialized as unterminated character sequences with a string literal.
 
 Wunused
index a13989a6607662d6ce16a21bb804d6e6abbac304..bc51cc2693bfe5d3bb5ac45e5b6c0ddabaaedc63 100644 (file)
@@ -116,8 +116,8 @@ static void push_member_name (tree);
 static int spelling_length (void);
 static char *print_spelling (char *);
 static void warning_init (location_t, int, const char *);
-static tree digest_init (location_t, tree, tree, tree, bool, bool, bool, bool,
-                        bool, bool);
+static tree digest_init (location_t, tree, tree, tree, tree, bool, bool, bool,
+                        bool, bool, bool);
 static void output_init_element (location_t, tree, tree, bool, tree, tree, bool,
                                 bool, struct obstack *);
 static void output_pending_init_elements (int, struct obstack *);
@@ -6844,7 +6844,7 @@ build_c_cast (location_t loc, tree type, tree expr)
          t = build_constructor_single (type, field, t);
          if (!maybe_const)
            t = c_wrap_maybe_const (t, true);
-         t = digest_init (loc, type, t,
+         t = digest_init (loc, field, type, t,
                           NULL_TREE, false, false, false, true, false, false);
          TREE_CONSTANT (t) = TREE_CONSTANT (value);
          return t;
@@ -8874,8 +8874,8 @@ store_init_value (location_t init_loc, tree decl, tree init, tree origtype)
     }
   bool constexpr_p = (VAR_P (decl)
                      && C_DECL_DECLARED_CONSTEXPR (decl));
-  value = digest_init (init_loc, type, init, origtype, npc, int_const_expr,
-                      arith_const_expr, true,
+  value = digest_init (init_loc, decl, type, init, origtype, npc,
+                      int_const_expr, arith_const_expr, true,
                       TREE_STATIC (decl) || constexpr_p, constexpr_p);
 
   /* Store the expression if valid; else report error.  */
@@ -9201,7 +9201,8 @@ check_constexpr_init (location_t loc, tree type, tree init,
              "type of object");
 }
 
-/* Digest the parser output INIT as an initializer for type TYPE.
+/* Digest the parser output INIT as an initializer for type TYPE
+   initializing DECL.
    Return a C expression of type TYPE to represent the initial value.
 
    If ORIGTYPE is not NULL_TREE, it is the original type of INIT.
@@ -9224,8 +9225,8 @@ check_constexpr_init (location_t loc, tree type, tree init,
    on initializers for 'constexpr' objects apply.  */
 
 static tree
-digest_init (location_t init_loc, tree type, tree init, tree origtype,
-            bool null_pointer_constant, bool int_const_expr,
+digest_init (location_t init_loc, tree decl, tree type, tree init,
+            tree origtype, bool null_pointer_constant, bool int_const_expr,
             bool arith_const_expr, bool strict_string,
             bool require_constant, bool require_constexpr)
 {
@@ -9360,27 +9361,38 @@ digest_init (location_t init_loc, tree type, tree init, tree origtype,
              && TREE_CODE (TYPE_SIZE (type)) == INTEGER_CST)
            {
              unsigned HOST_WIDE_INT len = TREE_STRING_LENGTH (inside_init);
-             unsigned unit = TYPE_PRECISION (typ1) / BITS_PER_UNIT;
-
-             /* Subtract the size of a single (possibly wide) character
-                because it's ok to ignore the terminating null char
-                that is counted in the length of the constant.  */
-             if (compare_tree_int (TYPE_SIZE_UNIT (type), len - unit) < 0)
-               pedwarn_init (init_loc, 0,
-                             ("initializer-string for array of %qT "
-                              "is too long"), typ1);
-             else if (warn_unterminated_string_initialization
-                      && compare_tree_int (TYPE_SIZE_UNIT (type), len) < 0)
-               warning_at (init_loc, OPT_Wunterminated_string_initialization,
-                           ("initializer-string for array of %qT "
-                            "is too long"), typ1);
+
              if (compare_tree_int (TYPE_SIZE_UNIT (type), len) < 0)
                {
-                 unsigned HOST_WIDE_INT size
+                 unsigned HOST_WIDE_INT avail
                    = tree_to_uhwi (TYPE_SIZE_UNIT (type));
+                 unsigned unit = TYPE_PRECISION (typ1) / BITS_PER_UNIT;
                  const char *p = TREE_STRING_POINTER (inside_init);
 
-                 inside_init = build_string (size, p);
+                 /* Construct truncated string.  */
+                 inside_init = build_string (avail, p);
+
+                 /* Subtract the size of a single (possibly wide) character
+                    because it may be ok to ignore the terminating NUL char
+                    that is counted in the length of the constant.  */
+                 if (len - unit > avail)
+                   pedwarn_init (init_loc, 0,
+                                 "initializer-string for array of %qT "
+                                 "is too long (%wu chars into %wu "
+                                 "available)", typ1, len, avail);
+                 else if (warn_cxx_compat)
+                   warning_at (init_loc, OPT_Wc___compat,
+                               "initializer-string for array of %qT "
+                               "is too long for C++ (%wu chars into %wu "
+                               "available)", typ1, len, avail);
+                 else if (warn_unterminated_string_initialization
+                          && get_attr_nonstring_decl (decl) == NULL_TREE)
+                   warning_at (init_loc,
+                               OPT_Wunterminated_string_initialization,
+                               "initializer-string for array of %qT "
+                               "truncates NUL terminator but destination "
+                               "lacks %qs attribute (%wu chars into %wu "
+                               "available)", typ1, "nonstring", len, avail);
                }
            }
 
@@ -11407,7 +11419,7 @@ output_init_element (location_t loc, tree value, tree origtype,
   if (!require_constexpr_value
       || !npc
       || TREE_CODE (constructor_type) != POINTER_TYPE)
-    new_value = digest_init (loc, type, new_value, origtype, npc,
+    new_value = digest_init (loc, field, type, new_value, origtype, npc,
                             int_const_expr, arith_const_expr, strict_string,
                             require_constant_value, require_constexpr_value);
   if (new_value == error_mark_node)
index e01d64c5229f8d9b7df7304df3f952ad3e531c2f..3781697ae55facca5919f673293b7d28b861c3ba 100644 (file)
@@ -8689,17 +8689,20 @@ give a larger number of false positives and is deactivated by default.
 @opindex Wunterminated-string-initialization
 @opindex Wno-unterminated-string-initialization
 @item -Wunterminated-string-initialization @r{(C and Objective-C only)}
-Warn about character arrays
-initialized as unterminated character sequences
-with a string literal.
+Warn about character arrays initialized as unterminated character sequences
+with a string literal, unless the declaration being initialized has
+the @code{nonstring} attribute.
 For example:
 
 @smallexample
-char arr[3] = "foo";
+char arr[3] = "foo"; /* Warning.  */
+char arr2[3] __attribute__((nonstring)) = "bar"; /* No warning.  */
 @end smallexample
 
-This warning is enabled by @option{-Wextra} and @option{-Wc++-compat}.
-In C++, such initializations are an error.
+This warning is enabled by @option{-Wextra}.  If @option{-Wc++-compat}
+is enabled, the warning has slightly different wording and warns even
+if the declaration being initialized has the @code{nonstring} warning,
+as in C++ such initializations are an error.
 
 @opindex Warray-compare
 @opindex Wno-array-compare
index 6df0ee197cca70cd1332623679832b1f50c5b03b..ef1f0c09baa37d6ba873200f8b1910de78b61243 100644 (file)
@@ -2,5 +2,5 @@
 /* { dg-options "-Wc++-compat" } */
 
 char a1[] = "a";
-char a2[1] = "a";      /* { dg-warning "initializer-string for array of 'char' is too long" } */
+char a2[1] = "a";      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
 char a3[2] = "a";
diff --git a/gcc/testsuite/gcc.dg/Wcxx-compat-23.c b/gcc/testsuite/gcc.dg/Wcxx-compat-23.c
new file mode 100644 (file)
index 0000000..fd09149
--- /dev/null
@@ -0,0 +1,33 @@
+/* PR c/117178 */
+/* { dg-do compile } */
+/* { dg-options "-Wc++-compat -Wunterminated-string-initialization" } */
+
+char a1[] = "a";
+char a2[1] = "a";      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+char a2nonstring[1] __attribute__((nonstring)) = "a";  /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+char a3[1] = "aa";     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+char a4[2] = "a";
+
+struct has_str {
+  int a;
+  char str1[4];
+  char str2[4];
+  char str3[4];
+  char str4[4];
+  char tag1[4] __attribute__((nonstring));
+  char tag2[4] __attribute__((nonstring));
+  char tag3[4] __attribute__((nonstring));
+  char tag4[4] __attribute__((nonstring));
+  int b;
+};
+
+struct has_str foo = {
+  .str1 = "111",
+  .str2 = "2222",      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+  .str3 = "33333",     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+  .str4 = { '4', '4', '4', '4' },
+  .tag1 = "AAA",
+  .tag2 = "BBBB",      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+  .tag3 = "CCCCC",     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+  .tag4 = { 'D', 'D', 'D', 'D' }
+};
diff --git a/gcc/testsuite/gcc.dg/Wcxx-compat-24.c b/gcc/testsuite/gcc.dg/Wcxx-compat-24.c
new file mode 100644 (file)
index 0000000..da652ae
--- /dev/null
@@ -0,0 +1,33 @@
+/* PR c/117178 */
+/* { dg-do compile } */
+/* { dg-options "-Wc++-compat -Wno-unterminated-string-initialization" } */
+
+char a1[] = "a";
+char a2[1] = "a";      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+char a2nonstring[1] __attribute__((nonstring)) = "a";  /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+char a3[1] = "aa";     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+char a4[2] = "a";
+
+struct has_str {
+  int a;
+  char str1[4];
+  char str2[4];
+  char str3[4];
+  char str4[4];
+  char tag1[4] __attribute__((nonstring));
+  char tag2[4] __attribute__((nonstring));
+  char tag3[4] __attribute__((nonstring));
+  char tag4[4] __attribute__((nonstring));
+  int b;
+};
+
+struct has_str foo = {
+  .str1 = "111",
+  .str2 = "2222",      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+  .str3 = "33333",     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+  .str4 = { '4', '4', '4', '4' },
+  .tag1 = "AAA",
+  .tag2 = "BBBB",      /* { dg-warning "initializer-string for array of 'char' is too long for C\\\+\\\+" } */
+  .tag3 = "CCCCC",     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+  .tag4 = { 'D', 'D', 'D', 'D' }
+};
index 13d5dbc66400ce3020f7d5966d0cc16a57b67f58..5f25f02991976cae2d85f133900fd14fefe03cf7 100644 (file)
@@ -1,6 +1,33 @@
+/* PR c/117178 */
 /* { dg-do compile } */
 /* { dg-options "-Wunterminated-string-initialization" } */
 
 char a1[] = "a";
-char a2[1] = "a";      /* { dg-warning "initializer-string for array of 'char' is too long" } */
-char a3[2] = "a";
+char a2[1] = "a";      /* { dg-warning "initializer-string for array of 'char' truncates" } */
+char a2nonstring[1] __attribute__((nonstring)) = "a";
+char a3[1] = "aa";     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+char a4[2] = "a";
+
+struct has_str {
+  int a;
+  char str1[4];
+  char str2[4];
+  char str3[4];
+  char str4[4];
+  char tag1[4] __attribute__((nonstring));
+  char tag2[4] __attribute__((nonstring));
+  char tag3[4] __attribute__((nonstring));
+  char tag4[4] __attribute__((nonstring));
+  int b;
+};
+
+struct has_str foo = {
+  .str1 = "111",
+  .str2 = "2222",      /* { dg-warning "initializer-string for array of 'char' truncates" } */
+  .str3 = "33333",     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+  .str4 = { '4', '4', '4', '4' },
+  .tag1 = "AAA",
+  .tag2 = "BBBB",
+  .tag3 = "CCCCC",     /* { dg-warning "initializer-string for array of 'char' is too long" } */
+  .tag4 = { 'D', 'D', 'D', 'D' }
+};