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