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

Lines changed: 30 additions & 18 deletions
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

Lines changed: 46 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy