Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions Lib/test/test_tools/test_c_analyzer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import os.path
import shutil
import subprocess
import tempfile
import unittest

from test.test_tools import imports_under_tool, skip_if_missing


skip_if_missing('c-analyzer')

with imports_under_tool('c-analyzer'):
from c_parser.info import FileInfo
from c_parser.parser import parse
from c_parser.preprocessor import gcc


class StaticAssertTests(unittest.TestCase):
def parse(self, text):
return list(parse(
(FileInfo('test.c', lno), line)
for lno, line in enumerate(text.splitlines(), 1)
))

def test_global(self):
for keyword in ('_Static_assert', 'static_assert'):
with self.subTest(keyword=keyword):
items = self.parse(f'''
int before;
{keyword}(
256 > 0 && !(256 & (256 - 1)),
"buffer size; ("
)
; int after;
''')
self.assertEqual([item.name for item in items],
['before', 'after'])

def test_struct(self):
for keyword in ('_Static_assert', 'static_assert'):
with self.subTest(keyword=keyword):
items = self.parse(f'''
struct example {{
int before;
{keyword}(sizeof(int) > 0, "int size");
int after;
}};
int global_after;
''')
struct = next(item for item in items
if item.name == 'example' and item.data)
self.assertEqual([field.name for field in struct.data],
['before', 'after'])
self.assertEqual(items[-1].name, 'global_after')

@unittest.skipUnless(shutil.which('gcc'), 'requires gcc')
@unittest.skipIf(os.name == 'nt', 'GCC backend does not handle Windows paths')
def test_preprocess(self):
version = subprocess.check_output(['gcc', '--version'], text=True)
if 'clang' in version.lower():
self.skipTest('requires GNU GCC, not Clang')
with tempfile.TemporaryDirectory() as tmpdir:
filename = os.path.join(tmpdir, 'test.c')
with open(filename, 'w', encoding='utf-8') as source:
source.write('''
#define _GNU_SOURCE 1
#include <assert.h>
static_assert(256 > 0 && !(256 & 255), "buffer size");
int global_after;
''')
lines = gcc.preprocess(filename, samefiles=(), cwd=tmpdir)
items = list(parse((line.file, line.data) for line in lines
if line.kind == 'source'))
self.assertEqual([item.name for item in items], ['global_after'])


if __name__ == '__main__':
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix the C analyzer failing on static assertions in free-threaded builds by
using C11 preprocessing and handling static assertion declarations.
15 changes: 15 additions & 0 deletions Tools/c-analyzer/c_parser/parser/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ def match_paren(text, depth=0):
raise ValueError(f'could not find matching parens for {text!r}')


def skip_static_assert(srcinfo):
m = re.match(r'^\s*(?:_Static_assert|static_assert)\b', srcinfo.text)
if not m:
return False
text = srcinfo.text[m.end():]
try:
end = match_paren(text)
except ValueError:
return True
remainder = text[end:].lstrip()
if remainder.startswith(';'):
srcinfo.advance(remainder[1:])
return True


VAR_DECL = set_capture_groups(_VAR_DECL, (
'STORAGE',
'TYPE_QUAL',
Expand Down
3 changes: 3 additions & 0 deletions Tools/c-analyzer/c_parser/parser/_compound_decl_body.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
log_match,
parse_var_decl,
set_capture_groups,
skip_static_assert,
)


Expand All @@ -31,6 +32,8 @@ def parse_struct_body(source, anon_name, parent):
while not done:
done = True
for srcinfo in source:
if skip_static_assert(srcinfo):
continue
m = STRUCT_MEMBER_RE.match(srcinfo.text)
if m:
break
Expand Down
3 changes: 3 additions & 0 deletions Tools/c-analyzer/c_parser/parser/_global.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
log_match,
parse_var_decl,
set_capture_groups,
skip_static_assert,
)
from ._compound_decl_body import DECL_BODY_PARSERS
from ._func_body import parse_function_statics as parse_function_body
Expand Down Expand Up @@ -36,6 +37,8 @@

def parse_globals(source, anon_name):
for srcinfo in source:
if skip_static_assert(srcinfo):
continue
m = GLOBAL_RE.match(srcinfo.text)
if not m:
# We need more text.
Expand Down
2 changes: 1 addition & 1 deletion Tools/c-analyzer/c_parser/preprocessor/gcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@

POST_ARGS = (
'-pthread',
'-std=c99',
'-std=c11',
#'-g',
#'-Og',
#'-Wno-unused-result',
Expand Down
3 changes: 3 additions & 0 deletions Tools/c-analyzer/cpython/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,9 @@ def format_tsv_lines(lines):
MACROS = format_tsv_lines([
# (glob, name, value)

# Alignment does not affect global-state classification.
('*', '_Py_ALIGNED_DEF(N, T)', 'T'),

('Include/internal/*.h', 'Py_BUILD_CORE', '1'),
('Python/**/*.c', 'Py_BUILD_CORE', '1'),
('Python/**/*.h', 'Py_BUILD_CORE', '1'),
Expand Down
Loading