https://gcc.gnu.org/g:a382bed036aa5f58622fc7551734f6280d1476c9
commit r17-4750-ga382bed036aa5f58622fc7551734f6280d1476c9 Author: Jonas Hansen <[email protected]> Date: Tue Sep 29 18:37:25 2026 +0200 c++: Normalize character array initializers after folding [PR127416] The call to braced_lists_to_strings in store_init_value does not look through TARGET_EXPR. A constexpr aggregate initialized through one can therefore retain a character array CONSTRUCTOR after constant evaluation. The middle end then treats an element of that array as a single-character string, causing a false -Wformat-truncation warning for a bounded snprintf call that reads only the remaining array elements. Move the existing conversion after constant folding, next to replace_placeholders, so it sees the folded initializer while remaining before split_nonconstant_init. Add diagnostic and runtime regressions covering bounded reads, overreads, truncation, constexpr copy and mutation, templates, embedded nuls, and implicit zero initialization. gcc/cp/ChangeLog: PR tree-optimization/127416 * typeck2.cc (store_init_value): Move braced_lists_to_strings after constant folding. gcc/testsuite/ChangeLog: PR tree-optimization/127416 * g++.dg/warn/Wformat-truncation-nonstring.C: New test. * g++.dg/torture/constexpr-array-string.C: New test. Signed-off-by: Jonas Hansen <[email protected]> Reviewed-by: Jason Merrill <[email protected]> Diff: --- gcc/cp/typeck2.cc | 8 ++-- .../g++.dg/torture/constexpr-array-string.C | 56 ++++++++++++++++++++++ .../g++.dg/warn/Wformat-truncation-nonstring.C | 48 +++++++++++++++++++ 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc index 6f17ce8c1bc9..c7ebd080edc6 100644 --- a/gcc/cp/typeck2.cc +++ b/gcc/cp/typeck2.cc @@ -1056,10 +1056,6 @@ store_init_value (tree decl, tree init, vec<tree, va_gc>** cleanups, int flags) value = digest_init_flags (type, init, flags, tf_warning_or_error); } - /* Look for braced array initializers for character arrays and - recursively convert them into STRING_CSTs. */ - value = braced_lists_to_strings (type, value); - current_ref_temp_count = 0; value = extend_ref_init_temps (decl, value, cleanups); @@ -1139,6 +1135,10 @@ store_init_value (tree decl, tree init, vec<tree, va_gc>** cleanups, int flags) /* Handle aggregate NSDMI in non-constant initializers, too. */ value = replace_placeholders (value, decl); + /* Look for braced array initializers for character arrays and + recursively convert them into STRING_CSTs. */ + value = braced_lists_to_strings (type, value); + /* Detect stuff like 'info r = ^^int;' outside a manifestly constant-evaluated context. */ if (flag_reflection diff --git a/gcc/testsuite/g++.dg/torture/constexpr-array-string.C b/gcc/testsuite/g++.dg/torture/constexpr-array-string.C new file mode 100644 index 000000000000..bc0829af5310 --- /dev/null +++ b/gcc/testsuite/g++.dg/torture/constexpr-array-string.C @@ -0,0 +1,56 @@ +// Check character arrays produced by constant evaluation, including copies, +// mutation, embedded nuls, and implicit zero initialization. +// { dg-do run { target c++14 } } + +struct Buffer { char data[3]; }; +constexpr auto original = Buffer{{'a', 'b', 'c'}}; + +constexpr Buffer make_buffer () +{ + Buffer b = original; + b.data[1] = 'x'; + return b; +} + +constexpr auto changed = make_buffer (); +constexpr auto embedded = Buffer{{'a', '\0', 'c'}}; +constexpr auto padded = Buffer{{'a'}}; + +template <char C> struct Holder +{ + static constexpr Buffer value = Buffer{{'a', C, 'c'}}; +}; +template <char C> constexpr Buffer Holder<C>::value; + +static_assert (original.data[1] == 'b', ""); +static_assert (changed.data[1] == 'x', ""); +static_assert (embedded.data[2] == 'c', ""); +static_assert (padded.data[2] == '\0', ""); +static_assert (Holder<'y'>::value.data[1] == 'y', ""); +static_assert (Holder<'z'>::value.data[1] == 'z', ""); + +void check (const char *data, int precision, const char *expected, int length) +{ + char out[4]; + if (__builtin_snprintf (out, sizeof out, "%.*s", precision, data) != length + || __builtin_memcmp (out, expected, length + 1)) + __builtin_abort (); +} + +int main () +{ + check (original.data + 1, 2, "bc", 2); + check (changed.data + 1, 2, "xc", 2); + check (embedded.data, 3, "a", 1); + check (embedded.data + 1, 2, "", 0); + check (embedded.data + 2, 1, "c", 1); + check (padded.data, 3, "a", 1); + check (padded.data + 1, 2, "", 0); + check (Holder<'y'>::value.data + 1, 2, "yc", 2); + check (Holder<'z'>::value.data + 1, 2, "zc", 2); + + Buffer copy = original; + copy.data[1] = 'z'; + check (copy.data + 1, 2, "zc", 2); + check (original.data + 1, 2, "bc", 2); +} diff --git a/gcc/testsuite/g++.dg/warn/Wformat-truncation-nonstring.C b/gcc/testsuite/g++.dg/warn/Wformat-truncation-nonstring.C new file mode 100644 index 000000000000..759675a43cfa --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wformat-truncation-nonstring.C @@ -0,0 +1,48 @@ +// A bounded %s conversion need not read a terminating nul. +// { dg-do compile { target c++11 } } +// { dg-options "-O1 -Wall" } + +struct Buffer { char data[3]; }; +constexpr auto buffer = Buffer{{'a', 'b', 'c'}}; + +int bounded () +{ + char out[3]; + return __builtin_snprintf (out, sizeof out, "%.*s", 2, + buffer.data + 1); // { dg-bogus "nul-terminated" "" { target *-*-* } .-1 } +} + +int bounded_literal () +{ + char out[3]; + return __builtin_snprintf (out, sizeof out, "%.2s", + buffer.data + 1); // { dg-bogus "nul-terminated" "" { target *-*-* } .-1 } +} + +int last_byte () +{ + char out[2]; + return __builtin_snprintf (out, sizeof out, "%.*s", 1, + buffer.data + 2); +} + +int overread () +{ + char out[8]; + return __builtin_snprintf (out, sizeof out, "%.*s", 3, + buffer.data + 1); // { dg-warning "nul-terminated" "" { target *-*-* } .-1 } +} + +int unbounded () +{ + char out[8]; + return __builtin_snprintf (out, sizeof out, "%s", + buffer.data + 1); // { dg-warning "nul-terminated" "" { target *-*-* } .-1 } +} + +int truncated () +{ + char out[2]; + return __builtin_snprintf (out, sizeof out, "%.*s", 2, + buffer.data + 1); // { dg-warning "truncated" "" { target *-*-* } .-1 } +}
