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