Class: Necropsy::CallGraph

Inherits:
Object
  • Object
show all
Defined in:
lib/necropsy/graph/call_graph.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(scan_result) ⇒ CallGraph

Returns a new instance of CallGraph.



8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# File 'lib/necropsy/graph/call_graph.rb', line 8

def initialize(scan_result)
  @nodes = {}
  @edges = Hash.new { |hash, key| hash[key] = {} }
  @call_sites = scan_result.call_sites
  @instantiated_classes = scan_result.instantiated_classes.dup
  @class_infos = scan_result.class_infos.to_h { |info| [info.id, info] }
  @entrypoint_hints = scan_result.entrypoint_hints
  @entry_points = []
  @profiles = []
  @uncertainties = scan_result.uncertainties
  @dynamic_alive = {}
  @observation = {}
  scan_result.nodes.each { |node| add_node(node) }
  retain_known_instantiated_classes
end

Instance Attribute Details

#call_sitesObject (readonly)

Returns the value of attribute call_sites.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def call_sites
  @call_sites
end

#class_infosObject (readonly)

Returns the value of attribute class_infos.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def class_infos
  @class_infos
end

#entry_pointsObject (readonly)

Returns the value of attribute entry_points.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def entry_points
  @entry_points
end

#entrypoint_hintsObject (readonly)

Returns the value of attribute entrypoint_hints.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def entrypoint_hints
  @entrypoint_hints
end

#instantiated_classesObject (readonly)

Returns the value of attribute instantiated_classes.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def instantiated_classes
  @instantiated_classes
end

#nodesObject (readonly)

Returns the value of attribute nodes.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def nodes
  @nodes
end

#observationObject (readonly)

Returns the value of attribute observation.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def observation
  @observation
end

#profilesObject (readonly)

Returns the value of attribute profiles.



5
6
7
# File 'lib/necropsy/graph/call_graph.rb', line 5

def profiles
  @profiles
end

Instance Method Details

#add_alive(node_id, evidence) ⇒ Object



53
54
55
56
57
58
# File 'lib/necropsy/graph/call_graph.rb', line 53

def add_alive(node_id, evidence)
  return unless nodes.key?(node_id)

  @dynamic_alive[node_id] ||= []
  @dynamic_alive[node_id] << evidence
end

#add_edge(caller_id, callee_id, evidence) ⇒ Object



46
47
48
49
50
51
# File 'lib/necropsy/graph/call_graph.rb', line 46

def add_edge(caller_id, callee_id, evidence)
  return unless nodes.key?(caller_id) && nodes.key?(callee_id)

  @edges[caller_id][callee_id] ||= []
  @edges[caller_id][callee_id] << evidence
end

#add_entry_point(node_id, reason) ⇒ Object



28
29
30
31
32
33
# File 'lib/necropsy/graph/call_graph.rb', line 28

def add_entry_point(node_id, reason)
  return unless nodes.key?(node_id)

  entry = EntryPoint.new(node_id: node_id, reason: reason)
  entry_points << entry unless entry_points.include?(entry)
end

#add_node(node) ⇒ Object



24
25
26
# File 'lib/necropsy/graph/call_graph.rb', line 24

def add_node(node)
  nodes[node.id] ||= node
end

#add_profile(profile) ⇒ Object



42
43
44
# File 'lib/necropsy/graph/call_graph.rb', line 42

def add_profile(profile)
  profiles << profile
end

#alive_evidences(node_id) ⇒ Object



64
65
66
# File 'lib/necropsy/graph/call_graph.rb', line 64

def alive_evidences(node_id)
  @dynamic_alive[node_id] || []
end

#apply_result(result) ⇒ Object



35
36
37
38
39
40
# File 'lib/necropsy/graph/call_graph.rb', line 35

def apply_result(result)
  result.edge_evidences.each { |edge| add_edge(edge.caller_id, edge.callee_id, edge.evidence) }
  result.alive_evidences.each { |alive| add_alive(alive.node_id, alive.evidence) }
  result.uncertainties.each { |node_id, messages| uncertainties[node_id].concat(Array(messages)) }
  observation.merge!(result.observation) { |_key, left, right| merge_observation(left, right) }
end

#candidate_nodes(message) ⇒ Object



113
114
115
# File 'lib/necropsy/graph/call_graph.rb', line 113

def candidate_nodes(message)
  method_nodes.select { |node| node.name == message }
end

#class_info(owner) ⇒ Object



98
99
100
# File 'lib/necropsy/graph/call_graph.rb', line 98

def class_info(owner)
  class_infos[owner]
end

#descendants_of(owner) ⇒ Object



102
103
104
# File 'lib/necropsy/graph/call_graph.rb', line 102

def descendants_of(owner)
  class_infos.keys.select { |candidate| candidate == owner || ancestor_chain(candidate).include?(owner) }
end

#dynamic_alive?(node_id) ⇒ Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/necropsy/graph/call_graph.rb', line 60

def dynamic_alive?(node_id)
  @dynamic_alive.key?(node_id)
end

#dynamic_enabled?Boolean

Returns:

  • (Boolean)


68
69
70
# File 'lib/necropsy/graph/call_graph.rb', line 68

def dynamic_enabled?
  @dynamic_alive.any? || observation.any?
end

#edgesObject



76
77
78
79
80
81
82
# File 'lib/necropsy/graph/call_graph.rb', line 76

def edges
  @edges.flat_map do |caller_id, callees|
    callees.map do |callee_id, evidences|
      Edge.new(caller_id: caller_id, callee_id: callee_id, evidences: evidences)
    end
  end
end

#edges_from(node_id) ⇒ Object



72
73
74
# File 'lib/necropsy/graph/call_graph.rb', line 72

def edges_from(node_id)
  @edges[node_id] || {}
end

#incoming_edges(node_id) ⇒ Object



84
85
86
# File 'lib/necropsy/graph/call_graph.rb', line 84

def incoming_edges(node_id)
  edges.select { |edge| edge.callee_id == node_id }
end

#method_nodesObject



94
95
96
# File 'lib/necropsy/graph/call_graph.rb', line 94

def method_nodes
  nodes.values.select(&:method?)
end

#modules_for(owner) ⇒ Object



106
107
108
109
110
111
# File 'lib/necropsy/graph/call_graph.rb', line 106

def modules_for(owner)
  info = class_info(owner)
  return [] unless info

  (info.prepends + info.includes + info.extends).uniq
end

#resolve_call_site(site, rta: false) ⇒ Object



117
118
119
120
121
# File 'lib/necropsy/graph/call_graph.rb', line 117

def resolve_call_site(site, rta: false)
  candidates = candidates_for_receiver(site)
  candidates = candidates.select { |node| rta_candidate?(node, site) } if rta
  candidates
end

#to_hObject



123
124
125
126
127
128
129
130
131
132
133
# File 'lib/necropsy/graph/call_graph.rb', line 123

def to_h
  {
    'nodes' => nodes.values.map(&:to_h),
    'edges' => edges.map(&:to_h),
    'entry_points' => entry_points.map(&:to_h),
    'class_infos' => class_infos.values.map(&:to_h),
    'instantiated_classes' => instantiated_classes.to_a.sort,
    'profiles' => profiles.map(&:to_h),
    'observation' => observation
  }
end

#uncertainties(node_id = nil) ⇒ Object



88
89
90
91
92
# File 'lib/necropsy/graph/call_graph.rb', line 88

def uncertainties(node_id = nil)
  return @uncertainties unless node_id

  @uncertainties[node_id] || []
end