docs: def _redundant_stack_variable_removal(self, function, data_graph):
          """
          If an argument passed from the stack (i.e. dword ptr [ebp+4h]) is saved to a local variable on the stack at the
          beginning of the function, and this local variable was never modified anywhere in this function, and no pointer
          of any stack variable is saved in any register, then we can replace all references to this local variable to
          that argument instead.
  
          :param function:
          :param networkx.MultiDiGraph data_graph:
          :return:
          """
  
          # check if there is any stack pointer being stored into any register other than esp
          # basically check all consumers of stack pointers
          stack_ptrs = [ ]
          sp_offset = self.project.arch.registers['esp'][0]
          bp_offset = self.project.arch.registers['ebp'][0]
          for n in data_graph.nodes():
              if isinstance(n.variable, SimRegisterVariable) and n.variable.reg in (sp_offset, bp_offset):
                  stack_ptrs.append(n)
  
          # for each stack pointer variable, make sure none of its consumers is a general purpose register
          for stack_ptr in stack_ptrs:
              out_edges = data_graph.out_edges(stack_ptr, data=True)
              for _, dst, data in out_edges:
                  if 'type' in data and data['type'] == 'kill':
                      # we don't care about killing edges
                      continue
                  if isinstance(dst.variable, SimRegisterVariable) and dst.variable.reg < 40 and \
                          dst.variable.reg not in (sp_offset, bp_offset):
                      # oops
                      l.debug('Function %s does not satisfy requirements of redundant stack variable removal.',
                              repr(function)
                              )
                      return
  
          argument_variables = [ ]
  
          for n in data_graph.nodes():
              if isinstance(n.variable, SimStackVariable) and n.variable.base == 'bp' and n.variable.offset >= 0:
                  argument_variables.append(n)
  
          if not argument_variables:
              return
  
          #print function
          #print argument_variables
  
          argument_to_local = { }
          argument_register_as_retval = set()
  
          # for each argument, find its correspondence on the local stack frame
  
          for argument_variable in argument_variables:
              # is it copied to the stack?
              successors0 = list(data_graph.successors(argument_variable))
  
              if not successors0:
                  continue
  
              if len(successors0) != 1:
                  continue
  
              if isinstance(successors0[0].variable, SimRegisterVariable):
                  # argument -> register -> stack
                  out_edges = data_graph.out_edges(successors0[0], data=True)
                  successors1 = [ s for _, s, data in out_edges if 'type' not in data or data['type'] != 'kill' ]
                  if len(successors1) == 1:
                      successor1 = successors1[0]
                      if isinstance(successor1.variable, SimStackVariable):
                          if (successor1.variable.base == 'sp' and successor1.variable.offset > 0) or \
                                  (successor1.variable.base == 'bp' and successor1.variable.offset < 0):
                              # yes it's copied onto the stack!
                              argument_to_local[argument_variable] = successor1
  
                  # if the register is eax, and it's not killed later, it might be the return value of this function
                  # in that case, we cannot eliminate the instruction that moves stack argument to that register
                  if successors0[0].variable.reg == self.project.arch.registers['eax'][0]:
                      killers = [ s for _, s, data in out_edges if 'type' in data and data['type'] == 'kill']
                      if not killers:
                          # it might be the return value
                          argument_register_as_retval.add(argument_variable)
  
              else:
                  # TODO:
                  import ipdb; ipdb.set_trace()
  
          #import pprint
          #pprint.pprint(argument_to_local, width=160)
  
          # find local correspondence that are not modified throughout this function
          redundant_stack_variables = [ ]
  
          for argument, local_var in argument_to_local.items():
              # local_var cannot be killed anywhere
              out_edges = data_graph.out_edges(local_var, data=True)
  
              consuming_locs = [ ]
  
              for _, consumer, data in out_edges:
                  consuming_locs.append(consumer.location)
                  if 'type' in data and data['type'] == 'kill':
                      break
              else:
                  # no killing edges. the value is not changed!
                  rsv = RedundantStackVariable(argument, local_var, consuming_locs)
                  if argument in argument_register_as_retval:
                      rsv.argument_register_as_retval = True
                  redundant_stack_variables.append(rsv)
  
          self.redundant_stack_variables.extend(redundant_stack_variables)