Search code examples
genericsrecordada

Is it possible to store a variant record to a generic type in Ada?


I have a generic library that reads data from a socket. See code listing at the end of the question.

This works fine when My_Type is a fixed size type, but trying to read a variant record using this code raises a STORAGE ERROR with the message object too large.

I know it's possible to send variant records over sockets since I've had an example working. I assume that the problem lies with the fact that I'm storing the record to a generic type. The exception is raised regardless of whether the variant record has a default discriminant. Is there a way of storing the variant record in this scenario?

reader_pkg.ads

with Sockets; use Sockets;
with Sockets.Stream_IO; use Sockets.Stream_IO;

generic
  My_Type: private
package Reader_Pkg is
  task type Receive_Task_Type is
    entry Start(FD: Socket_FD);
  end Receive_Task_Type;
end Reader_Pkg;

reader_pkg.adb

package body Reader_Pkg is
  task body Receive_Task is
    Recv_Socket: Socket_FD;
    Recv_Stream: aliased Socket_Stream_Type;
  begin
    select
      accept Start (FD : Socket_FD) do
        Recv_Socket := FD;
        Initialize (Recv_Stream, Recv_Socket);
        declare
          Message: My_Type := My_Type'Input(Recv_Stream'Access); -- STORAGE_ERROR raised here
        begin
          -- Message gets processed here
        end;
      end Start;
    or
      terminate;
    end select;
  end Receive_Task;
end Reader_Pkg;

Solution

  • I know you said the problem happens whether or not the type has default discriminants, but I think this may be related to ARM 3.7(28):

    If a discriminated type has default_expressions for its discriminants, then unconstrained variables of the type are permitted, and the values of the discriminants can be changed by an assignment to such a variable. If defaults are not provided for the discriminants, then all variables of the type are constrained, either by explicit constraint or by their initial value; the values of the discriminants of such a variable cannot be changed after initialization.

    If you declare a type like

    type Rec (Len : Natural) is record
       Data : String (1 .. Len);
    end record;
    

    then an instance, once created, has a fixed value of Len. However, if you say

    type Rec (Len : Natural := 4) is record
       Data : String (1 .. Len);
    end record;
    

    then the value of Len in an instance can be changed (only by assignment to the whole object), which means (for GNAT; some other compilers do this differently) that the compiler has to reserve enough space on the stack for the largest possible value; and in this case that means reserving enough space for a string of length 2^31 - 1. Which isn't going to work.

    GNAT will warn you about this problem at compile time if you compile with additional warnings enabled (I use -gnatwa, which is all common warnings). Run time detection should be improved by using -fstack-check.

    In the case above, GNAT said

    sm.adb:20:09: warning: creation of "Rec" object may raise Storage_Error
    

    One way of avoiding the problem may be to tell the compiler that you won't be assigning to the object:

    Message: constant My_Type := My_Type'Input(Recv_Stream'Access);
             ^^^^^^^^
    

    and another (which doesn't expect the compiler to be smart enough to recognise the situation) is to limit the maximum possible size:

    subtype Length is Natural range 0 .. 1024;
    type Rec (Len : Length := 4) is record
       Data : String (1 .. Len);
    end record;