From 9e403e42176b519df362ffa0e2f070d6952b74bf Mon Sep 17 00:00:00 2001 From: Olga Arkhangelskaia <arkholga@tarantool.org> Date: Wed, 5 Feb 2020 14:05:25 +0300 Subject: [PATCH] json: don't spoil instance with per-call options When json.decode is used with 2 arguments, 2nd argument seeps out to the json configuration of the instance. Moreover, due to current serializer.cfg implementation it remains invisible while checking settings using json.cfg table. This fixes commit 6508ddb792fafb4edcacce63e42cc9bc3556bbe8 ('json: fix stack-use-after-scope in json_decode()'). Closes #4761 (cherry picked from commit f54f4dc03afcc9b34261e6275881f0120faef67e) --- .../gh-4761-json-per-call-options.test.lua | 37 +++++++++++++++++++ third_party/lua-cjson/lua_cjson.c | 24 +++++++++--- 2 files changed, 56 insertions(+), 5 deletions(-) create mode 100755 test/app-tap/gh-4761-json-per-call-options.test.lua diff --git a/test/app-tap/gh-4761-json-per-call-options.test.lua b/test/app-tap/gh-4761-json-per-call-options.test.lua new file mode 100755 index 0000000000..1fb24744ec --- /dev/null +++ b/test/app-tap/gh-4761-json-per-call-options.test.lua @@ -0,0 +1,37 @@ +#!/usr/bin/env tarantool + +local json = require('json') +local tap = require('tap') + +-- +-- gh-4761: json.decode silently changes instance settings when +-- called with 2nd parameter. +-- +-- Verify json.encode as well. +-- +local res = tap.test('gh-4761-json-per-call-options', function(test) + test:plan(2) + + -- Preparation code: call :decode() with a custom option. + local ok, err = pcall(json.decode, '{"foo": {"bar": 1}}', + {decode_max_depth = 1}) + assert(not ok, 'expect "too many nested data structures" error') + + -- Verify that the instance option remains unchanged. + local exp_res = {foo = {bar = 1}} + local ok, res = pcall(json.decode, '{"foo": {"bar": 1}}') + test:is_deeply({ok, res}, {true, exp_res}, + 'json instance settings remain unchanged after :decode()') + + -- Same check for json.encode. + local nan = 1/0 + local ok, err = pcall(json.encode, {a = nan}, + {encode_invalid_numbers = false}) + assert(not ok, 'expected "number must not be NaN or Inf" error') + local exp_res = '{"a":inf}' + local ok, res = pcall(json.encode, {a = nan}) + test:is_deeply({ok, res}, {true, exp_res}, + 'json instance settings remain unchanged after :encode()') +end) + +os.exit(res and 0 or 1) diff --git a/third_party/lua-cjson/lua_cjson.c b/third_party/lua-cjson/lua_cjson.c index 1ee2e1c109..9c1fb6106a 100644 --- a/third_party/lua-cjson/lua_cjson.c +++ b/third_party/lua-cjson/lua_cjson.c @@ -998,13 +998,27 @@ static int json_decode(lua_State *l) luaL_argcheck(l, lua_gettop(l) == 2 || lua_gettop(l) == 1, 1, "expected 1 or 2 arguments"); + struct luaL_serializer *cfg = luaL_checkserializer(l); + + /* + * user_cfg is per-call local version of serializer instance + * options: it is used if a user passes custom options to + * :decode() method within a separate argument. In this case + * it is required to avoid modifying options of the instance. + * Life span of user_cfg is restricted by the scope of + * :decode() so it is enough to allocate it on the stack. + */ + struct luaL_serializer user_cfg; + json.cfg = cfg; if (lua_gettop(l) == 2) { - struct luaL_serializer *user_cfg = luaL_checkserializer(l); - luaL_serializer_parse_options(l, user_cfg); + /* + * on_update triggers are left uninitialized for user_cfg. + * The decoding code don't (and shouldn't) run them. + */ + luaL_serializer_copy_options(&user_cfg, cfg); + luaL_serializer_parse_options(l, &user_cfg); lua_pop(l, 1); - json.cfg = user_cfg; - } else { - json.cfg = luaL_checkserializer(l); + json.cfg = &user_cfg; } json.data = luaL_checklstring(l, 1, &json_len); -- GitLab