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') 35NEED_FIX_FILE = os.path.join(_DATA_DIR, 'globals-to-fix.tsv') 36KNOWN_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 63KINDS = frozenset((*KIND.TYPES, KIND.VARIABLE)) 64 65 66def 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 81def write_known(): 82 raise NotImplementedError 83 datafiles.write_known(decls, IGNORED_FILE, ['unsupported'], relroot=REPO_ROOT) 84 85 86def 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 93def write_ignored(): 94 raise NotImplementedError 95 _datafiles.write_ignored(variables, IGNORED_FILE, relroot=REPO_ROOT) 96 97 98def 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 119def 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 134def 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 158def _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 172def _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 196def _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 225def _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 237def _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 263class Analyzed(_info.Analyzed): 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 328class Analysis(_info.Analysis): 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 338def 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}' 356