[Bug sanitizer/70147] [6 Regression] testcase from hana testsuite gets miscompiled with -fsanitize=undefined
jakub at gcc dot gnu.org
gcc-bugzilla@gcc.gnu.org
Wed Mar 16 11:58:00 GMT 2016
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=70147
--- Comment #23 from Jakub Jelinek <jakub at gcc dot gnu.org> ---
Consider:
// PR c++/70147
// { dg-do run }
// { dg-options "-fsanitize=vptr" }
static int ac, ad, bc, bd, cc, cd, dc, dd;
struct A { A () { ac++; } virtual void f () {} ~A () { ad++; }; };
struct D { D (int x) { if (x) throw 1; dc++; } ~D () { dd++; } };
struct B : virtual A, D { B () : D (1) { bc++; } virtual void f () {} ~B () {
bd++; } };
struct C : B, virtual A { C () { cc++; } ~C () { cd++; } };
int
main ()
{
{
try { C c; }
catch (...) {}
}
if (ac != 1 || ad != 1 || bc || bd || cc || cd || dc || dd)
__builtin_abort ();
}
This shows that for -fsanitize=vptr this PR is still not resolved, maybe all
the sanitization vptr initializations to NULL need to be guarded with <NE_EXPR
current_in_charge_parm, integer_zero_node> if there are any virtual bases,
rather than just the initializers of the virtual base vtbl pointers.
But, this also shows the CLOBBER issue. Above, we have 8 byte long CLOBBER
in A::A, B::B and C::C and 0 byte long CLOBBER in D::D, the whole C is just 8
bytes long.
C::C first clobbers all the 8 bytes (ok), then constructs A::A at the same
address (this clobbers all of the 8 bytes (ok again), and sets the (only)
pointer in there to &_ZTV1A + 16 (ok), then calls B::B subobject ctor on the
same address, and this clobbers 8 bytes at that address again (wrong!, we rely
on the earlier value of the vtable from the A::A ctor), then calls D::D which
throws. Now, if say we'd e.g. inline the A::A and B::B ctors into C::C, but
not D::D, I think DSE could happily remove the store of &_ZTV1A + 16 into
_vptr.A.
If I modify the testcase to:
static int ac, ad, bc, bd, cc, cd, dc, dd;
struct A { A () { ac++; } virtual void f () {} __attribute__((noinline)) ~A ();
};
struct D { __attribute__((noinline)) D (int); ~D () { dd++; } };
struct B : virtual A, D { B () : D (1) { bc++; } virtual void f () {} ~B () {
bd++; } };
struct C : B, virtual A { C () { cc++; } ~C () { cd++; } };
D::D (int x)
{
if (x) throw 1;
dc++;
}
__attribute__((noinline, noclone)) void
foo (A *p)
{
p->f ();
}
A::~A ()
{
foo (this);
ad++;
}
int
main ()
{
{
try { C c; }
catch (...) {}
}
if (ac != 1 || ad != 1 || bc || bd || cc || cd || dc || dd)
__builtin_abort ();
}
then indeed I see e.g. in phicprop1:
MEM[(struct &)&c] ={v} {CLOBBER};
MEM[(struct A *)&c]._vptr.A = &MEM[(void *)&_ZTV1A + 16B];
ac.11_20 = ac;
_21 = ac.11_20 + 1;
ac = _21;
MEM[(struct &)&c] ={v} {CLOBBER};
D::D (&c.D.2413, 1);
and in the following dse2:
ac.11_20 = ac;
_21 = ac.11_20 + 1;
ac = _21;
MEM[(struct &)&c] ={v} {CLOBBER};
D::D (&c.D.2413, 1);
so the _vptr.A initialization is gone. The reason why this testcase doesn't
abort is that the destructor A::~A as the first thing it does is it changes the
_vptr.A again to &_ZTV1A + 16B.
Are so problematic only empty classes with virtual bases, or when exactly can
this happen? When I've added fields to all 4, A would not overlap with B and
this wouldn't be an issue.
More information about the Gcc-bugs
mailing list