<commit_before>require 'stringio'

module Furnace::AVM2::ABC
  class OpcodeSequence < ::Array
    attr_reader :root, :parent

    def initialize(options={})
      @root, @parent = options[:parent].root, options[:parent]
      @pos_cache    = {}
      @opcode_cache = {}

      @raw_code = nil
    end

    def read(io)
      @raw_code = io.read(@parent.code_length)
    end

    def write(io)
      if @raw_code
        io.write @raw_code
      else
        lookup!

        each do |opcode|
          opcode.write(io)
        end
      end
    end

    def each
      parse if @raw_code

      super
    end

    def map
      parse if @raw_code

      super
    end

    # Offsets

    def recache!
      flush!

      pos = 0
      each do |opcode|
        @pos_cache[pos]       = opcode
        @opcode_cache[opcode] = pos

        pos += opcode.byte_length
      end

      lookup!
    end

    def flush!
      @pos_cache    = {}
      @opcode_cache = {}
    end

    def opcode_at(position)
      @pos_cache[position]
    end

    def offset_of(opcode)
      @opcode_cache[opcode]
    end

    def byte_length
      map(&:byte_length).reduce(0, :+)
    end

    # Transformations

    def disassemble
      map(&:disassemble).join("\n")
    end

    def build_cfg
      graph = CFG::Graph.new

      targets = []

      each do |opcode|
        if opcode.is_a? ControlTransferOpcode
          targets << opcode.target
        elsif opcode.is_a? AS3LookupSwitch
          targets << opcode.default_target
          targets += opcode.case_targets
        end
      end

      pending_label = nil
      pending_queue = []

      cutoff = lambda do |targets|
        node = CFG::Node.new(graph, pending_label, pending_queue, nil, targets)

        if graph.nodes.empty?
          graph.entry = node
        end

        graph.nodes.add node

        pending_label = nil
        pending_queue = []
      end

      each do |opcode|
        if targets.include? opcode
          cutoff.([ opcode.offset ])
        end

        pending_label = opcode.offset if pending_label.nil?
        pending_queue << opcode

        if opcode.is_a? ControlTransferOpcode
          if opcode.conditional
            cutoff.([ opcode.target.offset, opcode.offset + opcode.byte_length ])
          else
            cutoff.([ opcode.target.offset ])
          end
        elsif opcode.is_a? AS3LookupSwitch
          cutoff.(opcode.parameters.flatten)
        end
      end

      cutoff.([])

      if exceptions.any?
        exception_node = CFG::Node.new(graph, :exception, [], nil,
            exceptions.map(&:target_offset))
      end

      graph
    end

    def eliminate_dead!
      cfg = build_cfg
      dead_opcodes = []

      cfg.nodes.each do |node|
        if node != cfg.entry && node.sources.empty?
          dead_opcodes.concat node.insns
        end
      end

      dead_opcodes.each do |opcode|
        delete opcode
      end

      recache!

      dead_opcodes.any?
    end

    protected

    def parse
      sub_io = StringIO.new(@raw_code)
      map    = Opcode::MAP

      until sub_io.eof?
        instruction = sub_io.read(1).unpack("C").at(0)

        opcode = map[instruction]
        if opcode.nil?
          raise "Unknown opcode 0x#{instruction.to_s(16)}"
        end

        element = opcode.new(self)

        @pos_cache[sub_io.pos - 1] = element<commit_msg>Fix DCE for exceptions and chained nodes.<commit_after>require 'stringio'

module Furnace::AVM2::ABC
  class OpcodeSequence < ::Array
    attr_reader :root, :parent

    def initialize(options={})
      @root, @parent = options[:parent].root, options[:parent]
      @pos_cache    = {}
      @opcode_cache = {}

      @raw_code = nil
    end

    def read(io)
      @raw_code = io.read(@parent.code_length)
    end

    def write(io)
      if @raw_code
        io.write @raw_code
      else
        lookup!

        each do |opcode|
          opcode.write(io)
        end
      end
    end

    def each
      parse if @raw_code

      super
    end

    def map
      parse if @raw_code

      super
    end

    # Offsets

    def recache!
      flush!

      pos = 0
      each do |opcode|
        @pos_cache[pos]       = opcode
        @opcode_cache[opcode] = pos

        pos += opcode.byte_length
      end

      lookup!
    end

    def flush!
      @pos_cache    = {}
      @opcode_cache = {}
    end

    def opcode_at(position)
      @pos_cache[position]
    end

    def offset_of(opcode)
      @opcode_cache[opcode]
    end

    def byte_length
      map(&:byte_length).reduce(0, :+)
    end

    # Transformations

    def disassemble
      map(&:disassemble).join("\n")
    end

    def build_cfg
      graph = CFG::Graph.new

      targets = []

      each do |opcode|
        if opcode.is_a? ControlTransferOpcode
          targets << opcode.target
        elsif opcode.is_a? AS3LookupSwitch
          targets << opcode.default_target
          targets += opcode.case_targets
        end
      end

      pending_label = nil
      pending_queue = []

      cutoff = lambda do |targets|
        node = CFG::Node.new(graph, pending_label, pending_queue, nil, targets)

        if graph.nodes.empty?
          graph.entry = node
        end

        graph.nodes.add node

        pending_label = nil
        pending_queue = []
      end

      each do |opcode|
        if targets.include? opcode
          cutoff.([ opcode.offset ])
        end

        pending_label = opcode.offset if pending_label.nil?
        pending_queue << opcode

        if opcode.is_a? ControlTransferOpcode
          if opcode.conditional
            cutoff.([ opcode.target.offset, opcode.offset + opcode.byte_length ])
          else
            cutoff.([ opcode.target.offset ])
          end
        elsif opcode.is_a? AS3LookupSwitch
          cutoff.(opcode.parameters.flatten)
        end
      end

      cutoff.([])

      if exceptions.any?
        exception_node = CFG::Node.new(graph, :exception, [], nil,
            exceptions.map(&:target_offset))
        graph.nodes.add exception_node
      end

      graph
    end

    def eliminate_dead!
      cfg = build_cfg
      dead_opcodes = []

      worklist = cfg.nodes.dup
      while worklist.any?
        node = worklist.first
        worklist.delete node

        next if node == cfg.entry

        if node.sources.count == 0 ||
              node.sources == [node]
          dead_opcodes.concat node.insns
        end
      end

      dead_opcodes.each do |opcode|
        delete opcode
      end

      recache!

      dead_opcodes.any?
    end

    protected

    def parse
      sub_io = StringIO.new(@raw_code)
      map    = Opcode::MAP

      until sub_io.eof?
        instruction = sub_io.read(1).unpack("C").at(0)

        opcode = map[instruction]
        if opcode.nil?
          raise "Unknown opcode 0x#{instruction.to_s(16)}"
        end

        element = opcode.new(self)

        @pos_cache[sub_io.pos - 1] = element
{'fraction_non_alphanumeric': 0.09111780743291721, 'fraction_numerical': 0.0033003300330033004, 'mean_word_length': 1.703646237393328, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5146491', 'n_tokens_mistral': 2234, 'n_tokens_neox': 2148, 'n_words': 489}