mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
Make redbean serialization deterministic
This commit is contained in:
parent
85aecbda67
commit
c9e68b0ebc
15 changed files with 452 additions and 150 deletions
11
libc/log/rop.h
Normal file
11
libc/log/rop.h
Normal 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
80
libc/stdio/strlist.c
Normal 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;
|
||||
}
|
18
libc/stdio/strlist.internal.h
Normal file
18
libc/stdio/strlist.internal.h
Normal 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_ */
|
58
test/libc/stdio/strlist_test.c
Normal file
58
test/libc/stdio/strlist_test.c
Normal 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);
|
||||
}
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
2
third_party/lua/cosmo.h
vendored
2
third_party/lua/cosmo.h
vendored
|
@ -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 *);
|
||||
|
|
27
third_party/lua/escapeluastring.c
vendored
27
third_party/lua/escapeluastring.c
vendored
|
@ -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;
|
||||
}
|
||||
|
|
88
third_party/lua/luaencodejsondata.c
vendored
88
third_party/lua/luaencodejsondata.c
vendored
|
@ -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;
|
||||
}
|
||||
|
|
102
third_party/lua/luaencodeluadata.c
vendored
102
third_party/lua/luaencodeluadata.c
vendored
|
@ -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;
|
||||
}
|
||||
|
|
17
third_party/lua/visitor.c
vendored
17
third_party/lua/visitor.c
vendored
|
@ -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) {
|
||||
|
|
2
third_party/lua/visitor.h
vendored
2
third_party/lua/visitor.h
vendored
|
@ -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_
|
||||
|
|
|
@ -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)))
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in a new issue