Skip to content

Commit 50b3887

Browse files
praetorian20iMichka
authored andcommitted
Fix patching of enums in default arguments for C++03
Given the following code namespace ns { enum color {red, green, blue}; void test( color arg=blue ); } CastXML produces a fully qualified name for the default argument - `::ns::color::blue`. While this default argument string is valid for C++11 and later, and indeed required if `color` were a scoped enumeration, the default argument is invalid for C++03 code. Prior to C++11, enumerator names were added to the enclosing scope, so they could not be qualified using the enum-name. Hence, for C++03, the default argument needs to be patched to say `::ns::blue`. Change-Id: I72c5d4fc53c5b5dc6f5fa38d10cb5484ba3bf85a Cherry-picked from the develop branch
1 parent b28cd27 commit 50b3887

File tree

6 files changed

+175
-52
lines changed

6 files changed

+175
-52
lines changed

pygccxml/parser/patcher.py

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010

1111
class default_argument_patcher_t(object):
1212

13-
def __init__(self, enums):
13+
def __init__(self, enums, cxx_std):
1414
object.__init__(self)
1515
self.__enums = enums
16+
self.__cxx_std = cxx_std
1617

1718
def __call__(self, decl):
1819
for arg in decl.arguments:
@@ -54,8 +55,12 @@ def __is_unqualified_enum(self, func, arg):
5455
def __fix_unqualified_enum(self, func, arg):
5556
type_ = declarations.remove_reference(declarations.remove_cv(arg.type))
5657
enum_type = declarations.enum_declaration(type_)
58+
if self.__cxx_std.is_cxx11_or_greater:
59+
qualifier_decl_string = enum_type.decl_string
60+
else:
61+
qualifier_decl_string = enum_type.parent.decl_string
5762
return self.__join_names(
58-
enum_type.parent.decl_string,
63+
qualifier_decl_string,
5964
arg.default_value.split('::')[-1])
6065

6166
def __is_invalid_integral(self, func, arg):
@@ -92,19 +97,25 @@ def __fix_invalid_integral(self, func, arg):
9297
pass
9398

9499
# may be we deal with enum
100+
# CastXML qualifies the enum value with enum type, so split the
101+
# argument and use only the enum value
102+
enum_value = arg.default_value.split('::')[-1]
95103
parent = func.parent
96104
while parent:
97-
found = self.__find_enum(parent, arg.default_value)
105+
found = self.__find_enum(parent, enum_value)
98106
if found:
99107
if declarations.is_fundamental(arg.type) and ' ' in \
100108
arg.type.decl_string:
101109
template = '(%s)(%s)'
102110
else:
103111
template = '%s(%s)'
112+
if self.__cxx_std.is_cxx11_or_greater:
113+
qualifier_decl_string = found.decl_string
114+
else:
115+
qualifier_decl_string = found.parent.decl_string
104116
return template % (arg.type.decl_string,
105-
self.__join_names(
106-
found.parent.decl_string,
107-
arg.default_value))
117+
self.__join_names(qualifier_decl_string,
118+
enum_value))
108119
else:
109120
parent = parent.parent
110121

@@ -132,7 +143,7 @@ def __fix_invalid_integral(self, func, arg):
132143

133144
def __find_enum(self, scope, default_value):
134145
# this algorithm could be improved: it could take into account
135-
# 1. unnamed namespaced
146+
# 1. unnamed namespace
136147
# 2. location within files
137148

138149
for enum in self.__enums:
@@ -221,8 +232,8 @@ def __call__(self, decl):
221232
_casting_oper_patcher_ = casting_operator_patcher_t()
222233

223234

224-
def fix_calldef_decls(decls, enums):
225-
default_arg_patcher = default_argument_patcher_t(enums)
235+
def fix_calldef_decls(decls, enums, cxx_std):
236+
default_arg_patcher = default_argument_patcher_t(enums, cxx_std)
226237
# decls should be flat list of all declarations, you want to apply patch on
227238
for decl in decls:
228239
default_arg_patcher(decl)

pygccxml/parser/source_reader.py

Lines changed: 9 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ def __init__(self, config, cache=None, decl_factory=None, join_decls=True):
9393
self.__join_decls = join_decls
9494
self.__search_directories = []
9595
self.__config = config
96+
self.__cxx_std = utils.cxx_standard(config.cflags)
9697
self.__search_directories.append(config.working_directory)
9798
self.__search_directories.extend(config.include_paths)
9899
if not cache:
@@ -163,30 +164,12 @@ def __create_command_line_castxml(self, source_file, xmlfile):
163164
# On mac or linux, use gcc or clang (the flag is the same)
164165
cmd.append('--castxml-cc-gnu ')
165166

166-
# Check for -std=xx flags passed to the compiler.
167-
# A regex could be used but this is a moving target.
168-
# See c++1z for example. It is preferable to have a defined
169-
# list of what is allowed. http://clang.llvm.org/cxx_status.html
170-
#
171-
# Version 98 and 03 are only there in the case somebody is using
172-
# these flags; this is the equivalent to not passing these flags.
173-
standards = [
174-
"-std=c++98",
175-
"-std=c++03",
176-
"-std=c++11",
177-
"-std=c++14",
178-
"-std=c++1z"]
179-
180-
std_flag = ""
181-
for standard in standards:
182-
if standard in self.__config.cflags:
183-
std_flag = " " + standard + " "
184-
185-
# A -std= flag was passed, but is not in the list
186-
if "-std=" in self.__config.cflags and std_flag == "":
187-
raise(RuntimeError("Unknown -std=c++xx flag used !"))
188-
189-
if std_flag != "":
167+
if self.__cxx_std.is_implicit:
168+
std_flag = ''
169+
else:
170+
std_flag = ' ' + self.__cxx_std.stdcxx + ' '
171+
172+
if std_flag:
190173
cmd.append(
191174
'"(" ' + self.__config.compiler_path + std_flag + '")"')
192175
else:
@@ -502,7 +485,8 @@ def __parse_xml_file(self, xml_file):
502485
# void ddd(){ typedef typename X::Y YY;}
503486
# if I will fail on this bug next time, the right way to fix it may be
504487
# different
505-
patcher.fix_calldef_decls(scanner_.calldefs(), scanner_.enums())
488+
patcher.fix_calldef_decls(scanner_.calldefs(), scanner_.enums(),
489+
self.__cxx_std)
506490
decls = [
507491
inst for inst in iter(
508492
decls.values()) if isinstance(

pygccxml/utils/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .utils import normalize_path
1818
from .utils import find_xml_generator
1919
from .utils import get_tr1
20+
from .utils import cxx_standard
2021

2122
# Version of xml generator which was used.
2223
xml_generator = None

pygccxml/utils/utils.py

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,3 +352,94 @@ def get_tr1(name):
352352
if "tr1" in name:
353353
tr1 = "tr1::"
354354
return tr1
355+
356+
357+
class cxx_standard(object):
358+
"""Helper class for parsing the C++ standard version.
359+
360+
This class holds the C++ standard version the XML generator has been
361+
configured with, and provides helpers functions for querying C++ standard
362+
version related information.
363+
"""
364+
365+
__STD_CXX = {
366+
'-std=c++98': 199711,
367+
'-std=gnu++98': 199711,
368+
'-std=c++03': 199711,
369+
'-std=gnu++03': 199711,
370+
'-std=c++0x': 201103,
371+
'-std=gnu++0x': 201103,
372+
'-std=c++11': 201103,
373+
'-std=gnu++11': 201103,
374+
'-std=c++1y': 201402,
375+
'-std=gnu++1y': 201402,
376+
'-std=c++14': 201402,
377+
'-std=gnu++14': 201402,
378+
'-std=c++1z': float('inf'),
379+
'-std=gnu++1z': float('inf'),
380+
}
381+
382+
def __init__(self, cflags):
383+
"""Class constructor that parses the XML generator's command line
384+
385+
Args:
386+
cflags (str): cflags command line arguments passed to the XML
387+
generator
388+
"""
389+
super(cxx_standard, self).__init__()
390+
391+
self._stdcxx = None
392+
self._is_implicit = False
393+
for key in cxx_standard.__STD_CXX:
394+
if key in cflags:
395+
self._stdcxx = key
396+
self._cplusplus = cxx_standard.__STD_CXX[key]
397+
398+
if not self._stdcxx:
399+
if '-std=' in cflags:
400+
raise RuntimeError('Unknown -std=c++xx flag used')
401+
402+
# Assume c++03 by default
403+
self._stdcxx = '-std=c++03'
404+
self._cplusplus = cxx_standard.__STD_CXX['-std=c++03']
405+
self._is_implicit = True
406+
407+
@property
408+
def stdcxx(self):
409+
"""Returns the -std=c++xx option passed to the constructor"""
410+
return self._stdcxx
411+
412+
@property
413+
def is_implicit(self):
414+
"""Indicates whether a -std=c++xx was specified"""
415+
return self._is_implicit
416+
417+
@property
418+
def is_cxx03(self):
419+
"""Returns true if -std=c++03 is being used"""
420+
return self._cplusplus == cxx_standard.__STD_CXX['-std=c++03']
421+
422+
@property
423+
def is_cxx11(self):
424+
"""Returns true if -std=c++11 is being used"""
425+
return self._cplusplus == cxx_standard.__STD_CXX['-std=c++11']
426+
427+
@property
428+
def is_cxx11_or_greater(self):
429+
"""Returns true if -std=c++11 or a newer standard is being used"""
430+
return self._cplusplus >= cxx_standard.__STD_CXX['-std=c++11']
431+
432+
@property
433+
def is_cxx14(self):
434+
"""Returns true if -std=c++14 is being used"""
435+
return self._cplusplus == cxx_standard.__STD_CXX['-std=c++14']
436+
437+
@property
438+
def is_cxx14_or_greater(self):
439+
"""Returns true if -std=c++14 or a newer standard is being used"""
440+
return self._cplusplus >= cxx_standard.__STD_CXX['-std=c++14']
441+
442+
@property
443+
def is_cxx1z(self):
444+
"""Returns true if -std=c++1z is being used"""
445+
return self._cplusplus == cxx_standard.__STD_CXX['-std=c++1z']

unittests/data/patcher.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,22 @@ void fix_enum3( fruit arg=orange );
2525

2626
}
2727

28+
#if __cplusplus >= 201103L
29+
namespace ns4{
30+
31+
enum class color {red, green, blue};
32+
void fix_enum4( color arg=color::blue );
33+
34+
}
35+
36+
namespace ns5{
37+
38+
using namespace ns4;
39+
void fix_enum5( color arg=color::blue );
40+
41+
}
42+
#endif
43+
2844
typedef unsigned long long ull;
2945
void fix_numeric( ull arg=(ull)-1 );
3046

@@ -76,6 +92,13 @@ static int const DEFAULT_1 = 20;
7692
int fun2( int arg1=DEFAULT_1, int arg2=ns1::DEFAULT_1, long arg3=::ns1::st1::DEFAULT_2 );
7793

7894

95+
enum ACE_Log_Priority_Index
96+
{
97+
LM_INVALID_BIT_INDEX = 32
98+
};
99+
static int log_priority_enabled(long priority_index = LM_INVALID_BIT_INDEX);
100+
101+
79102
/*struct default_arg_t{};*/
80103
/*default_arg_t create_default_argument();*/
81104
/*void double_call( default_arg_t x=create_default_argument() );*/

unittests/patcher_tester.py

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,50 @@ def __init__(self, architecture, *args):
1919
parser_test_case.parser_test_case_t.__init__(self, *args)
2020
self.architecture = architecture
2121
self.global_ns = None
22+
self.__cxx_std = utils.cxx_standard(self.config.cflags)
2223

2324
def test_enum_patcher(self):
2425
fix_enum = self.global_ns.free_fun("fix_enum")
2526
default_val = fix_enum.arguments[0].default_value
26-
self.assertEqual(default_val, "::ns1::ns2::apple")
27+
if self.__cxx_std.is_cxx11_or_greater:
28+
val = "::ns1::ns2::fruit::apple"
29+
else:
30+
val = "::ns1::ns2::apple"
31+
self.assertEqual(default_val, val)
2732

2833
if 32 == self.architecture or "CastXML" in utils.xml_generator:
2934
fix_enum2 = self.global_ns.free_fun("fix_enum2")
3035
default_val = fix_enum2.arguments[0].default_value
31-
self.assertEqual(default_val, "::ns1::ns2::apple")
36+
self.assertEqual(default_val, val)
3237

3338
ns1 = self.global_ns.namespace("ns1")
3439
ns2 = ns1.namespace("ns2")
3540
fix_enum2 = ns2.free_fun("fix_enum2")
3641
default_val = fix_enum2.arguments[0].default_value
37-
self.assertEqual(default_val, "::ns1::ns2::apple")
42+
self.assertEqual(default_val, val)
3843

3944
fix_enum3 = self.global_ns.free_fun("fix_enum3")
4045
default_val = fix_enum3.arguments[0].default_value
41-
self.assertEqual(default_val, "::ns1::ns2::orange")
46+
val = val.replace("apple", "orange")
47+
self.assertEqual(default_val, val)
48+
49+
if self.__cxx_std.is_cxx11_or_greater:
50+
fix_enum4 = self.global_ns.free_fun("fix_enum4")
51+
default_val = fix_enum4.arguments[0].default_value
52+
self.assertEqual(default_val, "::ns4::color::blue")
53+
54+
fix_enum5 = self.global_ns.free_fun("fix_enum5")
55+
default_val = fix_enum5.arguments[0].default_value
56+
self.assertEqual(default_val, "::ns4::color::blue")
4257

43-
# double_call = declarations.find_declaration(
44-
# decls, type=declarations.free_function_t, name='double_call' )
58+
lpe = self.global_ns.free_fun("log_priority_enabled")
59+
default_val = lpe.arguments[0].default_value
60+
if self.__cxx_std.is_cxx11_or_greater:
61+
val = "(long int)" + \
62+
"(::ACE_Log_Priority_Index::LM_INVALID_BIT_INDEX)"
63+
else:
64+
val = "(long int)(::LM_INVALID_BIT_INDEX)"
65+
self.assertEqual(default_val, val)
4566

4667
def test_numeric_patcher(self):
4768
fix_numeric = self.global_ns.free_function("fix_numeric")
@@ -126,13 +147,8 @@ def test_unqualified_integral_patcher(self):
126147

127148
def test_unnamed_enum_patcher(self):
128149
fix_unnamed = self.global_ns.free_fun("fix_unnamed")
129-
if ("CastXML" in utils.xml_generator and
130-
utils.xml_output_version >= 1.137):
131-
val = "fx::unnamed"
132-
else:
133-
val = "int(::fx::unnamed)"
134150
self.assertEqual(
135-
fix_unnamed.arguments[0].default_value, val)
151+
fix_unnamed.arguments[0].default_value, "int(::fx::unnamed)")
136152

137153
def test_function_call_patcher(self):
138154
fix_function_call = self.global_ns.free_fun("fix_function_call")
@@ -150,9 +166,8 @@ def test_function_call_patcher(self):
150166

151167
def test_fundamental_patcher(self):
152168
fcall = self.global_ns.free_fun("fix_fundamental")
153-
if ("CastXML" in utils.xml_generator and
154-
utils.xml_output_version >= 1.137):
155-
val = "fundamental::spam::eggs"
169+
if self.__cxx_std.is_cxx11_or_greater:
170+
val = "(unsigned int)(::fundamental::spam::eggs)"
156171
else:
157172
val = "(unsigned int)(::fundamental::eggs)"
158173
self.assertEqual(
@@ -163,14 +178,12 @@ def test_constructor_patcher(self):
163178
default_val = typedef__func.arguments[0].default_value
164179
if "0.9" in utils.xml_generator:
165180
val = "typedef_::original_name()"
166-
self.assertEqual(default_val, val)
167181
elif "CastXML" in utils.xml_generator:
168182
# Most clean output, no need to patch
169183
val = "typedef_::alias()"
170-
self.assertEqual(default_val, val)
171184
else:
172185
val = "::typedef_::alias( )"
173-
self.assertEqual(default_val, val)
186+
self.assertEqual(default_val, val)
174187
if 32 == self.architecture:
175188
clone_tree = self.global_ns.free_fun("clone_tree")
176189
default_values = []

0 commit comments

Comments
 (0)