diff --git a/src/frontc/cparser.mly b/src/frontc/cparser.mly index 7f6e1f42e..45a3f8d16 100644 --- a/src/frontc/cparser.mly +++ b/src/frontc/cparser.mly @@ -78,6 +78,10 @@ let smooth_expression lst = let currentFunctionName = ref "" +(* Set to true while parsing a typedef declaration's declarator list, so that + declarator_no_init calls add_type (not add_identifier) for typedef names. *) +let is_typedef_decl = ref false + let announceFunctionName ((n, decl, _, _):name) = !Lexerhack.add_identifier n; (* Start a context that includes the parameter names and the whole body. @@ -115,18 +119,18 @@ let applyPointer (ptspecs: attribute list list) (dt: decl_type) loop ptspecs let doDeclaration (loc: cabsloc) (specs: spec_elem list) (nl: init_name list) : definition = - if isTypedef specs then begin - (* Tell the lexer about the new type names *) - List.iter (fun ((n, _, _, _), _) -> !Lexerhack.add_type n) nl; + is_typedef_decl := false; + (* Lexer registrations (add_type / add_identifier) are done per-declarator in + declarator_no_init / declarator_init_start as each declarator is parsed + (C11 6.2.1.7: scope begins just after the completion of the declarator), so + nothing to do here. *) + if isTypedef specs then TYPEDEF ((specs, List.map (fun (n, _) -> n) nl), loc) - end else + else if nl = [] then ONLYTYPEDEF (specs, loc) - else begin - (* Tell the lexer about the new variable names *) - List.iter (fun ((n, _, _, _), _) -> !Lexerhack.add_identifier n) nl; + else DECDEF ((specs, nl), loc) - end let doFunctionDef (loc: cabsloc) @@ -388,7 +392,7 @@ let transformOffsetOf (speclist, dtype) member = %type init_declarator %type init_declarator_list -%type declarator +%type declarator declarator_no_init declarator_init_start %type field_decl %type <(Cabs.name * expression option) list> field_decl_list %type direct_decl @@ -1036,14 +1040,38 @@ init_declarator_attr: ; init_declarator: /* ISO 6.7 */ - declarator { ($1, NO_INIT) } -| declarator EQ init_expression location - { let (n, d, a, l) = $1 in ((n, d, a, joinLoc l $4), $3) } + declarator_no_init { ($1, NO_INIT) } +| declarator_init_start init_expression location + { let (n, d, a, l) = $1 in ((n, d, a, joinLoc l $3), $2) } +; + +/* (* Parses "declarator" (without initializer) and immediately registers the + declared name in the lexer hack, per C11 6.2.1.7 (scope begins just after + the completion of its declarator). + - For non-typedef declarations: calls add_identifier so subsequent + declarators in the same list see the name as a variable (not a type). + - For typedef declarations (is_typedef_decl = true): calls add_type so + subsequent declarators see the name as a type. + In both cases every declarator is registered at the right time without + going through doDeclaration. *) */ +declarator_no_init: + declarator { let (n, _, _, _) = $1 in + if !is_typedef_decl then !Lexerhack.add_type n + else !Lexerhack.add_identifier n; + $1 } +; + +/* (* Parses "declarator =" and adds the declared name as a variable identifier + in the lexer hack, so that in the initializer the name shadows any typedef + with the same name (C11 6.2.1.7: scope begins just after the completion of the + declarator). *) */ +declarator_init_start: + declarator EQ { let (n, _, _, _) = $1 in !Lexerhack.add_identifier n; $1 } ; decl_spec_list_common: /* ISO 6.7 */ /* ISO 6.7.1 */ -| TYPEDEF decl_spec_list_opt { SpecTypedef :: $2, $1 } +| TYPEDEF decl_spec_list_opt { is_typedef_decl := true; SpecTypedef :: $2, $1 } | EXTERN decl_spec_list_opt { SpecStorage EXTERN :: $2, $1 } | STATIC decl_spec_list_opt { SpecStorage STATIC :: $2, $1 } | AUTO decl_spec_list_opt { SpecStorage AUTO :: $2, $1 } diff --git a/test/small1/typedef_varname.c b/test/small1/typedef_varname.c new file mode 100644 index 000000000..3c36dec0a --- /dev/null +++ b/test/small1/typedef_varname.c @@ -0,0 +1,43 @@ +/* Test for typedef and variable name conflict (issue #114). + A variable with the same name as a typedef should shadow it in its initializer, + per C11 6.2.1.7: "Any other identifier has scope that begins just after the + completion of its declarator." */ +#include "testharness.h" +#include +#include + +typedef struct raxNode { + uint32_t iskey:1; + uint32_t isnull:1; + uint32_t iscompr:1; + uint32_t size:29; + unsigned char data[]; +} raxNode; + +typedef struct rax { + raxNode *head; + uint64_t numele; + uint64_t numnodes; +} rax; + +typedef int mytype; + +int main() { + rax *rax = malloc(sizeof(*rax)); /* variable rax shadows typedef rax in initializer */ + if (rax == 0) E(1); + free(rax); + + /* NO_INIT variable in the middle of a declaration list shadows a typedef. + After "mytype a = 1, mytype", the name "mytype" is an identifier (variable). + So "b = sizeof mytype" (without parens) is only valid when mytype is a variable + (types require parens: "sizeof(type)"). This fails to parse without the fix. */ + { + mytype a = 1, mytype, b = sizeof mytype; + mytype = 5; + if (a != 1) E(2); + if (mytype != 5) E(3); + if (b != sizeof(int)) E(4); + } + + SUCCESS; +} diff --git a/test/small1/typedef_varname_noinit.c b/test/small1/typedef_varname_noinit.c new file mode 100644 index 000000000..0d2f0f2f9 --- /dev/null +++ b/test/small1/typedef_varname_noinit.c @@ -0,0 +1,48 @@ +/* Test for typedef/variable name conflict in a declaration list with multiple + initialized declarators. + + Per C11 6.2.1.7, a declared identifier's scope begins just after the completion + of its declarator. For an initialized declarator "T = expr", scope begins just + after the declarator (before the "="), so the initializer of a LATER declarator + in the same list can refer to T as a variable. + + Concretely, after "T T = 1", the name T is registered as a variable (identifier) + by the lexer hack, so that the next initializer "T + 1" correctly sees T as a + variable. + + This test fails to parse (without the fix) because the initializer T + 1 would + see T as a type name (NAMED_TYPE), making "T + 1" a syntax error. */ +#include "testharness.h" + +typedef int T; +typedef int mytype; + +int test_init_chain(void) { + /* "T T = 1": declarator_init_start registers T as an identifier before "= 1". + "U = T + 1": T in the initializer must be the variable T (value 1), not the + typedef. If it's still a type, "T + 1" is a syntax error. */ + { + T T = 1, U = T + 1; + if (T != 1) E(1); + if (U != 2) E(2); + } + return 0; +} + +int test_typedef_varname_init(void) { + /* Original issue: rax-style conflict where a typedef and variable share a name. + The variable's initializer can use the variable name as a regular identifier. */ + { + mytype mytype = 42; + if (mytype != 42) E(3); + (void)mytype; + } + return 0; +} + +int main(void) { + if (test_init_chain()) return 1; + if (test_typedef_varname_init()) return 1; + SUCCESS; +} + diff --git a/test/testcil.pl b/test/testcil.pl index ce9f20818..2dc4b8e8b 100644 --- a/test/testcil.pl +++ b/test/testcil.pl @@ -471,6 +471,8 @@ sub addToGroup { addTest("testrun/for1 "); addTest("testrun/void _GNUCC=1"); addTest("test/voidtypedef "); +addTest("testrun/typedef_varname "); +addTest("testrun/typedef_varname_noinit "); addTest("testrun/wrongnumargs "); addBadComment("testrun/wrongnumargs", "Notbug. Should fail since we don't pad argument lists");