11364750dSJames Henderson# DExTer : Debugging Experience Tester
21364750dSJames Henderson# ~~~~~~   ~         ~~         ~   ~~
31364750dSJames Henderson#
41364750dSJames Henderson# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
51364750dSJames Henderson# See https://llvm.org/LICENSE.txt for license information.
61364750dSJames Henderson# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
71364750dSJames Henderson"""Default class for controlling debuggers."""
81364750dSJames Henderson
91364750dSJames Hendersonfrom itertools import chain
101364750dSJames Hendersonimport os
111364750dSJames Hendersonimport time
121364750dSJames Henderson
131364750dSJames Hendersonfrom dex.debugger.DebuggerControllers.DebuggerControllerBase import DebuggerControllerBase
141364750dSJames Hendersonfrom dex.debugger.DebuggerControllers.ControllerHelpers import in_source_file, update_step_watches
151364750dSJames Hendersonfrom dex.utils.Exceptions import DebuggerException, LoadDebuggerException
161364750dSJames Henderson
176cf69179SStephen Tozerclass EarlyExitCondition(object):
186cf69179SStephen Tozer    def __init__(self, on_line, hit_count, expression, values):
196cf69179SStephen Tozer        self.on_line = on_line
206cf69179SStephen Tozer        self.hit_count = hit_count
216cf69179SStephen Tozer        self.expression = expression
226cf69179SStephen Tozer        self.values = values
236cf69179SStephen Tozer
241364750dSJames Hendersonclass DefaultController(DebuggerControllerBase):
251364750dSJames Henderson    def __init__(self, context, step_collection):
26*3a094d8bSJeremy Morse        self.source_files = context.options.source_files
271364750dSJames Henderson        self.watches = set()
281364750dSJames Henderson        self.step_index = 0
29*3a094d8bSJeremy Morse        super(DefaultController, self).__init__(context, step_collection)
301364750dSJames Henderson
311364750dSJames Henderson    def _break_point_all_lines(self):
321364750dSJames Henderson        for s in self.context.options.source_files:
331364750dSJames Henderson            with open(s, 'r') as fp:
341364750dSJames Henderson                num_lines = len(fp.readlines())
351364750dSJames Henderson            for line in range(1, num_lines + 1):
361364750dSJames Henderson                try:
371364750dSJames Henderson                   self.debugger.add_breakpoint(s, line)
381364750dSJames Henderson                except DebuggerException:
391364750dSJames Henderson                   raise LoadDebuggerException(DebuggerException.msg)
401364750dSJames Henderson
416cf69179SStephen Tozer    def _get_early_exit_conditions(self):
426cf69179SStephen Tozer        commands = self.step_collection.commands
436cf69179SStephen Tozer        early_exit_conditions = []
446cf69179SStephen Tozer        if 'DexFinishTest' in commands:
456cf69179SStephen Tozer            finish_commands = commands['DexFinishTest']
466cf69179SStephen Tozer            for fc in finish_commands:
476cf69179SStephen Tozer                condition = EarlyExitCondition(on_line=fc.on_line,
486cf69179SStephen Tozer                                               hit_count=fc.hit_count,
496cf69179SStephen Tozer                                               expression=fc.expression,
506cf69179SStephen Tozer                                               values=fc.values)
516cf69179SStephen Tozer                early_exit_conditions.append(condition)
526cf69179SStephen Tozer        return early_exit_conditions
536cf69179SStephen Tozer
546cf69179SStephen Tozer    def _should_exit(self, early_exit_conditions, line_no):
556cf69179SStephen Tozer        for condition in early_exit_conditions:
566cf69179SStephen Tozer            if condition.on_line == line_no:
576cf69179SStephen Tozer                exit_condition_hit = condition.expression is None
586cf69179SStephen Tozer                if condition.expression is not None:
596cf69179SStephen Tozer                    # For the purposes of consistent behaviour with the
606cf69179SStephen Tozer                    # Conditional Controller, check equality in the debugger
616cf69179SStephen Tozer                    # rather than in python (as the two can differ).
626cf69179SStephen Tozer                    for value in condition.values:
636cf69179SStephen Tozer                        expr_val = self.debugger.evaluate_expression(f'({condition.expression}) == ({value})')
646cf69179SStephen Tozer                        if expr_val.value == 'true':
656cf69179SStephen Tozer                            exit_condition_hit = True
666cf69179SStephen Tozer                            break
676cf69179SStephen Tozer                if exit_condition_hit:
686cf69179SStephen Tozer                    if condition.hit_count <= 0:
696cf69179SStephen Tozer                        return True
706cf69179SStephen Tozer                    else:
716cf69179SStephen Tozer                        condition.hit_count -= 1
726cf69179SStephen Tozer        return False
736cf69179SStephen Tozer
746cf69179SStephen Tozer
75*3a094d8bSJeremy Morse    def _run_debugger_custom(self, cmdline):
761364750dSJames Henderson        self.step_collection.debugger = self.debugger.debugger_info
771364750dSJames Henderson        self._break_point_all_lines()
78*3a094d8bSJeremy Morse        self.debugger.launch(cmdline)
791364750dSJames Henderson
801364750dSJames Henderson        for command_obj in chain.from_iterable(self.step_collection.commands.values()):
811364750dSJames Henderson            self.watches.update(command_obj.get_watches())
826cf69179SStephen Tozer        early_exit_conditions = self._get_early_exit_conditions()
831364750dSJames Henderson
841364750dSJames Henderson        max_steps = self.context.options.max_steps
851364750dSJames Henderson        for _ in range(max_steps):
861364750dSJames Henderson            while self.debugger.is_running:
871364750dSJames Henderson                pass
881364750dSJames Henderson
891364750dSJames Henderson            if self.debugger.is_finished:
901364750dSJames Henderson                break
911364750dSJames Henderson
921364750dSJames Henderson            self.step_index += 1
931364750dSJames Henderson            step_info = self.debugger.get_step_info(self.watches, self.step_index)
941364750dSJames Henderson
951364750dSJames Henderson            if step_info.current_frame:
961364750dSJames Henderson                update_step_watches(step_info, self.watches, self.step_collection.commands)
971364750dSJames Henderson                self.step_collection.new_step(self.context, step_info)
986cf69179SStephen Tozer                if self._should_exit(early_exit_conditions, step_info.current_frame.loc.lineno):
996cf69179SStephen Tozer                    break
1001364750dSJames Henderson
1011364750dSJames Henderson            if in_source_file(self.source_files, step_info):
1021364750dSJames Henderson                self.debugger.step()
1031364750dSJames Henderson            else:
1041364750dSJames Henderson                self.debugger.go()
1051364750dSJames Henderson
1061364750dSJames Henderson            time.sleep(self.context.options.pause_between_steps)
1071364750dSJames Henderson        else:
1081364750dSJames Henderson            raise DebuggerException(
1091364750dSJames Henderson                'maximum number of steps reached ({})'.format(max_steps))
110