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