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