Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gh-81057: Add a CI Check for New Unsupported C Global Variables #102506

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
3651ee7
Print a helpful message when the globals check fails.
ericsnowcurrently Mar 7, 2023
1c1e0b0
Print the explanation even when the parser breaks.
ericsnowcurrently Mar 7, 2023
aa08d9d
Enable the CI globals check.
ericsnowcurrently Mar 7, 2023
94db0e4
Add the globals check to CI.
ericsnowcurrently Mar 7, 2023
68db07f
Drop the test.
ericsnowcurrently Mar 7, 2023
9ed998a
Fix the command.
ericsnowcurrently Mar 7, 2023
5ee7611
Add some temporary debugging code.
ericsnowcurrently Mar 7, 2023
bb1463e
Accommodate newer GCC versions.
ericsnowcurrently Mar 7, 2023
d9f45a8
Fix the "ignored" filename.
ericsnowcurrently Mar 7, 2023
f1e5827
Emit the message from the right script.
ericsnowcurrently Mar 7, 2023
d59e2af
Print the traceback.
ericsnowcurrently Mar 7, 2023
a959ccd
Let lineno be 0 for meta files.
ericsnowcurrently Mar 7, 2023
3390014
Allow meta files with flags.
ericsnowcurrently Mar 8, 2023
8f93550
Special-case <command-line>.
ericsnowcurrently Mar 8, 2023
76039c0
Flush stdout/stderr around the explanation.
ericsnowcurrently Mar 8, 2023
dc4ee3a
Print preprocessor errors by default.
ericsnowcurrently Mar 8, 2023
4c03fd0
Add a possible include dir for uuid.
ericsnowcurrently Mar 8, 2023
863e152
Add other possible include dirs.
ericsnowcurrently Mar 8, 2023
f230d79
Use Same Capitalization as Other Jobs
ericsnowcurrently Mar 14, 2023
08f9279
Add a make target.
ericsnowcurrently Mar 14, 2023
c220d54
Merge branch 'main' into enable-globals-ci-check
ericsnowcurrently Mar 14, 2023
9cc170c
Ignore a global that snuck in.
ericsnowcurrently Mar 14, 2023
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
3 changes: 3 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ jobs:
run: make smelly
- name: Check limited ABI symbols
run: make check-limited-abi
- name: Check for unsupported C global variables
if: github.event_name == 'pull_request' # $GITHUB_EVENT_NAME
run: make check-c-globals

build_win32:
name: 'Windows (x86)'
Expand Down
34 changes: 0 additions & 34 deletions Lib/test/test_check_c_globals.py

This file was deleted.

6 changes: 6 additions & 0 deletions Makefile.pre.in
Original file line number Diff line number Diff line change
Expand Up @@ -2560,6 +2560,12 @@ distclean: clobber docclean
smelly: all
$(RUNSHARED) ./$(BUILDPYTHON) $(srcdir)/Tools/build/smelly.py

# Check if any unsupported C global variables have been added.
check-c-globals:
$(PYTHON_FOR_REGEN) $(srcdir)/Tools/c-analyzer/check-c-globals.py \
--format summary \
--traceback

# Find files with funny names
funny:
find $(SUBDIRS) $(SUBDIRSTOO) \
Expand Down
6 changes: 3 additions & 3 deletions Tools/c-analyzer/c_parser/preprocessor/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,15 @@ def converted_error(tool, argv, filename):
def convert_error(tool, argv, filename, stderr, rc):
error = (stderr.splitlines()[0], rc)
if (_expected := is_os_mismatch(filename, stderr)):
logger.debug(stderr.strip())
logger.info(stderr.strip())
raise OSMismatchError(filename, _expected, argv, error, tool)
elif (_missing := is_missing_dep(stderr)):
logger.debug(stderr.strip())
logger.info(stderr.strip())
raise MissingDependenciesError(filename, (_missing,), argv, error, tool)
elif '#error' in stderr:
# XXX Ignore incompatible files.
error = (stderr.splitlines()[1], rc)
logger.debug(stderr.strip())
logger.info(stderr.strip())
raise ErrorDirectiveError(filename, argv, error, tool)
else:
# Try one more time, with stderr written to the terminal.
Expand Down
23 changes: 16 additions & 7 deletions Tools/c-analyzer/c_parser/preprocessor/gcc.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@

TOOL = 'gcc'

META_FILES = {
'<built-in>',
'<command-line>',
}

# https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html
# flags:
# 1 start of a new file
Expand Down Expand Up @@ -75,11 +80,15 @@ def _iter_lines(text, reqfile, samefiles, cwd, raw=False):

# The first line is special.
# The next two lines are consistent.
for expected in [
f'# 1 "{reqfile}"',
'# 1 "<built-in>"',
'# 1 "<command-line>"',
]:
firstlines = [
f'# 0 "{reqfile}"',
'# 0 "<built-in>"',
'# 0 "<command-line>"',
]
if text.startswith('# 1 '):
# Some preprocessors emit a lineno of 1 for line-less entries.
firstlines = [l.replace('# 0 ', '# 1 ') for l in firstlines]
for expected in firstlines:
line = next(lines)
if line != expected:
raise NotImplementedError((line, expected))
Expand Down Expand Up @@ -121,7 +130,7 @@ def _iter_top_include_lines(lines, topfile, cwd,
# _parse_marker_line() that the preprocessor reported lno as 1.
lno = 1
for line in lines:
if line == '# 1 "<command-line>" 2':
if line == '# 0 "<command-line>" 2' or line == '# 1 "<command-line>" 2':
# We're done with this top-level include.
return

Expand Down Expand Up @@ -174,8 +183,8 @@ def _parse_marker_line(line, reqfile=None):
return None, None, None
lno, origfile, flags = m.groups()
lno = int(lno)
assert origfile not in META_FILES, (line,)
assert lno > 0, (line, lno)
assert origfile not in ('<built-in>', '<command-line>'), (line,)
flags = set(int(f) for f in flags.split()) if flags else ()

if 1 in flags:
Expand Down
62 changes: 54 additions & 8 deletions Tools/c-analyzer/cpython/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import sys
import textwrap

from c_common.fsutil import expand_filenames, iter_files_by_suffix
from c_common.scriptutil import (
Expand All @@ -26,6 +27,39 @@
logger = logging.getLogger(__name__)


CHECK_EXPLANATION = textwrap.dedent('''
-------------------------

Non-constant global variables are generally not supported
in the CPython repo. We use a tool to analyze the C code
and report if any unsupported globals are found. The tool
may be run manually with:

./python Tools/c-analyzer/check-c-globals.py --format summary [FILE]

Occasionally the tool is unable to parse updated code.
If this happens then add the file to the "EXCLUDED" list
in Tools/c-analyzer/cpython/_parser.py and create a new
issue for fixing the tool (and CC ericsnowcurrently
on the issue).

If the tool reports an unsupported global variable and
it is actually const (and thus supported) then first try
fixing the declaration appropriately in the code. If that
doesn't work then add the variable to the "should be const"
section of Tools/c-analyzer/cpython/ignored.tsv.

If the tool otherwise reports an unsupported global variable
then first try to make it non-global, possibly adding to
PyInterpreterState (for core code) or module state (for
extension modules). In an emergency, you can add the
variable to Tools/c-analyzer/cpython/globals-to-fix.tsv
to get CI passing, but doing so should be avoided. If
this course it taken, be sure to create an issue for
eliminating the global (and CC ericsnowcurrently).
''')


def _resolve_filenames(filenames):
if filenames:
resolved = (_files.resolve_filename(f) for f in filenames)
Expand Down Expand Up @@ -123,14 +157,26 @@ def _cli_check(parser, **kwargs):
def cmd_check(filenames=None, **kwargs):
filenames = _resolve_filenames(filenames)
kwargs['get_file_preprocessor'] = _parser.get_preprocessor(log_err=print)
c_analyzer.cmd_check(
filenames,
relroot=REPO_ROOT,
_analyze=_analyzer.analyze,
_CHECKS=CHECKS,
file_maxsizes=_parser.MAX_SIZES,
**kwargs
)
try:
c_analyzer.cmd_check(
filenames,
relroot=REPO_ROOT,
_analyze=_analyzer.analyze,
_CHECKS=CHECKS,
file_maxsizes=_parser.MAX_SIZES,
**kwargs
)
except SystemExit as exc:
num_failed = exc.args[0] if getattr(exc, 'args', None) else None
if isinstance(num_failed, int):
if num_failed > 0:
sys.stderr.flush()
print(CHECK_EXPLANATION, flush=True)
raise # re-raise
except Exception:
sys.stderr.flush()
print(CHECK_EXPLANATION, flush=True)
raise # re-raise


def cmd_analyze(filenames=None, **kwargs):
Expand Down
9 changes: 7 additions & 2 deletions Tools/c-analyzer/cpython/_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,15 +106,20 @@ def clean_lines(text):
* ./Include/internal

Modules/_decimal/**/*.c Modules/_decimal/libmpdec
Modules/_elementtree.c Modules/expat
Modules/_hacl/*.c Modules/_hacl/include
Modules/_hacl/*.h Modules/_hacl/include
Modules/_tkinter.c /usr/include/tcl8.6
Modules/md5module.c Modules/_hacl/include
Modules/sha1module.c Modules/_hacl/include
Modules/sha2module.c Modules/_hacl/include
Modules/tkappinit.c /usr/include/tcl
Objects/stringlib/*.h Objects

# possible system-installed headers, just in case
Modules/_tkinter.c /usr/include/tcl8.6
Modules/_uuidmodule.c /usr/include/uuid
Modules/nismodule.c /usr/include/tirpc
Modules/tkappinit.c /usr/include/tcl

# @end=tsv@
''')[1:]

Expand Down
1 change: 1 addition & 0 deletions Tools/c-analyzer/cpython/ignored.tsv
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ Modules/_xxinterpchannelsmodule.c - _channelid_end_recv -
Modules/_xxinterpchannelsmodule.c - _channelid_end_send -
Modules/_zoneinfo.c - DAYS_BEFORE_MONTH -
Modules/_zoneinfo.c - DAYS_IN_MONTH -
Modules/_xxsubinterpretersmodule.c - no_exception -
Modules/arraymodule.c - descriptors -
Modules/arraymodule.c - emptybuf -
Modules/cjkcodecs/_codecs_cn.c - _mapping_list -
Expand Down