Skip to main content
GitHub

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

Non-compliant code
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.

Compliant code
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.

Non-compliant code
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.

Compliant code
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.

Non-compliant code
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.

Compliant code
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.

RuleSeverityLikelihoodDetectableRepairablePriorityLevel
CON05-FHighLikelyYesYesP27L1

Attachments: