diff --git a/cpp/ast.py b/cpp/ast.py index 6f9808c..d2eb5c4 100644 --- a/cpp/ast.py +++ b/cpp/ast.py @@ -97,11 +97,18 @@ def __init__(self, start, end, name, definition): Node.__init__(self, start, end) self.name = name self.definition = definition + # TODO(christarazi): + # Defines aren't bound to namespaces, so this is just a stopgap + # solution. + self.namespace = [] def __str__(self): value = '%s %s' % (self.name, self.definition) return self._string_helper(self.__class__.__name__, value) + def is_exportable(self): + return True + class Include(Node): @@ -274,7 +281,10 @@ class Union(Class): class Function(_GenericDeclaration): def __init__(self, start, end, name, return_type, parameters, - specializations, modifiers, templated_types, body, namespace): + specializations, modifiers, templated_types, body, namespace, + initializers=None): + if initializers is None: + initializers = {} _GenericDeclaration.__init__(self, start, end, name, namespace) converter = TypeConverter(namespace) self.return_type = converter.create_return_type(return_type) @@ -283,6 +293,7 @@ def __init__(self, start, end, name, return_type, parameters, self.modifiers = modifiers self.body = body self.templated_types = templated_types + self.initializers = initializers def is_declaration(self): return self.body is None @@ -311,6 +322,14 @@ class Method(Function): def __init__(self, start, end, name, in_class, return_type, parameters, specializations, modifiers, templated_types, body, namespace): + # TODO(christarazi): + # Add support for ctor initializers. + # For now, only inline defined ctors are supported because we would + # need to figure out how to keep state of which class is currently + # being processed in order to modify its body (var decls). + # + # Note: ctor initializers are inside Function because a inline defined + # class members are parsed as functions rather than methods. Function.__init__(self, start, end, name, return_type, parameters, specializations, modifiers, templated_types, body, namespace) @@ -1065,13 +1084,18 @@ def _get_method(self, return_type_and_name, modifiers, templated_types, assert_parse(token.token_type == tokenize.SYNTAX, token) # Handle ctor initializers. + # Supports C++11 method of direct initialization with braces. + initializers = {} if token.name == ':': while token.name != ';' and token.name != '{': - _, token = self.get_name() - if token.name == '(': - list(self._get_matching_char('(', ')')) - elif token.name == '{': - list(self._get_matching_char('{', '}')) + member, token = self.get_name() + member = member[0] + if token.name == '(' or token.name == '{': + end = '}' if token.name == '{' else ')' + initializers[member] = [x + for x in list(self._get_matching_char( + token.name, end)) + if x.name != ',' and x.name != end] token = self._get_next_token() # Handle pointer to functions. @@ -1138,7 +1162,8 @@ def _get_method(self, return_type_and_name, modifiers, templated_types, templated_types, body, self.namespace_stack) return Function(indices.start, indices.end, name.name, return_type, parameters, specializations, modifiers, - templated_types, body, self.namespace_stack) + templated_types, body, self.namespace_stack, + initializers) def _get_variable(self, tokens): name, type_name, templated_types, modifiers, default, _ = \ @@ -1526,6 +1551,27 @@ def _get_class(self, class_type, templated_types): quiet=self.quiet) body = list(ast.generate()) + ctor = None + for member in body: + if isinstance(member, Function) and member.name == class_name: + ctor = member + break + + # Merge ctor initializers with class members. + if ctor: + initializers = body[body.index(ctor)].initializers + var_decls = [x for x in body + if isinstance(x, VariableDeclaration)] + for var in var_decls: + for key, val in initializers.items(): + # TODO: CT + # In the future, support members that have ctors with + # more than one parameter. + if len(val) > 1: + continue + if len(val) == 1 and var.name == key.name: + body[body.index(var)].initial_value = val[0].name + if not self._handling_typedef: token = self._get_next_token() if token.token_type != tokenize.NAME: diff --git a/cpp/find_warnings.py b/cpp/find_warnings.py index 13c5415..60acc5f 100644 --- a/cpp/find_warnings.py +++ b/cpp/find_warnings.py @@ -249,8 +249,16 @@ def _determine_uses(self, included_files, forward_declarations): decl_uses[name] |= USES_REFERENCE except symbols.Error: module = Module(name, None) - self.symbol_table.add_symbol(node.name, node.namespace, node, - module) + symbol_table.add_symbol(node.name, node.namespace, node, + module) + + def _do_lookup(name, namespace): + try: + file_use_node = symbol_table.lookup_symbol(name, namespace) + except symbols.Error: + return + name = file_use_node[1].filename + file_uses[name] = file_uses.get(name, 0) | USES_DECLARATION def _add_declaration(name, namespace): if not name: @@ -280,30 +288,49 @@ def _add_reference(name, namespace): else: file_uses[name] |= USES_REFERENCE - def _add_use(name, namespace): - if isinstance(name, list): + def _add_use(node, namespace, name=''): + if isinstance(node, basestring): + name = node + elif isinstance(node, list): # name contains a list of tokens. name = '::'.join([n.name for n in name]) - elif not isinstance(name, basestring): + + # node is a Type so look for its symbol immediately. + if name: + _do_lookup(name, namespace) + return + + # Try to search for the value of the variable declaration for any + # symbols, such as `#define` values or other variable names which + # may be included in other files. + obj = getattr(node, "initial_value", None) + if obj: + _do_lookup(obj, namespace) + + # If node is a VariableDeclaration, check if the variable type is + # a symbol used in other includes. + obj = getattr(node, "type", None) + if obj and isinstance(obj.name, basestring): + _do_lookup(obj.name, namespace) + + if not isinstance(node, basestring): # Happens when variables are defined with inlined types, e.g.: # enum {...} variable; return - try: - file_use_node = symbol_table.lookup_symbol(name, namespace) - except symbols.Error: - return - - name = file_use_node[1].filename - file_uses[name] = file_uses.get(name, 0) | USES_DECLARATION def _add_variable(node, namespace, reference=False): - if node.reference or node.pointer or reference: - _add_reference(node.name, namespace) + obj = node.type if isinstance(node, ast.VariableDeclaration) else node + + if obj.reference or obj.pointer or reference: + _add_reference(obj.name, namespace) else: - _add_use(node.name, namespace) + # Add a use for the variable declaration type as well as the + # variable value. + _add_use(obj.name, namespace) + _add_use(node, namespace) # This needs to recurse when the node is a templated type. - _add_template_use(node.name, - node.templated_types, + _add_template_use(obj.name, + obj.templated_types, namespace, reference) @@ -390,7 +417,7 @@ def _process_types(nodes, namespace): for node in ast_seq.pop(): if isinstance(node, ast.VariableDeclaration): namespace = namespace_stack + node.namespace - _add_variable(node.type, namespace) + _add_variable(node, namespace) elif isinstance(node, ast.Function): namespace = namespace_stack + node.namespace _process_function(node, namespace) diff --git a/test.bash b/test.bash index c46746e..6a09213 100755 --- a/test.bash +++ b/test.bash @@ -8,6 +8,7 @@ do done $PYTHON ./cppclean test/c++11.h +$PYTHON ./cppclean test/init_lists.h rm -f '.tmp' $PYTHON ./cppclean \ diff --git a/test/const_define.h b/test/const_define.h new file mode 100644 index 0000000..d51622f --- /dev/null +++ b/test/const_define.h @@ -0,0 +1,6 @@ +#ifndef CONST_DEFINE_H +# define CONST_DEFINE_H + +# define CONST_DEFINE 1 + +#endif //! CONST_DEFINE_H diff --git a/test/init_lists.h b/test/init_lists.h new file mode 100644 index 0000000..a7d1ffb --- /dev/null +++ b/test/init_lists.h @@ -0,0 +1,22 @@ +#include "const_define.h" + +class A { + public: + A() : + abc(CONST_DEFINE), + efg(2), + hij(3) + {} + private: + int abc; + int efg; + int hij; +}; + +struct B { + int arg1; + + B() : + arg1(CONST_DEFINE) + {} +}; diff --git a/test_ast.py b/test_ast.py index 8ddb9e9..2f796ba 100755 --- a/test_ast.py +++ b/test_ast.py @@ -972,6 +972,68 @@ class AnotherAllocator : public Alloc { templated_types=types1,), nodes[0]) + def test_class_ctor_initializer_list(self): + code = """ + class Foo { + public: + Foo() : + arg1(1), + arg2(2), + arg3(3) + {} + private: + int arg1; + int arg2; + int arg3; + }; + """ + nodes = list(MakeBuilder(code).generate()) + ctor = nodes[0].body[0] + arg1 = nodes[0].body[1] + arg2 = nodes[0].body[2] + arg3 = nodes[0].body[3] + + exp_ctor = Function('Foo', [], [], modifiers=ast.FUNCTION_CTOR, body=[]) + exp_var = [VariableDeclaration('arg1', Type('int'), initial_value='1'), + VariableDeclaration('arg2', Type('int'), initial_value='2'), + VariableDeclaration('arg3', Type('int'), initial_value='3')] + + self.assertEqual(exp_ctor.return_type, ctor.return_type) + self.assertEqual(exp_ctor, ctor) + self.assertEqual(exp_var, [arg1, arg2, arg3]) + self.assertEqual(Class('Foo', body=[exp_ctor] + exp_var), nodes[0]) + + def test_class_ctor_initializer_list_cpp11(self): + code = """ + class Foo { + public: + Foo() : + arg1{1}, + arg2{2}, + arg3{3} + {} + private: + int arg1; + int arg2; + int arg3; + }; + """ + nodes = list(MakeBuilder(code).generate()) + ctor = nodes[0].body[0] + arg1 = nodes[0].body[1] + arg2 = nodes[0].body[2] + arg3 = nodes[0].body[3] + + exp_ctor = Function('Foo', [], [], modifiers=ast.FUNCTION_CTOR, body=[]) + exp_var = [VariableDeclaration('arg1', Type('int'), initial_value='1'), + VariableDeclaration('arg2', Type('int'), initial_value='2'), + VariableDeclaration('arg3', Type('int'), initial_value='3')] + + self.assertEqual(exp_ctor.return_type, ctor.return_type) + self.assertEqual(exp_ctor, ctor) + self.assertEqual(exp_var, [arg1, arg2, arg3]) + self.assertEqual(Class('Foo', body=[exp_ctor] + exp_var), nodes[0]) + def test_function_parses_operator_bracket(self): code = """ class A {