Class: Necropsy::Analyzers::Static::RTA

Inherits:
Necropsy::Analyzer show all
Defined in:
lib/necropsy/analyzers/static/rta.rb

Constant Summary collapse

ENUMERABLE_MESSAGES =
%w[
  all? any? chunk collect count cycle detect drop drop_while each_cons each_entry
  each_slice each_with_index each_with_object entries filter find find_all flat_map
  grep group_by include? inject map max min none? one? partition reduce reject
  reverse_each select sort sort_by take take_while to_a to_h
].freeze
COMPARISON_MESSAGES =
%w[< <= > >= between? clamp sort sort_by max min].freeze
CORE_ENUMERABLE_RECEIVERS =
%w[Array Enumerator Hash Range Set].freeze
CORE_COMPARISON_RECEIVERS =
%w[Array].freeze
KERNEL_OUTPUT_MESSAGES =
%w[print puts warn].freeze
REFLECTION_HOOK_MESSAGES =
%w[method respond_to?].freeze
BLOCK_DEPENDENT_ENUMERABLE_MESSAGES =
%w[
  all? any? chunk collect cycle detect drop_while each_cons each_entry each_slice
  each_with_index each_with_object filter find find_all flat_map group_by
  map none? one? partition reject reverse_each select sort_by take_while
].freeze
BLOCK_COMPARISON_MESSAGES =
%w[max min sort].freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(pruning: :rank_only, emit_redundant_edges: true) ⇒ RTA

Returns a new instance of RTA.



29
30
31
32
33
34
35
36
37
# File 'lib/necropsy/analyzers/static/rta.rb', line 29

def initialize(pruning: :rank_only, emit_redundant_edges: true)
  mode = pruning.to_s
  unless Configuration::RTA_PRUNING_MODES.include?(mode)
    raise Error, "RTA pruning must be one of: #{Configuration::RTA_PRUNING_MODES.join(', ')}"
  end

  @pruning = mode.to_sym
  @emit_redundant_edges = emit_redundant_edges == true
end

Instance Attribute Details

#emit_redundant_edgesObject (readonly)

Returns the value of attribute emit_redundant_edges.



27
28
29
# File 'lib/necropsy/analyzers/static/rta.rb', line 27

def emit_redundant_edges
  @emit_redundant_edges
end

#pruningObject (readonly)

Returns the value of attribute pruning.



27
28
29
# File 'lib/necropsy/analyzers/static/rta.rb', line 27

def pruning
  @pruning
end

Instance Method Details

#analyze(graph, _project) ⇒ Object



47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/necropsy/analyzers/static/rta.rb', line 47

def analyze(graph, _project)
  sites = expanded_call_sites(graph)
  original_call_site_ids = graph.call_sites.to_set(&:call_site_id)
  derived_sites = sites.reject { |site| original_call_site_ids.include?(site.call_site_id) }
   = (graph)
  analyses = sites.map do |site|
    targets = rta_candidates(graph, site)
    site_edges = targets.filter_map do |candidate|
      # In rank-only mode CHA/name resolution already materialize the
      # same conservative edge. Keep the RTA resolution record, but
      # avoid allocating a duplicate evidence object for an edge that
      # cannot change reachability. Legacy pruning still emits every
      # candidate because reconciliation uses those edge identities.
      next if pruning == :rank_only && !emit_redundant_edges &&
              graph.edge_present?(site.caller_id, candidate.graph_id)

      EdgeEvidence.new(
        caller_id: site.caller_id,
        callee_id: candidate.graph_id,
        evidence: evidence(
          kind: :call_edge,
          details: "RTA candidate at #{site.file}:#{site.line}",
          metadata: site.to_h.merge().merge('target_definition_id' => candidate.graph_id),
          grade: :heuristic,
          relation: :call_edge,
          source: call_site_evidence_source(site).merge('target_definition_id' => candidate.graph_id),
          scope: call_site_evidence_scope(site).merge('target_definition_id' => candidate.graph_id)
        )
      )
    end
    [site, targets, site_edges]
  end
  edge_evidences = analyses.flat_map(&:last)
  analyses_by_call_site_id = analyses.to_h { |site, targets, edges| [site.call_site_id, [targets, edges]] }
  resolutions = sites.map do |site|
    targets, edges = analyses_by_call_site_id.fetch(site.call_site_id)
    resolution_record(site, targets, edges, unknown_scope: graph.residual_scope_for(site))
  end

  AnalyzerResult.new(
    edge_evidences: edge_evidences,
    alive_evidences: [],
    uncertainties: {},
    observation: { 'rta' => { 'pruning' => pruning.to_s, 'analyzed_sites' => sites.map(&:to_h) } },
    resolutions: resolutions,
    evidences: result_evidences(edge_evidences),
    derived_call_sites: derived_sites
  )
end

#capabilitiesObject



113
114
115
# File 'lib/necropsy/analyzers/static/rta.rb', line 113

def capabilities
  [:complete_resolution].freeze
end

#expanded_call_sites(graph) ⇒ Object



117
118
119
120
121
# File 'lib/necropsy/analyzers/static/rta.rb', line 117

def expanded_call_sites(graph)
  graph.call_sites.flat_map do |site|
    [site, *implicit_sites(site, graph: graph)]
  end
end

#implicit_messages(site, graph: nil) ⇒ Object



134
135
136
# File 'lib/necropsy/analyzers/static/rta.rb', line 134

def implicit_messages(site, graph: nil)
  implicit_sites(site, graph: graph).map(&:message)
end

#implicit_sites(site, graph: nil) ⇒ Object



123
124
125
126
127
128
129
130
131
132
# File 'lib/necropsy/analyzers/static/rta.rb', line 123

def implicit_sites(site, graph: nil)
  summaries = []
  summaries << ['each', :same_receiver] if core_enumerable_receiver?(site) && enumerable_protocol?(site)
  summaries.concat(comparison_summaries(site)) if core_comparison_receiver?(site)
  summaries.concat(output_summaries(site)) if kernel_output_call?(site, graph)
  summaries << ['respond_to_missing?', :same_receiver] if reflection_hook_call?(site, graph)
  summaries.map.with_index do |(message, receiver), index|
    derived_protocol_site(site, message, receiver, index: index)
  end
end

#profileObject



97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/necropsy/analyzers/static/rta.rb', line 97

def profile
  description = if pruning == :legacy
                  'RTA pruning mode legacy removes static candidates without scanned construction evidence.'
                else
                  'RTA pruning mode rank_only records constructed-class hints without removing static edges.'
                end
  AnalyzerProfile.new(
    name: :rta,
    kind: :static,
    soundness: :partial,
    description: description,
    version: Necropsy::VERSION,
    assumptions: ['scanned_allocations', "pruning=#{pruning}"]
  )
end

#with_pruning(mode, emit_redundant_edges: @emit_redundant_edges) ⇒ Object



39
40
41
# File 'lib/necropsy/analyzers/static/rta.rb', line 39

def with_pruning(mode, emit_redundant_edges: @emit_redundant_edges)
  self.class.new(pruning: mode, emit_redundant_edges: emit_redundant_edges)
end

#without_redundant_edgesObject



43
44
45
# File 'lib/necropsy/analyzers/static/rta.rb', line 43

def without_redundant_edges
  self.class.new(pruning: pruning, emit_redundant_edges: false)
end