Вебинар: Использование статических анализаторов кода при разработке безопасного ПО - 19.12
Нам задают различные вопросы, связанные с использованием PC-Lint, VivaMP и других статических анализаторов для проверки параллельных программ, спрашивают, являются ли они конкурентами и задают другие схожие вопросы. Видимо это связанно с выходом новой версии PC-Lint 9.0, в которой заявлено о поддержке анализа параллельности (см. PC-lint Manual. Раздел: 12. Multi-thread support). Я решил объединить обсуждения, в которых я участвовал по этому поводу и представить их в виде двух вопросов, на которые я дам развернутые ответы.
Поддержка VivaMP была прекращена в 2014 году. По всем возникшим вопросам вы можете обратиться в нашу поддержку.
Вопрос первый.
Этот вопрос часто задают, но, к сожалению, он не удачен. Прямой на него ответ - да. Но люди на самом деле вкладывают в этот вопрос другой смысл - "Можно ли используя PC-Lint найти ошибки в программах, связанных с использованием распараллеливания алгоритмов?". Тогда ответ - нет, если не предпринять специальных действий.
Все дело в том, что статический анализатор не знает, выполняется ли определенный участок кода параллельно и может только строить некоторые предположения. Вот две основные причины такого неведения:
1) Статические анализаторы, как и компиляторы, работают с каждым .cpp файлом отдельно. И поэтому, если в файле A параллельно вызывается функция f() из файла B, то при анализе файла B мы не знаем об этом. Конечно, можно создать статические анализаторы (возможно, такие и есть), которые анализируют все файлы сразу, но это крайне сложная задача. По крайней мере, PC-Lint работает с каждым файлом отдельно.
2) Даже если вызов функции f() находится в том же файле, то все равно не так просто бывает понять, будет ли параллельный вызов или нет. Вызов может сложным образом зависеть от логики алгоритма и входных данных, которыми статический анализатор не обладает.
Возьмем пример функции (приведенный в руководстве к PC-Lint версии 9.0):
void f()
{
static int n = 0;
/* ... */
}
Проблема состоит в том, что если функция f() будет вызвана из параллельных потоков, то может возникнуть ошибка инициализации переменной 'n'. Для диагностики данной ситуации, необходимо явно указать анализатору PC-Lint, что функция f() может вызываться параллельно. Для этого необходимо использовать конструкции вида:
//lint -sem(f, thread)
Только тогда, при диагностике кода, вы получите предупреждение: Warning 457: "Thread 'f(void)' has an unprotected write access to variable 'n' which is used by thread 'f(void)" .
На самом деле, высказанное мною ранее утверждение, что, не предпринимая специальных действий, нельзя автоматически диагностировать ошибки в параллельном коде не совсем верно. PC-Lint поддерживает POSIX threads и может, например, автоматически обнаружить ошибки связанные с блокировками, если будут использоваться такие функции, как pthread_mutex_lock() и pthread_mutex_unlock(). Но если механизм параллельности построен не на POSIX threads, то вам опять-таки будет необходимо использовать специальные директивы, чтобы подсказать PC-Lint какие функции приводят к блокировке и разблокировке:
-sem(function-name, thread_lock)
-sem(function-name, thread_unlock)
В этом случае можно обнаружить ошибки в коде, подобному следующему:
//lint -sem( lock, thread_lock )
//lint -sem( unlock, thread_unlock )
extern int g();
void lock(void), unlock(void);
void f()
{
//-------------
lock();
if( g() )
return; // Warning 454
unlock();
//-------------
if( g() )
{
lock();
unlock();
unlock(); // Warning 455
return;
}
//-------------
if( g() )
lock();
{ // Warning 456
// do something interesting
}
}
Еще раз попробую дать заключительный ответ. Анализатор PC-Lint может эффективно находить ошибки связанные с параллельностью, если предварительно сделать ему все необходимые "подсказки". В противном случае он может протестировать параллельный код, считая его последовательным и тем самым не обнаружив в нем ряд ошибок.
Из всего описанного вытекает второй вопрос.
Ответа два:
1) PC-Lint не умеет находить ошибки в программах, построенных на технологии OpenMP.
2) VivaMP специализированный продукт для диагностики параллельных программ, в то время как PC-Lint хороший статический анализатор общего назначения. Еще раз подчеркну: хороший, но общего назначения. Анализ параллельности в нем это всего лишь одна из подсистем. Инструмент VivaMP специально создан для диагностики параллельных программ и обладает большими возможностями в этой области.
Можно заявить, что VivaMP и PC-Lint не являются конкурентами. Они дополняют друг друга. То, что умеет диагностировать VivaMP не умеет делать PC-Lint. То, что умеет делать PC-Lint, не умеет VivaMP. Но постепенно учится :). Поясню это на примерах.
Вновь возьмем пример со статической переменной:
void f()
{
#pragma omp parallel num_threads(2)
{
static int cachedResult = ComputeSomethingSlowly();
...
}
...
}
Статическая переменная объявлена внутри параллельной OpenMP секции, что приведет к ошибке инициализации. В данном примере PC-Lint не способен обнаружить ошибку. Мы не можем указать, что функция f() выполняется параллельно, поскольку в данном примере только часть кода функции будет распараллелена. VivaMP же обнаружит ошибку и выдаст сообщение: V1204. Data race risk. Unprotected static variable declaration in a parallel code.
Аналогичная ситуация будет в случае ошибки с блокировкой/разблокировкой. Рассмотрим следующий код:
void foo()
{
...
#pragma omp parallel sections
{
#pragma omp section // V1102.
{
omp_set_lock(&myLock);
}
#pragma omp section // V1102.
{
omp_unset_lock(&myLock);
}
}
...
}
В данном случае есть две OpenMP директивы, которые в двух секциях несимметрично используют функции omp_set_lock и omp_unset_lock. То есть приведенный код содержит две ошибки, о которых и сообщит анализатор VivaMP (V1102. Non-symmetrical use of set/unset functions for the following lock variable(s): myLock).
А вот PC-Lint здесь помочь не сможет. Дело в том, что OpenMP директивы для него ничего не значат, то есть он просто их игнорирует. А следовательно с точки зрения PC-Lint код будет выглядеть так:
//lint -sem(omp_set_lock, thread_lock)
//lint -sem(omp_unset_lock, thread_unlock)
void foo()
{
...
{
omp_set_lock(&myLock);
}
{
omp_unset_lock(&myLock);
}
...
}
Даже если мы укажем для PC-Lint, что omp_set_lock/omp_unset_lock используется для блокировки/разблокировки, то для него функция foo() в отличие от VivaMP будет корректна. Есть одна блокировка и одна разблокировка. Ошибки нет.
Еще раз повторю, что причина в том, что PC-Lint не анализирует директивы OpenMP заданные с помощью конструкций #pragma, из-за чего логика алгоритма для него представляется по-иному.
VivaMP и PC-Lint два хороших инструмента, ориентированных на различные аспекты параллельного программирования, и отлично дополняют друг друга.
0