Version 1.10 of ai05s/ai05-0157-1.txt
!standard 4.8(5.3/2) 10-06-08 AI05-0157-1/04
!standard 13.11.2(4)
!standard 13.11.2(17)
!class binding interpretation 09-06-02
!status Amendment 201Z 09-12-10
!status WG9 Approved 10-06-18
!status ARG Approved 10-0-1 09-11-08
!status work item 09-06-02
!status received 09-02-15
!priority Low
!difficulty Medium
!qualifier Omission
!subject Calling Unchecked_Deallocation is illegal for zero-sized pools
!summary
Calling an instance of Unchecked_Deallocation is illegal if the pool is
known to have Storage_Size equal to zero.
An instance of Unchecked_Deallocation raises Program_Error if the pool
has Storage_Size equal to zero (if it is not illegal).
!question
RM-4.8(5.3/2) says:
5.3/2 {AI95-00366-01} An allocator shall not be of an access type for which
the Storage_Size has been specified by a static expression with value zero or
is defined by the language to be zero.
But there is not a similar rule for Unchecked_Deallocation. That means that
Unchecked_Deallocation can be instantiated for such types, and every call
to such an Unchecked_Deallocation is guaranteed to be erroneous.
Should such instances be illegal? (No, but calls are.)
!recommendation
(See Summary.)
!wording
Delete the last sentence of 4.8(5.3/2).
Add before 13.11.2(4):
Legality Rules
A call on an instance of Unchecked_Deallocation is illegal if the actual
access type of the instance is a type for which the Storage_Size has been
specified by a static expression with value zero or is defined by the language
to be zero. In addition to the places where Legality Rules normally apply (see
12.3), this rule applies also in the private part of an instance of a generic unit.
[Editor's note: This is nearly a copy of 4.8(5.3/2).]
Add after 13.11.2(17): (Implementation Advice)
A call on an instance of Unchecked_Deallocation with a non-null access value should
raise Program_Error if the actual access type of the instance is a type for which
the Storage_Size has been specified to be zero or is defined by the language to be
zero.
AARM Note: If the call is not illegal (as in a generic body), we recommend that
it raise Program_Error. Since the execution of this call is erroneous (any allocator
from the pool will have raised Storage_Error, so the non-null access value must
have been allocated from a different pool or be a stack-allocated object), we can't
require any behavior - anything at all would be a legitimate implementation.
[Editor's note: I don't think we want to raise Program_Error if the access value is null;
that case is not erroneous and is defined to do nothing. It seems harmless.
Humm, reading the wording of 13.11.2(16), one could argue that calling an instance of
Unchecked_Deallocation with a null access value is always erroneous. It's not clear
what "the object" is in that case, but surely it wasn't created by an allocator with
Name'Storage_Pool. (Of course, even Adam would be embarrassed to actually claim this
seriously - no one could doubt the intent.) Do we need to clarify 13.11.2(16) in some
way (perhaps an AARM To Be Honest note would be sufficient)?]
!discussion
We had a number of choices here:
(1) Make the instance of Unchecked_Deallocation illegal. But this is
weird, because this is a legality check unrelated to the (normal) generic
contract. It would be a magic extra check.
A second objection to this choice is that it would make instances of
generics illegal if there is merely an instance of Unchecked_Deallocation
in the specification. That is quite reasonable, and the instance would not
necessarily be called. (Imagine a generic that determines whether or not
to call Free based on an additional generic parameter; it would always have
to instantiate it.) This would reduce the usability of generics.
(2) Make calls on the instance of Unchecked_Deallocation illegal. This is
also a magic check, but at least it doesn't run into issues with generic
units.
(3) Raise Program_Error if such a generic instance is detected. This
is less weird, but it turns what otherwise would be a legality rule into
a runtime check. This also could render some generics unusable (as in
case (1)); this could even be a broader problem as the runtime check
could cover cases that would not have been statically illegal.
(4) Raise Program_Error on the call of such a generic instance. This
also turns a legality rule into a runtime check, but at least it doesn't
have the problems with generic units.
(5) Leave calling the instantiation erroneous, but add a permission for
an implementation to reject any program that it can detect would have
erroneous execution. (One wonders if a similar permission should be
allowed for Bounded Errors.) On the other hand, this too could cause trouble
for any code conditionally compiled. Probably it would be better to follow
the rule in 1.1.5(6) (which suggests a warning when the compiler can
detect errors).
Since we want this to remain a legality rule, and we do not want to make
generic units less useful, we have chosen option (2), along with a recommendation
for option (4) if the call is legal but the size is known to be zero.
We delete the last line of 4.8(5.3/2) and do not add a similar line to
the new text. That line read: "This rule does not apply in the body
of a generic unit or within a body declared within the declarative region
of a generic unit, if the type of the allocator is a descendant of a
formal access type declared within the formal part of the generic unit."
However, this cannot usefully happen. For a formal access type, the
Storage_Size is not known and surely isn't static, so the legality rules can never
apply. For a formal derived type, the legality rules can only be triggered
by a parent type having the appropriate property. But Storage_Size can
never be specified for a derived access type, so it always has the same
value for all child types. That means that any actual that would match
the formal derived type necessarily has the same Storage_Size properties,
so it is harmless (and preferable) to check them in the body - they are
always known in that case. We cannot imagine changing this particular
invariant, as it is necessary so that (potentially implicit) type
conversions between derived access types are safe, so there is no
maintenance hazard potential from omitting the rule.
!corrigendum 4.8(5.3/2)
Replace the paragraph:
An allocator shall not be of an access type for which the Storage_Size
has been specified by a static expression with value zero or is defined by
the language to be zero. In addition to the places where Legality Rules
normally apply (see 12.3), this rule applies also in the private part
of an instance of a generic unit. This rule does not apply in the body
of a generic unit or within a body declared within the declarative region
of a generic unit, if the type of the allocator is a descendant of a
formal access type declared within the formal part of the generic unit.
by:
An allocator shall not be of an access type for which the Storage_Size
has been specified by a static expression with value zero or is defined by
the language to be zero. In addition to the places where Legality Rules
normally apply (see 12.3), this rule applies also in the private part
of an instance of a generic unit.
!corrigendum 13.11.2(4)
Insert before the paragraph:
Given an instance of Unchecked_Deallocation declared as follows:
the new paragraph:
Legality Rules
A call on an instance of Unchecked_Deallocation is illegal if the actual
access type of the instance is a type for which the Storage_Size has been
specified by a static expression with value zero or is defined by the language
to be zero. In addition to the places where Legality Rules normally apply (see
12.3), this rule applies also in the private part of an instance of a generic unit.
!corrigendum 13.11.2(17)
Insert after the paragraph:
For a standard storage pool, Free should actually reclaim the storage.
the new paragraph:
A call on an instance of Unchecked_Deallocation with a non-null access value should
raise Program_Error if the actual access type of the instance is a type for which
the Storage_Size has been specified to be zero or is defined by the language to be
zero.
!ACATS Test
!appendix
From: Bob Duff
Date: Sunday, February 15, 2009 11:17 AM
RM-4.8(5.3/2) says:
5.3/2 {AI95-00366-01} An allocator shall not be of an access type for which
the Storage_Size has been specified by a static expression with value zero or
is defined by the language to be zero.
But I can't find a similar rule for Unchecked_Deallocation. Seems like a hole,
to me. Unchecked_Deallocation should be illegal for such types, for the same
reasons as 4.8(5.3/2). Otherwise, every call to such an Unchecked_Deallocation
is guaranteed to be erroneous.
It's a nasty sort of erroneousness, because it will work fine on some
implementations.
****************************************************************
From: Robert Dewar
Date: Sunday, February 15, 2009 11:24 AM
Not likely, trying to allocate statically allocated stuff will most likely NOT
work and blow up! I agree this is an obvious hole. Bob, you should file this as
a bug if GNAT does not detect this case, and flag it as illegal.
****************************************************************
From: Bob Duff
Date: Sunday, February 15, 2009 12:22 PM
Sure, but the usual case is to allocate something with "new", then convert it to
another access type. If you then call U_D using the second access type, and
that has Storage_Size 0, GNAT will just deallocate the object. It won't blow up.
OK, just to be sure, I wrote this:
with Ada.Text_IO; use Ada.Text_IO;
with Unchecked_Deallocation;
procedure Main is
type A is access all String;
for A'Storage_Size use 0;
procedure Free is new Unchecked_Deallocation (String, A);
type Allocation_Type is access all String;
X : Allocation_Type := new String'("Hello, world.");
Y : A := A (X);
begin
Free (Y);
if Y = null then
Put_Line ("It's null.");
else
Put_Line ("It's not null.");
end if;
end Main;
It prints "It's null.". (A more realistic example would have various class-wide
types.)
The compiler warns, though: deallocation from empty storage pool.
So that's not so horrible.
>... I agree this is an obvious hole.
> Bob, you should file this as a bug if GNAT does not detect this case,
>and flag it as illegal.
Well, the current language doesn't make it illegal (except perhaps via "Robert's
Rule Of Absurdity") and we do give a warning...
****************************************************************
From: Tucker Taft
Date: Sunday, February 15, 2009 11:38 AM
I don't quite see how you can make it illegal (Unchecked_Deallocation is a
generic, after all), but you could define it to raise Program_Error (or perhaps
to do nothing?).
****************************************************************
From: Robert Dewar
Date: Sunday, February 15, 2009 11:42 AM
The instantiation should be illegal!
****************************************************************
From: Tucker Taft
Date: Sunday, February 15, 2009 12:03 PM
That could be a bit of a pain to implement for some. Generics can be renamed,
etc. At least in our implementation, there is nothing very special about
Unchecked_Deallocation until it is called, at which point we start treating it
specially.
****************************************************************
From: Robert Dewar
Date: Sunday, February 15, 2009 12:16 PM
How about then the following
a) a clear statement that instantiating UC or calling UC for such a type is
erroneous.
b) impl permission to make either the instantiation or the call illegal.
Actually it would make some sense to have impl permission that allows the
compiler to treat ANY recognized erroneous construct as illegal!
****************************************************************
From: Tucker Taft
Date: Sunday, February 15, 2009 12:51 PM
My only concern about this approach is one could imagine a generic that has a
formal access type and declares some kind of "Free" operation implemented using
Unchecked_Deallocation. The generic can reasonably be instantiated with an
access type with storage size zero, so long as the Free operation is never
called.
> Actually it would make some sense to have impl permission that allows
> the compiler to treat ANY recognized erroneous construct as illegal!
This would seem a natural extension of RM 1.1.5(6).
****************************************************************
From: Robert Dewar
Date: Sunday, February 15, 2009 1:07 PM
> My only concern about this approach is one could imagine a generic
> that has a formal access type and declares some kind of "Free"
> operation implemented using Unchecked_Deallocation. The generic can
> reasonably be instantiated with an access type with storage size zero,
> so long as the Free operation is never called.
That's a good argument, so it should be the Free operation that is always
erroneous, and impl permission to consider the call to Free to be illegal ...
****************************************************************
From: Bob Duff
Date: Sunday, February 15, 2009 12:12 PM
> I don't quite see how you can make it illegal (Unchecked_Deallocation
> is a generic, after all), but you could define it to raise
> Program_Error (or perhaps to do nothing?).
Not sure what you mean -- I'm proposing to make it illegal by adding a Legality
Rule:
For an instantiation of Unchecked_Deallocation, the generic actual
parameter for the Name generic formal parameter shall not have a
Storage_Size that has been specified by a static expression with value
zero, nor defined by the language to be zero.
Or:
An access type for which the Storage_Size has been specified by a static
expression with value zero or is defined by the language to be zero shall
not be passed to the Name parameter in an instantiation of
Unchecked_Deallocation.
Plus an AARM note: Including a renaming of Unchecked_Deallocation.
****************************************************************
From: Bob Duff
Date: Sunday, February 15, 2009 2:10 PM
> >> That could be a bit of a pain to implement for some.
> >> Generics can be renamed, etc. At least in our implementation,
> >> there is nothing very special about Unchecked_Deallocation until it
> >> is called, at which point we start treating it specially.
I have trouble believing this could be hard to implement in any compiler.
This is from the man who invented coextensions. ;-)
Seriously, during semantic analysis of an instantiation, you look in your symbol
table, and see if what you've got is called "unchecked_deallocation", and if its
parent is a root library unit called "ada". Yes, you have to follow renamings
-- big deal.
It's a special-case kludge. But it's not hard.
> > How about then the following
> >
> > a) a clear statement that instantiating UC or calling UC for such a
> > type is erroneous.
> >
> > b) impl permission to make either the instantiation or the call
> > illegal.
This is getting too complicated for my taste.
I'd rather leave the language alone.
We already have a warning, which is good enough in practise.
> My only concern about this approach is one could imagine a generic
> that has a formal access type and declares some kind of "Free"
> operation implemented using Unchecked_Deallocation. The generic can
> reasonably be instantiated with an access type with storage size zero,
> so long as the Free operation is never called.
The rule about "new" does not apply to such a generic formal type (I presume,
since otherwise it would be a contract model violation), and my proposed
legality rule should not apply to such a generic formal type. It only applies
if the Storage_Size is visibly and statically 0. So I think this is a red
herring.
****************************************************************
From: Randy Brukardt
Date: Tuesday, February 17, 2009 12:53 AM
> Seriously, during semantic analysis of an instantiation, you look in
> your symbol table, and see if what you've got is called
> "unchecked_deallocation", and if its parent is a root library unit
> called "ada". Yes, you have to follow renamings
> -- big deal.
>
> It's a special-case kludge. But it's not hard.
We've been *very* adverse to adding special-case kludges to generic
instantiations. Indeed, I proposed one to support the factory generic
(Generic_Dispatching_Constructor), and we decided instead to add the entire
mechanism to the language outright (abstract formal subprograms) -- which surely
was a lot more work.
I don't much care either way, BTW.
****************************************************************
From: Robert Dewar
Date: Tuesday, February 17, 2009 11:55 AM
> We've been *very* adverse to adding special-case kludges to generic
> instantiations.
I really think that Unchecked_Deallocation and Unchecked_Conversion are special
cases, they are really fundamental primitives in the language, which just happen
to be spelled as generics, but I don't see that we should let this aversion
cripple the definition in any way. In particular the rules for erroneousness are
already special.
****************************************************************
From: Randy Brukardt
Date: Tuesday, December 10, 2009 1:21 AM
In St. Pete, someone complained that the last sentence of the new wording for
13.11.2(3.1/3) is unnecessary:
A call on an instance of Unchecked_Deallocation is illegal if the actual access
type of the instance is a type for which the Storage_Size has been specified by
a static expression with value zero or is defined by the language to be zero. In
addition to the places where Legality Rules normally apply (see 12.3), this rule
applies also in the private part of an instance of a generic unit. This rule
does not apply in the body of a generic unit or within a body declared within
the declarative region of a generic unit, if the actual access type of the
instance is a descendant of a formal access type declared within the formal part
of the generic unit.
The thinking was that a generic formal access type never has a static value for
Storage_Size. There is an unusual case: that a generic formal derived type has a
parent access type that does have such a static value; but in that case, all
child types that would match the formal derived type also must have that static
value. So there would be no harm in applying the rule.
I argued that we shouldn't make a change because it is exactly the same as the
allocator rule, which was considered decisive.
But I missed the more important question: why is this sentence in the allocator
rule as well? The same points apply to it. AI95-0366-1 does not explain why this
sentence was included - it doesn't seem to be needed for allocators, either. In
any case, the legality rule (like any other legality rule) doesn't apply in the
bodies of instances, so that can't be the problem.
I wondered if it had anything to do with the other case: "defined by the
language to be zero". That's code for "remote types". But I don't think a
generic formal access type can be a remote access type. (A formal derived type
can be, but that again would not need any exceptions.)
So I suspect that the last sentence can be dropped from both 4.8(5.3/2) and the
new 13.11.2(3.1/3). Unless Steve can remember some obscure case involving child
generic units and/or formal packages.
Any other thoughts??
****************************************************************
From the minutes of the Amendement Subcommittee conference call of
December 10th:
Drop the extra sentence from both paragraphs. Explain in the AARM why there is
no problem that requires this extra sentence.
****************************************************************
From: Robert Dewar
Date: Sunday, July 11, 2010 1:52 PM
unexpectedly, this turned out to be a bit of a pain.
GNAT always gave a warning, but it is now required to give an error.
But there are legitimate low-level mucking places where GNAT does free from
empty storage pools, quite deliberately. There is a storage clause of zero to
avoid any allocators, but objects are allocated under the covers, then freed
using unchecked deallocation. I am sure I can work around this, but it is
interesting that such an innocuous looking AI should cause quite a bit of
trouble, and introduce significant incompatibilities. Will be interesting to see
if any customers are affected.
****************************************************************
From: Robert Dewar
Date: Sunday, July 11, 2010 2:02 PM
I wonder if this incompatibility is really worth it? I am sure this was agreed
to without any idea that it could invalidate existing code in a manner hard to
fix.
In practice giving a warning is perfectly good enough, oh well!
****************************************************************
From: Robert Dewar
Date: Sunday, July 11, 2010 2:14 PM
I ended up "fixing" this by simply removing the storage size clauses, so now I
lose the compile time protection of any allocations being errors, though they
would still generate a warning of Storage_Error in these particular cases, so
not too bad.
But odd that an AI intended to improve security at compile time ends up reducing
security at compile time.
Of course this is low level mucking, I realie, but stil interesting.
If I can do that low level mucking, so can any other Ada user :-)
****************************************************************
From: Bob Duff
Date: Sunday, July 11, 2010 2:18 PM
> But there are legitimate low-level mucking places where GNAT does free
> from empty storage pools, quite deliberately.
Can you point me to some of those cases?
****************************************************************
Questions? Ask the ACAA Technical Agent