[PATCH, PR 45934 4/6] Dynamic type change detection

Richard Guenther rguenther@suse.de
Thu Dec 2 16:17:00 GMT 2010


On Thu, 2 Dec 2010, Richard Guenther wrote:

> On Wed, 1 Dec 2010, Martin Jambor wrote:
> 
> > This is the crux of the matter.  I'll try to explain what and why I do
> > in order to detect sub-object type changes during their construction
> > first, below it there is the detection code as it looks like with the
> > next patch applied and then there is of course the patch at the end.
> > 
> > I have split the detection code into two patches, this one just
> > detects that there is a type change in force and the subsequent one
> > also tries to derive the new type.  I did this in order to better
> > structure the discussion about both and I also intend to commit the
> > separately, easing potential bisecting (I hope there will be no reason
> > for that of course).  If it was absolutely necessary, we could
> > postpone the next patch for stage1 but I hope to commit a variant of
> > it soon.
> > 
> > Because operations like placement new, memcpy and other byte-per-byte
> > operations with objects that have virtual methods are deemed to
> > produce code with undefined behavior and there are no unions of
> > non-POD types, dynamic type change has a special meaning for
> > devirtualization and only refers to what actual virtual method table a
> > VMT pointer of an object points to.  On the other hand the type in the
> > sense of what you get from TREE_TYPE of the base of any given access
> > to it is still the same and it is its static type.  An important
> > property of such objects is that their dynamic types (as given by the
> > current VMT) can only be altered in constructors and destructors.
> > 
> > This patch makes special assumptions about both constructors and
> > destructors which are all the functions that are allowed to alter the
> > dynamic types.  It assumes that destructors begin with assignment into
> > all VMT pointers and that constructors essentially look in the
> > following way:
> > 
> > 1) The very first thing they do is that they call constructors of the
> >    components (including ancestor sub-objects) that have them.
> > 
> > 2) Then VMT pointers of this and all its ancestors is set to new
> >    values corresponding to the type corresponding to the constructor.
> > 
> > 3) Only afterwards, other stuff such as the code written by the user
> >    is run.  Only this may include calling virtual functions, directly
> >    or indirectly.
> > 
> > There is no way to call a constructor of an ancestor sub-object in any
> > other way (or any component for that matter but type is interesting
> > only for ancestors).
> > 
> > This means that we do not have to care whether constructors get the
> > correct type information because they will always change it (in fact,
> > if we define the type to be given by the VMT pointer, it is
> > undefined).

You can still reach wrong conclusions for the dynamic type of *p
in the following testcase:

extern "C" void abort (void);
extern "C" void *malloc(__SIZE_TYPE__);
inline void* operator new(__SIZE_TYPE__, void* __p) throw() { return __p; 
}
int x;
class A {
public:
    virtual ~A() { }
};
class B : public A {
public:
    virtual ~B() { if (x == 1) abort (); x = 1; }
};
void __attribute__((noinline,noclone)) foo (void *p)
{
  B *b = reinterpret_cast<B *>(p);
  b->~B();
  new (p) A;
}
int main()
{
  void *p = __builtin_malloc (sizeof (B));
  new (p) B;
  foo(p);
  reinterpret_cast<A *>(p)->~A();
  return 0;
}

if you ignore the call to foo() then you see a vtable pointer store
to make *p a B, but in reality at the time of the virtual call to
the destructor it is an A.

Similar, if you add a char data[256]; member to A then a

 {
   B x;
   new (&x.data) A;
   reinterpret_cast<A *>(&x.data)->~A;
 }

might possibly mis-lead you as to the type of B at destruction time
(this example is more similar to the array issue I raised).

Richard.



More information about the Gcc-patches mailing list