Skip to content
Open
54 changes: 41 additions & 13 deletions src/frontc/cparser.mly
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ let smooth_expression lst =

let currentFunctionName = ref "<outside any function>"

(* 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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -388,7 +392,7 @@ let transformOffsetOf (speclist, dtype) member =

%type <Cabs.init_name> init_declarator
%type <Cabs.init_name list> init_declarator_list
%type <Cabs.name> declarator
%type <Cabs.name> declarator declarator_no_init declarator_init_start
%type <Cabs.name * expression option> field_decl
%type <(Cabs.name * expression option) list> field_decl_list
%type <string * Cabs.decl_type> direct_decl
Expand Down Expand Up @@ -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 }
Expand Down
43 changes: 43 additions & 0 deletions test/small1/typedef_varname.c
Original file line number Diff line number Diff line change
@@ -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 <stdint.h>
#include <stdlib.h>

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;
}
48 changes: 48 additions & 0 deletions test/small1/typedef_varname_noinit.c
Original file line number Diff line number Diff line change
@@ -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;
}

2 changes: 2 additions & 0 deletions test/testcil.pl
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
Loading