From 69f73bd39537419efe9d74c63c84e3292c9f4a15 Mon Sep 17 00:00:00 2001 From: Carlo Wood Date: Fri, 5 Dec 2003 02:40:53 +0000 Subject: [PATCH] re PR libstdc++/13045 (Demangler does demangle floating values.) PR libstdc++/13045 * bits/demangle.h namespace __gnu_cxx::demangler (enum substitution_nt): Removed trailing comma. (implementation_details): Added. (session::M_implementation_details): Added. (session::session): Pass implementation_details. (session::decode_encoding): Same. (session::decode_real): Added. (_GLIBCXX_DEMANGLER_STYLE_VOID _GLIBCXX_DEMANGLER_STYLE_LITERAL _GLIBCXX_DEMANGLER_STYLE_LITERAL_INT _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS _GLIBCXX_DEMANGLER_STYLE_SIZEOF_TYPENAME): Replaced with implementation_details equivalent. (session::decode_expression): Use M_implementation_details instead of macros. Add extra parentheses around 'larger than' operator in expressions in template arguments. (session::decode_bare_function_type): Idem. (session::decode_literal): Idem, and call decode_real for floating literals. (session::decode_type_with_postfix): Put the postfix of the return type of (member) functions after the function instead of after the return type. Also, put a space after the prefix of qualified function pointers: "int (* const". * src/demangle.cc: include most dependent header file first. * testsuite/demangle/regression/cw-16.cc: Updated two and added three tests. From-SVN: r74304 --- libstdc++-v3/ChangeLog | 30 ++ libstdc++-v3/include/bits/demangle.h | 327 ++++++++++++------ libstdc++-v3/src/demangle.cc | 2 +- .../testsuite/demangle/regression/cw-16.cc | 11 +- 4 files changed, 256 insertions(+), 114 deletions(-) diff --git a/libstdc++-v3/ChangeLog b/libstdc++-v3/ChangeLog index 3b7d1835c86..f2936ed29ef 100644 --- a/libstdc++-v3/ChangeLog +++ b/libstdc++-v3/ChangeLog @@ -1,3 +1,33 @@ +2003-12-05 Carlo Wood + + PR libstdc++/13045 + * bits/demangle.h + namespace __gnu_cxx::demangler + (enum substitution_nt): Removed trailing comma. + (implementation_details): Added. + (session::M_implementation_details): Added. + (session::session): Pass implementation_details. + (session::decode_encoding): Same. + (session::decode_real): Added. + (_GLIBCXX_DEMANGLER_STYLE_VOID _GLIBCXX_DEMANGLER_STYLE_LITERAL + _GLIBCXX_DEMANGLER_STYLE_LITERAL_INT + _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS + _GLIBCXX_DEMANGLER_STYLE_SIZEOF_TYPENAME): Replaced with + implementation_details equivalent. + (session::decode_expression): + Use M_implementation_details instead of macros. Add extra parentheses + around 'larger than' operator in expressions in template arguments. + (session::decode_bare_function_type): Idem. + (session::decode_literal): + Idem, and call decode_real for floating literals. + (session::decode_type_with_postfix): Put the postfix + of the return type of (member) functions after the function + instead of after the return type. Also, put a space after the + prefix of qualified function pointers: "int (* const". + * src/demangle.cc: include most dependent header file first. + * testsuite/demangle/regression/cw-16.cc: Updated two + and added three tests. + 2003-12-04 Benjamin Kosnik PR libstdc++/13284 diff --git a/libstdc++-v3/include/bits/demangle.h b/libstdc++-v3/include/bits/demangle.h index af8cff9645c..cb90acc224f 100644 --- a/libstdc++-v3/include/bits/demangle.h +++ b/libstdc++-v3/include/bits/demangle.h @@ -54,32 +54,6 @@ #define _GLIBCXX_DEMANGLER_CWDEBUG 1 #endif -// The following defines change the behaviour of the demangler. The -// default behaviour is that none of these macros is defined. - -// _GLIBCXX_DEMANGLER_STYLE_VOID -// Default behaviour: int f() -// Uses (void) instead of (): int f(void) - -// _GLIBCXX_DEMANGLER_STYLE_LITERAL -// Default behaviour: (long)13, -// (unsigned long long)19 -// Use extensions 'u', 'l' and 'll' for integral -// literals (as in template arguments): 13l, 19ull - -// _GLIBCXX_DEMANGLER_STYLE_LITERAL_INT -// Default behaviour: 4 -// Use also an explicit cast for int in literals: (int)4 - -// _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS -// Default behaviour: (i) < (3), sizeof (int) -// Don't output spaces around operators in expressions: (i)<(3), sizeof(int) - -// _GLIBCXX_DEMANGLER_STYLE_SIZEOF_TYPENAME -// Default behaviour: sizeof (X::t) -// Put 'typename' infront of types -// inside a 'sizeof': sizeof (typename X::t) - namespace __gnu_cxx { namespace demangler @@ -91,7 +65,7 @@ namespace __gnu_cxx template_template_param, nested_name_prefix, nested_name_template_prefix, - unscoped_template_name, + unscoped_template_name }; struct substitution_st @@ -312,6 +286,59 @@ namespace __gnu_cxx #endif }; + struct implementation_details + { + private: + unsigned int M_style; + + public: + // The following flags change the behaviour of the demangler. The + // default behaviour is that none of these flags is set. + + static unsigned int const style_void = 1; + // Default behaviour: int f() + // Use (void) instead of (): int f(void) + + static unsigned int const style_literal = 2; + // Default behaviour: (long)13, + // (unsigned long long)19 + // Use extensions 'u', 'l' and 'll' for integral + // literals (as in template arguments): 13l, 19ull + + static unsigned int const style_literal_int = 4; + // Default behaviour: 4 + // Use also an explicit + // cast for int in literals: (int)4 + + static unsigned int const style_compact_expr_ops = 8; + // Default behaviour: (i) < (3), sizeof (int) + // Don't output spaces around + // operators in expressions: (i)<(3), sizeof(int) + + static unsigned int const style_sizeof_typename = 16; + // Default behaviour: sizeof (X::t) + // Put 'typename' infront of + // types inside a 'sizeof': sizeof (typename X::t) + + public: + implementation_details(unsigned int style_flags = 0) : + M_style(style_flags) { } + virtual ~implementation_details() { } + bool get_style_void(void) const + { return (M_style & style_void); } + bool get_style_literal(void) const + { return (M_style & style_literal); } + bool get_style_literal_int(void) const + { return (M_style & style_literal_int); } + bool get_style_compact_expr_ops(void) const + { return (M_style & style_compact_expr_ops); } + bool get_style_sizeof_typename(void) const + { return (M_style & style_sizeof_typename); } + // This can be overridden by user implementations. + virtual bool decode_real(char* output, unsigned long* input, + size_t size_of_real) const { return false; } + }; + template class session { @@ -336,25 +363,29 @@ namespace __gnu_cxx std::vector M_template_arg_pos; int M_template_arg_pos_offset; std::vector M_substitutions_pos; + implementation_details const& M_implementation_details; #if _GLIBCXX_DEMANGLER_CWDEBUG bool M_inside_add_substitution; #endif public: - explicit session(char const* in, int len) + explicit session(char const* in, int len, + implementation_details const& id = implementation_details()) : M_str(in), M_pos(0), M_maxpos(len - 1), M_result(true), M_inside_template_args(0), M_inside_type(0), M_inside_substitution(0), M_saw_destructor(false), M_name_is_cdtor(false), M_name_is_template(false), M_name_is_conversion_operator(false), - M_template_args_need_space(false), M_template_arg_pos_offset(0) + M_template_args_need_space(false), M_template_arg_pos_offset(0), + M_implementation_details(id) #if _GLIBCXX_DEMANGLER_CWDEBUG , M_inside_add_substitution(false) #endif { } static int - decode_encoding(string_type& output, char const* input, int len); + decode_encoding(string_type& output, char const* input, int len, + implementation_details const& id = implementation_details()); bool decode_type(string_type& output, @@ -425,6 +456,7 @@ namespace __gnu_cxx bool decode_unscoped_name(string_type& output); bool decode_non_negative_decimal_integer(string_type& output); bool decode_special_name(string_type& output); + bool decode_real(string_type& output, size_t size_of_real); }; template @@ -879,6 +911,66 @@ namespace __gnu_cxx _GLIBCXX_DEMANGLER_RETURN; } + template + bool + session::decode_real(string_type& output, size_t size_of_real) + { + _GLIBCXX_DEMANGLER_DOUT_ENTERING("decode_real"); + + unsigned long words[4]; // 32 bit per long, maximum of 128 bits. + unsigned long* word = &words[0]; + + int saved_pos; + store(saved_pos); + + // The following assumes that leading zeroes are also included in the + // mangled name, I am not sure that is conforming to the C++-ABI, but + // it is what g++ does. + unsigned char nibble, c = current(); + for(size_t word_cnt = size_of_real / 4; word_cnt > 0; --word_cnt) + { + for (int nibble_cnt = 0; nibble_cnt < 8; ++nibble_cnt) + { + // Translate character into nibble. + if (c < '0' || c > 'f') + _GLIBCXX_DEMANGLER_FAILURE; + if (c <= '9') + nibble = c - '0'; + else if (c >= 'a') + nibble = c - 'a' + 10; + else + _GLIBCXX_DEMANGLER_FAILURE; + // Write nibble into word array. + if (nibble_cnt == 0) + *word = nibble << 28; + else + *word |= (nibble << (28 - 4 * nibble_cnt)); + c = next(); + } + ++word; + } + char buf[24]; + if (M_implementation_details.decode_real(buf, words, size_of_real)) + { + output += buf; + _GLIBCXX_DEMANGLER_RETURN; + } + restore(saved_pos); + + output += '['; + c = current(); + for(size_t nibble_cnt = 0; nibble_cnt < 2 * size_of_real; ++nibble_cnt) + { + if (c < '0' || c > 'f' || (c > '9' && c < 'a')) + _GLIBCXX_DEMANGLER_FAILURE; + output += c; + c = next(); + } + output += ']'; + + _GLIBCXX_DEMANGLER_RETURN; + } + template bool session::decode_literal(string_type& output) @@ -891,7 +983,7 @@ namespace __gnu_cxx _GLIBCXX_DEMANGLER_FAILURE; eat_current(); if ((M_pos += decode_encoding(output, M_str + M_pos, - M_maxpos - M_pos + 1)) < 0) + M_maxpos - M_pos + 1, M_implementation_details)) < 0) _GLIBCXX_DEMANGLER_FAILURE; } else @@ -907,34 +999,39 @@ namespace __gnu_cxx _GLIBCXX_DEMANGLER_RETURN; } char c = current(); -#ifdef _GLIBCXX_DEMANGLER_STYLE_LITERAL - if (c == 'i' || c == 'j' || c == 'l' || - c == 'm' || c == 'x' || c == 'y') + if ((c == 'i' || c == 'j' || c == 'l' || + c == 'm' || c == 'x' || c == 'y') && + M_implementation_details.get_style_literal()) eat_current(); - else -#else -#ifndef _GLIBCXX_DEMANGLER_STYLE_LITERAL_INT - if (c == 'i') + else if (c == 'i' && + !M_implementation_details.get_style_literal_int()) eat_current(); else -#endif -#endif { output += '('; if (!decode_type(output)) _GLIBCXX_DEMANGLER_FAILURE; output += ')'; } - if (!decode_number(output)) + if (c >= 'd' && c <= 'g') + { + size_t size_of_real = (c == 'd') ? sizeof(double) : + ((c == 'f') ? sizeof(float) : + (c == 'e') ? sizeof(long double) : 16); + if (!decode_real(output, size_of_real)) + _GLIBCXX_DEMANGLER_FAILURE; + } + else if (!decode_number(output)) _GLIBCXX_DEMANGLER_FAILURE; -#ifdef _GLIBCXX_DEMANGLER_STYLE_LITERAL - if (c == 'j' || c == 'm' || c == 'y') - output += 'u'; - if (c == 'l' || c == 'm') - output += 'l'; - if (c == 'x' || c == 'y') - output += "ll"; -#endif + if (M_implementation_details.get_style_literal()) + { + if (c == 'j' || c == 'm' || c == 'y') + output += 'u'; + if (c == 'l' || c == 'm') + output += 'l'; + if (c == 'x' || c == 'y') + output += "ll"; + } } _GLIBCXX_DEMANGLER_RETURN; } @@ -1204,11 +1301,10 @@ namespace __gnu_cxx if (opcode1 == 't' || opcode1 == 'z') { eat_current(); -#ifdef _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS - output += "sizeof("; -#else - output += "sizeof ("; -#endif + if (M_implementation_details.get_style_compact_expr_ops()) + output += "sizeof("; + else + output += "sizeof ("; if (opcode1 == 't') { // I cannot think of a mangled name that is valid for both cases @@ -1240,21 +1336,22 @@ namespace __gnu_cxx // This is ambiguity is very unlikely to happen and it is kind // of fuzzy to detect when adding a 'typename' makes sense. // -#ifdef _GLIBCXX_DEMANGLER_STYLE_SIZEOF_TYPENAME - // We can only get here inside a template parameter, - // so this is syntactically correct if the given type is - // a typedef. The only disadvantage is that it is inconsistent - // with all other places where the 'typename' keyword should be - // used and we don't. - // With this, the above example will demangle as - // void f<5, A>(C::q) - if (current() == 'N' || // - // This should be a safe bet. - (current() == 'S' && - next_peek() == 't')) // std::something, guess that - // this involves a typedef. - output += "typename "; -#endif + if (M_implementation_details.get_style_sizeof_typename()) + { + // We can only get here inside a template parameter, + // so this is syntactically correct if the given type is + // a typedef. The only disadvantage is that it is inconsistent + // with all other places where the 'typename' keyword should be + // used and we don't. + // With this, the above example will demangle as + // void f<5, A>(C::q) + if (current() == 'N' || // + // This should be a safe bet. + (current() == 'S' && + next_peek() == 't')) // std::something, guess that + // this involves a typedef. + output += "typename "; + } if (!decode_type(output)) _GLIBCXX_DEMANGLER_FAILURE; } @@ -1305,32 +1402,33 @@ namespace __gnu_cxx output += op; bool is_eq = (opcode1 != current()); eat_current(); + if (index == 34 && M_inside_template_args) // operator> + output += '('; output += '('; if (!decode_expression(output)) _GLIBCXX_DEMANGLER_FAILURE; output += ')'; if (entry.type != unary) { -#ifndef _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS - output += ' '; -#endif + if (!M_implementation_details.get_style_compact_expr_ops()) + output += ' '; output += op; if (is_eq) output += '='; -#ifndef _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS - output += ' '; -#endif + if (!M_implementation_details.get_style_compact_expr_ops()) + output += ' '; output += '('; if (!decode_expression(output)) _GLIBCXX_DEMANGLER_FAILURE; output += ')'; + if (index == 34 && M_inside_template_args) + output += ')'; if (entry.type == trinary) { -#ifdef _GLIBCXX_DEMANGLER_STYLE_COMPACT_EXPR_OPS - output += ":("; -#else - output += " : ("; -#endif + if (M_implementation_details.get_style_compact_expr_ops()) + output += ":("; + else + output += " : ("; if (!decode_expression(output)) _GLIBCXX_DEMANGLER_FAILURE; output += ')'; @@ -1440,8 +1538,7 @@ namespace __gnu_cxx M_saw_destructor = false; _GLIBCXX_DEMANGLER_RETURN; } -#ifndef _GLIBCXX_DEMANGLER_STYLE_VOID - if (current() == 'v') + if (current() == 'v' && !M_implementation_details.get_style_void()) { eat_current(); if (current() != 'E' && current() != 0) @@ -1450,7 +1547,6 @@ namespace __gnu_cxx M_saw_destructor = false; _GLIBCXX_DEMANGLER_RETURN; } -#endif output += '('; M_template_args_need_space = false; if (!decode_type(output)) // Must have at least one parameter. @@ -1508,8 +1604,8 @@ namespace __gnu_cxx // FE ==> R (Q)B "", "" ( recursive) // and "FE". // - // Note that if has postfix qualifiers (an array), then those - // are added AFTER the (member) function type. For example: + // Note that if has postfix qualifiers (an array or function), then + // those are added AFTER the (member) function type. For example: // FPAE ==> R (*(Q)B) [], where the PA added the prefix // "(*" and the postfix ") []". // @@ -1863,7 +1959,8 @@ namespace __gnu_cxx // Return type. // Constructors, destructors and conversion operators don't // have a return type, but seem to never get here. - if (!decode_type_with_postfix(prefix, postfix)) + string_type return_type_postfix; + if (!decode_type_with_postfix(prefix, return_type_postfix)) // substitution: recursive { failure = true; @@ -1885,9 +1982,10 @@ namespace __gnu_cxx add_substitution(start_pos, type); // substitution: all qualified types if any. qualifiers->decode_qualifiers(prefix, postfix); - prefix += ")"; - prefix += bare_function_type; - prefix += member_function_qualifiers; + postfix += ")"; + postfix += bare_function_type; + postfix += member_function_qualifiers; + postfix += return_type_postfix; goto decode_type_exit; } qualifiers->add_qualifier_start(pointer_to_member, start_pos, @@ -1929,17 +2027,19 @@ namespace __gnu_cxx // substitution: "", "" ( recursive) and "FE". // Return type. - if (!decode_type_with_postfix(prefix, postfix)) + string_type return_type_postfix; + if (!decode_type_with_postfix(prefix, return_type_postfix)) // Substitution: "". { failure = true; break; } - // Only array (pointer) types have a postfix. - // In that case we don't want the space but - // expect something like prefix is "int (*" - // and postfix is ") [1]". - if (postfix.size() == 0) + // Only array and function (pointer) types have a postfix. + // In that case we don't want the space but expect something + // like prefix is "int (*" and postfix is ") [1]". + // We do want the space if this pointer is qualified. + if (return_type_postfix.size() == 0 || + (prefix.size() > 0 && *prefix.rbegin() != '*')) prefix += ' '; prefix += '('; string_type bare_function_type; @@ -1953,10 +2053,11 @@ namespace __gnu_cxx add_substitution(start_pos, type); // Substitution: "FE". qualifiers->decode_qualifiers(prefix, postfix); // substitution: all qualified types, if any. - prefix += ")"; + postfix += ")"; if (extern_C) - prefix += " [extern \"C\"] "; - prefix += bare_function_type; + postfix += " [extern \"C\"] "; + postfix += bare_function_type; + postfix += return_type_postfix; break; } case 'T': @@ -2181,7 +2282,8 @@ namespace __gnu_cxx if (current() != 'Z' || M_pos >= M_maxpos) _GLIBCXX_DEMANGLER_FAILURE; if ((M_pos += decode_encoding(output, M_str + M_pos + 1, - M_maxpos - M_pos) + 1) < 0 || eat_current() != 'E') + M_maxpos - M_pos, M_implementation_details) + 1) < 0 || + eat_current() != 'E') _GLIBCXX_DEMANGLER_FAILURE; output += "::"; if (current() == 's') @@ -2480,7 +2582,7 @@ namespace __gnu_cxx if (!decode_call_offset(output) || !decode_call_offset(output) || (M_pos += decode_encoding(output, M_str + M_pos, - M_maxpos - M_pos + 1)) < 0) + M_maxpos - M_pos + 1, M_implementation_details)) < 0) _GLIBCXX_DEMANGLER_FAILURE; _GLIBCXX_DEMANGLER_RETURN; case 'C': // GNU extension? @@ -2507,7 +2609,7 @@ namespace __gnu_cxx output += "non-virtual thunk to "; if (!decode_call_offset(output) || (M_pos += decode_encoding(output, M_str + M_pos, - M_maxpos - M_pos + 1)) < 0) + M_maxpos - M_pos + 1, M_implementation_details)) < 0) _GLIBCXX_DEMANGLER_FAILURE; _GLIBCXX_DEMANGLER_RETURN; } @@ -2522,8 +2624,7 @@ namespace __gnu_cxx template int session::decode_encoding(string_type& output, - char const* in, - int len) + char const* in, int len, implementation_details const& id) { #if _GLIBCXX_DEMANGLER_CWDEBUG _GLIBCXX_DEMANGLER_DOUT(dc::demangler, @@ -2534,7 +2635,7 @@ namespace __gnu_cxx #endif if (len <= 0) return INT_MIN; - session demangler_session(in, len); + session demangler_session(in, len, id); string_type nested_name_qualifiers; int saved_pos; demangler_session.store(saved_pos); @@ -2577,8 +2678,10 @@ namespace __gnu_cxx typedef Allocator allocator_type; typedef std::basic_string, Allocator> string_type; - static string_type symbol(char const* in); - static string_type type(char const* in); + static string_type symbol(char const* in, + demangler::implementation_details const& id); + static string_type type(char const* in, + demangler::implementation_details const& id); }; // demangle::symbol() @@ -2587,7 +2690,8 @@ namespace __gnu_cxx // instance returned by nm(1). template std::basic_string, Allocator> - demangle::symbol(char const* input) + demangle::symbol(char const* input, + demangler::implementation_details const& id) { // ::= _Z // ::= _GLOBAL_ __ @@ -2616,8 +2720,8 @@ namespace __gnu_cxx } else if (input[1] == 'Z') { - int cnt = demangler_type::decode_encoding(result, input + 2, - INT_MAX); + int cnt = + demangler_type::decode_encoding(result, input + 2, INT_MAX, id); if (cnt < 0 || input[cnt + 2] != 0) failure = true; } @@ -2637,14 +2741,15 @@ namespace __gnu_cxx // name as for instance returned by std::type_info::name(). template std::basic_string, Allocator> - demangle::type(char const* input) + demangle::type(char const* input, + demangler::implementation_details const& id) { std::basic_string, Allocator> result; if (input == NULL) result = "(null)"; else { - demangler::session demangler_session(input, INT_MAX); + demangler::session demangler_session(input, INT_MAX, id); if (!demangler_session.decode_type(result) || demangler_session.remaining_input_characters()) { diff --git a/libstdc++-v3/src/demangle.cc b/libstdc++-v3/src/demangle.cc index fc5672bb0ed..58c076785e5 100644 --- a/libstdc++-v3/src/demangle.cc +++ b/libstdc++-v3/src/demangle.cc @@ -28,8 +28,8 @@ // invalidate any other reasons why the executable file might be covered by // the GNU General Public License. -#include #include +#include // __cxa_demangle // diff --git a/libstdc++-v3/testsuite/demangle/regression/cw-16.cc b/libstdc++-v3/testsuite/demangle/regression/cw-16.cc index 6ca0cd7ef16..8dbfcb1f56e 100644 --- a/libstdc++-v3/testsuite/demangle/regression/cw-16.cc +++ b/libstdc++-v3/testsuite/demangle/regression/cw-16.cc @@ -32,15 +32,22 @@ verify_demangle("_Z3fooIA6_KiEvA9_KT_rVPrS4_", "void foo(int const [9][6], int const restrict (* volatile restrict) [9][6])"); // 2003/11/12, libstdc++/12947 verify_demangle("_Z1fILi5E1AEvN1CIXqugtT_Li0ELi1ELi2EEE1qE", - "void f<5, A>(C<((5) > (0)) ? (1) : (2)>::q)"); + "void f<5, A>(C<(((5) > (0))) ? (1) : (2)>::q)"); verify_demangle("_Z1fILi5EEvN1AIXcvimlT_Li22EEE1qE", "void f<5>(A<(int)((5) * (22))>::q)"); verify_demangle("_Z1fPFYPFiiEiE", - "f(int (*)(int) (*) [extern \"C\"] (int))"); + "f(int (*(*) [extern \"C\"] (int))(int))"); verify_demangle("_Z1fI1XENT_1tES2_", "X::t f(X::t)"); verify_demangle("_Z1fILi5E1AEvN1CIXstN1T1tEEXszsrS2_1tEE1qE", "void f<5, A>(C::q)"); +// 2003/12/03, libstdc++/13045 +verify_demangle("_Z1fILi1ELc120EEv1AIXplT_cviLd4028ae147ae147aeEEE", + "void f<1, (char)120>(A<(1) + ((int)((double)[4028ae147ae147ae]))>)"); +verify_demangle("_Z1fILi1ELc120EEv1AIXplT_cviLf3f800000EEE", + "void f<1, (char)120>(A<(1) + ((int)((float)[3f800000]))>)"); +verify_demangle("_Z9hairyfuncM1YKFPVPFrPA2_PM1XKFKPA3_ilEPcEiE", + "hairyfunc(int (* const (X::** (* restrict (* volatile* (Y::*)(int) const)(char*)) [2])(long) const) [3])"); return 0; } -- 2.30.2