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 iterators) is implicitly shared, causing threads to overwrite the same memory location.
  • Incorrect privatization: A variable intended to aggregate results or maintain ga lobal 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 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 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

  !$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

The compliant solution uses default(none) and explicitly marks the scalar as private so each thread has its own isolated instance. Note that the variable i is implicitly private as it is the iteration variable.

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

Noncompliant Code Example

In this 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 subnroutine

Compliant Solution

In the compliant version, the results array is no longer privatized. Instead, it is explicitly declared as shared , ensuring that 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
! Compliant: results is shared so updates are visible outside the region
!$omp parallel do default(none) private(i) shared(input, results)

Noncompliant Code Example

In this 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 tis value is lost after 
  ! the loop.
  !$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

The lastprivate clause ensures that the value from the iteration that would be the last in a sequential execution is copied back to the host variable.

Compliant code
! Compliant: lastprivate preserves the value for use after the loop
!$omp parallel do default(none) lastprivate(last_val) shared(data) private(i)

Risk Assessment

Incorrect OpenMP data-scoping can lead to data races, causing non-deterministic behavior and potential security vulnerabilities.

RecommendationSeverityLikelihoodDetectableRepairablePriorityLevel
CON05-FHighLikelyYesYesP27L1

Attachments: