rdg 0.0.2 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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