AI22-0101-1

!standard 13.5.1(15)                                    25-04-24  AI22-0101-1/10

!standard 13.9(11/2)

!standard 13.9.1(4)

!standard 13.9.1(6/2)

!standard 13.9.1(6.1/2)

!standard 13.9.1(7)

!standard 13.9.2(0)

!standard 13.9.2(1)

!standard 13.9.2(14/6)

!standard B.3.3(26/2)

!class Amendment 24-04-16

!status Revision-202Y  25-03-21

!status WG9 Approved 25-06-09

!status ARG Approved  12-0-1  25-03-19

!status work item 24-04-16

!status received 24-02-22

!assigned author Randy Brukardt

!submitter Joshua Fletcher

!priority Low

!difficulty Easy

!subject Valid_Scalars attribute is defined

!summary

The Valid_Scalars attribute is defined to allow testing whether all scalar parts of an object are valid.

!issue

It is useful (for example, in a precondition) to be able to easily test whether all scalar parts of a given object are valid. A new attribute is defined to support this.

!recommendation

In general, follow the rules of GNAT’s existing implementation-defined Valid_Scalars attribute. Some additional corner cases are defined.

!wording

Modify 13.5.1(15):

An implementation may generate implementation-defined components (for example, one containing the offset of another component){ for composite types other than an all-scalar composite type (see 13.9.1)}.

 

{AARM Reason: We do not allow implementation-defined components in all-scalar composite types so that objects of those types never can have a representation which can cause issues beyond those caused by components with invalid representations. Since such types cannot have discriminant-dependent components, there is no reason to need such components anyway, and their use would cause problems with interfacing usages.}

Modify 13.9(11/2):

Otherwise, if the result type is scalar, the result of the function is implementation defined, and can have an invalid representation (see 13.9.1). {If the result type is an all-scalar composite type, then the result of the function is implementation defined, and the scalar subcomponents can have invalid representations. }If the result type is {some other} nonscalar{ type}, the {result}[effect] is implementation defined{, and if the result does not represent a value of the result subtype of the function}[; in particular], the result {is}[can be] abnormal (see 13.9.1).

{AARM To Be Honest: This wording is intended to be complete. Thus, results that have scalar types and all-scalar composite types cannot be abnormal; they just can have invalid values (or parts). For other nonscalar types, we say exactly which objects are abnormal. We intend an expansive meaning of “does not represent a value”, see 13.9.1 for more on that meaning.}

Add after 13.9.1(3):

AARM Language Design Principles

Invalid values are those that don’t meet the constraints and other limitations of the subtype, but don’t themselves cause any problems with execution. Abnormal values and objects are those that might not be as expected by the implementation, and thus might cause major problems if used, bad addresses, corrupt descriptors, and the like. Thus, any reading or copying of abnormal values is considered erroneous. Of course, being erroneous does not require bad behavior; many types not excluded will not cause problems on many implementations.

We separately define abnormal objects rather than directly making the creation of them erroneous so that a program can recover from such problems by reinitializing the objects before any further use. Note that it isn’t possible in general to detect whether a specific object is abnormal; detection of some errors (like bad access values) is likely to be impractical. Thus, the only expected recovery is reinitialization; testing of some types might work on specific implementations, but portable code should not rely on that.

We define a subset of composite types, the all-scalar composite types, that cannot become abnormal when input/imported/unchecked converted. These provide the possibility of portably checking objects created one of these ways for correctness without triggering erroneous execution.

[Editor’s note: This AARM note is intended to give a bit of background at the top of this section; some of it repeats bits of notes later. We might consider writing a Usage Note about the recommendation to use All-Scalar composite types for input/import as much as possible, as they should be guaranteed to work and be testable for correctness.]

Add before 13.9.1(4): (in the Dynamic Semantics section)

A composite subtype is an all-scalar composite subtype if:

         

AARM Ramification: Scalar discriminants are allowed, but no components can have access types.

         

AARM Reason: Tags can have multiple parts, especially when interfaces or nested extensions are involved. Also, it was considered important to allow implementation-defined components for tagged type.

         

AARM Reason: Some implementations may expect the padding bits to have a particular value (often zero). If we didn't exclude them, such implementations could get incorrect results when doing operations (such as equality comparisons). We do not require attribute Valid_Scalars to test such padding bits.

         

[Author's note: We could require such testing, but as it depends upon the implementation as to whether it matters, it gets messy and non-portable to do so.]

         

AARM To Be Honest: Representations are mainly defined for objects, so we mean any padding bits that are always included in objects of the type (in particular, padding bits in "gaps" between components). Padding bits (especially at the end) that are not usually included in objects of the type are not considered.

         

   

AARM Reason: An unconstrained subtype needs some sort of dope to store the bounds of the array. Similarly, some implementations might need dope to find components whose size is not known at compile-time. That dope might be considered part of the object by some implementations.

AARM Discussion: These definitions are given in the Dynamic Semantics section so it is clear that privacy is ignored for these definitions.

AARM Ramification: All of these properties are under the control of the programmer, either directly by declaration, or indirectly by the use of appropriate representation clauses. Therefore, it is possible to write definitions of such types that will work on any implementation. Thus, all-scalar composite subtypes provide a “safe-harbor” that can always be tested for validity without causing erroneous execution regardless of the source.

Modify 13.9.1(4):

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]{becomes} abnormal in the following ways:

Modify 13.9.1(6/2):

{AARM Discussion: Bad scalars or all-scalar composites merely have invalid representations after such a call; they do not become abnormal.}

Modify 13.9.1(6.1/2):

{AARM Ramification: A function result that is abnormal by this rule cannot be used in any way without causing erroneous execution, as the evaluation of the function call itself triggers the erroneous execution rules. That’s necessary as it may not be possible to copy the value nor select components from it, if necessary structures are corrupt. We need this rule primarily to define the cases that don’t become abnormal but are merely invalid.}

Modify 13.9.1(7):

[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.

{AARM To Be Honest: When we say that the representation of an object does not represent a value of some subtype, we are including the values of any tags, implementation-defined components, and even padding bits in that representation. Violation of any restrictions that the implementation has on these parts trigger these rules. For instance, if the implementation requires that the padding bits all be zero, a value in which some padding bits do not have the value zero triggers these rules.}

 

{AARM Discussion: We exclude scalar and all-scalar composite types from most of these rules so that interfacing operations can be portably constructed that do not have to worry about potentially abnormal objects. Disrupted assignments (especially from abort) can happen for any type, so that rule does not depend on the type of the object.}

 

[Author's notes: There is no advantage to allowing bad objects to stay normal in some unspecified cases. Any program (portable or not) will have to assume that the object can be abnormal, since there is no way to tell that it is not. This is not a testable property! In addition, since it is unspecified, it isn't even necessary for an implementation to be consistent (even from run to run). Finally, there is no requirement that the implementation causes something bad to happen just because the object is technically abnormal - erroneous execution always allows doing the expected thing. Having defined a "safe harbor" for composite types makes it even less necessary, since with care abnormal imported and inputted objects can be avoided.

The same is true in the disrupted assignment cases. Unlike the other cases, a disrupted assignment might leave a "good" value behind, while the input/import cases specifically only apply to bad values. However, as noted above, erroneous execution doesn't require anything bad to happen, and it isn't testable in general if the result value is bad. Thus one has to assume the worst when programming (especially as "unspecified" can change from run to run of a program), so it doesn't help to allow some objects to not be abnormal in these cases.]

Delete 13.9.1(8.b).

[Editor’s note: This was moved above to a place much closer to the associated rule.]

Replace 13.9.2(0):

Replace title “The Valid Attribute” with “Validity Attributes”.

Modify 13.9.2(1):

The Valid attribute can be used to check the validity of data produced by unchecked conversion, input, interface to foreign languages, and the like. {The Valid_Scalars attribute can be used to test the conjunction of the Valid attributes of all scalar parts of an object.}

Modify 13.9.2(12/6) [as updated by AI22-0115-1]:

Since evaluating X'Valid does not evaluate X unless X is normal and valid, it is not an error to check the validity of {a scalar}[an] object that is invalid or abnormal. Determining whether X satisfies the predicates of its nominal subtype can include an evaluation of X, but this determination is made only after it has been determined that X has a valid representation and therefore is safe to evaluate.

[Author’s note: This change is just to avoid any confusion between this note (which is about 'Valid) and the usage of 'Valid_Scalars.]

Add after 13.9.2(14/6):

For a prefix X that denotes an object of any type other than a class-wide type or formal private or derived type with unknown discriminants, the following attribute is defined:

      X'Valid_Scalars

If X is a scalar object then yields the result of evaluating X'Valid. Otherwise, yields True if and only if the result of evaluating C'Valid_Scalars for each component C of X yields True, and then, if all of the evaluations yield True, the value of X also satisfies the predicates of the nominal subtype of X. The value of this attribute is of the predefined type Boolean.

AARM Discussion: Roughly speaking, X'Valid_Scalars yields True if the Valid attribute of every scalar part of X yields True and the predicates of X are satisfied. In particular, if X has no scalar parts and no predicates then X'Valid_Scalars yields True. This rough description ignores predicates of subcomponents as well as some rules given below about short-circuiting, about order of evaluation, and about interactions with tagged types.

If X is of a specific tagged type T, then only components that are components of T are tested [Redundant: , regardless of the value of T'Class(X)'Tag].

AARM Ramification: If T’Class(X)’Tag /= T’Tag, any extension components of the actual underlying type are not tested.

If X is of a protected type, then evaluation of X'Valid_Scalars proceeds as for a call to a non-exclusive protected function, beginning by starting a protected action on X for concurrent read-only access and ending with completing that protected action.[a]

AARM Implementation Note: Additional implementation-defined checks can be performed on tags, gaps, or access, task, or implementation-defined subcomponents (see 13.5.1), so that evaluation may yield a result of False even if the semantics would appear to require a result of True. This is allowed as any object with any of these things corrupt would either have to have been created or modified by previous erroneous execution, or be an object which is abnormal (such that evaluation of the attribute necessarily causes erroneous execution). Since erroneous execution allows any result, an implementation can provide a friendly result.

In order to provide a more friendly result, it is recommended that for a discriminated object X that the discriminant components of X are tested before non-discriminant components. Additionally, if D'Valid_Scalars yields False for some discriminant component D of X, then it is recommended that non-discriminant components of X are not tested. End AARM Implementation Note.

Notwithstanding what this Reference Manual says elsewhere, if X has a scalar type, evaluating X'Valid_Scalars does not evaluate the prefix X unless X is normal and has a valid representation. If X is Volatile, evaluating X'Valid_Scalars is still considered a read of X.

Note 3: If the prefix X is abnormal and does not have a scalar type, then evaluating the prefix of the attribute Valid_Scalars causes erroneous execution. However, the intended use of checking objects immediately after inputting or importing them is safe if the prefix X has an all-scalar composite type, as objects of such types cannot become abnormal from inputting or importing them.

Modify AARM 13.9.2(14.b):

AARM Reason: Valid is defined only for scalar objects because the implementation and description burden would be too high for other types. For example, given a typical run-time model, it is impossible to check the validity of an access value. The same applies to composite types implemented with internal pointers. One can {use Valid_Scalars to check the validity of any scalar parts of an object, but this is only complete for objects of scalar types and all-scalar composite types (see above and 13.9.1)}[check the validity of a composite object by checking the validity of each of its scalar subcomponents. The user should ensure that any composite types that need to be checked for validity are represented in a way that does not involve implementation-defined components, or gaps between components. Furthermore, such types should not contain access subcomponents].

Add after B.3.3(26/2):

!discussion

This attribute has to be allowed on (almost) all types, not just those for which it makes sense. The definition of the attribute is purely dynamic, as is the definition of all-scalar composite subtype and abnormal object. These definitions do not depend on visibility. On the other hand, Legality Rules do depend on visibility. If we were to try to disallow the attribute for anything that does not have an all-scalar composite type, we would have to disallow it on any private types and any generic formal derived types, as we do not know if the completion (or actual for a formal type) has the property. This “assume-the-worst” requirement would severely limit the utility of the attribute, and also would prevent implementations from making “friendly” implementations that work properly in many cases that are not portably defined.


 

We do not allow the attribute on objects of types that might be class-wide. That is just generic formal private and derived types with unknown discriminants as well as class-wide types themselves. We do not need to include any partial views with unknown discriminants as the full type could not be class-wide (it is illegal to derive from class-wide types).

The reason we do not allow the attribute on class-wide types is that an appropriate implementation would be expensive. For a class-wide type, the operation needs to include all of the components of the actual object, not just those that are statically defined for the type. This is a dynamically-defined attribute, and for such operations (such as finalization), all components participate in operations whether or not they are known at the point of the usage. Moreover, all components participate in operations, and it would be problematic if they were invalid after a check.

For instance, class-wide tagged objects can be input from a stream using the attribute Input’Class. Just checking the root type’s components would mean that the attribute would be nearly useless for checking such objects (for many hierarchies, the root is abstract with few components), and a later dispatching call could be using invalid components even after a check.

This contrasts with the implementation for specific tagged types. In that case, extension components (if any) are ignored. This is consistent with the rest of the language; operations on an object of a specific tagged type operate as if there are no extension components even when they are inherited for an extension.

To implement a proper implementation of class-wide Valid_Scalars would require a dispatching operation to implement it associated with every tagged type (whether or not the attribute is used). For implementations that cannot remove unused dispatching operations from the program image[d], this could add a substantial size overhead.

It seems safer to ban the use of the attribute in class-wide cases, rather than allowing a misleading implementation of the attribute. A user can build a dispatching function that returns an appropriate Valid_Scalars result if they need the full capability, and it is best that it is obvious that they need to do this if a class-wide test is needed.

Note that this ban will not work if class-wide components are allowed in some future version of Ada. In that case, the attribute should be allowed on all types, and implementations should be required to implement the dispatching version of the operation.

Note that this attribute could usefully be used for tagged values that are streamed in. In the case of streaming, the actual tags are never read from the external data. Rather, the stream attributes create the tags. Specifically, stream attributes create the tags for any component of a (specific) tagged type; these are never included in the stream data. Class-wide tagged components are not allowed by the language. For an object as a whole, objects of a specific tagged type are handled similarly to components. Objects of a class-wide tagged type can be created using Input'Class; it can read the tag from the stream, but it is in the form of an external tag string, which is looked up, and the actual tag is that which was looked up. If the external tag is invalid, the lookup will fail and an exception will be raised; thus no bad tag can be created by this operation.

This is unlike tagged objects created from other sources, where the tag is likely to be included in the external data (and thus it can also be bad).

Thus, one could imagine an extension to all-scalar composite types that also specified that objects created/modified by stream attributes never become abnormal. This would allow some use of tagged objects and class-wide objects to be included in the “safe harbor” where testing Valid_Scalars can never itself cause erroneous execution. We didn’t do this because of concerns about how tags for interfaces and nested extensions might be implemented, and because it would never be completely safe to test a class-wide object (some unknown, possibly future extension could contain components not allowed in an all-scalar composite type).


 

An Unchecked_Union type omits the discriminant(s) while still having discriminant-dependent components. Determining which component to check requires reading the non-existent discriminant. The usual result when doing that is that Program_Error is raised.

The rule follows other Unchecked_Union rules in only applying to parts that lack inferable discriminants. The exceptions do not apply in this case, so this just boils down to parts that are unconstrained unchecked union subtypes. If the subtype is constrained, the discriminant can be retrieved from the constraint.

It’s unclear whether this additional complication is worthwhile. Besides the fact that constrained unchecked union types are rare, an Unchecked_Union type can never be an all-scalar composite type (since it has discriminant-dependent components). Thus, testing a bad value of a Unchecked_Union type necessarily causes erroneous execution, regardless of whether the subtype is constrained. We keep it only for consistency with other rules of the language.


 

We depend upon the fact that objects that are imported, passed back or returned from imported subprograms, returned from an instance of Unchecked_Conversion, or input from a stream or other I/O routine (see 13.9.1) are abnormal if they have a bad representation of any sort, unless the type are such that there can never be anything other than possibly invalid scalar parts in any object of the type.

If the object X can have any bad tag, bad implementation-defined components, or even bad padding bits, then X can be abnormal, and evaluation of X’Valid_Scalars causes erroneous execution if X is abnormal. This is necessary as it may not be possible to enumerate the components of X if a discriminant, an array bound, a run-time tag, or some other implementation-defined component does not correspond to the expected definition of the type of X. It does mean that the result of Valid_Scalars is only well-defined for types that are all-scalar composite types. For other types, a bad object necessarily causes erroneous execution, and the result can be either True or False in such a case (even if no other problems occur). For instance, Valid_Scalars may not check any padding bits, and therefore even if it returns True, if padding bits have an unexpected value, future operations (like equality) may not operate as expected.


 

Principles of this solution

Various operations can create new objects from untyped data, which often originates outside of the Ada program. These operations include Unchecked_Conversion, streaming Read and Input operations, Sequential_IO, and more. These operations take essentially a bunch of unknown bits

and cast them as an object of some Ada data type.

When that happens, the object of the Ada data type might not represent a properly constructed object of the Ada data type. We have various tools to describe what happens in that case; in particular, invalid representations and abnormal objects.

Invalid representations have a limited set of effects that can happen when they are used. In particular, using an invalid representation cannot directly cause erroneous execution. On the other hand, any use of an abnormal object (other than assigning a normal value to it) causes erroneous execution. The only purpose of the abnormal object is to allow recovery if the problem is detected early enough.

Abnormal representations are used in two cases. First, if an assignment is disrupted and has not necessarily written the entire object (as in an abort or failure of a language-defined check). In this case, we want the object to be abnormal as it might not meet the usual requirements for an object of the type. Here, abnormality allows a compiler to ignore the possibility of a disrupted assignment when generating code (since any problem necessarily causes erroneous execution, so it is no longer necessary to get a particular result). Second, objects that are newly created from untyped data and have problems with representation such that the implementation would do bad things should be abnormal. For instance, if a newly created object has a pointer to a subcomponent, and that pointer is incorrect, all manner of problems could occur (such as overwriting someone else's data).

A scalar object is just a bag of bits with no internal structure. As such, it can have a representation that is valid or invalid, but that cannot cause any problems beyond those already allowed for invalid objects. Thus, we do not want a newly created scalar object to ever be treated as abnormal (it can be invalid).

Composite objects can have complex representations or relatively simple ones. In order for it to be possible for programs to be able to safely read binary data from external sources, we need to define a "safe harbor" of data types that will not become abnormal regardless of what bits are used to construct them. It should be clear that a record type containing two scalar components and no padding cannot cause a problem, as each scalar component will be either valid or invalid for any possible set of bits, and there is nothing else in the record to cause problems. It should be obvious that we can extend this to any type that simply contains scalar subcomponents. We therefore define an "all-scalar component subtype" to be one that cannot include anything other than invalid scalar components. Such a type can never cause anything truly bad.

This means that the only cases that are abnormal are ones where the objects might have something in them that could cause incorrect results when used. Thus, we don’t need to do anything special for Valid_Scalars for the composite case: if the prefix is abnormal, then the attribute might do something weird (execution is erroneous as soon as the prefix is evaluated). But for the “safe” case, the prefix can never be abnormal, and the attribute has to operate as expected. This definition is portable, so programmers can depend on it to work on any Ada compiler.


 

We have chosen to ban implementation-defined components from all-scalar composite types. We need such types to only contain the components specified by the relevant type declarations (otherwise, a bad implementation-defined component could cause problems, and certainly would cause the definition of all-scalar composite types to be implementation-dependent). In theory, this could cause problems for an implementation that used such components. However, such components are very unlikely to be used in all-scalar composite types, as their presence would interfere with any representations specified by representation clauses and aspects. As such, we don’t think this change will have any material effect.

Similarly, an implementation could be depending somehow on input or import of all-scalar composite objects being abnormal if badly formatted. Since touching such an object causes erroneous execution, compilers could simply ignore the possibility of them existing. With the new definition, it is necessary to handle the possibility of invalid components without depending upon erroneous execution. However, that possibility exists in other ways (particularly for uninitialized scalar components), so it doesn’t seem possible to completely ignore the possibility. As such, the effects should be limited. This change should make Ada programs more predictable, as well as making the attribute Valid_Scalars well-defined.

!example

package Example is
   type Rec is record

      A, B : Positive;

   end record;
   Obj : Rec;

   function Obj_Is_OK return Boolean is (Obj’Valid_Scalars);

   -- Roughly speaking, returns Obj.A’Valid and Obj.B’Valid

end Example;

!ACATS test

An ACATS C-Test that tests the various cases mentioned in the wording would be appropriate.

I!appendix

This AI comes from issue #14 on ARG GitHub issue list.


 

From: Tucker Taft

Here is the description of the GNAT attribute with this same name:

4.74 Attribute Valid_Scalars

The 'Valid_Scalars attribute is intended to make it easier to check the validity of scalar subcomponents of composite objects. The attribute is defined for any prefix P which denotes an object. Prefix P can be any type except for tagged private or Unchecked_Union types. The value of the attribute is of type Boolean.

P'Valid_Scalars yields True if and only if the evaluation of C'Valid yields True for every scalar subcomponent C of P, or if P has no scalar subcomponents. Attribute 'Valid_Scalars is equivalent to attribute 'Valid for scalar types.

It is not specified in what order the subcomponents are checked, nor whether any more are checked after any one of them is determined to be invalid. If the prefix P is of a class-wide type T'Class (where T is the associated specific type), or if the prefix P is of a specific tagged type T, then only the subcomponents of T are checked; in other words, components of extensions of T are not checked even if T'Class (P)'Tag /= T'Tag.

The compiler will issue a warning if it can be determined at compile time that the prefix of the attribute has no scalar subcomponents.

Note: Valid_Scalars can generate a lot of code, especially in the case of a large variant record. If the attribute is called in many places in the same program applied to objects of the same type, it can reduce program size to write a function with a single use of the attribute, and then call that function from multiple places.


 

[a]I'm unsure of what GNAT does in this case, but it seems important to make it clear that Valid_Scalars does not break mutual exclusion for a protected object. This seems to be the only thing not associated with all-scalar composite types that would be testable, as it does not require a bad object in order to test (execution of the attribute does not cause erroneous execution on an object with no problems). OTOH, testing for exclusion failures is generally considered of low value, because even a proper test has a low likelihood of failure in any particular run.

[b]I couldn't decide if this part should be included or not. Including it is consistent with most of the other Unchecked_Union rules, but it adds complication. It seems bizarre that if the discriminant is specified, that we won't use it, but then the whole thing is likely to be erroneous anyway. I put it in because, as always, it is easier to delete unneeded wording than to add missing wording.

[c]Seems reasonable to require Program_Error only when the discriminant has to be read.  So I am fine with this proposed addition.

[d]Implementations really should be able to do this, as otherwise any object-oriented library will cause massive code bloat. For example, programs using the Claw windowing library are about 3 times larger when unused dispatching operations are not removed in Janus/Ada.

However, it isn't appropriate to try to convince implementers to do the right thing with an attribute in the Standard. One hopes that comes from their customers.