def _track(self, state, live_defs, statements):
        """
        Given all live definitions prior to this program point, track the changes, and return a new list of live
        definitions. We scan through the action list of the new state to track the changes.

        :param state:           The input state at that program point.
        :param live_defs:       All live definitions prior to reaching this program point.
        :param list statements: A list of VEX statements.
        :returns:               A list of new live definitions.
        :rtype:                 angr.analyses.ddg.LiveDefinitions
        """

        # Make a copy of live_defs
        self._live_defs = live_defs.copy()

        action_list = list(state.history.recent_actions)

        # Since all temporary variables are local, we simply track them in a dict
        self._temp_variables = { }
        self._temp_register_symbols = { }

        # All dependence edges are added to the graph either at the end of this method, or when they are going to be
        # overwritten by a new edge. This is because we sometimes have to modify a previous edge (e.g. add new labels
        # to the edge)
        self._temp_edges = defaultdict(list)
        self._register_edges = defaultdict(list)

        last_statement_id = None
        self._variables_per_statement = None  # program variables read out in the same statement. we keep a copy of those variables here so
                                              # we can link it to the tmp_write action right afterwards
        self._custom_data_per_statement = None

        for a in action_list:
            if last_statement_id is None or last_statement_id != a.stmt_idx:
                # update statement ID
                last_statement_id = a.stmt_idx
                statement = statements[last_statement_id] if statements and last_statement_id < len(statements) else None

                # initialize all per-statement data structures
                self._variables_per_statement = [ ]
                self._custom_data_per_statement = None

            if a.sim_procedure is None:
                current_code_location = CodeLocation(a.bbl_addr, a.stmt_idx, ins_addr=a.ins_addr)
            else:
                current_code_location = CodeLocation(None, None, sim_procedure=a.sim_procedure)

            if a.type == 'exit':
                self._handle_exit(a, current_code_location, state, statement)
            elif a.type == 'operation':
                self._handle_operation(a, current_code_location, state, statement)
            elif a.type == 'constraint':
                pass
            else:
                handler_name = "_handle_%s_%s" % (a.type, a.action)
                if hasattr(self, handler_name):
                    getattr(self, handler_name)(a, current_code_location, state, statement)
                else:
                    l.debug("Skip an unsupported action %s.", a)

        return self._live_defs