From 4e4a19df1d2e5100560fbe7d9afa7a5502e528ec Mon Sep 17 00:00:00 2001 From: Paul Kulchenko Date: Tue, 5 Nov 2013 11:52:05 -0800 Subject: [PATCH] Added `load` function to deserialize; updated documentation (closes #9). --- README.md | 14 ++++++++++++++ misc/serpent-0.26-1.rockspec | 25 +++++++++++++++++++++++++ src/serpent.lua | 20 +++++++++++++++++++- t/test.lua | 31 ++++++++++++++++++++++++++++--- 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 misc/serpent-0.26-1.rockspec diff --git a/README.md b/README.md index 3474028..1085a3d 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,10 @@ print(serpent.block(a)) -- multi-line indented, no self-ref section local fun, err = loadstring(serpent.dump(a)) if err then error(err) end local copy = fun() + +-- or using serpent.load: +local ok, copy = serpent.load(serpent.dump(a)) +print(ok and copy[3] == a[3]) ``` ## Functions @@ -51,6 +55,12 @@ internal function, but set different options by default: Note that `line` and `block` functions return pretty-printed data structures and if you want to deserialize them, you need to add `return` before running them through `loadstring`. For example: `loadstring('return '..require('mobdebug').line("foo"))() == "foo"`. +While you can use `loadstring` or `load` functions to load serialized fragments, Serpent also provides `load` function that adds safety checks and reports an error if there is any executable code in the fragment. + +* `ok, res = load(str[, {safe = true}])` -- loads serialized fragment; you need to pass `{safe = false}` as the second value if you want to turn safety checks off. + +Similar to `pcall` and `loadstring` calls, `load` returns status as the first value and the result or the error message as the second value. + ## Options * indent (string) -- indentation; triggers long multi-line output @@ -167,6 +177,10 @@ See LICENSE file. ## History +### v0.26 (Nov 05 2013) + - Added `load` function that (safely) loads serialized/pretty-printed values. + - Updated documentation. + ### v0.25 (Sep 29 2013) - Added `maxnum` option to limit the number of elements in tables. - Optimized processing of tables with numeric indexes. diff --git a/misc/serpent-0.26-1.rockspec b/misc/serpent-0.26-1.rockspec new file mode 100644 index 0000000..5a4f0ec --- /dev/null +++ b/misc/serpent-0.26-1.rockspec @@ -0,0 +1,25 @@ +package = "serpent" +version = "0.26-1" +source = { + url = "git://github.com/pkulchenko/serpent.git", + tag = "0.26" +} + +description = { + summary = "Lua serializer and pretty printer", + homepage = "https://github.com/pkulchenko/serpent", + maintainer = "Paul Kulchenko ", + license = "MIT", +} + +dependencies = { + "lua >= 5.1, < 5.3", +} + +build = { + type = "builtin", + modules = { + ["serpent"] = "src/serpent.lua", + }, + copy_directories = { "t" }, +} diff --git a/src/serpent.lua b/src/serpent.lua index f4f08de..885d7cf 100644 --- a/src/serpent.lua +++ b/src/serpent.lua @@ -1,4 +1,4 @@ -local n, v = "serpent", 0.25 -- (C) 2012-13 Paul Kulchenko; MIT License +local n, v = "serpent", 0.26 -- (C) 2012-13 Paul Kulchenko; MIT License local c, d = "Paul Kulchenko", "Lua serializer and pretty printer" local snum = {[tostring(1/0)]='1/0 --[[math.huge]]',[tostring(-1/0)]='-1/0 --[[-math.huge]]',[tostring(0/0)]='0/0'} local badtype = {thread = true, userdata = true, cdata = true} @@ -103,8 +103,26 @@ local function s(t, opts) return not name and body..warn or "do local "..body..sepr..tail.."return "..name..sepr.."end" end +local function deserialize(data, opts) + local f, res = (loadstring or load)('return '..data) + if not f then f, res = (loadstring or load)(data) end + if not f then return f, res end + if opts and opts.safe == false then return pcall(f) end + + local count, thread = 0, coroutine.running() + local h, m, c = debug.gethook(thread) + debug.sethook(function (e, l) count = count + 1 + if count >= 3 then error("cannot call functions") end + end, "c") + local res = {pcall(f)} + count = 0 -- set again, otherwise it's tripped on the next sethook + debug.sethook(thread, h, m, c) + return (table.unpack or unpack)(res) +end + local function merge(a, b) if b then for k,v in pairs(b) do a[k] = v end end; return a; end return { _NAME = n, _COPYRIGHT = c, _DESCRIPTION = d, _VERSION = v, serialize = s, + load = deserialize, dump = function(a, opts) return s(a, merge({name = '_', compact = true, sparse = true}, opts)) end, line = function(a, opts) return s(a, merge({sortkeys = true, comment = true}, opts)) end, block = function(a, opts) return s(a, merge({indent = ' ', sortkeys = true, comment = true}, opts)) end } diff --git a/t/test.lua b/t/test.lua index 83d96e6..8c602bd 100644 --- a/t/test.lua +++ b/t/test.lua @@ -261,8 +261,35 @@ do assert(called == false, "sorting is not called on numeric-only tables with maxnum: failed") end --- test serializing large numeric-only tables do + local ok, res = serpent.load(serpent.line(10)) + assert(ok and res == 10, "deserialization of simple number values: failed") + + local ok, res = serpent.load(serpent.line(true)) + assert(ok and res == true, "deserialization of simple boolean values: failed") + + local ok, res = serpent.load(serpent.line({3,4})) + assert(ok and #res == 2 and res[1] == 3 and res[2] == 4, + "deserialization of pretty-printed tables: failed") + + local ok, res = serpent.load(serpent.dump({3,4})) + assert(ok and #res == 2 and res[1] == 3 and res[2] == 4, + "deserialization of serialized tables: failed") + + local ok, res = serpent.load('{a = math.random()}') + assert(not ok and res:find("cannot call functions"), + "deserialization of unsafe values: failed") + + local ok, res = serpent.load('{a = math.random()}', {safe = false}) + assert(ok and res and res.a > 0, + "deserialization of unsafe values disabled: failed") +end + +print("All tests passed.") + +do + print("\nSerializing large numeric-only tables:") + local a, str = {} for i = 1, 100000 do a[i] = i end @@ -278,5 +305,3 @@ do str = serpent.dump(a, {sparse = false}) print("dump/sparse=false: "..(os.clock() - start), #str) end - -print("All tests passed.")