Вот весь код: var x = []; x[0x7fffffff]=1; JSON.stringify(x);


Для желающих попробовать: jsfiddle


Таким незамысловатым способом, можно намертво повесить firefox, довести до падения вкладку хрома и повесить основной поток nodejs.

Самое примечательное в этом то, что зависание происходит на уровне нативного кода функции JSON.stringify, что не позволяет прервать выполнение в том же firefox'е, как это обычно бывает при простом while(true);.

При выполнении внутри WebWorker'а в chrome, страница продолжает отвечать, но terminate не может завершить поток.

Так же по понятным причинам, такой код не обнаруживается jslint'ом.


Как это работает


var x = [];
x[0x7fffffff]=1; // предел знакового положительного 32х битного целого
JSON.stringify(x); // поскольку x массив, получаем очень много null...

Как автор докатился до жизни такой


Было 2 с лишним часа ночи, спал я уже давно и мало, а работать было надо. Нужно было реализовать коллекцию объектов в localStorage. Мозг думал уже туго, и вначале для хранения был выбран простой массив с сохранением как JSON. После осознания что с ID в данном случае работать будет удобнее, массив был заменен на объект, а для генерации случайного ID использовался следующий код Math.random() * 0x7fffffff >> 0, далее данные сериализовались и записывались в хранилище. После этого начались случайные зависания страницы, а при отладке обнаружилось что коллекция всё еще инициализировалась как массив.

Резюмируя, про себя...


На самом деле, заметка не о том что JSON.stringify плохой, а о том что надо быть внимательнее к тому, что в него отправляешь.

  1. Надо больше спать
  2. Надо чаще спать
  3. Не стоит делать числовые id, если они будут случайными(проще найти ошибку в случае чего).
  4. Нестрогая типизация иногда зло.

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

Комментарии (29)


  1. SleepwalkerOne
    16.03.2016 15:49
    +1

    ff сказал "out of memory" и продолжил жить


    1. findoff
      16.03.2016 15:55

      У меня linux dev версия висела около 3х минут, пока я её не убил.


    1. unStaiL
      16.03.2016 16:48

      нечто похожее: 44-я версия на linux, немного зависла, уже собирался убить, но firefox восстановил работу.
      Это от железа может зависеть?


      1. findoff
        16.03.2016 16:57

        У меня 46.0a2. Возможно он убивает когда память заканчиваться начинает.


    1. Biblusha
      16.03.2016 16:57
      +1

      Подтверждаю, перед тем как выдать out of memory, FF съел дополнительный гигабайт памяти (ровно гигабайт).


  1. asci
    16.03.2016 16:10

    Нестрогая типизация иногда зло.

    не совсем понимаю как тут спасет строгая типизация?


    1. koceg
      16.03.2016 16:12
      +3

      при отладке обнаружилось что коллекция всё еще инициализировалась как массив
      Если бы сериализовался объект, то в нём не было бы тысяч null'ов. Проверка типов помогла бы увидеть проблему на раннем этапе.


      1. asci
        16.03.2016 16:38

        а, понял. при изменении надо было бы внести правки в правила сериализации?


        1. koceg
          16.03.2016 16:45
          +1

          Не в правила сериализации (которых в js нет), а в инициализацию объекта.
          Изначально автор работал с массивом, но потом решил превратить его в объект. А вот инициализацию исправить забыл. Поскольку ключи у него были числовые, js никак не ругался, когда в массив куда-нибудь в индекс 100500 писалось значение, хотя предыдущих индексов в нём нет. Js оптимизирует такие "дырявые" массивы и не создаёт пустые недостающие промежуточные индексы, просто устанавливает свойство length. А вот при сериализации все дыры закрываются и в итоговой строке получаются миллионы null'ов и на последнем месте единица. Вернее, должны получаться, потому что браузер в процессе виснет и до финала не доходит.


        1. findoff
          16.03.2016 16:46
          +4

          Когда я сменил логику с books.push(book) на books[getId()] = book надо было еще и books = [] поменять на books = {}, но я этого не сделал, а JS за типами не следит, и при том что id цифровой воспринял это как номер ячейки.

          Ну и соотвественно на JSON.stringify вывел все null перед ячейкой 2e+6*random. Точнее попытался...


  1. MrGobus
    16.03.2016 16:44
    -2

    А ведь если подумать, это уязвимость. Вроде как у гуглов и т.п. за такое денежку платят =)


    1. findoff
      16.03.2016 16:52

      Ну это весьма косвенная уязвимость, в том плане что и Array.apply(null, Array(0x7fffffff)) тоже можно такой считать, но в нормальной ситуации внешние данные не должны влиять на индексы массива. Ну и сериализация всегда опасное дело, которое требует повышенной внимательности.


      1. koceg
        16.03.2016 16:55
        +1

        Сэкономлю время таким же любопытным:

        Array.apply(null, Array(0x7fffffff))
        Uncaught RangeError: Invalid array length


        1. findoff
          16.03.2016 16:59

          На значения по скромнее он не ругается, но да эффект конечно не такой мощный как с индексом.


      1. MrGobus
        17.03.2016 11:24
        +2

        Косвенная, возможно, только вот первое, что мне пришло в голову — https://jsfiddle.net/MrGobus/fj5xut89/, в гугле делает страницу безответной хотя отдаем должное, закрыть вкладку возможно, в файрфоксе либо смотрим ролик, либо умираем, кстати слушая звук, до момента, когда система предложит снять задачу. Несложно догадаться, что кое кому, кто любит рассказывать о том, как он чего то добился вложив 100500 денег в контору «Рога и Копыта», это может показаться удачной идеей для фокусировки внимания целевой аудитории на контенте с целью увеличения клиентской базы =) и он применит это в интернет рекламе. И вот, вместо того, чтобы смотреть пирацкий «Карточный домик» или «Наруту» многие будут слушать трели о том, как быстро разбогатеть в полной беспомощности перед такой вот «косенной» уязвимостью=)


        1. findoff
          17.03.2016 12:07

          Хмм, действительно интересная идея, правда сделать вкладку не закрываемой под linux-chome я так и не смог.
          Я зарепортил на хромиум. Надеюсь они поняли что я имею ввиду. (:


  1. spmbt
    16.03.2016 17:30

    Зачем так длинно писать, если JSON.stringify(Array(0x7fffffff)) должен дать тот же эффект (или Array(2e9))?


    1. koceg
      16.03.2016 17:34
      +5

      JSON.stringify(Array(0x7fffffff)) писать в коде никто не будет, а у автора реальный пример из жизни. Но сократить можно, конечно.


  1. 3axap4eHko
    16.03.2016 21:46
    +3

    Причем здесь нестрогая типизация?
    Это неверная инициализации переменной — сугубо Ваша ошибка изза усталости или неопытности, уж сами решайте.
    Например в строготипизированных языках, в которых есть указатели, можно их сложить вместо их значений, что будет в результате — сами понимаете. Но это не проблема языка, компилятор или интепритатор не должен следить за тем, что Вы пишите, он выдаст ошибку только если он не может это выполнить.

    Ударив себя молотком по пальцу — не вини молоток


    1. 3axap4eHko
      16.03.2016 21:54

      а подвесить скорее всего и так можно

      var x = [];
      x[0x7fffffff]=0;
      console.log(x);


      1. koceg
        16.03.2016 22:31

        Нельзя, я выше писал, что происходит.


        1. 3axap4eHko
          16.03.2016 22:46

          проверил, нода висит


          1. findoff
            17.03.2016 01:30

            Что логично, так как 'console.log(x)' это по сути 'JSON.stringify' с форматирование, обратной связью для формирования отступов и записью в stdout.

            Просто зависаний от 'console.log''а ждут многие, а от сериализации нет.
            Правда это скорее психология. Во всяком случае большинство знакомых программистов этому удивились.


            1. 3axap4eHko
              17.03.2016 01:34

              Нет, тут дело в том, что элементарно проитерировать массив с 2e9 элементов, даже в том c++ займет время, без вывода куда либо


    1. findoff
      17.03.2016 01:18

      Строгая типизация уменьшает риск человеческого фактора. И если говорить про молотки, то я бы сравнил строгую типизацию с толстой перчаткой, вроде бы и палец не всегда убережёт, и мешает иногда, но на стройке без перчаток не очень...

      П.С.: Я за опциональную типизацию в JS.

      Ну а что до указателей, то я как человек учивший в 12 лет первым серьёзным языком С++, без интернета и по учебнику C#, много их наелся постигая дзен.


      1. 3axap4eHko
        17.03.2016 01:52
        +1

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


        1. findoff
          17.03.2016 02:01
          +1

          Про сон вы правы, увы и сейчас занимаюсь тем же. Не зря же сон идет первыми двумя пунктами рекомендаций (;
          А вот jslint не обнаружил это, хотя я бы скорее удивился обратному.


    1. PsyHaSTe
      21.03.2016 16:30
      -1

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


  1. rshadow
    17.03.2016 11:50
    -2

    > зависание происходит на уровне нативного кода функции JSON.stringify

    Если это действительно так, то никто из браузеров событийку пока не осилил. =(