为什么SASS异常通过调用`backtrace`变成'nil`?

时间:2014-03-21 21:45:26

标签: ruby exception sass

下面的代码定义了一个钩子Kernel#at_exit来捕获异常并在退出时执行操作,然后通过传递一个无效的SASS字符串来引发Sass::SyntaxError

require "sass"

module Kernel
  at_exit do
    puts "--Before backtrace"
    p $!
    $!.backtrace
    puts "--After backtrace"
    p $!
  end
end

Sass::Engine.new("Invalid {sass").render

它给出的输出如下:

...
--Before backtrace
#<Sass::SyntaxError: ...>
--After backtrace
nil
...

它表示$!Sass::SyntaxError,但在nil被调用之后它就变为backtrace。为什么$!只是通过调用backtrace来改变它?

如果手动引发Sass::SyntaxError,则似乎不会发生此效果,如下所示:

raise Sass::SyntaxError.new("foo")

或引发不同类型的错误(可能是错误的)。

修改

我不确定,但是当引发sass错误时,sass可能使用set_backtrace来操纵回溯。这是为了提供有关sass文件中sass语法错误的位置的信息。并且手动引发错误和以编程方式引发错误之间的不同行为让人联想到Ruby 2.1中的一个错误,即中途执行backtrace_locations但在某些情况下返回nil。我有一个广泛的猜测,这些因素是干扰,但我不确定。

1 个答案:

答案 0 :(得分:1)

它发生的原因是因为此方法会覆盖backtrace方法:

def backtrace
  return nil if super.nil?
  return super if sass_backtrace.all? {|h| h.empty?}
  sass_backtrace.map do |h|
    "#{h[:filename] || "(sass)"}:#{h[:line]}" +
      (h[:mixin] ? ":in `#{h[:mixin]}'" : "")
  end + super
end

其中sass_backtrace是初始化程序中填充的哈希数组。导致$!nil的行为:

return super if sass_backtrace.all? {|h| h.empty?}

仅当all?返回nil时才会发生这种情况。我做了一些摆弄它,我发现问题总是发生,当我们调用任何没有完成整个迭代的迭代器时(all?在遇到第一个不满意的元素时终止迭代)。这个问题可以简单地转载:

at_exit do
  p $!         #=> #<RuntimeError: hello>
  [<any_non_empty_array>].all? {false}

  # Those would break $! as well
  # [<ANA>].any? {true}
  # [1,2,3].find {|n| n.even?}

  # Those will not break $!
  # [<ANA>].any? {false}
  # [<ANA>].all? {true}
  # [1,2,3].find {|n| n > 4}
  p $!         #=> nil
end

raise 'hello'

我能想到它为什么会这样工作的唯一原因是ruby循环在内部受到例外控制。当要停止迭代时,将在循环外部引发特殊类型的异常。

我的猜测是Ruby创建者并不希望在$!变量中看到此控件异常,因为这会表明出现了问题,并决定将其设置回nil