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;
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;