Class: Laser::Analysis::ControlFlow::ControlFlowGraph

Inherits:
RGL::ControlFlowGraph show all
Includes:
AliasAnalysis, ConstantPropagation, GuaranteedSuperDetection, LifetimeAnalysis, MethodCallSearch, RaiseProperties, Simulation, StaticSingleAssignment, UnreachabilityAnalysis, UnusedVariables, YieldProperties
Defined in:
lib/laser/analysis/control_flow/control_flow_graph.rb

Constant Summary

RETURN_POSTDOMINATOR_NAME =
'Return'
YIELD_POSTDOMINATOR_NAME =
'YieldWithoutBlock'
EXCEPTION_POSTDOMINATOR_NAME =
'UncaughtException'
FAILURE_POSTDOMINATOR_NAME =
'Failure'
DEFAULT_ANALYSIS_OPTS =
{optimize: true, simulate: true}

Constants included from Simulation

Simulation::DEFAULT_SIMULATION_OPTS, Simulation::MAX_SIMULATION_BLOCKS

Constants included from UnreachabilityAnalysis

UnreachabilityAnalysis::IGNORED_DEAD_CODE_NODES

Constants included from UnusedVariables

UnusedVariables::IGNORED_VARS

Constants inherited from RGL::ControlFlowGraph

RGL::ControlFlowGraph::EDGE_ABNORMAL, RGL::ControlFlowGraph::EDGE_BLOCK_TAKEN, RGL::ControlFlowGraph::EDGE_EXECUTABLE, RGL::ControlFlowGraph::EDGE_FAKE, RGL::ControlFlowGraph::EDGE_NORMAL

Instance Attribute Summary collapse

Attributes inherited from RGL::ControlFlowGraph

#enter, #exit, #vertex_lookup, #vertices

Instance Method Summary collapse

Methods included from GuaranteedSuperDetection

#guaranteed_super_on_success?

Methods included from Simulation

#reset_simulation!, #should_simulate_call, #simulate, #simulate_args, #simulate_assignment, #simulate_block, #simulate_call, #simulate_call_dispatch, #simulate_call_instruction, #simulate_define_method, #simulate_deterministic_phi_node, #simulate_exit_instruction, #simulate_instruction, #simulate_method_missing, #simulate_module_eval, #simulate_require, #simulate_special_method, #simulate_take_step

Methods included from RaiseProperties

#find_raise_frequency, #find_raise_properties

Methods included from MethodCallSearch

#find_method_calls

Methods included from YieldProperties

#calculate_yield_arity, #compute_yield_type, #find_yield_properties, #initial_block_aliases, #potential_block_calls

Methods included from AliasAnalysis

#value_aliases, #weak_local_aliases_for

Methods included from UnreachabilityAnalysis

#dfs_for_dead_code, #perform_dead_code_discovery, #unreachable_vertices

Methods included from UnusedVariables

#add_unused_variable_warnings, #killable_with_unused_target?, #simple_unused, #unused_variables

Methods included from StaticSingleAssignment

#static_single_assignment_form

Methods included from LifetimeAnalysis

#calculate_live

Methods included from ConstantPropagation

#perform_constant_propagation

Methods inherited from RGL::ControlFlowGraph

#[], #add_edge, #add_flag, #add_vertex, #degree, #directed?, #each_adjacent, #each_edge, #each_vertex, #edges, #empty?, #has_edge?, #has_vertex?, #in_degree, #is_abnormal?, #is_block_taken?, #is_executable?, #is_fake?, #num_edges, #num_vertices, #out_degree, #remove_edge, #remove_flag, #remove_vertex, #to_a, #vertex_with_name

Methods included from RGL::MutableGraph

#add_edge, #add_edges, #add_graph, #add_graphs, #add_vertex, #add_vertices, #cycles, #cycles_with_vertex, #from_graphxml, #remove_edge, #remove_vertex, #remove_vertices

Methods included from RGL::Graph

#acyclic?, #adjacent_vertices, #bfs_iterator, #bfs_search_tree_from, #build_dfst, #condensation_graph, #depth_first_search, #depth_first_spanning_tree, #depth_first_visit, #dfs_iterator, #directed?, #dominance_frontier, #dominator_tree, #each, #each_adjacent, #each_connected_component, #each_edge, #each_vertex, #edge_class, #edges, #edges_filtered_by, #empty?, #eql?, #has_vertex?, #implicit_graph, #iterated_dominance_frontier, #num_edges, #out_degree, #print_dotted_on, #reverse, #size, #strongly_connected_components, #to_adjacency, #to_dot_graph, #to_s, #to_undirected, #topsort_iterator, #transitive_closure, #transitive_reduction, #vertices, #vertices_filtered_by, #write_to_graphic_file

Methods included from Enumerable

#all?, #any?, #chunk, #collect, #collect_concat, #count, #cycle, #detect, #drop, #drop_while, #each_cons, #each_entry, #each_slice, #each_with_index, #each_with_object, #entries, #find_index, #first, #flat_map, #grep, #group_by, #include?, #inject, #map, #max, #max_by, #min, #min_by, #minmax, #minmax_by, #none?, #one?, #partition, #reject, #reverse_each, #select, #slice_before, #sort, #sort_by, #take, #take_while, #to_a, #to_set, #zip

Constructor Details

#initialize(formal_arguments = []) ⇒ ControlFlowGraph

Initializes the control flow graph, potentially with a list of argument bindings. Those bindings will almost always be extracted from parsing an argument list of a method or block.

formal_arguments: [ArgumentBinding]



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 38

def initialize(formal_arguments = [])
  @in_ssa = false
  @self_type = nil
  @formal_types = nil
  @block_type = nil
  @block_register = nil
  @all_cached_variables = Set.new
  @live = Hash.new { |hash, temp| hash[temp] = Set.new }
  @constants  = {}
  @globals = Set.new
  @name_stack = Hash.new { |hash, temp| hash[temp] = [] }
  @name_count = Hash.new { |hash, temp| hash[temp] = 0 }
  @formals = formal_arguments
  @yield_type = nil
  @yield_arity = nil
  @raise_frequency = Frequency::MAYBE
  @analyzed = false
  super()
end

Instance Attribute Details

#all_cached_variablesObject (readonly)

Returns the value of attribute all_cached_variables



29
30
31
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 29

def all_cached_variables
  @all_cached_variables
end

#analyzedObject (readonly)

Returns the value of attribute analyzed



29
30
31
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 29

def analyzed
  @analyzed
end

#block_registerObject

Returns the value of attribute block_register



25
26
27
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 25

def block_register
  @block_register
end

#block_typeObject (readonly)

Returns the value of attribute block_type



28
29
30
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 28

def block_type
  @block_type
end

#constantsObject (readonly)

Returns the value of attribute constants



26
27
28
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 26

def constants
  @constants
end

#definitionObject (readonly)

Returns the value of attribute definition



26
27
28
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 26

def definition
  @definition
end

#final_exceptionObject

Returns the value of attribute final_exception



25
26
27
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 25

def final_exception
  @final_exception
end

#final_returnObject

Returns the value of attribute final_return



25
26
27
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 25

def final_return
  @final_return
end

#formal_typesObject (readonly)

Returns the value of attribute formal_types



28
29
30
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 28

def formal_types
  @formal_types
end

#formalsObject (readonly)

Returns the value of attribute formals



26
27
28
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 26

def formals
  @formals
end

#globalsObject (readonly)

Returns the value of attribute globals



26
27
28
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 26

def globals
  @globals
end

#in_ssaObject (readonly)

Returns the value of attribute in_ssa



27
28
29
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 27

def in_ssa
  @in_ssa
end

#liveObject (readonly)

Returns the value of attribute live



26
27
28
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 26

def live
  @live
end

#raise_frequencyObject (readonly)

Returns the value of attribute raise_frequency



27
28
29
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 27

def raise_frequency
  @raise_frequency
end

#rootObject

Returns the value of attribute root



25
26
27
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 25

def root
  @root
end

#self_typeObject (readonly)

Returns the value of attribute self_type



28
29
30
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 28

def self_type
  @self_type
end

#usesObject (readonly)

Returns the value of attribute uses



26
27
28
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 26

def uses
  @uses
end

#yield_arityObject (readonly)

Returns the value of attribute yield_arity



27
28
29
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 27

def yield_arity
  @yield_arity
end

#yield_typeObject (readonly)

Returns the value of attribute yield_type



27
28
29
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 27

def yield_type
  @yield_type
end

Instance Method Details

#==(other) ⇒ Object

Compares the graphs for equality. Relies on the basic blocks having unique names to simplify isomorphism comparisons. Unfortunately this means tests will have to know block names. Oh well.



133
134
135
136
137
138
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 133

def ==(other)
  pairs = self.vertices.sort_by(&:name).zip(other.vertices.sort_by(&:name))
  (pairs.all? do |v1, v2|
    v1.name == v2.name && v1.instructions == v2.instructions
  end) && (self.edges.sort == other.edges.sort)
end

#all_errorsObject



268
269
270
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 268

def all_errors
  self.root.all_errors
end

#all_failure_postdominatorObject



301
302
303
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 301

def all_failure_postdominator
  vertex_with_name(FAILURE_POSTDOMINATOR_NAME)
end

#all_variablesObject

Returns the names of all variables in the graph



273
274
275
276
277
278
279
280
281
282
283
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 273

def all_variables
  return @all_cached_variables unless @all_cached_variables.empty?
  return @all_variables if @all_variables
  result = Set.new
  vertices.each do |vert|
    vert.variables.each do |var|
      result << var
    end
  end
  @all_variables = result
end

#analyze(opts = {}) ⇒ Object

Runs full analysis on the CFG. Puts it in SSA then searches for warnings.



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
259
260
261
262
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 214

def analyze(opts={})
  return if @analyzed
  @analyzed = true

  opts = DEFAULT_ANALYSIS_OPTS.merge(opts)
  # kill obvious dead code now.
  perform_dead_code_discovery(true)
  Laser.debug_puts('>>> Starting SSA Transformation <<<')
  static_single_assignment_form unless @in_ssa
  Laser.debug_puts('>>> Finished SSA Transformation <<<')
  if @root.type == :program
    Laser.debug_puts('>>> Starting Simulation <<<')
    begin
      simulate([], :mutation => true) if opts[:simulate]
    rescue Simulation::NonDeterminismHappened => err
      Laser.debug_puts('Note: Simulation was nondeterministic.')
    rescue Simulation::SimulationNonterminationError => err
      Laser.debug_puts('Note: Simulation was potentially nonterminating.')
    end
    Laser.debug_puts('>>> Finished Simulation <<<')
  else
    Laser.debug_puts('>>> Starting CP <<<')
    perform_constant_propagation(opts)
    Laser.debug_puts('>>> Finished CP <<<')
    if opts[:optimize]
      Laser.debug_puts('>>> Killing Unexecuted Edges <<<')
      kill_unexecuted_edges
      Laser.debug_puts('>>> Finished Killing Unexecuted Edges <<<')
      Laser.debug_puts('>>> Pruning Totally Useless Blocks <<<')
      prune_totally_useless_blocks
      Laser.debug_puts('>>> Finished Pruning Totally Useless Blocks <<<')
      Laser.debug_puts('>>> Dead Code Discovery <<<')
      perform_dead_code_discovery
      Laser.debug_puts('>>> Finished Dead Code Discovery <<<')
      Laser.debug_puts('>>> Adding Unused Variable Warnings <<<')
      add_unused_variable_warnings
      Laser.debug_puts('>>> Finished Adding Unused Variable Warnings <<<')

      if @root.type != :program
        Laser.debug_puts('>>> Determining Yield Properties <<<')
        find_yield_properties(opts)
        Laser.debug_puts('>>> Finished Determining Yield Properties <<<')
      end
      Laser.debug_puts('>>> Determining Raise Properties <<<')
      find_raise_properties
      Laser.debug_puts('>>> Finished Determining Raise Properties <<<')
    end
  end
end

#bind_block_type(new_block_type) ⇒ Object



208
209
210
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 208

def bind_block_type(new_block_type)
  @block_type = new_block_type
end

#bind_formal_types(new_formal_types) ⇒ Object



200
201
202
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 200

def bind_formal_types(new_formal_types)
  @formal_types = new_formal_types
end

#bind_self_type(new_self_type) ⇒ Object



184
185
186
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 184

def bind_self_type(new_self_type)
  @self_type = new_self_type
end

#bound_argument_types?Boolean



188
189
190
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 188

def bound_argument_types?
  !!@formal_types
end

#bound_arityObject



192
193
194
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 192

def bound_arity
  @formal_types.size
end

#clear_analysesObject



144
145
146
147
148
149
150
151
152
153
154
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 144

def clear_analyses
  all_variables.each do |temp|
    temp.bind! UNDEFINED
    temp.inferred_type = nil
  end
  vertices.each do |block|
    block.successors.each do |succ|
      block.remove_flag(succ, ControlFlowGraph::EDGE_EXECUTABLE)
    end
  end
end

#debug_dottyObject



264
265
266
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 264

def debug_dotty
  Laser.debug_dotty(self)
end

#dotty(params = {'shape' => 'box'}) ⇒ Object



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

def dotty(params = {'shape' => 'box'})
  super
end

#dupObject



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

def dup
  copy = self.class.new
  copy.initialize_dup(self)
  copy
end

#exception_postdominatorObject



297
298
299
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 297

def exception_postdominator
  vertex_with_name(EXCEPTION_POSTDOMINATOR_NAME)
end

#initialize_dup(source) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
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
128
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 64

def initialize_dup(source)
  @root = source.root
  @in_ssa = source.in_ssa
  @self_type = source.self_type
  @formal_types = source.formal_types
  @block_type = source.block_type
  # we'll be duplicating temporaries, and since we need to know how
  # our source data about temps (defs, uses, constants...) corresponds
  # the duplicated temps, we'll need a hash to look them up.
  temp_lookup = { Bootstrap::VISIBILITY_STACK => Bootstrap::VISIBILITY_STACK }
  block_lookup = { source.enter => @enter, source.exit => @exit }
  insn_lookup = Hash.new
  # copy all vars defined in the body
  source.all_cached_variables.each do |k|
    copy = k.deep_dup
    temp_lookup[k] = copy
    @all_cached_variables << copy
  end
  self.block_register = temp_lookup[source.block_register]
  self.final_exception = temp_lookup[source.final_exception]
  self.final_return = temp_lookup[source.final_return]
  # copy all formals and their mapping to initial bindings
  @formals = source.formals.map do |formal|
    temp_lookup[formal] = formal.deep_dup
  end

  new_blocks = source.vertices.reject { |v| TerminalBasicBlock === v }
  new_blocks.map! do |block|
    copy = block.duplicate_for_graph_copy(temp_lookup, insn_lookup)
    block_lookup[block] = copy
    copy
  end
  
  new_blocks.each do |new_block|
    add_vertex new_block
  end
  source.each_edge do |u, v|
    add_edge(block_lookup[u], block_lookup[v], u.get_flags(v))
  end
  
  # computed stuff we shouldn't lose:
  # @globals
  # @constants
  # @live
  # @formal_map
  temp_lookup.each do |old, new|
    new.definition = insn_lookup[old.definition]
    new.uses = Set.new(old.uses.map { |insn| insn_lookup[insn] })
  end
  source.globals.each do |global|
    @globals.add temp_lookup[global]
  end
  source.constants.each do |temp, value|
    # no need to dup value, as these constants *MUST NOT* be mutated.
    @constants[temp_lookup[temp]] = value
  end
  source.live.each do |temp, blocks|
    @live[temp_lookup[temp]] = Set.new(blocks.map { |block| block_lookup[block] })
  end
  # Tiny hope this speeds anything up
  temp_lookup.clear
  block_lookup.clear
  insn_lookup.clear
  self
end

#inspectObject



140
141
142
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 140

def inspect
  "<ControlFlowGraph num_verts=#{vertices.size}>"
end

#kill_unexecuted_edges(remove = false) ⇒ Object

Marks all edges that are not executable as fake edges. That way, the postdominance of Exit is preserved, but dead code analysis will ignore them.



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 356

def kill_unexecuted_edges(remove=false)
  killable = Set.new
  each_edge do |u, v|
    if !(is_executable?(u, v) || is_fake?(u, v))
      killable << [u, v]
    end
  end
  killable.each do |u, v|
    if remove
      remove_edge(u, v)
    else
      u.add_flag(v, EDGE_FAKE)
    end
  end
end

#prune_totally_useless_blocksObject

Removes blocks that are not reachable and which go nowhere: they have no effect on the program.



334
335
336
337
338
339
340
341
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 334

def prune_totally_useless_blocks
  vertices = self.to_a
  vertices.each do |vertex|
    if degree(vertex).zero?
      remove_vertex(vertex)
    end
  end
end

#prune_unexecuted_blocksObject



343
344
345
346
347
348
349
350
351
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 343

def prune_unexecuted_blocks
  kill_unexecuted_edges(true)  # be ruthless
  unreachable_vertices.each do |block|
    block.instructions.each do |insn|
      insn.operands.each { |op| op.uses -= ::Set[insn] }
    end
    remove_vertex block
  end
end

#raise_typeObject



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

def raise_type
  if !all_failure_postdominator || all_failure_postdominator.real_predecessors.empty?
    Types::EMPTY
  else
    @final_exception.expr_type
  end
end

#reachable_variables(block = @enter, visited = Set.new) ⇒ Object

Computes the variables reachable by DFSing the start node. This excludes variables defined in dead code.



321
322
323
324
325
326
327
328
329
330
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 321

def reachable_variables(block = @enter, visited = Set.new)
  visited << block
  block.real_successors.inject(block.variables) do |cur, succ|
    if visited.include?(succ)
      cur
    else
      cur | reachable_variables(succ, visited)
    end
  end
end

#reachable_verticesObject

Yields reachable vertices via depth-first-search on real edges



306
307
308
309
310
311
312
313
314
315
316
317
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 306

def reachable_vertices
  vertices = Set[]
  worklist = Set[self.enter]
  while worklist.any?
    block = worklist.pop
    yield block if block_given?
    block.real_successors.each do |succ|
      worklist.add(succ) if vertices.add?(succ)
    end
  end
  vertices
end

#real_block_typeObject



204
205
206
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 204

def real_block_type
  @block_type || Types::BLOCK
end

#real_formal_type(idx) ⇒ Object



196
197
198
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 196

def real_formal_type(idx)
  (@formal_types && @formal_types[idx]) || Types::TOP
end

#real_self_typeObject



180
181
182
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 180

def real_self_type
  @self_type || Types::TOP
end

#return_postdominatorObject



289
290
291
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 289

def return_postdominator
  vertex_with_name(RETURN_POSTDOMINATOR_NAME)
end

#return_typeObject



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

def return_type
  if (@exit.normal_predecessors & @exit.real_predecessors).empty?
    Types::EMPTY
  else
    @final_return.expr_type
  end
end

#save_pretty_picture(fmt = 'png', dotfile = 'graph', params = {'shape' => 'box'}) ⇒ Object



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

def save_pretty_picture(fmt='png', dotfile='graph', params = {'shape' => 'box'})
  write_to_graphic_file(fmt, dotfile, params)
end

#var_named(name) ⇒ Object



285
286
287
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 285

def var_named(name)
  all_variables.find { |x| x.name.start_with?(name) }
end

#yield_fail_postdominatorObject



293
294
295
# File 'lib/laser/analysis/control_flow/control_flow_graph.rb', line 293

def yield_fail_postdominator
  vertex_with_name(YIELD_POSTDOMINATOR_NAME)
end