2013-03-23 4 views
0

Скажем, я хочу получить какую-то трассировку стека, получив имя всех функций, которые были вызваны до текущей функции.Перемещение аргументов.callee.caller вызывает бесконечный цикл

я сделать что-то вроде этого:

 var callee; 
     var caller; 
     var _args = arguments; 
     var check = 0; 
     do { 
        check++; 

        callee = _args.callee; 
        caller = callee.caller; 

        var msg = 'Check ' + check + ' - ' + callee.name 
          + ' has been called by: ' + caller.name; 
        console.log(msg); 

        if (caller) { 
         // Get this caller's arguments 
         _args = caller.arguments; 
        } else { 
         reached_end = true; 
        } 

     } while (!reached_end); 

Это прекрасно работает, большую часть времени. Но иногда он застревает в бесконечном цикле, и мне интересно: как это возможно? И что я могу сделать с этим?

Вот результат моего бесконечного цикла:

Check 1 - __parent__ has been called by: add 
    Check 2 - add has been called by: afterComponentStartup 
    Check 3 - afterComponentStartup has been called by: _launchComponents [arg0:"startup"] 
    Check 4 - _launchComponents has been called by: beforeActionNext 
    Check 5 - beforeActionNext has been called by: beforeAction 
    Check 6 - beforeAction has been called by: afterComponentInitialize 
    Check 7 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"] 
    Check 8 - _launchComponents has been called by: beforeActionNext 
    Check 9 - beforeActionNext has been called by: beforeAction 
    Check 10 - beforeAction has been called by: afterComponentInitialize 
    Check 11 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"] 
    Check 12 - _launchComponents has been called by: beforeActionNext 
    Check 13 - beforeActionNext has been called by: beforeAction 
    Check 14 - beforeAction has been called by: afterComponentInitialize 
    Check 15 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"] 
    Check 16 - _launchComponents has been called by: beforeActionNext 
    Check 17 - beforeActionNext has been called by: beforeAction 
    Check 18 - beforeAction has been called by: afterComponentInitialize 
    Check 19 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"] 
    Check 20 - _launchComponents has been called by: beforeActionNext 
    Check 21 - beforeActionNext has been called by: beforeAction 
    Check 22 - beforeAction has been called by: afterComponentInitialize 
    Check 23 - afterComponentInitialize has been called by: _launchComponents [arg0:"startup"] 
    Check 24 - _launchComponents has been called by: beforeActionNext 
    Check 25 - beforeActionNext has been called by: beforeAction 
    Check 26 - beforeAction has been called by: afterComponentInitialize 
+0

['console.trace()'] (http://www.nodejs.org/api/stdio.html#stdio_console_trace_label)? –

ответ

4

arguments.callee.caller указывает на ссылку на функцию, которой она существует только один из каждой функции в каждом стеке вызовов.

Каждый раз, когда вызывается функция, устанавливается значение caller, что означает, что если одна и та же функция была вызвана несколько раз в стеке вызовов, как это происходит с рекурсивными функциями, предыдущее значение будет сброшено, а caller будет теперь указывайте на себя. Это то, что вызывает бесконечные петли.

Так что в вашем алгоритме, если вы дойдете до точки, где callee === callee.caller, вам нужно сломать, чтобы этого не произошло.

1

Вы должны добавить абонентов в массиве и проверить, является ли следующий вызывающий абонент находится в массиве. Если это так, то у вас есть циклическая или рекурсивная структура вызова. Попробуйте это:

var args = arguments; 
var callee = args.callee; 
var caller = callee.caller; 

var stack = [callee]; 

while (caller) { 
    if (stack.indexOf(caller) < 0) { 
     stack.push(caller); 
     args = caller.arguments; 
     callee = args.callee; 
     caller = callee.caller; 
    } else break; 
} 

console.log(stack); 
0

Я нашел способ передать информацию состояния (var .ua (Object) в этом случае) в callstack рекурсивных функций;

вместо

var fwa.animatedJavascriptControlCenter = { 
    ... 
    , 
    scanElements : function (el, scanResult) { 
    // a function that walks the DOM tree to find elements that require molding by my js framework 
    if (!scanResult) scanResult={}; 
    if (el.tagName.toLowerCase()!='svg') { 
     if (conditions_for_this_element_met(el)) { 
      scanResults[el.id] = el; 
     }; 
     if (el.children.length>0) { 
      for (var i=0; i < el.children.length; i++) { 
       if (el.children[i].tagName.toUpperCase()!=='IFRAME') { 
        scanResult = fwa.animatedJavascriptControlCenter.scanElements(el.children[i], scanResult); 
       } 
      } 
     } 
    } 
    return scanResult; 
    }, 
    ... 
} 

сделать:

var fwa.animatedJavascriptControlCenter = { 
    ... 
    , 
    scanElements : function (el, scanResult) { 
     if (!scanResult) scanResult={}; 
     if (el.tagName.toLowerCase()!='svg') { 
      if (conditions_for_this_element_met(el)) { 
       scanResults[el.id] = el; 
      } 
      if (el.children.length>0) { 
       for (var i=0; i < el.children.length; i++) { 
        if (el.children[i].tagName.toUpperCase()!=='IFRAME') { 
         var args = [el.children[i], scanResult]; 
         args.ua= tracer.findUA(arguments); 
         var passUAfunc = function(scanResult) { 
          return fwa.animatedJavascriptControlCenter.scanElements(el.children[i], scanResult); 
         } 
         passUAfunc.ua = args.ua; 
         scanResult = passUAfunc(scanResult); 
        } 
       } 
      } 
     } 
     return scanResult; 
    }, 
    ... 
} 

, чтобы сделать эту работу, вам также потребуется:

// Bonus : ;-) 
// Enable the passage of the 'this' object through the JavaScript timers 
// thanks to https://developer.mozilla.org/en/docs/DOM/window.setTimeout#Callback_arguments 
var __nativeST__ = window.setTimeout, __nativeSI__ = window.setInterval; 
window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) { 
    var 
    oThis = this, 
    ua = tracer.findUA(arguments), 
    aArgs = Array.prototype.slice.call(arguments, 2); 
    if (ua) aArgs.ua = ua; 
    //if (!ua) debugger; 
    return __nativeST__(vCallback instanceof Function ? function() { 
     vCallback.apply(oThis, aArgs); 
    } : vCallback, nDelay); 
}; 
window.setInterval = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) { 
    var 
    oThis = this, 
    aArgs = Array.prototype.slice.call(arguments, 2), 
    ua = tracer.findUA(arguments); 
    if (ua) aArgs.ua = ua; 
    return __nativeSI__(vCallback instanceof Function ? function() { 
     vCallback.apply(oThis, aArgs); 
    } : vCallback, nDelay); 
}; 

var tracer = { 
/* object last modified : 14 May 2013, 04:46 CEST 

    original code by http://javascriptweblog.wordpress.com/2010/06/01/a-tracer-utility-in-2kb/ 

    modified by [email protected] with: 
    (1) http://stackoverflow.com/a/15582432/2379929 

    augmented by [email protected] with (among other things): 
    http://stacktracejs.com/ 

    this code is now used in and offered as part of the web-framework at http://fancywebapps.com 
    (component name : hipLog, to be released later) 
*/ 


    nativeCodeEx: /\[native code\]/, 
    tracing: [], 
    traced : [], 
    userActions : [], 

    findUA : function (arg) { 
     var p = arg; 
     if (p.ua) return p.ua; 

     var callee = arg.callee; 
     var caller = callee.caller; 

     var stack = [callee]; 

     while (caller) { 
      if (stack.indexOf(caller) < 0) { 
       stack.push(caller); 
       args = caller.arguments; 
       callee = args.callee; 
       caller = callee.caller; 
      } else break; 
     } 


     while (p = stack.shift()) { 
      if (p.ua) return p.ua; 
      if (p.arguments && p.arguments.ua) return p.arguments.ua; 

     }; 
     return false; 
    }, 

    traceMe: function(func, methodName, path) { 
     var traceOn = function() { 
      var 
      ua = tracer.findUA(arguments), 
      startTime = +new Date; 

      if (!ua){ 
       //debugger; 
       //ua = tracer.findUA(arguments); 
      }; 
      //if (path=='fwa.animatedJavascriptControlCenter.scanElements') debugger; 

      if (!ua) { 
       //debugger; 
       var 
       uaIdx = tracer.userActions.length, 
       ua = tracer.userActions[uaIdx] = { 
        uaIdx : uaIdx, 
        startTime : startTime, 
        path : path, 
        stackLevel : 0 
       }; 
       tracer.traced[uaIdx] = []; 
      } else { 
       var uaIdx = ua.uaIdx; 
       ua.stackLevel++; 
      } 

      arguments.ua = ua; 

      var idx = tracer.traced[uaIdx].length; 
      tracer.traced[uaIdx][idx] = { 
       path : path, 
       arguments : arguments 
      }; 

      var result = func.apply(this, arguments); 

      tracer.traced[uaIdx][idx].stacktrace = printStackTrace() // see http://stacktracejs.com, free, quite useful 
      tracer.traced[uaIdx][idx].result = result; 
      tracer.traced[uaIdx][idx].timed = new Date - startTime; 
      tracer.traced[uaIdx][idx].stackLevel = ua.stackLevel; 
      ua.stackLevel--; 
      return result; 
     }; 
     traceOn.traceOff = func; 
     for (var prop in func) { 
      traceOn[prop] = func[prop]; 
     } 
     console.log("tracing " + path); 
     return traceOn; 
    }, 

    traceAll: function(root, path, recurse) { 
     if ((root == window) || !((typeof root == 'object') || (typeof root == 'function'))) {return;} 
     for (var key in root) { 
      if ((root.hasOwnProperty(key)) && (root[key] != root)) { 
       var thisObj = root[key]; 
       if (typeof thisObj == 'function') { 
        if ((this != root) && !thisObj.traceOff && !this.nativeCodeEx.test(thisObj)) { 
         root[key] = this.traceMe(root[key], key, path+'.'+key); 
         this.tracing.push({obj:root,methodName:key, path:path+'.'+key}); 
        } 
       } 
       recurse && this.traceAll(thisObj, path+'.'+key, true); 
      } 
     } 
    }, 

    untraceAll: function() { 
     for (var i=0; i<this.tracing.length; ++i) { 
      var thisTracing = this.tracing[i]; 
      thisTracing.obj[thisTracing.methodName] = 
       thisTracing.obj[thisTracing.methodName].traceOff; 
     } 
     //console.log("tracing disabled"); 
     tracer.tracing = []; 
    } 
} 

использоваться как:

// call this just after _completely_ defining yourFrameworkRootObject and yourSiteCodeRootObject 
tracer.traceAll (yourFrameworkRootObject, true); 
tracer.traceAll (yourSiteCodeRootObject, true); 

FYI: этот код был взят из моего js-фреймворка http://fancywebapps.com и будет частью его hipLog-компонента, который предназначен для отображения полного пути выполнения (с или без округлых поездок на сервер и/или setTimeout() s) любой пользовательский доступ, такой как клики, зависания или загрузка страниц.

Смежные вопросы