diff --git a/source/compiler/sc.h b/source/compiler/sc.h index 4d3cf54..2a313c0 100644 --- a/source/compiler/sc.h +++ b/source/compiler/sc.h @@ -231,6 +231,16 @@ typedef struct s_symbol { #define uRETNONE 0x010 /* uASSIGNED indicates that a value assigned to the variable is not used yet */ #define uASSIGNED 0x080 +/* uLOOPVAR is set when a variable is read inside of a loop condition. This is + * used to detect situations when a variable is used in a loop condition, but + * not modified inside of a loop body. */ +#define uLOOPVAR 0x1000 +/* uNOLOOPVAR is set when a variable is + * * modified inside of a loop condition before being read, or + * * used in an enclosing loop and should be excluded from checks in an inner loop, + * so the compiler would know it shouldn't set the uLOOPVAR flag when the variable + * is read inside a loop condition */ +#define uNOLOOPVAR 0x2000 #define flagDEPRECATED 0x01 /* symbol is deprecated (avoid use) */ #define flagNAKED 0x10 /* function is naked */ @@ -305,7 +315,9 @@ typedef struct s_valuepair { /* struct "symstate" is used to: * * synchronize the status of assignments between all "if" branches or "switch" * cases, so the compiler could detect unused assignments in all of those - * branches/cases, not only in the last one */ + * branches/cases, not only in the last one; + * * back up the "uNOLOOPVAR" flag when scanning for variables that were used + * in a loop exit condition, but weren't modified inside the loop body */ typedef struct s_assigninfo { int lnumber; /* line number of the first unused assignment made in one of * the branches (used for error messages) */ @@ -1011,6 +1023,8 @@ SC_VDECL int pc_retexpr; /* true if the current expression is a part of a " SC_VDECL int pc_retheap; /* heap space (in bytes) to be manually freed when returning an array returned by another function */ SC_VDECL int pc_nestlevel; /* number of active (open) compound statements */ SC_VDECL unsigned int pc_attributes;/* currently set attribute flags (for the "__pragma" operator) */ +SC_VDECL int pc_loopcond; /* true if the current expression is a loop condition */ +SC_VDECL int pc_numloopvars; /* number of variables used inside a loop condition */ SC_VDECL constvalue_root sc_automaton_tab; /* automaton table */ SC_VDECL constvalue_root sc_state_tab; /* state table */ diff --git a/source/compiler/sc1.c b/source/compiler/sc1.c index 5cba80c..f2a815e 100644 --- a/source/compiler/sc1.c +++ b/source/compiler/sc1.c @@ -116,6 +116,8 @@ static void make_report(symbol *root,FILE *log,char *sourcefile); static void reduce_referrers(symbol *root); static long max_stacksize(symbol *root,int *recursion); static int testsymbols(symbol *root,int level,int testlabs,int testconst); +static void scanloopvariables(symstate **loopvars); +static void testloopvariables(symstate *loopvars,int line); static void destructsymbols(symbol *root,int level); static constvalue *find_constval_byval(constvalue_root *table,cell val); static symbol *fetchlab(char *name); @@ -919,6 +921,7 @@ static void resetglobals(void) pc_naked=FALSE; pc_retexpr=FALSE; pc_attributes=0; + pc_loopcond=FALSE; emit_flags=0; emit_stgbuf_idx=-1; } @@ -5174,6 +5177,105 @@ static int testsymbols(symbol *root,int level,int testlabs,int testconst) return entry; } +static void scanloopvariables(symstate **loopvars) +{ + symbol *start,*sym; + int num; + + /* error messages are only printed on the "writing" pass, + * so if we are not writing yet, then we have a quick exit */ + if (sc_status!=statWRITE) + return; + + /* if there's only one active loop entry (the current loop), + * then there's no enclosing loop and we have an early exit */ + if (wqptr-wqSIZE==wq) + return; + + /* skip labels */ + start=&loctab; + while ((start=start->next)!=NULL && start->ident==iLABEL) + /* nothing */; + /* if there are no other local symbols, we have an early exit */ + if (start==NULL) + return; + + /* count the number of local symbols */ + for (num=0,sym=start; sym!=NULL; num++,sym=sym->next) + /* nothing */; + + assert(*loopvars==NULL); + assert(num!=0); + *loopvars=(symstate *)calloc((size_t)num,sizeof(symstate)); + if (*loopvars==NULL) + error(103); /* insufficient memory */ + + for (num=0,sym=start; sym!=NULL; num++,sym=sym->next) { + /* if the variable already has the uLOOPVAR flag set (from being used + * in an enclosing loop), we have to set the uNOLOOPVAR to exclude it + * from checks in the current loop */ + if ((sym->ident==iVARIABLE || sym->ident==iREFERENCE || sym->ident==iARRAY + || sym->ident==iREFARRAY) && (sym->usage & uLOOPVAR)!=0) { + /* ... but it might be already set from an enclosing loop, + * so we need to temporarily store it in "loopvars[num]" first */ + (*loopvars)[num].usage |= (sym->usage & uNOLOOPVAR); + sym->usage |= uNOLOOPVAR; + } /* if */ + } /* if */ +} + +static void testloopvariables(symstate *loopvars,int line) +{ + symbol *start,*sym; + int num,warnnum=0; + + /* the error messages are only printed on the "writing" pass, + * so if we are not writing yet, then we have a quick exit */ + if (sc_status!=statWRITE) + return; + + /* skip labels */ + start=&loctab; + while ((start=start->next)!=NULL && start->ident==iLABEL) + /* nothing */; + + /* decrement pc_numloopvars by 1 for each variable that wasn't modified + * inside the loop body; if pc_numloopvars gets zeroed after this, it would + * mean none of the variables used inside the loop condition were modified */ + if (pc_numloopvars!=0) { + warnnum=(pc_numloopvars==1) ? 250 : 251; + for (sym=start; sym!=NULL; sym=sym->next) + if ((sym->ident==iVARIABLE || sym->ident==iREFERENCE || sym->ident==iARRAY + || sym->ident==iREFARRAY) && (sym->usage & (uLOOPVAR | uNOLOOPVAR))==uLOOPVAR) + pc_numloopvars--; + if (pc_numloopvars==0 && warnnum==251) { + errorset(sSETPOS,line); + error(251); /* none of the variables used in loop condition are modified in loop body */ + errorset(sSETPOS,-1); + } /* if */ + } /* if */ + + for (num=0,sym=start; sym!=NULL; num++,sym=sym->next) { + if (sym->ident==iVARIABLE || sym->ident==iREFERENCE + || sym->ident==iARRAY || sym->ident==iREFARRAY) { + if ((sym->usage & (uLOOPVAR | uNOLOOPVAR))==uLOOPVAR) { + sym->usage &= ~uLOOPVAR; + /* warn only if none of the variables used inside the loop condition + * were modified inside the loop body */ + if (pc_numloopvars==0 && warnnum==250) { + errorset(sSETPOS,line); + error(250,sym->name); /* variable used in loop condition not modified in loop body */ + errorset(sSETPOS,-1); + } /* if */ + } /* if */ + sym->usage &= ~uNOLOOPVAR; + if (loopvars!=NULL) + sym->usage |= (loopvars[num].usage & uNOLOOPVAR); + } /* if */ + } /* for */ + free(loopvars); +} + static cell calc_array_datasize(symbol *sym, cell *offset) { cell length; @@ -5925,9 +6027,13 @@ static int doif(void) static int dowhile(void) { int wq[wqSIZE]; /* allocate local queue */ - int save_endlessloop,retcode; + int save_endlessloop,save_numloopvars,retcode; + int loopline=fline; + symstate *loopvars=NULL; save_endlessloop=endlessloop; + save_numloopvars=pc_numloopvars; + pc_numloopvars=0; addwhile(wq); /* add entry to queue for "break" */ setlabel(wq[wqLOOP]); /* loop label */ /* The debugger uses the "break" opcode to be able to "break" out of @@ -5935,18 +6041,23 @@ static int dowhile(void) * tiniest loop, set it below the top of the loop */ setline(TRUE); + scanloopvariables(&loopvars); pc_nestlevel++; /* temporarily increase the "compound statement" nesting level, * so any assignments made inside the loop control expression * could be cleaned up later */ + pc_loopcond=TRUE; endlessloop=test(wq[wqEXIT],TEST_DO,FALSE);/* branch to wq[wqEXIT] if false */ + pc_loopcond=FALSE; pc_nestlevel--; statement(NULL,FALSE); /* if so, do a statement */ clearassignments(pc_nestlevel+1); + testloopvariables(loopvars,loopline); jumplabel(wq[wqLOOP]); /* and loop to "while" start */ setlabel(wq[wqEXIT]); /* exit label */ delwhile(); /* delete queue entry */ retcode=endlessloop ? tENDLESS : tWHILE; + pc_numloopvars=save_numloopvars; endlessloop=save_endlessloop; return retcode; } @@ -5959,6 +6070,8 @@ static int dodo(void) { int wq[wqSIZE],top; int save_endlessloop,retcode; + int loopline=fline; + symstate *loopvars=NULL; save_endlessloop=endlessloop; addwhile(wq); /* see "dowhile" for more info */ @@ -5968,12 +6081,14 @@ static int dodo(void) needtoken(tWHILE); setlabel(wq[wqLOOP]); /* "continue" always jumps to WQLOOP. */ setline(TRUE); + scanloopvariables(&loopvars); pc_nestlevel++; /* temporarily increase the "compound statement" nesting level, * so any assignments made inside the loop control expression * could be cleaned up later */ endlessloop=test(wq[wqEXIT],TEST_OPT,FALSE); pc_nestlevel--; clearassignments(pc_nestlevel+1); + testloopvariables(loopvars,loopline); jumplabel(top); setlabel(wq[wqEXIT]); delwhile(); @@ -5988,13 +6103,17 @@ static int dofor(void) { int wq[wqSIZE],skiplab; cell save_decl; - int save_nestlevel,save_endlessloop; + int save_nestlevel,save_endlessloop,save_numloopvars; int index,endtok; int *ptr; + int loopline=fline; + symstate *loopvars=NULL; save_decl=declared; save_nestlevel=pc_nestlevel; save_endlessloop=endlessloop; + save_numloopvars=pc_numloopvars; + pc_numloopvars=0; addwhile(wq); skiplab=getlabel(); @@ -6027,6 +6146,7 @@ static int dofor(void) jumplabel(skiplab); /* skip expression 3 1st time */ setlabel(wq[wqLOOP]); /* "continue" goes to this label: expr3 */ setline(TRUE); + scanloopvariables(&loopvars); /* Expressions 2 and 3 are reversed in the generated code: expression 3 * precedes expression 2. When parsing, the code is buffered and marks for * the start of each expression are insterted in the buffer. @@ -6041,7 +6161,9 @@ static int dofor(void) if (matchtoken(';')) { endlessloop=1; } else { + pc_loopcond=TRUE; endlessloop=test(wq[wqEXIT],TEST_PLAIN,FALSE);/* expression 2 (jump to wq[wqEXIT] if false) */ + pc_loopcond=FALSE; needtoken(';'); } /* if */ stgmark((char)(sEXPRSTART+1)); /* mark start of 3th expression in stage */ @@ -6054,6 +6176,7 @@ static int dofor(void) stgset(FALSE); /* stop staging */ statement(NULL,FALSE); clearassignments(save_nestlevel+1); + testloopvariables(loopvars,loopline); jumplabel(wq[wqLOOP]); setlabel(wq[wqEXIT]); delwhile(); @@ -6072,6 +6195,7 @@ static int dofor(void) pc_nestlevel=save_nestlevel; /* reset 'compound statement' nesting level */ index=endlessloop ? tENDLESS : tFOR; + pc_numloopvars=save_numloopvars; endlessloop=save_endlessloop; return index; } diff --git a/source/compiler/sc2.c b/source/compiler/sc2.c index 50523aa..028595f 100644 --- a/source/compiler/sc2.c +++ b/source/compiler/sc2.c @@ -55,6 +55,7 @@ static symbol *find_symbol(const symbol *root,const char *name,int fnumber,int a static void substallpatterns(unsigned char *line,int buffersize); static int match(char *st,int end); static int alpha(char c); +static void markloopvariable(symbol *sym,int usage); #define SKIPMODE 1 /* bit field in "#if" stack */ #define PARSEMODE 2 /* bit field in "#if" stack */ @@ -3308,6 +3309,11 @@ SC_FUNC void markusage(symbol *sym,int usage) sym->lnumber=fline; if ((usage & uREAD)!=0 && (sym->ident==iVARIABLE || sym->ident==iREFERENCE)) sym->usage &= ~uASSIGNED; + if ((usage & (uREAD | uWRITTEN))!=0 + && (sym->vclass==sLOCAL || sym->vclass==sSTATIC) + && (sym->ident==iVARIABLE || sym->ident==iREFERENCE + || sym->ident==iARRAY || sym->ident==iREFARRAY)) + markloopvariable(sym,usage); /* check if (global) reference must be added to the symbol */ if ((usage & (uREAD | uWRITTEN))!=0) { /* only do this for global symbols */ @@ -3338,7 +3344,7 @@ SC_FUNC void clearassignments(int fromlevel) { symbol *sym; - /* the error messages are only printed on the "writing" pass, + /* error messages are only printed on the "writing" pass, * so if we are not writing yet, then we have a quick exit */ if (sc_status!=statWRITE) return; @@ -3355,7 +3361,7 @@ SC_FUNC void memoizeassignments(int fromlevel,symstate **assignments) symbol *sym; int num; - /* the error messages are only printed on the "writing" pass, + /* error messages are only printed on the "writing" pass, * so if we are not writing yet, then we have a quick exit */ if (sc_status!=statWRITE) return; @@ -3415,6 +3421,28 @@ SC_FUNC void restoreassignments(int fromlevel,symstate *assignments) free(assignments); } +static void markloopvariable(symbol *sym,int usage) +{ + while (sym->parent!=NULL) + sym=sym->parent; + /* check if the variable used inside a loop condition */ + if (pc_loopcond) { + if ((usage & uWRITTEN)!=0) { + /* the symbol is being modified inside a loop condition before being read; + * set the uNOLOOPVAR flag, so later we'll know we shouldn't mark the symbol + * with the uLOOPVAR flag */ + sym->usage |= uNOLOOPVAR; + pc_numloopvars++; + } else if ((usage & uREAD)!=0 && (sym->usage & (uNOLOOPVAR | uLOOPVAR))==0) { + sym->usage |= uLOOPVAR; + pc_numloopvars++; + } /* if */ + } /* if */ + /* unset the uLOOPVAR flag if the variable is being modified */ + if ((usage & uWRITTEN)!=0) + sym->usage &= ~uLOOPVAR; +} + /* findglb * diff --git a/source/compiler/sc3.c b/source/compiler/sc3.c index f54c545..7ed94e1 100644 --- a/source/compiler/sc3.c +++ b/source/compiler/sc3.c @@ -2177,6 +2177,7 @@ static int nesting=0; int nargs=0; /* number of arguments */ int heapalloc=0; int namedparams=FALSE; + int save_loopcond; value lval = {0}; arginfo *arg; char arglist[sMAXARGS]; @@ -2195,7 +2196,11 @@ static int nesting=0; /* functions cannot be called at global scope */ error(29); /* invalid expression, assumed zero */ return; - } + } /* if */ + /* make the compiler not count variables passed as function arguments + * as being used inside a loop condition */ + save_loopcond=pc_loopcond; + pc_loopcond=FALSE; /* check whether this is a function that returns an array */ symret=finddepend(sym); assert(symret==NULL || symret->ident==iREFARRAY); @@ -2609,6 +2614,7 @@ static int nesting=0; if (symret!=NULL) popreg(sPRI); /* pop hidden parameter as function result */ pc_sideeffect=TRUE; /* assume functions carry out a side-effect */ + pc_loopcond=save_loopcond; delete_consttable(&arrayszlst); /* clear list of array sizes */ delete_consttable(&taglst); /* clear list of parameter tags */ diff --git a/source/compiler/sc5.c b/source/compiler/sc5.c index d72803f..77c30a3 100644 --- a/source/compiler/sc5.c +++ b/source/compiler/sc5.c @@ -207,7 +207,9 @@ static char *warnmsg[] = { /*246*/ "multiplication overflow in enum element declaration (symbol \"%s\")\n", /*247*/ "use of operator \"~\" on a \"bool:\" value always results in \"true\"\n", /*248*/ "possible misuse of comma operator\n", -/*249*/ "" +/*249*/ "", +/*250*/ "variable \"%s\" used in loop condition not modified in loop body\n", +/*251*/ "none of the variables used in loop condition are modified in loop body\n" }; static char *noticemsg[] = { diff --git a/source/compiler/scvars.c b/source/compiler/scvars.c index 7afb577..57ea194 100644 --- a/source/compiler/scvars.c +++ b/source/compiler/scvars.c @@ -102,6 +102,8 @@ SC_VDEFINE int pc_retexpr=FALSE; /* true if the current expression is SC_VDEFINE int pc_retheap=0; /* heap space (in bytes) to be manually freed when returning an array returned by another function */ SC_VDEFINE int pc_nestlevel=0; /* number of active (open) compound statements */ SC_VDEFINE unsigned int pc_attributes=0; /* currently set attribute flags (for the "__pragma" operator) */ +SC_VDEFINE int pc_loopcond=FALSE; /* true if the current expression is a loop condition */ +SC_VDEFINE int pc_numloopvars=0; /* number of variables used inside a loop condition */ SC_VDEFINE constvalue_root sc_automaton_tab = { NULL, NULL}; /* automaton table */ SC_VDEFINE constvalue_root sc_state_tab = { NULL, NULL}; /* state table */