Test helper to show demangled C++ names
Heureka! found out that the C++ standard library exposes a cross vendor C++ ABI, which amongst others allows to show object code names and type-IDs in the language-level, human readable unmangeld form. Of course, actual application code should not rely on such a internal representation, yet it is of tremendous help when writing and debugging unit tests. Signed-off-by: Ichthyostega <prg@ichthyostega.de>
This commit is contained in:
parent
639fd224db
commit
088e4422fb
6 changed files with 188 additions and 84 deletions
101
research/try.cpp
101
research/try.cpp
|
|
@ -26,21 +26,11 @@
|
||||||
// 5/14 - c++11 transition: detect empty function object
|
// 5/14 - c++11 transition: detect empty function object
|
||||||
// 7/14 - c++11 transition: std hash function vs. boost hash
|
// 7/14 - c++11 transition: std hash function vs. boost hash
|
||||||
// 9/14 - variadic templates and perfect forwarding
|
// 9/14 - variadic templates and perfect forwarding
|
||||||
|
// 11/14 - pointer to member functions and name mangling
|
||||||
|
|
||||||
|
|
||||||
/** @file try.cpp
|
/** @file try.cpp
|
||||||
** Investigation: pitfalls of "perfect forwarding".
|
** Investigation: member function pointers, types and name mangling.
|
||||||
** Find out about the corner cases where chained argument forwarding
|
|
||||||
** does not work as expected. The key point is to put close attention on the
|
|
||||||
** template parameters we're passing on to the next lower layer. It is crucial
|
|
||||||
** either to add the reference explicitly, or to have it included implicitly
|
|
||||||
** by relying on the way how template params are matched on function calls:
|
|
||||||
** - lvalue -> parameter becomes TY &
|
|
||||||
** - rvalue -> parameter becomes TY
|
|
||||||
**
|
|
||||||
** In this final stage, the example shows what happens if we erroneously fail
|
|
||||||
** to take the variadic arguments by '&&' on forwarding: we end-up with a
|
|
||||||
** slicing copy to the base class.
|
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
@ -48,11 +38,12 @@
|
||||||
#include "lib/util.hpp"
|
#include "lib/util.hpp"
|
||||||
|
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <utility>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <functional>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
using lib::test::showType;
|
||||||
|
using lib::test::demangleCxx;
|
||||||
|
|
||||||
using std::string;
|
using std::string;
|
||||||
using std::cout;
|
using std::cout;
|
||||||
using std::endl;
|
using std::endl;
|
||||||
|
|
@ -60,21 +51,10 @@ using std::endl;
|
||||||
class Interface
|
class Interface
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Interface(){}
|
virtual ~Interface() { } ///< this is an interface
|
||||||
Interface(Interface const& o) { cout << "COPY.CT from "<<&o<<" !!!\n"; }
|
|
||||||
Interface(Interface const&& o) { cout << "MOVE.CT from "<<&o<<" !!!\n"; }
|
|
||||||
|
|
||||||
Interface&
|
virtual string moo() =0;
|
||||||
operator= (Interface const& o) { cout << "COPY= from "<<&o<<" !!!\n"; return *this; }
|
virtual string boo() =0;
|
||||||
Interface&
|
|
||||||
operator= (Interface const&& o) { cout << "MOVE= from "<<&o<<" !!!\n"; return *this; }
|
|
||||||
|
|
||||||
|
|
||||||
virtual ~Interface() { }
|
|
||||||
virtual string op() const
|
|
||||||
{
|
|
||||||
return "happy SLICING";
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Impl
|
class Impl
|
||||||
|
|
@ -82,11 +62,8 @@ class Impl
|
||||||
{
|
{
|
||||||
string s_;
|
string s_;
|
||||||
|
|
||||||
string
|
string moo() { return s_ + " Moo"; }
|
||||||
op() const override
|
string boo() { return s_ + " Boo"; }
|
||||||
{
|
|
||||||
return s_;
|
|
||||||
}
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Impl(string ss ="IMP")
|
Impl(string ss ="IMP")
|
||||||
|
|
@ -94,65 +71,27 @@ class Impl
|
||||||
{ }
|
{ }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename X>
|
|
||||||
string
|
|
||||||
showRefRRefVal()
|
|
||||||
{
|
|
||||||
return std::is_lvalue_reference<X>::value? " by REF"
|
|
||||||
: std::is_rvalue_reference<X>::value? " by MOVE": " VAL";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename... XS>
|
|
||||||
void
|
|
||||||
diagnostics (string id, XS const&... xs)
|
|
||||||
{
|
|
||||||
cout << "--"<<id<<"--\n"
|
|
||||||
<< lib::test::showVariadicTypes<XS...>(xs...)
|
|
||||||
<< "\n"
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void
|
|
||||||
invoke (Interface const& ref)
|
|
||||||
{
|
|
||||||
using Ty = Interface const&;
|
|
||||||
diagnostics<Ty> ("Invoke", ref);
|
|
||||||
cout << "instanceof Impl?" << bool(INSTANCEOF(Impl, &ref)) <<"\n";
|
|
||||||
cout << "________________"
|
|
||||||
<< ref.op()
|
|
||||||
<< "____\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class FUN, typename...ARG>
|
|
||||||
void
|
|
||||||
indirect_1 (FUN fun, ARG... args) // NOTE: erroneously taking ARG as-is, which results in taking BY VALUE when used in the forwarding chain!
|
|
||||||
{
|
|
||||||
diagnostics<ARG...> ("Indirect-1", args...);
|
|
||||||
fun (args...);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<class FUN, typename...ARG>
|
|
||||||
void
|
|
||||||
indirect_2 (FUN fun, ARG&&... args)
|
|
||||||
{
|
|
||||||
diagnostics<ARG&&...> ("Indirect-2", args...);
|
|
||||||
indirect_1 (fun, std::forward<ARG>(args)...);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int, char**)
|
main (int, char**)
|
||||||
{
|
{
|
||||||
Impl obj;
|
Impl obj;
|
||||||
Interface const& ref = obj;
|
Interface& ref = obj;
|
||||||
|
|
||||||
|
typedef string (Interface::*Memfun) (void);
|
||||||
|
|
||||||
|
|
||||||
cout << "before call. Address... "<<&ref<<"\n";
|
cout << "before call. Address... "<<&ref<<"\n";
|
||||||
|
|
||||||
std::function<void(Interface const&)> fun(invoke);
|
cout << ref.moo() << endl;
|
||||||
|
cout << ref.boo() << endl;
|
||||||
|
|
||||||
indirect_2 (fun, ref);
|
Memfun memfun = &Interface::moo;
|
||||||
indirect_2 (fun, Impl("honk"));
|
cout << demangleCxx (showType (memfun)) << endl;
|
||||||
|
cout << demangleCxx (showType(&Interface::moo)) << endl;
|
||||||
|
cout << (ref.*memfun) () << endl;
|
||||||
|
|
||||||
cout << "\n.gulp.\n";
|
cout << "\n.gulp.\n";
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -50,9 +50,11 @@ namespace test {
|
||||||
using std::shared_ptr;
|
using std::shared_ptr;
|
||||||
using boost::algorithm::trim;
|
using boost::algorithm::trim;
|
||||||
|
|
||||||
|
using util::cStr;
|
||||||
using util::isnil;
|
using util::isnil;
|
||||||
using util::contains;
|
using util::contains;
|
||||||
using lib::test::showType;
|
using lib::test::showType;
|
||||||
|
using lib::test::demangleCxx;
|
||||||
|
|
||||||
typedef map<string, Launcher*> TestMap;
|
typedef map<string, Launcher*> TestMap;
|
||||||
typedef shared_ptr<TestMap> PTestMap;
|
typedef shared_ptr<TestMap> PTestMap;
|
||||||
|
|
@ -174,7 +176,7 @@ namespace test {
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
INFO (test, "++------------------- invoking TEST: %s", showType(theTest).c());
|
INFO (test, "++------------------- invoking TEST: %s", cStr(demangleCxx(showType(theTest))));
|
||||||
theTest.run (cmdline);
|
theTest.run (cmdline);
|
||||||
return Suite::TEST_OK;
|
return Suite::TEST_OK;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,11 @@
|
||||||
#include "lib/test/test-helper.hpp"
|
#include "lib/test/test-helper.hpp"
|
||||||
#include "lib/test/testdummy.hpp"
|
#include "lib/test/testdummy.hpp"
|
||||||
#include "lib/format-string.hpp"
|
#include "lib/format-string.hpp"
|
||||||
|
#include "lib/unique-malloc-owner.hpp"
|
||||||
|
|
||||||
|
#ifdef __GNUG__
|
||||||
|
#include <cxxabi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
|
@ -41,6 +46,64 @@ namespace test{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef __GNUG__
|
||||||
|
/**
|
||||||
|
* \par Implementation notes
|
||||||
|
* GCC / G++ subscribes to a cross-vendor ABI for C++, sometimes called the IA64 ABI
|
||||||
|
* because it happens to be the native ABI for that platform. It is summarised
|
||||||
|
* \link http://www.codesourcery.com/cxx-abi/ mentor-embedded \endlink
|
||||||
|
* along with the current specification. For users of GCC greater than or equal to 3.x,
|
||||||
|
* entry points are exposed through the standard library in \c <cxxabi.h>
|
||||||
|
* relies on a vendor neutral ABI for C++ compiled programs
|
||||||
|
*
|
||||||
|
* char* abi::__cxa_demangle(const char* mangled_name,
|
||||||
|
* char* output_buffer, size_t* length,
|
||||||
|
* int* status)
|
||||||
|
*
|
||||||
|
* Parameters:
|
||||||
|
* - \c mangled_name
|
||||||
|
* NUL-terminated character string containing the name to be demangled.
|
||||||
|
* - \c output_buffer
|
||||||
|
* region of memory, allocated with \c malloc, of `*length` bytes,
|
||||||
|
* into which the demangled name is stored. If \c output_buffer is not long enough,
|
||||||
|
* it is expanded using \c realloc. output_buffer may instead be NULL; in that case,
|
||||||
|
* the demangled name is placed in a region of memory allocated with \c malloc.
|
||||||
|
* - \c length
|
||||||
|
* If length is non-NULL, the length of the buffer containing the demangled name is placed in `*length`.
|
||||||
|
* - \c status
|
||||||
|
* error flag: `*status` is set to one of the following values:
|
||||||
|
*
|
||||||
|
* 0: The demangling operation succeeded.
|
||||||
|
* -1: A memory allocation failure occurred.
|
||||||
|
* -2: mangled_name is not a valid name under the C++ ABI mangling rules.
|
||||||
|
* -3: One of the arguments is invalid.
|
||||||
|
*
|
||||||
|
* The function returns a pointer to the start of the NUL-terminated demangled name,
|
||||||
|
* or NULL if the demangling fails. The caller is responsible for deallocating
|
||||||
|
* this memory using \c free.
|
||||||
|
*/
|
||||||
|
string
|
||||||
|
demangleCxx (Literal rawName)
|
||||||
|
{
|
||||||
|
int error = -4;
|
||||||
|
UniqueMallocOwner<char> demangled (abi::__cxa_demangle (rawName,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
&error));
|
||||||
|
return 0==error? demangled.get()
|
||||||
|
: string(rawName);
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
string
|
||||||
|
demangleCxx (Literal rawName)
|
||||||
|
{
|
||||||
|
return string (rawName);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** @todo probably this can be done in a more clever way. Anyone...?
|
/** @todo probably this can be done in a more clever way. Anyone...?
|
||||||
*/
|
*/
|
||||||
string
|
string
|
||||||
|
|
@ -55,7 +118,8 @@ namespace test{
|
||||||
garbage[--p] = alpha[rand() % MAXAL];
|
garbage[--p] = alpha[rand() % MAXAL];
|
||||||
return garbage;
|
return garbage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/** storage for test-dummy flags */
|
/** storage for test-dummy flags */
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -72,6 +72,19 @@ namespace test{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** reverse the effect of C++ name mangling.
|
||||||
|
* @return string in language-level form of a C++ type or object name,
|
||||||
|
* or a string with the original input if demangling fails.
|
||||||
|
* @warning implementation relies on the cross vendor C++ ABI in use
|
||||||
|
* by GCC and compatible compilers, so portability is limited.
|
||||||
|
* The implementation is accessed through libStdC++
|
||||||
|
* Name representation in emitted object code and type IDs is
|
||||||
|
* essentially an implementation detail and subject to change.
|
||||||
|
*/
|
||||||
|
string
|
||||||
|
demangleCxx (Literal rawName);
|
||||||
|
|
||||||
|
|
||||||
/** for printing sizeof().
|
/** for printing sizeof().
|
||||||
* prints the given size and name literally, without any further magic */
|
* prints the given size and name literally, without any further magic */
|
||||||
string
|
string
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,13 @@ out: sizeof.+lib.+test.+test.+Wrmrmpft.+Murpf.+ = 1
|
||||||
END
|
END
|
||||||
|
|
||||||
|
|
||||||
TEST "TestOption_test" TestOption_test <<END
|
TEST "Helper to show demangled C++ names" TestHelperDemangling_test <<END
|
||||||
|
out: .+lib.test.test.Space.+Outer.+Inner
|
||||||
|
out-lit: lib::test::test::Space const* (*)(lib::test::test::Outer<lib::test::test::Space>::Inner const&&)
|
||||||
|
END
|
||||||
|
|
||||||
|
|
||||||
|
TEST "Testsuite option handling" TestOption_test <<END
|
||||||
out: Testing invocation with cmdline: ...
|
out: Testing invocation with cmdline: ...
|
||||||
out: --> Testgroup=ALL
|
out: --> Testgroup=ALL
|
||||||
out: --> Test-ID =--missing--
|
out: --> Test-ID =--missing--
|
||||||
|
|
|
||||||
80
tests/library/test/test-helper-demangling-test.cpp
Normal file
80
tests/library/test/test-helper-demangling-test.cpp
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
TestHelperDemangling(Test) - ensure a helper for C++ demangling works as expected
|
||||||
|
|
||||||
|
Copyright (C) Lumiera.org
|
||||||
|
2014, Hermann Vosseler <Ichthyostega@web.de>
|
||||||
|
|
||||||
|
This program is free software; you can redistribute it and/or
|
||||||
|
modify it under the terms of the GNU General Public License as
|
||||||
|
published by the Free Software Foundation; either version 2 of
|
||||||
|
the License, or (at your option) any later version.
|
||||||
|
|
||||||
|
This program is distributed in the hope that it will be useful,
|
||||||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with this program; if not, write to the Free Software
|
||||||
|
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||||
|
|
||||||
|
* *****************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#include "lib/test/run.hpp"
|
||||||
|
#include "lib/test/test-helper.hpp"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using std::string;
|
||||||
|
using std::cout;
|
||||||
|
using std::endl;
|
||||||
|
|
||||||
|
|
||||||
|
namespace lib {
|
||||||
|
namespace test{
|
||||||
|
namespace test{
|
||||||
|
|
||||||
|
template<class T>
|
||||||
|
struct Outer
|
||||||
|
{
|
||||||
|
struct Inner { };
|
||||||
|
|
||||||
|
static const T*
|
||||||
|
phantom (Inner const&&)
|
||||||
|
{
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Space { };
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**********************************************//**
|
||||||
|
* @test verify the demangling of C++ names,
|
||||||
|
* as available through the GCC platform ABI.
|
||||||
|
* The Lumiera support library exposes this
|
||||||
|
* non-portable feature through a convenience
|
||||||
|
* helper to ease the writing of unit tests.
|
||||||
|
*
|
||||||
|
* @see test-helper.hpp
|
||||||
|
*/
|
||||||
|
class TestHelperDemangling_test : public Test
|
||||||
|
{
|
||||||
|
void
|
||||||
|
run (Arg)
|
||||||
|
{
|
||||||
|
Outer<Space> ship;
|
||||||
|
auto magic = &ship.phantom;
|
||||||
|
|
||||||
|
cout << showType(magic) << endl;
|
||||||
|
cout << demangleCxx(showType(magic)) << endl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
LAUNCHER (TestHelperDemangling_test, "unit common");
|
||||||
|
|
||||||
|
|
||||||
|
}}} // namespace lib::test::test
|
||||||
Loading…
Reference in a new issue