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:
Fischlurch 2014-11-22 03:31:59 +01:00
parent 639fd224db
commit 088e4422fb
6 changed files with 188 additions and 84 deletions

View file

@ -26,21 +26,11 @@
// 5/14 - c++11 transition: detect empty function object
// 7/14 - c++11 transition: std hash function vs. boost hash
// 9/14 - variadic templates and perfect forwarding
// 11/14 - pointer to member functions and name mangling
/** @file try.cpp
** Investigation: pitfalls of "perfect forwarding".
** 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.
** Investigation: member function pointers, types and name mangling.
**
*/
@ -48,11 +38,12 @@
#include "lib/util.hpp"
#include <cstddef>
#include <utility>
#include <iostream>
#include <functional>
#include <string>
using lib::test::showType;
using lib::test::demangleCxx;
using std::string;
using std::cout;
using std::endl;
@ -60,21 +51,10 @@ using std::endl;
class Interface
{
public:
Interface(){}
Interface(Interface const& o) { cout << "COPY.CT from "<<&o<<" !!!\n"; }
Interface(Interface const&& o) { cout << "MOVE.CT from "<<&o<<" !!!\n"; }
virtual ~Interface() { } ///< this is an interface
Interface&
operator= (Interface const& o) { cout << "COPY= from "<<&o<<" !!!\n"; return *this; }
Interface&
operator= (Interface const&& o) { cout << "MOVE= from "<<&o<<" !!!\n"; return *this; }
virtual ~Interface() { }
virtual string op() const
{
return "happy SLICING";
}
virtual string moo() =0;
virtual string boo() =0;
};
class Impl
@ -82,11 +62,8 @@ class Impl
{
string s_;
string
op() const override
{
return s_;
}
string moo() { return s_ + " Moo"; }
string boo() { return s_ + " Boo"; }
public:
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
main (int, char**)
{
Impl obj;
Interface const& ref = obj;
Interface& ref = obj;
typedef string (Interface::*Memfun) (void);
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);
indirect_2 (fun, Impl("honk"));
Memfun memfun = &Interface::moo;
cout << demangleCxx (showType (memfun)) << endl;
cout << demangleCxx (showType(&Interface::moo)) << endl;
cout << (ref.*memfun) () << endl;
cout << "\n.gulp.\n";

View file

@ -50,9 +50,11 @@ namespace test {
using std::shared_ptr;
using boost::algorithm::trim;
using util::cStr;
using util::isnil;
using util::contains;
using lib::test::showType;
using lib::test::demangleCxx;
typedef map<string, Launcher*> TestMap;
typedef shared_ptr<TestMap> PTestMap;
@ -174,7 +176,7 @@ namespace test {
{
try
{
INFO (test, "++------------------- invoking TEST: %s", showType(theTest).c());
INFO (test, "++------------------- invoking TEST: %s", cStr(demangleCxx(showType(theTest))));
theTest.run (cmdline);
return Suite::TEST_OK;
}

View file

@ -24,6 +24,11 @@
#include "lib/test/test-helper.hpp"
#include "lib/test/testdummy.hpp"
#include "lib/format-string.hpp"
#include "lib/unique-malloc-owner.hpp"
#ifdef __GNUG__
#include <cxxabi.h>
#endif
#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...?
*/
string
@ -57,6 +120,7 @@ namespace test{
}
/** storage for test-dummy flags */
long Dummy::_local_checksum = 0;

View file

@ -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().
* prints the given size and name literally, without any further magic */
string

View file

@ -14,7 +14,13 @@ out: sizeof.+lib.+test.+test.+Wrmrmpft.+Murpf.+ = 1
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: --> Testgroup=ALL
out: --> Test-ID =--missing--

View 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