elseif tp == "function" then
return string.format("loadstring(%q)", string.dump(x))
elseif tp == "number" then
- -- Serialize integers with string.format to prevent
- -- scientific notation, which doesn't preserve
- -- precision and breaks things like node position
- -- hashes. Serialize floats normally.
- if math.floor(x) == x then
- return string.format("%d", x)
- else
- return tostring(x)
- end
+ -- Serialize numbers reversibly with string.format
+ return string.format("%.17g", x)
elseif tp == "table" then
local vals = {}
local idx_dumped = {}
-- Deserialization
-local env = {
- loadstring = loadstring,
-}
+local function safe_loadstring(...)
+ local func, err = loadstring(...)
+ if func then
+ setfenv(func, {})
+ return func
+ end
+ return nil, err
+end
-local safe_env = {
- loadstring = function() end,
-}
+local function dummy_func() end
function core.deserialize(str, safe)
if type(str) ~= "string" then
end
local f, err = loadstring(str)
if not f then return nil, err end
- setfenv(f, safe and safe_env or env)
+
+ -- The environment is recreated every time so deseralized code cannot
+ -- pollute it with permanent references.
+ setfenv(f, {loadstring = safe and dummy_func or safe_loadstring})
local good, data = pcall(f)
if good then
return nil, data
end
end
-
-
--- Unit tests
-local test_in = {cat={sound="nyan", speed=400}, dog={sound="woof"}}
-local test_out = core.deserialize(core.serialize(test_in))
-
-assert(test_in.cat.sound == test_out.cat.sound)
-assert(test_in.cat.speed == test_out.cat.speed)
-assert(test_in.dog.sound == test_out.dog.sound)
-
-test_in = {escape_chars="\n\r\t\v\\\"\'", non_european="θשׁ٩∂"}
-test_out = core.deserialize(core.serialize(test_in))
-assert(test_in.escape_chars == test_out.escape_chars)
-assert(test_in.non_european == test_out.non_european)