diff options
author | Eric Snow <ericsnowcurrently@gmail.com> | 2019-09-11 18:49:45 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-09-11 18:49:45 (GMT) |
commit | ee536b2020b1f0baad1286dbd4345e13870324af (patch) | |
tree | 2486233603db05a76aaef863bd6639455e3dfef7 /Tools/c-analyzer/c_parser/naive.py | |
parent | 9936371af298d465095ae70bc9c2943b4b16eac4 (diff) | |
download | cpython-ee536b2020b1f0baad1286dbd4345e13870324af.zip cpython-ee536b2020b1f0baad1286dbd4345e13870324af.tar.gz cpython-ee536b2020b1f0baad1286dbd4345e13870324af.tar.bz2 |
bpo-36876: Add a tool that identifies unsupported global C variables. (#15877)
Diffstat (limited to 'Tools/c-analyzer/c_parser/naive.py')
-rw-r--r-- | Tools/c-analyzer/c_parser/naive.py | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/Tools/c-analyzer/c_parser/naive.py b/Tools/c-analyzer/c_parser/naive.py new file mode 100644 index 0000000..e0370cc --- /dev/null +++ b/Tools/c-analyzer/c_parser/naive.py @@ -0,0 +1,180 @@ +import re + +from c_analyzer_common.info import UNKNOWN + +from .info import Variable +from .preprocessor import _iter_clean_lines + + +_NOT_SET = object() + + +def get_srclines(filename, *, + cache=None, + _open=open, + _iter_lines=_iter_clean_lines, + ): + """Return the file's lines as a list. + + Each line will have trailing whitespace removed (including newline). + + If a cache is given the it is used. + """ + if cache is not None: + try: + return cache[filename] + except KeyError: + pass + + with _open(filename) as srcfile: + srclines = [line + for _, line in _iter_lines(srcfile) + if not line.startswith('#')] + for i, line in enumerate(srclines): + srclines[i] = line.rstrip() + + if cache is not None: + cache[filename] = srclines + return srclines + + +def parse_variable_declaration(srcline): + """Return (name, decl) for the given declaration line.""" + # XXX possible false negatives... + decl, sep, _ = srcline.partition('=') + if not sep: + if not srcline.endswith(';'): + return None, None + decl = decl.strip(';') + decl = decl.strip() + m = re.match(r'.*\b(\w+)\s*(?:\[[^\]]*\])?$', decl) + if not m: + return None, None + name = m.group(1) + return name, decl + + +def parse_variable(srcline, funcname=None): + """Return a Variable for the variable declared on the line (or None).""" + line = srcline.strip() + + # XXX Handle more than just static variables. + if line.startswith('static '): + if '(' in line and '[' not in line: + # a function + return None, None + return parse_variable_declaration(line) + else: + return None, None + + +def iter_variables(filename, *, + srccache=None, + parse_variable=None, + _get_srclines=get_srclines, + _default_parse_variable=parse_variable, + ): + """Yield a Variable for each in the given source file.""" + if parse_variable is None: + parse_variable = _default_parse_variable + + indent = '' + prev = '' + funcname = None + for line in _get_srclines(filename, cache=srccache): + # remember current funcname + if funcname: + if line == indent + '}': + funcname = None + continue + else: + if '(' in prev and line == indent + '{': + if not prev.startswith('__attribute__'): + funcname = prev.split('(')[0].split()[-1] + prev = '' + continue + indent = line[:-len(line.lstrip())] + prev = line + + info = parse_variable(line, funcname) + if isinstance(info, list): + for name, _funcname, decl in info: + yield Variable.from_parts(filename, _funcname, name, decl) + continue + name, decl = info + + if name is None: + continue + yield Variable.from_parts(filename, funcname, name, decl) + + +def _match_varid(variable, name, funcname, ignored=None): + if ignored and variable in ignored: + return False + + if variable.name != name: + return False + + if funcname == UNKNOWN: + if not variable.funcname: + return False + elif variable.funcname != funcname: + return False + + return True + + +def find_variable(filename, funcname, name, *, + ignored=None, + srccache=None, # {filename: lines} + parse_variable=None, + _iter_variables=iter_variables, + ): + """Return the matching variable. + + Return None if the variable is not found. + """ + for variable in _iter_variables(filename, + srccache=srccache, + parse_variable=parse_variable, + ): + if _match_varid(variable, name, funcname, ignored): + return variable + else: + return None + + +def find_variables(varids, filenames=None, *, + srccache=_NOT_SET, + parse_variable=None, + _find_symbol=find_variable, + ): + """Yield a Variable for each ID. + + If the variable is not found then its decl will be UNKNOWN. That + way there will be one resulting Variable per given ID. + """ + if srccache is _NOT_SET: + srccache = {} + + used = set() + for varid in varids: + if varid.filename and varid.filename != UNKNOWN: + srcfiles = [varid.filename] + else: + if not filenames: + yield Variable(varid, UNKNOWN) + continue + srcfiles = filenames + for filename in srcfiles: + found = _find_varid(filename, varid.funcname, varid.name, + ignored=used, + srccache=srccache, + parse_variable=parse_variable, + ) + if found: + yield found + used.add(found) + break + else: + yield Variable(varid, UNKNOWN) |