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