Phi*_*ßen 11 javascript v8 node.js vows node-inspector
我花了一些时间在NodeJS测试套件中调试一个奇怪的无限循环问题.它只在罕见的条件下发生,但我可以在附加到chrome调试器时重现它.
我认为它与V8处理异常中的堆栈跟踪以及vows库对AssertionError对象所做的扩展(vows添加了一个toString方法)有关.我也错了,所以我想问一下我对V8实现的理解是否正确.
这是重现错误的最小示例:
$ git clone https://github.com/flatiron/vows.git
$ cd vows && npm install && npm install should
$ cat > example.js
var should = require('should');
var error = require('./lib/assert/error.js');
try {
'x'.should.be.json;
} catch (e) {
console.log(e.toString());
}
// without debug, it should fail as expected
$ node example.js
expected 'x' to have property 'headers' // should.js:61
// now with debug
$ node-inspector &
$ node --debug-brk example.js
// 1) open http://127.0.0.1:8080/debug?port=5858 in Chrome
// 2) set breakpoint at lib/assert/error.js#79 (the toString method)
// 3) Resume script execution (F8)
Run Code Online (Sandbox Code Playgroud)
现在程序以无限循环结束:在第83行的regexp中访问toString时,一次又一次地调用方法(由vows库添加)this.stack.
require('assert').AssertionError.prototype.toString = function () {
var that = this, // line 79: breakpoint
source;
if (this.stack) {
source = this.stack.match(/([a-zA-Z0-9._-]+\.(?:js|coffee))(:\d+):\d+/); // line 83: infinite loop takes place here (however, this.stack is undefined)
}
Run Code Online (Sandbox Code Playgroud)
当我this在调试器中检查时,它显示它是一个AssertionError但它的stack属性是undefined.但是,当我将鼠标悬停在它上面时,它会显示实际的堆栈跟踪.
我认为这种现象是由V8的懒惰优化引起的.它只根据需要计算堆栈跟踪.这样做会干扰增加toString的誓言方法.该toString方法再次访问堆栈跟踪(this.stack),因此循环继续.
这个结论是否正确?如果是这样,有没有办法修补誓言代码,所以它适用于V8(或者我至少可以将其报告为誓言项目中的错误)?
我在Ubuntu下使用节点v0.10.28.
更新:没有誓言的简化示例
这是一个简化版本.它不再依赖于誓言,而是我只复制了其toString扩展的相关部分:
var should = require('should');
require('assert').AssertionError.prototype.toString = function () {
var that = this,
source;
if (this.stack) {
source = this.stack.match(/([a-zA-Z0-9._-]+\.(?:js|coffee))(:\d+):\d+/);
}
return '<dummy-result>';
};
try {
'x'.should.be.json;
} catch (e) {
console.log(e.toString());
}
// expected result (without debug mode)
$ node example.js
<dummy-result>
Run Code Online (Sandbox Code Playgroud)
在调试模式下,递归发生在if语句中.
更新:使用ReferenceError的更简单版本
ReferenceError.prototype.toString = function () {
var that = this,
source;
if (this.stack) {
source = this.stack.match(/([a-zA-Z0-9._-]+\.(?:js|coffee))(:\d+):\d+/);
}
return '<dummy-result>';
};
try {
throw new ReferenceError('ABC');
} catch (e) {
console.log(e.toString());
}
Run Code Online (Sandbox Code Playgroud)
(我也创建了一个jsfiddle示例,但我无法在那里重现无限循环,只有节点.)
是的,这绝对是该版本节点中V8版本的一个错误.
V8版本中的代码您的Node版本使用的代码如下所示:
function FormatStackTrace(error, frames) {
var lines = [];
try {
lines.push(error.toString());
} catch (e) {
try {
lines.push("<error: " + e + ">");
} catch (ee) {
lines.push("<error>");
}
}
Run Code Online (Sandbox Code Playgroud)
这是NodeJS使用的版本的实际代码.
它正在做的事实error.toString()是导致循环,this.stack访问FormatStackTrace反过来正在做.toString().你的观察是正确的.
如果这是一种安慰,那么在新版本的V8中,该代码看起来非常不同.在节点0.11中,此错误已得到修复.
这是Vyacheslav Egorov在一年半前修改它的提交.
好吧,你的选择是有限的,但由于这是为了调试,总是可以阻止第二次迭代并停止循环:
ReferenceError.prototype.toString = function () {
var source;
if(this.alreadyCalled) return "ReferenceError";
this.alreadyCalled = true;
if (this.stack) {
source = this.stack.match(/([a-zA-Z0-9._-]+\.(?:js|coffee))(:\d+):\d+/);
}
return '<dummy-result>';
};
Run Code Online (Sandbox Code Playgroud)
没有表现出同样的问题.没有核心代码,你就无法做更多的事情.
| 归档时间: |
|
| 查看次数: |
263 次 |
| 最近记录: |