Skip to content

Commit aa23b40

Browse files
committed
Fix possible infinite recursion in find_noncopyable_vars() (#71)
To fix this bug, a list of already parsed variables is passed trhough the different methods. This is optional. In general, if you call is_noncopyable, you do not need to provide the list
1 parent 1beb856 commit aa23b40

File tree

1 file changed

+59
-10
lines changed

1 file changed

+59
-10
lines changed

pygccxml/declarations/type_traits_classes.py

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,18 @@ def find_copy_constructor(type_):
152152
return None
153153

154154

155-
def find_noncopyable_vars(type_):
155+
def find_noncopyable_vars(type_, already_visited_cls_vars=None):
156156
"""
157157
Returns list of all `noncopyable` variables.
158158
159+
If an already_visited_cls_vars list is provided as argument, the returned
160+
list will not contain these variables. This list will be extended with
161+
whatever variables pointing to classes have been found.
162+
159163
Args:
160164
type_ (declarations.class_t): the class to be searched.
165+
already_visited_cls_vars (list): optional list of vars that should not
166+
be checked a second time, to prevent infinite recursions.
161167
162168
Returns:
163169
list: list of all `noncopyable` variables.
@@ -172,6 +178,9 @@ def find_noncopyable_vars(type_):
172178
allow_empty=True)
173179
noncopyable_vars = []
174180

181+
if already_visited_cls_vars is None:
182+
already_visited_cls_vars = []
183+
175184
message = (
176185
"__contains_noncopyable_mem_var - %s - TRUE - " +
177186
"contains const member variable")
@@ -196,7 +205,13 @@ def find_noncopyable_vars(type_):
196205
if class_traits.is_my_case(type_):
197206

198207
cls = class_traits.get_declaration(type_)
199-
if is_noncopyable(cls):
208+
209+
# Exclude classes that have already been visited.
210+
if cls in already_visited_cls_vars:
211+
continue
212+
already_visited_cls_vars.append(cls)
213+
214+
if is_noncopyable(cls, already_visited_cls_vars):
200215
logger.debug((message + " - class that is not copyable")
201216
% type_.decl_string)
202217
noncopyable_vars.append(mvar)
@@ -632,8 +647,20 @@ def is_convertible(source, target):
632647
return __is_convertible_t(source, target).is_convertible()
633648

634649

635-
def __is_noncopyable_single(class_):
636-
"""implementation details"""
650+
def __is_noncopyable_single(class_, already_visited_cls_vars=None):
651+
"""
652+
Implementation detail.
653+
654+
Checks if the class is non copyable, without considering the base classes.
655+
656+
Args:
657+
class_ (declarations.class_t): the class to be checked
658+
already_visited_cls_vars (list): optional list of vars that should not
659+
be checked a second time, to prevent infinite recursions.
660+
661+
Returns:
662+
bool: if the class is non copyable
663+
"""
637664
# It is not enough to check base classes, we should also to check
638665
# member variables.
639666
logger = utils.loggers.cxx_parser
@@ -650,7 +677,11 @@ def __is_noncopyable_single(class_):
650677
" public destructor: yes"])
651678
logger.debug(msg)
652679
return False
653-
if find_noncopyable_vars(class_):
680+
681+
if already_visited_cls_vars is None:
682+
already_visited_cls_vars = []
683+
684+
if find_noncopyable_vars(class_, already_visited_cls_vars):
654685
logger.debug(
655686
("__is_noncopyable_single(TRUE) - %s - contains noncopyable " +
656687
"members") % class_.decl_string)
@@ -662,9 +693,22 @@ def __is_noncopyable_single(class_):
662693
return False
663694

664695

665-
def is_noncopyable(class_):
666-
"""returns True, if class is noncopyable, False otherwise"""
696+
def is_noncopyable(class_, already_visited_cls_vars=None):
697+
"""
698+
Checks if class is non copyable
699+
700+
Args:
701+
class_ (declarations.class_t): the class to be checked
702+
already_visited_cls_vars (list): optional list of vars that should not
703+
be checked a second time, to prevent infinite recursions.
704+
In general you can ignore this argument, it is mainly used during
705+
recursive calls of is_noncopyable() done by pygccxml.
706+
707+
Returns:
708+
bool: if the class is non copyable
709+
"""
667710
logger = utils.loggers.cxx_parser
711+
668712
class_decl = class_traits.get_declaration(class_)
669713

670714
true_header = "is_noncopyable(TRUE) - %s - " % class_.decl_string
@@ -683,6 +727,9 @@ def is_noncopyable(class_):
683727
if copy_ and copy_.access_type == 'public' and not copy_.is_artificial:
684728
return False
685729

730+
if already_visited_cls_vars is None:
731+
already_visited_cls_vars = []
732+
686733
for base_desc in class_decl.recursive_bases:
687734
assert isinstance(base_desc, class_declaration.hierarchy_info_t)
688735

@@ -700,13 +747,15 @@ def is_noncopyable(class_):
700747
true_header +
701748
"there is private copy constructor")
702749
return True
703-
elif __is_noncopyable_single(base_desc.related_class):
750+
elif __is_noncopyable_single(
751+
base_desc.related_class, already_visited_cls_vars):
704752
logger.debug(
705753
true_header +
706754
"__is_noncopyable_single returned True")
707755
return True
708756

709-
if __is_noncopyable_single(base_desc.related_class):
757+
if __is_noncopyable_single(
758+
base_desc.related_class, already_visited_cls_vars):
710759
logger.debug(
711760
true_header +
712761
"__is_noncopyable_single returned True")
@@ -722,7 +771,7 @@ def is_noncopyable(class_):
722771
logger.debug(true_header + "has private destructor")
723772
return True
724773
else:
725-
return __is_noncopyable_single(class_decl)
774+
return __is_noncopyable_single(class_decl, already_visited_cls_vars)
726775

727776

728777
def is_unary_operator(oper):

0 commit comments

Comments
 (0)