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:

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)