
[Release b]
<t4m_bug_copy_func_args>
Thu May 6 20:33:28 WEST 2004
In the current release of tendra4minix (a), after fixing <t4m_bug_tok_recur>,
the compilation of the file tok_recur.C produces this error
Internal error: 'tag 5' used but not defined.
This error is generated by the frontend [src/producers/common/construct/],
inside the function write_capsule_body [../output/capsule.c]. The tag mentioned
is found in the data structure 'static VAR_INFO vars[VAR_total]', declared in
the same file. It is an array of 6 copies of the structure VAR_INFO, and the
"tags" are stored in the entry 0 of this array, 'vars[0]'. Tags uses are stored
in 'unsigned char *vars[0].uses' and their definitions are stored in
'string *vars[0].names'. What is happening is that 'vars[0].names[5]' is the
null pointer (that is, there is no definition), but 'vars[0].uses[5]' says that
the tag 5 has been used in the file, and thus the above error is generated.
Since the structure 'vars' is static, it is accessed only from the same module
capsule.c, and so it is not difficult to locate the places where it is written
and, in particular, where the entries 'vars[0].names[5]' and 'vars[0].uses[5]'
are written. To find them, we instrument the functions capsule_no, capsule_name,
capsule_id, record_usage, and clear_usage, in the following way
ulong capsule_no
PROTO_N ( ( s, v ) )
PROTO_T ( string s X int v )
{
VAR_INFO *var = vars + v ;
ulong n = ( var->no )++ ;
if ( n >= var->sz ) extend_linkage ( v ) ;
var->uses [n] = USAGE_USE ;
var->names [n] = s ;
var->present = 1 ;
if (v == 0 && n == 5) {
BUFFER bf = NULL_buff;
clear_buffer(&bf, stderr);
bfprintf(&bf, "capsule_no(%s, %d)\n", s, v);
output_buffer(&bf, 1);
free_buffer(&bf);
}
return ( n | LINK_EXTERN ) ;
}
and we learn that from all the functions in capsule.c that seem to write
'vars[0].names[5]' and 'vars[0].uses[5]', only capsule_no is actually invoked
(with s = NULL), so it must be the one we are looking for. I have been tracing
and tracing, and I have found that compile_function [../output/compile.c] is
the nexus between the part of the frontend that processes external declarations
and the part that encodes the output TDF capsule (in ../output/). What follows
is a summary of the calls starting from external_declaration [declare.c] and
going down to enc_func_defn [../output/compile.c], which, as its name implies,
is in charge of encoding the body of the function 'operator<< <double> (B &,
const double &)', and it is during its activation when the error is reported.
external_declaration [declare.c]
clear_templates [instance.c], templ = 0
copy_template_list [instance.c]
copy_template_list [instance.c]
copy_template [instance.c], A &operator<< <double>(B &, const double &)
define_templ_member [instance.c]
bind_template [instance.c], tid != NULL_id
bind_template [instance.c], tid == NULL_id
copy_object [copy.c], A &operator<< <double>(B&, const double&)
pop_namespace [namespace.c]
end_function [function.c]
...
define_id [identifier.c]
compile_function [compile.c], A &operator<< <double>
make_tagdef [compile.c], A &operator<< <double>
capsule_id [capsule.c], A &operator<< <double>, ret 0
...
enc_func_defn [compile.c]
...
enc_compound_stmt [stmt.c]
[*]
end_templ_scope [rewrite.c]
end_declarator [namespace.c]
clear_templates [instance.c], templ = 0
[*] Let us trace this call with more detail, since the problem arises during
its activation (note the appearance of the message "capsule_no(, 0)"):
enc_compound_stmt, 65 exp_sequence_tag [stmt.c]
enc_compound_stmt, 84 exp_location_tag
enc_compound_stmt, 67 exp_decl_stmt_tag
enc_stmt, 67 exp_decl_stmt_tag [stmt.c]
enc_stmt, 65 exp_sequence_tag
enc_compound_stmt, 65 exp_sequence_tag
enc_compound_stmt, 84 exp_location_tag
enc_compound_stmt, 73 exp_return_stmt_tag
enc_stmt, 73 exp_return_stmt_tag, Simple return
enc_exp, 19 exp_address_tag [exp.c]
enc_addr_exp, 17 exp_indir_tag [exp.c]
enc_exp, 22 exp_func_id_tag
enc_func_id_call
...
capsule_id, A &A::operator<< <double>(const C<double>&)
...
enc_exp_list
enc_exp, 19 exp_address_tag
enc_addr_exp, 14 exp_init_tag
enc_init_tag, 49 exp_constr_tag
[exp_dummy_no written with 6 at init.c line 711]
enc_exp, 22 exp_func_id_tag
enc_func_id_call
...
capsule_id, A::A(const B &)
...
enc_exp_list
enc_exp, 19 exp_address_tag
enc_addr_exp, 87 exp_dummy_tag
enc_exp, 87 exp_dummy_tag
enc_dummy_exp, n = 6, cnt = 0
enc_dummy_exp: returning
enc_exp: returning
enc_addr_exp: returning
enc_exp: returning
enc_exp, 19 exp_address_tag
enc_addr_exp, 17 exp_indir_tag
enc_exp, 0 exp_identifier_tag
enc_addr_exp, 0 exp_identifier_tag
enc_addr_exp: returning
enc_exp: returning
enc_addr_exp: returning
enc_exp: returning
enc_exp_list: returning
enc_func_id_call: returning
enc_exp: returning
enc_init_tag: returning
enc_addr_exp: returning
enc_exp: returning
enc_exp, 49 exp_constr_tag
enc_exp, 22 exp_func_id_tag
enc_func_id_call
...
capsule_id, C<double>::C(const double &)
...
enc_exp_list
enc_exp, 19 exp_address_tag
enc_addr_exp, 87 exp_dummy_tag
enc_exp, 87 exp_dummy_tag
enc_dummy_exp, n = LINK_NONE, cnt = 0
capsule_no(, 0)
enc_dummy_exp: returning
enc_exp: returning
enc_addr_exp: returning
enc_exp: returning
enc_exp, 19 exp_address_tag
enc_addr_exp, 17 exp_indir_tag
enc_exp, 0 exp_identifier_tag
enc_addr_exp, 0 exp_identifier_tag
enc_addr_exp: returning
enc_exp: returning
enc_addr_exp: returning
enc_exp: returning
enc_exp_list
enc_func_id_call: returning
enc_exp: returning
enc_exp: returning
enc_exp_list: returning
enc_func_id_call: returning
enc_exp: returning
enc_addr_exp: returning
enc_exp: returning
enc_stmt: returning
enc_compound_stmt: returning
enc_compound_stmt: returning
enc_compound_stmt: returning
enc_stmt: returning
enc_stmt: returning
enc_compound_stmt: returning
enc_compound_stmt: returning
The member function 'A::operator<< <double> (const C<double> &)' takes two
arguments and both of them are temporary objects built by user-defined
constructors. In the above call tree, we can recognize clearly each one of the
traces corresponding to the encoding of the calls to each constructor,
'A::A(const B &)' and 'C<double>::C(const double &)'. Each constructor takes
two arguments: a 'this' pointer and a reference to another object. The 'this'
pointer passed to the constructor 'C<double>::C(const double &)' is initialized
to the address of a temporary object of the class 'C<double>', and something is
going wrong during the encoding of this temporary object.
The wrong value (LINK_NONE) passed as second argument to the function
capsule_no comes from the field exp_dummy_no in the function enc_exp
[../output/exp.c] (line 2860). I have been tracing every assigment made to
(instances of) the field exp_dummy_no throughout the frontend, and what is
happening is that: for the constructor 'A::A(const B &)', whose call is built
correctly, the field exp_dummy_no is written with the value 6 (this event is
marked in the call tree above); for the constructor 'C<double>::C(const double
&)', the field exp_dummy_no is not written at all (LINK_NONE is its default
value).
The assignment of the field exp_dummy_no for the constructor 'A::A(const B &)'
is done in the function enc_init_tag [../output/init.c], in the case
exp_constr_tag. The function enc_init_tag is called by enc_addr_exp
[../output/exp.c] in the case exp_init_tag. If we take a look at the call tree
above, in the subtree corresponding to the call to the constructor
'C<double>::C(const double &)', we see that the exp_constr_tag is present,
but exp_address_tag and exp_init_tag (which are the tags that drive the control
flow from enc_exp to enc_init_tag) are missing. The absence of exp_init_tag is
significant, since the constructor MAKE_exp_init is used only in three places
throughout the frontend:
- inside the function copy_exp [copy.c], with the obvious intention (this
function is used to instantiate templates);
- inside the function check_cond [statement.c], but in response to the
expressions lex_while and lex_for, which are not relevant right now;
- finally, inside the function make_temporary [initialise.c], which is used
to build temporary objects.
It is obvious that we are interested in the invocations of make_temporary. I
have written a file almost identical to tok_recur.C, named tok_recur_e.C, which
is compiled without problems, and which differs from tok_recur.C only in that
the call to the member function A::operator<< is made explicitly inside the
function operator<<
template <class T> // tok_recur_e.C, succeeds
inline A &operator << (B &b, const T &c) { return A(b).operator<<(C<T>(c)); }
instead of using an ambiguous expression, as in tok_recur.C,
template <class T> // tok_recur.C, fails
inline A &operator << (B &b, const T &c) { return A(b) << C<T>(c); }
I have been comparing the treatment given to the constructor
'C<double>::C(const double &)' in both programs, and this bug looks similar
in origin to <t4m_bug_func_id>, in the sense that there are certain things
which are tried during the overload resolution, but which are delayed due to
the presence of free template parameters and, later on, during template
instantiation, these things are not properly redone/retried.
We know that the function copy_object [copy.c] is in charge of instantiating
templates. Let us trace the calls to copy_exp [copy.c] from copy_object when
it is generating the instance 'operator<< <double> (B &, const double &)'.
First we will do it for the file that is compiled correctly, tok_recur_e.C,
copy_exp, 65 exp_sequence_tag
copy_exp, 84 exp_location_tag
copy_exp, 84 exp_location_tag
copy_exp, 73 exp_return_stmt_tag
copy_exp, 81 exp_opn_tag
apply_nary, 342 lex_func_Hop, cpy = 1
copy_func_exp, 23 exp_call_tag
copy_exp, 23 exp_call_tag
copy_exp, 1 exp_member_tag
rescan_member, template<class T> A &A::operator<<(const C<T> &)
copy_exp, 49 exp_constr_tag
copy_exp, 22 exp_func_id_tag
rescan_member, A::A(const B &)
copy_func_args, A::A(const B &)
copy_exp, 19 exp_address_tag /* Simple argument copy */
copy_exp, 87 exp_dummy_tag
copy_exp, 19 exp_address_tag /* Simple argument copy */
copy_exp, 17 exp_indir_tag
copy_exp, 0 exp_identifier_tag
rescan_member, b
copy_exp_list
copy_exp, 80 exp_op_tag
expand_type, C<T>
...
instance_type
copy_members
copy_member, template <class T> class C
copy_member, C<double>::c
copy_member, C<double>::C(const T &)
copy_member, C<double>::~C()
copy_member, C &C<double>::operator= (const C &)
apply_unary, 185 lex_cast, cpy = 1
copy_exp, 17 exp_indir_tag
copy_exp, 0 exp_identifier_tag
rescan_member, c
make_cast_exp
convert_reference
cast_exp
resolve_cast
init_direct
init_constr, type_compound_tag
convert_constr
constr_candidates
resolve_overload, C<double>::C(const C<double> &)
C<double>::C(const double &) [winner]
use_func_id, C<double>::C(const double &)
define_template, C<double>::C(const double &)
apply_constr
apply_func_id, C<double>::C(const double &)
cast_args
make_func_exp, 23 exp_call_tag, rescan = 1
rescan_func_id, template <class T> A &A::operator<< (const C<T> &)
rescan_member
resolve_call, template <class T> A &A::operator<< (const C<T> &)
deduce_args
inst_func_deduce
instance_func
use_func_id, A &A::operator<< <double> (const C<double> &)
define_template
apply_func_id, A &A::operator<< <double> (const C<double> &)
cast_args
make_temporary, t = A, tag(t) = 11, tag(e) = 49
init_assign, 6 type_ref_tag, t = const C<double> &
init_ref_lvalue /* Check base class conversions first */
convert_conv_aux
init_ref_lvalue
init_ref_rvalue
find_base_class
qualify_type
make_temporary, t = const C<double>, tag(t)=11, tag(e)=49
cast_class_class
make_ref_init
and with this (except for a call to init_assign that is probably called from
find_return_exp), all invocations return up to copy_object. [Note: in case we
want a wider portrait of this situation, the path from external_declaration
down to copy_object in tok_recur_e.C is identical to that we showed before for
tok_recur.C, and this subtree is also followed by a call to compile_function.]
In the above call trace, we must pay attention to two subtrees. The first
starts from
apply_unary, 185 lex_cast, cpy = 1
[operator.c] and it is the trace of the call to constructor 'C<double>::C(const
double &)'. In fact, the subexpression 'C<T>(c)' that appears in the program is
an explicit cast from the type 'const T &' to the type 'C<T>', hence the name
of the lexical operator.
The second subtree starts from
apply_func_id, A &A::operator<< <double> (const C<double> &)
[function.c] which, being called from make_func_exp [function.c], builds the
invocation to the member funcion 'A::operator<<'. To do this, it is necessary
to build two temporary objects, one of type 'A', another of type 'C<double>',
and pass them by reference. We see that this is done by cast_args [function.c]
calling make_temporary [initialise.c] on both types.
Now, we are going to trace the calls to copy_exp from copy_object in the case
of the program whose compilation fails, tok_recur.C,
copy_exp, 65 exp_sequence_tag
copy_exp, 84 exp_location_tag
copy_exp, 67 exp_decl_stmt_tag
copy_exp, 65 exp_sequence_tag
copy_exp, 84 exp_location_tag
copy_exp, 73 exp_return_stmt_tag
copy_exp, 19 exp_address_tag
copy_exp, 17 exp_indir_tag
copy_exp, 22 exp_func_id_tag
rescan_member, A &A::operator<< <T> (const C<T> &)
rescan_member,template<class T> A &A::operator<<(const C<T>&)
apply_template
apply_func_templ
instance_func
expand_name [../parse/hash.c]
expand_type, template <class T> A & (const C<T> &)
...
instance_type
copy_members
copy_member,template <class T> class C
copy_member,C<double>::c
copy_member,C<double>::C(const T &)
copy_member,C<double>::~C()
copy_member,C &C<double>::operator=(const C&)
define_template,A &A::operator<< <double>(const C<double>&)
copy_func_args, A &A::operator<< <double>(const C<double> &)
copy_exp, 19 exp_address_tag /* Simple argument copy */
copy_exp, 14 exp_init_tag
copy_exp, 49 exp_constr_tag
copy_exp, 22 exp_func_id_tag
rescan_member, A::A(const B &)
copy_func_args, A::A(const B &)
copy_exp, 19 exp_address_tag /* Simple arg. copy */
copy_exp, 87 exp_dummy_tag
copy_exp, 19 exp_address_tag /* Simple arg. copy */
copy_exp, 17 exp_indir_tag
copy_exp, 0 exp_identifier_tag
rescan_member, b
copy_exp, 80 exp_op_tag /* Do implicit argument conversion */
expand_type, C<T>
...
instance_type
apply_unary, 185 lex_cast, cpy = 1
copy_exp, 17 exp_indir_tag
copy_exp, 0 exp_identifier_tag
rescan_member, c
make_cast_exp
convert_reference
cast_exp
resolve_cast
init_direct
init_constr, type_compound_tag
convert_constr
constr_candidates
resolve_overload,C<double>::C(const C<double>&)
C<double>::C(const double &) [winner]
use_func_id, C<double>::C(const double &)
define_template, C<double>::C(const double &)
apply_constr
apply_func_id, C<double>::C(const double &)
cast_args
init_assign, 11 type_compound_tag, t = C<double>, e = c
init_direct, t = C<double>, e = c
init_constr, 11 type_compound_tag (the same arguments)
convert_constr t = C<double>, args = List(e)
constr_candidates
resolve_overload [C<double>::C(const C<double> &)]
use_func_id, C<double>::C(const C<double> &)
apply_constr
apply_trivial_func, n = 2 DEFAULT_COPY
convert_reference [convert.c]
...
convert_class [assign.c]
...
and with this all invocations return up to copy_object.
There are two important things in this tree. First, the subtree starting from
apply_unary, 185 lex_cast, cpy = 1
is identical to that of the file that is compiled correctly (tok_recur_e.C).
But, on the other hand, we do not see any call to make_temporary. There is a
misterious phenomenon which gives us a hint about what is happening: the
respective calls to the function init_assign [initialise.c] in each file.
This function is in charge of generating the initialization of the temporary
object. In the file whose compilation succeeds (tok_recur_e.C), we have a
subtree that starts from
init_assign, 6 type_ref_tag, t = const C<double> &
called from cast_args [function.c], where type_ref_tag is the tag of the type
of the argument of init_assign. But in the file whose compilation fails
(tok_recur.C), we have instead
init_assign, 11 type_compound_tag, t = C<double>, e = c
called from copy_func_args [copy.c], and this makes the compiler generate a
call to the copy constructor 'C<double>::C(const C<double> &)'. To understand
the cause of the difference between the tags, let us trace the call to
apply_func_id [function.c] on the function 'A::operator<< <T> (const C<T> &)'
in the compilation of tok_recur.C; this call happens sooner in the compilation,
after the overload resolution that takes place inside 'template <class T>
operator<< (B &, const T &)',
use_func_id, A &A::operator<< <T> (const C<T> &)
define_template /* Ignore template dependent instantiations */
apply_func_id, A &A::operator<< <T> (const C<T> &)
cast_args
make_temporary, t = A, tag(t) = 11, tag(e) = 49
init_assign, t = const C<T> &, type_ref_tag
init_ref_lvalue /* Check base class conversions first */
convert_conv_aux, cast = CAST_IMPLICIT [construct.c]
dependent_conv [template.c], returns 1
cast_templ_type [cast.c]
The function cast_templ_type [cast.c] creates a lex_implicit operator that
contains, as a subexpression, the lex_cast operator that was present in the
source program; init_assign [initialise.c] returns immediately in the
conditional 'if ( eq_type ( r, t ) ) break ;'. The function cast_templ_type
puts the destination type of the cast ('const C<T> &', in the present case)
inside the lex_implicit operator. It is assumed that this implicit cast must
be reprocessed inside the function copy_func_args [copy.c], finishing what
cast_args [function.c] left undone. But there is a subtle bug in function
copy_func_args.
The problem is as follows. First, the function implicit_cast_exp [copy.c] is
called to check if the expression 'e' is an implicit cast (as it really is).
The function implicit_cast_exp returns null if its argument is not an
implicit cast; otherwise it returns the expression being cast, that is, the
original lex_cast expression that was put inside the lex_implicit operator:
EXP e = DEREF_exp ( HEAD_list ( p ) ) ;
EXP a = implicit_cast_exp ( e ) ;
if ( !IS_NULL_exp ( a ) ) {
/* Do implicit argument conversion */
TYPE t ;
ERROR err = NULL_err ;
a = copy_exp ( a, NULL_type, NULL_type ) ;
t = DEREF_type ( exp_type ( a ) ) ;
e = init_assign ( t, cv_none, a, &err ) ;
It is ok that copy_exp is called with the argument 'a' without the lex_implicit
operator, since this was the original expression found in the program, and we
have already seen that the trace starting from "copy_exp, 80 exp_op_tag"
(just before "apply_unary, 185 lex_cast, cpy = 1") is the same for both files.
But the destination type of the cast, 'const C<T> &', which is the type of the
formal parameter of the function that is being applied, is lost when the
lex_implicit operator is discarded, and the type of the actual argument,
t = 'C<double>' is used instead. That is why init_assign [initialise.c] ends up
generating a call to the copy constructor: it thinks that the type of the
actual argument and the type of the formal parameters are the same type
'C<double>'.
Then, what we have to fix is the type 't' that is passed to the call
e = init_assign ( t, cv_none, a, &err ) ;
The type 't' should be taken from the expression 'e' (and not from 'a', as it
happens now). Recall that the implicit cast expression 'e' was built in this
way inside the function cast_templ_type [cast.c],
EXP cast_templ_type
PROTO_N ( ( t, a, cast ) )
PROTO_T ( TYPE t X EXP a X unsigned cast )
{
/* etc.
t = const C<T> &, op = lex_implicit
*/
MAKE_exp_op ( t, op, a, NULL_exp, e ) ;
return ( e ) ;
}
and so the type 't' can be recovered just changing the line
t = DEREF_type ( exp_type ( a ) ) ;
for
t = DEREF_type ( exp_type ( e ) ) ;
t = expand_type ( t, 1 ) ;
It is necessary to expand the type, since normally it will be dependent on the
parameters of the template we are currently instantiating. In the example
tok_recur.C, before expansion 't' is 'const C<T> &', and after expansion 't' is
'const C<double> &'.
The solution works, and it also works with two variants that caused the same
error message:
- tok_recur_f.C, where the template function 'A::operator<<' is not a member
function,
- tok_recur_t.C, where the classes A and B are also template classes.