VivaMP support had been canceled in the year 2014. If you have any questions, feel free to contact our support.

Abstract

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.

Introduction

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.

Diagnostic rules

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.

Generic exception A

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:

The parallel section is absent (there is no "parallel" directive).

A critical section defined by "critical" directive is used inside the parallel section.

There is a "master"-block inside the parallel section.

There is a "single"-block inside the parallel section.

There is an "ordered"-block inside the parallel section.

Inside the parallel section the omp_set_lock function is used and blocking is set.

Rule N1

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.

V1103. Threads number dependent code. The 'omp_get_num_threads' function is used in an arithmetic expresion.

Rule N9

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.

Rule N10

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.

Rule N11

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.

Rule N12

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.

Rule N13

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:

Static variables.

Static class members (in the current version of VivaMP, this rule is not implemented).

Variables defined outside the parallel section.

During the analysis of the code of the function which is called in a parallel way, global variables are considered global objects. If the analyzed function is a class member, then to consider the class members global or not depends on the way the call of this function is carried out. If the call is carried out from another function of the given class, the members of the class are considered global. If the call is carried out with the help of operator '.' or '->', the objects are also considered global.

The last item needs explaining. Let us site an example:

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

}

In the case of variant A, we will consider the class members common, i.e. they are global in reference to function IncFoo. As a result, we will detect an error of race condition inside function IncFoo.

In case of variant B, we will consider the class members local, and there is no error in IncFoo. However, there will be given a warning that a nonstick method of IncFoo is called in a parallel way from MyClass class. This will allow to find the error.

In the case of variant C, we will consider that the class members are local and that there is no error in IncFoo. And there are really no errors.

The object can be both of simple type and direct instance. The following operations relate to operations of object change:

Transfer of an object into a function by the non-constant link.

Transfer of an object into a function by the non-constant pointer (In the current version of VivaMP, this rule is not implemented).

Change of an object during arithmetic operations or assignment operations.

Call of a non-constant method in the object.

Excpetions:

Generic exception A.

"threadprivate", "private", "firstprivate", "lastprivate" or "reduction" directives are applied to the object. This exception doesn't concern static variables and static class fields which are always generic.

Modification of an object is protected by "atomic" directive.

Modification of an object is performed inside of only one section defined by "section" directive.

Initialization or modification of objects is implemented inside parallelized for operator (inside the operator itself and not inside the loop's body). Such objects are automatically considered private according to OpenMP specification. An example: int i; ... #pragma omp parallel for for (i = 0; i < n; i++) {}. // i - is private.

Rule N17

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.

Rule N18

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.

Rule N19

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.

Rule N20

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:

In Barrier directive

When critical directive enters and leaves the parallel section

When ordered directive enters and leaves the parallel section

When parallel directive enters and leaves the parallel section

When for directive leaves the parallel section

When sections directive leaves the parallel section

When single directive leaves the parallel section

When parallel for directive enters and leaves the parallel section

When parallel sections directive enters and leaves the parallel section

Diagnostic messages based on this rule:

In the current version of VivaMP, this rule is not implemented.

Rule N21

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.

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.

Rule N25

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>'.

Rule N26

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.

V1212. Data race risk. When accessing the array '%1%' in a parallel loop, different indexes are used for writing and reading.

Conclusion

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!