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(nil) == "nil")
assert(EncodeLua(0) == "0") assert(EncodeLua(0) == "0")
assert(EncodeLua(3.14) == "3.14") assert(EncodeLua(3.14) == "3.14")
assert(EncodeLua({1, 2}) == "{1, 2}") assert(EncodeLua({2, 1}) == "{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(EncodeJson(nil) == "null") assert(EncodeJson(nil) == "null")
assert(EncodeJson(0) == "0") assert(EncodeJson(0) == "0")
assert(EncodeJson(3.14) == "3.14") 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") url = ParseUrl("https://jart:pass@redbean.dev/2.0.html?x&y=z#frag")
assert(url.scheme == "https") 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") == "\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) == "\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") 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)) assert(unix.close(fd))
-- getdents -- getdents
t = {}
for name, kind, ino, off in assert(unix.opendir(tmpdir)) do for name, kind, ino, off in assert(unix.opendir(tmpdir)) do
table.insert(t, name)
end end
assert(EncodeLua(t) == '{".", "..", "foo"}');
end end

View file

@ -14,7 +14,7 @@ int LuaEncodeUrl(lua_State *);
int LuaParseUrl(lua_State *); int LuaParseUrl(lua_State *);
int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int); int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int);
int LuaPushHeaders(lua_State *, struct HttpMessage *, const char *); 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 LuaPrintStack(lua_State *);
void LuaPushLatin1(lua_State *, const char *, size_t); void LuaPushLatin1(lua_State *, const char *, size_t);
void LuaPushUrlParams(lua_State *, struct UrlParams *); void LuaPushUrlParams(lua_State *, struct UrlParams *);

View file

@ -17,25 +17,32 @@
PERFORMANCE OF THIS SOFTWARE. PERFORMANCE OF THIS SOFTWARE.
*/ */
#include "libc/bits/bits.h" #include "libc/bits/bits.h"
#include "libc/log/rop.h"
#include "libc/stdio/append.internal.h" #include "libc/stdio/append.internal.h"
#include "libc/str/str.h" #include "libc/str/str.h"
#include "third_party/lua/cosmo.h" #include "third_party/lua/cosmo.h"
#include "third_party/lua/lua.h" #include "third_party/lua/lua.h"
void EscapeLuaString(char *s, size_t len, char **buf) { int EscapeLuaString(char *s, size_t len, char **buf) {
appendw(buf, '"'); int rc;
for (size_t i = 0; i < len; i++) { size_t i;
RETURN_ON_ERROR(appendw(buf, '"'));
for (i = 0; i < len; i++) {
if (' ' <= s[i] && s[i] <= 0x7e) { if (' ' <= s[i] && s[i] <= 0x7e) {
appendw(buf, s[i]); RETURN_ON_ERROR(appendw(buf, s[i]));
} else if (s[i] == '\n') { } else if (s[i] == '\n') {
appendw(buf, '\\' | 'n' << 8); RETURN_ON_ERROR(appendw(buf, '\\' | 'n' << 8));
} else if (s[i] == '\\' || s[i] == '\'' || s[i] == '\"') { } else if (s[i] == '\\' || s[i] == '\'' || s[i] == '\"') {
appendw(buf, '\\' | s[i] << 8); RETURN_ON_ERROR(appendw(buf, '\\' | s[i] << 8));
} else { } else {
appendw(buf, '\\' | 'x' << 010 | RETURN_ON_ERROR(
"0123456789abcdef"[(s[i] & 0xF0) >> 4] << 020 | appendw(buf, '\\' | 'x' << 010 |
"0123456789abcdef"[(s[i] & 0x0F) >> 0] << 030); "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/assert.h"
#include "libc/bits/bits.h" #include "libc/bits/bits.h"
#include "libc/fmt/itoa.h" #include "libc/fmt/itoa.h"
#include "libc/log/rop.h"
#include "libc/mem/mem.h" #include "libc/mem/mem.h"
#include "libc/runtime/gc.internal.h" #include "libc/runtime/gc.internal.h"
#include "libc/stdio/append.internal.h" #include "libc/stdio/append.internal.h"
#include "libc/stdio/strlist.internal.h"
#include "net/http/escape.h" #include "net/http/escape.h"
#include "third_party/lua/cosmo.h" #include "third_party/lua/cosmo.h"
#include "third_party/lua/lauxlib.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, char *numformat, int idx,
struct LuaVisited *visited) { struct LuaVisited *visited) {
char *s; char *s;
int sli, rc;
bool isarray; bool isarray;
size_t tbllen, i, z; size_t tbllen, i, z;
struct StrList sl = {0};
char ibuf[21], fmt[] = "%.14g"; char ibuf[21], fmt[] = "%.14g";
if (level > 0) { if (level > 0) {
switch (lua_type(L, idx)) { switch (lua_type(L, idx)) {
case LUA_TNIL: case LUA_TNIL:
appendw(buf, READ32LE("null")); RETURN_ON_ERROR(appendw(buf, READ32LE("null")));
return 0; return 0;
case LUA_TBOOLEAN: case LUA_TBOOLEAN:
appendw(buf, lua_toboolean(L, idx) ? READ32LE("true") RETURN_ON_ERROR(appendw(buf, lua_toboolean(L, idx)
: READ64LE("false\0\0")); ? READ32LE("true")
: READ64LE("false\0\0")));
return 0; return 0;
case LUA_TSTRING: case LUA_TSTRING:
s = lua_tolstring(L, idx, &z); s = lua_tolstring(L, idx, &z);
s = EscapeJsStringLiteral(s, z, &z); if (!(s = EscapeJsStringLiteral(s, z, &z))) goto OnError;
appendw(buf, '"'); RETURN_ON_ERROR(appendw(buf, '"'));
appendd(buf, s, z); RETURN_ON_ERROR(appendd(buf, s, z));
appendw(buf, '"'); RETURN_ON_ERROR(appendw(buf, '"'));
free(s); free(s);
return 0; return 0;
case LUA_TNUMBER: case LUA_TNUMBER:
if (lua_isinteger(L, idx)) { if (lua_isinteger(L, idx)) {
appendd(buf, ibuf, RETURN_ON_ERROR(appendd(
FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf); buf, ibuf, FormatInt64(ibuf, luaL_checkinteger(L, idx)) - ibuf));
} else { } else {
// TODO(jart): replace this api // TODO(jart): replace this api
while (*numformat == '%' || *numformat == '.' || while (*numformat == '%' || *numformat == '.' ||
@ -73,16 +78,16 @@ static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
fmt[4] = *numformat; fmt[4] = *numformat;
break; break;
default: default:
free(visited->p); // prevent format string hacking
luaL_error(L, "numformat string not allowed"); goto OnError;
unreachable;
} }
appendf(buf, fmt, lua_tonumber(L, idx)); RETURN_ON_ERROR(appendf(buf, fmt, lua_tonumber(L, idx)));
} }
return 0; return 0;
case LUA_TTABLE: 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 lua_pushvalue(L, idx); // table ref
tbllen = lua_rawlen(L, -1); tbllen = lua_rawlen(L, -1);
// encode tables with numeric indices and empty tables as arrays // 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 tbllen > 0 || // integer keys present
(lua_pushnil(L), !lua_next(L, -2)) || // no non-integer keys (lua_pushnil(L), !lua_next(L, -2)) || // no non-integer keys
(lua_pop(L, 2), false); // pop key/value pushed by lua_next (lua_pop(L, 2), false); // pop key/value pushed by lua_next
appendw(buf, isarray ? '[' : '{');
if (isarray) { if (isarray) {
for (i = 1; i <= tbllen; i++) { 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 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); lua_pop(L, 1);
} }
} else { } else {
@ -103,45 +108,58 @@ static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level,
lua_pushnil(L); // push the first key lua_pushnil(L); // push the first key
while (lua_next(L, -2)) { while (lua_next(L, -2)) {
if (lua_type(L, -2) != LUA_TSTRING) { if (lua_type(L, -2) != LUA_TSTRING) {
free(visited->p); // json tables must be arrays or use string keys
luaL_error(L, "json tables must be arrays or use string keys"); goto OnError;
unreachable;
} }
if (i++ > 1) appendw(buf, ','); RETURN_ON_ERROR(sli = AppendStrList(&sl));
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -2, visited); RETURN_ON_ERROR(LuaEncodeJsonDataImpl(L, &sl.p[sli], level - 1,
appendw(buf, ':'); numformat, -2, visited));
LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1, 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 lua_pop(L, 1); // table/-2, key/-1
} }
// stack: table/-1, as the key was popped by lua_next // 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); LuaPopVisit(visited);
lua_pop(L, 1); // table ref lua_pop(L, 1); // table ref
return 0; return 0;
} else { } else {
free(visited->p); // cyclic data structure
luaL_error(L, "can't serialize cyclic data structure to json"); goto OnError;
unreachable;
} }
default: default:
free(visited->p); // unsupported lua type
luaL_error(L, "won't serialize %s to json", luaL_typename(L, idx)); goto OnError;
unreachable;
} }
} else { } else {
free(visited->p); // too much depth
luaL_error(L, "too many nested tables"); goto OnError;
unreachable;
} }
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 LuaEncodeJsonData(lua_State *L, char **buf, char *numformat, int idx) {
int rc; int rc;
struct LuaVisited visited = {0}; struct LuaVisited visited = {0};
rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx, &visited); rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx, &visited);
assert(!visited.n);
free(visited.p); free(visited.p);
return rc; return rc;
} }

View file

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

View file

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

View file

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

View file

@ -625,10 +625,8 @@
(compile (format "sh %s" file))) (compile (format "sh %s" file)))
((eq major-mode 'lua-mode) ((eq major-mode 'lua-mode)
(let* ((mode (cosmo--make-mode arg)) (let* ((mode (cosmo--make-mode arg))
(redbean (format "%s/o/%s/tool/net/redbean.com" root mode))) (redbean ))
(if (file-executable-p 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))))
(compile (format "%s -i %s" redbean file))
(compile (format "lua.com %s" file)))))
((and (eq major-mode 'python-mode) ((and (eq major-mode 'python-mode)
(cosmo-startswith "third_party/python/Lib/test/" file)) (cosmo-startswith "third_party/python/Lib/test/" file))
(let ((mode (cosmo--make-mode arg))) (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 URIs that do things like embed a PNG file in a web page. See
encodebase64.c. encodebase64.c.
EncodeJson(value[,options:table]) → json:str EncodeJson(value[,options:table])
Turns passed Lua value into a JSON string. Tables with non-zero ├─→ json:str
length (as reported by `#`) are encoded as arrays with non-array ├─→ true [if useoutput]
elements ignored. Empty tables are encoded as empty arrays. All └─→ nil, error:str
other tables are encoded as objects with numerical keys
converted to strings (so `{[3]=1}` is encoded as `{"3":1}`). Turns Lua data structure into a JSON string.
The following options can be used:
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 - useoutput: (bool=false) encodes the result directly to the
output buffer and returns `nil` value. This option is output buffer and returns `nil` value. This option is
ignored if used outside of request handling code. ignored if used outside of request handling code.
- numformat: sets numeric format to be used, which can be 'g', - numformat: sets numeric format to be used, which can be 'g',
'f', or 'a' [experimental api] 'f', or 'a' [experimental api]
EncodeLua(value[,options:table]) → json:str This function will fail if:
Turns passed Lua value into a Lua string. The following options
can be used: - `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 - useoutput: (bool=false) encodes the result directly to the
output buffer and returns `nil` value. This option is output buffer and returns `nil` value. This option is
ignored if used outside of request handling code. 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 EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str
Turns UTF-8 into ISO-8859-1 string. 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); numformat = luaL_optstring(L, -1, numformat);
} }
lua_settop(L, 1); // keep the passed argument on top lua_settop(L, 1); // keep the passed argument on top
Encoder(L, useoutput ? &outbuf : &p, numformat, -1); if (Encoder(L, useoutput ? &outbuf : &p, numformat, -1) == -1) {
if (useoutput) { free(p);
lua_pushnil(L); lua_pushnil(L);
lua_pushstring(L, "serialization failed");
return 2;
}
if (useoutput) {
lua_pushboolean(L, true);
} else { } else {
lua_pushstring(L, p); lua_pushstring(L, p);
free(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) { static void LuaInit(void) {
#ifndef STATIC #ifndef STATIC
lua_State *L = GL; lua_State *L = GL;
LuaSetArgv(L); LuaSetArgv(L);
if (interpretermode) { if (interpretermode) {
int rc = LuaInterpreter(L); int rc = LuaInterpreter(L);
LuaDestroy();
MemDestroy();
if (IsModeDbg()) { if (IsModeDbg()) {
CheckForMemoryLeaks(); CheckForMemoryLeaks();
} }
@ -5426,14 +5464,6 @@ static void LuaReload(void) {
#endif #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) { static const char *DescribeClose(void) {
if (killed) return "killed"; if (killed) return "killed";
if (meltdown) return "meltdown"; if (meltdown) return "meltdown";
@ -7183,29 +7213,6 @@ static void TlsDestroy(void) {
#endif #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[]) { static void GetOpts(int argc, char *argv[]) {
int opt; int opt;
bool storeasset = false; bool storeasset = false;