diff --git a/libsrc/common/getopt.c b/libsrc/common/getopt.c index 34b13b1e6..3c54c9d2f 100644 --- a/libsrc/common/getopt.c +++ b/libsrc/common/getopt.c @@ -29,7 +29,7 @@ int optopt; /* character checked for validity */ char *optarg; /* argument associated with option */ #define tell(s) fputs(*argv,stderr);fputs(s,stderr); \ - fputc(optopt,stderr);fputc('\n',stderr);return(BADCH); + fputc(optopt,stderr);fputc('\n',stderr);return(BADCH) int __fastcall__ getopt (int argc, char* const* argv, const char* optstring) /* Get option letter from argument vector */ diff --git a/src/cc65/error.c b/src/cc65/error.c index ae2d6f27d..97e651890 100644 --- a/src/cc65/error.c +++ b/src/cc65/error.c @@ -95,6 +95,7 @@ struct WarnMapEntry { static WarnMapEntry WarnMap[] = { /* Keep names sorted, even if it isn't used for now */ { &WarnConstComparison, "const-comparison" }, + { &WarnConstOverflow, "const-overflow" }, { &WarningsAreErrors, "error" }, { &WarnNoEffect, "no-effect" }, { &WarnPointerSign, "pointer-sign" }, @@ -108,7 +109,6 @@ static WarnMapEntry WarnMap[] = { { &WarnUnusedLabel, "unused-label" }, { &WarnUnusedParam, "unused-param" }, { &WarnUnusedVar, "unused-var" }, - { &WarnConstOverflow, "const-overflow" }, }; Collection DiagnosticStrBufs; @@ -142,7 +142,7 @@ void PrintFileInclusionInfo (const LineInfo* LI) -static LineInfo* GetDiagnosticLI (void) +LineInfo* GetDiagnosticLI (void) /* Get the line info where the diagnostic info refers to */ { if (CurTok.LI) { @@ -184,7 +184,7 @@ static unsigned GetDiagnosticLineNum (void) -void Fatal_ (const char *file, int line, const char* Format, ...) +void Fatal_ (const char* file, int line, const char* Format, ...) /* Print a message about a fatal error and die */ { va_list ap; @@ -208,7 +208,7 @@ void Fatal_ (const char *file, int line, const char* Format, ...) -void Internal_ (const char *file, int line, const char* Format, ...) +void Internal_ (const char* file, int line, const char* Format, ...) /* Print a message about an internal compiler error and die */ { va_list ap; @@ -279,7 +279,7 @@ static void IntError (errcat_t EC, LineInfo* LI, const char* Msg, va_list ap) -void LIError_ (const char *file, int line, errcat_t EC, LineInfo* LI, const char* Format, ...) +void LIError_ (const char* file, int line, errcat_t EC, LineInfo* LI, const char* Format, ...) /* Print an error message with the line info given explicitly */ { va_list ap; @@ -295,7 +295,7 @@ void LIError_ (const char *file, int line, errcat_t EC, LineInfo* LI, const char -void Error_ (const char *file, int line, const char* Format, ...) +void Error_ (const char* file, int line, const char* Format, ...) /* Print an error message */ { va_list ap; @@ -311,7 +311,7 @@ void Error_ (const char *file, int line, const char* Format, ...) -void PPError_ (const char *file, int line, const char* Format, ...) +void PPError_ (const char* file, int line, const char* Format, ...) /* Print an error message. For use within the preprocessor */ { va_list ap; @@ -370,7 +370,7 @@ static void IntWarning (errcat_t EC, LineInfo* LI, const char* Msg, va_list ap) -void LIWarning_ (const char *file, int line, errcat_t EC, LineInfo* LI, const char* Format, ...) +void LIWarning_ (const char* file, int line, errcat_t EC, LineInfo* LI, const char* Format, ...) /* Print a warning message with the line info given explicitly */ { va_list ap; @@ -386,7 +386,7 @@ void LIWarning_ (const char *file, int line, errcat_t EC, LineInfo* LI, const ch -void Warning_ (const char *file, int line, const char* Format, ...) +void Warning_ (const char* file, int line, const char* Format, ...) /* Print a warning message */ { va_list ap; @@ -402,7 +402,7 @@ void Warning_ (const char *file, int line, const char* Format, ...) -void PPWarning_ (const char *file, int line, const char* Format, ...) +void PPWarning_ (const char* file, int line, const char* Format, ...) /* Print a warning message. For use within the preprocessor */ { va_list ap; @@ -424,7 +424,45 @@ void UnreachableCodeWarning (void) */ { if (IS_Get (&WarnUnreachableCode)) { - Warning ("Unreachable code"); + + LineInfo* LI; + + /* Add special handling for compound statements if the current token + ** is from the source. Doing this here is a bit hacky but unfortunately + ** there's no better place. + */ + if (CurTok.LI && NextTok.LI) { + if (CurTok.Tok == TOK_LCURLY) { + /* Do not point to the compoung statement but to the first + ** statement within it. If the compound statement is empty + ** do not even output a warning. This fails of course for + ** nested compounds but will do the right thing in most cases. + */ + if (NextTok.Tok == TOK_RCURLY) { + return; + } + LI = NextTok.LI; + } else { + LI = CurTok.LI; + } + } else { + LI = GetCurLineInfo (); + } + + /* Now output the warning */ + LIWarning (EC_PARSER, LI, "Unreachable code"); + } +} + + + +void LIUnreachableCodeWarning (LineInfo* LI) +/* Print a warning about unreachable code at the given location if these +** warnings are enabled. +*/ +{ + if (IS_Get (&WarnUnreachableCode)) { + LIWarning (EC_PARSER, LI, "Unreachable code"); } } diff --git a/src/cc65/error.h b/src/cc65/error.h index 0a18d51a4..30c0d404e 100644 --- a/src/cc65/error.h +++ b/src/cc65/error.h @@ -103,6 +103,9 @@ struct StrBuf; void PrintFileInclusionInfo (const LineInfo* LI); /* Print hierarchy of file inclusion */ +LineInfo* GetDiagnosticLI (void); +/* Get the line info where the diagnostic info refers to */ + void Fatal_ (const char *file, int line, const char* Format, ...) attribute ((noreturn, format (printf, 3, 4))); #define Fatal(...) Fatal_(__FILE__, __LINE__, __VA_ARGS__) /* Print a message about a fatal error and die */ @@ -140,6 +143,11 @@ void UnreachableCodeWarning (void); ** warnings are enabled. */ +void LIUnreachableCodeWarning (LineInfo* LI); +/* Print a warning about unreachable code at the given location if these +** warnings are enabled. +*/ + IntStack* FindWarning (const char* Name); /* Search for a warning in the WarnMap table and return a pointer to the ** intstack that holds its state. Return NULL if there is no such warning. diff --git a/src/cc65/function.c b/src/cc65/function.c index 3123079d3..0e040d61d 100644 --- a/src/cc65/function.c +++ b/src/cc65/function.c @@ -626,10 +626,8 @@ void NewFunc (SymEntry* Func, FuncDesc* D) */ CurrentFunc->TopLevelSP = StackPtr; - /* Now process statements in this block */ - while (CurTok.Tok != TOK_RCURLY && CurTok.Tok != TOK_CEOF) { - AnyStatement (0); - } + /* Now process statements in this block checking for unreachable code */ + StatementBlock (0); /* Check if this function is missing a return value */ if (!F_HasVoidReturn (CurrentFunc) && !F_HasReturn (CurrentFunc)) { diff --git a/src/cc65/goto.c b/src/cc65/goto.c index e96ad6c4c..51fdc725d 100644 --- a/src/cc65/goto.c +++ b/src/cc65/goto.c @@ -41,10 +41,12 @@ #include "error.h" #include "exprdesc.h" #include "expr.h" +#include "function.h" #include "loadexpr.h" #include "scanner.h" #include "seqpoint.h" #include "standard.h" +#include "stmt.h" #include "symtab.h" #include "goto.h" @@ -56,8 +58,8 @@ -void GotoStatement (void) -/* Process a goto statement. */ +int GotoStatement (void) +/* Process a goto statement and return one of the SF_xxx flags from stmt.h. */ { /* Eat the "goto" */ NextToken (); @@ -165,6 +167,8 @@ void GotoStatement (void) } else { Error ("Label name expected"); } + + return SF_GOTO | SF_ANY_GOTO; } diff --git a/src/cc65/goto.h b/src/cc65/goto.h index 3ca8223e2..59a3058e9 100644 --- a/src/cc65/goto.h +++ b/src/cc65/goto.h @@ -44,8 +44,8 @@ -void GotoStatement (void); -/* Process a goto statement. */ +int GotoStatement (void); +/* Process a goto statement and return one of the SF_xxx flags from stmt.h. */ void DoLabel (void); /* Define a goto label. */ diff --git a/src/cc65/lineinfo.h b/src/cc65/lineinfo.h index 02e77cd9c..376dc761c 100644 --- a/src/cc65/lineinfo.h +++ b/src/cc65/lineinfo.h @@ -104,7 +104,7 @@ LineInfo* GetCurLineInfo (void); void UpdateCurrentLineInfo (const StrBuf* Line); /* Update the current line info - called if a new line is read */ -void RememberCheckedLI (struct LineInfo* LI); +void RememberCheckedLI (LineInfo* LI); /* Remember the latest checked line info struct */ LineInfo* GetPrevCheckedLI (void); diff --git a/src/cc65/stmt.c b/src/cc65/stmt.c index e28b844b5..51a8a6966 100644 --- a/src/cc65/stmt.c +++ b/src/cc65/stmt.c @@ -154,7 +154,7 @@ static int IfStatement (void) { unsigned Label1; unsigned TestResult; - int GotBreak; + int StmtFlags; /* Skip the if */ NextToken (); @@ -164,20 +164,27 @@ static int IfStatement (void) TestResult = TestInParens (Label1, 0); /* Parse the if body */ - GotBreak = AnyStatement (0); + StmtFlags = AnyStatement (0, 0); /* Else clause present? */ if (CurTok.Tok != TOK_ELSE) { g_defcodelabel (Label1); - /* Since there's no else clause, we're not sure, if the a break - ** statement is really executed. + /* If the test result is always true, any special statement (return, + ** break etc.) is always executed. If not, we cannot be sure. */ - return 0; + if (TestResult != TESTEXPR_TRUE) { + /* There's no else so we're not sure, if any special statements + ** are really executed. + */ + StmtFlags &= ~SF_MASK_UNREACH; + } } else { + int StmtFlags2; + /* Generate a jump around the else branch */ unsigned Label2 = GetLocalLabel (); g_jump (Label2); @@ -195,22 +202,40 @@ static int IfStatement (void) /* Define the target for the first test */ g_defcodelabel (Label1); - /* Total break only if both branches had a break. */ - GotBreak &= AnyStatement (0); + /* Parse the else body and evaluate special statements */ + StmtFlags2 = AnyStatement (0, 0); + if (TestResult == TESTEXPR_FALSE) { + /* The "else" part is always executed */ + StmtFlags = StmtFlags2; + } else if (TestResult != TESTEXPR_TRUE) { + /* If both branches have a special statement the following code is + ** unreachable and we combine the flags. Otherwise the code + ** following the "if" is always reachable. + */ + StmtFlags |= SF_Any (StmtFlags2); + if (SF_Unreach (StmtFlags) && SF_Unreach (StmtFlags2)) { + StmtFlags |= SF_Unreach (StmtFlags2); + } else { + StmtFlags &= ~SF_MASK_UNREACH; + } + } /* Generate the label for the else clause */ g_defcodelabel (Label2); - - /* Done */ - return GotBreak; } + + /* Done */ + return StmtFlags; } -static void DoStatement (void) +static int DoStatement (void) /* Handle the 'do' statement */ { + int StmtFlags; + unsigned TestResult; + /* Get the loop control labels */ unsigned LoopLabel = GetLocalLabel (); unsigned BreakLabel = GetLocalLabel (); @@ -226,14 +251,14 @@ static void DoStatement (void) g_defcodelabel (LoopLabel); /* Parse the loop body */ - AnyStatement (0); + StmtFlags = AnyStatement (0, 0); /* Output the label for a continue */ g_defcodelabel (ContinueLabel); /* Parse the end condition */ Consume (TOK_WHILE, "'while' expected"); - TestInParens (LoopLabel, 1); + TestResult = TestInParens (LoopLabel, 1); ConsumeSemi (); /* Define the break label */ @@ -241,17 +266,52 @@ static void DoStatement (void) /* Remove the loop from the loop stack */ DelLoop (); + + /* Fix the flags for the loop. */ + if (TestResult == TESTEXPR_TRUE) { + /* If the loop condition is always true, and we do not have a + ** "break" statement, the loop won't terminate. So the only valid + ** "unreach" flag is that for the endless loop. Otherwise - if there + ** is a "break" statement, the code after the loop is reachable. + */ + StmtFlags &= ~SF_MASK_UNREACH; + if (!SF_Any_Break (StmtFlags)) { + StmtFlags |= (SF_OTHER | SF_ANY_OTHER); + } + } else if (SF_Any_Break (StmtFlags)) { + /* If the loop condition is not always true, but we have a "break" + ** anywhere, the following code is reachable. + */ + StmtFlags &= ~SF_MASK_UNREACH; + } else { + /* Otherwise the last statement in the loop determines the status for + ** the following code, so we have to change "continue" into "other" + ** but apart from that flags stay as they are. + */ + if (SF_Continue (StmtFlags)) { + StmtFlags &= ~SF_CONTINUE; + StmtFlags |= (SF_OTHER | SF_ANY_OTHER); + } + } + + /* "break" and "continue" are not relevant for the following code */ + StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE); + + /* Done */ + return StmtFlags; } -static void WhileStatement (void) +static int WhileStatement (void) /* Handle the 'while' statement */ { int PendingToken; CodeMark CondCodeStart; /* Start of condition evaluation code */ CodeMark CondCodeEnd; /* End of condition evaluation code */ CodeMark Here; /* "Here" location of code */ + unsigned TestResult; /* Result of the while loop test expression */ + int StmtFlags; /* Get the loop control labels */ unsigned LoopLabel = GetLocalLabel (); @@ -274,8 +334,15 @@ static void WhileStatement (void) /* Remember the current position */ GetCodePos (&CondCodeStart); - /* Test the loop condition */ - TestInParens (LoopLabel, 1); + /* Test the loop condition. While loops are somewhat different from other + ** loops: While an "always true" condition is used often for endless loops + ** (or loops left by "break"), an "always false" condition doesn't make + ** sense, so we check that here and warn about it. + */ + TestResult = TestInParens (LoopLabel, 1); + if (TestResult == TESTEXPR_FALSE) { + UnreachableCodeWarning (); + } /* Remember the end of the condition evaluation code */ GetCodePos (&CondCodeEnd); @@ -284,7 +351,7 @@ static void WhileStatement (void) g_defcodelabel (LoopLabel); /* Loop body */ - AnyStatement (&PendingToken); + StmtFlags = AnyStatement (&PendingToken, 0); /* Emit the while condition label */ g_defcodelabel (CondLabel); @@ -303,11 +370,34 @@ static void WhileStatement (void) /* Remove the loop from the loop stack */ DelLoop (); + + /* Fix the flags for the loop. */ + if (TestResult == TESTEXPR_TRUE) { + /* If the loop condition is always true, and we do not have a + ** "break" statement, the loop won't terminate. So the only valid + ** "unreach" flag is that for the endless loop. Otherwise - if there + ** is a "break" statement, the code after the loop is reachable. + */ + StmtFlags &= ~SF_MASK_UNREACH; + if (!SF_Any_Break (StmtFlags)) { + StmtFlags |= (SF_OTHER | SF_ANY_OTHER); + } + } else { + /* If the loop condition is not always true, the code after the loop + ** is always reachable. + */ + StmtFlags &= ~SF_MASK_UNREACH; + } + /* "break" and "continue" are not relevant for the following code */ + StmtFlags &= ~(SF_ANY_BREAK | SF_ANY_CONTINUE); + + /* Done */ + return StmtFlags; } -static void ReturnStatement (void) +static int ReturnStatement (void) /* Handle the 'return' statement */ { ExprDesc Expr; @@ -357,11 +447,14 @@ static void ReturnStatement (void) /* Output a jump to the function exit code */ g_jump (F_GetRetLab (CurrentFunc)); + + /* Done */ + return SF_RETURN | SF_ANY_RETURN; } -static void BreakStatement (void) +static int BreakStatement (void) /* Handle the 'break' statement */ { LoopDesc* L; @@ -376,7 +469,7 @@ static void BreakStatement (void) if (L == 0) { /* Error: No current loop */ Error ("'break' statement not within loop or switch"); - return; + return SF_NONE; } /* Correct the stack pointer if needed */ @@ -384,11 +477,14 @@ static void BreakStatement (void) /* Jump to the exit label of the loop */ g_jump (L->BreakLabel); + + /* Done */ + return SF_BREAK | SF_ANY_BREAK; } -static void ContinueStatement (void) +static int ContinueStatement (void) /* Handle the 'continue' statement */ { LoopDesc* L; @@ -411,7 +507,7 @@ static void ContinueStatement (void) /* Did we find it? */ if (L == 0) { Error ("'continue' statement not within a loop"); - return; + return SF_NONE; } /* Correct the stackpointer if needed */ @@ -419,17 +515,22 @@ static void ContinueStatement (void) /* Jump to next loop iteration */ g_jump (L->ContinueLabel); + + /* Done */ + return SF_CONTINUE | SF_ANY_CONTINUE; } -static void ForStatement (void) +static int ForStatement (void) /* Handle a 'for' statement */ { int HaveIncExpr; CodeMark IncExprStart; CodeMark IncExprEnd; int PendingToken; + unsigned TestResult; + int StmtFlags; /* Get several local labels needed later */ unsigned TestLabel = GetLocalLabel (); @@ -463,9 +564,10 @@ static void ForStatement (void) /* Parse the test expression */ if (CurTok.Tok != TOK_SEMI) { - Test (BodyLabel, 1); + TestResult = Test (BodyLabel, 1); g_jump (BreakLabel); } else { + TestResult = TESTEXPR_TRUE; g_jump (BodyLabel); } ConsumeSemi (); @@ -497,7 +599,7 @@ static void ForStatement (void) /* Loop body */ g_defcodelabel (BodyLabel); - AnyStatement (&PendingToken); + StmtFlags = AnyStatement (&PendingToken, 0); /* If we had an increment expression, move the code to the bottom of ** the loop. In this case we don't need to jump there at the end of @@ -520,16 +622,21 @@ static void ForStatement (void) /* Remove the loop from the loop stack */ DelLoop (); + + /* If the condition is always true, any special statements are always + ** executed. Otherwise we don't know. + */ + return (TestResult == TESTEXPR_TRUE)? StmtFlags : SF_NONE; } -static int CompoundStatement (int* PendingToken) +static int CompoundStatement (int* PendingToken, struct SwitchCtrl* Switch) /* Compound statement. Allow any number of statements inside braces. The ** function returns true if the last statement was a break or return. */ { - int GotBreak = 0; + int StmtFlags; /* Remember the stack at block entry */ int OldStack = StackPtr; @@ -544,17 +651,11 @@ static int CompoundStatement (int* PendingToken) /* Parse local variable declarations if any */ DeclareLocals (); - /* Now process statements in this block */ - while (CurTok.Tok != TOK_RCURLY) { - if (CurTok.Tok != TOK_CEOF) { - GotBreak = AnyStatement (0); - } else { - break; - } - } + /* Now process statements in this block checking for unreachable code */ + StmtFlags = StatementBlock (Switch); /* Clean up the stack if the codeflow may reach the end */ - if (!GotBreak) { + if ((StmtFlags & SF_MASK_UNREACH) == SF_NONE) { g_space (StackPtr - OldStack); } @@ -564,7 +665,6 @@ static int CompoundStatement (int* PendingToken) if (OldBlockStackSize != CollCount (&CurrentFunc->LocalsBlockStack)) { CollPop (&CurrentFunc->LocalsBlockStack); } - StackPtr = OldStack; /* Emit references to imports/exports for this block */ @@ -576,12 +676,13 @@ static int CompoundStatement (int* PendingToken) /* Skip '}' */ CheckTok (TOK_RCURLY, "'}' expected", PendingToken); - return GotBreak; + /* Done */ + return StmtFlags; } -static void Statement (int* PendingToken) +static int Statement (int* PendingToken) /* Single-line statement */ { ExprDesc Expr; @@ -615,54 +716,73 @@ static void Statement (int* PendingToken) } CheckSemi (PendingToken); + + /* Done. All special statements are handled in other subroutines. */ + return SF_NONE; } -static int ParseAnyLabels (void) -/* Return -1 if there are any labels with a statement */ -{ - unsigned PrevErrorCount = ErrorCount; - int HasLabels = 0; - for (;;) { - if (CurTok.Tok == TOK_IDENT && NextTok.Tok == TOK_COLON) { - /* C 'goto' label */ - DoLabel (); - } else if (CurTok.Tok == TOK_CASE) { - /* C 'case' label */ - CaseLabel (); - } else if (CurTok.Tok == TOK_DEFAULT) { - /* C 'default' label */ - DefaultLabel (); - } else { - /* No labels */ - break; - } - HasLabels = 1; - } - - if (HasLabels) { - if (PrevErrorCount != ErrorCount || CheckLabelWithoutStatement ()) { - return -1; - } - } - - return 0; -} - - - -int AnyStatement (int* PendingToken) -/* Statement parser. Returns 1 if the statement does a return/break, returns -** 0 otherwise. If the PendingToken pointer is not NULL, the function will -** not skip the terminating token of the statement (closing brace or -** semicolon), but store true if there is a pending token, and false if there -** is none. The token is always checked, so there is no need for the caller to -** check this token, it must be skipped, however. If the argument pointer is -** NULL, the function will skip the token. +int StatementBlock (struct SwitchCtrl* Switch) +/* Parse multiple statements within curly braces checking for unreachable +** code. Returns the SF_xxx flags for the last statement. */ { - int GotBreak = 0; + /* We want to emit an "unreachable code" warning for statements following + ** a "goto", "return" etc. But only - and this is what complicates it - + ** if the following statement is not preceeded by a label. Since the latter + ** means that a jump may go there so the statement is actually reachable. + */ + if (CurTok.Tok != TOK_RCURLY && CurTok.Tok != TOK_CEOF) { + LineInfo* LI1 = UseLineInfo (GetDiagnosticLI ()); + int StmtFlags1 = AnyStatement (0, Switch); + int UnreachableWarning = 0; + while (CurTok.Tok != TOK_RCURLY && CurTok.Tok != TOK_CEOF) { + LineInfo* LI2 = UseLineInfo (GetDiagnosticLI ()); + int StmtFlags2 = AnyStatement (0, Switch); + if (!UnreachableWarning) { + /* Check if the code is unreachable because of a preceeding + ** jump and if the code doesn't have a jump label. + */ + if ((StmtFlags1 & SF_MASK_UNREACH) != SF_NONE && + (StmtFlags2 & SF_MASK_LABEL) == SF_NONE) { + LIUnreachableCodeWarning (LI2); + UnreachableWarning = 1; + } + } + if (LI1) { + ReleaseLineInfo (LI1); + } + LI1 = LI2; + StmtFlags1 = (StmtFlags1 & SF_MASK_ANY) | StmtFlags2; + } + if (LI1) { + ReleaseLineInfo (LI1); + } + return StmtFlags1; + } else { + return SF_NONE; + } +} + + + +int AnyStatement (int* PendingToken, struct SwitchCtrl* Switch) +/* Statement parser. Returns one of the SF_xxx flags describing if the +** statement does a return/break. If the PendingToken pointer is not NULL, +** the function will not skip the terminating token of the statement (closing +** brace or semicolon), but store true if there is a pending token, and false +** if there is none. The token is always checked, so there is no need for the +** caller to check this token, it must be skipped, however. If the argument +** pointer is NULL, the function will skip the token. When called to parse a +** switch body, the switch control structure must be passed via the Switch +** argument. Otherwise it must be NULL. +*/ +{ + int LabelFlags = SF_NONE; + int StmtFlags; + unsigned PrevErrorCount; + LineInfo* LI; /* Assume no pending token */ if (PendingToken) { @@ -670,75 +790,113 @@ int AnyStatement (int* PendingToken) } /* Handle any labels. A label is always part of a statement, it does not - ** replace one. + ** replace one. If we have errors parsing labels, return without reading + ** the following statement. */ - if (ParseAnyLabels ()) { - return 0; + PrevErrorCount = ErrorCount; + while (1) { + if (CurTok.Tok == TOK_IDENT && NextTok.Tok == TOK_COLON) { + /* C 'goto' label */ + DoLabel (); + LabelFlags |= SF_LABEL_GOTO; + } else if (CurTok.Tok == TOK_CASE) { + /* C 'case' label */ + CaseLabel (); + LabelFlags |= SF_LABEL_CASE; + } else if (CurTok.Tok == TOK_DEFAULT) { + /* C 'default' label */ + DefaultLabel (); + LabelFlags |= SF_LABEL_DEFAULT; + } else { + /* No labels */ + break; + } + } + if (LabelFlags != SF_NONE) { + /* We had labels, check for errors */ + if (PrevErrorCount != ErrorCount || CheckLabelWithoutStatement ()) { + return SF_NONE; + } } + /* Remember the line info for the now following statement */ + LI = UseLineInfo (GetDiagnosticLI ()); + + /* Now look at the actual statement. */ switch (CurTok.Tok) { case TOK_IF: - GotBreak = IfStatement (); + StmtFlags = IfStatement (); break; case TOK_SWITCH: - SwitchStatement (); + StmtFlags = SwitchStatement (); break; case TOK_WHILE: - WhileStatement (); + StmtFlags = WhileStatement (); break; case TOK_DO: - DoStatement (); + StmtFlags = DoStatement (); break; case TOK_FOR: - ForStatement (); + StmtFlags = ForStatement (); break; case TOK_GOTO: - GotoStatement (); + StmtFlags = GotoStatement (); CheckSemi (PendingToken); - GotBreak = 1; break; case TOK_RETURN: - ReturnStatement (); + StmtFlags = ReturnStatement (); CheckSemi (PendingToken); - GotBreak = 1; break; case TOK_BREAK: - BreakStatement (); + StmtFlags = BreakStatement (); CheckSemi (PendingToken); - GotBreak = 1; break; case TOK_CONTINUE: - ContinueStatement (); + StmtFlags = ContinueStatement (); CheckSemi (PendingToken); - GotBreak = 1; break; case TOK_SEMI: /* Empty statement. Ignore it */ CheckSemi (PendingToken); + StmtFlags = SF_NONE; break; case TOK_LCURLY: - GotBreak = CompoundStatement (PendingToken); + StmtFlags = CompoundStatement (PendingToken, Switch); break; default: /* Simple statement */ - Statement (PendingToken); + StmtFlags = Statement (PendingToken); break; } + /* The flags for labels returned by subroutines are invalid in most cases, + ** so we remove them and use the ones determined before. + */ + StmtFlags = (StmtFlags & ~SF_MASK_LABEL) | LabelFlags; + /* Reset SQP flags */ SetSQPFlags (SQP_KEEP_NONE); - return GotBreak; + /* If we're inside a switch, do tracking of special statements */ + if (Switch) { + SwitchBodyStatement (Switch, LI, StmtFlags); + } + + /* Release the line info we remembered above */ + ReleaseLineInfo (LI); + + /* Done */ + return StmtFlags; } diff --git a/src/cc65/stmt.h b/src/cc65/stmt.h index 8cff99d92..2b91d06bf 100644 --- a/src/cc65/stmt.h +++ b/src/cc65/stmt.h @@ -38,20 +38,128 @@ +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + +/* Enumeration for the AnyStatement() return flags. It is used for simple flow +** analysis and tracks occurance of "return", "break", "goto" and "continue" +** (anything that jumps). The flags for the distinct statements are only set +** if they are always executed. So for example in case of an if/else statement, +** SF_RETURN is set only if both branches contain a "return". The SF_ANY flag +** is set of any of the statements occurred. So for an if/else statement, if +** one branch contains a "return" and the other a "continue" statement, neither +** SF_RETURN, nor SF_CONTINUE is set, but SF_ANY. +** There are some additional flags that tell if the statement parsed was +** preceeded by at least one label. These flags do not also set SF_ANY. +*/ +enum { + SF_NONE = 0x0000, + + /* Flags for special statements that cause the next statement to be + ** unreachable. They are only return as long as the condition is true. + ** Please note that a statement function can return more than one of + ** these flags set. For example for an "if" that has "return" in one + ** branch and "continue" in another. + */ + SF_RETURN = 0x00001, /* Code definitely returns */ + SF_BREAK = 0x00002, /* Code definitely breaks */ + SF_GOTO = 0x00004, /* Code definitely jumps */ + SF_CONTINUE = 0x00008, /* Code definitely continues */ + SF_OTHER = 0x00010, /* Endless loop, terminating func */ + SF_MASK_UNREACH = 0x000FF, /* Following code is unreachable */ + + /* Flags for the occurance of any of the conditions from above. Statements + ** will clear whatever is no longer valid when exiting. A while loop, for + ** example will never return SF_ANY_BREAK or SF_ANY_CONTINUE since that was + ** handled inside the loop. + */ + SF_ANY_RETURN = 0x00100, /* Code contains a return statement */ + SF_ANY_BREAK = 0x00200, /* Code contains a break statement */ + SF_ANY_GOTO = 0x00400, /* Code contains a goto statement */ + SF_ANY_CONTINUE = 0x00800, /* Code contains a continue statement */ + SF_ANY_OTHER = 0x01000, /* Code contains endless loop etc. */ + SF_MASK_ANY = 0x0FF00, /* Code contains any of the above */ + + /* Flags for labels */ + SF_LABEL_GOTO = 0x10000, /* Statement preceeded by goto label */ + SF_LABEL_CASE = 0x20000, /* Statement preceeded by case label */ + SF_LABEL_DEFAULT = 0x40000, /* Statement preceeded by default label */ + SF_MASK_LABEL = 0x70000, /* Mask for any label */ +}; + +/* Forward */ +struct SwitchCtrl; + + + +/*****************************************************************************/ +/* Functions to handle the flow control flags */ +/*****************************************************************************/ + + + +static inline int SF_Break (int F) +/* Return just the "break" flag in F */ +{ + return (F & SF_BREAK); +} + +static inline int SF_Continue (int F) +/* Return just the "continue" flag in F */ +{ + return (F & SF_CONTINUE); +} + +static inline int SF_Unreach (int F) +/* Return just the "unreachable" part of the given flags */ +{ + return (F & SF_MASK_UNREACH); +} + +static inline int SF_Any_Break (int F) +/* Check if there was any "break" statement */ +{ + return (F & SF_ANY_BREAK); +} + +static inline int SF_Any (int F) +/* Return just the "any" part of the given flags */ +{ + return (F & SF_MASK_ANY); +} + +static inline int SF_Label (int F) +/* Return just the "label" part of the given flags */ +{ + return (F & SF_MASK_LABEL); +} + + + + /*****************************************************************************/ /* Code */ /*****************************************************************************/ -int AnyStatement (int* PendingToken); -/* Statement parser. Returns 1 if the statement does a return/break, returns -** 0 otherwise. If the PendingToken pointer is not NULL, the function will -** not skip the terminating token of the statement (closing brace or -** semicolon), but store true if there is a pending token, and false if there -** is none. The token is always checked, so there is no need for the caller to -** check this token, it must be skipped, however. If the argument pointer is -** NULL, the function will skip the token. +int StatementBlock (struct SwitchCtrl* Switch); +/* Parse multiple statements within curly braces checking for unreachable +** code. Returns the SF_xxx flags for the last statement. +*/ + +int AnyStatement (int* PendingToken, struct SwitchCtrl* Switch); +/* Statement parser. Returns one of the SF_xxx flags describing if the +** statement does a return/break. If the PendingToken pointer is not NULL, +** the function will not skip the terminating token of the statement (closing +** brace or semicolon), but store true if there is a pending token, and false +** if there is none. The token is always checked, so there is no need for the +** caller to check this token, it must be skipped, however. If the argument +** pointer is NULL, the function will skip the token. When called to parse a +** switch body, the switch control structure must be passed via the Switch +** argument. Otherwise it must be NULL. */ diff --git a/src/cc65/swstmt.c b/src/cc65/swstmt.c index fc213c9a1..591c893a4 100644 --- a/src/cc65/swstmt.c +++ b/src/cc65/swstmt.c @@ -61,15 +61,23 @@ +/* Some bitmapped flags for use in SwitchCtrl */ +#define SC_NONE 0x0000 +#define SC_CASE 0x0001 /* We had a case label */ +#define SC_DEFAULT 0x0002 /* We had a default label */ +#define SC_MASK_LABEL 0x0003 /* Mask for the labels */ +#define SC_WEIRD 0x0004 /* Flag for a weird switch that contains code + ** outside of any label. + */ + typedef struct SwitchCtrl SwitchCtrl; struct SwitchCtrl { Collection* Nodes; /* CaseNode tree */ const Type* ExprType; /* Switch controlling expression type */ unsigned Depth; /* Number of bytes the selector type has */ unsigned DefaultLabel; /* Label for the default branch */ - - - + unsigned CtrlFlags; /* Bitmapped flags as defined above */ + int StmtFlags; /* Collected statement flags */ }; /* Pointer to current switch control struct */ @@ -77,14 +85,60 @@ static SwitchCtrl* Switch = 0; +/*****************************************************************************/ +/* Functions that work with struct SwitchExpr */ +/*****************************************************************************/ + + + +static int SC_Label (const SwitchCtrl* S) +/* Check if we had a switch label */ +{ + return (S->CtrlFlags & SC_MASK_LABEL) != SC_NONE; +} + + + +static int SC_IsWeird (const SwitchCtrl* S) +/* Check if this switch is weird */ +{ + return (S->CtrlFlags & SC_WEIRD) != SC_NONE; +} + + + +static void SC_SetCase (SwitchCtrl* S) +/* Set the current label type to "case" */ +{ + S->CtrlFlags |= SC_CASE; +} + + + +static void SC_SetDefault (SwitchCtrl* S) +/* Set the current label type to "default" */ +{ + S->CtrlFlags |= SC_DEFAULT; +} + + + +static void SC_MakeWeird (SwitchCtrl* S) +/* Mark the switch as weird */ +{ + S->CtrlFlags |= SC_WEIRD; +} + + + /*****************************************************************************/ /* Code */ /*****************************************************************************/ -void SwitchStatement (void) -/* Handle a switch statement for chars with a cmp cascade for the selector */ +int SwitchStatement (void) +/* Handle a 'switch' statement and return the corresponding SF_xxx flags */ { ExprDesc SwitchExpr; /* Switch statement expression */ CodeMark CaseCodeStart; /* Start of code marker */ @@ -92,7 +146,7 @@ void SwitchStatement (void) CodeMark SwitchCodeEnd; /* End of switch code */ unsigned ExitLabel; /* Exit label */ unsigned SwitchCodeLabel;/* Label for the switch code */ - int HaveBreak = 0; /* True if the last statement had a break */ + int StmtFlags; /* True if the last statement had a break */ int RCurlyBrace; /* True if last token is right curly brace */ SwitchCtrl* OldSwitch; /* Pointer to old switch control data */ SwitchCtrl SwitchData; /* New switch data */ @@ -136,6 +190,8 @@ void SwitchStatement (void) SwitchData.ExprType = SwitchExpr.Type; SwitchData.Depth = SizeOf (SwitchExpr.Type); SwitchData.DefaultLabel = 0; + SwitchData.CtrlFlags = SC_NONE; + SwitchData.StmtFlags = SF_NONE; OldSwitch = Switch; Switch = &SwitchData; @@ -148,7 +204,7 @@ void SwitchStatement (void) /* Parse the following statement, which may actually be a compound ** statement if there is a curly brace at the current input position */ - HaveBreak = AnyStatement (&RCurlyBrace); + StmtFlags = AnyStatement (&RCurlyBrace, Switch); /* Check if we had any labels */ if (CollCount (SwitchData.Nodes) == 0 && SwitchData.DefaultLabel == 0) { @@ -162,7 +218,7 @@ void SwitchStatement (void) ** carry the label), add a jump to the exit. If it is useless, the ** optimizer will remove it later. */ - if (!HaveBreak) { + if (SF_Unreach (StmtFlags) == SF_NONE) { g_jump (ExitLabel); } @@ -201,6 +257,11 @@ void SwitchStatement (void) if (RCurlyBrace) { NextToken (); } + + /* We only return the combined "any" flags from all the statements within + ** the switch. Minus "break" which is handled inside the switch. + */ + return SwitchData.StmtFlags & ~SF_ANY_BREAK; } @@ -268,6 +329,9 @@ void CaseLabel (void) Warning (DiagMsg, CaseExpr.IVal); } + /* Remember that we're in a case label section now */ + SC_SetCase (Switch); + } else { /* case keyword outside a switch statement */ @@ -297,6 +361,9 @@ void DefaultLabel (void) Switch->DefaultLabel = GetLocalLabel (); g_defcodelabel (Switch->DefaultLabel); + /* Remember that we're in the default label section now */ + SC_SetDefault (Switch); + } else { /* We had the default label already */ Error ("Multiple default labels in one switch"); @@ -312,3 +379,31 @@ void DefaultLabel (void) /* Skip the colon */ ConsumeColon (); } + + + +void SwitchBodyStatement (struct SwitchCtrl* S, LineInfo* LI, int StmtFlags) +/* Helper function for flow analysis. Must be called for all statements within +** a switch passing the flags for special statements. +*/ +{ + /* The control structure passed must be the current one */ + PRECONDITION (S == Switch); + + /* Handle code without a label in the switch */ + if (SC_Label (S) == SC_NONE) { + /* This is a statement that preceedes any switch labels. If the + ** switch is not already marked as weird, output and the current + ** statement has no label, output a warning about unreachable code. + */ + if (!SC_IsWeird (S)) { + if (!SF_Label (StmtFlags)) { + LIUnreachableCodeWarning (LI); + } + SC_MakeWeird (S); + } + } + + /* Collect the statement flags. */ + S->StmtFlags = SF_Any (S->StmtFlags | StmtFlags); +} diff --git a/src/cc65/swstmt.h b/src/cc65/swstmt.h index b4facf2cd..98d0cd952 100644 --- a/src/cc65/swstmt.h +++ b/src/cc65/swstmt.h @@ -38,14 +38,25 @@ +/*****************************************************************************/ +/* Data */ +/*****************************************************************************/ + + + +/* Forwards */ +struct SwitchCtrl; + + + /*****************************************************************************/ /* Code */ /*****************************************************************************/ -void SwitchStatement (void); -/* Handle a 'switch' statement */ +int SwitchStatement (void); +/* Handle a 'switch' statement and return the corresponding SF_xxx flags */ void CaseLabel (void); /* Handle a case label */ @@ -53,6 +64,11 @@ void CaseLabel (void); void DefaultLabel (void); /* Handle a default label */ +void SwitchBodyStatement (struct SwitchCtrl* Switch, LineInfo* LI, int RetFlags); +/* Helper function for flow analysis. Must be called for all statements within +** a switch passing the flags for special statements. +*/ + /* End of swstmt.h */ diff --git a/test/misc/goto.ref b/test/misc/goto.ref index 2e0819887..d1fd3a793 100644 --- a/test/misc/goto.ref +++ b/test/misc/goto.ref @@ -1,4 +1,9 @@ +goto.c:3: Warning: Unreachable code goto.c:8: Warning: Goto at line 8 to label start jumps into a block with initialization of an object that has automatic storage duration +goto.c:17: Warning: Unreachable code +goto.c:38: Warning: Unreachable code +goto.c:59: Warning: Unreachable code +goto.c:80: Warning: Unreachable code goto.c:97: Warning: Variable 'a' is defined but never used goto.c:117: Warning: Variable 'a' is defined but never used goto.c:137: Warning: Variable 'a' is defined but never used @@ -9,6 +14,7 @@ goto.c:159: Warning: Goto at line 86 to label l8 jumps into a block with initial goto.c:159: Warning: Goto at line 106 to label l8 jumps into a block with initialization of an object that has automatic storage duration goto.c:159: Warning: Goto at line 126 to label l8 jumps into a block with initialization of an object that has automatic storage duration goto.c:159: Warning: Goto at line 146 to label l8 jumps into a block with initialization of an object that has automatic storage duration +goto.c:161: Warning: Unreachable code goto.c:180: Warning: Goto at line 24 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:180: Warning: Goto at line 45 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:180: Warning: Goto at line 66 to label l9 jumps into a block with initialization of an object that has automatic storage duration @@ -17,6 +23,7 @@ goto.c:180: Warning: Goto at line 107 to label l9 jumps into a block with initia goto.c:180: Warning: Goto at line 127 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:180: Warning: Goto at line 147 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:180: Warning: Goto at line 168 to label l9 jumps into a block with initialization of an object that has automatic storage duration +goto.c:182: Warning: Unreachable code goto.c:201: Warning: Goto at line 25 to label la jumps into a block with initialization of an object that has automatic storage duration goto.c:201: Warning: Goto at line 46 to label la jumps into a block with initialization of an object that has automatic storage duration goto.c:201: Warning: Goto at line 67 to label la jumps into a block with initialization of an object that has automatic storage duration @@ -26,6 +33,7 @@ goto.c:201: Warning: Goto at line 128 to label la jumps into a block with initia goto.c:201: Warning: Goto at line 148 to label la jumps into a block with initialization of an object that has automatic storage duration goto.c:201: Warning: Goto at line 169 to label la jumps into a block with initialization of an object that has automatic storage duration goto.c:201: Warning: Goto at line 190 to label la jumps into a block with initialization of an object that has automatic storage duration +goto.c:203: Warning: Unreachable code goto.c:221: Warning: Goto at line 26 to label lb jumps into a block with initialization of an object that has automatic storage duration goto.c:221: Warning: Goto at line 47 to label lb jumps into a block with initialization of an object that has automatic storage duration goto.c:221: Warning: Goto at line 68 to label lb jumps into a block with initialization of an object that has automatic storage duration @@ -57,6 +65,7 @@ goto.c:263: Warning: Goto at line 193 to label ld jumps into a block with initia goto.c:263: Warning: Goto at line 214 to label ld jumps into a block with initialization of an object that has automatic storage duration goto.c:263: Warning: Goto at line 234 to label ld jumps into a block with initialization of an object that has automatic storage duration goto.c:263: Warning: Goto at line 254 to label ld jumps into a block with initialization of an object that has automatic storage duration +goto.c:265: Warning: Unreachable code goto.c:271: Warning: Goto at line 271 to label l8 jumps into a block with initialization of an object that has automatic storage duration goto.c:272: Warning: Goto at line 272 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:273: Warning: Goto at line 273 to label la jumps into a block with initialization of an object that has automatic storage duration @@ -75,6 +84,7 @@ goto.c:284: Warning: Goto at line 215 to label le jumps into a block with initia goto.c:284: Warning: Goto at line 235 to label le jumps into a block with initialization of an object that has automatic storage duration goto.c:284: Warning: Goto at line 255 to label le jumps into a block with initialization of an object that has automatic storage duration goto.c:284: Warning: Goto at line 277 to label le jumps into a block with initialization of an object that has automatic storage duration +goto.c:286: Warning: Unreachable code goto.c:292: Warning: Goto at line 292 to label l8 jumps into a block with initialization of an object that has automatic storage duration goto.c:293: Warning: Goto at line 293 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:294: Warning: Goto at line 294 to label la jumps into a block with initialization of an object that has automatic storage duration @@ -94,6 +104,7 @@ goto.c:305: Warning: Goto at line 236 to label lf jumps into a block with initia goto.c:305: Warning: Goto at line 256 to label lf jumps into a block with initialization of an object that has automatic storage duration goto.c:305: Warning: Goto at line 278 to label lf jumps into a block with initialization of an object that has automatic storage duration goto.c:305: Warning: Goto at line 299 to label lf jumps into a block with initialization of an object that has automatic storage duration +goto.c:307: Warning: Unreachable code goto.c:313: Warning: Goto at line 313 to label l8 jumps into a block with initialization of an object that has automatic storage duration goto.c:314: Warning: Goto at line 314 to label l9 jumps into a block with initialization of an object that has automatic storage duration goto.c:315: Warning: Goto at line 315 to label la jumps into a block with initialization of an object that has automatic storage duration