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