After reading some speculation in bugs such as
http://code.google.com/p/chromium/issues/detail?id=8544 I felt
compelled to dispel some myths and misunderstandings about the origin
and meaning of the mythical _purecall_ exception. My hope is that then
you can spot the problems in our source code and fix them. Sorry for
the long post.

So first of all, what do you see when you get this error? if you are
in a debug build and you are not eating the exceptions via some custom
handler you see this dialog:

---------------------------
Debug Error!
R6025
- pure virtual function call
(Press Retry to debug the application)
---------------------------
Abort   Retry   Ignore
---------------------------

For chrome/chromium we install a special handler, which forces a crash
dump in which case you'll see in in the debugger analysis something
like this:

 [chrome_dll_main.cc:100] - `anonymous namespace'::PureCall()
 [purevirt.c:47] - _purecall

Before going into too much detail, let me show you a small program
that causes this exception:

=================================
class Base {
 public:
  virtual ~Base() {
    ThreeFn();
  }

  virtual void OneFn() = 0;
  virtual void TwoFn() = 0;

  void ThreeFn() {
    OneFn();
    TwoFn();
  }
};

class Concrete : public Base {
 public:
  Concrete() : state_(0) {
  }

  virtual void OneFn() {
    state_ += 1;
  }
  virtual void TwoFn() {
    state_ += 2;
  }
 private:
  int state_;
};


int _tmain(int argc, _TCHAR* argv[]) {

  Concrete* obj = new  Concrete();
  obj->OneFn();
  obj->TwoFn();
  obj->ThreeFn();

  delete obj;

  return 0;
}
=================================

Can you spot the problem? do you know at which line it crashes, do you
know why? if so I have wasted your time, apologies. If you are unsure
then read on.

This program crashes when trying to call OneFn() with a purecall
exception on debug build. On release build it exits with no error, but
your mileage might vary depending on what optimizations are active.

The call stack for the crash is:

        msvcr80d.dll!__purecall()  + 0x25                        <------ shows 
the
dialog (debug only)
        app.exe!Base::ThreeFn()  Line 16 + 0xfc       <-----  error here
        app.exe!Base::~Base()  Line 10  C++
        app.exe!Concrete::~Concrete()  + 0x2b
        app.exe!Concrete::`scalar deleting destructor'()  + 0x2b  <-----
delete obj

So as you have guessed it has to do with calling virtual functions
from a destructor.

What happens is that during construction an object evolves from the
earliest base class to the actual type and during destruction the
object devolves (is that a word?) from the actual object to the
earliest base class; when we reach ~Base() body the object is no
longer of type Concrete but of type Base and thus the call Base::OneFn
() is an error because that class does not in fact have any
implementation.

What the compiler does is create two vtables, the vtable of Concrete
looks like this:

vtable 1:
[ 0 ] -> Concrete::OneFn()
[ 1 ] -> Concrete::TwoFn()

vtable 2:
[ 0 ]-> msvcr80d.dll!__purecall()
[ 1 ]-> msvcr80d.dll!__purecall()

The dtor of Concrete is the default dtor which does nothing except
calling Base::~Base(), but the dtor of base does:

this->vtbl_ptr = vtable2
call ThreeFn()


Now, why doesn't the release build crash?

That's because the compiler does not bother with generating the second
vtable, after all is not going to be used and thus also eliminates the
related lines such as this->vtbl_ptr = vtable2. Therefore the object
reaches the base dtor with the vtbl_ptr pointing to vtable1 which
makes the call ThreeFn() just work.

But that was just luck. If you ever modify the base class, such as
introducing a new virtual function that is not pure, like this:

class Base {
 public:
  virtual ~Base() {
    ThreeFn();
  }

  virtual void OneFn() = 0;
  virtual void TwoFn() = 0;

  virtual void FourFn() {          <--- new function, not pure virtual
    wprintf(L"aw snap");
  }

  void ThreeFn() {
    OneFn();
    TwoFn();
  }
};

// Same program below.
// .......
// ========================

Then you are forcing the compiler to generate vtable 2, which looks:

vtable 2:
[ 0 ]-> msvcr80d.dll!__purecall()
[ 1 ]-> msvcr80d.dll!__purecall()
[ 2 [-> Base::FourFn()

And now the purecall crash magically happens (on the same spot) on
release builds, which is quite surprising since the trigger was the
introduction of FourFn() which has _nothing_ to do with the crash or
the problem and is many commits after the introduction of the problem.

So the moral of the story? beware of virtual calls on dtors and ctors.
Note that in practice this is quite tricky because of layers of
indirection / complexity of the code base.

... so and what about the manbearpig ? Ah, yes no longer a myth:
http://www.thinkgene.com/scientists-successfully-create-human-bear-pig-chimera/

-cpu

--~--~---------~--~----~------------~-------~--~----~
Chromium Developers mailing list: chromium-dev@googlegroups.com 
View archives, change email options, or unsubscribe: 
    http://groups.google.com/group/chromium-dev
-~----------~----~----~----~------~----~------~--~---

Reply via email to