Skip to content

Commit 04fb4da

Browse files
author
Jean-François DEVERGE
committed
Fix CI: close dump file handles in cppcheckdata.py
Both ElementTree.iterparse() calls passed a filename and were abandoned before the iterator was exhausted (early break in CppcheckData.__init__, generator close in iterconfigurations). Python 3.13+ emits a ResourceWarning for such unclosed iterators, which pytest promotes to failures ('multiple unraisable exception warnings') on the macOS runners using Python 3.15. Open the dump file explicitly and close it via a with-statement so the handle is released deterministically regardless of how far parsing progresses.
1 parent 8c205ed commit 04fb4da

1 file changed

Lines changed: 142 additions & 135 deletions

File tree

‎addons/cppcheckdata.py‎

Lines changed: 142 additions & 135 deletions
Original file line numberDiff line numberDiff line change
@@ -1237,27 +1237,30 @@ def __init__(self, filename):
12371237
# Parse general configuration options from <dumps> node
12381238
# We intentionally don't clean node resources here because we
12391239
# want to serialize in memory only small part of the XML tree.
1240-
for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')):
1241-
if platform_done and rawtokens_done and suppressions_done:
1242-
break
1243-
if node.tag == 'dumps':
1244-
self.language = node.get('language')
1245-
if node.tag == 'platform' and event == 'start':
1246-
self.platform = Platform(node)
1247-
platform_done = True
1248-
elif node.tag == 'rawtokens' and event == 'end':
1249-
for rawtokens_node in node:
1250-
if rawtokens_node.tag == 'file':
1251-
self.files.append(rawtokens_node.get('name'))
1252-
elif rawtokens_node.tag == 'tok':
1253-
tok = Token(rawtokens_node)
1254-
tok.file = self.files[int(rawtokens_node.get('fileIndex'))]
1255-
self.rawTokens.append(tok)
1256-
rawtokens_done = True
1257-
elif node.tag == 'suppressions' and event == 'end':
1258-
for suppressions_node in node:
1259-
self.suppressions.append(Suppression(suppressions_node))
1260-
suppressions_done = True
1240+
# The file handle is closed explicitly since the iterator may
1241+
# break early and Python 3.13+ warns about unclosed iterators.
1242+
with open(self.filename, 'rb') as dumpfile:
1243+
for event, node in ElementTree.iterparse(dumpfile, events=('start', 'end')):
1244+
if platform_done and rawtokens_done and suppressions_done:
1245+
break
1246+
if node.tag == 'dumps':
1247+
self.language = node.get('language')
1248+
if node.tag == 'platform' and event == 'start':
1249+
self.platform = Platform(node)
1250+
platform_done = True
1251+
elif node.tag == 'rawtokens' and event == 'end':
1252+
for rawtokens_node in node:
1253+
if rawtokens_node.tag == 'file':
1254+
self.files.append(rawtokens_node.get('name'))
1255+
elif rawtokens_node.tag == 'tok':
1256+
tok = Token(rawtokens_node)
1257+
tok.file = self.files[int(rawtokens_node.get('fileIndex'))]
1258+
self.rawTokens.append(tok)
1259+
rawtokens_done = True
1260+
elif node.tag == 'suppressions' and event == 'end':
1261+
for suppressions_node in node:
1262+
self.suppressions.append(Suppression(suppressions_node))
1263+
suppressions_done = True
12611264

12621265
global current_dumpfile_suppressions
12631266
current_dumpfile_suppressions = self.suppressions
@@ -1296,126 +1299,130 @@ def iterconfigurations(self):
12961299
# Use iterable objects to traverse XML tree for dump files incrementally.
12971300
# Iterative approach is required to avoid large memory consumption.
12981301
# Calling .clear() is necessary to let the element be garbage collected.
1299-
for event, node in ElementTree.iterparse(self.filename, events=('start', 'end')):
1300-
# Serialize new configuration node
1301-
if node.tag == 'dump':
1302-
if event == 'start':
1303-
cfg = Configuration(node.get('cfg'))
1302+
# The file handle is closed explicitly since the generator may
1303+
# be closed before the iterator is exhausted (Python 3.13+
1304+
# warns about unclosed iterators).
1305+
with open(self.filename, 'rb') as dumpfile:
1306+
for event, node in ElementTree.iterparse(dumpfile, events=('start', 'end')):
1307+
# Serialize new <dump> node contents
1308+
if node.tag == 'dump':
1309+
if event == 'start':
1310+
cfg = Configuration(node.get('cfg'))
1311+
continue
1312+
if event == 'end':
1313+
cfg.setIdMap(cfg_arguments)
1314+
yield cfg
1315+
cfg = None
1316+
cfg_arguments = []
1317+
1318+
elif node.tag == 'clang-warning' and event == 'start':
1319+
cfg.clang_warnings.append({'file': node.get('file'),
1320+
'line': int(node.get('line')),
1321+
'column': int(node.get('column')),
1322+
'message': node.get('message')})
1323+
# Parse standards
1324+
elif node.tag == "standards" and event == 'start':
13041325
continue
1305-
if event == 'end':
1306-
cfg.setIdMap(cfg_arguments)
1307-
yield cfg
1308-
cfg = None
1309-
cfg_arguments = []
1310-
1311-
elif node.tag == 'clang-warning' and event == 'start':
1312-
cfg.clang_warnings.append({'file': node.get('file'),
1313-
'line': int(node.get('line')),
1314-
'column': int(node.get('column')),
1315-
'message': node.get('message')})
1316-
# Parse standards
1317-
elif node.tag == "standards" and event == 'start':
1318-
continue
1319-
elif node.tag == 'c' and event == 'start':
1320-
cfg.standards.set_c(node)
1321-
elif node.tag == 'cpp' and event == 'start':
1322-
cfg.standards.set_cpp(node)
1323-
elif node.tag == 'posix' and event == 'start':
1324-
cfg.standards.set_posix(node)
1325-
1326-
# Parse directives list
1327-
elif node.tag == 'directive':
1328-
if event == 'start':
1329-
cfg.directives.append(Directive(node))
1330-
iter_directive = True
1331-
elif event == 'end':
1332-
iter_directive = False
1333-
# Parse macro usage
1334-
elif node.tag == 'macro' and event == 'start':
1335-
cfg.macro_usage.append(MacroUsage(node))
1336-
1337-
# Preprocessor #if/#elif condition
1338-
elif node.tag == "if-cond" and event == 'start':
1339-
cfg.preprocessor_if_conditions.append(PreprocessorIfCondition(node))
1340-
1341-
# Parse tokens
1342-
elif node.tag == 'tokenlist' and event == 'start':
1343-
continue
1344-
elif node.tag == 'token' and event == 'start' and not iter_directive and not iter_typedef_info:
1345-
cfg.tokenlist.append(Token(node))
1346-
1347-
# Parse scopes
1348-
elif node.tag == 'scopes' and event == 'start':
1349-
continue
1350-
elif node.tag == 'scope' and event == 'start':
1351-
cfg.scopes.append(Scope(node))
1352-
elif node.tag == 'varlist':
1353-
if event == 'start':
1354-
iter_scope_varlist = True
1355-
elif event == 'end':
1356-
iter_scope_varlist = False
1357-
1358-
# Parse functions
1359-
elif node.tag == 'functionList' and event == 'start':
1360-
continue
1361-
elif node.tag == 'function':
1362-
if event == 'start':
1363-
cfg_function = Function(node, cfg.scopes[-1])
1326+
elif node.tag == 'c' and event == 'start':
1327+
cfg.standards.set_c(node)
1328+
elif node.tag == 'cpp' and event == 'start':
1329+
cfg.standards.set_cpp(node)
1330+
elif node.tag == 'posix' and event == 'start':
1331+
cfg.standards.set_posix(node)
1332+
1333+
# Parse directives list
1334+
elif node.tag == 'directive':
1335+
if event == 'start':
1336+
cfg.directives.append(Directive(node))
1337+
iter_directive = True
1338+
elif event == 'end':
1339+
iter_directive = False
1340+
# Parse macro usage
1341+
elif node.tag == 'macro' and event == 'start':
1342+
cfg.macro_usage.append(MacroUsage(node))
1343+
1344+
# Preprocessor #if/#elif condition
1345+
elif node.tag == "if-cond" and event == 'start':
1346+
cfg.preprocessor_if_conditions.append(PreprocessorIfCondition(node))
1347+
1348+
# Parse tokens
1349+
elif node.tag == 'tokenlist' and event == 'start':
13641350
continue
1365-
if event == 'end':
1366-
cfg.functions.append(cfg_function)
1367-
cfg_function = None
1368-
1369-
# Parse function arguments
1370-
elif node.tag == 'arg' and event == 'start':
1371-
arg_nr = int(node.get('nr'))
1372-
arg_variable_id = node.get('variable')
1373-
cfg_function.argumentId[arg_nr] = arg_variable_id
1374-
1375-
# Parse variables
1376-
elif node.tag == 'var' and event == 'start':
1377-
if iter_scope_varlist:
1378-
cfg.scopes[-1].varlistId.append(node.get('id'))
1379-
else:
1380-
var = Variable(node)
1381-
if var.nameTokenId:
1382-
cfg.variables.append(var)
1351+
elif node.tag == 'token' and event == 'start' and not iter_directive and not iter_typedef_info:
1352+
cfg.tokenlist.append(Token(node))
1353+
1354+
# Parse scopes
1355+
elif node.tag == 'scopes' and event == 'start':
1356+
continue
1357+
elif node.tag == 'scope' and event == 'start':
1358+
cfg.scopes.append(Scope(node))
1359+
elif node.tag == 'varlist':
1360+
if event == 'start':
1361+
iter_scope_varlist = True
1362+
elif event == 'end':
1363+
iter_scope_varlist = False
1364+
1365+
# Parse functions
1366+
elif node.tag == 'functionList' and event == 'start':
1367+
continue
1368+
elif node.tag == 'function':
1369+
if event == 'start':
1370+
cfg_function = Function(node, cfg.scopes[-1])
1371+
continue
1372+
if event == 'end':
1373+
cfg.functions.append(cfg_function)
1374+
cfg_function = None
1375+
1376+
# Parse function arguments
1377+
elif node.tag == 'arg' and event == 'start':
1378+
arg_nr = int(node.get('nr'))
1379+
arg_variable_id = node.get('variable')
1380+
cfg_function.argumentId[arg_nr] = arg_variable_id
1381+
1382+
# Parse variables
1383+
elif node.tag == 'var' and event == 'start':
1384+
if iter_scope_varlist:
1385+
cfg.scopes[-1].varlistId.append(node.get('id'))
13831386
else:
1384-
cfg_arguments.append(var)
1385-
1386-
# Parse containers
1387-
elif node.tag == 'containers' and event == 'start':
1388-
continue
1389-
elif node.tag == 'container' and event == 'start':
1390-
cfg.containers.append(Container(node))
1391-
1392-
# Parse typedef info
1393-
elif node.tag == 'typedef-info':
1394-
iter_typedef_info = (event == 'start')
1395-
elif iter_typedef_info and node.tag == 'info' and event == 'start':
1396-
cfg.typedefInfo.append(TypedefInfo(node))
1397-
1398-
# Parse template-token
1399-
#elif node.tag == 'TokenAndName' and event == 'start': #todo add processing of containers
1400-
# cfg.containers.append(Container(node))
1401-
1402-
# Parse valueflows (list of values)
1403-
elif node.tag == 'valueflow' and event == 'start':
1404-
continue
1405-
elif node.tag == 'values':
1406-
if event == 'start':
1407-
cfg_valueflow = ValueFlow(node)
1387+
var = Variable(node)
1388+
if var.nameTokenId:
1389+
cfg.variables.append(var)
1390+
else:
1391+
cfg_arguments.append(var)
1392+
1393+
# Parse containers
1394+
elif node.tag == 'containers' and event == 'start':
14081395
continue
1409-
if event == 'end':
1410-
cfg.valueflow.append(cfg_valueflow)
1411-
cfg_valueflow = None
1396+
elif node.tag == 'container' and event == 'start':
1397+
cfg.containers.append(Container(node))
1398+
1399+
# Parse typedef info
1400+
elif node.tag == 'typedef-info':
1401+
iter_typedef_info = (event == 'start')
1402+
elif iter_typedef_info and node.tag == 'info' and event == 'start':
1403+
cfg.typedefInfo.append(TypedefInfo(node))
14121404

1413-
# Parse values
1414-
elif node.tag == 'value' and event == 'start':
1415-
cfg_valueflow.values.append(Value(node))
1405+
# Parse template-token
1406+
#elif node.tag == 'TokenAndName' and event == 'start': #todo add processing of containers
1407+
# cfg.containers.append(Container(node))
14161408

1417-
# Remove links to the sibling nodes
1418-
node.clear()
1409+
# Parse valueflows (list of values)
1410+
elif node.tag == 'valueflow' and event == 'start':
1411+
continue
1412+
elif node.tag == 'values':
1413+
if event == 'start':
1414+
cfg_valueflow = ValueFlow(node)
1415+
continue
1416+
if event == 'end':
1417+
cfg.valueflow.append(cfg_valueflow)
1418+
cfg_valueflow = None
1419+
1420+
# Parse values
1421+
elif node.tag == 'value' and event == 'start':
1422+
cfg_valueflow.values.append(Value(node))
1423+
1424+
# Remove links to the sibling nodes
1425+
node.clear()
14191426

14201427
def __repr__(self):
14211428
attrs = ["configurations", "platform"]

0 commit comments

Comments
 (0)