Существует открытый проект COVID-19 CovidSim Model, написанный на
языке C++. Существует статический анализатор кода PVS-Studio,
который умеет хорошо находить ошибки. Однажды они встретились.
Познайте хрупкость алгоритмов математического моделирования и
почему нужно прикладывать максимум усилий к качеству программного
кода.
На днях мне понадобилось кое-что найти на GitHub, что является началом этой маленькой истории. Изучая результаты поиска, я случайно набрёл на проект COVID-19 CovidSim Model. Недолго думая, я решил проверить его с помощью анализатора PVS-Studio.
Проект оказался совсем крошечным. В нём всего 13 000 строк кода, если не считать пустые строки и комментарии. И ошибок там тоже почти нет. Но одна ошибка настолько проста и красива, что я не могу пройти мимо!
void CalcLikelihood(int run, std::string const& DataFile, std::string const& OutFileBase){ .... double m = Data[row][col]; // numerator double N = Data[row][col + 1]; // denominator double ModelValue; // loop over all days of infection up to day of sample for (int k = offset; k < day; k++) { // add P1 to P2 to prevent degeneracy double prob_seroconvert = P.SeroConvMaxSens * (1.0 - 0.5 * ((exp(-((double)(_I64(day) - k)) * P.SeroConvP1) + 1.0) * exp(-((double)(_I64(day) - k)) * P.SeroConvP2))); ModelValue += c * TimeSeries[k - offset].incI * prob_seroconvert; } ModelValue += c * TimeSeries[day - offset].S * (1.0 - P.SeroConvSpec); ModelValue /= ((double)P.PopSize); // subtract saturated likelihood LL += m * log((ModelValue + 1e-20) / (m / N + 1e-20)) + (N - m) * log((1.0 - ModelValue + 1e-20) / (1.0 - m / N + 1e-20)); ....}
Серьёзный научный код. Что-то считается. Формулы. Выглядит всё умно и обстоятельно.
Вот только все эти вычисления разбиваются о человеческую невнимательность. Хорошо, что на помощь может прийти анализатор кода PVS-Studio и указать на баг: V614 [CWE-457] Uninitialized variable 'ModelValue' used. CovidSim.cpp 5412
И действительно, посмотрим внимательнее на это:
double ModelValue;for (int k = offset; k < day; k++){ double prob_seroconvert = ....; ModelValue += c * TimeSeries[k - offset].incI * prob_seroconvert;}
Перед нами простая и одновременно страшная ошибка: неинициализированная переменная. Этот алгоритм может насчитать всё что угодно.
Собственно, на этом всё. Пояснять тут нечего. Остаётся только вновь напомнить, что разработчики научных библиотек и научных приложений должны прикладывать дополнительные усилия для обеспечения качества кода. Падение обыкновенного приложения, скорее всего, будет стоить намного дешевле, чем использование некорректных результатов вычислений в научных, медицинских и прочих целях.
Это уже не первая наша статья на эту тему:
- Анализ кода ROOT фреймворка для анализа данных научных исследований
- NCBI Genome Workbench: научные исследования под угрозой
- Большой Калькулятор выходит из-под контроля
Используйте статический анализатор кода PVS-Studio! Польза от своевременно найденных ошибок может быть колоссальной. Спасибо за внимание.
Если хотите поделиться этой статьей с англоязычной аудиторией, то прошу использовать ссылку на перевод: Andrey Karpov. COVID-19 Research and Uninitialized Variable.