AI22-0131-2

!standard 3.3(0)                                    26-05-19  AI22-0131-2/05

!standard 6.1(30.1)

!standard 6.5(5.9)

!standard 8.5.1(6.1)

!standard 9.4(5)

!class Amendment 25-03-11

!status work item 25-03-11

!status received 25-03-11

!assigned author Tucker Taft

!submitter Tucker Taft

!priority Low

!difficulty Medium

!subject Part_Of aspect with aliased results

!summary

Provide a Part_Of aspect which can be associated with a function with an explicitly aliased result (see AI22-0075-1), or with a result type that is a reference type or an anonymous access type, 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 (logical) components.[a]

!issue

While developing the AI for explicitly aliased results (AI22-0075-1), the ability to return a 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. This applies both when using an explicitly aliased result, and when returning a result object of a reference type or of an anonymous access type.

For example:

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

function Glob_Access return access constant Glob_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 any change that might destroy the part of the Container that is being referenced. Similarly, if Glob_Access returns a reference to a part of a global object, we don't want that global object to be destroyed.

Should we consider providing a way to ensure that the enclosing object is not altered in a way that might destroy the referenced part of the enclosing object during the life of the result of the function call?[b][c] (Yes.)

!recommendation

As was suggested in AI22-0075-1, we propose to provide an aspect, now named Part_Of, which specifies that the result returned by a call on a function with an aliased result is to be considered a logical "part" of a global variable or formal parameter, and the global or actual corresponding to the formal should not be altered in a way that might destroy the part denoted by the aliased result.

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

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

 

This would mean that X shall not be updated in a way that might destroy the part denoted by the result of a call on Var_Indexing, while the result is still in use. Here are some of the restrictions this implies:

Although these restrictions might seem severe, one important use case for the explicitly aliased result is to immediately assign to the result, and the only conflicts to be considered for that are those occurring in the RHS of the assignment, and those might be considered unlikely, because it would be effectively an erroneous order-of-evaluation dependence for the RHS of an assignment to cause the LHS to no longer exist (per RM 3.7.2(4), RM 6.4.1(18/3), and 13.11.2(16/3)).  And any of those could be worked around in most cases by renaming the RHS first, and then doing the assignment from the renaming.

Another important use case is using the result as an operand in an expression of some sort.  This is no problem if the operations in the expression have no side effects of their own. If it is passed as a by-reference parameter to a user-defined subprogram, this is where many of the challenges will come from.

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 restricted 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 these restrictions with object renaming, while relaxing the restrictions on renaming of a discriminant-dependent part of an object.

type Disc(D : Boolean := False) is record
    case D is
        when False => B : Integer;
        when True  => Z : Float;
    end case;
end record;
X : Disc;
C : Obj renames X.B;

 

Such a renaming is normally illegal, but if we were to relax this rule, it would be permitted, but then certain operations on X would be restricted during the scope of C.

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 Part_Of aspect specified on the function, or alternatively, we could relax the rules relating to renaming, while enforcing the corresponding restrictions during the scope of the renaming.

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, enforcing the same restrictions during the scope of these names could work, though there seems no need to generalize the capability to this extent right away.

With this relatively limited set of restrictions, simply preventing the destruction of the (logical) part of the object named by Part_Of, we aren't introducing any new tasking issues, since it is already erroneous (or already detected by tasking conflict checks) when two tasks use the same object concurrently in conflicting ways.

!wording

Add after 3.10.2(10.2/5):

Add after 6.1(30.1):

For a function with an explicitly aliased result, or with a result type that is a reference type (see 4.1.5) or an anonymous access type, the following aspect may be specified:

Part_Of

This aspect shall be defined by a name that statically names a part of an object global to the function, or statically names a part of a formal parameter of the function.

When the associated function is called, the named part of the global object, or the part of the actual parameter associated with the named part of the formal parameter, is a restricted object, and is restricted during the scope of the result of the function call, which is defined as the text of the master construct associated with the master of the function call (see 3.10.2), other than that of the function call itself.

Within the scope of the result, only names that statically name a part of the function result, or that statically name objects that are not part of the restricted object, are unrestricted; all other names denote restricted views.

Within the scope of the result, a subprogram can be called only if the subprogram is declared outside the scope of the restricted 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 restricted object.

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

The following restrictions apply to a restricted object, within the scope of the result of the function call:

Modify 6.2(8):

Modify 6.5(5.9/5) from AI22-0075-1:

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 }the denoted view shall be a variable unless the result_profile has the reserved word constant{. Further:

Add after 9.4(5/1):

The subprogram_specification, if any, of a protected_operation_declaration shall not be a function_specification with an explicitly aliased result (see 6.1).

!discussion

We have disallowed protected functions from returning aliased results, as that would clearly create a likely data race.

We have proposed a conservative rule, relying on static naming to simplify the checks for overlap between a restricted object and other objects. In general, we treat objects other than the function result itself as restricted, unless there is no doubt they are not part of the restricted 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 restricted, so cannot be passed as out or in out parameters to a subprogram. On the other hand, we are not worrying about actions by other tasks, as those are not checked in Ada in general, and are considered erroneous if they conflict.  We are simply trying to be sure that the current task doesn't "destroy" the part of the object that is referenced by the result of the function.

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.  On the other hand, the run-time tampering-bit mechanism used by the language-defined containers is somewhat more flexible, which means for compatibility reasons we cannot substitute the Part_Of feature for tampering bits. However, for user-defined container abstractions, it is intended that the proposed Part_Of feature can provide equally safe compile-time checks, albeit with some limitations on usage.

Originally, the Reference routines in the generic Container packages did not have any tampering checks; 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 longer lived, due to being renamed or passed as a parameter. And thus the tampering checks were extended to cover uses of Reference as well. Note that for many uses of a Reference function (including the ones Part_Of would allow) 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 Part_Of => 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]Generalize this to handle functions returning a reference result -- accessibility level assumptions -- vectors more like arrays.

[b]Another goal would be to eliminate the need for the container itself to be declared aliased, even if the components need to be.  This aspect could perhaps take the place of making a formal explicitly aliased so the special checking at the call site and the return statement would be performed in the presence of either this aspect or the reserved word "aliased" on the formal.

Also worth checking whether "Part_Of" will conflict with the existing SPARK Part_Of aspect.

Finally, it seems we should require pass by reference if an object has an aliased subcomponent.  But I can't find that rule.  Either that or Part_Of should imply pass by reference if not elementary.

[c]I have added a change to require that a type with an aliased subcomponent be passed by reference.