181 lines
5.2 KiB
Python
181 lines
5.2 KiB
Python
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)
|