https://gcc.gnu.org/g:622968990beee7499e951590258363545b4a3b57
commit r15-7900-g622968990beee7499e951590258363545b4a3b57 Author: Jakub Jelinek <ja...@redhat.com> Date: Fri Mar 7 23:59:34 2025 +0100 c: do not warn about truncating NUL char when initializing nonstring arrays [PR117178] 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 <k...@kernel.org> Jakub Jelinek <ja...@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 <k...@kernel.org> Diff: --- gcc/c-family/c.opt | 2 +- gcc/c/c-typeck.cc | 62 +++++++++++++--------- gcc/doc/invoke.texi | 15 +++--- gcc/testsuite/gcc.dg/Wcxx-compat-14.c | 2 +- gcc/testsuite/gcc.dg/Wcxx-compat-23.c | 33 ++++++++++++ gcc/testsuite/gcc.dg/Wcxx-compat-24.c | 33 ++++++++++++ .../gcc.dg/Wunterminated-string-initialization.c | 31 ++++++++++- 7 files changed, 143 insertions(+), 35 deletions(-) diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index f432203b2ebc..6c6922ab2018 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -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 diff --git a/gcc/c/c-typeck.cc b/gcc/c/c-typeck.cc index a13989a66076..bc51cc2693bf 100644 --- a/gcc/c/c-typeck.cc +++ b/gcc/c/c-typeck.cc @@ -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) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index e01d64c5229f..3781697ae55f 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -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 diff --git a/gcc/testsuite/gcc.dg/Wcxx-compat-14.c b/gcc/testsuite/gcc.dg/Wcxx-compat-14.c index 6df0ee197cca..ef1f0c09baa3 100644 --- a/gcc/testsuite/gcc.dg/Wcxx-compat-14.c +++ b/gcc/testsuite/gcc.dg/Wcxx-compat-14.c @@ -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 index 000000000000..fd091495bb88 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wcxx-compat-23.c @@ -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 index 000000000000..da652ae9fc8a --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wcxx-compat-24.c @@ -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' } +}; diff --git a/gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c b/gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c index 13d5dbc66400..5f25f0299197 100644 --- a/gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c +++ b/gcc/testsuite/gcc.dg/Wunterminated-string-initialization.c @@ -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' } +};