查看: 3653|回复: 2

[讨论] eval()中异常的正确位置?

[复制链接]
梦石
0
星屑
65
在线时间
400 小时
注册时间
2005-10-24
回帖
579
发表于 2012-9-23 14:15:44 | 显示全部楼层 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有账号?注册会员

×
本帖最后由 叶子 于 2012-9-26 06:50 编辑

在Ace环境下,eval()里面抛出的异常通常都无法定位到正确的脚本与行号。测试脚本如下
[pre lang="ruby" line="1"]def foo
bar(5)
end

def bar
end

begin
  eval('foo')
rescue Exception=>e
  p e
  p e.backtrace
end

`PAUSE`[/pre]

使用Ace运行结果:
#<ArgumentError: wrong number of arguments (1 for 0)>
["{0151}:9:in `eval'", "(eval):1:in `<main>'", "{0151}:9:in `eval'", "{0151}:9:in `<main>'", "ruby:in `eval'"]

使用ruby 1.9.3运行结果:
#<ArgumentError: wrong number of arguments (1 for 0)>
["eval_test.rb:9:in `eval'", "eval_test.rb:2:in `foo'", "(eval):1:in `<main>'", "eval_test.rb:9:in `eval'", "eval_test.rb:9:in `<main>'"]


可见ruby 1.9.3的stacktrace的第二个是出错的正确位置,而Ace缺失这个信息。
事件脚本党应该深有体会,因为事件运行脚本command_355就是eval(script)
所以有Ace下的获得异常正确位置的解决办法吗?




经过艰苦的讨论,得出一个不完美的解决方案
[pre lang="ruby" line="1"]def wrap_trace_capture(script)
  "
  begin
    #{script}
  rescue Exception=>e
    true_backtrace = Marshal.load(Marshal.dump(e.backtrace))
    e.define_singleton_method(:correct_backtrace) do
      e.backtrace.replace(true_backtrace)
    end
    raise e
  end
  "
end

begin
  eval(wrap_trace_capture('foo'))
rescue Exception=>e
  p e
  p e.backtrace
  e.correct_backtrace if e.respond_to?(:correct_backtrace)
  raise e
end
[/pre]
有任何改进/建议么?




0回复,似乎没什么人在意这个颇消耗开发时间的问题..
放个即插即用版本好了

[pre lang="ruby" line="1"]#==============================================================================
# ■ EvalStackFix
#------------------------------------------------------------------------------
#   修正eval中抛出的异常的backtrace不正确,进而导致脚本编辑器跳转不正确的问题
# 使用方法:
#   直接插入脚本编辑器
#   放置位置应尽量靠近Main
# 注意事项:
#   本代码仅适用于RPG Maker Ace。相同的机制可应用于其他版本
#==============================================================================

module EvalStackFix
  module_function
  #--------------------------------------------------------------------------
  # ● 包装eval内容,截取正确的backtrace
  #--------------------------------------------------------------------------
  def wrap(script)
    "begin
#{script}
  # 上面一行没有缩进是因为script可能包含=begin关键字,而=begin前面不能有空格
rescue Exception=>e
  true_backtrace = Marshal.load(Marshal.dump(e.backtrace)) # deep clone
  e.define_singleton_method(:correct_backtrace) do
    e.backtrace.replace(true_backtrace)
  end
  raise e
end"
  end
end

alias rgss_main_no_eval_stack_fix rgss_main
def rgss_main(&proc)
  begin
    rgss_main_no_eval_stack_fix(&proc)
  rescue Exception=>e
    p e
    e.correct_backtrace if e.respond_to?(:correct_backtrace)
    p e.backtrace
    raise e
  end
end

alias eval_no_stack_fix eval
def eval(script, *args)
  eval_no_stack_fix(EvalStackFix.wrap(script), *args)
end[/pre]
头像被屏蔽
梦石
0
星屑
653
在线时间
3774 小时
注册时间
2011-2-26
回帖
1577

开拓者

发表于 2012-9-27 00:58:54 | 显示全部楼层
提示: 作者被禁止或删除 内容自动屏蔽
签名被屏蔽
回复

使用道具 举报

梦石
0
星屑
65
在线时间
400 小时
注册时间
2005-10-24
回帖
579
 楼主| 发表于 2012-9-27 02:16:10 | 显示全部楼层
晴兰 发表于 2012-9-27 00:58
def foo
bar(5)
end

的确,加了binding就可以让backtrace第一个不被替换成eval了
为啥会这样呢?eval默认就是在现在的binding内执行了

从源代码vm_eval.c#1032来看,应该是跟base_block有关
[pre lang="ruby" line="1"]         if (scope != Qnil) {
            if (rb_obj_is_kind_of(scope, rb_cBinding)) {
                GetBindingPtr(scope, bind);
                envval = bind->env;
                if (strcmp(file, "(eval)") == 0 && bind->path != Qnil) {
                    file = RSTRING_PTR(bind->path);
                    line = bind->first_lineno;
                }
            }
            else {
                rb_raise(rb_eTypeError,
                         "wrong argument type %s (expected Binding)",
                         rb_obj_classname(scope));
            }
            GetEnvPtr(envval, env);
            base_block = &env->block;
        }
        else {
            rb_control_frame_t *cfp = rb_vm_get_ruby_level_next_cfp(th, th->cfp);

            if (cfp != 0) {
                block = *RUBY_VM_GET_BLOCK_PTR_IN_CFP(cfp);
                base_block = &block;
                base_block->self = self;
                base_block->iseq = cfp->iseq;        /* TODO */
            }
            else {
                rb_raise(rb_eRuntimeError, "Can't eval on top of Fiber or Thread");
            }
        }[/pre]
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

Powered by Discuz! X5.0 © 2001-2026 Discuz! Team.

在本版发帖返回顶部