//#define _GNU_SOURCE #include #include #include #include #include #include "../include/opaque.hh" struct demangle_failed : std::exception { virtual int status() const noexcept = 0; const char* what() const noexcept override { auto s = status(); switch(s) { case -1: return "A memory allocation failure occurred"; case -2: return "`mangled` was not a valid name under the C++ ABI mangling rules"; case -3: return "One of the arguments was invalid"; default: return "Unknown error"; } } protected: inline demangle_failed() : std::exception(){} }; static std::string demangle_name(const char* mangled) { struct demangle_error final : public demangle_failed { inline demangle_error(int s) : demangle_failed(), _status(s) {} inline int status() const noexcept override final { return _status; } int _status; }; size_t len; int status; char * demangled = abi::__cxa_demangle(mangled, nullptr, &len, &status); const auto c_str_to_string = [len](char* str) noexcept(std::is_nothrow_constructible_v) { const auto _free = [&]() { free(static_cast(str)); }; if constexpr(std::is_nothrow_constructible_v) { std::string v{str, len}; _free(); return v; } else { try { std::string v{str, len}; _free(); return v; } catch(...) { _free(); throw; } } }; if(demangled) return c_str_to_string(demangled); else throw demangle_error(status); } inline static std::string demangle_name(const std::string& mangled) { return demangle_name(mangled.c_str()); } static inline void s_puts(const auto& string, FILE* to = nullptr) { std::string_view str{string}; to = to ?: stdout; #ifdef DIRECT_WRITE write(fileno(to), str.data(), str.size()); #else fprintf(to, "%.*s", (int)str.size(), str.data()); #endif } static void print_name(const std::type_info& type) { const char* name = type.name(); s_puts("type holds: "); try { s_puts(demangle_name(name)); s_puts("\n"); } catch(demangle_failed& err) { s_puts(" "); s_puts(name); s_puts("\n"); s_puts("Failed to demangle name: ", stderr); s_puts(err.what(), stderr); s_puts("\n", stderr); } } static void print_view(std::string_view ptr) { //std::string_view ptr{string}; size_t sz; #ifdef DIRECT_WRITE // Bypass all caching of the output and immediately invoke `write()` for everything if(__builtin_expect((sz = ptr.size()) > 0, true)) { write(1, "> ", 2); write(1, ptr.data(), sz); } else write(1, "! \n", 8); write(1, "\n", 1); #else // All the output from the program is cached and `write()` is only called once at the end. if(__builtin_expect((sz = ptr.size()) > 0, true)) { printf("> %.*s\n", (int)sz, ptr.data()); } else puts("! \n"); #endif } static inline void print(const std::string& string) { std::string_view sv{string}; return print_view(sv); } enum class moh_hck { Template, Ref, Owned, }; /// Example using `make_opaque_handle`, like would be done for a C interface template static void use_moh() { std::string* str = new std::string(__PRETTY_FUNCTION__); constexpr auto v_print = [](const opaque_handle& v) { try { print_name(v.get_type()); } catch(const opaque_unknown_type&) { s_puts("type holds: \n"); } print(*v); { auto copied = v; s_puts("(copied)"); print(*copied); s_puts("(moved)"); print(*std::move(copied)); } }; if constexpr(Create != moh_hck::Owned) { constexpr auto _h = [](std::string* ptr, auto op) noexcept { if(ptr) { switch(op) { case opaque_handle_operation::Clone: return new std::string(std::as_const(*ptr)); case opaque_handle_operation::Delete: delete ptr; break; } } return static_cast(nullptr); }; if constexpr(Create == moh_hck::Template) { const opaque_handle v{make_opaque_handle(str)}; v_print(v); } else /*if constexpr(Create == moh_hck::Ref)*/ { const opaque_handle v{make_opaque_handle(str, _h)}; v_print(v); } } else { const opaque_handle v{make_opaque_handle(str, [](std::string* ptr, auto op) noexcept { if(ptr) { switch(op) { case opaque_handle_operation::Clone: return new std::string(std::as_const(*ptr)); case opaque_handle_operation::Delete: delete ptr; break; } } return static_cast(nullptr); })}; v_print(v); } s_puts("---\n"); } // Testing a hypothetical C interface extern "C" { struct c_struct_example; struct opaque; void black_box(const opaque* __restrict__){} c_struct_example* cs_create(const c_struct_example* __restrict__ from); void cs_free(c_struct_example* p); void cs_print(const c_struct_example* p); } static void use_c_struct() { using type = c_struct_example; auto* allocp = cs_create(nullptr); opaque_handle impossible{make_opaque_handle>(nullptr)}; black_box(impossible); s_puts("Testing .get_type() throwing... "); try { print_name(impossible.get_type()); s_puts(" FAILED (expected `opaque_unknown_type` exception)\n"); std::terminate(); } catch(const opaque_unknown_type&) { s_puts(" OK\n"); } const opaque_handle v{make_opaque_handle(allocp, [](auto* ptr, auto op) noexcept { if(ptr) { switch(op) { case opaque_handle_operation::Clone: return cs_create(ptr); case opaque_handle_operation::Delete: cs_free(ptr); break; } } return static_cast(nullptr); })}; try { print_name(v.get_type()); } catch(const opaque_unknown_type&) { s_puts("type holds: \n"); } s_puts("> "); cs_print(v); { auto copied = v; s_puts("(copied)> "); cs_print(copied); s_puts("(moved)> "); cs_print(std::move(copied)); } } int main() { const opaque_handle v{make_opaque_object_handle("Hello world")}; { using prim_t = int; struct never_t{}; auto prim = make_opaque_object_handle(-115); puts("Primitive type handling"); s_puts("(pre-mutation)"); { prim_t& vref = prim.cast(); print(std::to_string(std::as_const(vref))); vref *= -1; } s_puts("(post-mutation)"); print(std::to_string(std::as_const(prim).cast())); print_name(prim.get_type()); s_puts(prim.is_type() ? "\t-> +v: yes, " : "\t-> +v: no, "); s_puts(prim.is_type() ? "!v: yes\n" : "!v: no\n"); } puts("\nAuto-downcasting to `const std::string&`:"); print_name(v.get_type()); print(*v); #if 1 s_puts("\nAttempting invalid safe cast... "); try { print_view(*v); // Try to cast to std::string_view, not the correct type... s_puts(" FAILED\n"); return 1; } catch(opaque_bad_cast&) { // Expected this exception to be thrown s_puts(" OK\n"); } catch(opaque_exception& other) { s_puts(" UNEXPECTED ("); s_puts(other.what()); s_puts(")\n"); return 2; } #endif puts("\nConverting view directly:"); print_view(std::string_view{v.cast()}); // Explicit cast needed, *v will choose the wrong type if not. puts("\nUsing opaque_make_handle():"); use_moh(); use_moh(); use_moh(); puts("\nUsing a C interface struct:"); use_c_struct(); return 0; } extern "C" { struct c_struct_example { int i,j; size_t sz; char buf[256]; }; c_struct_example* cs_create(const c_struct_example* __restrict__ from) { auto* ptr = reinterpret_cast(aligned_alloc(alignof(c_struct_example), sizeof(c_struct_example))); //memset(ptr, 0, sizeof(c_struct_example); if(!from) { ptr->i = 10; ptr->j = 11; snprintf(ptr->buf, 256, "Hello from C interface"); } else { //memcpy(ptr, from, sizeof(c_struct_example)); *ptr = *from; ptr->i +=1; ptr->j -=1; snprintf(ptr->buf, 256, "Hello from copied C interface (from %p)", from); } return ptr; } void cs_print(const c_struct_example* p) { #ifdef DIRECT_WRITE char ibuf[512]; size_t n = snprintf(ibuf, 512, "%s: (%d, %d)", p->buf, p->i, p->j); write(1, ibuf, n < sizeof(ibuf) ? n : 511); write(1, "\n", 1); #else printf("%s: (%d, %d)\n", p->buf, p->i, p->j); #endif } void cs_free(c_struct_example* p) { free(p); } }