https://gcc.gnu.org/bugzilla/show_bug.cgi?id=126258
Bug ID: 126258
Summary: Interprocedural analysis of virtual calls dispatched
through a non-primary base subobject (multiple
inheritance) doesn't correctly adjust this, so the
callee body is entered with the wrong receiver.
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: analyzer
Assignee: dmalcolm at gcc dot gnu.org
Reporter: egas.g.ribeiro at tecnico dot ulisboa.pt
Target Milestone: ---
- Current behavior
After the devirt support (PR97114), virtual calls through a non-primary base
(e.g. B::g on C : A, B via a B*) resolve through a this-adjusting thunk
(_ZThn8_...). get_fndecl_for_virtual_call resolves the thunk to the underlying
method via function_symbol, so can_throw_p sees the correct nothrow status and
throw/leak analysis is right.
However, the thunk's fixed_offset isn't applied when binding the callee's
'this', so the body is entered with the unadjusted (offset - 8) receiver.
Pointer identity is then lost interprocedurally (i.e a 'delete this' in the
body doesn't map back to the caller's region thus return values aren't
observed). This means devirt-multiple-inheritance-1.C only asserts the
throw/leak side-effect rather than a return-value __analyzer_eval (the eval
comes back UNKNOWN).
- Proposed approach
When a call resolves through a thunk, adjust the receiver region by
-fixed_offset (from thunk_info::get(node)) before binding this, so the body
sees the offset-0 pointer. The offset has to be threaded from
get_fndecl_for_virtual_call to the frame-binding point (push_frame / arg
binding) where this is set up.
One problem with this approach is that Virtual-base thunks (_ZTv...) use a
vtable-loaded offset, so fixed_offset alone won't cover them.
This also somewhat fights the current modeling, where resolution and
frame-binding are separate steps; threading the offset between them needs
design thought.
The following testcase exemplifies the issue:
```
#include "../../gcc.dg/analyzer/analyzer-decls.h"
struct A
{
virtual int f () { return 0; }
};
struct B
{
virtual int g () { return 0; }
};
struct C : public A, public B
{
int f () override { return 1; }
int g () override { return 60; }
};
__attribute__ ((noipa)) C *
make_c ()
{
return new C ();
}
void
test ()
{
C *c = make_c ();
B *b = c;
/* Devirt resolves b->g() to C::g through a _ZThn8_ thunk, but the
thunk's this-adjustment isn't applied when binding 'this', so the
body isn't stepped into with the correct receiver and the return
value isn't observed. This currently reports UNKNOWN; it should
be TRUE. */
__analyzer_eval (b->g () == 60); // TRUE expected, currently UNKNOWN
delete c;
}
```