我花了一些时间在 NodeJS 的测试套件中调试一个奇怪的无限循环问题。它只在罕见的情况下发生,但当我连接到 Chrome 调试器时,我可以重现它。
我认为这与 V8 对异常中的堆栈跟踪以及堆栈跟踪API有关,并且是 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)
现在这个程序陷入了一个无限循环中:当在第83行的正则表达式中访问this.stack
时,toString
方法(由vows库添加)会一遍又一遍地被调用。
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)
}
当我在调试器中检查this
时,它显示为AssertionError
,但它的stack
属性是undefined
。然而,当我用鼠标悬停在上面时,它显示实际的堆栈跟踪。我认为这种现象是由V8的惰性优化引起的。它只在需要时计算堆栈跟踪。这样做会干扰vows的
toString
方法的添加。toString
方法再次访问堆栈跟踪(this.stack
),所以循环继续进行。那个结论正确吗?如果是,是否有一种方法可以修补vows代码,使其能够在V8下工作(或者我至少可以将其报告为vows项目中的错误)?
我正在Ubuntu下使用node v0.10.28。
更新:没有vows的简化示例
这是一个简化版本。它不再依赖于vows,而是仅复制了其
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>
在调试模式下,递归发生在 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());
}
我还创建了一个jsfiddle示例,但是我在那里无法复制无限循环的情况,只有在node中才能实现。