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.

342 lines
8.2 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;
};
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<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, 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("<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);
}
}