diff --git a/lapi.c b/lapi.c index 27fa5247..6e3ef852 100644 --- a/lapi.c +++ b/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); diff --git a/lbuiltin.c b/lbuiltin.c index ec3f7abe..c85ffd56 100644 --- a/lbuiltin.c +++ b/lbuiltin.c @@ -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); } diff --git a/lcmd.c b/lcmd.c index aee06300..7870d6ab 100644 --- a/lcmd.c +++ b/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; +} diff --git a/lcmd.h b/lcmd.h index 565dd417..9954aac0 100644 --- a/lcmd.h +++ b/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 diff --git a/linit.c b/linit.c index a2110d63..456415ad 100644 --- a/linit.c +++ b/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); } diff --git a/lparser.c b/lparser.c index ade7a7e9..897ff5b2 100644 --- a/lparser.c +++ b/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 */ diff --git a/lua.h b/lua.h index ab473dc3..9f854fba 100644 --- a/lua.h +++ b/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 */ diff --git a/testes/lush/builtins.lua b/testes/lush/builtins.lua index 441845f8..5a58a4da 100644 --- a/testes/lush/builtins.lua +++ b/testes/lush/builtins.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"