On 04.07.2019 21:20, Jonas Maebe wrote:
On 03/07/2019 09:26, Ondrej Pokorny wrote:
On 02.07.2019 23:34, Jonas Maebe wrote:
Invalid data means undefined behaviour, always. "is" is not a special
case that is immune to this.
Don't you really see the need to handle invalid data with a /defined/
behavior?
My point is that is impossible to do so, so trying to do it in a way
that works in some/most cases, is much more dangerous than categorically
refusing to try to do it, as it creates a false sense of security.

For example:

{$mode objfpc}
type
   tc = class
     a: array[1..2000] of longint;
   end;

   tcc = class of tc;

   trec = record
     p: pointer;
   end;
   prec = ^trec;

var
   c: tc;
   p: prec;
   cc: tcc;
begin
   new(p);
   cc:=tc;
   p^.p:=pointer(cc);
   c:=tc(p);
   writeln(c is tc);
end.

This will print true, even though it's obviously bogus.

Enum and subrange types /can/ store invalid data and they /do/
store invalid data.
The same goes for classes, and by extension for any pointer type.

You have such a validity check for objects in FPC on the compiler side: there are compiler intrinsics fpc_check_object & fpc_check_object_ext that can be enabled with -Cr and -CR. So actually you do try to check the validity of objects.

But I see that this is an internal feature of FPC that is not to be executed manually and there is no assurance of correctness - it is only a helper feature for debugging.

With this said, I do accept that it is a no-go to check a valid enumeration value against its own type in the code (MyEnum is TMyEnum). We are left with a check of a simple value (ordinal, enumeration, subrange) against a different enumeration/subrange type:

(MyInt is TMyEnum)
(MyEnum2 is TMyEnum)
etc. as a shorthand for ((Ord(Low(TMyEnum)) <= MyInt) and (MyInt <= Ord(High(TMyEnum))).

This is still better than nothing.

+++++++

BUT (big BUT): I suggest to do an enumeration/subrange validity check on the compiler side when range checking is used. This is similar to the current fpc_check_object intrinsic.

When range checking is used, this check should be injected (at least) before every case-of and the new is/as operators if they are replaced with true. When range checking is disabled, the check should not be injected at all. Examples for range checking:

1.) case MyEnumValue of // <<< here an enumeration validity check should be injected 2.) IsValid := MyEnumValue is TEnumValue; //  <<< replace with TRUE but still insert a validity check intrinsic if range checking is used. If not replaced with true, do not insert validity check (so that false can be returned without a runtime error).

IMO such a solution is acceptable for both sides:
Your point: The clarity of the language will stay untouched because the new validity check will be only informative and used only for debugging without any assurance of correctness. (You see the difference: MyEnumValue is TEnumValue will raise an exception for an invalid value and not return false! False will be returned only for (MyInt is TEnumValue).) My point: We will get an information about invalid enum values (range check error) instead of "undefined behavior" when debugging - and we will be able to find code where invalid enum values can occur and fix the code where these values were assigned. Although this information may not be (theoretically) absolutely accurate (the same as fpc_check_object is not absolutely accurate), it will still be pretty accurate and it will be a big help during the debugging.

Ondrej

_______________________________________________
fpc-devel maillist  -  fpc-devel@lists.freepascal.org
https://lists.freepascal.org/cgi-bin/mailman/listinfo/fpc-devel

Reply via email to