Tarantool development patches archive
 help / color / mirror / Atom feed
From: Roman Khabibov via Tarantool-patches <tarantool-patches@dev.tarantool.org>
To: Sergey Bronnikov <sergeyb@tarantool.org>
Cc: tml <tarantool-patches@dev.tarantool.org>
Subject: Re: [Tarantool-patches] [PATCH] serializer: serialize recursive structures
Date: Fri, 16 Apr 2021 01:39:55 +0500	[thread overview]
Message-ID: <EF9EB8FF-D4F3-4815-80DD-65F5A32FCEDC@tarantool.org> (raw)
In-Reply-To: <9edf496a-3681-44f9-6e89-1d44bcc193b2@tarantool.org>

Hi! Thanks for the review.

> On Apr 13, 2021, at 20:54, Sergey Bronnikov <sergeyb@tarantool.org> wrote:
> 
> Hello!
> 
> thanks for the patch!
> 
> 1. I have reverted all changes and test passed.
I dropped this tests.

> 2. In commit message you declares recursive structures support.
> What is the max supported depth of recursive structure?
> I believe it is worth to describe possible limitations in commit message.
Hmm, I think this term ‘depth' (the number of nested tables) is not related
to this patch.
For example,
setmetatable({}, {__serialize = function(a) return {{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{{}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}} end})
This case works on master. The number of nested tables is defined by size of
allocated memory.

> See inline
> 
> On 11.03.2021 08:49, Roman Khabibov via Tarantool-patches wrote:
>> Fix bug with bus error during serializing of recursive
>> structures.
>> 
>> Closes #3228
>> ---
>> 
>> Branch: https://github.com/tarantool/tarantool/tree/romanhabibov/gh-3228-visited-set
>> Issue: https://github.com/tarantool/tarantool/issues/3228
>> 
>>  src/box/lua/call.c                            |   6 +-
>>  src/box/lua/execute.c                         |   2 +-
>>  src/box/lua/serialize_lua.c                   |  96 ++------
>>  src/box/lua/tuple.c                           |   2 +-
>>  src/box/sql/func.c                            |   2 +-
>>  src/lua/msgpack.c                             |  10 +-
>>  src/lua/pickle.c                              |   2 +-
>>  src/lua/utils.c                               | 226 ++++++++++++++++--
>>  src/lua/utils.h                               |  43 +++-
>>  ...-3228-serializer-look-for-recursion.result |  67 ++++++
>>  ...228-serializer-look-for-recursion.test.lua |  17 ++
>>  test/swim/swim.result                         |  18 +-
>>  third_party/lua-cjson/lua_cjson.c             |   4 +-
>>  third_party/lua-yaml/lyaml.cc                 |  88 +++----
>>  14 files changed, 390 insertions(+), 193 deletions(-)
>>  create mode 100644 test/app/gh-3228-serializer-look-for-recursion.result
>>  create mode 100644 test/app/gh-3228-serializer-look-for-recursion.test.lua
> 
> <snipped>
> 
>> diff --git a/test/app/gh-3228-serializer-look-for-recursion.test.lua b/test/app/gh-3228-serializer-look-for-recursion.test.lua
>> new file mode 100644
>> index 000000000..1c9b0375f
>> --- /dev/null
>> +++ b/test/app/gh-3228-serializer-look-for-recursion.test.lua
>> @@ -0,0 +1,17 @@
>> +test_run = require('test_run').new()
>> +
>> +--
>> +-- gh-3228: Check the error message in the case of a __serialize
>> +-- function generating infinite recursion.
>> +--
>> +setmetatable({}, {__serialize = function(a) return a end})
>> +setmetatable({}, {__serialize = function(a) return {a} end})
>> +setmetatable({}, {__serialize = function(a) return {a, a} end})
>> +setmetatable({}, {__serialize = function(a) return {a, a, a} end})
>> +setmetatable({}, {__serialize = function(a) return {{a, a}, a} end})
>> +setmetatable({}, {__serialize = function(a) return {a, 1} end})
>> +setmetatable({}, {__serialize = function(a) return {{{{a}}}} end})
>> +setmetatable({}, {__serialize = function(a) return {{{{1}}}} end})
> 
> 3. Are you sure 4 levels depth is enough to check recursive structure?
> 
> What is about testing max depth?
^^^
> 
>> +b = {}
>> +setmetatable({b}, {__serialize = function(a) return {a_1 = a, a_2 = a, b_1 = b, b_2 = b} end})
>> +setmetatable({b}, {__serialize = function(a) return {a_1 = a, a_2 = {a, b}, b = b} end})
>> diff --git a/test/swim/swim.result b/test/swim/swim.result
>> index bfc83c143..539131677 100644
>> --- a/test/swim/swim.result
>> +++ b/test/swim/swim.result
>> @@ -1322,16 +1322,13 @@ m_list
>>      incarnation: cdata {generation = 0ULL, version = 1ULL}
>>      uuid: 00000000-0000-1000-8000-000000000002
>>      payload_size: 0
>> -  - uri: 127.0.0.1:<port>
>> -    status: alive
>> -    incarnation: cdata {generation = 0ULL, version = 2ULL}
>> -    uuid: 00000000-0000-1000-8000-000000000001
>> -    payload_size: 8
>> -  - uri: 127.0.0.1:<port>
>> +  - &0
>> +    uri: 127.0.0.1:<port>
>>      status: alive
>>      incarnation: cdata {generation = 0ULL, version = 2ULL}
>>      uuid: 00000000-0000-1000-8000-000000000001
>>      payload_size: 8
>> +  - *0
>>  ...
>>  e_list
>>  ---
>> @@ -1374,16 +1371,13 @@ fiber.sleep(0)
>>  -- Two events - status update to 'left', and 'drop'.
>>  m_list
>>  ---
>> -- - uri: 127.0.0.1:<port>
>> -    status: left
>> -    incarnation: cdata {generation = 0ULL, version = 1ULL}
>> -    uuid: 00000000-0000-1000-8000-000000000002
>> -    payload_size: 0
>> -  - uri: 127.0.0.1:<port>
>> +- - &0
>> +    uri: 127.0.0.1:<port>
>>      status: left
>>      incarnation: cdata {generation = 0ULL, version = 1ULL}
>>      uuid: 00000000-0000-1000-8000-000000000002
>>      payload_size: 0
>> +  - *0
>>  ...
>>  e_list
>>  ---
> 
> 4. How changes in swim test related to patch?
> 
> It is not clear from commit message.
See new commit message.

> <snipped>

commit 7206b41ff1456e71474c2ff84b214f770e5897af
Author: Roman Khabibov <roman.habibov@tarantool.org>
Date:   Fri Feb 5 16:55:56 2021 +0300

    serializer: serialize recursive structures
    
    Fix bug with bus error during serializing of recursive structures
    and always print aliases if node become referenced before or
    after serialization.
    
    This patch adds map with serialized nodes and results. The map is
    used to mark referenced nodes which appears during serializing.
    
    Closes #3228
    
    @TarantoolBot
    __serialize parameter
    If __serialize parameter is initialized as a function then this
    function should be pure. A pure function is a function which
    returns identical values for identical arguments (no variation
    with local static variables, non-local variables, mutable
    reference arguments or input streams). Otherwise, the behavior
    is undefined.

diff --git a/changelogs/unreleased/serializer-bug.md b/changelogs/unreleased/serializer-bug.md
new file mode 100755
index 000000000..96f359d64
--- /dev/null
+++ b/changelogs/unreleased/serializer-bug.md
@@ -0,0 +1,4 @@
+## bugfix/core
+
+* Recursive structures appeared after __serialize call don't lead
+* to segfault anymore in lua and yaml serializers (gh-5392).
diff --git a/src/box/lua/call.c b/src/box/lua/call.c
index 0315e720c..e4907a3f7 100644
--- a/src/box/lua/call.c
+++ b/src/box/lua/call.c
@@ -193,7 +193,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg,
 		 */
 		for (int i = 1; i <= nrets; ++i) {
 			struct luaL_field field;
-			if (luaL_tofield(L, cfg, NULL, i, &field) < 0)
+			if (luaL_tofield(L, cfg, 0, NULL, i, &field) < 0)
 				return luaT_error(L);
 			struct tuple *tuple;
 			if (field.type == MP_EXT &&
@@ -222,7 +222,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg,
 	 * Inspect the first result
 	 */
 	struct luaL_field root;
-	if (luaL_tofield(L, cfg, NULL, 1, &root) < 0)
+	if (luaL_tofield(L, cfg, 0, NULL, 1, &root) < 0)
 		return luaT_error(L);
 	struct tuple *tuple;
 	if (root.type == MP_EXT && (tuple = luaT_istuple(L, 1)) != NULL) {
@@ -252,7 +252,7 @@ luamp_encode_call_16(lua_State *L, struct luaL_serializer *cfg,
 	for (uint32_t t = 1; t <= root.size; t++) {
 		lua_rawgeti(L, 1, t);
 		struct luaL_field field;
-		if (luaL_tofield(L, cfg, NULL, -1, &field) < 0)
+		if (luaL_tofield(L, cfg, 0, NULL, -1, &field) < 0)
 			return luaT_error(L);
 		if (field.type == MP_EXT && (tuple = luaT_istuple(L, -1))) {
 			tuple_to_mpstream(tuple, stream);
diff --git a/src/box/lua/execute.c b/src/box/lua/execute.c
index 926a0a61c..76d271f5c 100644
--- a/src/box/lua/execute.c
+++ b/src/box/lua/execute.c
@@ -328,7 +328,7 @@ lua_sql_bind_decode(struct lua_State *L, struct sql_bind *bind, int idx, int i)
 		bind->name = NULL;
 		bind->name_len = 0;
 	}
-	if (luaL_tofield(L, luaL_msgpack_default, NULL, -1, &field) < 0)
+	if (luaL_tofield(L, luaL_msgpack_default, 0, NULL, -1, &field) < 0)
 		return -1;
 	switch (field.type) {
 	case MP_UINT:
diff --git a/src/box/lua/serialize_lua.c b/src/box/lua/serialize_lua.c
index caa08a60f..17b434ba5 100644
--- a/src/box/lua/serialize_lua.c
+++ b/src/box/lua/serialize_lua.c
@@ -115,6 +115,7 @@ struct lua_dumper {
 	/** Anchors for self references */
 	int anchortable_index;
 	unsigned int anchor_number;
+	int serialized_objs_idx;
 
 	/** Error message buffer */
 	char err_msg[256];
@@ -215,7 +216,7 @@ trace_node(struct lua_dumper *d)
 	struct luaL_field field;
 
 	memset(&field, 0, sizeof(field));
-	luaL_checkfield(d->L, d->cfg, lua_gettop(d->L), &field);
+	luaL_checkfield(d->L, d->cfg, 0, lua_gettop(d->L), &field);
 
 	if (field.type < lengthof(mp_type_names)) {
 		if (field.type == MP_EXT) {
@@ -234,7 +235,7 @@ trace_node(struct lua_dumper *d)
 
 	memset(&field, 0, sizeof(field));
 
-	luaL_checkfield(d->L, d->cfg, top, &field);
+	luaL_checkfield(d->L, d->cfg, 0, top, &field);
 	say_info("serializer-trace: node    :\tfield type %s (%d)",
 		 type_str, field.type);
 }
@@ -339,41 +340,16 @@ emit_node(struct lua_dumper *d, struct node *nd, int indent,
 static const char *
 get_lua_anchor(struct lua_dumper *d)
 {
-	const char *s = "";
-
-	lua_pushvalue(d->L, -1);
-	lua_rawget(d->L, d->anchortable_index);
-	if (!lua_toboolean(d->L, -1)) {
-		lua_pop(d->L, 1);
-		return NULL;
+	const char *anchor = NULL;
+	int code = get_anchor(d->L, d->anchortable_index, &d->anchor_number,
+			      &anchor);
+	if (code == GET_ANCHOR_NAMED_NOT) {
+		trace_anchor(anchor, false);
+	} else if (code == GET_ANCHOR_ALIASED) {
+		trace_anchor(anchor, true);
+		return "";
 	}
-
-	if (lua_isboolean(d->L, -1)) {
-		/*
-		 * This element is referenced more
-		 * than once but has not been named.
-		 */
-		char buf[32];
-		snprintf(buf, sizeof(buf), "%u", d->anchor_number++);
-		lua_pop(d->L, 1);
-		lua_pushvalue(d->L, -1);
-		lua_pushstring(d->L, buf);
-		s = lua_tostring(d->L, -1);
-		lua_rawset(d->L, d->anchortable_index);
-		trace_anchor(s, false);
-	} else {
-		/*
-		 * An aliased element.
-		 *
-		 * FIXME: Need an example to use.
-		 *
-		 * const char *str = lua_tostring(d->L, -1);
-		 */
-		const char *str = lua_tostring(d->L, -1);
-		trace_anchor(str, true);
-		lua_pop(d->L, 1);
-	}
-	return s;
+	return anchor;
 }
 
 static void
@@ -783,7 +759,7 @@ dump_node(struct lua_dumper *d, struct node *nd, int indent)
 		return -1;
 
 	memset(field, 0, sizeof(*field));
-	luaL_checkfield(d->L, d->cfg, lua_gettop(d->L), field);
+	luaL_checkfield(d->L, d->cfg, 0, lua_gettop(d->L), field);
 
 	switch (field->type) {
 	case MP_NIL:
@@ -881,49 +857,6 @@ dump_node(struct lua_dumper *d, struct node *nd, int indent)
 	return emit_node(d, nd, indent, str, len);
 }
 
-/**
- * Find references to tables, we use it
- * to find self references in tables.
- */
-static void
-find_references(struct lua_dumper *d)
-{
-	int newval;
-
-	if (lua_type(d->L, -1) != LUA_TTABLE)
-		return;
-
-	/* Copy of a table for self refs */
-	lua_pushvalue(d->L, -1);
-	lua_rawget(d->L, d->anchortable_index);
-	if (lua_isnil(d->L, -1))
-		newval = 0;
-	else if (!lua_toboolean(d->L, -1))
-		newval = 1;
-	else
-		newval = -1;
-	lua_pop(d->L, 1);
-
-	if (newval != -1) {
-		lua_pushvalue(d->L, -1);
-		lua_pushboolean(d->L, newval);
-		lua_rawset(d->L, d->anchortable_index);
-	}
-
-	if (newval != 0)
-		return;
-
-	/*
-	 * Other values and keys in the table
-	 */
-	lua_pushnil(d->L);
-	while (lua_next(d->L, -2) != 0) {
-		find_references(d);
-		lua_pop(d->L, 1);
-		find_references(d);
-	}
-}
-
 /**
  * Dump recursively from the root node.
  */
@@ -935,7 +868,8 @@ dump_root(struct lua_dumper *d)
 	};
 	int ret;
 
-	luaL_checkfield(d->L, d->cfg, lua_gettop(d->L), &nd.field);
+	luaL_checkfield(d->L, d->cfg, d->serialized_objs_idx,
+			lua_gettop(d->L), &nd.field);
 
 	if (nd.field.type != MP_ARRAY || nd.field.size != 1) {
 		d->err = EINVAL;
@@ -982,9 +916,13 @@ lua_encode(lua_State *L, struct luaL_serializer *serializer,
 	dumper.anchortable_index = lua_gettop(L);
 	dumper.anchor_number = 0;
 
+	lua_newtable(L);
+	dumper.serialized_objs_idx = lua_gettop(L);
+
 	/* Push copy of arg we're processing */
 	lua_pushvalue(L, 1);
-	find_references(&dumper);
+	find_references_and_serialize(dumper.L, dumper.anchortable_index,
+				      dumper.serialized_objs_idx);
 
 	if (dump_root(&dumper) != 0)
 		goto out;
diff --git a/src/box/lua/tuple.c b/src/box/lua/tuple.c
index f7198a025..2779cf786 100644
--- a/src/box/lua/tuple.c
+++ b/src/box/lua/tuple.c
@@ -427,7 +427,7 @@ luamp_convert_key(struct lua_State *L, struct luaL_serializer *cfg,
 		return tuple_to_mpstream(tuple, stream);
 
 	struct luaL_field field;
-	if (luaL_tofield(L, cfg, NULL, index, &field) < 0)
+	if (luaL_tofield(L, cfg, 0, NULL, index, &field) < 0)
 		luaT_error(L);
 	if (field.type == MP_ARRAY) {
 		lua_pushvalue(L, index);
diff --git a/src/box/sql/func.c b/src/box/sql/func.c
index 9b6179f3a..2c7181f84 100644
--- a/src/box/sql/func.c
+++ b/src/box/sql/func.c
@@ -264,7 +264,7 @@ port_lua_get_vdbemem(struct port *base, uint32_t *size)
 		return NULL;
 	for (int i = 0; i < argc; i++) {
 		struct luaL_field field;
-		if (luaL_tofield(L, luaL_msgpack_default,
+		if (luaL_tofield(L, luaL_msgpack_default, 0,
 				 NULL, -1 - i, &field) < 0) {
 			goto error;
 		}
diff --git a/src/lua/msgpack.c b/src/lua/msgpack.c
index 1e74a6a3c..f9503a275 100644
--- a/src/lua/msgpack.c
+++ b/src/lua/msgpack.c
@@ -157,11 +157,11 @@ restart: /* used by MP_EXT of unidentified subtype */
 		lua_pushnil(L);  /* first key */
 		while (lua_next(L, top) != 0) {
 			lua_pushvalue(L, -2); /* push a copy of key to top */
-			if (luaL_tofield(L, cfg, opts, lua_gettop(L), field) < 0)
+			if (luaL_tofield(L, cfg, 0, opts, lua_gettop(L), field) < 0)
 				return luaT_error(L);
 			luamp_encode_r(L, cfg, opts, stream, field, level + 1);
 			lua_pop(L, 1); /* pop a copy of key */
-			if (luaL_tofield(L, cfg, opts, lua_gettop(L), field) < 0)
+			if (luaL_tofield(L, cfg, 0, opts, lua_gettop(L), field) < 0)
 				return luaT_error(L);
 			luamp_encode_r(L, cfg, opts, stream, field, level + 1);
 			lua_pop(L, 1); /* pop value */
@@ -182,7 +182,7 @@ restart: /* used by MP_EXT of unidentified subtype */
 		mpstream_encode_array(stream, size);
 		for (uint32_t i = 0; i < size; i++) {
 			lua_rawgeti(L, top, i + 1);
-			if (luaL_tofield(L, cfg, opts, top + 1, field) < 0)
+			if (luaL_tofield(L, cfg, 0, opts, top + 1, field) < 0)
 				return luaT_error(L);
 			luamp_encode_r(L, cfg, opts, stream, field, level + 1);
 			lua_pop(L, 1);
@@ -205,7 +205,7 @@ restart: /* used by MP_EXT of unidentified subtype */
 			if (type != MP_EXT)
 				return type; /* Value has been packed by the trigger */
 			/* Try to convert value to serializable type */
-			luaL_convertfield(L, cfg, top, field);
+			luaL_convertfield(L, cfg, 0, top, field);
 			/* handled by luaL_convertfield */
 			assert(field->type != MP_EXT);
 			assert(lua_gettop(L) == top);
@@ -230,7 +230,7 @@ luamp_encode(struct lua_State *L, struct luaL_serializer *cfg,
 	}
 
 	struct luaL_field field;
-	if (luaL_tofield(L, cfg, opts, lua_gettop(L), &field) < 0)
+	if (luaL_tofield(L, cfg, 0, opts, lua_gettop(L), &field) < 0)
 		return luaT_error(L);
 	enum mp_type top_type = luamp_encode_r(L, cfg, opts, stream, &field, 0);
 
diff --git a/src/lua/pickle.c b/src/lua/pickle.c
index 65208b5b3..86f0f77e8 100644
--- a/src/lua/pickle.c
+++ b/src/lua/pickle.c
@@ -80,7 +80,7 @@ lbox_pack(struct lua_State *L)
 		if (i > nargs)
 			luaL_error(L, "pickle.pack: argument count does not match "
 				   "the format");
-		luaL_checkfield(L, luaL_msgpack_default, i, &field);
+		luaL_checkfield(L, luaL_msgpack_default, 0, i, &field);
 		switch (*format) {
 		case 'B':
 		case 'b':
diff --git a/src/lua/utils.c b/src/lua/utils.c
index b5a6ca5b7..0580f4094 100644
--- a/src/lua/utils.c
+++ b/src/lua/utils.c
@@ -441,7 +441,8 @@ lua_gettable_wrapper(lua_State *L)
 
 static void
 lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg,
-			int idx, struct luaL_field *field)
+			 int serialized_objs_idx, int idx,
+			 struct luaL_field *field)
 {
 	if (!cfg->encode_load_metatables)
 		return;
@@ -463,59 +464,280 @@ lua_field_inspect_ucdata(struct lua_State *L, struct luaL_serializer *cfg,
 		lua_pcall(L, 1, 1, 0);
 		/* replace obj with the unpacked value */
 		lua_replace(L, idx);
-		if (luaL_tofield(L, cfg, NULL, idx, field) < 0)
+		if (luaL_tofield(L, cfg, serialized_objs_idx, NULL, idx,
+			         field) < 0)
 			luaT_error(L);
 	} /* else ignore lua_gettable exceptions */
 	lua_settop(L, top); /* remove temporary objects */
 }
 
+int
+get_anchor(struct lua_State *L, int anchortable_index,
+	   unsigned int *anchor_number, const char **anchor)
+{
+	lua_pushvalue(L, -1);
+	lua_rawget(L, anchortable_index);
+	if (lua_toboolean(L, -1) == 0) {
+		/* This element is not referenced. */
+		lua_pop(L, 1);
+		*anchor = NULL;
+		return 0;
+	}
+
+	if (lua_isboolean(L, -1) != 0) {
+		/*
+		 * This element is referenced more than once but
+		 * has not been named.
+		 */
+		char buf[32];
+		snprintf(buf, sizeof(buf), "%u", (*anchor_number)++);
+		lua_pop(L, 1);
+		/* Generate a string anchor and push to table. */
+		lua_pushvalue(L, -1);
+		lua_pushstring(L, buf);
+		*anchor = lua_tostring(L, -1);
+		lua_rawset(L, anchortable_index);
+		return GET_ANCHOR_NAMED_NOT;
+	} else {
+		/* This is an aliased element. */
+		*anchor = lua_tostring(L, -1);
+		lua_pop(L, 1);
+		return GET_ANCHOR_ALIASED;
+	}
+	return GET_ANCHOR_NO_REFS;
+}
+
+void
+find_references_and_serialize(struct lua_State *L, int anchortable_index,
+			      int serialized_objs_idx)
+{
+	int type = lua_type(L, -1);
+	if (type != LUA_TTABLE)
+		return;
+	int node_idx = lua_gettop(L);
+
+	/*
+	 * Check if the node is already serialized i.e. the record
+	 * is already in the map of serialized objects.
+	 */
+	lua_pushvalue(L, node_idx);
+	lua_rawget(L, serialized_objs_idx);
+	bool srlzd = lua_isnil(L, -1) == 0;
+	/*
+	 * This flag indicates that the object is being serialized
+	 * in the current function call.
+	 */
+	bool serialization = false;
+
+	if (!srlzd && luaL_getmetafield(L, node_idx, LUAL_SERIALIZE) != 0 &&
+	    lua_isfunction(L, -1) != 0) {
+		/* Delete nil after lua_rawget() above. */
+		lua_replace(L, -2);
+		srlzd = true;
+		serialization = true;
+		/*
+		 * Copy object itself and call serialize function
+		 * for it.
+		 */
+		lua_pushvalue(L, node_idx);
+		if (lua_pcall(L, 1, 1, 0) != 0) {
+			diag_set(LuajitError, lua_tostring(L, -1));
+			luaT_error(L);
+		}
+		/*
+		 * Add the result of serialization to the
+		 * serialized_objs map. Key is node (node_idx),
+		 * value is the result of serialization (now on
+		 * the top of stack).
+		 */
+		lua_pushvalue(L, node_idx);
+		lua_pushvalue(L, -2);
+		lua_rawset(L, serialized_objs_idx);
+	}
+
+	if (srlzd) {
+		if (lua_type(L, -1) == LUA_TTABLE) {
+			/*
+			 * Now we will process the new node - the
+			 * result of serialization. It is
+			 * necessary to take it into account in
+			 * the anchor table.
+			 */
+			node_idx = lua_gettop(L);
+		} else {
+			lua_pop(L, 1);
+			return;
+		}
+	} else {
+		if (lua_isnil(L, -1) == 0)
+			/*
+			 * Pop an extra field thrown out
+			 * by luaL_getmetafield(), it was
+			 * not a function.
+			 */
+			lua_pop(L, 1);
+		/* Delete nil after lua_rawget() above. */
+		lua_pop(L, 1);
+	}
+	/*
+	 * Take an entry from the anchor table about the current
+	 * node.
+	 */
+	lua_pushvalue(L, node_idx);
+	lua_rawget(L, anchortable_index);
+	int newval = -1;
+	if (lua_isnil(L, -1) == 1)
+		/*
+		 * The node has not yet been encountered, it is
+		 * bypassed for the first time.
+		 */
+		newval = 0;
+	else if (lua_toboolean(L, -1) == 0)
+		 /* The node has already met once. */
+		newval = 1;
+	lua_pop(L, 1);
+	if (newval != -1) {
+		lua_pushvalue(L, node_idx);
+		lua_pushboolean(L, newval);
+		lua_rawset(L, anchortable_index);
+	}
+	if (srlzd && !serialization) {
+		/*
+		 * The node has already been serialized not in the
+		 * current call, so there is no point in going
+		 * further in depth and checking the leaves. Pop
+		 * the processed sterilization result.
+		 */
+		lua_pop(L, 1);
+		return;
+	}
+	if (newval)
+		/* The node has already met twice or more, so
+		 * there is no point in going further in depth and
+		 * checking the leaves.
+		 */
+		return;
+
+	/* Recursively process other table values. */
+	lua_pushnil(L);
+	while (lua_next(L, node_idx) != 0) {
+		/* Find references on value. */
+		find_references_and_serialize(L, anchortable_index,
+					      serialized_objs_idx);
+		lua_pop(L, 1);
+		/* Find references on key. */
+		find_references_and_serialize(L, anchortable_index,
+					      serialized_objs_idx);
+	}
+	if (serialization)
+		/*
+		 * The loop above processed the nodes inside the
+		 * serialization result. Pop it, so as not to
+		 * break the integrity of the loop of the previous
+		 * call in recursion.
+		 */
+		lua_pop(L, 1);
+
+	return;
+}
+
+enum try_serialize_ret_code
+{
+
+	TRY_SERIALIZE_ERROR = -1,
+	TRY_SERIALIZE_RES_TABLE_NOT = 0,
+	TRY_SERIALIZE_RES_FROM_MAP,
+	TRY_SERIALIZE_DEFAULT_TABLE,
+};
+
 /**
  * Call __serialize method of a table object by index
- * if the former exists.
+ * if the former exists or pull result of serialization (if
+ * exists) from table with index @a serialized_objs_idx if it
+ * isn't 0.
  *
  * If __serialize does not exist then function does nothing
- * and the function returns 1;
+ * and the function returns TRY_SERIALIZE_DEFAULT_TABLE;
  *
  * If __serialize exists, is a function (which doesn't
  * raise any error) then a result of serialization
- * replaces old value by the index and the function returns 0;
+ * replaces old value by the index and the function returns
+ * TRY_SERIALIZE_RES_TABLE_NOT or TRY_SERIALIZE_DEFAULT_TABLE;
  *
  * If the serialization is a hint string (like 'array' or 'map'),
  * then field->type, field->size and field->compact
- * are set if necessary and the function returns 0;
+ * are set if necessary and the function returns
+ * TRY_SERIALIZE_RES_TABLE_NOT;
  *
- * Otherwise it is an error, set diag and the funciton returns -1;
+ * Otherwise it is an error, set diag and the funciton returns
+ * TRY_SERIALIZE_ERROR;
  *
  * Return values:
- * -1 - error occurs, diag is set, the top of guest stack
- *      is undefined.
- *  0 - __serialize field is available in the metatable,
- *      the result value is put in the origin slot,
- *      encoding is finished.
- *  1 - __serialize field is not available in the metatable,
- *      proceed with default table encoding.
+ * TRY_SERIALIZE_ERROR - error occurs, diag is set, the top of
+ * guest stack is undefined.
+ * TRY_SERIALIZE_RES_TABLE_NOT - __serialize field is available in
+ * the metatable, the result value is put in the origin slot,
+ * encoding is finished.
+ * TRY_SERIALIZE_RES_FROM_MAP - __serialize field is available in
+ * the metatable, the result value is table from serialized
+ * objects map.
+ * TRY_SERIALIZE_DEFAULT_TABLE - __serialize field is not
+ * available in the metatable, proceed with default table
+ * encoding.
  */
+
 static int
-lua_field_try_serialize(struct lua_State *L, struct luaL_serializer *cfg,
-			int idx, struct luaL_field *field)
+lua_field_try_serialize(struct lua_State *L, int serialized_objs_idx,
+			struct luaL_serializer *cfg, int idx,
+			struct luaL_field *field)
 {
 	if (luaL_getmetafield(L, idx, LUAL_SERIALIZE) == 0)
-		return 1;
+		return TRY_SERIALIZE_DEFAULT_TABLE;
 	if (lua_isfunction(L, -1)) {
-		/* copy object itself */
-		lua_pushvalue(L, idx);
-		if (lua_pcall(L, 1, 1, 0) != 0) {
-			diag_set(LuajitError, lua_tostring(L, -1));
-			return -1;
+		if (serialized_objs_idx != 0) {
+			/*
+			 * Pop the __serialize function for the
+			 * current node. It was already called in
+			 * find_references_and_serialize().
+			 */
+			lua_pop(L, 1);
+			/*
+			 * Get the result of serialization from
+			 * the map.
+			 */
+			lua_pushvalue(L, idx);
+			lua_rawget(L, serialized_objs_idx);
+			assert(lua_isnil(L, -1) == 0);
+
+			/*
+			 * Replace the serialized node with a new
+			 * result, if it is a table.
+			 */
+			if (lua_type(L, -1) == LUA_TTABLE) {
+				lua_replace(L, idx);
+				return TRY_SERIALIZE_RES_FROM_MAP;
+			}
+		} else {
+			/*
+			 * Serializer don't use map with
+			 * serialized objects. Copy object itself
+			 * and call __serialize for it.
+			 */
+			lua_pushvalue(L, idx);
+			if (lua_pcall(L, 1, 1, 0) != 0) {
+				diag_set(LuajitError, lua_tostring(L, -1));
+				return TRY_SERIALIZE_ERROR;
+			}
 		}
-		if (luaL_tofield(L, cfg, NULL, -1, field) != 0)
-			return -1;
+		if (luaL_tofield(L, cfg, serialized_objs_idx, NULL, -1,
+				 field) != 0)
+			return TRY_SERIALIZE_ERROR;
 		lua_replace(L, idx);
-		return 0;
+		return TRY_SERIALIZE_RES_TABLE_NOT;
 	}
 	if (!lua_isstring(L, -1)) {
 		diag_set(LuajitError, "invalid " LUAL_SERIALIZE " value");
-		return -1;
+		return TRY_SERIALIZE_ERROR;
 	}
 	const char *type = lua_tostring(L, -1);
 	if (strcmp(type, "array") == 0 || strcmp(type, "seq") == 0 ||
@@ -533,16 +755,17 @@ lua_field_try_serialize(struct lua_State *L, struct luaL_serializer *cfg,
 			field->compact = true;
 	} else {
 		diag_set(LuajitError, "invalid " LUAL_SERIALIZE " value");
-		return -1;
+		return TRY_SERIALIZE_ERROR;
 	}
 	/* Remove value set by luaL_getmetafield. */
 	lua_pop(L, 1);
-	return 0;
+	return TRY_SERIALIZE_RES_TABLE_NOT;
 }
 
 static int
-lua_field_inspect_table(struct lua_State *L, struct luaL_serializer *cfg,
-			int idx, struct luaL_field *field)
+lua_field_inspect_table(struct lua_State *L, int serialized_objs_idx,
+			struct luaL_serializer *cfg, int idx,
+			struct luaL_field *field)
 {
 	assert(lua_type(L, idx) == LUA_TTABLE);
 	uint32_t size = 0;
@@ -550,14 +773,20 @@ lua_field_inspect_table(struct lua_State *L, struct luaL_serializer *cfg,
 
 	if (cfg->encode_load_metatables) {
 		int top = lua_gettop(L);
-		int res = lua_field_try_serialize(L, cfg, idx, field);
-		if (res == -1)
+		int res = lua_field_try_serialize(L, serialized_objs_idx, cfg,
+						  idx, field);
+		if (res == TRY_SERIALIZE_ERROR)
 			return -1;
 		assert(lua_gettop(L) == top);
 		(void)top;
-		if (res == 0)
+		if (res == TRY_SERIALIZE_RES_TABLE_NOT ||
+		    (res == TRY_SERIALIZE_RES_FROM_MAP &&
+		    lua_type(L, -1) != LUA_TTABLE))
 			return 0;
-		/* Fallthrough with res == 1 */
+		/*
+		 * Fallthrough with res TRY_SERIALIZE_RES_FROM_MAP
+		 * or TRY_SERIALIZE_DEFAULT_TABLE.
+		 */
 	}
 
 	field->type = MP_ARRAY;
@@ -612,14 +841,14 @@ lua_field_tostring(struct lua_State *L, struct luaL_serializer *cfg, int idx,
 	lua_call(L, 1, 1);
 	lua_replace(L, idx);
 	lua_settop(L, top);
-	if (luaL_tofield(L, cfg, NULL, idx, field) < 0)
+	if (luaL_tofield(L, cfg, 0, NULL, idx, field) < 0)
 		luaT_error(L);
 }
 
 int
 luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
-	     const struct serializer_opts *opts, int index,
-	     struct luaL_field *field)
+	     int serialized_objs_idx, const struct serializer_opts *opts,
+	     int index, struct luaL_field *field)
 {
 	if (index < 0)
 		index = lua_gettop(L) + index + 1;
@@ -753,7 +982,8 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 	case LUA_TTABLE:
 	{
 		field->compact = false;
-		return lua_field_inspect_table(L, cfg, index, field);
+		return lua_field_inspect_table(L, serialized_objs_idx, cfg,
+					       index, field);
 	}
 	case LUA_TLIGHTUSERDATA:
 	case LUA_TUSERDATA:
@@ -773,8 +1003,8 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
 }
 
 void
-luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
-		  struct luaL_field *field)
+luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg,
+		  int serialized_objs_idx, int idx, struct luaL_field *field)
 {
 	if (idx < 0)
 		idx = lua_gettop(L) + idx + 1;
@@ -789,9 +1019,12 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
 			 */
 			GCcdata *cd = cdataV(L->base + idx - 1);
 			if (cd->ctypeid > CTID_CTYPEID)
-				lua_field_inspect_ucdata(L, cfg, idx, field);
+				lua_field_inspect_ucdata(L, cfg,
+							 serialized_objs_idx, idx,
+							 field);
 		} else if (type == LUA_TUSERDATA) {
-			lua_field_inspect_ucdata(L, cfg, idx, field);
+			lua_field_inspect_ucdata(L, cfg, serialized_objs_idx, idx,
+						 field);
 		}
 	}
 
diff --git a/src/lua/utils.h b/src/lua/utils.h
index 4a164868b..cb1025495 100644
--- a/src/lua/utils.h
+++ b/src/lua/utils.h
@@ -353,6 +353,44 @@ struct luaL_field {
 	bool compact;                /* a flag used by YAML serializer */
 };
 
+/**
+ * Find references to tables to look up self references in tables
+ * and serialize nodes if __serialize is function. Collect
+ * referenced nodes in a table with stack index @a
+ * anchortable_index and serialized nodes with results in a table
+ * with stack index @a serialized_objs_idx.
+ *
+ * @param L Lua stack.
+ * @param anchortable_index Index of anchor table on stack with
+          pairs: "node address" <-> "false (met once) or true
+          (met more than once)".
+ * @param serialized_objs_idx Index of serialized objects map on
+ *        stack with pairs: "node address" <-> "result address".
+ */
+void
+find_references_and_serialize(struct lua_State *L, int anchortable_index,
+			      int serialized_objs_idx);
+
+enum get_anchor_ret_code
+{
+	GET_ANCHOR_NO_REFS = 0,
+	GET_ANCHOR_NAMED_NOT,
+	GET_ANCHOR_ALIASED,
+};
+
+/**
+ * Generate aliases and anchor numbers for self references.
+ *
+ * @param L Lua stack.
+ * @param anchortable_index Index of anchor table on stack.
+ * @param[out] anchor_number Ptr to the number anchors in a
+               structure being under dumping.
+ * @param[out] anchor Ptr to anchor string.
+ */
+int
+get_anchor(struct lua_State *L, int anchortable_index,
+	   unsigned int *anchor_number, const char **anchor);
+
 /**
  * @brief Convert a value from the Lua stack to a lua_field structure.
  * This function is designed for use with Lua bindings and data
@@ -387,6 +425,8 @@ struct luaL_field {
  *
  * @param L stack
  * @param cfg configuration
+ * @param serialized_objs_idx Index of table with serialized
+                              objects map.
  * @param opts the Lua serializer additional options.
  * @param index stack index
  * @param field conversion result
@@ -396,8 +436,8 @@ struct luaL_field {
  */
 int
 luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
-	     const struct serializer_opts *opts, int index,
-	     struct luaL_field *field);
+	     int serialized_objs_idx, const struct serializer_opts *opts,
+	     int index, struct luaL_field *field);
 
 /**
  * @brief Try to convert userdata/cdata values using defined conversion logic.
@@ -405,18 +445,22 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg,
  *
  * @param L stack
  * @param cfg configuration
+ * @param serialized_objs_idx Index of table with serialized
+                              objects map.
  * @param idx stack index
  * @param field conversion result
  */
 void
-luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
-		  struct luaL_field *field);
+luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg,
+		  int serialized_objs_idx, int idx, struct luaL_field *field);
 
 /**
  * @brief A wrapper for luaL_tofield() and luaL_convertfield() that
  * tries to convert value or raise an error.
  * @param L stack
  * @param cfg configuration
+ * @param serialized_objs_idx Stack index of table with serialized
+                              objects map.
  * @param idx stack index
  * @param field conversion result
  * @sa lua_tofield()
@@ -432,14 +476,14 @@ luaL_convertfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
  * (tostring) -> (nil) -> exception
  */
 static inline void
-luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg, int idx,
-		struct luaL_field *field)
+luaL_checkfield(struct lua_State *L, struct luaL_serializer *cfg,
+		int serialized_objs_idx, int idx, struct luaL_field *field)
 {
-	if (luaL_tofield(L, cfg, NULL, idx, field) < 0)
+	if (luaL_tofield(L, cfg, serialized_objs_idx, NULL, idx, field) < 0)
 		luaT_error(L);
 	if (field->type != MP_EXT || field->ext_type != MP_UNKNOWN_EXTENSION)
 		return;
-	luaL_convertfield(L, cfg, idx, field);
+	luaL_convertfield(L, cfg, serialized_objs_idx, idx, field);
 }
 
 void
diff --git a/test/app/gh-3228-serializer-look-for-recursion.result b/test/app/gh-3228-serializer-look-for-recursion.result
new file mode 100644
index 000000000..c284fec6e
--- /dev/null
+++ b/test/app/gh-3228-serializer-look-for-recursion.result
@@ -0,0 +1,164 @@
+-- test-run result file version 2
+test_run = require('test_run').new()
+ | ---
+ | ...
+
+--
+-- gh-3228: Check that recursive structures are serialized
+-- properly.
+--
+setmetatable({}, {__serialize = function(a) return a end})
+ | ---
+ | - []
+ | ...
+setmetatable({}, {__serialize = function(a) return {a} end})
+ | ---
+ | - &0
+ |   - *0
+ | ...
+setmetatable({}, {__serialize = function(a) return {a, a} end})
+ | ---
+ | - &0
+ |   - *0
+ |   - *0
+ | ...
+setmetatable({}, {__serialize = function(a) return {a, a, a} end})
+ | ---
+ | - &0
+ |   - *0
+ |   - *0
+ |   - *0
+ | ...
+setmetatable({}, {__serialize = function(a) return {{a, a}, a} end})
+ | ---
+ | - &0
+ |   - - *0
+ |     - *0
+ |   - *0
+ | ...
+setmetatable({}, {__serialize = function(a) return {a, 1} end})
+ | ---
+ | - &0
+ |   - *0
+ |   - 1
+ | ...
+setmetatable({}, {__serialize = function(a) return {{{{a}}}} end})
+ | ---
+ | - &0
+ |   - - - - *0
+ | ...
+
+b = {}
+ | ---
+ | ...
+setmetatable({b}, {__serialize = function(a) return {a_1 = a, a_2 = a, b_1 = b, b_2 = b} end})
+ | ---
+ | - &0
+ |   b_2: &1 []
+ |   a_2: *0
+ |   a_1: *0
+ |   b_1: *1
+ | ...
+setmetatable({b}, {__serialize = function(a) return {a_1 = a, a_2 = {a, b}, b = b} end})
+ | ---
+ | - &0
+ |   b: &1 []
+ |   a_2:
+ |   - *0
+ |   - *1
+ |   a_1: *0
+ | ...
+
+a = {}
+ | ---
+ | ...
+a[a] = a
+ | ---
+ | ...
+recf = function(t) return setmetatable({}, {__serialize = recf}) end
+ | ---
+ | ...
+setmetatable(a, {__serialize = recf}) return a
+ | ---
+ | - []
+ | ...
+
+--
+-- __serialize function is pure, i.e. always returns identical
+-- value for identical argument. Otherwise, the behavior is
+-- undefined. So that, we ignore the side effects and just use the
+-- value after the first serialization.
+--
+a = {}
+ | ---
+ | ...
+b = {}
+ | ---
+ | ...
+b[a] = a
+ | ---
+ | ...
+show_a = true
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+serialize = function()
+    show_a = not show_a
+    if show_a then
+        return "a"
+    else
+        return "b" end
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+setmetatable(a, {__serialize = serialize})
+ | ---
+ | - b
+ | ...
+b
+ | ---
+ | - a: a
+ | ...
+
+test_run:cmd('setopt delimiter ";"')
+ | ---
+ | - true
+ | ...
+function example()
+    local a = {}
+    local b = {}
+    b[a] = a
+    local reta
+    local retb
+    local function swap_ser(o)
+        local newf
+        local f = getmetatable(o).__serialize
+        if f == reta then
+            newf = retb
+        else
+            newf = reta
+        end
+        getmetatable(o).__serialize = newf
+    end
+    reta = function(o) swap_ser(o) return "a" end
+    retb = function(o) swap_ser(o) return "b" end
+    setmetatable(a, {__serialize = reta})
+    return b
+end;
+ | ---
+ | ...
+test_run:cmd('setopt delimiter ""');
+ | ---
+ | - true
+ | ...
+example()
+ | ---
+ | - a: a
+ | ...
diff --git a/test/app/gh-3228-serializer-look-for-recursion.test.lua b/test/app/gh-3228-serializer-look-for-recursion.test.lua
new file mode 100644
index 000000000..e1dbd9f90
--- /dev/null
+++ b/test/app/gh-3228-serializer-look-for-recursion.test.lua
@@ -0,0 +1,69 @@
+test_run = require('test_run').new()
+
+--
+-- gh-3228: Check that recursive structures are serialized
+-- properly.
+--
+setmetatable({}, {__serialize = function(a) return a end})
+setmetatable({}, {__serialize = function(a) return {a} end})
+setmetatable({}, {__serialize = function(a) return {a, a} end})
+setmetatable({}, {__serialize = function(a) return {a, a, a} end})
+setmetatable({}, {__serialize = function(a) return {{a, a}, a} end})
+setmetatable({}, {__serialize = function(a) return {a, 1} end})
+setmetatable({}, {__serialize = function(a) return {{{{a}}}} end})
+
+b = {}
+setmetatable({b}, {__serialize = function(a) return {a_1 = a, a_2 = a, b_1 = b, b_2 = b} end})
+setmetatable({b}, {__serialize = function(a) return {a_1 = a, a_2 = {a, b}, b = b} end})
+
+a = {}
+a[a] = a
+recf = function(t) return setmetatable({}, {__serialize = recf}) end
+setmetatable(a, {__serialize = recf}) return a
+
+--
+-- __serialize function is pure, i.e. always returns identical
+-- value for identical argument. Otherwise, the behavior is
+-- undefined. So that, we ignore the side effects and just use the
+-- value after the first serialization.
+--
+a = {}
+b = {}
+b[a] = a
+show_a = true
+test_run:cmd('setopt delimiter ";"')
+serialize = function()
+    show_a = not show_a
+    if show_a then
+        return "a"
+    else
+        return "b" end
+end;
+test_run:cmd('setopt delimiter ""');
+setmetatable(a, {__serialize = serialize})
+b
+
+test_run:cmd('setopt delimiter ";"')
+function example()
+    local a = {}
+    local b = {}
+    b[a] = a
+    local reta
+    local retb
+    local function swap_ser(o)
+        local newf
+        local f = getmetatable(o).__serialize
+        if f == reta then
+            newf = retb
+        else
+            newf = reta
+        end
+        getmetatable(o).__serialize = newf
+    end
+    reta = function(o) swap_ser(o) return "a" end
+    retb = function(o) swap_ser(o) return "b" end
+    setmetatable(a, {__serialize = reta})
+    return b
+end;
+test_run:cmd('setopt delimiter ""');
+example()
diff --git a/test/swim/swim.result b/test/swim/swim.result
index bfc83c143..539131677 100644
--- a/test/swim/swim.result
+++ b/test/swim/swim.result
@@ -1322,16 +1322,13 @@ m_list
     incarnation: cdata {generation = 0ULL, version = 1ULL}
     uuid: 00000000-0000-1000-8000-000000000002
     payload_size: 0
-  - uri: 127.0.0.1:<port>
-    status: alive
-    incarnation: cdata {generation = 0ULL, version = 2ULL}
-    uuid: 00000000-0000-1000-8000-000000000001
-    payload_size: 8
-  - uri: 127.0.0.1:<port>
+  - &0
+    uri: 127.0.0.1:<port>
     status: alive
     incarnation: cdata {generation = 0ULL, version = 2ULL}
     uuid: 00000000-0000-1000-8000-000000000001
     payload_size: 8
+  - *0
 ...
 e_list
 ---
@@ -1374,16 +1371,13 @@ fiber.sleep(0)
 -- Two events - status update to 'left', and 'drop'.
 m_list
 ---
-- - uri: 127.0.0.1:<port>
-    status: left
-    incarnation: cdata {generation = 0ULL, version = 1ULL}
-    uuid: 00000000-0000-1000-8000-000000000002
-    payload_size: 0
-  - uri: 127.0.0.1:<port>
+- - &0
+    uri: 127.0.0.1:<port>
     status: left
     incarnation: cdata {generation = 0ULL, version = 1ULL}
     uuid: 00000000-0000-1000-8000-000000000002
     payload_size: 0
+  - *0
 ...
 e_list
 ---
diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c
index 38e999870..67a9762bc 100644
--- a/third_party/lua-cjson/lua_cjson.c
+++ b/third_party/lua-cjson/lua_cjson.c
@@ -354,7 +354,7 @@ static void json_append_object(lua_State *l, struct luaL_serializer *cfg,
             comma = 1;
 
     struct luaL_field field;
-    luaL_checkfield(l, cfg, -2, &field);
+    luaL_checkfield(l, cfg, 0, -2, &field);
     if (field.type == MP_UINT) {
         strbuf_append_char(json, '"');
         json_append_uint(cfg, json, field.ival);
@@ -384,7 +384,7 @@ static void json_append_data(lua_State *l, struct luaL_serializer *cfg,
                              int current_depth, strbuf_t *json)
 {
     struct luaL_field field;
-    luaL_checkfield(l, cfg, -1, &field);
+    luaL_checkfield(l, cfg, 0, -1, &field);
     switch (field.type) {
     case MP_UINT:
         return json_append_uint(cfg, json, field.ival);
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 9c3a4a646..249603ba3 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -79,6 +79,7 @@ struct lua_yaml_dumper {
    lua_State *L;
    struct luaL_serializer *cfg;
    int anchortable_index;
+   int serialized_objs_idx;
    unsigned int anchor_number;
    yaml_emitter_t emitter;
    char error;
@@ -500,33 +501,16 @@ usage_error:
 static int dump_node(struct lua_yaml_dumper *dumper);
 
 static yaml_char_t *get_yaml_anchor(struct lua_yaml_dumper *dumper) {
-   const char *s = "";
-   lua_pushvalue(dumper->L, -1);
-   lua_rawget(dumper->L, dumper->anchortable_index);
-   if (!lua_toboolean(dumper->L, -1)) {
-      lua_pop(dumper->L, 1);
-      return NULL;
-   }
-
-   if (lua_isboolean(dumper->L, -1)) {
-      /* this element is referenced more than once but has not been named */
-      char buf[32];
-      snprintf(buf, sizeof(buf), "%u", dumper->anchor_number++);
-      lua_pop(dumper->L, 1);
-      lua_pushvalue(dumper->L, -1);
-      lua_pushstring(dumper->L, buf);
-      s = lua_tostring(dumper->L, -1);
-      lua_rawset(dumper->L, dumper->anchortable_index);
-   } else {
-      /* this is an aliased element */
+   const char *anchor = NULL;
+   if (get_anchor(dumper->L, dumper->anchortable_index, &dumper->anchor_number,
+                  &anchor) == GET_ANCHOR_ALIASED) {
       yaml_event_t ev;
-      const char *str = lua_tostring(dumper->L, -1);
-      if (!yaml_alias_event_initialize(&ev, (yaml_char_t *) str) ||
+      if (!yaml_alias_event_initialize(&ev, (yaml_char_t *) anchor) ||
           !yaml_emitter_emit(&dumper->emitter, &ev))
          luaL_error(dumper->L, OOM_ERRMSG);
-      lua_pop(dumper->L, 1);
+      return (yaml_char_t *)"";
    }
-   return (yaml_char_t *)s;
+   return (yaml_char_t *)anchor;
 }
 
 static int dump_table(struct lua_yaml_dumper *dumper, struct luaL_field *field){
@@ -622,7 +606,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
    (void) unused;
 
    int top = lua_gettop(dumper->L);
-   luaL_checkfield(dumper->L, dumper->cfg, top, &field);
+   luaL_checkfield(dumper->L, dumper->cfg, dumper->serialized_objs_idx,
+                   top, &field);
    switch(field.type) {
    case MP_UINT:
       snprintf(buf, sizeof(buf) - 1, "%" PRIu64, field.ival);
@@ -745,35 +730,6 @@ static int append_output(void *arg, unsigned char *buf, size_t len) {
    return 1;
 }
 
-static void find_references(struct lua_yaml_dumper *dumper) {
-   int newval = -1, type = lua_type(dumper->L, -1);
-   if (type != LUA_TTABLE)
-      return;
-
-   lua_pushvalue(dumper->L, -1); /* push copy of table */
-   lua_rawget(dumper->L, dumper->anchortable_index);
-   if (lua_isnil(dumper->L, -1))
-      newval = 0;
-   else if (!lua_toboolean(dumper->L, -1))
-      newval = 1;
-   lua_pop(dumper->L, 1);
-   if (newval != -1) {
-      lua_pushvalue(dumper->L, -1);
-      lua_pushboolean(dumper->L, newval);
-      lua_rawset(dumper->L, dumper->anchortable_index);
-   }
-   if (newval)
-      return;
-
-   /* recursively process other table values */
-   lua_pushnil(dumper->L);
-   while (lua_next(dumper->L, -2) != 0) {
-      find_references(dumper); /* find references on value */
-      lua_pop(dumper->L, 1);
-      find_references(dumper); /* find references on key */
-   }
-}
-
 int
 lua_yaml_encode(lua_State *L, struct luaL_serializer *serializer,
                 const char *tag_handle, const char *tag_prefix)
@@ -817,9 +773,12 @@ lua_yaml_encode(lua_State *L, struct luaL_serializer *serializer,
 
    lua_newtable(L);
    dumper.anchortable_index = lua_gettop(L);
+   lua_newtable(L);
+   dumper.serialized_objs_idx = lua_gettop(L);
    dumper.anchor_number = 0;
    lua_pushvalue(L, 1); /* push copy of arg we're processing */
-   find_references(&dumper);
+   find_references_and_serialize(dumper.L, dumper.anchortable_index,
+                                 dumper.serialized_objs_idx);
    dump_document(&dumper);
    if (dumper.error)
       goto error;


  reply	other threads:[~2021-04-15 20:40 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-03-11  5:49 Roman Khabibov via Tarantool-patches
2021-03-14 12:42 ` Sergey Kaplun via Tarantool-patches
2021-04-05 22:05   ` Roman Khabibov via Tarantool-patches
2021-04-21  9:27     ` Sergey Kaplun via Tarantool-patches
2021-04-21 13:12       ` Sergey Bronnikov via Tarantool-patches
2021-04-21 18:20         ` Sergey Kaplun via Tarantool-patches
2021-04-13 15:54 ` Sergey Bronnikov via Tarantool-patches
2021-04-15 20:39   ` Roman Khabibov via Tarantool-patches [this message]
2021-04-21 12:34 ` Sergey Bronnikov via Tarantool-patches
2021-07-07 22:42 ` Alexander Turenko via Tarantool-patches

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=EF9EB8FF-D4F3-4815-80DD-65F5A32FCEDC@tarantool.org \
    --to=tarantool-patches@dev.tarantool.org \
    --cc=roman.habibov@tarantool.org \
    --cc=sergeyb@tarantool.org \
    --subject='Re: [Tarantool-patches] [PATCH] serializer: serialize recursive structures' \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox