https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127026

            Bug ID: 127026
           Summary: Array aggregate with non-static bounds
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: ada
          Assignee: unassigned at gcc dot gnu.org
          Reporter: andreaboido.asti at gmail dot com
                CC: dkm at gcc dot gnu.org
  Target Milestone: ---

Created attachment 65401
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65401&action=edit
Source file that reproduces the bug

Assigning a named array aggregate whose choice range has a non-static
bound to an out (or in out) formal of an unconstrained array type
stores the components at the aggregate's own index values instead of
sliding them to the bounds of the target. When the target's bounds
differ from the aggregate's, the target is left unmodified and the
stores land outside the target object (memory corruption). No
exception is raised. The required Constraint_Error on a length
mismatch (RM 5.2(11), 4.6(37-38)) is not raised either.

This is a regression that started with GCC 15.1 and is still present
on trunk. GCC 8.2 through 14.3 generate correct code.

$ cat min.adb
with Ada.Text_IO; use Ada.Text_IO;
procedure Min is
   type Buf is array (Integer range <>) of Integer;

   procedure Fill (B : out Buf; N : Integer) is
   begin
      B := (1 .. N => 7);
   end Fill;

   V : Buf (5 .. 8) := (others => 0);
begin
   Fill (V, 4);
   Put_Line ("V (5) =" & Integer'Image (V (5)) & "   (expected 7)");
end Min;

$ gnatmake min.adb
gcc -c min.adb
gnatbind -x min.ali
gnatlink min.ali
$ ./min
V (5) = 0   (expected 7)

The command triggering the bug is the compilation, exactly as invoked
by gnatmake: gcc -c min.adb (no other options; no output is produced).

Expected output: V (5) = 7. The aggregate (1 .. 4 => 7) has the same
length as V (5 .. 8), so per RM 5.2(11) and RM 4.6(37-38) the
assignment converts the value to the target subtype, which slides the
bounds; all four components of B must become 7.

There are no compilation warnings or errors. The behaviour is the same
at -O0 and -O2, and with -gnatVa -gnato. If Fill is called with N = 3
(length 3 versus target length 4), no Constraint_Error is raised
either; the assignment is silently mis-executed.

Where the stores go: with a slice of a larger array as the actual, the
components are written at the aggregate's index values relative to the
target's base, i.e. at positions First - B'First .. Last - B'First of
the target. For the reproducer above this writes 16 bytes below V on
the stack. A variant that sweeps target bounds corrupts adjacent stack
objects and can hang or crash.

Which forms are affected (target V (5 .. 8) via an out formal):

  B := (1 .. N => 7);    -- wrong, N variable
  B := (L .. 4 => 7);    -- wrong, L variable
  B := (L .. H => 7);    -- wrong, L and H variables
  B := (1 .. 4 => 7);    -- correct (static bounds, slid)
  B := (others => 7);    -- correct

A direct assignment S := (1 .. N => 7); to a declared object
S : Buf (5 .. 8) is handled correctly; the
target's nominal subtype must be unconstrained (an out or in out
formal) to trigger the bug.

The expanded code (-gnatG) shows the aggregate assignment expanded in
place with no sliding and no length check:

   procedure min__fill (b : out min__buf; n : integer) is
   begin
      L7b : for J6b in 1 .. integer'(n) loop
         b (J6b) := 7;
      end loop L7b;
   end min__fill;

Analysis: the regression was introduced by r15-5397 (commit
7617b8324245, "ada: Further cleanup in expansion of array aggregates
in allocators"). Before that change, In_Place_Assign_OK in exp_aggr.adb
required compile-time-known target bounds statically matching the
aggregate's before expanding an aggregate assignment in place, so this
case built a temporary and slid it. The change replaced that manual
check with a call to Must_Slide, and Must_Slide returns False when the
target type is not constrained ("an unconstrained type whose actual
subtype comes from the aggregate"). That reasoning is right for the
original use of Must_Slide in object declarations, where the object's
bounds do come from the aggregate, but wrong for an assignment
statement: an out formal of an unconstrained array type has its own
bounds at run time, so sliding (and the length check) is required.
In_Place_Assign_OK therefore returns True and the aggregate is
expanded in place, indexing the target with the aggregate's own choice
values. With static aggregate bounds the bug is masked because
Must_Slide's static-matching test then detects the mismatch.

$ gcc -v
Using built-in specs.
COLLECT_GCC=/home/ab/opt/gcc-16.1.0-gprbuild/bin/gcc
COLLECT_LTO_WRAPPER=/home/ab/opt/gcc-16.1.0-gprbuild/bin/../libexec/gcc/x86_64-pc-linux-gnu/16.1.0/lto-wrapper
Target: x86_64-pc-linux-gnu
Configured with: ../gcc/configure --prefix=/home/BUILD64/bin/gcc-16.1.0
--enable-languages=c,c++,ada --enable-clocale=generic
--with-mpfr=/home/PUBLIC/tjk/mpfr-4.0.1 --with-mpc=/home/PUBLIC/tjk/mpc-1.1.0
--with-gmp=/home/PUBLIC/tjk/gmp-6.3.0 --with-isl=/home/PUBLIC/tjk/isl-0.24
--enable-multilib
Thread model: posix
Supported LTO compression algorithms: zlib
gcc version 16.1.0 (GCC)

Also reproduced on x86-64 gnat 15.1, 15.2, 16.1, 16.2 and trunk on
compiler explorer (godbolt.org); correct output on 8.2, 9.5, 10.2,
11.1, 12.1, 13.1, 14.1, 14.2 and 14.3 there.

Reply via email to