c++/3211: Incorrect, substitution related mangling.

Carlo Wood carlo@alinoe.com
Sun Jun 17 09:59:00 GMT 2001


I am afraid this is going to be a long one.  The background is that
I've been working on a demangler for several weeks now, using g++-3.0 (pre)
and the demangler in libiberty for comparision (without looking at the source).

Now I've come to a point where start to find bugs in g++ instead of in
my own demangler ;).

PS If you don't have time to read it all, there is a CONCLUSION half way.

I've used http://reality.sgi.com/dehnert_engr/cxx/abi.html#mangling-type
as reference for writing my demangler.

---

This reference defines

<pointer-to-member-type> ::= M <class type> <member type>

being a <type> itself, it is subject to substitution.

Moreover, "<CV-qualifiers> <some type>" being a <type>, both
"<CV-qualifiers> <some type>" and "<some type>" are subject to
substitution.

For clarity, let <type> be an unqualified type below.  So we
have to write:

<pointer-to-member-type> ::= [<CV-qualifiers>] M [<CV-qualifiers>] <type1> [<CV-qualifiers>] <type2>
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  pointer-to-member-type ____/                   ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^
                                  class type ____/         member type ____/

and subsitution members are:

<type1>
<CV-qualifiers> <type1>
<type2>
<CV-qualifiers> <type2>
M <CV-qualifiers> <type1> <CV-qualifiers> <type2>
<CV-qualifiers> M <CV-qualifiers> <type1> <CV-qualifiers> <type2>

As an example, <type2> has qualifiers in this case:

--------------------
struct A {
  int volatile member;
};
   
int volatile (A::*member_pointer) = &A::member;
--------------------

We can use the following test program to find what g++-3.0 makes of it:

--------------------
#include <iostream>
#include <typeinfo>
 
struct A {
  int volatile member;
};
 
int volatile (A::*member_pointer) = &A::member;
 
int main(void)
{
  std::cout << typeid(member_pointer).name() << '\n';
  return 0;
}
--------------------

which gives:

M1AVi


Now note that "A const::*" is incorrect C++.

The following test case:

--------------------
#include <iostream>
#include <typeinfo>
 
struct A {
  int volatile member;
};
 
template<typename T>
  struct const_test {
    static int volatile (T::*member_pointer);
  };
 
template<typename T>
  int volatile (T::*const_test<T>::member_pointer) = &T::member;
 
int main(void)
{
  std::cout << typeid(const_test<A const>::member_pointer).name() << '\n';
  return 0;
}
--------------------

Still return "M1AVi", because the 'const' is simply dropped.

The only way to get a qualifier before <type1> is when <type2> is a member *function*
that is `const'.  Please not that even:

--------------------
#include <iostream>
#include <typeinfo>
 
struct A {
  int member(void) { }
};
 
template<typename T>
  struct const_test {
    typedef int (member_type)(void);
    member_type (T::*member_pointer);
  };
 
int main(void)
{
  std::cout << typeid(const_test<A const>::member_pointer).name() << '\n';
  return 0;
}
--------------------

Returns "MK1AFivE", where "1A" is <type1> and "FivE" is <type2>.  There is no
'const' qualifier for "1A".

This seems to indicate that qualifiers directly after the "M" really
belong to the member *function*.  For example:

--------------------
#include <iostream>
#include <typeinfo>
 
struct A {
  int member(void) const { }
};
 
int (A::*member_pointer)(void) const = &A::member;
 
int main(void)
{
  std::cout << typeid(member_pointer).name() << '\n';
  return 0;
}
--------------------

returns "MK1AFivE", with a 'K' in front of the <type1> -- which as we saw before
really can't BE there *unless* <type2> is a function.

[ Note that this is very equivalent with how qualifiers work with nested functions,
  for example:

  --------------------
    struct A {
      int f1(float);
      int f2(float) const volatile;
    };
     
    int A::f1(float) { }
    int A::f2(float) const volatile { }
  --------------------

  results in the mangled names:

  00000000 T _ZN1A2f1Ef
  00000010 T _ZNVK1A2f2Ef

  very similar like

  --------------------
  struct A;
  void f1p(int (A::*)(float)) { }
  void f2p(int (A::*)(float) const volatile) { }
  --------------------

  results in the mangled names

  00000000 T _Z3f1pM1AFifE
  00000020 T _Z3f2pMVK1AFifE
]

Imho, the draft is wrong and should have defined <pointer-to-member> like
it defines <nested-name>:

<pointer-to-member-type> ::= M [CV-qualifiers] <class type> <member type>

stripping off qualifiers that otherwise *errornous* seem to belong to the
class type (which is not possible).

Another argument for this is found by looking at what happens when
<class type> is a <nested-name> itself.  For example:

--------------------
#include <iostream>
#include <typeinfo>
 
struct A {
  struct B {
    int member(void) const { }
  };
};
 
int (A::B::*member_pointer)(void) const = &A::B::member;
 
int main(void)
{
  std::cout << typeid(member_pointer).name() << '\n';
  return 0;
}
--------------------

prints "MKN1A1BEFivE", as it should (imho) and NOT "MN1AK1BEFivE", or
"MNK1A1BEFivE" for that matter.

The demangler in libiberty is already *heavily* broken concerning qualifiers
(as I reported before), but how does the above affect the mangling of g++?

It does because it has effect on the used substitutions, after all - when using

<pointer-to-member-type> ::= M [CV-qualifiers] <class type> <member type>

the resulting substitutions would be:

<type1>			<-- unqualified, even when being a <nested-name>
<type2>
<CV-qualifiers> <type2>
M <CV-qualifiers> <type1> <CV-qualifiers> <type2>
<CV-qualifiers> M <CV-qualifiers> <type1> <CV-qualifiers> <type2>

And "<CV-qualifiers> <type1>" is gone from the list.

Now before you say 'yes, but this is at most a bug in the reference and not in g++',
let me point out an inconsistency in the reference related to this.  It states:
"substitutable components are the represented symbolic constructs, not their
 associated mangling character strings".  This would mean that a substitution
for "<CV-qualifiers> <type1>" would result in storing and re-using a string like
"A::B const", but it is possible to get:

MS3_FivE

for example, where S3_ represents the mentioned qualified <type1>.  Following the
reference literally we'd HAVE to demangle that as:

"void (A::B const::*)(int)"

where the string "A::B const" (S3_) appears literally.

==========
CONCLUSION
==========

This is why I am convinced that this substitution is wrong, it shouldn't exist.
The correct way to mangle the above is as follows:

with for example:

S2_ == "A::B"
S3_ == "A::B const"

"void (A::B::*)(int) const"

should mangle as

"MKS2_FviE" and not as "MS3_FviE".

g++-3.0 makes of this:

----------------------
struct A {
  struct B {
    int member(void) const { }
    static int (A::B::* const member_pointer)(void) const = &A::B::member;
    void foo(A::B const*, typeof(member_pointer));
  };
};
 
void A::B::foo(A::B const*, typeof(A::B::member_pointer)) { }
----------------------

where 'A::B::foo' is mangled as "_ZN1A1B3fooEPKS0_MS1_FivE".

                 this should be "_ZN1A1B3fooEPKS0_MKS0_FivE" imho.


-- 
Carlo Wood <carlo@alinoe.com>

=====================================================================================
PS Note that the demangler in libiberty chokes on this as well:

/usr/src/gcc/gcc-cvs-3.0/libiberty>c++filt _ZN1A1B3fooEPKS0_MKS0_FivE
 -> mangled-name             at position   0
 -> encoding                 at position   2
 -> name                     at position   2
 -> nested-name              at position   2
 -> prefix                   at position   3
 -> unqualified-name         at position   3
 -> source-name              at position   3
 -> number                   at position   3
 -> number*                  at position   3
 -> identifier               at position   4
SUBSTITUTIONS:
 S_   : A
 -> unqualified-name         at position   5
 -> source-name              at position   5
 -> number                   at position   5
 -> number*                  at position   5
 -> identifier               at position   6
SUBSTITUTIONS:
 S_   : A
 S0_  : A::B
 -> unqualified-name         at position   7
 -> source-name              at position   7
 -> number                   at position   7
 -> number*                  at position   7
 -> identifier               at position   8
 -> bare-function-type       at position  12
 -> type                     at position  12
 -> type*                    at position  12
 -> type*                    at position  13
 -> type                     at position  13
 -> CV-qualifiers            at position  13
 -> type                     at position  14
 -> substitution             at position  14
 -> number                   at position  15
 -> number*                  at position  15
SUBSTITUTIONS:
 S_   : A
 S0_  : A::B
 S1_  : A::B const
SUBSTITUTIONS:
 S_   : A
 S0_  : A::B
 S1_  : A::B const
 S2_  : A::B const*
 -> type                     at position  17
 -> type*                    at position  17
 -> type                     at position  18
 -> CV-qualifiers            at position  18
 -> type                     at position  19
 -> substitution             at position  19
 -> number                   at position  20
 -> number*                  at position  20
SUBSTITUTIONS:
 S_   : A
 S0_  : A::B
 S1_  : A::B const
 S2_  : A::B const*
 S3_  : A::B const
 -> type*                    at position  22
 -> function-type            at position  22
 -> bare-function-type       at position  23
 -> type                     at position  23
 -> builtin-type             at position  23
SUBSTITUTIONS:
 S_   : A
 S0_  : A::B
 S1_  : A::B const
 S2_  : A::B const*
 S3_  : A::B const
 S4_  : int ()()
SUBSTITUTIONS:
 S_   : A
 S0_  : A::B
 S1_  : A::B const
 S2_  : A::B const*
 S3_  : A::B const
 S4_  : int ()()
 S5_  : int (A::B const::*)()
A::B::foo(A::B const*, int (A::B const::*)())


Where "A::B const" appears TWICE in the substitution list, which is a definite bug.
Moreover, I am not convinved that "int ()()" should be there (it can neither be
used as a literal replacement; same argument).  Finally, the "A::B const::*"
is syntactical nonsense.



More information about the Gcc-bugs mailing list