コード例 #1
0
def test_ControlFlowGraph_node_attribute_changes_hash():
  """Test that hash depends on node attributes."""
  g1 = control_flow_graph.ControlFlowGraph()
  g1.add_node(0, name='A')

  g2 = control_flow_graph.ControlFlowGraph()
  g2.add_node(0, name='A', entry=True)

  assert hash(g1) != hash(g2)
コード例 #2
0
def test_ControlFlowGraph_unequal_edges():
  """Test that graphs with unequal edges are not equal."""
  # Graph 1: A --> B
  g1 = control_flow_graph.ControlFlowGraph()
  g1.add_node(0, name='A', entry=True)
  g1.add_node(1, name='B', exit=True)
  g1.add_edge(0, 1)

  # Graph 2: B --> A
  g2 = control_flow_graph.ControlFlowGraph()
  g2.add_node(0, name='A', entry=True)
  g2.add_node(1, name='B', exit=True)
  g2.add_edge(1, 0)

  assert g1 != g2
コード例 #3
0
def test_ControlFlowGraph_unequal_graph_names_are_equal():
  """Test that graph names are not used in comparison."""
  # Graph 1: A --> B
  g1 = control_flow_graph.ControlFlowGraph(name='foo')
  g1.add_node(0, name='A', entry=True)
  g1.add_node(1, name='B', exit=True)
  g1.add_edge(0, 1)

  # Graph 2: A --> B
  g2 = control_flow_graph.ControlFlowGraph(name='bar')
  g2.add_node(0, name='A', entry=True)
  g2.add_node(1, name='B', exit=True)
  g2.add_edge(0, 1)

  assert g1 == g2
コード例 #4
0
def test_ControlFlowGraph_unequal_edge_data():
  """Test that edge data is used in comparison."""
  # Graph 1: A --> B
  g1 = control_flow_graph.ControlFlowGraph(name='foo')
  g1.add_node(0, name='A', exit=True)
  g1.add_node(1, name='B')
  g1.add_edge(0, 1)

  # Graph 2: A --> B
  g2 = control_flow_graph.ControlFlowGraph(name='bar')
  g2.add_node(0, name='A')
  g2.add_node(1, name='B', entry=True)
  g2.add_edge(0, 1)

  assert g1 != g2
コード例 #5
0
def test_ControlFlowGraph_equivalent_hashes():
  """Test equivalent hashes, despite different graph names."""
  # Graph 1: A --> B
  g1 = control_flow_graph.ControlFlowGraph(name='foo')
  g1.add_node(0, name='A', entry=True)
  g1.add_node(1, name='B', exit=True)
  g1.add_edge(0, 1)

  # Graph 2: A --> B
  g2 = control_flow_graph.ControlFlowGraph(name='bar')
  g2.add_node(0, name='A', entry=True)
  g2.add_node(1, name='B', exit=True)
  g2.add_edge(0, 1)

  assert hash(g1) == hash(g2)
コード例 #6
0
def test_ControlFlowGraph_equal():
  """Test that equal graphs can be compared."""
  # Graph 1: A --> B
  g1 = control_flow_graph.ControlFlowGraph()
  g1.add_node(0, name='A', entry=True)
  g1.add_node(1, name='B', exit=True)
  g1.add_edge(0, 1)

  # Graph 2: A --> B
  g2 = control_flow_graph.ControlFlowGraph()
  g2.add_node(0, name='A', entry=True)
  g2.add_node(1, name='B', exit=True)
  g2.add_edge(0, 1)

  assert g1 == g2
コード例 #7
0
def test_ControlFlowGraph_ToProto_FromProto_equivalency():
  """Test that conversion to and from proto preserves values."""
  g1 = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----> B -----+
  #     |             |
  #     v             v
  #     A             D
  #     ^             ^
  #     |             |
  #     +----> C -----+
  g1.add_node(0, name='A', entry=True)
  g1.add_node(1, name='B')
  g1.add_node(2, name='C')
  g1.add_node(3, name='D', exit=True)
  g1.add_edge(0, 1)
  g1.add_edge(0, 2)
  g1.add_edge(1, 3)
  g1.add_edge(2, 3)

  proto = g1.ToProto()

  g2 = control_flow_graph.ControlFlowGraph.FromProto(proto)

  assert g1 == g2

  # Graph names are not used in equality checks.
  assert g1.name == g2.name
コード例 #8
0
def test_ControlFlowGraph_IsValidControlFlowGraph_exit_block_has_output():
  """Test that an if-else loop graph is valid."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----> B -----+
  #     |             |
  #     |             v
  #     A<-----------+D
  #     |             ^
  #     |             |
  #     +----> C -----+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C')
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(0, 2)
  g.add_edge(1, 3)
  g.add_edge(2, 3)
  g.add_edge(3, 0)
  with pytest.raises(control_flow_graph.InvalidNodeDegree) as e_ctx:
    g.ValidateControlFlowGraph()
  assert str(e_ctx.value) == "Exit block outdegree(D) = 1"
  assert not g.IsValidControlFlowGraph()
コード例 #9
0
def test_ControlFlowGraph_validate_empty_graph():
  """Test that empty graph is invalid."""
  g = control_flow_graph.ControlFlowGraph()
  with pytest.raises(control_flow_graph.NotEnoughNodes) as e_ctx:
    g.ValidateControlFlowGraph()
  assert str(e_ctx.value) == "Graph has 0 nodes"
  assert not g.IsValidControlFlowGraph()
コード例 #10
0
def test_ControlFlowGraph_IsValidControlFlowGraph_disconnected_graph():
  """A disconnected graph is not valid."""
  g = control_flow_graph.ControlFlowGraph()
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B', exit=True)
  with pytest.raises(control_flow_graph.UnconnectedNode) as e_ctx:
    g.ValidateControlFlowGraph()
  assert str(e_ctx.value) == "Unconnected node 'A'"
  assert not g.IsValidControlFlowGraph()
コード例 #11
0
def test_ControlFlowGraph_Reachables_simple_graph():
  """An empty graph has no reachables."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #   A  ->  B  ->  C
  g.add_edge(0, 1)
  g.add_edge(1, 2)
  assert list(g.Reachables(0)) == [False, True, True]
  assert list(g.Reachables(1)) == [False, False, True]
  assert list(g.Reachables(2)) == [False, False, False]
コード例 #12
0
def test_ControlFlowGraph_IsValidControlFlowGraph_invalid_degrees():
  """Test that a graph where two nodes could be fused is invalid."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #      A --> B --> C
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C', exit=True)
  g.add_edge(0, 1)
  g.add_edge(1, 2)
  with pytest.raises(control_flow_graph.InvalidNodeDegree) as e_ctx:
    g.ValidateControlFlowGraph()
  assert str(e_ctx.value) == "outdegree(A) = 1, indegree(B) = 1"
  assert not g.IsValidControlFlowGraph()
コード例 #13
0
def test_ControlFlowGraph_Reachables_back_edge():
  """Test reachability with a back edge in the graph."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #   A  ->  B  ->  C
  #   ^      |
  #   +------+
  g.add_edge(0, 1)
  g.add_edge(1, 0)
  g.add_edge(1, 2)
  # FIXME(cec): I don't belive these values.
  assert list(g.Reachables(0)) == [False, True, True]
  assert list(g.Reachables(1)) == [True, False, True]
  assert list(g.Reachables(2)) == [False, False, False]
コード例 #14
0
def test_ControlFlowGraph_IsValidControlFlowGraph_while_loop():
  """Test that a while loop graph is valid."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +--------+
  #     |        |
  #     v        |
  #     A+------>B       C
  #     |                ^
  #     |                |
  #     +----------------+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C', exit=True)
  g.add_edge(0, 1)
  g.add_edge(1, 0)
  g.add_edge(0, 2)
  assert g.ValidateControlFlowGraph() == g
  assert g.IsValidControlFlowGraph()
コード例 #15
0
def test_ControlFlowGraph_exit_block():
  """Test exit block."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----> B -----+
  #     |             |
  #     |             v
  #     A             D
  #     |             ^
  #     |             |
  #     +----> C -----+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C')
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(0, 2)
  g.add_edge(1, 3)
  g.add_edge(2, 3)
  assert g.exit_block == 3
コード例 #16
0
def test_ControlFlowGraph_IsValidControlFlowGraph_if_else_loop():
  """Test that an if-else loop graph is valid."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----> B -----+
  #     |             |
  #     |             v
  #     A             D
  #     |             ^
  #     |             |
  #     +----> C -----+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C')
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(0, 2)
  g.add_edge(1, 3)
  g.add_edge(2, 3)
  assert g.ValidateControlFlowGraph() == g
  assert g.IsValidControlFlowGraph()
コード例 #17
0
def test_ControlFlowGraph_IsValidControlFlowGraph_while_loop_with_exit():
  """Test that a while loop with an if branch exit is valid."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----------------+
  #     |                |
  #     v                |
  #     A+------>B+----->C       D
  #     |        |               ^
  #     |        |               |
  #     +------->+---------------+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C')
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(1, 2)
  g.add_edge(2, 0)
  g.add_edge(0, 3)
  g.add_edge(1, 3)
  assert g.ValidateControlFlowGraph() == g
  assert g.IsValidControlFlowGraph()
コード例 #18
0
def test_ControlFlowGraph_IsValidControlFlowGraph_unamed_nodes():
  """Test that all nodes in a graph must have a name."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----> B -----+
  #     |             |
  #     |             v
  #     A             D
  #     |             ^
  #     |             |
  #     +---->   -----+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2)
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(0, 2)
  g.add_edge(1, 3)
  g.add_edge(2, 3)
  with pytest.raises(control_flow_graph.MissingNodeName) as e_ctx:
    g.ValidateControlFlowGraph()
  assert str(e_ctx.value) == "Node 2 has no name"
  assert not g.IsValidControlFlowGraph()
コード例 #19
0
def test_ControlFlowGraph_IsValidControlFlowGraph_duplicate_names():
  """Test that duplicate names is an error."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #
  #     +----> B -----+
  #     |             |
  #     |             v
  #     A             D
  #     |             ^
  #     |             |
  #     +----> B -----+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='B')
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(0, 2)
  g.add_edge(1, 3)
  g.add_edge(2, 3)
  with pytest.raises(control_flow_graph.DuplicateNodeName) as e_ctx:
    g.ValidateControlFlowGraph()
  assert str(e_ctx.value) == "Duplicate node name 'B'"
  assert not g.IsValidControlFlowGraph()
コード例 #20
0
def test_ControlFlowGraph_IsValidControlFlowGraph_irreducible_loop():
  """Test that an irreducible graph is valid."""
  g = control_flow_graph.ControlFlowGraph()
  # Graph:
  #              +-------+
  #              |       |
  #              v       |
  #     A------->B+----->C
  #     |        |       ^
  #     |        |       |
  #     |        v       |
  #     |        D       |
  #     |                |
  #     +----------------+
  g.add_node(0, name='A', entry=True)
  g.add_node(1, name='B')
  g.add_node(2, name='C')
  g.add_node(3, name='D', exit=True)
  g.add_edge(0, 1)
  g.add_edge(1, 2)
  g.add_edge(2, 1)
  g.add_edge(1, 3)
  assert g.ValidateControlFlowGraph() == g
  assert g.IsValidControlFlowGraph()
コード例 #21
0
def test_ControlFlowGraph_Reachables_empty_graph():
  """An empty graph has no reachables."""
  g = control_flow_graph.ControlFlowGraph()
  assert list(g.Reachables(0)) == []
コード例 #22
0
def test_ControlFlowGraph_IsReachable_indirectly_reachable():
  """Test indirectly reachable node."""
  g = control_flow_graph.ControlFlowGraph()
  g.add_edge(0, 1)
  g.add_edge(1, 2)
  assert g.IsReachable(0, 2)
コード例 #23
0
def test_ControlFlowGraph_IsReachable_non_existent_node_raises_error():
  """Test that error is raised if node is not in graph."""
  g = control_flow_graph.ControlFlowGraph()
  with pytest.raises(nx.exception.NetworkXError):
    g.IsReachable(1, 0)
コード例 #24
0
def test_ControlFlowGraph_IsReachable_unreachable():
  """Test unreachable node."""
  g = control_flow_graph.ControlFlowGraph()
  g.add_edge(0, 1)
  assert not g.IsReachable(1, 0)
コード例 #25
0
ファイル: llvm_util.py プロジェクト: SpringRi/phd
def ControlFlowGraphFromDotSource(dot_source: str) -> cfg.ControlFlowGraph:
    """Create a control flow graph from an LLVM-generated dot file.

  The control flow graph generated from the dot source is not guaranteed to
  be valid. That is, it may contain fusible basic blocks. This can happen if
  the creating the graph from unoptimized bytecode. To disable this generate
  the bytecode with optimizations enabled, e.g. clang -emit-llvm -O3 -S ...

  Args:
    dot_source: The dot source generated by the LLVM -dot-cfg pass.

  Returns:
    A ControlFlowGraph instance.

  Raises:
    pyparsing.ParseException: If dotfile could not be parsed.
    ValueError: If dotfile could not be interpretted / is malformed.
  """
    try:
        parsed_dots = pydot.graph_from_dot_data(dot_source)
    except TypeError as e:
        raise pyparsing.ParseException("Failed to parse dot source") from e

    if len(parsed_dots) != 1:
        raise ValueError(f"Expected 1 Dot in source, found {len(parsed_dots)}")

    dot = parsed_dots[0]

    graph_re_match = re.match(r"\"CFG for '(\w+)' function\"", dot.get_name())
    if not graph_re_match:
        raise ValueError(f"Could not interpret graph name '{dot.get_name()}'")

    # Create the ControlFlowGraph instance.
    graph = cfg.ControlFlowGraph(name=graph_re_match.group(1))

    # Create the nodes and build a map from node names to indices.
    node_name_to_index_map = {}
    for i, node in enumerate(dot.get_nodes()):
        if node.get_name() in node_name_to_index_map:
            raise ValueError(f"Duplicate node name! '{node.get_name()}'")
        node_name_to_index_map[node.get_name()] = i
        # TODO(cec): Add node label code string.
        graph.add_node(i,
                       name=GetBasicBlockNameFromLabel(
                           node.get_attributes()['label']))

    def NodeIndex(node: pydot.Node) -> int:
        """Get the index of a node."""
        return node_name_to_index_map[node.get_name()]

    first_node_name = sorted(node_name_to_index_map.keys())[0]
    entry_block = dot.get_node(first_node_name)[0]
    graph.nodes[NodeIndex(entry_block)]['entry'] = True

    def IsExitNode(node: pydot.Node) -> bool:
        """Determine if the given node is an exit block.

    In LLVM bytecode, an exit block is one in which the final instruction begins
    with 'ret '. There should be only one exit block per graph.
    """
        label = node.get_attributes().get('label', '')
        # Node labels use \l to escape newlines.
        label_lines = label.split('\l')
        # The very last line is just a closing brace.
        last_line_with_instructions = label_lines[-2]
        return last_line_with_instructions.lstrip().startswith('ret ')

    # Set the exit node.
    exit_nodes = []
    for node in dot.get_nodes():
        if IsExitNode(node):
            exit_nodes.append(node)

    if len(exit_nodes) != 1:
        raise ValueError("Couldn't find an exit block")

    graph.nodes[NodeIndex(exit_nodes[0])]['exit'] = True

    for edge in dot.get_edges():
        # In the dot file, an edge looks like this:
        #     Node0x7f86c670c590:s0 -> Node0x7f86c65001a0;
        # We split the :sX suffix from the source to get the node name.
        # TODO(cec): We're currently throwing away the subrecord information and
        # True/False labels on edges. We may want to preserve that here.
        src = node_name_to_index_map[edge.get_source().split(':')[0]]
        dst = node_name_to_index_map[edge.get_destination()]
        graph.add_edge(src, dst)

    return graph
コード例 #26
0
    def GenerateOne(self) -> cfg.ControlFlowGraph:
        """Create a random CFG.

    Returns:
      A ControlFlowGraph instance.
    """
        # Sample the number of nodes to put in the graph, unless min == max.
        if self._num_nodes_min_max[0] == self._num_nodes_min_max[1]:
            num_nodes = self._num_nodes_min_max[0]
        else:
            num_nodes = self._rand.randint(*self._num_nodes_min_max)

        # Generate the graph and create the named nodes.
        graph = cfg.ControlFlowGraph(name=next(self._graph_name_sequence))
        node_name_sequence = UniqueNameSequence('A')
        [
            graph.add_node(i, name=next(node_name_sequence))
            for i in range(num_nodes)
        ]

        # Set the entry and exit blocks.
        entry_block = 0
        exit_block = num_nodes - 1
        graph.nodes[entry_block]['entry'] = True
        graph.nodes[exit_block]['exit'] = True

        # Generate an adjacency matrix of random binary values.
        adjacency_matrix = self._rand.rand(num_nodes, num_nodes)
        adjacency_matrix = np.rint(adjacency_matrix).astype(np.int32)

        # Add the edges to the graph, subject to the contraints of CFGs.
        def NotSelfLoop(i, j):
            """Self loops are not allowed in CFGs."""
            return i != j

        def NotExitNodeOutput(i, j):
            """Outputs are not allowed from the exit block."""
            del j
            return i != exit_block

        def IsEdge(i, j):
            """Return whether edge is set in adjacency matrix."""
            return adjacency_matrix[i, j]

        for j, row in enumerate(adjacency_matrix):
            for i in row:
                # CFG nodes cannot be connected to themselves.
                if NotSelfLoop(i, j) and NotExitNodeOutput(i, j) and IsEdge(
                        i, j):
                    graph.add_edge(i, j)

        # Apply finishing touches to the graph to make it a valid CFG.
        def AddRandomEdge(src):
            """Add an outgoing edge from src to a random destination."""
            dst = src
            while dst == src:
                dst = self._rand.randint(0, num_nodes)
            graph.add_edge(src, dst)

        def AddRandomIncomingEdge(dst):
            """Add an incoming edge to dst from a random source."""
            src = dst
            while src == dst or src == exit_block:
                src = self._rand.randint(0, num_nodes)
            graph.add_edge(src, dst)

        # Make sure that every node has one output. This will also include
        modified = True
        while modified:
            for node in graph.nodes:
                if NotExitNodeOutput(node, 0) and not graph.out_degree(node):
                    AddRandomEdge(node)
                    break
            else:
                modified = False

        modified = True
        while modified:
            for src, dst in graph.edges:
                if not (graph.out_degree(src) > 1
                        or graph.in_degree(dst) > 1) and (NotExitNodeOutput(
                            src, dst)):
                    AddRandomEdge(src)
                    break
            else:
                # We iterated through all nodes without making any modifications: we're
                # done.
                modified = False

        # Make sure the exit block has at least one incoming edge.
        if not graph.in_degree(exit_block):
            AddRandomIncomingEdge(exit_block)

        return graph.ValidateControlFlowGraph()