branchproof 0.8.0 → 0.9.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 (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +18 -0
  3. data/README.md +43 -10
  4. data/doc/Branchproof/DefaultInstrumentation.md +13 -0
  5. data/doc/Branchproof/DefaultRuntime.md +11 -0
  6. data/doc/Branchproof/DefaultSyntax.md +7 -0
  7. data/doc/Branchproof/ExceptionInstrumentation.md +9 -0
  8. data/doc/Branchproof/ExceptionRuntime.md +27 -0
  9. data/doc/Branchproof/ExceptionSyntax.md +9 -0
  10. data/doc/Branchproof/ExtendedAlternativeRuntime.md +19 -0
  11. data/doc/Branchproof/Instrumenter.md +1 -1
  12. data/doc/Branchproof/IterationInstrumentation.md +7 -0
  13. data/doc/Branchproof/IterationRuntime.md +21 -0
  14. data/doc/Branchproof/IterationSyntax.md +11 -0
  15. data/doc/Branchproof/Runtime.md +56 -1
  16. data/doc/Branchproof/Source.md +7 -1
  17. data/doc/Branchproof/ValueInstrumentation.md +7 -0
  18. data/doc/Branchproof/ValueRuntime.md +26 -0
  19. data/doc/Branchproof/ValueSyntax.md +26 -0
  20. data/doc/Branchproof.md +17 -3
  21. data/doc/CHANGELOG.md +18 -0
  22. data/doc/README.md +43 -10
  23. data/lib/branchproof/decision_syntax.rb +19 -9
  24. data/lib/branchproof/default_instrumentation.rb +140 -0
  25. data/lib/branchproof/default_runtime.rb +16 -0
  26. data/lib/branchproof/default_syntax.rb +94 -0
  27. data/lib/branchproof/evidence.rb +83 -1
  28. data/lib/branchproof/exception_instrumentation.rb +96 -0
  29. data/lib/branchproof/exception_runtime.rb +41 -0
  30. data/lib/branchproof/exception_syntax.rb +157 -0
  31. data/lib/branchproof/extended_alternative_runtime.rb +23 -0
  32. data/lib/branchproof/flow_instrumentation.rb +36 -7
  33. data/lib/branchproof/instrumenter.rb +50 -5
  34. data/lib/branchproof/iteration_instrumentation.rb +66 -0
  35. data/lib/branchproof/iteration_runtime.rb +79 -0
  36. data/lib/branchproof/iteration_syntax.rb +75 -0
  37. data/lib/branchproof/loader.rb +6 -1
  38. data/lib/branchproof/runtime.rb +10 -0
  39. data/lib/branchproof/source.rb +59 -32
  40. data/lib/branchproof/value_instrumentation.rb +35 -0
  41. data/lib/branchproof/value_runtime.rb +73 -0
  42. data/lib/branchproof/value_syntax.rb +116 -0
  43. data/lib/branchproof/version.rb +1 -1
  44. data/llms.txt +17 -3
  45. data/sig/branchproof.rbs +17 -0
  46. metadata +27 -1
@@ -4,12 +4,23 @@ require "digest"
4
4
  require "pathname"
5
5
  require "prism"
6
6
  require_relative "decision_syntax"
7
+ require_relative "iteration_syntax"
8
+ require_relative "exception_syntax"
9
+ require_relative "default_syntax"
10
+ require_relative "value_syntax"
7
11
  require_relative "constraints"
8
12
 
9
13
  module Branchproof
10
14
  # Inventories supported condition and decision occurrences from Ruby files.
11
15
  class Source
12
16
  include DecisionSyntax
17
+ # Contextual syntax modules are layered after the baseline classifier so
18
+ # each can extend discovery without coupling this walker to every syntax
19
+ # family. Additional modules can follow the same seam.
20
+ include IterationSyntax
21
+ include ExceptionSyntax
22
+ prepend DefaultSyntax
23
+ prepend ValueSyntax
13
24
 
14
25
  attr_reader :root, :limits
15
26
 
@@ -114,8 +125,9 @@ module Branchproof
114
125
  phase_one_nodes = []
115
126
  phase_two_nodes = []
116
127
  flow_nodes = []
128
+ value_nodes = []
117
129
 
118
- walk(program) do |node|
130
+ walk_skipping_defined_operands(program) do |node|
119
131
  if node.is_a?(Prism::DefinedNode)
120
132
  defined_ranges << node.location
121
133
  elsif node.is_a?(Prism::InNode)
@@ -123,12 +135,14 @@ module Branchproof
123
135
  guard_patterns[pattern] = true if pattern.is_a?(Prism::IfNode) || pattern.is_a?(Prism::UnlessNode)
124
136
  end
125
137
  phase_one_nodes << node if decision_node?(node) || subjectless_case?(node)
126
- phase_two_nodes << node if boolean_node?(node) || node.is_a?(Prism::MatchPredicateNode)
138
+ phase_two_nodes << node if boolean_node?(node) || node.is_a?(Prism::MatchPredicateNode) ||
139
+ node.is_a?(Prism::DefinedNode)
127
140
  flow_nodes << node if flow_decision_node?(node)
141
+ value_nodes << node if respond_to?(:value_candidate_node?, true) && value_candidate_node?(node)
128
142
  end
129
143
 
130
144
  { defined_ranges: defined_ranges, guard_patterns: guard_patterns, phase_one_nodes: phase_one_nodes,
131
- phase_two_nodes: phase_two_nodes, flow_nodes: flow_nodes }
145
+ phase_two_nodes: phase_two_nodes, flow_nodes: flow_nodes, value_nodes: value_nodes }
132
146
  end
133
147
 
134
148
  def decisions_for(program, bytes, source_id, file_reasons = [], encoding = "UTF-8")
@@ -162,15 +176,17 @@ module Branchproof
162
176
  # argument) are separate decisions when tree_for did not decompose them.
163
177
  collected[:phase_two_nodes].each do |node|
164
178
  next if inventoried_boolean_nodes[node]
165
-
166
- additional_reasons = if within_defined_expression?(node, defined_ranges)
167
- ["unsupported_defined_expression"]
168
- else
169
- []
170
- end
171
- context = node.is_a?(Prism::MatchPredicateNode) ? "pattern_in" : "short_circuit"
179
+ next if within_defined_expression?(node, defined_ranges) && !node.is_a?(Prism::DefinedNode)
180
+
181
+ context = if node.is_a?(Prism::MatchPredicateNode)
182
+ "pattern_in"
183
+ elsif node.is_a?(Prism::DefinedNode)
184
+ "defined"
185
+ else
186
+ "short_circuit"
187
+ end
172
188
  specs << { node: nil, predicate: node, context: context,
173
- additional_reasons: additional_reasons }
189
+ additional_reasons: [] }
174
190
  mark_semantic_boolean_nodes(node, inventoried_boolean_nodes)
175
191
  end
176
192
 
@@ -188,22 +204,24 @@ module Branchproof
188
204
  predicate: predicate, context: spec[:context],
189
205
  additional_reasons: spec[:additional_reasons] || [])
190
206
  end
191
- boolean_decisions + flow_decisions_for(program, bytes, source_id, file_reasons, encoding,
192
- defined_ranges: defined_ranges, nodes: collected[:flow_nodes])
207
+ decisions = boolean_decisions + flow_decisions_for(program, bytes, source_id, file_reasons, encoding,
208
+ defined_ranges: defined_ranges,
209
+ nodes: collected[:flow_nodes])
210
+ decisions + additional_decisions_for(program, bytes, source_id, file_reasons, encoding,
211
+ collected: collected, decisions: decisions)
212
+ end
213
+
214
+ def additional_decisions_for(_program, _bytes, _source_id, _file_reasons, _encoding, **_options)
215
+ []
193
216
  end
194
217
 
195
218
  def flow_decisions_for(program, bytes, source_id, file_reasons = [], encoding = "UTF-8",
196
219
  defined_ranges: nil, nodes: nil)
197
220
  defined_ranges ||= collect_defined_ranges(program)
198
221
 
199
- super(program, bytes, source_id, file_reasons, encoding, nodes: nodes).map do |decision|
200
- next decision unless range_within_defined_expression?(decision, defined_ranges)
201
-
202
- decision.merge(
203
- support_status: "UNSUPPORTED",
204
- support_reasons: (Array(decision[:support_reasons]) + ["unsupported_defined_expression"]).uniq
205
- )
206
- end
222
+ nodes ||= collect_flow_decision_nodes(program)
223
+ nodes = nodes.reject { |node| within_defined_expression?(node, defined_ranges) }
224
+ super(program, bytes, source_id, file_reasons, encoding, nodes: nodes)
207
225
  end
208
226
 
209
227
  def collect_defined_ranges(program)
@@ -271,10 +289,14 @@ module Branchproof
271
289
  literal_truth = leaf.delete(:_literal_truth)
272
290
  constraint = leaf.delete(:_constraint)
273
291
  constraint_safe = leaf.delete(:_constraint_safe)
274
- Records.build(id: nil, index: index, byte_start: location.start_offset, byte_length: location.length,
275
- line: location.start_line, column: location.start_column,
276
- expression: expression, literal_truth: literal_truth, coupling: "unknown",
277
- constraint: constraint, constraint_safe: decision_constraint_safe && constraint_safe == true)
292
+ contextual = leaf.delete(:_contextual)
293
+ condition = Records.build(id: nil, index: index, byte_start: location.start_offset,
294
+ byte_length: location.length,
295
+ line: location.start_line, column: location.start_column,
296
+ expression: expression, literal_truth: literal_truth, coupling: "unknown",
297
+ constraint: constraint,
298
+ constraint_safe: decision_constraint_safe && constraint_safe == true)
299
+ contextual ? condition.merge(contextual: contextual) : condition
278
300
  end
279
301
  decision_id = Records.decision_id(source_id: source_id, context: context, byte_start: start_offset,
280
302
  byte_length: length, tree: tree)
@@ -319,7 +341,7 @@ module Branchproof
319
341
  inventoried_boolean_nodes[node] = true
320
342
  mark_semantic_boolean_nodes(node.left, inventoried_boolean_nodes)
321
343
  mark_semantic_boolean_nodes(node.right, inventoried_boolean_nodes)
322
- when Prism::MatchPredicateNode
344
+ when Prism::DefinedNode, Prism::MatchPredicateNode
323
345
  inventoried_boolean_nodes[node] = true
324
346
  when Prism::CallNode
325
347
  mark_semantic_boolean_nodes(node.receiver, inventoried_boolean_nodes) if unary_not?(node)
@@ -355,6 +377,9 @@ module Branchproof
355
377
  { start: location.start_offset, length: location.length }
356
378
  end
357
379
  }
380
+ leaf[:_contextual] = "implicit_regexp" if node.is_a?(Prism::MatchLastLineNode) ||
381
+ node.is_a?(Prism::InterpolatedMatchLastLineNode)
382
+ leaf[:_contextual] = "flip_flop" if node.is_a?(Prism::FlipFlopNode)
358
383
  leaves << leaf
359
384
  Records.build(type: :atom, index: leaves.length - 1)
360
385
  end
@@ -393,12 +418,7 @@ module Branchproof
393
418
 
394
419
  def unsupported_reasons(predicate, bytes)
395
420
  reasons = []
396
- walk(predicate) do |node|
397
- if node.is_a?(Prism::MatchLastLineNode) || node.is_a?(Prism::InterpolatedMatchLastLineNode)
398
- reasons << "unsupported_implicit_regexp"
399
- end
400
- reasons << "unsupported_flip_flop" if node.is_a?(Prism::FlipFlopNode)
401
- reasons << "unsupported_defined_expression" if node.is_a?(Prism::DefinedNode)
421
+ walk_skipping_defined_operands(predicate) do |node|
402
422
  reasons << "unsupported_heredoc" if node.respond_to?(:opening_loc) && node.opening_loc &&
403
423
  bytes.byteslice(node.opening_loc.start_offset,
404
424
  node.opening_loc.length).start_with?("<<")
@@ -406,6 +426,13 @@ module Branchproof
406
426
  reasons
407
427
  end
408
428
 
429
+ def walk_skipping_defined_operands(node, &block)
430
+ yield node
431
+ return if node.is_a?(Prism::DefinedNode)
432
+
433
+ node.child_nodes.each { |child| walk_skipping_defined_operands(child, &block) if child }
434
+ end
435
+
409
436
  def unwrap_predicate(node)
410
437
  while node.is_a?(Prism::ParenthesesNode)
411
438
  statements = node.body
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Branchproof
4
+ # Source rewrite for value observations; it preserves the wrapped value.
5
+ module ValueInstrumentation
6
+ private
7
+
8
+ def render_flow(bytes, decision, nested, encloses)
9
+ return render_value(bytes, decision, nested, encloses) if decision.dig(:instrumentation, :type) == "value"
10
+
11
+ super
12
+ end
13
+
14
+ # rubocop:disable-next Metrics/MethodLength -- success and exception traces differ.
15
+ def render_value(bytes, decision, nested, encloses)
16
+ expression = render_children(bytes, decision[:byte_start], decision[:byte_length], nested, encloses)
17
+ metadata = decision.fetch(:instrumentation)
18
+ runtime = self.class::RUNTIME
19
+ id = decision.fetch(:id).inspect
20
+ domain = metadata.fetch(:domain).to_sym.inspect
21
+ count = decision.fetch(:alternatives).length
22
+ enter = "#{runtime}.enter(#{id}); #{runtime}.set_alternative_count(#{id}, #{count})"
23
+ if metadata.fetch(:domain).to_sym == :dispatch
24
+ "(begin; #{enter}; begin; " \
25
+ "(begin; #{runtime}.dispatch_path(#{id}, (#{expression}), false); end); " \
26
+ "rescue ::Exception; #{runtime}.dispatch_path(#{id}, nil, true); raise; " \
27
+ "ensure; #{runtime}.leave(#{id}); end; end)"
28
+ else
29
+ "(begin; #{enter}; begin; " \
30
+ "#{runtime}.value_path(#{id}, (#{expression}), #{domain}); " \
31
+ "ensure; #{runtime}.leave(#{id}); end; end)"
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Branchproof
4
+ # Runtime mapping for value observations.
5
+ module ValueRuntime
6
+ # rubocop:disable Style/CaseEquality, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
7
+ def comparison_index(value)
8
+ return 3 if nil.equal?(value)
9
+ return unless Integer === value || Float === value || Rational === value
10
+ return if Float === value && value.nan?
11
+ return 0 if value < 0 # rubocop:disable Style/NumericPredicate
12
+ return 1 if value == 0 # rubocop:disable Style/NumericPredicate
13
+ return 2 if value > 0 # rubocop:disable Style/NumericPredicate
14
+
15
+ nil
16
+ end
17
+ module_function :comparison_index
18
+ # rubocop:enable Style/CaseEquality, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
19
+
20
+ def set_alternative_count(decision_id, count)
21
+ frame = current_frame(decision_id)
22
+ frame[:alternative_count] = count if frame
23
+ nil
24
+ end
25
+
26
+ # rubocop:disable-next Metrics/MethodLength -- trace state branches are explicit.
27
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity -- domain dispatch mirrors the observable value contract.
28
+ def value_path(decision_id, value, domain)
29
+ frame = current_frame(decision_id)
30
+ return value unless frame
31
+ return value if frame[:finished]
32
+
33
+ index = case domain.to_sym
34
+ when :truthiness, :defined then value ? 1 : 0
35
+ when :lookup
36
+ if nil.equal?(value)
37
+ 2
38
+ elsif value
39
+ 0
40
+ else
41
+ 1
42
+ end
43
+ when :comparison then comparison_index(value)
44
+ when :dispatch then 0
45
+ end
46
+ if index
47
+ frame[:observations] = if frame.fetch(:alternative_count, index + 1) == 2
48
+ [[0, index.zero?], [1, index == 1]]
49
+ else
50
+ (0..index).map { |position| [position, position == index] }
51
+ end
52
+ frame[:outcome] = true
53
+ frame[:finished] = true
54
+ end
55
+ value
56
+ rescue NoMethodError, ArgumentError
57
+ # A user-defined <=> may return an incomparable object. Preserve it and
58
+ # leave the vector incomplete instead of inventing a domain bucket.
59
+ value
60
+ end
61
+
62
+ def dispatch_path(decision_id, value, raised)
63
+ frame = current_frame(decision_id)
64
+ return value unless frame
65
+
66
+ index = raised ? 1 : 0
67
+ frame[:observations] = [[0, index.zero?], [1, index == 1]]
68
+ frame[:outcome] = true
69
+ frame[:finished] = true
70
+ value
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "prism"
4
+
5
+ module Branchproof
6
+ # Inventory for expressions whose result is itself the observable value.
7
+ # This deliberately excludes control-flow predicates already owned by Source.
8
+ module ValueSyntax
9
+ BOOLEAN_OPERATORS = %i[== != < <= > >= === =~ !~].freeze
10
+ DISPATCH_METHODS = %i[public_send send __send__].freeze
11
+
12
+ # rubocop:disable-next Metrics/ParameterLists -- source seam mirrors Source#decisions_for.
13
+ def additional_decisions_for(program, bytes, source_id, file_reasons, encoding, decisions:, collected:)
14
+ occupied = decisions.each_with_object({}) do |decision, ranges|
15
+ ranges[range_key(decision[:byte_start], decision[:byte_length])] = true
16
+ Array(decision[:conditions]).each do |condition|
17
+ ranges[range_key(condition[:byte_start], condition[:byte_length])] = true
18
+ end
19
+ end
20
+ value_decisions_for(
21
+ program, bytes, source_id, nodes: collected.fetch(:value_nodes), occupied_ranges: occupied,
22
+ file_reasons: file_reasons, encoding: encoding
23
+ )
24
+ end
25
+
26
+ # `nodes:` is supplied by Source's fused AST walk. It remains optional for
27
+ # the standalone discovery API used by focused syntax tests.
28
+ # rubocop:disable-next Metrics/MethodLength, Metrics/ParameterLists -- source seam mirrors Source#decisions_for.
29
+ def value_decisions_for(program, bytes, source_id, nodes: nil, occupied_ranges: {}, file_reasons: [],
30
+ encoding: "UTF-8")
31
+ occupied = occupied_ranges.transform_keys do |range|
32
+ range.is_a?(Array) ? range_key(*range) : range
33
+ end
34
+ decisions = []
35
+ each_value_node(program, nodes) do |node|
36
+ spec = value_spec(node)
37
+ next unless spec
38
+
39
+ key = range_key(node.location.start_offset, node.location.length)
40
+ next if occupied[key]
41
+
42
+ decisions << build_value_decision(node, spec, bytes, source_id, file_reasons, encoding)
43
+ occupied[key] = true
44
+ end
45
+ decisions
46
+ end
47
+
48
+ private
49
+
50
+ def value_candidate_node?(node)
51
+ !value_spec(node).nil?
52
+ end
53
+
54
+ def each_value_node(program, nodes, &)
55
+ return nodes.each(&) if nodes
56
+
57
+ walker = respond_to?(:walk_skipping_defined_operands, true) ? :walk_skipping_defined_operands : :walk
58
+ send(walker, program, &)
59
+ end
60
+
61
+ def range_key(start_offset, length)
62
+ (start_offset << 32) | length
63
+ end
64
+
65
+ # rubocop:disable-next Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
66
+ def value_spec(node)
67
+ if Prism.const_defined?(:MatchWriteNode) && node.is_a?(Prism::MatchWriteNode)
68
+ return { context: "match_capture", domain: :truthiness, alternatives: %w[false true], type: "value" }
69
+ end
70
+ return nil unless node.is_a?(Prism::CallNode)
71
+
72
+ name = node.name.to_sym
73
+ if name == :<=>
74
+ return { context: "comparison", domain: :comparison, alternatives: %w[negative zero positive nil],
75
+ type: "value" }
76
+ end
77
+ return { context: "lookup", domain: :lookup, alternatives: %w[truthy false nil], type: "value" } if name == :[]
78
+ if DISPATCH_METHODS.include?(name)
79
+ return { context: "dispatch", domain: :dispatch, alternatives: %w[success exception],
80
+ type: "value" }
81
+ end
82
+ if BOOLEAN_OPERATORS.include?(name) || name.to_s.end_with?("?")
83
+ return { context: "predicate", domain: :truthiness, alternatives: %w[false true],
84
+ type: "value" }
85
+ end
86
+
87
+ nil
88
+ end
89
+
90
+ # rubocop:disable-next Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/ParameterLists, Metrics/PerceivedComplexity -- record assembly is intentionally explicit.
91
+ def build_value_decision(node, spec, bytes, source_id, file_reasons, encoding)
92
+ location = node.location
93
+ expression = text_value(bytes.byteslice(location.start_offset, location.length), encoding)
94
+ alternatives = spec.fetch(:alternatives).each_with_index.map do |label, index|
95
+ { index: index, id: nil, expression: label, byte_start: location.start_offset, byte_length: location.length }
96
+ end
97
+ id = Records.decision_id(source_id: source_id, context: spec.fetch(:context),
98
+ byte_start: location.start_offset, byte_length: location.length,
99
+ tree: nil)
100
+ alternatives = alternatives.map { |item| item.merge(id: Records.condition_id(id, item.fetch(:index))) }
101
+ kind = alternatives.length == 2 ? "implicit" : "multiway"
102
+ reasons = Array(file_reasons)
103
+ reasons += Array(unsupported_reasons(node, bytes)) if respond_to?(:unsupported_reasons, true)
104
+ limit = @limits[:conditions_per_decision] if defined?(@limits) && @limits.respond_to?(:[])
105
+ reasons << "alternative_limit_exceeded" if limit && alternatives.length > limit
106
+ Records.build(id: id, source_id: source_id, kind: kind, context: spec.fetch(:context),
107
+ byte_start: location.start_offset, byte_length: location.length,
108
+ line: location.start_line, column: location.start_column,
109
+ expression: expression, tree: nil, conditions: [],
110
+ alternatives: alternatives, discovered_condition_count: 0,
111
+ support_status: reasons.empty? ? "SUPPORTED" : "UNSUPPORTED",
112
+ support_reasons: reasons.uniq, opaque_ranges: [],
113
+ instrumentation: { type: "value", domain: spec.fetch(:domain) })
114
+ end
115
+ end
116
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Branchproof
4
- VERSION = "0.8.0"
4
+ VERSION = "0.9.0"
5
5
  end
data/llms.txt CHANGED
@@ -2,10 +2,11 @@
2
2
 
3
3
  | | |
4
4
  | --- | --- |
5
- | **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/comparison.rb, lib/branchproof/constraints.rb, lib/branchproof/instrumenter.rb, lib/branchproof/runtime_flow.rb, lib/branchproof/saved_report.rb, lib/branchproof/rails_support.rb, lib/branchproof/coverage_index.rb, lib/branchproof/decision_table.rb, lib/branchproof/focused_report.rb, lib/branchproof/decision_syntax.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb, lib/branchproof/flow_instrumentation.rb |
5
+ | **Defined in** | lib/branchproof.rb, lib/branchproof/cli.rb, lib/branchproof/limits.rb, lib/branchproof/loader.rb, lib/branchproof/report.rb, lib/branchproof/source.rb, lib/branchproof/worker.rb, lib/branchproof/project.rb, lib/branchproof/records.rb, lib/branchproof/runtime.rb, lib/branchproof/version.rb, lib/branchproof/analyzer.rb, lib/branchproof/evidence.rb, lib/branchproof/minimizer.rb, lib/branchproof/comparison.rb, lib/branchproof/constraints.rb, lib/branchproof/instrumenter.rb, lib/branchproof/runtime_flow.rb, lib/branchproof/saved_report.rb, lib/branchproof/value_syntax.rb, lib/branchproof/rails_support.rb, lib/branchproof/value_runtime.rb, lib/branchproof/coverage_index.rb, lib/branchproof/decision_table.rb, lib/branchproof/default_syntax.rb, lib/branchproof/focused_report.rb, lib/branchproof/decision_syntax.rb, lib/branchproof/default_runtime.rb, lib/branchproof/exception_syntax.rb, lib/branchproof/iteration_syntax.rb, lib/branchproof/minitest_adapter.rb, lib/branchproof/comparison_report.rb, lib/branchproof/exception_runtime.rb, lib/branchproof/iteration_runtime.rb, lib/branchproof/flow_instrumentation.rb, lib/branchproof/value_instrumentation.rb, lib/branchproof/default_instrumentation.rb, lib/branchproof/exception_instrumentation.rb, lib/branchproof/iteration_instrumentation.rb, lib/branchproof/extended_alternative_runtime.rb |
6
6
 
7
- Keep each bounded source rewrite together so its evaluation order can be
8
- audited. rubocop:disable Metrics/AbcSize, Metrics/MethodLength
7
+ Keep source-boundary and parameter-binding rules together for auditing.
8
+ rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity,
9
+ Metrics/MethodLength, Metrics/ModuleLength, Metrics/PerceivedComplexity
9
10
 
10
11
  ## Constants
11
12
  ### `VERSION` <a id="constant-VERSION"></a> <a id="VERSION-constant"></a>
@@ -22,11 +23,21 @@ Not documented.
22
23
  - [Branchproof/CoverageIndex.md](doc/Branchproof/CoverageIndex.md)
23
24
  - [Branchproof/DecisionSyntax.md](doc/Branchproof/DecisionSyntax.md)
24
25
  - [Branchproof/DecisionTable.md](doc/Branchproof/DecisionTable.md)
26
+ - [Branchproof/DefaultInstrumentation.md](doc/Branchproof/DefaultInstrumentation.md)
27
+ - [Branchproof/DefaultRuntime.md](doc/Branchproof/DefaultRuntime.md)
28
+ - [Branchproof/DefaultSyntax.md](doc/Branchproof/DefaultSyntax.md)
25
29
  - [Branchproof/Error.md](doc/Branchproof/Error.md)
26
30
  - [Branchproof/Evidence.md](doc/Branchproof/Evidence.md)
31
+ - [Branchproof/ExceptionInstrumentation.md](doc/Branchproof/ExceptionInstrumentation.md)
32
+ - [Branchproof/ExceptionRuntime.md](doc/Branchproof/ExceptionRuntime.md)
33
+ - [Branchproof/ExceptionSyntax.md](doc/Branchproof/ExceptionSyntax.md)
34
+ - [Branchproof/ExtendedAlternativeRuntime.md](doc/Branchproof/ExtendedAlternativeRuntime.md)
27
35
  - [Branchproof/FlowInstrumentation.md](doc/Branchproof/FlowInstrumentation.md)
28
36
  - [Branchproof/FocusedReport.md](doc/Branchproof/FocusedReport.md)
29
37
  - [Branchproof/Instrumenter.md](doc/Branchproof/Instrumenter.md)
38
+ - [Branchproof/IterationInstrumentation.md](doc/Branchproof/IterationInstrumentation.md)
39
+ - [Branchproof/IterationRuntime.md](doc/Branchproof/IterationRuntime.md)
40
+ - [Branchproof/IterationSyntax.md](doc/Branchproof/IterationSyntax.md)
30
41
  - [Branchproof/Limits.md](doc/Branchproof/Limits.md)
31
42
  - [Branchproof/Loader.md](doc/Branchproof/Loader.md)
32
43
  - [Branchproof/Minimizer.md](doc/Branchproof/Minimizer.md)
@@ -40,6 +51,9 @@ Not documented.
40
51
  - [Branchproof/RuntimeFlow.md](doc/Branchproof/RuntimeFlow.md)
41
52
  - [Branchproof/SavedReport.md](doc/Branchproof/SavedReport.md)
42
53
  - [Branchproof/Source.md](doc/Branchproof/Source.md)
54
+ - [Branchproof/ValueInstrumentation.md](doc/Branchproof/ValueInstrumentation.md)
55
+ - [Branchproof/ValueRuntime.md](doc/Branchproof/ValueRuntime.md)
56
+ - [Branchproof/ValueSyntax.md](doc/Branchproof/ValueSyntax.md)
43
57
  - [Branchproof/Worker.md](doc/Branchproof/Worker.md)
44
58
  - [CHANGELOG.md](doc/CHANGELOG.md)
45
59
  - [README.md](doc/README.md)
data/sig/branchproof.rbs CHANGED
@@ -68,6 +68,23 @@ module Branchproof
68
68
  def self.flow_path: (String decision_id, Integer index) -> nil
69
69
  def self.flow_finish: (String decision_id, untyped value, ?Integer? default_path) -> untyped
70
70
  def self.flow_receiver: (String decision_id, untyped receiver) -> untyped
71
+ def self.flow_assignment_receiver: (String decision_id, untyped receiver) -> untyped
72
+ def self.flow_assignment_path: (String decision_id, Integer index) -> nil
73
+ def self.flow_assignment_finish: (String decision_id, untyped value, ?Integer? default_path) -> untyped
74
+ def self.exception_enter: (String decision_id, Integer unhandled_index) -> nil
75
+ def self.exception_path: (String decision_id, Integer index) -> nil
76
+ def self.exception_value: (String decision_id, untyped value, Integer index) -> untyped
77
+ def self.exception_finish: (String decision_id, untyped value) -> untyped
78
+ def self.exception_unhandled: (String decision_id) -> nil
79
+ def self.exception_leave: (String decision_id) -> nil
80
+ def self.default_binding: (String decision_id, Integer index) -> nil
81
+ def self.set_alternative_count: (String decision_id, Integer count) -> nil
82
+ def self.value_path: (String decision_id, untyped value, Symbol domain) -> untyped
83
+ def self.dispatch_path: (String decision_id, untyped value, bool raised) -> untyped
84
+ def self.flow_iteration_begin: (String decision_id, untyped receiver, ?Integer alternative_count) -> untyped
85
+ def self.flow_iteration_callback: (String decision_id, ?Integer alternative_count) -> nil
86
+ def self.flow_iteration_finish: (String decision_id, untyped value, ?Integer? default_path) -> untyped
87
+ def self.flow_iteration_leave: (String decision_id) -> nil
71
88
  def self.context: (?test_id: String?, ?phase: String?) -> nil
72
89
  def self.snapshot: () -> record
73
90
  def self.diagnostics: () -> Array[record]
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: branchproof
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lucian Ghinda
@@ -75,11 +75,21 @@ files:
75
75
  - doc/Branchproof/CoverageIndex.md
76
76
  - doc/Branchproof/DecisionSyntax.md
77
77
  - doc/Branchproof/DecisionTable.md
78
+ - doc/Branchproof/DefaultInstrumentation.md
79
+ - doc/Branchproof/DefaultRuntime.md
80
+ - doc/Branchproof/DefaultSyntax.md
78
81
  - doc/Branchproof/Error.md
79
82
  - doc/Branchproof/Evidence.md
83
+ - doc/Branchproof/ExceptionInstrumentation.md
84
+ - doc/Branchproof/ExceptionRuntime.md
85
+ - doc/Branchproof/ExceptionSyntax.md
86
+ - doc/Branchproof/ExtendedAlternativeRuntime.md
80
87
  - doc/Branchproof/FlowInstrumentation.md
81
88
  - doc/Branchproof/FocusedReport.md
82
89
  - doc/Branchproof/Instrumenter.md
90
+ - doc/Branchproof/IterationInstrumentation.md
91
+ - doc/Branchproof/IterationRuntime.md
92
+ - doc/Branchproof/IterationSyntax.md
83
93
  - doc/Branchproof/Limits.md
84
94
  - doc/Branchproof/Loader.md
85
95
  - doc/Branchproof/Minimizer.md
@@ -93,6 +103,9 @@ files:
93
103
  - doc/Branchproof/RuntimeFlow.md
94
104
  - doc/Branchproof/SavedReport.md
95
105
  - doc/Branchproof/Source.md
106
+ - doc/Branchproof/ValueInstrumentation.md
107
+ - doc/Branchproof/ValueRuntime.md
108
+ - doc/Branchproof/ValueSyntax.md
96
109
  - doc/Branchproof/Worker.md
97
110
  - doc/CHANGELOG.md
98
111
  - doc/README.md
@@ -107,10 +120,20 @@ files:
107
120
  - lib/branchproof/coverage_index.rb
108
121
  - lib/branchproof/decision_syntax.rb
109
122
  - lib/branchproof/decision_table.rb
123
+ - lib/branchproof/default_instrumentation.rb
124
+ - lib/branchproof/default_runtime.rb
125
+ - lib/branchproof/default_syntax.rb
110
126
  - lib/branchproof/evidence.rb
127
+ - lib/branchproof/exception_instrumentation.rb
128
+ - lib/branchproof/exception_runtime.rb
129
+ - lib/branchproof/exception_syntax.rb
130
+ - lib/branchproof/extended_alternative_runtime.rb
111
131
  - lib/branchproof/flow_instrumentation.rb
112
132
  - lib/branchproof/focused_report.rb
113
133
  - lib/branchproof/instrumenter.rb
134
+ - lib/branchproof/iteration_instrumentation.rb
135
+ - lib/branchproof/iteration_runtime.rb
136
+ - lib/branchproof/iteration_syntax.rb
114
137
  - lib/branchproof/limits.rb
115
138
  - lib/branchproof/loader.rb
116
139
  - lib/branchproof/minimizer.rb
@@ -123,6 +146,9 @@ files:
123
146
  - lib/branchproof/runtime_flow.rb
124
147
  - lib/branchproof/saved_report.rb
125
148
  - lib/branchproof/source.rb
149
+ - lib/branchproof/value_instrumentation.rb
150
+ - lib/branchproof/value_runtime.rb
151
+ - lib/branchproof/value_syntax.rb
126
152
  - lib/branchproof/version.rb
127
153
  - lib/branchproof/worker.rb
128
154
  - lib/mcdc.rb