Merge pull request #2272 from acqn/Diagnostics

[cc65] Improved diagnostics
This commit is contained in:
Bob Andrews 2023-11-28 15:06:39 +01:00 committed by GitHub
commit 5537b61e6a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 255 additions and 74 deletions

View File

@ -102,7 +102,7 @@ static SymEntry* AsmGetSym (unsigned Arg, unsigned Type)
/* Did we find a symbol with this name? */
if (Sym == 0) {
Error ("Undefined symbol '%s' for argument %u", CurTok.Ident, Arg);
Error ("Undeclared symbol '%s' for argument %u", CurTok.Ident, Arg);
AsmErrorSkip ();
return 0;
}

View File

@ -94,6 +94,8 @@ static void Parse (void)
while (CurTok.Tok != TOK_CEOF) {
DeclSpec Spec;
int NeedClean = 0;
unsigned PrevErrorCount = ErrorCount;
/* Check for empty statements */
if (CurTok.Tok == TOK_SEMI) {
@ -144,7 +146,11 @@ static void Parse (void)
Declarator Decl;
/* Read the next declaration */
ParseDecl (&Spec, &Decl, DM_NEED_IDENT);
NeedClean = ParseDecl (&Spec, &Decl, DM_NEED_IDENT);
if (Decl.Ident[0] == '\0') {
Sym = 0;
goto NextDecl;
}
/* Check if we must reserve storage for the variable. We do this,
**
@ -310,6 +316,7 @@ static void Parse (void)
}
NextDecl:
/* Check for end of declaration list */
if (CurTok.Tok == TOK_COMMA) {
NextToken ();
@ -325,6 +332,7 @@ static void Parse (void)
/* Function */
if (CurTok.Tok == TOK_SEMI) {
/* Prototype only */
NeedClean = 0;
NextToken ();
} else if (CurTok.Tok == TOK_LCURLY) {
/* ISO C: The type category in a function definition cannot be
@ -337,6 +345,7 @@ static void Parse (void)
}
/* Parse the function body anyways */
NeedClean = 0;
NewFunc (Sym, FuncDef);
/* Make sure we aren't omitting any work */
@ -345,10 +354,27 @@ static void Parse (void)
} else {
/* Must be followed by a semicolon */
ConsumeSemi ();
if (Sym) {
/* Must be followed by a semicolon */
if (CurTok.Tok != TOK_SEMI) {
NeedClean = -1;
}
ConsumeSemi ();
}
}
/* Try some smart error recovery */
if (PrevErrorCount != ErrorCount && NeedClean < 0) {
/* Some fix point tokens that are used for error recovery */
static const token_t TokenList[] = { TOK_SEMI, TOK_RCURLY };
SmartErrorSkip ();
SkipTokens (TokenList, sizeof (TokenList) / sizeof (TokenList[0]));
if (CurTok.Tok == TOK_SEMI || CurTok.Tok == TOK_RCURLY) {
NextToken ();
}
}
}
/* Done with deferred operations */

View File

@ -83,6 +83,104 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
static void OpenBrace (Collection* C, token_t Tok)
/* Consume an opening parenthesis/bracket/curly brace and remember that */
{
switch (Tok) {
case TOK_LPAREN: Tok = TOK_RPAREN; break;
case TOK_LBRACK: Tok = TOK_RBRACK; break;
case TOK_LCURLY: Tok = TOK_RCURLY; break;
default: Internal ("Unexpected opening token: %02X", (unsigned)Tok);
}
CollAppend (C, (void*)Tok);
NextToken ();
}
static int CloseBrace (Collection* C, token_t Tok)
/* Consume a closing parenthesis/bracket/curly brace if it is matched with an
** opening one and return 0, or bail out and return -1 if it is not matched.
*/
{
if (CollCount (C) > 0) {
token_t LastTok = (token_t)CollLast (C);
if (LastTok == Tok) {
CollPop (C);
NextToken ();
return 0;
}
}
return -1;
}
int SmartErrorSkip (void)
/* Try some smart error recovery. Skip tokens until either a comma or semicolon
** that is not enclosed in an open parenthesis/bracket/curly brace, or until an
** unpaired right parenthesis/bracket/curly brace is reached. Return 0 if it is
** the former case, or -1 if it is the latter case. */
{
Collection C = AUTO_COLLECTION_INITIALIZER;
int Res = 0;
/* Some fix point tokens that are used for error recovery */
static const token_t TokenList[] = { TOK_COMMA, TOK_SEMI,
TOK_LPAREN, TOK_RPAREN, TOK_LBRACK, TOK_RBRACK, TOK_LCURLY, TOK_RCURLY };
while (CurTok.Tok != TOK_CEOF) {
SkipTokens (TokenList, sizeof (TokenList) / sizeof (TokenList[0]));
switch (CurTok.Tok) {
case TOK_LPAREN:
case TOK_LBRACK:
case TOK_LCURLY:
OpenBrace (&C, CurTok.Tok);
break;
case TOK_RPAREN:
case TOK_RBRACK:
if (CloseBrace (&C, CurTok.Tok)) {
Res = -1;
goto ExitPoint;
}
break;
case TOK_RCURLY:
if (CloseBrace (&C, CurTok.Tok)) {
Res = -1;
goto ExitPoint;
} else if (CollCount (&C) == 0) {
goto ExitPoint;
}
break;
case TOK_COMMA:
if (CollCount (&C) == 0) {
goto ExitPoint;
}
NextToken ();
break;
case TOK_SEMI:
case TOK_CEOF:
Res = -1;
goto ExitPoint;
default:
Internal ("Unexpected token: %02X", (unsigned)CurTok.Tok);
}
}
ExitPoint:
DoneCollection (&C);
return Res;
}
static unsigned ParseOneStorageClass (void)
/* Parse and return a storage class specifier */
{
@ -1589,6 +1687,12 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
*SignednessSpecified = 1;
}
break;
} else if ((TSFlags & TS_MASK_DEFAULT_TYPE) == TS_DEFAULT_TYPE_NONE) {
/* Treat this identifier as an unknown type */
Error ("Unknown type name '%s'", CurTok.Ident);
TypeCopy (Spec->Type, type_int);
NextToken ();
break;
}
} else {
/* This is a label. Use the default type flag to end the loop
@ -1670,14 +1774,13 @@ static void ParseOldStyleParamList (FuncDesc* F)
NextToken ();
} else {
/* Some fix point tokens that are used for error recovery */
static const token_t TokenList[] = { TOK_COMMA, TOK_RPAREN, TOK_SEMI };
/* Not a parameter name */
Error ("Identifier expected for parameter name");
/* Try some smart error recovery */
SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
if (SmartErrorSkip () < 0) {
break;
}
}
/* Check for more parameters */
@ -1763,12 +1866,9 @@ static void ParseOldStyleParamList (FuncDesc* F)
ConsumeSemi ();
}
if (PrevErrorCount != ErrorCount) {
/* Some fix point tokens that are used for error recovery */
static const token_t TokenList[] = { TOK_COMMA, TOK_SEMI };
if (PrevErrorCount != ErrorCount && CurTok.Tok != TOK_LCURLY) {
/* Try some smart error recovery */
SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
SmartErrorSkip ();
}
}
@ -1783,6 +1883,7 @@ static void ParseAnsiParamList (FuncDesc* F)
DeclSpec Spec;
Declarator Decl;
SymEntry* Param;
unsigned PrevErrorCount = ErrorCount;
/* Allow an ellipsis as last parameter */
if (CurTok.Tok == TOK_ELLIPSIS) {
@ -1818,7 +1919,7 @@ static void ParseAnsiParamList (FuncDesc* F)
/* Allow parameters without a name, but remember if we had some to
** eventually print an error message later.
*/
ParseDecl (&Spec, &Decl, DM_ACCEPT_IDENT);
ParseDecl (&Spec, &Decl, DM_ACCEPT_PARAM_IDENT);
if (Decl.Ident[0] == '\0') {
/* Unnamed symbol. Generate a name that is not user accessible,
@ -1850,6 +1951,13 @@ static void ParseAnsiParamList (FuncDesc* F)
/* Count arguments */
++F->ParamCount;
if (PrevErrorCount != ErrorCount) {
/* Try some smart error recovery */
if (SmartErrorSkip () < 0) {
break;
}
}
/* Check for more parameters */
if (CurTok.Tok == TOK_COMMA) {
NextToken ();
@ -1940,7 +2048,7 @@ static FuncDesc* ParseFuncDecl (void)
static void DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* Recursively process direct declarators. Build a type array in reverse order. */
{
/* Read optional function or pointer qualifiers that modify the identifier
@ -1957,61 +2065,49 @@ static void DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* Skip the star */
NextToken ();
/* A pointer type cannot be used as an empty declaration */
if (Mode == DM_ACCEPT_IDENT) {
Mode = DM_NEED_IDENT;
}
/* Allow const, restrict, and volatile qualifiers */
Qualifiers |= OptionalQualifiers (Qualifiers, T_QUAL_CVR);
/* Parse the type that the pointer points to */
DirectDecl (Spec, D, Mode);
Mode = DirectDecl (Spec, D, Mode);
/* Add the type */
AddTypeCodeToDeclarator (D, T_PTR | Qualifiers);
return;
return Mode;
}
if (CurTok.Tok == TOK_LPAREN) {
NextToken ();
DirectDecl (Spec, D, Mode);
ConsumeRParen ();
} else {
/* Things depend on Mode now:
** - Mode == DM_NEED_IDENT means:
** we *must* have a type and a variable identifer.
** - Mode == DM_NO_IDENT means:
** we must have a type but no variable identifer
** (if there is one, it's not read).
** - Mode == DM_ACCEPT_IDENT means:
** we *may* have an identifier. If there is an identifier,
** it is read, but it is no error, if there is none.
/* An empty declaration cannot contain parentheses where an identifier
** would show up if it were a non-empty declaration.
*/
if (Mode == DM_NO_IDENT) {
D->Ident[0] = '\0';
} else if (CurTok.Tok == TOK_IDENT) {
strcpy (D->Ident, CurTok.Ident);
NextToken ();
} else {
if (Mode == DM_NEED_IDENT) {
/* Some fix point tokens that are used for error recovery */
static const token_t TokenList[] = { TOK_COMMA, TOK_SEMI, TOK_LCURLY, TOK_RCURLY };
Error ("Identifier expected");
/* Try some smart error recovery */
SkipTokens (TokenList, sizeof(TokenList) / sizeof(TokenList[0]));
/* Skip curly braces */
if (CurTok.Tok == TOK_LCURLY) {
static const token_t CurlyToken[] = { TOK_RCURLY };
SkipTokens (CurlyToken, sizeof(CurlyToken) / sizeof(CurlyToken[0]));
NextToken ();
} else if (CurTok.Tok == TOK_RCURLY) {
NextToken ();
}
}
D->Ident[0] = '\0';
if (Mode == DM_ACCEPT_IDENT) {
Mode = DM_NEED_IDENT;
}
Mode = DirectDecl (Spec, D, Mode);
ConsumeRParen ();
} else if (CurTok.Tok == TOK_IDENT) {
strcpy (D->Ident, CurTok.Ident);
NextToken ();
} else {
D->Ident[0] = '\0';
if (Mode == DM_NEED_IDENT) {
Error ("Identifier expected");
}
}
while (CurTok.Tok == TOK_LBRACK || CurTok.Tok == TOK_LPAREN) {
/* An array or function type cannot be used as an empty declaration */
if (Mode == DM_ACCEPT_IDENT && D->Ident[0] == '\0') {
Mode = DM_NEED_IDENT;
Error ("Identifier expected");
}
if (CurTok.Tok == TOK_LPAREN) {
/* Function declarator */
@ -2097,6 +2193,8 @@ static void DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
if (Qualifiers & T_QUAL_CDECL) {
Error ("Invalid '__cdecl__' qualifier");
}
return Mode;
}
@ -2129,8 +2227,10 @@ Type* ParseType (Type* T)
void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* Parse a variable, type or function declarator */
int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* Parse a variable, type or function declarator. Return -1 if this stops at
** an unpaired right parenthesis/bracket/curly brace.
*/
{
/* Used to check if we have any errors during parsing this */
unsigned PrevErrorCount = ErrorCount;
@ -2181,7 +2281,7 @@ void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
}
/* Check a few pre-C99 things */
if ((Spec->Flags & DS_DEF_TYPE) != 0) {
if (D->Ident[0] != '\0' && (Spec->Flags & DS_DEF_TYPE) != 0) {
/* Check and warn about an implicit int return in the function */
if (IsTypeFunc (D->Type) && IsRankInt (GetFuncReturnType (D->Type))) {
/* Function has an implicit int return. Output a warning if we don't
@ -2193,7 +2293,7 @@ void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
GetFuncDesc (D->Type)->Flags |= FD_OLDSTYLE_INTRET;
}
/* For anthing that is not a function or typedef, check for an implicit
/* For anything that is not a function or typedef, check for an implicit
** int declaration.
*/
if ((D->StorageClass & SC_FUNC) != SC_FUNC &&
@ -2208,14 +2308,38 @@ void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
}
if (PrevErrorCount != ErrorCount) {
/* Make the declaration fictitious if is is not parsed correctly */
D->StorageClass |= SC_FICTITIOUS;
if ((Spec->Flags & DS_DEF_TYPE) == 0 && Mode == DM_NEED_IDENT && D->Ident[0] == '\0') {
/* Make the declaration fictitious if is is not parsed correctly */
D->StorageClass |= SC_FICTITIOUS;
if (Mode == DM_NEED_IDENT && D->Ident[0] == '\0') {
/* Use a fictitious name for the identifier if it is missing */
AnonName (D->Ident, "global");
const char* Level = "";
switch (GetLexicalLevel ()) {
case LEX_LEVEL_GLOBAL:
Level = "global";
break;
case LEX_LEVEL_FUNCTION:
case LEX_LEVEL_BLOCK:
Level = "local";
break;
case LEX_LEVEL_STRUCT:
Level = "field";
break;
default:
Level = "unknown";
break;
}
AnonName (D->Ident, Level);
}
/* Try some smart error recovery */
if (CurTok.Tok != TOK_LCURLY || !IsTypeFunc (D->Type)) {
return SmartErrorSkip ();
}
}
return 0;
}

View File

@ -98,11 +98,26 @@ struct Declarator {
unsigned Index; /* Used to build Type */
};
/* Modes for ParseDecl */
/* Modes for ParseDecl:
** - DM_NEED_IDENT means:
** we *must* have a type and a variable identifer.
** - DM_NO_IDENT means:
** we must have a type but no variable identifer
** (if there is one, it's not read).
** - DM_ACCEPT_IDENT means:
** we *may* have an identifier, or none. If it is the latter case,
** the type must be used as an empty declaration, or it is an error.
** Note: this is used for struct/union members.
** - DM_IGNORE_IDENT means:
** we *may* have an identifier. If there is an identifier,
** it is read, but it is no error, if there is none.
** Note: this is used for function parameter type lists.
*/
typedef enum {
DM_NEED_IDENT, /* We must have an identifier */
DM_NO_IDENT, /* We won't read an identifier */
DM_ACCEPT_IDENT, /* We will accept an id if there is one */
DM_NEED_IDENT,
DM_NO_IDENT,
DM_ACCEPT_IDENT,
DM_ACCEPT_PARAM_IDENT,
} declmode_t;
@ -113,11 +128,19 @@ typedef enum {
int SmartErrorSkip (void);
/* Try some smart error recovery. Skip tokens until either a comma or semicolon
** that is not enclosed in an open parenthesis/bracket/curly brace, or until an
** unpaired right parenthesis/bracket/curly brace is reached. Return 0 if it is
** the former case, or -1 if it is the latter case. */
Type* ParseType (Type* Type);
/* Parse a complete type specification */
void ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode);
/* Parse a variable, type or function declarator */
int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode);
/* Parse a variable, type or function declarator. Return -1 if this stops at
** an unpaired right parenthesis/bracket/curly brace.
*/
void ParseDeclSpec (DeclSpec* Spec, typespec_t TSFlags, unsigned DefStorage);
/* Parse a declaration specification */

View File

@ -61,6 +61,8 @@
/* Count of errors/warnings */
unsigned ErrorCount = 0;
unsigned WarningCount = 0;
unsigned RecentLineNo = 0;
unsigned RecentErrorCount = 0;
/* Warning and error options */
IntStack WarnEnable = INTSTACK(1); /* Enable warnings */
@ -205,8 +207,16 @@ static void IntError (const char* Filename, unsigned LineNo, const char* Msg, va
if (Line) {
Print (stderr, 1, "Input: %.*s\n", (int) SB_GetLen (Line), SB_GetConstBuf (Line));
}
++ErrorCount;
if (ErrorCount > 20) {
if (RecentLineNo != LineNo) {
RecentLineNo = LineNo;
RecentErrorCount = 0;
} else {
++RecentErrorCount;
}
if (RecentErrorCount > 20 || ErrorCount > 200) {
Fatal ("Too many errors");
}
}

View File

@ -1322,10 +1322,10 @@ static void Primary (ExprDesc* E)
E->Name = (uintptr_t) Sym->Name;
} else {
/* Undeclared Variable */
Error ("Undeclared identifier '%s'", Ident);
Sym = AddLocalSym (Ident, type_int, SC_AUTO | SC_REF, 0);
E->Flags = E_LOC_STACK | E_RTYPE_LVAL;
E->Type = type_int;
Error ("Undefined symbol: '%s'", Ident);
}
}

View File

@ -1493,7 +1493,7 @@ void MakeZPSym (const char* Name)
if (Entry) {
Entry->Flags |= SC_ZEROPAGE;
} else {
Error ("Undefined symbol: '%s'", Name);
Error ("Undeclared symbol: '%s'", Name);
}
}

View File

@ -1,5 +1,3 @@
bug1889-missing-identifier.c:3: Error: Identifier expected
bug1889-missing-identifier.c:3: Error: ';' expected
bug1889-missing-identifier.c:3: Warning: Implicit 'int' is an obsolete feature
bug1889-missing-identifier.c:4: Error: Identifier expected
bug1889-missing-identifier.c:4: Warning: Implicit 'int' is an obsolete feature

View File

@ -1,5 +1,5 @@
custom-reference-error.c:18: Error: Call to undeclared function 'printf'
custom-reference-error.c:19: Error: Undefined symbol: 'n'
custom-reference-error.c:19: Error: Undeclared identifier 'n'
custom-reference-error.c:21: Warning: Control reaches end of non-void function [-Wreturn-type]
custom-reference-error.c:21: Warning: Parameter 'argc' is never used
custom-reference-error.c:21: Warning: Parameter 'argv' is never used