Path: blob/main/Tools/c-analyzer/c_parser/preprocessor/gcc.py
12 views
import os.path1import re23from . import common as _common456TOOL = 'gcc'78META_FILES = {9'<built-in>',10'<command-line>',11}1213# https://gcc.gnu.org/onlinedocs/cpp/Preprocessor-Output.html14# flags:15# 1 start of a new file16# 2 returning to a file (after including another)17# 3 following text comes from a system header file18# 4 following text treated wrapped in implicit extern "C" block19LINE_MARKER_RE = re.compile(r'^# (\d+) "([^"]+)"((?: [1234])*)$')20PREPROC_DIRECTIVE_RE = re.compile(r'^\s*#\s*(\w+)\b.*')21COMPILER_DIRECTIVE_RE = re.compile(r'''22^23(.*?) # <before>24(__\w+__) # <directive>25\s*26[(] [(]27(28[^()]*29(?:30[(]31[^()]*32[)]33[^()]*34)*35) # <args>36( [)] [)] ) # <closed>37''', re.VERBOSE)3839POST_ARGS = (40'-pthread',41'-std=c99',42#'-g',43#'-Og',44#'-Wno-unused-result',45#'-Wsign-compare',46#'-Wall',47#'-Wextra',48'-E',49)505152def preprocess(filename,53incldirs=None,54includes=None,55macros=None,56samefiles=None,57cwd=None,58):59if not cwd or not os.path.isabs(cwd):60cwd = os.path.abspath(cwd or '.')61filename = _normpath(filename, cwd)6263postargs = POST_ARGS64if os.path.basename(filename) == 'socketmodule.h':65# Modules/socketmodule.h uses pycore_time.h which needs Py_BUILD_CORE.66# Usually it's defined by the C file which includes it.67postargs += ('-DPy_BUILD_CORE=1',)6869text = _common.preprocess(70TOOL,71filename,72incldirs=incldirs,73includes=includes,74macros=macros,75#preargs=PRE_ARGS,76postargs=postargs,77executable=['gcc'],78compiler='unix',79cwd=cwd,80)81return _iter_lines(text, filename, samefiles, cwd)828384def _iter_lines(text, reqfile, samefiles, cwd, raw=False):85lines = iter(text.splitlines())8687# The first line is special.88# The next two lines are consistent.89firstlines = [90f'# 0 "{reqfile}"',91'# 0 "<built-in>"',92'# 0 "<command-line>"',93]94if text.startswith('# 1 '):95# Some preprocessors emit a lineno of 1 for line-less entries.96firstlines = [l.replace('# 0 ', '# 1 ') for l in firstlines]97for expected in firstlines:98line = next(lines)99if line != expected:100raise NotImplementedError((line, expected))101102# Do all the CLI-provided includes.103filter_reqfile = (lambda f: _filter_reqfile(f, reqfile, samefiles))104make_info = (lambda lno: _common.FileInfo(reqfile, lno))105last = None106for line in lines:107assert last != reqfile, (last,)108lno, included, flags = _parse_marker_line(line, reqfile)109if not included:110raise NotImplementedError((line,))111if included == reqfile:112# This will be the last one.113assert not flags, (line, flags)114else:115assert 1 in flags, (line, flags)116yield from _iter_top_include_lines(117lines,118_normpath(included, cwd),119cwd,120filter_reqfile,121make_info,122raw,123)124last = included125# The last one is always the requested file.126assert included == reqfile, (line,)127128129def _iter_top_include_lines(lines, topfile, cwd,130filter_reqfile, make_info,131raw):132partial = 0 # depth133files = [topfile]134# We start at 1 in case there are source lines (including blank onces)135# before the first marker line. Also, we already verified in136# _parse_marker_line() that the preprocessor reported lno as 1.137lno = 1138for line in lines:139if line == '# 0 "<command-line>" 2' or line == '# 1 "<command-line>" 2':140# We're done with this top-level include.141return142143_lno, included, flags = _parse_marker_line(line)144if included:145lno = _lno146included = _normpath(included, cwd)147# We hit a marker line.148if 1 in flags:149# We're entering a file.150# XXX Cycles are unexpected?151#assert included not in files, (line, files)152files.append(included)153elif 2 in flags:154# We're returning to a file.155assert files and included in files, (line, files)156assert included != files[-1], (line, files)157while files[-1] != included:158files.pop()159# XXX How can a file return to line 1?160#assert lno > 1, (line, lno)161else:162if included == files[-1]:163# It's the next line from the file.164assert lno > 1, (line, lno)165else:166# We ran into a user-added #LINE directive,167# which we promptly ignore.168pass169elif not files:170raise NotImplementedError((line,))171elif filter_reqfile(files[-1]):172assert lno is not None, (line, files[-1])173if (m := PREPROC_DIRECTIVE_RE.match(line)):174name, = m.groups()175if name != 'pragma':176raise Exception(line)177else:178line = re.sub(r'__inline__', 'inline', line)179if not raw:180line, partial = _strip_directives(line, partial=partial)181yield _common.SourceLine(182make_info(lno),183'source',184line or '',185None,186)187lno += 1188189190def _parse_marker_line(line, reqfile=None):191m = LINE_MARKER_RE.match(line)192if not m:193return None, None, None194lno, origfile, flags = m.groups()195lno = int(lno)196assert origfile not in META_FILES, (line,)197assert lno > 0, (line, lno)198flags = set(int(f) for f in flags.split()) if flags else ()199200if 1 in flags:201# We're entering a file.202assert lno == 1, (line, lno)203assert 2 not in flags, (line,)204elif 2 in flags:205# We're returning to a file.206#assert lno > 1, (line, lno)207pass208elif reqfile and origfile == reqfile:209# We're starting the requested file.210assert lno == 1, (line, lno)211assert not flags, (line, flags)212else:213# It's the next line from the file.214assert lno > 1, (line, lno)215return lno, origfile, flags216217218def _strip_directives(line, partial=0):219# We assume there are no string literals with parens in directive bodies.220while partial > 0:221if not (m := re.match(r'[^{}]*([()])', line)):222return None, partial223delim, = m.groups()224partial += 1 if delim == '(' else -1 # opened/closed225line = line[m.end():]226227line = re.sub(r'__extension__', '', line)228line = re.sub(r'__thread\b', '_Thread_local', line)229230while (m := COMPILER_DIRECTIVE_RE.match(line)):231before, _, _, closed = m.groups()232if closed:233line = f'{before} {line[m.end():]}'234else:235after, partial = _strip_directives(line[m.end():], 2)236line = f'{before} {after or ""}'237if partial:238break239240return line, partial241242243def _filter_reqfile(current, reqfile, samefiles):244if current == reqfile:245return True246if current == '<stdin>':247return True248if current in samefiles:249return True250return False251252253def _normpath(filename, cwd):254assert cwd255return os.path.normpath(os.path.join(cwd, filename))256257258