AI22-0131-1

!standard 3.3(0)                                    25-09-30  AI22-0131-1/05

!standard 6.1(30.2)

!standard 8.5.1(6.1)

!class Amendment 25-03-11

!status No Action  10-0-0   25-12-11

!status work item 25-03-11

!status received 25-03-11

!assigned author Tucker Taft

!submitter Tucker Taft

!priority Low

!difficulty Medium

!subject Read_Only aspect with aliased results

!summary

Provide a Read_Only aspect which can be associated with a function with an explicitly aliased result (see AI22-0075-1), as well as object renaming for discriminant-dependent components,

to help ensure that an enclosing object doesn't get altered in a way that would disrupt the use of a longer-lived reference to one of its components.

!issue

While developing the AI for explicitly aliased results (AI22-0075-1), the ability to return a writable reference to a part of another object suggested the usefulness in having a way to restrict access to the enclosing object while the function's result is in use.

For example:

function Var_Indexing (X : aliased in out Container; Y : Index_Type)
   return aliased Elem_Type;

 

The result of a call on Var_Indexing provides a writable reference to part of the specified Container.  While this result is in use, we would like to prevent changes to the object referred to by X.

Should we consider providing a way to ensure that the enclosing object is not updated during the life of the result object? (Yes.)

!recommendation

As was suggested in AI22-0075-1, we propose to provide a Read_Only aspect, which specifies that an aliased formal parameter or a global variable is read only during the lifetime of the result object of a call on the function.

So for the above example, this would look like the following:

function Var_Indexing (X : aliased in out Container; Y : Index_Type)
   return aliased Elem_Type
   with Read_Only => X;

 

This would mean that X shall not be updated while the result object of a call on Var_Indexing is still in use.  Here are some of the restrictions this implies:

Although these restrictions might seem severe, the most important use cases for the explicitly aliased result is to immediately assign to the result, so the only conflicts to be considered are those occurring in the RHS of the assignment.  And any of those could be worked around in most cases by renaming the RHS first, and then doing the assignment from the renaming.

For compatibility reasons, we could not change the existing containers to use this new feature, because of the requirement to only pass the result to declared-pure subprograms, or subprograms with no way of accessing the read-only object.  But hopefully this would significantly simplify newly defined containers while still providing compile-time safety from unsafe tampering.

It would also be worth considering the use of this aspect with object renaming, as follows:

type Disc(D : Boolean := False) is record
   
case D of
       
when False => B : Integer;
       
when True  => Z : Float;
   
end case;
end record;
X :
Disc;
[a][b]C : Obj renames X.B
     
with Read_Only => X.D;

 

Such a renaming is normally illegal, but with this aspect, it would be permitted, with the effect of X.D (and anything that might alias with X.D) being treated as a constant throughout the scope of C, even though C itself is considered a variable.  Presumably the Read_Only aspect needs to statically name the read-only object.

These two cases are already linked in that, in AI22-0075-1, we have the following proposed rule:

If the function has an explicitly aliased result, the expression of the simple_return_statement  shall be a name that denotes an aliased object for which renaming is allowed (see 8.5.1), and …

Clearly, we would like to relax this rule if there is a Read_Only aspect specified on the function, which if applied to the renaming would allow the renaming to occur.

Note that there are other places where we use the phrase "for which renaming is allowed" as part of a restriction.  For example, in 5.5.2(6.1/6):

The iterator_name or iterable_name of an iterator_specification shall denote an object for which renaming is allowed (see 8.5.1)

Other places this requirement appears are for formal in-out objects and the prefix of a prefixed view used for a formal subprogram.  In all of these cases, allowing the specification of a Read_Only aspect might make sense, though there seems no need to generalize the capability to this extent right away.

In the presence of tasking, we would need to presume that any synchronization with another task during the scope of C would effectively "invoke" that task, and so that task must be known to not be able to update X.D.

!wording

Add after 6.1(31/2):

Static Semantics

For a function with an explicitly aliased result, the following aspect may be specified:

Read_Only

This aspect shall be defined by a name that statically names a part of a variable global to the function, or statically names a part of a formal parameter of the function having mode out or in out.

When the associated function is called, the named part of the global variable, or the part of the actual parameter associated with the named part of the formal parameter, is a read-only object, and is considered to be a constant during the scope of the result of the function call, which is defined as follows:

Within the scope of the result, only names that statically name a variable part of the function result, or that statically name variables that are not part of the read-only object, denote variable views; all other names denote constant views.

Within the scope of the result, a subprogram can be called only if the subprogram is declared outside the scope of the read-only object, or has a global aspect (see 6.1.2) that is null or whose global variable sets associated with out or in out mode are each a set of statically named objects none of which are part of the read-only object.

If the Read_Only aspect definition names a part of a formal parameter, and the associated actual parameter is not statically named, then all objects other than variable parts of the result of the function call are considered to be constant during the scope of the result.

NOTE: Wording for allowing a Read_Only aspect specification on an object renaming is TBD, and probably better left to another AI.

!discussion

We have proposed a conservative rule, relying on static naming to simplify the checks for overlap between the read-only object and other objects.  In general, we treat objects other than the function result itself as constant, unless there is no doubt they are not part of the read-only object.

Note that the restriction on subprogram calls during the scope of the result is also quite conservative, and is further restricted by the fact that many objects are considered constants, so cannot be passed as out or in out parameters to a subprogram.

Some future version of the standard might allow more flexibility, but we propose a conservative rule for this initial definition of the aspect.

So-called "tampering bits" are a run-time mechanism that attempts to address this same problem, but relies on a type with an Implicit_Dereference aspect being controlled, and as such introduces significant complexity and potential run-time overhead. Some sort of relatively lightweight compile-time restriction (from the user perspective, at least) would seem preferable. Having to wait until run-time to find out that there is a problem is clearly not ideal.

Note that the Reference routines in the generic Container packages originally did not have any tampering checks; originally it was thought that the static accessibility check alone was enough to avoid any problems. But it turned out that there was a chance of erroneous execution when the result of a Reference was renamed or passed as a parameter. And thus the tampering checks were extended to cover uses of Reference as well.

For many uses of a Reference function the tampering check is certain to succeed, and compilers should go the extra mile to remove those checks when they are not needed.

!example

Where appropriate, the examples from AI22-0075-1 could be augmented using this aspect:

function Element(C : aliased in out Container; K : Key_Type)
  return aliased Element_Type
  with Read_Only => C;

 

But note that for the examples where we are simply returning a reference to a component of a global variable, there is no particular reason to restrict access to the global variable itself, unless the component is discriminant-dependent and the global is unconstrained (i.e. that the renaming rules would have made it illegal to return a reference to the component).

!ACATS test

This is largely a compile-time rule, so both B-tests and C-tests would be appropriate, both with cases that involve explicit calls on functions with explicitly aliased results, as well as implicit calls that arise from user-defined indexing and iteration.

!appendix

[a]Using an access-to-presumed-constrained would be compatible with this.

[b]It's almost like taking an access discriminant and "turning it inside out".