Show line numbers in static analysis errors (clumsily.)
Chris Pressey
4 years ago
9 | 9 | |
10 | 10 | |
11 | 11 | class StaticAnalysisError(ValueError): |
12 | pass | |
12 | def __init__(self, line_number, message): | |
13 | super(StaticAnalysisError, self).__init__(line_number, message) | |
14 | ||
15 | def __str__(self): | |
16 | return "{} (Line {})".format(self.args[1], self.args[0]) | |
13 | 17 | |
14 | 18 | |
15 | 19 | class UnmeaningfulReadError(StaticAnalysisError): |
93 | 97 | |
94 | 98 | for ref in inputs: |
95 | 99 | if ref.is_constant(): |
96 | raise ConstantConstraintError('%s in %s' % (ref.name, routine.name)) | |
100 | raise ConstantConstraintError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) | |
97 | 101 | self._range[ref] = ref.max_range() |
98 | 102 | output_names = set() |
99 | 103 | for ref in outputs: |
100 | 104 | if ref.is_constant(): |
101 | raise ConstantConstraintError('%s in %s' % (ref.name, routine.name)) | |
105 | raise ConstantConstraintError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) | |
102 | 106 | output_names.add(ref.name) |
103 | 107 | self._writeable.add(ref) |
104 | 108 | for ref in trashes: |
105 | 109 | if ref.is_constant(): |
106 | raise ConstantConstraintError('%s in %s' % (ref.name, routine.name)) | |
110 | raise ConstantConstraintError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) | |
107 | 111 | if ref.name in output_names: |
108 | raise InconsistentConstraintsError('%s in %s' % (ref.name, routine.name)) | |
112 | raise InconsistentConstraintsError(self.routine.line_number, '%s in %s' % (ref.name, routine.name)) | |
109 | 113 | self._writeable.add(ref) |
110 | 114 | |
111 | 115 | def __str__(self): |
141 | 145 | message = '%s in %s' % (ref.name, self.routine.name) |
142 | 146 | if kwargs.get('message'): |
143 | 147 | message += ' (%s)' % kwargs['message'] |
144 | raise exception_class(message) | |
148 | raise exception_class(self.routine.line_number, message) | |
145 | 149 | elif isinstance(ref, IndexedRef): |
146 | 150 | self.assert_meaningful(ref.ref, **kwargs) |
147 | 151 | self.assert_meaningful(ref.index, **kwargs) |
158 | 162 | message = '%s in %s' % (ref.name, self.routine.name) |
159 | 163 | if kwargs.get('message'): |
160 | 164 | message += ' (%s)' % kwargs['message'] |
161 | raise exception_class(message) | |
165 | raise exception_class(self.routine.line_number, message) | |
162 | 166 | |
163 | 167 | def assert_in_range(self, inside, outside): |
164 | 168 | # FIXME there's a bit of I'm-not-sure-the-best-way-to-do-this-ness, here... |
175 | 179 | outside_range = (0, outside.type.size-1) |
176 | 180 | |
177 | 181 | if inside_range[0] < outside_range[0] or inside_range[1] > outside_range[1]: |
178 | raise RangeExceededError( | |
182 | raise RangeExceededError(self.routine.line_number, | |
179 | 183 | "Possible range of {} {} exceeds acceptable range of {} {}".format( |
180 | 184 | inside, inside_range, outside, outside_range |
181 | 185 | ) |
235 | 239 | def assert_type(self, type, *locations): |
236 | 240 | for location in locations: |
237 | 241 | if location.type != type: |
238 | raise TypeMismatchError('%s in %s' % | |
242 | raise TypeMismatchError(self.current_routine.line_number, '%s in %s' % | |
239 | 243 | (location.name, self.current_routine.name) |
240 | 244 | ) |
241 | 245 | |
292 | 296 | # even if we goto another routine, we can't trash an output. |
293 | 297 | for ref in trashed: |
294 | 298 | if ref in type_.outputs: |
295 | raise UnmeaningfulOutputError('%s in %s' % (ref.name, routine.name)) | |
299 | raise UnmeaningfulOutputError(routine.line_number, '%s in %s' % (ref.name, routine.name)) | |
296 | 300 | |
297 | 301 | if not self.has_encountered_goto: |
298 | 302 | for ref in type_.outputs: |
300 | 304 | for ref in context.each_touched(): |
301 | 305 | if ref not in type_.outputs and ref not in type_.trashes and not routine_has_static(routine, ref): |
302 | 306 | message = '%s in %s' % (ref.name, routine.name) |
303 | raise ForbiddenWriteError(message) | |
307 | raise ForbiddenWriteError(routine.line_number, message) | |
304 | 308 | self.current_routine = None |
305 | 309 | |
306 | 310 | def analyze_block(self, block, context): |
333 | 337 | if TableType.is_a_table_type(src.ref.type, TYPE_BYTE) and dest.type == TYPE_BYTE: |
334 | 338 | pass |
335 | 339 | else: |
336 | raise TypeMismatchError('%s and %s in %s' % | |
340 | raise TypeMismatchError(instr.line_number, '%s and %s in %s' % | |
337 | 341 | (src.ref.name, dest.name, self.current_routine.name) |
338 | 342 | ) |
339 | 343 | context.assert_meaningful(src, src.index) |
343 | 347 | if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: |
344 | 348 | pass |
345 | 349 | else: |
346 | raise TypeMismatchError((src, dest)) | |
350 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
347 | 351 | context.assert_meaningful(src.ref, REG_Y) |
348 | 352 | elif src.type != dest.type: |
349 | raise TypeMismatchError('%s and %s in %s' % | |
353 | raise TypeMismatchError(instr.line_number, '%s and %s in %s' % | |
350 | 354 | (src.name, dest.name, self.current_routine.name) |
351 | 355 | ) |
352 | 356 | else: |
358 | 362 | if src.type == TYPE_BYTE and TableType.is_a_table_type(dest.ref.type, TYPE_BYTE): |
359 | 363 | pass |
360 | 364 | else: |
361 | raise TypeMismatchError((src, dest)) | |
365 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
362 | 366 | context.assert_meaningful(dest.index) |
363 | 367 | context.assert_in_range(dest.index, dest.ref) |
364 | 368 | context.set_written(dest.ref) |
367 | 371 | if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE: |
368 | 372 | pass |
369 | 373 | else: |
370 | raise TypeMismatchError((src, dest)) | |
374 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
371 | 375 | context.assert_meaningful(dest.ref, REG_Y) |
372 | 376 | context.set_written(dest.ref) |
373 | 377 | elif src.type != dest.type: |
374 | raise TypeMismatchError('%r and %r in %s' % | |
378 | raise TypeMismatchError(instr.line_number, '%r and %r in %s' % | |
375 | 379 | (src, dest, self.current_routine.name) |
376 | 380 | ) |
377 | 381 | else: |
442 | 446 | context.set_unmeaningful(ref) |
443 | 447 | elif opcode == 'copy': |
444 | 448 | if dest == REG_A: |
445 | raise ForbiddenWriteError("{} cannot be used as destination for copy".format(dest)) | |
449 | raise ForbiddenWriteError(instr.line_number, "{} cannot be used as destination for copy".format(dest)) | |
446 | 450 | |
447 | 451 | # 1. check that their types are compatible |
448 | 452 | |
450 | 454 | if isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType): |
451 | 455 | pass |
452 | 456 | else: |
453 | raise TypeMismatchError((src, dest)) | |
457 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
454 | 458 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef): |
455 | 459 | if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType): |
456 | 460 | pass |
457 | 461 | else: |
458 | raise TypeMismatchError((src, dest)) | |
462 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
459 | 463 | elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): |
460 | 464 | if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE: |
461 | 465 | pass |
462 | 466 | else: |
463 | raise TypeMismatchError((src, dest)) | |
467 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
464 | 468 | |
465 | 469 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef): |
466 | 470 | if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD): |
472 | 476 | RoutineType.executable_types_compatible(src.type, dest.ref.type.of_type)): |
473 | 477 | pass |
474 | 478 | else: |
475 | raise TypeMismatchError((src, dest)) | |
479 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
476 | 480 | context.assert_in_range(dest.index, dest.ref) |
477 | 481 | |
478 | 482 | elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): |
482 | 486 | RoutineType.executable_types_compatible(src.ref.type.of_type, dest.type.of_type)): |
483 | 487 | pass |
484 | 488 | else: |
485 | raise TypeMismatchError((src, dest)) | |
489 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
486 | 490 | context.assert_in_range(src.index, src.ref) |
487 | 491 | |
488 | 492 | elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef): |
493 | 497 | self.assert_affected_within('outputs', src.type, dest.type.of_type) |
494 | 498 | self.assert_affected_within('trashes', src.type, dest.type.of_type) |
495 | 499 | else: |
496 | raise TypeMismatchError((src, dest)) | |
500 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
497 | 501 | else: |
498 | raise TypeMismatchError((src, dest)) | |
502 | raise TypeMismatchError(instr.line_number, (src, dest)) | |
499 | 503 | |
500 | 504 | # 2. check that the context is meaningful |
501 | 505 | |
527 | 531 | type_ = location.type |
528 | 532 | |
529 | 533 | if not isinstance(type_, (RoutineType, VectorType)): |
530 | raise TypeMismatchError(location) | |
534 | raise TypeMismatchError(instr.line_number, location) | |
531 | 535 | |
532 | 536 | # assert that the dest routine's inputs are all initialized |
533 | 537 | if isinstance(type_, VectorType): |