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