Skip to content

Commit 8d4abd7

Browse files
committed
Fix extern 'C' parsing
- Linkage wasn't being recorded previously - Namespaces were being destroyed
1 parent 4d307a8 commit 8d4abd7

File tree

2 files changed

+76
-18
lines changed

2 files changed

+76
-18
lines changed

CppHeaderParser/CppHeaderParser.py

+30-18
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ def trace_print(*args):
139139
#: Symbols to ignore, usually special macros
140140
ignoreSymbols = ["Q_OBJECT"]
141141

142+
_BRACE_REASON_OTHER = 0
143+
_BRACE_REASON_NS = 1
144+
_BRACE_REASON_EXTERN = 2
142145

143146
# Track what was added in what order and at what depth
144147
parseHistory = []
@@ -1506,6 +1509,11 @@ def cur_namespace(self, add_double_colon=False):
15061509
i += 1
15071510
return rtn
15081511

1512+
def cur_linkage(self):
1513+
if len(self.linkage_stack):
1514+
return self.linkage_stack[-1]
1515+
return ""
1516+
15091517
def guess_ctypes_type(self, string):
15101518
pointers = string.count("*")
15111519
string = string.replace("*", "")
@@ -2372,6 +2380,7 @@ def _evaluate_method_stack(self):
23722380
self._get_location(self.nameStack),
23732381
)
23742382
newMethod["parent"] = None
2383+
newMethod["linkage"] = self.cur_linkage()
23752384
self.functions.append(newMethod)
23762385

23772386
# Reset template once it has been used
@@ -2524,6 +2533,7 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None):
25242533
self._get_location(self.nameStack),
25252534
)
25262535
newVar["namespace"] = self.current_namespace()
2536+
newVar["linkage"] = self.cur_linkage()
25272537
if self.curClass:
25282538
klass = self.classes[self.curClass]
25292539
klass["properties"][self.curAccessSpecifier].append(newVar)
@@ -2542,6 +2552,7 @@ def _evaluate_property_stack(self, clearStack=True, addToVar=None):
25422552
self._get_location(self.nameStack),
25432553
)
25442554
newVar["namespace"] = self.cur_namespace(False)
2555+
newVar["linkage"] = self.cur_linkage()
25452556
if addToVar:
25462557
newVar.update(addToVar)
25472558
self.variables.append(newVar)
@@ -2600,6 +2611,7 @@ def _evaluate_class_stack(self):
26002611
)
26012612
self.curTemplate = None
26022613
newClass["declaration_method"] = self.nameStack[0]
2614+
newClass["linkage"] = self.cur_linkage()
26032615
self.classes_order.append(newClass) # good idea to save ordering
26042616
self.stack = [] # fixes if class declared with ';' in closing brace
26052617
self.stmtTokens = []
@@ -2782,6 +2794,7 @@ def __init__(
27822794
self.curAccessSpecifier = "private" # private is default
27832795
self.curTemplate = None
27842796
self.accessSpecifierStack = []
2797+
self.linkage_stack = []
27852798
debug_print(
27862799
"curAccessSpecifier changed/defaulted to %s", self.curAccessSpecifier
27872800
)
@@ -2828,16 +2841,6 @@ def __init__(
28282841
new_m += "\n" * (num_newlines)
28292842
headerFileStr = headerFileStr.replace(m, new_m)
28302843

2831-
# Filter out Extern "C" statements. These are order dependent
2832-
matches = re.findall(
2833-
re.compile(r'extern[\t ]+"[Cc]"[\t \n\r]*{', re.DOTALL), headerFileStr
2834-
)
2835-
for m in matches:
2836-
# Keep the newlines so that linecount doesnt break
2837-
num_newlines = len([a for a in m if a == "\n"])
2838-
headerFileStr = headerFileStr.replace(m, "\n" * num_newlines)
2839-
headerFileStr = re.sub(r'extern[ ]+"[Cc]"[ ]*', "", headerFileStr)
2840-
28412844
# Filter out any ignore symbols that end with "()" to account for #define magic functions
28422845
for ignore in ignoreSymbols:
28432846
if not ignore.endswith("()"):
@@ -2876,6 +2879,8 @@ def __init__(
28762879
)
28772880

28782881
self.braceDepth = 0
2882+
self.braceReason = []
2883+
self.lastBraceReason = _BRACE_REASON_OTHER
28792884

28802885
lex = Lexer(self.headerFileName)
28812886
lex.input(headerFileStr)
@@ -2948,23 +2953,20 @@ def __init__(
29482953
continue
29492954

29502955
if parenDepth == 0 and tok.type == "{":
2956+
self.lastBraceReason = _BRACE_REASON_OTHER
29512957
if len(self.nameStack) >= 2 and is_namespace(
29522958
self.nameStack
29532959
): # namespace {} with no name used in boost, this sets default?
2954-
if (
2955-
self.nameStack[1]
2956-
== "__IGNORED_NAMESPACE__CppHeaderParser__"
2957-
): # Used in filtering extern "C"
2958-
self.nameStack[1] = ""
29592960
self.nameSpaces.append("".join(self.nameStack[1:]))
29602961
ns = self.cur_namespace()
29612962
self.stack = []
29622963
self.stmtTokens = []
29632964
if ns not in self.namespaces:
29642965
self.namespaces.append(ns)
2966+
self.lastBraceReason = _BRACE_REASON_NS
29652967
# Detect special condition of macro magic before class declaration so we
29662968
# can filter it out
2967-
if "class" in self.nameStack and self.nameStack[0] != "class":
2969+
elif "class" in self.nameStack and self.nameStack[0] != "class":
29682970
classLocationNS = self.nameStack.index("class")
29692971
classLocationS = self.stack.index("class")
29702972
if (
@@ -2997,14 +2999,20 @@ def __init__(
29972999
self.stmtTokens = []
29983000
if not self.braceHandled:
29993001
self.braceDepth += 1
3002+
self.braceReason.append(self.lastBraceReason)
30003003

30013004
elif parenDepth == 0 and tok.type == "}":
30023005
if self.braceDepth == 0:
30033006
continue
3004-
if self.braceDepth == len(self.nameSpaces):
3005-
tmp = self.nameSpaces.pop()
3007+
reason = self.braceReason.pop()
3008+
if reason == _BRACE_REASON_NS:
3009+
self.nameSpaces.pop()
30063010
self.stack = [] # clear stack when namespace ends?
30073011
self.stmtTokens = []
3012+
elif reason == _BRACE_REASON_EXTERN:
3013+
self.linkage_stack.pop()
3014+
self.stack = [] # clear stack when linkage ends?
3015+
self.stmtTokens = []
30083016
else:
30093017
self._evaluate_stack()
30103018
self.braceDepth -= 1
@@ -3368,8 +3376,10 @@ def _evaluate_stack(self, token=None):
33683376
pass
33693377
elif len(self.nameStack) == 2 and self.nameStack[0] == "extern":
33703378
debug_print("trace extern")
3379+
self.linkage_stack.append(self.nameStack[1].strip('"'))
33713380
self.stack = []
33723381
self.stmtTokens = []
3382+
self.lastBraceReason = _BRACE_REASON_EXTERN
33733383
elif (
33743384
len(self.nameStack) == 2 and self.nameStack[0] == "friend"
33753385
): # friend class declaration
@@ -3677,12 +3687,14 @@ def _parse_enum(self):
36773687
def _install_enum(self, newEnum, instancesData):
36783688
if len(self.curClass):
36793689
newEnum["namespace"] = self.cur_namespace(False)
3690+
newEnum["linkage"] = self.cur_linkage()
36803691
klass = self.classes[self.curClass]
36813692
klass["enums"][self.curAccessSpecifier].append(newEnum)
36823693
if self.curAccessSpecifier == "public" and "name" in newEnum:
36833694
klass._public_enums[newEnum["name"]] = newEnum
36843695
else:
36853696
newEnum["namespace"] = self.cur_namespace(True)
3697+
newEnum["linkage"] = self.cur_linkage()
36863698
self.enums.append(newEnum)
36873699
if "name" in newEnum and newEnum["name"]:
36883700
self.global_enums[newEnum["name"]] = newEnum

test/test_CppHeaderParser.py

+46
Original file line numberDiff line numberDiff line change
@@ -4183,5 +4183,51 @@ def test_fn3(self):
41834183
self.assertEqual(fn["pure_virtual"], True)
41844184

41854185

4186+
class ExternCQuirk(unittest.TestCase):
4187+
# bug where extern "C" reset the namespace
4188+
def setUp(self):
4189+
self.cppHeader = CppHeaderParser.CppHeader(
4190+
"""
4191+
namespace cs {
4192+
extern "C" {
4193+
struct InCSAndExtern {};
4194+
void FnInCSAndExtern(InCSAndExtern *n);
4195+
}
4196+
4197+
class InCS {};
4198+
4199+
}
4200+
4201+
void FnNotInCSOrExtern();
4202+
4203+
""",
4204+
"string",
4205+
)
4206+
4207+
def test_fn(self):
4208+
4209+
# NotCS should be in namespace cs, extern C
4210+
c = self.cppHeader.classes["InCSAndExtern"]
4211+
self.assertEqual(c["namespace"], "cs")
4212+
self.assertEqual(c["linkage"], "C")
4213+
4214+
# FnNotCS should be in namespace cs, extern C
4215+
fn = self.cppHeader.functions[0]
4216+
self.assertEqual(fn["name"], "FnInCSAndExtern")
4217+
self.assertEqual(fn["namespace"], "cs::")
4218+
self.assertEqual(fn["linkage"], "C")
4219+
4220+
# InCS should be in namespace cs
4221+
c = self.cppHeader.classes["InCS"]
4222+
self.assertEqual(c["namespace"], "cs")
4223+
self.assertEqual(c["linkage"], "")
4224+
4225+
# FnNotCS should not in namespace cs nor extern C
4226+
fn = self.cppHeader.functions[1]
4227+
self.assertEqual(fn["name"], "FnNotInCSOrExtern")
4228+
self.assertEqual(fn["namespace"], "")
4229+
self.assertEqual(fn["linkage"], "")
4230+
4231+
41864232
if __name__ == "__main__":
41874233
unittest.main()

0 commit comments

Comments
 (0)