From f3558490493118b201d6dd83082ec2f7e14c7aca Mon Sep 17 00:00:00 2001 From: Roman Tsisyk <roman@tsisyk.com> Date: Mon, 6 Jul 2015 18:25:03 +0300 Subject: [PATCH] C++ reflection: add ability to dynamically invoke C++ members by name Implement rtti-like scheme to export C++ type information to FFI. --- src/CMakeLists.txt | 1 + src/box/error.cc | 20 ++- src/box/error.h | 1 + src/box/lua/error.cc | 26 +-- src/box/xlog.cc | 16 +- src/box/xlog.h | 3 + src/exception.cc | 51 +++--- src/exception.h | 29 ++-- src/fiber.cc | 9 +- src/fiber.h | 3 +- src/lua/utils.cc | 3 +- src/lua/utils.h | 1 + src/reflection.c | 47 ++++++ src/reflection.h | 269 ++++++++++++++++++++++++++++++++ src/sio.cc | 4 +- src/sio.h | 1 + test/box/misc.result | 6 +- test/unit/CMakeLists.txt | 5 + test/unit/reflection_c.c | 33 ++++ test/unit/reflection_c.result | 5 + test/unit/reflection_cxx.cc | 174 +++++++++++++++++++++ test/unit/reflection_cxx.result | 30 ++++ 22 files changed, 679 insertions(+), 58 deletions(-) create mode 100644 src/reflection.c create mode 100644 src/reflection.h create mode 100644 test/unit/reflection_c.c create mode 100644 test/unit/reflection_c.result create mode 100644 test/unit/reflection_cxx.cc create mode 100644 test/unit/reflection_cxx.result diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 573e325ef5..aa36296f8b 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -46,6 +46,7 @@ set (core_sources exception.cc coro.cc object.cc + reflection.c assoc.c ) diff --git a/src/box/error.cc b/src/box/error.cc index 6ccad390ef..5ec6435bb9 100644 --- a/src/box/error.cc +++ b/src/box/error.cc @@ -28,11 +28,17 @@ */ #include "error.h" #include <stdio.h> -#include <typeinfo> + +static struct method clienterror_methods[] = { + make_method(&type_ClientError, "code", &ClientError::errcode), + METHODS_SENTINEL +}; +const struct type type_ClientError = make_type("ClientError", &type_Exception, + clienterror_methods); ClientError::ClientError(const char *file, unsigned line, uint32_t errcode, ...) - : Exception(file, line) + : Exception(&type_ClientError, file, line) { m_errcode = errcode; va_list ap; @@ -44,7 +50,7 @@ ClientError::ClientError(const char *file, unsigned line, ClientError::ClientError(const char *file, unsigned line, const char *msg, uint32_t errcode) - : Exception(file, line) + : Exception(&type_ClientError, file, line) { m_errcode = errcode; strncpy(m_errmsg, msg, sizeof(m_errmsg) - 1); @@ -61,10 +67,10 @@ ClientError::log() const uint32_t ClientError::get_errcode(const Exception *e) { - const ClientError *error = dynamic_cast<const ClientError *>(e); - if (error) - return error->errcode(); - if (typeid(*e) == typeid(OutOfMemory)) + ClientError *client_error = type_cast(ClientError, e); + if (client_error) + return client_error->errcode(); + if (type_cast(OutOfMemory, e)) return ER_MEMORY_ISSUE; return ER_PROC_LUA; } diff --git a/src/box/error.h b/src/box/error.h index caaebc0c6f..3473879793 100644 --- a/src/box/error.h +++ b/src/box/error.h @@ -31,6 +31,7 @@ #include "errcode.h" #include "exception.h" +extern const struct type type_ClientError; class ClientError: public Exception { public: virtual void raise() diff --git a/src/box/lua/error.cc b/src/box/lua/error.cc index 9e86f19cc9..c393ae8cc0 100644 --- a/src/box/lua/error.cc +++ b/src/box/lua/error.cc @@ -121,22 +121,22 @@ lbox_error_last(lua_State *L) } else { lua_newtable(L); - lua_pushstring(L, "message"); - lua_pushstring(L, e->errmsg()); - lua_settable(L, -3); - lua_pushstring(L, "type"); - lua_pushstring(L, e->type()); - lua_settable(L, -3); - - lua_pushstring(L, "code"); - lua_pushinteger(L, ClientError::get_errcode(e)); + lua_pushstring(L, e->type->name); lua_settable(L, -3); - if (SystemError *se = dynamic_cast<SystemError *>(e)) { - lua_pushstring(L, "errno"); - lua_pushinteger(L, se->errnum()); - lua_settable(L, -3); + type_foreach_method(e->type, method) { + if (method_invokable<const char *>(method, e)) { + const char *s = method_invoke<const char *>(method, e); + lua_pushstring(L, method->name); + lua_pushstring(L, s); + lua_settable(L, -3); + } else if (method_invokable<int>(method, e)) { + int code = method_invoke<int>(method, e); + lua_pushstring(L, method->name); + lua_pushinteger(L, code); + lua_settable(L, -3); + } } } return 1; diff --git a/src/box/xlog.cc b/src/box/xlog.cc index 06eca9f56b..ed92d94df7 100644 --- a/src/box/xlog.cc +++ b/src/box/xlog.cc @@ -54,9 +54,10 @@ static const log_magic_t eof_marker = mp_bswap_u32(0xd510aded); /* host byte ord static const char inprogress_suffix[] = ".inprogress"; static const char v12[] = "0.12\n"; +const struct type type_XlogError = make_type("XlogError", &type_Exception); XlogError::XlogError(const char *file, unsigned line, const char *format, ...) - :Exception(file, line) + :Exception(&type_XlogError, file, line) { va_list ap; va_start(ap, format); @@ -64,10 +65,21 @@ XlogError::XlogError(const char *file, unsigned line, va_end(ap); } +XlogError::XlogError(const struct type *type, const char *file, unsigned line, + const char *format, ...) + :Exception(type, file, line) +{ + va_list ap; + va_start(ap, format); + vsnprintf(m_errmsg, sizeof(m_errmsg), format, ap); + va_end(ap); +} + +const struct type type_XlogGapError = make_type("XlogGapError", &type_Exception); XlogGapError::XlogGapError(const char *file, unsigned line, const struct vclock *from, const struct vclock *to) - :XlogError(file, line, "") + :XlogError(&type_XlogGapError, file, line, "") { char *s_from = vclock_to_string(from); char *s_to = vclock_to_string(to); diff --git a/src/box/xlog.h b/src/box/xlog.h index 8ddea55742..11ee522b26 100644 --- a/src/box/xlog.h +++ b/src/box/xlog.h @@ -43,6 +43,9 @@ struct XlogError: public Exception { XlogError(const char *file, unsigned line, const char *format, ...); +protected: + XlogError(const struct type *type, const char *file, unsigned line, + const char *format, ...); }; struct XlogGapError: public XlogError diff --git a/src/exception.cc b/src/exception.cc index 3d006cb961..861ef2b6c1 100644 --- a/src/exception.cc +++ b/src/exception.cc @@ -33,7 +33,6 @@ #include <stdio.h> #include <string.h> #include <errno.h> -#include <typeinfo> /** out_of_memory::size is zero-initialized by the linker. */ static OutOfMemory out_of_memory(__FILE__, __LINE__, @@ -45,6 +44,16 @@ diag_get() return &fiber()->diag; } +static const struct method exception_methods[] = { + make_method(&type_Exception, "message", &Exception::errmsg), + make_method(&type_Exception, "file", &Exception::file), + make_method(&type_Exception, "line", &Exception::line), + make_method(&type_Exception, "log", &Exception::log), + METHODS_SENTINEL +}; +const struct type type_Exception = make_type("Exception", NULL, + exception_methods); + void * Exception::operator new(size_t size) { @@ -68,8 +77,16 @@ Exception::~Exception() } } -Exception::Exception(const char *file, unsigned line) - : m_ref(0), m_file(file), m_line(line){ +Exception::Exception(const struct type *type_arg, const char *file, + unsigned line) + : Object(), type(type_arg), m_ref(0) { + if (m_file != NULL) { + snprintf(m_file, sizeof(m_file), "%s", file); + m_line = line; + } else { + m_file[0] = 0; + m_line = 0; + } m_errmsg[0] = 0; if (this == &out_of_memory) { /* A special workaround for out_of_memory static init */ @@ -78,25 +95,21 @@ Exception::Exception(const char *file, unsigned line) } } -const char * -Exception::type() const -{ - const char *name = typeid(*this).name(); - /** A quick & dirty version of name demangle for class names */ - char *res = NULL; - (void) strtol(name, &res, 10); - return res && strlen(res) ? res : name; -} - void Exception::log() const { - _say(S_ERROR, m_file, m_line, m_errmsg, "%s", type()); + _say(S_ERROR, m_file, m_line, m_errmsg, "%s", type->name); } +static const struct method systemerror_methods[] = { + make_method(&type_SystemError, "errnum", &SystemError::errnum), + METHODS_SENTINEL +}; -SystemError::SystemError(const char *file, unsigned line) - : Exception(file, line), +const struct type type_SystemError = make_type("SystemError", &type_Exception, + systemerror_methods); +SystemError::SystemError(const struct type *type, const char *file, unsigned line) + :Exception(type, file, line), m_errno(errno) { /* nothing */ @@ -104,7 +117,7 @@ SystemError::SystemError(const char *file, unsigned line) SystemError::SystemError(const char *file, unsigned line, const char *format, ...) - :Exception(file, line), + : Exception(&type_SystemError, file, line), m_errno(errno) { va_list ap; @@ -135,10 +148,12 @@ SystemError::log() const m_errmsg); } +const struct type type_OutOfMemory = + make_type("OutOfMemory", &type_Exception); OutOfMemory::OutOfMemory(const char *file, unsigned line, size_t amount, const char *allocator, const char *object) - :SystemError(file, line) + : SystemError(&type_OutOfMemory, file, line) { m_errno = ENOMEM; snprintf(m_errmsg, sizeof(m_errmsg), diff --git a/src/exception.h b/src/exception.h index 6b7c05909a..54d964c4a9 100644 --- a/src/exception.h +++ b/src/exception.h @@ -31,15 +31,18 @@ #include "object.h" #include <stdarg.h> #include <assert.h> +#include <limits.h> /* _POSIX_PATH_MAX */ + +#include "reflection.h" #include "say.h" enum { TNT_ERRMSG_MAX = 512 }; +extern const struct type type_Exception; class Exception: public Object { -protected: - /** Allocated size. */ - size_t size; public: + const struct type *type; /* reflection */ + void *operator new(size_t size); void operator delete(void*); virtual void raise() @@ -48,14 +51,20 @@ class Exception: public Object { throw this; } - const char *type() const; - const char * errmsg() const { return m_errmsg; } + const char *file() const { + return m_file; + } + + int line() const { + return m_line; + } + virtual void log() const; virtual ~Exception(); @@ -71,19 +80,20 @@ class Exception: public Object { } protected: - Exception(const char *file, unsigned line); + Exception(const struct type *type, const char *file, unsigned line); /* Ref counter */ size_t m_ref; - /* file name */ - const char *m_file; /* line number */ unsigned m_line; + /* file name */ + char m_file[_POSIX_PATH_MAX]; /* error description */ char m_errmsg[TNT_ERRMSG_MAX]; }; +extern const struct type type_SystemError; class SystemError: public Exception { public: @@ -103,7 +113,7 @@ class SystemError: public Exception { SystemError(const char *file, unsigned line, const char *format, ...); protected: - SystemError(const char *file, unsigned line); + SystemError(const struct type *type, const char *file, unsigned line); void init(const char *format, ...); @@ -116,6 +126,7 @@ class SystemError: public Exception { int m_errno; }; +extern const struct type type_OutOfMemory; class OutOfMemory: public SystemError { public: OutOfMemory(const char *file, unsigned line, diff --git a/src/fiber.cc b/src/fiber.cc index 8bc0d87d02..188f49eaf6 100644 --- a/src/fiber.cc +++ b/src/fiber.cc @@ -36,12 +36,14 @@ #include "assoc.h" #include "memory.h" #include "trigger.h" -#include <typeinfo> static struct cord main_cord; __thread struct cord *cord_ptr = NULL; pthread_t main_thread_id; +const struct type type_FiberCancelException = + make_type("FiberCancelException", &type_Exception); + static void update_last_stack_frame(struct fiber *fiber) { @@ -223,7 +225,7 @@ fiber_join(struct fiber *fiber) /* Move exception to the caller */ diag_move(&fiber->diag, &fiber()->diag); Exception *e = diag_last_error(&fiber()->diag); - if (e != NULL && typeid(*e) != typeid(FiberCancelException)) + if (e != NULL && type_cast(FiberCancelException, e)) e->raise(); fiber_testcancel(); } @@ -705,9 +707,8 @@ cord_costart_thread_func(void *arg) } diag_move(&f->diag, &fiber()->diag); Exception *e = diag_last_error(&fiber()->diag); - if (e != NULL && typeid(*e) != typeid(FiberCancelException)) { + if (e != NULL && type_cast(FiberCancelException, e)) e->raise(); - } return NULL; } diff --git a/src/fiber.h b/src/fiber.h index 0b489c29d1..658fb0d264 100644 --- a/src/fiber.h +++ b/src/fiber.h @@ -85,10 +85,11 @@ enum { * cancelled. */ #if defined(__cplusplus) +extern const struct type type_FiberCancelException; class FiberCancelException: public Exception { public: FiberCancelException(const char *file, unsigned line) - : Exception(file, line) { + : Exception(&type_FiberCancelException, file, line) { /* Nothing */ } diff --git a/src/lua/utils.cc b/src/lua/utils.cc index fa62f3901b..c07c5a86ed 100644 --- a/src/lua/utils.cc +++ b/src/lua/utils.cc @@ -708,9 +708,10 @@ tarantool_lua_utils_init(struct lua_State *L) return 0; } +const struct type type_LuajitError = make_type("LuajitError", &type_Exception); LuajitError::LuajitError(const char *file, unsigned line, struct lua_State *L) - :Exception(file, line) + : Exception(&type_LuajitError, file, line) { const char *msg = lua_tostring(L, -1); snprintf(m_errmsg, sizeof(m_errmsg), "%s", msg ? msg : ""); diff --git a/src/lua/utils.h b/src/lua/utils.h index f1d346695e..2578193e80 100644 --- a/src/lua/utils.h +++ b/src/lua/utils.h @@ -434,6 +434,7 @@ tarantool_lua_utils_init(struct lua_State *L); } /* extern "C" */ #include "exception.h" +extern const struct type type_LuajitError; class LuajitError: public Exception { public: LuajitError(const char *file, unsigned line, diff --git a/src/reflection.c b/src/reflection.c new file mode 100644 index 0000000000..f1de38743c --- /dev/null +++ b/src/reflection.c @@ -0,0 +1,47 @@ +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "reflection.h" +/* TODO: sorry, unimplemented: non-trivial designated initializers */ + +const struct method METHODS_SENTINEL = { + .owner = NULL, + .name = NULL, + .rtype = CTYPE_VOID, + .atype = {}, + .nargs = 0, + .isconst = false, + ._spacer = {} +}; + +extern inline bool +type_assignable(const struct type *type, const struct type *object); + +extern inline const struct method * +type_method_by_name(const struct type *type, const char *name); diff --git a/src/reflection.h b/src/reflection.h new file mode 100644 index 0000000000..0b44b6e198 --- /dev/null +++ b/src/reflection.h @@ -0,0 +1,269 @@ +#ifndef TARANTOOL_REFLECTION_H_INCLUDED +#define TARANTOOL_REFLECTION_H_INCLUDED +/* + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * 1. Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials + * provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY <COPYRIGHT HOLDER> ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * <COPYRIGHT HOLDER> OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include <stddef.h> +#include <assert.h> +#include <stdbool.h> +#include <string.h> /* strcmp */ + +#if defined(__cplusplus) +extern "C" { +#endif /* defined(__cplusplus) */ + +struct type; +struct method; + +/** + * Primitive C types + */ +enum ctype { + CTYPE_VOID = 0, + CTYPE_INT, + CTYPE_CONST_CHAR_PTR +}; + +struct type { + const char *name; + const struct type *parent; + const struct method *methods; +}; + +inline bool +type_assignable(const struct type *type, const struct type *object) +{ + assert(object != NULL); + do { + if (object == type) + return true; + assert(object->parent != object); + object = object->parent; + } while (object != NULL); + return false; +} + +/** + * Determine if the specified object is assignment-compatible with + * the object represented by type. + */ +#define type_cast(T, obj) ({ \ + T *r = NULL; \ + if (type_assignable(&type_ ## T, (obj->type))) \ + r = (T *) obj; \ + (r); \ + }) + +#if defined(__cplusplus) +/* Pointer to arbitrary C++ member function */ +typedef void (type::*method_thiscall_f)(void); +#endif + +enum { METHOD_ARG_MAX = 8 }; + +struct method { + const struct type *owner; + const char *name; + enum ctype rtype; + enum ctype atype[METHOD_ARG_MAX]; + int nargs; + bool isconst; + + union { + /* Add extra space to get proper struct size in C */ + void *_spacer[2]; +#if defined(__cplusplus) + method_thiscall_f thiscall; + static_assert(sizeof(thiscall) <= sizeof(_spacer), + "sizeof(thiscall)"); +#endif /* defined(__cplusplus) */ + }; +}; + +#define type_foreach_method(m, method) \ + for(const struct type *_m = (m); _m != NULL; _m = _m->parent) \ + for (const struct method *(method) = _m->methods; \ + (method)->name != NULL; (method)++) + +inline const struct method * +type_method_by_name(const struct type *type, const char *name) +{ + type_foreach_method(type, method) { + if (strcmp(method->name, name) == 0) + return method; + } + return NULL; +} + +extern const struct method METHODS_SENTINEL; + +#if defined(__cplusplus) +} /* extern "C" */ + +/* + * Begin of C++ syntax sugar + */ + +/* + * Initializer for struct type without methods + */ +inline type +make_type(const char *name, const type *parent) +{ + /* TODO: sorry, unimplemented: non-trivial designated initializers */ + type t; + t.name = name; + t.parent = parent; + t.methods = &METHODS_SENTINEL; + return t; +} + +/* + * Initializer for struct type with methods + */ +inline struct type +make_type(const char *name, const type *parent, const method *methods) +{ + /* TODO: sorry, unimplemented: non-trivial designated initializers */ + type t; + t.name = name; + t.parent = parent; + t.methods = methods; + return t; +} + +template<typename T> inline enum ctype ctypeof(); +template<> inline enum ctype ctypeof<void>() { return CTYPE_VOID; } +template<> inline enum ctype ctypeof<int>() { return CTYPE_INT; } +template<> inline enum ctype ctypeof<const char *>() { return CTYPE_CONST_CHAR_PTR; } + +/** + * \cond false + */ + +/** A helper for recursive templates */ +template <int N, typename A, typename... Args> +struct method_helper { + static bool + invokable(const method *method) + { + if (method->atype[N] != ctypeof<A>()) + return false; + return method_helper<N + 1, Args... >::invokable(method); + } + + static void + init(struct method *method) + { + method->atype[N] = ctypeof<A>(); + return method_helper<N + 1, Args... >::init(method); + } +}; + +template <int N, typename R> +struct method_helper<N, R> { + static bool + invokable(const method *method) + { + if (method->nargs != N) + return false; + if (method->rtype != ctypeof<R>()) + return false; + return true; + } + + static void + init(struct method *method) + { + method->rtype = ctypeof<R>(); + method->nargs = N; + } +}; + +/** + * \endcond false + */ + +/** + * Initializer for R (T::*)(void) C++ member methods + */ +template<typename R, typename... Args, typename T> inline method +make_method(const struct type *owner, const char *name, + R (T::*method_arg)(Args...)) +{ + struct method m; + m.owner = owner; + m.name = name; + m.thiscall = (method_thiscall_f) method_arg; + m.isconst = false; + method_helper<0, Args..., R>::init(&m); + return m; +} + +template<typename R, typename... Args, typename T> inline method +make_method(const struct type *owner, const char *name, + R (T::*method_arg)(Args...) const) +{ + struct method m = make_method(owner, name, (R (T::*)(Args...)) method_arg); + m.isconst = true; + return m; +} + +/** + * Check if method is invokable with provided argument types + */ +template<typename R, typename... Args, typename T> inline bool +method_invokable(const struct method *method, T *object) +{ + if (!type_assignable(method->owner, object->type)) + return false; + return method_helper<0, Args..., R>::invokable(method); +} + +/** + * Invoke method with object and provided arguments. + */ +template<typename R, typename... Args, typename T > inline R +method_invoke(const struct method *method, T *object, Args... args) +{ + assert((method_invokable<R, Args...>(method, object))); + typedef R (T::*MemberFunction)(Args...); + return (object->*(MemberFunction) method->thiscall)(args...); +} + +template<typename R, typename... Args, typename T > inline R +method_invoke(const struct method *method, const T *object, Args... args) +{ + assert(method->isconst); + return method_invoke<R, Args...>(object, args...); +} + +#endif /* defined(__cplusplus) */ + +#endif /* TARANTOOL_REFLECTION_H_INCLUDED */ diff --git a/src/sio.cc b/src/sio.cc index 8c981f041c..5f9705bade 100644 --- a/src/sio.cc +++ b/src/sio.cc @@ -46,9 +46,11 @@ #include "say.h" #include "trivia/util.h" +const struct type type_SocketError = make_type("SocketError", + &type_SystemError); SocketError::SocketError(const char *file, unsigned line, int fd, const char *format, ...) - : SystemError(file, line) + : SystemError(&type_SocketError, file, line) { int save_errno = errno; diff --git a/src/sio.h b/src/sio.h index d527c38026..1bfcecdf21 100644 --- a/src/sio.h +++ b/src/sio.h @@ -43,6 +43,7 @@ enum { SERVICE_NAME_MAXLEN = 32 }; +extern const struct type type_SocketError; class SocketError: public SystemError { public: SocketError(const char *file, unsigned line, int fd, diff --git a/test/box/misc.result b/test/box/misc.result index b937e97b0d..df5fbd1597 100644 --- a/test/box/misc.result +++ b/test/box/misc.result @@ -94,9 +94,11 @@ box.error.raise() ... box.error.last() --- -- type: ClientError - message: Illegal parameters, bla bla +- line: -1 code: 1 + type: ClientError + message: Illegal parameters, bla bla + file: '[C]' ... box.error.clear() --- diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 485c94971a..104305c9d7 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -103,3 +103,8 @@ target_link_libraries(guava.test salad) add_executable(find_path.test find_path.c ${CMAKE_SOURCE_DIR}/src/find_path.c ) + +add_executable(reflection_c.test reflection_c.c unit.c + ${CMAKE_SOURCE_DIR}/src/reflection.c) +add_executable(reflection_cxx.test reflection_cxx.cc unit.c + ${CMAKE_SOURCE_DIR}/src/reflection.c) diff --git a/test/unit/reflection_c.c b/test/unit/reflection_c.c new file mode 100644 index 0000000000..e35258a683 --- /dev/null +++ b/test/unit/reflection_c.c @@ -0,0 +1,33 @@ +#include "reflection.h" + +#include "unit.h" + +static struct type type_Object = { + .parent = NULL, + .name = "Object", + .methods = NULL, +}; +static struct type type_Database = { + .parent = &type_Object, + .name = "Database", + .methods = NULL, +}; +static struct type type_Tarantool = { + .parent = &type_Database, + .name = "Tarantool", + .methods = NULL +}; + +int +main() +{ + plan(4); + + /* inheritance */ + ok(type_assignable(&type_Object, &type_Tarantool), "assignable"); + ok(type_assignable(&type_Database, &type_Tarantool), "assignable"); + ok(type_assignable(&type_Tarantool, &type_Tarantool), "assignable"); + ok(!type_assignable(&type_Tarantool, &type_Database), "assignable"); + + return check_plan(); +} diff --git a/test/unit/reflection_c.result b/test/unit/reflection_c.result new file mode 100644 index 0000000000..294fa97995 --- /dev/null +++ b/test/unit/reflection_c.result @@ -0,0 +1,5 @@ +1..4 +ok 1 - assignable +ok 2 - assignable +ok 3 - assignable +ok 4 - assignable diff --git a/test/unit/reflection_cxx.cc b/test/unit/reflection_cxx.cc new file mode 100644 index 0000000000..ee100a77dc --- /dev/null +++ b/test/unit/reflection_cxx.cc @@ -0,0 +1,174 @@ +#include "unit.h" + +#include <string.h> +#include "reflection.h" + +extern const struct type type_Object; +struct Object { + Object() + : type(&type_Object) + {} + + virtual ~Object() + {} + + const struct type *type; + Object(const struct type *type_arg) + : type(type_arg) + {} +}; +const struct type type_Object = make_type("Object", NULL); + +extern const struct type type_Database; +struct Database: public Object { + Database() + : Object(&type_Database) + {} + + virtual const char * + getString() const + { + return m_str; + } + + virtual void + putString(const char *str) + { + snprintf(m_str, sizeof(m_str), "%s", str); + } + + virtual int + getInt() const + { + return m_int; + } + + virtual void + putInt(int val) { + m_int = val; + } +protected: + Database(const struct type *type) + : Object(type) + {} + int m_int; + char m_str[128]; +}; +static const struct method database_methods[] = { + make_method(&type_Database, "getString", &Database::getString), + make_method(&type_Database, "getInt", &Database::getInt), + make_method(&type_Database, "putString", &Database::putString), + make_method(&type_Database, "putInt", &Database::putInt), + METHODS_SENTINEL +}; +const struct type type_Database = make_type("Database", &type_Object, + database_methods); + +extern const struct type type_Tarantool; +struct Tarantool: public Database { + Tarantool() + : Database(&type_Tarantool) + {} + + void inc() { + ++m_int; + } +}; +static const struct method tarantool_methods[] = { + make_method(&type_Tarantool, "inc", &Tarantool::inc), + METHODS_SENTINEL +}; +const struct type type_Tarantool = make_type("Tarantool", &type_Database, + tarantool_methods); + +int +main() +{ + plan(29); + + Object obj; + Tarantool tntobj; + const struct method *get_string = type_method_by_name(tntobj.type, + "getString"); + const struct method *put_string = type_method_by_name(tntobj.type, + "putString"); + const struct method *get_int = type_method_by_name(tntobj.type, + "getInt"); + const struct method *put_int = type_method_by_name(tntobj.type, + "putInt"); + const struct method *inc = type_method_by_name(tntobj.type, + "inc"); + + /* struct type members */ + ok(strcmp(type_Object.name, "Object") == 0, "type.name"); + is(type_Object.parent, NULL, "type.parent"); + is(type_Database.parent, &type_Object, "type.parent"); + + /* inheritance */ + ok(type_assignable(&type_Object, &type_Tarantool), "is_instance"); + ok(type_assignable(&type_Database, &type_Tarantool), "is_instance"); + ok(type_assignable(&type_Tarantool, &type_Tarantool), "is_instance"); + ok(!type_assignable(&type_Tarantool, &type_Database), "is_instance"); + + /* methods */ + const char *methods_order[] = { + "inc", + "getString", + "getInt", + "putString", + "putInt" + }; + int i = 0; + type_foreach_method(&type_Tarantool, method) { + ok(strcmp(method->name, methods_order[i]) == 0, "methods order"); + ++i; + } + + + /* + * struct method members + */ + is(get_string->owner, &type_Database, "method.owner"); + ok(strcmp(get_string->name, "getString") == 0, "method.name"); + is(get_string->rtype, CTYPE_CONST_CHAR_PTR, "method.rtype (non void)"); + is(put_string->rtype, CTYPE_VOID, "method.rtype (void)"); + is(get_string->nargs, 0, "method.nargs (zero)"); + is(put_string->nargs, 1, "method.nargs (non-zero)"); + is(put_string->atype[0], CTYPE_CONST_CHAR_PTR, "method.atype"); + is(get_string->isconst, true, "method.isconst"); + is(put_string->isconst, false, "!method.isconst"); + + /* + * Invokable + */ + ok(!method_invokable<int>(get_string, &tntobj), + "!invokable<invalid args>"); + ok(!(method_invokable<const char *, int> (get_string, &tntobj)), + "!invokable<extra args>"); + ok(!method_invokable<int>(get_string, &obj), + "!invokable<>(invalid object)"); + ok(method_invokable<const char *>(get_string, &tntobj), + "invokable<const char *>"); + ok((method_invokable<void, const char *>(put_string, &tntobj)), + "invokable<void, const char *>"); + + /* + * Invoke + */ + + /* int */ + method_invoke<void, int>(put_int, &tntobj, 48); + int iret = method_invoke<int>(get_int, &tntobj); + is(iret, 48, "invoke (int)"); + + /* const char */ + method_invoke<void, const char *>(put_string, &tntobj, "test string"); + const char *sret = method_invoke<const char *>(get_string, &tntobj); + ok(strcmp(sret, "test string") == 0, "invoke (const char *)"); + + method_invoke<void>(inc, &tntobj); + iret = method_invoke<int>(get_int, &tntobj); + is(iret, 49, "invoke (void)"); + + return check_plan(); +} diff --git a/test/unit/reflection_cxx.result b/test/unit/reflection_cxx.result new file mode 100644 index 0000000000..92a5459391 --- /dev/null +++ b/test/unit/reflection_cxx.result @@ -0,0 +1,30 @@ +1..29 +ok 1 - type.name +ok 2 - type.parent +ok 3 - type.parent +ok 4 - is_instance +ok 5 - is_instance +ok 6 - is_instance +ok 7 - is_instance +ok 8 - methods order +ok 9 - methods order +ok 10 - methods order +ok 11 - methods order +ok 12 - methods order +ok 13 - method.owner +ok 14 - method.name +ok 15 - method.rtype (non void) +ok 16 - method.rtype (void) +ok 17 - method.nargs (zero) +ok 18 - method.nargs (non-zero) +ok 19 - method.atype +ok 20 - method.isconst +ok 21 - !method.isconst +ok 22 - !invokable<invalid args> +ok 23 - !invokable<extra args> +ok 24 - !invokable<>(invalid object) +ok 25 - invokable<const char *> +ok 26 - invokable<void, const char *> +ok 27 - invoke (int) +ok 28 - invoke (const char *) +ok 29 - invoke (void) -- GitLab