Module: Bulldogger::Probe::TargetResolver

Defined in:
lib/bulldogger/probe/target_resolver.rb

Overview

Turns "Klass#method" / "Klass.method" strings into Targets, failing before any TracePoint is enabled. Resolving every target up front (not lazily, on first call) is the contract's own requirement: a probe session must not let bad instrumentation surface only after the caller's code has already started running.

Constant Summary collapse

INSTANCE_PATTERN =
/\A(.+)#(.+)\z/
SINGLETON_PATTERN =
/\A(.+)\.(.+)\z/

Class Method Summary collapse

Class Method Details

.assert_targetable!(unbound_method, target_string) ⇒ Object

The only way to learn that a method is C-implemented is to actually try targeting it: UnboundMethod itself resolves fine for a C method (Array.instance_method(:push) succeeds); it is TracePoint#enable(target:) that raises ArgumentError (measured: "specified target is not supported", Array#push). The probe TracePoint built here is disabled again immediately -- its only purpose is to surface that error before any of the caller's targets go live.



76
77
78
79
80
81
82
83
# File 'lib/bulldogger/probe/target_resolver.rb', line 76

def assert_targetable!(unbound_method, target_string)
  probe_tp = TracePoint.new(:call) {}
  probe_tp.enable(target: unbound_method)
  probe_tp.disable
rescue ArgumentError
  raise ArgumentError,
        "bulldogger: probe target #{target_string.inspect} is a C-implemented method and can't be traced"
end

.resolve!(target_strings) ⇒ Object



19
20
21
# File 'lib/bulldogger/probe/target_resolver.rb', line 19

def resolve!(target_strings)
  target_strings.map { |s| resolve_one!(s) }
end

.resolve_constant!(owner_name, target_string) ⇒ Object

const_get(name, false) at each nesting level (not a single Object.const_get("A::B")) so a wrong nested name fails with the segment that is actually missing, and so this never accidentally resolves a same-named top-level constant that const_get's own inherit-search could reach from a deeper module (the false argument).



49
50
51
52
53
54
55
56
# File 'lib/bulldogger/probe/target_resolver.rb', line 49

def resolve_constant!(owner_name, target_string)
  owner_name.split("::").reject(&:empty?).reduce(Object) do |mod, name|
    mod.const_get(name, false)
  end
rescue NameError
  raise NameError,
        "bulldogger: no constant #{owner_name.inspect} for probe target #{target_string.inspect}"
end

.resolve_one!(target_string) ⇒ Object



23
24
25
26
27
28
29
# File 'lib/bulldogger/probe/target_resolver.rb', line 23

def resolve_one!(target_string)
  owner_name, method_name, kind = split(target_string)
  owner = resolve_constant!(owner_name, target_string)
  unbound_method = resolve_unbound_method!(owner, method_name, kind, target_string)
  assert_targetable!(unbound_method, target_string)
  Target.new(target_string, unbound_method)
end

.resolve_unbound_method!(owner, method_name, kind, target_string) ⇒ Object



58
59
60
61
62
63
64
65
66
# File 'lib/bulldogger/probe/target_resolver.rb', line 58

def resolve_unbound_method!(owner, method_name, kind, target_string)
  if kind == :instance
    owner.instance_method(method_name.to_sym)
  else
    owner.method(method_name.to_sym).unbind
  end
rescue NameError
  raise NameError, "bulldogger: no method for probe target #{target_string.inspect}"
end

.split(target_string) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
# File 'lib/bulldogger/probe/target_resolver.rb', line 31

def split(target_string)
  if (m = INSTANCE_PATTERN.match(target_string))
    [m[1], m[2], :instance]
  elsif (m = SINGLETON_PATTERN.match(target_string))
    [m[1], m[2], :singleton]
  else
    raise ArgumentError,
          "bulldogger: invalid probe target #{target_string.inspect} " \
          '(expected "Klass#method" or "Klass.method")'
  end
end