Hide shell functions from _G using upvalues on the main chunk
Store __command, __interactive, __getenv, __setenv as upvalues populated by lua_load() from the registry, keeping them invisible to user code while accessible to the parser's codegen.
This commit is contained in:
22
lapi.c
22
lapi.c
@@ -29,6 +29,7 @@
|
||||
#include "ltm.h"
|
||||
#include "lundump.h"
|
||||
#include "lvm.h"
|
||||
#include "lcmd.h"
|
||||
|
||||
|
||||
|
||||
@@ -1135,6 +1136,27 @@ LUA_API int lua_load (lua_State *L, lua_Reader reader, void *data,
|
||||
setobj(L, f->upvals[0]->v.p, >);
|
||||
luaC_barrier(L, f->upvals[0], >);
|
||||
}
|
||||
if (f->nupvalues >= LUSH_NUM_UPVALS) { /* has lush upvalues? */
|
||||
/* populate shell function upvalues from registry */
|
||||
Table *regt = hvalue(&G(L)->l_registry);
|
||||
TValue lushtv;
|
||||
lu_byte tag = luaH_getint(regt, LUA_RIDX_LUSH, &lushtv);
|
||||
if (novariant(tag) == LUA_TTABLE) {
|
||||
static const char *const fields[] = {
|
||||
"command", "interactive", "getenv", "setenv"
|
||||
};
|
||||
Table *lusht = hvalue(&lushtv);
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
TValue val;
|
||||
TString *key = luaS_new(L, fields[i]);
|
||||
if (luaH_getstr(lusht, key, &val) != LUA_TNIL) {
|
||||
setobj(L, f->upvals[i + 1]->v.p, &val);
|
||||
luaC_barrier(L, f->upvals[i + 1], &val);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lua_unlock(L);
|
||||
return APIstatus(status);
|
||||
|
||||
@@ -155,5 +155,9 @@ void luaopen_builtins (lua_State *L) {
|
||||
lua_setfield(L, -2, "exec");
|
||||
lua_pushcfunction(L, builtin_umask);
|
||||
lua_setfield(L, -2, "umask");
|
||||
lua_setglobal(L, "__builtins");
|
||||
/* Store as __lush.builtins in registry */
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
||||
lua_insert(L, -2);
|
||||
lua_setfield(L, -2, "builtins");
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
|
||||
47
lcmd.c
47
lcmd.c
@@ -874,15 +874,21 @@ static int exec_pipeline (lua_State *L, char **stages, int nstages,
|
||||
*/
|
||||
static int try_builtin (lua_State *L, ParsedArgs *pa) {
|
||||
int i;
|
||||
if (lua_getglobal(L, "__builtins") != LUA_TTABLE) {
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
||||
if (!lua_istable(L, -1)) {
|
||||
lua_pop(L, 1);
|
||||
return 0;
|
||||
}
|
||||
if (lua_getfield(L, -1, pa->argv[0]) != LUA_TFUNCTION) {
|
||||
if (lua_getfield(L, -1, "builtins") != LUA_TTABLE) {
|
||||
lua_pop(L, 2);
|
||||
return 0;
|
||||
}
|
||||
lua_remove(L, -2); /* remove __builtins, keep function */
|
||||
if (lua_getfield(L, -1, pa->argv[0]) != LUA_TFUNCTION) {
|
||||
lua_pop(L, 3);
|
||||
return 0;
|
||||
}
|
||||
lua_remove(L, -2); /* remove builtins table */
|
||||
lua_remove(L, -2); /* remove lush table */
|
||||
for (i = 0; i < pa->argc; i++)
|
||||
lua_pushstring(L, pa->argv[i]);
|
||||
lua_call(L, pa->argc, 1);
|
||||
@@ -890,9 +896,9 @@ static int try_builtin (lua_State *L, ParsedArgs *pa) {
|
||||
}
|
||||
|
||||
|
||||
/* ===== luaB_command ===== */
|
||||
/* ===== lushCmd_command ===== */
|
||||
|
||||
int luaB_command (lua_State *L) {
|
||||
int lushCmd_command (lua_State *L) {
|
||||
const char *cmd = luaL_checkstring(L, 1);
|
||||
char *stages[MAX_PIPELINE_STAGES];
|
||||
int nstages;
|
||||
@@ -1033,9 +1039,9 @@ int luaB_command (lua_State *L) {
|
||||
}
|
||||
|
||||
|
||||
/* ===== luaB_interactive ===== */
|
||||
/* ===== lushCmd_interactive ===== */
|
||||
|
||||
int luaB_interactive (lua_State *L) {
|
||||
int lushCmd_interactive (lua_State *L) {
|
||||
const char *cmd = luaL_checkstring(L, 1);
|
||||
char *stages[MAX_PIPELINE_STAGES];
|
||||
int nstages;
|
||||
@@ -1142,3 +1148,30 @@ int luaB_interactive (lua_State *L) {
|
||||
|
||||
return 0; /* void — no Lua return values */
|
||||
}
|
||||
|
||||
|
||||
/* ===== lushCmd_getenv ===== */
|
||||
|
||||
int lushCmd_getenv (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
const char *val = getenv(name);
|
||||
if (val == NULL)
|
||||
lua_pushnil(L);
|
||||
else
|
||||
lua_pushstring(L, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
/* ===== lushCmd_setenv ===== */
|
||||
|
||||
int lushCmd_setenv (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
if (lua_isnoneornil(L, 2))
|
||||
unsetenv(name);
|
||||
else {
|
||||
const char *val = luaL_tolstring(L, 2, NULL);
|
||||
setenv(name, val, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
14
lcmd.h
14
lcmd.h
@@ -9,7 +9,17 @@
|
||||
|
||||
#include "lua.h"
|
||||
|
||||
int luaB_command (lua_State *L);
|
||||
int luaB_interactive (lua_State *L);
|
||||
/* Upvalue indices for shell functions on the main chunk.
|
||||
** Index 0 is _ENV (standard). Indices 1-4 are lush shell functions. */
|
||||
#define LUSH_UPVAL_COMMAND 1
|
||||
#define LUSH_UPVAL_INTERACTIVE 2
|
||||
#define LUSH_UPVAL_GETENV 3
|
||||
#define LUSH_UPVAL_SETENV 4
|
||||
#define LUSH_NUM_UPVALS 5 /* total: _ENV(0) + 4 shell functions */
|
||||
|
||||
int lushCmd_command (lua_State *L);
|
||||
int lushCmd_interactive (lua_State *L);
|
||||
int lushCmd_getenv (lua_State *L);
|
||||
int lushCmd_setenv (lua_State *L);
|
||||
|
||||
#endif
|
||||
|
||||
44
linit.c
44
linit.c
@@ -43,41 +43,21 @@ static const luaL_Reg stdlibs[] = {
|
||||
};
|
||||
|
||||
|
||||
static int luaB_getenv (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
const char *val = getenv(name);
|
||||
if (val == NULL)
|
||||
lua_pushnil(L);
|
||||
else
|
||||
lua_pushstring(L, val);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
static int luaB_setenv (lua_State *L) {
|
||||
const char *name = luaL_checkstring(L, 1);
|
||||
if (lua_isnoneornil(L, 2))
|
||||
unsetenv(name);
|
||||
else {
|
||||
const char *val = luaL_tolstring(L, 2, NULL);
|
||||
setenv(name, val, 1);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Register shell built-in globals (called after standard libraries).
|
||||
** Store shell functions in a registry table at LUA_RIDX_LUSH.
|
||||
** These are populated as upvalues on the main chunk by lua_load().
|
||||
*/
|
||||
static void opencommand (lua_State *L) {
|
||||
lua_pushcfunction(L, luaB_command);
|
||||
lua_setglobal(L, "__command");
|
||||
lua_pushcfunction(L, luaB_interactive);
|
||||
lua_setglobal(L, "__interactive");
|
||||
lua_pushcfunction(L, luaB_getenv);
|
||||
lua_setglobal(L, "__getenv");
|
||||
lua_pushcfunction(L, luaB_setenv);
|
||||
lua_setglobal(L, "__setenv");
|
||||
lua_createtable(L, 0, 5);
|
||||
lua_pushcfunction(L, lushCmd_command);
|
||||
lua_setfield(L, -2, "command");
|
||||
lua_pushcfunction(L, lushCmd_interactive);
|
||||
lua_setfield(L, -2, "interactive");
|
||||
lua_pushcfunction(L, lushCmd_getenv);
|
||||
lua_setfield(L, -2, "getenv");
|
||||
lua_pushcfunction(L, lushCmd_setenv);
|
||||
lua_setfield(L, -2, "setenv");
|
||||
lua_rawseti(L, LUA_REGISTRYINDEX, LUA_RIDX_LUSH);
|
||||
}
|
||||
|
||||
|
||||
|
||||
45
lparser.c
45
lparser.c
@@ -27,6 +27,7 @@
|
||||
#include "lstate.h"
|
||||
#include "lstring.h"
|
||||
#include "ltable.h"
|
||||
#include "lcmd.h"
|
||||
|
||||
|
||||
|
||||
@@ -499,6 +500,19 @@ static void singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
** Look up a lush shell function (e.g. __command) that is registered
|
||||
** as an upvalue on the main chunk. singlevaraux will find it directly
|
||||
** as VUPVAL, emitting OP_GETUPVAL instead of OP_GETTABUP.
|
||||
*/
|
||||
static void buildlushvar (LexState *ls, TString *varname, expdesc *var) {
|
||||
FuncState *fs = ls->fs;
|
||||
init_exp(var, VGLOBAL, -1);
|
||||
singlevaraux(fs, varname, var, 1);
|
||||
lua_assert(var->k == VUPVAL); /* must be found as upvalue */
|
||||
}
|
||||
|
||||
|
||||
static void buildglobal (LexState *ls, TString *varname, expdesc *var) {
|
||||
FuncState *fs = ls->fs;
|
||||
expdesc key;
|
||||
@@ -552,8 +566,8 @@ static void codecommand (LexState *ls, expdesc *v, expdesc *cmdstr) {
|
||||
expdesc func;
|
||||
TString *cmdname = luaX_newstring(ls, "__command", 9);
|
||||
line = ls->linenumber;
|
||||
/* look up __command as _ENV["__command"] */
|
||||
buildglobal(ls, cmdname, &func);
|
||||
/* look up __command as upvalue */
|
||||
buildlushvar(ls, cmdname, &func);
|
||||
luaK_exp2nextreg(fs, &func);
|
||||
base = func.u.info;
|
||||
/* push the command string argument */
|
||||
@@ -1243,7 +1257,7 @@ static void commandexp (LexState *ls, expdesc *v) {
|
||||
TString *cmdname = luaX_newstring(ls, "__command", 9);
|
||||
TString *tsname = luaX_newstring(ls, "tostring", 8);
|
||||
/* load __command function first so it occupies the base register */
|
||||
buildglobal(ls, cmdname, &func);
|
||||
buildlushvar(ls, cmdname, &func);
|
||||
luaK_exp2nextreg(fs, &func);
|
||||
base = func.u.info;
|
||||
/* load the first fragment */
|
||||
@@ -1308,7 +1322,7 @@ static void codeenvget (LexState *ls, expdesc *v, TString *name) {
|
||||
expdesc func, arg;
|
||||
TString *fname = luaX_newstring(ls, "__getenv", 8);
|
||||
line = ls->linenumber;
|
||||
buildglobal(ls, fname, &func);
|
||||
buildlushvar(ls, fname, &func);
|
||||
luaK_exp2nextreg(fs, &func);
|
||||
base = func.u.info;
|
||||
codestring(&arg, name);
|
||||
@@ -2193,7 +2207,7 @@ static void envstat (LexState *ls) {
|
||||
luaX_next(ls); /* skip TK_ENVVAR */
|
||||
checknext(ls, '=');
|
||||
fname = luaX_newstring(ls, "__setenv", 8);
|
||||
buildglobal(ls, fname, &func);
|
||||
buildlushvar(ls, fname, &func);
|
||||
luaK_exp2nextreg(fs, &func);
|
||||
base = func.u.info;
|
||||
codestring(&arg, name);
|
||||
@@ -2219,7 +2233,7 @@ static void interactivestat (LexState *ls) {
|
||||
expdesc func, cmdstr, v;
|
||||
TString *fname = luaX_newstring(ls, "__interactive", 13);
|
||||
line = ls->linenumber;
|
||||
buildglobal(ls, fname, &func);
|
||||
buildlushvar(ls, fname, &func);
|
||||
luaK_exp2nextreg(fs, &func);
|
||||
base = func.u.info;
|
||||
codestring(&cmdstr, ls->t.seminfo.ts);
|
||||
@@ -2236,7 +2250,7 @@ static void interactivestat (LexState *ls) {
|
||||
int line = ls->linenumber;
|
||||
TString *fname = luaX_newstring(ls, "__interactive", 13);
|
||||
TString *tsname = luaX_newstring(ls, "tostring", 8);
|
||||
buildglobal(ls, fname, &func);
|
||||
buildlushvar(ls, fname, &func);
|
||||
luaK_exp2nextreg(fs, &func);
|
||||
base = func.u.info;
|
||||
/* load the first fragment */
|
||||
@@ -2396,8 +2410,12 @@ static void statement (LexState *ls) {
|
||||
** upvalue named LUA_ENV
|
||||
*/
|
||||
static void mainfunc (LexState *ls, FuncState *fs) {
|
||||
static const char *const lush_upval_names[] = {
|
||||
"__command", "__interactive", "__getenv", "__setenv"
|
||||
};
|
||||
BlockCnt bl;
|
||||
Upvaldesc *env;
|
||||
int i;
|
||||
open_func(ls, fs, &bl);
|
||||
setvararg(fs, PF_ISVARARG); /* main function is always vararg */
|
||||
env = allocupvalue(fs); /* ...set environment upvalue */
|
||||
@@ -2406,6 +2424,15 @@ static void mainfunc (LexState *ls, FuncState *fs) {
|
||||
env->kind = VDKREG;
|
||||
env->name = ls->envn;
|
||||
luaC_objbarrier(ls->L, fs->f, env->name);
|
||||
/* allocate upvalues for lush shell functions */
|
||||
for (i = 0; i < 4; i++) {
|
||||
Upvaldesc *uv = allocupvalue(fs);
|
||||
uv->instack = 1;
|
||||
uv->idx = 0;
|
||||
uv->kind = VDKREG;
|
||||
uv->name = luaS_new(ls->L, lush_upval_names[i]);
|
||||
luaC_objbarrier(ls->L, fs->f, uv->name);
|
||||
}
|
||||
luaX_next(ls); /* read first token */
|
||||
statlist(ls); /* parse main body */
|
||||
check(ls, TK_EOS);
|
||||
@@ -2417,7 +2444,7 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
|
||||
Dyndata *dyd, const char *name, int firstchar) {
|
||||
LexState lexstate;
|
||||
FuncState funcstate;
|
||||
LClosure *cl = luaF_newLclosure(L, 1); /* create main closure */
|
||||
LClosure *cl = luaF_newLclosure(L, LUSH_NUM_UPVALS); /* create main closure */
|
||||
setclLvalue2s(L, L->top.p, cl); /* anchor it (to avoid being collected) */
|
||||
luaD_inctop(L);
|
||||
lexstate.h = luaH_new(L); /* create table for scanner */
|
||||
@@ -2432,7 +2459,7 @@ LClosure *luaY_parser (lua_State *L, ZIO *z, Mbuffer *buff,
|
||||
dyd->actvar.n = dyd->gt.n = dyd->label.n = 0;
|
||||
luaX_setinput(L, &lexstate, z, funcstate.f->source, firstchar);
|
||||
mainfunc(&lexstate, &funcstate);
|
||||
lua_assert(!funcstate.prev && funcstate.nups == 1 && !lexstate.fs);
|
||||
lua_assert(!funcstate.prev && funcstate.nups == LUSH_NUM_UPVALS && !lexstate.fs);
|
||||
/* all scopes should be correctly finished */
|
||||
lua_assert(dyd->actvar.n == 0 && dyd->gt.n == 0 && dyd->label.n == 0);
|
||||
L->top.p--; /* remove scanner's table */
|
||||
|
||||
3
lua.h
3
lua.h
@@ -83,7 +83,8 @@ typedef struct lua_State lua_State;
|
||||
/* index 1 is reserved for the reference mechanism */
|
||||
#define LUA_RIDX_GLOBALS 2
|
||||
#define LUA_RIDX_MAINTHREAD 3
|
||||
#define LUA_RIDX_LAST 3
|
||||
#define LUA_RIDX_LUSH 4
|
||||
#define LUA_RIDX_LAST LUA_RIDX_LUSH
|
||||
|
||||
|
||||
/* type of numbers in Lua */
|
||||
|
||||
@@ -3,13 +3,10 @@
|
||||
|
||||
print "testing shell builtins"
|
||||
|
||||
-- === __builtins table ===
|
||||
-- === __builtins is NOT in _G (internal, stored in registry) ===
|
||||
|
||||
do
|
||||
assert(type(__builtins) == "table")
|
||||
assert(type(__builtins.cd) == "function")
|
||||
assert(type(__builtins.exec) == "function")
|
||||
assert(type(__builtins.umask) == "function")
|
||||
assert(__builtins == nil, "__builtins should not be in _G")
|
||||
end
|
||||
|
||||
|
||||
@@ -117,9 +114,9 @@ end
|
||||
|
||||
-- === exec ===
|
||||
|
||||
-- exec with no command returns error
|
||||
-- exec with no command returns error (tested via backtick)
|
||||
do
|
||||
local r = __builtins.exec("exec")
|
||||
local r = `exec`
|
||||
assert(r.code == 1)
|
||||
assert(r.stderr:find("command required"),
|
||||
"expected 'command required', got: " .. r.stderr)
|
||||
@@ -127,7 +124,7 @@ end
|
||||
|
||||
-- exec nonexistent command returns error
|
||||
do
|
||||
local r = __builtins.exec("exec", "nonexistent_cmd_xyz_999")
|
||||
local r = `exec nonexistent_cmd_xyz_999`
|
||||
assert(r.code == 1)
|
||||
assert(r.stderr:find("exec:"),
|
||||
"expected exec error, got: " .. r.stderr)
|
||||
@@ -140,21 +137,4 @@ do
|
||||
end
|
||||
|
||||
|
||||
-- === builtins are overridable ===
|
||||
|
||||
do
|
||||
local old_cd = __builtins.cd
|
||||
local called = false
|
||||
__builtins.cd = function(...)
|
||||
called = true
|
||||
return old_cd(...)
|
||||
end
|
||||
local before = `pwd`.stdout:gsub("\n$", "")
|
||||
`cd /tmp`
|
||||
assert(called, "custom cd was not called")
|
||||
__builtins.cd = old_cd
|
||||
`cd ${before}`
|
||||
end
|
||||
|
||||
|
||||
print "OK"
|
||||
|
||||
Reference in New Issue
Block a user