ibex 0.3.0 → 0.4.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.
- checksums.yaml +4 -4
- data/README.md +7 -2
- data/docs/declarative-configuration.md +58 -46
- data/docs/development.md +17 -0
- data/docs/direct-ielr-decision.md +4 -4
- data/docs/error-ux-round2-review-status-v1.json +1 -1
- data/docs/error-ux-round2-v1.json +39 -3
- data/docs/impact.md +101 -0
- data/docs/maturity.md +3 -2
- data/docs/runtime-abi-evolution.md +11 -8
- data/docs/stability.md +2 -1
- data/lib/ibex/analysis/sets.rb +14 -0
- data/lib/ibex/cli/impact.rb +393 -0
- data/lib/ibex/cli.rb +5 -0
- data/lib/ibex/diff.rb +2 -1
- data/lib/ibex/impact/action_impact.rb +148 -0
- data/lib/ibex/impact/automaton_impact.rb +57 -0
- data/lib/ibex/impact/coverage_impact.rb +53 -0
- data/lib/ibex/impact/graph.rb +132 -0
- data/lib/ibex/impact/propagation.rb +147 -0
- data/lib/ibex/impact/report.rb +273 -0
- data/lib/ibex/impact/seeds.rb +69 -0
- data/lib/ibex/impact/severity.rb +139 -0
- data/lib/ibex/impact.rb +16 -0
- data/lib/ibex/version.rb +1 -1
- data/lib/ibex.rb +1 -0
- data/schema/impact-v1.schema.json +208 -0
- data/sig/ibex/analysis/sets.rbs +10 -0
- data/sig/ibex/cli/impact.rbs +95 -0
- data/sig/ibex/impact/action_impact.rbs +58 -0
- data/sig/ibex/impact/automaton_impact.rbs +37 -0
- data/sig/ibex/impact/coverage_impact.rbs +28 -0
- data/sig/ibex/impact/graph.rbs +67 -0
- data/sig/ibex/impact/propagation.rbs +56 -0
- data/sig/ibex/impact/report.rbs +107 -0
- data/sig/ibex/impact/seeds.rbs +36 -0
- data/sig/ibex/impact/severity.rbs +55 -0
- data/sig/ibex/impact.rbs +6 -0
- metadata +25 -3
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
module Ibex
|
|
5
|
+
module Impact
|
|
6
|
+
# Converts command-line symbols and Diff rule ids into analysis seeds.
|
|
7
|
+
class Seeds
|
|
8
|
+
# @rbs @grammar: IR::Grammar
|
|
9
|
+
# @rbs @sets: Analysis::Sets
|
|
10
|
+
# @rbs @records: Array[Hash[Symbol, Object?]]
|
|
11
|
+
# @rbs @ids: Array[Integer]
|
|
12
|
+
attr_reader :ids #: Array[Integer]
|
|
13
|
+
attr_reader :records #: Array[Hash[Symbol, Object?]]
|
|
14
|
+
|
|
15
|
+
# @rbs (IR::Grammar grammar, Array[String] names, ?origin: String) -> void
|
|
16
|
+
def initialize(grammar, names, origin: "symbol")
|
|
17
|
+
@grammar = grammar
|
|
18
|
+
@sets = Analysis::Sets.new(grammar)
|
|
19
|
+
@records = names.flat_map { |name| resolve(name, origin) }.sort_by { |record| seed_symbol(record) }
|
|
20
|
+
@ids = @records.map { |record| seed_id(record) }.uniq.freeze
|
|
21
|
+
@records = @records.freeze
|
|
22
|
+
freeze
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @rbs (IR::Grammar grammar, Hash[Symbol, Object?] diff, ?origin: String) -> Seeds
|
|
26
|
+
def self.from_diff(grammar, diff, origin: "diff")
|
|
27
|
+
rules = diff.fetch(:rules) #: Hash[Symbol, Object?]
|
|
28
|
+
names = %i[added removed changed].flat_map do |section|
|
|
29
|
+
records = rules.fetch(section) #: Array[Hash[Symbol, Object?]]
|
|
30
|
+
records.map { |record| record.fetch(:id).to_s }
|
|
31
|
+
end.uniq.sort
|
|
32
|
+
new(grammar, names, origin: origin)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
# @rbs (Hash[Symbol, Object?] record) -> String
|
|
38
|
+
def seed_symbol(record)
|
|
39
|
+
record.fetch(:symbol) #: String
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @rbs (Hash[Symbol, Object?] record) -> Integer
|
|
43
|
+
def seed_id(record)
|
|
44
|
+
record.fetch(:id) #: Integer
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# @rbs (String name, String origin) -> Array[Hash[Symbol, Object?]]
|
|
48
|
+
def resolve(name, origin)
|
|
49
|
+
definition = @grammar.symbol(name)
|
|
50
|
+
raise Ibex::Error, "(impact):1:1: unknown symbol #{name}" unless definition
|
|
51
|
+
raise Ibex::Error, "(impact):1:1: impact seed #{name} is not a nonterminal" unless definition.nonterminal?
|
|
52
|
+
|
|
53
|
+
[{ symbol: definition.name, id: definition.id, origin: origin,
|
|
54
|
+
nullable_boundary: nullable_boundary?(definition.id) }]
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# @rbs (Integer) -> bool
|
|
58
|
+
def nullable_boundary?(id)
|
|
59
|
+
return true if @sets.nullable?(id)
|
|
60
|
+
|
|
61
|
+
@grammar.productions.any? do |production|
|
|
62
|
+
production.rhs.include?(id) && production.rhs.any? do |symbol_id|
|
|
63
|
+
@grammar.symbol_by_id(symbol_id)&.nonterminal? && @sets.nullable?(symbol_id)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require "json"
|
|
5
|
+
require "optparse"
|
|
6
|
+
|
|
7
|
+
module Ibex
|
|
8
|
+
module Impact
|
|
9
|
+
# Stores only conflict identities so a baseline is stable across state ids.
|
|
10
|
+
class Baseline
|
|
11
|
+
# @rbs (String path) -> void
|
|
12
|
+
def initialize(path)
|
|
13
|
+
@path = path
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# @rbs () -> Array[String]
|
|
17
|
+
def conflicts
|
|
18
|
+
return [] unless File.file?(@path)
|
|
19
|
+
|
|
20
|
+
value = JSON.parse(File.binread(@path))
|
|
21
|
+
validate_document(value)
|
|
22
|
+
conflicts = value.fetch("conflicts") #: Array[untyped]
|
|
23
|
+
unless conflicts.all? { |identity| identity.is_a?(String) && !identity.empty? }
|
|
24
|
+
raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: conflicts must contain non-empty strings"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
conflicts.sort.uniq
|
|
28
|
+
rescue JSON::ParserError => e
|
|
29
|
+
raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: #{e.message}"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @rbs (Object?) -> void
|
|
33
|
+
def validate_document(value)
|
|
34
|
+
unless value.is_a?(Hash) && value.keys.sort == %w[conflicts schema_version]
|
|
35
|
+
raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: expected schema_version and conflicts"
|
|
36
|
+
end
|
|
37
|
+
unless value.fetch("conflicts").is_a?(Array)
|
|
38
|
+
raise Ibex::Error, "#{@path}:1:1: invalid impact baseline: conflicts must be an array"
|
|
39
|
+
end
|
|
40
|
+
return if value.fetch("schema_version") == 1
|
|
41
|
+
|
|
42
|
+
raise Ibex::Error, "#{@path}:1:1: unsupported impact baseline schema"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @rbs (Array[String] identities) -> void
|
|
46
|
+
def write(identities)
|
|
47
|
+
File.write(@path, "#{JSON.pretty_generate({ 'schema_version' => 1, 'conflicts' => identities.sort.uniq })}\n")
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Severity ranking and CI gate evaluation for impact findings.
|
|
52
|
+
module Severity
|
|
53
|
+
LEVELS = %w[info low medium high critical].freeze #: Array[String]
|
|
54
|
+
FAIL_ON = %w[
|
|
55
|
+
new_conflict nullable_change first_change follow_change action_arity unreachable
|
|
56
|
+
].freeze #: Array[String]
|
|
57
|
+
RANK = LEVELS.each_with_index.to_h.freeze #: Hash[String, Integer]
|
|
58
|
+
GATE_TO_KIND = {
|
|
59
|
+
"nullable_change" => "nullable", "first_change" => "first", "follow_change" => "follow"
|
|
60
|
+
}.freeze #: Hash[String, String]
|
|
61
|
+
|
|
62
|
+
module_function
|
|
63
|
+
|
|
64
|
+
# @rbs (String left, String right) -> String
|
|
65
|
+
def max(left, right)
|
|
66
|
+
RANK.fetch(left) >= RANK.fetch(right) ? left : right
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# @rbs (Hash[String, Array[String]], Array[Hash[Symbol, String]]) -> Hash[String, String]
|
|
70
|
+
def symbols(changes, actions)
|
|
71
|
+
levels = Hash.new("info") #: Hash[String, String]
|
|
72
|
+
changes.each do |name, kinds|
|
|
73
|
+
kinds.each do |kind|
|
|
74
|
+
level = level_for_kind(kind)
|
|
75
|
+
levels[name] = max(levels[name], level)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
actions.each do |finding|
|
|
79
|
+
name = finding.fetch(:production).split(" -> ").first
|
|
80
|
+
levels[name] = max(levels[name], finding.fetch(:severity))
|
|
81
|
+
end
|
|
82
|
+
levels
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs (String) -> String
|
|
86
|
+
def level_for_kind(kind)
|
|
87
|
+
return "high" if %w[first follow nullable].include?(kind)
|
|
88
|
+
return "medium" if %w[reference precedence].include?(kind)
|
|
89
|
+
|
|
90
|
+
"low"
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @rbs (Hash[Symbol, Object?] report, Array[String] gates) -> bool
|
|
94
|
+
def fails?(report, gates)
|
|
95
|
+
return false if gates.empty?
|
|
96
|
+
|
|
97
|
+
return true if conflict_gate?(report, gates)
|
|
98
|
+
return true if unreachable_gate?(report, gates)
|
|
99
|
+
return true if action_gate?(report, gates)
|
|
100
|
+
|
|
101
|
+
symbol_records = report.fetch(:symbols, []) #: Array[Hash[Symbol, Object?]]
|
|
102
|
+
gates.any? do |gate|
|
|
103
|
+
kind = GATE_TO_KIND[gate]
|
|
104
|
+
kind && symbol_records.any? { |item| item[:kinds].is_a?(Array) && item[:kinds].include?(kind) }
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# @rbs (Hash[Symbol, Object?], Array[String]) -> bool
|
|
109
|
+
def conflict_gate?(report, gates)
|
|
110
|
+
gates.include?("new_conflict") && report.dig(:automaton, :conflicts, :added)&.any?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# @rbs (Hash[Symbol, Object?], Array[String]) -> bool
|
|
114
|
+
def unreachable_gate?(report, gates)
|
|
115
|
+
return false unless gates.include?("unreachable")
|
|
116
|
+
|
|
117
|
+
states = report.dig(:automaton, :unreachable) || []
|
|
118
|
+
nonterminals = report.dig(:automaton, :unreachable_nonterminals) || []
|
|
119
|
+
states.any? || nonterminals.any?
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# @rbs (Hash[Symbol, Object?], Array[String]) -> bool
|
|
123
|
+
def action_gate?(report, gates)
|
|
124
|
+
return false unless gates.include?("action_arity")
|
|
125
|
+
|
|
126
|
+
action_records = report.fetch(:actions, []) #: Array[Hash[Symbol, Object?]]
|
|
127
|
+
action_records.any? { |item| item[:severity] == "high" }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
# @rbs (Array[String] gates) -> Array[String]
|
|
131
|
+
def validate_gates(gates)
|
|
132
|
+
unknown = gates - FAIL_ON
|
|
133
|
+
raise OptionParser::InvalidArgument, "unknown --fail-on value #{unknown.first.inspect}" unless unknown.empty?
|
|
134
|
+
|
|
135
|
+
gates.uniq
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
data/lib/ibex/impact.rb
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
require_relative "impact/graph"
|
|
5
|
+
require_relative "impact/propagation"
|
|
6
|
+
require_relative "impact/seeds"
|
|
7
|
+
require_relative "impact/automaton_impact"
|
|
8
|
+
require_relative "impact/action_impact"
|
|
9
|
+
require_relative "impact/coverage_impact"
|
|
10
|
+
require_relative "impact/severity"
|
|
11
|
+
require_relative "impact/report"
|
|
12
|
+
|
|
13
|
+
module Ibex
|
|
14
|
+
module Impact
|
|
15
|
+
end
|
|
16
|
+
end
|
data/lib/ibex/version.rb
CHANGED
data/lib/ibex.rb
CHANGED
|
@@ -27,6 +27,7 @@ require_relative "ibex/verify"
|
|
|
27
27
|
require_relative "ibex/equiv"
|
|
28
28
|
require_relative "ibex/diff"
|
|
29
29
|
require_relative "ibex/metrics"
|
|
30
|
+
require_relative "ibex/impact"
|
|
30
31
|
require_relative "ibex/fix"
|
|
31
32
|
require_relative "ibex/bison_import"
|
|
32
33
|
require_relative "ibex/grammar_tests"
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://raw.githubusercontent.com/ydah/ibex/main/schema/impact-v1.schema.json",
|
|
4
|
+
"title": "Ibex grammar impact report v1",
|
|
5
|
+
"type": "object",
|
|
6
|
+
"required": ["ibex_report", "schema_version", "mode", "algorithm", "grammar_digest", "seeds", "symbols", "automaton", "actions", "coverage", "warnings", "totals"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"ibex_report": { "const": "impact" },
|
|
9
|
+
"schema_version": { "const": 1 },
|
|
10
|
+
"mode": { "enum": ["potential", "diff"] },
|
|
11
|
+
"algorithm": { "enum": ["slr", "lalr1", "ielr1", "lr1"] },
|
|
12
|
+
"grammar_digest": {
|
|
13
|
+
"type": "object",
|
|
14
|
+
"required": ["before", "after"],
|
|
15
|
+
"properties": {
|
|
16
|
+
"before": { "type": ["string", "null"], "pattern": "^sha256:[0-9a-f]{64}$" },
|
|
17
|
+
"after": { "type": ["string", "null"], "pattern": "^sha256:[0-9a-f]{64}$" }
|
|
18
|
+
},
|
|
19
|
+
"additionalProperties": false
|
|
20
|
+
},
|
|
21
|
+
"seeds": {
|
|
22
|
+
"type": "array",
|
|
23
|
+
"items": {
|
|
24
|
+
"type": "object",
|
|
25
|
+
"required": ["symbol", "origin", "nullable_boundary"],
|
|
26
|
+
"properties": {
|
|
27
|
+
"symbol": { "type": "string" },
|
|
28
|
+
"origin": { "enum": ["symbol", "diff"] },
|
|
29
|
+
"nullable_boundary": { "type": "boolean" }
|
|
30
|
+
},
|
|
31
|
+
"additionalProperties": false
|
|
32
|
+
},
|
|
33
|
+
"maxItems": 100000
|
|
34
|
+
},
|
|
35
|
+
"symbols": {
|
|
36
|
+
"type": "array",
|
|
37
|
+
"items": { "$ref": "#/$defs/symbol" },
|
|
38
|
+
"maxItems": 100000
|
|
39
|
+
},
|
|
40
|
+
"automaton": {
|
|
41
|
+
"type": "object",
|
|
42
|
+
"required": ["states", "affected_states", "conflicts", "unreachable", "unreachable_nonterminals"],
|
|
43
|
+
"properties": {
|
|
44
|
+
"states": {
|
|
45
|
+
"type": "object",
|
|
46
|
+
"required": ["before", "after", "delta"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"before": { "type": ["integer", "null"], "minimum": 0 },
|
|
49
|
+
"after": { "type": "integer", "minimum": 0 },
|
|
50
|
+
"delta": { "type": ["integer", "null"] }
|
|
51
|
+
},
|
|
52
|
+
"additionalProperties": false
|
|
53
|
+
},
|
|
54
|
+
"affected_states": { "type": "array", "items": { "type": "integer", "minimum": 0 }, "uniqueItems": true, "maxItems": 100000 },
|
|
55
|
+
"conflicts": { "$ref": "#/$defs/classification" },
|
|
56
|
+
"unreachable": { "type": "array", "items": { "type": "integer", "minimum": 0 }, "uniqueItems": true, "maxItems": 100000 },
|
|
57
|
+
"unreachable_nonterminals": {
|
|
58
|
+
"type": "array",
|
|
59
|
+
"items": { "type": "string" },
|
|
60
|
+
"uniqueItems": true,
|
|
61
|
+
"maxItems": 100000
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
"additionalProperties": false
|
|
65
|
+
},
|
|
66
|
+
"actions": {
|
|
67
|
+
"type": "array",
|
|
68
|
+
"items": { "$ref": "#/$defs/action" },
|
|
69
|
+
"maxItems": 100000
|
|
70
|
+
},
|
|
71
|
+
"coverage": {
|
|
72
|
+
"type": "object",
|
|
73
|
+
"required": ["status", "reports"],
|
|
74
|
+
"properties": {
|
|
75
|
+
"status": { "enum": ["not_requested", "matched", "unmatched"] },
|
|
76
|
+
"reports": {
|
|
77
|
+
"type": "array",
|
|
78
|
+
"items": {
|
|
79
|
+
"type": "object",
|
|
80
|
+
"required": ["path", "productions"],
|
|
81
|
+
"properties": {
|
|
82
|
+
"path": { "type": "string" },
|
|
83
|
+
"productions": { "type": "array", "items": { "type": "integer", "minimum": 0 }, "uniqueItems": true, "maxItems": 100000 }
|
|
84
|
+
},
|
|
85
|
+
"additionalProperties": false
|
|
86
|
+
},
|
|
87
|
+
"maxItems": 100000
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
"additionalProperties": false
|
|
91
|
+
},
|
|
92
|
+
"warnings": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "maxItems": 100000 },
|
|
93
|
+
"totals": {
|
|
94
|
+
"type": "object",
|
|
95
|
+
"required": ["info", "low", "medium", "high", "critical"],
|
|
96
|
+
"properties": {
|
|
97
|
+
"info": { "type": "integer", "minimum": 0 },
|
|
98
|
+
"low": { "type": "integer", "minimum": 0 },
|
|
99
|
+
"medium": { "type": "integer", "minimum": 0 },
|
|
100
|
+
"high": { "type": "integer", "minimum": 0 },
|
|
101
|
+
"critical": { "type": "integer", "minimum": 0 }
|
|
102
|
+
},
|
|
103
|
+
"additionalProperties": false
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"additionalProperties": false,
|
|
107
|
+
"$defs": {
|
|
108
|
+
"classification": {
|
|
109
|
+
"type": "object",
|
|
110
|
+
"required": ["added", "removed", "changed"],
|
|
111
|
+
"properties": {
|
|
112
|
+
"added": { "type": "array", "items": { "$ref": "#/$defs/classification_record" }, "maxItems": 100000 },
|
|
113
|
+
"removed": { "type": "array", "items": { "$ref": "#/$defs/classification_record" }, "maxItems": 100000 },
|
|
114
|
+
"changed": { "type": "array", "items": { "$ref": "#/$defs/classification_record" }, "maxItems": 100000 }
|
|
115
|
+
},
|
|
116
|
+
"additionalProperties": false
|
|
117
|
+
},
|
|
118
|
+
"classification_record": {
|
|
119
|
+
"oneOf": [
|
|
120
|
+
{
|
|
121
|
+
"type": "object",
|
|
122
|
+
"required": ["id", "value"],
|
|
123
|
+
"properties": { "id": { "type": "string" }, "value": {} },
|
|
124
|
+
"additionalProperties": false
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"type": "object",
|
|
128
|
+
"required": ["id", "before", "after"],
|
|
129
|
+
"properties": { "id": { "type": "string" }, "before": {}, "after": {} },
|
|
130
|
+
"additionalProperties": false
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
},
|
|
134
|
+
"change": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"required": ["added", "removed"],
|
|
137
|
+
"properties": {
|
|
138
|
+
"added": { "type": "array", "items": { "type": "string" }, "maxItems": 100000 },
|
|
139
|
+
"removed": { "type": "array", "items": { "type": "string" }, "maxItems": 100000 }
|
|
140
|
+
},
|
|
141
|
+
"additionalProperties": false
|
|
142
|
+
},
|
|
143
|
+
"symbol": {
|
|
144
|
+
"type": "object",
|
|
145
|
+
"required": ["symbol", "severity", "kinds", "distance", "component", "sets", "witness"],
|
|
146
|
+
"properties": {
|
|
147
|
+
"symbol": { "type": "string" },
|
|
148
|
+
"severity": { "enum": ["info", "low", "medium", "high", "critical"] },
|
|
149
|
+
"kinds": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "maxItems": 100000 },
|
|
150
|
+
"distance": { "type": "integer", "minimum": 0 },
|
|
151
|
+
"component": { "type": "array", "items": { "type": "string" }, "uniqueItems": true, "maxItems": 100000 },
|
|
152
|
+
"sets": {
|
|
153
|
+
"type": "object",
|
|
154
|
+
"required": ["first", "follow", "nullable"],
|
|
155
|
+
"properties": {
|
|
156
|
+
"first": { "$ref": "#/$defs/change" },
|
|
157
|
+
"follow": { "$ref": "#/$defs/change" },
|
|
158
|
+
"nullable": {
|
|
159
|
+
"type": ["object", "null"],
|
|
160
|
+
"required": ["before", "after"],
|
|
161
|
+
"properties": { "before": { "type": "boolean" }, "after": { "type": "boolean" } },
|
|
162
|
+
"additionalProperties": false
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
"additionalProperties": false
|
|
166
|
+
},
|
|
167
|
+
"witness": { "type": "array", "items": { "$ref": "#/$defs/witness" }, "maxItems": 100000 }
|
|
168
|
+
},
|
|
169
|
+
"additionalProperties": false
|
|
170
|
+
},
|
|
171
|
+
"witness": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"required": ["from", "to", "production", "loc"],
|
|
174
|
+
"properties": {
|
|
175
|
+
"from": { "type": "string" },
|
|
176
|
+
"to": { "type": "string" },
|
|
177
|
+
"production": { "type": ["string", "null"] },
|
|
178
|
+
"loc": {
|
|
179
|
+
"type": ["object", "null"],
|
|
180
|
+
"properties": {
|
|
181
|
+
"file": { "type": "string" },
|
|
182
|
+
"line": { "type": "integer", "minimum": 1 },
|
|
183
|
+
"column": { "type": "integer", "minimum": 1 }
|
|
184
|
+
},
|
|
185
|
+
"additionalProperties": false
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"additionalProperties": false
|
|
189
|
+
},
|
|
190
|
+
"action": {
|
|
191
|
+
"type": "object",
|
|
192
|
+
"required": ["production", "severity", "reason", "context_length", "loc"],
|
|
193
|
+
"properties": {
|
|
194
|
+
"production": { "type": "string" },
|
|
195
|
+
"severity": { "enum": ["medium", "high"] },
|
|
196
|
+
"reason": { "type": "string" },
|
|
197
|
+
"context_length": {
|
|
198
|
+
"type": "object",
|
|
199
|
+
"required": ["before", "after"],
|
|
200
|
+
"properties": { "before": { "type": "integer", "minimum": 0 }, "after": { "type": "integer", "minimum": 0 } },
|
|
201
|
+
"additionalProperties": false
|
|
202
|
+
},
|
|
203
|
+
"loc": { "$ref": "#/$defs/witness/properties/loc" }
|
|
204
|
+
},
|
|
205
|
+
"additionalProperties": false
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
data/sig/ibex/analysis/sets.rbs
CHANGED
|
@@ -10,6 +10,13 @@ module Ibex
|
|
|
10
10
|
|
|
11
11
|
attr_reader follow_bits: Array[Integer]
|
|
12
12
|
|
|
13
|
+
# The index is a symbol id; each value lists the symbols that must be
|
|
14
|
+
# recomputed when the indexed symbol's set changes. These edges already
|
|
15
|
+
# point in the impact-propagation direction.
|
|
16
|
+
attr_reader first_dependencies: Array[Array[Integer]]
|
|
17
|
+
|
|
18
|
+
attr_reader follow_dependencies: Array[Array[Integer]]
|
|
19
|
+
|
|
13
20
|
@grammar: IR::Grammar
|
|
14
21
|
|
|
15
22
|
# @rbs (IR::Grammar grammar) -> void
|
|
@@ -44,6 +51,9 @@ module Ibex
|
|
|
44
51
|
# @rbs () -> void
|
|
45
52
|
def compute_follow: () -> void
|
|
46
53
|
|
|
54
|
+
# @rbs (Array[Array[Integer]] dependencies) -> Array[Array[Integer]]
|
|
55
|
+
def freeze_dependencies: (Array[Array[Integer]] dependencies) -> Array[Array[Integer]]
|
|
56
|
+
|
|
47
57
|
# @rbs (IR::Production production, Array[Array[Integer]] dependencies) -> void
|
|
48
58
|
def initialize_follow: (IR::Production production, Array[Array[Integer]] dependencies) -> void
|
|
49
59
|
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# Generated from lib/ibex/cli/impact.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
# CLI adapter for potential and confirmed grammar impact reports.
|
|
5
|
+
# rubocop:disable Metrics/ModuleLength, Metrics/MethodLength, Metrics/AbcSize
|
|
6
|
+
module CLIImpact
|
|
7
|
+
include CLIAnalysis
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
# @rbs (Array[String] arguments) -> Integer
|
|
12
|
+
def run_impact_command: (Array[String] arguments) -> Integer
|
|
13
|
+
|
|
14
|
+
# @rbs (Array[String]) -> Hash[Symbol, untyped]
|
|
15
|
+
def impact_option_parser: (Array[String]) -> Hash[Symbol, untyped]
|
|
16
|
+
|
|
17
|
+
# @rbs (String value) -> Array[Symbol]
|
|
18
|
+
def parse_kinds: (String value) -> Array[Symbol]
|
|
19
|
+
|
|
20
|
+
# @rbs (Array[String], Hash[Symbol, untyped]) -> [Hash[Symbol, Object?], Array[String], Hash[Symbol, Object?]]
|
|
21
|
+
def potential_impact: (Array[String], Hash[Symbol, untyped]) -> [ Hash[Symbol, Object?], Array[String], Hash[Symbol, Object?] ]
|
|
22
|
+
|
|
23
|
+
# @rbs (Array[String], Hash[Symbol, untyped]) -> [Hash[Symbol, Object?], Array[String], Hash[Symbol, Object?]]
|
|
24
|
+
def confirmed_impact: (Array[String], Hash[Symbol, untyped]) -> [ Hash[Symbol, Object?], Array[String], Hash[Symbol, Object?] ]
|
|
25
|
+
|
|
26
|
+
# @rbs (Impact::Graph, Array[Integer], Hash[Symbol, untyped]) ->
|
|
27
|
+
# [Hash[Integer, Impact::Node], Hash[Integer, Array[String]]]
|
|
28
|
+
def propagate: (Impact::Graph, Array[Integer], Hash[Symbol, untyped]) -> [ Hash[Integer, Impact::Node], Hash[Integer, Array[String]] ]
|
|
29
|
+
|
|
30
|
+
# @rbs (IR::Automaton, Array[Integer], Hash[Symbol, untyped]) -> Impact::CoverageImpact
|
|
31
|
+
def load_coverage: (IR::Automaton, Array[Integer], Hash[Symbol, untyped]) -> Impact::CoverageImpact
|
|
32
|
+
|
|
33
|
+
# @rbs (Impact::Seeds, Impact::CoverageImpact) -> Array[String]
|
|
34
|
+
def nullable_warnings: (Impact::Seeds, Impact::CoverageImpact) -> Array[String]
|
|
35
|
+
|
|
36
|
+
# @rbs (IR::Automaton, Impact::AutomatonImpact) -> Hash[Symbol, Object?]
|
|
37
|
+
def potential_automaton_document: (IR::Automaton, Impact::AutomatonImpact) -> Hash[Symbol, Object?]
|
|
38
|
+
|
|
39
|
+
# @rbs (IR::Automaton, IR::Automaton, Hash[Symbol, Object?], Impact::AutomatonImpact) -> Hash[Symbol, Object?]
|
|
40
|
+
def confirmed_automaton_document: (IR::Automaton, IR::Automaton, Hash[Symbol, Object?], Impact::AutomatonImpact) -> Hash[Symbol, Object?]
|
|
41
|
+
|
|
42
|
+
# @rbs (IR::Grammar) -> Array[String]
|
|
43
|
+
def unreachable_nonterminal_names: (IR::Grammar) -> Array[String]
|
|
44
|
+
|
|
45
|
+
# @rbs (IR::Grammar, IR::Grammar) -> Array[String]
|
|
46
|
+
def newly_unreachable_nonterminal_names: (IR::Grammar, IR::Grammar) -> Array[String]
|
|
47
|
+
|
|
48
|
+
# @rbs (IR::Automaton) -> Array[Integer]
|
|
49
|
+
def unreachable_state_ids: (IR::Automaton) -> Array[Integer]
|
|
50
|
+
|
|
51
|
+
# @rbs (IR::Automaton, IR::Automaton) -> Array[Integer]
|
|
52
|
+
def newly_unreachable_state_ids: (IR::Automaton, IR::Automaton) -> Array[Integer]
|
|
53
|
+
|
|
54
|
+
# @rbs (IR::Automaton) -> Array[String]
|
|
55
|
+
def unreachable_state_keys: (IR::Automaton) -> Array[String]
|
|
56
|
+
|
|
57
|
+
# @rbs (IR::Automaton, Integer) -> String
|
|
58
|
+
def unreachable_state_key: (IR::Automaton, Integer) -> String
|
|
59
|
+
|
|
60
|
+
# @rbs (IR::Automaton, IR::Automaton) ->
|
|
61
|
+
# [Hash[String, Hash[Symbol, Object?]], Hash[String, Array[String]]]
|
|
62
|
+
def compare_sets: (IR::Automaton, IR::Automaton) -> [ Hash[String, Hash[Symbol, Object?]], Hash[String, Array[String]] ]
|
|
63
|
+
|
|
64
|
+
# @rbs (Analysis::Sets, Analysis::Sets, IR::GrammarSymbol?, IR::GrammarSymbol?, Symbol) ->
|
|
65
|
+
# Hash[Symbol, Array[String]]
|
|
66
|
+
def set_change: (Analysis::Sets, Analysis::Sets, IR::GrammarSymbol?, IR::GrammarSymbol?, Symbol) -> Hash[Symbol, Array[String]]
|
|
67
|
+
|
|
68
|
+
# @rbs (Hash[Symbol, Array[String]], Hash[Symbol, Array[String]], Hash[Symbol, Object?]?) -> bool
|
|
69
|
+
def set_change_empty?: (Hash[Symbol, Array[String]], Hash[Symbol, Array[String]], Hash[Symbol, Object?]?) -> bool
|
|
70
|
+
|
|
71
|
+
# @rbs (Analysis::Sets, Analysis::Sets, IR::GrammarSymbol?, IR::GrammarSymbol?) -> Hash[Symbol, Object?]?
|
|
72
|
+
def nullable_change: (Analysis::Sets, Analysis::Sets, IR::GrammarSymbol?, IR::GrammarSymbol?) -> Hash[Symbol, Object?]?
|
|
73
|
+
|
|
74
|
+
# @rbs (Hash[Integer, Array[String]], Hash[String, Array[String]], IR::Grammar) -> Hash[Integer, Array[String]]
|
|
75
|
+
def merge_symbol_kinds: (Hash[Integer, Array[String]], Hash[String, Array[String]], IR::Grammar) -> Hash[Integer, Array[String]]
|
|
76
|
+
|
|
77
|
+
# @rbs (Hash[Symbol, Array[Hash[Symbol, Object?]]], IR::Grammar) -> Hash[String, Array[String]]
|
|
78
|
+
def precedence_symbol_kinds: (Hash[Symbol, Array[Hash[Symbol, Object?]]], IR::Grammar) -> Hash[String, Array[String]]
|
|
79
|
+
|
|
80
|
+
# @rbs (IR::Automaton) -> Array[String]
|
|
81
|
+
def conflict_identities: (IR::Automaton) -> Array[String]
|
|
82
|
+
|
|
83
|
+
# @rbs (IR::Grammar, Hash[Symbol, Object?]) -> String
|
|
84
|
+
def conflict_identity: (IR::Grammar, Hash[Symbol, Object?]) -> String
|
|
85
|
+
|
|
86
|
+
# @rbs (IR::Grammar, Integer) -> String
|
|
87
|
+
def production_shape: (IR::Grammar, Integer) -> String
|
|
88
|
+
|
|
89
|
+
# @rbs (Hash[Symbol, Object?], Array[String], Hash[Symbol, untyped]) -> void
|
|
90
|
+
def apply_baseline: (Hash[Symbol, Object?], Array[String], Hash[Symbol, untyped]) -> void
|
|
91
|
+
|
|
92
|
+
# @rbs (Hash[Symbol, Object?], String) -> void
|
|
93
|
+
def write_impact_report: (Hash[Symbol, Object?], String) -> void
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Generated from lib/ibex/impact/action_impact.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Impact
|
|
5
|
+
# Checks only structured action metadata; action source remains opaque.
|
|
6
|
+
class ActionImpact
|
|
7
|
+
# @rbs @before: IR::Grammar
|
|
8
|
+
# @rbs @after: IR::Grammar
|
|
9
|
+
# @rbs @affected_names: Array[String]?
|
|
10
|
+
attr_reader findings: Array[Hash[Symbol, Object?]]
|
|
11
|
+
|
|
12
|
+
# @rbs (IR::Grammar before, IR::Grammar after, ?affected_names: Array[String]?) -> void
|
|
13
|
+
def initialize: (IR::Grammar before, IR::Grammar after, ?affected_names: Array[String]?) -> void
|
|
14
|
+
|
|
15
|
+
# @rbs () -> Array[Hash[Symbol, Object?]]
|
|
16
|
+
def to_a: () -> Array[Hash[Symbol, Object?]]
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
# @rbs () -> Array[Hash[Symbol, Object?]]
|
|
21
|
+
def compare: () -> Array[Hash[Symbol, Object?]]
|
|
22
|
+
|
|
23
|
+
# @rbs (String name) -> Array[Hash[Symbol, Object?]]
|
|
24
|
+
def compare_rule: (String name) -> Array[Hash[Symbol, Object?]]
|
|
25
|
+
|
|
26
|
+
# @rbs (Array[IR::Production] before, Array[IR::Production] after) -> Array[[IR::Production, IR::Production]]
|
|
27
|
+
def pair_productions: (Array[IR::Production] before, Array[IR::Production] after) -> Array[[ IR::Production, IR::Production ]]
|
|
28
|
+
|
|
29
|
+
# @rbs (Array[IR::Production] unmatched, Array[IR::Production] after) -> Array[[IR::Production, IR::Production]]
|
|
30
|
+
def pair_exact_productions: (Array[IR::Production] unmatched, Array[IR::Production] after) -> Array[[ IR::Production, IR::Production ]]
|
|
31
|
+
|
|
32
|
+
# @rbs (Array[IR::Production] unmatched, Array[IR::Production] after,
|
|
33
|
+
# Array[[IR::Production, IR::Production]]) -> Array[[IR::Production, IR::Production]]
|
|
34
|
+
def pair_by_location: (Array[IR::Production] unmatched, Array[IR::Production] after, Array[[ IR::Production, IR::Production ]]) -> Array[[ IR::Production, IR::Production ]]
|
|
35
|
+
|
|
36
|
+
# @rbs (IR::Grammar grammar, String name) -> Array[IR::Production]
|
|
37
|
+
def productions_for: (IR::Grammar grammar, String name) -> Array[IR::Production]
|
|
38
|
+
|
|
39
|
+
# @rbs (IR::Grammar grammar, IR::Production production) -> [Array[String], String?]
|
|
40
|
+
def production_signature: (IR::Grammar grammar, IR::Production production) -> [ Array[String], String? ]
|
|
41
|
+
|
|
42
|
+
# @rbs (IR::Production production) -> [Integer, Integer]?
|
|
43
|
+
def action_location_identity: (IR::Production production) -> [ Integer, Integer ]?
|
|
44
|
+
|
|
45
|
+
# @rbs (IR::Production before, IR::Production after) -> Hash[Symbol, Object?]
|
|
46
|
+
def finding_for: (IR::Production before, IR::Production after) -> Hash[Symbol, Object?]
|
|
47
|
+
|
|
48
|
+
# @rbs (IR::Action?, Integer, Integer) -> [String, String]
|
|
49
|
+
def finding_reason: (IR::Action?, Integer, Integer) -> [ String, String ]
|
|
50
|
+
|
|
51
|
+
# @rbs (IR::Action?, Integer) -> bool
|
|
52
|
+
def out_of_range?: (IR::Action?, Integer) -> bool
|
|
53
|
+
|
|
54
|
+
# @rbs (IR::Grammar grammar, IR::Production production) -> String
|
|
55
|
+
def production_name: (IR::Grammar grammar, IR::Production production) -> String
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Generated from lib/ibex/impact/automaton_impact.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
module Ibex
|
|
4
|
+
module Impact
|
|
5
|
+
# Maps propagated grammar symbols to parser states and conflict changes.
|
|
6
|
+
class AutomatonImpact
|
|
7
|
+
# @rbs @automaton: IR::Automaton
|
|
8
|
+
# @rbs @symbol_ids: Array[Integer]
|
|
9
|
+
# @rbs @production_ids: Array[Integer]
|
|
10
|
+
attr_reader affected_states: Array[Integer]
|
|
11
|
+
|
|
12
|
+
attr_reader production_ids: Array[Integer]
|
|
13
|
+
|
|
14
|
+
attr_reader conflict_states: Array[Integer]
|
|
15
|
+
|
|
16
|
+
# @rbs (IR::Automaton automaton, Array[Integer]) -> void
|
|
17
|
+
def initialize: (IR::Automaton automaton, Array[Integer]) -> void
|
|
18
|
+
|
|
19
|
+
# @rbs (Hash[Symbol, Object?] diff) -> Hash[Symbol, Object?]
|
|
20
|
+
def conflict_changes: (Hash[Symbol, Object?] diff) -> Hash[Symbol, Object?]
|
|
21
|
+
|
|
22
|
+
# @rbs () -> Hash[Symbol, Object?]
|
|
23
|
+
def to_h: () -> Hash[Symbol, Object?]
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
# @rbs () -> Array[Integer]
|
|
28
|
+
def affected_productions: () -> Array[Integer]
|
|
29
|
+
|
|
30
|
+
# @rbs () -> Array[Integer]
|
|
31
|
+
def affected_state_ids: () -> Array[Integer]
|
|
32
|
+
|
|
33
|
+
# @rbs () -> Array[Integer]
|
|
34
|
+
def conflict_state_ids: () -> Array[Integer]
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|