welcome to 1:1 swiss fake franck muller watches uk store!
best replica-watches stores report.

70%-80% discount and high quality replica breitling.

hundreds of newly rolex replicas for sale usa.

Оптимизированные грабли

Тут на днях коллеги подложили нам свинью.

#define true false    // happy debugging!

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

Система наша не то чтобы старая. Политкорректно выражаясь, можно сказать, что она имеет историю. И, как в приличных домах, состоит из ядра и множества внешних компонентов, поставляющих данные в систему. Компоненты эти общаются с ядром через свой собственный несложный API и могут быть написаны вообще кем угодно и на чем угодно. Что и случалось раньше и даже по сей день случается.

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

В целях сохранения совместимости с историей системы (то есть, чтобы не переписывать и перетестировать все  тысячи внешних компонентов и не заставлять делать это сторонних вендоров, имевших несчастье компонент этот написать), было принято благоразумное решение оригинальную функцию не трогать, а рядом с ней объявить вторую такую же, с расширенным списком параметров. Соответственно, старые компоненты будут по-прежнему обращаться к старой функции, которая, в свою очередь, будет уже внутри ядра вызывать новую функцию со значением по умолчанию для нововведенного параметра.

Скоро код пишется, да не скоро митинг кончается. В общем, код отрефакторили, написали, начали тестировать, да и к нам прибежали. Дескать, не работает компонент наш. Ага, тот самый, который ни ухом ни рылом в новом API, поскольку написан был три года назад.

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

По идее, наш компонент, сидючи в своей dll, должен был вызывать функцию_ядра() с набором параметров. В новой системе эта функция ядра передавала вызов в функцию_ядра_второй_версии() вместе со своими параметрами,  в качестве значения нововведенного параметра подсунув некое ЗНАЧЕНИЕ_ПО_УМОЛЧАНИЮ.

По крайней мере, это следовало из кода программы. На деле же в дизассемблированном листинге релизной версии программы тело функции_ядра() состояло из одной-единственной инструкции ret. То есть функция_ядра() в новой версии системы не делала ровным счетом нихрена.  (Отладочной версии по определенным причинам под рукой не было, прим. автора)

Чезанаха искали долго. Сначала уволили отладчик студии. Потом приняли на работу обратно, так как windbg показал то же самое. Потом потрошили настройки проекта на предмет вдруг чего куда-нибудь не туда прилинковалось (при определенном радиусе кривизны рук и не такое может случиться).

Потом вся надежда иссякла. И в этот поворотный момент нам на помощь пришел я. Мне почему-то смутно казалось, что чудес все-таки не бывает и оптимизатор, наверное, имел какие-то определенные причины, побудившие его полностью похерить вызов функции_ядра_второй_версии(). Оказалось, не казалось. Помните, я упомянул, что старая функция просто вызывает новую со ЗНАЧЕНИЕМ_ПО_УМОЛЧАНИЮ для нового параметра? Так вот,  функция_ядра_второй_версии() начиналась ни с чего-нибудь, а с проверки этого параметра. Примерно вот так:

void kernel_function_ver_2(int param, int newParam)
{
    if (newParam != ЗНАЧЕНИЕ_ПО_УМОЛЧАНИЮ)
    {
        //lots of useful stuff
    }
}

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

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

А мораль тут простая — надо, <censored> всегда писать <censored> автоматизированные тесты, <very censored>! Особенно, <multiple censored> если расширяете API, сохраняя обратную совместимость. Простейший юнит (5 минут на написание) или функциональный тест (25 минут на написание) для старой функции указал бы на косяк при первой же компиляции. Но теста не было и случилось то, что случилось — баг вылез в виде совершенно неожиданного спецэффекта в компоненте, который вообще к проблеме отношения не имел, а для его локализации в новом коде и собственно возвращения кода на доработку ненулевому количеству весьма занятых людей потребовалось на весьма ненулевое время напрячь серое вещество. Хотя подобных блох компьютер прекрасно мог бы ловить сам, если бы кому-то не было лениво писать автоматические тесты.

Оставить комментарий


Примечание - Вы можете использовать эти HTML tags and attributes:
<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

5 посетителей онлайн
3 гостей, 2 bots, 0 зарегистрированных
Максимум сегодня:: 15 в 01:42 am UTC
В этом месяце: 44 в 08-10-2022 04:43 pm UTC
В этом году: 285 в 04-27-2022 03:09 am UTC
За все время: 771 в 10-19-2021 06:42 pm UTC