public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
From: Jakub Jelinek <jakub@redhat.com>
To: "Joseph S. Myers" <josmyers@redhat.com>,
	Marek Polacek <polacek@redhat.com>, Kees Cook <kees@kernel.org>
Cc: gcc-patches@gcc.gnu.org
Subject: [PATCH] c, v2: do not warn about truncating NUL char when initializing nonstring arrays [PR117178]
Date: Thu, 13 Feb 2025 14:10:25 +0100	[thread overview]
Message-ID: <Z63vQc7tBvolrk27@tucnak> (raw)
In-Reply-To: <202502051048.EA39EE6AC@keescook>

On Wed, Feb 05, 2025 at 10:53:24AM -0800, Kees Cook wrote:
> On Wed, Feb 05, 2025 at 12:59:58PM +0100, Jakub Jelinek wrote:
> > Kees, any progress on this?
> 
> I need to take another run at it. I got stalled out when I discovered
> that I array-of-char-arrays attributes got applied at the "wrong" depth,
> and stuff wasn't working.
> 
> e.g.:
> 
> char acpi_table[TABLE_SIZE][4] __attribute((nonstring)) = {
> 	{ "ohai" },
> 	{ "1234" },
> };
> 
> when nonstring was checked for on something like "acpi_table[2]" it
> wouldn't be found, since it was applied at the top level.

While I think we should address that, I think it should be handled
incrementally, it is basically a change in the nonstring attribute and
needs to be dealt wherever nonstring is handled.

In order to speed things up, I took your patch and applied Marek's and my
review comments to it, furthermore removed unreachable code -
if (warn_cxx_compat || len - unit > avail)
...
else if (warn_unterminated_string_initialization)
{
if (len - unit > avail)
...
else
...
}
makes no sense, as the second len - unit > avail will be always false.
And tweaked the test coverage a little bit as well.

Kees, are you submitting this under assignment to FSF (maybe the Google one
if it has one) or DCO?  See https://gcc.gnu.org/contribute.html#legal
for details.  If DCO, can you add your Signed-off-by: tag for it?

So far lightly tested, ok for trunk if it passes bootstrap/regtest?

2025-02-13  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.

--- gcc/doc/invoke.texi.jj	2025-02-13 10:17:17.320789358 +0100
+++ gcc/doc/invoke.texi	2025-02-13 13:11:42.089042791 +0100
@@ -8661,17 +8661,20 @@ give a larger number of false positives
 @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
--- gcc/c-family/c.opt.jj	2025-01-02 11:47:29.681229781 +0100
+++ gcc/c-family/c.opt	2025-02-13 12:49:47.187320829 +0100
@@ -1550,7 +1550,7 @@ C ObjC Var(warn_unsuffixed_float_constan
 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
--- gcc/c/c-typeck.cc.jj	2025-01-14 09:36:43.751522483 +0100
+++ gcc/c/c-typeck.cc	2025-02-13 12:52:14.366275230 +0100
@@ -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,
 	  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, t
     }
   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, tr
 	      "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, tr
    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 t
 	      && 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);
 		}
 	    }
 
@@ -11405,7 +11417,7 @@ output_init_element (location_t loc, tre
   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)
--- gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c.jj	2024-07-16 13:36:54.199749481 +0200
+++ gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c	2025-02-13 12:43:26.928616073 +0100
@@ -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' }
+};
--- gcc/testsuite/gcc.dg/Wcxx-compat-14.c.jj	2024-07-16 13:36:54.199749481 +0200
+++ gcc/testsuite/gcc.dg/Wcxx-compat-14.c	2025-02-13 13:20:49.631427826 +0100
@@ -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";
--- gcc/testsuite/gcc.dg/Wcxx-compat-23.c.jj	2025-02-13 13:21:12.635107900 +0100
+++ gcc/testsuite/gcc.dg/Wcxx-compat-23.c	2025-02-13 13:27:43.992665072 +0100
@@ -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' }
+};
--- gcc/testsuite/gcc.dg/Wcxx-compat-24.c.jj	2025-02-13 13:27:57.727474060 +0100
+++ gcc/testsuite/gcc.dg/Wcxx-compat-24.c	2025-02-13 13:28:04.309382516 +0100
@@ -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' }
+};


	Jakub


  reply	other threads:[~2025-02-13 13:10 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-12-16  4:02 [PATCH] c: " Kees Cook
2024-12-16 10:15 ` Alejandro Colomar
2024-12-23 17:11 ` Qing Zhao
2025-01-06 21:34 ` Marek Polacek
2025-02-05 11:59   ` Jakub Jelinek
2025-02-05 12:11     ` Jakub Jelinek
2025-02-05 18:53     ` Kees Cook
2025-02-13 13:10       ` Jakub Jelinek [this message]
2025-02-14 10:21         ` [PATCH] c, v2: " Jakub Jelinek
2025-02-18 19:51           ` Kees Cook
2025-03-08  5:41             ` Kees Cook
2025-03-10 20:03               ` Kees Cook
2025-03-10 20:07                 ` Kees Cook
2025-03-07 22:50           ` Kees Cook
2025-03-07 22:13         ` Joseph Myers

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=Z63vQc7tBvolrk27@tucnak \
    --to=jakub@redhat.com \
    --cc=gcc-patches@gcc.gnu.org \
    --cc=josmyers@redhat.com \
    --cc=kees@kernel.org \
    --cc=polacek@redhat.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).