SourceForge.net Logo

[Release a]
<t4m_bug_templvirtini>

Virtual base pointers are not correctly initialized when some initializer for
an intermediate class (not the virtual base) is called with arguments that
depend (value-wise or type-wise) on template parameters. Schematically,

    class B : public virtual A { /* ... */ };

    template <int N> class C : public B {
      C() : A(), B(N) { /* ... */ }
    };

Note that the initializer B(N) takes an argument that is a template parameter.

There is an extra hidden argument that is passed to constructors to tell them
whether they must skip (extra = 0) initialization of virtual bases (see
add_constr_args, line 608 of src/producers/common/construct/construct.c); this
is necessary because the initialization must be performed only once, in the
constructor for class C, so that the virtual subobject A can be placed after
the end of class C data members. Now, in the present case, this argument is
wrong (extra = 1), and the constructor for class B invokes the constructor for
class A for a second time, re-initializing its virtual base pointer to A to
point to the end of its own data members, which is exactly the place where the
data members of C are located, and thus the subobjects A and C lie overlapped
in memory.

The program AB.C illustrates this problem. The constructor for class A gets
called twice, and the data members A::a and C::c[0] both have the same address
in memory.

The origin of the problem is the static flag in_ctor_base_init (line 597 of
src/producers/common/construct/construct.c). This flag is raised by
ctor_initialise (line 1685) when it is generating the initializer list for a
constructor. But the task of ctor_initialise will not be finished when the
initializer depends on template parameters. At line 2607, the function
convert_constr (called by init_constr, which is called by init_general, which
is called by ctor_initialise) checks if the initializer depends on template
parameteres and, in that case, it avoids constructing the call: it converts the
initializer to a static cast expression[1] to be evaluated later on, forgetting
about the flag in_ctor_base_init. When the template constructor gets
instantiated, it is the function apply_nary (line 532, operator.c) who calls
convert_constr again, but at this time the flag in_ctor_base_init is totally
forgotten, which makes add_constr_args pass the (wrong) argument extra = 1 to
the constructor for class B.

[1] This is suggested by the spec X3J16/96-0225, section 5.2.9,
[expr.static.cast], paragraph 2.

To fix this, what I have done is to create a new operator for internal use
only, called templ_virtual_init (see MAKE_exp_opn in file
src/producers/common/obj_c/exp_ops.h) defined en construct.h. This operator is
handled exactly in the same way as lex_static_Hcast except in two places in the
code: (1) in function convert_constr, if in_ctor_base_init is true, then the
initializer is converted to an expression based on the new operator
templ_virtual_init, instead of lex_static_Hcast; (2) in function apply_nary,
the operator templ_virtual_init is handled in a manner similar to the way
lex_static_Hcast is handled, but raising the flag in_ctor_base_init before
calling convert_constr. In this way, the operator templ_virtual_init keeps
record of the activation of the flag in_ctor_base_init.

In order to implement this idea, the following modifications need to be done
(all file names are relative to src/producers/common/construct/):       

( ) construct.h, add the macro

#define templ_virtual_init 1053

( ) construct.c, line 2609, inside function convert_constr, change

	MAKE_exp_opn ( t, lex_static_Hcast, args, e ) ;

for

	MAKE_exp_opn ( t, in_ctor_base_init ? templ_virtual_init
					: lex_static_Hcast, args, e ) ;

( ) construct.c, line 597, export in_ctor_base_init, change

static int in_ctor_base_init = 0 ;

for

int in_ctor_base_init = 0 ;

( ) operator.c, line 587, inside function apply_nary, add the case

	case templ_virtual_init : {
	    /* Construct 't2 ( p0, ..., pn )' initializer with virtual bases */
	    ERROR err = NULL_err ;
            int icbi = in_ctor_base_init ;
            in_ctor_base_init = 1 ;
	    if ( cpy ) p = copy_exp_list ( p, t1, t2 ) ;
	    p = convert_args ( p ) ;
	    e = convert_constr ( t2, p, &err, CAST_STATIC ) ;
            in_ctor_base_init = icbi ;
	    if ( !IS_NULL_err ( err ) ) report ( crt_loc, err ) ;
	    break ;
	}

( ) operator.c, before function apply_nary (line 530), declare
    in_ctor_base_init,

extern int in_ctor_base_init ;

( ) operator.c, line 346, inside function apply_unary (I am not sure if
    this is necessary, but it will not hurt), add a fall-through for
    templ_virtual_init,

	case templ_virtual_init :
	case lex_static_Hcast :

( ) cast.c, line 1769, inside function make_new_cast_exp (this is tied
    to the previous case, and again I am not sure whether it is
    necessary), add a fall-through for templ_virtual_init,

	case templ_virtual_init :
	case lex_static_Hcast : {
	    e = make_static_cast_exp ( t, a, n ) ;
	    break ;
	}

By treating templ_virtual_init in the same way as lex_static_Hcast,
we are making sure that we are not introducing new bugs.