Make redbean serialization deterministic

This commit is contained in:
Justine Tunney 2022-07-09 04:09:51 -07:00
parent 85aecbda67
commit c9e68b0ebc
15 changed files with 452 additions and 150 deletions

11
libc/log/rop.h Normal file
View file

@ -0,0 +1,11 @@
#ifndef COSMOPOLITAN_LIBC_LOG_ROP_H_
#define COSMOPOLITAN_LIBC_LOG_ROP_H_
#define RETURN_ON_ERROR(expr) \
do { \
if ((expr) == -1) { \
goto OnError; \
} \
} while (0)
#endif /* COSMOPOLITAN_LIBC_LOG_ROP_H_ */

80
libc/stdio/strlist.c Normal file
View file

@ -0,0 +1,80 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/alg/alg.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/strlist.internal.h"
#include "libc/str/str.h"
static int CompareStrings(const void *p1, const void *p2) {
const char **a = p1;
const char **b = p2;
return strcmp(*a, *b);
}
void FreeStrList(struct StrList *sl) {
int i;
for (i = 0; i < sl->i; ++i) {
free(sl->p[i]);
}
free(sl->p);
sl->p = 0;
sl->i = 0;
sl->n = 0;
}
int AppendStrList(struct StrList *sl) {
int n2;
char **p2;
if (sl->i == sl->n) {
n2 = sl->n;
if (!n2) n2 = 2;
n2 += n2 >> 1;
if ((p2 = realloc(sl->p, n2 * sizeof(*p2)))) {
sl->p = p2;
sl->n = n2;
} else {
return -1;
}
}
sl->p[sl->i] = 0;
appendr(&sl->p[sl->i], 0);
return sl->i++;
}
void SortStrList(struct StrList *sl) {
qsort(sl->p, sl->i, sizeof(*sl->p), CompareStrings);
}
int JoinStrList(struct StrList *sl, char **buf, uint64_t sep) {
int i;
if (!*buf && !sl->i) {
return appendr(buf, 0);
}
for (i = 0; i < sl->i; ++i) {
if (i) {
if (appendw(buf, sep) == -1) {
return -1;
}
}
if (appends(buf, sl->p[i]) == -1) {
return -1;
}
}
return 0;
}

View file

@ -0,0 +1,18 @@
#ifndef COSMOPOLITAN_LIBC_STDIO_STRLIST_H_
#define COSMOPOLITAN_LIBC_STDIO_STRLIST_H_
#if !(__ASSEMBLER__ + __LINKER__ + 0)
COSMOPOLITAN_C_START_
struct StrList {
int i, n;
char **p;
};
void FreeStrList(struct StrList *) hidden;
int AppendStrList(struct StrList *) hidden;
void SortStrList(struct StrList *) hidden;
int JoinStrList(struct StrList *, char **, uint64_t) hidden;
COSMOPOLITAN_C_END_
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
#endif /* COSMOPOLITAN_LIBC_STDIO_STRLIST_H_ */

View file

@ -0,0 +1,58 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2022 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/bits.h"
#include "libc/intrin/kprintf.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.internal.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/stdio.h"
#include "libc/stdio/strlist.internal.h"
#include "libc/testlib/testlib.h"
struct StrList sl;
void TearDown(void) {
FreeStrList(&sl);
}
TEST(strlist, test) {
int i;
char *b = 0;
ASSERT_NE(-1, (i = AppendStrList(&sl)));
ASSERT_NE(-1, appends(&sl.p[i], "world"));
ASSERT_NE(-1, (i = AppendStrList(&sl)));
ASSERT_NE(-1, appends(&sl.p[i], "hello"));
SortStrList(&sl);
ASSERT_NE(-1, JoinStrList(&sl, &b, READ16LE(", ")));
EXPECT_STREQ("hello, world", b);
free(b);
}
TEST(strlist, testNumbers) {
int i;
char *b = 0;
ASSERT_NE(-1, (i = AppendStrList(&sl)));
ASSERT_NE(-1, appends(&sl.p[i], "2"));
ASSERT_NE(-1, (i = AppendStrList(&sl)));
ASSERT_NE(-1, appends(&sl.p[i], "1"));
SortStrList(&sl);
ASSERT_NE(-1, JoinStrList(&sl, &b, ':'));
EXPECT_STREQ("1:2", b);
free(b);
}

View file

@ -54,22 +54,39 @@ assert(EncodeLatin1("helloÿÀ") == "hello\xff\xc0")
assert(EncodeLua(nil) == "nil")
assert(EncodeLua(0) == "0")
assert(EncodeLua(3.14) == "3.14")
assert(EncodeLua({1, 2}) == "{1, 2}")
x = {1, 2}
x[3] = x
assert(string.match(EncodeLua(x), "{1, 2, \"cyclic@0x%x+\"}"))
-- TODO(jart): EncodeLua() should sort tables
-- x = {}
-- x.c = 'c'
-- x.a = 'a'
-- x.b = 'b'
-- assert(EncodeLua(x) == '{a="a", b="b", c="c"}')
assert(EncodeLua({2, 1}) == "{1, 2}")
assert(EncodeJson(nil) == "null")
assert(EncodeJson(0) == "0")
assert(EncodeJson(3.14) == "3.14")
assert(EncodeJson({1, 2}) == "[1,2]")
assert(EncodeJson({2, 1}) == "[1,2]")
-- EncodeLua() permits serialization of cyclic data structures
x = {2, 1}
x[3] = x
assert(string.match(EncodeLua(x), "{\"cyclic@0x%x+\", 1, 2}"))
-- EncodeLua() sorts table entries
x = {}
x.c = 'c'
x.a = 'a'
x.b = 'b'
assert(EncodeLua(x) == '{a="a", b="b", c="c"}')
-- EncodeJson() doesn't permit serialization of cyclic data structures
x = {2, 1}
x[3] = x
json, err = EncodeJson(x)
assert(not json)
assert(err == "serialization failed")
-- EncodeJson() sorts table entries
-- JSON always requires quotes around key names
x = {}
x.c = 'c'
x.a = 'a'
x.b = 'b'
assert(EncodeJson(x) == '{"a":"a","b":"b","c":"c"}')
url = ParseUrl("https://jart:pass@redbean.dev/2.0.html?x&y=z#frag")
assert(url.scheme == "https")
@ -141,3 +158,17 @@ assert(Uncompress(Compress("hello")) == "hello")
assert(Compress("hello") == "\x05\x86\xa6\x106x\x9c\xcbH\xcd\xc9\xc9\x07\x00\x06,\x02\x15")
assert(Compress("hello", 0) == "\x05\x86\xa6\x106x\x01\x01\x05\x00\xfa\xffhello\x06,\x02\x15")
assert(Compress("hello", 0, true) == "x\x01\x01\x05\x00\xfa\xffhello\x06,\x02\x15")
----------------------------------------------------------------------------------------------------
-- benchmarks
function LuaSerialization()
EncodeLua({2, 1, 10, 3, "hello"})
end
function JsonSerialization()
EncodeJson({2, 1, 10, 3, "hello"})
end
print(Benchmark(LuaSerialization), "LuaSerialization")
print(Benchmark(JsonSerialization), "JsonSerialization")

View file

@ -140,8 +140,11 @@ function UnixTest()
assert(unix.close(fd))
-- getdents
t = {}
for name, kind, ino, off in assert(unix.opendir(tmpdir)) do
table.insert(t, name)
end
assert(EncodeLua(t) == '{".", "..", "foo"}');
end

View file

@ -14,7 +14,7 @@ int LuaEncodeUrl(lua_State *);
int LuaParseUrl(lua_State *);
int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int);
int LuaPushHeaders(lua_State *, struct HttpMessage *, const char *);
void EscapeLuaString(char *, size_t, char **);
int EscapeLuaString(char *, size_t, char **);
void LuaPrintStack(lua_State *);
void LuaPushLatin1(lua_State *, const char *, size_t);
void LuaPushUrlParams(lua_State *, struct UrlParams *);

View file

@ -17,25 +17,32 @@
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/bits/bits.h"
#include "libc/log/rop.h"
#include "libc/stdio/append.internal.h"
#include "libc/str/str.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lua.h"
void EscapeLuaString(char *s, size_t len, char **buf) {
appendw(buf, '"');
for (size_t i = 0; i < len; i++) {
int EscapeLuaString(char *s, size_t len, char **buf) {
int rc;
size_t i;
RETURN_ON_ERROR(appendw(buf, '"'));
for (i = 0; i < len; i++) {
if (' ' <= s[i] && s[i] <= 0x7e) {
appendw(buf, s[i]);
RETURN_ON_ERROR(appendw(buf, s[i]));
} else if (s[i] == '\n') {
appendw(buf, '\\' | 'n' << 8);
RETURN_ON_ERROR(appendw(buf, '\\' | 'n' << 8));
} else if (s[i] == '\\' || s[i] == '\'' || s[i] == '\"') {
appendw(buf, '\\' | s[i] << 8);
RETURN_ON_ERROR(appendw(buf, '\\' | s[i] << 8));
} else {
appendw(buf, '\\' | 'x' << 010 |
"0123456789abcdef"[(s[i] & 0xF0) >> 4] << 020 |
"0123456789abcdef"[(s[i] & 0x0F) >> 0] << 030);
RETURN_ON_ERROR(
appendw(buf, '\\' | 'x' << 010 |
"0123456789abcdef"[(s[i] & 0xF0) >> 4] << 020 |
"0123456789abcdef"[(s[i] & 0x0F) >> 0] << 030));
}
}
appendw(buf, '"');
RETURN_ON_ERROR(appendw(buf, '"'));
return 0;
OnError:
return -1;
}

View file

@ -19,9 +19,11 @@
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/fmt/itoa.h"
#include "libc/log/rop.h"
#include "libc/mem/mem.h"
#include "libc/runtime/gc.internal.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/strlist.internal.h"
#include "net/http/escape.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
@ -32,34 +34,37 @@ static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
char *numformat, int idx,
struct LuaVisited *visited) {
char *s;
int sli, rc;
bool isarray;
size_t tbllen, i, z;
struct StrList sl = {0};
char ibuf[21], fmt[] = "%.14g";
if (level > 0) {
switch (lua_type(L, idx)) {
case LUA_TNIL:
appendw(buf, READ32LE("null"));
RETURN_ON_ERROR(appendw(buf, READ32LE("null")));
return 0;
case LUA_TBOOLEAN:
appendw(buf, lua_toboolean(L, idx) ? READ32LE("true")
: READ64LE("false\0\0"));
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
? READ32LE("true")
: READ64LE("false\0\0")));
return 0;
case LUA_TSTRING:
s = lua_tolstring(L, idx, &z);
s = EscapeJsStringLiteral(s, z, &z);
appendw(buf, '"');
appendd(buf, s, z);
appendw(buf, '"');
if (!(s = EscapeJsStringLiteral(s, z, &z))) goto OnError;
RETURN_ON_ERROR(appendw(buf, '"'));
RETURN_ON_ERROR(appendd(buf, s, z));
RETURN_ON_ERROR(appendw(buf, '"'));
free(s);
return 0;
case LUA_TNUMBER:
if (lua_isinteger(L, idx)) {
appendd(buf, ibuf,
FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf);
RETURN_ON_ERROR(appendd(
buf, ibuf, FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf));
} else {
// TODO(jart): replace this api
while (*numformat == '%' || *numformat == '.' ||
@ -73,16 +78,16 @@ static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
fmt[4] = *numformat;
break;
default:
free(visited->p);
luaL_error(L, "numformat string not allowed");
unreachable;
// prevent format string hacking
goto OnError;
}
appendf(buf, fmt, lua_tonumber(L, idx));
RETURN_ON_ERROR(appendf(buf, fmt, lua_tonumber(L, idx)));
}
return 0;
case LUA_TTABLE:
if (LuaPushVisit(visited, lua_topointer(L, idx))) {
RETURN_ON_ERROR(rc = LuaPushVisit(visited, lua_topointer(L, idx)));
if (!rc) {
lua_pushvalue(L, idx); // table ref
tbllen = lua_rawlen(L, -1);
// encode tables with numeric indices and empty tables as arrays
@ -90,12 +95,12 @@ static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
tbllen > 0 || // integer keys present
(lua_pushnil(L), !lua_next(L, -2)) || // no non-integer keys
(lua_pop(L, 2), false); // pop key/value pushed by lua_next
appendw(buf, isarray ? '[' : '{');
if (isarray) {
for (i = 1; i <= tbllen; i++) {
if (i > 1) appendw(buf, ',');
RETURN_ON_ERROR(sli = AppendStrList(&sl));
lua_rawgeti(L, -1, i); // table/-2, value/-1
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1, visited);
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(L, &sl.p[sli], level - 1,
numformat, -1, visited));
lua_pop(L, 1);
}
} else {
@ -103,45 +108,58 @@ static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
lua_pushnil(L); // push the first key
while (lua_next(L, -2)) {
if (lua_type(L, -2) != LUA_TSTRING) {
free(visited->p);
luaL_error(L, "json tables must be arrays or use string keys");
unreachable;
// json tables must be arrays or use string keys
goto OnError;
}
if (i++ > 1) appendw(buf, ',');
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -2, visited);
appendw(buf, ':');
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1, visited);
RETURN_ON_ERROR(sli = AppendStrList(&sl));
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(L, &sl.p[sli], level - 1,
numformat, -2, visited));
RETURN_ON_ERROR(appendw(&sl.p[sli], ':'));
RETURN_ON_ERROR(LuaEncodeJsonDataImpl(L, &sl.p[sli], level - 1,
numformat, -1, visited));
lua_pop(L, 1); // table/-2, key/-1
}
// stack: table/-1, as the key was popped by lua_next
}
appendw(buf, isarray ? ']' : '}');
RETURN_ON_ERROR(appendw(buf, isarray ? '[' : '{'));
SortStrList(&sl);
RETURN_ON_ERROR(JoinStrList(&sl, buf, ','));
FreeStrList(&sl);
RETURN_ON_ERROR(appendw(buf, isarray ? ']' : '}'));
LuaPopVisit(visited);
lua_pop(L, 1); // table ref
return 0;
} else {
free(visited->p);
luaL_error(L, "can't serialize cyclic data structure to json");
unreachable;
// cyclic data structure
goto OnError;
}
default:
free(visited->p);
luaL_error(L, "won't serialize %s to json", luaL_typename(L, idx));
unreachable;
// unsupported lua type
goto OnError;
}
} else {
free(visited->p);
luaL_error(L, "too many nested tables");
unreachable;
// too much depth
goto OnError;
}
OnError:
FreeStrList(&sl);
return -1;
}
/**
* Encodes Lua data structure as JSON.
*
* @param L is Lua interpreter state
* @param buf receives encoded output string
* @param numformat controls double formatting
* @param idx is index of item on Lua stack
* @return 0 on success, or -1 on error
*/
int LuaEncodeJsonData(lua_State *L, char **buf, char *numformat, int idx) {
int rc;
struct LuaVisited visited = {0};
rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx, &visited);
assert(!visited.n);
free(visited.p);
return rc;
}

View file

@ -19,9 +19,11 @@
#include "libc/assert.h"
#include "libc/bits/bits.h"
#include "libc/fmt/itoa.h"
#include "libc/log/rop.h"
#include "libc/math.h"
#include "libc/mem/mem.h"
#include "libc/stdio/append.internal.h"
#include "libc/stdio/strlist.internal.h"
#include "libc/x/x.h"
#include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.h"
@ -60,41 +62,45 @@ static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
char *s;
bool isarray;
lua_Integer i;
int ktype, vtype;
struct StrList sl = {0};
int ktype, vtype, sli, rc;
size_t tbllen, buflen, slen;
char ibuf[24], fmt[] = "%.14g";
if (level > 0) {
switch (lua_type(L, idx)) {
case LUA_TNIL:
appendw(buf, READ32LE("nil"));
RETURN_ON_ERROR(appendw(buf, READ32LE("nil")));
return 0;
case LUA_TSTRING:
s = lua_tolstring(L, idx, &slen);
EscapeLuaString(s, slen, buf);
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
return 0;
case LUA_TFUNCTION:
appendf(buf, "\"func@%p\"", lua_topointer(L, idx));
RETURN_ON_ERROR(
appendf(buf, "\"%s@%p\"", "func", lua_topointer(L, idx)));
return 0;
case LUA_TLIGHTUSERDATA:
appendf(buf, "\"light@%p\"", lua_topointer(L, idx));
RETURN_ON_ERROR(
appendf(buf, "\"%s@%p\"", "light", lua_topointer(L, idx)));
return 0;
case LUA_TTHREAD:
appendf(buf, "\"thread@%p\"", lua_topointer(L, idx));
RETURN_ON_ERROR(
appendf(buf, "\"%s@%p\"", "thread", lua_topointer(L, idx)));
return 0;
case LUA_TUSERDATA:
if (luaL_callmeta(L, idx, "__repr")) {
if (lua_type(L, -1) == LUA_TSTRING) {
s = lua_tolstring(L, -1, &slen);
appendd(buf, s, slen);
RETURN_ON_ERROR(appendd(buf, s, slen));
} else {
appendf(buf, "[[error %s returned a %s value]]", "__repr",
luaL_typename(L, -1));
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
"__repr", luaL_typename(L, -1)));
}
lua_pop(L, 1);
return 0;
@ -102,21 +108,23 @@ static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
if (luaL_callmeta(L, idx, "__tostring")) {
if (lua_type(L, -1) == LUA_TSTRING) {
s = lua_tolstring(L, -1, &slen);
EscapeLuaString(s, slen, buf);
RETURN_ON_ERROR(EscapeLuaString(s, slen, buf));
} else {
appendf(buf, "[[error %s returned a %s value]]", "__tostring",
luaL_typename(L, -1));
RETURN_ON_ERROR(appendf(buf, "[[error %s returned a %s value]]",
"__tostring", luaL_typename(L, -1)));
}
lua_pop(L, 1);
return 0;
}
appendf(buf, "\"udata@%p\"", lua_touserdata(L, idx));
RETURN_ON_ERROR(
appendf(buf, "\"%s@%p\"", "udata", lua_touserdata(L, idx)));
return 0;
case LUA_TNUMBER:
if (lua_isinteger(L, idx)) {
appendd(buf, ibuf,
FormatFlex64(ibuf, luaL_checkinteger(L, idx), 2) - ibuf);
RETURN_ON_ERROR(
appendd(buf, ibuf,
FormatFlex64(ibuf, luaL_checkinteger(L, idx), 2) - ibuf));
} else {
// TODO(jart): replace this api
while (*numformat == '%' || *numformat == '.' ||
@ -130,71 +138,87 @@ static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level,
fmt[4] = *numformat;
break;
default:
free(visited->p);
luaL_error(L, "numformat string not allowed");
unreachable;
// prevent format string hacking
goto OnError;
}
appendf(buf, fmt, lua_tonumber(L, idx));
RETURN_ON_ERROR(appendf(buf, fmt, lua_tonumber(L, idx)));
}
return 0;
case LUA_TBOOLEAN:
appendw(buf, lua_toboolean(L, idx) ? READ32LE("true")
: READ64LE("false\0\0"));
RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
? READ32LE("true")
: READ64LE("false\0\0")));
return 0;
case LUA_TTABLE:
i = 0;
if (LuaPushVisit(visited, lua_topointer(L, idx))) {
appendw(buf, '{');
RETURN_ON_ERROR(rc = LuaPushVisit(visited, lua_topointer(L, idx)));
if (!rc) {
lua_pushvalue(L, idx); // idx becomes invalid once we change stack
isarray = IsLuaArray(L);
lua_pushnil(L); // push the first key
while (lua_next(L, -2)) {
ktype = lua_type(L, -2);
vtype = lua_type(L, -1);
if (i++ > 0) appendw(buf, ',' | ' ' << 8);
RETURN_ON_ERROR(sli = AppendStrList(&sl));
if (isarray) {
// use {v₁,v₂,...} for lua-normal integer keys
} else if (ktype == LUA_TSTRING && IsLuaIdentifier(L, -2)) {
// use {𝑘=𝑣} syntax when 𝑘 is legal as a lua identifier
s = lua_tolstring(L, -2, &slen);
appendd(buf, s, slen);
appendw(buf, '=');
RETURN_ON_ERROR(appendd(&sl.p[sli], s, slen));
RETURN_ON_ERROR(appendw(&sl.p[sli], '='));
} else {
// use {[𝑘]=𝑣} otherwise
appendw(buf, '[');
LuaEncodeLuaDataImpl(L, buf, level - 1, numformat, -2, visited);
appendw(buf, ']' | '=' << 010);
RETURN_ON_ERROR(appendw(&sl.p[sli], '['));
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
numformat, -2, visited));
RETURN_ON_ERROR(appendw(&sl.p[sli], ']' | '=' << 010));
}
LuaEncodeLuaDataImpl(L, buf, level - 1, numformat, -1, visited);
RETURN_ON_ERROR(LuaEncodeLuaDataImpl(L, &sl.p[sli], level - 1,
numformat, -1, visited));
lua_pop(L, 1); // table/-2, key/-1
}
lua_pop(L, 1); // table ref
RETURN_ON_ERROR(appendw(buf, '{'));
SortStrList(&sl);
RETURN_ON_ERROR(JoinStrList(&sl, buf, READ16LE(", ")));
RETURN_ON_ERROR(appendw(buf, '}'));
FreeStrList(&sl);
LuaPopVisit(visited);
appendw(buf, '}');
} else {
appendf(buf, "\"cyclic@%p\"", lua_topointer(L, idx));
RETURN_ON_ERROR(
appendf(buf, "\"%s@%p\"", "cyclic", lua_topointer(L, idx)));
}
return 0;
default:
free(visited->p);
luaL_error(L, "can't serialize value of this type");
unreachable;
// unsupported lua type
goto OnError;
}
} else {
free(visited->p);
luaL_error(L, "too many nested tables");
unreachable;
// too much depth
goto OnError;
}
OnError:
FreeStrList(&sl);
return -1;
}
/**
* Encodes Lua data structure as Lua code string.
*
* @param L is Lua interpreter state
* @param buf receives encoded output string
* @param numformat controls double formatting
* @param idx is index of item on Lua stack
* @return 0 on success, or -1 on error
*/
int LuaEncodeLuaData(lua_State *L, char **buf, char *numformat, int idx) {
int rc;
struct LuaVisited visited = {0};
rc = LuaEncodeLuaDataImpl(L, buf, 64, numformat, idx, &visited);
assert(!visited.n);
free(visited.p);
return rc;
}

View file

@ -20,16 +20,23 @@
#include "libc/x/x.h"
#include "third_party/lua/visitor.h"
bool LuaPushVisit(struct LuaVisited *visited, const void *p) {
int i;
int LuaPushVisit(struct LuaVisited *visited, const void *p) {
int i, n2;
const void **p2;
for (i = 0; i < visited->n; ++i) {
if (visited->p[i] == p) {
return false;
return 1;
}
}
visited->p = xrealloc(visited->p, ++visited->n * sizeof(*visited->p));
n2 = visited->n;
if ((p2 = realloc(visited->p, ++n2 * sizeof(*visited->p)))) {
visited->p = p2;
visited->n = n2;
} else {
return -1;
}
visited->p[visited->n - 1] = p;
return true;
return 0;
}
void LuaPopVisit(struct LuaVisited *visited) {

View file

@ -8,7 +8,7 @@ struct LuaVisited {
const void **p;
};
bool LuaPushVisit(struct LuaVisited *, const void *);
int LuaPushVisit(struct LuaVisited *, const void *);
void LuaPopVisit(struct LuaVisited *);
COSMOPOLITAN_C_END_

View file

@ -625,10 +625,8 @@
(compile (format "sh %s" file)))
((eq major-mode 'lua-mode)
(let* ((mode (cosmo--make-mode arg))
(redbean (format "%s/o/%s/tool/net/redbean.com" root mode)))
(if (file-executable-p redbean)
(compile (format "%s -i %s" redbean file))
(compile (format "lua.com %s" file)))))
(redbean ))
(compile (format "make -j16 MODE=%s o/%s/tool/net/redbean.com && o/%s/tool/net/redbean.com -i %s" mode mode mode file))))
((and (eq major-mode 'python-mode)
(cosmo-startswith "third_party/python/Lib/test/" file))
(let ((mode (cosmo--make-mode arg)))

View file

@ -647,26 +647,66 @@ FUNCTIONS
URIs that do things like embed a PNG file in a web page. See
encodebase64.c.
EncodeJson(value[,options:table]) → json:str
Turns passed Lua value into a JSON string. Tables with non-zero
length (as reported by `#`) are encoded as arrays with non-array
elements ignored. Empty tables are encoded as empty arrays. All
other tables are encoded as objects with numerical keys
converted to strings (so `{[3]=1}` is encoded as `{"3":1}`).
The following options can be used:
EncodeJson(value[,options:table])
├─→ json:str
├─→ true [if useoutput]
└─→ nil, error:str
Turns Lua data structure into a JSON string.
Tables with non-zero length (as reported by `#`) are encoded
as arrays with non-array elements ignored. Empty tables are
encoded as empty arrays. All other tables are encoded as
objects with numerical keys converted to strings (so `{[3]=1}`
is encoded as `{"3":1}`).
The following options may be used:
- useoutput: (bool=false) encodes the result directly to the
output buffer and returns `nil` value. This option is
ignored if used outside of request handling code.
- numformat: sets numeric format to be used, which can be 'g',
'f', or 'a' [experimental api]
EncodeLua(value[,options:table]) → json:str
Turns passed Lua value into a Lua string. The following options
can be used:
This function will fail if:
- `value` is cyclic
- `value` has depth greater than 64
- `value` contains functions, user data, or threads
When arrays and objects are serialized, entries will be sorted
in a deterministic order.
EncodeLua(value[,options:table])
├─→ luacode:str
├─→ true [if useoutput]
└─→ nil, error:str
Turns Lua data structure into Lua code string.
The following options may be used:
- useoutput: (bool=false) encodes the result directly to the
output buffer and returns `nil` value. This option is
ignored if used outside of request handling code.
This function will fail if:
- `value` has depth greater than 64
If a user data object has a `__repr` or `__tostring` meta
method, then that'll be used to encode the Lua code.
Non-encodable value types (e.g. threads, functions) will be
represented as a string literal with the type name and pointer
address. Note this is subject to change in the future.
This encoder detects cyclic tables, and encodes a string
literal saying it's cyclic when cycles are encountered.
When tables are serialized, entries will be sorted in a
deterministic order.
EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str
Turns UTF-8 into ISO-8859-1 string.

View file

@ -4231,9 +4231,14 @@ static int LuaEncodeSmth(lua_State *L,
numformat = luaL_optstring(L, -1, numformat);
}
lua_settop(L, 1); // keep the passed argument on top
Encoder(L, useoutput ? &outbuf : &p, numformat, -1);
if (useoutput) {
if (Encoder(L, useoutput ? &outbuf : &p, numformat, -1) == -1) {
free(p);
lua_pushnil(L);
lua_pushstring(L, "serialization failed");
return 2;
}
if (useoutput) {
lua_pushboolean(L, true);
} else {
lua_pushstring(L, p);
free(p);
@ -5394,12 +5399,45 @@ static int LuaInterpreter(lua_State *L) {
}
}
static void LuaDestroy(void) {
#ifndef STATIC
lua_State *L = GL;
lua_close(L);
free(g_lua_path_default);
#endif
}
static void MemDestroy(void) {
FreeAssets();
CollectGarbage();
inbuf.p = 0, inbuf.n = 0, inbuf.c = 0;
Free(&inbuf_actual.p), inbuf_actual.n = inbuf_actual.c = 0;
Free(&unmaplist.p), unmaplist.n = unmaplist.c = 0;
Free(&freelist.p), freelist.n = freelist.c = 0;
Free(&hdrbuf.p), hdrbuf.n = hdrbuf.c = 0;
Free(&servers.p), servers.n = 0;
Free(&ports.p), ports.n = 0;
Free(&ips.p), ips.n = 0;
Free(&outbuf);
FreeStrings(&stagedirs);
FreeStrings(&hidepaths);
Free(&launchbrowser);
Free(&serverheader);
Free(&extrahdrs);
Free(&pidpath);
Free(&logpath);
Free(&brand);
Free(&polls);
}
static void LuaInit(void) {
#ifndef STATIC
lua_State *L = GL;
LuaSetArgv(L);
if (interpretermode) {
int rc = LuaInterpreter(L);
LuaDestroy();
MemDestroy();
if (IsModeDbg()) {
CheckForMemoryLeaks();
}
@ -5426,14 +5464,6 @@ static void LuaReload(void) {
#endif
}
static void LuaDestroy(void) {
#ifndef STATIC
lua_State *L = GL;
lua_close(L);
free(g_lua_path_default);
#endif
}
static const char *DescribeClose(void) {
if (killed) return "killed";
if (meltdown) return "meltdown";
@ -7183,29 +7213,6 @@ static void TlsDestroy(void) {
#endif
}
static void MemDestroy(void) {
FreeAssets();
CollectGarbage();
inbuf.p = 0, inbuf.n = 0, inbuf.c = 0;
Free(&inbuf_actual.p), inbuf_actual.n = inbuf_actual.c = 0;
Free(&unmaplist.p), unmaplist.n = unmaplist.c = 0;
Free(&freelist.p), freelist.n = freelist.c = 0;
Free(&hdrbuf.p), hdrbuf.n = hdrbuf.c = 0;
Free(&servers.p), servers.n = 0;
Free(&ports.p), ports.n = 0;
Free(&ips.p), ips.n = 0;
Free(&outbuf);
FreeStrings(&stagedirs);
FreeStrings(&hidepaths);
Free(&launchbrowser);
Free(&serverheader);
Free(&extrahdrs);
Free(&pidpath);
Free(&logpath);
Free(&brand);
Free(&polls);
}
static void GetOpts(int argc, char *argv[]) {
int opt;
bool storeasset = false;