1
0
mirror of https://github.com/cc65/cc65.git synced 2024-06-04 18:29:31 +00:00

Fixed missing diagnostics on empty enum/struct/union declareations without tag names.

Improved error recovery with local declarations and _Static_assert.
This commit is contained in:
acqn 2023-12-10 15:43:24 +08:00
parent a16a6298e2
commit 79b4690077
9 changed files with 514 additions and 357 deletions

View File

@ -79,7 +79,6 @@
static void Parse (void)
/* Top level parser routine. */
{
int comma;
SymEntry* Sym;
FuncDesc* FuncDef = 0;
@ -94,8 +93,8 @@ static void Parse (void)
while (CurTok.Tok != TOK_CEOF) {
DeclSpec Spec;
int Comma;
int NeedClean = 0;
unsigned PrevErrorCount = ErrorCount;
/* Check for empty statements */
if (CurTok.Tok == TOK_SEMI) {
@ -119,7 +118,7 @@ static void Parse (void)
continue;
}
/* Read variable defs and functions */
/* Read the declaration specifier */
ParseDeclSpec (&Spec, TS_DEFAULT_TYPE_INT, SC_EXTERN | SC_STATIC);
/* Don't accept illegal storage classes */
@ -139,17 +138,19 @@ static void Parse (void)
}
/* Read declarations for this type */
Sym = 0;
comma = 0;
Comma = 0;
while (1) {
Declarator Decl;
Sym = 0;
/* Read the next declaration */
NeedClean = ParseDecl (&Spec, &Decl, DM_NEED_IDENT);
if (Decl.Ident[0] == '\0') {
Sym = 0;
goto NextDecl;
NeedClean = ParseDecl (&Spec, &Decl, DM_IDENT_OR_EMPTY);
/* Bail out if there are errors */
if (NeedClean <= 0) {
break;
}
/* Check if we must reserve storage for the variable. We do this,
@ -191,10 +192,6 @@ static void Parse (void)
FuncDef->Flags = (FuncDef->Flags & ~FD_EMPTY) | FD_VOID_PARAM;
}
} else {
if (CurTok.Tok != TOK_COMMA && CurTok.Tok != TOK_SEMI) {
Error ("Expected ',' or ';' after top level declarator");
}
/* Just a declaration */
Decl.StorageClass |= SC_DECL;
}
@ -317,50 +314,49 @@ static void Parse (void)
}
NextDecl:
/* Check for end of declaration list */
if (CurTok.Tok == TOK_COMMA) {
NextToken ();
comma = 1;
} else {
if (CurTok.Tok != TOK_COMMA) {
break;
}
Comma = 1;
Spec.Flags |= DS_NO_EMPTY_DECL;
NextToken ();
}
/* Finish the declaration */
if (Sym) {
/* Function definition? */
if (IsTypeFunc (Sym->Type) && CurTok.Tok == TOK_LCURLY) {
if (IsTypeFunc (Spec.Type) && TypeCmp (Sym->Type, Spec.Type).C >= TC_EQUAL) {
/* ISO C: The type category in a function definition cannot be
** inherited from a typedef.
*/
Error ("Function cannot be defined with a typedef");
} else if (comma) {
/* ISO C: A function definition cannot shall its return type
** specifier with other declarators.
*/
Error ("';' expected after top level declarator");
}
if (Sym && IsTypeFunc (Sym->Type) && CurTok.Tok == TOK_LCURLY) {
/* A function definition is not terminated with a semicolon */
if (IsTypeFunc (Spec.Type) && TypeCmp (Sym->Type, Spec.Type).C >= TC_EQUAL) {
/* ISO C: The type category in a function definition cannot be
** inherited from a typedef.
*/
Error ("Function cannot be defined with a typedef");
} else if (Comma) {
/* ISO C: A function definition cannot shall its return type
** specifier with other declarators.
*/
Error ("';' expected after top level declarator");
}
/* Parse the function body anyways */
NeedClean = 0;
NewFunc (Sym, FuncDef);
/* Parse the function body anyways */
NeedClean = 0;
NewFunc (Sym, FuncDef);
/* Make sure we aren't omitting any work */
CheckDeferredOpAllDone ();
/* Make sure we aren't omitting any work */
CheckDeferredOpAllDone ();
} else if (NeedClean > 0) {
/* Must be followed by a semicolon */
if (CurTok.Tok != TOK_SEMI) {
Error ("',' or ';' expected after top level declarator");
NeedClean = -1;
} else {
/* Must be followed by a semicolon */
if (ConsumeSemi ()) {
NeedClean = 0;
} else {
NeedClean = -1;
}
NextToken ();
NeedClean = 0;
}
}
/* Try some smart error recovery */
if (PrevErrorCount != ErrorCount && NeedClean < 0) {
if (NeedClean < 0) {
SmartErrorSkip (1);
}
}

View File

@ -72,7 +72,7 @@
static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSpecified);
static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags);
/* Parse a type specifier */
@ -83,125 +83,6 @@ 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 (int WholeDecl)
/* Try some smart error recovery.
**
** - If WholeDecl is 0:
** Skip tokens until a comma or closing curly brace that is not enclosed in
** an open parenthesis/bracket/curly brace, or until a semicolon, EOF or
** unpaired right parenthesis/bracket/curly brace is reached.
**
** - If WholeDecl is non-0:
** Skip tokens until a closing curly brace that is not enclosed in an open
** parenthesis/bracket/curly brace, or until a semicolon or EOF is reached.
**
** Return 0 if this exits as soon as it reaches an EOF. Return 0 as well if
** this exits with no open parentheses/brackets/curly braces. Otherwise, return
** -1.
*/
{
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) < 0) {
if (!WholeDecl) {
Res = -1;
goto ExitPoint;
}
NextToken ();
}
break;
case TOK_RCURLY:
if (CloseBrace (&C, CurTok.Tok) < 0) {
if (!WholeDecl) {
Res = -1;
goto ExitPoint;
}
NextToken ();
} else if (CollCount (&C) == 0) {
goto ExitPoint;
}
break;
case TOK_COMMA:
if (CollCount (&C) == 0 && !WholeDecl) {
goto ExitPoint;
}
NextToken ();
break;
case TOK_SEMI:
if (CollCount (&C) != 0) {
Res = -1;
}
goto ExitPoint;
case TOK_CEOF:
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 */
{
@ -451,20 +332,36 @@ static void OptionalInt (void)
static void OptionalSigned (int* SignednessSpecified)
static void OptionalSigned (DeclSpec* Spec)
/* Eat an optional "signed" token */
{
if (CurTok.Tok == TOK_SIGNED) {
/* Skip it */
NextToken ();
if (SignednessSpecified != NULL) {
*SignednessSpecified = 1;
if (Spec != NULL) {
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
}
}
}
static void UseDefaultType (DeclSpec* Spec, typespec_t TSFlags)
/* Use the default type for the type specifier */
{
if ((TSFlags & TS_MASK_DEFAULT_TYPE) == TS_DEFAULT_TYPE_NONE) {
Spec->Flags |= DS_NO_TYPE;
Spec->Type[0].C = T_INT;
Spec->Type[1].C = T_END;
} else {
Spec->Flags |= DS_DEF_TYPE;
Spec->Type[0].C = T_INT;
Spec->Type[1].C = T_END;
}
}
static void InitDeclSpec (DeclSpec* Spec)
/* Initialize the DeclSpec struct for use */
{
@ -1080,7 +977,6 @@ static SymEntry* ParseUnionSpec (const char* Name, unsigned* DSFlags)
/* Get the type of the entry */
DeclSpec Spec;
int SignednessSpecified = 0;
int NeedClean = 0;
/* Check for a _Static_assert */
@ -1090,7 +986,17 @@ static SymEntry* ParseUnionSpec (const char* Name, unsigned* DSFlags)
}
InitDeclSpec (&Spec);
ParseTypeSpec (&Spec, TS_DEFAULT_TYPE_NONE, &SignednessSpecified);
ParseTypeSpec (&Spec, TS_DEFAULT_TYPE_NONE);
/* Check if this is only a type declaration */
if (CurTok.Tok == TOK_SEMI && (Spec.Flags & DS_EXTRA_TYPE) == 0) {
CheckEmptyDecl (&Spec);
NextToken ();
continue;
}
/* Allow anonymous bit-fields */
Spec.Flags |= DS_ALLOW_BITFIELD;
/* Read fields with this type */
while (1) {
@ -1098,7 +1004,12 @@ static SymEntry* ParseUnionSpec (const char* Name, unsigned* DSFlags)
Declarator Decl;
/* Get type and name of the struct field */
NeedClean = ParseDecl (&Spec, &Decl, DM_ACCEPT_IDENT);
NeedClean = ParseDecl (&Spec, &Decl, DM_IDENT_OR_EMPTY);
/* Bail out if there are errors */
if (NeedClean <= 0) {
break;
}
/* Check for a bit-field declaration */
FieldWidth = ParseFieldWidth (&Decl);
@ -1123,9 +1034,7 @@ static SymEntry* ParseUnionSpec (const char* Name, unsigned* DSFlags)
Decl.Type[0].C &= ~T_QUAL_CVR;
}
} else {
/* A non bit-field without a name is legal but useless */
Warning ("Declaration does not declare anything");
/* Invalid member */
goto NextMember;
}
} else if (FieldWidth > 0) {
@ -1160,7 +1069,7 @@ static SymEntry* ParseUnionSpec (const char* Name, unsigned* DSFlags)
** bit-field.
*/
AddBitField (Decl.Ident, Decl.Type, 0, 0, FieldWidth,
SignednessSpecified);
(Spec.Flags & DS_EXPLICIT_SIGNEDNESS) != 0);
} else if (Decl.Ident[0] != '\0') {
/* Add the new field to the table */
Field = AddLocalSym (Decl.Ident, Decl.Type, SC_STRUCTFIELD, 0);
@ -1189,17 +1098,22 @@ static SymEntry* ParseUnionSpec (const char* Name, unsigned* DSFlags)
}
}
NextMember: if (CurTok.Tok != TOK_COMMA) {
NextMember:
/* Check for end of declaration list */
if (CurTok.Tok != TOK_COMMA) {
break;
}
Spec.Flags |= DS_NO_EMPTY_DECL;
NextToken ();
}
/* Must be followed by a semicolon */
if (NeedClean >= 0 && ConsumeSemi ()) {
NeedClean = 0;
} else {
NeedClean = -1;
if (NeedClean > 0) {
/* Must be followed by a semicolon */
if (ConsumeSemi ()) {
NeedClean = 0;
} else {
NeedClean = -1;
}
}
/* Try some smart error recovery */
@ -1265,7 +1179,6 @@ static SymEntry* ParseStructSpec (const char* Name, unsigned* DSFlags)
/* Get the type of the entry */
DeclSpec Spec;
int SignednessSpecified = 0;
int NeedClean = 0;
/* Check for a _Static_assert */
@ -1275,7 +1188,17 @@ static SymEntry* ParseStructSpec (const char* Name, unsigned* DSFlags)
}
InitDeclSpec (&Spec);
ParseTypeSpec (&Spec, TS_DEFAULT_TYPE_NONE, &SignednessSpecified);
ParseTypeSpec (&Spec, TS_DEFAULT_TYPE_NONE);
/* Check if this is only a type declaration */
if (CurTok.Tok == TOK_SEMI && (Spec.Flags & DS_EXTRA_TYPE) == 0) {
CheckEmptyDecl (&Spec);
NextToken ();
continue;
}
/* Allow anonymous bit-fields */
Spec.Flags |= DS_ALLOW_BITFIELD;
/* Read fields with this type */
while (1) {
@ -1291,7 +1214,12 @@ static SymEntry* ParseStructSpec (const char* Name, unsigned* DSFlags)
}
/* Get type and name of the struct field */
NeedClean = ParseDecl (&Spec, &Decl, DM_ACCEPT_IDENT);
NeedClean = ParseDecl (&Spec, &Decl, DM_IDENT_OR_EMPTY);
/* Bail out if there are errors */
if (NeedClean <= 0) {
break;
}
/* Check for a bit-field declaration */
FieldWidth = ParseFieldWidth (&Decl);
@ -1335,9 +1263,7 @@ static SymEntry* ParseStructSpec (const char* Name, unsigned* DSFlags)
Decl.Type[0].C &= ~T_QUAL_CVR;
}
} else {
/* A non bit-field without a name is legal but useless */
Warning ("Declaration does not declare anything");
/* Invalid member */
goto NextMember;
}
} else if (FieldWidth > 0) {
@ -1387,8 +1313,8 @@ static SymEntry* ParseStructSpec (const char* Name, unsigned* DSFlags)
** bit-field as a char type in expressions.
*/
CHECK (BitOffs < CHAR_BITS);
AddBitField (Decl.Ident, Decl.Type, StructSize, BitOffs,
FieldWidth, SignednessSpecified);
AddBitField (Decl.Ident, Decl.Type, StructSize, BitOffs, FieldWidth,
(Spec.Flags & DS_EXPLICIT_SIGNEDNESS) != 0);
BitOffs += FieldWidth;
CHECK (BitOffs <= CHAR_BITS * SizeOf (Decl.Type));
/* Add any full bytes to the struct size */
@ -1427,17 +1353,22 @@ static SymEntry* ParseStructSpec (const char* Name, unsigned* DSFlags)
}
}
NextMember: if (CurTok.Tok != TOK_COMMA) {
NextMember:
/* Check for end of declaration list */
if (CurTok.Tok != TOK_COMMA) {
break;
}
Spec.Flags |= DS_NO_EMPTY_DECL;
NextToken ();
}
/* Must be followed by a semicolon */
if (NeedClean >= 0 && ConsumeSemi ()) {
NeedClean = 0;
} else {
NeedClean = -1;
if (NeedClean > 0) {
/* Must be followed by a semicolon */
if (ConsumeSemi ()) {
NeedClean = 0;
} else {
NeedClean = -1;
}
}
/* Try some smart error recovery */
@ -1472,7 +1403,7 @@ NextMember: if (CurTok.Tok != TOK_COMMA) {
static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSpecified)
static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags)
/* Parse a type specifier. Store whether one of "signed" or "unsigned" was
** specified, so bit-fields of unspecified signedness can be treated as
** unsigned; without special handling, it would be treated as signed.
@ -1482,10 +1413,6 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
SymEntry* TagEntry;
TypeCode Qualifiers = T_QUAL_NONE;
if (SignednessSpecified != NULL) {
*SignednessSpecified = 0;
}
/* Assume we have an explicit type */
Spec->Flags &= ~DS_DEF_TYPE;
@ -1511,15 +1438,13 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
case TOK_LONG:
NextToken ();
if (CurTok.Tok == TOK_UNSIGNED) {
if (SignednessSpecified != NULL) {
*SignednessSpecified = 1;
}
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
NextToken ();
OptionalInt ();
Spec->Type[0].C = T_ULONG;
Spec->Type[1].C = T_END;
} else {
OptionalSigned (SignednessSpecified);
OptionalSigned (Spec);
OptionalInt ();
Spec->Type[0].C = T_LONG;
Spec->Type[1].C = T_END;
@ -1529,15 +1454,13 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
case TOK_SHORT:
NextToken ();
if (CurTok.Tok == TOK_UNSIGNED) {
if (SignednessSpecified != NULL) {
*SignednessSpecified = 1;
}
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
NextToken ();
OptionalInt ();
Spec->Type[0].C = T_USHORT;
Spec->Type[1].C = T_END;
} else {
OptionalSigned (SignednessSpecified);
OptionalSigned (Spec);
OptionalInt ();
Spec->Type[0].C = T_SHORT;
Spec->Type[1].C = T_END;
@ -1550,10 +1473,8 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
Spec->Type[1].C = T_END;
break;
case TOK_SIGNED:
if (SignednessSpecified != NULL) {
*SignednessSpecified = 1;
}
case TOK_SIGNED:
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
NextToken ();
switch (CurTok.Tok) {
@ -1589,9 +1510,7 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
break;
case TOK_UNSIGNED:
if (SignednessSpecified != NULL) {
*SignednessSpecified = 1;
}
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
NextToken ();
switch (CurTok.Tok) {
@ -1640,12 +1559,16 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
case TOK_UNION:
NextToken ();
/* */
/* Check for tag name */
if (CurTok.Tok == TOK_IDENT) {
strcpy (Ident, CurTok.Ident);
NextToken ();
} else {
} else if (CurTok.Tok == TOK_LCURLY) {
AnonName (Ident, "union");
} else {
Error ("Tag name identifier or '{' expected");
UseDefaultType (Spec, TS_DEFAULT_TYPE_NONE);
break;
}
/* Remember we have an extra type decl */
Spec->Flags |= DS_EXTRA_TYPE;
@ -1659,12 +1582,16 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
case TOK_STRUCT:
NextToken ();
/* */
/* Check for tag name */
if (CurTok.Tok == TOK_IDENT) {
strcpy (Ident, CurTok.Ident);
NextToken ();
} else {
} else if (CurTok.Tok == TOK_LCURLY) {
AnonName (Ident, "struct");
} else {
Error ("Tag name identifier or '{' expected");
UseDefaultType (Spec, TS_DEFAULT_TYPE_NONE);
break;
}
/* Remember we have an extra type decl */
Spec->Flags |= DS_EXTRA_TYPE;
@ -1678,15 +1605,16 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
case TOK_ENUM:
NextToken ();
/* Named enum */
/* Check for tag name */
if (CurTok.Tok == TOK_IDENT) {
strcpy (Ident, CurTok.Ident);
NextToken ();
} else {
if (CurTok.Tok != TOK_LCURLY) {
Error ("Identifier expected for enum tag name");
}
} else if (CurTok.Tok == TOK_LCURLY) {
AnonName (Ident, "enum");
} else {
Error ("Tag name identifier or '{' expected");
UseDefaultType (Spec, TS_DEFAULT_TYPE_NONE);
break;
}
/* Remember we have an extra type decl */
Spec->Flags |= DS_EXTRA_TYPE;
@ -1699,9 +1627,7 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
/* The signedness of enums is determined by the type, so say this is specified to avoid
** the int -> unsigned int handling for plain int bit-fields in AddBitField.
*/
if (SignednessSpecified) {
*SignednessSpecified = 1;
}
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
break;
case TOK_IDENT:
@ -1718,9 +1644,7 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
** Unforunately, this will cause plain int bit-fields defined via typedefs
** to be treated as signed rather than unsigned.
*/
if (SignednessSpecified) {
*SignednessSpecified = 1;
}
Spec->Flags |= DS_EXPLICIT_SIGNEDNESS;
break;
} else if ((TSFlags & TS_MASK_DEFAULT_TYPE) == TS_DEFAULT_TYPE_NONE) {
/* Treat this identifier as an unknown type */
@ -1742,15 +1666,7 @@ static void ParseTypeSpec (DeclSpec* Spec, typespec_t TSFlags, int* SignednessSp
/* FALL THROUGH */
default:
if ((TSFlags & TS_MASK_DEFAULT_TYPE) == TS_DEFAULT_TYPE_NONE) {
Spec->Flags |= DS_NO_TYPE;
Spec->Type[0].C = T_INT;
Spec->Type[1].C = T_END;
} else {
Spec->Flags |= DS_DEF_TYPE;
Spec->Type[0].C = T_INT;
Spec->Type[1].C = T_END;
}
UseDefaultType (Spec, TSFlags);
break;
}
@ -1839,6 +1755,9 @@ static void ParseOldStyleParamList (FuncDesc* F)
/* Read the declaration specifier */
ParseDeclSpec (&Spec, TS_DEFAULT_TYPE_NONE, SC_AUTO);
/* Paremeters must have identifiers as names */
Spec.Flags |= DS_NO_EMPTY_DECL;
/* We accept only auto and register as storage class specifiers, but
** we ignore all this, since we use auto anyway.
*/
@ -1859,7 +1778,7 @@ static void ParseOldStyleParamList (FuncDesc* F)
Declarator Decl;
/* Read the parameter */
ParseDecl (&Spec, &Decl, DM_NEED_IDENT);
ParseDecl (&Spec, &Decl, DM_IDENT_OR_EMPTY);
/* Warn about new local type declaration */
if ((Spec.Flags & DS_NEW_TYPE_DECL) != 0) {
@ -2083,7 +2002,7 @@ static FuncDesc* ParseFuncDecl (void)
static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
static void DirectDecl (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
@ -2101,19 +2020,19 @@ static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mo
NextToken ();
/* A pointer type cannot be used as an empty declaration */
if (Mode == DM_ACCEPT_IDENT) {
Mode = DM_NEED_IDENT;
if (Mode == DM_IDENT_OR_EMPTY) {
Spec->Flags |= DS_NO_EMPTY_DECL;
}
/* Allow const, restrict, and volatile qualifiers */
Qualifiers |= OptionalQualifiers (Qualifiers, T_QUAL_CVR);
/* Parse the type that the pointer points to */
Mode = DirectDecl (Spec, D, Mode);
DirectDecl (Spec, D, Mode);
/* Add the type */
AddTypeCodeToDeclarator (D, T_PTR | Qualifiers);
return Mode;
return;
}
if (CurTok.Tok == TOK_LPAREN) {
@ -2121,28 +2040,24 @@ static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mo
/* An empty declaration cannot contain parentheses where an identifier
** would show up if it were a non-empty declaration.
*/
if (Mode == DM_ACCEPT_IDENT) {
Mode = DM_NEED_IDENT;
if (Mode == DM_IDENT_OR_EMPTY) {
Spec->Flags |= DS_NO_EMPTY_DECL;
}
Mode = DirectDecl (Spec, D, 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) {
if ((Spec->Flags & DS_NO_EMPTY_DECL) != 0 &&
CurTok.Tok != TOK_LBRACK &&
((Spec->Flags & DS_ALLOW_BITFIELD) == 0 || CurTok.Tok != TOK_COLON)) {
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 */
@ -2181,6 +2096,18 @@ static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mo
/* Array declarator */
long Size = UNSPECIFIED;
/* An array type cannot be used as an empty declaration */
if (Mode == DM_IDENT_OR_EMPTY) {
Spec->Flags |= DS_NO_EMPTY_DECL;
if (D->Ident[0] == '\0') {
if ((Spec->Flags & DS_DEF_TYPE) == 0) {
Error ("Identifier or ';' expected after declaration specifiers");
} else {
Error ("Identifier expected");
}
}
}
/* We cannot have any qualifiers for an array */
if (Qualifiers != T_QUAL_NONE) {
Error ("Invalid qualifiers for array");
@ -2228,8 +2155,6 @@ static declmode_t DirectDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mo
if (Qualifiers & T_QUAL_CDECL) {
Error ("Invalid '__cdecl__' qualifier");
}
return Mode;
}
@ -2248,7 +2173,7 @@ Type* ParseType (Type* T)
/* Get a type without a default */
InitDeclSpec (&Spec);
ParseTypeSpec (&Spec, TS_DEFAULT_TYPE_NONE, NULL);
ParseTypeSpec (&Spec, TS_DEFAULT_TYPE_NONE);
/* Parse additional declarators */
ParseDecl (&Spec, &Decl, DM_NO_IDENT);
@ -2262,14 +2187,23 @@ Type* ParseType (Type* T)
int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
int ParseDecl (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.
** an unpaired right parenthesis/bracket/curly brace. Return 0 if this stops
** after consuming a semicolon or closing curly brace, or reaching an EOF.
** Return 1 otherwise.
*/
{
/* Used to check if we have any errors during parsing this */
unsigned PrevErrorCount = ErrorCount;
/* If there is no explicit type specifier, an optional identifier becomes
** required.
*/
if (Mode == DM_IDENT_OR_EMPTY && (Spec->Flags & DS_DEF_TYPE) != 0) {
Spec->Flags |= DS_NO_EMPTY_DECL;
}
/* Initialize the Declarator struct */
InitDeclarator (D);
@ -2283,6 +2217,11 @@ int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* Use the storage class from the declspec */
D->StorageClass = Spec->StorageClass;
/* If we have a function, add a special symbol type */
if (IsTypeFunc (D->Type)) {
D->StorageClass |= SC_FUNC;
}
/* Do several fixes on qualifiers */
FixQualifiers (D->Type);
@ -2297,24 +2236,6 @@ int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* Parse attributes for this declarator */
ParseAttribute (D);
/* If we have a function, add a special storage class */
if (IsTypeFunc (D->Type)) {
D->StorageClass |= SC_FUNC;
} else if (!IsTypeVoid (D->Type)) {
/* Check the size of the generated type */
unsigned Size = SizeOf (D->Type);
if (Size >= 0x10000) {
if (D->Ident[0] != '\0') {
Error ("Size of '%s' is invalid (0x%06X)", D->Ident, Size);
} else {
Error ("Invalid size in declaration (0x%06X)", Size);
}
}
}
/* Check a few pre-C99 things */
if (D->Ident[0] != '\0' && (Spec->Flags & DS_DEF_TYPE) != 0) {
/* Check and warn about an implicit int return in the function */
@ -2331,7 +2252,7 @@ int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
/* For anything that is not a function or typedef, check for an implicit
** int declaration.
*/
if ((D->StorageClass & SC_FUNC) != SC_FUNC &&
if (!IsTypeFunc (D->Type) &&
(D->StorageClass & SC_TYPEMASK) != SC_TYPEDEF) {
/* If the standard was not set explicitly to C89, print a warning
** for variables with implicit int type.
@ -2342,11 +2263,32 @@ int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
}
}
if (PrevErrorCount != ErrorCount) {
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;
/* Check the size of the declared type */
if (IsObjectType (D->Type)) {
unsigned Size = SizeOf (D->Type);
if (Size >= 0x10000) {
if (D->Ident[0] != '\0') {
Error ("Size of '%s' is too large (0x%06X)", D->Ident, Size);
} else {
Error ("Size in declaration is too large (0x%06X)", Size);
}
}
}
/* An empty declaration must be terminated with a semicolon */
if (PrevErrorCount == ErrorCount &&
Mode == DM_IDENT_OR_EMPTY &&
D->Ident[0] == '\0' &&
CurTok.Tok != TOK_SEMI &&
((Spec->Flags & DS_ALLOW_BITFIELD) == 0 || CurTok.Tok != TOK_COLON)) {
Error ("Identifier or ';' expected after declaration specifiers");
}
if (PrevErrorCount != ErrorCount) {
if ((Spec->Flags & DS_DEF_TYPE) == 0 &&
(Spec->Flags & DS_NO_EMPTY_DECL) != 0 &&
D->Ident[0] == '\0') {
/* Use a fictitious name for the identifier if it is missing */
const char* Level = "";
@ -2366,15 +2308,21 @@ int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode)
break;
}
AnonName (D->Ident, Level);
/* Make the declarator fictitious */
D->StorageClass |= SC_FICTITIOUS;
}
/* Try some smart error recovery */
if (CurTok.Tok != TOK_LCURLY || !IsTypeFunc (D->Type)) {
return SmartErrorSkip (0);
/* Skip to the end of the whole declaration if it is not part of a
** parameter list or a type cast.
*/
return SmartErrorSkip (Mode == DM_IDENT_OR_EMPTY);
}
}
return 0;
return 1;
}
@ -2389,7 +2337,7 @@ void ParseDeclSpec (DeclSpec* Spec, typespec_t TSFlags, unsigned DefStorage)
Spec->Flags &= ~DS_DEF_STORAGE;
/* Parse the type specifiers */
ParseTypeSpec (Spec, TSFlags | TS_STORAGE_CLASS_SPEC | TS_FUNCTION_SPEC, NULL);
ParseTypeSpec (Spec, TSFlags | TS_STORAGE_CLASS_SPEC | TS_FUNCTION_SPEC);
/* If no explicit storage class is given, use the default */
if (Spec->StorageClass == 0) {
@ -2407,6 +2355,10 @@ void CheckEmptyDecl (const DeclSpec* Spec)
*/
{
if ((Spec->Flags & DS_EXTRA_TYPE) == 0) {
Warning ("Useless declaration");
Warning ("Declaration does not declare anything");
} else if (IsClassStruct (Spec->Type) &&
!IsIncompleteESUType (Spec->Type) &&
SymHasAnonName (GetESUTagSym (Spec->Type))) {
Warning ("Unnamed %s that defines no instances", GetBasicTypeName (Spec->Type));
}
}

View File

@ -77,6 +77,11 @@ enum typespec_t {
#define DS_NEW_TYPE_DECL 0x0010U /* New type declared */
#define DS_NEW_TYPE_DEF 0x0020U /* New type defined */
#define DS_NEW_TYPE (DS_NEW_TYPE_DECL | DS_NEW_TYPE_DEF)
#define DS_EXPLICIT_SIGNEDNESS 0x0040U /* Signedness specified */
#define DS_NO_EMPTY_DECL 0x0100U /* Disallow empty declaration */
#define DS_ALLOW_BITFIELD 0x0200U /* Allow anonymous bit-fields */
/* Result of ParseDeclSpec */
typedef struct DeclSpec DeclSpec;
@ -99,24 +104,22 @@ struct Declarator {
};
/* Modes for ParseDecl:
** - DM_NEED_IDENT means:
** we *must* have a type and a variable identifer.
** - DM_IDENT_OR_EMPTY means:
** we *may* have an identifier, or none. If it is the latter case,
** the type specifier must be used for an empty declaration,
** or it is an error.
** - 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:
** Note: this is used for type names.
** - DM_ACCEPT_PARAM_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,
DM_IDENT_OR_EMPTY,
DM_NO_IDENT,
DM_ACCEPT_IDENT,
DM_ACCEPT_PARAM_IDENT,
} declmode_t;
@ -128,29 +131,14 @@ typedef enum {
int SmartErrorSkip (int WholeDecl);
/* Try some smart error recovery.
**
** - If WholeDecl is 0:
** Skip tokens until a comma or closing curly brace that is not enclosed in
** an open parenthesis/bracket/curly brace, or until a semicolon, EOF or
** unpaired right parenthesis/bracket/curly brace is reached.
**
** - If WholeDecl is non-0:
** Skip tokens until a closing curly brace that is not enclosed in an open
** parenthesis/bracket/curly brace, or until a semicolon or EOF is reached.
**
** Return 0 if this exits as soon as it reaches an EOF. Return 0 as well if
** this exits with no open parentheses/brackets/curly braces. Otherwise, return
** -1.
*/
Type* ParseType (Type* Type);
/* Parse a complete type specification */
int ParseDecl (const DeclSpec* Spec, Declarator* D, declmode_t Mode);
int ParseDecl (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.
** an unpaired right parenthesis/bracket/curly brace. Return 0 if this stops
** after consuming a semicolon or closing curly brace, or reaching an EOF.
** Return 1 otherwise.
*/
void ParseDeclSpec (DeclSpec* Spec, typespec_t TSFlags, unsigned DefStorage);

View File

@ -1422,7 +1422,7 @@ static void Primary (ExprDesc* E)
Declarator Decl;
/* Parse one declaration */
ParseDecl (&Spec, &Decl, DM_ACCEPT_IDENT);
ParseDecl (&Spec, &Decl, DM_IDENT_OR_EMPTY);
if (CurTok.Tok == TOK_ASSIGN) {
NextToken ();
ParseInit (Decl.Type);

View File

@ -441,14 +441,15 @@ static void ParseStaticDecl (Declarator* Decl)
static void ParseOneDecl (const DeclSpec* Spec)
static int ParseOneDecl (DeclSpec* Spec)
/* Parse one variable declarator. */
{
Declarator Decl; /* Declarator data structure */
Declarator Decl; /* Declarator data structure */
int NeedClean;
/* Read the declarator */
ParseDecl (Spec, &Decl, DM_NEED_IDENT);
NeedClean = ParseDecl (Spec, &Decl, DM_IDENT_OR_EMPTY);
/* Check if there are any non-extern storage classes set for function
** declarations. Function can only be declared inside functions with the
@ -538,6 +539,8 @@ static void ParseOneDecl (const DeclSpec* Spec)
/* Make sure we aren't missing some work */
CheckDeferredOpAllDone ();
return NeedClean;
}
@ -553,15 +556,8 @@ void DeclareLocals (void)
/* Loop until we don't find any more variables */
while (1) {
/* Check variable declarations. We need to distinguish between a
** default int type and the end of variable declarations. So we
** will do the following: If there is no explicit storage class
** specifier *and* no explicit type given, *and* no type qualifiers
** have been read, it is assumed that we have reached the end of
** declarations.
*/
DeclSpec Spec;
int NeedClean;
/* Check for a _Static_assert */
if (CurTok.Tok == TOK_STATIC_ASSERT) {
@ -569,10 +565,18 @@ void DeclareLocals (void)
continue;
}
/* Read the declaration specifier */
ParseDeclSpec (&Spec, TS_DEFAULT_TYPE_INT, SC_AUTO);
if ((Spec.Flags & DS_DEF_STORAGE) != 0 && /* No storage spec */
(Spec.Flags & DS_DEF_TYPE) != 0 && /* No type given */
GetQualifier (Spec.Type) == T_QUAL_NONE) { /* No type qualifier */
/* Check variable declarations. We need distinguish between a default
** int type and the end of variable declarations. So we will do the
** following: If there is no explicit storage class specifier *and* no
** explicit type given, *and* no type qualifiers have been read, it is
** assumed that we have reached the end of declarations.
*/
if ((Spec.Flags & DS_DEF_STORAGE) != 0 && /* No storage spec */
(Spec.Flags & DS_DEF_TYPE) == DS_DEF_TYPE && /* No type given */
GetQualifier (Spec.Type) == T_QUAL_NONE) { /* No type qualifier */
break;
}
@ -587,8 +591,11 @@ void DeclareLocals (void)
/* Parse a comma separated variable list */
while (1) {
/* Parse one declaration */
ParseOneDecl (&Spec);
/* Parse one declarator */
NeedClean = ParseOneDecl (&Spec);
if (NeedClean <= 0) {
break;
}
/* Check if there is more */
if (CurTok.Tok == TOK_COMMA) {
@ -600,8 +607,19 @@ void DeclareLocals (void)
}
}
/* A semicolon must follow */
ConsumeSemi ();
if (NeedClean > 0) {
/* Must be followed by a semicolon */
if (ConsumeSemi ()) {
NeedClean = 0;
} else {
NeedClean = -1;
}
}
/* Try some smart error recovery */
if (NeedClean < 0) {
SmartErrorSkip (1);
}
}
/* Be sure to allocate any reserved space for locals */

View File

@ -1235,6 +1235,163 @@ void SkipTokens (const token_t* TokenList, unsigned TokenCount)
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 void PopBrace (Collection* C)
/* Close the latest open parenthesis/bracket/curly brace */
{
if (CollCount (C) > 0) {
CollPop (C);
}
}
static int CloseBrace (Collection* C, token_t Tok)
/* Consume a closing parenthesis/bracket/curly brace if it is matched with an
** opening one to close 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 (int TillEnd)
/* Try some smart error recovery.
**
** - If TillEnd == 0:
** Skip tokens until a comma or closing curly brace that is not enclosed in
** an open parenthesis/bracket/curly brace, or until a semicolon, EOF or
** unpaired right parenthesis/bracket/curly brace is reached. The closing
** curly brace is consumed in the former case.
**
** - If TillEnd != 0:
** Skip tokens until a right curly brace or semicolon is reached and consumed
** while there are no open parentheses/brackets/curly braces, or until an EOF
** is reached anytime. Any open parenthesis/bracket/curly brace is considered
** to be closed by consuming a right parenthesis/bracket/curly brace even if
** they didn't match.
**
** - Return -1:
** If this exits at a semicolon or unpaired right parenthesis/bracket/curly
** brace while there are still open parentheses/brackets/curly braces.
**
** - Return 0:
** If this exits as soon as it reaches an EOF;
** Or if this exits right after consuming a semicolon or right curly brace
** while there are no open parentheses/brackets/curly braces.
**
** - Return 1:
** If this exits at a non-EOF without consuming it.
*/
{
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) < 0) {
if (!TillEnd) {
Res = -1;
goto ExitPoint;
}
PopBrace (&C);
NextToken ();
}
break;
case TOK_RCURLY:
if (CloseBrace (&C, CurTok.Tok) < 0) {
if (!TillEnd) {
Res = -1;
goto ExitPoint;
}
PopBrace (&C);
NextToken ();
}
if (CollCount (&C) == 0) {
/* We consider this as a terminator as well */
Res = 0;
goto ExitPoint;
}
break;
case TOK_COMMA:
if (CollCount (&C) == 0 && !TillEnd) {
Res = 1;
goto ExitPoint;
}
NextToken ();
break;
case TOK_SEMI:
if (CollCount (&C) == 0) {
if (TillEnd) {
NextToken ();
Res = 0;
} else {
Res = 1;
}
goto ExitPoint;
}
NextToken ();
break;
case TOK_CEOF:
/* We cannot consume this */
Res = 0;
goto ExitPoint;
default:
Internal ("Unexpected token: %02X", (unsigned)CurTok.Tok);
}
}
ExitPoint:
DoneCollection (&C);
return Res;
}
int Consume (token_t Token, const char* ErrorMsg)
/* Eat token if it is the next in the input stream, otherwise print an error
** message. Returns true if the token was found and false otherwise.

View File

@ -310,6 +310,35 @@ void SkipTokens (const token_t* TokenList, unsigned TokenCount);
** This routine is used for error recovery.
*/
int SmartErrorSkip (int TillEnd);
/* Try some smart error recovery.
**
** - If TillEnd == 0:
** Skip tokens until a comma or closing curly brace that is not enclosed in
** an open parenthesis/bracket/curly brace, or until a semicolon, EOF or
** unpaired right parenthesis/bracket/curly brace is reached. The closing
** curly brace is consumed in the former case.
**
** - If TillEnd != 0:
** Skip tokens until a right curly brace or semicolon is reached and consumed
** while there are no open parentheses/brackets/curly braces, or until an EOF
** is reached anytime. Any open parenthesis/bracket/curly brace is considered
** to be closed by consuming a right parenthesis/bracket/curly brace even if
** they didn't match.
**
** - Return -1:
** If this exits at a semicolon or unpaired right parenthesis/bracket/curly
** brace while there are still open parentheses/brackets/curly braces.
**
** - Return 0:
** If this exits as soon as it reaches an EOF;
** Or if this exits right after consuming a semicolon or right curly brace
** while there are no open parentheses/brackets/curly braces.
**
** - Return 1:
** If this exits at a non-EOF without consuming it.
*/
int Consume (token_t Token, const char* ErrorMsg);
/* Eat token if it is the next in the input stream, otherwise print an error
** message. Returns true if the token was found and false otherwise.

View File

@ -45,7 +45,7 @@
void ParseStaticAssert ()
void ParseStaticAssert (void)
{
/*
** static_assert-declaration ::=
@ -53,20 +53,23 @@ void ParseStaticAssert ()
** _Static_assert ( constant-expression , string-literal ) ;
*/
ExprDesc Expr;
int failed;
unsigned PrevErrorCount = ErrorCount;
int failed = 0;
/* Skip the _Static_assert token itself */
CHECK (CurTok.Tok == TOK_STATIC_ASSERT);
NextToken ();
/* We expect an opening paren */
if (!ConsumeLParen ()) {
return;
if (ConsumeLParen ()) {
/* Parse assertion condition */
Expr = NoCodeConstAbsIntExpr (hie1);
failed = !Expr.IVal;
}
/* Parse assertion condition */
Expr = NoCodeConstAbsIntExpr (hie1);
failed = !Expr.IVal;
if (PrevErrorCount != ErrorCount) {
goto ExitPoint;
}
/* If there is a comma, we also have an error message. The message is optional because we
** support the C2X syntax with only an expression.
@ -84,19 +87,16 @@ void ParseStaticAssert ()
/* String literal */
if (CurTok.Tok != TOK_SCONST) {
Error ("String literal expected for static_assert message");
return;
}
} else {
/* Issue an error including the message if the static_assert failed. */
if (failed) {
Error ("static_assert failed '%s'", GetLiteralStr (CurTok.SVal));
}
/* Issue an error including the message if the static_assert failed. */
if (failed) {
Error ("static_assert failed '%s'", GetLiteralStr (CurTok.SVal));
}
/* Consume the string constant, now that we don't need it anymore.
** This should never fail since we checked the token type above.
*/
if (!Consume (TOK_SCONST, "String literal expected")) {
return;
/* Consume the string constant, now that we don't need it anymore.
** This should never fail since we checked the token type above.
*/
Consume (TOK_SCONST, "String literal expected");
}
} else {
/* No message. */
@ -105,7 +105,24 @@ void ParseStaticAssert ()
}
}
/* Closing paren and semi-colon needed */
ConsumeRParen ();
ConsumeSemi ();
/* The assertion failure error is not a syntax error */
if (failed) {
++PrevErrorCount;
}
if (PrevErrorCount == ErrorCount) {
/* Closing paren needed */
ConsumeRParen ();
}
if (PrevErrorCount == ErrorCount) {
/* Must be followed by a semicolon */
ConsumeSemi ();
}
ExitPoint:
/* Try some smart error recovery */
if (PrevErrorCount != ErrorCount) {
SmartErrorSkip (1);
}
}

View File

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