rdg 0.0.2 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +3 -0
  3. data/.ruby-version +1 -0
  4. data/.travis.yml +2 -2
  5. data/Gemfile +1 -1
  6. data/RELEASES.md +10 -0
  7. data/TODO.md +68 -0
  8. data/bin/ast +5 -4
  9. data/bin/cfg +5 -4
  10. data/lib/rdg/analysis/analyser.rb +30 -0
  11. data/lib/rdg/analysis/composite.rb +23 -0
  12. data/lib/rdg/analysis/context.rb +17 -0
  13. data/lib/rdg/analysis/equivalences.rb +25 -0
  14. data/lib/rdg/analysis/propagater.rb +51 -0
  15. data/lib/rdg/analysis/registry.rb +35 -0
  16. data/lib/rdg/cfg.rb +12 -41
  17. data/lib/rdg/control/begin.rb +6 -4
  18. data/lib/rdg/control/break.rb +19 -0
  19. data/lib/rdg/control/case.rb +25 -0
  20. data/lib/rdg/control/{while.rb → conditional_loop.rb} +8 -7
  21. data/lib/rdg/control/def.rb +21 -7
  22. data/lib/rdg/control/ensure.rb +30 -0
  23. data/lib/rdg/control/for.rb +29 -0
  24. data/lib/rdg/control/handler.rb +25 -0
  25. data/lib/rdg/control/if.rb +7 -6
  26. data/lib/rdg/control/jump.rb +33 -0
  27. data/lib/rdg/control/jump_to_start.rb +11 -0
  28. data/lib/rdg/control/next.rb +9 -0
  29. data/lib/rdg/control/none.rb +5 -3
  30. data/lib/rdg/control/redo.rb +9 -0
  31. data/lib/rdg/control/rescue.rb +31 -0
  32. data/lib/rdg/control/rescue_body.rb +29 -0
  33. data/lib/rdg/control/retry.rb +13 -0
  34. data/lib/rdg/control/return.rb +5 -6
  35. data/lib/rdg/control/when.rb +27 -0
  36. data/lib/rdg/graph/bidirected_adjacency_graph.rb +19 -0
  37. data/lib/rdg/graph/rgl/allow_duplicates.rb +45 -0
  38. data/lib/rdg/tree/ast.rb +15 -4
  39. data/lib/rdg/version.rb +1 -1
  40. data/rdg.gemspec +3 -2
  41. data/spec/integration/cfg/conditionals/case_spec.rb +66 -0
  42. data/spec/integration/cfg/{if_spec.rb → conditionals/if_spec.rb} +24 -9
  43. data/spec/integration/cfg/conditionals/unless_spec.rb +53 -0
  44. data/spec/integration/cfg/exceptions_spec.rb +131 -0
  45. data/spec/integration/cfg/loops/loop_control_spec.rb +90 -0
  46. data/spec/integration/cfg/loops/loop_spec.rb +70 -0
  47. data/spec/integration/cfg/sequence_spec.rb +7 -1
  48. data/spec/support/doubles/fake_ast.rb +15 -0
  49. data/spec/support/matchers/flow_between_matcher.rb +1 -1
  50. data/spec/unit/analysis/composite_spec.rb +47 -0
  51. data/spec/unit/analysis/equivalences_spec.rb +29 -0
  52. data/spec/unit/{control/analyser_spec.rb → analysis/propagater_spec.rb} +23 -12
  53. data/spec/unit/analysis/registry_spec.rb +61 -0
  54. data/spec/unit/control/begin_spec.rb +3 -6
  55. data/spec/unit/control/break_spec.rb +26 -0
  56. data/spec/unit/control/case_spec.rb +66 -0
  57. data/spec/unit/control/conditional_loop_spec.rb +22 -0
  58. data/spec/unit/control/ensure_spec.rb +33 -0
  59. data/spec/unit/control/for_spec.rb +26 -0
  60. data/spec/unit/control/handler_spec.rb +27 -0
  61. data/spec/unit/control/if_spec.rb +26 -18
  62. data/spec/unit/control/jump_spec.rb +43 -0
  63. data/spec/unit/control/jump_to_start_spec.rb +22 -0
  64. data/spec/unit/control/rescue_body_spec.rb +26 -0
  65. data/spec/unit/control/rescue_spec.rb +62 -0
  66. data/spec/unit/control/return_spec.rb +1 -10
  67. data/spec/unit/control/when_spec.rb +59 -0
  68. data/spec/unit/graph/bidirected_adjacency_graph_spec.rb +49 -0
  69. metadata +91 -18
  70. data/lib/rdg/control/analyser.rb +0 -44
  71. data/spec/integration/cfg/methods_spec.rb +0 -39
  72. data/spec/unit/control/def_spec.rb +0 -28
  73. data/spec/unit/control/while_spec.rb +0 -25
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: af60440c383c82705747bf477f53b83e1cad635f
4
- data.tar.gz: e815153ed63da6c542a6da44175131ccce12e613
3
+ metadata.gz: 3267ca615757cdfe8bb5552efafc08f53ce883c7
4
+ data.tar.gz: e91b39981bfb05cb0737e372c05d26f18cedf738
5
5
  SHA512:
6
- metadata.gz: a3cf79483cbd687128ea25905ee796f50582f89c5e4d0d7d23a22cf12e22371f4de81d15a0c8ed01ee1d25335410ebdef64abd0253a776f95d574437f799f5d4
7
- data.tar.gz: 2695d85dd9d28e27c51e91872ee595cf4febc8a16c09246ab08a5c4cb9d187eb9f10dd3cec6e5be22310e2416e0b5cdd6124267c67bcee9a50c619b0b40a031b
6
+ metadata.gz: bdd71c1d0de2ce1f8f6223177a690fe5f91d70b0a23bbd469af41b4a236ff0a4636ff85d6eae3fd60219f570f9dd036d728d07fe78c3a348aa3fbad7ba13bbd2
7
+ data.tar.gz: 819759b62d7dd041329595424b654b8772310ac086ad732664bb1bcb64c3a5f50d39f767bf5d17fbffdbd24a428e49863e85947f4abe2c19744ee02f02157ad7
@@ -15,3 +15,6 @@ LineLength:
15
15
 
16
16
  Documentation:
17
17
  Enabled: false
18
+
19
+ Style/TrivialAccessors:
20
+ ExactNameMatch: true
@@ -0,0 +1 @@
1
+ 2.2.2
@@ -1,8 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.0
3
+ - 2.2.2
4
4
  before_install:
5
5
  - gem update bundler
6
6
  addons:
7
7
  code_climate:
8
- repo_token: 437cae623819bbf323bd0dbc9e2b857149c9f725841f3825ca55e09f564d8e0a
8
+ repo_token: 437cae623819bbf323bd0dbc9e2b857149c9f725841f3825ca55e09f564d8e0a
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- ruby "2.2.0"
1
+ ruby "2.2.2"
2
2
  source "https://rubygems.org"
3
3
 
4
4
  # Specify your gem's dependencies in rdg.gemspec
@@ -0,0 +1,10 @@
1
+ # Release History
2
+
3
+ ## v0.1.0 (April 2015)
4
+ * Complete implementation of intra-procedural control flow graphs.
5
+
6
+ ## v0.0.2 (March 2015)
7
+ * Provide initial partial implementation of intra-procedural control flow graphs.
8
+
9
+ ## v0.0.1 (February 2015)
10
+ * Provide basic support for importing abstract syntax trees and iterating over them.
data/TODO.md ADDED
@@ -0,0 +1,68 @@
1
+ ## Control Flow Graphs
2
+
3
+ ### Basics
4
+ - [x] `begin` ... `end`
5
+ - [x] `def` ... `end`
6
+
7
+ ### Conditionals
8
+
9
+ #### `if` expressions
10
+ - [x] no `else`
11
+ - [x] `else`
12
+ - [x] `elsif`
13
+ - [x] ternary `a ? b : c`
14
+ - [x] expression modifier `if`
15
+
16
+ #### `unless` expressions
17
+ - [x] no else
18
+ - [x] else
19
+ - [x] expression modifier `unless`
20
+
21
+ #### `case` expressions
22
+ - [x] single when
23
+ - [x] else
24
+ - [x] several when
25
+
26
+ ### Loops
27
+
28
+ #### Looping expressions
29
+ - [x] `while`
30
+ - [x] `until`
31
+ - [x] `for`
32
+ - [x] `while` and `until` as modifiers
33
+
34
+ #### Skipping
35
+ - [x] `break`
36
+ - [x] `next`
37
+ - [x] `redo`
38
+
39
+ ### Exceptions
40
+ - [x] `begin` / `rescue`
41
+ - [x] def / `rescue`
42
+ - [x] multiple `rescues`
43
+ - [x] `retry`
44
+ - [x] `ensure` part
45
+ - [x] `ensure` part with rescues
46
+ - [x] `ensure` part with rescues and else
47
+ - [x] `else` part
48
+
49
+ Exception control flow might be neater if there was some notion of hierarchy in the CFG. Right now, every statement within the rescuable block has a control flow edge to each of the exception handlers. It would be neater to have a single control flow edge from some kind of "parent" node which contains each of the statements in the rescuable block.
50
+
51
+ ### Blocks
52
+
53
+ Not too sure how to approach this yet.
54
+
55
+ It might be reasonable to assume that any method that takes a block could cause control to flow into that block. If we have the source for that method, we could also check for yield or a call before adding this control flow edge.
56
+
57
+ No matter what, blocks support the same skipping constructs as loops:
58
+
59
+ - [ ] `break`
60
+ - [ ] `next`
61
+ - [ ] `redo`
62
+
63
+ ## Inter-method Control Flow Graphs
64
+
65
+ - [ ] `return` expressions
66
+ - [ ] direct method invocations
67
+ - [ ] method invocations via `send` ?
68
+ - [ ] ensure control flow edges from a method invocation to a block only added when callee yields to block
data/bin/ast CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  require_relative "../lib/rdg/tree/ast"
3
3
 
4
- if ARGV.size == 1
5
- RDG::Tree::AST.from_path(ARGV[0]).write_to_graphic_file('png', ARGV[0] + ".ast")
4
+ if ARGV.size == 1 || ARGV.size == 2
5
+ image_format = ARGV.size == 2 ? ARGV[1] : 'pdf'
6
+ RDG::Tree::AST.from_path(ARGV[0]).write_to_graphic_file(image_format, ARGV[0] + ".ast")
6
7
  else
7
- puts "ast: expected 1 argument but received #{ARGV.size}"
8
+ puts "ast: expected 1 or 2 arguments but received #{ARGV.size}"
8
9
  puts "Usage:"
9
- puts " ast path/to/source.rb"
10
+ puts " ast path/to/source.rb [image_format]"
10
11
  end
data/bin/cfg CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env ruby
2
2
  require_relative "../lib/rdg/cfg"
3
3
 
4
- if ARGV.size == 1
5
- RDG::CFG.from_path(ARGV[0]).write_to_graphic_file('png', ARGV[0] + ".cfg")
4
+ if ARGV.size == 1 || ARGV.size == 2
5
+ image_format = ARGV.size == 2 ? ARGV[1] : 'pdf'
6
+ RDG::CFG.from_path(ARGV[0]).write_to_graphic_file(image_format, ARGV[0] + ".cfg")
6
7
  else
7
- puts "cfg: expected 1 argument but received #{ARGV.size}"
8
+ puts "cfg: expected 1 or 2 arguments but received #{ARGV.size}"
8
9
  puts "Usage:"
9
- puts " cfg path/to/source.rb"
10
+ puts " cfg path/to/source.rb [image_format]"
10
11
  end
@@ -0,0 +1,30 @@
1
+ require_relative "context"
2
+ require_relative "registry"
3
+
4
+ module RDG
5
+ module Analysis
6
+ class Analyser
7
+ extend Forwardable
8
+ def_delegators :@context, :graph, :equivalences, :registry
9
+
10
+ def self.register_analyser(*types)
11
+ Registry.register_by_type(self, *types)
12
+ end
13
+
14
+ def self.register_default_analyser
15
+ Registry.register_default(self)
16
+ end
17
+
18
+ def initialize(ast_node, context = Context.new)
19
+ @ast_node, @context = ast_node, context
20
+ prepare
21
+ end
22
+
23
+ private
24
+
25
+ def prepare
26
+ # do nothing
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,23 @@
1
+ require_relative "analyser"
2
+
3
+ module RDG
4
+ module Analysis
5
+ class Composite < Analyser
6
+ def self.compose(*ts)
7
+ Class.new(Composite) do
8
+ define_method :types do
9
+ ts
10
+ end
11
+ end
12
+ end
13
+
14
+ def initialize(ast_node, context = Context.new)
15
+ @delegates = types.map { |t| t.new(ast_node, context) }
16
+ end
17
+
18
+ def analyse
19
+ @delegates.each(&:analyse)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require_relative "../graph/bidirected_adjacency_graph"
2
+ require_relative "equivalences"
3
+ require_relative "registry"
4
+
5
+ module RDG
6
+ module Analysis
7
+ class Context
8
+ attr_reader :graph, :equivalences, :registry
9
+
10
+ def initialize(graph = nil, equivalences = nil, registry = nil)
11
+ @graph = graph || Graph::BidirectedAdjacencyGraph.new
12
+ @equivalences = equivalences || Equivalences.new
13
+ @registry = registry || Registry.new
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,25 @@
1
+ module RDG
2
+ module Analysis
3
+ class Equivalences
4
+ def initialize
5
+ @equivalences = {}
6
+ end
7
+
8
+ def add(original, results)
9
+ @equivalences[original] = results
10
+ end
11
+
12
+ def all(original)
13
+ if @equivalences.key?(original)
14
+ @equivalences[original].map { |e| all(e) }.flatten
15
+ else
16
+ [original]
17
+ end
18
+ end
19
+
20
+ def first(original)
21
+ all(original).first
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,51 @@
1
+ require_relative "analyser"
2
+
3
+ module RDG
4
+ module Analysis
5
+ class Propagater < Analyser
6
+ def analyse
7
+ add_internal_flow_edges
8
+ propogate_incoming_flow
9
+ propogate_outgoing_flow
10
+ remove_non_flow_vertices
11
+ add_equivalences
12
+ end
13
+
14
+ private
15
+
16
+ def children
17
+ @ast_node.children
18
+ end
19
+
20
+ def nodes
21
+ children.reject(&:empty?)
22
+ end
23
+
24
+ def add_internal_flow_edges
25
+ internal_flow_edges.each { |s, t| graph.add_edge(s, t) }
26
+ end
27
+
28
+ def propogate_incoming_flow
29
+ graph.each_predecessor(@ast_node) do |predecessor|
30
+ graph.remove_edge(predecessor, @ast_node)
31
+ graph.add_edge(predecessor, start_node)
32
+ end
33
+ end
34
+
35
+ def propogate_outgoing_flow
36
+ graph.each_successor(@ast_node) do |successor|
37
+ graph.remove_edge(@ast_node, successor)
38
+ end_nodes.each { |n| graph.add_edge(n, successor) }
39
+ end
40
+ end
41
+
42
+ def remove_non_flow_vertices
43
+ graph.remove_vertex(@ast_node)
44
+ end
45
+
46
+ def add_equivalences
47
+ equivalences.add(@ast_node, nodes)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,35 @@
1
+ module RDG
2
+ module Analysis
3
+ class Registry
4
+ def self.register_by_type(analyser, *types)
5
+ types.each { |type| by_type[type] = analyser }
6
+ end
7
+
8
+ def self.register_default(analyser)
9
+ by_type.default = analyser
10
+ end
11
+
12
+ def self.clear
13
+ by_type.clear
14
+ end
15
+
16
+ def analyser_for(ast_node, context)
17
+ by_node[ast_node].new(ast_node, context)
18
+ end
19
+
20
+ def prepend_for(ast_node, analyser)
21
+ by_node[ast_node] = Composite.compose(analyser, by_node[ast_node])
22
+ end
23
+
24
+ private
25
+
26
+ def self.by_type
27
+ @by_type ||= {}
28
+ end
29
+
30
+ def by_node
31
+ @by_node ||= Hash.new { |h, node| h[node] = Registry.by_type[node.type] }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -1,10 +1,8 @@
1
+ require "require_all"
2
+
1
3
  require_relative "tree/ast"
2
- require_relative "control/def"
3
- require_relative "control/begin"
4
- require_relative "control/if"
5
- require_relative "control/while"
6
- require_relative "control/return"
7
- require_relative "control/none"
4
+ require_rel "analysis/*"
5
+ require_rel "control/*"
8
6
 
9
7
  module RDG
10
8
  class CFG
@@ -17,59 +15,32 @@ module RDG
17
15
  end
18
16
 
19
17
  def initialize(ast)
20
- @graph = BiDiDirectedAdjacencyGraph.new
18
+ @context = Analysis::Context.new
19
+ @context.graph.add_vertex(ast.root)
21
20
  analyse(ast)
22
21
  end
23
22
 
24
23
  def write_to_graphic_file(format = 'png', filename = "cfg")
25
- @graph.write_to_graphic_file(format, filename)
24
+ @context.graph.write_to_graphic_file(format, filename)
26
25
  end
27
26
 
28
27
  def vertices
29
- @graph.each_vertex.to_a
28
+ @context.graph.each_vertex.to_a
30
29
  end
31
30
 
32
31
  def successors(v)
33
- @graph.each_adjacent(v).to_a
32
+ @context.graph.each_adjacent(v).to_a
34
33
  end
35
34
 
36
- def has_edge?(u, v)
37
- @graph.has_edge?(u, v)
35
+ def edge?(u, v)
36
+ @context.graph.has_edge?(u, v)
38
37
  end
39
38
 
40
39
  private
41
40
 
42
41
  def analyse(ast)
43
- state = {}
44
42
  ast.pre_order_iterator.select(&:compound?).each do |ast_node|
45
- analyser_for(ast_node.type).new(ast_node, @graph, state).analyse
46
- end
47
- end
48
-
49
- def analyser_for(ast_node_type)
50
- case ast_node_type
51
- when :def
52
- Control::Def
53
- when :begin, :kwbegin
54
- Control::Begin
55
- when :if
56
- Control::If
57
- when :while
58
- Control::While
59
- when :return
60
- Control::Return
61
- else
62
- Control::None
63
- end
64
- end
65
-
66
- class BiDiDirectedAdjacencyGraph < ::RGL::DirectedAdjacencyGraph
67
- def each_predecessor(vertex, &block)
68
- each_vertex.select { |v| each_adjacent(v).include?(vertex) }.each(&block)
69
- end
70
-
71
- def each_successor(vertex, &block)
72
- each_adjacent(vertex, &block) if has_vertex?(vertex)
43
+ @context.registry.analyser_for(ast_node, @context).analyse
73
44
  end
74
45
  end
75
46
  end
@@ -1,14 +1,16 @@
1
- require_relative "analyser"
1
+ require "rdg/analysis/propagater"
2
2
 
3
3
  module RDG
4
4
  module Control
5
- class Begin < Analyser
5
+ class Begin < Analysis::Propagater
6
+ register_analyser :begin, :kwbegin
7
+
6
8
  def internal_flow_edges
7
9
  children.each_cons(2).to_a
8
10
  end
9
11
 
10
- def start_nodes
11
- children.first(1)
12
+ def start_node
13
+ children.first
12
14
  end
13
15
 
14
16
  def end_nodes