protected-duration-validity

Protected Duration validity checks

Automatically selected lock-free protected bodies retain invalid Duration validity checks.

AcceptedApplies standaloneSince 1.1.0

Where it applies.

How each patchset treats this bundle on each GCC major
PatchsetGCC 13GCC 14GCC 15GCC 16
1.2.0 (latest)Patchedgcc-13-1613.2.0Patchedgcc-13-1614.2.0Patchedgcc-13-1615.3.0Patchedgcc-13-1616.2.0
1.1.0Patchedgcc-13-1613.2.0Patchedgcc-13-1614.2.0Patchedgcc-13-1615.3.0Patchedgcc-13-1616.2.0
1.0.1Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0
1.0.0Not applicable13.2.0Not applicable14.2.0Not applicable15.3.0Not applicable16.1.0

Explanation.

With validity checking enabled, an automatically selected lock-free protected body is first analyzed before the protected type is marked lock-free. GNAT therefore inserts validity checks that would have been suppressed for an explicitly lock-free protected body, then copies those stale checks into the generated atomic implementation.

On Linux x86-64 with -gnatVa, GCC 13.2.0 through 16.2.0 fail in two ways before the patch:

  • At -O0 the program builds but raises CONSTRAINT_ERROR ... invalid data on assignment of a valid negative Duration.
  • At -O2 the front end aborts in fold_convert_loc and no executable is produced. The exact fold-const.cc line differs per release.

The same executable source succeeds on Linux and macOS AArch64 before the patch, so those lanes are application and build controls rather than demonstrations of the target-dependent failure. The stale check is present in the tree on every target.

The correction removes only compiler-generated invalid-data raises while the lock-free statement copy is traversed. Source raise Constraint_Error statements and other run-time checks are preserved. The executable gnat.dg test assigns and reads a negative Duration at -O2 -gnatVa.

patches/gcc-13-16.patch applies with patch --fuzz=0 to all pinned GCC 13.2.0, 14.2.0, 15.3.0, 16.1.0, and 16.2.0 FSF and Darwin source baselines. The pinned FSF and Darwin exp_ch9.adb blobs are identical per GCC release, and the hunk anchor is unique in each file, so the single canonical patch is unambiguous even where a release moved the surrounding code and the hunk lands at an offset.

Patch.

Variant gcc-13-16

Applies to 13.2.0, 14.2.0, 15.3.0, 16.1.0, 16.2.0. Source flavors: linux, darwin_arm64.

+46 −0 2 files

Download the patch

gcc/ada/exp_ch9.adb +15−0modified
Unified diff for gcc/ada/exp_ch9.adb: original line, patched line, change, source
@@ -2769 +2769 @@package body Exp_Ch9 is
27692769 -- Start of processing for Process_Node
27702770
27712771 begin
2772Added line. -- Validity checks are suppressed while an explicitly
2773Added line. -- lock-free protected subprogram is analyzed directly.
2774Added line. -- When the implementation is selected automatically the
2775Added line. -- checks have already been inserted in the original
2776Added line. -- statements before they are copied here, so drop those
2777Added line. -- stale checks from the generated lock-free body.
2778Added line.
2779Added line. if Nkind (N) = N_Raise_Constraint_Error
2780Added line. and then RT_Exception_Code'Val (UI_To_Int (Reason (N))) =
2781Added line. CE_Invalid_Data
2782Added line. then
2783Added line. Rewrite (N, Make_Null_Statement (Sloc (N)));
2784Added line. return Skip;
2785Added line. end if;
2786Added line.
27722787 -- Wrap each return and raise statement that appear inside a
27732788 -- procedure. Skip the last return statement which is added by
27742789 -- default since it is transformed into an exit statement.
gcc/testsuite/gnat.dg/protected_duration_validity.adb +31−0new file
Unified diff for gcc/testsuite/gnat.dg/protected_duration_validity.adb: original line, patched line, change, source
@@ -0 +1 @@
1Added line. -- { dg-do run }
2Added line. -- { dg-options "-O2 -gnatVa" }
3Added line.
4Added line. with Ada.Text_IO; use Ada.Text_IO;
5Added line.
6Added line. procedure Protected_Duration_Validity is
7Added line. protected Control is
8Added line. procedure Set (Value : Duration);
9Added line. function Get return Duration;
10Added line. private
11Added line. Current : Duration := 0.0;
12Added line. end Control;
13Added line.
14Added line. protected body Control is
15Added line. procedure Set (Value : Duration) is
16Added line. begin
17Added line. Current := Value;
18Added line. end Set;
19Added line.
20Added line. function Get return Duration is
21Added line. begin
22Added line. return Current;
23Added line. end Get;
24Added line. end Control;
25Added line. begin
26Added line. Control.Set (-1.0);
27Added line. if Control.Get /= -1.0 then
28Added line. raise Program_Error with "protected Duration round trip failed";
29Added line. end if;
30Added line. Put_Line ("PASS protected Duration validity");
31Added line. end Protected_Duration_Validity;
32

Tests.

protected_duration_validity.adb Ada · 31 lines
--  { dg-do run }
--  { dg-options "-O2 -gnatVa" }

with Ada.Text_IO; use Ada.Text_IO;

procedure Protected_Duration_Validity is
   protected Control is
      procedure Set (Value : Duration);
      function Get return Duration;
   private
      Current : Duration := 0.0;
   end Control;

   protected body Control is
      procedure Set (Value : Duration) is
      begin
         Current := Value;
      end Set;

      function Get return Duration is
      begin
         return Current;
      end Get;
   end Control;
begin
   Control.Set (-1.0);
   if Control.Get /= -1.0 then
      raise Program_Error with "protected Duration round trip failed";
   end if;
   Put_Line ("PASS protected Duration validity");
end Protected_Duration_Validity;

Download · View in repository

run-test.sh shell · 92 lines
#!/usr/bin/env bash
set -euo pipefail

if [[ $# -ne 3 ]]; then
  echo "usage: $0 TOOLCHAIN_ROOT GCC_VERSION unpatched|patched" >&2
  exit 2
fi

root=$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)
source "$root/scripts/regression-common.sh"
resolve_regression_toolchain "$1"
version=$2
state=$3
[[ "$state" == unpatched || "$state" == patched ]] || {
  echo "error: state must be unpatched or patched" >&2
  exit 2
}

target=$("${REGRESSION_ENV[@]}" "$REGRESSION_GCC" -dumpmachine)
fixture="$root/bundles/protected-duration-validity/tests/protected_duration_validity.adb"
work=$(mktemp -d "${TMPDIR:-/tmp}/gnat-protected-duration-test.XXXXXX")
trap 'rm -rf "$work"' EXIT

# The stale check is in the tree on every target, but it only becomes
# observable where the automatic lock-free implementation is selected for this
# protected type. Linux x86-64 is the target where that is demonstrable: the
# optimized build aborts in the front end and the unoptimized build rejects a
# valid negative Duration at run time. Other targets execute the round trip
# unchanged before the patch and are unpatched target controls.
demonstrable=no
[[ "$target" != x86_64*-linux* ]] || demonstrable=yes

for optimization in 0 2; do
  case_dir="$work/O$optimization"
  mkdir -p "$case_dir"
  cp "$fixture" "$case_dir/protected_duration_validity.adb"
  set +e
  (
    cd "$case_dir"
    "${REGRESSION_ENV[@]}" "$REGRESSION_GNATMAKE" -q -f \
      "-O$optimization" -gnatVa protected_duration_validity.adb
  ) >"$case_dir/build.log" 2>&1
  build_status=$?
  set -e

  if [[ "$state" == unpatched && "$demonstrable" == yes ]]; then
    if [[ "$optimization" == 2 ]]; then
      [[ $build_status -ne 0 ]] || {
        echo "error: unpatched protected-Duration regression unexpectedly compiled at -O2" >&2
        exit 1
      }
      grep -Eiq 'GNAT BUG DETECTED|fold_convert_loc' "$case_dir/build.log" || {
        cat "$case_dir/build.log"
        exit 1
      }
      echo "protected-duration-validity -O2: expected compiler abort ($target, GCC $version)"
      continue
    fi

    [[ $build_status -eq 0 ]] || { cat "$case_dir/build.log"; exit 1; }
    set +e
    "${REGRESSION_ENV[@]}" "$case_dir/protected_duration_validity" \
      >"$case_dir/output.log" 2>&1
    run_status=$?
    set -e
    [[ $run_status -ne 0 ]] || {
      echo "error: unpatched protected-Duration regression unexpectedly passed at -O0" >&2
      exit 1
    }
    if ! grep -Eiq 'CONSTRAINT_ERROR' "$case_dir/output.log" ||
       ! grep -Eiq 'invalid data' "$case_dir/output.log"; then
      cat "$case_dir/output.log"
      exit 1
    fi
    echo "protected-duration-validity -O0: expected invalid-data rejection ($target, GCC $version)"
    continue
  fi

  [[ $build_status -eq 0 ]] || { cat "$case_dir/build.log"; exit 1; }
  "${REGRESSION_ENV[@]}" "$case_dir/protected_duration_validity" \
    >"$case_dir/output.log" 2>&1 || {
      cat "$case_dir/output.log"
      exit 1
    }
  grep -F "PASS protected Duration validity" "$case_dir/output.log"
  if [[ "$state" == unpatched ]]; then
    expected=target-control
  else
    expected=patched
  fi
  echo "protected-duration-validity -O$optimization: $expected ($target, GCC $version)"
done

Download · View in repository

Commands.

Apply the patch
patch --fuzz=0 -p1 -i bundles/protected-duration-validity/patches/gcc-13-16.patch
Build the compiler
PATH=/path/to/bootstrap/bin:$PATH ./scripts/build-gnat.sh SOURCE BUILD INSTALL
Run the regression
./scripts/run-regressions.sh INSTALL 1.1.0 GCC_MAJOR patched

Metadata.