Files
stupidtools/src/ext/lua.c
T

877 lines
26 KiB
C

/*
* 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 <stddef.h>
#include <stdlib.h>
#include <string.h>
#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);
}