bug, exception in new initializer (recursive new calls)

Alexander Schiemann aschiem@count.math.uni-sb.de
Thu Nov 19 17:58:00 GMT 1998


Hello,

I suspect there is a bug concerning the 1st argument in the
call to an operator delete if the initializer in a 
new-expresssion throws an error. This happens if the new-
expression is nested in the ctor of a new-expressions 
(or even deeper). 

The class T_data from the following code contains a handle
to T_data (which might be 0).
The ctor  T_data(int x,int y)  produces a chain of x T_data
containing a handle to T_data containing...
and the ctor-body of in the y-th level throws an exception.

In the example below, x=y=2.
When the exception occurs there are 2 not fully constructed
objects which cause calls to matching deletes. 
The first compiler generated call to the matching operator  
delete is o.k. 
The second one produces a wrong (1st) pointer argument in the
call to operator delete (and an output notifying this).
Because the placement variable contains a redundant pointer,
the call to ::delete is o.k. and the program ends normally.

Of course it might as well be an error in my program, but the
situation is stable under variations of it (and the workaround
by redundant placement arguments works well).

Thanks
Alexander Schiemann



>>>>> start example program

#define CAGE_INTERNAL_DEBUG  2 // to get a lot of debugging output
#if(CAGE_USAGE_DEBUG<=2)
#  define CAGE_USAGE_DEBUG 3
#endif
#define  CAGE_USAGE_DEBUG 2

#include <iostream.h>
#include <iomanip.h>

#include <exception>


class cage_node{
  friend class cage_handle;

  class init_binder;

  friend class init_binder;

public:
  static void* operator new(size_t size, const init_binder& B);

protected:
  inline cage_node();
  inline cage_node(const cage_node& B);

  inline virtual ~cage_node();

public:
  static void operator delete(void* p, const init_binder& B);
  static void operator delete(void* p);

private:
  virtual void new_copy(cage_handle& H)const=0;

  inline static void checkout_delete(cage_node *p)
  {p->SKR_info=false; delete p;}

  inline void bind_if_dynamic();

  // Data
  mutable long            Lcount;// number of cage_handles controlling *this
                                 // Lcount==0 indicates that the object is on
                                 // the stack or global (not on the heap).
  mutable bool            SKR_info;

  static const init_binder *Latest_init_binder; 
                                 // if not 0 address of the (temporary) 
                                 // init_binder used in the most recent 
                                 // placement-new (see requirement R2' above)
};



class cage_handle{
  friend void cage_node::bind_if_dynamic();
  friend void cage_node::operator delete(void*, const cage_node::init_binder&);

public:
  inline cage_handle();
  inline cage_handle(const cage_node& P);

  // lazy copy (delayed until the 1st non-constant method is called)
  inline cage_handle(const cage_handle& B);

  // destructs the underlying object if *this is the last handle
  // pointing to it 
  inline ~cage_handle();

  // lazy assignment (delayed until the 1st non-constant method is called)
  inline cage_handle& operator=(const cage_handle& B);
  
  inline operator cage_node::init_binder();

  inline static bool delayed_deletion();

  inline static void cleanup();

private:
  // used by cage_node::cage_node and by cage_node::new_copy
  inline void rebind(cage_node* PP);

  inline static void delayed_delete(cage_node* p);

  // pointer to data 
  mutable cage_node  *Point;

  static cage_node  *Illegal_pointer;
};


/* ***********************************************************
       definitions of missing inline members and subclasses
 *********************************************************** */

// cage_node::init_binder
class cage_node::init_binder
{ friend void* cage_node::operator new(size_t,const init_binder& );
  friend void cage_node::operator delete(void* ,const init_binder& );
  friend void* cage_node::bind_if_dynamic();
  friend cage_handle::operator cage_node::init_binder();
private:
  // data
  cage_handle*        HandlePointer;
  mutable cage_node*  NodePointer;
  mutable void*       MemPointer;
  mutable size_t      Size;
  
  // disable copy, op=, make op& private
  init_binder(const init_binder& );
  void operator=(const init_binder&);
  const init_binder* operator&()const{return this;}
  // the only constructor (called by the conversion-operator of cage_handle)
  inline explicit init_binder(cage_handle& HH):
    HandlePointer(&HH),NodePointer(0),MemPointer(0),Size(0){}


  void checkin_if_necessary()const
  {if(NodePointer)
    {// a new object was successfully constructed and *HandlePointer set to it
    if(CAGE_INTERNAL_DEBUG==7)
      cout<<"~init_binder of "<<this
	  <<" belonging to fully constructed object with\n"
	  <<"    cage_node at "<<NodePointer<<" and MemPointer "<<MemPointer
	  <<endl;
    NodePointer->SKR_info=true;
    NodePointer=0;}
  else
    if(CAGE_INTERNAL_DEBUG==7)
      cout<<"~init_binder of "<<this
	  <<" belonging to unconstructed object"<<endl;
  }

public:
  inline ~init_binder()
  {checkin_if_necessary();}

};


// cage_node::
inline void cage_node::bind_if_dynamic()
{if(Latest_init_binder)
  {// check whether this lies in ((char*)LIB->MemPointer)[LIB->Size]
  // where LIB stands for Latest_init_binder 
  char *this_charpoint=reinterpret_cast<char*>(this);
  char *mem_charpoint =reinterpret_cast<char*>(Latest_init_binder->MemPointer);
  size_t possible_offset=this_charpoint-mem_charpoint;
  // support machines where pointer comparisons are not always meaningful
  if(possible_offset >= 0  &&  possible_offset < Latest_init_binder->Size &&
     mem_charpoint+possible_offset==this_charpoint)
    {// this belongs to an object on the heap that was allocated with 
    // placement-new with Latest_init_binder as argument
    if(CAGE_INTERNAL_DEBUG==7)
      cout<<"\n  belongs to memory at "<<Latest_init_binder->MemPointer
	  <<"\n  is bound to handle "<<Latest_init_binder->HandlePointer
	  <<"\n  which has former value "
	  <<Latest_init_binder->HandlePointer->Point;
    Latest_init_binder->HandlePointer->rebind(this);
    Latest_init_binder->NodePointer=this;
    // indicate that Latest_init_binder is no longer used
    Latest_init_binder=0;}
  }
if(CAGE_INTERNAL_DEBUG==7)
  cout<<endl;
}

inline cage_node::cage_node():Lcount(0),SKR_info(false)
{if(CAGE_INTERNAL_DEBUG==7)
  cout<<"\ncage_node default-ctor at "<<this; 
bind_if_dynamic();}

inline cage_node::cage_node(const cage_node& B):Lcount(0),SKR_info(false)
{if(CAGE_INTERNAL_DEBUG==7)
  cout<<"\ncage_node copy-ctor at "<<this; 
bind_if_dynamic();}

inline cage_node::~cage_node()
{if(CAGE_INTERNAL_DEBUG==7)
  cout<<"\ncage_node dtor at "<<this<<endl;
}


// cage_handle::
inline cage_handle::cage_handle():Point(0){}

inline cage_handle::cage_handle(const cage_node& Ref):
  Point(const_cast<cage_node*>(&Ref))
{if(Point)
  if(Point->Lcount++<=0)
    {--Point->Lcount;
    if(Point==Illegal_pointer)
      throw 3.5;
    throw 3.5;}
}

inline cage_handle::cage_handle(const cage_handle& B): Point(B.Point)
{if(Point)
  ++Point->Lcount;
}

inline cage_handle::~cage_handle()
{if(Point && !--Point->Lcount) delayed_delete(Point);}
  
inline cage_handle::operator cage_node::init_binder()
{return cage_node::init_binder(*this);}

inline cage_handle& cage_handle::operator=(const cage_handle& B)
{// do NOT change the order of the statements!
 // (needed for assignment to itself)
if(B.Point)
  ++B.Point->Lcount;
if(Point && !--Point->Lcount)
  cage_node::checkout_delete(Point);
Point=B.Point;return *this;}


inline void cage_handle::rebind(cage_node* PP)
{// do NOT change the order of the statements!
 // (needed for PP==Point)
if(PP)++PP->Lcount;
if(Point && --Point->Lcount==0) 
  cage_node::checkout_delete(Point);
Point=PP;}

inline void cage_handle::delayed_delete(cage_node *p)
{// first thing to do
p->SKR_info=false;
// free Illegal_pointer 
while(Illegal_pointer)
  {// we must reset Illegal_pointer BEFORE calling delete
  // (possible recursion)
  cage_node *temp=Illegal_pointer;
  Illegal_pointer=0;
  ::delete temp;}
if(CAGE_USAGE_DEBUG>=3)
  {if(CAGE_INTERNAL_DEBUG==7)
    cout<<"\ncage_handle::delayed_delete of cage_node "<<p<<endl;
  Illegal_pointer=p;}
else
  ::delete p;
}

inline bool cage_handle::delayed_deletion()
{return Illegal_pointer!=0;}

inline void cage_handle::cleanup()
{while(Illegal_pointer)
  {// we must reset Illegal_pointer BEFORE calling delete
  // (possible recursion)
  cage_node *temp=Illegal_pointer;
  Illegal_pointer=0;
  delete temp;}
}



/**  static data of cage_node, cage_handle **/
const cage_node::init_binder *cage_node::Latest_init_binder=0;
cage_node                  *cage_handle::Illegal_pointer   =0;


/**  non-inline members **/


void* cage_node::operator new(size_t size, const init_binder& B)
{// detect recursive calls to new between such a call and the ctor
// or re-use of non-temporary init_binder
if(Latest_init_binder || B.MemPointer)
  throw 3.5;
// here B.MemPointer and B.Size are 0 (no pending checkin)
void *p= ::operator new(size);
B.MemPointer=p;
if(CAGE_INTERNAL_DEBUG==7)
  cout<<"\nnew object derived from cage_node at "<<B.MemPointer
      <<" with init_binder at "<<&B<<endl; 
B.Size=size;
Latest_init_binder=&B;
cout<<"\ncage_node::operator new(size_t size, const init_binder& B)"
    <<"\n  return value: "<<p <<endl;
return p;} 


void cage_node::operator delete(void* p)
{cout<<"\ncage_node::operator delete(void* p)"
     <<"\n  first argument: "<<p <<endl;
::delete p;}


void cage_node::operator delete(void* p, const init_binder& B)
{// this function is only called if the ctor of a new-expression fails
 // it resets *B.HandlePointer to 0 without calling the destructor of
 // the underlying object (because it has already been destruceted as far
 // as it had been constructed)
 // If called explicitly (B.MemPointer==0) it will do nothing
static bool warning_shown=false;

cout<<"\ncage_node::operator delete(void* p, const init_binder& B)"
    <<"\n  first argument: "<<p <<endl;

if(!uncaught_exception())
  throw 3.5;
if(CAGE_INTERNAL_DEBUG==7)
  cout<<"\nplacement delete with init_binder at "<<&B<<':'
      <<"\n    values: MemPointer "<<B.MemPointer<<",  NodePointer "
      <<B.NodePointer
      <<"\n    belonging to handle "<<B.HandlePointer
      <<"\n    direct memory-pointer is "<<p<<endl;
if(!B.MemPointer)
  {cerr<<"cage_node::operator delete(void* p, const init_binder& B):\n"
       <<"    FATAL ERROR , pointer to memory missing. This certainly\n"
       <<"    was caused by an illegal explicit call\n"<<endl;
  abort();}
if(B.MemPointer!=p && !warning_shown)
  {cerr<<"    ERROR: inconsistent arguments detected. Pointer to memory `p'\n"
       <<"    does not match redundant information in B.MemPointer\n"
       <<"    WARNING: ignoring pointer to memory p and relying on\n"
       <<"    data in B (workaround for compiler bug of egcs-2.90.29 980515\n"
       <<"    concerning exceptions from nested new-initializer)\n"
       <<"    THIS MESSAGE IS SHOWN ONLY ONCE\n"<<endl;
  warning_shown=true;}
// check whether cage_node ctor finished
if(B.NodePointer)
  {B.HandlePointer->Point=0;
  B.NodePointer=0;
  if(Latest_init_binder)
    {cerr<<"cage_node::operator delete(void* p, const init_binder& B):\n"
	 <<"    FATAL ERROR concerning exception during object-construction\n"
         <<"    after cage_node construction\n"<<endl;
    abort();}
  }
else
  {if(!Latest_init_binder)
    {cerr<<"cage_node::operator delete(void* p, const init_binder& B):\n"
	 <<"    FATAL ERROR concerning exception during object-construction\n"
         <<"    before cage_node construction\n"<<endl;
    abort();}
  Latest_init_binder=0;}
::operator delete(B.MemPointer);
B.MemPointer=0;
B.Size=0;
}



static cage_handle temp_handle(){return cage_handle();}

class T_data: public cage_node{
private:
  mutable cage_handle H;

  static int Counter;


  virtual void new_copy(cage_handle& HH)const
  {new(HH)T_data(*this);}

public:
  // public constructors to test coexistence of automatic and dynamic objects
  T_data():cage_node(),H()
  {++Counter;}

  // constructs a chain of T_data of length max(1,`depth') (starting with *this)
  // and the ctor in depth max(1,`ex_depth') throws an exception 
  T_data(int depth, int ex_depth, int k=0): cage_node(), 
    H((depth<=1)? cage_handle()
                 :cage_handle(*new(temp_handle())T_data(depth-1,ex_depth-1,k+1)))
  {cout<<'\n'<<setw(k)<<"\0"<<"ctor depth "<<k+1;
   if(ex_depth==1)
     {cout<<"   throwing exception\n"<<flush;
     throw 3.5;}
   ++Counter;}

  T_data(const T_data& B):cage_node(B),H(B.H)
  {++Counter;}

  ~T_data(){--Counter;}

  static int counter(){return Counter;}
};


int T_data::Counter=0;


int main()
{int depth,exdepth;

cout<<"\nTrying recursive constructions with exceptions in different depths:\n\n";
try{
  cout<<"trying depth "<<2<<", exception in depth "<<2<<'\n';
  T_data* P=new(temp_handle())T_data(2,2);}
catch(...)
  {;}

cage_handle::cleanup();
cout<<"\nThere are "<<T_data::counter()<<" objects of class T_data left\n";
}

<<<<< end example program

>>>>> start shell protocol
New_exception -> g++ -v
Reading specs from /usr/lib/gcc-lib/i486-linux/egcs-2.90.29/specs
gcc version egcs-2.90.29 980515 (egcs-1.0.3 release)
(Thu Nov 19) tetra : aschiem
New_exception -> g++ -Wall -g cage_handle_test2.cc -o a.out
cage_handle_test2.cc:6: warning: `CAGE_USAGE_DEBUG' redefined
cage_handle_test2.cc:4: warning: this is the location of the previous definition
cage_handle_test2.cc: In method `void T_data::new_copy(class cage_handle &) const':
cage_handle_test2.cc:357: warning: value computed is not used
cage_handle_test2.cc: In function `int main()':
cage_handle_test2.cc:393: warning: unused variable `class T_data * P'
cage_handle_test2.cc:388: warning: unused variable `int exdepth'
cage_handle_test2.cc:388: warning: unused variable `int depth'
(Thu Nov 19) tetra : aschiem
New_exception -> a.out

Trying recursive constructions with exceptions in different depths:

trying depth 2, exception in depth 2

cage_node::operator new(size_t size, const init_binder& B)
  return value: 0x804c770

cage_node::operator new(size_t size, const init_binder& B)
  return value: 0x804c788

 ctor depth 2   throwing exception

cage_node::operator delete(void* p, const init_binder& B)
  first argument: 0x804c788

cage_node::operator delete(void* p, const init_binder& B)
  first argument: 0xbffffcf8
    ERROR: inconsistent arguments detected. Pointer to memory `p'
    does not match redundant information in B.MemPointer
    WARNING: ignoring pointer to memory p and relying on
    data in B (workaround for compiler bug of egcs-2.90.29 980515
    concerning exceptions from nested new-initializer)
    THIS MESSAGE IS SHOWN ONLY ONCE


There are 0 objects of class T_data left
(Thu Nov 19) tetra : aschiem
New_exception -> 

<<<<<< end shell protocol

System:
HOSTTYPE=i486
MACHTYPE=i486-pc-linux-gnu
OSTYPE=linux-gnu

I do not have the 'configure' arguments for gcc at hand
but I would try to provide them if the bug cannot be reproduced
otherwise 

Best Regards
Alexander Schiemann



More information about the Gcc-bugs mailing list