comp.lang.ada
 help / color / mirror / Atom feed
* A Gnother Gnasty bug
@ 2012-04-07  0:44 sbelmont700
  2012-04-07 11:09 ` Ludovic Brenta
  2012-04-07 11:54 ` Simon Wright
  0 siblings, 2 replies; 7+ messages in thread
From: sbelmont700 @ 2012-04-07  0:44 UTC (permalink / raw)


Hi everyone,

I've stumbled across another dubious GNAT GPL 2011 situation, yet this one is convoluted enough to perhaps be mistaken thinking on my part, so I would be appreciative of any independent tests before I submit it.  Additionally, I would like to try to describe it using the rich Ada lexicon, so please check me on that as well:

"When an object of a limited record type containing an object of a non-limited controlled record type which contains an access value to a classwide type is created from a value-returning function whose argument is supplied from another value-returning function whose argument is supplied from a classwide allocator, a constraint error is raised."

It's clearly an edge case, and would not surprise me if it is related to a different bug where allocating a classwide type mangles the dispatch table, but as this behavior is different in that an exception is raised instead of erroneous execution, perhaps it's something else entirely, or maybe even doing what it's supposed to do (in which case please explain it to me).

A small test procedure that demonstrates this behavior is as follows:

with Ada.Finalization;

procedure test is

  type I is limited interface;
  
  type I_Ptr is access I'class;
  
  type C is new Ada.Finalization.Limited_Controlled and I with
    record
      d : integer;
    end record;
  
  function F (arg : Integer) return I'class is
  begin
    return C'(Ada.Finalization.Limited_Controlled with d => arg);
  end F;

  
  type K is new Ada.Finalization.Controlled with
    record
      e : I_Ptr;
    end record;
  
  function G (p_i : I_Ptr) return K is
  begin
    return K'(Ada.Finalization.Controlled with e => p_i);
  end G;
  
  type J is limited record
    h : K;
  end record;
  
  function H (arg : K) return J is
  begin
    return J'(h => arg);
  end H;
  
  x : J := H(arg =>
               G(p_i => 
                  I_Ptr'(new I'class'(F(arg=> 42)))));

begin

   null;

end test;



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: A Gnother Gnasty bug
  2012-04-07  0:44 A Gnother Gnasty bug sbelmont700
@ 2012-04-07 11:09 ` Ludovic Brenta
  2012-04-07 12:48   ` sbelmont700
  2012-04-07 13:55   ` Robert A Duff
  2012-04-07 11:54 ` Simon Wright
  1 sibling, 2 replies; 7+ messages in thread
From: Ludovic Brenta @ 2012-04-07 11:09 UTC (permalink / raw)


sbelmont700@gmail.com writes:
>  new I'class'(F(arg=> 42)))));

I don't think that's legal.  You can have a class-wide access value but
not an allocator for a class-wide type because the allocator doesn't
know how much memory to allocate:

> 4.8(4): An initialized allocator is an allocator with a
> qualified_expression. [...]

Your allocator is initialized because it has a qualified_expression.

> 4.8(7/2): For the evaluation of an initialized allocator, the
> evaluation of the qualified_expression is performed first. An object
> of the designated type is created and the value of the
> qualified_expression is converted to the designated subtype and
> assigned to the object.

Now the allocator must create an object of the type designated by the
qualified_expression.

> 3.3(23): [...] A class-wide subtype is defined to have unknown
> discriminants, and is therefore an indefinite subtype. An indefinite
> subtype does not by itself provide enough information to create an
> object; an additional constraint or explicit initialization expression
> is necessary (see *note 3.3.1).

The type designatd by your qualified_expression is class-wide, it is
therefore impossible for the allocator to create the object.

Therefore I think the compiler should have rejected your program.  The
fact that the value in the qualified_expression is the result of a
function call fools the compiler.  Here is a smaller example to
demonstrate:

with Ada.Finalization;
procedure T is

   type B is new Ada.Finalization.Controlled with record
      D : Integer;
   end record;

   type Access_B is access B'Class;


   function F return B'Class is
   begin
      return B'(Ada.Finalization.Controlled with D => 42);
   end F;

   J : Access_B := new B'Class'(Ada.Finalization.Controlled with D => 42); -- Line 16
   K : Access_B := new B'Class'(F); -- Line 17: Not diagnosed

begin
   null;
end T;

gnatmake -gnatwa t
gcc-4.4 -c -gnatwa t.adb
t.adb:16:32: expected type "B'Class" defined at line 4
t.adb:16:32: found a composite type
gnatmake: "t.adb" compilation error

gnatmake -gnatwa t
gcc-4.6 -c -gnatwa t.adb
t.adb:16:32: expected type "B'Class" defined at line 4
t.adb:16:32: found a composite type

So, the compiler correctly rejects line 16 but not line 17, even though
both lines do exactly the same illegal thing.

The correct way to call the allocator is, of course:

   J : Access_B := new B'(Ada.Finalization.Controlled with D => 42);
   K : Access_B := new B'(F);

HTH

-- 
Ludovic Brenta.



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: A Gnother Gnasty bug
  2012-04-07  0:44 A Gnother Gnasty bug sbelmont700
  2012-04-07 11:09 ` Ludovic Brenta
@ 2012-04-07 11:54 ` Simon Wright
  1 sibling, 0 replies; 7+ messages in thread
From: Simon Wright @ 2012-04-07 11:54 UTC (permalink / raw)


I don't think you needed all those levels to trigger the bug (and bug I
think it is, though the code is very very complex):

  s : I_Ptr := I_Ptr'(new I'class'(F(arg=> 42)));

  t : K := G(p_i => s);         <--- this is OK

  u : K :=
    G(p_i =>
        I_Ptr'
        (new I'class'           <--- Constraint_Error here
           (F(arg=> 42))));



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: A Gnother Gnasty bug
  2012-04-07 11:09 ` Ludovic Brenta
@ 2012-04-07 12:48   ` sbelmont700
  2012-04-07 13:55   ` Robert A Duff
  1 sibling, 0 replies; 7+ messages in thread
From: sbelmont700 @ 2012-04-07 12:48 UTC (permalink / raw)


Hi,

I'm no language lawyer, but the same section from the LRM:

> 3.3(23): [...] A class-wide subtype is defined to have unknown 
> discriminants, and is therefore an indefinite subtype. An indefinite 
> subtype does not by itself provide enough information to create an 
> object; an additional constraint or explicit initialization expression 
> is necessary (see *note 3.3.1). 

seems to imply that it is legal, given an initializer ("an ... explicit initialization expression is necessary").  Given 

> 4.8(4): An initialized allocator is an allocator with a 
> qualified_expression. [...] 

Then I would read it as being acceptable.  After all, the fundamental problem is the same (how much room to make) whether you do it on the heap or the stack, and Ada has no problem with

o : T'Class := Make_D;

(Where an abstract T is extended by D), or any other indefinite types returned from functions (i.e. arrays, discriminated records).

-sb



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: A Gnother Gnasty bug
  2012-04-07 11:09 ` Ludovic Brenta
  2012-04-07 12:48   ` sbelmont700
@ 2012-04-07 13:55   ` Robert A Duff
  2012-04-09 21:09     ` Adam Beneschan
  1 sibling, 1 reply; 7+ messages in thread
From: Robert A Duff @ 2012-04-07 13:55 UTC (permalink / raw)


Ludovic Brenta <ludovic@ludovic-brenta.org> writes:

> sbelmont700@gmail.com writes:
>>  new I'class'(F(arg=> 42)))));
>
> I don't think that's legal.

I didn't look at the original post, but
I think it's legal.

>...You can have a class-wide access value but
> not an allocator for a class-wide type because the allocator doesn't
> know how much memory to allocate:

It knows how much to allocate from the initial value.
It's UNinitialized allocators (and uninitialized object
declarations) that are illegal when the type is indefinite.

It also needs to know the alignment, which it can get
from the 'Tag of the initial value.

>> 4.8(4): An initialized allocator is an allocator with a
>> qualified_expression. [...]
>
> Your allocator is initialized because it has a qualified_expression.

Right, and that's why it's legal.

> The type designatd by your qualified_expression is class-wide, it is
> therefore impossible for the allocator to create the object.

There are at least two ways to implement it:

    Call F(42), putting the result in a temp.
    Call Allocate with the size and alignment of that temp.
    Copy the temp into the newly-allocated object,
        with adjustment/finalization if controlled.

    Call F, passing it a hidden thunk, which does
        the allocation.
    At the point where F does "return", call the thunk,
        passing this size and alignment.
    Store the result of F at the address returned by
        the thunk.

If the designated type is limited, the second way is
necessary, because the first way won't work (it involves
a copy, which won't work for limited types).

In this example, the type is nonlimited, so either method
works.  GNAT currently uses the first, but might someday
use the second for better efficiency (avoiding the copy,
if it's big, and avoiding adjust/fin) in some cases.

> with Ada.Finalization;
> procedure T is
>
>    type B is new Ada.Finalization.Controlled with record
>       D : Integer;
>    end record;
>
>    type Access_B is access B'Class;
>
>
>    function F return B'Class is
>    begin
>       return B'(Ada.Finalization.Controlled with D => 42);
>    end F;
>
>    J : Access_B := new B'Class'(Ada.Finalization.Controlled with D => 42); -- Line 16

This is illegal because the expected type for an aggregate
cannot be class-wide.  Otherwise, how would it know what 'Tag
to use, and how would it know whether you've obeyed the
full coverage rule?

This is a property of aggregates, whether or not "new" is involved.
If F said "return B'Class'(...)" instead of "return B'(...)",
then that would be illegal, too.

In general, for aggregates, the compiler does NOT determine
the type (or the 'Tag) by looking inside the aggregate.
It does NOT say, hmm, let's find a type derived from Controlled
that happens to have a component D of some integer type.
Instead, this information must come from the context.

>    K : Access_B := new B'Class'(F); -- Line 17: Not diagnosed

Here, there's no aggregate.  This is legal.

> gnatmake -gnatwa t
> gcc-4.6 -c -gnatwa t.adb
> t.adb:16:32: expected type "B'Class" defined at line 4
> t.adb:16:32: found a composite type

GNAT is correct here.

- Bob



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: A Gnother Gnasty bug
  2012-04-07 13:55   ` Robert A Duff
@ 2012-04-09 21:09     ` Adam Beneschan
  2012-04-09 21:39       ` Robert A Duff
  0 siblings, 1 reply; 7+ messages in thread
From: Adam Beneschan @ 2012-04-09 21:09 UTC (permalink / raw)


On Saturday, April 7, 2012 6:55:29 AM UTC-7, Robert A Duff wrote:
> There are at least two ways to implement it:
> 
>     Call F(42), putting the result in a temp.
>     Call Allocate with the size and alignment of that temp.
>     Copy the temp into the newly-allocated object,
>         with adjustment/finalization if controlled.
> 
>     Call F, passing it a hidden thunk, which does
>         the allocation.
>     At the point where F does "return", call the thunk,
>         passing this size and alignment.
>     Store the result of F at the address returned by
>         the thunk.
> 
> If the designated type is limited, the second way is
> necessary, because the first way won't work (it involves
> a copy, which won't work for limited types).

That last isn't really true, I think.  The language says there's no *assignment* for limited types; the assignment operation is defined by the language, and that operation involves finalization, adjustment, maybe some constraint checks, maybe some other things.  But there's no rule saying that the compiler can't implement things by doing a byte copy "under the hood", as long as it doesn't result in any additional Adjust/Finalizes being called.  The language specifies how things look semantically, not how they're implemented.

The tricky case is where the result of F contains self-references (possibly in a component); now, if the compiler generates code to do a byte copy, it has to make sure any self-references are fixed.  I don't think that generating code like this violates any language semantics, though.  (In fact, see AARM 7.6(17.q-17.v); I think those say that the "intended implementation" is that the address of J.all is the same as the address of the return object when F is called, but that's not a requirement and alternative implementations are theoretically possible.)

                    -- Adam



^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: A Gnother Gnasty bug
  2012-04-09 21:09     ` Adam Beneschan
@ 2012-04-09 21:39       ` Robert A Duff
  0 siblings, 0 replies; 7+ messages in thread
From: Robert A Duff @ 2012-04-09 21:39 UTC (permalink / raw)


Adam Beneschan <adam@irvine.com> writes:

> That last isn't really true, I think.

Yes, you're right.  An implementation can copy limited objects any time
it likes, so long as it makes the copy semantically invisible (which, as
you say, would involve fixing up all the pointers).

An implementation that does copying garbage collection would do that.
But otherwise, it isn't really practical.  Think about copying locks
(as in protected objects).

If a copy isn't semantically visible, then is it a copy?  ;-)

- Bob



^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2012-04-09 21:39 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2012-04-07  0:44 A Gnother Gnasty bug sbelmont700
2012-04-07 11:09 ` Ludovic Brenta
2012-04-07 12:48   ` sbelmont700
2012-04-07 13:55   ` Robert A Duff
2012-04-09 21:09     ` Adam Beneschan
2012-04-09 21:39       ` Robert A Duff
2012-04-07 11:54 ` Simon Wright

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox