[Date Prev][Date Next][Thread Prev][Thread Next][Author Index][Date Index][Thread Index]

Constructor Bombs without Assignment to "this"



Abstract: The effect of constructor bombs can be achieved portably in
C++ without the assignment to "this" feature.  One plants a smart bomb
in the context which contains the "new Foo (..)"  expression, and
passes a pointer to that bomb as an argument to "new".  The invoked
"operator new" then arms the bomb.  If you leave the calling context
via a BLAST, then the bomb deletes the object properly.


The C++ language no longer specifies that an assignment to "this"
inside a constructor be treated specially.  The argument being that
anything useful that you could use this feature for could be done at
least as well using the new "operator new" stuff.  Unfortunately, our
own Bomb package's use of constructor bombs seemed to be a counter-
example.  Fortunately cfront 2.0 still supports the feature, so our
Bomb package still works.  However, it is no longer portable according
to the language definition.  This may become significant as we try to
port to any of the claimed-to-be-conforming C++ implementations
(including conceivably cfront 2.1--we don't know.  Also, new
implementations probably won`t bother implementing "old cruft").

However, at some loss of modularity, I believe I see how we can do
proper safe constructor bombs using only the standard language (even
better, using only the intersection of the specified language and the
implemented language).  For us, the loss of modularity is minor
because we use pseudo-constructors.  Were calls to constructors
scattered far and wide through our code, then the loss of modularity
would be significant.  (Actually, given that our constructor calls are
generated by the translator, even this wouldn't be a problem for us
personally.  It still would have been for our C++ third parties.)

Consider the following code:

#define	DESIGN_CONSTRUCTOR_BOMB(MYCLASS)				\
	DESIGN_SMART_BOMB(MYCLASS, MYCLASS *,				\
		{							\
			if (SOURCE == BLASTING) {			\
				delete CHARGE;				\
			}						\
		}							\
	);
/*
This DESIGN_CONSTRUCTOR_BOMB is as it already appears in bombx.hxx.
The behavior is that on abnormal exit the relevent object gets
deleted.  On normal exit the bomb goes away without molesting the
object.
*/


#define BOMB(TAG) CAT(TAG,_BombTag) /* so we can name the bomb object */
#define BOMB_TYPE(KIND) CAT(KIND,_Bomb) /* a type of bomb */

/* Note that in bombx.hxx we already have: */
#define	PLANT_BOMB(KIND, TAG)					\
	CAT(KIND,_Bomb) CAT(TAG,_BombTag);


DESIGN_CONSTRUCTOR_BOMB(Heaper);
/*
All Heapers will get the effect of the old constructor bombs using
just this DESIGN, as opposed to DESIGNing a separate bomb type for
every class that needs a constructor bomb.  This saves some code-space
overhead.  Other than that, the technique presented here has
essentially identical overheads to Michael's current technique.  Note
that we are still not building in any Heaper-specific assumption into
the bomb package.  Anyone's class hierarchy can use this technique for
it's roots (given that they declare a virtual destructor at those
roots).
*/


SPTR(Foo) foo () /* pseudo-constructor for class Foo */
{
    PLANT_BOMB(Heaper,exampleBomb);
    return new (&BOMB(exampleBomb)) Foo ();
    /*
    If Foo() might BLAST during construction, then (absent a
    garbage collector) this is the *only* way to safely heap allocate a
    new Foo().  The syntactic overhead is now on the guys who call the
    contructor, instead of (in Michael's scheme) the guy who writes the
    constructor.  For the typical C++ coding style where the clients of a
    class invoke the constructor freely, this would be *bad*.  However,
    with pseudo-constructors, it's all hidden, and within the same
    responsibility boundary.
    
    Note that we are just PLANTing the bomb.  It is up to the relevent
    "operator new" to arm it.  Should the "operator new" succeed, then the
    bomb stands ready to delete the object until we successfully exit the
    pseudo-constructor.
    */
}


CLASS(Heaper,Tofu) {
    ...
    static void *operator new (size_t actualSize, BOMB_TYPE(Heaper) * constructorBomb);
    ...
};

void * Healer::operator new (size_t actualSize, BOMB_TYPE(Heaper) * constructorBomb)
{
    void * storage = Heaper::operator new (actualSize);
    /*
    Note that if we fail to allocate memory, then we BLAST out
    of here (and in turn out of the pseudo-constructor) without having
    armed the bomb (since there is nothing to delete).
    */

    constructorBomb->armBomb ((Heaper *)storage);
    /*
    Yes, this cast should indeed make us nervous.  Should a BLAST occur
    before we have successfully installed the Heaper vtable in the storage
    (i.e., until the storage *is* a Heaper), then we are toast.
    Practically in our case, this means only that Tofu() may not BLAST.  A
    condition which is quite reasonable.
    */

    return storage;
}


Two mooses:

1) How do we combine the above scheme with the "become" technique in
my previous message?  I simply haven't thought about it.

2) What is it that we write in Smalltalk, and how is it handled in the
translator?  This question also applies to the "become" scheme.  I
haven't though about this either.