Skip to content

Commit

Permalink
Implement basic support for ctor initializer lists and exporting defi…
Browse files Browse the repository at this point in the history
…nes with values (#128)

* Implement preliminary solution to #56 for exporting defines

This change modifies WarningHunter._determine_uses()._add_use() to take
in a full VariableDeclaration object instead of Type. This allows us to
check for the initial_value attribute of a VariableDeclaration object
which may contain a symbol from an included file (e.g. a #define). This
change also exports Define objects in order to capture their value and
check if they are being used.

To fully implement the issue rasied in #56, inline constructor member
initializer lists need to be supported.

* Implement basic support for ctor initializer lists

This change adds basic support for initializing variables (initial_value
attribute of VariableDeclaration) inside ctor initializer lists. It only
supports one parameter for a given member. The ctor must also be inline
defined. In other words, a ctor with its definition outside the class is
not supported. Below is an example of what's supported:

```
class Foo
{
public:
  Foo() :
    foo(1)
  {}

private:
  int foo;
};
```

In this case, the `initial_value` of `foo` is set to `1`.

Whereas, the following is not supported, and will be ignored:

```
class Foo
{
public:
  Foo() :
    bar(1, 2, 3, 4)
  {}

private:
  Bar bar;
};
```

This ctor is defined outside the class, and thus not supported:

```
class Foo
{
public:
  Foo();

private:
  Bar bar;
};

Foo:Foo() :
  bar(1)
{}
```

* Add tests for ctor initializer lists

* Fix travis ci errors

* Fix using explicit python3 type

This fixes the broken build for Python 2.

* Fix inconsistency with usage of local variable
  • Loading branch information
christarazi authored and myint committed Mar 31, 2018
1 parent 4bb45d6 commit 9fcab0f
Show file tree
Hide file tree
Showing 6 changed files with 189 additions and 25 deletions.
60 changes: 53 additions & 7 deletions cpp/ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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, _ = \
Expand Down Expand Up @@ -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:
Expand Down
63 changes: 45 additions & 18 deletions cpp/find_warnings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions test.bash
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ do
done

$PYTHON ./cppclean test/c++11.h
$PYTHON ./cppclean test/init_lists.h

rm -f '.tmp'
$PYTHON ./cppclean \
Expand Down
6 changes: 6 additions & 0 deletions test/const_define.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#ifndef CONST_DEFINE_H
# define CONST_DEFINE_H

# define CONST_DEFINE 1

#endif //! CONST_DEFINE_H
22 changes: 22 additions & 0 deletions test/init_lists.h
Original file line number Diff line number Diff line change
@@ -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)
{}
};
62 changes: 62 additions & 0 deletions test_ast.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down

0 comments on commit 9fcab0f

Please sign in to comment.