!topic LSN on Exception Occurrences !key LSN-1082 on Exception Occurrences !reference RM9X-11.2;4.0 !from Bob Duff $Date: 94/02/11 15:10:11 $ $Revision: 1.5 $ !discussion Several issues related to exception occurrences were raised at the December DR/XRG meeting in the UK: - On some systems, it may be desirable to reduce the amount of memory used to store exception names. - Given the current semantics, some implementations may wish to make the size of an Exception_Occurrence big enough to store the maximum possible amount of information, which includes (at least) a 200-character message string. This is unfortunate for programs that do not use exception messages, or always use short ones. - Given the current semantics, if one wants to attach a message to an exception, one loses the ability to get a meaningful exception name, because all such exceptions are called "Exception_With_Message". We propose the following changes, which address the above issues, while supporting essentially the same functionality as in version 4.0: - Add a pragma that allows implementations to reduce the storage allocated for exception names, enumeration literal names, etc. - Make Exception_Occurrence limited, so the implementation has better control over copying. - Add an Identity attribute, so that the identity of an arbitrary exception can be determined. Here are the details. Delete the Exceptions.Messages package. Modify package Exceptions: package Ada.Exceptions is type Exception_Id is private; function Exception_Name(Id: Exception_Id) return String; type Exception_Occurrence is limited private; type Exception_Occurrence_Access is access all Exception_Occurrence; Null_Occurrence: constant Exception_Occurrence; procedure Save_Occurrence(Target: out Exception_Occurrence; Source: Exception_Occurrence); function Save_Occurrence(Source: Exception_Occurrence) return Exception_Occurrence_Access; function Exception_Identity(X: Exception_Occurrence) return Exception_Id; function Exception_Name(X: Exception_Occurrence) return String; -- Same as Exception_Name(Exception_Identity(X)). function Exception_Message(X: Exception_Occurrence) return String; function Exception_Information(X: Exception_Occurrence) return String; procedure Raise_Exception(E: Exception_Id; Message : String := ""); procedure Reraise_Occurrence(X: Exception_Occurrence); private ... -- @i{not specified by the language} end Ada.Exceptions; Add an attribute: For a prefix E denoting an exception, the following attribute is defined: E'Identity Returns the unique identity of the exception. The type of this attribute is Exception_Id. The permission to truncate the message to 200 characters should apply to the Save_Occurrence procedure (not the function), to the Reraise_Occurrence procedure, and to the re-raise statement. For implemention-defined information, the intent is that these same three cases can lose information. Of course, the identity of the exception and the ability to reraise must be preserved. In version 4.0, it was intended that assignment of an Exception_Occurrence could lose information. However, an information-losing assignment seems strange -- an information-losing Save_Occurrence procedure seems like a better idea. The Save_Occurrence function, on the other hand, does not lose information (at least as far as the language-defined information goes) -- it allocates a new object in the heap, and can therefore allocate as much as is necessary. Note that the Save_Information function is called only if the user asks for it -- there need not be any heap usage unless the user wants it (although some implementations may use the heap anyway if a message is present). Note that exceptions in Ada behave as if declared at library level; there is no "natural scope" for an exception; an exception always exists. Hence, there is no harm in saving an exception occurrence in a data structure, and reraising it later. The reraise has to occur as part of the same partition execution, so saving exception occurrences in a file and reading them back in from a different partition execution is not required to work. This is similar to I/O of access types. Here's one way to implement Exception_Occurrence in the private part of the package. Using this method, an implementation need only store the actual number of characters in exception messages. If the user always uses small messages, then exception occurrences can be small. If the user never uses messages, then exception occurrences can be smaller still: type Exception_Occurrence(Message_Length: Natural := 200) is limited record Id: Exception_Id; Message: String(1..Message_Length); end record; At the point where an exception is raised, an Exception_Occurrence can be allocated on the stack with exactly the right amount of space for the message -- none for an empty message. This is just like declaring a constrained object of the type: Temp: Exception_Occurrence(10); -- for a 10-character message After finding the appropriate handler, the stack can be cut back, and the Temp copied to the right place. This is similar to returning an unknown-sized object from a function. It is not necessary to allocate the maximum possible size for every Exception_Occurrence. If, however, the user declares an Exception_Occurrence object, the discriminant will be permanently set to 200. The Save_Occurrence procedure would then truncate the information. Thus, information is not lost until the user tries to save the occurrence. If the user is willing to pay the cost of heap allocation, the Save_Occurrence function can be used instead. Note that any arbitrary-sized implementation-defined information can be handled in a similar way. For example, if the Exception_Occurrence includes a stack traceback, a discriminant can control the number of stack frames stored. The traceback would be truncated or entirely deleted by Save_Occurrence -- as the implementation sees fit. Using the above method, heap space is never allocated unless the user calls the Save_Occurrence function. An alternative implementation would be to store the message strings on the heap when the exception is raised. (It could be the global heap, or it could be a special heap just for this purpose -- it doesn't matter.) This representation would be used only for choice parameters. For normal user-defined exception occurrences, the Save_Occurrence procedure would copy the message string into the occurrence itself, truncating as necessary. Thus, in this implementation, Exception_Occurrence would be implemented as a variant record: type Exception_Occurrence_Kind is (Normal, Special); type Exception_Occurrence(Kind: Exception_Occurrence_Kind := Normal) is limited record case Kind is when Normal => ... -- space for 200 characters when Special => ... -- pointer to heap string end case; end record; Exception_Occurrences created by the run-time system during exception raising would be Special. User-declared ones would be Normal -- the user cannot see the discriminant, and so cannot set it to Special. The strings in the heap would be freed upon completion of the handler. This alternative implementation corresponds to a heap-based implementation of functions returning unknown-sized results. One possible implementation of Reraise_Occurrence is as follows: procedure Reraise_Occurrence(X: Exception_Occurrence) is begin Raise_Exception(Identity(X), Exception_Message(X)); end Reraise_Occurrence; However, some implementations may wish to retain more information across a re-raise -- a stack traceback, for example. ---------------- The issue of memory used for exception names is essentially the same issue as (in Ada 83) for enumeration literal names, and (in Ada 9X) for tag names. Therefore, we propose a solution that applies to all three: pragma Discard_Names[([On => ] name)]; The name (if present) shall denote an enumeration type, tagged type, or exception. The pragma applies to the denoted entity. Without a name, the pragma applies to all entities declared after the pragma, within the same declarative region. Alternatively, the pragma can be used as a configuration pragma, in which case it can apply (for example) to all such entities in an entire program library, as explained in 10.1.5. With a name, the pragma is a representation pragma, so all the associated freezing-point rules and whatnot apply. If the pragma applies to a tagged type, then it applies also to all descendants of the type. If the pragma applies to an entity, then the run-time behavior of the function returning that entity's name (Tags.Expanded_Name, Exceptions.Exception_Name, or the Image attribute) is implementation-defined for that entity. Implementation Advice: If the pragma applies to an entity, then the implementation should reduce the amount of storage associated with storing the name of that entity. Here are some examples of reasonable behaviors that have been suggested: - Raise Program_Error. - Return an empty string. - Return a 8-character string that uniquely identifies the entity, but only if the user has an implementation-generated table mapping these 8-character strings to the actual entity name. The 8-character string might be a hexadecimal representation of an address associated with the entity, for example. - Ignore the pragma. This is the simplest implementation, and makes sense on systems that have plenty of memory. Any behavior is allowed, but the implementation has to document its choice. The implementation can give warning messages, if that is desired. ---------------- SUMMARY: The new proposal for exception occurrences has essentially the same capabilities as the old one. There need not be any distributed overhead -- memory space is used only if needed. Users are not stuck with the name "Exception_With_Message" for all of their exceptions that have associated messages. Assignment is not allowed for exception occurrences; instead, the Save_Occurrence procedure is allowed to lose information. The Discard_Names pragma can be used to reduce the cost associated with storing the names of various entities at run-time.