diff --git a/CC.pas b/CC.pas index 323491f..fb74f35 100644 --- a/CC.pas +++ b/CC.pas @@ -140,6 +140,8 @@ DoGlobals; {create the ~GLOBALS and ~ARRAYS segment {shut down the compiler} TermHeader; {make sure the compiled header file is closed} CheckStaticFunctions; {check for undefined functions} +if (lint & lintUnused) <> 0 then {check for unused static vars} + CheckUnused(globalTable); ffDCBGS.action := 7; {purge the source file} ffDCBGS.pcount := 14; ffDCBGS.pathName := @includeFileGS.theString; diff --git a/CCommon.pas b/CCommon.pas index 8834759..a7f4337 100644 --- a/CCommon.pas +++ b/CCommon.pas @@ -93,6 +93,7 @@ const lintOverflow = $0020; {check for overflows} lintC99Syntax = $0040; {check for syntax that C99 disallows} lintReturn = $0080; {flag issues with how functions return} + lintUnused = $0100; {check for unused variables} {bit masks for GetLInfo flags} {----------------------------} @@ -375,6 +376,7 @@ type iPtr: initializerPtr; {pointer to the first initializer} isForwardDeclared: boolean; {does this var use a forward declared type?} class: tokenEnum; {storage class} + used: boolean; {is this identifier used?} case storage: storageType of stackFrame: (lln: integer; {local label #} clnext: identPtr); {next compound literal} diff --git a/Expression.pas b/Expression.pas index 5ea6342..d29b06e 100644 --- a/Expression.pas +++ b/Expression.pas @@ -986,6 +986,7 @@ var stack^.token.class := longlongConstant; stack^.token.kind := longlongconst; stack^.token.qval := longlong0; + id := nil; end {if} {if the id is not declared, create a function returning integer} @@ -1022,6 +1023,9 @@ var stack^.token.kind := intconst; stack^.token.ival := id^.itype^.eval; end; {else if} + + if id <> nil then + id^.used := true; stack^.id := id; {save the identifier} ComplexTerm; {handle subscripts, selection, etc.} 1: diff --git a/Header.pas b/Header.pas index 54e8b4f..3e7e978 100644 --- a/Header.pas +++ b/Header.pas @@ -1810,6 +1810,7 @@ var sp^.isForwardDeclared := boolean(ReadByte); sp^.class := tokenEnum(ReadByte); sp^.storage := storageType(ReadByte); + sp^.used := false; if sp^.storage = none then sp^.anonMemberField := false else if sp^.storage = external then diff --git a/Parser.pas b/Parser.pas index 1ce9462..8f2b469 100644 --- a/Parser.pas +++ b/Parser.pas @@ -4115,6 +4115,7 @@ if isFunction then begin lp := NewSymbol(@'__orcac_va_info', vaInfoPtr, autosy, variableSpace, declared, false); lp^.lln := GetLocalLabel; + lp^.used := true; Gen2(dc_loc, lp^.lln, ord(vaInfoPtr^.size)); Gen2(pc_lda, lastParameterLLN, lastParameterSize); Gen2t(pc_cop, lp^.lln, 0, cgULong); diff --git a/Scanner.pas b/Scanner.pas index bc4ae4b..4fab662 100644 --- a/Scanner.pas +++ b/Scanner.pas @@ -114,6 +114,18 @@ procedure Error (err: integer); { err - error number } +procedure ErrorWithExtraString (err:integer; extraStr: stringPtr); + +{ flag an error, with an extra string to be attached to it } +{ } +{ err - error number } +{ extraStr - extra string to include in error message } +{ } +{ Note: } +{ extraStr must point to a pString allocated with new. } +{ This call transfers ownership of it. } + + {procedure Error2 (loc, err: integer); {debug} { flag an error } @@ -190,13 +202,14 @@ const {----} defaultName = '13:ORCACDefs:Defaults.h'; {default include file name} maxErr = 10; {max errors on one line} - maxLint = 170; {maximum lint error code} + maxLint = 185; {maximum lint error code} type errorType = record {record of a single error} num: integer; {error number} line: longint; {line number} col: integer; {column number} + extraStr: stringPtr; {extra text to include in message} end; {file inclusion} @@ -787,8 +800,13 @@ if list or (numErr <> 0) then begin 182: msg := @'''='' expected'; 183: msg := @'array index out of bounds'; 184: msg := @'segment exceeds bank size'; + 185: msg := @'lint: unused variable: '; otherwise: Error(57); end; {case} + if extraStr <> nil then begin + extraStr^ := concat(msg^,extraStr^); + msg := extraStr; + end; {if} writeln(msg^); if terminalErrors and (numErrors <> 0) and (lintIsError or not (num in lintErrors)) then begin @@ -803,6 +821,8 @@ if list or (numErr <> 0) then begin else TermError(0); end; {if} + if extraStr <> nil then + dispose(extraStr); end; {with} {handle pauses} if ((numErr <> 0) and wait) or KeyPress then begin @@ -3767,12 +3787,31 @@ else begin with errors[numErr] do begin {record the position of the error} line := tokenLine; col := tokenColumn; + extraStr := nil; end; {with} if numErrors <> 0 then codeGeneration := false; {inhibit code generation} end; {Error} +procedure ErrorWithExtraString {err:integer; extraStr: stringPtr}; + +{ flag an error, with an extra string to be attached to it } +{ } +{ err - error number } +{ extraStr - extra string to include in error message } +{ } +{ Note: } +{ extraStr must point to a pString allocated with new. } +{ This call transfers ownership of it. } + +begin {ErrorWithExtraString} +Error(err); +if errors[numErr].num <> 4 then + errors[numErr].extraStr := extraStr; +end; {ErrorWithExtraString} + + {procedure Error2 {loc, err: integer} {debug} { flag an error } @@ -4490,7 +4529,8 @@ strictMode := false; {...with extensions} {error codes for lint messages} {if changed, also change maxLint} -lintErrors := [51,104,105,110,124,125,128,129,130,147,151,152,153,154,155,170]; +lintErrors := + [51,104,105,110,124,125,128,129,130,147,151,152,153,154,155,170,185]; spaceStr := ' '; {strings used in stringization} quoteStr := '"'; diff --git a/Symbol.pas b/Symbol.pas index d30a9af..f87fbea 100644 --- a/Symbol.pas +++ b/Symbol.pas @@ -100,6 +100,11 @@ procedure CheckStaticFunctions; { check for undefined functions } +procedure CheckUnused (tPtr: symbolTablePtr); + +{ check for unused variables in symbol table } + + function CompTypes (t1, t2: typePtr): boolean; { Determine if the two types are compatible } @@ -2339,6 +2344,7 @@ if needSymbol then begin {p^.isForwardDeclared := false;} {assume no forward declarations are used} p^.name := name; {record the name} {p^.next := nil;} + {p^.used := false;} {unused for now} if space <> fieldListSpace then {insert the symbol in the hash bucket} begin if itype = nil then @@ -2389,6 +2395,47 @@ useGlobalPool := lUseGlobalPool; {restore the useGlobalPool variable} end; {NewSymbol} +procedure CheckUnused {tPtr: symbolTablePtr}; + +{ check for unused variables in symbol table } + +var + i: integer; {loop variable} + ip: identPtr; {current symbol} + nameStr: stringPtr; + +begin {CheckUnused} +for i := 0 to hashSize do begin {loop over all hash buckets} + ip := tPtr^.buckets[i]; {trace through non-static symbols} + while ip <> nil do begin + if not ip^.used then + if ip^.itype <> nil then + if not (ip^.itype^.kind in [functionType,enumConst]) then + if ip^.storage in [stackFrame,private] then + if not (ip^.name^[1] in ['~','@']) then begin + new(nameStr); + nameStr^ := ip^.name^; + ErrorWithExtraString(185, nameStr); + end; {if} + ip := ip^.next; + end; {while} + ip := globalTable^.buckets[i]; {trace through static symbols} + while ip <> nil do begin + if not ip^.used then + if ip^.itype <> nil then + if not (ip^.itype^.kind in [functionType,enumConst]) then + if ip^.storage = private then + if copy(ip^.name^,1,5) = tPtr^.staticNum then begin + new(nameStr); + nameStr^ := copy(ip^.name^, 6, maxint); + ErrorWithExtraString(185, nameStr); + end; {if} + ip := ip^.next; + end; {while} + end; {for} +end; {CheckUnused} + + procedure PopTable; { Pop a symbol table (remove definitions local to a block) } @@ -2400,6 +2447,8 @@ begin {PopTable} tPtr := table; {if printSymbols then {debug} { PrintTable(tPtr); {debug} +if (lint & lintUnused) <> 0 then + CheckUnused(tPtr); if tPtr^.next <> nil then begin table := table^.next; dispose(tPtr); diff --git a/cc.notes b/cc.notes index e930e80..e67093f 100644 --- a/cc.notes +++ b/cc.notes @@ -757,6 +757,10 @@ ORCA/C can now detect several elements of C syntax that were allowed in C89 but If #pragma lint bit 7 (a value of 128) is set, ORCA/C detects some situations where a function with a non-void return type may return an unpredictable value, either by executing a return statement with no value or by executing to the end of the function with no return statement. (The former case is also detected by #pragma lint bit 6.) It also detects some situations where a _Noreturn function could return. Note that these checks only detect some cases of these problems, not all of them. Also, they may report a potential problem in some situations where the code at the end of a function is not actually reachable. +* Checking for unused variables: + +If #pragma lint bit 8 (a value of 256) is set, ORCA/C checks for variables that are declared but never subsequently used. This covers local variables in functions, as well as file-scope static variables. + #pragma extensions ------------------