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
next prev parent 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).