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 resolve_object(self, ref): 35 36 """ 37 Return the given 'ref' in resolved form, given knowledge of the entire 38 program. 39 """ 40 41 if ref.has_kind("<depends>"): 42 return self.importer.get_object(ref.get_origin()) 43 else: 44 return ref 45 46 def get_resolved_object(self, path): 47 48 """ 49 Get the details of an object with the given 'path' within this module. 50 Where the object has not been resolved, None is returned. This differs 51 from the get_object method used elsewhere in that it does not return an 52 unresolved object reference. 53 """ 54 55 if self.objects.has_key(path): 56 ref = self.objects[path] 57 if ref.has_kind("<depends>"): 58 return None 59 else: 60 return ref 61 else: 62 return None 63 64 def get_resolved_global_or_builtin(self, name): 65 66 "Return the resolved global or built-in object with the given 'name'." 67 68 return self.get_global(name) or self.importer.get_object("__builtins__.%s" % name) 69 70 # Post-inspection resolution activities. 71 72 def resolve(self): 73 74 "Resolve dependencies and complete definitions." 75 76 self.resolve_members() 77 self.resolve_class_bases() 78 self.check_special() 79 self.check_names_used() 80 self.resolve_initialisers() 81 self.resolve_literals() 82 self.remove_redundant_accessors() 83 84 def resolve_members(self): 85 86 "Resolve any members referring to deferred references." 87 88 for name, ref in self.objects.items(): 89 if ref.has_kind("<depends>"): 90 ref = self.importer.get_object(name) 91 92 # Alias the member and write back to the importer. 93 94 ref = ref.alias(name) 95 self.importer.objects[name] = self.objects[name] = ref 96 97 def resolve_class_bases(self): 98 99 "Resolve all class bases since some of them may have been deferred." 100 101 for name, bases in self.classes.items(): 102 resolved = [] 103 bad = [] 104 105 for base in bases: 106 ref = self.resolve_object(base) 107 108 # Obtain the origin of the base class reference. 109 110 if not ref or not ref.has_kind("<class>"): 111 bad.append(base) 112 break 113 114 resolved.append(ref) 115 116 if bad: 117 print >>sys.stderr, "Bases of class %s were not classes." % (name, ", ".join(map(str, bad))) 118 else: 119 self.importer.classes[name] = self.classes[name] = resolved 120 121 def check_special(self): 122 123 "Check special names." 124 125 for name, value in self.special.items(): 126 self.special[name] = self.get_resolved_object(value.get_origin()) 127 128 def check_names_used(self): 129 130 "Check the names used by each function." 131 132 for path in self.names_used.keys(): 133 self.check_names_used_for_path(path) 134 135 def check_names_used_for_path(self, path): 136 137 "Check the names used by the given 'path'." 138 139 names = self.names_used.get(path) 140 if not names: 141 return 142 143 in_function = self.function_locals.has_key(path) 144 145 for name in names: 146 if name in predefined_constants or in_function and name in self.function_locals[path]: 147 continue 148 149 # Find local definitions (within static namespaces). 150 151 key = "%s.%s" % (path, name) 152 ref = self.get_resolved_object(key) 153 if ref: 154 self.name_references[key] = ref.final() or key 155 self.resolve_accesses(path, name, ref) 156 continue 157 158 # Find global or built-in definitions. 159 160 ref = self.get_resolved_global_or_builtin(name) 161 objpath = ref and (ref.final() or ref.get_name()) 162 if objpath: 163 self.name_references[key] = objpath 164 self.resolve_accesses(path, name, ref) 165 continue 166 167 print >>sys.stderr, "Name not recognised: %s in %s" % (name, path) 168 init_item(self.names_missing, path, set) 169 self.names_missing[path].add(name) 170 171 def resolve_accesses(self, path, name, ref): 172 173 """ 174 Resolve any unresolved accesses in the function at the given 'path' 175 for the given 'name' corresponding to the indicated 'ref'. Note that 176 this mechanism cannot resolve things like inherited methods because 177 they are not recorded as program objects in their inherited locations. 178 """ 179 180 attr_accesses = self.global_attr_accesses.get(path) 181 all_attrnames = attr_accesses and attr_accesses.get(name) 182 183 if not all_attrnames: 184 return 185 186 # Insist on constant accessors. 187 188 if not ref.has_kind(["<class>", "<module>"]): 189 return 190 191 found_attrnames = set() 192 193 for attrnames in all_attrnames: 194 195 # Start with the resolved name, adding attributes. 196 197 attrs = ref.get_path() 198 remaining = attrnames.split(".") 199 last_ref = ref 200 201 # Add each component, testing for a constant object. 202 203 while remaining: 204 attrname = remaining[0] 205 attrs.append(attrname) 206 del remaining[0] 207 208 # Find any constant object reference. 209 210 attr_ref = self.get_resolved_object(".".join(attrs)) 211 212 # Non-constant accessors terminate the traversal. 213 214 if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]): 215 216 # Provide the furthest constant accessor unless the final 217 # access can be resolved. 218 219 if remaining: 220 remaining.insert(0, attrs.pop()) 221 else: 222 last_ref = attr_ref 223 break 224 225 # Follow any reference to a constant object. 226 # Where the given name refers to an object in another location, 227 # switch to the other location in order to be able to test its 228 # attributes. 229 230 last_ref = attr_ref 231 attrs = attr_ref.get_path() 232 233 # Record a constant accessor only if an object was found 234 # that is different from the namespace involved. 235 236 if last_ref: 237 objpath = ".".join(attrs) 238 if objpath != path: 239 240 # Establish a constant access. 241 242 init_item(self.const_accesses, path, dict) 243 self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining)) 244 245 if len(attrs) > 1: 246 found_attrnames.add(attrs[1]) 247 248 # Remove any usage records for the name. 249 250 if found_attrnames: 251 252 # NOTE: Should be only one name version. 253 254 versions = [] 255 for version in self.attr_usage[path][name]: 256 new_usage = set() 257 for usage in version: 258 if found_attrnames.intersection(usage): 259 new_usage.add(tuple(set(usage).difference(found_attrnames))) 260 else: 261 new_usage.add(usage) 262 versions.append(new_usage) 263 264 self.attr_usage[path][name] = versions 265 266 def resolve_initialisers(self): 267 268 "Resolve initialiser values for names." 269 270 # Get the initialisers in each namespace. 271 272 for path, name_initialisers in self.name_initialisers.items(): 273 const_accesses = self.const_accesses.get(path) 274 275 # Resolve values for each name in a scope. 276 277 for name, values in name_initialisers.items(): 278 if path == self.name: 279 assigned_path = name 280 else: 281 assigned_path = "%s.%s" % (path, name) 282 283 initialised_names = {} 284 aliased_names = {} 285 286 for i, name_ref in enumerate(values): 287 288 # Unwrap invocations. 289 290 if isinstance(name_ref, InvocationRef): 291 invocation = True 292 name_ref = name_ref.name_ref 293 else: 294 invocation = False 295 296 # Obtain a usable reference from names or constants. 297 298 if isinstance(name_ref, ResolvedNameRef): 299 if not name_ref.reference(): 300 continue 301 ref = name_ref.reference() 302 303 # Obtain a reference from instances. 304 305 elif isinstance(name_ref, InstanceRef): 306 if not name_ref.reference(): 307 continue 308 ref = name_ref.reference() 309 310 # Resolve accesses that employ constants. 311 312 elif isinstance(name_ref, AccessRef): 313 ref = None 314 315 if const_accesses: 316 resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames)) 317 if resolved_access: 318 objpath, ref, remaining_attrnames = resolved_access 319 if remaining_attrnames: 320 ref = None 321 322 # Accesses that do not employ constants cannot be resolved, 323 # but they may be resolvable later. 324 325 if not ref: 326 if not invocation: 327 aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number 328 continue 329 330 # Attempt to resolve a plain name reference. 331 332 elif isinstance(name_ref, LocalNameRef): 333 key = "%s.%s" % (path, name_ref.name) 334 origin = self.name_references.get(key) 335 336 # Accesses that do not refer to known static objects 337 # cannot be resolved, but they may be resolvable later. 338 339 if not origin: 340 if not invocation: 341 aliased_names[i] = name_ref.name, None, name_ref.number 342 continue 343 344 ref = self.get_resolved_object(origin) 345 if not ref: 346 continue 347 348 elif isinstance(name_ref, NameRef): 349 key = "%s.%s" % (path, name_ref.name) 350 origin = self.name_references.get(key) 351 if not origin: 352 continue 353 354 ref = self.get_resolved_object(origin) 355 if not ref: 356 continue 357 358 else: 359 continue 360 361 # Resolve any hidden dependencies involving external objects 362 # or unresolved names referring to globals or built-ins. 363 364 if ref.has_kind("<depends>"): 365 ref = self.importer.get_object(ref.get_origin()) or \ 366 self.importer.get_object(self.name_references.get(ref.get_origin())) 367 368 # Convert class invocations to instances. 369 370 if invocation: 371 ref = ref.has_kind("<class>") and ref.instance_of() or None 372 373 if ref: 374 initialised_names[i] = ref 375 376 if initialised_names: 377 self.initialised_names[assigned_path] = initialised_names 378 if aliased_names: 379 self.aliased_names[assigned_path] = aliased_names 380 381 def resolve_literals(self): 382 383 "Resolve constant value types." 384 385 # Get the constants defined in each namespace. 386 387 for path, constants in self.constants.items(): 388 for constant, n in constants.items(): 389 objpath = "%s.$c%d" % (path, n) 390 _constant, value_type = self.constant_values[objpath] 391 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 392 393 # Get the literals defined in each namespace. 394 395 for path, literals in self.literals.items(): 396 for n in range(0, literals): 397 objpath = "%s.$C%d" % (path, n) 398 value_type = self.literal_types[objpath] 399 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 400 401 def remove_redundant_accessors(self): 402 403 "Remove now-redundant modifier and accessor information." 404 405 for path, const_accesses in self.const_accesses.items(): 406 accesses = self.attr_accessors.get(path) 407 modifiers = self.attr_access_modifiers.get(path) 408 if not accesses: 409 continue 410 for access in const_accesses.keys(): 411 if accesses.has_key(access): 412 del accesses[access] 413 if modifiers and modifiers.has_key(access): 414 del modifiers[access] 415 416 # vim: tabstop=4 expandtab shiftwidth=4