This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: [PATCH] warn on mem calls modifying objects of non-trivial types (PR 80560)


On 07/05/2017 02:58 PM, Andrew Pinski wrote:
On Sun, Apr 30, 2017 at 1:02 PM, Pedro Alves <palves@redhat.com> wrote:
Hi Martin,

Thanks much for doing this.  A few comments below, in light of my
experience doing the equivalent checks in the gdb patch linked below,
using standard C++11.

On 04/29/2017 09:09 PM, Martin Sebor wrote:
Calling memset, memcpy, or similar to write to an object of
a non-trivial type (such as one that defines a ctor or dtor,
or has such a member) can break the invariants otherwise
maintained by the class and cause undefined behavior.

The motivating example that prompted this work was a review of
a change that added to a plain old struct a new member with a ctor
and dtor (in this instance the member was of type std::vector).

To help catch problems of this sort some projects (such as GDB)
have apparently even devised their own clever solutions to detect
them: https://sourceware.org/ml/gdb-patches/2017-04/msg00378.html.

The attached patch adds a new warning, -Wnon-trivial-memaccess,
that has GCC detect these mistakes.  The patch also fixes up
a handful of instances of the problem in GCC.  These instances
correspond to the two patterns below:

  struct A
  {
    void *p;
    void foo (int n) { p = malloc (n); }
    ~A () { free (p); }
  };

  void init (A *a)
  {
    memset (a, 0, sizeof *a);
  }

and

  struct B
  {
    int i;
    ~A ();
  };

(typo: "~B ();")


  void copy (B *p, const B *q)
  {
    memcpy (p, q, sizeof *p);
    ...
   }


IMO the check should be relaxed from "type is trivial" to "type is
trivially copyable" (which is what the gdb detection at
https://sourceware.org/ml/gdb-patches/2017-04/msg00378.html
uses for memcpy/memmove).  Checking that the destination is trivial is
going to generate false positives -- specifically, [basic-types]/3
specifies that it's fine to memcpy trivially _copyable_ types, not
trivial types.  A type can be both non-trivial and trivially copyable
at the same time.  For example, this compiles, but triggers
your new warning:

#include <stdlib.h>
#include <string.h>
#include <type_traits>

struct NonTrivialButTriviallyCopyable
{
  NonTrivialButTriviallyCopyable () : i (0) {}
  int i;
};

static_assert (!std::is_trivial<NonTrivialButTriviallyCopyable>::value, "");
static_assert (std::is_trivially_copyable<NonTrivialButTriviallyCopyable>::value, "");

void copy (NonTrivialButTriviallyCopyable *dst, NonTrivialButTriviallyCopyable *src)
{
  memcpy (dst, src, sizeof (*src));
}

$ /opt/gcc/bin/g++ -std=gnu++11 trivial-warn.cc -o trivial-warn -g3 -O0 -Wall -Wextra -c
trivial-warn.cc: In function ‘void copy(NonTrivialButTriviallyCopyable*, NonTrivialButTriviallyCopyable*)’:
trivial-warn.cc:16:34: warning: calling ‘void* memcpy(void*, const void*, size_t)’ with a pointer to a non-trivial type ‘struct NonTrivialButTriviallyCopyable’ [-Wnon-trivial-memaccess]
   memcpy (dst, src, sizeof (*src));
                                  ^
$

Implementations of vector-like classes can very well (and are
encouraged) to make use of std::is_trivially_copyable to know whether
they can copy a range of elements to new storage
using memcpy/memmove/mempcpy.

Running your patch against GDB trips on such a case:

src/gdb/btrace.h: In function ‘btrace_insn_s* VEC_btrace_insn_s_quick_insert(VEC_btrace_insn_s*, unsigned int, const btrace_insn_s*, const char*, unsigned int)’:
src/gdb/common/vec.h:948:62: error: calling ‘void* memmove(void*, const void*, size_t)’ with a pointer to a non-trivial type ‘btrace_insn_s {aka struct btrace_insn}’ [-Werror=non-trivial-memaccess]
   memmove (slot_ + 1, slot_, (vec_->num++ - ix_) * sizeof (T));    \
                                                              ^

There is nothing wrong with the code being warned here.
While "struct btrace_insn" is trivial (has a user-provided default
ctor), it is still trivially copyable.


Any news on getting a "fix" for this issue.  Right now it blocks my
testing of GCC/gdb because I am building the trunk of both in a CI
loop and my build is broken due to this warning.  Should I just add
--disable-werror to my gdb build instead?

I'm not aware of any serious bugs in the warning that need fixing.
The warning points out raw memory accesses to objects of non-trivial
types (among other things), or those with user-defined default or
copy ctors, dtor, or copy assignment operator.  Objects of such
types should be manipulated using these special member functions
rather than by raw memory functions.  In many (though not all(*))
cases the raw memory calls can put.leave such objects in an invalid
state and make using them undefined.

In the instance of the warning above, btrace_insn_s is a non-trivial
type because it has a user-defined default ctor, as a result of
defining a member of such a type (flags, which is of type
enum_flags<enum btrace_insn_flag>).  To avoid the warning either
the memcpy/memmove calls should be replaced with a loop that makes
use of the special function(s), or in C++ 11 and later, the class
made trivial by defaulting the ctors and copy assignment operators.
In the GDB case, this can be done by replacing the defintion of
the enum_flags default ctor like so:

--- a/gdb/common/enum-flags.h
+++ b/gdb/common/enum-flags.h
@@ -116,9 +116,7 @@ private:

 public:
   /* Allow default construction.  */
-  enum_flags ()
-    : m_enum_value ((enum_type) 0)
-  {}
+  enum_flags () = default;

   /* If you get an error saying these two overloads are ambiguous,
      then you tried to mix values of different enum types.  */

In cases where copying such objects using memcpy/memmove is safe
and changing the code to use the special member functions is not
practical the warning can be suppressed by casting the pointer
to char*.

Martin

[*] Whether a call to memcpy or memove to copy an object of
a non-trivial type like btrace_insn_s (i.e., one with a trivial
copy ctor and copy assignment but with a non-trivial default
ctor) is safe depends on whether the destination of the copy
is an already constructed object of the type or one that has
just been allocated but whose lifetime hasn't yet begun (i.e.,
whose ctor has been called yet).  Because there is no way to
tell which of these two it is, the warning makes the conservative
assumption that it's the latter.  This was done deliberately to
help non-expert users, with the expectation that advanced users
will easily either adjust their code or suppress the warning.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]