2.1 --- a/inspector.py Sat Mar 11 16:46:36 2017 +0100
2.2 +++ b/inspector.py Sat Mar 11 22:27:45 2017 +0100
2.3 @@ -271,7 +271,7 @@
2.4 self.trackers[-1].abandon_branch()
2.5
2.6 elif isinstance(n, compiler.ast.Return):
2.7 - self.process_structure(n)
2.8 + self.record_return_value(self.process_structure_node(n.value))
2.9 self.trackers[-1].abandon_returning_branch()
2.10
2.11 # Print statements.
2.12 @@ -654,7 +654,14 @@
2.13
2.14 self.start_tracking(locals)
2.15 self.process_structure_node(n.code)
2.16 - self.stop_tracking()
2.17 + returns_value = self.stop_tracking()
2.18 +
2.19 + # Record any null result.
2.20 +
2.21 + is_initialiser = is_method and name == "__init__"
2.22 +
2.23 + if not returns_value and not is_initialiser:
2.24 + self.record_return_value(ResolvedNameRef("None", self.get_builtin("None")))
2.25
2.26 # Exit to the parent.
2.27
2.28 @@ -1075,6 +1082,7 @@
2.29
2.30 """
2.31 Stop tracking attribute usage, recording computed usage for the current
2.32 + namespace. Indicate whether a value is always returned from the
2.33 namespace.
2.34 """
2.35
2.36 @@ -1085,6 +1093,8 @@
2.37 self.attr_usage[path] = tracker.get_all_usage()
2.38 self.name_initialisers[path] = tracker.get_all_values()
2.39
2.40 + return tracker.returns_value()
2.41 +
2.42 def start_tracking_in_module(self):
2.43
2.44 "Start tracking attribute usage in the module."
2.45 @@ -1469,4 +1479,14 @@
2.46
2.47 self.exception_namespaces.add(self.get_namespace_path())
2.48
2.49 + # Return values.
2.50 +
2.51 + def record_return_value(self, expr):
2.52 +
2.53 + "Record the given return 'expr'."
2.54 +
2.55 + path = self.get_namespace_path()
2.56 + init_item(self.return_values, path, set)
2.57 + self.return_values[path].add(expr)
2.58 +
2.59 # vim: tabstop=4 expandtab shiftwidth=4
3.1 --- a/modules.py Sat Mar 11 16:46:36 2017 +0100
3.2 +++ b/modules.py Sat Mar 11 22:27:45 2017 +0100
3.3 @@ -71,6 +71,10 @@
3.4
3.5 self.exception_namespaces = set()
3.6
3.7 + # Return value details.
3.8 +
3.9 + self.return_values = {}
3.10 +
3.11 # Attribute usage at module and function levels.
3.12
3.13 self.attr_usage = {}
3.14 @@ -398,6 +402,7 @@
3.15 self._get_constant_literals(f)
3.16 self._get_constant_values(f)
3.17 self._get_exception_namespaces(f)
3.18 + self._get_return_values(f)
3.19
3.20 finally:
3.21 f.close()
3.22 @@ -618,6 +623,15 @@
3.23 self.exception_namespaces = value and set(value.split(", ")) or set()
3.24 f.readline()
3.25
3.26 + def _get_return_values(self, f):
3.27 + f.readline() # "return values:"
3.28 + line = f.readline().rstrip()
3.29 + while line:
3.30 + path, values = self._get_fields(line)
3.31 + values = values.split(", ")
3.32 + self.return_values[path] = map(decode_reference, values)
3.33 + line = f.readline().rstrip()
3.34 +
3.35 # Generic parsing methods.
3.36
3.37 def from_lines(self, f, d):
3.38 @@ -875,6 +889,13 @@
3.39 paths.sort()
3.40 print >>f, ", ".join(paths)
3.41
3.42 + print >>f
3.43 + print >>f, "return values:"
3.44 + paths = self.return_values.keys()
3.45 + paths.sort()
3.46 + for path in paths:
3.47 + print >>f, path, ", ".join(map(str, self.return_values[path]))
3.48 +
3.49 finally:
3.50 f.close()
3.51
4.1 --- a/resolving.py Sat Mar 11 16:46:36 2017 +0100
4.2 +++ b/resolving.py Sat Mar 11 22:27:45 2017 +0100
4.3 @@ -40,6 +40,7 @@
4.4 self.check_names_used()
4.5 self.check_invocations()
4.6 self.resolve_initialisers()
4.7 + self.resolve_return_values()
4.8 self.resolve_literals()
4.9
4.10 def resolve_class_bases(self):
4.11 @@ -235,7 +236,6 @@
4.12 # Get the initialisers in each namespace.
4.13
4.14 for path, name_initialisers in self.name_initialisers.items():
4.15 - const_accesses = self.const_accesses.get(path)
4.16
4.17 # Resolve values for each name in a scope.
4.18
4.19 @@ -244,109 +244,154 @@
4.20 aliased_names = {}
4.21
4.22 for i, name_ref in enumerate(values):
4.23 -
4.24 - # Unwrap invocations.
4.25 -
4.26 - if isinstance(name_ref, InvocationRef):
4.27 - invocation = True
4.28 - name_ref = name_ref.name_ref
4.29 - else:
4.30 - invocation = False
4.31 -
4.32 - # Obtain a usable reference from names or constants.
4.33 -
4.34 - if isinstance(name_ref, ResolvedNameRef):
4.35 - if not name_ref.reference():
4.36 - continue
4.37 - ref = name_ref.reference()
4.38 -
4.39 - # Obtain a reference from instances.
4.40 -
4.41 - elif isinstance(name_ref, InstanceRef):
4.42 - if not name_ref.reference():
4.43 - continue
4.44 - ref = name_ref.reference()
4.45 -
4.46 - # Resolve accesses that employ constants.
4.47 -
4.48 - elif isinstance(name_ref, AccessRef):
4.49 - ref = None
4.50 -
4.51 - if const_accesses:
4.52 - resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
4.53 - if resolved_access:
4.54 - objpath, ref, remaining_attrnames = resolved_access
4.55 - if remaining_attrnames:
4.56 - ref = None
4.57 -
4.58 - # Accesses that do not employ constants cannot be resolved,
4.59 - # but they may be resolvable later.
4.60 -
4.61 - if not ref:
4.62 - if not invocation:
4.63 -
4.64 - # Record the path used for tracking purposes
4.65 - # alongside original name, attribute and access
4.66 - # number details.
4.67 -
4.68 - aliased_names[i] = path, name_ref.original_name, name_ref.attrnames, name_ref.number
4.69 -
4.70 - continue
4.71 -
4.72 - # Attempt to resolve a plain name reference.
4.73 -
4.74 - elif isinstance(name_ref, LocalNameRef):
4.75 - key = "%s.%s" % (path, name_ref.name)
4.76 - ref = self.name_references.get(key)
4.77 -
4.78 - # Accesses that do not refer to known static objects
4.79 - # cannot be resolved, but they may be resolvable later.
4.80 -
4.81 - if not ref:
4.82 - if not invocation:
4.83 -
4.84 - # Record the path used for tracking purposes
4.85 - # alongside original name, attribute and access
4.86 - # number details.
4.87 -
4.88 - aliased_names[i] = path, name_ref.name, None, name_ref.number
4.89 -
4.90 - continue
4.91 -
4.92 - ref = self.get_resolved_object(ref.get_origin())
4.93 - if not ref:
4.94 - continue
4.95 -
4.96 - elif isinstance(name_ref, NameRef):
4.97 - key = "%s.%s" % (path, name_ref.name)
4.98 - ref = self.name_references.get(key)
4.99 -
4.100 - ref = ref and self.get_resolved_object(ref.get_origin())
4.101 - if not ref:
4.102 - continue
4.103 -
4.104 - else:
4.105 - continue
4.106 -
4.107 - # Resolve any hidden dependencies involving external objects
4.108 - # or unresolved names referring to globals or built-ins.
4.109 -
4.110 - if ref.has_kind("<depends>"):
4.111 - ref = self.importer.identify(ref.get_origin())
4.112 -
4.113 - # Convert class invocations to instances.
4.114 -
4.115 - if ref and invocation:
4.116 - ref = self.convert_invocation(ref)
4.117 -
4.118 - if ref and not ref.has_kind("<var>"):
4.119 - initialised_names[i] = ref
4.120 + initialised_ref, aliased_name = self.resolve_reference(path, name_ref)
4.121 + if initialised_ref:
4.122 + initialised_names[i] = initialised_ref
4.123 + if aliased_name:
4.124 + aliased_names[i] = aliased_name
4.125
4.126 if initialised_names:
4.127 self.initialised_names[(path, name)] = initialised_names
4.128 if aliased_names:
4.129 self.aliased_names[(path, name)] = aliased_names
4.130
4.131 + def resolve_return_values(self):
4.132 +
4.133 + "Resolve return values using name references."
4.134 +
4.135 + return_values = {}
4.136 +
4.137 + # Get the return values from each namespace.
4.138 +
4.139 + for path, values in self.return_values.items():
4.140 + l = set()
4.141 +
4.142 + for value in values:
4.143 + if not value:
4.144 + ref = None
4.145 + else:
4.146 + ref, aliased_name = self.resolve_reference(path, value)
4.147 +
4.148 + l.add(ref or Reference("<var>"))
4.149 +
4.150 + return_values[path] = l
4.151 +
4.152 + # Replace the original values.
4.153 +
4.154 + self.return_values = return_values
4.155 +
4.156 + def resolve_reference(self, path, name_ref):
4.157 +
4.158 + """
4.159 + Within the namespace 'path', resolve the given 'name_ref', returning any
4.160 + initialised reference, along with any aliased name information.
4.161 + """
4.162 +
4.163 + const_accesses = self.const_accesses.get(path)
4.164 +
4.165 + initialised_ref = None
4.166 + aliased_name = None
4.167 + no_reference = None, None
4.168 +
4.169 + # Unwrap invocations.
4.170 +
4.171 + if isinstance(name_ref, InvocationRef):
4.172 + invocation = True
4.173 + name_ref = name_ref.name_ref
4.174 + else:
4.175 + invocation = False
4.176 +
4.177 + # Obtain a usable reference from names or constants.
4.178 +
4.179 + if isinstance(name_ref, ResolvedNameRef):
4.180 + if not name_ref.reference():
4.181 + return no_reference
4.182 + ref = name_ref.reference()
4.183 +
4.184 + # Obtain a reference from instances.
4.185 +
4.186 + elif isinstance(name_ref, InstanceRef):
4.187 + if not name_ref.reference():
4.188 + return no_reference
4.189 + ref = name_ref.reference()
4.190 +
4.191 + # Resolve accesses that employ constants.
4.192 +
4.193 + elif isinstance(name_ref, AccessRef):
4.194 + ref = None
4.195 +
4.196 + if const_accesses:
4.197 + resolved_access = const_accesses.get((name_ref.original_name, name_ref.attrnames))
4.198 + if resolved_access:
4.199 + objpath, ref, remaining_attrnames = resolved_access
4.200 + if remaining_attrnames:
4.201 + ref = None
4.202 +
4.203 + # Accesses that do not employ constants cannot be resolved,
4.204 + # but they may be resolvable later.
4.205 +
4.206 + if not ref:
4.207 + if not invocation:
4.208 +
4.209 + # Record the path used for tracking purposes
4.210 + # alongside original name, attribute and access
4.211 + # number details.
4.212 +
4.213 + aliased_name = path, name_ref.original_name, name_ref.attrnames, name_ref.number
4.214 +
4.215 + return no_reference
4.216 +
4.217 + # Attempt to resolve a plain name reference.
4.218 +
4.219 + elif isinstance(name_ref, LocalNameRef):
4.220 + key = "%s.%s" % (path, name_ref.name)
4.221 + ref = self.name_references.get(key)
4.222 +
4.223 + # Accesses that do not refer to known static objects
4.224 + # cannot be resolved, but they may be resolvable later.
4.225 +
4.226 + if not ref:
4.227 + if not invocation:
4.228 +
4.229 + # Record the path used for tracking purposes
4.230 + # alongside original name, attribute and access
4.231 + # number details.
4.232 +
4.233 + aliased_name = path, name_ref.name, None, name_ref.number
4.234 +
4.235 + return no_reference
4.236 +
4.237 + ref = self.get_resolved_object(ref.get_origin())
4.238 + if not ref:
4.239 + return no_reference
4.240 +
4.241 + elif isinstance(name_ref, NameRef):
4.242 + key = "%s.%s" % (path, name_ref.name)
4.243 + ref = self.name_references.get(key)
4.244 +
4.245 + ref = ref and self.get_resolved_object(ref.get_origin())
4.246 + if not ref:
4.247 + return no_reference
4.248 +
4.249 + else:
4.250 + return no_reference
4.251 +
4.252 + # Resolve any hidden dependencies involving external objects
4.253 + # or unresolved names referring to globals or built-ins.
4.254 +
4.255 + if ref.has_kind("<depends>"):
4.256 + ref = self.importer.identify(ref.get_origin())
4.257 +
4.258 + # Convert class invocations to instances.
4.259 +
4.260 + if ref and invocation:
4.261 + ref = self.convert_invocation(ref)
4.262 +
4.263 + if ref and not ref.has_kind("<var>"):
4.264 + initialised_ref = ref
4.265 +
4.266 + return initialised_ref, aliased_name
4.267 +
4.268 def resolve_literals(self):
4.269
4.270 "Resolve constant value types."