Forum > General

Range checking Enum types. Unreliable or confusing?

(1/3) > >>

dje:

I've been looking for a better (safer) way to cast integers to enum types. I thought I understood "range checking" but after some testing, it appears I don't. I've attached the source to the tests I've been trying.

So, lets say you have an enum type:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TEnum_All = (opt0, opt1, opt2, opt3, opt4);
And you then define a sub range of this type:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TEnum_1_3 = opt1..opt3;  
And, for completeness, lets define a type which includes all of the TEnum_All values:

--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---TEnum_0_4 = opt0..opt4;
When casting from an integer to one of my enum types, there are 3 possible casting situations, and 3 possible variable destinations. meaning 3x3 = 9 tests.


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program EnumRangeCheckTesting; {$mode objfpc}{$H+}{$R+} uses  SysUtils; type   TEnum_All = (opt0, opt1, opt2, opt3, opt4);  TEnum_1_3 = opt1..opt3;  TEnum_0_4 = opt0..opt4; var  IntValue: integer;  Enum_All: TEnum_All;  Enum_1_3: TEnum_1_3;  Enum_0_4: TEnum_0_4;   procedure Group1_1;  begin    Write('Enum_All := TEnum_All(IntValue)');    Enum_All := TEnum_All(IntValue);  end;   procedure Group1_2;  begin    Write('Enum_1_3 := TEnum_All(IntValue)');    Enum_1_3 := TEnum_All(IntValue);  end;   procedure Group1_3;  begin    Write('Enum_0_4 := TEnum_All(IntValue)');    Enum_0_4 := TEnum_All(IntValue);  end;   procedure Group2_1;  begin    Write('Enum_All := TEnum_1_3(IntValue)');    Enum_All := TEnum_1_3(IntValue);  end;   procedure Group2_2;  begin    Write('Enum_1_3 := TEnum_1_3(IntValue)');    Enum_1_3 := TEnum_1_3(IntValue);  end;   procedure Group2_3;  begin    Write('Enum_0_4 := TEnum_1_3(IntValue)');    Enum_0_4 := TEnum_1_3(IntValue);  end;   procedure Group3_1;  begin    Write('Enum_All := TEnum_0_4(IntValue)');    Enum_All := TEnum_0_4(IntValue);  end;   procedure Group3_2;  begin    Write('Enum_1_3 := TEnum_0_4(IntValue)');    Enum_1_3 := TEnum_0_4(IntValue);  end;   procedure Group3_3;  begin    Write('Enum_0_4 := TEnum_0_4(IntValue)');    Enum_0_4 := TEnum_0_4(IntValue);  end;   procedure TestProc(ATestProc: TProcedure);  begin    try      ATestProc;      WriteLn(' Success');    except      on E: Exception do WriteLn(' Failed: ', E.Message);    end;  end; begin  IntValue := 1234; (* Integer IntValue is outside all enum ranges *)   Writeln('Group 1');  TestProc(@Group1_1);  TestProc(@Group1_2);  TestProc(@Group1_3);  WriteLn;   Writeln('Group 2');  TestProc(@Group2_1);  TestProc(@Group2_2);  TestProc(@Group2_3);  WriteLn;   Writeln('Group 3');  TestProc(@Group3_1);  TestProc(@Group3_2);  TestProc(@Group3_3);  WriteLn;   ReadLn;end.           

Running this with an integer value of 1234, I get:

Group 1
Enum_All := TEnum_All(IntValue) Success
Enum_1_3 := TEnum_All(IntValue) Failed: Range check error
Enum_0_4 := TEnum_All(IntValue) Failed: Range check error

Group 2
Enum_All := TEnum_1_3(IntValue) Failed: Range check error
Enum_1_3 := TEnum_1_3(IntValue) Success
Enum_0_4 := TEnum_1_3(IntValue) Failed: Range check error

Group 3
Enum_All := TEnum_0_4(IntValue) Failed: Range check error
Enum_1_3 := TEnum_0_4(IntValue) Failed: Range check error
Enum_0_4 := TEnum_0_4(IntValue) Success

Which means, the most common enum type casting doesn't raise a range check error (?)
Interestingly the case Enum_All := TEnum_0_4(IntValue) fails, while Enum_All := TEnum_All(IntValue) succeeds (?)

I'm sure all of this is perfectly logical, but all I can deduce from my tests is, I cant rely of range checking to protect my code from illegal enum castings.

My main question is, how are other people handling enum type casting from external sources (ie: db tables, config files, etc)? I'm currently manually checking the integer is in range.

note: I have come across forum requests for language features to make "int to enum casting" safer and easier.

Sorry. I forgot, Im using Lazarus 2.0.10 (Raspberry Pi 400)

Thaddy:
Check my wiki entry:
https://wiki.freepascal.org/Defensive_programming_techniques
It explains to some length how you should handle this.

dje:

--- Quote from: Thaddy on July 30, 2022, 06:49:55 am ---Check my wiki entry:
https://wiki.freepascal.org/Defensive_programming_techniques
It explains to some length how you should handle this.

--- End quote ---

Thanks Thaddy for the reply. I use most of the techniques on that page. My post is part of a larger idea. I wanted to verify that range checking was not going to identify bugs before I proposed the following idea:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---generic function safe_enum<T>(AValue: integer): T;begin  if (AValue < Ord(Low(T))) or (AValue > Ord(High(T))) then begin    raise ERangeError.Create('safe_enum range check error');  end;  Result := T(AValue);end;    
The updated testing code is now:


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---program EnumRangeCheckTesting; {$mode objfpc}{$H+}{$R+} uses  SysUtils; generic function safe_enum<T>(AValue: integer): T;begin  if (AValue < Ord(Low(T))) or (AValue > Ord(High(T))) then begin    raise ERangeError.Create('safe_enum range check error');  end;  Result := T(AValue);end; type   TEnum_All = (opt0, opt1, opt2, opt3, opt4);  TEnum_1_3 = opt1..opt3;  TEnum_0_4 = opt0..opt4; var  IntValue: integer;  Enum_All: TEnum_All;  Enum_1_3: TEnum_1_3;  Enum_0_4: TEnum_0_4;   procedure Group1_1;  begin    Write('Enum_All := TEnum_All(IntValue)');    Enum_All := specialize safe_enum<TEnum_All>(IntValue);  end;   procedure Group1_2;  begin    Write('Enum_1_3 := TEnum_All(IntValue)');    Enum_1_3 := specialize safe_enum<TEnum_All>(IntValue);  end;   procedure Group1_3;  begin    Write('Enum_0_4 := TEnum_All(IntValue)');    Enum_0_4 := specialize safe_enum<TEnum_All>(IntValue);  end;   procedure Group2_1;  begin    Write('Enum_All := TEnum_1_3(IntValue)');    Enum_All := specialize safe_enum<TEnum_1_3>(IntValue);  end;   procedure Group2_2;  begin    Write('Enum_1_3 := TEnum_1_3(IntValue)');    Enum_1_3 := specialize safe_enum<TEnum_1_3>(IntValue);  end;   procedure Group2_3;  begin    Write('Enum_0_4 := TEnum_1_3(IntValue)');    Enum_0_4 := specialize safe_enum<TEnum_1_3>(IntValue);  end;   procedure Group3_1;  begin    Write('Enum_All := TEnum_0_4(IntValue)');    Enum_All := specialize safe_enum<TEnum_0_4>(IntValue);  end;   procedure Group3_2;  begin    Write('Enum_1_3 := TEnum_0_4(IntValue)');    Enum_1_3 := specialize safe_enum<TEnum_0_4>(IntValue);  end;   procedure Group3_3;  begin    Write('Enum_0_4 := TEnum_0_4(IntValue)');    Enum_0_4 := specialize safe_enum<TEnum_0_4>(IntValue);  end;   procedure TestProc(ATestProc: TProcedure);  begin    try      ATestProc;      WriteLn(' Success');    except      on E: Exception do WriteLn(' Failed: ', E.Message);    end;  end; begin  IntValue := 0; (* Integer IntValue is outside all enum ranges *)   Writeln('Group 1');  TestProc(@Group1_1);  TestProc(@Group1_2);  TestProc(@Group1_3);  WriteLn;   Writeln('Group 2');  TestProc(@Group2_1);  TestProc(@Group2_2);  TestProc(@Group2_3);  WriteLn;   Writeln('Group 3');  TestProc(@Group3_1);  TestProc(@Group3_2);  TestProc(@Group3_3);  WriteLn;   ReadLn;end.
For a IntValue of 0 the output is:

Group 1
Enum_All := TEnum_All(IntValue) Success
Enum_1_3 := TEnum_All(IntValue) Failed: Range check error
Enum_0_4 := TEnum_All(IntValue) Success

Group 2
Enum_All := TEnum_1_3(IntValue) Failed: safe_enum range check error
Enum_1_3 := TEnum_1_3(IntValue) Failed: safe_enum range check error
Enum_0_4 := TEnum_1_3(IntValue) Failed: safe_enum range check error

Group 3
Enum_All := TEnum_0_4(IntValue) Success
Enum_1_3 := TEnum_0_4(IntValue) Failed: Range check error
Enum_0_4 := TEnum_0_4(IntValue) Success

IntValue = 1, you get:

Group 1
Enum_All := TEnum_All(IntValue) Success
Enum_1_3 := TEnum_All(IntValue) Success
Enum_0_4 := TEnum_All(IntValue) Success

Group 2
Enum_All := TEnum_1_3(IntValue) Success
Enum_1_3 := TEnum_1_3(IntValue) Success
Enum_0_4 := TEnum_1_3(IntValue) Success

Group 3
Enum_All := TEnum_0_4(IntValue) Success
Enum_1_3 := TEnum_0_4(IntValue) Success
Enum_0_4 := TEnum_0_4(IntValue) Success

And, an IntValue of 10 outputs:

Group 1
Enum_All := TEnum_All(IntValue) Failed: safe_enum range check error
Enum_1_3 := TEnum_All(IntValue) Failed: safe_enum range check error
Enum_0_4 := TEnum_All(IntValue) Failed: safe_enum range check error

Group 2
Enum_All := TEnum_1_3(IntValue) Failed: safe_enum range check error
Enum_1_3 := TEnum_1_3(IntValue) Failed: safe_enum range check error
Enum_0_4 := TEnum_1_3(IntValue) Failed: safe_enum range check error

Group 3
Enum_All := TEnum_0_4(IntValue) Failed: safe_enum range check error
Enum_1_3 := TEnum_0_4(IntValue) Failed: safe_enum range check error
Enum_0_4 := TEnum_0_4(IntValue) Failed: safe_enum range check error

Which is what I "expected" from my initial tests. ie: all of the castings should fail with IntValue 1234.




dje:
So, my question is, does FreePascal or Delphi have a "safe_enum" style generic function hiding somewhere?


--- Code: Pascal  [+][-]window.onload = function(){var x1 = document.getElementById("main_content_section"); if (x1) { var x = document.getElementsByClassName("geshi");for (var i = 0; i < x.length; i++) { x[i].style.maxHeight='none'; x[i].style.height = Math.min(x[i].clientHeight+15,306)+'px'; x[i].style.resize = "vertical";}};} ---generic function safe_enum<T>(AValue: integer): T;begin  if (AValue < Ord(Low(T))) or (AValue > Ord(High(T))) then begin    raise ERangeError.Create('safe_enum range check error');  end;  Result := T(AValue);end;    

Thaddy:
Enums ARE safe. Your code is simply wrong. And the compiler tells you very friendly....
Enums count from zero...
Ask again if you do not see the issue....

Navigation

[0] Message Index

[#] Next page

Go to full version