The article describes principles on which implementation of the static code analyzer VivaMP is based. The described set of testing logical conditions allows you to diagnose some errors in parallel programs created on the basis of OpenMP technology.
VivaMP support had been canceled in the year 2014. If you have any questions, feel free to contact our support.
Many errors in the programs developed on the basis of OpenMP technology can be diagnosed with the help of the static code analyzer [1]. This article gives a set of diagnostic rules detecting potentially unsafe code sections which most likely contain errors. The rules described below are meant for testing C and C++ code. But many rules can be applied to the Fortran programs as well after some modification.
The rules described in the article are the basis of the static code analyzer VivaMP developed by OOO "Program Verification Systems" [2]. VivaMP analyzer is intended for testing code of C/C++ applications. The analyzer integrates into Visual Studio 2005/2008 development environments and also adds a documentation section into MSDN Help system. VivaMP code analyzer is included in PVS-Studio software product.
This article is being renewed and modified together with the development of PVS-Studio. At present, this is the third variant of the article, which refers to PVS-Studio 3.40. If it is mentioned in the article that some diagnostic features are not implemented, this refers to PVS-Studio 3.40. In the following versions of the analyzer, such features can be implemented. See a newer variant of this article or PVS-Studio documentation.
It is considered that the directives in all the rules are used together with "omp" directive, if not stated otherwise. That is, we omit mentioning that "omp" directive is used in order to shorten the rules' text.
This exception is used in several rules and is singled out to shorten their description. In general, the point is that we operate outside a parallel section or explicitly point which thread is used or shield the code with lockups.
We can consider a case safe when one of the following conditions is satisfied for the code being tested:
We should consider unsafe using "for" and "sections" directives without "parallel" directive.
Exceptions:
"for" or "sections" directives are located inside the parallel section defined by "parallel" directive.
An example of unsafe code:
#pragma omp for
for(int i = 0; i < 100; i++)
...
An example of safe code:
#pragma omp parallel
{
#pragma omp for
for(int i = 0; i < 100; i++)
...
}
Diagnostic messages based on this rule:
V1001. Missing 'parallel' keyword.
We should consider unsafe using one of the directives relating to OpenMP without "omp" directive.
Exceptions:
Using "warning" directive.
An example of unsafe code:
#pragma single
An example of safe code:
#pragma warning(disable : 4793)
Diagnostic messages based on this rule:
V1002. Missing 'omp' keyword.
We should consider unsafe using for operator immediately after "parallel" directive without "for" directive.
An example of unsafe code:
#pragma omp parallel num_threads(2)
for(int i = 0; i < 2; i++)
...
An example of safe code:
#pragma omp parallel num_threads(2)
{
for(int i = 0; i < 2; i++)
...
}
Diagnostic messages based on this rule:
V1003. Missing 'for' keyword. Each thread will execute the entire loop.
We should consider unsafe creating a parallel loop with "parallel" and "for" directives inside a parallel section created by "parallel" directive.
An example of unsafe code:
#pragma omp parallel
{
#pragma omp parallel for
for(int i = 0; i < 100; i++)
...
}
An example of safe code:
#pragma omp parallel
{
#pragma omp for
for(int i = 0; i < 100; i++)
...
}
Diagnostic messages based on this rule:
V1004. Nested parallelization of a 'for' loop.
We should consider unsafe using "for" and "ordered" directives together if after that "ordered" directive is not used inside the loop defined by for operator.
An example of unsafe code:
#pragma omp parallel for ordered
for(int i = 0; i < 4; i++)
{
foo(i);
}
An example of safe code:
#pragma omp parallel for ordered
for(int i = 0; i < 4; i++)
{
#pragma omp ordered
{
foo(i);
}
}
Diagnostic messages based on this rule:
V1005. The 'ordered' directive is not present in an ordered loop.
We should consider unsafe call of omp_set_num_threads function inside a parallel section defined by "parallel" directive.
An example of unsafe code:
#pragma omp parallel
{
omp_set_num_threads(2);
}
An example of safe code:
omp_set_num_threads(2);
#pragma omp parallel
{
...
}
Diagnostic messages based on this rule:
V1101. Redefining number of threads in a parallel code.
We should consider unsafe odd use of omp_set_lock, omp_set_nest_lock, omp_unset_lock and omp_unset_nest_lock functions inside a parallel section.
An example of unsafe code:
#pragma omp parallel sections
{
#pragma omp section
{
omp_set_lock(&myLock);
}
}
An example of safe code:
#pragma omp parallel sections
{
#pragma omp section
{
omp_set_lock(&myLock);
omp_unset_lock(&myLock);
}
}
Diagnostic messages based on this rule:
V1102. Non-symmetrical use of set/unset functions for the following lock variable(s): %1%.
We should consider unsafe using omp_get_num_threads function in arithmetic operations.
Exceptions:
The returned value of omp_get_num_threads function is used for comparing or equating with a variable.
An example of unsafe code:
int lettersPerThread =
26 / omp_get_num_threads();
An example of safe code:
bool b = omp_get_num_threads() == 2;
switch(omp_get_num_threads())
{
...
}
Diagnostic messages based on this rule:
V1103. Threads number dependent code. The 'omp_get_num_threads' function is used in an arithmetic expresion.
We should consider unsafe call of omp_set_nested function inside a parallel section defined by "parallel" directive.
Exceptions:
The function is located in an embedded block created by "master" or "single" directive.
An example of unsafe code:
#pragma omp parallel
{
omp_set_nested(2);
}
An example of safe code:
#pragma omp parallel
{
#pragma omp master
{
omp_set_nested(2);
}
}
Diagnostic messages based on this rule:
V1104. Redefining nested parallelism in a parallel code.
We should consider unsafe functions using common resources. An example of such functions is printf.
Exceptions:
Generic exception A.
An example of unsafe code:
#pragma omp parallel
{
printf("abcd");
}
An example of safe code:
#pragma omp parallel
{
#pragma omp critical
{
printf("abcd");
}
}
Diagnostic messages based on this rule:
V1201. Concurrent usage of a shared resource via an unprotected call of the '%1%' function.
We should consider unsafe applying flush directive to pointers.
An example of unsafe code:
int *t;
...
#pragma omp flush(t)
An example of safe code:
int t;
...
#pragma omp flush(t)
Diagnostic messages based on this rule:
V1202. The 'flush' directive should not be used for the '%1%' variable, because the variable has pointer type.
We should consider unsafe using "threadprivate" directive.
An example of unsafe code:
#pragma omp threadprivate(var)
Diagnostic messages based on this rule:
V1203. Using the 'threadprivate' directive is dangerous, because it affects the entire file. Use local variables or specify access type for each parallel block explicitly instead.
We should consider unsafe initialization or modification of an object (variable) in a parallel section if the object is global (common for the threads) in relation to this section.
Explanation of the rule:
The following objects are global in relation to a parallel section:
class MyClass {
public:
int m_a;
void IncFoo() { a++; }
void Foo() {
#pragma omp parallel for
for (int i = 0; i < 10; i++)
IncFoo(); // Variant. A
}
};
MyClass object_1;
#pragma omp parallel for
for (int i = 0; i < 10; i++)
{
object_1.IncFoo(); // Variant. B
MyClass object_2;
object_2.IncFoo(); // Variant. C
The object can be both of simple type and direct instance. The following operations relate to operations of object change:
Excpetions:
An example of unsafe code:
#pragma omp parallel
{
static int st = 1; // V1204
}
void foo(int &) {}
...
int value;
MyObjectType obj;
#pragma omp parallel for
for(int i = 0; i < 33; i++)
{
++value; // V1205
foo(value); // V1206
obj.non_const_foo(); // V1207
}
An example of safe code:
#pragma omp parallel
{
#pragma omp critical
{
static int st = 1;
}
}
void foo(const int &) {}
...
int value;
MyObjectType obj;
#pragma omp parallel for
for(int i = 0; i < 33; i++)
{
#pragma omp atomic
++value;
foo(value);
obj.const_foo();
}
Diagnostic messages based on this rule:
V1204. Data race risk. Unprotected static variable declaration in a parallel code.
V1205. Data race risk. Unprotected concurrent operation with the '%1%' variable.
V1206. Data race risk. The value of the '%1%' variable can be changed concurrently via the '%2%' function.
V1207. Data race risk. The '%1%' object can be changed concurrently by a non-const function.
We should consider unsafe applying "private", "firstprivate" and "threadprivate" directives to links and pointers (not arrays).
An example of unsafe code:
int *arr;
#pragma omp parallel for private(arr)
An example of safe code:
int arr[4];
#pragma omp parallel for private(arr)
Diagnostic messages based on this rule:
V1208. The '%1%' variable of reference type cannot be private.
V1209. Warning: The '%1%' variable of pointer type should not be private.
We should consider unsafe absence of modification of a variable marked by "lastprivate" directive in the last section ("section ").
Exceptions:
The variable is not modified in all the other sections as well.
An example of unsafe code:
#pragma omp sections lastprivate(a)
{
#pragma omp section
{
a = 10;
}
#pragma omp section
{
}
}
An example of safe code:
#pragma omp sections lastprivate(a)
{
#pragma omp section
{
a = 10;
}
#pragma omp section
{
a = 20;
}
}
Diagnostic messages based on this rule:
V1210. The '%1%' variable is marked as lastprivate but is not changed in the last section.
We should consider unsafe using a variable of omp_lock_t / omp_nest_lock_t type without its being initialized beforehand inside omp_init_lock / omp_init_nest_lock function.
Under using we understand call of omp_set_lock function etc.
An example of unsafe code:
omp_lock_t myLock;
#pragma omp parallel num_threads(2)
{
...
omp_set_lock(&myLock);
}
An example of safe code:
omp_lock_t myLock;
omp_init_lock(&myLock);
#pragma omp parallel num_threads(2)
{
...
omp_set_lock(&myLock);
}
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
We should consider unsafe using variables defined in a parallel section as private with use of "private" and "lastprivate" without their being initialized beforehand.
An example of unsafe code:
int a = 0;
#pragma omp parallel private(a)
{
a++;
}
An example of safe code:
int a = 0;
#pragma omp parallel private(a)
{
a = 0;
a++;
}
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
We should consider unsafe the case when after a parallel section variables are used to which "private", "threadprivate" or "firstprivate" directive was applied without its being initialized beforehand.
An example of unsafe code:
#pragma omp parallel private(a)
{
...
}
a++;
An example of safe code:
#pragma omp parallel private(a)
{
...
}
a = 10;
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
We should consider unsafe applying "firstprivate" and "lastprivate" directives to direct instances in which copy constructor is absent.
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
We should consider ineffective using "flush" directive where it is executed implicitly. The cases where "flush" directive is implicit and there is no sense in using it:
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
We should consider ineffective the use of flush directive for local variables (declared in the parallel section), as well as of variables marked as threadprivate, private, lastprivate, and firstprivate.
Example:
int a = 1;
#pragma omp parallel for private(a)
for (int i = 10; i < 100; ++i) {
#pragma omp flush(a);
...
}
Diagnostic messages based on this rule:
V1211. The use of 'flush' directive has no sense for private 'NN' variable, and can reduce performance.
We should consider ineffective using critical sections or functions of omp_set_lock class where "atomic" directive is enough.
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
We should consider ineffective using flush directive for local variables (declared in a parallel section) and also for variables marked as threadprivate, private, lastprivate, firstprivate.
flush directive has no sense for the enumerated variables because they always contain actual values. But it reduces the code performance.
An example of unsafe code:
int a = 1;
#pragma omp parallel for private(a)
for (int i = 10; i < 100; ++i) {
#pragma omp flush(a);
...
}
An example of safe code:
int a = 1;
#pragma omp parallel for
for (int i = 10; i < 100; ++i) {
#pragma omp flush(a);
...
}
Diagnostic warnings based on this rule:
In the current version of VivaMP, this rule is not implemented.
According to OpenMP specification, all the exceptions must be processed inside a parallel section. It is considered that the code generates exceptions if:
throw operator is used in it;
new operator is used in it;
a function marked as throw(...) is called in it;
Such code must be inside a try..catch block inside a parallel section.
Exceptions:
new operator is used that does not throw exceptions (new(std::nothrow) float[10000];).
An example of unsafe code:
void MyNotThrowFoo() throw() { }
...
#pragma omp parallel for num_threads(4)
for(int i = 0; i < 4; i++)
{
...
throw 1;
...
float *ptr = new float[10000];
...
MyThrowFoo();
}
An example of safe code:
size_t errCount = 0;
#pragma omp parallel for num_threads(4) reduction(+: errCount)
for(int i = 0; i < 4; i++)
{
try {
//...
throw 1;
}
catch (...)
{
++errCount;
}
}
if (errCount != 0)
throw 1;
Note: nothrow new construction is somewhat deceptive, as there can occur an impression that there can be no exceptions here. But we should take into account that exceptions can be generated in the builder of the created objects. That means, if at least one std::string is allocated or a class itself allocates memory by new (without nothrow), then exceptions at call of new(nothrow) anyway can be generated. The diagnostics of the given errors lies in the analysis of builders bodies (and other objects builders bodies included in the class), which are called inside parallel sections. At present, this feature is not implemented in VivaMP.
Diagnostic messages based on this rule:
V1301. The 'throw' keyword cannot be used outside of a try..catch block in a parallel section.
V1302. The 'new' operator cannot be used outside of a try..catch block in a parallel section.
V1303. The '%1%' function which throws an exception cannot be used in a parallel section outside of a try..catch block.
We should consider unsafe not including the header file <omp.h> in the file where OpenMP directives are used.
Diagnostic messages based on this rule:
V1006. Missing omp.h header file. Use '#include <omp.h>'.
We should consider unsafe the presence of unused variables marked in the reduction directive. This may be evidence both of an error presence or simply that some other directive or variable was forgotten and not removed during the process of code refactoring.
Example of not using abcde variable:
#pragma omp parallel for reduction (+:sum, abcde)
for (i=1; i<999; i++)
{
sum = sum + a[i];
Diagnostic messages based on this rule:
In the current version of VivaMP, this rule is not implemented.
You should consider unsafe an unprotected access in a parallel loop to an array item using an index different from that used for reading.
Note. The error might occur in case of a protected access as well (single, critical, ...), but for the purpose of clearness we will consider here that the protected access is safe in this case.
Exceptions:
1. The index is a constant.
Here is an example of dangerous code:
#pragma omp parallel for
for (int i=2; i < 10; i++)
array[i] = i * array[i-1]; //V1212
This is an example of safe code:
#pragma omp parallel for
for (int i=2; i < 10; i++)
{
array[i] = array [i] / 2;
array_2[i] = i * array[i-1];
}
Diagnostic warnings that are generated relying on this rule:
V1212. Data race risk. When accessing the array '%1%' in a parallel loop, different indexes are used for writing and reading.
If you are interested in methodology of testing program code on the basis of static analysis, write us (support@viva64.com). We hope that we will find mutual interests and opportunities to collaborate!