Instruction:
How would you explain what the following Python 3 function does
def _construct(self):

        worklist = []
        worklist_set = set()

        # Initialize the worklist
        if self._start is None:
            # initial nodes are those nodes in CFG that has no in-degrees
            for n in self._cfg.graph.nodes():
                if self._cfg.graph.in_degree(n) == 0:
                    # Put it into the worklist
                    job = DDGJob(n, 0)
                    self._worklist_append(job, worklist, worklist_set)
        else:
            for n in self._cfg.get_all_nodes(self._start):
                job = DDGJob(n, 0)
                self._worklist_append(job, worklist, worklist_set)

        # A dict storing defs set
        # DDGJob -> LiveDefinition
        live_defs_per_node = {}

        while worklist:
            # Pop out a node
            ddg_job = worklist[0]
            l.debug("Processing %s.", ddg_job)
            node, call_depth = ddg_job.cfg_node, ddg_job.call_depth
            worklist = worklist[ 1 : ]
            worklist_set.remove(node)

            # Grab all final states. There are usually more than one (one state for each successor), and we gotta
            # process all of them
            final_states = node.final_states

            if node in live_defs_per_node:
                live_defs = live_defs_per_node[node]
            else:
                live_defs = LiveDefinitions()
                live_defs_per_node[node] = live_defs

            successing_nodes = list(self._cfg.graph.successors(node))

            # try to assign every final state to a successor and vice versa
            match_suc = defaultdict(bool)
            match_state = defaultdict(set)

            for suc in successing_nodes:
                matched = False
                for state in final_states:
                    try:
                        if state.solver.eval(state.ip) == suc.addr:
                            match_suc[suc.addr] = True
                            match_state[state].add(suc)
                            matched = True
                    except (SimUnsatError, SimSolverModeError, ZeroDivisionError):
                        # ignore
                        matched = matched
                if not matched:
                    break

            # whether all final states could be matched to a successor and vice versa
            matches = len(match_suc) == len(successing_nodes) and len(match_state) == len(final_states)

            for state in final_states:
                if not matches and state.history.jumpkind == 'Ijk_FakeRet' and len(final_states) > 1:
                    # Skip fakerets if there are other control flow transitions available
                    continue

                new_call_depth = call_depth
                if state.history.jumpkind == 'Ijk_Call':
                    new_call_depth += 1
                elif state.history.jumpkind == 'Ijk_Ret':
                    new_call_depth -= 1

                if self._call_depth is not None and call_depth > self._call_depth:
                    l.debug('Do not trace into %s due to the call depth limit', state.ip)
                    continue

                new_defs = self._track(state, live_defs, node.irsb.statements if node.irsb is not None else None)

                #corresponding_successors = [n for n in successing_nodes if
                #                            not state.ip.symbolic and n.addr == state.solver.eval(state.ip)]
                #if not corresponding_successors:
                #    continue

                changed = False

                # if every successor can be matched with one or more final states (by IP address),
                # only take over the LiveDefinition of matching states
                if matches:
                    add_state_to_sucs = match_state[state]
                else:
                    add_state_to_sucs = successing_nodes

                for successing_node in add_state_to_sucs:

                    if (state.history.jumpkind == 'Ijk_Call' or state.history.jumpkind.startswith('Ijk_Sys')) and \
                            (state.ip.symbolic or successing_node.addr != state.solver.eval(state.ip)):
                        suc_new_defs = self._filter_defs_at_call_sites(new_defs)
                    else:
                        suc_new_defs = new_defs

                    if successing_node in live_defs_per_node:
                        defs_for_next_node = live_defs_per_node[successing_node]
                    else:
                        defs_for_next_node = LiveDefinitions()
                        live_defs_per_node[successing_node] = defs_for_next_node

                    for var, code_loc_set in suc_new_defs.items():
                        # l.debug("Adding %d new definitions for variable %s.", len(code_loc_set), var)
                        changed |= defs_for_next_node.add_defs(var, code_loc_set)

                if changed:
                    if (self._call_depth is None) or \
                            (self._call_depth is not None and 0 <= new_call_depth <= self._call_depth):
                        # Put all reachable successors back to our work-list again
                        for successor in self._cfg.get_all_successors(node):
                            nw = DDGJob(successor, new_call_depth)
                            self._worklist_append(nw, worklist, worklist_set)

Response: Constructs the data dependence graph.


Instruction:
Write a Python 3 function that can
give 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

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


Instruction:
Explain what the following Python 3 code does
def _def_lookup(self, variable):  # pylint:disable=no-self-use

        prevdefs = {}

        for code_loc in self._live_defs.lookup_defs(variable):
            # Label edges with cardinality or actual sets of addresses
            if isinstance(variable, SimMemoryVariable):
                type_ = 'mem'
            elif isinstance(variable, SimRegisterVariable):
                type_ = 'reg'
            else:
                raise AngrDDGError('Unknown variable type %s' % type(variable))

            prevdefs[code_loc] = {
                'type': type_,
                'data': variable
            }

        return prevdefs

Response: This method is a backward lookup in the previous defs.


Instruction:
Write a Python 3 script for
killing previous defs. addr_list is a list of normalized addresses.

Response:
def _kill(self, variable, code_loc):  # pylint:disable=no-self-use
        """
        Kill previous defs. addr_list is a list of normalized addresses.
        """

        # Case 1: address perfectly match, we kill
        # Case 2: a is a subset of the original address
        # Case 3: a is a superset of the original address

        # the previous definition is killed. mark it in data graph.

        if variable in self._live_defs:
            for loc in self._live_defs.lookup_defs(variable):
                pv = ProgramVariable(variable, loc, arch=self.project.arch)
                self._data_graph_add_edge(pv, ProgramVariable(variable, code_loc, arch=self.project.arch), type='kill')

        self._live_defs.kill_def(variable, code_loc)


Instruction:
Given the following Python 3 function, write the documentation
def _get_register_size(self, reg_offset):

        # TODO: support registers that are not aligned
        if reg_offset in self.project.arch.register_names:
            reg_name = self.project.arch.register_names[reg_offset]
            reg_size = self.project.arch.registers[reg_name][1]
            return reg_size

        l.warning("_get_register_size(): unsupported register offset %d. Assum size 1. "
                  "More register name mappings should be implemented in archinfo.", reg_offset)
        return 1

Response: Get the size of a register.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _get_actual_addrs(action, state):

        if action.actual_addrs is None:
            # For now, mem reads don't necessarily have actual_addrs set properly
            try:
                addr_list = {state.solver.eval(action.addr.ast)}
            except (SimSolverModeError, SimUnsatError, ZeroDivisionError):
                # FIXME: ZeroDivisionError should have been caught by claripy and simuvex.
                # FIXME: see claripy issue #75. this is just a temporary workaround.
                # it's symbolic... just continue
                addr_list = {0x60000000}  # TODO: this is a random address that I pick. Fix it.
        else:
            addr_list = set(action.actual_addrs)

        return addr_list

Response: Get a list of addresses that are accessed with that action.


Instruction:
Given the following Python 3 function, write the documentation
def _create_memory_variable(self, action, addr, addrs):

        variable = None
        if len(addrs) == 1 and len(action.addr.tmp_deps) == 1:
            addr_tmp = list(action.addr.tmp_deps)[0]
            if addr_tmp in self._temp_register_symbols:
                # it must be a stack variable
                sort, offset = self._temp_register_symbols[addr_tmp]
                variable = SimStackVariable(offset, action.size.ast // 8, base=sort, base_addr=addr - offset)

        if variable is None:
            variable = SimMemoryVariable(addr, action.size.ast // 8)

        return variable

Response: Create a SimStackVariable or SimMemoryVariable based on action objects and address.


Instruction:
How would you code a function in Python 3 to
add an edge to the data dependence graph.

Response:
def _data_graph_add_edge(self, src, dst, **edge_labels):
        """
        Add an edge in the data dependence graph.

        :param ProgramVariable src: Source node.
        :param ProgramVariable dst: Destination node.
        :param edge_labels: All labels associated with the edge.
        :return: None
        """

        if src in self._data_graph and dst in self._data_graph[src]:
            return

        self._data_graph.add_edge(src, dst, **edge_labels)

        self._simplified_data_graph = None


Instruction:
Write a Python 3 function that can
add an edge from a program location src to another program location dst.

Response:
def _stmt_graph_add_edge(self, src, dst, **edge_labels):
        """
        Add an edge in the statement dependence graph from a program location `src` to another program location `dst`.

        :param CodeLocation src: Source node.
        :param CodeLocation dst: Destination node.
        :param edge_labels: All labels associated with the edge.
        :returns: None
        """

        # Is that edge already in the graph ?
        # If at least one is new, then we are not redoing the same path again
        if src in self._stmt_graph and dst in self._stmt_graph[src]:
            return

        self._stmt_graph.add_edge(src, dst, **edge_labels)


Instruction:
Can you generate the documentation for the following Python 3 function
def _stmt_graph_annotate_edges(self, edges_to_annotate, **new_labels):

        graph = self.graph

        for src, dst in edges_to_annotate:

            if src not in graph:
                continue
            if dst not in graph[src]:
                continue

            data = graph[src][dst]

            for k, v in new_labels.items():
                if k in data:
                    if v not in data[k]:
                        data[k] = data[k] + (v,)
                else:
                    # Construct a tuple
                    data[k] = (v,)

Response: Add new annotations to edges in the statement dependence graph.


Instruction:
Here you have a function in Python 3, explain what it does
def _simplify_data_graph(self, data_graph):  # pylint:disable=no-self-use

        graph = networkx.MultiDiGraph(data_graph)

        all_nodes = [ n for n in graph.nodes() if isinstance(n.variable, SimTemporaryVariable) ]

        for tmp_node in all_nodes:
            # remove each tmp node by linking their successors and predecessors directly
            in_edges = graph.in_edges(tmp_node, data=True)
            out_edges = graph.out_edges(tmp_node, data=True)

            for pred, _, _ in in_edges:
                graph.remove_edge(pred, tmp_node)
            for _, suc, _ in out_edges:
                graph.remove_edge(tmp_node, suc)

            for pred, _, data_in in in_edges:
                for _, suc, data_out in out_edges:
                    if pred is not tmp_node and suc is not tmp_node:
                        if suc not in graph[pred]:
                            data = data_in.copy()
                            data.update(data_out)
                            graph.add_edge(pred, suc, **data)

            graph.remove_node(tmp_node)

        return graph

Response: Simplify a data dependence graph by removing all temp variable nodes and linking them directly to the data nodes.


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _worklist_append(self, node_wrapper, worklist, worklist_set):

        if node_wrapper.cfg_node in worklist_set:
            # It's already in the work-list
            return

        worklist.append(node_wrapper)
        worklist_set.add(node_wrapper.cfg_node)

        stack = [ node_wrapper ]
        traversed_nodes = { node_wrapper.cfg_node }
        inserted = { node_wrapper.cfg_node }

        while stack:
            nw = stack.pop()
            n, call_depth = nw.cfg_node, nw.call_depth

            # Get successors
            edges = self._cfg.graph.out_edges(n, data=True)

            for _, dst, data in edges:
                if (dst not in traversed_nodes # which means we haven't touch this node in this appending procedure
                        and dst not in worklist_set): # which means this node is not in the work-list
                    # We see a new node!
                    traversed_nodes.add(dst)

                    if data['jumpkind'] == 'Ijk_Call':
                        if self._call_depth is None or call_depth < self._call_depth:
                            inserted.add(dst)
                            new_nw = DDGJob(dst, call_depth + 1)
                            worklist.append(new_nw)
                            worklist_set.add(dst)
                            stack.append(new_nw)
                    elif data['jumpkind'] == 'Ijk_Ret':
                        if call_depth > 0:
                            inserted.add(dst)
                            new_nw = DDGJob(dst, call_depth - 1)
                            worklist.append(new_nw)
                            worklist_set.add(dst)
                            stack.append(new_nw)
                    else:
                        new_nw = DDGJob(dst, call_depth)
                        inserted.add(dst)
                        worklist_set.add(dst)
                        worklist.append(new_nw)
                        stack.append(new_nw)

        return inserted

Response: Append a CFGNode and its successors into the work - list and respect the call - depth limit.


Instruction:
Explain what the following Python 3 code does
def _build_function_dependency_graphs(self):

        # This is a map between functions and its corresponding dependencies
        self._function_data_dependencies = defaultdict(networkx.DiGraph)

        # Group all dependencies first

        block_addr_to_func = { }
        for _, func in self.kb.functions.items():
            for block in func.blocks:
                block_addr_to_func[block.addr] = func

        for src, dst, data in self.graph.edges(data=True):
            src_target_func = None
            if src.block_addr in block_addr_to_func:
                src_target_func = block_addr_to_func[src.block_addr]
                self._function_data_dependencies[src_target_func].add_edge(src, dst, **data)

            if dst.block_addr in block_addr_to_func:
                dst_target_func = block_addr_to_func[dst.block_addr]
                if not dst_target_func is src_target_func:
                    self._function_data_dependencies[dst_target_func].add_edge(src, dst, **data)

Response: Builds the function dependency graphs for each function and saves them in self. _function_data_dependencies.


Instruction:
Given the following Python 3 function, write the documentation
def _filter_defs_at_call_sites(self, defs):

        # TODO: make definition killing architecture independent and calling convention independent
        # TODO: use information from a calling convention analysis
        filtered_defs = LiveDefinitions()
        for variable, locs in defs.items():
            if isinstance(variable, SimRegisterVariable):
                if self.project.arch.name == 'X86':
                    if variable.reg in (self.project.arch.registers['eax'][0],
                                        self.project.arch.registers['ecx'][0],
                                        self.project.arch.registers['edx'][0]):
                        continue

            filtered_defs.add_defs(variable, locs)

        return filtered_defs

Response: Filter out the defs that are not called at real execution.


Instruction:
How would you code a function in Python 3 to
find all definitions of the given variable.

Response:
def find_definitions(self, variable, location=None, simplified_graph=True):
        """
        Find all definitions of the given variable.

        :param SimVariable variable:
        :param bool simplified_graph: True if you just want to search in the simplified graph instead of the normal
                                      graph. Usually the simplified graph suffices for finding definitions of register
                                      or memory variables.
        :return: A collection of all variable definitions to the specific variable.
        :rtype: list
        """

        if simplified_graph:
            graph = self.simplified_data_graph
        else:
            graph = self.data_graph

        defs = []

        for n in graph.nodes():  # type: ProgramVariable
            if n.variable == variable:
                if location is None:
                    defs.append(n)
                else:
                    # TODO: finish this part
                    if n.location.block_addr == location.block_addr:
                        defs.append(n)

        return defs


Instruction:
Create a Python 3 function for
finding all consumers to the specified variable definition.

Response:
def find_consumers(self, var_def, simplified_graph=True):
        """
        Find all consumers to the specified variable definition.

        :param ProgramVariable var_def: The variable definition.
        :param bool simplified_graph: True if we want to search in the simplified graph, False otherwise.
        :return: A collection of all consumers to the specified variable definition.
        :rtype: list
        """

        if simplified_graph:
            graph = self.simplified_data_graph
        else:
            graph = self.data_graph

        if var_def not in graph:
            return []

        consumers = []
        srcs = [var_def]
        traversed = set()

        while srcs:
            src = srcs.pop()
            out_edges = graph.out_edges(src, data=True)
            for _, dst, data in out_edges:
                if 'type' in data and data['type'] == 'kill':
                    # skip killing edges
                    continue
                if isinstance(dst.variable, SimTemporaryVariable):
                    if dst not in traversed:
                        srcs.append(dst)
                        traversed.add(dst)
                else:
                    if dst not in consumers:
                        consumers.append(dst)

        return consumers


Instruction:
Given the following Python 3 function, write the documentation
def find_killers(self, var_def, simplified_graph=True):

        if simplified_graph:
            graph = self.simplified_data_graph
        else:
            graph = self.data_graph

        if var_def not in graph:
            return []

        killers = []
        out_edges = graph.out_edges(var_def, data=True)
        for _, dst, data in out_edges:
            if 'type' in data and data['type'] == 'kill':
                killers.append(dst)

        return killers

Response: Find all killers to the specified variable definition.


Instruction:
Here you have a function in Python 3, explain what it does
def find_sources(self, var_def, simplified_graph=True):

        if simplified_graph:
            graph = self.simplified_data_graph
        else:
            graph = self.data_graph

        if var_def not in graph:
            return []

        sources = []
        defs = [ var_def ]
        traversed = set()

        while defs:
            definition = defs.pop()
            in_edges = graph.in_edges(definition, data=True)
            for src, _, data in in_edges:
                if 'type' in data and data['type'] == 'kill':
                    continue
                if isinstance(src.variable, SimTemporaryVariable):
                    if src not in traversed:
                        defs.append(src)
                        traversed.add(src)
                else:
                    if src not in sources:
                        sources.append(src)

        return sources

Response: Find all sources to the specified variable definition.


Instruction:
Given the following Python 3 function, write the documentation
def new_array(state, element_type, size, default_value_generator=None):
        size_bounded = SimSootExpr_NewMultiArray._bound_multi_array_size(state, size)
        # return the reference of the array base
        # => elements getting lazy initialized in the javavm memory
        return SimSootValue_ArrayBaseRef(heap_alloc_id=state.javavm_memory.get_new_uuid(),
                                         element_type=element_type,
                                         size=size_bounded,
                                         default_value_generator=default_value_generator)

Response: Allocates a new multi array in memory and returns the reference to the base.


Instruction:
Implement a Python 3 function for
getting the minimum solution of an address.

Response:
def _min(self, memory, addr, **kwargs):
        """
        Gets the minimum solution of an address.
        """
        return memory.state.solver.min(addr, exact=kwargs.pop('exact', self._exact), **kwargs)


Instruction:
Write a Python 3 function that can
get the maximum solution of an address.

Response:
def _max(self, memory, addr, **kwargs):
        """
        Gets the maximum solution of an address.
        """
        return memory.state.solver.max(addr, exact=kwargs.pop('exact', self._exact), **kwargs)


Instruction:
Can you generate a brief explanation for the following Python 3 code
def _any(self, memory, addr, **kwargs):
        return memory.state.solver.eval(addr, exact=kwargs.pop('exact', self._exact), **kwargs)

Response: Gets any solution of an address.


Instruction:
Write a Python 3 script for
evaluating the current state of the current solver.

Response:
def _eval(self, memory, addr, n, **kwargs):
        """
        Gets n solutions for an address.
        """
        return memory.state.solver.eval_upto(addr, n, exact=kwargs.pop('exact', self._exact), **kwargs)


Instruction:
Write a Python 3 script to
get the min max range of solutions for an address.

Response:
def _range(self, memory, addr, **kwargs):
        """
        Gets the (min, max) range of solutions for an address.
        """
        return (self._min(memory, addr, **kwargs), self._max(memory, addr, **kwargs))


Instruction:
Given the following Python 3 function, write the documentation
def concretize(self, memory, addr):
        if self._filter is None or self._filter(memory, addr):
            return self._concretize(memory, addr)

Response: Concretizes the address into a list of values.


Instruction:
Write a Python 3 function that can
give a local transition graph of a function find all merge points inside and then perform quasi - topological sort of those merge points.

Response:
def find_merge_points(function_addr, function_endpoints, graph):  # pylint:disable=unused-argument
        """
        Given a local transition graph of a function, find all merge points inside, and then perform a
        quasi-topological sort of those merge points.

        A merge point might be one of the following cases:
        - two or more paths come together, and ends at the same address.
        - end of the current function

        :param int function_addr: Address of the function.
        :param list function_endpoints: Endpoints of the function. They typically come from Function.endpoints.
        :param networkx.DiGraph graph: A local transition graph of a function. Normally it comes from Function.graph.
        :return: A list of ordered addresses of merge points.
        :rtype: list
        """

        merge_points = set()

        for node in graph.nodes():
            if graph.in_degree(node) > 1:
                merge_points.add(node)

        ordered_merge_points = CFGUtils.quasi_topological_sort_nodes(graph, merge_points)

        addrs = [n.addr for n in ordered_merge_points]
        return addrs


Instruction:
Write a Python 3 function that can
give a local transition graph of a function find all widening points inside.

Response:
def find_widening_points(function_addr, function_endpoints, graph):  # pylint: disable=unused-argument
        """
        Given a local transition graph of a function, find all widening points inside.

        Correctly choosing widening points is very important in order to not lose too much information during static
        analysis. We mainly consider merge points that has at least one loop back edges coming in as widening points.

        :param int function_addr: Address of the function.
        :param list function_endpoints: Endpoints of the function, typically coming from Function.endpoints.
        :param networkx.DiGraph graph: A local transition graph of a function, normally Function.graph.
        :return: A list of addresses of widening points.
        :rtype: list
        """

        sccs = networkx.strongly_connected_components(graph)

        widening_addrs = set()

        for scc in sccs:
            if len(scc) == 1:
                node = next(iter(scc))
                if graph.has_edge(node, node):
                    # self loop
                    widening_addrs.add(node.addr)
            else:
                for n in scc:
                    predecessors = graph.predecessors(n)
                    if any([ p not in scc for p in predecessors]):
                        widening_addrs.add(n.addr)
                        break

        return list(widening_addrs)


Instruction:
Given the following Python 3 function, write the documentation
def reverse_post_order_sort_nodes(graph, nodes=None):

        post_order = networkx.dfs_postorder_nodes(graph)

        if nodes is None:
            return reversed(list(post_order))

        addrs_to_index = {}
        for i, n in enumerate(post_order):
            addrs_to_index[n.addr] = i
        return sorted(nodes, key=lambda n: addrs_to_index[n.addr], reverse=True)

Response: Sort a given set of nodes in reverse post ordering.


Instruction:
How would you explain what the following Python 3 function does
def quasi_topological_sort_nodes(graph, nodes=None):

        # fast path for single node graphs
        if graph.number_of_nodes() == 1:
            return graph.nodes()

        # make a copy to the graph since we are gonna modify it
        graph_copy = networkx.DiGraph()

        # find all strongly connected components in the graph
        sccs = [ scc for scc in networkx.strongly_connected_components(graph) if len(scc) > 1 ]

        # collapse all strongly connected components
        for src, dst in graph.edges():
            scc_index = CFGUtils._components_index_node(sccs, src)
            if scc_index is not None:
                src = SCCPlaceholder(scc_index)
            scc_index = CFGUtils._components_index_node(sccs, dst)
            if scc_index is not None:
                dst = SCCPlaceholder(scc_index)

            if isinstance(src, SCCPlaceholder) and isinstance(dst, SCCPlaceholder) and src == dst:
                continue
            if src == dst:
                continue

            graph_copy.add_edge(src, dst)

        # add loners
        out_degree_zero_nodes = [node for (node, degree) in graph.out_degree() if degree == 0]
        for node in out_degree_zero_nodes:
            if graph.in_degree(node) == 0:
                graph_copy.add_node(node)

        # topological sort on acyclic graph `graph_copy`
        tmp_nodes = networkx.topological_sort(graph_copy)

        ordered_nodes = [ ]
        for n in tmp_nodes:
            if isinstance(n, SCCPlaceholder):
                CFGUtils._append_scc(graph, ordered_nodes, sccs[n.scc_id])
            else:
                ordered_nodes.append(n)

        if nodes is None:
            return ordered_nodes

        nodes = set(nodes)
        ordered_nodes = [ n for n in ordered_nodes if n in nodes ]
        return ordered_nodes

Response: This function sorts a given set of nodes in a given graph.


Instruction:
How would you implement a function in Python 3 that
appends all nodes from a strongly connected component to a list of ordered nodes and ensure the topological sort order.

Response:
def _append_scc(graph, ordered_nodes, scc):
        """
        Append all nodes from a strongly connected component to a list of ordered nodes and ensure the topological
        order.

        :param networkx.DiGraph graph: The graph where all nodes belong to.
        :param list ordered_nodes:     Ordered nodes.
        :param iterable scc:           A set of nodes that forms a strongly connected component in the graph.
        :return:                       None
        """

        # find the first node in the strongly connected component that is the successor to any node in ordered_nodes
        loop_head = None
        for parent_node in reversed(ordered_nodes):
            for n in scc:
                if n in graph[parent_node]:
                    loop_head = n
                    break

            if loop_head is not None:
                break

        if loop_head is None:
            # randomly pick one
            loop_head = next(iter(scc))

        subgraph = graph.subgraph(scc).copy()  # type: networkx.DiGraph
        for src, _ in list(subgraph.in_edges(loop_head)):
            subgraph.remove_edge(src, loop_head)

        ordered_nodes.extend(CFGUtils.quasi_topological_sort_nodes(subgraph))


Instruction:
Write a Python 3 script to
make a copy of this simulation manager.

Response:
def copy(self, deep=False): # pylint: disable=arguments-differ
        """
        Make a copy of this simulation manager. Pass ``deep=True`` to copy all the states in it as well.
        """
        simgr = SimulationManager(self._project,
                                  stashes=self._copy_stashes(deep=deep),
                                  hierarchy=self._hierarchy,
                                  resilience=self._resilience,
                                  auto_drop=self._auto_drop,
                                  completion_mode=self.completion_mode,
                                  errored=self._errored)
        return simgr


Instruction:
Can you create a Python 3 function that
uses an exploration technique with this SimulationManager.

Response:
def use_technique(self, tech):
        """
        Use an exploration technique with this SimulationManager.

        Techniques can be found in :mod:`angr.exploration_techniques`.

        :param tech:    An ExplorationTechnique object that contains code to modify
                        this SimulationManager's behavior.
        :type tech:     ExplorationTechnique
        :return:        The technique that was added, for convenience
        """
        if not isinstance(tech, ExplorationTechnique):
            raise SimulationManagerError

        # XXX: as promised
        tech.project = self._project
        tech.setup(self)

        HookSet.install_hooks(self, **tech._get_hooks())
        self._techniques.append(tech)
        return tech


Instruction:
How would you explain what the following Python 3 function does
def remove_technique(self, tech):
        if not isinstance(tech, ExplorationTechnique):
            raise SimulationManagerError

        def _is_overriden(name):
            return getattr(tech, name).__code__ is not getattr(ExplorationTechnique, name).__code__

        overriden = filter(_is_overriden, ('step', 'filter', 'selector', 'step_state', 'successors'))
        hooks = {name: getattr(tech, name) for name in overriden}
        HookSet.remove_hooks(self, **hooks)

        self._techniques.remove(tech)
        return tech

Response: Removes an exploration technique from a list of active techniques.


Instruction:
Given the following Python 3 function, write the documentation
def explore(self, stash='active', n=None, find=None, avoid=None, find_stash='found', avoid_stash='avoid', cfg=None,
                num_find=1, **kwargs):
        """
        Tick stash "stash" forward (up to "n" times or until "num_find" states are found), looking for condition "find",
        avoiding condition "avoid". Stores found states into "find_stash' and avoided states into "avoid_stash".

        The "find" and "avoid" parameters may be any of:

        - An address to find
        - A set or list of addresses to find
        - A function that takes a state and returns whether or not it matches.

        If an angr CFG is passed in as the "cfg" parameter and "find" is either a number or a list or a set, then
        any states which cannot possibly reach a success state without going through a failure state will be
        preemptively avoided.
        """
        num_find += len(self._stashes[find_stash]) if find_stash in self._stashes else 0
        tech = self.use_technique(Explorer(find, avoid, find_stash, avoid_stash, cfg, num_find))

        try:
            self.run(stash=stash, n=n, **kwargs)
        finally:
            self.remove_technique(tech)

        return self

Response: Explore the current state.


Instruction:
Given the following Python 3 function, write the documentation
def run(self, stash='active', n=None, until=None, **kwargs):
        for _ in (itertools.count() if n is None else range(0, n)):
            if not self.complete() and self._stashes[stash]:
                self.step(stash=stash, **kwargs)
                if not (until and until(self)):
                    continue
            break
        return self

Response: Runs the simulation manager until the current state is reached.


Instruction:
Can you generate the documentation for the following Python 3 function
def complete(self):
        if not self._techniques:
            return False
        if not any(tech._is_overriden('complete') for tech in self._techniques):
            return False
        return self.completion_mode(tech.complete(self) for tech in self._techniques if tech._is_overriden('complete'))

Response: Returns whether or not this manager has reached a completed state.


Instruction:
Write a Python 3 function for
stepping a stash of states forward and categorize the successors appropriately.

Response:
def step(self, stash='active', n=None, selector_func=None, step_func=None,
             successor_func=None, until=None, filter_func=None, **run_args):
        """
        Step a stash of states forward and categorize the successors appropriately.

        The parameters to this function allow you to control everything about the stepping and
        categorization process.

        :param stash:           The name of the stash to step (default: 'active')
        :param selector_func:   If provided, should be a function that takes a state and returns a
                                boolean. If True, the state will be stepped. Otherwise, it will be
                                kept as-is.
        :param step_func:       If provided, should be a function that takes a SimulationManager and
                                returns a SimulationManager. Will be called with the SimulationManager
                                at every step. Note that this function should not actually perform any
                                stepping - it is meant to be a maintenance function called after each step.
        :param successor_func:  If provided, should be a function that takes a state and return its successors.
                                Otherwise, project.factory.successors will be used.
        :param filter_func:     If provided, should be a function that takes a state and return the name
                                of the stash, to which the state should be moved.
        :param until:           (DEPRECATED) If provided, should be a function that takes a SimulationManager and
                                returns True or False. Stepping will terminate when it is True.
        :param n:               (DEPRECATED) The number of times to step (default: 1 if "until" is not provided)

        Additionally, you can pass in any of the following keyword args for project.factory.successors:

        :param jumpkind:        The jumpkind of the previous exit
        :param addr:            An address to execute at instead of the state's ip.
        :param stmt_whitelist:  A list of stmt indexes to which to confine execution.
        :param last_stmt:       A statement index at which to stop execution.
        :param thumb:           Whether the block should be lifted in ARM's THUMB mode.
        :param backup_state:    A state to read bytes from instead of using project memory.
        :param opt_level:       The VEX optimization level to use.
        :param insn_bytes:      A string of bytes to use for the block instead of the project.
        :param size:            The maximum size of the block, in bytes.
        :param num_inst:        The maximum number of instructions.
        :param traceflags:      traceflags to be passed to VEX. Default: 0

        :returns:           The simulation manager, for chaining.
        :rtype:             SimulationManager
        """
        l.info("Stepping %s of %s", stash, self)
        # 8<----------------- Compatibility layer -----------------
        if n is not None or until is not None:
            if once('simgr_step_n_until'):
                print("\x1b[31;1mDeprecation warning: the use of `n` and `until` arguments is deprecated. "
                      "Consider using simgr.run() with the same arguments if you want to specify "
                      "a number of steps or an additional condition on when to stop the execution.\x1b[0m")
            return self.run(stash, n, until, selector_func=selector_func, step_func=step_func,
                            successor_func=successor_func, filter_func=filter_func, **run_args)
        # ------------------ Compatibility layer ---------------->8
        bucket = defaultdict(list)

        for state in self._fetch_states(stash=stash):

            goto = self.filter(state, filter_func=filter_func)
            if isinstance(goto, tuple):
                goto, state = goto

            if goto not in (None, stash):
                bucket[goto].append(state)
                continue

            if not self.selector(state, selector_func=selector_func):
                bucket[stash].append(state)
                continue

            pre_errored = len(self._errored)
            successors = self.step_state(state, successor_func=successor_func, **run_args)

            # handle degenerate stepping cases here. desired behavior:
            # if a step produced only unsat states, always add them to the unsat stash since this usually indicates a bug
            # if a step produced sat states and save_unsat is False, drop the unsats
            # if a step produced no successors, period, add the original state to deadended

            # first check if anything happened besides unsat. that gates all this behavior
            if not any(v for k, v in successors.items() if k != 'unsat') and len(self._errored) == pre_errored:
                # then check if there were some unsats
                if successors.get('unsat', []):
                    # only unsats. current setup is acceptable.
                    pass
                else:
                    # no unsats. we've deadended.
                    bucket['deadended'].append(state)
                    continue
            else:
                # there were sat states. it's okay to drop the unsat ones if the user said so.
                if not self._save_unsat:
                    successors.pop('unsat', None)

            for to_stash, successor_states in successors.items():
                bucket[to_stash or stash].extend(successor_states)

        self._clear_states(stash=stash)
        for to_stash, states in bucket.items():
            self._store_states(to_stash or stash, states)

        if step_func is not None:
            return step_func(self)
        return self


Instruction:
How would you code a function in Python 3 to
return a dict of state and successors.

Response:
def step_state(self, state, successor_func=None, **run_args):
        """
        Don't use this function manually - it is meant to interface with exploration techniques.
        """
        try:
            successors = self.successors(state, successor_func=successor_func, **run_args)
            stashes = {None: successors.flat_successors,
                       'unsat': successors.unsat_successors,
                       'unconstrained': successors.unconstrained_successors}

        except (SimUnsatError, claripy.UnsatError) as e:
            if self._hierarchy:
                self._hierarchy.unreachable_state(state)
                self._hierarchy.simplify()
            stashes = {'pruned': [state]}

        except tuple(self._resilience) as e:
            self._errored.append(ErrorRecord(state, e, sys.exc_info()[2]))
            stashes = {}

        return stashes


Instruction:
Write a Python 3 script to
return the set of successors for the given state.

Response:
def successors(self, state, successor_func=None, **run_args):
        """
        Don't use this function manually - it is meant to interface with exploration techniques.
        """
        if successor_func is not None:
            return successor_func(state, **run_args)
        return self._project.factory.successors(state, **run_args)


Instruction:
How would you implement a function in Python 3 that
moves all unsatisfiable states in a given stash into a different stash.

Response:
def prune(self, filter_func=None, from_stash='active', to_stash='pruned'):
        """
        Prune unsatisfiable states from a stash.

        This function will move all unsatisfiable states in the given stash into a different stash.

        :param filter_func: Only prune states that match this filter.
        :param from_stash:  Prune states from this stash. (default: 'active')
        :param to_stash:    Put pruned states in this stash. (default: 'pruned')

        :returns:           The simulation manager, for chaining.
        :rtype:             SimulationManager
        """
        def _prune_filter(state):
            to_prune = not filter_func or filter_func(state)
            if to_prune and not state.satisfiable():
                if self._hierarchy:
                    self._hierarchy.unreachable_state(state)
                    self._hierarchy.simplify()
                return True
            return False

        self.move(from_stash, to_stash, _prune_filter)
        return self


Instruction:
Can you generate the documentation for the following Python 3 function
def move(self, from_stash, to_stash, filter_func=None):
        filter_func = filter_func or (lambda s: True)
        stash_splitter = lambda states: reversed(self._filter_states(filter_func, states))
        return self.split(stash_splitter, from_stash=from_stash, to_stash=to_stash)

Response: Moves states from one stash to another.


Instruction:
Can you generate the documentation for the following Python 3 function
def stash(self, filter_func=None, from_stash='active', to_stash='stashed'):
        return self.move(from_stash, to_stash, filter_func=filter_func)

Response: Moves some states from one stash to another.


Instruction:
Can you implement a function in Python 3 that
drops states from a stash.

Response:
def drop(self, filter_func=None, stash='active'):
        """
        Drops states from a stash. This is an alias for move(), with defaults for the stashes.

        :param filter_func: Drop states that match this filter. Should be a function that takes
                            a state and returns True or False. (default: drop all states)
        :param stash:       Drop matching states from this stash. (default: 'active')

        :returns:           The simulation manager, for chaining.
        :rtype:             SimulationManager
        """
        return self.move(stash, self.DROP, filter_func=filter_func)


Instruction:
How would you explain what the following Python 3 function does
def apply(self, state_func=None, stash_func=None, stash='active', to_stash=None):
        to_stash = to_stash or stash

        def _stash_splitter(states):
            keep, split = [], []
            if state_func is not None:
                for s in states:
                    ns = state_func(s)
                    if isinstance(ns, SimState):
                        split.append(ns)
                    elif isinstance(ns, (list, tuple, set)):
                        split.extend(ns)
                    else:
                        split.append(s)
            if stash_func is not None:
                split = stash_func(states)
            if to_stash is not stash:
                keep = states
            return keep, split

        return self.split(_stash_splitter, from_stash=stash, to_stash=to_stash)

Response: Applies a given function to every state in a given stash.


Instruction:
Here you have a function in Python 3, explain what it does
def split(self, stash_splitter=None, stash_ranker=None, state_ranker=None,
              limit=8, from_stash='active', to_stash='stashed'):
        """
        Split a stash of states into two stashes depending on the specified options.

        The stash from_stash will be split into two stashes depending on the other options
        passed in. If to_stash is provided, the second stash will be written there.

        stash_splitter overrides stash_ranker, which in turn overrides state_ranker.
        If no functions are provided, the states are simply split according to the limit.

        The sort done with state_ranker is ascending.

        :param stash_splitter:  A function that should take a list of states and return a tuple
                                of two lists (the two resulting stashes).
        :param stash_ranker:    A function that should take a list of states and return a sorted
                                list of states. This list will then be split according to "limit".
        :param state_ranker:    An alternative to stash_splitter. States will be sorted with outputs
                                of this function, which are to be used as a key. The first "limit"
                                of them will be kept, the rest split off.
        :param limit:           For use with state_ranker. The number of states to keep. Default: 8
        :param from_stash:      The stash to split (default: 'active')
        :param to_stash:        The stash to write to (default: 'stashed')

        :returns:               The simulation manager, for chaining.
        :rtype:                 SimulationManager
        """
        states = self._fetch_states(stash=from_stash)

        if stash_splitter is not None:
            keep, split = stash_splitter(states)
        elif stash_ranker is not None:
            ranked_paths = stash_ranker(states)
            keep, split = ranked_paths[:limit], ranked_paths[limit:]
        elif state_ranker is not None:
            ranked_paths = sorted(states, key=state_ranker)
            keep, split = ranked_paths[:limit], ranked_paths[limit:]
        else:
            keep, split = states[:limit], states[limit:]

        keep, split = map(list, (keep, split))

        self._clear_states(from_stash)
        self._store_states(from_stash, keep)
        self._store_states(to_stash, split)
        return self

Response: Splits a stash into two states.


Instruction:
Explain what the following Python 3 code does
def merge(self, merge_func=None, merge_key=None, stash='active'):
        self.prune(from_stash=stash)
        to_merge = self._fetch_states(stash=stash)
        not_to_merge = []
        if merge_key is None: merge_key = self._merge_key

        merge_groups = [ ]
        while to_merge:
            base_key = merge_key(to_merge[0])
            g, to_merge = self._filter_states(lambda s: base_key == merge_key(s), to_merge)
            if len(g) <= 1:
                not_to_merge.extend(g)
            else:
                merge_groups.append(g)

        for g in merge_groups:
            try:
                m = self._merge_states(g) if merge_func is None else merge_func(*g)
                not_to_merge.append(m)
            except SimMergeError:
                l.warning("SimMergeError while merging %d states", len(g), exc_info=True)
                not_to_merge.extend(g)

        self._clear_states(stash)
        self._store_states(stash, not_to_merge)
        return self

Response: Merge the states in a given stash.


Instruction:
Make a summary of the following Python 3 code
def _merge_states(self, states):

        if self._hierarchy:
            optimal, common_history, others = self._hierarchy.most_mergeable(states)
        else:
            optimal, common_history, others = states, None, []

        if len(optimal) >= 2:
            # We found optimal states (states that share a common ancestor) to merge.
            # Compute constraints for each state starting from the common ancestor,
            # and use them as merge conditions.
            constraints = [s.history.constraints_since(common_history) for s in optimal]

            o = optimal[0]
            m, _, _ = o.merge(*optimal[1:],
                              merge_conditions=constraints,
                              common_ancestor=common_history.strongref_state
                              )

        else:
            l.warning(
                "Cannot find states with common history line to merge. Fall back to the naive merging strategy "
                "and merge all states."
                )
            s = states[0]
            m, _, _ = s.merge(*states[1:])

            others = []

        if self._hierarchy:
            self._hierarchy.add_state(m)

        if len(others):
            others.append(m)
            return self._merge_states(others)
        else:
            return m

Response: Merges a list of states into a single state.


Instruction:
Write a Python 3 function that can
launch a postmortem debug shell at the site of the error.

Response:
def debug(self):
        """
        Launch a postmortem debug shell at the site of the error.
        """
        try:
            __import__('ipdb').post_mortem(self.traceback)
        except ImportError:
            __import__('pdb').post_mortem(self.traceback)


Instruction:
Explain what the following Python 3 code does
def complement(self, other):

        s = SimVariableSet()
        s.register_variables = self.register_variables - other.register_variables
        s.register_variable_offsets = self.register_variable_offsets - other.register_variable_offsets
        s.memory_variables = self.memory_variables - other.memory_variables
        s.memory_variable_addresses = self.memory_variable_addresses - other.memory_variable_addresses

        return s

Response: Calculate the complement of self and other.


Instruction:
Explain what the following Python 3 code does
def _get_cfg_node(cfg, state):

        call_stack_suffix = state.callstack.stack_suffix(cfg.context_sensitivity_level)
        is_syscall = state.history.jumpkind is not None and state.history.jumpkind.startswith('Ijk_Sys')

        block_id = cfg._generate_block_id(call_stack_suffix, state.addr, is_syscall)

        return cfg.get_node(block_id)

Response: Get the CFGNode object on the control flow graph given an angr state.


Instruction:
Create a Python 3 function to
perform a depth - first search on the given DiGraph and return an iterator of edges.

Response:
def _dfs_edges(graph, source, max_steps=None):
        """
        Perform a depth-first search on the given DiGraph, with a limit on maximum steps.

        :param networkx.DiGraph graph:  The graph to traverse.
        :param Any source:              The source to begin traversal.
        :param int max_steps:           Maximum steps of the traversal, or None if not limiting steps.
        :return: An iterator of edges.
        """

        if max_steps is None:
            yield networkx.dfs_edges(graph, source)

        else:
            steps_map = defaultdict(int)
            traversed = { source }
            stack = [ source ]

            while stack:
                src = stack.pop()
                for dst in graph.successors(src):
                    if dst in traversed:
                        continue
                    traversed.add(dst)

                    dst_steps = max(steps_map[src] + 1, steps_map[dst])

                    if dst_steps > max_steps:
                        continue

                    yield src, dst

                    steps_map[dst] = dst_steps
                    stack.append(dst)


Instruction:
Implement a Python 3 function for
checking if the specified address will be executed.

Response:
def check(self, cfg, state, peek_blocks):
        """
        Check if the specified address will be executed

        :param cfg:
        :param state:
        :param int peek_blocks:
        :return:
        :rtype: bool
        """

        # Get the current CFGNode from the CFG
        node = self._get_cfg_node(cfg, state)

        if node is None:
            # Umm it doesn't exist on the control flow graph - why?
            l.error('Failed to find CFGNode for state %s on the control flow graph.', state)
            return False

        # crawl the graph to see if we can reach the target address next
        for src, dst in self._dfs_edges(cfg.graph, node, max_steps=peek_blocks):
            if src.addr == self.addr or dst.addr == self.addr:
                l.debug("State %s will reach %#x.", state, self.addr)
                return True

        l.debug('SimState %s will not reach %#x.', state, self.addr)
        return False


Instruction:
Create a Python 3 function for
checking if the function will be reached with certain arguments.

Response:
def check(self, cfg, state, peek_blocks):
        """
        Check if the specified function will be reached with certain arguments.

        :param cfg:
        :param state:
        :param peek_blocks:
        :return:
        """

        # Get the current CFGNode
        node = self._get_cfg_node(cfg, state)

        if node is None:
            l.error("Failed to find CFGNode for state %s on the control flow graph.", state)
            return False

        # crawl the graph to see if we can reach the target function within the limited steps
        for src, dst in self._dfs_edges(cfg.graph, node, max_steps=peek_blocks):
            the_node = None
            if src.addr == self.function.addr:
                the_node = src
            elif dst.addr == self.function.addr:
                the_node = dst

            if the_node is not None:
                if self.arguments is None:
                    # we do not care about arguments
                    return True

                else:
                    # check arguments
                    arch = state.arch
                    state = the_node.input_state
                    same_arguments = self._check_arguments(arch, state)

                    if same_arguments:
                        # all arguments are the same!
                        return True

        l.debug("SimState %s will not reach function %s.", state, self.function)
        return False


Instruction:
Write a Python 3 function that can
check if the specific function is reached with certain arguments.

Response:
def check_state(self, state):
        """
        Check if the specific function is reached with certain arguments

        :param angr.SimState state: The state to check
        :return: True if the function is reached with certain arguments, False otherwise.
        :rtype: bool
        """

        if state.addr == self.function.addr:
            arch = state.arch
            if self._check_arguments(arch, state):
                return True

        return False


Instruction:
Can you implement a function in Python 3 that
makes sure all current basic block on each state shows up in the CFG. For blocks that are not in the CFG, start CFG recovery from them with a maximum basic block depth of 100. :param simgr: :return:

Response:
def _peek_forward(self, simgr):
        """
        Make sure all current basic block on each state shows up in the CFG. For blocks that are not in the CFG, start
        CFG recovery from them with a maximum basic block depth of 100.

        :param simgr:
        :return:
        """

        if self._cfg is None:

            starts = list(simgr.active)
            self._cfg_kb = KnowledgeBase(self.project)

            self._cfg = self.project.analyses.CFGEmulated(kb=self._cfg_kb, starts=starts, max_steps=self._peek_blocks,
                                                          keep_state=self._cfg_keep_states
                                                          )

        else:

            starts = list(simgr.active)

            self._cfg.resume(starts=starts, max_steps=self._peek_blocks)


Instruction:
Can you tell what is the following Python 3 function doing
def _load_fallback_states(self, pg):

        # take back some of the deprioritized states
        l.debug("No more active states. Load some deprioritized states to 'active' stash.")
        if 'deprioritized' in pg.stashes and pg.deprioritized:
            pg.active.extend(pg.deprioritized[-self._num_fallback_states : ])
            pg.stashes['deprioritized'] = pg.deprioritized[ : -self._num_fallback_states]

Response: Load the last N deprioritized states from the deprioritized stash and put them to the active stash.


Instruction:
Explain what the following Python 3 code does
def _categorize_states(self, simgr):

        past_active_states = len(simgr.active)
        # past_deprioritized_states = len(simgr.deprioritized)

        for goal in self._goals:
            for p in simgr.active:
                if self._check_goals(goal, p):
                    if self._goal_satisfied_callback is not None:
                        self._goal_satisfied_callback(goal, p, simgr)

        simgr.stash(
            filter_func=lambda p: all(not goal.check(self._cfg, p, peek_blocks=self._peek_blocks) for goal in
                                      self._goals
                                      ),
            from_stash='active',
            to_stash='deprioritized',
        )

        if simgr.active:
            # TODO: pick some states from depriorized stash to active stash to avoid overfitting
            pass

        active_states = len(simgr.active)
        # deprioritized_states = len(simgr.deprioritized)

        l.debug('%d/%d active states are deprioritized.', past_active_states - active_states, past_active_states)

        return simgr

Response: Categorizes all states into two different groups.


Instruction:
Can you generate the documentation for the following Python 3 function
def has_xor(self):

        def _has_xor(expr):
            return isinstance(expr, pyvex.IRExpr.Binop) and expr.op.startswith("Iop_Xor")

        found_xor = False

        for block in self._function.blocks:
            if block.size == 0:
                continue
            for stmt in block.vex.statements:
                if isinstance(stmt, pyvex.IRStmt.Put):
                    found_xor = found_xor or _has_xor(stmt.data)
                elif isinstance(stmt, pyvex.IRStmt.WrTmp):
                    found_xor = found_xor or _has_xor(stmt.data)
            if found_xor:
                break

        if found_xor:
            return { CodeTags.HAS_XOR }
        return None

Response: Detects if there is any xor operation in the function.


Instruction:
Implement a Python 3 function for
detecting if there is any bitwise operation in the function.

Response:
def has_bitshifts(self):
        """
        Detects if there is any bitwise operation in the function.

        :return: Tags.
        """

        def _has_bitshifts(expr):
            if isinstance(expr, pyvex.IRExpr.Binop):
                return expr.op.startswith("Iop_Shl") or expr.op.startswith("Iop_Shr") \
                       or expr.op.startswith("Iop_Sar")
            return False

        found_bitops = False

        for block in self._function.blocks:
            if block.size == 0:
                continue
            for stmt in block.vex.statements:
                if isinstance(stmt, pyvex.IRStmt.Put):
                    found_bitops = found_bitops or _has_bitshifts(stmt.data)
                elif isinstance(stmt, pyvex.IRStmt.WrTmp):
                    found_bitops = found_bitops or _has_bitshifts(stmt.data)

            if found_bitops:
                break

        if found_bitops:
            return { CodeTags.HAS_BITSHIFTS }
        return None


Instruction:
Can you implement a function in Python 3 that
merges another KeyedRegion into this one.

Response:
def merge(self, other, replacements=None):
        """
        Merge another KeyedRegion into this KeyedRegion.

        :param KeyedRegion other: The other instance to merge with.
        :return: None
        """

        # TODO: is the current solution not optimal enough?
        for _, item in other._storage.items():  # type: RegionObject
            for so in item.stored_objects:  # type: StoredObject
                if replacements and so.obj in replacements:
                    so = StoredObject(so.start, replacements[so.obj], so.size)
                self._object_mapping[so.obj_id] = so
                self.__store(so, overwrite=False)

        return self


Instruction:
Can you generate the documentation for the following Python 3 function
def replace(self, replacements):

        for old_var, new_var in replacements.items():
            old_var_id = id(old_var)
            if old_var_id in self._object_mapping:
                # FIXME: we need to check if old_var still exists in the storage
                old_so = self._object_mapping[old_var_id]  # type: StoredObject
                self._store(old_so.start, new_var, old_so.size, overwrite=True)

        return self

Response: Replace variables with other variables.


Instruction:
How would you explain what the following Python 3 function does
def dbg_repr(self):
        keys = self._storage.keys()
        offset_to_vars = { }

        for key in sorted(keys):
            ro = self._storage[key]
            variables = [ obj.obj for obj in ro.stored_objects ]
            offset_to_vars[key] = variables

        s = [ ]
        for offset, variables in offset_to_vars.items():
            s.append("Offset %#x: %s" % (offset, variables))
        return "\n".join(s)

Response: Get a debugging representation of this keyed region.


Instruction:
Write a Python 3 function for
adding a variable to this region at the given offset.

Response:
def add_variable(self, start, variable):
        """
        Add a variable to this region at the given offset.

        :param int start:
        :param SimVariable variable:
        :return: None
        """

        size = variable.size if variable.size is not None else 1

        self.add_object(start, variable, size)


Instruction:
Write a Python 3 script for
adding an object to this region at the given offset.

Response:
def add_object(self, start, obj, object_size):
        """
        Add/Store an object to this region at the given offset.

        :param start:
        :param obj:
        :param int object_size: Size of the object
        :return:
        """

        self._store(start, obj, object_size, overwrite=False)


Instruction:
Make a summary of the following Python 3 code
def set_variable(self, start, variable):

        size = variable.size if variable.size is not None else 1

        self.set_object(start, variable, size)

Response: Add a variable to this region at the given offset and remove all other variables that are fully covered by this variable.


Instruction:
Can you tell what is the following Python 3 function doing
def set_object(self, start, obj, object_size):

        self._store(start, obj, object_size, overwrite=True)

Response: Add an object to this region at the given offset and remove all other objects that are fully covered by this object.


Instruction:
Explain what the following Python 3 code does
def get_base_addr(self, addr):

        base_addr, container = self._get_container(addr)
        if container is None:
            return None
        else:
            return base_addr

Response: Get the base offset of a specific key.


Instruction:
Can you write a function in Python 3 where it
finds variables covering the given region offset.

Response:
def get_variables_by_offset(self, start):
        """
        Find variables covering the given region offset.

        :param int start:
        :return: A list of stack variables.
        :rtype:  set
        """

        _, container = self._get_container(start)
        if container is None:
            return []
        else:
            return container.internal_objects


Instruction:
Write a Python 3 script for
finding objects covering the given region offset.

Response:
def get_objects_by_offset(self, start):
        """
        Find objects covering the given region offset.

        :param start:
        :return:
        """

        _, container = self._get_container(start)
        if container is None:
            return set()
        else:
            return container.internal_objects


Instruction:
Can you tell what is the following Python 3 function doing
def _store(self, start, obj, size, overwrite=False):

        stored_object = StoredObject(start, obj, size)
        self._object_mapping[stored_object.obj_id] = stored_object
        self.__store(stored_object, overwrite=overwrite)

Response: Store a variable into the storage.


Instruction:
Explain what the following Python 3 code does
def __store(self, stored_object, overwrite=False):

        start = stored_object.start
        object_size = stored_object.size
        end = start + object_size

        # region items in the middle
        overlapping_items = list(self._storage.irange(start, end-1))

        # is there a region item that begins before the start and overlaps with this variable?
        floor_key, floor_item = self._get_container(start)
        if floor_item is not None and floor_key not in overlapping_items:
            # insert it into the beginning
            overlapping_items.insert(0, floor_key)

        # scan through the entire list of region items, split existing regions and insert new regions as needed
        to_update = {start: RegionObject(start, object_size, {stored_object})}
        last_end = start

        for floor_key in overlapping_items:
            item = self._storage[floor_key]
            if item.start < start:
                # we need to break this item into two
                a, b = item.split(start)
                if overwrite:
                    b.set_object(stored_object)
                else:
                    self._add_object_with_check(b, stored_object)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            elif item.start > last_end:
                # there is a gap between the last item and the current item
                # fill in the gap
                new_item = RegionObject(last_end, item.start - last_end, {stored_object})
                to_update[new_item.start] = new_item
                last_end = new_item.end
            elif item.end > end:
                # we need to split this item into two
                a, b = item.split(end)
                if overwrite:
                    a.set_object(stored_object)
                else:
                    self._add_object_with_check(a, stored_object)
                to_update[a.start] = a
                to_update[b.start] = b
                last_end = b.end
            else:
                if overwrite:
                    item.set_object(stored_object)
                else:
                    self._add_object_with_check(item, stored_object)
                to_update[item.start] = item

        self._storage.update(to_update)

Response: Store a variable into the storage.


Instruction:
How would you explain what the following Python 3 function does
def _initialize_progressbar(self):

        self._progressbar = progressbar.ProgressBar(widgets=Analysis._PROGRESS_WIDGETS, maxval=10000 * 100).start()

Response:
Initialize the progressbar.
        :return: None


Instruction:
Explain what the following Python 3 code does
def _update_progress(self, percentage, **kwargs):

        if self._show_progressbar:
            if self._progressbar is None:
                self._initialize_progressbar()

            self._progressbar.update(percentage * 10000)

        if self._progress_callback is not None:
            self._progress_callback(percentage, **kwargs)

Response: Update the progress with a percentage.


Instruction:
Create a Python 3 function to
mark the progressbar as finished. :return: None

Response:
def _finish_progress(self):
        """
        Mark the progressbar as finished.
        :return: None
        """

        if self._show_progressbar:
            if self._progressbar is None:
                self._initialize_progressbar()
            if self._progressbar is not None:
                self._progressbar.finish()

        if self._progress_callback is not None:
            self._progress_callback(100.0)


Instruction:
Given the following Python 3 function, write the documentation
def merge(self, other, successor=None):

        replacements = {}
        if successor in self.dominance_frontiers:
            replacements = self._make_phi_variables(successor, self, other)

        merged_concrete_states =  [ self._concrete_states[0] ] # self._merge_concrete_states(other)

        new_stack_region = self.stack_region.copy().replace(replacements)
        new_stack_region.merge(other.stack_region, replacements=replacements)

        new_register_region = self.register_region.copy().replace(replacements)
        new_register_region.merge(other.register_region, replacements=replacements)

        return VariableRecoveryState(successor, self._analysis, self.arch, self.function, merged_concrete_states,
                                     stack_region=new_stack_region,
                                     register_region=new_register_region
                                     )

Response: Merges two abstract states.


Instruction:
Here you have a function in Python 3, explain what it does
def _addr_to_stack_offset(self, addr):

        def _parse(addr):
            if addr.op == '__add__':
                # __add__ might have multiple arguments
                parsed = [ _parse(arg) for arg in addr.args ]
                annotated = [ True for annotated, _ in parsed if annotated is True ]
                if len(annotated) != 1:
                    # either nothing is annotated, or more than one element is annotated
                    raise ValueError()

                return True, sum([ offset for _, offset in parsed ])
            elif addr.op == '__sub__':
                # __sub__ might have multiple arguments

                parsed = [ _parse(arg) for arg in addr.args ]
                first_annotated, first_offset = parsed[0]
                if first_annotated is False:
                    # the first argument is not annotated. we don't support it.
                    raise ValueError()
                if any([ annotated for annotated, _ in parsed[1:] ]):
                    # more than one argument is annotated. we don't support it.
                    raise ValueError()

                return True, first_offset - sum([ offset for _, offset in parsed[1:] ])
            else:
                anno = next(iter(anno for anno in addr.annotations if isinstance(anno, StackLocationAnnotation)), None)
                if anno is None:
                    if addr.op == 'BVV':
                        return False, addr._model_concrete.value
                    raise ValueError()
                return True, anno.offset

        # find the annotated AST
        try: annotated, offset = _parse(addr)
        except ValueError: return None

        if not annotated:
            return None

        return self._to_signed(offset)

Response: Convert an address to a stack offset.


Instruction:
How would you explain what the following Python 3 function does
def _run_on_node(self, node, state):

        l.debug('Analyzing block %#x, iteration %d.', node.addr, self._node_iterations[node])

        concrete_state = state.get_concrete_state(node.addr)

        if concrete_state is None:
            # didn't find any state going to here
            l.error("_run_on_node(): cannot find any state for address %#x.", node.addr)
            return False, state

        state = state.copy()
        self._instates[node.addr] = state

        if self._node_iterations[node] >= self._max_iterations:
            l.debug('Skip node %s as we have iterated %d times on it.', node, self._node_iterations[node])
            return False, state

        state.register_callbacks([ concrete_state ])

        successors = self.project.factory.successors(concrete_state,
                                                     addr=node.addr,
                                                     size=node.size,
                                                     opt_level=0  # disable the optimization in order to have
                                                                  # instruction-level analysis results
                                                     )
        output_states = successors.all_successors

        state.concrete_states = [ state for state in output_states if not state.ip.symbolic ]

        self._outstates[node.addr] = state

        self._node_iterations[node] += 1

        return True, state

Response: Execute the node and derive an output state.


Instruction:
Here you have a function in Python 3, explain what it does
def _initialize_cfg(self):

        self.kb.functions = FunctionManager(self.kb)

        self._jobs_to_analyze_per_function = defaultdict(set)
        self._completed_functions = set()

Response: Re - create the DiGraph


Instruction:
Here you have a function in Python 3, explain what it does
def make_copy(self, copy_to):

        for attr, value in self.__dict__.items():
            if attr.startswith('__') and attr.endswith('__'):
                continue
            setattr(copy_to, attr, value)

Response: Copy self attributes to the new object.


Instruction:
Create a Python 3 function to
merge two adjacent CFGNodes into one.

Response:
def _merge_cfgnodes(self, cfgnode_0, cfgnode_1):
        """
        Merge two adjacent CFGNodes into one.

        :param CFGNode cfgnode_0:   The first CFGNode.
        :param CFGNode cfgnode_1:   The second CFGNode.
        :return:                    None
        """

        assert cfgnode_0.addr + cfgnode_0.size == cfgnode_1.addr
        addr0, addr1 = cfgnode_0.addr, cfgnode_1.addr
        new_node = cfgnode_0.merge(cfgnode_1)

        # Update the graph and the nodes dict accordingly
        if addr1 in self._nodes_by_addr:
            self._nodes_by_addr[addr1].remove(cfgnode_1)
            if not self._nodes_by_addr[addr1]:
                del self._nodes_by_addr[addr1]
        del self._nodes[cfgnode_1.block_id]

        self._nodes_by_addr[addr0].remove(cfgnode_0)
        if not self._nodes_by_addr[addr0]:
            del self._nodes_by_addr[addr0]
        del self._nodes[cfgnode_0.block_id]

        in_edges = list(self.graph.in_edges(cfgnode_0, data=True))
        out_edges = list(self.graph.out_edges(cfgnode_1, data=True))

        self.graph.remove_node(cfgnode_0)
        self.graph.remove_node(cfgnode_1)

        self.graph.add_node(new_node)
        for src, _, data in in_edges:
            self.graph.add_edge(src, new_node, **data)
        for _, dst, data in out_edges:
            self.graph.add_edge(new_node, dst, **data)

        # Put the new node into node dicts
        self._nodes[new_node.block_id] = new_node
        self._nodes_by_addr[addr0].append(new_node)


Instruction:
Make a summary of the following Python 3 code
def _to_snippet(self, cfg_node=None, addr=None, size=None, thumb=False, jumpkind=None, base_state=None):

        if cfg_node is not None:
            addr = cfg_node.addr
            size = cfg_node.size
            thumb = cfg_node.thumb
        else:
            addr = addr
            size = size
            thumb = thumb

        if addr is None:
            raise ValueError('_to_snippet(): Either cfg_node or addr must be provided.')

        if self.project.is_hooked(addr) and jumpkind != 'Ijk_NoHook':
            hooker = self.project._sim_procedures[addr]
            size = hooker.kwargs.get('length', 0)
            return HookNode(addr, size, type(hooker))

        if cfg_node is not None:
            return BlockNode(addr, size, thumb=thumb, bytestr=cfg_node.byte_string)  # pylint: disable=no-member
        else:
            return self.project.factory.snippet(addr, size=size, jumpkind=jumpkind, thumb=thumb,
                                                backup_state=base_state)

Response: Convert a CFGNode instance to a CodeNode object.


Instruction:
Can you generate the documentation for the following Python 3 function
def _arm_thumb_filter_jump_successors(self, addr, size, successors, get_ins_addr, get_exit_stmt_idx):

        if not successors:
            return [ ]

        it_counter = 0
        conc_temps = {}
        can_produce_exits = set()
        bb = self._lift(addr, size=size, thumb=True, opt_level=0)

        for stmt in bb.vex.statements:
            if stmt.tag == 'Ist_IMark':
                if it_counter > 0:
                    it_counter -= 1
                    can_produce_exits.add(stmt.addr + stmt.delta)
            elif stmt.tag == 'Ist_WrTmp':
                val = stmt.data
                if val.tag == 'Iex_Const':
                    conc_temps[stmt.tmp] = val.con.value
            elif stmt.tag == 'Ist_Put':
                if stmt.offset == self.project.arch.registers['itstate'][0]:
                    val = stmt.data
                    if val.tag == 'Iex_RdTmp':
                        if val.tmp in conc_temps:
                            # We found an IT instruction!!
                            # Determine how many instructions are conditional
                            it_counter = 0
                            itstate = conc_temps[val.tmp]
                            while itstate != 0:
                                it_counter += 1
                                itstate >>= 8

        if it_counter != 0:
            l.debug('Basic block ends before calculated IT block (%#x)', addr)

        THUMB_BRANCH_INSTRUCTIONS = ('beq', 'bne', 'bcs', 'bhs', 'bcc', 'blo', 'bmi', 'bpl', 'bvs',
                                     'bvc', 'bhi', 'bls', 'bge', 'blt', 'bgt', 'ble', 'cbz', 'cbnz')
        for cs_insn in bb.capstone.insns:
            if cs_insn.mnemonic.split('.')[0] in THUMB_BRANCH_INSTRUCTIONS:
                can_produce_exits.add(cs_insn.address)

        successors_filtered = [suc for suc in successors
                               if get_ins_addr(suc) in can_produce_exits or get_exit_stmt_idx(suc) == DEFAULT_STATEMENT]

        return successors_filtered

Response: Filter successors for THUMB mode basic blocks and remove those that won t be taken normally.


Instruction:
Implement a Python 3 function for
checking if the given memory region is extremely sparse.

Response:
def _is_region_extremely_sparse(self, start, end, base_state=None):
        """
        Check whether the given memory region is extremely sparse, i.e., all bytes are the same value.

        :param int start: The beginning of the region.
        :param int end:   The end of the region.
        :param base_state: The base state (optional).
        :return:           True if the region is extremely sparse, False otherwise.
        :rtype:            bool
        """

        all_bytes = None

        if base_state is not None:
            all_bytes = base_state.memory.load(start, end - start + 1)
            try:
                all_bytes = base_state.solver.eval(all_bytes, cast_to=bytes)
            except SimError:
                all_bytes = None

        size = end - start + 1

        if all_bytes is None:
            # load from the binary
            all_bytes = self._fast_memory_load_bytes(start, size)

        if all_bytes is None:
            return True

        if len(all_bytes) < size:
            l.warning("_is_region_extremely_sparse: The given region %#x-%#x is not a continuous memory region in the "
                      "memory space. Only the first %d bytes (%#x-%#x) are processed.", start, end, len(all_bytes),
                      start, start + len(all_bytes) - 1)

        the_byte_value = None
        for b in all_bytes:
            if the_byte_value is None:
                the_byte_value = b
            else:
                if the_byte_value != b:
                    return False

        return True


Instruction:
Implement a function in Python 3 to
return True if we should skip the region False otherwise.

Response:
def _should_skip_region(self, region_start):
        """
        Some regions usually do not contain any executable code, but are still marked as executable. We should skip
        those regions by default.

        :param int region_start: Address of the beginning of the region.
        :return:                 True/False
        :rtype:                  bool
        """

        obj = self.project.loader.find_object_containing(region_start, membership_check=False)
        if obj is None:
            return False
        if isinstance(obj, PE):
            section = obj.find_section_containing(region_start)
            if section is None:
                return False
            if section.name in {'.textbss'}:
                return True

        return False


Instruction:
Given the following Python 3 function, write the documentation
def _executable_memory_regions(self, objects=None, force_segment=False):

        if objects is None:
            binaries = self.project.loader.all_objects
        else:
            binaries = objects

        memory_regions = [ ]

        for b in binaries:
            if isinstance(b, ELF):
                # If we have sections, we get result from sections
                if not force_segment and b.sections:
                    # Get all executable sections
                    for section in b.sections:
                        if section.is_executable:
                            tpl = (section.min_addr, section.max_addr)
                            memory_regions.append(tpl)

                else:
                    # Get all executable segments
                    for segment in b.segments:
                        if segment.is_executable:
                            tpl = (segment.min_addr, segment.max_addr)
                            memory_regions.append(tpl)

            elif isinstance(b, PE):
                for section in b.sections:
                    if section.is_executable:
                        tpl = (section.min_addr, section.max_addr)
                        memory_regions.append(tpl)

            elif isinstance(b, MachO):
                if b.segments:
                    # Get all executable segments
                    for seg in b.segments:
                        if seg.is_executable:
                            # Take all sections from this segment (MachO style)
                            for section in seg.sections:
                                tpl = (section.min_addr, section.max_addr)
                                memory_regions.append(tpl)

            elif isinstance(b, Blob):
                # a blob is entirely executable
                tpl = (b.min_addr, b.max_addr)
                memory_regions.append(tpl)

            elif isinstance(b, self._cle_pseudo_objects):
                pass

            else:
                l.warning('Unsupported object format "%s". Treat it as an executable.', b.__class__.__name__)

                tpl = (b.min_addr, b.max_addr)
                memory_regions.append(tpl)

        if not memory_regions:
            memory_regions = [(start, start + len(backer)) for start, backer in self.project.loader.memory.backers()]

        memory_regions = sorted(memory_regions, key=lambda x: x[0])

        return memory_regions

Response: Get all executable memory regions from the binaries.


Instruction:
Write a Python 3 script to
test if the given address belongs to an executable memory region.

Response:
def _addr_in_exec_memory_regions(self, addr):
        """
        Test if the address belongs to an executable memory region.

        :param int addr: The address to test
        :return: True if the address belongs to an exectubale memory region, False otherwise
        :rtype: bool
        """

        for start, end in self._exec_mem_regions:
            if start <= addr < end:
                return True
        return False


Instruction:
Create a Python 3 function for
testing if two addresses belong to the same section.

Response:
def _addrs_belong_to_same_section(self, addr_a, addr_b):
        """
        Test if two addresses belong to the same section.

        :param int addr_a:  The first address to test.
        :param int addr_b:  The second address to test.
        :return:            True if the two addresses belong to the same section or both of them do not belong to any
                            section, False otherwise.
        :rtype:             bool
        """

        obj = self.project.loader.find_object_containing(addr_a, membership_check=False)

        if obj is None:
            # test if addr_b also does not belong to any object
            obj_b = self.project.loader.find_object_containing(addr_b, membership_check=False)
            if obj_b is None:
                return True
            return False

        src_section = obj.find_section_containing(addr_a)
        if src_section is None:
            # test if addr_b also does not belong to any section
            dst_section = obj.find_section_containing(addr_b)
            if dst_section is None:
                return True
            return False

        return src_section.contains_addr(addr_b)


Instruction:
Write a Python 3 script to
check whether the given address belongs to a hook or a syscall.

Response:
def _addr_hooked_or_syscall(self, addr):
        """
        Check whether the address belongs to a hook or a syscall.

        :param int addr:    The address to check.
        :return:            True if the address is hooked or belongs to a syscall. False otherwise.
        :rtype:             bool
        """

        return self.project.is_hooked(addr) or self.project.simos.is_syscall_addr(addr)


Instruction:
Can you write a function in Python 3 where it
performs a fast memory loading of some data.

Response:
def _fast_memory_load_bytes(self, addr, length):
        """
        Perform a fast memory loading of some data.

        :param int addr: Address to read from.
        :param int length: Size of the string to load.
        :return:         A string or None if the address does not exist.
        :rtype:          bytes or None
        """

        try:
            return self.project.loader.memory.load(addr, length)
        except KeyError:
            return None


Instruction:
Can you generate the documentation for the following Python 3 function
def _fast_memory_load_pointer(self, addr, size=None):

        try:
            return self.project.loader.memory.unpack_word(addr, size=size)
        except KeyError:
            return None

Response: Perform a fast memory loading of a pointer.


Instruction:
How would you explain what the following Python 3 function does
def _determine_function_returning(self, func, all_funcs_completed=False):

        # If there is at least one return site, then this function is definitely returning
        if func.has_return:
            return True

        # Let's first see if it's a known SimProcedure that does not return
        if self.project.is_hooked(func.addr):
            procedure = self.project.hooked_by(func.addr)
        else:
            try:
                procedure = self.project.simos.syscall_from_addr(func.addr, allow_unsupported=False)
            except AngrUnsupportedSyscallError:
                procedure = None

        if procedure is not None and hasattr(procedure, 'NO_RET'):
            return not procedure.NO_RET

        # did we finish analyzing this function?
        if not all_funcs_completed and func.addr not in self._completed_functions:
            return None

        if not func.block_addrs_set:
            # there is no block inside this function
            # it might happen if the function has been incorrectly identified as part of another function
            # the error will be corrected during post-processing. In fact at this moment we cannot say anything
            # about whether this function returns or not. We always assume it returns.
            return True

        bail_out = False

        # if this function has jump-out sites or ret-out sites, it returns as long as any of the target function
        # returns
        for goout_site, type_ in [(site, 'jumpout') for site in func.jumpout_sites] + \
                                 [(site, 'retout') for site in func.retout_sites]:

            # determine where it jumps/returns to
            goout_site_successors = goout_site.successors()
            if not goout_site_successors:
                # not sure where it jumps to. bail out
                bail_out = True
                continue

            # for ret-out sites, determine what function it calls
            if type_ == 'retout':
                # see whether the function being called returns or not
                func_successors = [succ for succ in goout_site_successors if isinstance(succ, Function)]
                if func_successors and all(func_successor.returning in (None, False)
                                           for func_successor in func_successors):
                    # the returning of all possible function calls are undetermined, or they do not return
                    # ignore this site
                    continue

            if type_ == 'retout':
                goout_target = next((succ for succ in goout_site_successors if not isinstance(succ, Function)), None)
            else:
                goout_target = next((succ for succ in goout_site_successors), None)
            if goout_target is None:
                # there is no jumpout site, which is weird, but what can we do...
                continue
            if not self.kb.functions.contains_addr(goout_target.addr):
                # wait it does not jump to a function?
                bail_out = True
                continue

            target_func = self.kb.functions[goout_target.addr]
            if target_func.returning is True:
                return True
            elif target_func.returning is None:
                # the returning status of at least one of the target functions is not decided yet.
                bail_out = True

        if bail_out:
            # We cannot determine at this point. bail out
            return None

        # well this function does not return then
        return False

Response: Determines if a function returns or not.


Instruction:
Write a Python 3 script for
analyzing the function features of functions in the function manager.

Response:
def _analyze_function_features(self, all_funcs_completed=False):
        """
        For each function in the function_manager, try to determine if it returns or not. A function does not return if
        it calls another function that is known to be not returning, and this function does not have other exits.

        We might as well analyze other features of functions in the future.

        :param bool all_funcs_completed:    Ignore _completed_functions set and treat all functions as completed. This
                                            can be set to True after the entire CFG is built and _post_analysis() is
                                            called (at which point analysis on all functions must be completed).
        """

        changes = {
            'functions_return': [],
            'functions_do_not_return': []
        }

        if self._updated_nonreturning_functions is not None:
            all_func_addrs = self._updated_nonreturning_functions

            # Convert addresses to objects
            all_functions = [ self.kb.functions.get_by_addr(f) for f in all_func_addrs
                              if self.kb.functions.contains_addr(f) ]

        else:
            all_functions = list(self.kb.functions.values())

        analyzed_functions = set()
        # short-hand
        functions = self.kb.functions  # type: angr.knowledge.FunctionManager

        while all_functions:
            func = all_functions.pop(-1)  # type: angr.knowledge.Function
            analyzed_functions.add(func.addr)

            if func.returning is not None:
                # It has been determined before. Skip it
                continue

            returning = self._determine_function_returning(func, all_funcs_completed=all_funcs_completed)

            if returning:
                func.returning = True
                changes['functions_return'].append(func)
            elif returning is False:
                func.returning = False
                changes['functions_do_not_return'].append(func)

            if returning is not None:
                # Add all callers of this function to all_functions list
                if func.addr in functions.callgraph:
                    callers = functions.callgraph.predecessors(func.addr)
                    for caller in callers:
                        if caller in analyzed_functions:
                            continue
                        if functions.contains_addr(caller):
                            all_functions.append(functions.get_by_addr(caller))

        return changes