28 | 28 |
pass
|
29 | 29 |
|
30 | 30 |
|
31 | |
class InconsistentConstraintsError(StaticAnalysisError):
|
32 | |
pass
|
33 | |
|
34 | |
|
35 | 31 |
class TypeMismatchError(StaticAnalysisError):
|
36 | 32 |
pass
|
37 | 33 |
|
38 | 34 |
|
39 | |
class IncompatibleConstraintsError(StaticAnalysisError):
|
40 | |
pass
|
41 | |
|
42 | |
|
43 | 35 |
class IllegalJumpError(StaticAnalysisError):
|
|
36 |
pass
|
|
37 |
|
|
38 |
|
|
39 |
class ConstraintsError(StaticAnalysisError):
|
|
40 |
pass
|
|
41 |
|
|
42 |
|
|
43 |
class ConstantConstraintError(ConstraintsError):
|
|
44 |
pass
|
|
45 |
|
|
46 |
|
|
47 |
class InconsistentConstraintsError(ConstraintsError):
|
|
48 |
pass
|
|
49 |
|
|
50 |
|
|
51 |
class IncompatibleConstraintsError(ConstraintsError):
|
44 | 52 |
pass
|
45 | 53 |
|
46 | 54 |
|
|
56 | 64 |
A location is writeable if it was listed in the outputs and trashes
|
57 | 65 |
lists of this routine.
|
58 | 66 |
"""
|
59 | |
def __init__(self, routine, inputs, outputs, trashes):
|
|
67 |
def __init__(self, routines, routine, inputs, outputs, trashes):
|
|
68 |
self.routines = routines # Location -> AST node
|
60 | 69 |
self.routine = routine
|
61 | 70 |
self._touched = set()
|
62 | 71 |
self._meaningful = set()
|
63 | 72 |
self._writeable = set()
|
64 | 73 |
|
65 | 74 |
for ref in inputs:
|
|
75 |
if ref.is_constant():
|
|
76 |
raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
|
66 | 77 |
self._meaningful.add(ref)
|
67 | 78 |
output_names = set()
|
68 | 79 |
for ref in outputs:
|
|
80 |
if ref.is_constant():
|
|
81 |
raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
|
69 | 82 |
output_names.add(ref.name)
|
70 | 83 |
self._writeable.add(ref)
|
71 | 84 |
for ref in trashes:
|
|
85 |
if ref.is_constant():
|
|
86 |
raise ConstantConstraintError('%s in %s' % (ref.name, routine.name))
|
72 | 87 |
if ref.name in output_names:
|
73 | |
raise InconsistentConstraintsError(ref.name)
|
|
88 |
raise InconsistentConstraintsError('%s in %s' % (ref.name, routine.name))
|
74 | 89 |
self._writeable.add(ref)
|
75 | 90 |
|
76 | 91 |
def clone(self):
|
77 | |
c = Context(self.routine, [], [], [])
|
|
92 |
c = Context(self.routines, self.routine, [], [], [])
|
78 | 93 |
c._touched = set(self._touched)
|
79 | 94 |
c._meaningful = set(self._meaningful)
|
80 | 95 |
c._writeable = set(self._writeable)
|
81 | 96 |
return c
|
82 | 97 |
|
83 | 98 |
def set_from(self, c):
|
|
99 |
assert c.routines == self.routines
|
84 | 100 |
assert c.routine == self.routine
|
85 | 101 |
self._touched = set(c._touched)
|
86 | 102 |
self._meaningful = set(c._meaningful)
|
|
97 | 113 |
def assert_meaningful(self, *refs, **kwargs):
|
98 | 114 |
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
99 | 115 |
for ref in refs:
|
100 | |
if isinstance(ref, ConstantRef):
|
|
116 |
if isinstance(ref, ConstantRef) or ref in self.routines:
|
101 | 117 |
pass
|
102 | 118 |
elif isinstance(ref, LocationRef):
|
103 | 119 |
if ref not in self._meaningful:
|
|
140 | 156 |
def __init__(self):
|
141 | 157 |
self.current_routine = None
|
142 | 158 |
self.has_encountered_goto = False
|
|
159 |
self.routines = {}
|
143 | 160 |
|
144 | 161 |
def analyze_program(self, program):
|
145 | 162 |
assert isinstance(program, Program)
|
146 | |
routines = {r.name: r for r in program.routines}
|
|
163 |
self.routines = {r.location: r for r in program.routines}
|
147 | 164 |
for routine in program.routines:
|
148 | |
self.analyze_routine(routine, routines)
|
149 | |
|
150 | |
def analyze_routine(self, routine, routines):
|
|
165 |
self.analyze_routine(routine)
|
|
166 |
|
|
167 |
def analyze_routine(self, routine):
|
151 | 168 |
assert isinstance(routine, Routine)
|
152 | 169 |
self.current_routine = routine
|
153 | 170 |
self.has_encountered_goto = False
|
|
155 | 172 |
# it's an extern, that's fine
|
156 | 173 |
return
|
157 | 174 |
type = routine.location.type
|
158 | |
context = Context(routine, type.inputs, type.outputs, type.trashes)
|
159 | |
self.analyze_block(routine.block, context, routines)
|
|
175 |
context = Context(self.routines, routine, type.inputs, type.outputs, type.trashes)
|
|
176 |
self.analyze_block(routine.block, context)
|
160 | 177 |
if not self.has_encountered_goto:
|
161 | 178 |
for ref in type.outputs:
|
162 | 179 |
context.assert_meaningful(ref, exception_class=UnmeaningfulOutputError)
|
|
166 | 183 |
raise ForbiddenWriteError(message)
|
167 | 184 |
self.current_routine = None
|
168 | 185 |
|
169 | |
def analyze_block(self, block, context, routines):
|
|
186 |
def analyze_block(self, block, context):
|
170 | 187 |
assert isinstance(block, Block)
|
171 | 188 |
for i in block.instrs:
|
172 | 189 |
if self.has_encountered_goto:
|
173 | 190 |
raise IllegalJumpError(i)
|
174 | |
self.analyze_instr(i, context, routines)
|
175 | |
|
176 | |
def analyze_instr(self, instr, context, routines):
|
|
191 |
self.analyze_instr(i, context)
|
|
192 |
|
|
193 |
def analyze_instr(self, instr, context):
|
177 | 194 |
assert isinstance(instr, Instr)
|
178 | 195 |
opcode = instr.opcode
|
179 | 196 |
dest = instr.dest
|
|
227 | 244 |
elif opcode == 'if':
|
228 | 245 |
context1 = context.clone()
|
229 | 246 |
context2 = context.clone()
|
230 | |
self.analyze_block(instr.block1, context1, routines)
|
|
247 |
self.analyze_block(instr.block1, context1)
|
231 | 248 |
if instr.block2 is not None:
|
232 | |
self.analyze_block(instr.block2, context2, routines)
|
|
249 |
self.analyze_block(instr.block2, context2)
|
233 | 250 |
# TODO may we need to deal with touched separately here too?
|
234 | 251 |
# probably not; if it wasn't meaningful in the first place, it
|
235 | 252 |
# doesn't really matter if you modified it or not, coming out.
|
|
241 | 258 |
elif opcode == 'repeat':
|
242 | 259 |
# it will always be executed at least once, so analyze it having
|
243 | 260 |
# been executed the first time.
|
244 | |
self.analyze_block(instr.block, context, routines)
|
|
261 |
self.analyze_block(instr.block, context)
|
245 | 262 |
|
246 | 263 |
# now analyze it having been executed a second time, with the context
|
247 | 264 |
# of it having already been executed.
|
248 | |
self.analyze_block(instr.block, context, routines)
|
|
265 |
self.analyze_block(instr.block, context)
|
249 | 266 |
|
250 | 267 |
# NB I *think* that's enough... but it might not be?
|
251 | 268 |
elif opcode == 'copy':
|
|
274 | 291 |
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
275 | 292 |
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
276 | 293 |
elif opcode == 'with-sei':
|
277 | |
self.analyze_block(instr.block, context, routines)
|
|
294 |
self.analyze_block(instr.block, context)
|
278 | 295 |
elif opcode == 'goto':
|
279 | 296 |
location = instr.location
|
280 | 297 |
type = location.type
|