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