diff --git a/source/compiler/sc.h b/source/compiler/sc.h index 483ac31..cfa7d7a 100644 --- a/source/compiler/sc.h +++ b/source/compiler/sc.h @@ -229,6 +229,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 */ @@ -300,15 +310,17 @@ typedef struct s_valuepair { long second; } valuepair; -/* struct "assigninfo" is used to synchronize the status of assignments that - * were made in multiple "if" and "switch" branches, so the compiler could - * detect unused assignments in all of those branches, not only the last one */ +/* 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; + * * 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 unused; /* true if the variable has an unused value assigned to it - * in one of the branches" */ int lnumber; /* line number of the first unused assignment made in one of * the branches (used for error messages) */ -} assigninfo; + short usage; /* usage flags to memoize (currently only uASSIGNED) */ +} symstate; /* macros for code generation */ #define opcodes(n) ((n)*sizeof(cell)) /* opcode size */ @@ -733,8 +745,8 @@ SC_FUNC int refer_symbol(symbol *entry,symbol *bywhom); SC_FUNC void markusage(symbol *sym,int usage); SC_FUNC void markinitialized(symbol *sym,int assignment); SC_FUNC void clearassignments(int fromlevel); -SC_FUNC void memoizeassignments(int fromlevel,assigninfo **assignments); -SC_FUNC void restoreassignments(int fromlevel,assigninfo *assignments); +SC_FUNC void memoizeassignments(int fromlevel,symstate **assignments); +SC_FUNC void restoreassignments(int fromlevel,symstate *assignments); SC_FUNC void rename_symbol(symbol *sym,const char *newname); SC_FUNC symbol *findglb(const char *name,int filter); SC_FUNC symbol *findloc(const char *name); @@ -1009,6 +1021,8 @@ SC_VDECL int pc_retheap; /* heap space (in bytes) to be manually freed when 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_ispackedstr; /* true if the last tokenized string is packed */ +SC_VDECL int pc_loopcond; /* equals to 'tFOR', 'tWHILE' or 'tDO' if the current expression is a loop condition, zero otherwise */ +SC_VDECL int pc_numloopvars; /* number of variables used inside a loop condition */ SC_VDECL char *sc_tokens[]; diff --git a/source/compiler/sc1.c b/source/compiler/sc1.c index ae00669..2810382 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,int dowhile); +static void testloopvariables(symstate *loopvars,int dowhile,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=0; emit_flags=0; emit_stgbuf_idx=-1; } @@ -5173,6 +5176,112 @@ static int testsymbols(symbol *root,int level,int testlabs,int testconst) return entry; } +static void scanloopvariables(symstate **loopvars,int dowhile) +{ + 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 no enclosing loop (only one active loop entry, which is the + * current loop), and the current loop is not 'do-while', then we don't need + * to memoize usage flags for local variables, so we have an early exit */ + if (wqptr-wqSIZE==wq && !dowhile) + 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) + && (dowhile || (sym->usage & uLOOPVAR)!=0)) { + /* ... but it might be already set from an enclosing loop as well, so we + * have to temporarily store it in "loopvars[num]" first. Also, if this is + * a 'do-while' loop, we need to memoize and unset the 'uWRITTEN' flag, so + * later when analyzing the loop condition (which comes after the loop + * body) we'll be able to determine if the variable was modified inside + * the loop body by checking if the 'uWRITTEN' flag is set. */ + (*loopvars)[num].usage |= (sym->usage & (uNOLOOPVAR | uWRITTEN)); + sym->usage &= ~uWRITTEN; + if (wqptr-wqSIZE!=wq) + sym->usage |= uNOLOOPVAR; + } /* if */ + } /* if */ +} + +static void testloopvariables(symstate *loopvars,int dowhile,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->usage & (uLOOPVAR | uNOLOOPVAR))==uLOOPVAR + && (!dowhile || (sym->usage & uWRITTEN)==0)) + 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) { + 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; + } /* if */ + } /* for */ + free(loopvars); +} + static cell calc_array_datasize(symbol *sym, cell *offset) { cell length; @@ -5883,7 +5992,7 @@ static int doif(void) int ifindent; int lastst_true; int returnst=tIF; - assigninfo *assignments=NULL; + symstate *assignments=NULL; lastst=0; /* reset the last statement */ ifindent=stmtindent; /* save the indent of the "if" instruction */ @@ -5924,9 +6033,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 @@ -5934,18 +6047,23 @@ static int dowhile(void) * tiniest loop, set it below the top of the loop */ setline(TRUE); + scanloopvariables(&loopvars,FALSE); 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=tWHILE; endlessloop=test(wq[wqEXIT],TEST_DO,FALSE);/* branch to wq[wqEXIT] if false */ + pc_loopcond=0; pc_nestlevel--; statement(NULL,FALSE); /* if so, do a statement */ clearassignments(pc_nestlevel+1); + testloopvariables(loopvars,FALSE,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; } @@ -5957,12 +6075,17 @@ static int dowhile(void) static int dodo(void) { int wq[wqSIZE],top; - 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); /* see "dowhile" for more info */ top=getlabel(); /* make a label first */ setlabel(top); /* loop label */ + scanloopvariables(&loopvars,TRUE); statement(NULL,FALSE); needtoken(tWHILE); setlabel(wq[wqLOOP]); /* "continue" always jumps to WQLOOP. */ @@ -5970,15 +6093,19 @@ static int dodo(void) 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=tDO; endlessloop=test(wq[wqEXIT],TEST_OPT,FALSE); + pc_loopcond=0; pc_nestlevel--; clearassignments(pc_nestlevel+1); + testloopvariables(loopvars,TRUE,loopline); jumplabel(top); setlabel(wq[wqEXIT]); delwhile(); needtoken(tTERM); retcode=endlessloop ? tENDLESS : tDO; + pc_numloopvars=save_numloopvars; endlessloop=save_endlessloop; return retcode; } @@ -5987,13 +6114,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(); @@ -6026,6 +6157,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,FALSE); /* 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. @@ -6040,7 +6172,9 @@ static int dofor(void) if (matchtoken(';')) { endlessloop=1; } else { + pc_loopcond=tFOR; endlessloop=test(wq[wqEXIT],TEST_PLAIN,FALSE);/* expression 2 (jump to wq[wqEXIT] if false) */ + pc_loopcond=0; needtoken(';'); } /* if */ stgmark((char)(sEXPRSTART+1)); /* mark start of 3th expression in stage */ @@ -6053,6 +6187,7 @@ static int dofor(void) stgset(FALSE); /* stop staging */ statement(NULL,FALSE); clearassignments(save_nestlevel+1); + testloopvariables(loopvars,FALSE,loopline); jumplabel(wq[wqLOOP]); setlabel(wq[wqEXIT]); delwhile(); @@ -6071,6 +6206,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; } @@ -6103,7 +6239,7 @@ static int doswitch(void) constvalue_root caselist = { NULL, NULL}; /* case list starts empty */ constvalue *cse,*csp,*newval; char labelname[sNAMEMAX+1]; - assigninfo *assignments=NULL; + symstate *assignments=NULL; endtok= matchtoken('(') ? ')' : tDO; ident=doexpr(TRUE,FALSE,FALSE,FALSE,&swtag,NULL,TRUE,NULL); /* evaluate switch expression */ diff --git a/source/compiler/sc2.c b/source/compiler/sc2.c index 18d8f30..3f6adc8 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 */ @@ -3276,6 +3277,8 @@ 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->ident==iVARIABLE || sym->ident==iREFERENCE)) + markloopvariable(sym,usage); /* check if (global) reference must be added to the symbol */ if ((usage & (uREAD | uWRITTEN))!=0) { /* only do this for global symbols */ @@ -3306,7 +3309,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; @@ -3318,12 +3321,12 @@ SC_FUNC void clearassignments(int fromlevel) } /* memoizes all assignments done on the specified compound level and higher */ -SC_FUNC void memoizeassignments(int fromlevel,assigninfo **assignments) +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; @@ -3338,7 +3341,7 @@ SC_FUNC void memoizeassignments(int fromlevel,assigninfo **assignments) /* if there are no variables, then we have an early exit */ if (num==0) return; - *assignments=(assigninfo *)calloc((size_t)num,sizeof(assigninfo)); + *assignments=(symstate *)calloc((size_t)num,sizeof(symstate)); if (*assignments==NULL) error(103); /* insufficient memory */ } /* if */ @@ -3353,16 +3356,17 @@ SC_FUNC void memoizeassignments(int fromlevel,assigninfo **assignments) sym->usage &= ~uASSIGNED; /* memoize the assignment only if there was no other unused assignment * in any other "if" or "switch" branch */ - if ((*assignments)[num].unused==FALSE) { - (*assignments)[num].unused=TRUE; + assert_static(sizeof(sym->usage)==sizeof((*assignments)->usage)); + if (((*assignments)[num].usage & uASSIGNED)==0) { (*assignments)[num].lnumber=sym->lnumber; + (*assignments)[num].usage |= uASSIGNED; } /* if */ } /* if */ } /* for */ } /* restores all memoized assignments */ -SC_FUNC void restoreassignments(int fromlevel,assigninfo *assignments) +SC_FUNC void restoreassignments(int fromlevel,symstate *assignments) { symbol *sym; int num; @@ -3370,7 +3374,7 @@ SC_FUNC void restoreassignments(int fromlevel,assigninfo *assignments) sym=&loctab; while ((sym=sym->next)!=NULL && sym->ident==iLABEL) {} /* skip labels */ for (num=0; sym!=NULL; num++,sym=sym->next) { - if (assignments!=NULL && assignments[num].unused) { + if (assignments!=NULL && (assignments[num].usage & uASSIGNED)!=0) { sym->usage |= uASSIGNED; sym->lnumber=assignments[num].lnumber; } /* if */ @@ -3382,6 +3386,37 @@ SC_FUNC void restoreassignments(int fromlevel,assigninfo *assignments) free(assignments); } +static void markloopvariable(symbol *sym,int usage) +{ + if (sc_status!=statWRITE) + return; + while (sym->parent!=NULL) + sym=sym->parent; + /* check if the variable used inside a loop condition */ + if (pc_loopcond!=0) { + if (sym->vclass==sGLOBAL) { + /* stop counting variables that were used in loop condition, otherwise + * warnings 250 and 251 may be inaccurate (global variables can be + * modified from another function(s) called from the loop body, and + * currently there's no reasonable way to track this) */ + pc_loopcond=0; + pc_numloopvars=0; + } else 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 c710dde..d64da9a 100644 --- a/source/compiler/sc3.c +++ b/source/compiler/sc3.c @@ -1804,6 +1804,12 @@ restart: error(51); /* invalid subscript, must use [ ] */ invsubscript=TRUE; } /* if */ + if (pc_loopcond!=0) { + /* stop counting variables that were used in loop condition, + * otherwise warnings 250 and 251 may be inaccurate */ + pc_loopcond=0; + pc_numloopvars=0; + } /* if */ if (invsubscript) { if (sym!=NULL && sym->ident!=iFUNCTN) sym->usage |= uREAD; /* avoid the "symbol is never used" warning */ @@ -2229,7 +2235,13 @@ static int nesting=0; /* functions cannot be called at global scope */ error(29); /* invalid expression, assumed zero */ return; - } + } /* if */ + if (pc_loopcond!=0) { + /* stop counting variables that were used in loop condition, + * otherwise warnings 249 and 250 may be inaccurate */ + pc_loopcond=0; + pc_numloopvars=0; + } /* if */ /* check whether this is a function that returns an array */ symret=sym->child; assert(symret==NULL || symret->ident==iREFARRAY); @@ -2424,9 +2436,9 @@ static int nesting=0; check_tagmismatch_multiple(arg[argidx].tags,arg[argidx].numtags,lval.tag,-1); if (lval.tag!=0) append_constval(&taglst,arg[argidx].name,lval.tag,0); - argidx++; /* argument done */ - if (lval.sym!=NULL) + if (lval.sym!=NULL && (arg[argidx].usage & uCONST)==0) markusage(lval.sym,uWRITTEN); + argidx++; /* argument done */ break; case iREFARRAY: if (lval.ident!=iARRAY && lval.ident!=iREFARRAY diff --git a/source/compiler/sc5.c b/source/compiler/sc5.c index ba3e4e3..e9ad0ca 100644 --- a/source/compiler/sc5.c +++ b/source/compiler/sc5.c @@ -206,7 +206,10 @@ static char *warnmsg[] = { /*245*/ "enum increment \"%s %d\" has no effect on zero value (symbol \"%s\")\n", /*246*/ "multiplication overflow in enum element declaration (symbol \"%s\")\n", /*247*/ "use of operator \"%s\" on %s\n", -/*248*/ "possible misuse of comma operator\n" +/*248*/ "possible misuse of comma operator\n", +/*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 53158ad..30a87f3 100644 --- a/source/compiler/scvars.c +++ b/source/compiler/scvars.c @@ -103,6 +103,8 @@ SC_VDEFINE int pc_retheap=0; /* heap space (in bytes) to be manua 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_ispackedstr=FALSE; /* true if the last tokenized string is packed */ +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 char *sc_tokens[] = { "*=", "/=", "%=", "+=", "-=", "<<=", ">>>=", ">>=", "&=", "^=", "|=", diff --git a/source/compiler/tests/warning_250_251.meta b/source/compiler/tests/warning_250_251.meta new file mode 100644 index 0000000..42bbdb4 --- /dev/null +++ b/source/compiler/tests/warning_250_251.meta @@ -0,0 +1,16 @@ +{ + 'test_type': 'output_check', + 'errors': """ +warning_250_251.pwn(19) : warning 250: variable "n" used in loop condition not modified in loop body +warning_250_251.pwn(20) : warning 250: variable "n" used in loop condition not modified in loop body +warning_250_251.pwn(21) : warning 250: variable "i" used in loop condition not modified in loop body +warning_250_251.pwn(42) : warning 250: variable "n" used in loop condition not modified in loop body +warning_250_251.pwn(43) : warning 250: variable "n" used in loop condition not modified in loop body +warning_250_251.pwn(44) : warning 250: variable "i" used in loop condition not modified in loop body +warning_250_251.pwn(59) : warning 251: none of the variables used in loop condition are modified in loop body +warning_250_251.pwn(60) : warning 251: none of the variables used in loop condition are modified in loop body +warning_250_251.pwn(61) : warning 251: none of the variables used in loop condition are modified in loop body +warning_250_251.pwn(122) : warning 250: variable "n" used in loop condition not modified in loop body +warning_250_251.pwn(123) : warning 251: none of the variables used in loop condition are modified in loop body +""" +} diff --git a/source/compiler/tests/warning_250_251.pwn b/source/compiler/tests/warning_250_251.pwn new file mode 100644 index 0000000..7cd1b4f --- /dev/null +++ b/source/compiler/tests/warning_250_251.pwn @@ -0,0 +1,124 @@ +#include +#include + +new glbvar = 0; + +stock UseVarByRef(&arg) + return arg; + +#pragma warning disable 238 // "meaningless combination of class specifiers (const reference)" +stock UseVarByConstRef(const &arg) + return arg; + +main() +{ + new n = 0, m = 10; + static st = 0; + + // Case 1: Variable is used inside a loop condition without being modified. + while (n < 10) {} // warning 250: variable "n" used in loop condition not modified in loop body + do {} while (n < 10); // warning 250: variable "n" used in loop condition not modified in loop body + for (new i = 0, j = 0; i < 10; ++j) {} // warning 250: variable "i" used in loop condition not modified in loop body + + // Case 2: Variable is used inside a loop condition and modified in the loop body. + while (n != 0) { n++; } + do { n++; } while (n < 10); + for (new i = 0; i < 10; ) { i++; } + + // Case 3: Variable is used inside a loop condition and modified in the + // loop counter increment/decrement section. + for (new i = 0; i < 10; i++) {} + + // Case 4: Variable is used and modified inside a loop condition. + while (n++ != 0) {} + while (++n != 0) {} + do {} while (n++ != 0); + do {} while (++n != 0); + for (new i = 0; i++ < 10; ) {} + for (new i = 0; ++i < 10; ) {} + + // Case 5: Same variable is used inside a loop condition more than once + // and it's not modified. + while (n == 0 || n < 10) {} // warning 250: variable "n" used in loop condition not modified in loop body + do {} while (n == 0 || n < 10); // warning 250: variable "n" used in loop condition not modified in loop body + for (new i = 0; i == 0 || i < 10; ) {} // warning 250: variable "i" used in loop condition not modified in loop body + + // Case 6: Same variable is used inside a loop condition more than once, + // but it's modified. + while (n == 0 || n < 10) { n++; } + do { n++; } while (n == 0 || n < 10); + for (new i = 0; i == 0 || i < 10; i++) {} + + // Case 7: Two variables are used inside a loop condition, both aren't modified. + // Printing warning 250 for each unmodified variable wouldn't be productive, because: + // 1. the user would be spammed with multiple warnings, which can be annoying; + // 2. depending on the context, only one of the variables might be supposed + // to be modified, but the compiler can't know which one exactly. + // Solution: introduce a warning that simply says that none of the variables + // were modified, and let the user decide which variable should be modified. + while (n < m) {} // warning 251: none of the variables used in loop condition are modified in loop body + do {} while (n < m); // warning 251: none of the variables used in loop condition are modified in loop body + for (new i = 0; i < m; ) {} // warning 251: none of the variables used in loop condition are modified in loop body + + // Case 7: Two variables are used in a loop condition, but one of them + // is modified inside the loop body (or the loop counter increment/decrement + // section of a "for" loop), and the other one is not modified. + while (n < m) { ++n; } + do { --m; } while (n < m); + for (new i = 0; i < m; ) { i++; } + for (new i = 0; i < m; i++) {} + + // Case 8: Two variables are used in a loop condition, but one of them + // is being modified prior to being used, and the other one is not modified. + while (++n < m) {} + do {} while (++n < m); + for (new i = 0; ++i < m; ) {} + + // Case 9: Two variables are used in a loop condition, but one of them + // is static and it's modified inside the loop body, and the other one + // is a stack variable and it's left unmodified. + while (st < m) { st++; } + + // Case 10: Warnings 250 and 251 may be inaccurate when an array is indexed + // inside a loop condition. The problem is that we can't memoize the array + // that is being indexed by a variable, to unset the "uLOOPVAR" flag for the + // array symbol later when the variable is modified. + // This is why I had to completely disable those diagnostics for arrays. + { + new a[3] = { 0, 0, 1 }; + n = random(sizeof(a)); + while (a[n] == 0) // Shouldn't warn about "n" not being modified + a[n] = random(3); + for (new i = 0; a[i++] == 0; ) {} // shouldn't warn about "a" not being modified + for (a[0] = 0, m = 10; a[0] < m; ++a[0]) {} // shouldn't warn about "m" not being modified + } + + // Case 11: Just as with arrays, warnings 250 and 251 are disabled when + // there's a function call inside a loop condition, as those diagnostics + // may be inaccurate otherwise. + new File:f = fopen("test.txt", io_read); + new line[128]; + while (fread(f,line,sizeof(line),false) < m) {} // shouldn't warn about "f" or "m" not being modified + do {} while (fread(f,line,sizeof(line),false) < m); // shouldn't warn about "f" or "m" not being modified + fclose(f); + + // Case 12: Warnings 250 and 251 shouldn't trigger when at least one global + // variable is used inside the loop condition, as globals can be modified + // from a function called from the loop body and currently there's no easy + // way to track this. + while (n < glbvar) {} + do {} while (n < glbvar); + for (new i = 0; i < glbvar; ) {} + + // Case 13: Warnings 250 and 251 shouldn't trigger when the loop counter + // variable is passed to a function by reference. + while (n < 10) UseVarByRef(n); + while (n < m) UseVarByRef(n); + + // Case 14: While const references for single function arguments are + // meaningless and there's warning 238 for this, such references still + // shouldn't affect warnings 250 and 251, as variables passed by const + // references aren't counted as modified. + while (n < 10) UseVarByConstRef(n); // warning 250: variable "n" used in loop condition not modified in loop body + while (n < m) UseVarByConstRef(n); // warning 251: none of the variables used in loop condition are modified in loop body +}