Tarantool development patches archive
 help / color / mirror / Atom feed
* [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes
@ 2019-01-22  2:12 Alexander Turenko
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 1/3] lua-yaml: verify arguments count Alexander Turenko
                   ` (4 more replies)
  0 siblings, 5 replies; 23+ messages in thread
From: Alexander Turenko @ 2019-01-22  2:12 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: Alexander Turenko, tarantool-patches

https://github.com/tarantool/tarantool/issues/3476
https://github.com/tarantool/tarantool/issues/3662
https://github.com/tarantool/tarantool/issues/3583
https://github.com/tarantool/tarantool/tree/kh/gh-3662-yaml-2.1

AKhatskevich (1):
  lua-yaml: verify arguments count

Alexander Turenko (2):
  lua-yaml: fix boolean/null representation in yaml
  lua-yaml: treat an empty document/value as null

 test/app-tap/console.test.lua   |   37 +-
 test/app/fio.result             |    2 +-
 test/app/socket.result          |   26 +-
 test/box/access.result          |    8 +-
 test/box/misc.result            |    2 +-
 test/box/role.result            |    8 +-
 test/box/sequence.result        |    2 +-
 test/vinyl/errinj_tx.result     |   10 +-
 test/vinyl/hermitage.result     |  122 ++--
 test/vinyl/mvcc.result          | 1066 +++++++++++++++----------------
 test/vinyl/mvcc.test.lua        |    4 +-
 test/vinyl/tx_conflict.result   |    2 +-
 test/vinyl/tx_conflict.test.lua |    2 +-
 test/vinyl/tx_gap_lock.result   |  189 +++---
 test/vinyl/tx_gap_lock.test.lua |    3 +-
 test/vinyl/tx_serial.result     |    2 +-
 test/vinyl/tx_serial.test.lua   |    2 +-
 third_party/lua-yaml/lyaml.cc   |   87 ++-
 18 files changed, 819 insertions(+), 755 deletions(-)

-- 
2.20.1

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] [PATCH v2 1/3] lua-yaml: verify arguments count
  2019-01-22  2:12 [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes Alexander Turenko
@ 2019-01-22  2:12 ` Alexander Turenko
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml Alexander Turenko
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-01-22  2:12 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: AKhatskevich, tarantool-patches

From: AKhatskevich <avkhatskevich@tarantool.org>

Added arguments count check for yaml.encode() and decode.decode()
functions.

Without these checks the functions could read garbage outside of a Lua
stack when called w/o arguments.
---
 third_party/lua-yaml/lyaml.cc | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index c6d118a79..9b07992d8 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -400,7 +400,8 @@ static void load(struct lua_yaml_loader *loader) {
  */
 static int l_load(lua_State *L) {
    struct lua_yaml_loader loader;
-   if (! lua_isstring(L, 1)) {
+   int top = lua_gettop(L);
+   if (!(top == 1 || top == 2) || !lua_isstring(L, 1)) {
 usage_error:
       return luaL_error(L, "Usage: yaml.decode(document, "\
                         "[{tag_only = boolean}])");
@@ -416,7 +417,7 @@ usage_error:
       return luaL_error(L, OOM_ERRMSG);
    yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
    bool tag_only;
-   if (lua_gettop(L) > 1) {
+   if (lua_gettop(L) == 2) {
       if (! lua_istable(L, 2))
          goto usage_error;
       lua_getfield(L, 2, "tag_only");
@@ -794,7 +795,7 @@ error:
 static int l_dump(lua_State *L) {
    struct luaL_serializer *serializer = luaL_checkserializer(L);
    int top = lua_gettop(L);
-   if (top > 2) {
+   if (!(top == 1 || top == 2)) {
 usage_error:
       return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
                         "tag_handle = <string>})");
-- 
2.20.1

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-01-22  2:12 [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes Alexander Turenko
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 1/3] lua-yaml: verify arguments count Alexander Turenko
@ 2019-01-22  2:12 ` Alexander Turenko
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 3/3] lua-yaml: treat an empty document/value as null Alexander Turenko
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-01-22  2:12 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: Alexander Turenko, tarantool-patches

Encode changes:

Wrap the following string literals into single quotes: 'false', 'no',
'true', 'yes', '~', 'null', 'Null', 'NULL'. The content of these strings
is equals to null or boolean values representation in YAML.

Reverted the a2d7643c commit to use single quotes instead of multiline
encoding for 'true' and 'false' string literals.

'~', 'null', 'Null', 'NULL', 'no' and 'yes' were encoded w/o quotes
before, so '~', 'null', 'no', 'yes' were decoded with a wrong type then.
Multiline encoding was used for 'true' and 'false'.

Decode changes:

Treat 'Null', 'NULL' as null in addition to '~' and 'null'.

Note: this commit leaves an empty document/value treatment as an empty
string, which is not standard (should be treated as null). This is fixed
in the following commit.

Follows up #3476
Closes #3662
Closes #3583
---
 test/app-tap/console.test.lua | 23 +++++++---
 test/box/misc.result          |  2 +-
 third_party/lua-yaml/lyaml.cc | 80 ++++++++++++++++++++++++++---------
 3 files changed, 80 insertions(+), 25 deletions(-)

diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
index 4f915afd7..68f273234 100755
--- a/test/app-tap/console.test.lua
+++ b/test/app-tap/console.test.lua
@@ -21,7 +21,7 @@ local EOL = "\n...\n"
 
 test = tap.test("console")
 
-test:plan(59)
+test:plan(68)
 
 -- Start console and connect to it
 local server = console.listen(CONSOLE_SOCKET)
@@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
 
 --
 -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
+-- gh-3662: yaml.encode encodes booleans with multiline format.
+-- gh-3583: yaml.encode encodes null incorrectly.
 --
-test:is(type(yaml.decode(yaml.encode('false'))), 'string')
-test:is(type(yaml.decode(yaml.encode('true'))), 'string')
-test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
-test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
+
+test:is(yaml.encode(false), "--- false\n...\n")
+test:is(yaml.encode(true), "--- true\n...\n")
+test:is(yaml.encode('false'), "--- 'false'\n...\n")
+test:is(yaml.encode('true'), "--- 'true'\n...\n")
+test:is(yaml.encode(nil), "--- null\n...\n")
+
+test:is(yaml.decode('false'), false)
+test:is(yaml.decode('no'), false)
+test:is(yaml.decode('true'), true)
+test:is(yaml.decode('yes'), true)
+test:is(yaml.decode('~'), nil)
+test:is(yaml.decode('null'), nil)
+test:is(yaml.decode('Null'), nil)
+test:is(yaml.decode('NULL'), nil)
 
 box.cfg{
     listen=IPROTO_SOCKET;
diff --git a/test/box/misc.result b/test/box/misc.result
index c3cabcc8a..94ee11b63 100644
--- a/test/box/misc.result
+++ b/test/box/misc.result
@@ -55,7 +55,7 @@ t = {} for n in pairs(box) do table.insert(t, tostring(n)) end table.sort(t)
 ...
 t
 ---
-- - NULL
+- - 'NULL'
   - atomic
   - backup
   - begin
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 9b07992d8..73e5d2c31 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -94,6 +94,46 @@ struct lua_yaml_dumper {
    luaL_Buffer yamlbuf;
 };
 
+enum yaml_type {
+   YAML_NO_MATCH = 0,
+   YAML_FALSE,
+   YAML_TRUE,
+   YAML_NULL
+};
+
+/**
+ * Verify whether a string represents a boolean literal in YAML.
+ *
+ * Non-standard: only subset of YAML 1.1 boolean literals are
+ * treated as boolean values.
+ */
+static yaml_type
+yaml_get_bool(const char *str, const size_t len)
+{
+   if (len > 5)
+      return YAML_NO_MATCH;
+   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
+      return YAML_FALSE;
+   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
+      return YAML_TRUE;
+   return YAML_NO_MATCH;
+}
+
+/**
+ * Verify whether a string represents a null literal in YAML.
+ *
+ * Non-standard: don't match an empty string as null.
+ */
+static yaml_type
+yaml_get_null(const char *str, const size_t len){
+   if (len == 1 && str[0] == '~')
+      return YAML_NULL;
+   if (len == 4 && (strcmp(str, "null") == 0 || strcmp(str, "Null") == 0 ||
+       strcmp(str, "NULL") == 0))
+      return YAML_NULL;
+   return YAML_NO_MATCH;
+}
+
 static void generate_error_message(struct lua_yaml_loader *loader) {
    char buf[256];
    luaL_Buffer b;
@@ -209,7 +249,7 @@ static void load_scalar(struct lua_yaml_loader *loader) {
          lua_pushnumber(loader->L, dval);
          return;
       } else if (!strcmp(tag, "bool")) {
-         lua_pushboolean(loader->L, !strcmp(str, "true") || !strcmp(str, "yes"));
+         lua_pushboolean(loader->L, yaml_get_bool(str, length) == YAML_TRUE);
          return;
       } else if (!strcmp(tag, "binary")) {
          frombase64(loader->L, (const unsigned char *)str, length);
@@ -218,20 +258,20 @@ static void load_scalar(struct lua_yaml_loader *loader) {
    }
 
    if (loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
-      if (!strcmp(str, "~")) {
-         luaL_pushnull(loader->L);
-         return;
-      } else if (!strcmp(str, "true") || !strcmp(str, "yes")) {
-         lua_pushboolean(loader->L, 1);
-         return;
-      } else if (!strcmp(str, "false") || !strcmp(str, "no")) {
-         lua_pushboolean(loader->L, 0);
+      yaml_type type;
+      if (!length) {
+         /*
+          * Non-standard: an empty value/document is null
+          * according to the standard, but we decode it as an
+          * empty string.
+          */
+         lua_pushliteral(loader->L, "");
          return;
-      } else if (!strcmp(str, "null")) {
+      } else if (yaml_get_null(str, length) == YAML_NULL) {
          luaL_pushnull(loader->L);
          return;
-      } else if (!length) {
-         lua_pushliteral(loader->L, "");
+      } else if ((type = yaml_get_bool(str, length)) != YAML_NO_MATCH) {
+         lua_pushboolean(loader->L, type == YAML_TRUE);
          return;
       }
 
@@ -548,7 +588,6 @@ static int yaml_is_flow_mode(struct lua_yaml_dumper *dumper) {
                (evp->type == YAML_MAPPING_START_EVENT &&
                 evp->data.mapping_start.style == YAML_FLOW_MAPPING_STYLE)) {
             return 1;
-            break;
          }
       }
    }
@@ -597,8 +636,13 @@ static int dump_node(struct lua_yaml_dumper *dumper)
       return dump_table(dumper, &field);
    case MP_STR:
       str = lua_tolstring(dumper->L, -1, &len);
-      if (lua_isnumber(dumper->L, -1)) {
-         /* string is convertible to number, quote it to preserve type */
+      if ((yaml_get_null(str, len) == YAML_NULL)
+         || (yaml_get_bool(str, len) != YAML_NO_MATCH)
+         || (lua_isnumber(dumper->L, -1))) {
+         /*
+          * The string is convertible to a null, a boolean or
+          * a number, quote it to preserve its type.
+          */
          style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
          break;
       }
@@ -606,12 +650,10 @@ static int dump_node(struct lua_yaml_dumper *dumper)
       if (utf8_check_printable(str, len)) {
          if (yaml_is_flow_mode(dumper)) {
             style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
-         } else if (strstr(str, "\n\n") != NULL || strcmp(str, "true") == 0 ||
-		    strcmp(str, "false") == 0) {
+         } else if (strstr(str, "\n\n") != 0) {
             /*
              * Tarantool-specific: use literal style for string
-             * with empty lines and strings representing boolean
-             * types.
+             * with empty lines.
              * Useful for tutorial().
              */
             style = YAML_LITERAL_SCALAR_STYLE;
-- 
2.20.1

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] [PATCH v2 3/3] lua-yaml: treat an empty document/value as null
  2019-01-22  2:12 [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes Alexander Turenko
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 1/3] lua-yaml: verify arguments count Alexander Turenko
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml Alexander Turenko
@ 2019-01-22  2:12 ` Alexander Turenko
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-01-24 21:26 ` [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes Vladislav Shpilevoy
  2019-02-25 11:27 ` Kirill Yukhin
  4 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-01-22  2:12 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: Alexander Turenko, tarantool-patches

It improves our compatibility with the YAML standard.

yaml.encode('') result changed from

---
...

to

--- ''
...

yaml.decode('') returns zero values count before, now it returns
box.NULL. yaml.decode('- ') returns {''} before, now {box.NULL}.

This commit touches many tests and test result files, which use console,
without behaviour changes. It also adds two test cases to
app-tap/console.test.lua.
---
 test/app-tap/console.test.lua   |   16 +-
 test/app/fio.result             |    2 +-
 test/app/socket.result          |   26 +-
 test/box/access.result          |    8 +-
 test/box/role.result            |    8 +-
 test/box/sequence.result        |    2 +-
 test/vinyl/errinj_tx.result     |   10 +-
 test/vinyl/hermitage.result     |  122 ++--
 test/vinyl/mvcc.result          | 1066 +++++++++++++++----------------
 test/vinyl/mvcc.test.lua        |    4 +-
 test/vinyl/tx_conflict.result   |    2 +-
 test/vinyl/tx_conflict.test.lua |    2 +-
 test/vinyl/tx_gap_lock.result   |  189 +++---
 test/vinyl/tx_gap_lock.test.lua |    3 +-
 test/vinyl/tx_serial.result     |    2 +-
 test/vinyl/tx_serial.test.lua   |    2 +-
 third_party/lua-yaml/lyaml.cc   |   22 +-
 17 files changed, 747 insertions(+), 739 deletions(-)

diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
index 68f273234..b694a2787 100755
--- a/test/app-tap/console.test.lua
+++ b/test/app-tap/console.test.lua
@@ -21,7 +21,7 @@ local EOL = "\n...\n"
 
 test = tap.test("console")
 
-test:plan(68)
+test:plan(74)
 
 -- Start console and connect to it
 local server = console.listen(CONSOLE_SOCKET)
@@ -68,12 +68,12 @@ client_info = nil
 
 -- Check console.delimiter()
 client:write("require('console').delimiter(';')\n")
-test:is(yaml.decode(client:read(EOL)), '', "set delimiter to ';'")
+test:is(yaml.decode(client:read(EOL)), nil, "set delimiter to ';'")
 test:is(state.delimiter, ';', "state.delimiter is ';'")
 client:write("require('console').delimiter();\n")
 test:is(yaml.decode(client:read(EOL))[1], ';', "get delimiter is ';'")
 client:write("require('console').delimiter('');\n")
-test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
+test:is(yaml.decode(client:read(EOL)), nil, "clear delimiter")
 
 --
 -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
@@ -86,16 +86,26 @@ test:is(yaml.encode(true), "--- true\n...\n")
 test:is(yaml.encode('false'), "--- 'false'\n...\n")
 test:is(yaml.encode('true'), "--- 'true'\n...\n")
 test:is(yaml.encode(nil), "--- null\n...\n")
+test:is(yaml.encode('false'), "--- 'false'\n...\n")
+test:is(yaml.encode('true'), "--- 'true'\n...\n")
+test:is(yaml.encode(''), "--- ''\n...\n")
 
 test:is(yaml.decode('false'), false)
 test:is(yaml.decode('no'), false)
 test:is(yaml.decode('true'), true)
 test:is(yaml.decode('yes'), true)
 test:is(yaml.decode('~'), nil)
+test:is(yaml.decode(''), nil)
 test:is(yaml.decode('null'), nil)
 test:is(yaml.decode('Null'), nil)
 test:is(yaml.decode('NULL'), nil)
 
+-- Verify that yaml.decode() correctly handles nested nulls.
+test:is(yaml.decode('-')[1], nil)
+
+-- Verify that yaml.decode() returns null, not zero values count.
+test:is(select('#', yaml.decode('')), 1)
+
 box.cfg{
     listen=IPROTO_SOCKET;
     memtx_memory = 107374182,
diff --git a/test/app/fio.result b/test/app/fio.result
index 486cb8043..2f3530509 100644
--- a/test/app/fio.result
+++ b/test/app/fio.result
@@ -69,7 +69,7 @@ err:match("basename") ~= nil
 ...
 fio.basename('/')
 ---
-- 
+- ''
 ...
 fio.basename('abc')
 ---
diff --git a/test/app/socket.result b/test/app/socket.result
index 1209ec218..35c53d253 100644
--- a/test/app/socket.result
+++ b/test/app/socket.result
@@ -1235,7 +1235,7 @@ c:error()
 ...
 x, type(x), #x
 ---
-- 
+- ''
 - string
 - 0
 ...
@@ -1248,7 +1248,7 @@ c:error()
 ...
 x, type(x), #x
 ---
-- 
+- ''
 - string
 - 0
 ...
@@ -1285,7 +1285,7 @@ c:error()
 ...
 x, type(x), #x
 ---
-- 
+- ''
 - string
 - 0
 ...
@@ -1298,7 +1298,7 @@ c:error()
 ...
 x, type(x), #x
 ---
-- 
+- ''
 - string
 - 0
 ...
@@ -1311,7 +1311,7 @@ c:error()
 ...
 x, type(x), #x
 ---
-- 
+- ''
 - string
 - 0
 ...
@@ -1975,7 +1975,7 @@ c:receive(4)
 ...
 c:receive("*l")
 ---
-- 
+- ''
 ...
 wch:put("Fu")
 ---
@@ -1999,19 +1999,19 @@ c:receive()
 ---
 - null
 - closed
-- 
+- ''
 ...
 c:receive(10)
 ---
 - null
 - closed
-- 
+- ''
 ...
 c:receive("*a")
 ---
 - null
 - closed
-- 
+- ''
 ...
 c:close()
 ---
@@ -2345,7 +2345,7 @@ received_message == '' -- expected true
 ...
 received_message
 ---
-- 
+- ''
 ...
 e == 0 -- expected true
 ---
@@ -2368,7 +2368,7 @@ received_message == '' -- expected true
 ...
 received_message
 ---
-- 
+- ''
 ...
 from ~= nil -- expected true
 ---
@@ -2445,7 +2445,7 @@ received_message == '' -- expected true
 ...
 received_message
 ---
-- 
+- ''
 ...
 e == 0 -- expected true
 ---
@@ -2468,7 +2468,7 @@ received_message == '' -- expected true
 ...
 received_message
 ---
-- 
+- ''
 ...
 from ~= nil -- expected true
 ---
diff --git a/test/box/access.result b/test/box/access.result
index 9c190240f..269881bc1 100644
--- a/test/box/access.result
+++ b/test/box/access.result
@@ -999,7 +999,7 @@ box.schema.user.info('test_user')
     - test_space
   - - session,usage
     - universe
-    - 
+    - ''
   - - alter
     - user
     - test_user
@@ -1032,7 +1032,7 @@ box.schema.user.info('test_user')
     - public
   - - session,usage
     - universe
-    - 
+    - ''
   - - alter
     - user
     - test_user
@@ -1350,7 +1350,7 @@ e.type, e.access_type, e.object_type, e.message
 obj_type, obj_name, op_type
 ---
 - universe
-- 
+- ''
 - Usage
 ...
 euid, auid
@@ -1370,7 +1370,7 @@ c = (require 'net.box').connect(LISTEN.host, LISTEN.service, {user="test_user",
 obj_type, obj_name, op_type
 ---
 - universe
-- 
+- ''
 - Session
 ...
 euid, auid
diff --git a/test/box/role.result b/test/box/role.result
index 3a54e2460..9b044667a 100644
--- a/test/box/role.result
+++ b/test/box/role.result
@@ -28,7 +28,7 @@ box.schema.role.info('iddqd')
 ---
 - - - execute
     - universe
-    - 
+    - ''
 ...
 box.schema.role.revoke('iddqd', 'execute', 'universe')
 ---
@@ -48,7 +48,7 @@ box.schema.user.info('tester')
     - public
   - - session,usage
     - universe
-    - 
+    - ''
   - - alter
     - user
     - tester
@@ -66,7 +66,7 @@ box.schema.user.info('tester')
     - iddqd
   - - session,usage
     - universe
-    - 
+    - ''
   - - alter
     - user
     - tester
@@ -940,7 +940,7 @@ box.schema.user.info('test_user')
     - public
   - - session,usage
     - universe
-    - 
+    - ''
   - - alter
     - user
     - test_user
diff --git a/test/box/sequence.result b/test/box/sequence.result
index b3907659f..f429d7dae 100644
--- a/test/box/sequence.result
+++ b/test/box/sequence.result
@@ -1361,7 +1361,7 @@ box.schema.user.info()
     - _priv
   - - session,usage
     - universe
-    - 
+    - ''
   - - alter
     - user
     - user
diff --git a/test/vinyl/errinj_tx.result b/test/vinyl/errinj_tx.result
index a7583b2e2..2df9bc6d0 100644
--- a/test/vinyl/errinj_tx.result
+++ b/test/vinyl/errinj_tx.result
@@ -207,28 +207,28 @@ c0 = txn_proxy.new()
 ...
 c0:begin()
 ---
-- 
+- null
 ...
 c1 = txn_proxy.new()
 ---
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2 = txn_proxy.new()
 ---
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3 = txn_proxy.new()
 ---
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 --
 -- Prepared transactions
@@ -336,7 +336,7 @@ c3('s:select{3}') -- c2 is not visible
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 s:drop()
 ---
diff --git a/test/vinyl/hermitage.result b/test/vinyl/hermitage.result
index 23495fde1..fbe2090ce 100644
--- a/test/vinyl/hermitage.result
+++ b/test/vinyl/hermitage.result
@@ -51,11 +51,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 11}")
 ---
@@ -71,7 +71,7 @@ c1("t:replace{2, 21}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2("t:replace{2, 22}")
 ---
@@ -79,7 +79,7 @@ c2("t:replace{2, 22}")
 ...
 c2:commit() -- success, the last writer wins
 ---
-- 
+- null
 ...
 t:get{1} -- {1, 12}
 ---
@@ -107,11 +107,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:update(1, {{'=', 2, 11}})")
 ---
@@ -127,7 +127,7 @@ c1("t:update(2, {{'=', 2, 21}})")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2("t:update(2, {{'=', 2, 22}})")
 ---
@@ -163,11 +163,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 101}")
 ---
@@ -179,7 +179,7 @@ c2("t:replace{1, 10}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 c2("t:get{1}") -- {1, 10}
 ---
@@ -187,7 +187,7 @@ c2("t:get{1}") -- {1, 10}
 ...
 c2:commit() -- true
 ---
-- 
+- null
 ...
 -- teardown
 t:truncate()
@@ -207,11 +207,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 101}")
 ---
@@ -227,7 +227,7 @@ c1("t:replace{1, 11}")
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c2("t:get{1}") -- {1, 10}
 ---
@@ -235,7 +235,7 @@ c2("t:get{1}") -- {1, 10}
 ...
 c2:commit() -- ok
 ---
-- 
+- null
 ...
 -- teardown
 t:truncate()
@@ -255,11 +255,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 11}")
 ---
@@ -279,7 +279,7 @@ c2("t:get{1}") -- {1, 10}
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c2:commit() -- rollback (@fixme: not necessary)
 ---
@@ -303,15 +303,15 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 11}")
 ---
@@ -327,7 +327,7 @@ c2("t:replace{1, 12}")
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 11}
 ---
@@ -343,7 +343,7 @@ c3("t:get{2}") -- {2, 19}
 ...
 c2:commit() -- write only transaction - OK to commit
 ---
-- 
+- null
 ...
 c3("t:get{2}") -- {2, 19}
 ---
@@ -355,7 +355,7 @@ c3("t:get{1}") -- {1, 11}
 ...
 c3:commit() -- read only transaction - OK to commit, stays with its read view
 ---
-- 
+- null
 ...
 -- teardown
 t:truncate()
@@ -375,11 +375,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:select()") -- {1, 10}, {2, 20}
 ---
@@ -391,7 +391,7 @@ c2("t:replace{3, 30}")
 ...
 c2:commit() -- ok
 ---
-- 
+- null
 ...
 c1("t:select()") -- still {1, 10}, {2, 20}
 ---
@@ -399,7 +399,7 @@ c1("t:select()") -- still {1, 10}, {2, 20}
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 -- teardown
 t:truncate()
@@ -419,11 +419,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 20}")
 ---
@@ -443,11 +443,11 @@ c2("t:get{2}") -- {2, 20}
 ...
 c2("t:delete{2}")
 ---
-- 
+- null
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c2("t:get{1}") -- {1, 10}
 ---
@@ -483,11 +483,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -507,7 +507,7 @@ c2("t:replace{1, 12}")
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c2:commit() -- rollback -- conflict
 ---
@@ -531,11 +531,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -559,7 +559,7 @@ c2("t:replace{2, 18}")
 ...
 c2:commit() -- ok
 ---
-- 
+- null
 ...
 c1("t:get{2}") -- {2, 20}
 ---
@@ -567,7 +567,7 @@ c1("t:get{2}") -- {2, 20}
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 -- teardown
 t:truncate()
@@ -587,11 +587,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -615,15 +615,15 @@ c2("t:replace{2, 18}")
 ...
 c2:commit() -- T2
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 c1("t:get{2}") -- finds nothing
 ---
-- 
+- null
 ...
 c1:commit() -- rollback
 ---
@@ -647,11 +647,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -679,7 +679,7 @@ c2("t:replace{1, 21}")
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c2:commit() -- rollback -- conflict
 ---
@@ -703,11 +703,11 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 -- select * from test where value % 3 = 0
 c1("t:select()") -- {1, 10}, {2, 20}
@@ -728,7 +728,7 @@ c2("t:replace{4, 42}")
 ...
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 c2:commit() -- rollback
 ---
@@ -752,7 +752,7 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -764,7 +764,7 @@ c1("t:get{2}") -- {2, 20}
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:replace{2, 25}")
 ---
@@ -772,11 +772,11 @@ c2("t:replace{2, 25}")
 ...
 c2:commit() -- ok
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 10}
 ---
@@ -788,7 +788,7 @@ c3("t:get{2}") -- {2, 25}
 ...
 c3:commit() -- ok
 ---
-- 
+- null
 ...
 c1("t:replace{1, 0}")
 ---
@@ -816,7 +816,7 @@ t:replace{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -828,7 +828,7 @@ c1("t:get{2}") -- {2, 20}
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:replace{2, 25}")
 ---
@@ -836,11 +836,11 @@ c2("t:replace{2, 25}")
 ...
 c2:commit() -- ok
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 10}
 ---
@@ -852,12 +852,12 @@ c3("t:get{2}") -- {2, 25}
 ...
 c3:commit() -- ok
 ---
-- 
+- null
 ...
 -- c1("t:replace{1, 0)")
 c1:commit() -- ok
 ---
-- 
+- null
 ...
 -- teardown
 t:truncate()
diff --git a/test/vinyl/mvcc.result b/test/vinyl/mvcc.result
index 1941744b9..db7fc8e83 100644
--- a/test/vinyl/mvcc.result
+++ b/test/vinyl/mvcc.result
@@ -41,29 +41,29 @@ t = box.space.test
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 -- empty transaction rollback
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 -- single-statement transaction commit
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1}")
 ---
@@ -71,7 +71,7 @@ c1("t:replace{1}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
@@ -80,14 +80,14 @@ c1("t:get{1}")
 -- cleanup
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- single-statement transaction rollback
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1}")
 ---
@@ -95,18 +95,18 @@ c1("t:replace{1}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 --
 -- basic effects: if a transaction is rolled back, it has no effect
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1}")
 ---
@@ -118,15 +118,15 @@ c1("t:get{1}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 c2("t:get{1}")
 ---
-- 
+- null
 ...
 --
 -- multi-statement transaction
@@ -137,7 +137,7 @@ test_run:cmd("setopt delimiter ';'")
 ...
 c1:begin();
 ---
-- 
+- null
 ...
 for i = 1,100 do
     c1(string.format("t:insert{%d}", i))
@@ -147,7 +147,7 @@ end;
 ...
 c1:commit();
 ---
-- 
+- null
 ...
 for i = 1,100 do
     c1(string.format("t:delete{%d}", i))
@@ -155,7 +155,7 @@ end;
 ---
 ...
 for i = 1,100 do
-    assert(#c1(string.format("t:get{%d}", i)) == 0)
+    assert(c1(string.format("t:get{%d}", i)) == nil)
 end;
 ---
 ...
@@ -172,7 +172,7 @@ test_run:cmd("setopt delimiter ';'")
 ...
 c1:begin();
 ---
-- 
+- null
 ...
 for i = 1,100 do
     c1(string.format("t:insert{%d}", i))
@@ -182,10 +182,10 @@ end;
 ...
 c1:rollback();
 ---
-- 
+- null
 ...
 for i = 1,100 do
-    assert(#c1(string.format("t:get{%d}", i)) == 0)
+    assert(c1(string.format("t:get{%d}", i)) == nil)
 end;
 ---
 ...
@@ -196,7 +196,7 @@ test_run:cmd("setopt delimiter ''");
 -- transaction_set_set_get_commit(void)
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 1}")
 ---
@@ -212,7 +212,7 @@ c1("t:get{1}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
@@ -220,12 +220,12 @@ c1("t:get{1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- transaction_set_set_commit_get(void)
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1}")
 ---
@@ -237,11 +237,11 @@ c1("t:replace{1, 2}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:get{1}")
 ---
@@ -249,16 +249,16 @@ c2("t:get{1}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- transaction_set_set_rollback_get(void)
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1}")
 ---
@@ -270,24 +270,24 @@ c1("t:replace{1, 2}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:get{1}")
 ---
-- 
+- null
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 -- transaction_set_delete_get_commit(void)
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1}")
 ---
@@ -295,20 +295,20 @@ c1("t:insert{1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 -- transaction_set_delete_get_commit_get(void)
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1}")
 ---
@@ -316,26 +316,26 @@ c1("t:insert{1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 --
 -- transaction_set_delete_set_commit_get(void)
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1, 1}")
 ---
@@ -343,7 +343,7 @@ c1("t:insert{1, 1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:insert{1, 2}")
 ---
@@ -355,7 +355,7 @@ c1("t:get{1}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2("t:get{1}")
 ---
@@ -366,14 +366,14 @@ c2("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- transaction_set_delete_commit_get_set(void)
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1}")
 ---
@@ -381,15 +381,15 @@ c1("t:insert{1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 c1("t:insert{1}")
 ---
@@ -401,22 +401,22 @@ c1("t:get{1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
-- 
+- null
 ...
 --
 -- transaction_p_set_commit(void)
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:replace{1, 10}")
 ---
@@ -424,7 +424,7 @@ c1("t:replace{1, 10}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2("t:replace{2, 15}");
 ---
@@ -432,7 +432,7 @@ c2("t:replace{2, 15}");
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
@@ -444,11 +444,11 @@ c1("t:get{2}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 --
 -- no dirty reads: if a transaction is not committed, its effects are not
@@ -456,7 +456,7 @@ c1("t:delete{2}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1}")
 ---
@@ -471,11 +471,11 @@ c1("t:get{1}")
 --
 c2("t:get{1}")
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 -- become visible in c2 after c1 commits (c2 runs in autocommit)
@@ -491,7 +491,7 @@ c2("t:get{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}")
 ---
@@ -510,7 +510,7 @@ c1("t:get{1}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 -- still not visible, even though c2 has committed
@@ -522,7 +522,7 @@ c1("t:get{1}")
 -- commits ok since is a read only transaction
 c1:commit()
 ---
-- 
+- null
 ...
 --
 -- now visible
@@ -533,7 +533,7 @@ c1("t:get{1}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- *******************************
 -- tx manager tests  from sophia *
@@ -544,19 +544,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:replace{1, 10}")
 ---
@@ -569,7 +569,7 @@ c1("t:get{1}") -- {1, 10}
 --
 c1:commit()
 ---
-- 
+- null
 ...
 --
 --
@@ -585,18 +585,18 @@ c2("t:get{2}") -- {2, 15}
 --
 c2:commit()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -605,19 +605,19 @@ c1("t:delete{2}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -627,7 +627,7 @@ c1("t:replace{1, 10}")
 --
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{2, 15}")
@@ -636,12 +636,12 @@ c2("t:replace{2, 15}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -654,18 +654,18 @@ c1("t:get{2}") -- {2, 15}
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_p_set_commit_get1(void)
@@ -673,19 +673,19 @@ c1("t:delete{2}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}")
 ---
-- 
+- null
 ...
 c2("t:get{200}")
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -694,7 +694,7 @@ c2("t:replace{1, 10}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 -- try writing an unrelated key
@@ -705,12 +705,12 @@ c1("t:replace{2, 15}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:get{1}") -- {1, 10}
 ---
@@ -718,37 +718,37 @@ c2("t:get{1}") -- {1, 10}
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 -- --
 --  now try the same key
 -- --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}")
 ---
-- 
+- null
 ...
 c2("t:get{200}")
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -757,7 +757,7 @@ c2("t:replace{1, 10}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -766,12 +766,12 @@ c1("t:replace{1, 15}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:get{1}") -- {1, 15}
 ---
@@ -779,14 +779,14 @@ c2("t:get{1}") -- {1, 15}
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_p_set_commit_get2(void)
@@ -794,19 +794,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}")
 ---
-- 
+- null
 ...
 c2("t:get{200}")
 ---
-- 
+- null
 ...
 --
 --
@@ -816,7 +816,7 @@ c1("t:replace{2, 15}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 --
@@ -826,12 +826,12 @@ c2("t:replace{1, 10}")
 ...
 c2:commit() -- commits successfully
 ---
-- 
+- null
 ...
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- {1, 10}
 ---
@@ -844,18 +844,18 @@ c1("t:get{2}") -- {2, 15}
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_p_set_rollback_get0(void)
@@ -863,19 +863,19 @@ c1("t:delete{2}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}")
 ---
-- 
+- null
 ...
 c2("t:get{200}")
 ---
-- 
+- null
 ...
 --
 --
@@ -885,7 +885,7 @@ c1("t:replace{1, 10}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{2, 15}")
@@ -894,24 +894,24 @@ c2("t:replace{2, 15}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- finds nothing
 ---
-- 
+- null
 ...
 c3("t:get{2}") -- finds nothing
 ---
-- 
+- null
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_p_set_rollback_get1(void)
@@ -920,19 +920,19 @@ c3:rollback()
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -941,7 +941,7 @@ c2("t:replace{1, 10}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{2, 15}")
@@ -950,24 +950,24 @@ c1("t:replace{2, 15}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- finds nothing
 ---
-- 
+- null
 ...
 c3("t:get{2}") -- finds nothing
 ---
-- 
+- null
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -976,19 +976,19 @@ c3:rollback()
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -998,7 +998,7 @@ c2("t:replace{1, 10}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1007,29 +1007,29 @@ c1("t:replace{1, 15}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- finds nothing
 ---
-- 
+- null
 ...
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -1039,7 +1039,7 @@ c2("t:replace{1, 10}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1048,7 +1048,7 @@ c1("t:replace{1, 15}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- {1, 15}
@@ -1060,7 +1060,7 @@ c3("t:get{1}") -- {1, 15}
 --
 c3("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_c_set_commit0(void)
@@ -1068,19 +1068,19 @@ c3("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:replace{1, 10}")
 ---
@@ -1088,7 +1088,7 @@ c1("t:replace{1, 10}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -1097,7 +1097,7 @@ c2("t:replace{1, 15}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:get{1}")  -- {1,15}
@@ -1108,7 +1108,7 @@ c2("t:get{1}")  -- {1,15}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_c_set_commit1(void)
@@ -1116,19 +1116,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -1137,7 +1137,7 @@ c2("t:replace{1, 10}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1146,7 +1146,7 @@ c1("t:replace{1, 15}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- {1, 15}
@@ -1158,7 +1158,7 @@ c3("t:get{1}") -- {1, 15}
 --
 c3("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_c_set_commit2(void)
@@ -1166,19 +1166,19 @@ c3("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1193,11 +1193,11 @@ c2("t:replace{1, 10}")
 --
 c2:commit()
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- {1, 15}
@@ -1209,24 +1209,24 @@ c3("t:get{1}") -- {1, 15}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1242,11 +1242,11 @@ c2("t:replace{1, 10}")
 -- sic: commit order
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit() -- write after write is ok, the last writer to commit wins
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- {1, 10}
@@ -1258,7 +1258,7 @@ c3("t:get{1}") -- {1, 10}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_c_set_commit_rollback_a0(void)
@@ -1266,19 +1266,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -1288,7 +1288,7 @@ c2("t:replace{1, 10}")
 --
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1298,7 +1298,7 @@ c1("t:replace{1, 15}")
 --
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
@@ -1310,26 +1310,26 @@ c3("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 --
 -- statement order is irrelevant, rollback order is important
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1343,11 +1343,11 @@ c2("t:replace{1, 15}")
 --
 c2:rollback()
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
@@ -1359,7 +1359,7 @@ c3("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_c_set_commit_rollback_a1(void)
@@ -1367,19 +1367,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -1393,37 +1393,37 @@ c1("t:replace{1, 15}")
 --
 c2:rollback()
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- statements in different order now
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1437,18 +1437,18 @@ c2("t:replace{1, 15}")
 --
 c2:rollback()
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1457,19 +1457,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -1478,7 +1478,7 @@ c2("t:replace{1, 10}")
 ...
 c2:commit() -- success
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 15}")
@@ -1487,7 +1487,7 @@ c1("t:replace{1, 15}")
 ...
 c1:rollback() -- success
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
@@ -1498,7 +1498,7 @@ c3("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_c_set_commit_rollback_b1(void)
@@ -1506,19 +1506,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -1532,11 +1532,11 @@ c1("t:replace{1, 10}")
 --
 c2:commit()
 ---
-- 
+- null
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
@@ -1548,26 +1548,26 @@ c3("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- now commit the second transaction
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -1581,11 +1581,11 @@ c1("t:replace{1, 10}")
 --
 c2:commit()
 ---
-- 
+- null
 ...
 c1:commit() -- ok, the last committer wins
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- {1, 10}
@@ -1597,7 +1597,7 @@ c3("t:get{1}") -- {1, 10}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1606,19 +1606,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -1627,7 +1627,7 @@ c2("t:replace{1, 15}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1636,38 +1636,38 @@ c1("t:replace{1, 10}")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- now commit the second transaction
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -1676,7 +1676,7 @@ c2("t:replace{1, 15}")
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1685,7 +1685,7 @@ c1("t:replace{1, 10}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
@@ -1697,7 +1697,7 @@ c3("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1706,19 +1706,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 10}")
@@ -1732,23 +1732,23 @@ c1("t:replace{1, 15}")
 --
 c2:rollback()
 ---
-- 
+- null
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c2("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1757,19 +1757,19 @@ c2("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -1784,11 +1784,11 @@ c1("t:replace{1, 10}")
 --
 c1:commit() -- success
 ---
-- 
+- null
 ...
 c2:commit() -- success, the last writer wins
 ---
-- 
+- null
 ...
 --
 c2("t:get{1}") -- {1, 15}
@@ -1800,7 +1800,7 @@ c2("t:get{1}") -- {1, 15}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1809,19 +1809,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1836,18 +1836,18 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c1:commit() -- success, the last writer wins
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c2("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1856,19 +1856,19 @@ c2("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1883,18 +1883,18 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1903,19 +1903,19 @@ c1("t:delete{1}")
 --
 c2:begin()
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1930,18 +1930,18 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1950,19 +1950,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -1977,18 +1977,18 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -1997,19 +1997,19 @@ c1("t:delete{1}")
 --
 c2:begin()
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -2024,18 +2024,18 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c1:rollback() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2044,19 +2044,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -2071,22 +2071,22 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c2:rollback() -- not in transaction
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2095,19 +2095,19 @@ c1("t:delete{1}")
 --
 c2:begin()
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -2122,26 +2122,26 @@ c2("t:replace{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c2:rollback() -- not in transaction
 ---
-- 
+- null
 ...
 c1:commit() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2150,27 +2150,27 @@ c1("t:delete{2}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -2191,23 +2191,23 @@ c3("t:replace{1, 20}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c3:commit() -- success
 ---
-- 
+- null
 ...
 c1:commit() -- success, the last committer wins
 ---
-- 
+- null
 ...
 c2:commit() -- not in transaction
 ---
-- 
+- null
 ...
 c3:commit() -- not in transaction
 ---
-- 
+- null
 ...
 --
 c3:get{1} -- {1, 20}
@@ -2219,7 +2219,7 @@ c3:get{1} -- {1, 20}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2228,27 +2228,27 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -2269,15 +2269,15 @@ c3("t:replace{1, 30}")
 --
 c1:commit() -- success
 ---
-- 
+- null
 ...
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c3:commit() -- success
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- {1, 30}
@@ -2288,7 +2288,7 @@ c3("t:get{1}") -- {1, 30}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2297,27 +2297,27 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -2338,22 +2338,22 @@ c3("t:replace{1, 20}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c3:commit() -- rollback
 ---
-- 
+- null
 ...
 c1:rollback() -- success
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2362,27 +2362,27 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -2403,30 +2403,30 @@ c3("t:replace{1, 20}")
 --
 c2:commit()  -- success
 ---
-- 
+- null
 ...
 c3:commit() -- rollback
 ---
-- 
+- null
 ...
 c2:rollback() -- success, not in transaction in tarantool
 ---
-- 
+- null
 ...
 c3:commit() -- success, not in transaction in tarantool
 ---
-- 
+- null
 ...
 c1:commit() -- rollback
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2435,27 +2435,27 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -2476,22 +2476,22 @@ c3("t:replace{1, 20}")
 --
 c3:rollback()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2500,27 +2500,27 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -2540,22 +2540,22 @@ c3("t:replace{1, 20}")
 --
 c2:commit()
 ---
-- 
+- null
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2564,27 +2564,27 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{300}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 --
@@ -2605,22 +2605,22 @@ c3("t:replace{1, 20}")
 --
 c2:commit()
 ---
-- 
+- null
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2629,19 +2629,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -2650,7 +2650,7 @@ c1("t:replace{1, 10}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:get{1}") -- find newest {1, 10}
@@ -2664,12 +2664,12 @@ c2("t:replace{1, 15}")
 ...
 c2:commit() -- rollback
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 10}
 ---
@@ -2677,14 +2677,14 @@ c3("t:get{1}") -- {1, 10}
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2693,19 +2693,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -2715,12 +2715,12 @@ c1("t:replace{1, 10}")
 --
 c1:rollback()
 ---
-- 
+- null
 ...
 --
 c2("t:get{1}") -- finds nothing
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 15}")
@@ -2729,12 +2729,12 @@ c2("t:replace{1, 15}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 15}
 ---
@@ -2742,14 +2742,14 @@ c3("t:get{1}") -- {1, 15}
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2758,15 +2758,15 @@ c1("t:delete{1}")
 --
 c7:begin()
 ---
-- 
+- null
 ...
 c7("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 1}")
@@ -2776,7 +2776,7 @@ c1("t:replace{1, 1}")
 --
 c2:begin()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 2}")
@@ -2786,7 +2786,7 @@ c2("t:replace{1, 2}")
 --
 c4:begin()
 ---
-- 
+- null
 ...
 c4("t:replace{1, 4}")
 ---
@@ -2795,7 +2795,7 @@ c4("t:replace{1, 4}")
 --
 c5:begin()
 ---
-- 
+- null
 ...
 c5("t:replace{1, 5}")
 ---
@@ -2804,11 +2804,11 @@ c5("t:replace{1, 5}")
 --
 c6:begin()
 ---
-- 
+- null
 ...
 c6("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- {1, 1}
@@ -2833,62 +2833,62 @@ c5("t:get{1}") -- {1, 5}
 --
 c6("t:get{1}") --  nothing
 ---
-- 
+- null
 ...
 --
 c7("t:get{1}") --  nothing
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}") -- nothing
 ---
-- 
+- null
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 c1:rollback()
 ---
-- 
+- null
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 c4:rollback()
 ---
-- 
+- null
 ...
 c5:rollback()
 ---
-- 
+- null
 ...
 c6:rollback()
 ---
-- 
+- null
 ...
 c7:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -2897,24 +2897,24 @@ c1("t:delete{1}")
 --
 c7:begin()
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c7("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:get{1}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:replace{1, 3}")
 ---
@@ -2922,24 +2922,24 @@ c3("t:replace{1, 3}")
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 --
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c2("t:get{500}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{600}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{1}") -- {1, 3}
 ---
@@ -2952,16 +2952,16 @@ c3("t:replace{1, 6}")
 ...
 c3:commit() -- c2 goes to read view now
 ---
-- 
+- null
 ...
 --
 c4:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 --
 c3("t:replace{1, 9}")
@@ -2970,24 +2970,24 @@ c3("t:replace{1, 9}")
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 --
 c5:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c5("t:get{800}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c3("t:get{900}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c3("t:replace{1, 12}")
@@ -2996,16 +2996,16 @@ c3("t:replace{1, 12}")
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 --
 c6:begin()
 ---
-- 
+- null
 ...
 c6("t:get{1000}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 --
 c2("t:get{1}") -- {1, 3}
@@ -3030,7 +3030,7 @@ c6("t:get{1}") -- {1, 12}
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 12}
 ---
@@ -3038,12 +3038,12 @@ c3("t:get{1}") -- {1, 12}
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- nothing
 ---
-- 
+- null
 ...
 --
 c7("t:get{1}") -- {1, 12}
@@ -3053,7 +3053,7 @@ c7("t:get{1}") -- {1, 12}
 --
 c2:rollback()
 ---
-- 
+- null
 ...
 --
 c4("t:get{1}") -- {1, 12}
@@ -3073,7 +3073,7 @@ c6("t:get{1}") -- {1, 12}
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 12}
 ---
@@ -3081,12 +3081,12 @@ c3("t:get{1}") -- {1, 12}
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- nothing
 ---
-- 
+- null
 ...
 --
 c7("t:get{1}") -- {1, 12}
@@ -3096,7 +3096,7 @@ c7("t:get{1}") -- {1, 12}
 --
 c4:rollback()
 ---
-- 
+- null
 ...
 --
 c5("t:get{1}") -- {1, 12}
@@ -3111,7 +3111,7 @@ c6("t:get{1}") -- {1, 12}
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 12}
 ---
@@ -3119,12 +3119,12 @@ c3("t:get{1}") -- {1, 12}
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- nothing
 ---
-- 
+- null
 ...
 --
 c7("t:get{1}") -- {1, 12}
@@ -3134,7 +3134,7 @@ c7("t:get{1}") -- {1, 12}
 --
 c5:rollback()
 ---
-- 
+- null
 ...
 --
 c6("t:get{1}") -- {1, 12}
@@ -3144,7 +3144,7 @@ c6("t:get{1}") -- {1, 12}
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 12}
 ---
@@ -3152,12 +3152,12 @@ c3("t:get{1}") -- {1, 12}
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- nothing
 ---
-- 
+- null
 ...
 --
 c7("t:get{1}") -- {1, 12}
@@ -3167,12 +3167,12 @@ c7("t:get{1}") -- {1, 12}
 --
 c6:rollback()
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 12}
 ---
@@ -3180,12 +3180,12 @@ c3("t:get{1}") -- {1, 12}
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- nothing
 ---
-- 
+- null
 ...
 --
 c7("t:get{1}") -- {1, 12}
@@ -3195,16 +3195,16 @@ c7("t:get{1}") -- {1, 12}
 --
 c1:rollback()
 ---
-- 
+- null
 ...
 c7:rollback()
 ---
-- 
+- null
 ...
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}") -- {1, 12}
 ---
@@ -3212,14 +3212,14 @@ c3("t:get{1}") -- {1, 12}
 ...
 c3:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -3228,19 +3228,19 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:get{100}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c2("t:get{200}") -- start transaction in the engine
 ---
-- 
+- null
 ...
 c1("t:replace{1, 10}")
 ---
@@ -3253,7 +3253,7 @@ c2("t:replace{1, 15}")
 --
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 20}") -- should not reset conflict flag
@@ -3263,7 +3263,7 @@ c2("t:replace{1, 20}") -- should not reset conflict flag
 --
 c2:commit() --  rollback
 ---
-- 
+- null
 ...
 --
 c3("t:get{1}")
@@ -3275,7 +3275,7 @@ c3("t:get{1}")
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -3284,7 +3284,7 @@ c1("t:delete{1}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{1, 10}")
@@ -3301,7 +3301,7 @@ c2("t:replace{1, 15}")
 --
 c1:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:get{1}") -- {1, 10}
@@ -3311,7 +3311,7 @@ c2("t:get{1}") -- {1, 10}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- transaction_sc_get(void)
@@ -3324,7 +3324,7 @@ c1("t:replace{1, 7}")
 --
 c2:begin()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{1, 8}")
@@ -3339,7 +3339,7 @@ c1("t:get{1}") -- {1, 7}
 --
 c2:commit()
 ---
-- 
+- null
 ...
 --
 c1("t:get{1}") -- {1, 8}
@@ -3356,18 +3356,18 @@ c3("t:get{1}") -- {1, 8}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 -- --------------------------------------------------------------------------
 -- two conflicting inserts
 -- --------------------------------------------------------------------------
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 --
 c1("t:insert{1, 10}")
@@ -3382,7 +3382,7 @@ c2("t:insert{1, 15}")
 --
 c1:commit() -- success
 ---
-- 
+- null
 ...
 c2:commit() -- rollback, c2 reads {1} before writing it
 ---
@@ -3399,16 +3399,16 @@ c3("t:get{1}") -- {1, 10}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 --
 c1("t:insert{1, 10}")
@@ -3423,7 +3423,7 @@ c2("t:insert{1, 15}")
 --
 c2:commit() -- success
 ---
-- 
+- null
 ...
 c1:commit() -- rollback, c1 reads {1} before writing it
 ---
@@ -3440,7 +3440,7 @@ c3("t:get{1}") -- {1, 15}
 --
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 --
 -- --------------------------------------------------------------------------
@@ -3456,7 +3456,7 @@ t:insert{2, 20}
 ...
 c7:begin()
 ---
-- 
+- null
 ...
 c7("t:insert{8, 800}")
 ---
@@ -3464,7 +3464,7 @@ c7("t:insert{8, 800}")
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:get{1}")
 ---
@@ -3472,15 +3472,15 @@ c3("t:get{1}")
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 --
 c1("t:replace{4, 40}")
@@ -3495,7 +3495,7 @@ c2("t:get{1}")
 --
 c3:begin()
 ---
-- 
+- null
 ...
 c3("t:insert{3, 30}")
 ---
@@ -3503,7 +3503,7 @@ c3("t:insert{3, 30}")
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 --
 c2("t:replace{5, 50}")
@@ -3516,15 +3516,15 @@ c1("t:get{1}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c7:rollback()
 ---
-- 
+- null
 ...
 --
 -- cleanup
@@ -3557,11 +3557,11 @@ t:insert{2, 20}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:select{}")
 ---
@@ -3581,7 +3581,7 @@ c2("t:replace{2, 'new'}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit() -- rollback
 ---
@@ -3593,7 +3593,7 @@ c2:commit() -- rollback
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:select{}")
 ---
@@ -3645,7 +3645,7 @@ c1("t:select({3}, {iterator='eq'})")
 ...
 c1("t:delete{3}")
 ---
-- 
+- null
 ...
 c1("t:select({3}, {iterator='ge'})")
 ---
@@ -3669,7 +3669,7 @@ c1("t:replace{3}")
 ...
 c1("t:delete{2}")
 ---
-- 
+- null
 ...
 c1("t:select({3}, {iterator='lt'})")
 ---
@@ -3685,7 +3685,7 @@ c1("t:replace{2}")
 ...
 c1("t:delete{1}")
 ---
-- 
+- null
 ...
 c1("t:select({3}, {iterator='lt'})")
 ---
@@ -3697,7 +3697,7 @@ c1("t:select({3}, {iterator='le'})")
 ...
 c1("t:delete{3}")
 ---
-- 
+- null
 ...
 c1("t:select({3}, {iterator='lt'})")
 ---
@@ -3709,7 +3709,7 @@ c1("t:select({3}, {iterator='le'})")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 c1("t:select{}")
 ---
@@ -3722,7 +3722,7 @@ c1("t:select{}")
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:select{1}")
 ---
@@ -3734,11 +3734,11 @@ c1("for k, v in box.space.test:pairs() do box.commit() end")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:select{1}")
 ---
@@ -3750,7 +3750,7 @@ c1("for k, v in box.space.test:pairs() do box.rollback() end")
 ...
 c1:rollback()
 ---
-- 
+- null
 ...
 t:truncate()
 ---
@@ -3764,7 +3764,7 @@ t:replace{1}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t.index.pk:max()") -- {1}
 ---
@@ -3780,7 +3780,7 @@ c1("t.index.pk:count()") -- 1
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:replace{2}") -- conflicts with c1 so c1 starts using a read view
 ---
@@ -3788,7 +3788,7 @@ c2("t:replace{2}") -- conflicts with c1 so c1 starts using a read view
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c1("t.index.pk:max()") -- {1}
 ---
@@ -3804,7 +3804,7 @@ c1("t.index.pk:count()") -- 1
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 --
 -- Convert the reader to a read view: in this test we have
@@ -3813,7 +3813,7 @@ c1:commit()
 --
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t.index.pk:max()") -- {2}
 ---
@@ -3829,7 +3829,7 @@ c1("t.index.pk:count()") -- 2
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:replace{1, 'new'}") -- conflits with c1 so c1 starts using a read view
 ---
@@ -3841,7 +3841,7 @@ c2("t:replace{3}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c1("t.index.pk:max()") -- {2}
 ---
@@ -3857,7 +3857,7 @@ c1("t.index.pk:count()") -- 2
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 t:truncate()
 ---
@@ -3876,7 +3876,7 @@ t:replace{2}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c1("t:select({}, {limit = 0})") -- none
 ---
@@ -3884,7 +3884,7 @@ c1("t:select({}, {limit = 0})") -- none
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:replace{1, 'new'}")
 ---
@@ -3892,7 +3892,7 @@ c2("t:replace{1, 'new'}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c1("t:select({}, {limit = 1})") -- {1, 'new'}
 ---
@@ -3900,7 +3900,7 @@ c1("t:select({}, {limit = 1})") -- {1, 'new'}
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c2("t:replace{2, 'new'}")
 ---
@@ -3908,7 +3908,7 @@ c2("t:replace{2, 'new'}")
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c1("t:select()") -- {1, 'new'}, {2, 'new'}
 ---
@@ -3916,7 +3916,7 @@ c1("t:select()") -- {1, 'new'}, {2, 'new'}
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 t:truncate()
 ---
@@ -3929,11 +3929,11 @@ _ = t:create_index('sk', {parts = {2, 'unsigned'}, unique = true})
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("t:insert{1, 2}")
 ---
@@ -3945,7 +3945,7 @@ c2("t:insert{2, 2}")
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit() -- rollback
 ---
@@ -4034,19 +4034,19 @@ c4 = txn_proxy.new()
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c4:begin()
 ---
-- 
+- null
 ...
 box.stat.vinyl().tx.read_views -- 0 (no read views needed)
 ---
@@ -4094,7 +4094,7 @@ box.stat.vinyl().tx.transactions -- 4
 ...
 c4:commit()
 ---
-- 
+- null
 ...
 box.stat.vinyl().tx.read_views -- 1 (one read view for all TXs)
 ---
@@ -4106,7 +4106,7 @@ box.stat.vinyl().tx.transactions -- 3
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 box.stat.vinyl().tx.read_views -- 1 (one read view for all TXs)
 ---
@@ -4118,7 +4118,7 @@ box.stat.vinyl().tx.transactions -- 2
 ...
 c2:rollback()
 ---
-- 
+- null
 ...
 box.stat.vinyl().tx.read_views -- 1 (one read view for all TXs)
 ---
@@ -4130,7 +4130,7 @@ box.stat.vinyl().tx.transactions -- 1
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 box.stat.vinyl().tx.read_views -- 0 (no read views needed)
 ---
diff --git a/test/vinyl/mvcc.test.lua b/test/vinyl/mvcc.test.lua
index 79b1f3428..9455e66fa 100644
--- a/test/vinyl/mvcc.test.lua
+++ b/test/vinyl/mvcc.test.lua
@@ -73,7 +73,7 @@ for i = 1,100 do
     c1(string.format("t:delete{%d}", i))
 end;
 for i = 1,100 do
-    assert(#c1(string.format("t:get{%d}", i)) == 0)
+    assert(c1(string.format("t:get{%d}", i)) == nil)
 end;
 test_run:cmd("setopt delimiter ''");
 
@@ -88,7 +88,7 @@ for i = 1,100 do
 end;
 c1:rollback();
 for i = 1,100 do
-    assert(#c1(string.format("t:get{%d}", i)) == 0)
+    assert(c1(string.format("t:get{%d}", i)) == nil)
 end;
 test_run:cmd("setopt delimiter ''");
 
diff --git a/test/vinyl/tx_conflict.result b/test/vinyl/tx_conflict.result
index 03cc62a49..7809cb1e3 100644
--- a/test/vinyl/tx_conflict.result
+++ b/test/vinyl/tx_conflict.result
@@ -186,7 +186,7 @@ function apply(t, k, op)
         table.insert(order_of_commit, t)
         num_committed = num_committed + 1
         local res = tx.con:commit()
-        if res ~= "" and res[1]['error'] then
+        if res ~= nil and res[1]['error'] then
             tx.conflicted = true
         else
             tx.select_all = s1:select{}
diff --git a/test/vinyl/tx_conflict.test.lua b/test/vinyl/tx_conflict.test.lua
index 9208c256e..a02b87246 100644
--- a/test/vinyl/tx_conflict.test.lua
+++ b/test/vinyl/tx_conflict.test.lua
@@ -153,7 +153,7 @@ function apply(t, k, op)
         table.insert(order_of_commit, t)
         num_committed = num_committed + 1
         local res = tx.con:commit()
-        if res ~= "" and res[1]['error'] then
+        if res ~= nil and res[1]['error'] then
             tx.conflicted = true
         else
             tx.select_all = s1:select{}
diff --git a/test/vinyl/tx_gap_lock.result b/test/vinyl/tx_gap_lock.result
index a456c017e..c1fa996b6 100644
--- a/test/vinyl/tx_gap_lock.result
+++ b/test/vinyl/tx_gap_lock.result
@@ -42,7 +42,7 @@ _ = s:insert{3}
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select()") -- {1}, {3}
 ---
@@ -57,7 +57,7 @@ c("s:select()") -- {1}, {3}
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -71,7 +71,7 @@ _ = s:insert{2}
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select()") -- {1}, {2}
 ---
@@ -86,7 +86,7 @@ c("s:select()") -- {1}, {2}
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -100,7 +100,7 @@ _ = s:insert{3}
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select()") -- {2}, {3}
 ---
@@ -115,7 +115,7 @@ c("s:select()") -- {2}, {3}
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -126,11 +126,11 @@ _ = s:insert{123}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("s:select({}, {iterator = 'GT'})") -- {123}
 ---
@@ -153,11 +153,11 @@ c2("s:select({}, {iterator = 'LT'})") -- {123}
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -176,27 +176,27 @@ _ = s:insert{30}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c4:begin()
 ---
-- 
+- null
 ...
 c5:begin()
 ---
-- 
+- null
 ...
 c6:begin()
 ---
-- 
+- null
 ...
 c1("s:select({10}, {iterator = 'GE'})") -- {10}, {20}, {30}
 ---
@@ -254,11 +254,11 @@ _ = s:replace{15, 2} -- send c2 and c3 to read view
 ...
 c2("s:get(15)") -- none
 ---
-- 
+- null
 ...
 c3("s:get(15)") -- none
 ---
-- 
+- null
 ...
 c4("s:get(15)") -- {15, 2}
 ---
@@ -277,39 +277,39 @@ _ = s:replace{35, 3} -- send c4, c5, and c6 to read view
 ...
 c4("s:get(35)") -- none
 ---
-- 
+- null
 ...
 c5("s:get(35)") -- none
 ---
-- 
+- null
 ...
 c6("s:get(35)") -- none
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 c4:commit()
 ---
-- 
+- null
 ...
 c5:commit()
 ---
-- 
+- null
 ...
 c6:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -328,27 +328,27 @@ _ = s:insert{30}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c4:begin()
 ---
-- 
+- null
 ...
 c5:begin()
 ---
-- 
+- null
 ...
 c6:begin()
 ---
-- 
+- null
 ...
 c1("s:select({30}, {iterator = 'LE'})") -- {30}, {20}, {10}
 ---
@@ -406,11 +406,11 @@ _ = s:replace{25, 2} -- send c2 and c3 to read view
 ...
 c2("s:get(25)") -- none
 ---
-- 
+- null
 ...
 c3("s:get(25)") -- none
 ---
-- 
+- null
 ...
 c4("s:get(25)") -- {25, 2}
 ---
@@ -429,39 +429,39 @@ _ = s:replace{5, 3} -- send c4, c5, and c6 to read view
 ...
 c4("s:get(5)") -- none
 ---
-- 
+- null
 ...
 c5("s:get(5)") -- none
 ---
-- 
+- null
 ...
 c6("s:get(5)") -- none
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 c4:commit()
 ---
-- 
+- null
 ...
 c5:commit()
 ---
-- 
+- null
 ...
 c6:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -474,19 +474,19 @@ for i = 1, 9 do s:insert{i * 10} end
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c4:begin()
 ---
-- 
+- null
 ...
 c1("s:select({20}, {iterator = 'GE', limit = 3})") -- {20}, {30}, {40}
 ---
@@ -562,7 +562,7 @@ _ = s:replace{25, 4} -- send c3 to read view
 ...
 c3("s:get(25)") -- none
 ---
-- 
+- null
 ...
 c4("s:get(25)") -- {25, 4}
 ---
@@ -573,23 +573,23 @@ _ = s:replace{75, 5} -- send c4 to read view
 ...
 c4("s:get(75)") -- none
 ---
-- 
+- null
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 c4:commit()
 ---
-- 
+- null
 ...
 s:drop()
 ---
@@ -620,11 +620,11 @@ _ = s:insert{3, 3}
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c1("s:select({2}, {iterator = 'EQ'})")  -- {2, 1}, {2, 2}, {2, 3}
 ---
@@ -669,11 +669,11 @@ c2("s:select({2}, {iterator = 'REQ'})") -- {2, 3}, {2, 2}, {2, 1}
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 s:drop()
 ---
@@ -708,7 +708,7 @@ gap_lock_count() -- 0
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select({10}, {iterator = 'GE', limit = 4})") -- locks [10, 40]
 ---
@@ -769,11 +769,11 @@ _ = s:insert{25} -- send c to read view
 ...
 c("s:get(25)") -- none
 ---
-- 
+- null
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -797,7 +797,7 @@ gap_lock_count() -- 0
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select({1},  {iterator = 'GT', limit = 1})") -- locks  (1, 10]
 ---
@@ -842,11 +842,11 @@ _ = s:insert{5} -- send c to read view
 ...
 c("s:get(5)") -- none
 ---
-- 
+- null
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -861,7 +861,7 @@ gap_lock_count() -- 0
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select({100}, {iterator = 'GT'})") -- locks (100, +inf)
 ---
@@ -888,11 +888,11 @@ _ = s:insert{1000} -- send c to read view
 ...
 c("s:get(1000)") -- none
 ---
-- 
+- null
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -916,7 +916,7 @@ gap_lock_count() -- 0
 ...
 c:begin()
 ---
-- 
+- null
 ...
 c("s:select({1}, {iterator = 'GE', limit = 2})") -- locks [1, 2]
 ---
@@ -932,7 +932,7 @@ gap_lock_count() -- 1
 ...
 c:commit()
 ---
-- 
+- null
 ...
 s:drop()
 ---
@@ -950,19 +950,19 @@ gap_lock_count() -- 0
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c4:begin()
 ---
-- 
+- null
 ...
 c1("s:select({100}, {iterator = 'GE'})") -- c1: locks [{100}, +inf)
 ---
@@ -1005,11 +1005,11 @@ _ = s:insert{100, 50} -- send c1 and c2 to read view
 ...
 c1("s:get({100, 50})") -- none
 ---
-- 
+- null
 ...
 c2("s:get({100, 50})") -- none
 ---
-- 
+- null
 ...
 c3("s:get({100, 50})") -- {100, 50}
 ---
@@ -1028,7 +1028,7 @@ _ = s:insert{100, 100} -- send c3 to read view
 ...
 c3("s:get({100, 100})") -- none
 ---
-- 
+- null
 ...
 c4("s:get({100, 100})") -- {100, 100}
 ---
@@ -1043,7 +1043,7 @@ _ = s:insert{100, 101} -- send c4 to read view
 ...
 c4("s:get({100, 101})") -- none
 ---
-- 
+- null
 ...
 gap_lock_count() -- 6
 ---
@@ -1051,19 +1051,19 @@ gap_lock_count() -- 6
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 c4:commit()
 ---
-- 
+- null
 ...
 s:truncate()
 ---
@@ -1075,19 +1075,19 @@ gap_lock_count() -- 0
 ...
 c1:begin()
 ---
-- 
+- null
 ...
 c2:begin()
 ---
-- 
+- null
 ...
 c3:begin()
 ---
-- 
+- null
 ...
 c4:begin()
 ---
-- 
+- null
 ...
 c1("s:select({100}, {iterator = 'LE'})") -- c1: locks (-inf, {100}]
 ---
@@ -1130,11 +1130,11 @@ _ = s:insert{100, 150} -- send c1 and c2 to read view
 ...
 c1("s:get({100, 150})") -- none
 ---
-- 
+- null
 ...
 c2("s:get({100, 150})") -- none
 ---
-- 
+- null
 ...
 c3("s:get({100, 150})") -- {100, 150}
 ---
@@ -1153,7 +1153,7 @@ _ = s:insert{100, 100} -- send c3 to read view
 ...
 c3("s:get({100, 100})") -- none
 ---
-- 
+- null
 ...
 c4("s:get({100, 100})") -- {100, 100}
 ---
@@ -1168,7 +1168,7 @@ _ = s:insert{100, 99} -- send c4 to read view
 ...
 c4("s:get({100, 99})") -- none
 ---
-- 
+- null
 ...
 gap_lock_count() -- 6
 ---
@@ -1176,19 +1176,19 @@ gap_lock_count() -- 6
 ...
 c1:commit()
 ---
-- 
+- null
 ...
 c2:commit()
 ---
-- 
+- null
 ...
 c3:commit()
 ---
-- 
+- null
 ...
 c4:commit()
 ---
-- 
+- null
 ...
 s:drop()
 ---
@@ -1391,7 +1391,8 @@ invalid = {};
 for i = 1, TX_COUNT do
     local tx = tx_list[i]
     local v = tx.conn(string.format("s:get({%d, %d})",
-                      conflict[1], conflict[2]))[1]
+                      conflict[1], conflict[2]))
+    v = v ~= nil and v[1] or nil
     local was_aborted = false
     if v == nil or v[PAYLOAD_FIELD] == nil then
         was_aborted = true
diff --git a/test/vinyl/tx_gap_lock.test.lua b/test/vinyl/tx_gap_lock.test.lua
index 4ad558608..b57bf0426 100644
--- a/test/vinyl/tx_gap_lock.test.lua
+++ b/test/vinyl/tx_gap_lock.test.lua
@@ -508,7 +508,8 @@ invalid = {};
 for i = 1, TX_COUNT do
     local tx = tx_list[i]
     local v = tx.conn(string.format("s:get({%d, %d})",
-                      conflict[1], conflict[2]))[1]
+                      conflict[1], conflict[2]))
+    v = v ~= nil and v[1] or nil
     local was_aborted = false
     if v == nil or v[PAYLOAD_FIELD] == nil then
         was_aborted = true
diff --git a/test/vinyl/tx_serial.result b/test/vinyl/tx_serial.result
index 37c3f4467..133fec710 100644
--- a/test/vinyl/tx_serial.result
+++ b/test/vinyl/tx_serial.result
@@ -152,7 +152,7 @@ function apply(t, k, op)
         table.insert(order_of_commit, t)
         num_committed = num_committed + 1
         local res = tx.con:commit()
-        if res ~= "" and res[1]['error'] then
+        if res ~= nil and res[1]['error'] then
             tx.conflicted = true
         else
             tx.select_all = s1:select{}
diff --git a/test/vinyl/tx_serial.test.lua b/test/vinyl/tx_serial.test.lua
index 0a695a7e5..7395ef36a 100644
--- a/test/vinyl/tx_serial.test.lua
+++ b/test/vinyl/tx_serial.test.lua
@@ -123,7 +123,7 @@ function apply(t, k, op)
         table.insert(order_of_commit, t)
         num_committed = num_committed + 1
         local res = tx.con:commit()
-        if res ~= "" and res[1]['error'] then
+        if res ~= nil and res[1]['error'] then
             tx.conflicted = true
         else
             tx.select_all = s1:select{}
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 73e5d2c31..3a427263e 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -121,12 +121,10 @@ yaml_get_bool(const char *str, const size_t len)
 
 /**
  * Verify whether a string represents a null literal in YAML.
- *
- * Non-standard: don't match an empty string as null.
  */
 static yaml_type
 yaml_get_null(const char *str, const size_t len){
-   if (len == 1 && str[0] == '~')
+   if (len == 0 || (len == 1 && str[0] == '~'))
       return YAML_NULL;
    if (len == 4 && (strcmp(str, "null") == 0 || strcmp(str, "Null") == 0 ||
        strcmp(str, "NULL") == 0))
@@ -259,15 +257,7 @@ static void load_scalar(struct lua_yaml_loader *loader) {
 
    if (loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
       yaml_type type;
-      if (!length) {
-         /*
-          * Non-standard: an empty value/document is null
-          * according to the standard, but we decode it as an
-          * empty string.
-          */
-         lua_pushliteral(loader->L, "");
-         return;
-      } else if (yaml_get_null(str, length) == YAML_NULL) {
+      if (yaml_get_null(str, length) == YAML_NULL) {
          luaL_pushnull(loader->L);
          return;
       } else if ((type = yaml_get_bool(str, length)) != YAML_NO_MATCH) {
@@ -407,8 +397,14 @@ static void load(struct lua_yaml_loader *loader) {
       if (!do_parse(loader))
          return;
 
-      if (loader->event.type == YAML_STREAM_END_EVENT)
+      if (loader->event.type == YAML_STREAM_END_EVENT) {
+         if (loader->document_count == 0) {
+            /* Return null, not zero values count. */
+            loader->document_count++;
+            luaL_pushnull(loader->L);
+         }
          return;
+      }
 
       loader->document_count++;
       if (load_node(loader) != 1)
-- 
2.20.1

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes
  2019-01-22  2:12 [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes Alexander Turenko
                   ` (2 preceding siblings ...)
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 3/3] lua-yaml: treat an empty document/value as null Alexander Turenko
@ 2019-01-24 21:26 ` Vladislav Shpilevoy
  2019-02-25 11:27 ` Kirill Yukhin
  4 siblings, 0 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-24 21:26 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko

Hi! Thanks for the patchset.

On 22/01/2019 05:12, Alexander Turenko wrote:
> https://github.com/tarantool/tarantool/issues/3476
> https://github.com/tarantool/tarantool/issues/3662
> https://github.com/tarantool/tarantool/issues/3583
> https://github.com/tarantool/tarantool/tree/kh/gh-3662-yaml-2.1

Please, next time write a cover-letter.

> 
> AKhatskevich (1):
>    lua-yaml: verify arguments count
> 
> Alexander Turenko (2):
>    lua-yaml: fix boolean/null representation in yaml
>    lua-yaml: treat an empty document/value as null
> 
>   test/app-tap/console.test.lua   |   37 +-
>   test/app/fio.result             |    2 +-
>   test/app/socket.result          |   26 +-
>   test/box/access.result          |    8 +-
>   test/box/misc.result            |    2 +-
>   test/box/role.result            |    8 +-
>   test/box/sequence.result        |    2 +-
>   test/vinyl/errinj_tx.result     |   10 +-
>   test/vinyl/hermitage.result     |  122 ++--
>   test/vinyl/mvcc.result          | 1066 +++++++++++++++----------------
>   test/vinyl/mvcc.test.lua        |    4 +-
>   test/vinyl/tx_conflict.result   |    2 +-
>   test/vinyl/tx_conflict.test.lua |    2 +-
>   test/vinyl/tx_gap_lock.result   |  189 +++---
>   test/vinyl/tx_gap_lock.test.lua |    3 +-
>   test/vinyl/tx_serial.result     |    2 +-
>   test/vinyl/tx_serial.test.lua   |    2 +-
>   third_party/lua-yaml/lyaml.cc   |   87 ++-
>   18 files changed, 819 insertions(+), 755 deletions(-)
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 3/3] lua-yaml: treat an empty document/value as null
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 3/3] lua-yaml: treat an empty document/value as null Alexander Turenko
@ 2019-01-24 21:26   ` Vladislav Shpilevoy
  2019-02-05  3:30     ` Alexander Turenko
  0 siblings, 1 reply; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-24 21:26 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko

Thanks for the patch!

On 22/01/2019 05:12, Alexander Turenko wrote:
> It improves our compatibility with the YAML standard.
> 
> yaml.encode('') result changed from
> 
> ---
> ...
> 
> to
> 
> --- ''
> ...
> 
> yaml.decode('') returns zero values count before, now it returns
> box.NULL. yaml.decode('- ') returns {''} before, now {box.NULL}.
> 
> This commit touches many tests and test result files, which use console,
> without behaviour changes. It also adds two test cases to
> app-tap/console.test.lua.

Not sure if it makes sense listing factual changes here, because
literally the same I see below and in "git diff --stat".

> ---
>   test/app-tap/console.test.lua   |   16 +-
>   test/app/fio.result             |    2 +-
>   test/app/socket.result          |   26 +-
>   test/box/access.result          |    8 +-
>   test/box/role.result            |    8 +-
>   test/box/sequence.result        |    2 +-
>   test/vinyl/errinj_tx.result     |   10 +-
>   test/vinyl/hermitage.result     |  122 ++--
>   test/vinyl/mvcc.result          | 1066 +++++++++++++++----------------
>   test/vinyl/mvcc.test.lua        |    4 +-
>   test/vinyl/tx_conflict.result   |    2 +-
>   test/vinyl/tx_conflict.test.lua |    2 +-
>   test/vinyl/tx_gap_lock.result   |  189 +++---
>   test/vinyl/tx_gap_lock.test.lua |    3 +-
>   test/vinyl/tx_serial.result     |    2 +-
>   test/vinyl/tx_serial.test.lua   |    2 +-
>   third_party/lua-yaml/lyaml.cc   |   22 +-
>   17 files changed, 747 insertions(+), 739 deletions(-)
> 
> diff --git a/test/vinyl/mvcc.result b/test/vinyl/mvcc.result
> index 1941744b9..db7fc8e83 100644
> --- a/test/vinyl/mvcc.result
> +++ b/test/vinyl/mvcc.result
> @@ -41,29 +41,29 @@ t = box.space.test
>   --
>   c1:begin()
>   ---
> -- 
> +- null

How about to start returning nothing instead of returning null so
as to do not change the tests (or at least make the diff smaller)?

I see, that most of the diff is from txn_proxy, which always does
return 'something'. You can change it so as to do not just return, but
check if a result is not null, and only then return. Otherwise do nothing
or empty return.

I am sure it will work since space:get(), for example, still can
return nothing (!= null), and this is why other tests didn't fail.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 1/3] lua-yaml: verify arguments count
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 1/3] lua-yaml: verify arguments count Alexander Turenko
@ 2019-01-24 21:26   ` Vladislav Shpilevoy
  2019-02-05  3:29     ` Alexander Turenko
  0 siblings, 1 reply; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-24 21:26 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko; +Cc: AKhatskevich

Thanks for the patch!

On 22/01/2019 05:12, Alexander Turenko wrote:
> From: AKhatskevich <avkhatskevich@tarantool.org>
> 
> Added arguments count check for yaml.encode() and decode.decode()

Typo: 'decode.decode' -> 'yaml.decode'.

> functions.
> 
> Without these checks the functions could read garbage outside of a Lua
> stack when called w/o arguments.

Honestly, I do not understand how is it possible. Please,
provide a test for both functions. See my 3 doubts below.

> ---
>   third_party/lua-yaml/lyaml.cc | 7 ++++---
>   1 file changed, 4 insertions(+), 3 deletions(-)
> 
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index c6d118a79..9b07992d8 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -400,7 +400,8 @@ static void load(struct lua_yaml_loader *loader) {
>    */
>   static int l_load(lua_State *L) {
>      struct lua_yaml_loader loader;
> -   if (! lua_isstring(L, 1)) {
> +   int top = lua_gettop(L);
> +   if (!(top == 1 || top == 2) || !lua_isstring(L, 1)) {

1. How could the old code lead to a bug, if there was a
check if the first argument is a string? The second argument
is not used until the next hunk, about which see my next
comment

>   usage_error:
>         return luaL_error(L, "Usage: yaml.decode(document, "\
>                           "[{tag_only = boolean}])");
> @@ -416,7 +417,7 @@ usage_error:
>         return luaL_error(L, OOM_ERRMSG);
>      yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>      bool tag_only;
> -   if (lua_gettop(L) > 1) {
> +   if (lua_gettop(L) == 2) {

2. This function never touches anything beyond second value on
the stack, so here lua_gettop(L) > 1 means the same as
lua_gettop(L) == 2 - the second argument exist. Third and next
values do not matter.

>         if (! lua_istable(L, 2))
>            goto usage_error;
>         lua_getfield(L, 2, "tag_only");
> @@ -794,7 +795,7 @@ error:
>   static int l_dump(lua_State *L) {
>      struct luaL_serializer *serializer = luaL_checkserializer(L);
>      int top = lua_gettop(L);
> -   if (top > 2) {
> +   if (!(top == 1 || top == 2)) {

3. Here my reasoning is the same - the previous checking works
as well.

>   usage_error:
>         return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
>                           "tag_handle = <string>})");
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml Alexander Turenko
@ 2019-01-24 21:26   ` Vladislav Shpilevoy
  2019-01-24 21:32     ` Vladislav Shpilevoy
  2019-02-05  3:29     ` Alexander Turenko
  0 siblings, 2 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-24 21:26 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko

Thanks for the patch!

On 22/01/2019 05:12, Alexander Turenko wrote:
> Encode changes:
> 
> Wrap the following string literals into single quotes: 'false', 'no',
> 'true', 'yes', '~', 'null', 'Null', 'NULL'. The content of these strings
> is equals to null or boolean values representation in YAML.
> 
> Reverted the a2d7643c commit to use single quotes instead of multiline
> encoding for 'true' and 'false' string literals.
> 
> '~', 'null', 'Null', 'NULL', 'no' and 'yes' were encoded w/o quotes
> before, so '~', 'null', 'no', 'yes' were decoded with a wrong type then.
> Multiline encoding was used for 'true' and 'false'.

Unfortunately, I hardly understood what happened in this commit
by reading this message, sorry. Maybe I am confused by imperative
in the message body, or by too frequent repetition of this infinite
sequence of strings: "'~', 'null', 'Null', 'NULL', 'no' and 'yes'",
or by too mixed style of narration of what is the true and standard
way of these values encoding - with quotes or without.

Could you, please, make it simpler somehow?

> 
> Decode changes:
> 
> Treat 'Null', 'NULL' as null in addition to '~' and 'null'.
> 
> Note: this commit leaves an empty document/value treatment as an empty
> string, which is not standard (should be treated as null). This is fixed
> in the following commit.
> 
> Follows up #3476
> Closes #3662
> Closes #3583
> ---
>   test/app-tap/console.test.lua | 23 +++++++---
>   test/box/misc.result          |  2 +-
>   third_party/lua-yaml/lyaml.cc | 80 ++++++++++++++++++++++++++---------
>   3 files changed, 80 insertions(+), 25 deletions(-)
> 
> diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
> index 4f915afd7..68f273234 100755
> --- a/test/app-tap/console.test.lua
> +++ b/test/app-tap/console.test.lua
> @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
>   
>   --
>   -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
> +-- gh-3662: yaml.encode encodes booleans with multiline format.
> +-- gh-3583: yaml.encode encodes null incorrectly.
>   --
> -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
> -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')

1. Are you sure that this is the correct behaviour? So now it is
not true that decode(encode(obj)) == obj? Looks utterly confusing.
Or why else you removed these tests?

> +
> +test:is(yaml.encode(false), "--- false\n...\n")
> +test:is(yaml.encode(true), "--- true\n...\n")
> +test:is(yaml.encode('false'), "--- 'false'\n...\n")
> +test:is(yaml.encode('true'), "--- 'true'\n...\n")
> +test:is(yaml.encode(nil), "--- null\n...\n")
> +
> +test:is(yaml.decode('false'), false)
> +test:is(yaml.decode('no'), false)
> +test:is(yaml.decode('true'), true)
> +test:is(yaml.decode('yes'), true)
> +test:is(yaml.decode('~'), nil)
> +test:is(yaml.decode('null'), nil)
> +test:is(yaml.decode('Null'), nil)
> +test:is(yaml.decode('NULL'), nil)
>   
>   box.cfg{
>       listen=IPROTO_SOCKET;
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index 9b07992d8..73e5d2c31 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -94,6 +94,46 @@ struct lua_yaml_dumper {
>      luaL_Buffer yamlbuf;
>   };
>   
> +enum yaml_type {
> +   YAML_NO_MATCH = 0,
> +   YAML_FALSE,
> +   YAML_TRUE,
> +   YAML_NULL
> +};
> +
> +/**
> + * Verify whether a string represents a boolean literal in YAML.
> + *
> + * Non-standard: only subset of YAML 1.1 boolean literals are
> + * treated as boolean values.
> + */
> +static yaml_type
> +yaml_get_bool(const char *str, const size_t len)

2. Usually in such cases we use int 0/-1 returned value
and return the result via an out-parameter. So it should be

     static inline int
     yaml_parse_bool(const char *str, size_t len, bool *value);

Yes, I've changed 'get' to 'parse' and inlined the function,
if you do not mind.

The same for the next function, parsing null. After that you
can remove enum yaml_type.

Which, btw, is not a type, strictly speaking, because it
does not have YAML_INT, YAML_DOUBLE ...

Also, I do not see any point in making 'len' be 'const'.

Scarcely it is a case when you need a parameter, passed by
value instead of pointer, be explicitly const. The compiler
can detect it easily.

> +{
> +   if (len > 5)
> +      return YAML_NO_MATCH;
> +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
> +      return YAML_FALSE;
> +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)

3. I think, it is redundant to firstly check length and then
call strcmp, which also calculates length (by checking zero
byte). We need either use only strcmp(), or use length comparison
+ memcmp. The same below for the null parser.

> +      return YAML_TRUE;
> +   return YAML_NO_MATCH;
> +}
> +
> +/**
> + * Verify whether a string represents a null literal in YAML.
> + *
> + * Non-standard: don't match an empty string as null.
> + */
> +static yaml_type
> +yaml_get_null(const char *str, const size_t len){

4. ?

     static inline int
     yaml_parse_null(const char *str, size_t len, bool *is_null);

> +   if (len == 1 && str[0] == '~')
> +      return YAML_NULL;
> +   if (len == 4 && (strcmp(str, "null") == 0 || strcmp(str, "Null") == 0 ||

5. Hmm. Length of "null" and "Null" is 3, not 4. As I understand,
len does not count terminating zero. And I can prove it - load_scalar()
in case if !strcmp(tag, "str") does lua_pushlstring(loader->L, str, length),
so length == strlen(str).

Even if I am mistaken about it, why two lines above you consider len
of "~" as 1, not 2?

> +       strcmp(str, "NULL") == 0))
> +      return YAML_NULL;
> +   return YAML_NO_MATCH;
> +}
> +
>   static void generate_error_message(struct lua_yaml_loader *loader) {
>      char buf[256];
>      luaL_Buffer b;
> @@ -597,8 +636,13 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>         return dump_table(dumper, &field);
>      case MP_STR:
>         str = lua_tolstring(dumper->L, -1, &len);
> -      if (lua_isnumber(dumper->L, -1)) {
> -         /* string is convertible to number, quote it to preserve type */
> +      if ((yaml_get_null(str, len) == YAML_NULL)
> +         || (yaml_get_bool(str, len) != YAML_NO_MATCH)
> +         || (lua_isnumber(dumper->L, -1))) {
> +         /*
> +          * The string is convertible to a null, a boolean or
> +          * a number, quote it to preserve its type.
> +          */

6. I think, it is related to my first comment. So why did you
delete those tests?

>            style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
>            break;
>         }

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-01-24 21:32     ` Vladislav Shpilevoy
  2019-02-05  3:29     ` Alexander Turenko
  1 sibling, 0 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-01-24 21:32 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko


>      static inline int
>      yaml_parse_null(const char *str, size_t len, bool *is_null);
> 
>> +   if (len == 1 && str[0] == '~')
>> +      return YAML_NULL;
>> +   if (len == 4 && (strcmp(str, "null") == 0 || strcmp(str, "Null") == 0 ||
> 
> 5. Hmm. Length of "null" and "Null" is 3, not 4. As I understand,
> len does not count terminating zero. And I can prove it - load_scalar()
> in case if !strcmp(tag, "str") does lua_pushlstring(loader->L, str, length),
> so length == strlen(str).
> 
> Even if I am mistaken about it, why two lines above you consider len
> of "~" as 1, not 2?

Sorry, my fault. I was too tired. Of course, strlen('null') == 4. I
counted 'll' for one. But please, still consider my point about
strcmp -> memcmp.

> 
>> +       strcmp(str, "NULL") == 0))
>> +      return YAML_NULL;
>> +   return YAML_NO_MATCH;
>> +}
>> +

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 1/3] lua-yaml: verify arguments count
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-02-05  3:29     ` Alexander Turenko
  2019-02-05 19:36       ` Vladislav Shpilevoy
  0 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-02-05  3:29 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches, AKhatskevich

Hi!

See answers below.

I'll send the updated patchset as v3 soon.

WBR, Alexander Turenko.

On Fri, Jan 25, 2019 at 12:26:53AM +0300, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> On 22/01/2019 05:12, Alexander Turenko wrote:
> > From: AKhatskevich <avkhatskevich@tarantool.org>
> > 
> > Added arguments count check for yaml.encode() and decode.decode()
> 
> Typo: 'decode.decode' -> 'yaml.decode'.

Thx. Fixed.

> 
> > functions.
> > 
> > Without these checks the functions could read garbage outside of a Lua
> > stack when called w/o arguments.
> 
> Honestly, I do not understand how is it possible. Please,
> provide a test for both functions. See my 3 doubts below.

lua_isstring(L, 1) checks a garbage w/o preliminary lua_gettop() check.
yaml.encode() gives me "unsupported Lua type 'thread'" on the current
tarantool 2.1.

Anyway, added bad API usage test cases. Also I changed this:

diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 3a427263e..46374970f 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -453,7 +453,7 @@ usage_error:
       return luaL_error(L, OOM_ERRMSG);
    yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
    bool tag_only;
-   if (lua_gettop(L) == 2) {
+   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
       if (! lua_istable(L, 2))
          goto usage_error;
       lua_getfield(L, 2, "tag_only");

We should not raise an usage error for yaml.decode(object, nil).

> 
> > ---
> >   third_party/lua-yaml/lyaml.cc | 7 ++++---
> >   1 file changed, 4 insertions(+), 3 deletions(-)
> > 
> > diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> > index c6d118a79..9b07992d8 100644
> > --- a/third_party/lua-yaml/lyaml.cc
> > +++ b/third_party/lua-yaml/lyaml.cc
> > @@ -400,7 +400,8 @@ static void load(struct lua_yaml_loader *loader) {
> >    */
> >   static int l_load(lua_State *L) {
> >      struct lua_yaml_loader loader;
> > -   if (! lua_isstring(L, 1)) {
> > +   int top = lua_gettop(L);
> > +   if (!(top == 1 || top == 2) || !lua_isstring(L, 1)) {
> 
> 1. How could the old code lead to a bug, if there was a
> check if the first argument is a string? The second argument

It does not check a stack top (arguments count).

> is not used until the next hunk, about which see my next
> comment

As I see it is usual way to write such functions: check whether count of
arguments and types of mandatory arguments are valid at start of the
function.

> 
> >   usage_error:
> >         return luaL_error(L, "Usage: yaml.decode(document, "\
> >                           "[{tag_only = boolean}])");
> > @@ -416,7 +417,7 @@ usage_error:
> >         return luaL_error(L, OOM_ERRMSG);
> >      yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
> >      bool tag_only;
> > -   if (lua_gettop(L) > 1) {
> > +   if (lua_gettop(L) == 2) {
> 
> 2. This function never touches anything beyond second value on
> the stack, so here lua_gettop(L) > 1 means the same as
> lua_gettop(L) == 2 - the second argument exist. Third and next
> values do not matter.

I read this as 'those are equivalent' (correct me if I'm wrong). Ok. I'd
prefer to leave it with ==. Also note the fix I pasted above.

> 
> >         if (! lua_istable(L, 2))
> >            goto usage_error;
> >         lua_getfield(L, 2, "tag_only");
> > @@ -794,7 +795,7 @@ error:
> >   static int l_dump(lua_State *L) {
> >      struct luaL_serializer *serializer = luaL_checkserializer(L);
> >      int top = lua_gettop(L);
> > -   if (top > 2) {
> > +   if (!(top == 1 || top == 2)) {
> 
> 3. Here my reasoning is the same - the previous checking works
> as well.

It will not give an error in case of yaml.encode() and yaml.encode({},
{}, {}).

> 
> >   usage_error:
> >         return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
> >                           "tag_handle = <string>})");
> > 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
  2019-01-24 21:32     ` Vladislav Shpilevoy
@ 2019-02-05  3:29     ` Alexander Turenko
  2019-02-05 19:36       ` Vladislav Shpilevoy
  1 sibling, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-02-05  3:29 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi!

I dropped support of 'Null' and 'NULL' as null values to stick with the
old approach (only 'null' and '~' are decoded as nulls). At least while
changes are not requested explicitly it is better to keep backward
compatibility even if some parts of behaviour is not standard.

Answered your comments below.

I'll resend the updated patchset as v3 soon.

WBR, Alexander Turenko.

On Fri, Jan 25, 2019 at 12:26:56AM +0300, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> On 22/01/2019 05:12, Alexander Turenko wrote:
> > Encode changes:
> > 
> > Wrap the following string literals into single quotes: 'false', 'no',
> > 'true', 'yes', '~', 'null', 'Null', 'NULL'. The content of these strings
> > is equals to null or boolean values representation in YAML.
> > 
> > Reverted the a2d7643c commit to use single quotes instead of multiline
> > encoding for 'true' and 'false' string literals.
> > 
> > '~', 'null', 'Null', 'NULL', 'no' and 'yes' were encoded w/o quotes
> > before, so '~', 'null', 'no', 'yes' were decoded with a wrong type then.
> > Multiline encoding was used for 'true' and 'false'.
> 
> Unfortunately, I hardly understood what happened in this commit
> by reading this message, sorry. Maybe I am confused by imperative
> in the message body, or by too frequent repetition of this infinite
> sequence of strings: "'~', 'null', 'Null', 'NULL', 'no' and 'yes'",
> or by too mixed style of narration of what is the true and standard
> way of these values encoding - with quotes or without.
> 
> Could you, please, make it simpler somehow?

You are right, it was horrible. I had changed wording and reflected the
patch changes (described above).

```
lua-yaml: fix strings literals encoding in yaml

yaml.encode() now wraps a string literal whose content is equal to a
null or a boolean value representation in YAML into single quotes. Those
literals are: 'false', 'no', 'true', 'yes', '~', 'null'.

Reverted the a2d7643c commit to use single quotes instead of multiline
encoding for 'true' and 'false' string literals.

Follows up #3476
Closes #3662
Closes #3583
```

> 
> > 
> > Decode changes:
> > 
> > Treat 'Null', 'NULL' as null in addition to '~' and 'null'.
> > 
> > Note: this commit leaves an empty document/value treatment as an empty
> > string, which is not standard (should be treated as null). This is fixed
> > in the following commit.
> > 
> > Follows up #3476
> > Closes #3662
> > Closes #3583
> > ---
> >   test/app-tap/console.test.lua | 23 +++++++---
> >   test/box/misc.result          |  2 +-
> >   third_party/lua-yaml/lyaml.cc | 80 ++++++++++++++++++++++++++---------
> >   3 files changed, 80 insertions(+), 25 deletions(-)
> > 
> > diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
> > index 4f915afd7..68f273234 100755
> > --- a/test/app-tap/console.test.lua
> > +++ b/test/app-tap/console.test.lua
> > @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
> >   --
> >   -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
> > +-- gh-3662: yaml.encode encodes booleans with multiline format.
> > +-- gh-3583: yaml.encode encodes null incorrectly.
> >   --
> > -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
> > -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
> > -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> > -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> 
> 1. Are you sure that this is the correct behaviour? So now it is
> not true that decode(encode(obj)) == obj? Looks utterly confusing.
> Or why else you removed these tests?

They are work as before, but now superseded with simpler ones.

> 
> > +
> > +test:is(yaml.encode(false), "--- false\n...\n")
> > +test:is(yaml.encode(true), "--- true\n...\n")
> > +test:is(yaml.encode('false'), "--- 'false'\n...\n")
> > +test:is(yaml.encode('true'), "--- 'true'\n...\n")
> > +test:is(yaml.encode(nil), "--- null\n...\n")
> > +
> > +test:is(yaml.decode('false'), false)
> > +test:is(yaml.decode('no'), false)
> > +test:is(yaml.decode('true'), true)
> > +test:is(yaml.decode('yes'), true)
> > +test:is(yaml.decode('~'), nil)
> > +test:is(yaml.decode('null'), nil)
> > +test:is(yaml.decode('Null'), nil)
> > +test:is(yaml.decode('NULL'), nil)
> >   box.cfg{
> >       listen=IPROTO_SOCKET;
> > diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> > index 9b07992d8..73e5d2c31 100644
> > --- a/third_party/lua-yaml/lyaml.cc
> > +++ b/third_party/lua-yaml/lyaml.cc
> > @@ -94,6 +94,46 @@ struct lua_yaml_dumper {
> >      luaL_Buffer yamlbuf;
> >   };
> > +enum yaml_type {
> > +   YAML_NO_MATCH = 0,
> > +   YAML_FALSE,
> > +   YAML_TRUE,
> > +   YAML_NULL
> > +};
> > +
> > +/**
> > + * Verify whether a string represents a boolean literal in YAML.
> > + *
> > + * Non-standard: only subset of YAML 1.1 boolean literals are
> > + * treated as boolean values.
> > + */
> > +static yaml_type
> > +yaml_get_bool(const char *str, const size_t len)
> 
> 2. Usually in such cases we use int 0/-1 returned value
> and return the result via an out-parameter. So it should be
> 
>     static inline int
>     yaml_parse_bool(const char *str, size_t len, bool *value);
> 
> Yes, I've changed 'get' to 'parse' and inlined the function,
> if you do not mind.
> 
> The same for the next function, parsing null. After that you
> can remove enum yaml_type.
> 
> Which, btw, is not a type, strictly speaking, because it
> does not have YAML_INT, YAML_DOUBLE ...
> 
> Also, I do not see any point in making 'len' be 'const'.
> 
> Scarcely it is a case when you need a parameter, passed by
> value instead of pointer, be explicitly const. The compiler
> can detect it easily.

Agreed with all these comments and fixed them.

> 
> > +{
> > +   if (len > 5)
> > +      return YAML_NO_MATCH;
> > +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
> > +      return YAML_FALSE;
> > +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
> 
> 3. I think, it is redundant to firstly check length and then
> call strcmp, which also calculates length (by checking zero
> byte). We need either use only strcmp(), or use length comparison
> + memcmp. The same below for the null parser.

Okay, it was the premature optimization.

The more important thing is that is was incorrect: "fals" gives
YAML_FALSE.

I had fixed it like so, is it okay?

```
if ((len == 5 && memcmp(str, "false", 5) == 0) ||
    (len == 2 && memcmp(str, "no", 2) == 0)) {
   if (out != NULL)
      *out = false;
   return 0;
}
...
```

> 
> > +      return YAML_TRUE;
> > +   return YAML_NO_MATCH;
> > +}
> > +
> > +/**
> > + * Verify whether a string represents a null literal in YAML.
> > + *
> > + * Non-standard: don't match an empty string as null.
> > + */
> > +static yaml_type
> > +yaml_get_null(const char *str, const size_t len){
> 
> 4. ?
> 
>     static inline int
>     yaml_parse_null(const char *str, size_t len, bool *is_null);

is_null is redundant here, because a return code contain all needed
information. Agreed except that. Changed.

> 
> > +   if (len == 1 && str[0] == '~')
> > +      return YAML_NULL;
> > +   if (len == 4 && (strcmp(str, "null") == 0 || strcmp(str, "Null") == 0 ||
> 
> 5. Hmm. Length of "null" and "Null" is 3, not 4. As I understand,
> len does not count terminating zero. And I can prove it - load_scalar()
> in case if !strcmp(tag, "str") does lua_pushlstring(loader->L, str, length),
> so length == strlen(str).
> 
> Even if I am mistaken about it, why two lines above you consider len
> of "~" as 1, not 2?

Just for the record: you fixed youself in a separate email: null is 4
chars length.

> 
> > +       strcmp(str, "NULL") == 0))
> > +      return YAML_NULL;
> > +   return YAML_NO_MATCH;
> > +}
> > +
> >   static void generate_error_message(struct lua_yaml_loader *loader) {
> >      char buf[256];
> >      luaL_Buffer b;
> > @@ -597,8 +636,13 @@ static int dump_node(struct lua_yaml_dumper *dumper)
> >         return dump_table(dumper, &field);
> >      case MP_STR:
> >         str = lua_tolstring(dumper->L, -1, &len);
> > -      if (lua_isnumber(dumper->L, -1)) {
> > -         /* string is convertible to number, quote it to preserve type */
> > +      if ((yaml_get_null(str, len) == YAML_NULL)
> > +         || (yaml_get_bool(str, len) != YAML_NO_MATCH)
> > +         || (lua_isnumber(dumper->L, -1))) {
> > +         /*
> > +          * The string is convertible to a null, a boolean or
> > +          * a number, quote it to preserve its type.
> > +          */
> 
> 6. I think, it is related to my first comment. So why did you
> delete those tests?

No-no, see my answer for the first comment.

> 
> >            style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
> >            break;
> >         }

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 3/3] lua-yaml: treat an empty document/value as null
  2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
@ 2019-02-05  3:30     ` Alexander Turenko
  0 siblings, 0 replies; 23+ messages in thread
From: Alexander Turenko @ 2019-02-05  3:30 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi!

Thanks. Your scepsis pushed me to rethink the change and I decided to
discard this patch. See below for more info.

WBR, Alexander Turenko.

On Fri, Jan 25, 2019 at 12:26:49AM +0300, Vladislav Shpilevoy wrote:
> Thanks for the patch!
> 
> On 22/01/2019 05:12, Alexander Turenko wrote:
> > It improves our compatibility with the YAML standard.
> > 
> > yaml.encode('') result changed from
> > 
> > ---
> > ...
> > 
> > to
> > 
> > --- ''
> > ...
> > 
> > yaml.decode('') returns zero values count before, now it returns
> > box.NULL. yaml.decode('- ') returns {''} before, now {box.NULL}.
> > 
> > This commit touches many tests and test result files, which use console,
> > without behaviour changes. It also adds two test cases to
> > app-tap/console.test.lua.
> 
> Not sure if it makes sense listing factual changes here, because
> literally the same I see below and in "git diff --stat".

The idea of the message was to mark which test changes are just due to
the change and which one are specifically designed to test the new
feature. I tried to clarify that and would rewrite as follows (but
dropped the patch, see below).

```
Several test cases were added to the app-tap/console.test.lua test,
other test and test result changes are just induced by the encode/decode
behaviour change.
```

> 
> > ---
> >   test/app-tap/console.test.lua   |   16 +-
> >   test/app/fio.result             |    2 +-
> >   test/app/socket.result          |   26 +-
> >   test/box/access.result          |    8 +-
> >   test/box/role.result            |    8 +-
> >   test/box/sequence.result        |    2 +-
> >   test/vinyl/errinj_tx.result     |   10 +-
> >   test/vinyl/hermitage.result     |  122 ++--
> >   test/vinyl/mvcc.result          | 1066 +++++++++++++++----------------
> >   test/vinyl/mvcc.test.lua        |    4 +-
> >   test/vinyl/tx_conflict.result   |    2 +-
> >   test/vinyl/tx_conflict.test.lua |    2 +-
> >   test/vinyl/tx_gap_lock.result   |  189 +++---
> >   test/vinyl/tx_gap_lock.test.lua |    3 +-
> >   test/vinyl/tx_serial.result     |    2 +-
> >   test/vinyl/tx_serial.test.lua   |    2 +-
> >   third_party/lua-yaml/lyaml.cc   |   22 +-
> >   17 files changed, 747 insertions(+), 739 deletions(-)
> > 
> > diff --git a/test/vinyl/mvcc.result b/test/vinyl/mvcc.result
> > index 1941744b9..db7fc8e83 100644
> > --- a/test/vinyl/mvcc.result
> > +++ b/test/vinyl/mvcc.result
> > @@ -41,29 +41,29 @@ t = box.space.test
> >   --
> >   c1:begin()
> >   ---
> > -- 
> > +- null
> 
> How about to start returning nothing instead of returning null so
> as to do not change the tests (or at least make the diff smaller)?
> 
> I see, that most of the diff is from txn_proxy, which always does
> return 'something'. You can change it so as to do not just return, but
> check if a result is not null, and only then return. Otherwise do nothing
> or empty return.
> 
> I am sure it will work since space:get(), for example, still can
> return nothing (!= null), and this is why other tests didn't fail.

Changing tests is not the main question here. The question is what is
the right thing to do with encoding of an empty document. It is null
according to the standard. But our approach allows to encode null and
empty values set differently.

I factored out this patch from the Alex's Kh. original commit to discuss
it separately. After a yesterday discussion with Vladimir I understood
that current approach has its benefit (see above). So there are no
reason to change it at least until it will be requested by someone. I
dropped the patch from the patchset.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-02-05  3:29     ` Alexander Turenko
@ 2019-02-05 19:36       ` Vladislav Shpilevoy
  2019-02-15 21:06         ` Vladislav Shpilevoy
  2019-02-18 18:55         ` Alexander Turenko
  0 siblings, 2 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-05 19:36 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Thanks for the fixes!

>>> diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
>>> index 4f915afd7..68f273234 100755
>>> --- a/test/app-tap/console.test.lua
>>> +++ b/test/app-tap/console.test.lua
>>> @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
>>>    --
>>>    -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
>>> +-- gh-3662: yaml.encode encodes booleans with multiline format.
>>> +-- gh-3583: yaml.encode encodes null incorrectly.
>>>    --
>>> -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
>>> -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
>>> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>>> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>>
>> 1. Are you sure that this is the correct behaviour? So now it is
>> not true that decode(encode(obj)) == obj? Looks utterly confusing.
>> Or why else you removed these tests?
> 
> They are work as before, but now superseded with simpler ones.

It is not obvious. I see a test

     yaml.encode(false) == "--- false\n...\n"

I see another test:

     yaml.decode('false') == false

But where is this? -

     yaml.decode("--- false\n...\n") == false

And why "false" == "--- false\n...\n" according to the tests
in the patch?

>>
>>> +{
>>> +   if (len > 5)
>>> +      return YAML_NO_MATCH;
>>> +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
>>> +      return YAML_FALSE;
>>> +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
>>
>> 3. I think, it is redundant to firstly check length and then
>> call strcmp, which also calculates length (by checking zero
>> byte). We need either use only strcmp(), or use length comparison
>> + memcmp. The same below for the null parser.
> 
> Okay, it was the premature optimization.
> 
> The more important thing is that is was incorrect: "fals" gives
> YAML_FALSE.
> 
> I had fixed it like so, is it okay?
> 
> ```
> if ((len == 5 && memcmp(str, "false", 5) == 0) ||
>      (len == 2 && memcmp(str, "no", 2) == 0)) {
>     if (out != NULL)
>        *out = false;
>     return 0;
> }
> ...
> ```

Yes, this fix is ok, thanks.

> 
>>
>>> +      return YAML_TRUE;
>>> +   return YAML_NO_MATCH;
>>> +}
>>> +
>>> +/**
>>> + * Verify whether a string represents a null literal in YAML.
>>> + *
>>> + * Non-standard: don't match an empty string as null.
>>> + */
>>> +static yaml_type
>>> +yaml_get_null(const char *str, const size_t len){
>>
>> 4. ?
>>
>>      static inline int
>>      yaml_parse_null(const char *str, size_t len, bool *is_null);
> 
> is_null is redundant here, because a return code contain all needed
> information. Agreed except that. Changed.

My point was to standardize parser functions so as to always
return 0/-1 on success/error, and always return an actual value
via an out parameter. But now I see, that -1 here is not really an
error, but rather 'not being of a requested type'. So here we
do not have even a notion of 'error'.

In such a case, please, consider the fixes below. Also, I removed
optionality of out parameter in yaml_parse_bool in order to get rid
of 'if'.

diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 5afcace1b..a4e03a8ba 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -96,46 +96,43 @@ struct lua_yaml_dumper {
  
  /**
   * Verify whether a string represents a boolean literal in YAML.
- *
   * Non-standard: only subset of YAML 1.1 boolean literals are
   * treated as boolean values.
- *
- * Return 0 at success, -1 at error.
+ * @param str Literal.
+ * @param len Length of @a str.
+ * @param[out] out Result boolean value.
+ * @retval Whether @a str represents a boolean value or not.
   */
-static inline int
-yaml_parse_bool(const char *str, size_t len, bool *out)
+static inline bool
+yaml_is_bool(const char *str, size_t len, bool *out)
  {
     if ((len == 5 && memcmp(str, "false", 5) == 0) ||
         (len == 2 && memcmp(str, "no", 2) == 0)) {
-      if (out != NULL)
-         *out = false;
-      return 0;
+      *out = false;
+      return true;
     }
     if ((len == 4 && memcmp(str, "true", 4) == 0) ||
         (len == 3 && memcmp(str, "yes", 3) == 0)) {
-      if (out != NULL)
-         *out = true;
-      return 0;
+      *out = true;
+      return true;
     }
-   return -1;
+   return false;
  }
  
  /**
   * Verify whether a string represents a null literal in YAML.
- *
   * Non-standard: don't match an empty string, 'Null' and 'NULL' as
   * null.
- *
- * Return 0 at success, -1 at error.
+ * @retval Whether @a str represents NULL or not.
   */
-static inline int
-yaml_parse_null(const char *str, size_t len)
+static inline bool
+yaml_is_null(const char *str, size_t len)
  {
     if (len == 1 && str[0] == '~')
-      return 0;
+      return true;
     if (len == 4 && memcmp(str, "null", 4) == 0)
-      return 0;
-   return -1;
+      return true;
+   return false;
  }
  
  static void generate_error_message(struct lua_yaml_loader *loader) {
@@ -254,9 +251,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
           return;
        } else if (!strcmp(tag, "bool")) {
           bool value = false;
-         int rc = yaml_parse_bool(str, length, &value);
+         bool rc = yaml_is_bool(str, length, &value);
           (void) rc;
-         assert(rc == 0);
+         assert(rc);
           lua_pushboolean(loader->L, value);
           return;
        } else if (!strcmp(tag, "binary")) {
@@ -275,10 +272,10 @@ static void load_scalar(struct lua_yaml_loader *loader) {
            */
           lua_pushliteral(loader->L, "");
           return;
-      } else if (yaml_parse_null(str, length) == 0) {
+      } else if (yaml_is_null(str, length)) {
           luaL_pushnull(loader->L);
           return;
-      } else if (yaml_parse_bool(str, length, &value) == 0) {
+      } else if (yaml_is_bool(str, length, &value)) {
           lua_pushboolean(loader->L, value);
           return;
        }
@@ -610,6 +607,7 @@ static int dump_node(struct lua_yaml_dumper *dumper)
     yaml_event_t ev;
     yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
     int is_binary = 0;
+   bool unused;
     char buf[FPCONV_G_FMT_BUFSIZE];
     struct luaL_field field;
  
@@ -644,9 +642,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
        return dump_table(dumper, &field);
     case MP_STR:
        str = lua_tolstring(dumper->L, -1, &len);
-      if ((yaml_parse_null(str, len) == 0)
-         || (yaml_parse_bool(str, len, NULL) == 0)
-         || (lua_isnumber(dumper->L, -1))) {
+      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
+          lua_isnumber(dumper->L, -1)) {
           /*
            * The string is convertible to a null, a boolean or
            * a number, quote it to preserve its type.

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 1/3] lua-yaml: verify arguments count
  2019-02-05  3:29     ` Alexander Turenko
@ 2019-02-05 19:36       ` Vladislav Shpilevoy
  2019-02-11 13:32         ` Alexander Turenko
  0 siblings, 1 reply; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-05 19:36 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches, AKhatskevich

Hi! Thanks for the fixes!

>>> functions.
>>>
>>> Without these checks the functions could read garbage outside of a Lua
>>> stack when called w/o arguments.
>>
>> Honestly, I do not understand how is it possible. Please,
>> provide a test for both functions. See my 3 doubts below.
> 
> lua_isstring(L, 1) checks a garbage w/o preliminary lua_gettop() check.
> yaml.encode() gives me "unsupported Lua type 'thread'" on the current
> tarantool 2.1.

I looked at lua_isstring implementation, and I see, that it checks
top. If an index is above top, then the type is nil.

	static TValue *index2adr(lua_State *L, int idx)
	{
	  if (idx > 0) {
	    TValue *o = L->base + (idx - 1);
	    return o < L->top ? o : niltv(L);
	...

> 
> Anyway, added bad API usage test cases. Also I changed this:
> 
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index 3a427263e..46374970f 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -453,7 +453,7 @@ usage_error:
>         return luaL_error(L, OOM_ERRMSG);
>      yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>      bool tag_only;
> -   if (lua_gettop(L) == 2) {
> +   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
>         if (! lua_istable(L, 2))
>            goto usage_error;
>         lua_getfield(L, 2, "tag_only");
> 
> We should not raise an usage error for yaml.decode(object, nil).

Why? It is said, that the second value either does not exist, or
is a table. Nil is not a table. So why? If your logic was about
considering nil as a not existing value, then why don't we handle
cases like this: yaml.decode(object, nil, nil, nil, nil) ? The same
for l_dump() and encode.

>>>    usage_error:
>>>          return luaL_error(L, "Usage: yaml.decode(document, "\
>>>                            "[{tag_only = boolean}])");
>>> @@ -416,7 +417,7 @@ usage_error:
>>>          return luaL_error(L, OOM_ERRMSG);
>>>       yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>>>       bool tag_only;
>>> -   if (lua_gettop(L) > 1) {
>>> +   if (lua_gettop(L) == 2) {
>>
>> 2. This function never touches anything beyond second value on
>> the stack, so here lua_gettop(L) > 1 means the same as
>> lua_gettop(L) == 2 - the second argument exist. Third and next
>> values do not matter.
> 
> I read this as 'those are equivalent' (correct me if I'm wrong). Ok. I'd
> prefer to leave it with ==. Also note the fix I pasted above.

Why? Again. I do not see any reason behind this change except personal
preference. I reverted all the changes about l_load() function, and the
tests passed. So why do we need to make diff bigger?

> 
>>
>>>          if (! lua_istable(L, 2))
>>>             goto usage_error;
>>>          lua_getfield(L, 2, "tag_only");
>>> @@ -794,7 +795,7 @@ error:
>>>    static int l_dump(lua_State *L) {
>>>       struct luaL_serializer *serializer = luaL_checkserializer(L);
>>>       int top = lua_gettop(L);
>>> -   if (top > 2) {
>>> +   if (!(top == 1 || top == 2)) {
>>
>> 3. Here my reasoning is the same - the previous checking works
>> as well.
> 
> It will not give an error in case of yaml.encode() and yaml.encode({},
> {}, {}).

Decent. Here you are right.

> 
>>
>>>    usage_error:
>>>          return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
>>>                            "tag_handle = <string>})");
>>>

My diff, which reverts some changes and makes this patch one-liner:

diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index 354cafe86..854794dd1 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -400,8 +400,7 @@ static void load(struct lua_yaml_loader *loader) {
   */
  static int l_load(lua_State *L) {
     struct lua_yaml_loader loader;
-   int top = lua_gettop(L);
-   if (!(top == 1 || top == 2) || !lua_isstring(L, 1)) {
+   if (! lua_isstring(L, 1)) {
  usage_error:
        return luaL_error(L, "Usage: yaml.decode(document, "\
                          "[{tag_only = boolean}])");
@@ -417,7 +416,7 @@ usage_error:
        return luaL_error(L, OOM_ERRMSG);
     yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
     bool tag_only;
-   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
+   if (lua_gettop(L) > 1) {
        if (! lua_istable(L, 2))
           goto usage_error;
        lua_getfield(L, 2, "tag_only");

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 1/3] lua-yaml: verify arguments count
  2019-02-05 19:36       ` Vladislav Shpilevoy
@ 2019-02-11 13:32         ` Alexander Turenko
  2019-02-15 21:28           ` Vladislav Shpilevoy
  0 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-02-11 13:32 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches, AKhatskevich

lua_is* really checks whether an acceptable index is a valid one, so
there are two possible approaches, one of which we should stick I think:

* Verify lua_gettop() upper and lower bounds right at start of a
  function.
* Use lua_is* (including lua_isnone() and lua_isnoneornil()) and don't
  verify arguments count explicitly.

I think we should use one of these ways within a module: this is more
important then the patch size. The only difference for a user is that
the latter approach does not check for extra arguments.

Now I implemented the latter approach as I see you want to minimize
explicit checks. See the patch at end of the email.

It is possible to reduce the patch further, but loss consistency in what
we check: lua_is* or lua_gettop(). I'll do if you insist, but don't
think it is the right way to proceed.

NB: branch: kh/gh-3662-yaml-2.1

WBR, Alexander Turenko.

On Tue, Feb 05, 2019 at 10:36:41PM +0300, Vladislav Shpilevoy wrote:
> Hi! Thanks for the fixes!
> 
> > > > functions.
> > > > 
> > > > Without these checks the functions could read garbage outside of a Lua
> > > > stack when called w/o arguments.
> > > 
> > > Honestly, I do not understand how is it possible. Please,
> > > provide a test for both functions. See my 3 doubts below.
> > 
> > lua_isstring(L, 1) checks a garbage w/o preliminary lua_gettop() check.
> > yaml.encode() gives me "unsupported Lua type 'thread'" on the current
> > tarantool 2.1.
> 
> I looked at lua_isstring implementation, and I see, that it checks
> top. If an index is above top, then the type is nil.
> 
> 	static TValue *index2adr(lua_State *L, int idx)
> 	{
> 	  if (idx > 0) {
> 	    TValue *o = L->base + (idx - 1);
> 	    return o < L->top ? o : niltv(L);
> 	...
> 

Ouch, lua_isstring() is called only in case of top == 2, so this is out
of scope of the discussion. The real cause of this weird "unsupported
Lua type 'thread'" error is lua_yaml_encode() code: it calls
`lua_newthread(L)` and then `lua_pushvalue(L, 1);`. A 1st stack item
should be a value we encode, but when there are no arguments for
yaml.encode() the new lua thread is the 1st item.

Anyway, I don't think that "unsupported Lua type 'thread'" is the right
error message for `yaml.encode()`. Are you agree?

> > 
> > Anyway, added bad API usage test cases. Also I changed this:
> > 
> > diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> > index 3a427263e..46374970f 100644
> > --- a/third_party/lua-yaml/lyaml.cc
> > +++ b/third_party/lua-yaml/lyaml.cc
> > @@ -453,7 +453,7 @@ usage_error:
> >         return luaL_error(L, OOM_ERRMSG);
> >      yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
> >      bool tag_only;
> > -   if (lua_gettop(L) == 2) {
> > +   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
> >         if (! lua_istable(L, 2))
> >            goto usage_error;
> >         lua_getfield(L, 2, "tag_only");
> > 
> > We should not raise an usage error for yaml.decode(object, nil).
> 
> Why? It is said, that the second value either does not exist, or
> is a table. Nil is not a table. So why? If your logic was about
> considering nil as a not existing value, then why don't we handle
> cases like this: yaml.decode(object, nil, nil, nil, nil) ? The same
> for l_dump() and encode.

There is the difference between `yaml.decode(object, nil)` and
`yaml.decode(object, nil, nil, nil, nil)`. The former one is likely to
appear due to passing though the 2nd argument, say:

```
local function load_cfg(raw, opts)
    local object = yaml.decode(raw, opts)
    ...some post-processing...
    return object
end
```

The latter is definitely wrong usage.

But now I removed checks for extra args, see above.

> 
> > > >    usage_error:
> > > >          return luaL_error(L, "Usage: yaml.decode(document, "\
> > > >                            "[{tag_only = boolean}])");
> > > > @@ -416,7 +417,7 @@ usage_error:
> > > >          return luaL_error(L, OOM_ERRMSG);
> > > >       yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
> > > >       bool tag_only;
> > > > -   if (lua_gettop(L) > 1) {
> > > > +   if (lua_gettop(L) == 2) {
> > > 
> > > 2. This function never touches anything beyond second value on
> > > the stack, so here lua_gettop(L) > 1 means the same as
> > > lua_gettop(L) == 2 - the second argument exist. Third and next
> > > values do not matter.
> > 
> > I read this as 'those are equivalent' (correct me if I'm wrong). Ok. I'd
> > prefer to leave it with ==. Also note the fix I pasted above.
> 
> Why? Again. I do not see any reason behind this change except personal
> preference.

It does not matter much, because I anyway need to add ` && !
lua_isnil(L, 2)` or use `! lua_isnoneornil(L, 2)` here to make decode
behaviour consistent with encode one (against 2nd argument). Yep, it is
personal preference. Anyway, now it is `! lua_isnoneornil(L, 2)`.

> I reverted all the changes about l_load() function, and the
> tests passed. So why do we need to make diff bigger?

yaml.decode('', nil, {}) don't pass before (don't raise an error).
Other tests are passed, because of two reasons:

* no test on yaml.decode('', nil);
* lua_isstring() checks stack size.

Re test: added for encode and decode.

Re lua_isstring(): okay, now I understood that it checks given index by
the API:

http://pgl.yoyo.org/luai/i/lua_isstring ("acceptable index")
https://www.lua.org/manual/5.3/manual.html#4.3 ("Valid and Acceptable Indices")
https://www.lua.org/manual/5.1/manual.html#3.2 (the same for Lua 5.1)

So I changed the description of the commit to make it clear that the
reason of the change is to make the code more consistent.

> 
> > 
> > > 
> > > >          if (! lua_istable(L, 2))
> > > >             goto usage_error;
> > > >          lua_getfield(L, 2, "tag_only");
> > > > @@ -794,7 +795,7 @@ error:
> > > >    static int l_dump(lua_State *L) {
> > > >       struct luaL_serializer *serializer = luaL_checkserializer(L);
> > > >       int top = lua_gettop(L);
> > > > -   if (top > 2) {
> > > > +   if (!(top == 1 || top == 2)) {
> > > 
> > > 3. Here my reasoning is the same - the previous checking works
> > > as well.
> > 
> > It will not give an error in case of yaml.encode() and yaml.encode({},
> > {}, {}).
> 
> Decent. Here you are right.
> 
> > 
> > > 
> > > >    usage_error:
> > > >          return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
> > > >                            "tag_handle = <string>})");
> > > > 
> 
> My diff, which reverts some changes and makes this patch one-liner:
> 
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index 354cafe86..854794dd1 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -400,8 +400,7 @@ static void load(struct lua_yaml_loader *loader) {
>   */
>  static int l_load(lua_State *L) {
>     struct lua_yaml_loader loader;
> -   int top = lua_gettop(L);
> -   if (!(top == 1 || top == 2) || !lua_isstring(L, 1)) {
> +   if (! lua_isstring(L, 1)) {
>  usage_error:
>        return luaL_error(L, "Usage: yaml.decode(document, "\
>                          "[{tag_only = boolean}])");
> @@ -417,7 +416,7 @@ usage_error:
>        return luaL_error(L, OOM_ERRMSG);
>     yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>     bool tag_only;
> -   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
> +   if (lua_gettop(L) > 1) {
>        if (! lua_istable(L, 2))
>           goto usage_error;
>        lua_getfield(L, 2, "tag_only");

----

The new patch description and diff (w/o tests):

lua-yaml: verify args in a consistent manner

Use lua_is*() functions instead of explicit lua_gettop() checks in
yaml.encode() and yaml.decode() functions.

Behaviour changes:

* yaml.decode(object, nil) ignores nil (it is consistent with encode
  behaviour).
* yaml.encode() gives an usage error instead of "unsupported Lua type
  'thread'".
* yaml.encode('', {}, {}) ignores 3rd argument (it is consistent with
  decode behaviour).

diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index c6d118a79..bd876ab29 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -416,7 +416,7 @@ usage_error:
       return luaL_error(L, OOM_ERRMSG);
    yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
    bool tag_only;
-   if (lua_gettop(L) > 1) {
+   if (! lua_isnoneornil(L, 2)) {
       if (! lua_istable(L, 2))
          goto usage_error;
       lua_getfield(L, 2, "tag_only");
@@ -793,14 +793,13 @@ error:
  */
 static int l_dump(lua_State *L) {
    struct luaL_serializer *serializer = luaL_checkserializer(L);
-   int top = lua_gettop(L);
-   if (top > 2) {
+   if (lua_isnone(L, 1)) {
 usage_error:
       return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
                         "tag_handle = <string>})");
    }
    const char *prefix = NULL, *handle = NULL;
-   if (top == 2 && !lua_isnil(L, 2)) {
+   if (! lua_isnoneornil(L, 2)) {
       if (! lua_istable(L, 2))
          goto usage_error;
       lua_getfield(L, 2, "tag_prefix");

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-02-05 19:36       ` Vladislav Shpilevoy
@ 2019-02-15 21:06         ` Vladislav Shpilevoy
  2019-02-15 21:23           ` Alexander Turenko
  2019-02-18 18:55         ` Alexander Turenko
  1 sibling, 1 reply; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-15 21:06 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches

Hi! Any news here?

On 05/02/2019 20:36, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
>>>> diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
>>>> index 4f915afd7..68f273234 100755
>>>> --- a/test/app-tap/console.test.lua
>>>> +++ b/test/app-tap/console.test.lua
>>>> @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
>>>>    --
>>>>    -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
>>>> +-- gh-3662: yaml.encode encodes booleans with multiline format.
>>>> +-- gh-3583: yaml.encode encodes null incorrectly.
>>>>    --
>>>> -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
>>>> -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
>>>> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>>>> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>>>
>>> 1. Are you sure that this is the correct behaviour? So now it is
>>> not true that decode(encode(obj)) == obj? Looks utterly confusing.
>>> Or why else you removed these tests?
>>
>> They are work as before, but now superseded with simpler ones.
> 
> It is not obvious. I see a test
> 
>      yaml.encode(false) == "--- false\n...\n"
> 
> I see another test:
> 
>      yaml.decode('false') == false
> 
> But where is this? -
> 
>      yaml.decode("--- false\n...\n") == false
> 
> And why "false" == "--- false\n...\n" according to the tests
> in the patch?
> 
>>>
>>>> +{
>>>> +   if (len > 5)
>>>> +      return YAML_NO_MATCH;
>>>> +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
>>>> +      return YAML_FALSE;
>>>> +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
>>>
>>> 3. I think, it is redundant to firstly check length and then
>>> call strcmp, which also calculates length (by checking zero
>>> byte). We need either use only strcmp(), or use length comparison
>>> + memcmp. The same below for the null parser.
>>
>> Okay, it was the premature optimization.
>>
>> The more important thing is that is was incorrect: "fals" gives
>> YAML_FALSE.
>>
>> I had fixed it like so, is it okay?
>>
>> ```
>> if ((len == 5 && memcmp(str, "false", 5) == 0) ||
>>      (len == 2 && memcmp(str, "no", 2) == 0)) {
>>     if (out != NULL)
>>        *out = false;
>>     return 0;
>> }
>> ...
>> ```
> 
> Yes, this fix is ok, thanks.
> 
>>
>>>
>>>> +      return YAML_TRUE;
>>>> +   return YAML_NO_MATCH;
>>>> +}
>>>> +
>>>> +/**
>>>> + * Verify whether a string represents a null literal in YAML.
>>>> + *
>>>> + * Non-standard: don't match an empty string as null.
>>>> + */
>>>> +static yaml_type
>>>> +yaml_get_null(const char *str, const size_t len){
>>>
>>> 4. ?
>>>
>>>      static inline int
>>>      yaml_parse_null(const char *str, size_t len, bool *is_null);
>>
>> is_null is redundant here, because a return code contain all needed
>> information. Agreed except that. Changed.
> 
> My point was to standardize parser functions so as to always
> return 0/-1 on success/error, and always return an actual value
> via an out parameter. But now I see, that -1 here is not really an
> error, but rather 'not being of a requested type'. So here we
> do not have even a notion of 'error'.
> 
> In such a case, please, consider the fixes below. Also, I removed
> optionality of out parameter in yaml_parse_bool in order to get rid
> of 'if'.
> 
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index 5afcace1b..a4e03a8ba 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -96,46 +96,43 @@ struct lua_yaml_dumper {
> 
>   /**
>    * Verify whether a string represents a boolean literal in YAML.
> - *
>    * Non-standard: only subset of YAML 1.1 boolean literals are
>    * treated as boolean values.
> - *
> - * Return 0 at success, -1 at error.
> + * @param str Literal.
> + * @param len Length of @a str.
> + * @param[out] out Result boolean value.
> + * @retval Whether @a str represents a boolean value or not.
>    */
> -static inline int
> -yaml_parse_bool(const char *str, size_t len, bool *out)
> +static inline bool
> +yaml_is_bool(const char *str, size_t len, bool *out)
>   {
>      if ((len == 5 && memcmp(str, "false", 5) == 0) ||
>          (len == 2 && memcmp(str, "no", 2) == 0)) {
> -      if (out != NULL)
> -         *out = false;
> -      return 0;
> +      *out = false;
> +      return true;
>      }
>      if ((len == 4 && memcmp(str, "true", 4) == 0) ||
>          (len == 3 && memcmp(str, "yes", 3) == 0)) {
> -      if (out != NULL)
> -         *out = true;
> -      return 0;
> +      *out = true;
> +      return true;
>      }
> -   return -1;
> +   return false;
>   }
> 
>   /**
>    * Verify whether a string represents a null literal in YAML.
> - *
>    * Non-standard: don't match an empty string, 'Null' and 'NULL' as
>    * null.
> - *
> - * Return 0 at success, -1 at error.
> + * @retval Whether @a str represents NULL or not.
>    */
> -static inline int
> -yaml_parse_null(const char *str, size_t len)
> +static inline bool
> +yaml_is_null(const char *str, size_t len)
>   {
>      if (len == 1 && str[0] == '~')
> -      return 0;
> +      return true;
>      if (len == 4 && memcmp(str, "null", 4) == 0)
> -      return 0;
> -   return -1;
> +      return true;
> +   return false;
>   }
> 
>   static void generate_error_message(struct lua_yaml_loader *loader) {
> @@ -254,9 +251,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>            return;
>         } else if (!strcmp(tag, "bool")) {
>            bool value = false;
> -         int rc = yaml_parse_bool(str, length, &value);
> +         bool rc = yaml_is_bool(str, length, &value);
>            (void) rc;
> -         assert(rc == 0);
> +         assert(rc);
>            lua_pushboolean(loader->L, value);
>            return;
>         } else if (!strcmp(tag, "binary")) {
> @@ -275,10 +272,10 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>             */
>            lua_pushliteral(loader->L, "");
>            return;
> -      } else if (yaml_parse_null(str, length) == 0) {
> +      } else if (yaml_is_null(str, length)) {
>            luaL_pushnull(loader->L);
>            return;
> -      } else if (yaml_parse_bool(str, length, &value) == 0) {
> +      } else if (yaml_is_bool(str, length, &value)) {
>            lua_pushboolean(loader->L, value);
>            return;
>         }
> @@ -610,6 +607,7 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>      yaml_event_t ev;
>      yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
>      int is_binary = 0;
> +   bool unused;
>      char buf[FPCONV_G_FMT_BUFSIZE];
>      struct luaL_field field;
> 
> @@ -644,9 +642,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>         return dump_table(dumper, &field);
>      case MP_STR:
>         str = lua_tolstring(dumper->L, -1, &len);
> -      if ((yaml_parse_null(str, len) == 0)
> -         || (yaml_parse_bool(str, len, NULL) == 0)
> -         || (lua_isnumber(dumper->L, -1))) {
> +      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
> +          lua_isnumber(dumper->L, -1)) {
>            /*
>             * The string is convertible to a null, a boolean or
>             * a number, quote it to preserve its type.
> 
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-02-15 21:06         ` Vladislav Shpilevoy
@ 2019-02-15 21:23           ` Alexander Turenko
  0 siblings, 0 replies; 23+ messages in thread
From: Alexander Turenko @ 2019-02-15 21:23 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

Hi! Sorry, but no. I'll update the patch on the next week.

WBR, Alexander Turenko.

On Sat, Feb 16, 2019 at 12:06:48AM +0300, Vladislav Shpilevoy wrote:
> Hi! Any news here?
> 
> On 05/02/2019 20:36, Vladislav Shpilevoy wrote:
> > Thanks for the fixes!
> > 
> > > > > diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
> > > > > index 4f915afd7..68f273234 100755
> > > > > --- a/test/app-tap/console.test.lua
> > > > > +++ b/test/app-tap/console.test.lua
> > > > > @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
> > > > >    --
> > > > >    -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
> > > > > +-- gh-3662: yaml.encode encodes booleans with multiline format.
> > > > > +-- gh-3583: yaml.encode encodes null incorrectly.
> > > > >    --
> > > > > -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
> > > > > -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
> > > > > -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> > > > > -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> > > > 
> > > > 1. Are you sure that this is the correct behaviour? So now it is
> > > > not true that decode(encode(obj)) == obj? Looks utterly confusing.
> > > > Or why else you removed these tests?
> > > 
> > > They are work as before, but now superseded with simpler ones.
> > 
> > It is not obvious. I see a test
> > 
> >      yaml.encode(false) == "--- false\n...\n"
> > 
> > I see another test:
> > 
> >      yaml.decode('false') == false
> > 
> > But where is this? -
> > 
> >      yaml.decode("--- false\n...\n") == false
> > 
> > And why "false" == "--- false\n...\n" according to the tests
> > in the patch?
> > 
> > > > 
> > > > > +{
> > > > > +   if (len > 5)
> > > > > +      return YAML_NO_MATCH;
> > > > > +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
> > > > > +      return YAML_FALSE;
> > > > > +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
> > > > 
> > > > 3. I think, it is redundant to firstly check length and then
> > > > call strcmp, which also calculates length (by checking zero
> > > > byte). We need either use only strcmp(), or use length comparison
> > > > + memcmp. The same below for the null parser.
> > > 
> > > Okay, it was the premature optimization.
> > > 
> > > The more important thing is that is was incorrect: "fals" gives
> > > YAML_FALSE.
> > > 
> > > I had fixed it like so, is it okay?
> > > 
> > > ```
> > > if ((len == 5 && memcmp(str, "false", 5) == 0) ||
> > >      (len == 2 && memcmp(str, "no", 2) == 0)) {
> > >     if (out != NULL)
> > >        *out = false;
> > >     return 0;
> > > }
> > > ...
> > > ```
> > 
> > Yes, this fix is ok, thanks.
> > 
> > > 
> > > > 
> > > > > +      return YAML_TRUE;
> > > > > +   return YAML_NO_MATCH;
> > > > > +}
> > > > > +
> > > > > +/**
> > > > > + * Verify whether a string represents a null literal in YAML.
> > > > > + *
> > > > > + * Non-standard: don't match an empty string as null.
> > > > > + */
> > > > > +static yaml_type
> > > > > +yaml_get_null(const char *str, const size_t len){
> > > > 
> > > > 4. ?
> > > > 
> > > >      static inline int
> > > >      yaml_parse_null(const char *str, size_t len, bool *is_null);
> > > 
> > > is_null is redundant here, because a return code contain all needed
> > > information. Agreed except that. Changed.
> > 
> > My point was to standardize parser functions so as to always
> > return 0/-1 on success/error, and always return an actual value
> > via an out parameter. But now I see, that -1 here is not really an
> > error, but rather 'not being of a requested type'. So here we
> > do not have even a notion of 'error'.
> > 
> > In such a case, please, consider the fixes below. Also, I removed
> > optionality of out parameter in yaml_parse_bool in order to get rid
> > of 'if'.
> > 
> > diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> > index 5afcace1b..a4e03a8ba 100644
> > --- a/third_party/lua-yaml/lyaml.cc
> > +++ b/third_party/lua-yaml/lyaml.cc
> > @@ -96,46 +96,43 @@ struct lua_yaml_dumper {
> > 
> >   /**
> >    * Verify whether a string represents a boolean literal in YAML.
> > - *
> >    * Non-standard: only subset of YAML 1.1 boolean literals are
> >    * treated as boolean values.
> > - *
> > - * Return 0 at success, -1 at error.
> > + * @param str Literal.
> > + * @param len Length of @a str.
> > + * @param[out] out Result boolean value.
> > + * @retval Whether @a str represents a boolean value or not.
> >    */
> > -static inline int
> > -yaml_parse_bool(const char *str, size_t len, bool *out)
> > +static inline bool
> > +yaml_is_bool(const char *str, size_t len, bool *out)
> >   {
> >      if ((len == 5 && memcmp(str, "false", 5) == 0) ||
> >          (len == 2 && memcmp(str, "no", 2) == 0)) {
> > -      if (out != NULL)
> > -         *out = false;
> > -      return 0;
> > +      *out = false;
> > +      return true;
> >      }
> >      if ((len == 4 && memcmp(str, "true", 4) == 0) ||
> >          (len == 3 && memcmp(str, "yes", 3) == 0)) {
> > -      if (out != NULL)
> > -         *out = true;
> > -      return 0;
> > +      *out = true;
> > +      return true;
> >      }
> > -   return -1;
> > +   return false;
> >   }
> > 
> >   /**
> >    * Verify whether a string represents a null literal in YAML.
> > - *
> >    * Non-standard: don't match an empty string, 'Null' and 'NULL' as
> >    * null.
> > - *
> > - * Return 0 at success, -1 at error.
> > + * @retval Whether @a str represents NULL or not.
> >    */
> > -static inline int
> > -yaml_parse_null(const char *str, size_t len)
> > +static inline bool
> > +yaml_is_null(const char *str, size_t len)
> >   {
> >      if (len == 1 && str[0] == '~')
> > -      return 0;
> > +      return true;
> >      if (len == 4 && memcmp(str, "null", 4) == 0)
> > -      return 0;
> > -   return -1;
> > +      return true;
> > +   return false;
> >   }
> > 
> >   static void generate_error_message(struct lua_yaml_loader *loader) {
> > @@ -254,9 +251,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
> >            return;
> >         } else if (!strcmp(tag, "bool")) {
> >            bool value = false;
> > -         int rc = yaml_parse_bool(str, length, &value);
> > +         bool rc = yaml_is_bool(str, length, &value);
> >            (void) rc;
> > -         assert(rc == 0);
> > +         assert(rc);
> >            lua_pushboolean(loader->L, value);
> >            return;
> >         } else if (!strcmp(tag, "binary")) {
> > @@ -275,10 +272,10 @@ static void load_scalar(struct lua_yaml_loader *loader) {
> >             */
> >            lua_pushliteral(loader->L, "");
> >            return;
> > -      } else if (yaml_parse_null(str, length) == 0) {
> > +      } else if (yaml_is_null(str, length)) {
> >            luaL_pushnull(loader->L);
> >            return;
> > -      } else if (yaml_parse_bool(str, length, &value) == 0) {
> > +      } else if (yaml_is_bool(str, length, &value)) {
> >            lua_pushboolean(loader->L, value);
> >            return;
> >         }
> > @@ -610,6 +607,7 @@ static int dump_node(struct lua_yaml_dumper *dumper)
> >      yaml_event_t ev;
> >      yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
> >      int is_binary = 0;
> > +   bool unused;
> >      char buf[FPCONV_G_FMT_BUFSIZE];
> >      struct luaL_field field;
> > 
> > @@ -644,9 +642,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
> >         return dump_table(dumper, &field);
> >      case MP_STR:
> >         str = lua_tolstring(dumper->L, -1, &len);
> > -      if ((yaml_parse_null(str, len) == 0)
> > -         || (yaml_parse_bool(str, len, NULL) == 0)
> > -         || (lua_isnumber(dumper->L, -1))) {
> > +      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
> > +          lua_isnumber(dumper->L, -1)) {
> >            /*
> >             * The string is convertible to a null, a boolean or
> >             * a number, quote it to preserve its type.
> > 
> > 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 1/3] lua-yaml: verify arguments count
  2019-02-11 13:32         ` Alexander Turenko
@ 2019-02-15 21:28           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-15 21:28 UTC (permalink / raw)
  To: tarantool-patches, Alexander Turenko, Kirill Yukhin

This commit LGTM. Next is pending of fixes.

On 11/02/2019 14:32, Alexander Turenko wrote:
> lua_is* really checks whether an acceptable index is a valid one, so
> there are two possible approaches, one of which we should stick I think:
> 
> * Verify lua_gettop() upper and lower bounds right at start of a
>    function.
> * Use lua_is* (including lua_isnone() and lua_isnoneornil()) and don't
>    verify arguments count explicitly.
> 
> I think we should use one of these ways within a module: this is more
> important then the patch size. The only difference for a user is that
> the latter approach does not check for extra arguments.
> 
> Now I implemented the latter approach as I see you want to minimize
> explicit checks. See the patch at end of the email.
> 
> It is possible to reduce the patch further, but loss consistency in what
> we check: lua_is* or lua_gettop(). I'll do if you insist, but don't
> think it is the right way to proceed.
> 
> NB: branch: kh/gh-3662-yaml-2.1
> 
> WBR, Alexander Turenko.
> 
> On Tue, Feb 05, 2019 at 10:36:41PM +0300, Vladislav Shpilevoy wrote:
>> Hi! Thanks for the fixes!
>>
>>>>> functions.
>>>>>
>>>>> Without these checks the functions could read garbage outside of a Lua
>>>>> stack when called w/o arguments.
>>>>
>>>> Honestly, I do not understand how is it possible. Please,
>>>> provide a test for both functions. See my 3 doubts below.
>>>
>>> lua_isstring(L, 1) checks a garbage w/o preliminary lua_gettop() check.
>>> yaml.encode() gives me "unsupported Lua type 'thread'" on the current
>>> tarantool 2.1.
>>
>> I looked at lua_isstring implementation, and I see, that it checks
>> top. If an index is above top, then the type is nil.
>>
>> 	static TValue *index2adr(lua_State *L, int idx)
>> 	{
>> 	  if (idx > 0) {
>> 	    TValue *o = L->base + (idx - 1);
>> 	    return o < L->top ? o : niltv(L);
>> 	...
>>
> 
> Ouch, lua_isstring() is called only in case of top == 2, so this is out
> of scope of the discussion. The real cause of this weird "unsupported
> Lua type 'thread'" error is lua_yaml_encode() code: it calls
> `lua_newthread(L)` and then `lua_pushvalue(L, 1);`. A 1st stack item
> should be a value we encode, but when there are no arguments for
> yaml.encode() the new lua thread is the 1st item.
> 
> Anyway, I don't think that "unsupported Lua type 'thread'" is the right
> error message for `yaml.encode()`. Are you agree?
> 
>>>
>>> Anyway, added bad API usage test cases. Also I changed this:
>>>
>>> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
>>> index 3a427263e..46374970f 100644
>>> --- a/third_party/lua-yaml/lyaml.cc
>>> +++ b/third_party/lua-yaml/lyaml.cc
>>> @@ -453,7 +453,7 @@ usage_error:
>>>          return luaL_error(L, OOM_ERRMSG);
>>>       yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>>>       bool tag_only;
>>> -   if (lua_gettop(L) == 2) {
>>> +   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
>>>          if (! lua_istable(L, 2))
>>>             goto usage_error;
>>>          lua_getfield(L, 2, "tag_only");
>>>
>>> We should not raise an usage error for yaml.decode(object, nil).
>>
>> Why? It is said, that the second value either does not exist, or
>> is a table. Nil is not a table. So why? If your logic was about
>> considering nil as a not existing value, then why don't we handle
>> cases like this: yaml.decode(object, nil, nil, nil, nil) ? The same
>> for l_dump() and encode.
> 
> There is the difference between `yaml.decode(object, nil)` and
> `yaml.decode(object, nil, nil, nil, nil)`. The former one is likely to
> appear due to passing though the 2nd argument, say:
> 
> ```
> local function load_cfg(raw, opts)
>      local object = yaml.decode(raw, opts)
>      ...some post-processing...
>      return object
> end
> ```
> 
> The latter is definitely wrong usage.
> 
> But now I removed checks for extra args, see above.
> 
>>
>>>>>     usage_error:
>>>>>           return luaL_error(L, "Usage: yaml.decode(document, "\
>>>>>                             "[{tag_only = boolean}])");
>>>>> @@ -416,7 +417,7 @@ usage_error:
>>>>>           return luaL_error(L, OOM_ERRMSG);
>>>>>        yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>>>>>        bool tag_only;
>>>>> -   if (lua_gettop(L) > 1) {
>>>>> +   if (lua_gettop(L) == 2) {
>>>>
>>>> 2. This function never touches anything beyond second value on
>>>> the stack, so here lua_gettop(L) > 1 means the same as
>>>> lua_gettop(L) == 2 - the second argument exist. Third and next
>>>> values do not matter.
>>>
>>> I read this as 'those are equivalent' (correct me if I'm wrong). Ok. I'd
>>> prefer to leave it with ==. Also note the fix I pasted above.
>>
>> Why? Again. I do not see any reason behind this change except personal
>> preference.
> 
> It does not matter much, because I anyway need to add ` && !
> lua_isnil(L, 2)` or use `! lua_isnoneornil(L, 2)` here to make decode
> behaviour consistent with encode one (against 2nd argument). Yep, it is
> personal preference. Anyway, now it is `! lua_isnoneornil(L, 2)`.
> 
>> I reverted all the changes about l_load() function, and the
>> tests passed. So why do we need to make diff bigger?
> 
> yaml.decode('', nil, {}) don't pass before (don't raise an error).
> Other tests are passed, because of two reasons:
> 
> * no test on yaml.decode('', nil);
> * lua_isstring() checks stack size.
> 
> Re test: added for encode and decode.
> 
> Re lua_isstring(): okay, now I understood that it checks given index by
> the API:
> 
> http://pgl.yoyo.org/luai/i/lua_isstring ("acceptable index")
> https://www.lua.org/manual/5.3/manual.html#4.3 ("Valid and Acceptable Indices")
> https://www.lua.org/manual/5.1/manual.html#3.2 (the same for Lua 5.1)
> 
> So I changed the description of the commit to make it clear that the
> reason of the change is to make the code more consistent.
> 
>>
>>>
>>>>
>>>>>           if (! lua_istable(L, 2))
>>>>>              goto usage_error;
>>>>>           lua_getfield(L, 2, "tag_only");
>>>>> @@ -794,7 +795,7 @@ error:
>>>>>     static int l_dump(lua_State *L) {
>>>>>        struct luaL_serializer *serializer = luaL_checkserializer(L);
>>>>>        int top = lua_gettop(L);
>>>>> -   if (top > 2) {
>>>>> +   if (!(top == 1 || top == 2)) {
>>>>
>>>> 3. Here my reasoning is the same - the previous checking works
>>>> as well.
>>>
>>> It will not give an error in case of yaml.encode() and yaml.encode({},
>>> {}, {}).
>>
>> Decent. Here you are right.
>>
>>>
>>>>
>>>>>     usage_error:
>>>>>           return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
>>>>>                             "tag_handle = <string>})");
>>>>>
>>
>> My diff, which reverts some changes and makes this patch one-liner:
>>
>> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
>> index 354cafe86..854794dd1 100644
>> --- a/third_party/lua-yaml/lyaml.cc
>> +++ b/third_party/lua-yaml/lyaml.cc
>> @@ -400,8 +400,7 @@ static void load(struct lua_yaml_loader *loader) {
>>    */
>>   static int l_load(lua_State *L) {
>>      struct lua_yaml_loader loader;
>> -   int top = lua_gettop(L);
>> -   if (!(top == 1 || top == 2) || !lua_isstring(L, 1)) {
>> +   if (! lua_isstring(L, 1)) {
>>   usage_error:
>>         return luaL_error(L, "Usage: yaml.decode(document, "\
>>                           "[{tag_only = boolean}])");
>> @@ -417,7 +416,7 @@ usage_error:
>>         return luaL_error(L, OOM_ERRMSG);
>>      yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>>      bool tag_only;
>> -   if (lua_gettop(L) == 2 && ! lua_isnil(L, 2)) {
>> +   if (lua_gettop(L) > 1) {
>>         if (! lua_istable(L, 2))
>>            goto usage_error;
>>         lua_getfield(L, 2, "tag_only");
> 
> ----
> 
> The new patch description and diff (w/o tests):
> 
> lua-yaml: verify args in a consistent manner
> 
> Use lua_is*() functions instead of explicit lua_gettop() checks in
> yaml.encode() and yaml.decode() functions.
> 
> Behaviour changes:
> 
> * yaml.decode(object, nil) ignores nil (it is consistent with encode
>    behaviour).
> * yaml.encode() gives an usage error instead of "unsupported Lua type
>    'thread'".
> * yaml.encode('', {}, {}) ignores 3rd argument (it is consistent with
>    decode behaviour).
> 
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index c6d118a79..bd876ab29 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -416,7 +416,7 @@ usage_error:
>         return luaL_error(L, OOM_ERRMSG);
>      yaml_parser_set_input_string(&loader.parser, (yaml_char_t *) document, len);
>      bool tag_only;
> -   if (lua_gettop(L) > 1) {
> +   if (! lua_isnoneornil(L, 2)) {
>         if (! lua_istable(L, 2))
>            goto usage_error;
>         lua_getfield(L, 2, "tag_only");
> @@ -793,14 +793,13 @@ error:
>    */
>   static int l_dump(lua_State *L) {
>      struct luaL_serializer *serializer = luaL_checkserializer(L);
> -   int top = lua_gettop(L);
> -   if (top > 2) {
> +   if (lua_isnone(L, 1)) {
>   usage_error:
>         return luaL_error(L, "Usage: encode(object, {tag_prefix = <string>, "\
>                           "tag_handle = <string>})");
>      }
>      const char *prefix = NULL, *handle = NULL;
> -   if (top == 2 && !lua_isnil(L, 2)) {
> +   if (! lua_isnoneornil(L, 2)) {
>         if (! lua_istable(L, 2))
>            goto usage_error;
>         lua_getfield(L, 2, "tag_prefix");
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-02-05 19:36       ` Vladislav Shpilevoy
  2019-02-15 21:06         ` Vladislav Shpilevoy
@ 2019-02-18 18:55         ` Alexander Turenko
  2019-02-22 15:14           ` Vladislav Shpilevoy
  1 sibling, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-02-18 18:55 UTC (permalink / raw)
  To: Vladislav Shpilevoy; +Cc: tarantool-patches

I answered to the comments below and noted the fixes were made. Rebased
the patchset on top of fresh 2.1.

The new patch is at end of the email.

WBR, Alexander Turenko.

On Tue, Feb 05, 2019 at 10:36:40PM +0300, Vladislav Shpilevoy wrote:
> Thanks for the fixes!
> 
> > > > diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
> > > > index 4f915afd7..68f273234 100755
> > > > --- a/test/app-tap/console.test.lua
> > > > +++ b/test/app-tap/console.test.lua
> > > > @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
> > > >    --
> > > >    -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
> > > > +-- gh-3662: yaml.encode encodes booleans with multiline format.
> > > > +-- gh-3583: yaml.encode encodes null incorrectly.
> > > >    --
> > > > -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
> > > > -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
> > > > -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> > > > -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
> > > 
> > > 1. Are you sure that this is the correct behaviour? So now it is
> > > not true that decode(encode(obj)) == obj? Looks utterly confusing.
> > > Or why else you removed these tests?
> > 
> > They are work as before, but now superseded with simpler ones.
> 
> It is not obvious. I see a test
> 
>     yaml.encode(false) == "--- false\n...\n"
> 
> I see another test:
> 
>     yaml.decode('false') == false
> 
> But where is this? -
> 
>     yaml.decode("--- false\n...\n") == false

The removed test cases covered one case that is not covered with the new
cases: they decode a yaml document, not just a scalar. And it is
confusing why I remove test cases in the patch. So it worth to just add
them back.

Added.

> 
> And why "false" == "--- false\n...\n" according to the tests
> in the patch?

`---` and `...` is a yaml document start and end, see [1]. A sequence of
documents forms a yaml stream like a sequence of stanzas forms a xml
stream. We decode one document to its value. Multiple documents however
forms a table. Anyway, it does not matter much in context of this issue.

[1]: https://yaml.org/spec/1.2/spec.html#id2800401

> 
> > > 
> > > > +{
> > > > +   if (len > 5)
> > > > +      return YAML_NO_MATCH;
> > > > +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
> > > > +      return YAML_FALSE;
> > > > +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
> > > 
> > > 3. I think, it is redundant to firstly check length and then
> > > call strcmp, which also calculates length (by checking zero
> > > byte). We need either use only strcmp(), or use length comparison
> > > + memcmp. The same below for the null parser.
> > 
> > Okay, it was the premature optimization.
> > 
> > The more important thing is that is was incorrect: "fals" gives
> > YAML_FALSE.
> > 
> > I had fixed it like so, is it okay?
> > 
> > ```
> > if ((len == 5 && memcmp(str, "false", 5) == 0) ||
> >      (len == 2 && memcmp(str, "no", 2) == 0)) {
> >     if (out != NULL)
> >        *out = false;
> >     return 0;
> > }
> > ...
> > ```
> 
> Yes, this fix is ok, thanks.

Ok.

> 
> > 
> > > 
> > > > +      return YAML_TRUE;
> > > > +   return YAML_NO_MATCH;
> > > > +}
> > > > +
> > > > +/**
> > > > + * Verify whether a string represents a null literal in YAML.
> > > > + *
> > > > + * Non-standard: don't match an empty string as null.
> > > > + */
> > > > +static yaml_type
> > > > +yaml_get_null(const char *str, const size_t len){
> > > 
> > > 4. ?
> > > 
> > >      static inline int
> > >      yaml_parse_null(const char *str, size_t len, bool *is_null);
> > 
> > is_null is redundant here, because a return code contain all needed
> > information. Agreed except that. Changed.
> 
> My point was to standardize parser functions so as to always
> return 0/-1 on success/error, and always return an actual value
> via an out parameter. But now I see, that -1 here is not really an
> error, but rather 'not being of a requested type'. So here we
> do not have even a notion of 'error'.
> 
> In such a case, please, consider the fixes below. Also, I removed
> optionality of out parameter in yaml_parse_bool in order to get rid
> of 'if'.

I don't mind. Integrated these changes into the patch (with minor
wording / formatting changes).

> 
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index 5afcace1b..a4e03a8ba 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -96,46 +96,43 @@ struct lua_yaml_dumper {
>  /**
>   * Verify whether a string represents a boolean literal in YAML.
> - *
>   * Non-standard: only subset of YAML 1.1 boolean literals are
>   * treated as boolean values.
> - *
> - * Return 0 at success, -1 at error.
> + * @param str Literal.
> + * @param len Length of @a str.
> + * @param[out] out Result boolean value.
> + * @retval Whether @a str represents a boolean value or not.
>   */
> -static inline int
> -yaml_parse_bool(const char *str, size_t len, bool *out)
> +static inline bool
> +yaml_is_bool(const char *str, size_t len, bool *out)
>  {
>     if ((len == 5 && memcmp(str, "false", 5) == 0) ||
>         (len == 2 && memcmp(str, "no", 2) == 0)) {
> -      if (out != NULL)
> -         *out = false;
> -      return 0;
> +      *out = false;
> +      return true;
>     }
>     if ((len == 4 && memcmp(str, "true", 4) == 0) ||
>         (len == 3 && memcmp(str, "yes", 3) == 0)) {
> -      if (out != NULL)
> -         *out = true;
> -      return 0;
> +      *out = true;
> +      return true;
>     }
> -   return -1;
> +   return false;
>  }
>  /**
>   * Verify whether a string represents a null literal in YAML.
> - *
>   * Non-standard: don't match an empty string, 'Null' and 'NULL' as
>   * null.
> - *
> - * Return 0 at success, -1 at error.
> + * @retval Whether @a str represents NULL or not.
>   */
> -static inline int
> -yaml_parse_null(const char *str, size_t len)
> +static inline bool
> +yaml_is_null(const char *str, size_t len)
>  {
>     if (len == 1 && str[0] == '~')
> -      return 0;
> +      return true;
>     if (len == 4 && memcmp(str, "null", 4) == 0)
> -      return 0;
> -   return -1;
> +      return true;
> +   return false;
>  }
>  static void generate_error_message(struct lua_yaml_loader *loader) {
> @@ -254,9 +251,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>           return;
>        } else if (!strcmp(tag, "bool")) {
>           bool value = false;
> -         int rc = yaml_parse_bool(str, length, &value);
> +         bool rc = yaml_is_bool(str, length, &value);
>           (void) rc;
> -         assert(rc == 0);
> +         assert(rc);

I removed the assert() here, because yaml.decode('!!bool null') should
not lead to an assertion fail. I didn't investigate why the behaviour is
to produce `false` rather then raise an error, just keep the behaviour
the same as before. However I don't add a test, because it is unclear
whether the module behaves in this way with intention or by occasion.

>           lua_pushboolean(loader->L, value);
>           return;
>        } else if (!strcmp(tag, "binary")) {
> @@ -275,10 +272,10 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>            */
>           lua_pushliteral(loader->L, "");
>           return;
> -      } else if (yaml_parse_null(str, length) == 0) {
> +      } else if (yaml_is_null(str, length)) {
>           luaL_pushnull(loader->L);
>           return;
> -      } else if (yaml_parse_bool(str, length, &value) == 0) {
> +      } else if (yaml_is_bool(str, length, &value)) {
>           lua_pushboolean(loader->L, value);
>           return;
>        }
> @@ -610,6 +607,7 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>     yaml_event_t ev;
>     yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
>     int is_binary = 0;
> +   bool unused;
>     char buf[FPCONV_G_FMT_BUFSIZE];
>     struct luaL_field field;
> @@ -644,9 +642,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>        return dump_table(dumper, &field);
>     case MP_STR:
>        str = lua_tolstring(dumper->L, -1, &len);
> -      if ((yaml_parse_null(str, len) == 0)
> -         || (yaml_parse_bool(str, len, NULL) == 0)
> -         || (lua_isnumber(dumper->L, -1))) {
> +      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
> +          lua_isnumber(dumper->L, -1)) {
>           /*
>            * The string is convertible to a null, a boolean or
>            * a number, quote it to preserve its type.
> 

----

commit 8531b4af03016a69d82bff35504ef6ae3ffbdb82
Author: Alexander Turenko <alexander.turenko@tarantool.org>
Date:   Tue Jan 22 01:03:01 2019 +0300

    lua-yaml: fix strings literals encoding in yaml
    
    yaml.encode() now wraps a string literal whose content is equal to a
    null or a boolean value representation in YAML into single quotes. Those
    literals are: 'false', 'no', 'true', 'yes', '~', 'null'.
    
    Reverted the a2d7643c commit to use single quotes instead of multiline
    encoding for 'true' and 'false' string literals.
    
    Follows up #3476
    Closes #3662
    Closes #3583

diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
index 4f915afd7..a03df5277 100755
--- a/test/app-tap/console.test.lua
+++ b/test/app-tap/console.test.lua
@@ -21,7 +21,7 @@ local EOL = "\n...\n"
 
 test = tap.test("console")
 
-test:plan(59)
+test:plan(70)
 
 -- Start console and connect to it
 local server = console.listen(CONSOLE_SOCKET)
@@ -77,12 +77,28 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
 
 --
 -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
+-- gh-3662: yaml.encode encodes booleans with multiline format.
+-- gh-3583: yaml.encode encodes null incorrectly.
 --
+
 test:is(type(yaml.decode(yaml.encode('false'))), 'string')
 test:is(type(yaml.decode(yaml.encode('true'))), 'string')
 test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
 test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
 
+test:is(yaml.encode(false), "--- false\n...\n")
+test:is(yaml.encode(true), "--- true\n...\n")
+test:is(yaml.encode('false'), "--- 'false'\n...\n")
+test:is(yaml.encode('true'), "--- 'true'\n...\n")
+test:is(yaml.encode(nil), "--- null\n...\n")
+
+test:is(yaml.decode('false'), false)
+test:is(yaml.decode('no'), false)
+test:is(yaml.decode('true'), true)
+test:is(yaml.decode('yes'), true)
+test:is(yaml.decode('~'), nil)
+test:is(yaml.decode('null'), nil)
+
 box.cfg{
     listen=IPROTO_SOCKET;
     memtx_memory = 107374182,
diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
index bd876ab29..46c98bde1 100644
--- a/third_party/lua-yaml/lyaml.cc
+++ b/third_party/lua-yaml/lyaml.cc
@@ -94,6 +94,55 @@ struct lua_yaml_dumper {
    luaL_Buffer yamlbuf;
 };
 
+/**
+ * Verify whether a string represents a boolean literal in YAML.
+ *
+ * Non-standard: only subset of YAML 1.1 boolean literals are
+ * treated as boolean values.
+ *
+ * @param str Literal to check.
+ * @param len Length of @a str.
+ * @param[out] out Result boolean value.
+ *
+ * @retval Whether @a str represents a boolean value.
+ */
+static inline bool
+yaml_is_bool(const char *str, size_t len, bool *out)
+{
+   if ((len == 5 && memcmp(str, "false", 5) == 0) ||
+       (len == 2 && memcmp(str, "no", 2) == 0)) {
+      *out = false;
+      return true;
+   }
+   if ((len == 4 && memcmp(str, "true", 4) == 0) ||
+       (len == 3 && memcmp(str, "yes", 3) == 0)) {
+      *out = true;
+      return true;
+   }
+   return false;
+}
+
+/**
+ * Verify whether a string represents a null literal in YAML.
+ *
+ * Non-standard: don't match an empty string, 'Null' and 'NULL' as
+ * null.
+ *
+ * @param str Literal to check.
+ * @param len Length of @a str.
+ *
+ * @retval Whether @a str represents a null value.
+ */
+static inline bool
+yaml_is_null(const char *str, size_t len)
+{
+   if (len == 1 && str[0] == '~')
+      return true;
+   if (len == 4 && memcmp(str, "null", 4) == 0)
+      return true;
+   return false;
+}
+
 static void generate_error_message(struct lua_yaml_loader *loader) {
    char buf[256];
    luaL_Buffer b;
@@ -209,7 +258,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
          lua_pushnumber(loader->L, dval);
          return;
       } else if (!strcmp(tag, "bool")) {
-         lua_pushboolean(loader->L, !strcmp(str, "true") || !strcmp(str, "yes"));
+         bool value = false;
+         yaml_is_bool(str, length, &value);
+         lua_pushboolean(loader->L, value);
          return;
       } else if (!strcmp(tag, "binary")) {
          frombase64(loader->L, (const unsigned char *)str, length);
@@ -218,20 +269,20 @@ static void load_scalar(struct lua_yaml_loader *loader) {
    }
 
    if (loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
-      if (!strcmp(str, "~")) {
-         luaL_pushnull(loader->L);
-         return;
-      } else if (!strcmp(str, "true") || !strcmp(str, "yes")) {
-         lua_pushboolean(loader->L, 1);
-         return;
-      } else if (!strcmp(str, "false") || !strcmp(str, "no")) {
-         lua_pushboolean(loader->L, 0);
+      bool value;
+      if (!length) {
+         /*
+          * Non-standard: an empty value/document is null
+          * according to the standard, but we decode it as an
+          * empty string.
+          */
+         lua_pushliteral(loader->L, "");
          return;
-      } else if (!strcmp(str, "null")) {
+      } else if (yaml_is_null(str, length)) {
          luaL_pushnull(loader->L);
          return;
-      } else if (!length) {
-         lua_pushliteral(loader->L, "");
+      } else if (yaml_is_bool(str, length, &value)) {
+         lua_pushboolean(loader->L, value);
          return;
       }
 
@@ -547,7 +598,6 @@ static int yaml_is_flow_mode(struct lua_yaml_dumper *dumper) {
                (evp->type == YAML_MAPPING_START_EVENT &&
                 evp->data.mapping_start.style == YAML_FLOW_MAPPING_STYLE)) {
             return 1;
-            break;
          }
       }
    }
@@ -564,6 +614,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
    int is_binary = 0;
    char buf[FPCONV_G_FMT_BUFSIZE];
    struct luaL_field field;
+   bool unused;
+   (void) unused;
 
    int top = lua_gettop(dumper->L);
    luaL_checkfield(dumper->L, dumper->cfg, top, &field);
@@ -596,8 +648,12 @@ static int dump_node(struct lua_yaml_dumper *dumper)
       return dump_table(dumper, &field);
    case MP_STR:
       str = lua_tolstring(dumper->L, -1, &len);
-      if (lua_isnumber(dumper->L, -1)) {
-         /* string is convertible to number, quote it to preserve type */
+      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
+          lua_isnumber(dumper->L, -1)) {
+         /*
+          * The string is convertible to a null, a boolean or
+          * a number, quote it to preserve its type.
+          */
          style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
          break;
       }
@@ -605,12 +661,10 @@ static int dump_node(struct lua_yaml_dumper *dumper)
       if (utf8_check_printable(str, len)) {
          if (yaml_is_flow_mode(dumper)) {
             style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
-         } else if (strstr(str, "\n\n") != NULL || strcmp(str, "true") == 0 ||
-		    strcmp(str, "false") == 0) {
+         } else if (strstr(str, "\n\n") != 0) {
             /*
              * Tarantool-specific: use literal style for string
-             * with empty lines and strings representing boolean
-             * types.
+             * with empty lines.
              * Useful for tutorial().
              */
             style = YAML_LITERAL_SCALAR_STYLE;

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml
  2019-02-18 18:55         ` Alexander Turenko
@ 2019-02-22 15:14           ` Vladislav Shpilevoy
  0 siblings, 0 replies; 23+ messages in thread
From: Vladislav Shpilevoy @ 2019-02-22 15:14 UTC (permalink / raw)
  To: Alexander Turenko, Kirill Yukhin; +Cc: tarantool-patches

LGTM.

On 18/02/2019 21:55, Alexander Turenko wrote:
> I answered to the comments below and noted the fixes were made. Rebased
> the patchset on top of fresh 2.1.
> 
> The new patch is at end of the email.
> 
> WBR, Alexander Turenko.
> 
> On Tue, Feb 05, 2019 at 10:36:40PM +0300, Vladislav Shpilevoy wrote:
>> Thanks for the fixes!
>>
>>>>> diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
>>>>> index 4f915afd7..68f273234 100755
>>>>> --- a/test/app-tap/console.test.lua
>>>>> +++ b/test/app-tap/console.test.lua
>>>>> @@ -77,11 +77,24 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
>>>>>     --
>>>>>     -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
>>>>> +-- gh-3662: yaml.encode encodes booleans with multiline format.
>>>>> +-- gh-3583: yaml.encode encodes null incorrectly.
>>>>>     --
>>>>> -test:is(type(yaml.decode(yaml.encode('false'))), 'string')
>>>>> -test:is(type(yaml.decode(yaml.encode('true'))), 'string')
>>>>> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>>>>> -test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>>>>
>>>> 1. Are you sure that this is the correct behaviour? So now it is
>>>> not true that decode(encode(obj)) == obj? Looks utterly confusing.
>>>> Or why else you removed these tests?
>>>
>>> They are work as before, but now superseded with simpler ones.
>>
>> It is not obvious. I see a test
>>
>>      yaml.encode(false) == "--- false\n...\n"
>>
>> I see another test:
>>
>>      yaml.decode('false') == false
>>
>> But where is this? -
>>
>>      yaml.decode("--- false\n...\n") == false
> 
> The removed test cases covered one case that is not covered with the new
> cases: they decode a yaml document, not just a scalar. And it is
> confusing why I remove test cases in the patch. So it worth to just add
> them back.
> 
> Added.
> 
>>
>> And why "false" == "--- false\n...\n" according to the tests
>> in the patch?
> 
> `---` and `...` is a yaml document start and end, see [1]. A sequence of
> documents forms a yaml stream like a sequence of stanzas forms a xml
> stream. We decode one document to its value. Multiple documents however
> forms a table. Anyway, it does not matter much in context of this issue.
> 
> [1]: https://yaml.org/spec/1.2/spec.html#id2800401
> 
>>
>>>>
>>>>> +{
>>>>> +   if (len > 5)
>>>>> +      return YAML_NO_MATCH;
>>>>> +   if (strcmp(str, "false") == 0 || strcmp(str, "no") == 0)
>>>>> +      return YAML_FALSE;
>>>>> +   if (strcmp(str, "true") == 0 || strcmp(str, "yes") == 0)
>>>>
>>>> 3. I think, it is redundant to firstly check length and then
>>>> call strcmp, which also calculates length (by checking zero
>>>> byte). We need either use only strcmp(), or use length comparison
>>>> + memcmp. The same below for the null parser.
>>>
>>> Okay, it was the premature optimization.
>>>
>>> The more important thing is that is was incorrect: "fals" gives
>>> YAML_FALSE.
>>>
>>> I had fixed it like so, is it okay?
>>>
>>> ```
>>> if ((len == 5 && memcmp(str, "false", 5) == 0) ||
>>>       (len == 2 && memcmp(str, "no", 2) == 0)) {
>>>      if (out != NULL)
>>>         *out = false;
>>>      return 0;
>>> }
>>> ...
>>> ```
>>
>> Yes, this fix is ok, thanks.
> 
> Ok.
> 
>>
>>>
>>>>
>>>>> +      return YAML_TRUE;
>>>>> +   return YAML_NO_MATCH;
>>>>> +}
>>>>> +
>>>>> +/**
>>>>> + * Verify whether a string represents a null literal in YAML.
>>>>> + *
>>>>> + * Non-standard: don't match an empty string as null.
>>>>> + */
>>>>> +static yaml_type
>>>>> +yaml_get_null(const char *str, const size_t len){
>>>>
>>>> 4. ?
>>>>
>>>>       static inline int
>>>>       yaml_parse_null(const char *str, size_t len, bool *is_null);
>>>
>>> is_null is redundant here, because a return code contain all needed
>>> information. Agreed except that. Changed.
>>
>> My point was to standardize parser functions so as to always
>> return 0/-1 on success/error, and always return an actual value
>> via an out parameter. But now I see, that -1 here is not really an
>> error, but rather 'not being of a requested type'. So here we
>> do not have even a notion of 'error'.
>>
>> In such a case, please, consider the fixes below. Also, I removed
>> optionality of out parameter in yaml_parse_bool in order to get rid
>> of 'if'.
> 
> I don't mind. Integrated these changes into the patch (with minor
> wording / formatting changes).
> 
>>
>> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
>> index 5afcace1b..a4e03a8ba 100644
>> --- a/third_party/lua-yaml/lyaml.cc
>> +++ b/third_party/lua-yaml/lyaml.cc
>> @@ -96,46 +96,43 @@ struct lua_yaml_dumper {
>>   /**
>>    * Verify whether a string represents a boolean literal in YAML.
>> - *
>>    * Non-standard: only subset of YAML 1.1 boolean literals are
>>    * treated as boolean values.
>> - *
>> - * Return 0 at success, -1 at error.
>> + * @param str Literal.
>> + * @param len Length of @a str.
>> + * @param[out] out Result boolean value.
>> + * @retval Whether @a str represents a boolean value or not.
>>    */
>> -static inline int
>> -yaml_parse_bool(const char *str, size_t len, bool *out)
>> +static inline bool
>> +yaml_is_bool(const char *str, size_t len, bool *out)
>>   {
>>      if ((len == 5 && memcmp(str, "false", 5) == 0) ||
>>          (len == 2 && memcmp(str, "no", 2) == 0)) {
>> -      if (out != NULL)
>> -         *out = false;
>> -      return 0;
>> +      *out = false;
>> +      return true;
>>      }
>>      if ((len == 4 && memcmp(str, "true", 4) == 0) ||
>>          (len == 3 && memcmp(str, "yes", 3) == 0)) {
>> -      if (out != NULL)
>> -         *out = true;
>> -      return 0;
>> +      *out = true;
>> +      return true;
>>      }
>> -   return -1;
>> +   return false;
>>   }
>>   /**
>>    * Verify whether a string represents a null literal in YAML.
>> - *
>>    * Non-standard: don't match an empty string, 'Null' and 'NULL' as
>>    * null.
>> - *
>> - * Return 0 at success, -1 at error.
>> + * @retval Whether @a str represents NULL or not.
>>    */
>> -static inline int
>> -yaml_parse_null(const char *str, size_t len)
>> +static inline bool
>> +yaml_is_null(const char *str, size_t len)
>>   {
>>      if (len == 1 && str[0] == '~')
>> -      return 0;
>> +      return true;
>>      if (len == 4 && memcmp(str, "null", 4) == 0)
>> -      return 0;
>> -   return -1;
>> +      return true;
>> +   return false;
>>   }
>>   static void generate_error_message(struct lua_yaml_loader *loader) {
>> @@ -254,9 +251,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>>            return;
>>         } else if (!strcmp(tag, "bool")) {
>>            bool value = false;
>> -         int rc = yaml_parse_bool(str, length, &value);
>> +         bool rc = yaml_is_bool(str, length, &value);
>>            (void) rc;
>> -         assert(rc == 0);
>> +         assert(rc);
> 
> I removed the assert() here, because yaml.decode('!!bool null') should
> not lead to an assertion fail. I didn't investigate why the behaviour is
> to produce `false` rather then raise an error, just keep the behaviour
> the same as before. However I don't add a test, because it is unclear
> whether the module behaves in this way with intention or by occasion.
> 
>>            lua_pushboolean(loader->L, value);
>>            return;
>>         } else if (!strcmp(tag, "binary")) {
>> @@ -275,10 +272,10 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>>             */
>>            lua_pushliteral(loader->L, "");
>>            return;
>> -      } else if (yaml_parse_null(str, length) == 0) {
>> +      } else if (yaml_is_null(str, length)) {
>>            luaL_pushnull(loader->L);
>>            return;
>> -      } else if (yaml_parse_bool(str, length, &value) == 0) {
>> +      } else if (yaml_is_bool(str, length, &value)) {
>>            lua_pushboolean(loader->L, value);
>>            return;
>>         }
>> @@ -610,6 +607,7 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>>      yaml_event_t ev;
>>      yaml_scalar_style_t style = YAML_PLAIN_SCALAR_STYLE;
>>      int is_binary = 0;
>> +   bool unused;
>>      char buf[FPCONV_G_FMT_BUFSIZE];
>>      struct luaL_field field;
>> @@ -644,9 +642,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>>         return dump_table(dumper, &field);
>>      case MP_STR:
>>         str = lua_tolstring(dumper->L, -1, &len);
>> -      if ((yaml_parse_null(str, len) == 0)
>> -         || (yaml_parse_bool(str, len, NULL) == 0)
>> -         || (lua_isnumber(dumper->L, -1))) {
>> +      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
>> +          lua_isnumber(dumper->L, -1)) {
>>            /*
>>             * The string is convertible to a null, a boolean or
>>             * a number, quote it to preserve its type.
>>
> 
> ----
> 
> commit 8531b4af03016a69d82bff35504ef6ae3ffbdb82
> Author: Alexander Turenko <alexander.turenko@tarantool.org>
> Date:   Tue Jan 22 01:03:01 2019 +0300
> 
>      lua-yaml: fix strings literals encoding in yaml
>      
>      yaml.encode() now wraps a string literal whose content is equal to a
>      null or a boolean value representation in YAML into single quotes. Those
>      literals are: 'false', 'no', 'true', 'yes', '~', 'null'.
>      
>      Reverted the a2d7643c commit to use single quotes instead of multiline
>      encoding for 'true' and 'false' string literals.
>      
>      Follows up #3476
>      Closes #3662
>      Closes #3583
> 
> diff --git a/test/app-tap/console.test.lua b/test/app-tap/console.test.lua
> index 4f915afd7..a03df5277 100755
> --- a/test/app-tap/console.test.lua
> +++ b/test/app-tap/console.test.lua
> @@ -21,7 +21,7 @@ local EOL = "\n...\n"
>   
>   test = tap.test("console")
>   
> -test:plan(59)
> +test:plan(70)
>   
>   -- Start console and connect to it
>   local server = console.listen(CONSOLE_SOCKET)
> @@ -77,12 +77,28 @@ test:is(yaml.decode(client:read(EOL)), '', "clear delimiter")
>   
>   --
>   -- gh-3476: yaml.encode encodes 'false' and 'true' incorrectly.
> +-- gh-3662: yaml.encode encodes booleans with multiline format.
> +-- gh-3583: yaml.encode encodes null incorrectly.
>   --
> +
>   test:is(type(yaml.decode(yaml.encode('false'))), 'string')
>   test:is(type(yaml.decode(yaml.encode('true'))), 'string')
>   test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>   test:is(type(yaml.decode(yaml.encode({a = 'false'})).a), 'string')
>   
> +test:is(yaml.encode(false), "--- false\n...\n")
> +test:is(yaml.encode(true), "--- true\n...\n")
> +test:is(yaml.encode('false'), "--- 'false'\n...\n")
> +test:is(yaml.encode('true'), "--- 'true'\n...\n")
> +test:is(yaml.encode(nil), "--- null\n...\n")
> +
> +test:is(yaml.decode('false'), false)
> +test:is(yaml.decode('no'), false)
> +test:is(yaml.decode('true'), true)
> +test:is(yaml.decode('yes'), true)
> +test:is(yaml.decode('~'), nil)
> +test:is(yaml.decode('null'), nil)
> +
>   box.cfg{
>       listen=IPROTO_SOCKET;
>       memtx_memory = 107374182,
> diff --git a/third_party/lua-yaml/lyaml.cc b/third_party/lua-yaml/lyaml.cc
> index bd876ab29..46c98bde1 100644
> --- a/third_party/lua-yaml/lyaml.cc
> +++ b/third_party/lua-yaml/lyaml.cc
> @@ -94,6 +94,55 @@ struct lua_yaml_dumper {
>      luaL_Buffer yamlbuf;
>   };
>   
> +/**
> + * Verify whether a string represents a boolean literal in YAML.
> + *
> + * Non-standard: only subset of YAML 1.1 boolean literals are
> + * treated as boolean values.
> + *
> + * @param str Literal to check.
> + * @param len Length of @a str.
> + * @param[out] out Result boolean value.
> + *
> + * @retval Whether @a str represents a boolean value.
> + */
> +static inline bool
> +yaml_is_bool(const char *str, size_t len, bool *out)
> +{
> +   if ((len == 5 && memcmp(str, "false", 5) == 0) ||
> +       (len == 2 && memcmp(str, "no", 2) == 0)) {
> +      *out = false;
> +      return true;
> +   }
> +   if ((len == 4 && memcmp(str, "true", 4) == 0) ||
> +       (len == 3 && memcmp(str, "yes", 3) == 0)) {
> +      *out = true;
> +      return true;
> +   }
> +   return false;
> +}
> +
> +/**
> + * Verify whether a string represents a null literal in YAML.
> + *
> + * Non-standard: don't match an empty string, 'Null' and 'NULL' as
> + * null.
> + *
> + * @param str Literal to check.
> + * @param len Length of @a str.
> + *
> + * @retval Whether @a str represents a null value.
> + */
> +static inline bool
> +yaml_is_null(const char *str, size_t len)
> +{
> +   if (len == 1 && str[0] == '~')
> +      return true;
> +   if (len == 4 && memcmp(str, "null", 4) == 0)
> +      return true;
> +   return false;
> +}
> +
>   static void generate_error_message(struct lua_yaml_loader *loader) {
>      char buf[256];
>      luaL_Buffer b;
> @@ -209,7 +258,9 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>            lua_pushnumber(loader->L, dval);
>            return;
>         } else if (!strcmp(tag, "bool")) {
> -         lua_pushboolean(loader->L, !strcmp(str, "true") || !strcmp(str, "yes"));
> +         bool value = false;
> +         yaml_is_bool(str, length, &value);
> +         lua_pushboolean(loader->L, value);
>            return;
>         } else if (!strcmp(tag, "binary")) {
>            frombase64(loader->L, (const unsigned char *)str, length);
> @@ -218,20 +269,20 @@ static void load_scalar(struct lua_yaml_loader *loader) {
>      }
>   
>      if (loader->event.data.scalar.style == YAML_PLAIN_SCALAR_STYLE) {
> -      if (!strcmp(str, "~")) {
> -         luaL_pushnull(loader->L);
> -         return;
> -      } else if (!strcmp(str, "true") || !strcmp(str, "yes")) {
> -         lua_pushboolean(loader->L, 1);
> -         return;
> -      } else if (!strcmp(str, "false") || !strcmp(str, "no")) {
> -         lua_pushboolean(loader->L, 0);
> +      bool value;
> +      if (!length) {
> +         /*
> +          * Non-standard: an empty value/document is null
> +          * according to the standard, but we decode it as an
> +          * empty string.
> +          */
> +         lua_pushliteral(loader->L, "");
>            return;
> -      } else if (!strcmp(str, "null")) {
> +      } else if (yaml_is_null(str, length)) {
>            luaL_pushnull(loader->L);
>            return;
> -      } else if (!length) {
> -         lua_pushliteral(loader->L, "");
> +      } else if (yaml_is_bool(str, length, &value)) {
> +         lua_pushboolean(loader->L, value);
>            return;
>         }
>   
> @@ -547,7 +598,6 @@ static int yaml_is_flow_mode(struct lua_yaml_dumper *dumper) {
>                  (evp->type == YAML_MAPPING_START_EVENT &&
>                   evp->data.mapping_start.style == YAML_FLOW_MAPPING_STYLE)) {
>               return 1;
> -            break;
>            }
>         }
>      }
> @@ -564,6 +614,8 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>      int is_binary = 0;
>      char buf[FPCONV_G_FMT_BUFSIZE];
>      struct luaL_field field;
> +   bool unused;
> +   (void) unused;
>   
>      int top = lua_gettop(dumper->L);
>      luaL_checkfield(dumper->L, dumper->cfg, top, &field);
> @@ -596,8 +648,12 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>         return dump_table(dumper, &field);
>      case MP_STR:
>         str = lua_tolstring(dumper->L, -1, &len);
> -      if (lua_isnumber(dumper->L, -1)) {
> -         /* string is convertible to number, quote it to preserve type */
> +      if (yaml_is_null(str, len) || yaml_is_bool(str, len, &unused) ||
> +          lua_isnumber(dumper->L, -1)) {
> +         /*
> +          * The string is convertible to a null, a boolean or
> +          * a number, quote it to preserve its type.
> +          */
>            style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
>            break;
>         }
> @@ -605,12 +661,10 @@ static int dump_node(struct lua_yaml_dumper *dumper)
>         if (utf8_check_printable(str, len)) {
>            if (yaml_is_flow_mode(dumper)) {
>               style = YAML_SINGLE_QUOTED_SCALAR_STYLE;
> -         } else if (strstr(str, "\n\n") != NULL || strcmp(str, "true") == 0 ||
> -		    strcmp(str, "false") == 0) {
> +         } else if (strstr(str, "\n\n") != 0) {
>               /*
>                * Tarantool-specific: use literal style for string
> -             * with empty lines and strings representing boolean
> -             * types.
> +             * with empty lines.
>                * Useful for tutorial().
>                */
>               style = YAML_LITERAL_SCALAR_STYLE;
> 

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes
  2019-01-22  2:12 [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes Alexander Turenko
                   ` (3 preceding siblings ...)
  2019-01-24 21:26 ` [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes Vladislav Shpilevoy
@ 2019-02-25 11:27 ` Kirill Yukhin
  2019-03-05 16:40   ` Alexander Turenko
  4 siblings, 1 reply; 23+ messages in thread
From: Kirill Yukhin @ 2019-02-25 11:27 UTC (permalink / raw)
  To: tarantool-patches; +Cc: Vladislav Shpilevoy, Alexander Turenko

Hello,

On 22 Jan 05:12, Alexander Turenko wrote:
> https://github.com/tarantool/tarantool/issues/3476
> https://github.com/tarantool/tarantool/issues/3662
> https://github.com/tarantool/tarantool/issues/3583
> https://github.com/tarantool/tarantool/tree/kh/gh-3662-yaml-2.1
> 
> AKhatskevich (1):
>   lua-yaml: verify arguments count
> 
> Alexander Turenko (2):
>   lua-yaml: fix boolean/null representation in yaml
>   lua-yaml: treat an empty document/value as null

I've checked your patches (1/3 and 2/3) into 2.1 branch.

Could you please rebase the patchset on top of 1.10 as well?

--
Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes
  2019-02-25 11:27 ` Kirill Yukhin
@ 2019-03-05 16:40   ` Alexander Turenko
  2019-03-06  7:21     ` Kirill Yukhin
  0 siblings, 1 reply; 23+ messages in thread
From: Alexander Turenko @ 2019-03-05 16:40 UTC (permalink / raw)
  To: Kirill Yukhin; +Cc: tarantool-patches, Vladislav Shpilevoy

On Mon, Feb 25, 2019 at 02:27:24PM +0300, Kirill Yukhin wrote:
> Hello,
> 
> On 22 Jan 05:12, Alexander Turenko wrote:
> > https://github.com/tarantool/tarantool/issues/3476
> > https://github.com/tarantool/tarantool/issues/3662
> > https://github.com/tarantool/tarantool/issues/3583
> > https://github.com/tarantool/tarantool/tree/kh/gh-3662-yaml-2.1
> > 
> > AKhatskevich (1):
> >   lua-yaml: verify arguments count
> > 
> > Alexander Turenko (2):
> >   lua-yaml: fix boolean/null representation in yaml
> >   lua-yaml: treat an empty document/value as null
> 
> I've checked your patches (1/3 and 2/3) into 2.1 branch.
> 
> Could you please rebase the patchset on top of 1.10 as well?

I cherry-picked these two commits on top of fresh 1.10 and all seems to
be okay (the commits are applied cleanly and all tests are passed).

I pushed it to https://github.com/tarantool/tarantool/commits/kh/gh-3662-yaml-1.10
Travis-CI: https://travis-ci.org/tarantool/tarantool/builds/502095402

WBR, Alexander Turenko.

> 
> --
> Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 23+ messages in thread

* [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes
  2019-03-05 16:40   ` Alexander Turenko
@ 2019-03-06  7:21     ` Kirill Yukhin
  0 siblings, 0 replies; 23+ messages in thread
From: Kirill Yukhin @ 2019-03-06  7:21 UTC (permalink / raw)
  To: Alexander Turenko; +Cc: tarantool-patches, Vladislav Shpilevoy

Hello,

On 05 Mar 19:40, Alexander Turenko wrote:
> On Mon, Feb 25, 2019 at 02:27:24PM +0300, Kirill Yukhin wrote:
> > Hello,
> > 
> > On 22 Jan 05:12, Alexander Turenko wrote:
> > > https://github.com/tarantool/tarantool/issues/3476
> > > https://github.com/tarantool/tarantool/issues/3662
> > > https://github.com/tarantool/tarantool/issues/3583
> > > https://github.com/tarantool/tarantool/tree/kh/gh-3662-yaml-2.1
> > > 
> > > AKhatskevich (1):
> > >   lua-yaml: verify arguments count
> > > 
> > > Alexander Turenko (2):
> > >   lua-yaml: fix boolean/null representation in yaml
> > >   lua-yaml: treat an empty document/value as null
> > 
> > I've checked your patches (1/3 and 2/3) into 2.1 branch.
> > 
> > Could you please rebase the patchset on top of 1.10 as well?
> 
> I cherry-picked these two commits on top of fresh 1.10 and all seems to
> be okay (the commits are applied cleanly and all tests are passed).
> 
> I pushed it to https://github.com/tarantool/tarantool/commits/kh/gh-3662-yaml-1.10
> Travis-CI: https://travis-ci.org/tarantool/tarantool/builds/502095402

Strange. Anyway, I've comitted it to 1.10 as well.

--
Regards, Kirill Yukhin

^ permalink raw reply	[flat|nested] 23+ messages in thread

end of thread, other threads:[~2019-03-06  7:21 UTC | newest]

Thread overview: 23+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-01-22  2:12 [tarantool-patches] [PATCH v2 0/3] lua-yaml null/boolean fixes Alexander Turenko
2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 1/3] lua-yaml: verify arguments count Alexander Turenko
2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
2019-02-05  3:29     ` Alexander Turenko
2019-02-05 19:36       ` Vladislav Shpilevoy
2019-02-11 13:32         ` Alexander Turenko
2019-02-15 21:28           ` Vladislav Shpilevoy
2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 2/3] lua-yaml: fix boolean/null representation in yaml Alexander Turenko
2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
2019-01-24 21:32     ` Vladislav Shpilevoy
2019-02-05  3:29     ` Alexander Turenko
2019-02-05 19:36       ` Vladislav Shpilevoy
2019-02-15 21:06         ` Vladislav Shpilevoy
2019-02-15 21:23           ` Alexander Turenko
2019-02-18 18:55         ` Alexander Turenko
2019-02-22 15:14           ` Vladislav Shpilevoy
2019-01-22  2:12 ` [tarantool-patches] [PATCH v2 3/3] lua-yaml: treat an empty document/value as null Alexander Turenko
2019-01-24 21:26   ` [tarantool-patches] " Vladislav Shpilevoy
2019-02-05  3:30     ` Alexander Turenko
2019-01-24 21:26 ` [tarantool-patches] Re: [PATCH v2 0/3] lua-yaml null/boolean fixes Vladislav Shpilevoy
2019-02-25 11:27 ` Kirill Yukhin
2019-03-05 16:40   ` Alexander Turenko
2019-03-06  7:21     ` Kirill Yukhin

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