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 }
+}

Reply via email to