From b42302f52ada1db87a28905310818eb32d92e1f1 Mon Sep 17 00:00:00 2001
From: Vladimir Davydov <vdavydov@tarantool.org>
Date: Thu, 2 Feb 2023 15:21:56 +0300
Subject: [PATCH] lua-yaml: enable aliasing for objects returned by __serialize

The YAML serializer fails to detect aliases in objects returned by
the __serialize method:

tarantool> x = {}
---
...

tarantool> {a = x, b = x}
---
- a: &0 []
  b: *0
...

tarantool> setmetatable({}, {
         >     __serialize = function() return {a = x, b = x} end,
         > })
---
- a: []
  b: []
...

Fix this by scanning the object returned by the __serialize method
(called by luaL_checkfield) for references.

Closes #8240

NO_DOC=bug fix
---
 .../unreleased/gh-8240-yaml-alias-serialize-fix.md |  4 ++++
 src/lua/serializer.c                               |  9 ++++++---
 src/lua/serializer.h                               |  2 ++
 .../gh_8240_yaml_alias_serialize_test.lua          | 14 ++++++++++++++
 third_party/lua-yaml/lyaml.cc                      |  4 ++++
 5 files changed, 30 insertions(+), 3 deletions(-)
 create mode 100644 changelogs/unreleased/gh-8240-yaml-alias-serialize-fix.md

diff --git a/changelogs/unreleased/gh-8240-yaml-alias-serialize-fix.md b/changelogs/unreleased/gh-8240-yaml-alias-serialize-fix.md
new file mode 100644
index 0000000000..dc99402e77
--- /dev/null
+++ b/changelogs/unreleased/gh-8240-yaml-alias-serialize-fix.md
@@ -0,0 +1,4 @@
+## bugfix/lua
+
+* Fixed alias detection in the YAML serializer in case the input contains
+  objects that implement the `__serialize` meta method (gh-8240).
diff --git a/src/lua/serializer.c b/src/lua/serializer.c
index c2e0229c74..231a973a3c 100644
--- a/src/lua/serializer.c
+++ b/src/lua/serializer.c
@@ -324,6 +324,7 @@ lua_field_try_serialize(struct lua_State *L, struct luaL_serializer *cfg,
 		if (luaL_tofield(L, cfg, -1, field) != 0)
 			return -1;
 		lua_replace(L, idx);
+		field->serialized = true;
 		return 0;
 	}
 	if (!lua_isstring(L, -1)) {
@@ -433,6 +434,11 @@ int
 luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 	     struct luaL_field *field)
 {
+	field->type = MP_NIL;
+	field->ext_type = MP_UNKNOWN_EXTENSION;
+	field->compact = false;
+	field->serialized = false;
+
 	if (index < 0)
 		index = lua_gettop(L) + index + 1;
 
@@ -568,10 +574,7 @@ luaL_tofield(struct lua_State *L, struct luaL_serializer *cfg, int index,
 		field->type = MP_STR;
 		return 0;
 	case LUA_TTABLE:
-	{
-		field->compact = false;
 		return lua_field_inspect_table(L, cfg, index, field);
-	}
 	case LUA_TLIGHTUSERDATA:
 	case LUA_TUSERDATA:
 		field->sval.data = NULL;
diff --git a/src/lua/serializer.h b/src/lua/serializer.h
index aa143153a9..2e73aecc01 100644
--- a/src/lua/serializer.h
+++ b/src/lua/serializer.h
@@ -241,6 +241,8 @@ struct luaL_field {
 	/* subtypes of MP_EXT */
 	enum mp_extension_type ext_type;
 	bool compact;                /* a flag used by YAML serializer */
+	/** Set if __serialize metamethod was called for this field. */
+	bool serialized;
 };
 
 /**
diff --git a/test/app-luatest/gh_8240_yaml_alias_serialize_test.lua b/test/app-luatest/gh_8240_yaml_alias_serialize_test.lua
index 274254e4b8..e08e4bf74a 100644
--- a/test/app-luatest/gh_8240_yaml_alias_serialize_test.lua
+++ b/test/app-luatest/gh_8240_yaml_alias_serialize_test.lua
@@ -27,3 +27,17 @@ g.test_objects_that_implement_serialize_are_aliased = function()
     t.assert_is(nested2[1], nested2.a[1][1])
     t.assert_is(nested2[1], nested2.a.b.c)
 end
+
+g.test_objects_returned_by_serialize_are_aliased = function()
+    local o1 = {}
+    local o2 = serialize(setmetatable({}, {
+        __serialize = function() return {o1, {a = o1}} end,
+    }))
+    local o3 = serialize(setmetatable({}, {
+        __serialize = function() return {o2, o2} end
+    }))
+    t.assert_equals(o2, {o1, {a = o1}})
+    t.assert_is(o2[1], o2[2].a)
+    t.assert_equals(o3, {{o1, {a = o1}}, {o1, {a = o1}}})
+    t.assert_is(o3[1], o3[2])
+end
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 2a18730b0d..762d85bca5 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -616,6 +616,8 @@ static int yaml_is_flow_mode(struct lua_yaml_dumper *dumper) {
    return 0;
 }
 
+static void find_references(struct lua_yaml_dumper *dumper);
+
 static int dump_node(struct lua_yaml_dumper *dumper)
 {
    size_t len = 0;
@@ -635,6 +637,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
 
    int top = lua_gettop(dumper->L);
    luaL_checkfield(dumper->L, dumper->cfg, top, &field);
+   if (field.serialized)
+      find_references(dumper);
    switch(field.type) {
    case MP_UINT:
       snprintf(buf, sizeof(buf) - 1, "%" PRIu64, field.ival);
-- 
GitLab