You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
344 lines
8.4 KiB
344 lines
8.4 KiB
//#define _GNU_SOURCE
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
#include <cstdio>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <cxxabi.h>
|
|
|
|
#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;
|
|
};
|
|
|
|
int status = 0;
|
|
char * demangled = abi::__cxa_demangle(mangled, nullptr, nullptr /* This never seems to be even close to accurate, it's always off by a long shot and idk why? */, &status);
|
|
|
|
auto c_str_to_string = [](char* str)
|
|
noexcept(std::is_nothrow_constructible_v<std::string, const char*, size_t>)
|
|
{
|
|
const auto _free = [&]() { free(static_cast<void*>(str)); };
|
|
|
|
if constexpr(std::is_nothrow_constructible_v<std::string, const char*, size_t>) {
|
|
std::string v{str};
|
|
_free();
|
|
return v;
|
|
} else {
|
|
try {
|
|
std::string v{str};
|
|
_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)
|
|
{
|
|
const auto& tmp = mangled;
|
|
return demangle_name(tmp.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 {
|
|
const auto dm = demangle_name(name);
|
|
s_puts("`");
|
|
s_puts(dm);
|
|
s_puts("`\n");
|
|
} catch(const demangle_failed& err) {
|
|
s_puts("<mangled> `");
|
|
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, "! <null>\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("! <null>\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<moh_hck Create = moh_hck::Owned>
|
|
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: <unknown>\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<std::string*>(nullptr);
|
|
};
|
|
|
|
if constexpr(Create == moh_hck::Template) {
|
|
const opaque_handle v{make_opaque_handle<std::string, _h>(str)};
|
|
v_print(v);
|
|
} else /*if constexpr(Create == moh_hck::Ref)*/ {
|
|
const opaque_handle v{make_opaque_handle<std::string>(str, _h)};
|
|
v_print(v);
|
|
}
|
|
} else {
|
|
const opaque_handle v{make_opaque_handle<std::string>(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<std::string*>(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<opaque, opaque_empty_handler<opaque>>(nullptr)};
|
|
black_box(impossible);
|
|
|
|
s_puts("Testing <incomplete>.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<type>(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<type*>(nullptr);
|
|
})};
|
|
|
|
try {
|
|
print_name(v.get_type());
|
|
} catch(const opaque_unknown_type&) { s_puts("type holds: <unknown at this point>\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<std::string>("Hello world")};
|
|
|
|
{
|
|
using prim_t = int;
|
|
struct never_t{};
|
|
auto prim = make_opaque_object_handle<prim_t>(-115);
|
|
puts("Primitive type handling");
|
|
s_puts("(pre-mutation)");
|
|
{
|
|
prim_t& vref = prim.cast<prim_t>();
|
|
print(std::to_string(std::as_const(vref)));
|
|
vref *= -1;
|
|
}
|
|
s_puts("(post-mutation)");
|
|
print(std::to_string(std::as_const(prim).cast<prim_t>()));
|
|
print_name(prim.get_type());
|
|
s_puts(prim.is_type<prim_t>() ? "\t-> +v: yes, " : "\t-> +v: no, ");
|
|
s_puts(prim.is_type<never_t>() ? "!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<std::string, opaque_handle::CAST_UNSAFE /* SAFETY: We know this is the type of `v`'s pointee, so we don't need the type check. */>()}); // Explicit cast needed, *v will choose the wrong type if not.
|
|
|
|
puts("\nUsing opaque_make_handle():");
|
|
use_moh<moh_hck::Template>();
|
|
use_moh<moh_hck::Ref>();
|
|
use_moh<moh_hck::Owned>();
|
|
|
|
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<c_struct_example*>(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);
|
|
}
|
|
}
|