1 #!/usr/bin/env python 2 3 """ 4 Name resolution. 5 6 Copyright (C) 2016 Paul Boddie <paul@boddie.org.uk> 7 8 This program is free software; you can redistribute it and/or modify it under 9 the terms of the GNU General Public License as published by the Free Software 10 Foundation; either version 3 of the License, or (at your option) any later 11 version. 12 13 This program is distributed in the hope that it will be useful, but WITHOUT 14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more 16 details. 17 18 You should have received a copy of the GNU General Public License along with 19 this program. If not, see <http://www.gnu.org/licenses/>. 20 """ 21 22 from common import init_item, predefined_constants 23 from results import AccessRef, InstanceRef, InvocationRef, LocalNameRef, \ 24 NameRef, ResolvedNameRef 25 from referencing import Reference 26 import sys 27 28 class NameResolving: 29 30 "Resolving names mix-in for inspected modules." 31 32 # Object resolution. 33 34 def get_resolved_object(self, path): 35 36 """ 37 Get the details of an object with the given 'path' within this module. 38 Where the object has not been resolved, None is returned. This differs 39 from the get_object method used elsewhere in that it does not return an 40 unresolved object reference. 41 """ 42 43 if self.objects.has_key(path): 44 ref = self.objects[path] 45 if ref.has_kind("<depends>"): 46 return None 47 else: 48 return ref 49 else: 50 return None 51 52 # Post-inspection resolution activities. 53 54 def resolve(self): 55 56 "Resolve dependencies and complete definitions." 57 58 self.resolve_members() 59 self.resolve_class_bases() 60 self.check_special() 61 self.check_names_used() 62 self.check_invocations() 63 self.resolve_initialisers() 64 self.resolve_literals() 65 self.remove_redundant_accessors() 66 67 def resolve_members(self): 68 69 """ 70 Resolve any members referring to deferred references, using information 71 stored in the importer. This updates stored object and external name 72 records in this module. 73 """ 74 75 for impd, d in [ 76 (self.importer.objects, self.objects), 77 (self.importer.all_name_references, self.name_references) 78 ]: 79 80 for name, ref in d.items(): 81 82 # Obtain resolved names from the importer. 83 84 if ref.has_kind("<depends>"): 85 ref = self.importer.identify(name) 86 d[name] = ref 87 88 def resolve_class_bases(self): 89 90 "Resolve all class bases since some of them may have been deferred." 91 92 for name, bases in self.classes.items(): 93 resolved = [] 94 bad = [] 95 96 for base in bases: 97 ref = self.importer.identify(base.get_origin()) 98 99 # Obtain the origin of the base class reference. 100 101 if not ref or not ref.has_kind("<class>"): 102 bad.append(base) 103 break 104 105 resolved.append(ref) 106 107 if bad: 108 print >>sys.stderr, "Bases of class %s were not classes." % (name, ", ".join(map(str, bad))) 109 else: 110 self.importer.classes[name] = self.classes[name] = resolved 111 112 def check_special(self): 113 114 "Check special names." 115 116 for name, value in self.special.items(): 117 self.special[name] = self.importer.identify(value.get_origin()) 118 119 def check_names_used(self): 120 121 "Check the external names used by each scope." 122 123 for key, ref in self.name_references.items(): 124 path, name = key.rsplit(".", 1) 125 self.resolve_accesses(path, name, ref) 126 127 def check_invocations(self): 128 129 "Find invocations amongst module data and replace their results." 130 131 # Find members and replace invocation results with values. This is 132 # effectively the same as is done for initialised names, but refers to 133 # any unchanging value after initialisation. 134 135 for key, ref in self.objects.items(): 136 if ref.has_kind("<invoke>"): 137 ref = self.convert_invocation(ref) 138 self.importer.objects[key] = self.objects[key] = ref 139 140 # Rewrite function defaults, which are effectively extra members of the 141 # module. 142 143 defaults = self.function_defaults.items() 144 145 for fname, parameters in defaults: 146 l = [] 147 for pname, ref in parameters: 148 if ref.has_kind("<invoke>"): 149 ref = self.convert_invocation(ref) 150 l.append((pname, ref)) 151 self.function_defaults[fname] = l 152 153 def convert_invocation(self, ref): 154 155 "Convert the given invocation 'ref', handling instantiation." 156 157 ref = self.importer.identify(ref.get_origin()) 158 return ref and ref.has_kind("<class>") and ref.instance_of() or Reference("<var>") 159 160 def resolve_accesses(self, path, name, ref): 161 162 """ 163 Resolve any unresolved accesses in the function at the given 'path' 164 for the given 'name' corresponding to the indicated 'ref'. Note that 165 this mechanism cannot resolve things like inherited methods because 166 they are not recorded as program objects in their inherited locations. 167 """ 168 169 attr_accesses = self.global_attr_accesses.get(path) 170 all_attrnames = attr_accesses and attr_accesses.get(name) 171 172 if not all_attrnames: 173 return 174 175 # Insist on constant accessors. 176 177 if not ref.has_kind(["<class>", "<module>"]): 178 return 179 180 found_attrnames = set() 181 182 for attrnames in all_attrnames: 183 184 # Start with the resolved name, adding attributes. 185 186 attrs = ref.get_path() 187 remaining = attrnames.split(".") 188 last_ref = ref 189 190 # Add each component, testing for a constant object. 191 192 while remaining: 193 attrname = remaining[0] 194 attrs.append(attrname) 195 del remaining[0] 196 197 # Find any constant object reference. 198 199 attr_ref = self.get_resolved_object(".".join(attrs)) 200 201 # Non-constant accessors terminate the traversal. 202 203 if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]): 204 205 # Provide the furthest constant accessor unless the final 206 # access can be resolved. 207 208 if remaining: 209 remaining.insert(0, attrs.pop()) 210 else: 211 last_ref = attr_ref 212 break 213 214 # Follow any reference to a constant object. 215 # Where the given name refers to an object in another location, 216 # switch to the other location in order to be able to test its 217 # attributes. 218 219 last_ref = attr_ref 220 attrs = attr_ref.get_path() 221 222 # Record a constant accessor only if an object was found 223 # that is different from the namespace involved. 224 225 if last_ref: 226 objpath = ".".join(attrs) 227 if objpath != path: 228 229 if last_ref.has_kind("<invoke>"): 230 last_ref = self.convert_invocation(last_ref) 231 232 # Establish a constant access. 233 234 init_item(self.const_accesses, path, dict) 235 self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining)) 236 237 if len(attrs) > 1: 238 found_attrnames.add(attrs[1]) 239 240 # Remove any usage records for the name. 241 242 if found_attrnames: 243 244 # NOTE: Should be only one name version. 245 246 versions = [] 247 for version in self.attr_usage[path][name]: 248 new_usage = set() 249 for usage in version: 250 if found_attrnames.intersection(usage): 251 new_usage.add(tuple(set(usage).difference(found_attrnames))) 252 else: 253 new_usage.add(usage) 254 versions.append(new_usage) 255 256 self.attr_usage[path][name] = versions 257 258 def resolve_initialisers(self): 259 260 "Resolve initialiser values for names." 261 262 # Get the initialisers in each namespace. 263 264 for path, name_initialisers in self.name_initialisers.items(): 265 const_accesses = self.const_accesses.get(path) 266 267 # Resolve values for each name in a scope. 268 269 for name, values in name_initialisers.items(): 270 if path == self.name: 271 assigned_path = name 272 else: 273 assigned_path = "%s.%s" % (path, name) 274 275 initialised_names = {} 276 aliased_names = {} 277 278 for i, name_ref in enumerate(values): 279 280 # Unwrap invocations. 281 282 if isinstance(name_ref, InvocationRef): 283 invocation = True 284 name_ref = name_ref.name_ref 285 else: 286 invocation = False 287 288 # Obtain a usable reference from names or constants. 289 290 if isinstance(name_ref, ResolvedNameRef): 291 if not name_ref.reference(): 292 continue 293 ref = name_ref.reference() 294 295 # Obtain a reference from instances. 296 297 elif isinstance(name_ref, InstanceRef): 298 if not name_ref.reference(): 299 continue 300 ref = name_ref.reference() 301 302 # Resolve accesses that employ constants. 303 304 elif isinstance(name_ref, AccessRef): 305 ref = None 306 307 if const_accesses: 308 resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames)) 309 if resolved_access: 310 objpath, ref, remaining_attrnames = resolved_access 311 if remaining_attrnames: 312 ref = None 313 314 # Accesses that do not employ constants cannot be resolved, 315 # but they may be resolvable later. 316 317 if not ref: 318 if not invocation: 319 aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number 320 continue 321 322 # Attempt to resolve a plain name reference. 323 324 elif isinstance(name_ref, LocalNameRef): 325 key = "%s.%s" % (path, name_ref.name) 326 origin = self.name_references.get(key) 327 328 # Accesses that do not refer to known static objects 329 # cannot be resolved, but they may be resolvable later. 330 331 if not origin: 332 if not invocation: 333 aliased_names[i] = name_ref.name, None, name_ref.number 334 continue 335 336 ref = self.get_resolved_object(origin) 337 if not ref: 338 continue 339 340 elif isinstance(name_ref, NameRef): 341 key = "%s.%s" % (path, name_ref.name) 342 origin = self.name_references.get(key) 343 if not origin: 344 continue 345 346 ref = self.get_resolved_object(origin) 347 if not ref: 348 continue 349 350 else: 351 continue 352 353 # Resolve any hidden dependencies involving external objects 354 # or unresolved names referring to globals or built-ins. 355 356 if ref.has_kind("<depends>"): 357 ref = self.importer.identify(ref.get_origin()) 358 359 # Convert class invocations to instances. 360 361 if invocation: 362 ref = self.convert_invocation(ref) 363 364 if ref and not ref.has_kind("<var>"): 365 initialised_names[i] = ref 366 367 if initialised_names: 368 self.initialised_names[assigned_path] = initialised_names 369 if aliased_names: 370 self.aliased_names[assigned_path] = aliased_names 371 372 def resolve_literals(self): 373 374 "Resolve constant value types." 375 376 # Get the constants defined in each namespace. 377 378 for path, constants in self.constants.items(): 379 for constant, n in constants.items(): 380 objpath = "%s.$c%d" % (path, n) 381 _constant, value_type = self.constant_values[objpath] 382 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 383 384 # Get the literals defined in each namespace. 385 386 for path, literals in self.literals.items(): 387 for n in range(0, literals): 388 objpath = "%s.$C%d" % (path, n) 389 value_type = self.literal_types[objpath] 390 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 391 392 def remove_redundant_accessors(self): 393 394 "Remove now-redundant modifier and accessor information." 395 396 for path, const_accesses in self.const_accesses.items(): 397 accesses = self.attr_accessors.get(path) 398 modifiers = self.attr_access_modifiers.get(path) 399 if not accesses: 400 continue 401 for access in const_accesses.keys(): 402 if accesses.has_key(access): 403 del accesses[access] 404 if modifiers and modifiers.has_key(access): 405 del modifiers[access] 406 407 # vim: tabstop=4 expandtab shiftwidth=4