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