Repository: eliben/pycparser

Issue:
"'NoneType' object is not iterable" seen for switch statement without cases
Seen calling parse_file on a file containing the below snippet (similar code was generated by a preprocessor). This compiles with `gcc`. I think checking if block_items is None may be sufficient.

```c
int main() {
    int type = 2;
    switch(type) {}
}
```

```
/usr/local/lib/python3.7/site-packages/pycparser/ply/yacc.py in parseopt_notrack(self, input, lexer, debug, tracking, tokenfunc)
   1116                             del symstack[-plen:]
   1117                             self.state = state
-> 1118                             p.callable(pslice)
   1119                             del statestack[-plen:]
   1120                             symstack.append(sym)

/usr/local/lib/python3.7/site-packages/pycparser/c_parser.py in p_selection_statement_3(self, p)
   1505         """ selection_statement : SWITCH LPAREN expression RPAREN pragmacomp_or_statement """
   1506         p[0] = fix_switch_cases(
-> 1507                 c_ast.Switch(p[3], p[5], self._token_coord(p, 1)))
   1508 
   1509     def p_iteration_statement_1(self, p):

/usr/local/lib/python3.7/site-packages/pycparser/ast_transforms.py in fix_switch_cases(switch_node)
     75     # Goes over the children of the Compound below the Switch, adding them
     76     # either directly below new_compound or below the last Case as appropriate
---> 77     for child in switch_node.stmt.block_items:
     78         if isinstance(child, (c_ast.Case, c_ast.Default)):
     79             # If it's a Case/Default:

TypeError: 'NoneType' object is not iterable
```

Comments:

Patch:
diff --git a/pycparser/ast_transforms.py b/pycparser/ast_transforms.py
index ba50966..0aeb88f 100644
--- a/pycparser/ast_transforms.py
+++ b/pycparser/ast_transforms.py
@@ -74,7 +74,8 @@ def fix_switch_cases(switch_node):
 
     # Goes over the children of the Compound below the Switch, adding them
     # either directly below new_compound or below the last Case as appropriate
-    for child in switch_node.stmt.block_items:
+    # (for `switch(cond) {}`, block_items would have been None)
+    for child in (switch_node.stmt.block_items or []):
         if isinstance(child, (c_ast.Case, c_ast.Default)):
             # If it's a Case/Default:
             # 1. Add it to the Compound and mark as "last case"


Test patch:
diff --git a/tests/test_c_parser.py b/tests/test_c_parser.py
index ad9a218..49cada3 100755
--- a/tests/test_c_parser.py
+++ b/tests/test_c_parser.py
@@ -1792,6 +1792,7 @@ class TestCParser_whole_code(TestCParser_base):
         switch = ps1.ext[0].body.block_items[0]
 
         block = switch.stmt.block_items
+        self.assertEqual(len(block), 4)
         assert_case_node(block[0], '10')
         self.assertEqual(len(block[0].stmts), 3)
         assert_case_node(block[1], '20')
@@ -1819,6 +1820,7 @@ class TestCParser_whole_code(TestCParser_base):
         switch = ps2.ext[0].body.block_items[0]
 
         block = switch.stmt.block_items
+        self.assertEqual(len(block), 5)
         assert_default_node(block[0])
         self.assertEqual(len(block[0].stmts), 2)
         assert_case_node(block[1], '10')
@@ -1830,6 +1832,18 @@ class TestCParser_whole_code(TestCParser_base):
         assert_case_node(block[4], '40')
         self.assertEqual(len(block[4].stmts), 1)
 
+        s3 = r'''
+        int foo(void) {
+            switch (myvar) {
+            }
+            return 0;
+        }
+        '''
+        ps3 = self.parse(s3)
+        switch = ps3.ext[0].body.block_items[0]
+
+        self.assertEqual(switch.stmt.block_items, [])
+
     def test_for_statement(self):
         s2 = r'''
         void x(void)