13.9.1 Data Validity
Certain actions that can potentially lead to erroneous
execution are not directly erroneous, but instead can cause objects to
become abnormal. Subsequent uses of abnormal objects can be erroneous.
A scalar object can have an
invalid representation,
which means that the object's representation does not represent any value
of the object's subtype.
The primary cause of invalid
representations is uninitialized variables.
Abnormal objects and invalid representations are
explained in this subclause.
Dynamic Semantics
When
an object is first created, and any explicit or default initializations
have been performed, the object and all of its parts are in the
normal
state. Subsequent operations generally leave them normal. However, an
object or part of an object can become
abnormal in the following
ways:
An assignment to the object
is disrupted due to an abort (see
9.8) or due
to the failure of a language-defined check (see
11.6).
{
AI95-00426-01}
The object is not scalar, and is passed to an
in out or
out
parameter of an imported procedure, the Read procedure of an instance
of Sequential_IO, Direct_IO, or Storage_IO, or the stream attribute T'Read,
if after return from the procedure the representation of the parameter
does not represent a value of the parameter's subtype.
{
AI95-00426-01}
The object is the return object of a function call of a nonscalar type,
and the function is an imported function, an instance of Unchecked_Conversion,
or the stream attribute T'Input, if after return from the function the
representation of the return object does not represent a value of the
function's subtype.
Discussion: We explicitly list the routines
involved in order to avoid future arguments. All possibilities are listed.
We did not include Stream_IO.Read in the list
above. A Stream_Element should include all possible bit patterns, and
thus it cannot be invalid. Therefore, the parameter will always represent
a value of its subtype. By omitting this routine, we make it possible
to write arbitrary I/O operations without any possibility of abnormal
objects.
{
AI95-00426-01}
[For an imported object, it is the programmer's responsibility to ensure
that the object remains in a normal state.]
Proof: This follows (and echos) the standard
rule of interfacing; the programmer must ensure that Ada semantics are
followed (see
B.1).
Whether or not an object actually
becomes abnormal in these cases is not specified. An abnormal object
becomes normal again upon successful completion of an assignment to the
object as a whole.
Erroneous Execution
It is erroneous to evaluate a
primary that
is a
name
denoting an abnormal object, or to evaluate a
prefix
that denotes an abnormal object.
Ramification: The in out or out
parameter case does not apply to scalars; bad scalars are merely invalid
representations, rather than abnormal, in this case.
Reason: {
AI95-00114-01}
The reason we allow access objects, and objects containing subcomponents
of an access type, to become abnormal is because the correctness of an
access value cannot necessarily be determined merely by looking at the
bits of the object. The reason we allow scalar objects to become abnormal
is that we wish to allow the compiler to optimize assuming that the value
of a scalar object belongs to the object's subtype, if the compiler can
prove that the object is initialized with a value that belongs to the
subtype. The reason we allow composite objects to become abnormal is
that such object might be represented with implicit levels of indirection;
if those are corrupted, then even assigning into a component of the object,
or simply asking for its Address, might have an unpredictable effect.
The same is true if the discriminants have been destroyed.
Bounded (Run-Time) Errors
If
the representation of a scalar object does not represent a value of the
object's subtype (perhaps because the object was not initialized), the
object is said to have an
invalid representation. It is a bounded
error to evaluate the value of such an object. If the error is detected,
either Constraint_Error or Program_Error is raised.
Otherwise, execution continues using the invalid
representation. The rules of the language outside this subclause assume
that all objects have valid representations. The semantics of operations
on invalid representations are as follows:
Discussion: The AARM is more explicit
about what happens when the value of the case expression is an invalid
representation.
Ramification: {
AI95-00426-01}
This includes the result object of functions, including the result of
Unchecked_Conversion, T'Input, and imported functions.
If the representation of the object represents
a value of the object's type, the value of the type is used.
If the representation of the object does not represent
a value of the object's type, the semantics of operations on such representations
is implementation-defined, but does not by itself lead to erroneous or
unpredictable execution, or to other objects becoming abnormal.
Implementation Note: {
AI95-00426-01}
This means that the implementation must take care not to use an invalid
representation in a way that might cause erroneous execution. For instance,
the exception mandated for
case_statements
must be raised. Array indexing must not cause memory outside of the array
to be written (and usually, not read either). These cases and similar
cases may require explicit checks by the implementation.
Erroneous Execution
{
AI95-00167-01}
{
AI05-0279-1}
A call to an imported function or an instance of
Unchecked_Conversion is erroneous if the result is scalar, the result
object has an invalid representation, and the result is used other than
as the
expression
of an
assignment_statement
or an
object_declaration,
as the
object_name
of an
object_renaming_declaration,
or as the
prefix
of a Valid attribute. If such a result object is used as the source of
an assignment, and the assigned value is an invalid representation for
the target of the assignment, then any use of the target object prior
to a further assignment to the target object, other than as the
prefix
of a Valid attribute reference, is erroneous.
Ramification: {
AI95-00167-01}
In a typical implementation, every bit pattern that fits in an object
of a signed integer subtype will represent a value of the type, if not
of the subtype. However, for an enumeration or floating point type, as
well as some modular types, there are typically bit patterns that do
not represent any value of the type. In such cases, the implementation
ought to define the semantics of operations on the invalid representations
in the obvious manner (assuming the bounded error is not detected): a
given representation should be equal to itself, a representation that
is in between the internal codes of two enumeration literals should behave
accordingly when passed to comparison operators and membership tests,
etc. We considered
requiring such sensible behavior, but it resulted
in too much arcane verbiage, and since implementations have little incentive
to behave irrationally, such verbiage is not important to have.
{
AI95-00167-01}
If a stand-alone scalar object is initialized to a an in-range value,
then the implementation can take advantage of the fact that the use of
any out-of-range value has to be erroneous. Such an out-of-range value
can be produced only by things like unchecked conversion, imported functions,
and abnormal values caused by disruption of an assignment due to abort
or to failure of a language-defined check. This depends on out-of-range
values being checked before assignment (that is, checks are not optimized
away unless they are proven redundant).
Consider the following
example:
{
AI95-00167-01}
type My_Int
is range 0..99;
function Safe_Convert
is new Unchecked_Conversion(My_Int, Integer);
function Unsafe_Convert
is new Unchecked_Conversion(My_Int, Positive);
X : Positive := Safe_Convert(0); --
Raises Constraint_Error.
Y : Positive := Unsafe_Convert(0); --
Bounded Error, may be invalid.
B : Boolean := Y'Valid; --
OK, B = False.
Z : Positive := Y+1; --
Erroneous to use Y.
{
AI95-00167-01}
{
AI95-00426-01}
The call to Unsafe_Convert is a bounded error, which might raise Constraint_Error,
Program_Error, or return an invalid value. Moreover, if an exception
is not raised, most uses of that invalid value (including the use of
Y) cause erroneous execution. The call to Safe_Convert is not erroneous.
The result object is an object of subtype Integer containing the value
0. The assignment to X is required to do a constraint check; the fact
that the conversion is unchecked does not obviate the need for subsequent
checks required by the language rules.
{
AI95-00167-01}
{
AI95-00426-01}
The reason for delaying erroneous execution until the object is used
is so that the invalid representation can be tested for validity using
the Valid attribute (see
13.9.2) without
causing execution to become erroneous. Note that this delay does not
imply an exception will not be raised; an implementation could treat
both conversions in the example in the same way and raise Constraint_Error.
{
AI05-0279-1}
The rules are defined in terms of the result object, and thus the name
used to reference that object is irrelevant. That is why we don't need
any special rules to describe what happens when the function result is
renamed.
Implementation Note: If an implementation
wants to have a “friendly” mode, it might always assign an
uninitialized scalar a default initial value that is outside the object's
subtype (if there is one), and check for this value on some or all reads
of the object, so as to help detect references to uninitialized scalars.
Alternatively, an implementation might want to provide an “unsafe”
mode where it presumed even uninitialized scalars were always within
their subtype.
Ramification: The above rules imply that
it is a bounded error to apply a predefined operator to an object with
a scalar subcomponent having an invalid representation, since this implies
reading the value of each subcomponent. Either Program_Error or Constraint_Error
is raised, or some result is produced, which if composite, might have
a corresponding scalar subcomponent still with an invalid representation.
Note that it is not an error to assign, convert,
or pass as a parameter a composite object with an uninitialized scalar
subcomponent. In the other hand, it is a (bounded) error to apply a predefined
operator such as =, <, and xor to a composite operand with
an invalid scalar subcomponent.
{
AI05-0054-2}
The dereference of an access value is erroneous if
it does not designate an object of an appropriate type or a subprogram
with an appropriate profile, if it designates a nonexistent object, or
if it is an access-to-variable value that designates a constant object
and it did not originate from an attribute_reference applied to an aliased
variable view of a controlled or immutably limited object. [An access
value whose dereference is erroneous can exist, for example, because
of Unchecked_Deallocation, Unchecked_Access, or Unchecked_Conversion.]
Ramification: The above mentioned Unchecked_...
features are not the only causes of such access values. For example,
interfacing to other languages can also cause the problem.
{
AI05-0054-2}
We permit the use of access-to-variable values that designate constant
objects so long as they originate from an aliased variable view of a
controlled or immutably limited constant, such as during the initialization
of a constant (both via the “current instance” and during
a call to Initialize) or during an assignment (during a call to Adjust).
NOTE Objects can become abnormal
due to other kinds of actions that directly update the object's representation;
such actions are generally considered directly erroneous, however.
Wording Changes from Ada 83
In order to reduce the amount of erroneousness,
we separate the concept of an undefined value into objects with invalid
representation (scalars only) and abnormal objects.
Reading an object with an invalid representation
is a bounded error rather than erroneous; reading an abnormal object
is still erroneous. In fact, the only safe thing to do to an abnormal
object is to assign to the object as a whole.
Wording Changes from Ada 95
{
AI95-00167-01}
The description of erroneous execution for Unchecked_Conversion and imported
objects was tightened up so that using the Valid attribute to test such
a value is not erroneous.
{
AI95-00426-01}
Clarified the definition of objects that can become abnormal; made sure
that all of the possibilities are included.
Wording Changes from Ada 2005
{
AI05-0054-2}
Correction: Common programming techniques such as squirreling
away
an access to a controlled object during initialization
and using a self-referencing discriminant (the so-called “Rosen
trick”
) no longer are immediately erroneous
if the object is declared constant, so these techniques can be used portably
and safely. Practically, these techniques already worked as compilers
did not take much advantage of this rule, so the impact of this change
will be slight.
{
AI05-0279-1}
Correction: The description of erroneous execution for Unchecked_Conversion
and imported objects was adjusted to clarify that renaming such an object
is not, by itself, erroneous.
Ada 2005 and 2012 Editions sponsored in part by Ada-Europe