[gnu.g++.bug] inheritance bugs

vaughan%cadillac.cad.mcc.com@MCC.COM (Paul Vaughan) (01/12/90)

Here are a couple of bugs that pop up in long, obscure inheritance
hierarchies.

The code generated from the following source prints an incorrect
result.  The NamedObject destructor and constructor print the "this"
pointer, but note that a NamedObject is destructed that was not
constructed.  Uncommenting out the destructor of IntegerAt produces
correct behavior.

#include <stream.h>
class NameSpaceTable;

class Object {
public:
  Object() {};
  virtual ~Object() {cout << "~Object()\n";}
};

class NamedObject: public Object
{
public:
  NamedObject(const char* n = 0);
  ~NamedObject();

  virtual const char* Name() const;
private:
  const char* name=0;
};

const char* NamedObject::Name() const {
  if (name) return name; else return "?????";
}

NamedObject::NamedObject(const char* n = 0): () {
  name = n;
  cout << (int) this << " Constructor NamedObject() " << (int)name << "\n";
}

NamedObject::~NamedObject() {
  cout << (int) this << " ~NamedObject() " << (int)name << "\n";
}

class Entity: public  NamedObject {
public:
  Entity(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0,
	 int namespaceSize = 100) : NamedObject(n) {};

  ~Entity() { cout << "~Entity()\n";}
};

class Attribute : public Entity {
public:
  Attribute(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0) 
    : Entity(n,o,nspace,0) {};
  ~Attribute() {cout << "~Attribute()\n";}
};

class IntegerAt : public Attribute {
public:
  IntegerAt(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0)
    : Attribute(n,o,nspace) {};
//  ~IntegerAt() {cout << "~IntegerAt()\n";}
};

class Behavior : public Entity {
public:
  Behavior(const char* n= 0, Entity* o = 0, NameSpaceTable* ns = 0, int s = 0) 
    : Entity (n, o, ns, s) {};
  virtual ~Behavior() {cout << "~Behavior()\n";}
};

class AttributeBehavior : public Behavior {
public:
  AttributeBehavior(const char* n = 0, Entity* o = 0, NameSpaceTable* ns = 0, 
		    int s = 0) : Behavior (n, o, ns, s) {}
  ~AttributeBehavior() {cout << "~AttributeBehavior()\n";}
};

class InterAtt : public AttributeBehavior {
public:
  ~InterAtt() {cout << "~InterAtt()\n";}
//  ~InterAtt() {cout << "~InterAtt()\n";}
};

class IgIntAtt : public InterAtt, public IntegerAt {
public:
  IgIntAtt(char* n, Entity* o): IntegerAt(n, o) {};
  ~IgIntAtt();
  const char* Name() const {}
};

IgIntAtt::~IgIntAtt() {cout << "~IgIntAtt()\n";}

main() {
  IgIntAtt* iat = new IgIntAtt("fred", 0);
  
  delete iat;
}
  

[2.234]puma) !g
g++-1.36.2 -v -g -o AtHier1 AtHier1.cc
g++ version 1.36.2- (based on GCC 1.36)
 /usr/local/gnu/1.36.2/ylib/gcc-cpp -+ -v -undef -D__GNUC__ -D__GNUG__ -D__cplusplus -Dmc68000 -Dsun -Dunix -D__mc68000__ -D__sun__ -D__unix__ -D__HAVE_68881__ -Dmc68020 AtHier1.cc /usr/tmp/cca06057.cpp
GNU CPP version 1.36
 /usr/local/gnu/1.36.2/ylib/gcc-cc1plus /usr/tmp/cca06057.cpp -quiet -dumpbase AtHier1.cc -g -version -o /usr/tmp/cca06057.s
GNU C++ version 1.36.2- (based on GCC 1.36) (68k, MIT syntax) compiled by GNU C version 1.36.
default target switches: -m68020 -mc68020 -m68881 -mbitfield
 /usr/local/gnu/1.36.2/ylib/gcc-as -mc68020 -o AtHier1.o /usr/tmp/cca06057.s
 /usr/local/gnu/1.36.2/ylib/gcc-ld -o AtHier1 -e start -dc -dp -Bstatic /lib/crt0.o /lib/Mcrt1.o AtHier1.o -lg++ /usr/local/gnu/1.36.2/ylib/gcc-gnulib -lg -lc

[2.235]puma) !A
AtHier1
141732 Constructor NamedObject() 11718
141724 Constructor NamedObject() 0
~IgIntAtt()
~Attribute()
~Entity()
141740 ~NamedObject() 0
~Object()
~InterAtt()
~AttributeBehavior()
~Behavior()
~Entity()
141724 ~NamedObject() 0
~Object()
[2.236]puma) 


------------------------------------------------------------------

In this second example, there are again two NamedObjects in the final
IServiceDelayAt object.  The Name() method (defined first on
NamedObject) is therefore ambiguous in the IServiceDelayAt, except
that I have defined an IServiceDelayAt::Name() to call the
ServiceDelayAt::Name().  Note that the "this" pointer printed out by
ServiceDelayAt::Name() is equal to the "this" pointer printed by
IServiceDelayAt::Name().  Those pointers should not be equal.


#include <stream.h>
class NameSpaceTable;

class Object {
public:
  Object() {};
  virtual ~Object() {cout << "~Object()\n";}
};

class NamedObject: public Object
{
public:
  NamedObject(const char* n = 0);
  ~NamedObject();

  virtual const char* Name() const;
private:
  const char* name=0;
};

const char* NamedObject::Name() const {
  if (name) return name; else return "?????";
}

NamedObject::NamedObject(const char* n = 0): () {
  name = n;
  cout << (int) this << " NamedObject() Name() = " << Name() << "\n";
}

NamedObject::~NamedObject() {
  cout << (int) this << " ~NamedObject() " << (int)name << "\n";
}

class Entity: public  NamedObject {
public:
  Entity(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0,
	 int namespaceSize = 100) : NamedObject(n) {};

  ~Entity() { cout << "~Entity()\n";}
};

class Attribute : public Entity {
public:
  Attribute(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0) 
    : Entity(n,o,nspace,0) {};
  ~Attribute() {cout << "~Attribute()\n";}
};

class IntegerAt : public Attribute {
public:
  IntegerAt(const char* n=0,Entity* o=0,NameSpaceTable* nspace=0)
    : Attribute(n,o,nspace) {};
//  ~IntegerAt() {cout << "~IntegerAt()\n";}
};

class Behavior : public Entity {
public:
  Behavior(const char* n= 0, Entity* o = 0, NameSpaceTable* ns = 0, int s = 0) 
    : Entity (n, o, ns, s) {};  
  virtual ~Behavior() {cout << "~Behavior()\n";}
};

class AttributeBehavior : public Behavior {
public:
  AttributeBehavior(const char* n = 0, Entity* o = 0, NameSpaceTable* ns = 0, 
		    int s = 0) : Behavior (n, o, ns, s) {}
  ~AttributeBehavior() {cout << "~AttributeBehavior()\n";}
};

class InterAtt : public AttributeBehavior {
public:
  InterAtt()  {  cout << "InterAtt() Name() = " << Name() << "\n"; };
//  ~InterAtt() {cout << "~InterAtt()\n";}
};

class ServiceDelayAt : public IntegerAt {
public:
  ServiceDelayAt() : IntegerAt("delay") {  
    cout << "ServiceDelayAt() Name() = " << Name() << "\n";
  };
  virtual const char* Name() const { 
    cout << "<" << (int) this << " ServiceDelayAt::Name>";
    return NamedObject::Name(); }
  ~ServiceDelayAt() {};
};

class IServiceDelayAt : public InterAtt, public ServiceDelayAt {
public:
  IServiceDelayAt();
  ~IServiceDelayAt();
  virtual const char* Name() const { 
    cout << "<" << (int) this << " IServiceDelayAt::Name>";
    return ServiceDelayAt::Name();
  }
};

IServiceDelayAt::~IServiceDelayAt() {cout << "~ServiceDelayAt()\n";}

IServiceDelayAt::IServiceDelayAt() {  
  cout << "IServiceDelayAt()Name() = " << Name() << "\n";
}

main() {
  IServiceDelayAt* delay = new IServiceDelayAt;
  cout << "\n";
  cout << "delay->Name() = " << delay->Name() << "\n\n";
  delete delay;
}
  
[2.244]puma) !g
g++-1.36.2 -v -g -o AtHier1 AtHier.cc
g++ version 1.36.2- (based on GCC 1.36)
 /usr/local/gnu/1.36.2/ylib/gcc-cpp -+ -v -undef -D__GNUC__ -D__GNUG__ -D__cplusplus -Dmc68000 -Dsun -Dunix -D__mc68000__ -D__sun__ -D__unix__ -D__HAVE_68881__ -Dmc68020 AtHier.cc /usr/tmp/cca06094.cpp
GNU CPP version 1.36
 /usr/local/gnu/1.36.2/ylib/gcc-cc1plus /usr/tmp/cca06094.cpp -quiet -dumpbase AtHier.cc -g -version -o /usr/tmp/cca06094.s
GNU C++ version 1.36.2- (based on GCC 1.36) (68k, MIT syntax) compiled by GNU C version 1.36.
default target switches: -m68020 -mc68020 -m68881 -mbitfield
 /usr/local/gnu/1.36.2/ylib/gcc-as -mc68020 -o AtHier.o /usr/tmp/cca06094.s
 /usr/local/gnu/1.36.2/ylib/gcc-ld -o AtHier1 -e start -dc -dp -Bstatic /lib/crt0.o /lib/Mcrt1.o AtHier.o -lg++ /usr/local/gnu/1.36.2/ylib/gcc-gnulib -lg -lc
[2.245]puma) !A
AtHier1
141724 NamedObject() Name() = ?????
InterAtt() Name() = ?????
141732 NamedObject() Name() = delay
ServiceDelayAt() Name() = <141732 ServiceDelayAt::Name>delay
IServiceDelayAt()Name() = <141724 IServiceDelayAt::Name><141724 ServiceDelayAt::Name>?????

delay->Name() = <141724 IServiceDelayAt::Name><141724 ServiceDelayAt::Name>?????

~ServiceDelayAt()
~Attribute()
~Entity()
141732 ~NamedObject() 10881
~Object()
~AttributeBehavior()
~Behavior()
~Entity()
141724 ~NamedObject() 0
~Object()
[2.246]puma) 

-------------------------------------------------------------
Now for a note about dubious compiler behavior.  If you remove both
the ServiceDelayAt::Name() and IServiceDelayAt::Name() declarations
(and definitions), the Name() method on IServiceDelayAt is ambiguous.
When the compiler finds something like

((IServiceDelayAt* ) p)->Name(),

it prints the error message

AtHier.cc:116: member `Name' belongs to MI-distinct base classes `NamedObject'

which is certainly correct.  However it does not complain at the time
of the IServiceDelayAt class definition.  If you have a
ServiceDelayAt::Name(), but no IServiceDelayAt::Name(), the compile warns that

AtHier.cc:93: ambiguous virtual function `const char *ServiceDelayAt::Name ()const '
AtHier.cc:24: ambiguating function `const char *NamedObject::Name ()const ' (joined by type `IServiceDelayAt')

when compiling the IServiceDelayAt class definition.  This is also
certainly correct.  In my opinion, this sort of message should have
been printed when compiling the IServiceDelayAt class definition in
the first case also, because the situation is equally ambiguous.  For
instance, it is not clear at all which NamedObject would be accessed
by 
((Attribute*) p)->Name(), 

but this does not result in any error message.  It can produce
counterintuitive results, however.

--------------------------------------------
Finally, a bit of explanation for weird code.  First, we were never
able to isolate our (perceived) compiler bugs with using virtual base
classes.  We last decided to just work around having duplicate
structures in our objects.  At this point, I'm beginning to think we
should have worked more on isolating the virtual base class problems.

I usually try to avoid sending such large pieces of source to
illustrate our compiler bugs, but I am finding that more and more
difficult.  In working with this example, it took a long time to
figure out what was important and finally get it down into a single
file of source code that we can send out without violating our code
hoarding secrecy doctrines.  I expect that this process is fairly
essential in many cases, though, because it is this process that
usually verifies that it is a compiler bug, and not one of our own
breeds.  The only alternative that I know that still gives you a
compilable and executable example would be to tar up hundreds of
megabytes of code/makefiles/libraries.  Perhaps if we can ever manage
to procure the services of your new support company we will have an
easier time of it?