diff --git a/libc/log/rop.h b/libc/log/rop.h new file mode 100644 index 000000000..e2cce0ed9 --- /dev/null +++ b/libc/log/rop.h @@ -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_ */ diff --git a/libc/stdio/strlist.c b/libc/stdio/strlist.c new file mode 100644 index 000000000..812b164d2 --- /dev/null +++ b/libc/stdio/strlist.c @@ -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; +} diff --git a/libc/stdio/strlist.internal.h b/libc/stdio/strlist.internal.h new file mode 100644 index 000000000..b501296bb --- /dev/null +++ b/libc/stdio/strlist.internal.h @@ -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_ */ diff --git a/test/libc/stdio/strlist_test.c b/test/libc/stdio/strlist_test.c new file mode 100644 index 000000000..1a056424f --- /dev/null +++ b/test/libc/stdio/strlist_test.c @@ -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); +} diff --git a/test/tool/net/lfuncs_test.lua b/test/tool/net/lfuncs_test.lua index bb0684f67..f3f51a8ad 100644 --- a/test/tool/net/lfuncs_test.lua +++ b/test/tool/net/lfuncs_test.lua @@ -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") diff --git a/test/tool/net/lunix_test.lua b/test/tool/net/lunix_test.lua index 40823c22b..0b07073c3 100644 --- a/test/tool/net/lunix_test.lua +++ b/test/tool/net/lunix_test.lua @@ -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 diff --git a/third_party/lua/cosmo.h b/third_party/lua/cosmo.h index b717418c6..db75d54fc 100644 --- a/third_party/lua/cosmo.h +++ b/third_party/lua/cosmo.h @@ -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 *); diff --git a/third_party/lua/escapeluastring.c b/third_party/lua/escapeluastring.c index 24d9f730c..84045cc24 100644 --- a/third_party/lua/escapeluastring.c +++ b/third_party/lua/escapeluastring.c @@ -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; } diff --git a/third_party/lua/luaencodejsondata.c b/third_party/lua/luaencodejsondata.c index 3b18f0f7d..b13c19e90 100644 --- a/third_party/lua/luaencodejsondata.c +++ b/third_party/lua/luaencodejsondata.c @@ -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; } diff --git a/third_party/lua/luaencodeluadata.c b/third_party/lua/luaencodeluadata.c index 7e855286b..666946276 100644 --- a/third_party/lua/luaencodeluadata.c +++ b/third_party/lua/luaencodeluadata.c @@ -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; } diff --git a/third_party/lua/visitor.c b/third_party/lua/visitor.c index 8d0b28386..7bf4dd9a0 100644 --- a/third_party/lua/visitor.c +++ b/third_party/lua/visitor.c @@ -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) { diff --git a/third_party/lua/visitor.h b/third_party/lua/visitor.h index dcf150e22..a2ab94a92 100644 --- a/third_party/lua/visitor.h +++ b/third_party/lua/visitor.h @@ -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_ diff --git a/tool/emacs/cosmo-stuff.el b/tool/emacs/cosmo-stuff.el index a7cbf1749..b3c4bb792 100644 --- a/tool/emacs/cosmo-stuff.el +++ b/tool/emacs/cosmo-stuff.el @@ -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))) diff --git a/tool/net/help.txt b/tool/net/help.txt index 10669b4be..f58bc6283 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -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. diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 3bab4c1ef..e3c67839d 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -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;