From a006514ea138a29b6031058d9002b48a572b5dd6 Mon Sep 17 00:00:00 2001 From: Roberto Ierusalimschy Date: Mon, 29 Oct 2018 14:26:48 -0300 Subject: Big revamp in the implmentation of labels/gotos Added restriction that, when a label is created, there cannot be another label with the same name visible. That allows backward goto's to be resolved when they are read. Backward goto's get a close if they jump out of the scope of some variable; labels get a close only if previous goto to it jumps out of the scope of some upvalue. --- lparser.c | 241 ++++++++++++++++++++++++++++++-------------------------------- 1 file changed, 117 insertions(+), 124 deletions(-) (limited to 'lparser.c') diff --git a/lparser.c b/lparser.c index c3dc6362..499e9531 100644 --- a/lparser.c +++ b/lparser.c @@ -49,8 +49,6 @@ typedef struct BlockCnt { struct BlockCnt *previous; /* chain */ int firstlabel; /* index of first label in this block */ int firstgoto; /* index of first pending goto in this block */ - int brks; /* list of break jumps in this block */ - lu_byte brkcls; /* true if some 'break' needs to close upvalues */ lu_byte nactvar; /* # active locals outside the block */ lu_byte upval; /* true if some variable in the block is an upvalue */ lu_byte isloop; /* true if 'block' is a loop */ @@ -330,50 +328,56 @@ static void adjust_assign (LexState *ls, int nvars, int nexps, expdesc *e) { #define leavelevel(ls) ((ls)->L->nCcalls--) -static void closegoto (LexState *ls, int g, Labeldesc *label) { +/* +** Generates an error that a goto jumps into the scope of some +** local variable. +*/ +static l_noret jumpscopeerror (LexState *ls, Labeldesc *gt) { + const char *varname = getstr(getlocvar(ls->fs, gt->nactvar)->varname); + const char *msg = " at line %d jumps into the scope of local '%s'"; + msg = luaO_pushfstring(ls->L, msg, getstr(gt->name), gt->line, varname); + luaK_semerror(ls, msg); /* raise the error */ +} + + +/* +** Solves the goto at index 'g' to given 'label' and removes it +** from the list of pending goto's. +** If it jumps into the scope of some variable, raises an error. +*/ +static void solvegoto (LexState *ls, int g, Labeldesc *label) { int i; - FuncState *fs = ls->fs; - Labellist *gl = &ls->dyd->gt; - Labeldesc *gt = &gl->arr[g]; + Labellist *gl = &ls->dyd->gt; /* list of goto's */ + Labeldesc *gt = &gl->arr[g]; /* goto to be resolved */ lua_assert(eqstr(gt->name, label->name)); - if (gt->nactvar < label->nactvar) { - TString *vname = getlocvar(fs, gt->nactvar)->varname; - const char *msg = luaO_pushfstring(ls->L, - " at line %d jumps into the scope of local '%s'", - getstr(gt->name), gt->line, getstr(vname)); - luaK_semerror(ls, msg); - } - luaK_patchgoto(fs, gt->pc, label->pc, 1); - /* remove goto from pending list */ - for (i = g; i < gl->n - 1; i++) + if (gt->nactvar < label->nactvar) /* enter some scope? */ + jumpscopeerror(ls, gt); + luaK_patchlist(ls->fs, gt->pc, label->pc); + for (i = g; i < gl->n - 1; i++) /* remove goto from pending list */ gl->arr[i] = gl->arr[i + 1]; gl->n--; } /* -** try to close a goto with existing labels; this solves backward jumps +** Search for an active label with the given name. */ -static int solvelabel (LexState *ls, int g) { +static Labeldesc *findlabel (LexState *ls, TString *name) { int i; - BlockCnt *bl = ls->fs->bl; Dyndata *dyd = ls->dyd; - Labeldesc *gt = &dyd->gt.arr[g]; - /* check labels in current block for a match */ - for (i = bl->firstlabel; i < dyd->label.n; i++) { + /* check labels in current function for a match */ + for (i = ls->fs->firstlabel; i < dyd->label.n; i++) { Labeldesc *lb = &dyd->label.arr[i]; - if (eqstr(lb->name, gt->name)) { /* correct label? */ - if (gt->nactvar > lb->nactvar && - (bl->upval || dyd->label.n > bl->firstlabel)) - luaK_patchclose(ls->fs, gt->pc); - closegoto(ls, g, lb); /* close it */ - return 1; - } + if (eqstr(lb->name, name)) /* correct label? */ + return lb; } - return 0; /* label not found; cannot close goto */ + return NULL; /* label not found */ } +/* +** Adds a new label/goto in the corresponding list. +*/ static int newlabelentry (LexState *ls, Labellist *l, TString *name, int line, int pc) { int n = l->n; @@ -382,6 +386,7 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, l->arr[n].name = name; l->arr[n].line = line; l->arr[n].nactvar = ls->fs->nactvar; + l->arr[n].close = 0; l->arr[n].pc = pc; l->n = n + 1; return n; @@ -389,51 +394,64 @@ static int newlabelentry (LexState *ls, Labellist *l, TString *name, /* -** check whether new label 'lb' matches any pending gotos in current -** block; solves forward jumps +** Solves forward jumps. Check whether new label 'lb' matches any +** pending gotos in current block and solves them. Return true +** if any of the goto's need to close upvalues. */ -static void solvegotos (LexState *ls, Labeldesc *lb) { +static int solvegotos (LexState *ls, Labeldesc *lb) { Labellist *gl = &ls->dyd->gt; int i = ls->fs->bl->firstgoto; + int needsclose = 0; while (i < gl->n) { - if (eqstr(gl->arr[i].name, lb->name)) - closegoto(ls, i, lb); /* will remove 'i' from the list */ + if (eqstr(gl->arr[i].name, lb->name)) { + needsclose |= gl->arr[i].close; + solvegoto(ls, i, lb); /* will remove 'i' from the list */ + } else i++; } + return needsclose; +} + + +/* +** Create a new label with the given 'name' at the given 'line'. +** 'last' tells whether label is the last non-op statement in its +** block. Solves all pending goto's to this new label and adds +** a close instruction if necessary. +** Returns true iff it added a close instruction. +*/ +static int createlabel (LexState *ls, TString *name, int line, + int last) { + FuncState *fs = ls->fs; + Labellist *ll = &ls->dyd->label; + int l = newlabelentry(ls, ll, name, line, luaK_getlabel(fs)); + if (last) { /* label is last no-op statement in the block? */ + /* assume that locals are already out of scope */ + ll->arr[l].nactvar = fs->bl->nactvar; + } + if (solvegotos(ls, &ll->arr[l])) { /* need close? */ + luaK_codeABC(fs, OP_CLOSE, fs->nactvar, 0, 0); + return 1; + } + return 0; } /* -** export pending gotos to outer level, to check them against -** outer labels; if the block being exited has upvalues, and -** the goto exits the scope of any variable (which can be the -** upvalue), close those variables being exited. Also export -** break list. +** Adjust pending gotos to outer level of a block. */ static void movegotosout (FuncState *fs, BlockCnt *bl) { - int i = bl->firstgoto; + int i; Labellist *gl = &fs->ls->dyd->gt; - /* correct pending gotos to current block and try to close it - with visible labels */ - while (i < gl->n) { /* for each pending goto */ + /* correct pending gotos to current block */ + for (i = bl->firstgoto; i < gl->n; i++) { /* for each pending goto */ Labeldesc *gt = &gl->arr[i]; if (gt->nactvar > bl->nactvar) { /* leaving a variable scope? */ - if (bl->upval) /* variable may be an upvalue? */ - luaK_patchclose(fs, gt->pc); /* jump will need a close */ gt->nactvar = bl->nactvar; /* update goto level */ + gt->close |= bl->upval; /* jump may need a close */ } - if (!solvelabel(fs->ls, i)) - i++; /* move to next one */ - /* else, 'solvelabel' removed current goto from the list - and 'i' now points to next one */ } - /* handles break list */ - if (bl->upval) /* exiting the scope of an upvalue? */ - luaK_patchclose(fs, bl->brks); /* breaks will need OP_CLOSE */ - /* move breaks to outer block */ - luaK_concat(fs, &bl->previous->brks, bl->brks); - bl->previous->brkcls |= bl->brkcls; } @@ -442,8 +460,6 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { bl->nactvar = fs->nactvar; bl->firstlabel = fs->ls->dyd->label.n; bl->firstgoto = fs->ls->dyd->gt.n; - bl->brks = NO_JUMP; - bl->brkcls = 0; bl->upval = 0; bl->previous = fs->bl; fs->bl = bl; @@ -451,20 +467,6 @@ static void enterblock (FuncState *fs, BlockCnt *bl, lu_byte isloop) { } -/* -** Fix all breaks in block 'bl' to jump to the end of the block. -*/ -static void fixbreaks (FuncState *fs, BlockCnt *bl) { - int target = fs->pc; - if (bl->brkcls) /* does the block need to close upvalues? */ - luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); - luaK_patchgoto(fs, bl->brks, target, bl->brkcls); - bl->brks = NO_JUMP; /* no more breaks to fix */ - bl->brkcls = 0; /* no more need to close upvalues */ - lua_assert(!bl->upval); /* loop body cannot have local variables */ -} - - /* ** generates an error for an undefined 'goto'. */ @@ -478,11 +480,10 @@ static l_noret undefgoto (LexState *ls, Labeldesc *gt) { static void leaveblock (FuncState *fs) { BlockCnt *bl = fs->bl; LexState *ls = fs->ls; - if (bl->upval && bl->brks != NO_JUMP) /* breaks in upvalue scopes? */ - bl->brkcls = 1; /* these breaks must close the upvalues */ - if (bl->isloop) - fixbreaks(fs, bl); /* fix pending breaks */ - if (bl->previous && bl->upval) + int hasclose = 0; + if (bl->isloop) /* fix pending breaks? */ + hasclose = createlabel(ls, luaS_newliteral(ls->L, "break"), 0, 0); + if (!hasclose && bl->previous && bl->upval) luaK_codeABC(fs, OP_CLOSE, bl->nactvar, 0, 0); fs->bl = bl->previous; removevars(fs, bl->nactvar); @@ -492,7 +493,6 @@ static void leaveblock (FuncState *fs) { if (bl->previous) /* inner block? */ movegotosout(fs, bl); /* update pending gotos to outer block */ else { - lua_assert(bl->brks == NO_JUMP); /* no pending breaks */ if (bl->firstgoto < ls->dyd->gt.n) /* pending gotos in outer block? */ undefgoto(ls, &ls->dyd->gt.arr[bl->firstgoto]); /* error */ } @@ -550,6 +550,7 @@ static void open_func (LexState *ls, FuncState *fs, BlockCnt *bl) { fs->nactvar = 0; fs->needclose = 0; fs->firstlocal = ls->dyd->actvar.n; + fs->firstlabel = ls->dyd->label.n; fs->bl = NULL; f->source = ls->source; f->maxstacksize = 2; /* registers 0/1 are always valid */ @@ -1204,63 +1205,59 @@ static int cond (LexState *ls) { } -static void gotostat (LexState *ls, int pc) { +static void gotostat (LexState *ls) { + FuncState *fs = ls->fs; int line = ls->linenumber; - int g; - luaX_next(ls); /* skip 'goto' */ - g = newlabelentry(ls, &ls->dyd->gt, str_checkname(ls), line, pc); - solvelabel(ls, g); /* close it if label already defined */ + TString *name = str_checkname(ls); /* label's name */ + Labeldesc *lb = findlabel(ls, name); + if (lb == NULL) /* no label? */ + /* forward jump; will be resolved when the label is declared */ + newlabelentry(ls, &ls->dyd->gt, name, line, luaK_jump(fs)); + else { /* found a label */ + /* backward jump; will be resolved here */ + if (fs->nactvar > lb->nactvar) /* leaving the scope of some variable? */ + luaK_codeABC(fs, OP_CLOSE, lb->nactvar, 0, 0); + /* create jump and link it to the label */ + luaK_patchlist(fs, luaK_jump(fs), lb->pc); + } } +/* +** Break statement. Semantically equivalent to "goto break". +*/ static void breakstat (LexState *ls, int pc) { FuncState *fs = ls->fs; + int line = ls->linenumber; BlockCnt *bl = fs->bl; luaX_next(ls); /* skip break */ + newlabelentry(ls, &ls->dyd->gt, luaS_newliteral(ls->L, "break"), line, pc); while (bl && !bl->isloop) { bl = bl->previous; } if (!bl) luaX_syntaxerror(ls, "no loop to break"); - luaK_concat(fs, &fs->bl->brks, pc); } -/* check for repeated labels on the same block */ -static void checkrepeated (FuncState *fs, Labellist *ll, TString *label) { - int i; - for (i = fs->bl->firstlabel; i < ll->n; i++) { - if (eqstr(label, ll->arr[i].name)) { - const char *msg = luaO_pushfstring(fs->ls->L, - "label '%s' already defined on line %d", - getstr(label), ll->arr[i].line); - luaK_semerror(fs->ls, msg); - } +/* +** Check whether there is already a label with the given 'name'. +*/ +static void checkrepeated (LexState *ls, TString *name) { + Labeldesc *lb = findlabel(ls, name); + if (lb != NULL) { /* already defined? */ + const char *msg = "label '%s' already defined on line %d"; + msg = luaO_pushfstring(ls->L, msg, getstr(name), lb->line); + luaK_semerror(ls, msg); /* error */ } } -/* skip no-op statements */ -static void skipnoopstat (LexState *ls) { - while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) - statement(ls); -} - - -static void labelstat (LexState *ls, TString *label, int line) { +static void labelstat (LexState *ls, TString *name, int line) { /* label -> '::' NAME '::' */ - FuncState *fs = ls->fs; - Labellist *ll = &ls->dyd->label; - int l; /* index of new label being created */ - checkrepeated(fs, ll, label); /* check for repeated labels */ checknext(ls, TK_DBCOLON); /* skip double colon */ - /* create new entry for this label */ - l = newlabelentry(ls, ll, label, line, luaK_getlabel(fs)); - luaK_codeABC(fs, OP_CLOSE, fs->nactvar, 0, 0); - skipnoopstat(ls); /* skip other no-op statements */ - if (block_follow(ls, 0)) { /* label is last no-op statement in the block? */ - /* assume that locals are already out of scope */ - ll->arr[l].nactvar = fs->bl->nactvar; - } - solvegotos(ls, &ll->arr[l]); + while (ls->t.token == ';' || ls->t.token == TK_DBCOLON) + statement(ls); /* skip other no-op statements */ + checkrepeated(ls, name); /* check for repeated labels */ + createlabel(ls, name, line, block_follow(ls, 0)); } @@ -1295,8 +1292,6 @@ static void repeatstat (LexState *ls, int line) { statlist(ls); check_match(ls, TK_UNTIL, TK_REPEAT, line); condexit = cond(ls); /* read condition (inside scope block) */ - if (bl2.upval) /* upvalues? */ - luaK_patchclose(fs, condexit); leaveblock(fs); /* finish scope */ if (bl2.upval) { /* upvalues? */ int exit = luaK_jump(fs); /* normal exit must jump over fix */ @@ -1453,15 +1448,12 @@ static void test_then_block (LexState *ls, int *escapelist) { luaX_next(ls); /* skip IF or ELSEIF */ expr(ls, &v); /* read condition */ checknext(ls, TK_THEN); - if (ls->t.token == TK_GOTO || ls->t.token == TK_BREAK) { + if (ls->t.token == TK_BREAK) { luaK_goiffalse(ls->fs, &v); /* will jump to label if condition is true */ enterblock(fs, &bl, 0); /* must enter block before 'goto' */ - if (ls->t.token == TK_GOTO) - gotostat(ls, v.t); /* handle goto */ - else - breakstat(ls, v.t); /* handle break */ + breakstat(ls, v.t); /* handle break */ while (testnext(ls, ';')) {} /* skip semicolons */ - if (block_follow(ls, 0)) { /* 'goto'/'break' is the entire block? */ + if (block_follow(ls, 0)) { /* 'break' is the entire block? */ leaveblock(fs); return; /* and that is it */ } @@ -1683,7 +1675,8 @@ static void statement (LexState *ls) { break; } case TK_GOTO: { /* stat -> 'goto' NAME */ - gotostat(ls, luaK_jump(ls->fs)); + luaX_next(ls); /* skip 'goto' */ + gotostat(ls); break; } default: { /* stat -> func | assignment */ -- cgit v1.2.3-55-g6feb