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 # NOTE: Needs to handle Ref classes. 118 self.special[name] = self.get_resolved_object(value.get_origin()) 119 120 def check_names_used(self): 121 122 "Check the names used by each function." 123 124 for path in self.names_used.keys(): 125 self.check_names_used_for_path(path) 126 127 def check_names_used_for_path(self, path): 128 129 "Check the names used by the given 'path'." 130 131 names = self.names_used.get(path) 132 if not names: 133 return 134 135 in_function = self.function_locals.has_key(path) 136 137 for name in names: 138 if name in predefined_constants or in_function and name in self.function_locals[path]: 139 continue 140 141 # Find local definitions (within static namespaces). 142 143 key = "%s.%s" % (path, name) 144 ref = self.get_resolved_object(key) 145 if ref: 146 self.name_references[key] = ref.final() or key 147 self.resolve_accesses(path, name, ref) 148 continue 149 150 # Find global or built-in definitions. 151 152 ref = self.get_global_or_builtin(name) 153 objpath = ref and (ref.final() or ref.get_name()) 154 if objpath: 155 self.name_references[key] = objpath 156 self.resolve_accesses(path, name, ref) 157 continue 158 159 print >>sys.stderr, "Name not recognised: %s in %s" % (name, path) 160 init_item(self.names_missing, path, set) 161 self.names_missing[path].add(name) 162 163 def resolve_accesses(self, path, name, ref): 164 165 """ 166 Resolve any unresolved accesses in the function at the given 'path' 167 for the given 'name' corresponding to the indicated 'ref'. Note that 168 this mechanism cannot resolve things like inherited methods because 169 they are not recorded as program objects in their inherited locations. 170 """ 171 172 attr_accesses = self.global_attr_accesses.get(path) 173 all_attrnames = attr_accesses and attr_accesses.get(name) 174 175 if not all_attrnames: 176 return 177 178 # Insist on constant accessors. 179 180 if not ref.has_kind(["<class>", "<module>"]): 181 return 182 183 found_attrnames = set() 184 185 for attrnames in all_attrnames: 186 187 # Start with the resolved name, adding attributes. 188 189 attrs = ref.get_path() 190 remaining = attrnames.split(".") 191 last_ref = ref 192 193 # Add each component, testing for a constant object. 194 195 while remaining: 196 attrname = remaining[0] 197 attrs.append(attrname) 198 del remaining[0] 199 200 # Find any constant object reference. 201 202 attr_ref = self.get_resolved_object(".".join(attrs)) 203 204 # Non-constant accessors terminate the traversal. 205 206 if not attr_ref or not attr_ref.has_kind(["<class>", "<module>", "<function>"]): 207 208 # Provide the furthest constant accessor unless the final 209 # access can be resolved. 210 211 if remaining: 212 remaining.insert(0, attrs.pop()) 213 else: 214 last_ref = attr_ref 215 break 216 217 # Follow any reference to a constant object. 218 # Where the given name refers to an object in another location, 219 # switch to the other location in order to be able to test its 220 # attributes. 221 222 last_ref = attr_ref 223 attrs = attr_ref.get_path() 224 225 # Record a constant accessor only if an object was found 226 # that is different from the namespace involved. 227 228 if last_ref: 229 objpath = ".".join(attrs) 230 if objpath != path: 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 # Convert class invocations to instances. 354 355 if invocation: 356 ref = ref.has_kind("<class>") and ref.instance_of() or None 357 358 if ref: 359 initialised_names[i] = ref 360 361 if initialised_names: 362 self.initialised_names[assigned_path] = initialised_names 363 if aliased_names: 364 self.aliased_names[assigned_path] = aliased_names 365 366 def resolve_literals(self): 367 368 "Resolve constant value types." 369 370 # Get the constants defined in each namespace. 371 372 for path, constants in self.constants.items(): 373 for constant, n in constants.items(): 374 objpath = "%s.$c%d" % (path, n) 375 _constant, value_type = self.constant_values[objpath] 376 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 377 378 # Get the literals defined in each namespace. 379 380 for path, literals in self.literals.items(): 381 for n in range(0, literals): 382 objpath = "%s.$C%d" % (path, n) 383 value_type = self.literal_types[objpath] 384 self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)} 385 386 def remove_redundant_accessors(self): 387 388 "Remove now-redundant modifier and accessor information." 389 390 for path, const_accesses in self.const_accesses.items(): 391 accesses = self.attr_accessors.get(path) 392 modifiers = self.attr_access_modifiers.get(path) 393 if not accesses: 394 continue 395 for access in const_accesses.keys(): 396 if accesses.has_key(access): 397 del accesses[access] 398 if modifiers and modifiers.has_key(access): 399 del modifiers[access] 400 401 # vim: tabstop=4 expandtab shiftwidth=4