lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


I needed a way to specify the upvalues of a function explicitly so
that I could serialise and de-serialise user defined functions in a
reliable way.  I have added the syntax of an angle bracketed parameter
list for upvalues which comes after the argument parameter list in the
function definition.

For example, the main chunk which was previously defined as:

function (...)
  stmt;*
  return
end

is now defined as

function (...) <_ENV>
  stmt;*
  return
end

A general existing function has the defined upvalues of <...> (i.e.
whatever it likes).

The declared upvalues must exist in the context of the function
definition (i.e. they cannot be globals to be looked up).

If a fixed upvalue list (i.e. without ...) is provided without _ENV,
then the function cannot lookup global values and this will actually
generate a syntax error at parsing rather than at runtime.  This has
the advantage that 'strict' mode is now enforceable at the parsing
stage rather than at runtime (current with _ENV modification).

If a fixed upvalue list is provided with _ENV but without locals from
its context then those locals will not be accessible and globals will
be sought instead.  This brings the 'strict' behaviour to closures
since I can prevent them from accidentally seeing a local variable in
their scope, and in combination with the normal strict _ENV I can
report it at runtime, or with disabling _ENV as well cause a syntax
error at parse time.

My motivation for this was to allow users to provide custom functions
in a controlled way.  I let them provide a function as a string, and
then I compile it with the following:

compiled = load("local "..table.concat(upvals, ", ").."; return
function (...) <"..table.concat(upvals,", ")..">; "..userfunc..";
end")()

Which gives a syntax error if they try to access a
variable/function/table that is not declared either as a local by them
or in the upvals list, and means that when I serialise using
`string.dump(compiled,true)` I know exactly what upvalues to bind
where when reloading the function.

Anyway, the patch is attached, and if anyone has an feedback I'd be
interested to hear it.

Regards,

Duane.
Index: lparser.c
@@ -227,6 +227,7 @@ static int searchupvalue (FuncState *fs, TString *name) {
 static int newupvalue (FuncState *fs, TString *name, expdesc *v) {
   Proto *f = fs->f;
   int oldsize = f->sizeupvalues;
+  lua_assert(oldsize >= 0);
   checklimit(fs, fs->nups + 1, MAXUPVAL, "upvalues");
   luaM_growvector(fs->ls->L, f->upvalues, fs->nups, f->sizeupvalues,
                   Upvaldesc, MAXUPVAL, "upvalues");
@@ -278,6 +279,8 @@ static int singlevaraux (FuncState *fs, TString *n, expdesc *var, int base) {
     else {  /* not found as local at current level; try upvalues */
       int idx = searchupvalue(fs, n);  /* try existing upvalues */
       if (idx < 0) {  /* not found? */
+        if (fs->f->sizeupvalues < 0)  /* no more upvalues */
+          return VVOID;  /* look in globals */
         if (singlevaraux(fs->prev, n, var, 0) == VVOID) /* try upper levels */
           return VVOID;  /* not found; is a global */
         /* else was LOCAL or UPVAL */
@@ -295,7 +298,10 @@ static void singlevar (LexState *ls, expdesc *var) {
   FuncState *fs = ls->fs;
   if (singlevaraux(fs, varname, var, 1) == VVOID) {  /* global name? */
     expdesc key;
-    singlevaraux(fs, ls->envn, var, 1);  /* get environment variable */
+    if (singlevaraux(fs, ls->envn, var, 1) == VVOID) {  /* get environment variable */
+      luaX_syntaxerror(ls, luaO_pushfstring(ls->L,
+        "variable '%s' not found (_ENV disabled)", getstr(varname)));
+    }
     lua_assert(var->k == VLOCAL || var->k == VUPVAL);
     codestring(ls, &key, varname);  /* key is variable name */
     luaK_indexed(fs, var, &key);  /* env[varname] */
@@ -559,7 +565,9 @@ static void close_func (LexState *ls) {
   f->sizep = fs->np;
   luaM_reallocvector(L, f->locvars, f->sizelocvars, fs->nlocvars, LocVar);
   f->sizelocvars = fs->nlocvars;
-  luaM_reallocvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc);
+  if (f->sizeupvalues >= 0) {
+    luaM_reallocvector(L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc);
+  }
   f->sizeupvalues = fs->nups;
   lua_assert(fs->bl == NULL);
   ls->fs = fs->prev;
@@ -744,6 +752,49 @@ static void constructor (LexState *ls, expdesc *t) {
 
 
 
+static void force_upval (FuncState *fs, TString *name) {
+  expdesc var;
+  const char *msg = NULL;
+  if (searchupvalue(fs, name) >= 0) {
+    msg = "upval '%s' already declared";
+  } else if (singlevaraux(fs->prev, name, &var, 0) == VVOID) {
+    msg = "upval '%s' not found in closure context";
+  } else {
+    newupvalue(fs, name, &var);
+  }
+  if (msg) luaX_syntaxerror(fs->ls, luaO_pushfstring(fs->ls->L, msg, getstr(name)));
+}
+
+
+static void upvlist (LexState *ls) {
+  FuncState *fs = ls->fs;
+  Proto *f = fs->f;
+  int locked = 1;
+  luaX_next(ls);
+  if (ls->t.token != '>') {
+    do {
+      switch (ls->t.token) {
+        case TK_NAME: {  /* upval -> NAME */
+          force_upval(fs, str_checkname(ls));
+          break;
+        }
+        case TK_DOTS: {  /* unlocked */
+          luaX_next(ls);
+          locked = 0;
+          break;
+        }
+        default: luaX_syntaxerror(ls, "<name> or '...' expected");
+      }
+    } while (locked && testnext(ls, ','));
+  }
+  checknext(ls, '>');
+  if (locked) {
+    luaM_reallocvector(ls->L, f->upvalues, f->sizeupvalues, fs->nups, Upvaldesc);
+    f->sizeupvalues = -1;
+  }
+}
+
+
 static void parlist (LexState *ls) {
   /* parlist -> [ param { ',' param } ] */
   FuncState *fs = ls->fs;
@@ -787,6 +838,9 @@ static void body (LexState *ls, expdesc *e, int ismethod, int line) {
   }
   parlist(ls);
   checknext(ls, ')');
+  if (ls->t.token == '<') {
+    upvlist(ls);
+  }
   statlist(ls);
   new_fs.f->lastlinedefined = ls->linenumber;
   check_match(ls, TK_END, TK_FUNCTION, line);