From: Hibou57 (Yannick Duchêne) on
Hello boys and girls,

I'm actually experiencing an deeply unexpected behavior to me. Let me
explain in few words, before you go and dinner with the Test program
which will come later soon.

Limited types does not know what copy is. I always though a limited
type "returned" by a function, was Initialized when it was created and
finalized when the scope of its real instance (the function actual
invocation's target) was Finalized.

What a surprise to me to experience something different : limited
object Finalized right after Initialization.

The Test program comes as a single file for convenience (the two
packages it is built upon are defined in its declarative region). Copy/
paste, name it Test.adb (or any other name if ever you compiler don't
like this one), compile and run.

Read comments : every things is explained (as well as my initial
expectation) in there.

Side note : if this was really the legal behavior, I should urgently
go back to Ada basics. I will re-check the RM about all of what this
story relies anyway.


with Ada.Text_IO;
-- To give feedback about what's going on.
with Ada.Finalization;
-- For the implementation of Spies.Instance_Type.

procedure Test is
-- This Test application exposes an (from the autor
-- point of view) unexpected behaviour of limited objects
-- "returned" by a function : a case of Finalization
-- which occurs right after Initialization, even before
-- the function "returning" the limited object
-- has terminated.
--
-- This Test application is built around Dispatching,
-- a package which defines the type which will make
-- us meet the latter introduced behaviour, and Spies,
-- a package, which will provide us the ability to see
-- when exactly objects are Initialized and then Finalized.

-- -------------------------------------------------------------
-- Spies package (specification and body)

package Spies is
-- Provides a spy which is an object you
-- may put in any scope (included record,
-- protected and task type). It will tell
-- tales you of Initialization and
-- Finalization of the scope where it
-- is intruded.

type Instance_Type (Client_Name : access String) is
limited private;
-- It is limited, so that it could spy scope in
-- limited types.
-- It will report you what it is experiencing,
-- displaying messages on the output stream. The
-- message includes the Client_Name, which should
-- be the name of a block or the name of a type.

private

type Instance_Type (Client_Name : access String) is
new Ada.Finalization.Limited_Controlled
with null record;
-- When the host scope will be initialized,
-- Limited_Controlled.Initialize will be invoked,
-- the same with finalization. This is how
-- this spy reports things it is experiencing.

overriding procedure Initialize
(Instance : in out Instance_Type);

overriding procedure Finalize
(Instance : in out Instance_Type);

end Spies;

package body Spies is

overriding procedure Initialize
(Instance : in out Instance_Type)
is
Me : Instance_Type renames Instance;
begin
Ada.Text_IO.Put_Line
("Spy : I am coping Initialization of " &
Me.Client_Name.all &
".");
end;

overriding procedure Finalize
(Instance : in out Instance_Type)
is
Me : Instance_Type renames Instance;
begin
Ada.Text_IO.Put_Line
("Spy : I am coping with Finalization of " &
Me.Client_Name.all &
".");
end;

end Spies;

-- -------------------------------------------------------------
-- Dispatch package (specification and body)

package Dispatch is
-- Was initialy created to analyze the cause
-- of a dispatching failure, thus the name of
-- this package; Actually, it exposes an
-- unexpected behaviour of limited objects
-- created with New_Instance : objects are
-- Finalized right after they are Initialized,
-- even before the function New_Instance, which
-- "returns" a limited type has terminated.
-- The action take place in the implementation
-- of New_Instance (see the package body).

type Instance_Type is synchronized interface;
subtype Instance_Class is Instance_Type'Class;
procedure P (Instance : in out Instance_Type) is abstract;
function New_Instance return Instance_Class;

end Dispatch;

package body Dispatch is

-- For testing purpose, two different implementation
-- of Instance_Class was defined.

-- ----------------------------------------------------------
-- A_Type

protected type A_Type is new Instance_Type with
overriding procedure P;
private
Spy : Spies.Instance_Type
(Client_Name => new String'("A_Type"));
-- Don't worry about memory leaks : it's for
-- testing purpose only.
end;

protected body A_Type is
overriding procedure P is
begin
Ada.Text_IO.Put_Line ("Invoked P of A_Type.");
end;
end;

-- ----------------------------------------------------------
-- B_Type

protected type B_Type is new Instance_Type with
overriding procedure P;
private
Spy : Spies.Instance_Type
(Client_Name => new String'("B_Type"));
-- Don't worry about memory leaks : it's for
-- testing purpose only.
end;

protected body B_Type is
overriding procedure P is
begin
Ada.Text_IO.Put_Line ("Invoked P of B_Type.");
end;
end;

-- ----------------------------------------------------------
-- Creating instance of the interface's class.

function New_A return A_Type is
-- Required by the actual GNAT GPL to return
-- a class wide type from a function returning
-- a limited type.
begin
return Result : A_Type do
null;
end return;
end;

function New_B return B_Type is
-- Same motivations as for New_A.
begin
return Result : B_Type do
null;
end return;
end;

function New_Instance return Instance_Class is
-- Returns either an A_Type or a B_Type.
-- Change the corresponding extended return
-- statement to switch from one to another.
begin
Ada.Text_IO.Put_Line
("New_Instance : Step 1 " &
"(before return statement).");
-- Here is where the action takes place.
-- The message "Step 1" will be displayed before
-- the spy intruded in A_Type, will report
-- Initialization of Result. That's OK so far.
-- See next step.
return Result : Instance_Class := New_A do
Ada.Text_IO.Put_Line
("New_Instance : Step 2 " &
"(inside of return statement).");
-- The message "Step 2" appears after the
-- spy reports Finalization of Result.
-- I suppose something is going wrong there,
-- as the type is limited, Result is not
-- really its instance : its real instance
-- the target of the function "returning"
-- the limited object.
null;
end return;
end;

end Dispatch;

-- -------------------------------------------------------------
-- Testing

Tested_Interface : Dispatch.Instance_Class :=
(Dispatch.New_Instance);
-- Seems to never come into real existence.

begin
Dispatch.P (Instance => Tested_Interface);
-- This will raise an error, as unfortunately,
-- the Tested_Interface instance is already finalized
-- at this point (erroneous or legal behavior ?)
end Test;
From: Hibou57 (Yannick Duchêne) on
Unless someone know a reason for an opposite interpretation, after
I've check the ARM, it seems indeed, things should be like I was
expecting.

[ARM 7.5(8.1/2)]
> For an aggregate of a limited type used to initialize an object as
> allowed above, the implementation shall not create a separate
> anonymous object for the aggregate. For a function_call of a type
> with a part that is of a task, protected, or explicitly limited
> record type that is used to initialize an object as allowed above,
> the implementation shall not create a separate return object (see
> 6.5) for the function_call. The aggregate or function_call shall be
> constructed directly in the new object.

[ARM 7.5(8.a/2)]
> Discussion: For a function_call, we only require
> build-in-place{build-in-place [partial]} for a limited type that
> would have been a return-by-reference type in Ada 95. We do this
> because we want to minimize disruption to Ada 95 implementations and
> users.

[ARM 7.5(9/2)]
> While it is allowed to write initializations of limited objects,
> such initializations never copy a limited object. The source of such
> an assignment operation must be an aggregate or function_call, and
> such aggregates and function_calls must be built directly in the
> target object. The following are consequences of the rules for
> limited types:

[ARM 7.5(9.a/2)]
> To be honest: This isn't quite true if the type can become
> nonlimited (see below); function_calls only are required to be
> build-in-place for “really” limited types.

[ARM 7.5(9.a/2)]
> As illustrated in 7.3.1, an untagged limited type can become
> nonlimited under certain circumstances.

[ARM 7.3.1(5/1)]
> For example, an array type whose component type is limited private
> becomes nonlimited if the full view of the component type is
> nonlimited and visible at some later place immediately within the
> declarative region in which the array type is declared. within the
> immediate scope of the array type. In such a case, the predefined
> "=" operator is implicitly declared at that place, and assignment is
> allowed after that place.

The latter, which would be the only one kind of exception, as stated
by [ARM 7.5(9.a/2)], does not apply in the example of the Test
program.

So I'm suspecting an error here : the object should indeed not be
Finalized when New_A or New_B terminates, as it do in Test.

All view are limited : public and private view. So no where a limited
view becomes a nonlimited view. Use of extended return statement is
made in all place. Although Tested_Interface is initialized via an
invocation of New_Instance, no Finalization occurs (as the spy shows)
when its scope terminates. Its initialized where it is expected to be
(in New_A and New_B), but it is Finalized at the wrong place, just
like if it was returned by copy.

Protected, and Task are indeed to be Initialized/Finalized the way I
was expecting :

[ARM 7.6(9.1-9.5)]
> 9.1/2 A type is said to need finalization if:{needs finalization}
> {type (needs finalization)}
> 9.2/2 * it is a controlled type, a task type or a protected type; or
> 9.3/2 * it has a component that needs finalization; or
> 9.4/2 * it is a limited type that has an access discriminant whose
> designated type needs finalization; or
> 9.5/2 * it is one of a number of language-defined types that are
> explicitly defined to need finalization.

And Protected/Task are obviously limited :

[ARM 3.9.4(5/2)]
> An interface with the reserved word limited, task, protected, or
> synchronized in its definition is termed, respectively, a limited
> interface, a task interface, a protected interface, or a
> synchronized interface. In addition,{interface (synchronized)
> [partial]} {interface (protected) [partial]} {interface (task)
> [partial]} {interface (limited) [partial]} {interface (nonlimited)
> [partial]} {synchronized interface} {protected interface} {task
> interface} {limited interface} {nonlimited interface} all task and
> protected interfaces are synchronized interfaces, and all
> synchronized interfaces are limited interfaces.

If I'm wrong, I do not figure where I'm wrong.
From: Ludovic Brenta on
Yannick Duchêne wrote on comp.lang.ada:
>    Tested_Interface : Dispatch.Instance_Class :=
>      (Dispatch.New_Instance);

Here you are initializing with an aggregate that contains the result
of the function call. I suspect this triggers a compiler bug. What
happens if you remove the parentheses?

--
Ludovic Brenta.
From: Adam Beneschan on
On Feb 11, 7:16 am, Robert A Duff <bobd...(a)shell01.TheWorld.com>
wrote:
> "Hibou57 (Yannick Duchêne)" <yannick_duch...(a)yahoo.fr> writes:
>
> > What a surprise to me to experience something different : limited
> > object Finalized right after Initialization.
>
> I'm not going to read your code carefully, but that doesn't
> seem right.  If you have:
>
>     X : T := F(...);
>
> the result of F is "built in place" in X, and should be finalized
> when the procedure containing X is left.

Yes, I believe that's right, and it's spelled out clearly in the RM,
in sections 7.6(17.1/3-17.11/3 and especially 17.7/3), 6.5(5.8/3),
6.5(23/2), 7.6(4), and 3.10.2(10.1/2).

P.S. That was intended as sarcasm. I think these references are
right, but some of them contain some of the nastiest language in the
RM. If Yannick really wants to re-check the RM, as he mentioned, I
recommend having a bottle of Tylenol handy. Or a few beers.

But more simply, as I understand it: (1) When you have an extended
return, the return object is used as the anonymous object that holds
the function result at the point of the function call, so it's not
finalized until the caller is done with the anonymous object; and (2)
when the object is built in place, the anonymous object "mutates into"
the new object and is not finalized (7.6(17.7/3)). So yes, no
finalization should be done until X goes out of scope.

-- Adam

From: Adam Beneschan on
On Feb 11, 11:10 am, Robert A Duff <bobd...(a)shell01.TheWorld.com>
wrote:
> Adam Beneschan <a...(a)irvine.com> writes:
> > But more simply, as I understand it: (1) When you have an extended
> > return, the return object is used as the anonymous object that holds
> > the function result at the point of the function call, so it's not
> > finalized until the caller is done with the anonymous object; and (2)
> > when the object is built in place, the anonymous object "mutates into"
> > the new object and is not finalized (7.6(17.7/3)).  So yes, no
> > finalization should be done until X goes out of scope.
>
> Just to be clear: There's nothing particularly special about
> extended return statements, other than the fact that they
> provide a name for the result.  

That last is an important distinction, however. A function call
involves (conceptually) an anonymous object. For a normal return (for
a non-limited type):

return Some_Expression;

Some_Expression is computed and then assigned into the anonymous
object. This assignment involves an Adjust. Then, at some point,
whatever objects went into creating Some_Expression will get finalized
when the function exits. If Some_Expression is a local variable, for
instance, that variable will get finalized. If some other temporary
needs to be created in order to hold the value of Some_Expression,
that temporary will be finalized.

For an extended return,

return R : T;

R is not a local variable, and it isn't "assigned" into the anonymous
object; thus, there's no Adjust, and R is not finalized when the
function returns (but the anonymous object will be finalized later,
except when it mutates into some other object). I think that's the
point of the nasty language in 3.10.2(10.1). Which is another way of
saying that R is a name for the result (as you said), not a distinct
object that gets copied to the result. I thought it would be helpful
to point it out---not to you, but to other readers. It's hardly a
trivial difference, and it's an important one to understand when
adjusts and finalizations are involved. Anyway, I hope this helps
someone.

-- Adam