necropsy 0.2.0 → 0.3.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/CHANGELOG.md +26 -0
- data/MEASUREMENTS.md +27 -0
- data/README.md +182 -17
- data/bench/README.md +92 -0
- data/bench/audit.rb +113 -0
- data/bench/audits/0.2.1/audit.json +501 -0
- data/bench/audits/0.2.1/audit.md +55 -0
- data/bench/audits/0.2.1/baseline_performance.yml +16 -0
- data/bench/audits/0.2.1/config.yml +42 -0
- data/bench/audits/0.2.1/review.yml +56 -0
- data/bench/audits/0.2.1/review_queue.yml +3651 -0
- data/bench/corpora/v1/README.md +30 -0
- data/bench/corpora/v1/labels.yml +37 -0
- data/bench/corpora/v1/manifest.yml +63 -0
- data/bench/corpora/v1/rubocop.necropsy.yml +7 -0
- data/bench/corpora/v1/self.necropsy.yml +8 -0
- data/bench/corpora/v1/tools/debride.yml +42 -0
- data/bench/corpora/v1/tools/spoom.yml +12 -0
- data/bench/corpora/v1/tools/type_aware.yml +11 -0
- data/bench/golden/v1/candidate_union.json +21918 -0
- data/bench/golden/v1/metadata.json +12 -0
- data/bench/golden/v1/reports/dynamic_evidence.json +146 -0
- data/bench/golden/v1/reports/plain_ruby.json +135 -0
- data/bench/golden/v1/reports/rails.json +176 -0
- data/bench/golden/v1/reports/rubocop_1_75_0.json +26848 -0
- data/bench/golden/v1/reports/self.json +3449 -0
- data/bench/review_queue.rb +35 -0
- data/bench/run.rb +31 -0
- data/bench/schema/candidate-union-v1.schema.json +70 -0
- data/docs/impv_implementation_matrix.md +179 -0
- data/docs/migrations/0.2.1.md +57 -0
- data/docs/migrations/0.3.0.md +207 -0
- data/docs/migrations/0.4.0.md +13 -0
- data/docs/necropsy_performance_adr.md +43 -0
- data/docs/necropsy_scope_decisions.md +58 -0
- data/docs/necropsy_type_facts_adr.md +22 -0
- data/gemfiles/prism_min.gemfile +9 -0
- data/gemfiles/prism_min.gemfile.lock +50 -0
- data/lib/necropsy/analyzer.rb +121 -2
- data/lib/necropsy/analyzers/dynamic/coverage_collector.rb +69 -17
- data/lib/necropsy/analyzers/dynamic/coverage_importer.rb +99 -9
- data/lib/necropsy/analyzers/dynamic/coverband_importer.rb +69 -299
- data/lib/necropsy/analyzers/dynamic/coverband_payload_set.rb +149 -0
- data/lib/necropsy/analyzers/dynamic/observation_policy.rb +96 -0
- data/lib/necropsy/analyzers/dynamic/redis_input_limits.rb +121 -0
- data/lib/necropsy/analyzers/dynamic/redis_nonblocking_io.rb +94 -0
- data/lib/necropsy/analyzers/dynamic/redis_payload_loader.rb +165 -0
- data/lib/necropsy/analyzers/dynamic/redis_transport.rb +217 -0
- data/lib/necropsy/analyzers/dynamic/runtime_reference.rb +96 -0
- data/lib/necropsy/analyzers/dynamic/trace_point_collector.rb +133 -23
- data/lib/necropsy/analyzers/dynamic/trace_point_importer.rb +3 -1
- data/lib/necropsy/analyzers/legacy_result_adapter.rb +226 -0
- data/lib/necropsy/analyzers/static/cha.rb +33 -73
- data/lib/necropsy/analyzers/static/name_resolution.rb +133 -12
- data/lib/necropsy/analyzers/static/rta.rb +237 -29
- data/lib/necropsy/ast_scanner/call_recording.rb +149 -30
- data/lib/necropsy/ast_scanner/call_site_creation.rb +54 -0
- data/lib/necropsy/ast_scanner/definition_creation.rb +43 -0
- data/lib/necropsy/ast_scanner/dsl_macros.rb +431 -41
- data/lib/necropsy/ast_scanner/method_definitions.rb +232 -50
- data/lib/necropsy/ast_scanner/references.rb +43 -9
- data/lib/necropsy/ast_scanner/ruby_semantics.rb +101 -15
- data/lib/necropsy/ast_scanner/traversal.rb +234 -71
- data/lib/necropsy/ast_scanner/value_definitions.rb +23 -13
- data/lib/necropsy/ast_scanner.rb +67 -6
- data/lib/necropsy/bench/candidate_union.rb +555 -0
- data/lib/necropsy/bench/claim_gate.rb +112 -0
- data/lib/necropsy/bench/evaluator.rb +329 -16
- data/lib/necropsy/bench/finding_facts.rb +152 -0
- data/lib/necropsy/bench/precision_gate.rb +144 -0
- data/lib/necropsy/bench/release_audit/adversarial_runner.rb +56 -0
- data/lib/necropsy/bench/release_audit/artifact_writer.rb +112 -0
- data/lib/necropsy/bench/release_audit/config_validator.rb +112 -0
- data/lib/necropsy/bench/release_audit/git_snapshot.rb +36 -0
- data/lib/necropsy/bench/release_audit/performance_gate.rb +165 -0
- data/lib/necropsy/bench/release_audit/run_provenance.rb +133 -0
- data/lib/necropsy/bench/release_audit.rb +360 -0
- data/lib/necropsy/bench/report_normalizer.rb +140 -0
- data/lib/necropsy/bench/review_queue.rb +154 -0
- data/lib/necropsy/bench/safety_mutation_harness.rb +59 -0
- data/lib/necropsy/bench/seed_runner.rb +408 -0
- data/lib/necropsy/bounded_canonicalizer.rb +218 -0
- data/lib/necropsy/cache/scan_cache.rb +85 -17
- data/lib/necropsy/call_site_identity.rb +54 -0
- data/lib/necropsy/cli.rb +220 -33
- data/lib/necropsy/clock.rb +40 -0
- data/lib/necropsy/confidence/scorer.rb +103 -58
- data/lib/necropsy/configuration.rb +224 -21
- data/lib/necropsy/convention_rules.rb +138 -0
- data/lib/necropsy/definition_identity/canonical_digest.rb +278 -0
- data/lib/necropsy/definition_identity.rb +37 -0
- data/lib/necropsy/diagnostics.rb +176 -36
- data/lib/necropsy/embedded_ruby.rb +55 -0
- data/lib/necropsy/entry_points/plain.rb +111 -10
- data/lib/necropsy/entry_points/rails.rb +322 -41
- data/lib/necropsy/entry_points/test.rb +6 -1
- data/lib/necropsy/flow_interpreter.rb +460 -0
- data/lib/necropsy/graph/blocker_matching.rb +338 -0
- data/lib/necropsy/graph/call_graph.rb +1099 -109
- data/lib/necropsy/graph/definition_index.rb +149 -0
- data/lib/necropsy/graph/dynamic_evidence_tracking.rb +206 -0
- data/lib/necropsy/graph/evidence_store.rb +213 -0
- data/lib/necropsy/graph/resolution_store.rb +497 -0
- data/lib/necropsy/graph_self_check.rb +79 -0
- data/lib/necropsy/guardrail/baseline.rb +350 -13
- data/lib/necropsy/guardrail/quarantine.rb +94 -9
- data/lib/necropsy/load_graph.rb +206 -0
- data/lib/necropsy/models.rb +878 -13
- data/lib/necropsy/performance_profiler.rb +108 -0
- data/lib/necropsy/project.rb +327 -25
- data/lib/necropsy/reachability/engine.rb +54 -13
- data/lib/necropsy/reference_barrier.rb +458 -0
- data/lib/necropsy/report.rb +113 -4
- data/lib/necropsy/reporter.rb +431 -15
- data/lib/necropsy/runner.rb +233 -18
- data/lib/necropsy/runtime_feedback.rb +136 -0
- data/lib/necropsy/semantics_matrix.rb +153 -0
- data/lib/necropsy/type_facts.rb +53 -0
- data/lib/necropsy/version.rb +1 -1
- data/lib/necropsy/why_not_explanation.rb +436 -0
- data/lib/necropsy/why_not_renderer.rb +197 -0
- data/lib/necropsy/world_policy.rb +90 -0
- data/lib/necropsy.rb +35 -2
- data/schema/necropsy-report-v2.schema.json +366 -0
- metadata +85 -1
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Necropsy
|
|
4
|
+
module BlockerMatching
|
|
5
|
+
def blockers
|
|
6
|
+
@blockers.dup
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def add_blocker(blocker)
|
|
10
|
+
blocker, key = canonical_blocker(blocker)
|
|
11
|
+
return blocker unless @blocker_keys.add?(key)
|
|
12
|
+
|
|
13
|
+
@dynamic_ancestry_cache = {} if blocker.kind == :dynamic_ancestry
|
|
14
|
+
@blockers << blocker
|
|
15
|
+
@blocker_keys_by_object[blocker] = key
|
|
16
|
+
index_blocker(blocker)
|
|
17
|
+
blocker
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Test-source uncertainty is diagnostic-only for production findings. A test
|
|
21
|
+
# call site cannot make a production definition callable in the deployed app.
|
|
22
|
+
def matching_blockers(node_or_id, caller_domain: :runtime)
|
|
23
|
+
node = node_or_id.is_a?(Node) ? node_or_id : nodes[node_or_id]
|
|
24
|
+
return [] unless node
|
|
25
|
+
|
|
26
|
+
blocker_candidates(node).select do |blocker|
|
|
27
|
+
blocker.caller_domain == caller_domain.to_sym && blocker_matches_node?(blocker, node)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def initialize_blocker_indexes
|
|
34
|
+
@blocker_keys = Set.new
|
|
35
|
+
@blocker_keys_by_object = {}.compare_by_identity
|
|
36
|
+
@blockers_by_message = Hash.new { |hash, key| hash[key] = [] }
|
|
37
|
+
@exact_blockers_by_scope = Hash.new do |scopes, kind|
|
|
38
|
+
scopes[kind] = Hash.new { |values, value| values[value] = [] }
|
|
39
|
+
end
|
|
40
|
+
@unmessaged_exact_owner_blockers = []
|
|
41
|
+
@glob_blockers_by_scope = Hash.new { |scopes, kind| scopes[kind] = [] }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def remove_blockers_matching(validate_retained: true, &)
|
|
45
|
+
removed, retained = @blockers.partition(&)
|
|
46
|
+
return if retained.length == @blockers.length
|
|
47
|
+
|
|
48
|
+
removed.each do |blocker|
|
|
49
|
+
key = @blocker_keys_by_object.delete(blocker)
|
|
50
|
+
@blocker_keys.delete(key) if key
|
|
51
|
+
end
|
|
52
|
+
@blockers = retained
|
|
53
|
+
rebuild_blocker_indexes(validate_retained: validate_retained)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def rebuild_blocker_indexes(validate_retained: true)
|
|
57
|
+
retained = @blockers
|
|
58
|
+
return rebuild_blocker_indexes_without_validation(retained) unless validate_retained
|
|
59
|
+
|
|
60
|
+
initialize_blocker_indexes
|
|
61
|
+
@blockers = []
|
|
62
|
+
retained.each { |blocker| add_blocker(blocker) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def rebuild_blocker_indexes_without_validation(retained)
|
|
66
|
+
retained_keys = @blocker_keys_by_object
|
|
67
|
+
initialize_blocker_indexes
|
|
68
|
+
@blockers = retained
|
|
69
|
+
retained.each do |blocker|
|
|
70
|
+
key = retained_keys.fetch(blocker) { blocker_key(blocker) }
|
|
71
|
+
@blocker_keys.add?(key)
|
|
72
|
+
@blocker_keys_by_object[blocker] = key
|
|
73
|
+
index_blocker(blocker)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def index_blocker(blocker)
|
|
78
|
+
kind = blocker.scope_kind.to_sym
|
|
79
|
+
match = blocker_scope_match(blocker)
|
|
80
|
+
if match == :glob
|
|
81
|
+
@glob_blockers_by_scope[kind] << blocker
|
|
82
|
+
else
|
|
83
|
+
messages = blocker_index_messages(blocker).compact
|
|
84
|
+
if messages.any?
|
|
85
|
+
messages.each { |message| @blockers_by_message[message] << blocker }
|
|
86
|
+
elsif kind == :owner
|
|
87
|
+
@unmessaged_exact_owner_blockers << blocker
|
|
88
|
+
else
|
|
89
|
+
exact_scope_values(blocker).each { |value| @exact_blockers_by_scope[kind][value] << blocker }
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def blocker_candidates(node)
|
|
95
|
+
candidates = []
|
|
96
|
+
candidates.concat(@blockers_by_message.fetch(node.name, []))
|
|
97
|
+
candidates.concat(exact_scope_blocker_candidates(node))
|
|
98
|
+
candidates.concat(unmessaged_owner_blocker_candidates(node))
|
|
99
|
+
candidates.concat(glob_scope_blocker_candidates(node))
|
|
100
|
+
candidates.uniq
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def exact_scope_blocker_candidates(node)
|
|
104
|
+
candidates = []
|
|
105
|
+
candidates.concat(@exact_blockers_by_scope[:definition].fetch(node.graph_id, []))
|
|
106
|
+
candidates.concat(@exact_blockers_by_scope[:symbol].fetch(node.symbol_id, []))
|
|
107
|
+
candidates.concat(@exact_blockers_by_scope[:message].fetch(node.name, []))
|
|
108
|
+
namespace_scope_values(node.owner).each do |namespace|
|
|
109
|
+
candidates.concat(@exact_blockers_by_scope[:namespace].fetch(namespace, []))
|
|
110
|
+
end
|
|
111
|
+
candidates.concat(@exact_blockers_by_scope[:file].fetch(node.file.to_s, []))
|
|
112
|
+
candidates.concat(@exact_blockers_by_scope[:global].values.flatten)
|
|
113
|
+
candidates
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def unmessaged_owner_blocker_candidates(node)
|
|
117
|
+
@unmessaged_exact_owner_blockers.select do |blocker|
|
|
118
|
+
exact_scope_values(blocker).any? { |owner| blocker_owner_matches?(blocker, node, owner) }
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def namespace_scope_values(owner)
|
|
123
|
+
parts = owner.to_s.split('::')
|
|
124
|
+
parts.length.times.map { |index| parts.first(index + 1).join('::') }
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def glob_scope_blocker_candidates(node)
|
|
128
|
+
@glob_blockers_by_scope.values.flatten.select do |blocker|
|
|
129
|
+
blocker_glob_scope_matches_node?(blocker, node)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def blocker_index_messages(blocker)
|
|
134
|
+
return Array(blocker.scope_value).compact.map(&:to_s) if blocker.scope_kind.to_sym == :message
|
|
135
|
+
return [blocker.message&.to_s] unless blocker.scope_kind.to_sym == :symbol
|
|
136
|
+
|
|
137
|
+
Array(blocker.scope_value).map { |symbol_id| logical_method_name(symbol_id) }.uniq
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def exact_scope_values(blocker)
|
|
141
|
+
Array(blocker.scope_value).compact.map(&:to_s)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def logical_method_name(symbol_id)
|
|
145
|
+
value = symbol_id.to_s
|
|
146
|
+
separator = value.index(/[.#]/)
|
|
147
|
+
separator ? value[(separator + 1)..] : value
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def blocker_key(blocker)
|
|
151
|
+
metadata = blocker.metadata
|
|
152
|
+
[
|
|
153
|
+
blocker.kind.to_s,
|
|
154
|
+
blocker.scope_kind.to_s,
|
|
155
|
+
canonical_key_value(blocker.scope_value),
|
|
156
|
+
canonical_key_value(blocker.source),
|
|
157
|
+
metadata['file'] || metadata[:file],
|
|
158
|
+
metadata['line'] || metadata[:line],
|
|
159
|
+
metadata['caller_id'] || metadata[:caller_id],
|
|
160
|
+
blocker.caller_domain.to_s,
|
|
161
|
+
metadata['caller_kind'] || metadata[:caller_kind],
|
|
162
|
+
metadata['receiver_kind'] || metadata[:receiver_kind],
|
|
163
|
+
metadata['original_message'] || metadata[:original_message],
|
|
164
|
+
metadata['call_site_id'] || metadata[:call_site_id],
|
|
165
|
+
metadata['producer'] || metadata[:producer],
|
|
166
|
+
metadata['producer_version'] || metadata[:producer_version],
|
|
167
|
+
metadata['resolution_record_id'] || metadata[:resolution_record_id],
|
|
168
|
+
metadata['evidence_id'] || metadata[:evidence_id],
|
|
169
|
+
canonical_key_value(metadata['assumptions'] || metadata[:assumptions]),
|
|
170
|
+
canonical_key_value(
|
|
171
|
+
metadata['known_target_definition_ids'] || metadata[:known_target_definition_ids]
|
|
172
|
+
),
|
|
173
|
+
blocker_scope_match(blocker).to_s,
|
|
174
|
+
metadata.fetch('include_private') { metadata[:include_private] },
|
|
175
|
+
blocker.message&.to_s,
|
|
176
|
+
blocker.reason.to_s
|
|
177
|
+
].freeze
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def canonical_key_value(value)
|
|
181
|
+
BoundedCanonicalizer.dump(value)
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def canonical_blocker(blocker)
|
|
185
|
+
[blocker, blocker_key(blocker)]
|
|
186
|
+
rescue BoundedCanonicalizer::Error, SystemStackError => e
|
|
187
|
+
sanitized = uncanonicalizable_blocker(blocker, e)
|
|
188
|
+
[sanitized, blocker_key(sanitized)]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def uncanonicalizable_blocker(blocker, error)
|
|
192
|
+
Blocker.new(
|
|
193
|
+
kind: :blocker_invalid,
|
|
194
|
+
scope_kind: :global,
|
|
195
|
+
scope_value: '*',
|
|
196
|
+
source: :blocker_store,
|
|
197
|
+
reason: 'Analyzer blocker could not be canonicalized safely',
|
|
198
|
+
suggested_action: :fix_analyzer,
|
|
199
|
+
metadata: {
|
|
200
|
+
'caller_domain' => safe_blocker_domain(blocker),
|
|
201
|
+
'receiver_kind' => 'implicit',
|
|
202
|
+
'original_kind' => blocker.kind.to_s,
|
|
203
|
+
'original_source_type' => blocker.source.class.name.to_s,
|
|
204
|
+
'canonicalization_error' => error.class.name
|
|
205
|
+
}
|
|
206
|
+
)
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def safe_blocker_domain(blocker)
|
|
210
|
+
blocker.caller_domain.to_s
|
|
211
|
+
rescue StandardError, SystemStackError
|
|
212
|
+
'runtime'
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
def blocker_matches_node?(blocker, node)
|
|
216
|
+
return false if blocker_known_targets(blocker).include?(node.graph_id)
|
|
217
|
+
return false unless blocker_message_matches?(blocker, node)
|
|
218
|
+
return false unless blocker_visibility_matches?(blocker, node)
|
|
219
|
+
return blocker_glob_scope_matches_node?(blocker, node) if blocker_scope_match(blocker) == :glob
|
|
220
|
+
|
|
221
|
+
values = Array(blocker.scope_value).compact.map(&:to_s)
|
|
222
|
+
case blocker.scope_kind.to_sym
|
|
223
|
+
when :definition
|
|
224
|
+
values.include?(node.graph_id)
|
|
225
|
+
when :owner
|
|
226
|
+
values.any? { |owner| blocker_owner_matches?(blocker, node, owner) }
|
|
227
|
+
when :namespace
|
|
228
|
+
values.any? { |namespace| node.owner == namespace || node.owner&.start_with?("#{namespace}::") }
|
|
229
|
+
when :file
|
|
230
|
+
values.include?(node.file)
|
|
231
|
+
when :symbol
|
|
232
|
+
values.include?(node.symbol_id)
|
|
233
|
+
when :message, :global
|
|
234
|
+
true
|
|
235
|
+
else
|
|
236
|
+
false
|
|
237
|
+
end
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def blocker_known_targets(blocker)
|
|
241
|
+
metadata = blocker.metadata
|
|
242
|
+
Array(metadata['known_target_definition_ids'] || metadata[:known_target_definition_ids]).map(&:to_s)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def blocker_message_matches?(blocker, node)
|
|
246
|
+
return true if blocker.scope_kind.to_sym == :symbol
|
|
247
|
+
if blocker.scope_kind.to_sym == :message && blocker_scope_match(blocker) == :glob
|
|
248
|
+
return scope_patterns(blocker).any? { |pattern| File.fnmatch?(pattern, node.name) }
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
blocker.message.nil? || blocker.message.to_s == node.name
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def blocker_scope_match(blocker)
|
|
255
|
+
value = blocker.metadata['scope_match'] || blocker.metadata[:scope_match] || :exact
|
|
256
|
+
value.to_sym
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def blocker_glob_scope_matches_node?(blocker, node)
|
|
260
|
+
values = glob_scope_node_values(blocker.scope_kind.to_sym, node)
|
|
261
|
+
return true if blocker.scope_kind.to_sym == :global
|
|
262
|
+
|
|
263
|
+
scope_patterns(blocker).any? do |pattern|
|
|
264
|
+
values.any? { |value| File.fnmatch?(pattern, value, File::FNM_PATHNAME | File::FNM_EXTGLOB) }
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def glob_scope_node_values(kind, node)
|
|
269
|
+
case kind
|
|
270
|
+
when :definition then [node.graph_id]
|
|
271
|
+
when :symbol then [node.symbol_id]
|
|
272
|
+
when :message then [node.name]
|
|
273
|
+
when :owner then [node.owner].compact
|
|
274
|
+
when :namespace then namespace_scope_values(node.owner)
|
|
275
|
+
when :file then [node.file]
|
|
276
|
+
else []
|
|
277
|
+
end.map(&:to_s)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def scope_patterns(blocker)
|
|
281
|
+
Array(blocker.scope_value).compact.map(&:to_s)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def blocker_visibility_matches?(blocker, node)
|
|
285
|
+
return true if %i[analyzer_failure duplicate_definition parse_incomplete].include?(blocker.kind.to_sym)
|
|
286
|
+
|
|
287
|
+
metadata = blocker.metadata
|
|
288
|
+
receiver_kind = (metadata['receiver_kind'] || metadata[:receiver_kind])&.to_sym
|
|
289
|
+
original_message = (metadata['original_message'] || metadata[:original_message])&.to_s
|
|
290
|
+
return node.visibility == :public if original_message == 'public_send'
|
|
291
|
+
return true if %w[send __send__ method].include?(original_message)
|
|
292
|
+
return respond_to_include_private(metadata) != false if original_message == 'respond_to?'
|
|
293
|
+
return true if %i[implicit self super].include?(receiver_kind)
|
|
294
|
+
|
|
295
|
+
node.visibility != :private
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
def respond_to_include_private(metadata)
|
|
299
|
+
return metadata['include_private'] if metadata.key?('include_private')
|
|
300
|
+
return metadata[:include_private] if metadata.key?(:include_private)
|
|
301
|
+
|
|
302
|
+
false
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def blocker_owner_matches?(blocker, node, owner)
|
|
306
|
+
receiver_kind = (blocker.metadata['receiver_kind'] || blocker.metadata[:receiver_kind])&.to_sym
|
|
307
|
+
caller_kind = (blocker.metadata['caller_kind'] || blocker.metadata[:caller_kind])&.to_sym
|
|
308
|
+
if receiver_kind == :constant || (%i[implicit self].include?(receiver_kind) && caller_kind == :singleton_method)
|
|
309
|
+
return constant_owner_matches?(node, owner)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
if receiver_kind == :super
|
|
313
|
+
expected_kind = caller_kind == :singleton_method ? :singleton_method : :instance_method
|
|
314
|
+
return node.kind == expected_kind && super_dispatch_owners(owner).include?(node.owner)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
node.kind == :instance_method && instance_dispatch_owners(owner).include?(node.owner)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def constant_owner_matches?(node, owner)
|
|
321
|
+
return true if node.kind == :singleton_method && constant_dispatch_owners(owner).include?(node.owner)
|
|
322
|
+
|
|
323
|
+
node.kind == :instance_method && Array(class_info(owner)&.extends).include?(node.owner)
|
|
324
|
+
end
|
|
325
|
+
|
|
326
|
+
def constant_dispatch_owners(owner)
|
|
327
|
+
[owner, *ancestor_chain(owner), *Array(class_info(owner)&.extends)].compact.uniq
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
def super_dispatch_owners(owner)
|
|
331
|
+
ancestor_chain(owner).flat_map { |candidate| cached_lookup_chain(candidate) }.uniq
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def instance_dispatch_owners(owner)
|
|
335
|
+
descendants_of(owner).flat_map { |candidate| cached_lookup_chain(candidate) }.uniq
|
|
336
|
+
end
|
|
337
|
+
end
|
|
338
|
+
end
|