comp.lang.ada
 help / color / mirror / Atom feed
From: dog.ee.lbl.gov!overload.lbl.gov!agate!howland.reston.ans.net!noc.near.net !inmet!spock!stt@ucbvax.Berkeley.EDU  (Tucker Taft)
Subject: Re: Unchecked_Conversion question
Date: 2 Sep 93 15:43:57 GMT	[thread overview]
Message-ID: <CCqH1A.2y7@inmet.camb.inmet.com> (raw)

In article <1993Sep2.133859.26958@Rapnet.Sanders.Lockheed.Com> 
  lvonrude@Rapnet.Sanders.Lockheed.Com (Lowell S. VonRuden x5294) writes:

>In article <CCop4H.Hz8@irvine.com> 
>  adam@irvine.com (Adam Beneschan) writes:

>>In article <1993Sep1.154715.10498@Rapnet.Sanders.Lockheed.Com> 
>> lvonrude@Rapnet.Sanders.Lockheed.Com (Lowell S. VonRuden x5294) writes:

>>>   I am doing something that seems to work using a Verdix compiler, but I
>>>   have not been able to determine if this is something that will be safely
>>>   transportable.  Hopefully, someone here can tell me.
>>>
>>>   I have a 32 bit value coming in from an external interface as an
>>>   integer, which I am mapping to an enumeration type.  The enumeration
>>>   type has representation clauses for both size (Integer'Size) and
>>>   implementation values.  Assigning the result of an unchecked conversion
>>>   from the integer to an object of the enumeration type doesn't raise any
>>>   exception if the integer is out of range for the enumeration type
>>>   representation.  I found that if I do an explicit conversion of the
>>>   enumeration object to its type, then the range gets checked.

In my view, it is generally unwise to rely on type conversions (checked
or unchecked) to perform data validation.  It is easy enough
(if a bit tedious) to perform data validation yourself.  

Because this has been a common problem, we have proposed a standard
object attribute in Ada 9X called "Valid" which checks whether
the current contents of an object corresponds to the representation
for a valid value of its subtype.

Hence, after "E := Unchecked_Convert(Int);" one will be able
to write:
   if not E'Valid then 
      <complain appropriately>
   else
      <use E with confidence>
   end if;

(Note that the 'Valid attribute is only proposed for scalar types,
since checking "validity" for access types and composite types
was felt to be too difficult to define in any semantically formal way.)

>>>   procedure Sample (Int : Integer) is
>>>
>>>     type Enum is (AAA, BBB, CCC, DDD);
>>>     for  Enum use (AAA=> 1,
>>>                    BBB=> 2,
>>>                    CCC=> 13,
>>>                    DDD=> 14);
>>>     for  Enum'Size use Integer'Size;
>>>
>>>     function Convert is new Unchecked_Conversion (Source => Integer,
>>>                                                   Target => Enum);
>>>   begin
>>>
>>>     E := Convert (Int);  -- no exception raised here if Int is out of range
>>>
>>>     E := Enum (Convert (Int));  -- this does raise constraint error if
>>>                                 -- Int is out of range
>>>
>>>   end Sample;
>>>
>>>
>>>   So, is this a dependable thing to do?
>>
>>I don't think so....

I agree with this assessment

>That's what I'm concerned about.  The Verdix compiler I'm using
>generates the check for the seemingly unnecessary conversion both with
>and without the optimizer turned on, but I still feel funny trusting
>this in all situations.
>
>I saw someone else's attempt at trying to catch bad values for X.  They
>passed the Enum resulting from the unchecked conversion into a procedure,
>which did some unrelated thing, but they had a local block with an exception
>handler for Constraint_Error surrounding the call.  This would assume
>that the constraints of the Enum type would always be checked when the
>object is passed to another procedure.
>
>  E := Convert (Int);
>
>  begin
>    Do_Something_Unrelated (E);
>  exception
>    when Constraint_Error =>
>      -- assume Int must not have been a valid Enum representation
>  end;
>
>Would this be a safe assumption?

If you are going to all this trouble, you might be better off
checking the validity of the value before you convert it from an
integer type.  There are various ways to do this, depending
on your time/space tradeoffs.  You might want to start by
defining the enumeration representation using named numbers, e.g:

    type Enum is (AAA, BBB, CCC, DDD);
    AAA_Rep : constant := 1;
    BBB_Rep : constant := 2;
    CCC_Rep : constant := 13;
    DDD_Rep : constant := 14;
    for Enum use (AAA=>AAA_Rep, BBB=>BBB_Rep, CCC=>CCC_Rep, DDD=>DDD_Rep);

    Enum_Min_Rep : constant := AAA_Rep;
    Enum_Max_Rep : constant := DDD_Rep;

Given the above, you can now pretty easily
write whatever kind of validation routine you want.
For example, you could create a boolean array:

    type Validity_Array is array(Enum_Min_Rep .. Enum_Max_Rep) of Boolean;

    Is_Valid : constant Validity_Array := 
      Validity_Array'(AAA_Rep|BBB_Rep|CCC_Rep|DDD_Rep => True, 
         others => False);

    function Is_Valid_Rep(Rep : Integer) return Boolean;
    pragma Inline(Is_Valid_Rep);

    function Is_Valid_Rep(Rep : Integer) return Boolean is
    begin
       return Rep in Is_Valid'Range and then Is_Valid(Rep);
    end Is_Valid_Rep;

My general rule would be to do things the obvious, explicit way, rather
than try to use features of the language in a way that is not guaranteed
to work, with a hope that the compiler will figure out what you mean,
and "do the right thing."

Of course, as indicated above, the obvious, explicit way is pretty
tedious if you have to do this for every enumeration type, which
is one reason why we have proposed the 'Valid attribute.
But if you only have one or two such enumeration types in you
program, you would be wise, at least until "'Valid" shows up in 
your local Ada compiler, to use an explicit, if tedious, validity 
test before any conversions.

P.S. My own view of enumeration representation clauses is that
they should be used only when really necessary.  The problem is that
the attributes 'Pos, 'Val, 'Succ, 'Pred, 'Image, and 'Value,
for-loops over the enumeration type, array indexing with the enumeration
type, etc. have to do some hidden (and potentially inefficient) stuff to work 
with "holey" enumeration types.

Anytime there is a simple-looking feature with hidden
implementation overhead, one should be careful in use of the feature.
In most applications I have seen, the only reason for
a "holey" enumeration type was that there was just no
name for some of the intermediate values; there was generally
no requirement to be able to iterate over the named enumeration
values, automatically skipping the unnamed values (the automatic
"skipping" is what makes for-loops a pain).  
For example, one might be tempted to define EBCDIC as a "holey"
enumeration type, since some of the 8-bit codes have no corresponding
graphic character.  However, this would be a big mistake (the reason
is left as an exercise for the reader).

Given the above, it is often more straightforward to either 
specify "fillers" for the gaps in the enumeration type (that's
essentially what is done for the predefined enumeration type Character),
or stick with an integer type (perhaps visibly "private")
while providing named constants corresponding to the interesting 
values.  Which approach is best depends on how sparse is the type.  
Any of these approaches makes many things more straightforward, 
including data validity tests, and eliminates the hidden 
baggage from 'Pos, 'Val, for-loops, array indexing, etc.

For example, in your case, the type is sparse enough that 
an integer type with named constants might be best:

    type Enum is range 1..14;
    AAA : constant Enum := 1;
    BBB : constant Enum := 2
    CCC : constant Enum := 13;
    DDD : constant Enum := 14;

Note that because Ada has strong type checking for user-defined
integer types (unlike C or C++), there are no implicit conversions
from other named integer types to Enum, so you get some amount
of protection.  If you want to enforce further privacy, then 
you could put the above in a private part, and make only the 
following visible:

    type Enum is private;
    AAA : constant Enum;
    BBB : constant Enum;
    CCC : constant Enum;
    DDD : constant Enum;

    function Convert(Rep : Integer) return Enum;
      -- raises Invalid_Rep if Integer not a valid value
    Invalid_Rep : exception;
private
    type Enum is range 1..14;
    ...etc... See above

So the bottom line might be, you should consider an alternative
to using "holey" enumeration types.

>-- Usual disclaimers apply...               Lowell Von Ruden      --
>-- lvonrude@rapnet.sanders.lockheed.com     Lockheed Sanders, Inc --

S. Tucker Taft   stt@inmet.com
Intermetrics, Inc.
Cambridge, MA  02138

             reply	other threads:[~1993-09-02 15:43 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
1993-09-02 15:43 dog.ee.lbl.gov!overload.lbl.gov!agate!howland.reston.ans.net!noc.near.net [this message]
  -- strict thread matches above, loose matches on Subject: below --
1993-09-04  0:13 Unchecked_Conversion question Robert Dewar
1993-09-03 21:55 Robert Parkhill
1993-09-03 20:14 Wes Groleau x1240 C73-8
1993-09-03 19:51 dog.ee.lbl.gov!agate!howland.reston.ans.net!spool.mu.edu!sdd.hp.com!netwo
1993-09-03 19:06 dog.ee.lbl.gov!agate!spool.mu.edu!umn.edu!email.sp.paramax.com!not-for-ma
1993-09-02 13:38 Lowell S. VonRuden x5294
1993-09-02  3:00 Robert Dewar
1993-09-01 16:43 dog.ee.lbl.gov!network.ucsd.edu!news.cerf.net!shrike.irvine.com!adam
1993-09-01 15:47 Lowell S. VonRuden x5294
replies disabled

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox