/* * src/ext/lua.c - embedded sandboxed Lua runtime (plan todo 19). * * Initializes the vendored Lua 5.4 as the stupidtools extension runtime * with a SANDBOXED environment. See src/ext/lua.h for the exact * sandbox contract (what is opened, blocked, removed) and the error * contract of st_lua_run(). * * LINKING / THE -lm TRAP (important for the build wiring): * The unit-test harness compiles every test file under tests/unit/ * with NO extra libraries -- in particular no -lm. Stock Lua needs * libm in TWO * places: (1) lmathlib.c (sin/cos/pow/log/...), and (2) the CORE * itself: lvm.c calls floor(), ltable.c frexp()+fabs(), lstrlib.c * floor()+frexp(), lobject.c and lcode.c ldexp()+fabs(), and the * arithmetic dispatcher luaO_arith() calls pow() and fmod(). On this * host `floor` does NOT resolve without -lm (verified: gcc 16.2.1 / * glibc 2.44 -- libm is a separate library). * * Two consequences: * a) lmathlib.c and linit.c (which references luaopen_math) are NOT * part of the link list; the curated `math` table below is * written by hand with libm-free code, so extensions still get * math.abs etc. (the plan's test requires math.abs(-3) to work). * b) The core's remaining libm references are satisfied by the * portable IEEE-754 binary64 shims at the top of this file * (fabs/floor/frexp/ldexp/fmod/pow). They are correct for the * common paths (the C23-annex-F binary64 assumption holds on * gcc/clang x86-64/aarch64); pow's exotic sign edge cases are * approximate. The main binary (todo 21+) may link the real * libm later; these shims stay harmless because Lua's undefined * references then simply resolve against libm instead... in * fact they will NOT: our definitions take precedence. When the * real libm becomes available, delete this block and add -lm. * * Include paths: the vendored headers are pulled in via path relative * to THIS file (../../thirdparty/lua/...), because the unit-test * harness only adds -I thirdparty/munit and -I src -- it never adds a * Lua include dir. The file-relative form works under any invocation * that compiles this file at its real location. */ #ifndef _POSIX_C_SOURCE #define _POSIX_C_SOURCE 200809L /* strdup (POSIX.1-2008) */ #endif #include #include #include #include "../../thirdparty/lua/lauxlib.h" #include "../../thirdparty/lua/lua.h" #include "../../thirdparty/lua/lualib.h" #include "lua.h" /* The probe-spec struct (struct st_lua_probe_spec) and its static-inline * free live in api.h (todo 21). Including it here is deliberate and safe * under the frozen unit-test link lines: api.h contributes only a struct * definition + a static inline, so lua.c gains NO reference to any * api.c/abi.c symbol -- tests/unit/test_lua.c and test_discovery.c, which * link lua.c WITHOUT api.c/abi.c, keep linking. */ #include "api.h" /* ------------------------------------------------------------------ */ /* libm-free math shims */ /* ------------------------------------------------------------------ */ /* * All six are exactly the symbols the vendored Lua core references from * libm (see the -lm note above). fabs uses the compiler builtin (a * single ANDPS, no call); floor/frexp/ldexp/fmod/pow are hand-written. * Bit-level code assumes IEEE 754 binary64 doubles (8-byte), which C23 * annex F plus gcc/clang guarantee on the supported targets. */ double fabs(double x); double floor(double x); double frexp(double x, int *e); double ldexp(double x, int n); double fmod(double x, double y); double pow(double x, double y); double fabs(double x) { return __builtin_fabs(x); } /* Truncation toward zero, safe for the full double range (casting an * out-of-range double to long long is UB, hence the guard). */ static double l_shim_trunc(double x) { static const double maxint = 9223372036854775808.0; /* 2^63 */ if (x > -maxint && x < maxint) { return (double)(long long)x; } return x; /* NaN, +/-inf, or a magnitude already integral */ } double floor(double x) { static const double minint = -9223372036854775808.0; static const double maxint = 9223372036854775808.0; if (x == 0.0) return x; /* preserves the sign of zero */ if (x > minint && x < maxint) { double t = (double)(long long)x; return x < t ? t - 1.0 : t; } return x; /* NaN, +/-inf, or out of range (already integral) */ } double frexp(double x, int *e) { union { double d; unsigned long long u; } v; unsigned long long expfield; v.d = x; expfield = (v.u >> 52) & 0x7FFULL; if (expfield == 0x7FFULL) { /* inf or NaN: value returned unchanged */ *e = 0; return x; } if (expfield == 0) { /* zero or subnormal */ if ((v.u & 0x000FFFFFFFFFFFFFULL) == 0ULL) { /* zero */ *e = 0; return x; } /* Normalize a subnormal by scaling it up 2^64 (the largest * subnormal is ~2^-1022, so the result is normal), then fix * the exponent. */ x = frexp(x * 0x1p64, e); *e -= 64; return x; } /* Normal case: keep the fraction, force the exponent field to 1022 * so the result lands in [0.5, 1), and report the difference. */ *e = (int)expfield - 1022; v.u = (v.u & 0x800FFFFFFFFFFFFFULL) | 0x3FE0000000000000ULL; return v.d; } double ldexp(double x, int n) { union { double d; unsigned long long u; } v; if (n > 1023) { x *= 0x1p1023; n -= 1023; if (n > 1023) { x *= 0x1p1023; n -= 1023; if (n > 1023) n = 1023; } } else if (n < -1022) { /* pre-scale so the final multiply stays clear of the subnormal * range and rounds once (avoids double rounding) */ x *= 0x1p-1022 * 0x1p53; n += 1022 - 53; if (n < -1022) { x *= 0x1p-1022 * 0x1p53; n += 1022 - 53; if (n < -1022) n = -1022; } } v.u = (unsigned long long)(0x3FF + n) << 52; return x * v.d; } double fmod(double x, double y) { double r; double ax, ay; int ex, ey; int neg; if (y == 0.0 || x != x || y != y) return __builtin_nan(""); if (x == 0.0) return x; /* keeps the sign */ if (fabs(x) == __builtin_inf()) return __builtin_nan(""); if (fabs(y) == __builtin_inf()) return x; /* fmod(x, inf) = x */ neg = x < 0.0; ax = fabs(x); ay = fabs(y); frexp(ax, &ex); frexp(ay, &ey); /* Binary long division: subtract the largest multiple of ay that * fits, then halve. Bounded by the exponent range (~2100 steps). */ r = ax; while (ex >= ey && r != 0.0) { double s = ldexp(ay, ex - ey); if (r >= s) r -= s; ex--; } return neg ? -r : r; } /* e^x via the reduction x = k*ln(2) + r, |r| <= ln(2)/2, then a Taylor * series (20 terms: the truncation error is ~4e-30, far below double * precision). */ static double l_shim_exp(double x) { static const double ln2 = 0.69314718055994530942; static const double invln2 = 1.44269504088896340736; long long k; double r, t, y; int i; if (x > 709.7827128933839731) return __builtin_inf(); /* overflow */ if (x < -745.1332191019411085) return 0.0; /* underflow */ if (x != x) return x; /* NaN */ k = (long long)(x * invln2 + (x >= 0.0 ? 0.5 : -0.5)); r = x - (double)k * ln2; y = 1.0; t = 1.0; for (i = 1; i <= 20; i++) { t = t * (r / (double)i); y += t; } return ldexp(y, (int)k); } /* ln(x) via x = m * 2^e (m in [0.5, 1)), then the atanh series around * m = 2/3: log(m) = log(2/3) + 2*atanh(t), t = (m - 2/3)/(m + 2/3), * |t| <= 0.2. 12 terms leave ~1e-18, i.e. a few ulps. */ static double l_shim_log(double x) { static const double ln2 = 0.69314718055994530942; static const double log32 = -0.40546510810816438098; /* ln(2/3) */ double m, t, p, term, sum; int e; int i; if (x < 0.0 || x != x) return __builtin_nan(""); if (x == 0.0) return -__builtin_inf(); if (x == __builtin_inf()) return x; m = frexp(x, &e); t = (m - 0.66666666666666663) / (m + 0.66666666666666663); p = t * t; term = t; sum = t; for (i = 1; i <= 12; i++) { term *= p; sum += term / (double)(2 * i + 1); } return (double)e * ln2 + log32 + 2.0 * sum; } double pow(double x, double y) { if (y == 0.0) return 1.0; /* even pow(NaN, 0) == 1 */ if (x == 1.0) return 1.0; if ((x == -1.0) && (y == __builtin_inf() || y == -__builtin_inf())) return 1.0; if (x != x || y != y) return __builtin_nan(""); /* Integral exponent within the exactly-representable range: exact * repeated squaring (this also covers negative bases). */ if (y == l_shim_trunc(y) && y > -9007199254740992.0 && y < 9007199254740992.0) { long long n = (long long)y; unsigned long long m = n < 0 ? (unsigned long long)(-n) : (unsigned long long)n; double r = 1.0; double b = x; while (m != 0ULL) { if ((m & 1ULL) != 0ULL) r *= b; b *= b; m >>= 1; } return n < 0 ? 1.0 / r : r; } if (x == __builtin_inf()) return y > 0.0 ? x : 0.0; if (x == -__builtin_inf()) return y > 0.0 ? __builtin_inf() : 0.0; if (x == 0.0) return y > 0.0 ? 0.0 : __builtin_inf(); if (y == __builtin_inf()) return x > 1.0 ? __builtin_inf() : 0.0; if (y == -__builtin_inf()) return x < 1.0 ? __builtin_inf() : 0.0; if (x < 0.0) return __builtin_nan(""); /* negative base, non-int y */ return l_shim_exp(y * l_shim_log(x)); } /* ------------------------------------------------------------------ */ /* runtime state */ /* ------------------------------------------------------------------ */ struct st_lua_reg { struct st_lua_reg *next; char *name; /* owned copy */ void *probe_spec; /* checks: a struct st_lua_probe_spec * (owned); languages: always NULL (name only in v1) */ }; struct st_lua_rt { lua_State *L; struct st_lua_reg *checks; /* insertion-ordered linked lists */ struct st_lua_reg *languages; size_t check_count; size_t language_count; }; /* Appends (name, spec) at the TAIL of *head (insertion order, matching * the ABI registries' contract) unless it is a duplicate. Returns 0 on * success, -1 on duplicate or allocation failure. */ static int reg_append(struct st_lua_reg **head, size_t *count, const char *name, void *spec) { struct st_lua_reg *n, *p; for (p = *head; p != NULL; p = p->next) { if (strcmp(p->name, name) == 0) return -1; /* duplicate */ } n = malloc(sizeof *n); if (n == NULL) return -1; n->name = strdup(name); if (n->name == NULL) { free(n); return -1; } n->probe_spec = spec; n->next = NULL; if (*head == NULL) { *head = n; } else { for (p = *head; p->next != NULL; p = p->next) { } p->next = n; } *count += 1; return 0; } static void regs_free(struct st_lua_reg *head) { struct st_lua_reg *p = head; while (p != NULL) { struct st_lua_reg *next = p->next; free(p->name); st_lua_probe_spec_free((struct st_lua_probe_spec *)p->probe_spec); free(p); p = next; } } static const struct st_lua_reg * reg_at(const struct st_lua_reg *head, size_t i) { const struct st_lua_reg *p = head; while (p != NULL && i > 0) { p = p->next; i--; } return p; } /* ------------------------------------------------------------------ */ /* sandboxed globals */ /* ------------------------------------------------------------------ */ /* The one string sandbox violations contain; st_lua_run() classifies * errors by finding it in the message. Keep in sync with l_os_blocked(). */ #define SANDBOX_PREFIX "sandbox blocked: " /* os.execute / os.exit / os.remove / os.rename all land here: a stub * that raises a clear sandbox error no matter how it is called (direct, * pcall'd, aliased, ...). */ static int l_os_blocked(lua_State *L) { const char *name = lua_tostring(L, lua_upvalueindex(1)); return luaL_error(L, SANDBOX_PREFIX "os.%s is not available to " "extension code", name != NULL ? name : "?"); } /* The ONLY os table extensions see: the four stubs above, nothing else * (no clock/date/getenv -- extension code needs none of them in v1 and * a smaller surface is a better sandbox). The stock loslib is not even * linked. */ static void l_os_open_sandboxed(lua_State *L) { static const char *const blocked[] = { "execute", "exit", "remove", "rename", NULL }; int i; lua_newtable(L); for (i = 0; blocked[i] != NULL; i++) { lua_pushstring(L, blocked[i]); /* upvalue: the function's name */ lua_pushcclosure(L, l_os_blocked, 1); lua_setfield(L, -2, blocked[i]); } lua_setglobal(L, "os"); } /* Curated, libm-FREE math library (stock lmathlib would pull sin/cos/ * pow/log/... from libm; see the header note). abs/max/min/tointeger/ * type/maxinteger/mininteger/pi/huge cover what extension code needs * in v1; floor/ceil/sqrt etc. arrive when the main binary links real * libm (todo 21+). */ static int l_math_abs(lua_State *L) { if (lua_isinteger(L, 1)) { lua_Integer n = lua_tointeger(L, 1); lua_pushinteger(L, n < 0 ? (lua_Integer)(0u - (lua_Unsigned)n) : n); } else { lua_pushnumber(L, fabs(luaL_checknumber(L, 1))); } return 1; } static int l_math_max(lua_State *L) { int n = lua_gettop(L); int i; lua_Number m; if (n == 0) return luaL_error(L, "math.max: expected at least one argument"); m = luaL_checknumber(L, 1); for (i = 2; i <= n; i++) { lua_Number v = luaL_checknumber(L, i); if (v > m) m = v; } lua_pushnumber(L, m); return 1; } static int l_math_min(lua_State *L) { int n = lua_gettop(L); int i; lua_Number m; if (n == 0) return luaL_error(L, "math.min: expected at least one argument"); m = luaL_checknumber(L, 1); for (i = 2; i <= n; i++) { lua_Number v = luaL_checknumber(L, i); if (v < m) m = v; } lua_pushnumber(L, m); return 1; } /* Mirrors stock math.tointeger: converts a value that has an exact * integer representation (this exercises the floor() shim through * luaV_flttointeger). */ static int l_math_tointeger(lua_State *L) { int valid; lua_Integer n = lua_tointegerx(L, 1, &valid); if (valid) { lua_pushinteger(L, n); } else { luaL_checkany(L, 1); lua_pushnil(L); /* not convertible */ } return 1; } static int l_math_type(lua_State *L) { if (lua_type(L, 1) == LUA_TNUMBER) { if (lua_isinteger(L, 1)) { lua_pushliteral(L, "integer"); } else { lua_pushliteral(L, "float"); } } else { luaL_checkany(L, 1); lua_pushnil(L); } return 1; } static void l_math_open(lua_State *L) { static const luaL_Reg funcs[] = { { "abs", l_math_abs }, { "max", l_math_max }, { "min", l_math_min }, { "tointeger", l_math_tointeger }, { "type", l_math_type }, { NULL, NULL }, }; luaL_newlib(L, funcs); lua_pushinteger(L, LUA_MAXINTEGER); lua_setfield(L, -2, "maxinteger"); lua_pushinteger(L, LUA_MININTEGER); lua_setfield(L, -2, "mininteger"); lua_pushnumber(L, 3.14159265358979323846); lua_setfield(L, -2, "pi"); lua_pushnumber(L, __builtin_inf()); lua_setfield(L, -2, "huge"); lua_setglobal(L, "math"); } /* ------------------------------------------------------------------ */ /* registration API (Lua side) */ /* ------------------------------------------------------------------ */ /* The `st` table: st.register_check(name[, spec]) and * st.register_language(name[, spec]). Both write into the runtime's own * registries through the same C API todo 21 enumerates when bridging into * a struct st_ext_ctx. The rt travels as a light-userdata upvalue -- there * is no global state. A present second argument (a probe-spec TABLE) is * parsed into a heap struct st_lua_probe_spec (api.h) and passed through * as the registry entry's opaque spec pointer. */ static void l_links_free(char **link) { size_t i; if (link == NULL) { return; } for (i = 0; link[i] != NULL; i++) { free(link[i]); } free(link); } /* Parses the optional probe-spec table (argument 2) into a heap * struct st_lua_probe_spec. Returns NULL when argument 2 is absent or * nil (bare registration); raises a Lua error on a malformed table. * All fields are strdup'd (so the borrowed lua_tostring pointers never * outlive their stack slot); the caller owns the result. */ static struct st_lua_probe_spec * l_parse_probe_spec(lua_State *L) { struct st_lua_probe_spec *spec; char *kind_dup = NULL; char *source_dup = NULL; char **link = NULL; size_t nlink = 0; size_t i; const char *s; if (lua_gettop(L) < 2 || lua_isnoneornil(L, 2)) { return NULL; } luaL_checktype(L, 2, LUA_TTABLE); lua_getfield(L, 2, "kind"); if (lua_type(L, -1) != LUA_TSTRING) { luaL_error(L, "probe spec 'kind' must be a string"); return NULL; } s = lua_tostring(L, -1); if (strcmp(s, "compile") != 0 && strcmp(s, "link") != 0 && strcmp(s, "run") != 0) { luaL_error(L, "probe spec 'kind' must be \"compile\", \"link\" or " "\"run\""); return NULL; } kind_dup = strdup(s); lua_pop(L, 1); if (kind_dup == NULL) { luaL_error(L, "out of memory"); return NULL; } lua_getfield(L, 2, "source"); if (lua_type(L, -1) != LUA_TSTRING) { free(kind_dup); luaL_error(L, "probe spec 'source' must be a string"); return NULL; } s = lua_tostring(L, -1); source_dup = strdup(s); lua_pop(L, 1); if (source_dup == NULL) { free(kind_dup); luaL_error(L, "out of memory"); return NULL; } lua_getfield(L, 2, "link"); if (!lua_isnoneornil(L, -1)) { luaL_checktype(L, -1, LUA_TTABLE); nlink = lua_rawlen(L, -1); link = calloc(nlink + 1, sizeof *link); if (link == NULL) { free(kind_dup); free(source_dup); luaL_error(L, "out of memory"); return NULL; } for (i = 0; i < nlink; i++) { size_t slen; lua_geti(L, -1, (lua_Integer)i + 1); s = lua_tostring(L, -1); slen = lua_rawlen(L, -1); if (lua_type(L, -1) != LUA_TSTRING || s == NULL || strlen(s) != slen) { lua_pop(L, 1); l_links_free(link); free(kind_dup); free(source_dup); luaL_error(L, "probe spec 'link' entries must be strings " "without NUL bytes"); return NULL; } link[i] = strdup(s); lua_pop(L, 1); if (link[i] == NULL) { l_links_free(link); free(kind_dup); free(source_dup); luaL_error(L, "out of memory"); return NULL; } } link[nlink] = NULL; } lua_pop(L, 1); spec = malloc(sizeof *spec); if (spec == NULL) { l_links_free(link); free(kind_dup); free(source_dup); luaL_error(L, "out of memory"); return NULL; } spec->kind = kind_dup; spec->source = source_dup; spec->link = link; return spec; } static int l_st_register_check(lua_State *L) { struct st_lua_rt *rt = lua_touserdata(L, lua_upvalueindex(1)); struct st_lua_probe_spec *spec; size_t len; const char *name; if (lua_type(L, 1) != LUA_TSTRING) { return luaL_typeerror(L, 1, "string"); } name = lua_tostring(L, 1); len = lua_rawlen(L, 1); if (name == NULL || strlen(name) != len) { return luaL_error(L, "check name must not contain a NUL byte"); } spec = l_parse_probe_spec(L); if (st_lua_register_check(rt, name, spec) != 0) { st_lua_probe_spec_free(spec); /* not stored: free it */ return luaL_error(L, "check '%s' is already registered", name); } return 0; } static int l_st_register_language(lua_State *L) { struct st_lua_rt *rt = lua_touserdata(L, lua_upvalueindex(1)); size_t len; const char *name; if (lua_type(L, 1) != LUA_TSTRING) { return luaL_typeerror(L, 1, "string"); } name = lua_tostring(L, 1); len = lua_rawlen(L, 1); if (name == NULL || strlen(name) != len) { return luaL_error(L, "language name must not contain a NUL byte"); } /* Optional second arg: accept a probe-spec TABLE for forward * compatibility, but for v1 register the name only -- the table's * contents (detection info) are consumed by todo 22. A non-table * second arg is a clean error. */ if (lua_gettop(L) >= 2 && !lua_isnoneornil(L, 2)) { luaL_checktype(L, 2, LUA_TTABLE); } if (st_lua_register_language(rt, name) != 0) { return luaL_error(L, "language '%s' is already registered", name); } return 0; } static void l_st_open(lua_State *L, struct st_lua_rt *rt) { lua_newtable(L); lua_pushlightuserdata(L, rt); lua_pushcclosure(L, l_st_register_check, 1); lua_setfield(L, -2, "register_check"); lua_pushlightuserdata(L, rt); lua_pushcclosure(L, l_st_register_language, 1); lua_setfield(L, -2, "register_language"); lua_setglobal(L, "st"); } /* ------------------------------------------------------------------ */ /* runtime lifecycle + sandbox assembly */ /* ------------------------------------------------------------------ */ struct st_lua_rt * st_lua_rt_new(void) { struct st_lua_rt *rt = calloc(1, sizeof *rt); lua_State *L; if (rt == NULL) return NULL; L = luaL_newstate(); if (L == NULL) { free(rt); return NULL; } rt->L = L; /* Selective library set -- deliberately NOT luaL_openlibs(): * base / table / string are the only stock libraries loaded; * math and os are the hand-built sandboxed ones below; * package/io/debug/coroutine are never loaded (and their .c * files are not even linked -- see the -lm note at the top); * lmathlib/linit are excluded from the link for the same reason. */ luaL_requiref(L, "base", luaopen_base, 1); lua_pop(L, 1); luaL_requiref(L, "table", luaopen_table, 1); lua_pop(L, 1); luaL_requiref(L, "string", luaopen_string, 1); lua_pop(L, 1); /* Remove the base-library globals that reach files/modules. (`load` * is kept: with io/os/package gone it can only compile strings, and * that is how extension loading works in todo 20/21.) */ lua_pushnil(L); lua_setglobal(L, "dofile"); lua_pushnil(L); lua_setglobal(L, "loadfile"); lua_pushnil(L); lua_setglobal(L, "require"); /* defensive: 5.4 defines it in loadlib */ l_os_open_sandboxed(L); l_math_open(L); l_st_open(L, rt); return rt; } void st_lua_rt_free(struct st_lua_rt *rt) { if (rt == NULL) return; if (rt->L != NULL) lua_close(rt->L); regs_free(rt->checks); regs_free(rt->languages); free(rt); } /* ------------------------------------------------------------------ */ /* registration API (C side) + todo-21 enumeration bridge */ /* ------------------------------------------------------------------ */ int st_lua_register_check(struct st_lua_rt *rt, const char *name, void *probe_spec_or_null) { if (rt == NULL || name == NULL || name[0] == '\0') return -1; return reg_append(&rt->checks, &rt->check_count, name, probe_spec_or_null); } int st_lua_register_language(struct st_lua_rt *rt, const char *name) { if (rt == NULL || name == NULL || name[0] == '\0') return -1; return reg_append(&rt->languages, &rt->language_count, name, NULL); } size_t st_lua_check_count(const struct st_lua_rt *rt) { return rt != NULL ? rt->check_count : 0; } const char * st_lua_check_name(const struct st_lua_rt *rt, size_t i) { const struct st_lua_reg *r = rt != NULL ? reg_at(rt->checks, i) : NULL; return r != NULL ? r->name : NULL; } void * st_lua_check_probe_spec(const struct st_lua_rt *rt, size_t i) { const struct st_lua_reg *r = rt != NULL ? reg_at(rt->checks, i) : NULL; return r != NULL ? r->probe_spec : NULL; } size_t st_lua_language_count(const struct st_lua_rt *rt) { return rt != NULL ? rt->language_count : 0; } const char * st_lua_language_name(const struct st_lua_rt *rt, size_t i) { const struct st_lua_reg *r = rt != NULL ? reg_at(rt->languages, i) : NULL; return r != NULL ? r->name : NULL; } /* ------------------------------------------------------------------ */ /* execution */ /* ------------------------------------------------------------------ */ /* Takes the error object Lua left on the top of the stack, classifies * it (sandbox violation vs. everything else), builds the st_error and * pops the object. */ static struct st_error * lua_error_to_st_error(lua_State *L) { const char *msg = lua_tostring(L, -1); struct st_error *err; if (msg == NULL) msg = "(non-string Lua error)"; /* luaL_error prepends "chunkname:line: " to the message, so the * sandbox marker is matched as a substring, not a prefix. */ if (strstr(msg, SANDBOX_PREFIX) != NULL) { err = st_error_kdl_schema(msg); } else { err = st_error_internal(msg); } lua_pop(L, 1); return err; } struct st_error * st_lua_run(struct st_lua_rt *rt, const char *chunk, const char *chunkname) { lua_State *L; int status; if (rt == NULL) return st_error_internal("st_lua_run: NULL runtime"); if (chunk == NULL) return st_error_internal("st_lua_run: NULL chunk"); L = rt->L; status = luaL_loadbuffer(L, chunk, strlen(chunk), chunkname != NULL ? chunkname : "=(extension)"); if (status == LUA_OK) { status = lua_pcall(L, 0, 0, 0); } if (status == LUA_OK) return NULL; return lua_error_to_st_error(L); }