草庐IT

递归 Hook 时 Javascript 丢失上下文

coder 2025-01-09 原文

我开始研究 JS 的动态分析工具,我想不引人注意地分析整个环境。我基本上是在遍历各种上下文,深入研究对象,每次我遇到一个函数时,我都会 Hook 它。现在,除了在处理 jQuery/prototype 等库时它会中断之外,它工作得相对较好。

这是我到目前为止的代码(尽我所能评论):

var __PROFILER_global_props = new Array(); // visited properties

/**
* Hook into a function
* @name the name of the function
* @fn the reference to the function
* @parent the parent object
*/
function __PROFILER_hook(name, fn, parent) {
    //console.log('hooking ' + name + ' ' + fn + ' ' + parent);

    if (typeof parent == 'undefined')
        parent = window;

    for (var i in parent) {
        // find the right function
        if (parent[i] === fn) {
            // hook into it
            console.log('--> hooking ' + name);
                parent[i] = function() {
                        console.log('called ' + name);
                        return fn.apply(parent, arguments);
                }

                //parent[i] = fn; // <-- this works (obviously)
                break;
        }
    }
}

/**
* Traverse object recursively, looking for functions or objects
* @obj the object we're going into
* @parent the parent (used for keeping a breadcrumb)
*/
function __PROFILER_traverse(obj, parent) {
    for (i in obj) {
        // get the toString object type
        var oo = Object.prototype.toString.call(obj[i]);
        // if we're NOT an object Object or an object Function
        if (oo != '[object Object]' && oo != '[object Function]') {
            console.log("...skipping " + i);
            // skip
            // ... the reason we do this is because Functions can have sub-functions and sub-objects (just like Objects)
            continue;
        }
        if (__PROFILER_global_props.indexOf(i) == -1 // first we want to make sure we haven't already visited this property
            && (i != '__PROFILER_global_props'       // we want to make sure we're not descending infinitely
            && i != '__PROFILER_traverse'            // or recusrively hooking into our own hooking functions
            && i != '__PROFILER_hook'                // ...
            && i != 'Event'              // Event tends to be called a lot, so just skip it
            && i != 'log'                // using FireBug for debugging, so again, avoid hooking into the logging functions
            && i != 'notifyFirebug')) {          // another firebug quirk, skip this as well

            // log the element we're looking at
            console.log(parent+'.'+i);
            // push it.. it's going to end up looking like '__PROFILER_BASE_.something.somethingElse.foo'
            __PROFILER_global_props.push(parent+'.'+i);
            try {
                // traverse the property recursively
                __PROFILER_traverse(obj[i], parent+'.'+i);
                // hook into it (this function does nothing if obj[i] is not a function)
                __PROFILER_hook(i, obj[i], obj);
            } catch (err) {
                // most likely a security exception. we don't care about this.
            }
        } else {
            // some debugging
            console.log(i + ' already visited');
        }
    }
}

这就是配置文件,这就是我调用它的方式:

// traverse the window
__PROFILER_traverse(window, '__PROFILER_BASE_');

// testing this on jQuery.com
$("p.neat").addClass("ohmy").show("slow");

只要函数简单且非匿名,遍历和 Hook 就可以正常工作(我认为 Hook 到匿名函数是不可能的,所以我不太担心)。

这是预处理阶段的一些 trim 输出。

notifyFirebug already visited
...skipping firebug
...skipping userObjects
__PROFILER_BASE_.loadFirebugConsole
--> hooking loadFirebugConsole
...skipping location
__PROFILER_BASE_.$
__PROFILER_BASE_.$.fn
__PROFILER_BASE_.$.fn.init
--> hooking init
...skipping selector
...skipping jquery
...skipping length
__PROFILER_BASE_.$.fn.size
--> hooking size
__PROFILER_BASE_.$.fn.toArray
--> hooking toArray
__PROFILER_BASE_.$.fn.get
--> hooking get
__PROFILER_BASE_.$.fn.pushStack
--> hooking pushStack
__PROFILER_BASE_.$.fn.each
--> hooking each
__PROFILER_BASE_.$.fn.ready
--> hooking ready
__PROFILER_BASE_.$.fn.eq
--> hooking eq
__PROFILER_BASE_.$.fn.first
--> hooking first
__PROFILER_BASE_.$.fn.last
--> hooking last
__PROFILER_BASE_.$.fn.slice
--> hooking slice
__PROFILER_BASE_.$.fn.map
--> hooking map
__PROFILER_BASE_.$.fn.end
--> hooking end
__PROFILER_BASE_.$.fn.push
--> hooking push
__PROFILER_BASE_.$.fn.sort
--> hooking sort
__PROFILER_BASE_.$.fn.splice
--> hooking splice
__PROFILER_BASE_.$.fn.extend
--> hooking extend
__PROFILER_BASE_.$.fn.data
--> hooking data
__PROFILER_BASE_.$.fn.removeData
--> hooking removeData
__PROFILER_BASE_.$.fn.queue

当我在 jQuery.com 上(通过 Firebug)执行 $("p.neat").addClass("ohmy").show("slow"); 时,我得到了一个适当的调用堆栈,但我似乎在某个地方失去了我的上下文,因为没有任何反应,我从 jQuery 得到一个 e is undefined 错误(很明显, Hook 搞砸了一些事情)。

called init
called init
called find
called find
called pushStack
called pushStack
called init
called init
called isArray
called isArray
called merge
called merge
called addClass
called addClass
called isFunction
called isFunction
called show
called show
called each
called each
called isFunction
called isFunction
called animate
called animate
called speed
called speed
called isFunction
called isFunction
called isEmptyObject
called isEmptyObject
called queue
called queue
called each
called each
called each
called each
called isFunction
called isFunction

问题是我认为我在调用时丢失了 this 上下文

return fn.apply(parent, arguments);

这是另一个有趣的怪癖。如果我在遍历之前 Hook ,即:

        // hook into it (this function does nothing if obj[i] is not a function)
        __PROFILER_hook(i, obj[i], obj);
        // traverse the property recursively
        __PROFILER_traverse(obj[i], parent+'.'+i);

.. 应用程序运行得非常好,但是由于某种原因调用堆栈被改变了(而且我似乎没有得到特定于 jQuery 的函数):

called $
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called setInterval
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called getComputedStyle
called clearInterval

.. 而不是 animationshowmerge 等等。现在,钩子(Hook)所做的只是说 called functionName 但最终我想执行堆栈跟踪和时间函数(通过 Java applet)。

这个问题最终变得很大,我深表歉意,但感谢您的帮助!

注意:如果您不小心,上面的代码可能会使您的浏览器崩溃。公平警告:P

最佳答案

我认为您走在正确的轨道上。当您使用 apply 时,this 的值会被破坏。 jQuery 中定义的函数可能在内部通过 apply 调用,并且取决于 this 的值。

apply 的第一个参数是将用于this 的值。您确定应该为此使用 parent 吗?

我能够通过以下方式重现该问题:

var obj = {
   fn : function() { 
      if(this == "monkeys") {
         console.log("Monkeys are funny!");
      }

      else {
         console.log("There are no monkeys :(");
      }
   }
};

obj.fn.apply("monkeys");

var ref = obj.fn;

//assuming parent here is obj
obj.fn = function() {
   console.log("hooking to obj.fn");
   return ref.apply(obj);
};

obj.fn.apply("monkeys");

这里,函数依赖于 this 的值来打印文本 Monkeys are funny!。如您所见,使用您的hook 算法,此上下文丢失了。 Firebug 显示:

Monkeys are funny!
hooking to obj.fn
There are no monkeys :(

I made a slight change and used this in the apply instead of obj (the parent):

obj.fn = function() {
   console.log("hooking to obj.fn");
   return ref.apply(this);
};

这次 Firebug 说:

Monkeys are funny!
hooking to obj.fn
Monkeys are funny!

The root of your problem IMHO is that you're setting an explicit value for this (i.e., parent which refers to the parent object). So your hook function ends up overwriting the value of this, which might have been explicitly set by whichever code that is calling the original function. Of course, that code doesn't know that you wrapped the original function with your own hook function. So your hook function should preserve the value of this when it is calling the original function:

return fn.apply(this, arguments);

因此,如果您在申请中使用 this 而不是 parent,您的问题可能会得到解决。

如果我没有正确理解您的问题,我深表歉意。不对的地方请指正。

更新

jQuery 中有两种函数。那些附加到 jQuery 对象本身(有点像静态方法)然后你有一些对 jQuery(selector) 的结果进行操作(有点像实例方法)。你需要关心的是后者。在这里,this 非常重要,因为这是您实现链接的方式。

我能够让下面的例子工作。请注意,我正在处理对象的实例,而不是对象本身。因此,在您的示例中,我将处理 jQuery("#someId") 而不仅仅是 jQuery:

var obj = function(element) {
   this.element = element;
   this.fn0 = function(arg) {
      console.log(arg, element);
      return this;
   }

   this.fn1 = function(arg) {
      console.log(arg, arg, element);
      return this;
   }

   if(this instanceof obj) {
      return this.obj;
   }

   else {
      return new obj(element);
   }
};

var objInst = obj("monkeys");

var ref0 = objInst.fn0;

objInst.fn0 = function(arg) {
   console.log("calling f0");
   return ref0.apply(this, [arg]);
};

var ref1 = objInst.fn1;

objInst.fn1 = function(arg) {
   console.log("calling f1");
   return ref1.apply(this, [arg]);
};

objInst.fn0("hello").fn1("bye");

我不知道这是否能解决您的问题。也许查看 jQuery 源代码会给您更多的见解 :)。我认为您的困难在于区分通过 apply 调用的函数和通过链接调用(或直接调用)的函数。

关于递归 Hook 时 Javascript 丢失上下文,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6157882/

有关递归 Hook 时 Javascript 丢失上下文的更多相关文章

  1. ruby - 在 Ruby 中,在类方法的上下文中,什么是实例变量和类变量? - 2

    如果我有以下一段Ruby代码:classBlahdefself.bleh@blih="Hello"@@bloh="World"endend@blih和@@bloh到底是什么?@blih是Blah类中的一个实例变量,@@bloh是Blah类中的一个类变量,对吗?这是否意味着@@bloh是Blah的类Class中的一个变量? 最佳答案 人们似乎忽略了该方法是类方法。@blih将是常量Bleh的类Class实例的实例变量。因此:irb(main):001:0>classBlehirb(main):002:1>defself.blehirb

  2. ruby-on-rails - 在所有延迟的作业之前 Hook - 2

    是否可以在所有delayed_job任务之前运行一个方法?基本上,我们试图确保每个运行delayed_job的服务器都有我们代码的最新实例,所以我们想运行一个方法来在每个作业运行之前检查它。(我们已经有了“check”方法并在别处使用它。问题只是关于如何从delayed_job中调用它。) 最佳答案 现在有一种官方方法可以通过插件来做到这一点。这篇博文通过示例清楚地描述了如何执行此操作http://www.salsify.com/blog/delayed-jobs-callbacks-and-hooks-in-rails(本文中描述

  3. ruby-on-rails - 使用 javascript 更改数据方法不会更改 ajax 调用用户的什么方法? - 2

    我遇到了一个非常奇怪的问题,我很难解决。在我看来,我有一个与data-remote="true"和data-method="delete"的链接。当我单击该链接时,我可以看到对我的Rails服务器的DELETE请求。返回的JS代码会更改此链接的属性,其中包括href和data-method。再次单击此链接后,我的服务器收到了对新href的请求,但使用的是旧的data-method,即使我已将其从DELETE到POST(它仍然发送一个DELETE请求)。但是,如果我刷新页面,HTML与"new"HTML相同(随返回的JS发生变化),但它实际上发送了正确的请求类型。这就是这个问题令我困惑的

  4. ruby - 刚刚分配的变量是否有 ruby 钩子(Hook)? - 2

    这是我理想中想要的。用户做:a="hello"输出为Youjustallocated"a"!=>"Hello"顺序无关紧要,只要我能实现该消息即可。 最佳答案 不,没有直接的方法可以做到这一点,因为在执行代码之前,Ruby字节码编译器会丢弃局部变量名。YARV(MRI1.9.2中使用的RubyVM)提供的关于局部变量的唯一指令是getlocal和setlocal,它们都对整数索引进行操作,而不是变量名。以下是1.9.2源代码中insns.def的摘录:/****************************************

  5. ruby - 递归地将所有数字字符串转换为 Ruby 哈希中的整数 - 2

    我有一个随机大小的散列,它可能有类似"100"的值,我想将其转换为整数。我知道我可以使用value.to_iifvalue.to_i.to_s==value来做到这一点,但我不确定我将如何在我的散列中递归地做到这一点,考虑到一个值可以是一个字符串,或一个数组(哈希或字符串),或另一个哈希。 最佳答案 这是一个非常简单的递归实现(尽管必须同时处理数组和散列会增加一些技巧)。deffixnumifyobjifobj.respond_to?:to_i#IfwecancastittoaFixnum,doit.obj.to_ielsifobj

  6. Ruby:标准递归模式 - 2

    我经常迷上ruby​​的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情

  7. ruby - 在 Ruby 中的另一个上下文中评估潜在的相对 URI - 2

    我在Ruby程序中有两个URI。一个肯定是绝对URI,另一个可能是绝对URI或相对URI。我想在第一个的上下文中将第二个转换为绝对URI,所以如果第一个是http://pupeno.com/blog第二个是/about,结果应该是http://pupeno.com/about.有什么想法吗? 最佳答案 Ruby的内置URI和Addressablegem,做这个简短的工作。我更喜欢Addressable,因为它功能更全面,但URI是内置的。require'uri'URI.join('http://pupeno.com/blog','/

  8. ruby - 为什么我用递归得到 "stack level too deep"? - 2

    我有这个ruby代码:defget_sumnreturn0ifn似乎正在为999之前的值工作。当我尝试9999时,它给了我这个:stackleveltoodeep(SystemStackError)所以,我添加了这个:RubyVM::InstructionSequence.compile_option={:tailcall_optimization=>true,:trace_instruction=>false}但什么也没发生。我的ruby版本是:ruby1.9.3p392(2013-02-22revision39386)[x86_64-darwin12.2.1]我还增加了机器的堆栈大

  9. ruby - 在 Mechanize 中使用 JavaScript 单击链接 - 2

    我有这个:AccountSummary我想单击该链接,但在使用link_to时出现错误。我试过:bot.click(page.link_with(:href=>/menu_home/))bot.click(page.link_with(:class=>'top_level_active'))bot.click(page.link_with(:href=>/AccountSummary/))我得到的错误是:NoMethodError:nil:NilClass的未定义方法“[]” 最佳答案 那是一个javascript链接。Mechan

  10. ruby-on-rails - Ubuntu 14.04 Rails 丢失文件 - 2

    安装Rails时,一切都很好,但后来,我写道:rails-v和输出:/home/toshiba/.rvm/rubies/ruby-2.2.1/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in`require':cannotloadsuchfile--rails/cli(LoadError)from/home/toshiba/.rvm/rubies/ruby-2.2.1/lib/ruby/site_ruby/2.2.0/rubygems/core_ext/kernel_require.rb:54:in`r

随机推荐