Module: Necropsy::Bench::FindingFacts

Defined in:
lib/necropsy/bench/finding_facts.rb

Constant Summary collapse

ACTIONABLE_CLASSIFICATIONS =
%i[unreachable unused].freeze
UNKNOWN_BLOCKER_KINDS =
%i[
  analyzer_failure ambiguity_limit_exceeded dynamic_dispatch evidence_collision
  dynamic_ancestry incomplete_analysis incomplete_source parse_failure parse_incomplete
  partial_dispatch reference_scope_incomplete resolution_conflict resolution_invalid
  unknown_dispatch unparsed_external_reference unsupported_refinement variable_eval
].freeze
RULE_ROOT_REASONS =
%i[
  callback_registered rails_component rails_migration rails_route
  rails_view_helper rails_view_reference
].freeze
ORDINARY_DEFINITION_KINDS =
%i[def defs define_method].freeze

Class Method Summary collapse

Class Method Details

.actionable?(finding) ⇒ Boolean

Returns:

  • (Boolean)


21
22
23
# File 'lib/necropsy/bench/finding_facts.rb', line 21

def actionable?(finding)
  ACTIONABLE_CLASSIFICATIONS.include?(finding.classification.to_sym)
end

.category(finding) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/necropsy/bench/finding_facts.rb', line 64

def category(finding)
  explicit = explicit_category(finding)
  return explicit if explicit

  blocker = finding.blockers.first
  return blocker.kind.to_s if blocker

  defined_via = finding.node.defined_via.to_sym
  return 'generated_method' unless ORDINARY_DEFINITION_KINDS.include?(defined_via)

  finding.classification.to_s
end

.loc(finding) ⇒ Object



25
26
27
28
29
30
31
# File 'lib/necropsy/bench/finding_facts.rb', line 25

def loc(finding)
  start_line = positive_integer(finding.node.line)
  end_line = positive_integer(finding.node.end_line) || start_line
  return 0 unless start_line

  [end_line - start_line + 1, 1].max
end

.ratio(numerator, denominator) ⇒ Object



112
113
114
# File 'lib/necropsy/bench/finding_facts.rb', line 112

def ratio(numerator, denominator)
  denominator.zero? ? 0.0 : (numerator.to_f / denominator).round(4)
end

.report_risk_counts(report) ⇒ Object



88
89
90
# File 'lib/necropsy/bench/finding_facts.rb', line 88

def report_risk_counts(report)
  tally(report.reportable_findings.flat_map { |finding| risk_flags(finding) })
end

.report_rule_counts(report) ⇒ Object



77
78
79
80
81
82
83
84
85
86
# File 'lib/necropsy/bench/finding_facts.rb', line 77

def report_rule_counts(report)
  finding_rules = report.reportable_findings.flat_map { |finding| rule_hits(finding) }
  root_rules = report.graph.entry_points.filter_map do |root|
    node = report.graph.nodes.exact(root.definition_id)
    next unless node && report_path?(report, node.file)

    root.reason.to_s if RULE_ROOT_REASONS.include?(root.reason.to_sym)
  end
  tally(finding_rules + root_rules)
end

.resolution_counts(report) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/necropsy/bench/finding_facts.rb', line 96

def resolution_counts(report)
  visible_call_site_ids = report.graph.call_sites.filter_map do |site|
    site.call_site_id if report_path?(report, site.file)
  end.to_set
  records = report.graph.resolution_records.select do |record|
    visible_call_site_ids.include?(record.resolution.call_site_id)
  end
  counts = records.map { |record| record.resolution.status.to_s }.tally
  {
    'total' => records.length,
    'complete' => counts.fetch('complete', 0),
    'partial' => counts.fetch('partial', 0),
    'unknown' => counts.fetch('unknown', 0)
  }
end

.risk_flags(finding) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/necropsy/bench/finding_facts.rb', line 37

def risk_flags(finding)
  node = finding.node
  flags = []
  flags << 'public_or_protected_visibility' if %i[public protected].include?(node.visibility.to_sym)
  flags << 'generated_method' unless ORDINARY_DEFINITION_KINDS.include?(node.defined_via.to_sym)
  flags << 'no_owner' if node.owner.to_s.empty?
  flags << 'test_definition' if node.test
  flags << 'analysis_incomplete' if finding.blockers.any?
  flags << 'duplicate_or_redefinition' if finding.blockers.any? do |blocker|
    blocker.kind.to_sym == :duplicate_definition
  end
  flags.uniq.sort
end

.rule_hits(finding) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/necropsy/bench/finding_facts.rb', line 51

def rule_hits(finding)
  evidence_rules = finding.evidences.flat_map do |evidence|
     = stringify_keys(evidence.)
    values = [['rule_id'], ['rule'], ['rules']]
    values.flatten.compact
  end
  blocker_rules = finding.blockers.filter_map do |blocker|
     = stringify_keys(blocker.)
    ['rule_id'] || ['rule']
  end
  (evidence_rules + blocker_rules).map(&:to_s).reject(&:empty?).uniq.sort
end

.tally(values) ⇒ Object



92
93
94
# File 'lib/necropsy/bench/finding_facts.rb', line 92

def tally(values)
  values.map(&:to_s).reject(&:empty?).tally.sort.to_h
end

.unknown?(finding) ⇒ Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/necropsy/bench/finding_facts.rb', line 33

def unknown?(finding)
  finding.blockers.any? { |blocker| unknown_blocker?(blocker) }
end