Ленивая генерация трассировки стека в V8, кажется, вызывает бесконечный цикл в библиотеке vows
Я потратил некоторое время на отладку странной проблемы бесконечного цикла в тестовом наборе NodeJS. Это происходит только в редких случаях, но я могу воспроизвести его, когда присоединяюсь к отладчику Chrome.
Я думаю, что это связано с обработкой V8 следов стека в исключениях и расширением, которое библиотека vows сделала для AssertionError
объект (обеты добавили 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)
Теперь программа заканчивается бесконечным циклом: toString
метод (добавленный библиотекой обетов) вызывается снова и снова, когда this.stack
Доступ к регулярному выражению в строке 83.
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. Он только вычисляет трассировку стека по требованию. При этом он мешает добавленному toString
метод обетов. toString
метод снова обращается к трассировке стека (this.stack
), поэтому цикл продолжается.
Это заключение правильно? Если да, есть ли способ исправить код обетов, чтобы он работал с V8 (или я могу хотя бы сообщить об этом как об ошибке в проекте обетов)?
Я использую узел v0.10.28 под Ubuntu.
Обновление: упрощенный пример без обетов
Вот упрощенная версия. Это больше не зависит от обетов, но вместо этого я скопировал только соответствующие части его 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, но не могу воспроизвести там бесконечный цикл, только с узлом.)
1 ответ
Поздравляем, вы нашли ошибку в V8!
Да, это определенно ошибка в версии 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>");
}
}
Вот фактический код из версии, используемой 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>';
};
Не демонстрирует ту же проблему. Не так много вы могли бы сделать без доступа к основному коду.