Browse Source
bpo-36876: Small adjustments to the C-analyzer tool. (GH-23045)
bpo-36876: Small adjustments to the C-analyzer tool. (GH-23045)
This is a little bit of clean-up, small fixes, and additional helpers prior to building an updated & accurate list of globals to eliminate.pull/23050/head
committed by
GitHub
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 633 additions and 218 deletions
-
8Tools/c-analyzer/c_analyzer/__init__.py
-
104Tools/c-analyzer/c_analyzer/__main__.py
-
6Tools/c-analyzer/c_analyzer/analyze.py
-
3Tools/c-analyzer/c_analyzer/datafiles.py
-
42Tools/c-analyzer/c_analyzer/info.py
-
212Tools/c-analyzer/c_analyzer/match.py
-
51Tools/c-analyzer/c_common/scriptutil.py
-
2Tools/c-analyzer/c_parser/datafiles.py
-
166Tools/c-analyzer/c_parser/info.py
-
177Tools/c-analyzer/c_parser/match.py
-
6Tools/c-analyzer/c_parser/parser/__init__.py
-
15Tools/c-analyzer/c_parser/parser/_info.py
-
3Tools/c-analyzer/c_parser/parser/_regexes.py
-
5Tools/c-analyzer/cpython/__main__.py
-
7Tools/c-analyzer/cpython/_analyzer.py
-
44Tools/c-analyzer/cpython/_parser.py
@ -0,0 +1,212 @@ |
|||
import os.path |
|||
|
|||
from c_parser import ( |
|||
info as _info, |
|||
match as _match, |
|||
) |
|||
|
|||
|
|||
_KIND = _info.KIND |
|||
|
|||
|
|||
# XXX Use known.tsv for these? |
|||
SYSTEM_TYPES = { |
|||
'int8_t', |
|||
'uint8_t', |
|||
'int16_t', |
|||
'uint16_t', |
|||
'int32_t', |
|||
'uint32_t', |
|||
'int64_t', |
|||
'uint64_t', |
|||
'size_t', |
|||
'ssize_t', |
|||
'intptr_t', |
|||
'uintptr_t', |
|||
'wchar_t', |
|||
'', |
|||
# OS-specific |
|||
'pthread_cond_t', |
|||
'pthread_mutex_t', |
|||
'pthread_key_t', |
|||
'atomic_int', |
|||
'atomic_uintptr_t', |
|||
'', |
|||
# lib-specific |
|||
'WINDOW', # curses |
|||
'XML_LChar', |
|||
'XML_Size', |
|||
'XML_Parser', |
|||
'enum XML_Error', |
|||
'enum XML_Status', |
|||
'', |
|||
} |
|||
|
|||
|
|||
def is_system_type(typespec): |
|||
return typespec in SYSTEM_TYPES |
|||
|
|||
|
|||
################################## |
|||
# decl matchers |
|||
|
|||
def is_public(decl): |
|||
if not decl.filename.endswith('.h'): |
|||
return False |
|||
if 'Include' not in decl.filename.split(os.path.sep): |
|||
return False |
|||
return True |
|||
|
|||
|
|||
def is_process_global(vardecl): |
|||
kind, storage, _, _, _ = _info.get_parsed_vartype(vardecl) |
|||
if kind is not _KIND.VARIABLE: |
|||
raise NotImplementedError(vardecl) |
|||
if 'static' in (storage or ''): |
|||
return True |
|||
|
|||
if hasattr(vardecl, 'parent'): |
|||
parent = vardecl.parent |
|||
else: |
|||
parent = vardecl.get('parent') |
|||
return not parent |
|||
|
|||
|
|||
def is_fixed_type(vardecl): |
|||
if not vardecl: |
|||
return None |
|||
_, _, _, typespec, abstract = _info.get_parsed_vartype(vardecl) |
|||
if 'typeof' in typespec: |
|||
raise NotImplementedError(vardecl) |
|||
elif not abstract: |
|||
return True |
|||
|
|||
if '*' not in abstract: |
|||
# XXX What about []? |
|||
return True |
|||
elif _match._is_funcptr(abstract): |
|||
return True |
|||
else: |
|||
for after in abstract.split('*')[1:]: |
|||
if not after.lstrip().startswith('const'): |
|||
return False |
|||
else: |
|||
return True |
|||
|
|||
|
|||
def is_immutable(vardecl): |
|||
if not vardecl: |
|||
return None |
|||
if not is_fixed_type(vardecl): |
|||
return False |
|||
_, _, typequal, _, _ = _info.get_parsed_vartype(vardecl) |
|||
# If there, it can only be "const" or "volatile". |
|||
return typequal == 'const' |
|||
|
|||
|
|||
def is_public_api(decl): |
|||
if not is_public(decl): |
|||
return False |
|||
if decl.kind is _KIND.TYPEDEF: |
|||
return True |
|||
elif _match.is_type_decl(decl): |
|||
return not _match.is_forward_decl(decl) |
|||
else: |
|||
return _match.is_external_reference(decl) |
|||
|
|||
|
|||
def is_public_declaration(decl): |
|||
if not is_public(decl): |
|||
return False |
|||
if decl.kind is _KIND.TYPEDEF: |
|||
return True |
|||
elif _match.is_type_decl(decl): |
|||
return _match.is_forward_decl(decl) |
|||
else: |
|||
return _match.is_external_reference(decl) |
|||
|
|||
|
|||
def is_public_definition(decl): |
|||
if not is_public(decl): |
|||
return False |
|||
if decl.kind is _KIND.TYPEDEF: |
|||
return True |
|||
elif _match.is_type_decl(decl): |
|||
return not _match.is_forward_decl(decl) |
|||
else: |
|||
return not _match.is_external_reference(decl) |
|||
|
|||
|
|||
def is_public_impl(decl): |
|||
if not _KIND.is_decl(decl.kind): |
|||
return False |
|||
# See filter_forward() about "is_public". |
|||
return getattr(decl, 'is_public', False) |
|||
|
|||
|
|||
def is_module_global_decl(decl): |
|||
if is_public_impl(decl): |
|||
return False |
|||
if _match.is_forward_decl(decl): |
|||
return False |
|||
return not _match.is_local_var(decl) |
|||
|
|||
|
|||
################################## |
|||
# filtering with matchers |
|||
|
|||
def filter_forward(items, *, markpublic=False): |
|||
if markpublic: |
|||
public = set() |
|||
actual = [] |
|||
for item in items: |
|||
if is_public_api(item): |
|||
public.add(item.id) |
|||
elif not _match.is_forward_decl(item): |
|||
actual.append(item) |
|||
else: |
|||
# non-public duplicate! |
|||
# XXX |
|||
raise Exception(item) |
|||
for item in actual: |
|||
_info.set_flag(item, 'is_public', item.id in public) |
|||
yield item |
|||
else: |
|||
for item in items: |
|||
if _match.is_forward_decl(item): |
|||
continue |
|||
yield item |
|||
|
|||
|
|||
################################## |
|||
# grouping with matchers |
|||
|
|||
def group_by_storage(decls, **kwargs): |
|||
def is_module_global(decl): |
|||
if not is_module_global_decl(decl): |
|||
return False |
|||
if decl.kind == _KIND.VARIABLE: |
|||
if _info.get_effective_storage(decl) == 'static': |
|||
# This is covered by is_static_module_global(). |
|||
return False |
|||
return True |
|||
def is_static_module_global(decl): |
|||
if not _match.is_global_var(decl): |
|||
return False |
|||
return _info.get_effective_storage(decl) == 'static' |
|||
def is_static_local(decl): |
|||
if not _match.is_local_var(decl): |
|||
return False |
|||
return _info.get_effective_storage(decl) == 'static' |
|||
#def is_local(decl): |
|||
# if not _match.is_local_var(decl): |
|||
# return False |
|||
# return _info.get_effective_storage(decl) != 'static' |
|||
categories = { |
|||
#'extern': is_extern, |
|||
'published': is_public_impl, |
|||
'module-global': is_module_global, |
|||
'static-module-global': is_static_module_global, |
|||
'static-local': is_static_local, |
|||
} |
|||
return _match.group_by_category(decls, categories, **kwargs) |
|||
@ -0,0 +1,177 @@ |
|||
import re |
|||
|
|||
from . import info as _info |
|||
from .parser._regexes import SIMPLE_TYPE |
|||
|
|||
|
|||
_KIND = _info.KIND |
|||
|
|||
|
|||
def match_storage(decl, expected): |
|||
default = _info.get_default_storage(decl) |
|||
#assert default |
|||
if expected is None: |
|||
expected = {default} |
|||
elif isinstance(expected, str): |
|||
expected = {expected or default} |
|||
elif not expected: |
|||
expected = _info.STORAGE |
|||
else: |
|||
expected = {v or default for v in expected} |
|||
storage = _info.get_effective_storage(decl, default=default) |
|||
return storage in expected |
|||
|
|||
|
|||
################################## |
|||
# decl matchers |
|||
|
|||
def is_type_decl(item): |
|||
return _KIND.is_type_decl(item.kind) |
|||
|
|||
|
|||
def is_decl(item): |
|||
return _KIND.is_decl(item.kind) |
|||
|
|||
|
|||
def is_pots(typespec, *, |
|||
_regex=re.compile(rf'^{SIMPLE_TYPE}$', re.VERBOSE), |
|||
): |
|||
|
|||
if not typespec: |
|||
return None |
|||
if type(typespec) is not str: |
|||
_, _, _, typespec, _ = _info.get_parsed_vartype(typespec) |
|||
return _regex.match(typespec) is not None |
|||
|
|||
|
|||
def is_funcptr(vartype): |
|||
if not vartype: |
|||
return None |
|||
_, _, _, _, abstract = _info.get_parsed_vartype(vartype) |
|||
return _is_funcptr(abstract) |
|||
|
|||
|
|||
def _is_funcptr(declstr): |
|||
if not declstr: |
|||
return None |
|||
# XXX Support "(<name>*)(". |
|||
return '(*)(' in declstr.replace(' ', '') |
|||
|
|||
|
|||
def is_forward_decl(decl): |
|||
if decl.kind is _KIND.TYPEDEF: |
|||
return False |
|||
elif is_type_decl(decl): |
|||
return not decl.data |
|||
elif decl.kind is _KIND.FUNCTION: |
|||
# XXX This doesn't work with ParsedItem. |
|||
return decl.signature.isforward |
|||
elif decl.kind is _KIND.VARIABLE: |
|||
# No var decls are considered forward (or all are...). |
|||
return False |
|||
else: |
|||
raise NotImplementedError(decl) |
|||
|
|||
|
|||
def can_have_symbol(decl): |
|||
return decl.kind in (_KIND.VARIABLE, _KIND.FUNCTION) |
|||
|
|||
|
|||
def has_external_symbol(decl): |
|||
if not can_have_symbol(decl): |
|||
return False |
|||
if _info.get_effective_storage(decl) != 'extern': |
|||
return False |
|||
if decl.kind is _KIND.FUNCTION: |
|||
return not decl.signature.isforward |
|||
else: |
|||
# It must be a variable, which can only be implicitly extern here. |
|||
return decl.storage != 'extern' |
|||
|
|||
|
|||
def has_internal_symbol(decl): |
|||
if not can_have_symbol(decl): |
|||
return False |
|||
return _info.get_actual_storage(decl) == 'static' |
|||
|
|||
|
|||
def is_external_reference(decl): |
|||
if not can_have_symbol(decl): |
|||
return False |
|||
# We have to check the declared storage rather tnan the effective. |
|||
if decl.storage != 'extern': |
|||
return False |
|||
if decl.kind is _KIND.FUNCTION: |
|||
return decl.signature.isforward |
|||
# Otherwise it's a variable. |
|||
return True |
|||
|
|||
|
|||
def is_local_var(decl): |
|||
if not decl.kind is _KIND.VARIABLE: |
|||
return False |
|||
return True if decl.parent else False |
|||
|
|||
|
|||
def is_global_var(decl): |
|||
if not decl.kind is _KIND.VARIABLE: |
|||
return False |
|||
return False if decl.parent else True |
|||
|
|||
|
|||
################################## |
|||
# filtering with matchers |
|||
|
|||
def filter_by_kind(items, kind): |
|||
if kind == 'type': |
|||
kinds = _KIND._TYPE_DECLS |
|||
elif kind == 'decl': |
|||
kinds = _KIND._TYPE_DECLS |
|||
try: |
|||
okay = kind in _KIND |
|||
except TypeError: |
|||
kinds = set(kind) |
|||
else: |
|||
kinds = {kind} if okay else set(kind) |
|||
for item in items: |
|||
if item.kind in kinds: |
|||
yield item |
|||
|
|||
|
|||
################################## |
|||
# grouping with matchers |
|||
|
|||
def group_by_category(decls, categories, *, ignore_non_match=True): |
|||
collated = {} |
|||
for decl in decls: |
|||
# Matchers should be mutually exclusive. (First match wins.) |
|||
for category, match in categories.items(): |
|||
if match(decl): |
|||
if category not in collated: |
|||
collated[category] = [decl] |
|||
else: |
|||
collated[category].append(decl) |
|||
break |
|||
else: |
|||
if not ignore_non_match: |
|||
raise Exception(f'no match for {decl!r}') |
|||
return collated |
|||
|
|||
|
|||
def group_by_kind(items): |
|||
collated = {kind: [] for kind in _KIND} |
|||
for item in items: |
|||
try: |
|||
collated[item.kind].append(item) |
|||
except KeyError: |
|||
raise ValueError(f'unsupported kind in {item!r}') |
|||
return collated |
|||
|
|||
|
|||
def group_by_kinds(items): |
|||
# Collate into kind groups (decl, type, etc.). |
|||
collated = {_KIND.get_group(k): [] for k in _KIND} |
|||
for item in items: |
|||
group = _KIND.get_group(item.kind) |
|||
collated[group].append(item) |
|||
return collated |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue