Class: Laser::Analysis::ControlFlow::GraphBuilder

Inherits:
Object
  • Object
show all
Defined in:
lib/laser/analysis/control_flow/cfg_builder.rb

Overview

This class builds a control flow graph. The algorithm used is derived from Robert Morgan's “Building an Optimizing Compiler”.

Instance Attribute Summary (collapse)

Instance Method Summary (collapse)

Constructor Details

- (GraphBuilder) initialize(sexp, formals = [], scope = Scope::GlobalScope)



10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 10

def initialize(sexp, formals=[], scope=Scope::GlobalScope)
  @sexp = sexp
  @formals = formals
  @graph = @enter = @exit = nil
  @scope_stack = [scope]
  @self_stack = []
  @temporary_counter = 0
  @temporary_table = Hash.new do |hash, keys|
    @temporary_counter += 1
    hash[keys] = Bindings::TemporaryBinding.new("%t#{@temporary_counter}", nil)
  end
end

Instance Attribute Details

- (Object) current_block (readonly)

Returns the value of attribute current_block



7
8
9
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 7

def current_block
  @current_block
end

- (Object) enter (readonly)

Returns the value of attribute enter



7
8
9
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 7

def enter
  @enter
end

- (Object) exit (readonly)

Returns the value of attribute exit



7
8
9
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 7

def exit
  @exit
end

- (Object) graph (readonly)

Returns the value of attribute graph



7
8
9
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 7

def graph
  @graph
end

- (Object) self_register (readonly)

Returns the value of attribute self_register



8
9
10
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 8

def self_register
  @self_register
end

- (Object) sexp (readonly)

Returns the value of attribute sexp



7
8
9
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 7

def sexp
  @sexp
end

- (Object) temporary_counter (readonly)

Returns the value of attribute temporary_counter



7
8
9
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 7

def temporary_counter
  @temporary_counter
end

Instance Method Details

- (Object) build



23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 23

def build
  initialize_graph
  @current_node = @sexp
  @self_register = Bindings::TemporaryBinding.new('self', current_scope.self_ptr)
  build_prologue
  result = walk_node @sexp, value: true
  if @sexp.type == :program
    uncond_instruct @current_return
  else
    return_uncond_jump_instruct result
  end
  
  @graph.prune_totally_useless_blocks
  @graph
end

- (Object) build_block_with_jump(target = nil, name = nil)

Creates a new block that jumps to the given target upon completion. Very useful for building branches.



262
263
264
265
266
267
268
269
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 262

def build_block_with_jump(target = nil, name = nil)
  new_block = name ? create_block(name) : create_block
  with_current_basic_block(new_block) do
    yield
    uncond_instruct target if target
  end
  new_block
end

- (Object) build_exception_blocks



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 102

def build_exception_blocks
  @return_register = create_temporary('t#return_value')
  @graph.final_return    = create_temporary('t#final_return')
  @exception_register    = create_temporary('t#exception_value')
  @graph.final_exception = create_temporary('t#exit_exception')
  @current_return = create_block(ControlFlowGraph::RETURN_POSTDOMINATOR_NAME)
  @current_rescue = create_block(ControlFlowGraph::EXCEPTION_POSTDOMINATOR_NAME)
  @current_yield_fail = create_block(ControlFlowGraph::YIELD_POSTDOMINATOR_NAME)
  joined = create_block(ControlFlowGraph::FAILURE_POSTDOMINATOR_NAME)
  with_current_basic_block(@current_rescue) do
    uncond_instruct joined, flags: RGL::ControlFlowGraph::EDGE_ABNORMAL
  end
  with_current_basic_block(@current_yield_fail) do
    uncond_instruct joined, flags: RGL::ControlFlowGraph::EDGE_ABNORMAL
  end
  with_current_basic_block(joined) do
    copy_instruct(@graph.final_exception, @exception_register)
    add_instruction(:raise, @graph.final_exception)
    uncond_instruct @exit, flags: RGL::ControlFlowGraph::EDGE_ABNORMAL, jump_instruct: false
  end
  with_current_basic_block(@current_return) do
    copy_instruct(@graph.final_return, @return_register)
    add_instruction(:return, @graph.final_return)
    uncond_instruct @exit, jump_instruct: false
  end
end

- (Object) build_formal_args(formals, opts = {})



179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 179

def build_formal_args(formals, opts={})
  formals.each { |formal| current_scope.add_binding!(formal) }

  nb_formals = formals.last.is_block? ? formals[0..-2] : formals
  has_rest  = rest_arg = nb_formals.find(&:is_rest?)
  min_arity = nb_formals.count(&:is_positional?)
  optionals = nb_formals.select(&:is_optional?)
  num_optionals = optionals.size
  min_nonrest_arity = min_arity + num_optionals
  # zero dynamic args = easy and more efficient case
  if !rest_arg && optionals.empty?
    copy_positionals(nb_formals)
  else
    # pre-dynamic positionals
    cur_arity = call_instruct(ClassRegistry['Laser#Magic'].binding,
        :current_arity, value: true, raise: false)
    pre_dynamic_positional = nb_formals.take_while(&:is_positional?)
    num_pre_dynamics = pre_dynamic_positional.size
    copy_positionals(pre_dynamic_positional)
    # optional args
    previous_optional_block = nil
    optionals.each_with_index do |argument, index|
      max_arity_indicating_missing = const_instruct(index + min_arity)
      has_arg, no_arg = create_blocks 2
      if previous_optional_block
        with_current_basic_block(previous_optional_block) do
          uncond_instruct no_arg
        end
      end
      cond_result = call_instruct(cur_arity, :<, max_arity_indicating_missing,
                                  value: true, raise: false)
      cond_instruct cond_result, no_arg, has_arg
      
      start_block no_arg
      arg_value = walk_node(argument.default_value_sexp, value: true)
      copy_instruct(argument, arg_value)
      previous_optional_block = @current_block
      
      start_block has_arg
      copy_instruct(argument, formal_arg_at(const_instruct(index + num_pre_dynamics)))
    end
    
    optionals_done = create_block
    # rest args
    if has_rest
      rest_start = const_instruct(num_pre_dynamics + num_optionals)
      rest_size = call_instruct(cur_arity, :-, const_instruct(min_nonrest_arity), value: true, raise: false)
      copy_instruct(rest_arg, formal_arg_range(rest_start, rest_size))
    end
    uncond_instruct optionals_done

    if previous_optional_block
      with_current_basic_block(previous_optional_block) do
        # at this point, if there was a rest arg, it's empty.
        if has_rest
          empty_rest = call_instruct(ClassRegistry['Array'].binding, :[], value: true, raise: false)
          copy_instruct(rest_arg, empty_rest)
        end
        uncond_instruct optionals_done
      end
    end
    start_block optionals_done

    # post-dynamic conditionals
    post_dynamic_positional = nb_formals[num_pre_dynamics..-1].select(&:is_positional?)
    post_dynamic_start = call_instruct(cur_arity, :-,
        const_instruct(post_dynamic_positional.size), value: true, raise: false)
    copy_positionals_with_offset(post_dynamic_positional, post_dynamic_start)
  end
  block_arg = formals.find(&:is_block?)
  if block_arg
    if opts[:block_arg]
      the_block = opts[:block_arg]
    else
      the_block = call_instruct(ClassRegistry['Laser#Magic'].binding,
          :current_block, value: true, raise: false)
    end
    copy_instruct(block_arg, the_block)
  end
end

- (Object) build_prologue



134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 134

def build_prologue
  uncond_instruct create_block
  if @sexp.type == :program
    push_self(Scope::GlobalScope.self_ptr)
  else
    push_self(query_self)
  end
  reset_visibility_stack
  build_exception_blocks
  if @sexp.type != :program
    dynamic_context = ClosedScope.new(current_scope, current_self)
    @scope_stack = [dynamic_context]
    @block_arg = call_instruct(ClassRegistry['Laser#Magic'].binding,
        :current_block, value: true, raise: false)
    @block_arg.name = 't#current_block'
    @graph.block_register = @block_arg
    reobserve_current_exception
    build_formal_args(@formals, block_arg: @block_arg) unless @formals.empty?
  end
end

- (Object) copy_positionals(args)



165
166
167
168
169
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 165

def copy_positionals(args)
  args.each_with_index do |pos, idx|
    copy_instruct(pos, formal_arg_at(const_instruct(idx)))
  end
end

- (Object) copy_positionals_with_offset(args, offset)



171
172
173
174
175
176
177
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 171

def copy_positionals_with_offset(args, offset)
  args.each_with_index do |pos, idx|
    dynamic_idx = idx.zero? ? offset : call_instruct(const_instruct(idx), :+, offset,
        value: true, raise: false)
    copy_instruct(pos, formal_arg_at(dynamic_idx))
  end
end

- (Object) current_namespace



39
40
41
42
43
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 39

def current_namespace
  scope = current_scope
  scope = scope.parent if @scope_stack.size == 1 && @sexp.type != :program
  scope.lexical_target
end

- (Object) current_scope



49
50
51
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 49

def current_scope
  @scope_stack.last
end

- (Object) current_self



69
70
71
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 69

def current_self
  @self_stack.last
end

- (Object) formal_arg_at(idx)



160
161
162
163
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 160

def formal_arg_at(idx)
  call_instruct(ClassRegistry['Laser#Magic'].binding, :current_argument, idx,
      value: true, raise: false)
end

- (Object) formal_arg_range(start, size)



155
156
157
158
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 155

def formal_arg_range(start, size)
  call_instruct(ClassRegistry['Laser#Magic'].binding, :current_argument_range,
      start, size, value: true, raise: false)
end

- (Object) novalue_walk(node)

Walks the node expecting that the expression's return value will be discarded. Since everything is an expression in Ruby, knowing when to ignore return values is nice.



461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 461

def novalue_walk(node)
  with_current_node(node) do
    case node.type
    when :void_stmt
      # Do nothing.
    when :massign
      lhs, rhs = node.children
      multiple_assign_instruct(lhs, rhs, value: false)
    when :opassign
      lhs, op, rhs = node.children
      op = op.expanded_identifier[0..-2].to_sym
      if lhs.type == :field
        receiver = walk_node lhs[1], value: true
        method_name = lhs[3].expanded_identifier
        # Receiver is ONLY EVALUATED ONCE
        # (on ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-darwin10.6.0])
        current_val = call_instruct(receiver, method_name.to_sym, block: false, value: true)
        if op == :||"
          false_block, after = create_blocks 2
          cond_instruct(current_val, after, false_block)

          start_block false_block
          rhs_value = walk_node rhs, value: true
          call_instruct(receiver, "#{method_name}=".to_sym, rhs_value, block: false, value: false)
          uncond_instruct after
        
          start_block after
        elsif op == :&&"
          true_block, after = create_blocks 2
          cond_instruct(current_val, true_block, after)

          start_block true_block
          rhs_value = walk_node rhs, value: true
          call_instruct(receiver, "#{method_name}=".to_sym, rhs_value, block: false, value: false)
          uncond_instruct after
        
          start_block after
        else
          rhs_value = walk_node rhs, value: true
          temp_result = call_instruct(current_val, op, rhs_value, block: false, value: true)
          call_instruct(receiver, "#{method_name}=".to_sym, temp_result, block: false, value: false)
        end
      # TODO(adgar): aref_field
      else
        result = binary_instruct(lhs, op, rhs, value: true)
        single_assign_instruct(lhs, result)
      end
    when :case
      after = create_block
      argument, body = node.children
      argument_value = walk_node argument, value: true
    
      while body && body.type == :when
        when_opts, when_body, body = body.children
        when_body_block = create_block
        when_opts.each do |opt|
          after_fail = create_block
          condition_result = call_instruct(walk_node(opt, value: true), :===, argument_value, value: true)
          cond_instruct(condition_result, when_body_block, after_fail)
          start_block after_fail
        end
        all_fail = @current_block

        start_block when_body_block
        walk_body when_body, value: false
        uncond_instruct after
      
        start_block all_fail
      end
      if body && body.type == :else
        walk_body body[1], value: false
      end
      uncond_instruct after
    when :var_ref
      nil
    when :for
      lhs, receiver, body = node.children
      receiver_value = walk_node receiver, value: true
      if Symbol === lhs[0]
        # field or var_ref/const_ref
        case lhs.type
        when :field
          # TODO(adgar): generate calls
        else
          # just get the value
          arg_bindings = [lhs.binding]
          call_method_with_block(receiver_value, :each, [], arg_bindings, body, value: false)
        end
      else
        # TODO(adgar): multiple assign
      end
    when :string_embexpr
      node[1].each { |elt| walk_node(elt, value: false) }
    when :@CHAR, :@tstring_content, :@int, :@float, :@regexp_end, :symbol,
         :@label, :symbol_literal, :defined
      # do nothing
    when :string_literal
      content_nodes = node[1].children
      content_nodes.each do |node|
        walk_node node, value: false
      end
    when :xstring_literal
      body = build_string_instruct(node[1])
      call_instruct(self_register, :`, body, value: false)
    when :regexp_literal
      node[1].each { |part| walk_node node, value: false }
    when :dyna_symbol
      content_nodes = node[1].children
      content_nodes.each { |node| walk_node node, value: false }
    when :array
      receiver = ClassRegistry['Array'].binding
      generic_call_instruct(receiver, :[], node[1], false, value: false)
    when :hash
      if node[1]
        walk_node node[1], value: false
      else
        const_instruct({}, value: false)
      end
    when :assoclist_from_args, :bare_assoc_hash
      pairs = node[1]
      key_value_paired = pairs.map {|a, b| [walk_node(a, value: true), walk_node(b, value: true)] }.flatten
      receiver = ClassRegistry['Hash'].binding
      call_instruct(receiver, :[], *key_value_paired, block: false, value: false, raise: false)
    else
      raise ArgumentError.new("Unknown AST node type #{node.type.inspect}")
    end
  end
end

- (Object) observe_just_raised_exception



96
97
98
99
100
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 96

def observe_just_raised_exception
  cur_exception = call_instruct(ClassRegistry['Laser#Magic'].binding,
      :get_just_raised_exception, value: true, raise: false)
  copy_instruct(@exception_register, cur_exception)
end

- (Object) pop_scope



53
54
55
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 53

def pop_scope
  @scope_stack.pop
end

- (Object) pop_self



73
74
75
76
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 73

def pop_self
  @self_stack.pop
  copy_instruct(@self_register, current_self)
end

- (Object) push_scope(scope)



45
46
47
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 45

def push_scope(scope)
  @scope_stack.push scope
end

- (Object) push_self(obj)



64
65
66
67
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 64

def push_self(obj)
  copy_instruct(@self_register, obj)
  @self_stack.push obj
end

- (Object) query_self



85
86
87
88
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 85

def query_self
  call_instruct(ClassRegistry['Laser#Magic'].binding,
      :current_self, value: true, raise: false)
end

- (Object) reobserve_current_exception



90
91
92
93
94
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 90

def reobserve_current_exception
  cur_exception = call_instruct(ClassRegistry['Laser#Magic'].binding,
      :current_exception, value: true, raise: false)
  copy_instruct(@exception_register, cur_exception)
end

- (Object) reset_visibility_stack



129
130
131
132
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 129

def reset_visibility_stack
  initial = call_instruct(ClassRegistry['Array'].binding, :[], const_instruct(:private), value: true, raise: false)
  copy_instruct(Bootstrap::VISIBILITY_STACK, initial)
end

- (Object) value_walk(node)

Walks the node with the expectation that the return value will be used.



591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 591

def value_walk(node)
  with_current_node(node) do
    case node.type
    when :opassign
      lhs, op, rhs = node.children
      op = op.expanded_identifier[0..-2].to_sym
      if lhs.type == :field
        receiver = walk_node lhs[1], value: true
        method_name = lhs[3].expanded_identifier
        # Receiver is ONLY EVALUATED ONCE
        # (on ruby 1.9.2p136 (2010-12-25 revision 30365) [x86_64-darwin10.6.0])
        current_val = call_instruct(receiver, method_name.to_sym, block: false, value: true)
        if op == :||"
          result = create_temporary
          true_block, false_block, after = create_blocks 3
          cond_instruct(current_val, true_block, false_block)

          start_block true_block
          copy_instruct result, current_val
          uncond_instruct after

          start_block false_block
          rhs_value = walk_node rhs, value: true
          call_instruct(receiver, "#{method_name}=".to_sym, rhs_value, block: false, value: false)
          copy_instruct result, rhs_value
          uncond_instruct after
        
          start_block after
          result
        elsif op == :&&"
          result = create_temporary
          true_block, false_block, after = create_blocks 3
          cond_instruct(current_val, true_block, false_block)

          start_block true_block
          rhs_value = walk_node rhs, value: true
          call_instruct(receiver, "#{method_name}=".to_sym, rhs_value, block: false, value: false)
          copy_instruct result, rhs_value
          uncond_instruct after

          start_block false_block
          copy_instruct result, current_val
          uncond_instruct after
        
          start_block after
          result
        else
          rhs_value = walk_node rhs, value: true
          temp_result = call_instruct(current_val, op, rhs_value, block: false, value: true)
          call_instruct(receiver, "#{method_name}=".to_sym, temp_result, block: false, value: false)
          temp_result
        end
      # TODO(adgar): aref_field
      else
        result = binary_instruct(lhs, op, rhs, value: true)
        single_assign_instruct(lhs, result)
        result
      end
    when :var_field
      variable_instruct(node)
    when :var_ref
      if node[1].type == :@const
        const_lookup(node[1].expanded_identifier)
      elsif node[1].type == :@ident || node[1].expanded_identifier == 'self'
        variable_instruct(node)
      elsif node[1].type == :@kw
        const_instruct(node.constant_value)
      elsif node[1].type == :@ivar
        call_instruct(current_self, :instance_variable_get,
            const_instruct(node.expanded_identifier), value: true, ignore_privacy: true)
      elsif node[1].type == :@gvar
        call_instruct(ClassRegistry['Laser#Magic'].binding, :get_global,
            const_instruct(node.expanded_identifier), raise: false, value: true)
      end
    when :top_const_ref
      const = node[1]
      ident = const_instruct(const.expanded_identifier)
      call_instruct(ClassRegistry['Object'].binding,
          :const_get, ident, value: true)
    when :for
      lhs, receiver, body = node.children
      receiver_value = walk_node receiver, value: true
      if Symbol === lhs[0]
        # field or var_ref/const_ref
        case lhs.type
        when :field
          # call
        else
          # just get the value
          arg_bindings = [lhs.binding]
          call_method_with_block(receiver_value, :each, [], arg_bindings, body, value: true)
        end
      # TODO(adgar): aref_field
      else
        # TODO(adgar): multiple assign
      end
    when :case
      after = create_block
      result = create_temporary
      argument, body = node.children
      argument_value = walk_node argument, value: true
    
      while body && body.type == :when
        when_opts, when_body, body = body.children
        when_body_block = create_block
        when_opts.each do |opt|
          after_fail = create_block
          condition_result = call_instruct(walk_node(opt, value: true), :===, argument_value, value: true)
          cond_instruct(condition_result, when_body_block, after_fail)
          start_block after_fail
        end
        all_fail = @current_block

        start_block when_body_block
        when_body_result = walk_body when_body, value: true
        copy_instruct(result, when_body_result)
        uncond_instruct after
      
        start_block all_fail
      end
      if body.nil?
        copy_instruct(result, nil)
        uncond_instruct after
      elsif body.type == :else
        else_body_result = walk_body body[1], value: true
        copy_instruct(result, else_body_result)
        uncond_instruct after
      end
      
      start_block after
      result
    when :@CHAR, :@tstring_content, :@int, :@float, :@regexp_end, :symbol,
         :@label, :symbol_literal
      const_instruct(node.constant_value)
    when :string_literal
      content_nodes = node[1].children
      build_string_instruct(content_nodes)
    when :string_embexpr
      final = walk_body node[1], value: true
      call_instruct(final, :to_s, value: true)
    when :xstring_literal
      body = build_string_instruct(node[1])
      call_instruct(self_register, :`, body, value: true)
    when :regexp_literal
      body = build_string_instruct(node[1])
      options = const_instruct(node[2].constant_value)
      receiver = ClassRegistry['Regexp'].binding
      call_instruct(receiver, :new, body, options, value: true)
    when :dyna_symbol
      content_nodes = node[1].children
      string_version = build_string_instruct(content_nodes)
      call_instruct(string_version, :to_sym, value: true, raise: false)
    when :array
      receiver = ClassRegistry['Array'].binding
      generic_call_instruct(receiver, :[], node[1], false, value: true)
    when :hash
      if node[1]
        walk_node node[1], value: true
      else
        const_instruct({})
      end
    when :assoclist_from_args, :bare_assoc_hash
      pairs = node[1].map { |_, k, v| [k, v] }
      key_value_paired = pairs.map {|a, b| [walk_node(a, value: true), walk_node(b, value: true)] }.flatten
      receiver = ClassRegistry['Hash'].binding
      call_instruct(receiver, :[], *key_value_paired, block: false, value: true)
    when :defined
      defined_op_instruct(node[1])
    else
      raise ArgumentError.new("Unknown AST node type #{node.type.inspect}")
    end
  end
end

- (Object) walk_node(node, opts = {})

Walks the node differently based on whether the value is needed.



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 290

def walk_node(node, opts={})
  with_current_node(node) do
    case node.type
    when :bodystmt
      bodystmt_walk node
    when :class
      class_name, superclass, body = node.children
      class_instruct(class_name, superclass, body, opts)
    when :module
      module_name, body = node.children
      module_instruct(module_name, body, opts)
    when :sclass
      receiver, body = node.children
      singleton_class_instruct receiver, body, opts
    when :def
      name, args, body = node.children
      name = const_instruct(name.expanded_identifier.to_sym)
      parsed_args = Signature.arg_list_for_arglist(args)
      def_opts =  opts.merge(line_number: node.line_number)
      def_instruct(current_namespace, name, parsed_args, body, def_opts)
    when :defs
      recv, _, name, args, body = node.children
      name = const_instruct(name.expanded_identifier.to_sym)
      receiver = walk_node(recv, value: true)
      singleton = call_instruct(receiver, :singleton_class, value: true)
      parsed_args = Signature.arg_list_for_arglist(args)
      def_opts =  opts.merge(line_number: node.line_number)
      def_instruct(singleton, name, parsed_args, body, def_opts)
    when :alias
      lhs, rhs = node.children
      lhs_val = const_instruct(lhs[1].expanded_identifier.to_sym)
      rhs_val = const_instruct(rhs[1].expanded_identifier.to_sym)
      call_instruct(current_namespace, :alias_method, lhs_val, rhs_val,
                    value: false, ignore_privacy: true)
    when :undef
      undeffed = node.children.first
      undeffed.each do |method|
        method_name = const_instruct(method[1].expanded_identifier.to_sym)
        call_instruct(current_namespace, :undef_method, method_name,
            value: false, raise: true)
      end
      nil
    when :assign
      lhs, rhs = node.children
      single_assign_instruct(lhs, rhs, opts)
    when :massign
      lhs, rhs = node.children
      multiple_assign_instruct(lhs, rhs, opts)
    when :begin
      walk_node node[1], opts
    when :paren
      walk_body node[1], opts
    when :while
      condition, body = node.children
      while_instruct(condition, body, opts)
    when :while_mod
      condition, body_stmt = node.children
      while_instruct(condition, [body_stmt], opts)
    when :until
      condition, body = node.children
      until_instruct(condition, body, opts)
    when :until_mod
      condition, body_stmt = node.children
      until_instruct(condition, [body_stmt], opts)
    when :if
      if_instruct(node, false, opts)
    when :unless
      condition, body, else_block = node.children
      unless_instruct(condition, body, else_block, opts)
    when :if_mod
      if_instruct(node, true, opts)
    when :unless_mod
      condition, body = node.children
      unless_instruct(condition, [body], nil, opts)
    when :rescue_mod
      rescue_expr, guarded_expr = node.children
      rescue_mod_instruct(rescue_expr, guarded_expr, opts)
    when :unary
      op, receiver = node.children
      receiver = walk_node(receiver, value: true)
      call_instruct(receiver, op, opts)
    when :binary
      # If someone makes an overloaded operator that mutates something....
      # we have to run it (maybe), even if we hate them.
      lhs, op, rhs = node.children
      binary_instruct(lhs, op, rhs, opts)
    when :ifop
      cond, if_true, if_false = node.children
      ternary_instruct(cond, if_true, if_false, opts)
    when :const_path_ref
      lhs, const = node.children
      lhs_value = walk_node lhs, value: true
      ident = const_instruct(const.expanded_identifier)
      call_instruct(lhs_value, :const_get, ident, opts)
    when :call, :command, :command_call, :aref, :method_add_arg, :vcall
      issue_call node, opts
    when :method_add_block
      # need: the receiver, the method name, the arguments, and the block body
      method_call = node.method_call
      receiver = if method_call.receiver_node
                 then walk_node(method_call.receiver_node, value: true)
                 else self_instruct
                 end
      arg_node = method_call.arg_node
      arg_node = arg_node[1] if arg_node && arg_node.type == :arg_paren
      block_arg_bindings = node[2][1] ? Signature.arg_list_for_arglist(node[2][1][1]) : []
      body_sexp = node[2][2]
      case node[1].type
      when :super
        arg_node = arg_node[1] if arg_node.type == :args_add_block
        call_method_with_block(
            receiver, method_call.method_name, arg_node,
            block_arg_bindings, body_sexp, opts)
      when :zsuper
        call_zsuper_with_block(node[1], block_arg_bindings, body_sexp, opts)
      else
        opts = opts.merge(ignore_privacy: true) if method_call.implicit_receiver?
        call_method_with_block(
            receiver, method_call.method_name, arg_node, block_arg_bindings, body_sexp, opts)
      end
    when :super
      issue_super_call(node)
    when :zsuper
      # TODO(adgar): blocks in args & style
      block = rb_check_convert_type(@block_arg, ClassRegistry['Proc'].binding, :to_proc)
      invoke_super_with_block(*compute_zsuper_arguments, block, opts)
    when :yield
      yield_instruct(node[1], opts)
    when :yield0
      yield_instruct(nil, opts)
    when :return
      return_instruct node
      const_instruct(nil) if opts[:value]
    when :return0
      return0_instruct
      const_instruct(nil) if opts[:value]
    when :break
      break_instruct(node[1])
      const_instruct(nil) if opts[:value]
    when :next
      next_instruct(node[1])
      const_instruct(nil) if opts[:value]
    when :redo
      redo_instruct
      const_instruct(nil) if opts[:value]
    when :void_stmt
      const_instruct(nil) if opts[:value]
    when :program
      uncond_instruct create_block
      walk_body node[1], value: false
    when :dot3
      start, stop = node.children
      start_val = walk_node(start, value: true)
      stop_val = walk_node(stop, value: true)
      true_val = const_instruct(true)
      call_instruct(ClassRegistry['Range'].binding, :new, start_val, stop_val, true_val, opts)
    when :dot2
      start, stop = node.children
      start_val = walk_node(start, value: true)
      stop_val = walk_node(stop, value: true)
      false_val = const_instruct(false)
      call_instruct(ClassRegistry['Range'].binding, :new, start_val, stop_val, false_val, opts)
    else
      opts[:value] ? value_walk(node) : novalue_walk(node)
    end
  end
end

- (Object) with_current_basic_block(basic_block)

yields with the current basic block set to the provided basic block. useful for quickly adding an edge without directly touching the graph object.



274
275
276
277
278
279
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 274

def with_current_basic_block(basic_block)
  old_block, @current_block = @current_block, basic_block
  yield
ensure
  @current_block = old_block
end

- (Object) with_current_node(node)



281
282
283
284
285
286
287
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 281

def with_current_node(node)
  old_node, @current_node = @current_node, node
  @current_node.scope = current_scope
  yield
ensure
  @current_node = old_node
end

- (Object) with_scope(scope)



57
58
59
60
61
62
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 57

def with_scope(scope)
  push_scope scope
  yield
ensure
  pop_scope
end

- (Object) with_self(namespace)



78
79
80
81
82
83
# File 'lib/laser/analysis/control_flow/cfg_builder.rb', line 78

def with_self(namespace)
  push_self namespace
  yield
ensure
  pop_self
end