CON05-F. Ensure correct OpenMP datascoping of variables in parallel regions
Developers shall correctly specify data-sharing attributes (shared, private, lastprivate, etc.) for all variables in OpenMP parallel regions to prevent data races, undefined behavior, and incorrect results.
In multithreaded code, data races occur when multiple threads access the same memory location concurrently, with at least one write and no proper synchronization. Incorrect OpenMP data-scoping manifests mainly in three ways:
- Incorrect sharing: A variable that should be local to each thread (e.g., loop iterations) is implicitly
shared, causing threads to overwrite the same memory location. - Incorrect privatization: A variable intended to aggregate results or maintain a global state is declared
private, so each thread updates its local copy, and changes are lost. - Missing
lastprivate: A variable correctly privatized for a loop does not retain the value from the logically last loop iteration, leading to inconsistent post-loop results.
Using default(none) in combination with explicit attribute declarations enforces safe, predictable data-scoping and prevents accidental sharing or privatization errors.
Noncompliant Code Example
In this noncompliant example, the temporary variable scaling_factor is shared among all threads because it is not declared private. Each thread overwrites the same memory location, causing a data race when multiple threads simultaneously compute and assign to A(i).
subroutine apply_weights(A, B, weight)
implicit none
real, intent(inout) :: A(:)
real, intent(in) :: B(:), weight
real :: scaling_factor
integer :: i
! Noncompliant: scaling_factor is shared among the threads.
!$omp parallel do shared(A, B, weight, scaling_factor)
do i = 1, size(A)
scaling_factor = B(i) * weight
A(i) = A(i) + scaling_factor
end do
end subroutine
Compliant Solution
In this compliant solution, the default(none) OpenMP clause is used and the private OpenMP clause marks the scalar scaling_factor explicitly in order to provide each thread with its own isolated instance. Note that the variable i is implicitly private as it is the loop iteration variable.
subroutine apply_weights(A, B, weight)
implicit none
real, intent(inout) :: A(:)
real, intent(in) :: B(:), weight
real :: scaling_factor
integer :: i
! Compliant: scaling_factor is now local to each thread.
!$omp parallel do default(none) shared(A, B, weight) private(scaling_factor)
do i = 1, size(A)
scaling_factor = B(i) * weight
A(i) = A(i) + scaling_factor
end do
end subroutine
Noncompliant Code Example
In this noncompliant example, the programmer intends to populate the results array. However, by marking results as private, the threads operate on local copies that are not synchronized with the actual array on the host. Consequently, the results array remains unchanged after the subroutine executes.
subroutine compute_log(input, results)
implicit none
real, intent(in) :: input(:)
real, intent(inout) :: results(:)
integer :: i
! Noncompliant: results array is privatized; host updates are lost
!$omp parallel do private(i, results) shared(input)
do i = 1, size(input)
results(i) = log(input(i))
end do
end subroutine
Compliant Solution
In this compliant solution, the results array is no longer privatized. Instead, it is explicitly declared as shared to ensure that the updates performed by each thread are applied to the host-visible array. Using default(none) enforces explicit data-sharing attributes and prevents accidental privatization.
subroutine compute_log(input, results)
implicit none
real, intent(in) :: input(:)
real, intent(inout) :: results(:)
integer :: i
! Compliant: results is shared so updates are visible outside the region
!$omp parallel do default(none) private(i) shared(input, results)
do i = 1, size(input)
results(i) = log(input(i))
end do
end subroutine
Noncompliant Code Example
In this noncompliant example, the programmer intends to populate the results array. However, by marking results as private, the threads operate on local copies that are not synchronized with the actual array on the host. Consequently, the results array remains unchanged after the subroutine executes.
real function process_increment(data)
implicit none
real, intent(in) :: data(:)
real :: last_val
integer :: i
! Noncompliant: last_val is private, so its value is lost at loop end.
!$omp parallel do private(i, last_val) shared(data)
do i = 1, size(data)
last_val = data(i) * 2.0
end do
process_increment = last_val + 10.0
end function
Compliant Solution
In this compliant solution, the lastprivate OpenMP clause ensures that the value computed in the last loop iteration in a sequential execution is copied back to the host variable.
real function process_increment(data)
implicit none
real, intent(in) :: data(:)
real :: last_val
integer :: i
! Compliant: lastprivate preserves the value at loop end.
!$omp parallel do default(none) lastprivate(last_val) shared(data) private(i)
do i = 1, size(data)
last_val = data(i) * 2.0
end do
process_increment = last_val + 10.0
end function
Risk Assessment
Incorrect OpenMP data-scoping can lead to data races, causing non-deterministic behavior and potential security vulnerabilities.
| Rule | Severity | Likelihood | Detectable | Repairable | Priority | Level |
|---|---|---|---|---|---|---|
| CON05-F | High | Likely | Yes | Yes | P27 | L1 |
Attachments:
button_arrow_left.png (image/png)
button_arrow_up.png (image/png)
button_arrow_right.png (image/png)


