]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: Small initial fixes for zeroing of padding bits [PR117256]
authorJakub Jelinek <jakub@redhat.com>
Thu, 28 Nov 2024 10:30:32 +0000 (11:30 +0100)
committerJakub Jelinek <jakub@gcc.gnu.org>
Thu, 28 Nov 2024 10:38:19 +0000 (11:38 +0100)
https://eel.is/c++draft/dcl.init#general-6
says that even padding bits are supposed to be zeroed during
zero-initialization.
The following patch on top of the
https://gcc.gnu.org/pipermail/gcc-patches/2024-October/665565.html
patch attempts to implement that, though only for the easy
cases so far, in particular marks the CONSTRUCTOR created during
zero-initialization (or zero-initialization done during the
value-initialization) as having padding bits cleared and for
constexpr evaluation attempts to preserve that bit on a new CONSTRUCTOR
created for CONSTRUCTOR_ZERO_PADDING_BITS lhs.

I think we need far more than that, but am not sure where exactly
to implement that.
In particular, I think __builtin_bitcast should take it into account
during constant evaluation, if the padding bits in something are guaranteed
to be zero, then I'd think std::bitcast out of it and testing those
bits in there should be well defined.
But if we do that, the flag needs to be maintained precisely, not just
conservatively, so e.g. any place where some object is copied into another
one (except bitcast?) which would be element-wise copy, the bit should
be cleared (or preserved from the earlier state?  I'd hope
element-wise copying invalidates even the padding bits, but then what
about just stores into some members, do those invalidate the padding bits
in the rest of the object?).  But if it is an elided copy, it shouldn't.
And am not really sure what happens e.g. with non-automatic constexpr
variables.  If it is constructed by something that doesn't guarantee
the zeroing of the padding bits (so similarly constructed constexpr automatic
variable would have undefined state of the padding bits), are those padding
bits well defined because it isn't automatic variable?

Anyway, I hope the following patch is at least a small step in the right
direction.

2024-11-28  Jakub Jelinek  <jakub@redhat.com>

PR c++/78620
PR c++/117256
* init.cc (build_zero_init_1): Set CONSTRUCTOR_ZERO_PADDING_BITS.
(build_value_init_noctor): Likewise.
* constexpr.cc (cxx_eval_store_expression): Propagate
CONSTRUCTOR_ZERO_PADDING_BITS flag.

gcc/cp/constexpr.cc
gcc/cp/init.cc
gcc/testsuite/g++.dg/cpp0x/zero-init1.C [new file with mode: 0644]

index 55e44fcbafbab71f4a2b4bb9dc3aee5cfc368cc6..bc3dc3b2559e4ccc1e2ba06a1a41aaf16bb5487b 100644 (file)
@@ -6409,6 +6409,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
 
   type = TREE_TYPE (object);
   bool no_zero_init = true;
+  bool zero_padding_bits = false;
 
   auto_vec<tree *> ctors;
   releasing_vec indexes;
@@ -6421,6 +6422,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
        {
          *valp = build_constructor (type, NULL);
          CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init;
+         CONSTRUCTOR_ZERO_PADDING_BITS (*valp) = zero_padding_bits;
        }
       else if (STRIP_ANY_LOCATION_WRAPPER (*valp),
               TREE_CODE (*valp) == STRING_CST)
@@ -6480,8 +6482,10 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
        }
 
       /* If the value of object is already zero-initialized, any new ctors for
-        subobjects will also be zero-initialized.  */
+        subobjects will also be zero-initialized.  Similarly with zeroing of
+        padding bits.  */
       no_zero_init = CONSTRUCTOR_NO_CLEARING (*valp);
+      zero_padding_bits = CONSTRUCTOR_ZERO_PADDING_BITS (*valp);
 
       if (code == RECORD_TYPE && is_empty_field (index))
        /* Don't build a sub-CONSTRUCTOR for an empty base or field, as they
@@ -6666,6 +6670,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
        {
          *valp = build_constructor (type, NULL);
          CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init;
+         CONSTRUCTOR_ZERO_PADDING_BITS (*valp) = zero_padding_bits;
        }
       new_ctx.ctor = empty_base ? NULL_TREE : *valp;
       new_ctx.object = target;
@@ -6707,6 +6712,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
          /* But do make sure we have something in *valp.  */
          *valp = build_constructor (type, nullptr);
          CONSTRUCTOR_NO_CLEARING (*valp) = no_zero_init;
+         CONSTRUCTOR_ZERO_PADDING_BITS (*valp) = zero_padding_bits;
        }
     }
   else if (*valp && TREE_CODE (*valp) == CONSTRUCTOR
@@ -6719,6 +6725,8 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
       TREE_SIDE_EFFECTS (*valp) = TREE_SIDE_EFFECTS (init);
       CONSTRUCTOR_NO_CLEARING (*valp)
        = CONSTRUCTOR_NO_CLEARING (init);
+      CONSTRUCTOR_ZERO_PADDING_BITS (*valp)
+        = CONSTRUCTOR_ZERO_PADDING_BITS (init);
     }
   else
     *valp = init;
index 8526d7581a781a9e8aeb847c564c13e9c0c5052f..ae516407c9278bf2d92d574a11478d2119b70b30 100644 (file)
@@ -248,6 +248,7 @@ build_zero_init_1 (tree type, tree nelts, bool static_storage_p,
 
       /* Build a constructor to contain the initializations.  */
       init = build_constructor (type, v);
+      CONSTRUCTOR_ZERO_PADDING_BITS (init) = 1;
     }
   else if (TREE_CODE (type) == ARRAY_TYPE)
     {
@@ -466,7 +467,9 @@ build_value_init_noctor (tree type, tsubst_flags_t complain)
            }
 
          /* Build a constructor to contain the zero- initializations.  */
-         return build_constructor (type, v);
+         tree ret = build_constructor (type, v);
+         CONSTRUCTOR_ZERO_PADDING_BITS (ret) = 1;
+         return ret;
        }
     }
   else if (TREE_CODE (type) == ARRAY_TYPE)
diff --git a/gcc/testsuite/g++.dg/cpp0x/zero-init1.C b/gcc/testsuite/g++.dg/cpp0x/zero-init1.C
new file mode 100644 (file)
index 0000000..d4b75c5
--- /dev/null
@@ -0,0 +1,70 @@
+// PR c++/117256
+// { dg-do run { target c++11 } }
+// { dg-options "-O0" }
+
+void *operator new (decltype (sizeof 0), void *p) noexcept { return p; }
+
+struct A { char c; int i; };
+#if __cplusplus >= 201402L
+struct B { A a; constexpr B (char x, int y) : a () { a.c = x; a.i = y; } };
+#else
+struct B { A a; B (char x, int y) : a () { a.c = x; a.i = y; } };
+#endif
+
+[[gnu::noipa]] void
+foo ()
+{
+  unsigned char buf[sizeof (B)];
+  A a1 = A ();
+  __builtin_memcpy (buf, &a1, sizeof (buf));
+  if (buf[1])
+    __builtin_abort ();
+  unsigned char m1 alignas (A) [sizeof (A)];
+  __builtin_memset (m1, -1, sizeof (m1));
+  A *a2 = new (m1) A ();
+  __builtin_memcpy (buf, a2, sizeof (*a2));
+  if (buf[1])
+    __builtin_abort ();
+  B b1 (42, -42);
+  __builtin_memcpy (buf, &b1, sizeof (b1));
+  if (buf[1])
+    __builtin_abort ();
+  unsigned char m2 alignas (B) [sizeof (B)];
+  B *b2 = new (m2) B (1, 2);
+  __builtin_memcpy (buf, b2, sizeof (*b2));
+  if (buf[1])
+    __builtin_abort ();
+#if __cplusplus >= 201402L
+  constexpr B b3 (3, 4);
+  __builtin_memcpy (buf, &b3, sizeof (b3));
+  if (buf[1])
+    __builtin_abort ();
+#endif
+}
+
+[[gnu::noipa]] void
+bar (unsigned char *p)
+{
+  (void) p;
+}
+
+[[gnu::noipa]] void
+baz ()
+{
+  unsigned char buf[256];
+  __builtin_memset (buf, -1, sizeof (buf));
+  bar (buf);
+}
+
+int
+main ()
+{
+  if (__builtin_offsetof (A, c) == 0
+      && __builtin_offsetof (A, i) != 1
+      && __builtin_offsetof (B, a) == 0
+      && sizeof (A) == sizeof (B))
+    {
+      baz ();
+      foo ();
+    }
+}