• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1import os.path
2import re
3
4from c_common.clsutil import classonly
5from c_parser.info import (
6    KIND,
7    DeclID,
8    Declaration,
9    TypeDeclaration,
10    TypeDef,
11    Struct,
12    Member,
13    FIXED_TYPE,
14)
15from c_parser.match import (
16    is_type_decl,
17    is_pots,
18    is_funcptr,
19)
20from c_analyzer.match import (
21    is_system_type,
22    is_process_global,
23    is_fixed_type,
24    is_immutable,
25)
26import c_analyzer as _c_analyzer
27import c_analyzer.info as _info
28import c_analyzer.datafiles as _datafiles
29from . import _parser, REPO_ROOT
30
31
32_DATA_DIR = os.path.dirname(__file__)
33KNOWN_FILE = os.path.join(_DATA_DIR, 'known.tsv')
34IGNORED_FILE = os.path.join(_DATA_DIR, 'ignored.tsv')
35KNOWN_IN_DOT_C = {
36    'struct _odictobject': False,
37    'PyTupleObject': False,
38    'struct _typeobject': False,
39    'struct _arena': True,  # ???
40    'struct _frame': False,
41    'struct _ts': True,  # ???
42    'struct PyCodeObject': False,
43    'struct _is': True,  # ???
44    'PyWideStringList': True,  # ???
45    # recursive
46    'struct _dictkeysobject': False,
47}
48# These are loaded from the respective .tsv files upon first use.
49_KNOWN = {
50    # {(file, ID) | ID => info | bool}
51    #'PyWideStringList': True,
52}
53#_KNOWN = {(Struct(None, typeid.partition(' ')[-1], None)
54#           if typeid.startswith('struct ')
55#           else TypeDef(None, typeid, None)
56#           ): ([], {'unsupported': None if supported else True})
57#          for typeid, supported in _KNOWN_IN_DOT_C.items()}
58_IGNORED = {
59    # {ID => reason}
60}
61
62KINDS = frozenset((*KIND.TYPES, KIND.VARIABLE))
63
64
65def read_known():
66    if not _KNOWN:
67        # Cache a copy the first time.
68        extracols = None  # XXX
69        #extracols = ['unsupported']
70        known = _datafiles.read_known(KNOWN_FILE, extracols, REPO_ROOT)
71        # For now we ignore known.values() (i.e. "extra").
72        types, _ = _datafiles.analyze_known(
73            known,
74            analyze_resolved=analyze_resolved,
75        )
76        _KNOWN.update(types)
77    return _KNOWN.copy()
78
79
80def write_known():
81    raise NotImplementedError
82    datafiles.write_known(decls, IGNORED_FILE, ['unsupported'], relroot=REPO_ROOT)
83
84
85def read_ignored():
86    if not _IGNORED:
87        _IGNORED.update(_datafiles.read_ignored(IGNORED_FILE, relroot=REPO_ROOT))
88    return dict(_IGNORED)
89
90
91def write_ignored():
92    raise NotImplementedError
93    _datafiles.write_ignored(variables, IGNORED_FILE, relroot=REPO_ROOT)
94
95
96def analyze(filenames, *,
97            skip_objects=False,
98            **kwargs
99            ):
100    if skip_objects:
101        # XXX Set up a filter.
102        raise NotImplementedError
103
104    known = read_known()
105
106    decls = iter_decls(filenames)
107    results = _c_analyzer.analyze_decls(
108        decls,
109        known,
110        analyze_resolved=analyze_resolved,
111    )
112    analysis = Analysis.from_results(results)
113
114    return analysis
115
116
117def iter_decls(filenames, **kwargs):
118    decls = _c_analyzer.iter_decls(
119        filenames,
120        # We ignore functions (and statements).
121        kinds=KINDS,
122        parse_files=_parser.parse_files,
123        **kwargs
124    )
125    for decl in decls:
126        if not decl.data:
127            # Ignore forward declarations.
128            continue
129        yield decl
130
131
132def analyze_resolved(resolved, decl, types, knowntypes, extra=None):
133    if decl.kind not in KINDS:
134        # Skip it!
135        return None
136
137    typedeps = resolved
138    if typedeps is _info.UNKNOWN:
139        if decl.kind in (KIND.STRUCT, KIND.UNION):
140            typedeps = [typedeps] * len(decl.members)
141        else:
142            typedeps = [typedeps]
143    #assert isinstance(typedeps, (list, TypeDeclaration)), typedeps
144
145    if extra is None:
146        extra = {}
147    elif 'unsupported' in extra:
148        raise NotImplementedError((decl, extra))
149
150    unsupported = _check_unsupported(decl, typedeps, types, knowntypes)
151    extra['unsupported'] = unsupported
152
153    return typedeps, extra
154
155
156def _check_unsupported(decl, typedeps, types, knowntypes):
157    if typedeps is None:
158        raise NotImplementedError(decl)
159
160    if decl.kind in (KIND.STRUCT, KIND.UNION):
161        return _check_members(decl, typedeps, types, knowntypes)
162    elif decl.kind is KIND.ENUM:
163        if typedeps:
164            raise NotImplementedError((decl, typedeps))
165        return None
166    else:
167        return _check_typedep(decl, typedeps, types, knowntypes)
168
169
170def _check_members(decl, typedeps, types, knowntypes):
171    if isinstance(typedeps, TypeDeclaration):
172        raise NotImplementedError((decl, typedeps))
173
174    #members = decl.members or ()  # A forward decl has no members.
175    members = decl.members
176    if not members:
177        # A forward decl has no members, but that shouldn't surface here..
178        raise NotImplementedError(decl)
179    if len(members) != len(typedeps):
180        raise NotImplementedError((decl, typedeps))
181
182    unsupported = []
183    for member, typedecl in zip(members, typedeps):
184        checked = _check_typedep(member, typedecl, types, knowntypes)
185        unsupported.append(checked)
186    if any(None if v is FIXED_TYPE else v for v in unsupported):
187        return unsupported
188    elif FIXED_TYPE in unsupported:
189        return FIXED_TYPE
190    else:
191        return None
192
193
194def _check_typedep(decl, typedecl, types, knowntypes):
195    if not isinstance(typedecl, TypeDeclaration):
196        if hasattr(type(typedecl), '__len__'):
197            if len(typedecl) == 1:
198                typedecl, = typedecl
199    if typedecl is None:
200        # XXX Fail?
201        return 'typespec (missing)'
202    elif typedecl is _info.UNKNOWN:
203        # XXX Is this right?
204        return 'typespec (unknown)'
205    elif not isinstance(typedecl, TypeDeclaration):
206        raise NotImplementedError((decl, typedecl))
207
208    if isinstance(decl, Member):
209        return _check_vartype(decl, typedecl, types, knowntypes)
210    elif not isinstance(decl, Declaration):
211        raise NotImplementedError(decl)
212    elif decl.kind is KIND.TYPEDEF:
213        return _check_vartype(decl, typedecl, types, knowntypes)
214    elif decl.kind is KIND.VARIABLE:
215        if not is_process_global(decl):
216            return None
217        checked = _check_vartype(decl, typedecl, types, knowntypes)
218        return 'mutable' if checked is FIXED_TYPE else checked
219    else:
220        raise NotImplementedError(decl)
221
222
223def _check_vartype(decl, typedecl, types, knowntypes):
224    """Return failure reason."""
225    checked = _check_typespec(decl, typedecl, types, knowntypes)
226    if checked:
227        return checked
228    if is_immutable(decl.vartype):
229        return None
230    if is_fixed_type(decl.vartype):
231        return FIXED_TYPE
232    return 'mutable'
233
234
235def _check_typespec(decl, typedecl, types, knowntypes):
236    typespec = decl.vartype.typespec
237    if typedecl is not None:
238        found = types.get(typedecl)
239        if found is None:
240            found = knowntypes.get(typedecl)
241
242        if found is not None:
243            _, extra = found
244            if extra is None:
245                # XXX Under what circumstances does this happen?
246                extra = {}
247            unsupported = extra.get('unsupported')
248            if unsupported is FIXED_TYPE:
249                unsupported = None
250            return 'typespec' if unsupported else None
251    # Fall back to default known types.
252    if is_pots(typespec):
253        return None
254    elif is_system_type(typespec):
255        return None
256    elif is_funcptr(decl.vartype):
257        return None
258    return 'typespec'
259
260
261class Analyzed(_info.Analyzed):
262
263    @classonly
264    def is_target(cls, raw):
265        if not super().is_target(raw):
266            return False
267        if raw.kind not in KINDS:
268            return False
269        return True
270
271    #@classonly
272    #def _parse_raw_result(cls, result, extra):
273    #    typedecl, extra = super()._parse_raw_result(result, extra)
274    #    if typedecl is None:
275    #        return None, extra
276    #    raise NotImplementedError
277
278    def __init__(self, item, typedecl=None, *, unsupported=None, **extra):
279        if 'unsupported' in extra:
280            raise NotImplementedError((item, typedecl, unsupported, extra))
281        if not unsupported:
282            unsupported = None
283        elif isinstance(unsupported, (str, TypeDeclaration)):
284            unsupported = (unsupported,)
285        elif unsupported is not FIXED_TYPE:
286            unsupported = tuple(unsupported)
287        self.unsupported = unsupported
288        extra['unsupported'] = self.unsupported  # ...for __repr__(), etc.
289        if self.unsupported is None:
290            #self.supported = None
291            self.supported = True
292        elif self.unsupported is FIXED_TYPE:
293            if item.kind is KIND.VARIABLE:
294                raise NotImplementedError(item, typedecl, unsupported)
295            self.supported = True
296        else:
297            self.supported = not self.unsupported
298        super().__init__(item, typedecl, **extra)
299
300    def render(self, fmt='line', *, itemonly=False):
301        if fmt == 'raw':
302            yield repr(self)
303            return
304        rendered = super().render(fmt, itemonly=itemonly)
305        # XXX ???
306        #if itemonly:
307        #    yield from rendered
308        supported = self._supported
309        if fmt in ('line', 'brief'):
310            rendered, = rendered
311            parts = [
312                '+' if supported else '-' if supported is False else '',
313                rendered,
314            ]
315            yield '\t'.join(parts)
316        elif fmt == 'summary':
317            raise NotImplementedError(fmt)
318        elif fmt == 'full':
319            yield from rendered
320            if supported:
321                yield f'\tsupported:\t{supported}'
322        else:
323            raise NotImplementedError(fmt)
324
325
326class Analysis(_info.Analysis):
327    _item_class = Analyzed
328
329    @classonly
330    def build_item(cls, info, result=None):
331        if not isinstance(info, Declaration) or info.kind not in KINDS:
332            raise NotImplementedError((info, result))
333        return super().build_item(info, result)
334
335
336def check_globals(analysis):
337    # yield (data, failure)
338    ignored = read_ignored()
339    for item in analysis:
340        if item.kind != KIND.VARIABLE:
341            continue
342        if item.supported:
343            continue
344        if item.id in ignored:
345            continue
346        reason = item.unsupported
347        if not reason:
348            reason = '???'
349        elif not isinstance(reason, str):
350            if len(reason) == 1:
351                reason, = reason
352        reason = f'({reason})'
353        yield item, f'not supported {reason:20}\t{item.storage or ""} {item.vartype}'
354