如何在Ruby中抛出对象时查看调用堆栈

How to view the call stack when an object is thrown in Ruby

warden gem使用throwcatch函数来处理控制流。 这使得调试身份验证系统变得非常困难,特别是在devise中,其中gem throw:warden符号,而warden gem catch是它。

我的程序,我有一个(间接)抛出:warden符号的函数,但我不知道throw函数的确切调用站点是什么。 有没有办法找到它?

似乎我们不能使用TracePoint类,因为它只支持raise事件。


通过捕获c_call,我能够使用TracePoint获得一些工作并且throw是一个c语言例程:

1
2
3
4
5
TracePoint.new(:c_call) do |trace|
  if trace.method_id == :throw
    p [trace.path, trace.lineno]
  end
end

这只会让你到达实际调用throw的位置,而不是那个被调用的所有内容的完整堆栈跟踪,尽管你也可以使用catch :call,并将一些东西放在一起以捕获更多信息。 作为一个简单的例子:

1
2
3
4
5
6
7
TracePoint.new(:call, :c_call) do |trace|
  if trace.event == :call || trace.method_id == :throw
    p [trace.method_id, trace.path, trace.lineno]
  end

  trace.disable if trace.method_id == :throw
end

完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
# might_throw_cont.rb
def might_throw_second
  throw :warden if rand(100) < 10

  might_throw_third
end

def might_throw_third
  throw :warden if rand(100) < 10

  might_throw_final
end

# example.rb
require './might_throw_cont'

def might_throw_first
  throw :warden if rand(100) < 10

  might_throw_second
end

def might_throw_final
  throw :warden if rand(100) < 10

  will_throw
end

def will_throw
  throw :warden
end

TracePoint.new(:call, :c_call) do |trace|
  if trace.event == :call || trace.method_id == :throw
    p [trace.method_id, trace.path, trace.lineno]
  end

  trace.disable if trace.method_id == :throw
end.enable do
  catch :warden do
    might_throw_first
  end

  puts"done"
end

显然,在这个例子中很难说出哪个方法实际上抛出了符号。 但运行该示例,我将能够在输出中看到几次(2个示例运行):

1
2
3
4
5
6
7
8
9
10
11
12
13
# run 1
[:might_throw_first,"example.rb", 3]
[:might_throw_second,"/Users/simplelime/Documents/Ruby/might_throw_cont.rb", 1]
[:might_throw_third,"/Users/simplelime/Documents/Ruby/might_throw_cont.rb", 7]
[:might_throw_final,"example.rb", 9]
[:will_throw,"example.rb", 15]
[:throw,"example.rb", 16] # < only line you'll see if you trace only :c_call
done

# run 2
[:might_throw_first,"example.rb", 3]
[:throw,"example.rb", 4] # < only line you'll see if you trace only :c_call
done