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