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:
Cormac Shannon
2026-03-12 22:46:56 +00:00
parent 34bfabccbd
commit f88b17959f
8 changed files with 134 additions and 77 deletions

View File

@@ -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 */