docs: 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