В 2002 году Тим Петерс разработал гибридный алгоритм сортировки Timsort, который хитроумно сочетает в себе сортировку вставками и сортировку слиянием. Сейчас это стандартный алгоритм сортировки в Python, OpenJDK, Sun JDK в Android JDK. К сожалению, недавно в нём обнаружен явный баг. Учитывая популярность вышеупомянутых платформ, речь идёт о миллиардах компьютеров и мобильных устройств.

Специалисты из консорциума Envisage провели анализ, как работают реализации Timsort в Java и Python. Ониубедились, что в обоих случаях возможен выход за пределы массива.

Более подробное исследование позволило найти конкретный баг в реализациях TimSort. Этот баг содержится в коде, который отвечает за слияние подмассивов. В Java/Android он выглядит так.

private void mergeCollapse() {
  while (stackSize > 1) {
    int n = stackSize - 2;
    if (n > 0 && runLen[n-1] <= runLen[n] + runLen[n+1]) {
      if (runLen[n - 1] < runLen[n + 1])
         n--;
      mergeAt(n);
    } else if (runLen[n] <= runLen[n + 1]) {
      mergeAt(n);
    } else {
      break; // Invariant is established
    }
  }
}

Согласно алгоритму, слияние подмассивов происходит до тех пор, пока не достигается выполнение двух правил:

X > Y + Z
Y > Z

Так вот, при некоторых значениях вышеприведённый код mergeCollapse приводит к ошибке. Например, если взять такие значения runLen.

120, 80, 25, 20, 30

После первой итерации цикла происходит слияние 25 и 20 (поскольку 25 < 20 + 30 и 25 < 30).

120, 80, 45, 30

После второй итерации программа прекращает работу, потому что результат удовлетворяет поставленным условиям (80 > 45 + 30 и 45 > 30). Но на самом деле 120 < 80 + 45, а проверка этого условия не предусмотрена кодом, что и приводит к ошибке.

Функцию в Java/Android следует исправить следующим образом.

 private void newMergeCollapse() {
     while (stackSize > 1) {
       int n = stackSize - 2;
       if ( n > 0 && runLen[n-1]  0 && runLen[n-2] <= runLen[n] + runLen[n-1]) {
                if (runLen[n - 1] < runLen[n + 1])
                    n--;
            } else if (n runLen[n + 1]) {
                break; // Invariant is established
            }
            mergeAt(n);
        }
    }

На видео показано, как вызывается exception.

Подробное описание бага содержится в научной статье. На сайте Envisage выложен генератор тестов TestTimSort.java, который «ломает» TimSort, а также исходный код генератора.

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    4 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии