1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/inspector.py Tue Aug 30 16:51:10 2016 +0200
1.3 @@ -0,0 +1,1765 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +"""
1.7 +Inspect and obtain module structure.
1.8 +
1.9 +Copyright (C) 2007, 2008, 2009, 2010, 2011, 2012, 2013,
1.10 + 2014, 2015, 2016 Paul Boddie <paul@boddie.org.uk>
1.11 +
1.12 +This program is free software; you can redistribute it and/or modify it under
1.13 +the terms of the GNU General Public License as published by the Free Software
1.14 +Foundation; either version 3 of the License, or (at your option) any later
1.15 +version.
1.16 +
1.17 +This program is distributed in the hope that it will be useful, but WITHOUT
1.18 +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
1.19 +FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
1.20 +details.
1.21 +
1.22 +You should have received a copy of the GNU General Public License along with
1.23 +this program. If not, see <http://www.gnu.org/licenses/>.
1.24 +"""
1.25 +
1.26 +from branching import BranchTracker
1.27 +from common import *
1.28 +from modules import *
1.29 +from os import listdir
1.30 +from os.path import extsep, split, splitext
1.31 +from referencing import Reference
1.32 +import compiler
1.33 +import sys
1.34 +
1.35 +class AccessRef(Result):
1.36 +
1.37 + """
1.38 + A reference to an attribute access that is generally only returned from a
1.39 + processed access for possible initialiser resolution for assignments.
1.40 + """
1.41 +
1.42 + def __init__(self, original_name, attrnames, number):
1.43 + self.original_name = original_name
1.44 + self.attrnames = attrnames
1.45 + self.number = number
1.46 +
1.47 + def reference(self):
1.48 + return None
1.49 +
1.50 + def __repr__(self):
1.51 + return "AccessRef(%r, %r, %r)" % (self.original_name, self.attrnames, self.number)
1.52 +
1.53 +class InvocationRef(Result):
1.54 +
1.55 + "An invocation of a name reference."
1.56 +
1.57 + def __init__(self, name_ref):
1.58 + self.name_ref = name_ref
1.59 +
1.60 + def __repr__(self):
1.61 + return "InvocationRef(%r)" % self.name_ref
1.62 +
1.63 +class InspectedModule(BasicModule, CacheWritingModule):
1.64 +
1.65 + "A module inspector."
1.66 +
1.67 + def __init__(self, name, importer):
1.68 + BasicModule.__init__(self, name, importer)
1.69 + self.in_class = False
1.70 + self.in_conditional = False
1.71 + self.global_attr_accesses = {}
1.72 +
1.73 + # Nested scope handling.
1.74 +
1.75 + self.parent_function = None
1.76 + self.propagated_names = {}
1.77 +
1.78 + # Usage tracking.
1.79 +
1.80 + self.trackers = []
1.81 + self.attr_accessor_branches = {}
1.82 +
1.83 + def __repr__(self):
1.84 + return "InspectedModule(%r, %r)" % (self.name, self.importer)
1.85 +
1.86 + def parse(self, filename):
1.87 +
1.88 + "Parse the file having the given 'filename'."
1.89 +
1.90 + self.parse_file(filename)
1.91 +
1.92 + # Inspect the module.
1.93 +
1.94 + self.start_tracking_in_module()
1.95 +
1.96 + # Detect and record imports and globals declared in the module.
1.97 +
1.98 + self.assign_general_local("__name__", self.get_constant("str", self.name))
1.99 + self.assign_general_local("__file__", self.get_constant("str", filename))
1.100 + self.process_structure(self.astnode)
1.101 +
1.102 + # Set the class of the module after the definition has occurred.
1.103 +
1.104 + ref = self.get_builtin("object")
1.105 + self.set_name("__class__", ref)
1.106 +
1.107 + # Get module-level attribute usage details.
1.108 +
1.109 + self.stop_tracking_in_module()
1.110 +
1.111 + # Check names used and resolve them.
1.112 +
1.113 + self.register_submodules()
1.114 + self.loaded = True
1.115 +
1.116 + def register_submodules(self):
1.117 +
1.118 + "For package modules add submodule details."
1.119 +
1.120 + if splitext(split(self.filename)[1])[0] == "__init__":
1.121 + for subname in listdir(split(self.filename)[0]):
1.122 + name, ext = splitext(subname)
1.123 +
1.124 + # Add modules whose names are not already defined as globals.
1.125 +
1.126 + if ext == ("%spy" % extsep) and name != "__init__" and not self.get_global(name):
1.127 + module_name = self.get_global_path(name)
1.128 + top, submodule = self.get_module(module_name, True)
1.129 + self.set_module(name, submodule, hidden=True)
1.130 +
1.131 + def check_special(self):
1.132 +
1.133 + "Check special names."
1.134 +
1.135 + for name, value in self.special.items():
1.136 + if value.has_kind("<depends>"):
1.137 + self.find_imported_name(name, self.name)
1.138 + self.special[name] = self.get_object(value.get_origin())
1.139 +
1.140 + def check_names_used(self):
1.141 +
1.142 + "Check the names used by each function."
1.143 +
1.144 + for path in self.names_used.keys():
1.145 + self.check_names_used_for_path(path)
1.146 +
1.147 + def check_names_used_for_path(self, path):
1.148 +
1.149 + "Check the names used by the given 'path'."
1.150 +
1.151 + names = self.names_used.get(path)
1.152 + if not names:
1.153 + return
1.154 +
1.155 + in_function = self.function_locals.has_key(path)
1.156 +
1.157 + for name in names:
1.158 + if name in predefined_constants or in_function and name in self.function_locals[path]:
1.159 + continue
1.160 +
1.161 + # Resolve names that have been imported locally.
1.162 +
1.163 + self.find_imported_name(name, path)
1.164 +
1.165 + # Find local definitions.
1.166 +
1.167 + key = "%s.%s" % (path, name)
1.168 + ref = self.get_object(key)
1.169 + if ref:
1.170 + self.name_references[key] = ref.final() or key
1.171 + self.resolve_accesses(path, name, ref)
1.172 + continue
1.173 +
1.174 + # Resolve names that have been imported globally.
1.175 +
1.176 + self.find_imported_name(name, self.name)
1.177 +
1.178 + # Find global or built-in definitions.
1.179 +
1.180 + ref = self.get_global_or_builtin(name)
1.181 + objpath = ref and (ref.final() or ref.get_name())
1.182 + if objpath:
1.183 + self.name_references[key] = objpath
1.184 + self.resolve_accesses(path, name, ref)
1.185 + continue
1.186 +
1.187 + print >>sys.stderr, "Name not recognised: %s in %s" % (name, path)
1.188 + init_item(self.names_missing, path, set)
1.189 + self.names_missing[path].add(name)
1.190 +
1.191 + def resolve_members(self):
1.192 +
1.193 + "Resolve any members referring to deferred references."
1.194 +
1.195 + for name, ref in self.objects.items():
1.196 + if ref.has_kind("<depends>"):
1.197 + ref = self.get_object(ref.get_origin())
1.198 + ref = ref.alias(name)
1.199 + self.importer.objects[name] = self.objects[name] = ref
1.200 +
1.201 + def resolve_accesses(self, path, name, ref):
1.202 +
1.203 + """
1.204 + Resolve any unresolved accesses in the function at the given 'path'
1.205 + for the given 'name' corresponding to the indicated 'ref'. Note that
1.206 + this mechanism cannot resolve things like inherited methods because
1.207 + they are not recorded as program objects in their inherited locations.
1.208 + """
1.209 +
1.210 + attr_accesses = self.global_attr_accesses.get(path)
1.211 + all_attrnames = attr_accesses and attr_accesses.get(name)
1.212 +
1.213 + if not all_attrnames:
1.214 + return
1.215 +
1.216 + # Insist on constant accessors.
1.217 +
1.218 + if not ref.has_kind(["<class>", "<module>"]):
1.219 + return
1.220 +
1.221 + found_attrnames = set()
1.222 +
1.223 + for attrnames in all_attrnames:
1.224 +
1.225 + # Start with the resolved name, adding attributes.
1.226 +
1.227 + attrs = ref.get_path()
1.228 + remaining = attrnames.split(".")
1.229 + last_ref = ref
1.230 +
1.231 + # Add each component, testing for a constant object.
1.232 +
1.233 + while remaining:
1.234 + attrname = remaining[0]
1.235 + attrs.append(attrname)
1.236 + del remaining[0]
1.237 +
1.238 + # Find any constant object reference.
1.239 +
1.240 + attr_ref = self.get_object(".".join(attrs))
1.241 +
1.242 + # Non-constant accessors terminate the traversal.
1.243 +
1.244 + if not attr_ref.has_kind(["<class>", "<module>", "<function>"]):
1.245 +
1.246 + # Provide the furthest constant accessor unless the final
1.247 + # access can be resolved.
1.248 +
1.249 + if remaining:
1.250 + remaining.insert(0, attrs.pop())
1.251 + else:
1.252 + last_ref = attr_ref
1.253 + break
1.254 +
1.255 + # A module may expose an attribute imported from a hidden
1.256 + # module.
1.257 +
1.258 + elif last_ref.has_kind("<module>"):
1.259 + module, leaf_module = self.get_module(last_ref.get_origin())
1.260 + self.find_imported_name(attrname, module.name, module)
1.261 +
1.262 + # Follow any reference to a constant object.
1.263 + # Where the given name refers to an object in another location,
1.264 + # switch to the other location in order to be able to test its
1.265 + # attributes.
1.266 +
1.267 + last_ref = attr_ref
1.268 + attrs = attr_ref.get_path()
1.269 +
1.270 + # Record a constant accessor only if an object was found
1.271 + # that is different from the namespace involved.
1.272 +
1.273 + if last_ref:
1.274 + objpath = ".".join(attrs)
1.275 + if objpath != path:
1.276 +
1.277 + # Establish a constant access.
1.278 +
1.279 + init_item(self.const_accesses, path, dict)
1.280 + self.const_accesses[path][(name, attrnames)] = (objpath, last_ref, ".".join(remaining))
1.281 +
1.282 + if len(attrs) > 1:
1.283 + found_attrnames.add(attrs[1])
1.284 +
1.285 + # Remove any usage records for the name.
1.286 +
1.287 + if found_attrnames:
1.288 +
1.289 + # NOTE: Should be only one name version.
1.290 +
1.291 + versions = []
1.292 + for version in self.attr_usage[path][name]:
1.293 + new_usage = set()
1.294 + for usage in version:
1.295 + if found_attrnames.intersection(usage):
1.296 + new_usage.add(tuple(set(usage).difference(found_attrnames)))
1.297 + else:
1.298 + new_usage.add(usage)
1.299 + versions.append(new_usage)
1.300 +
1.301 + self.attr_usage[path][name] = versions
1.302 +
1.303 + def resolve_initialisers(self):
1.304 +
1.305 + "Resolve initialiser values for names."
1.306 +
1.307 + # Get the initialisers in each namespace.
1.308 +
1.309 + for path, name_initialisers in self.name_initialisers.items():
1.310 + const_accesses = self.const_accesses.get(path)
1.311 +
1.312 + # Resolve values for each name in a scope.
1.313 +
1.314 + for name, values in name_initialisers.items():
1.315 + if path == self.name:
1.316 + assigned_path = name
1.317 + else:
1.318 + assigned_path = "%s.%s" % (path, name)
1.319 +
1.320 + initialised_names = {}
1.321 + aliased_names = {}
1.322 +
1.323 + for i, name_ref in enumerate(values):
1.324 +
1.325 + # Unwrap invocations.
1.326 +
1.327 + if isinstance(name_ref, InvocationRef):
1.328 + invocation = True
1.329 + name_ref = name_ref.name_ref
1.330 + else:
1.331 + invocation = False
1.332 +
1.333 + # Obtain a usable reference from names or constants.
1.334 +
1.335 + if isinstance(name_ref, ResolvedNameRef):
1.336 + if not name_ref.reference():
1.337 + continue
1.338 + ref = name_ref.reference()
1.339 +
1.340 + # Obtain a reference from instances.
1.341 +
1.342 + elif isinstance(name_ref, InstanceRef):
1.343 + if not name_ref.reference():
1.344 + continue
1.345 + ref = name_ref.reference()
1.346 +
1.347 + # Resolve accesses that employ constants.
1.348 +
1.349 + elif isinstance(name_ref, AccessRef):
1.350 + ref = None
1.351 +
1.352 + if const_accesses:
1.353 + resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
1.354 + if resolved_access:
1.355 + objpath, ref, remaining_attrnames = resolved_access
1.356 + if remaining_attrnames:
1.357 + ref = None
1.358 +
1.359 + # Accesses that do not employ constants cannot be resolved,
1.360 + # but they may be resolvable later.
1.361 +
1.362 + if not ref:
1.363 + if not invocation:
1.364 + aliased_names[i] = name_ref.original_name, name_ref.attrnames, name_ref.number
1.365 + continue
1.366 +
1.367 + # Attempt to resolve a plain name reference.
1.368 +
1.369 + elif isinstance(name_ref, LocalNameRef):
1.370 + key = "%s.%s" % (path, name_ref.name)
1.371 + origin = self.name_references.get(key)
1.372 +
1.373 + # Accesses that do not refer to known static objects
1.374 + # cannot be resolved, but they may be resolvable later.
1.375 +
1.376 + if not origin:
1.377 + if not invocation:
1.378 + aliased_names[i] = name_ref.name, None, name_ref.number
1.379 + continue
1.380 +
1.381 + ref = self.get_object(origin)
1.382 + if not ref:
1.383 + continue
1.384 +
1.385 + elif isinstance(name_ref, NameRef):
1.386 + key = "%s.%s" % (path, name_ref.name)
1.387 + origin = self.name_references.get(key)
1.388 + if not origin:
1.389 + continue
1.390 +
1.391 + ref = self.get_object(origin)
1.392 + if not ref:
1.393 + continue
1.394 +
1.395 + else:
1.396 + continue
1.397 +
1.398 + # Convert class invocations to instances.
1.399 +
1.400 + if invocation:
1.401 + ref = ref.has_kind("<class>") and ref.instance_of() or None
1.402 +
1.403 + if ref:
1.404 + initialised_names[i] = ref
1.405 +
1.406 + if initialised_names:
1.407 + self.initialised_names[assigned_path] = initialised_names
1.408 + if aliased_names:
1.409 + self.aliased_names[assigned_path] = aliased_names
1.410 +
1.411 + def resolve_literals(self):
1.412 +
1.413 + "Resolve constant value types."
1.414 +
1.415 + # Get the constants defined in each namespace.
1.416 +
1.417 + for path, constants in self.constants.items():
1.418 + for constant, n in constants.items():
1.419 + objpath = "%s.$c%d" % (path, n)
1.420 + _constant, value_type = self.constant_values[objpath]
1.421 + self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
1.422 +
1.423 + # Get the literals defined in each namespace.
1.424 +
1.425 + for path, literals in self.literals.items():
1.426 + for n in range(0, literals):
1.427 + objpath = "%s.$C%d" % (path, n)
1.428 + value_type = self.literal_types[objpath]
1.429 + self.initialised_names[objpath] = {0 : Reference("<instance>", value_type)}
1.430 +
1.431 + def remove_redundant_accessors(self):
1.432 +
1.433 + "Remove now-redundant modifier and accessor information."
1.434 +
1.435 + for path, const_accesses in self.const_accesses.items():
1.436 + accesses = self.attr_accessors.get(path)
1.437 + modifiers = self.attr_access_modifiers.get(path)
1.438 + if not accesses:
1.439 + continue
1.440 + for access in const_accesses.keys():
1.441 + if accesses.has_key(access):
1.442 + del accesses[access]
1.443 + if modifiers and modifiers.has_key(access):
1.444 + del modifiers[access]
1.445 +
1.446 + def set_invocation_usage(self):
1.447 +
1.448 + """
1.449 + Discard the current invocation storage figures, retaining the maximum
1.450 + values.
1.451 + """
1.452 +
1.453 + for path, (current, maximum) in self.function_targets.items():
1.454 + self.importer.function_targets[path] = self.function_targets[path] = maximum
1.455 +
1.456 + for path, (current, maximum) in self.function_arguments.items():
1.457 + self.importer.function_arguments[path] = self.function_arguments[path] = maximum
1.458 +
1.459 + # Module structure traversal.
1.460 +
1.461 + def process_structure_node(self, n):
1.462 +
1.463 + "Process the individual node 'n'."
1.464 +
1.465 + # Module global detection.
1.466 +
1.467 + if isinstance(n, compiler.ast.Global):
1.468 + self.process_global_node(n)
1.469 +
1.470 + # Module import declarations.
1.471 +
1.472 + elif isinstance(n, compiler.ast.From):
1.473 + self.process_from_node(n)
1.474 +
1.475 + elif isinstance(n, compiler.ast.Import):
1.476 + self.process_import_node(n)
1.477 +
1.478 + # Nodes using operator module functions.
1.479 +
1.480 + elif isinstance(n, compiler.ast.Operator):
1.481 + return self.process_operator_node(n)
1.482 +
1.483 + elif isinstance(n, compiler.ast.AugAssign):
1.484 + self.process_augassign_node(n)
1.485 +
1.486 + elif isinstance(n, compiler.ast.Compare):
1.487 + return self.process_compare_node(n)
1.488 +
1.489 + elif isinstance(n, compiler.ast.Slice):
1.490 + return self.process_slice_node(n)
1.491 +
1.492 + elif isinstance(n, compiler.ast.Sliceobj):
1.493 + return self.process_sliceobj_node(n)
1.494 +
1.495 + elif isinstance(n, compiler.ast.Subscript):
1.496 + return self.process_subscript_node(n)
1.497 +
1.498 + # Namespaces within modules.
1.499 +
1.500 + elif isinstance(n, compiler.ast.Class):
1.501 + self.process_class_node(n)
1.502 +
1.503 + elif isinstance(n, compiler.ast.Function):
1.504 + self.process_function_node(n, n.name)
1.505 +
1.506 + elif isinstance(n, compiler.ast.Lambda):
1.507 + return self.process_lambda_node(n)
1.508 +
1.509 + # Assignments.
1.510 +
1.511 + elif isinstance(n, compiler.ast.Assign):
1.512 +
1.513 + # Handle each assignment node.
1.514 +
1.515 + for node in n.nodes:
1.516 + self.process_assignment_node(node, n.expr)
1.517 +
1.518 + # Assignments within non-Assign nodes.
1.519 +
1.520 + elif isinstance(n, compiler.ast.AssName):
1.521 + self.process_assignment_node(n, None)
1.522 +
1.523 + elif isinstance(n, compiler.ast.AssAttr):
1.524 + self.process_attribute_access(n)
1.525 +
1.526 + # Accesses.
1.527 +
1.528 + elif isinstance(n, compiler.ast.Getattr):
1.529 + return self.process_attribute_access(n)
1.530 +
1.531 + # Name recording for later testing.
1.532 +
1.533 + elif isinstance(n, compiler.ast.Name):
1.534 + return self.process_name_node(n)
1.535 +
1.536 + # Conditional statement tracking.
1.537 +
1.538 + elif isinstance(n, compiler.ast.For):
1.539 + self.process_for_node(n)
1.540 +
1.541 + elif isinstance(n, compiler.ast.While):
1.542 + self.process_while_node(n)
1.543 +
1.544 + elif isinstance(n, compiler.ast.If):
1.545 + self.process_if_node(n)
1.546 +
1.547 + elif isinstance(n, (compiler.ast.And, compiler.ast.Or)):
1.548 + return self.process_logical_node(n)
1.549 +
1.550 + # Exception control-flow tracking.
1.551 +
1.552 + elif isinstance(n, compiler.ast.TryExcept):
1.553 + self.process_try_node(n)
1.554 +
1.555 + elif isinstance(n, compiler.ast.TryFinally):
1.556 + self.process_try_finally_node(n)
1.557 +
1.558 + # Control-flow modification statements.
1.559 +
1.560 + elif isinstance(n, compiler.ast.Break):
1.561 + self.trackers[-1].suspend_broken_branch()
1.562 +
1.563 + elif isinstance(n, compiler.ast.Continue):
1.564 + self.trackers[-1].suspend_continuing_branch()
1.565 +
1.566 + elif isinstance(n, compiler.ast.Raise):
1.567 + self.process_structure(n)
1.568 + self.trackers[-1].abandon_branch()
1.569 +
1.570 + elif isinstance(n, compiler.ast.Return):
1.571 + self.process_structure(n)
1.572 + self.trackers[-1].abandon_returning_branch()
1.573 +
1.574 + # Invocations.
1.575 +
1.576 + elif isinstance(n, compiler.ast.CallFunc):
1.577 + return self.process_invocation_node(n)
1.578 +
1.579 + # Constant usage.
1.580 +
1.581 + elif isinstance(n, compiler.ast.Const):
1.582 + return self.get_literal_instance(n, n.value.__class__.__name__)
1.583 +
1.584 + elif isinstance(n, compiler.ast.Dict):
1.585 + return self.get_literal_instance(n, "dict")
1.586 +
1.587 + elif isinstance(n, compiler.ast.List):
1.588 + return self.get_literal_instance(n, "list")
1.589 +
1.590 + elif isinstance(n, compiler.ast.Tuple):
1.591 + return self.get_literal_instance(n, "tuple")
1.592 +
1.593 + # List comprehensions and if expressions.
1.594 +
1.595 + elif isinstance(n, compiler.ast.ListComp):
1.596 + self.process_listcomp_node(n)
1.597 +
1.598 + elif isinstance(n, compiler.ast.IfExp):
1.599 + self.process_ifexp_node(n)
1.600 +
1.601 + # All other nodes are processed depth-first.
1.602 +
1.603 + else:
1.604 + self.process_structure(n)
1.605 +
1.606 + # By default, no expression details are returned.
1.607 +
1.608 + return None
1.609 +
1.610 + # Specific node handling.
1.611 +
1.612 + def process_assignment_node(self, n, expr):
1.613 +
1.614 + "Process the individual node 'n' to be assigned the contents of 'expr'."
1.615 +
1.616 + # Names and attributes are assigned the entire expression.
1.617 +
1.618 + if isinstance(n, compiler.ast.AssName):
1.619 +
1.620 + name_ref = expr and self.process_structure_node(expr)
1.621 +
1.622 + # Name assignments populate either function namespaces or the
1.623 + # general namespace hierarchy.
1.624 +
1.625 + self.assign_general_local(n.name, name_ref)
1.626 +
1.627 + # Record usage of the name.
1.628 +
1.629 + self.record_name(n.name)
1.630 +
1.631 + elif isinstance(n, compiler.ast.AssAttr):
1.632 + if expr: self.process_structure_node(expr)
1.633 + self.process_attribute_access(n)
1.634 +
1.635 + # Lists and tuples are matched against the expression and their
1.636 + # items assigned to expression items.
1.637 +
1.638 + elif isinstance(n, (compiler.ast.AssList, compiler.ast.AssTuple)):
1.639 + self.process_assignment_node_items(n, expr)
1.640 +
1.641 + # Slices and subscripts are permitted within assignment nodes.
1.642 +
1.643 + elif isinstance(n, compiler.ast.Slice):
1.644 + self.process_slice_node(n, expr)
1.645 +
1.646 + elif isinstance(n, compiler.ast.Subscript):
1.647 + self.process_subscript_node(n, expr)
1.648 +
1.649 + def process_attribute_access(self, n):
1.650 +
1.651 + "Process the given attribute access node 'n'."
1.652 +
1.653 + # Obtain any completed chain and return the reference to it.
1.654 +
1.655 + name_ref = self.process_attribute_chain(n)
1.656 + if self.have_access_expression(n):
1.657 + return name_ref
1.658 +
1.659 + # Where the start of the chain of attributes has been reached, determine
1.660 + # the complete access.
1.661 +
1.662 + # Given a non-access node, this chain can be handled in its entirety,
1.663 + # either being name-based and thus an access rooted on a name, or being
1.664 + # based on some other node and thus an anonymous access of some kind.
1.665 +
1.666 + path = self.get_namespace_path()
1.667 +
1.668 + # Start with the the full attribute chain.
1.669 +
1.670 + remaining = self.attrs
1.671 + attrnames = ".".join(remaining)
1.672 +
1.673 + # If the accessor cannot be identified, or where attributes
1.674 + # remain in an attribute chain, record the anonymous accesses.
1.675 +
1.676 + if not isinstance(name_ref, NameRef): # includes ResolvedNameRef
1.677 +
1.678 + assignment = isinstance(n, compiler.ast.AssAttr)
1.679 +
1.680 + init_item(self.attr_accesses, path, set)
1.681 + self.attr_accesses[path].add(attrnames)
1.682 +
1.683 + self.record_access_details(None, attrnames, assignment)
1.684 + del self.attrs[0]
1.685 + return
1.686 +
1.687 + # Name-based accesses will handle the first attribute in a
1.688 + # chain.
1.689 +
1.690 + else:
1.691 + attrname = remaining[0]
1.692 +
1.693 + # Attribute assignments are used to identify instance attributes.
1.694 +
1.695 + if isinstance(n, compiler.ast.AssAttr) and \
1.696 + self.in_class and self.in_function and n.expr.name == "self":
1.697 +
1.698 + self.set_instance_attr(attrname)
1.699 +
1.700 + # Record attribute usage using any name local to this namespace,
1.701 + # if assigned in the namespace, or using an external name
1.702 + # (presently just globals within classes).
1.703 +
1.704 + name = self.get_name_for_tracking(name_ref.name, name_ref.final())
1.705 + tracker = self.trackers[-1]
1.706 +
1.707 + immediate_access = len(self.attrs) == 1
1.708 + assignment = immediate_access and isinstance(n, compiler.ast.AssAttr)
1.709 +
1.710 + del self.attrs[0]
1.711 +
1.712 + # Record global-based chains for subsequent resolution.
1.713 +
1.714 + is_global = self.in_function and not self.function_locals[path].has_key(name) or \
1.715 + not self.in_function
1.716 +
1.717 + if is_global:
1.718 + self.record_global_access_details(name, attrnames)
1.719 +
1.720 + # Make sure the name is being tracked: global names will not
1.721 + # already be initialised in a branch and must be added
1.722 + # explicitly.
1.723 +
1.724 + if not tracker.have_name(name):
1.725 + tracker.assign_names([name])
1.726 + if self.in_function:
1.727 + self.scope_globals[path].add(name)
1.728 +
1.729 + # Record attribute usage in the tracker, and record the branch
1.730 + # information for the access.
1.731 +
1.732 + branches = tracker.use_attribute(name, attrname)
1.733 +
1.734 + if not branches:
1.735 + print >>sys.stderr, "In %s, name %s is accessed using %s before an assignment." % (
1.736 + path, name, attrname)
1.737 + branches = tracker.use_attribute(name, attrname)
1.738 +
1.739 + self.record_branches_for_access(branches, name, attrnames)
1.740 + access_number = self.record_access_details(name, attrnames, assignment)
1.741 + return AccessRef(name, attrnames, access_number)
1.742 +
1.743 + def process_class_node(self, n):
1.744 +
1.745 + "Process the given class node 'n'."
1.746 +
1.747 + path = self.get_namespace_path()
1.748 +
1.749 + # To avoid notions of class "versions" where the same definition
1.750 + # might be parameterised with different state and be referenced
1.751 + # elsewhere (as base classes, for example), classes in functions or
1.752 + # conditions are forbidden.
1.753 +
1.754 + if self.in_function or self.in_conditional:
1.755 + print >>sys.stderr, "In %s, class %s in function or conditional statement ignored." % (
1.756 + path, n.name)
1.757 + return
1.758 +
1.759 + # Resolve base classes.
1.760 +
1.761 + bases = []
1.762 +
1.763 + for base in n.bases:
1.764 + base_class = self.get_class(base)
1.765 +
1.766 + if not base_class:
1.767 + print >>sys.stderr, "In %s, class %s has unidentifiable base classes." % (
1.768 + path, n.name)
1.769 + return
1.770 + else:
1.771 + bases.append(base_class)
1.772 +
1.773 + # Record bases for the class and retain the class name.
1.774 +
1.775 + class_name = self.get_object_path(n.name)
1.776 +
1.777 + if not bases and class_name != "__builtins__.core.object":
1.778 + ref = self.get_object("__builtins__.object")
1.779 + bases.append(ref)
1.780 +
1.781 + self.importer.classes[class_name] = self.classes[class_name] = bases
1.782 + self.importer.subclasses[class_name] = set()
1.783 + self.scope_globals[class_name] = set()
1.784 +
1.785 + # Set the definition before entering the namespace rather than
1.786 + # afterwards because methods may reference it. In normal Python,
1.787 + # a class is not accessible until the definition is complete, but
1.788 + # methods can generally reference it since upon being called the
1.789 + # class will already exist.
1.790 +
1.791 + self.set_definition(n.name, "<class>")
1.792 +
1.793 + in_class = self.in_class
1.794 + self.in_class = class_name
1.795 + self.set_instance_attr("__class__", Reference("<class>", class_name))
1.796 + self.enter_namespace(n.name)
1.797 + self.set_name("__fn__") # special instantiator attribute
1.798 + self.set_name("__args__") # special instantiator attribute
1.799 + self.assign_general_local("__name__", self.get_constant("str", class_name))
1.800 + self.process_structure_node(n.code)
1.801 + self.exit_namespace()
1.802 + self.in_class = in_class
1.803 +
1.804 + def process_from_node(self, n):
1.805 +
1.806 + "Process the given node 'n', importing from another module."
1.807 +
1.808 + path = self.get_namespace_path()
1.809 +
1.810 + modname, names = self.get_module_name(n)
1.811 +
1.812 + # Load the mentioned module.
1.813 +
1.814 + top, module = self.get_module(modname, True)
1.815 + self.set_module(None, module, hidden=True)
1.816 +
1.817 + if not module:
1.818 + print >>sys.stderr, "In %s, from statement importing from %s failed." % (
1.819 + path, modname)
1.820 +
1.821 + # Attempt to obtain the referenced objects.
1.822 +
1.823 + for name, alias in n.names:
1.824 +
1.825 + # NOTE: Package submodules are not implicitly imported.
1.826 +
1.827 + if name == "*":
1.828 + if module:
1.829 +
1.830 + # Warn about a circular import that probably doesn't find
1.831 + # the names.
1.832 +
1.833 + if not module.loaded:
1.834 + print >>sys.stderr, "In %s, from statement performs circular import %s of %s." % (
1.835 + path, modname)
1.836 +
1.837 + for name, value in module.get_globals().items():
1.838 + if name != "__name__":
1.839 + value = ResolvedNameRef(name, value)
1.840 + self.set_general_local(name, value)
1.841 + self.set_imported_name(name, modname)
1.842 + break
1.843 +
1.844 + # Explicit names.
1.845 +
1.846 + ref = self.import_name_from_module(name, modname, module, alias)
1.847 + value = ResolvedNameRef(alias or name, ref)
1.848 + self.set_general_local(alias or name, value)
1.849 + self.set_imported_name(name, modname, alias)
1.850 +
1.851 + def import_name_from_module(self, name, modname, module, alias=None):
1.852 +
1.853 + """
1.854 + Import 'name' from the module having the given 'modname', with 'module'
1.855 + having been obtained for the module name, using 'alias' for the imported
1.856 + name in the current namespace.
1.857 + """
1.858 +
1.859 + path = self.get_namespace_path()
1.860 +
1.861 + if module and module.get_global(name):
1.862 + value = module.get_global(name)
1.863 +
1.864 + # Warn about an import that fails to provide a name, perhaps due
1.865 + # to a circular import.
1.866 +
1.867 + if not value:
1.868 + print >>sys.stderr, "In %s, from statement cannot import %s from %s%s." % (
1.869 + path, name, modname, not module.loaded and "(circular import)")
1.870 +
1.871 + return value
1.872 +
1.873 + # Record the name as a dependency.
1.874 +
1.875 + else:
1.876 + return Reference("<depends>", "%s.%s" % (modname, name))
1.877 +
1.878 + def process_function_node(self, n, name):
1.879 +
1.880 + """
1.881 + Process the given function or lambda node 'n' with the given 'name'.
1.882 + """
1.883 +
1.884 + is_lambda = isinstance(n, compiler.ast.Lambda)
1.885 +
1.886 + # Where a function is declared conditionally, use a separate name for
1.887 + # the definition, and assign the definition to the stated name.
1.888 +
1.889 + if (self.in_conditional or self.in_function) and not is_lambda:
1.890 + original_name = name
1.891 + name = self.get_lambda_name()
1.892 + else:
1.893 + original_name = None
1.894 +
1.895 + # Initialise argument and local records.
1.896 +
1.897 + function_name = self.get_object_path(name)
1.898 +
1.899 + argnames = self.importer.function_parameters[function_name] = \
1.900 + self.function_parameters[function_name] = get_argnames(n.argnames)
1.901 + locals = self.function_locals[function_name] = {}
1.902 +
1.903 + for argname in argnames:
1.904 + locals[argname] = Reference("<var>")
1.905 +
1.906 + globals = self.scope_globals[function_name] = set()
1.907 +
1.908 + # Process the defaults.
1.909 +
1.910 + defaults = self.importer.function_defaults[function_name] = \
1.911 + self.function_defaults[function_name] = []
1.912 +
1.913 + for argname, default in compiler.ast.get_defaults(n):
1.914 + if default:
1.915 +
1.916 + # Obtain any reference for the default.
1.917 +
1.918 + name_ref = self.process_structure_node(default)
1.919 + defaults.append((argname, name_ref.is_name() and name_ref.reference() or Reference("<var>")))
1.920 +
1.921 + # Reset conditional tracking to focus on the function contents.
1.922 +
1.923 + parent_function = self.parent_function
1.924 + self.parent_function = self.in_function and self.get_namespace_path() or None
1.925 +
1.926 + in_conditional = self.in_conditional
1.927 + self.in_conditional = False
1.928 +
1.929 + in_function = self.in_function
1.930 + self.in_function = function_name
1.931 +
1.932 + self.enter_namespace(name)
1.933 +
1.934 + # Track attribute usage within the namespace.
1.935 +
1.936 + path = self.get_namespace_path()
1.937 + init_item(self.propagated_names, path, set)
1.938 +
1.939 + self.start_tracking(locals)
1.940 + self.process_structure_node(n.code)
1.941 + self.stop_tracking()
1.942 +
1.943 + # Propagate names from parent scopes.
1.944 +
1.945 + for local in self.propagated_names[path]:
1.946 + if not local in argnames and self.trackers[-1].have_name(local):
1.947 + argnames.append(local)
1.948 + defaults.append((local, Reference("<var>")))
1.949 + self.set_function_local(local)
1.950 +
1.951 + # Exit to the parent and note propagated names.
1.952 +
1.953 + self.exit_namespace()
1.954 +
1.955 + parent = self.get_namespace_path()
1.956 + if self.propagated_names.has_key(parent):
1.957 + for local in self.propagated_names[path]:
1.958 + self.propagated_names[parent].add(local)
1.959 +
1.960 + # Update flags.
1.961 +
1.962 + self.in_function = in_function
1.963 + self.in_conditional = in_conditional
1.964 + self.parent_function = parent_function
1.965 +
1.966 + # Define the function using the appropriate name.
1.967 +
1.968 + self.set_definition(name, "<function>")
1.969 +
1.970 + # Where a function is set conditionally, assign the name.
1.971 +
1.972 + if original_name:
1.973 + self.process_assignment_for_function(original_name, name)
1.974 +
1.975 + def process_global_node(self, n):
1.976 +
1.977 + """
1.978 + Process the given "global" node 'n'.
1.979 + """
1.980 +
1.981 + path = self.get_namespace_path()
1.982 +
1.983 + if path != self.name:
1.984 + self.scope_globals[path].update(n.names)
1.985 +
1.986 + def process_if_node(self, n):
1.987 +
1.988 + """
1.989 + Process the given "if" node 'n'.
1.990 + """
1.991 +
1.992 + tracker = self.trackers[-1]
1.993 + tracker.new_branchpoint()
1.994 +
1.995 + for test, body in n.tests:
1.996 + self.process_structure_node(test)
1.997 +
1.998 + tracker.new_branch()
1.999 +
1.1000 + in_conditional = self.in_conditional
1.1001 + self.in_conditional = True
1.1002 + self.process_structure_node(body)
1.1003 + self.in_conditional = in_conditional
1.1004 +
1.1005 + tracker.shelve_branch()
1.1006 +
1.1007 + # Maintain a branch for the else clause.
1.1008 +
1.1009 + tracker.new_branch()
1.1010 + if n.else_:
1.1011 + self.process_structure_node(n.else_)
1.1012 + tracker.shelve_branch()
1.1013 +
1.1014 + tracker.merge_branches()
1.1015 +
1.1016 + def process_ifexp_node(self, n):
1.1017 +
1.1018 + "Process the given if expression node 'n'."
1.1019 +
1.1020 + name_ref = self.process_structure_node(self.convert_ifexp_node(n))
1.1021 +
1.1022 + path = self.get_namespace_path()
1.1023 + self.allocate_arguments(path, self.function_defaults[name_ref.get_origin()])
1.1024 + self.deallocate_arguments(path, self.function_defaults[name_ref.get_origin()])
1.1025 +
1.1026 + return InvocationRef(name_ref)
1.1027 +
1.1028 + def process_import_node(self, n):
1.1029 +
1.1030 + "Process the given import node 'n'."
1.1031 +
1.1032 + path = self.get_namespace_path()
1.1033 +
1.1034 + # Load the mentioned module.
1.1035 +
1.1036 + for name, alias in n.names:
1.1037 + module, leaf_module = self.get_module(name, alias)
1.1038 +
1.1039 + if not module:
1.1040 + print >>sys.stderr, "In %s, import statement importing from %s failed." % (
1.1041 + path, name)
1.1042 + if module and not module.loaded:
1.1043 + print >>sys.stderr, "In %s, import statement performs circular import of %s." % (
1.1044 + path, name)
1.1045 +
1.1046 + self.set_module(alias or name.split(".")[0], module, leaf_module)
1.1047 +
1.1048 + def process_invocation_node(self, n):
1.1049 +
1.1050 + "Process the given invocation node 'n'."
1.1051 +
1.1052 + path = self.get_namespace_path()
1.1053 +
1.1054 + self.allocate_arguments(path, n.args)
1.1055 +
1.1056 + try:
1.1057 + # Process the expression, obtaining any identified reference.
1.1058 +
1.1059 + name_ref = self.process_structure_node(n.node)
1.1060 +
1.1061 + # Process the arguments.
1.1062 +
1.1063 + for arg in n.args:
1.1064 + self.process_structure_node(arg)
1.1065 +
1.1066 + # Detect class invocations.
1.1067 +
1.1068 + if isinstance(name_ref, ResolvedNameRef) and name_ref.has_kind("<class>"):
1.1069 + return InstanceRef(name_ref.reference().instance_of())
1.1070 +
1.1071 + elif isinstance(name_ref, NameRef):
1.1072 + return InvocationRef(name_ref)
1.1073 +
1.1074 + return None
1.1075 +
1.1076 + finally:
1.1077 + self.deallocate_arguments(path, n.args)
1.1078 +
1.1079 + def process_lambda_node(self, n):
1.1080 +
1.1081 + "Process the given lambda node 'n'."
1.1082 +
1.1083 + name = self.get_lambda_name()
1.1084 + self.process_function_node(n, name)
1.1085 +
1.1086 + origin = self.get_object_path(name)
1.1087 + return ResolvedNameRef(name, Reference("<function>", origin))
1.1088 +
1.1089 + def process_listcomp_node(self, n):
1.1090 +
1.1091 + "Process the given list comprehension node 'n'."
1.1092 +
1.1093 + name_ref = self.process_structure_node(self.convert_listcomp_node(n))
1.1094 +
1.1095 + path = self.get_namespace_path()
1.1096 + self.allocate_arguments(path, self.function_defaults[name_ref.get_origin()])
1.1097 + self.deallocate_arguments(path, self.function_defaults[name_ref.get_origin()])
1.1098 +
1.1099 + return InvocationRef(name_ref)
1.1100 +
1.1101 + def process_logical_node(self, n):
1.1102 +
1.1103 + "Process the given operator node 'n'."
1.1104 +
1.1105 + self.process_operator_chain(n.nodes, self.process_structure_node)
1.1106 +
1.1107 + def process_name_node(self, n):
1.1108 +
1.1109 + "Process the given name node 'n'."
1.1110 +
1.1111 + path = self.get_namespace_path()
1.1112 +
1.1113 + # Special names.
1.1114 +
1.1115 + if n.name.startswith("$"):
1.1116 + value = self.get_special(n.name)
1.1117 + if value:
1.1118 + return value
1.1119 +
1.1120 + # Special case for operator functions introduced through code
1.1121 + # transformations.
1.1122 +
1.1123 + if n.name.startswith("$op"):
1.1124 +
1.1125 + # Obtain the location of the actual function defined in the operator
1.1126 + # package.
1.1127 +
1.1128 + op = n.name[len("$op"):]
1.1129 +
1.1130 + # Access the operator module.
1.1131 +
1.1132 + top, module = self.get_module("operator", True)
1.1133 + self.set_module(None, module, hidden=True)
1.1134 +
1.1135 + # Link the operation to the operator module definition in this
1.1136 + # module.
1.1137 +
1.1138 + self.set_imported_name(op, "operator", n.name, self.name)
1.1139 +
1.1140 + # Attempt to get a reference.
1.1141 +
1.1142 + ref = self.import_name_from_module(op, "operator", module)
1.1143 + ref = self.get_object("operator.%s" % op) or ref
1.1144 +
1.1145 + # Record the imported name and provide the resolved name reference.
1.1146 +
1.1147 + value = ResolvedNameRef(n.name, ref)
1.1148 + self.set_special(n.name, value)
1.1149 + return value
1.1150 +
1.1151 + # Record usage of the name.
1.1152 +
1.1153 + self.record_name(n.name)
1.1154 +
1.1155 + # Search for unknown names in non-function scopes immediately.
1.1156 + # External names in functions are resolved later.
1.1157 +
1.1158 + ref = self.find_name(n.name)
1.1159 + if ref:
1.1160 + return ResolvedNameRef(n.name, ref)
1.1161 +
1.1162 + # Global name.
1.1163 +
1.1164 + elif self.in_function and n.name in self.scope_globals[path]:
1.1165 + return NameRef(n.name)
1.1166 +
1.1167 + # Examine other names.
1.1168 +
1.1169 + else:
1.1170 + tracker = self.trackers[-1]
1.1171 +
1.1172 + # Check local names.
1.1173 +
1.1174 + branches = tracker.tracking_name(n.name)
1.1175 +
1.1176 + # Find names inherited from a parent scope.
1.1177 +
1.1178 + if not branches and self.parent_function:
1.1179 + branches = tracker.have_name(n.name)
1.1180 + if branches:
1.1181 + self.propagate_name(n.name)
1.1182 +
1.1183 + # Local or inherited name.
1.1184 +
1.1185 + if branches:
1.1186 + self.record_branches_for_access(branches, n.name, None)
1.1187 + access_number = self.record_access_details(n.name, None, False)
1.1188 + return LocalNameRef(n.name, access_number)
1.1189 +
1.1190 + # Possible global name.
1.1191 +
1.1192 + else:
1.1193 + return NameRef(n.name)
1.1194 +
1.1195 + def process_operator_chain(self, nodes, fn):
1.1196 +
1.1197 + """
1.1198 + Process the given chain of 'nodes', applying 'fn' to each node or item.
1.1199 + Each node starts a new conditional region, effectively making a deeply-
1.1200 + nested collection of if-like statements.
1.1201 + """
1.1202 +
1.1203 + tracker = self.trackers[-1]
1.1204 +
1.1205 + for item in nodes:
1.1206 + tracker.new_branchpoint()
1.1207 + tracker.new_branch()
1.1208 + fn(item)
1.1209 +
1.1210 + for item in nodes[:-1]:
1.1211 + tracker.shelve_branch()
1.1212 + tracker.new_branch()
1.1213 + tracker.shelve_branch()
1.1214 + tracker.merge_branches()
1.1215 +
1.1216 + tracker.shelve_branch()
1.1217 + tracker.merge_branches()
1.1218 +
1.1219 + def process_try_node(self, n):
1.1220 +
1.1221 + """
1.1222 + Process the given "try...except" node 'n'.
1.1223 + """
1.1224 +
1.1225 + tracker = self.trackers[-1]
1.1226 + tracker.new_branchpoint()
1.1227 +
1.1228 + self.process_structure_node(n.body)
1.1229 +
1.1230 + for name, var, handler in n.handlers:
1.1231 + if name is not None:
1.1232 + self.process_structure_node(name)
1.1233 +
1.1234 + # Any abandoned branches from the body can now be resumed in a new
1.1235 + # branch.
1.1236 +
1.1237 + tracker.resume_abandoned_branches()
1.1238 +
1.1239 + # Establish the local for the handler.
1.1240 +
1.1241 + if var is not None:
1.1242 + self.process_structure_node(var)
1.1243 + if handler is not None:
1.1244 + self.process_structure_node(handler)
1.1245 +
1.1246 + tracker.shelve_branch()
1.1247 +
1.1248 + # The else clause maintains the usage from the body but without the
1.1249 + # abandoned branches since they would never lead to the else clause
1.1250 + # being executed.
1.1251 +
1.1252 + if n.else_:
1.1253 + tracker.new_branch()
1.1254 + self.process_structure_node(n.else_)
1.1255 + tracker.shelve_branch()
1.1256 +
1.1257 + # Without an else clause, a null branch propagates the successful
1.1258 + # outcome.
1.1259 +
1.1260 + else:
1.1261 + tracker.new_branch()
1.1262 + tracker.shelve_branch()
1.1263 +
1.1264 + tracker.merge_branches()
1.1265 +
1.1266 + def process_try_finally_node(self, n):
1.1267 +
1.1268 + """
1.1269 + Process the given "try...finally" node 'n'.
1.1270 + """
1.1271 +
1.1272 + tracker = self.trackers[-1]
1.1273 + self.process_structure_node(n.body)
1.1274 +
1.1275 + # Any abandoned branches from the body can now be resumed.
1.1276 +
1.1277 + branches = tracker.resume_all_abandoned_branches()
1.1278 + self.process_structure_node(n.final)
1.1279 +
1.1280 + # At the end of the finally clause, abandoned branches are discarded.
1.1281 +
1.1282 + tracker.restore_active_branches(branches)
1.1283 +
1.1284 + def process_while_node(self, n):
1.1285 +
1.1286 + "Process the given while node 'n'."
1.1287 +
1.1288 + tracker = self.trackers[-1]
1.1289 + tracker.new_branchpoint(loop_node=True)
1.1290 +
1.1291 + # Evaluate any test or iterator outside the loop.
1.1292 +
1.1293 + self.process_structure_node(n.test)
1.1294 +
1.1295 + # Propagate attribute usage to branches.
1.1296 +
1.1297 + tracker.new_branch(loop_node=True)
1.1298 +
1.1299 + # Enter the loop.
1.1300 +
1.1301 + in_conditional = self.in_conditional
1.1302 + self.in_conditional = True
1.1303 + self.process_structure_node(n.body)
1.1304 + self.in_conditional = in_conditional
1.1305 +
1.1306 + # Continuing branches are resumed before any test.
1.1307 +
1.1308 + tracker.resume_continuing_branches()
1.1309 +
1.1310 + # Evaluate any continuation test within the body.
1.1311 +
1.1312 + self.process_structure_node(n.test)
1.1313 +
1.1314 + tracker.shelve_branch(loop_node=True)
1.1315 +
1.1316 + # Support the non-looping condition.
1.1317 +
1.1318 + tracker.new_branch()
1.1319 + tracker.shelve_branch()
1.1320 +
1.1321 + tracker.merge_branches()
1.1322 +
1.1323 + # Evaluate any else clause outside branches.
1.1324 +
1.1325 + if n.else_:
1.1326 + self.process_structure_node(n.else_)
1.1327 +
1.1328 + # Connect broken branches to the code after any loop.
1.1329 +
1.1330 + tracker.resume_broken_branches()
1.1331 +
1.1332 + # Branch tracking methods.
1.1333 +
1.1334 + def start_tracking(self, names):
1.1335 +
1.1336 + """
1.1337 + Start tracking attribute usage for names in the current namespace,
1.1338 + immediately registering the given 'names'.
1.1339 + """
1.1340 +
1.1341 + path = self.get_namespace_path()
1.1342 + parent = self.trackers[-1]
1.1343 + tracker = BranchTracker()
1.1344 + self.trackers.append(tracker)
1.1345 +
1.1346 + # For functions created from expressions or for functions within
1.1347 + # functions, propagate usage to the new namespace.
1.1348 +
1.1349 + if self.parent_function:
1.1350 + tracker.inherit_branches(parent, names)
1.1351 +
1.1352 + # Record the given names established as new branches.
1.1353 +
1.1354 + tracker.assign_names(names)
1.1355 +
1.1356 + def assign_name(self, name, name_ref):
1.1357 +
1.1358 + "Assign to 'name' the given 'name_ref' in the current namespace."
1.1359 +
1.1360 + name = self.get_name_for_tracking(name)
1.1361 + self.trackers[-1].assign_names([name], [name_ref])
1.1362 +
1.1363 + def stop_tracking(self):
1.1364 +
1.1365 + """
1.1366 + Stop tracking attribute usage, recording computed usage for the current
1.1367 + namespace.
1.1368 + """
1.1369 +
1.1370 + path = self.get_namespace_path()
1.1371 + tracker = self.trackers.pop()
1.1372 + self.record_assignments_for_access(tracker)
1.1373 +
1.1374 + self.attr_usage[path] = tracker.get_all_usage()
1.1375 + self.name_initialisers[path] = tracker.get_all_values()
1.1376 +
1.1377 + def start_tracking_in_module(self):
1.1378 +
1.1379 + "Start tracking attribute usage in the module."
1.1380 +
1.1381 + tracker = BranchTracker()
1.1382 + self.trackers.append(tracker)
1.1383 +
1.1384 + def stop_tracking_in_module(self):
1.1385 +
1.1386 + "Stop tracking attribute usage in the module."
1.1387 +
1.1388 + tracker = self.trackers[0]
1.1389 + self.record_assignments_for_access(tracker)
1.1390 + self.attr_usage[self.name] = tracker.get_all_usage()
1.1391 + self.name_initialisers[self.name] = tracker.get_all_values()
1.1392 +
1.1393 + def propagate_name(self, name):
1.1394 +
1.1395 + "Propagate the given 'name' into the current namespace."
1.1396 +
1.1397 + path = self.get_namespace_path()
1.1398 + self.propagated_names[path].add(name)
1.1399 +
1.1400 + def record_assignments_for_access(self, tracker):
1.1401 +
1.1402 + """
1.1403 + For the current path, use the given 'tracker' to record assignment
1.1404 + version information for attribute accesses.
1.1405 + """
1.1406 +
1.1407 + path = self.get_path_for_access()
1.1408 +
1.1409 + if not self.attr_accessor_branches.has_key(path):
1.1410 + return
1.1411 +
1.1412 + init_item(self.attr_accessors, path, dict)
1.1413 + attr_accessors = self.attr_accessors[path]
1.1414 +
1.1415 + # Obtain the branches applying during each access.
1.1416 +
1.1417 + for access, all_branches in self.attr_accessor_branches[path].items():
1.1418 + name, attrnames = access
1.1419 + init_item(attr_accessors, access, list)
1.1420 +
1.1421 + # Obtain the assignments applying to each branch.
1.1422 +
1.1423 + for branches in all_branches:
1.1424 + positions = tracker.get_assignment_positions_for_branches(name, branches)
1.1425 +
1.1426 + # Detect missing name information.
1.1427 +
1.1428 + if None in positions:
1.1429 + globals = self.global_attr_accesses.get(path)
1.1430 + accesses = globals and globals.get(name)
1.1431 + if not accesses:
1.1432 + print >>sys.stderr, "In %s, %s may not be defined when used." % (
1.1433 + self.get_namespace_path(), name)
1.1434 + positions.remove(None)
1.1435 +
1.1436 + attr_accessors[access].append(positions)
1.1437 +
1.1438 + def record_branches_for_access(self, branches, name, attrnames):
1.1439 +
1.1440 + """
1.1441 + Record the given 'branches' for an access involving the given 'name' and
1.1442 + 'attrnames'.
1.1443 + """
1.1444 +
1.1445 + access = name, attrnames
1.1446 + path = self.get_path_for_access()
1.1447 +
1.1448 + init_item(self.attr_accessor_branches, path, dict)
1.1449 + attr_accessor_branches = self.attr_accessor_branches[path]
1.1450 +
1.1451 + init_item(attr_accessor_branches, access, list)
1.1452 + attr_accessor_branches[access].append(branches)
1.1453 +
1.1454 + def record_access_details(self, name, attrnames, assignment):
1.1455 +
1.1456 + """
1.1457 + For the given 'name' and 'attrnames', record an access indicating
1.1458 + whether 'assignment' is occurring.
1.1459 +
1.1460 + These details correspond to accesses otherwise recorded by the attribute
1.1461 + accessor and attribute access dictionaries.
1.1462 + """
1.1463 +
1.1464 + access = name, attrnames
1.1465 + path = self.get_path_for_access()
1.1466 +
1.1467 + init_item(self.attr_access_modifiers, path, dict)
1.1468 + init_item(self.attr_access_modifiers[path], access, list)
1.1469 +
1.1470 + access_number = len(self.attr_access_modifiers[path][access])
1.1471 + self.attr_access_modifiers[path][access].append(assignment)
1.1472 + return access_number
1.1473 +
1.1474 + def record_global_access_details(self, name, attrnames):
1.1475 +
1.1476 + """
1.1477 + Record details of a global access via the given 'name' involving the
1.1478 + indicated 'attrnames'.
1.1479 + """
1.1480 +
1.1481 + path = self.get_namespace_path()
1.1482 +
1.1483 + init_item(self.global_attr_accesses, path, dict)
1.1484 + init_item(self.global_attr_accesses[path], name, set)
1.1485 + self.global_attr_accesses[path][name].add(attrnames)
1.1486 +
1.1487 + # Namespace modification.
1.1488 +
1.1489 + def record_name(self, name):
1.1490 +
1.1491 + "Record the use of 'name' in a namespace."
1.1492 +
1.1493 + path = self.get_namespace_path()
1.1494 + init_item(self.names_used, path, set)
1.1495 + self.names_used[path].add(name)
1.1496 +
1.1497 + def set_module(self, name, module, leaf_module=None, hidden=False):
1.1498 +
1.1499 + """
1.1500 + Set a module in the current namespace using the given 'name' and
1.1501 + corresponding 'module' object, with the 'leaf_module' being recorded
1.1502 + if different. If 'hidden' is a true value, the modules are recorded as
1.1503 + not necessarily being exposed by this module. This module is, however,
1.1504 + recorded as accessing the given modules and is thus dependent on them.
1.1505 + """
1.1506 +
1.1507 + if name:
1.1508 + self.set_general_local(name, module and Reference("<module>", module.name) or None)
1.1509 + if module:
1.1510 + if hidden:
1.1511 + self.imported_hidden.add(module)
1.1512 + if leaf_module and leaf_module is not module:
1.1513 + self.imported_hidden.add(leaf_module)
1.1514 + else:
1.1515 + self.imported.add(module)
1.1516 + module.accessing_modules.add(self.name)
1.1517 + if leaf_module and leaf_module is not module:
1.1518 + self.imported.add(leaf_module)
1.1519 + leaf_module.accessing_modules.add(self.name)
1.1520 +
1.1521 + def set_definition(self, name, kind):
1.1522 +
1.1523 + """
1.1524 + Set the definition having the given 'name' and 'kind'.
1.1525 +
1.1526 + Definitions are set in the static namespace hierarchy, but they can also
1.1527 + be recorded for function locals.
1.1528 + """
1.1529 +
1.1530 + if self.is_global(name):
1.1531 + print >>sys.stderr, "In %s, %s is defined as being global." % (
1.1532 + self.get_namespace_path(), name)
1.1533 +
1.1534 + path = self.get_object_path(name)
1.1535 + self.set_object(path, kind)
1.1536 +
1.1537 + ref = self.get_object(path)
1.1538 + if ref.get_kind() == "<var>":
1.1539 + print >>sys.stderr, "In %s, %s is defined more than once." % (
1.1540 + self.get_namespace_path(), name)
1.1541 +
1.1542 + if not self.is_global(name) and self.in_function:
1.1543 + self.set_function_local(name, ref)
1.1544 +
1.1545 + def set_function_local(self, name, ref=None):
1.1546 +
1.1547 + "Set the local with the given 'name' and optional 'ref'."
1.1548 +
1.1549 + locals = self.function_locals[self.get_namespace_path()]
1.1550 + multiple = not ref or locals.has_key(name) and locals[name] != ref
1.1551 + locals[name] = multiple and Reference("<var>") or ref
1.1552 +
1.1553 + def assign_general_local(self, name, name_ref):
1.1554 +
1.1555 + """
1.1556 + Set for 'name' the given 'name_ref', recording the name for attribute
1.1557 + usage tracking.
1.1558 + """
1.1559 +
1.1560 + self.set_general_local(name, name_ref)
1.1561 + self.assign_name(name, name_ref)
1.1562 +
1.1563 + def set_general_local(self, name, value=None):
1.1564 +
1.1565 + """
1.1566 + Set the 'name' with optional 'value' in any kind of local namespace,
1.1567 + where the 'value' should be a reference if specified.
1.1568 + """
1.1569 +
1.1570 + init_value = self.get_initialising_value(value)
1.1571 +
1.1572 + # Module global names.
1.1573 +
1.1574 + if self.is_global(name):
1.1575 + path = self.get_global_path(name)
1.1576 + self.set_object(path, init_value)
1.1577 +
1.1578 + # Function local names.
1.1579 +
1.1580 + elif self.in_function:
1.1581 + path = self.get_object_path(name)
1.1582 + self.set_function_local(name, init_value)
1.1583 +
1.1584 + # Other namespaces (classes).
1.1585 +
1.1586 + else:
1.1587 + path = self.get_object_path(name)
1.1588 + self.set_name(name, init_value)
1.1589 +
1.1590 + def set_name(self, name, ref=None):
1.1591 +
1.1592 + "Attach the 'name' with optional 'ref' to the current namespace."
1.1593 +
1.1594 + self.set_object(self.get_object_path(name), ref)
1.1595 +
1.1596 + def set_instance_attr(self, name, ref=None):
1.1597 +
1.1598 + """
1.1599 + Add an instance attribute of the given 'name' to the current class,
1.1600 + using the optional 'ref'.
1.1601 + """
1.1602 +
1.1603 + init_item(self.instance_attrs, self.in_class, set)
1.1604 + self.instance_attrs[self.in_class].add(name)
1.1605 +
1.1606 + if ref:
1.1607 + init_item(self.instance_attr_constants, self.in_class, dict)
1.1608 + self.instance_attr_constants[self.in_class][name] = ref
1.1609 +
1.1610 + def get_initialising_value(self, value):
1.1611 +
1.1612 + "Return a suitable initialiser reference for 'value'."
1.1613 +
1.1614 + if isinstance(value, (NameRef, AccessRef, InstanceRef)): # includes LiteralSequenceRef, ResolvedNameRef
1.1615 + return value.reference()
1.1616 +
1.1617 + # In general, invocations do not produce known results. However, the
1.1618 + # name initialisers are resolved once a module has been inspected.
1.1619 +
1.1620 + elif isinstance(value, InvocationRef):
1.1621 + return None
1.1622 +
1.1623 + else:
1.1624 + return value
1.1625 +
1.1626 + # Static, program-relative naming.
1.1627 +
1.1628 + def find_name(self, name):
1.1629 +
1.1630 + """
1.1631 + Return the qualified name for the given 'name' used in the current
1.1632 + non-function namespace.
1.1633 + """
1.1634 +
1.1635 + path = self.get_namespace_path()
1.1636 + ref = None
1.1637 +
1.1638 + if not self.in_function and name not in predefined_constants:
1.1639 + if self.in_class:
1.1640 + ref = self.get_object(self.get_object_path(name))
1.1641 + if not ref:
1.1642 + ref = self.get_global_or_builtin(name)
1.1643 +
1.1644 + return ref
1.1645 +
1.1646 + def get_class(self, node):
1.1647 +
1.1648 + """
1.1649 + Use the given 'node' to obtain the identity of a class. Return a
1.1650 + reference for the class. Unresolved dependencies are permitted and must
1.1651 + be resolved later.
1.1652 + """
1.1653 +
1.1654 + ref = self._get_class(node)
1.1655 + return ref.has_kind(["<class>", "<depends>"]) and ref or None
1.1656 +
1.1657 + def _get_class(self, node):
1.1658 +
1.1659 + """
1.1660 + Use the given 'node' to find a class definition. Return a reference to
1.1661 + the class.
1.1662 + """
1.1663 +
1.1664 + if isinstance(node, compiler.ast.Getattr):
1.1665 +
1.1666 + # Obtain the identity of the access target.
1.1667 +
1.1668 + ref = self._get_class(node.expr)
1.1669 +
1.1670 + # Where the target is a class or module, obtain the identity of the
1.1671 + # attribute.
1.1672 +
1.1673 + if ref.has_kind(["<function>", "<var>"]):
1.1674 + return None
1.1675 + else:
1.1676 + attrname = "%s.%s" % (ref.get_origin(), node.attrname)
1.1677 + return self.get_object(attrname)
1.1678 +
1.1679 + # Names can be module-level or built-in.
1.1680 +
1.1681 + elif isinstance(node, compiler.ast.Name):
1.1682 +
1.1683 + # Record usage of the name and attempt to identify it.
1.1684 +
1.1685 + self.record_name(node.name)
1.1686 + return self.get_global_or_builtin(node.name)
1.1687 + else:
1.1688 + return None
1.1689 +
1.1690 + def get_constant(self, name, value):
1.1691 +
1.1692 + "Return a constant reference for the given type 'name' and 'value'."
1.1693 +
1.1694 + ref = self.get_literal_builtin(name)
1.1695 + return self.get_constant_reference(ref, value)
1.1696 +
1.1697 + def get_literal_instance(self, n, name):
1.1698 +
1.1699 + "For node 'n', return a reference to an instance of 'name'."
1.1700 +
1.1701 + # Get a class reference.
1.1702 +
1.1703 + ref = self.get_literal_builtin(name)
1.1704 +
1.1705 + # Obtain the details of the literal itself.
1.1706 + # An alias to the type is generated for sequences.
1.1707 +
1.1708 + if name in ("dict", "list", "tuple"):
1.1709 + self.set_special_literal(name, ref)
1.1710 + return self.process_literal_sequence_node(n, name, ref, LiteralSequenceRef)
1.1711 +
1.1712 + # Constant values are independently recorded.
1.1713 +
1.1714 + else:
1.1715 + return self.get_constant_reference(ref, n.value)
1.1716 +
1.1717 + def get_literal_builtin(self, name):
1.1718 +
1.1719 + "Return a reference for a built-in literal type of the given 'name'."
1.1720 +
1.1721 + ref = self.get_builtin(name)
1.1722 + true_origin = "__builtins__.%s.%s" % (name, name)
1.1723 + exposed_origin = "__builtins__.%s" % name
1.1724 +
1.1725 + # Obtain fully-imported built-in class references.
1.1726 +
1.1727 + if ref and ref.has_kind("<class>"):
1.1728 + pass
1.1729 +
1.1730 + # Early-stage references need explicit references.
1.1731 +
1.1732 + elif ref:
1.1733 + ref = Reference("<class>", true_origin)
1.1734 +
1.1735 + # Otherwise, the normal locations can be used.
1.1736 +
1.1737 + else:
1.1738 + ref = Reference("<class>", true_origin, exposed_origin)
1.1739 +
1.1740 + return ref
1.1741 +
1.1742 + # Functions and invocations.
1.1743 +
1.1744 + def allocate_arguments(self, path, args):
1.1745 +
1.1746 + """
1.1747 + Allocate temporary argument storage using current and maximum
1.1748 + requirements for the given 'path' and 'args'.
1.1749 + """
1.1750 +
1.1751 + init_item(self.function_targets, path, lambda: [0, 0])
1.1752 + t = self.function_targets[path]
1.1753 + t[0] += 1
1.1754 + t[1] = max(t[0], t[1])
1.1755 +
1.1756 + init_item(self.function_arguments, path, lambda: [0, 0])
1.1757 + t = self.function_arguments[path]
1.1758 + t[0] += len(args) + 1
1.1759 + t[1] = max(t[0], t[1])
1.1760 +
1.1761 + def deallocate_arguments(self, path, args):
1.1762 +
1.1763 + "Deallocate temporary argument storage for the given 'path' and 'args'."
1.1764 +
1.1765 + self.function_targets[path][0] -= 1
1.1766 + self.function_arguments[path][0] -= len(args) + 1
1.1767 +
1.1768 +# vim: tabstop=4 expandtab shiftwidth=4