class SyntaxTree::YARV::DataFlowGraph

Constructs a data-flow-graph of a YARV instruction sequence, via a control-flow-graph. Data flow is discovered locally and then globally. The graph only considers data flow through the stack - local variables and objects are considered fully escaped in this analysis.

You can use this class by calling the ::compile method and passing it a control flow graph. It will return a data flow graph object.

iseq = RubyVM::InstructionSequence.compile("1 + 2")
iseq = SyntaxTree::YARV::InstructionSequence.from(iseq.to_a)
cfg = SyntaxTree::YARV::ControlFlowGraph.compile(iseq)
dfg = SyntaxTree::YARV::DataFlowGraph.compile(cfg)

Attributes

block_flows[R]
cfg[R]
insn_flows[R]

Public Class Methods

compile(cfg) click to toggle source
# File lib/syntax_tree/yarv/data_flow_graph.rb, line 204
def self.compile(cfg)
  Compiler.new(cfg).compile
end
new(cfg, insn_flows, block_flows) click to toggle source
# File lib/syntax_tree/yarv/data_flow_graph.rb, line 68
def initialize(cfg, insn_flows, block_flows)
  @cfg = cfg
  @insn_flows = insn_flows
  @block_flows = block_flows
end

Public Instance Methods

blocks() click to toggle source
# File lib/syntax_tree/yarv/data_flow_graph.rb, line 74
def blocks
  cfg.blocks
end
disasm() click to toggle source
# File lib/syntax_tree/yarv/data_flow_graph.rb, line 78
def disasm
  fmt = Disassembler.new(cfg.iseq)
  fmt.puts("== dfg: #{cfg.iseq.inspect}")

  blocks.each do |block|
    fmt.puts(block.id)
    fmt.with_prefix("    ") do |prefix|
      unless block.incoming_blocks.empty?
        from = block.incoming_blocks.map(&:id)
        fmt.puts("#{prefix}== from: #{from.join(", ")}")
      end

      block_flow = block_flows.fetch(block.id)
      unless block_flow.in.empty?
        fmt.puts("#{prefix}== in: #{block_flow.in.join(", ")}")
      end

      fmt.format_insns!(block.insns, block.block_start) do |_, length|
        insn_flow = insn_flows[length]
        next if insn_flow.in.empty? && insn_flow.out.empty?

        fmt.print(" # ")
        unless insn_flow.in.empty?
          fmt.print("in: #{insn_flow.in.join(", ")}")
          fmt.print("; ") unless insn_flow.out.empty?
        end

        unless insn_flow.out.empty?
          fmt.print("out: #{insn_flow.out.join(", ")}")
        end
      end

      to = block.outgoing_blocks.map(&:id)
      to << "leaves" if block.insns.last.leaves?
      fmt.puts("#{prefix}== to: #{to.join(", ")}")

      unless block_flow.out.empty?
        fmt.puts("#{prefix}== out: #{block_flow.out.join(", ")}")
      end
    end
  end

  fmt.string
end
to_mermaid() click to toggle source
# File lib/syntax_tree/yarv/data_flow_graph.rb, line 127
def to_mermaid
  Mermaid.flowchart do |flowchart|
    disasm = Disassembler::Squished.new

    blocks.each do |block|
      block_flow = block_flows.fetch(block.id)
      graph_name =
        if block_flow.in.any?
          "#{block.id} #{block_flows[block.id].in.join(", ")}"
        else
          block.id
        end

      flowchart.subgraph(graph_name) do
        previous = nil

        block.each_with_length do |insn, length|
          node =
            flowchart.node(
              "node_#{length}",
              "%04d %s" % [length, insn.disasm(disasm)],
              shape: :rounded
            )

          flowchart.link(previous, node, color: :red) if previous
          insn_flows[length].in.each do |input|
            if input.is_a?(LocalArgument)
              from = flowchart.fetch("node_#{input.length}")
              flowchart.link(from, node, color: :green)
            end
          end

          previous = node
        end
      end
    end

    blocks.each do |block|
      block.outgoing_blocks.each do |outgoing|
        offset =
          block.block_start + block.insns.sum(&:length) -
            block.insns.last.length

        from = flowchart.fetch("node_#{offset}")
        to = flowchart.fetch("node_#{outgoing.block_start}")
        flowchart.link(from, to, color: :red)
      end
    end
  end
end
to_son() click to toggle source
# File lib/syntax_tree/yarv/data_flow_graph.rb, line 123
def to_son
  SeaOfNodes.compile(self)
end
verify() click to toggle source

Verify that we constructed the data flow graph correctly.

# File lib/syntax_tree/yarv/data_flow_graph.rb, line 179
def verify
  # Check that the first block has no arguments.
  raise unless block_flows.fetch(blocks.first.id).in.empty?

  # Check all control flow edges between blocks pass the right number of
  # arguments.
  blocks.each do |block|
    block_flow = block_flows.fetch(block.id)

    if block.outgoing_blocks.empty?
      # With no outgoing blocks, there should be no output arguments.
      raise unless block_flow.out.empty?
    else
      # Check with outgoing blocks...
      block.outgoing_blocks.each do |outgoing_block|
        outgoing_flow = block_flows.fetch(outgoing_block.id)

        # The block should have as many output arguments as the
        # outgoing block has input arguments.
        raise unless block_flow.out.size == outgoing_flow.in.size
      end
    end
  end
end