Подтвердить что ты не робот

Почему вложенность кучи блоков вызывает переполнение стека в JavaScript

Код {} совершенно допустим в JavaScript, поскольку представляет блок.

Тем не менее, я заметил, что в Chrome * появляется множество блоков ({{...}}) внутри другого:

Uncaught RangeError: превышен максимальный размер стека вызовов

Почему здесь происходит переполнение стека?


Вот кодовая ручка, иллюстрирующая проблему (jsfiddle вылетает).

При запросе в JSRoom Зирак обнаружил, что магическое число составляет 3913 блоков в Chrome и 2555 в Firefox.

Что выталкивается в стек? Зачем?


(*) Я проверил, и это также происходит в IE и Firefox

Обновление: я проверил и ненадежно IE может избежать исключения. Он бросил его два раза, но не третий. Если у кого-то из читателей есть IE, и он тоже хочет протестировать его более старые версии (например, IE8 и 9) и сообщить мне, что произойдет, я буду очень признателен.

4b9b3361

Ответ 1

Прежде всего, ghord полностью верен. Это вызвано рекурсивной природой парсера, поэтому дайте ему возвысить любовь. Но нужно иметь доказательства, а ОП хотел, чтобы я опубликовал это как отдельный ответ.

Firefox

Итак, где узнать, как это было сделано? Спросите некоторых парней, которые находятся в двигателе. Поэтому я перешел к каналу #jsapi на irc://irc.mozilla.org и спросил их:

< bhackett> zirak: well, with a recursive descent parser all the productions will roughly correspond to a frame on the C stack

< bhackett> zirak: the parser is at js/src/frontent/Parser.cpp

< Waldo> zirak: Parser<ParseHandler>::statement(bool canHaveDirectives) and Parser<ParseHandler>::statements() pretty much

< bhackett> zirak: in this case, the recursion will be Parser::blockStatement ->Parser::statements -> Parser::statement -> Parser::blockStatement

Это в значительной степени ответ. Перейдя в центральный хранилище mozilla и выкапывая, у нас есть наши подозреваемые:

Итак, что у нас есть:

  • statements, который вызывает blockStatement, который анализирует блок, чтобы найти другой блок, вызывая
    • statements, который вызывает blockStatement, который анализирует блок, чтобы найти другой блок, вызывая
      • statements, который вызывает blockStatement, который анализирует блок, чтобы найти другой блок, вызывая
        • ...

Пока пакет не упадет, я предполагаю здесь.

Итак, у нас есть источник для Firefox.

Chrome/Chromium/все остальное на основе v8

Изучив мой урок из Firefox, я пошел в проект v8 и искал файл с именем parser. Конечно, он был там!

Следующее, что нужно было искать, когда блок анализируется, поэтому я наивно искал statements, прибыв на перспективный ParseStatement.

И это наш счастливый день, гигантский switch! И первый случай - это то, о чем мы заботимся, призыв к ParseBlock, еще одно многообещающее имя!

Действительно, внутри ParseBlock, мы находим вызов до ParseStatement. Итак, чтобы быть ясным, мы имеем две функции:

И они звонят друг другу, как мы видели в Firefox:

  • ParseStatement, который вызывает ParseBlock, который анализирует блок, чтобы найти другой блок, вызывающий
    • ParseStatement, который вызывает ParseBlock, который анализирует блок, чтобы найти другой блок, вызывающий
      • ParseStatement, который вызывает ParseBlock, который анализирует блок, чтобы найти другой блок, вызывающий
        • ...

До тех пор, пока kaboom не перейдет в стек.

Safari

(Извините за то, что он вызвал закрытый файл в последнем редакторе!) Safari js engine JavaScriptCore, который находится в проекте WebKit. Поиск функций был почти таким же, как поиск их для Chrome, поэтому позвольте перейти к интересной части:

У нас есть дополнительная функция в середине, но принцип тот же:

  • parseSourceElements, который вызывает ParseStatement, который вызывает parseBlockStatement, который анализирует блок, чтобы найти другой блок, вызывающий
    • parseSourceElements, который вызывает ParseStatement, который вызывает parseBlockStatement, который анализирует блок, чтобы найти другой блок, вызывающий
      • parseSourceElements, который вызывает ParseStatement, который вызывает parseBlockStatement, который анализирует блок, чтобы найти другой блок, вызывающий
        • ...

БУМ

IE (и все остальные закрытые исходники, такие как Opera)

... останется загадкой, если только они не почувствуют внезапное желание открыть свой источник или если предприимчивый сотрудник поделился с нами внутренностями. Два больших движка выше делают это одинаково, поэтому мы можем предположить, что другие браузеры делают это аналогично.

Если браузер не разрушается, это интересный вопрос, но тот, на который этот ответ не может надеяться ответить кашлем.

Ответ 2

Стандартная реализация Рекурсивный синтаксический анализатор нисходящего потока в то время как простой и элегантный, анализирует каждое правило грамматики языка одним способом. Эти методы рекурсивно вызывают другие методы, поэтому, когда у вас слишком много вложенных правил, оно превышает размер стека. Chrome и Firefox используют такую ​​реализацию интерпретатора.

Вы заметите, что много "+", не имея ничего общего с областью видимости, вызовет одно и то же исключение:

+ + + + + + + + + ... // same error