Uninitialized variable in case statement - delphi

I still can't figure out how to get rid of warnings about uninitialized variables whenever I use the following structure, even though I know that this can never happen.
TCustomEnum = (ceValue1, ceValue2, ceValue3);
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
end;
Result := 2 * lNumber;
end;
W1036 Variable 'lNumber' might not have been initialized
I found 3 solutions, but i don't like any of them. Especially with more variables or statements. Is there any other way how to avoid this?
Wrap function with {$WARN USE_BEFORE_DEF OFF} and {$WARN USE_BEFORE_DEF ON}
In every case statement use else Exit; with Result := 0 on the beginning
Initialize every variable although the value will be never used

By doing something like the following
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
else raise exception.create('Oops I forgot one of the LI_Enum values')
end;
Result := 2 * lNumber;
end;
Perhaps a better exception text or even not raising an exception at all (and assigning a different value to lNumber), but raising an exception does have the benefit of prompting you if, say, six months down the line you add a new case value.
Edit
The point really is that the compiler is correct. The underlying structure for an enum is some form of (unsigned) integer so it is perfectly possible for the enum to contain an illegal value, say 27, for example. There are lots of ways this can arise in practice. So you need to cater for that possibility if you are writing complete code. The compiler is just warning you that you have not catered for that possibility.

I find this compiler warning a little disappointing. After all, surely the compiler can detect that you have covered all possible values of the enumerated type. I don't believe that it should be worrying about you having put an invalid ordinal in the enumerated type, if indeed that is the thinking behind this warning.
In any case, I personally use the following helper methods to deal with this:
procedure RaiseAssertionFailed; overload;
procedure RaiseAssertionFailed(var v1); overload;
procedure RaiseAssertionFailed(var v1,v2); overload;
....
procedure DoRaiseAssertionFailed;
begin
raise EAssertionFailed.CreateFmt(
'A critical error has occurred:'+ sLineBreak + sLineBreak +
' Assertion failed at %p.'+ sLineBreak + sLineBreak +
'In order to avoid invalid results or data corruption please close the program and report '+
'the above error code along with any other information relating to this problem.',
[ReturnAddress]
) at ReturnAddress;
end;
procedure RaiseAssertionFailed;
asm
JMP DoRaiseAssertionFailed;
end;
procedure RaiseAssertionFailed(var v1);
asm
JMP DoRaiseAssertionFailed;
end;
procedure RaiseAssertionFailed(var v1,v2);
asm
JMP DoRaiseAssertionFailed;
end;
Your code would then become:
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
else
RaiseAssertionFailed(lNumber);
end;
Result := 2 * lNumber;
end;
This is very similar to the approach outlined by #Dsm. If you use that approach then compiler can see that you are raising an exception, and knows that lNumber does not need to be initialized.
I prefer though to wrap the raising of the exception into a shared function. That way I don't need to write the same error message again and again. An application of the DRY principle.
However, if you do this, and move the raise into a shared function, then the compiler is not capable of determining that the function will raise an exception. Hence the untyped var parameters. This allows you to mark the variable as being potentially modified and so suppress the compiler warning.
Yet another approach would be to declare an exception class that supplied the text in its parameterless constructor.
type
EInternalError = class(Exception)
public
constructor Create;
end;
constructor EInternalError.Create;
begin
inherited Create(
'...' // your text goes here
);
end;
Then your code becomes:
function DoSomething(LI_Enum: TCustomEnum): Integer;
var
lNumber : Integer;
begin
case LI_Enum of
ceValue1 : lNumber := 1;
ceValue2 : lNumber := 2;
ceValue3 : lNumber := 3;
else
raise EInternalError.Create;
end;
Result := 2 * lNumber;
end;

Related

Has function initialization code changed from Seattle to Tokyo?

I am in the process of upgrading code from Delphi 10 Seattle to Delphi 10.2 Tokyo and get a lot of H2077 hints Value assigned to ... never used on assignments.
(Even in places where these were explicitly added in the past to get rid of 'may not have a value' warnings).
These are all function initialized like:
Result := 0;
...
Or:
Result := ftType1; // where ftType1 is an enumerated type
...
Did the compiler get smarter in detecting these or has something changed regarding the initial return values of functions?
We have always had these hints 'on', and I always build (not compile).
Example function (1) that builds without hints in Seattle,
but gives the hint H2077 Value assigned to 'GetDatabaseDialect' not used on the first Result := 0 line in Tokyo.
function GetDatabaseDialect(DBName, User, Pswd: string) : integer;
var
status: array[1..19] of longint;
szDbName, szDbParam: PANSIChar;
dbHandle : pointer;
rslt: longint;
lDPBBuffer : ANSIString;
lDPBLength : integer;
cItem: ANSIChar;
szRslt: PANSIChar; //array[0..IBResultBufferSize-1] of ANSIChar;
begin
Result := 0;
dbHandle := nil;
// init database parameter block with version number
lDPBBuffer := '';
SetLength(lDPBBuffer, 1);
lDPBBuffer[1] := ANSIChar(isc_dpb_version1);
lDPBLength := 1;
// fill Database Parameter Buffer with user name/password
lDPBBuffer := lDPBBuffer +
ANSIChar(isc_dpb_user_name) +
ANSIChar(Length(User)) +
ANSIString( User );
Inc(lDPBLength, 2 + Length(User));
lDPBBuffer := lDPBBuffer +
ANSIChar(isc_dpb_password) +
ANSIChar(Length(Pswd)) +
ANSIString( Pswd );
Inc(lDPBLength, 2 + Length(Pswd));
//Pointers naar naam + buffer
szDbName := PANSIChar(ANSISTring(DBName));
szDbParam := PANSIChar( lDPBBuffer );
// attach to the database and set dialect
rslt := isc_attach_database(#status, 0, szDbName, #dbHandle, lDPBLength, szDbParam);
if rslt <> 0 then
raise EDatabaseError.Create('Error attaching database! ISC# ' + IntToStr(rslt));
//Haal sql dialect op
szRslt := AllocMem(1000);
try
FillChar( szRslt^, 1000, 0);
cItem := ANSIChar( isc_info_db_SQL_dialect );
rslt := isc_database_info(#status, #DBHandle, 1, #cItem, 1000, szRslt);
if rslt <> 0 then
raise EDatabaseError.Create('Error retrieving database info ! ISC# ' + IntToStr(rslt));
Result := Ord(szRslt[3]); //3e positie is dialect
finally
FreeMem(szRslt);
end;
// Drop the connection to the database
rslt := isc_detach_database(#status, #dbHandle);
if rslt <> 0 then
raise EDatabaseError.Create('Error detaching database! ISC# ' + IntToStr(rslt));
end;
Example (2) from a third party library that does not seem to be optimized for Tokyo,
illustrating the case with enumerated types:
H2077 Value assigned to 'TppTemplate.StreamType' not used
Note that changing the assignment to Result := ftASCII; does not make the hint go away (my initial assumption that it was associated with the first enumeration value was incorrect).
type TppFormatType = (ftBinary, ftASCII);
function TppTemplate.StreamType(aStream: TStream): TppFormatType;
var
lSavePos: Integer;
begin
{save stream position}
lSavePos := aStream.Position;
Result := ftBinary;
try
ComputeOffsetFromStream(aStream);
aStream.Seek(FOffset, soBeginning);
if IsValidASCIISignature(aStream) then
Result := ftASCII
else if IsValidBinarySignature(aStream) then
Result := ftBinary
else
raise EInvalidTemplateError.Create(ppLoadStr(49));
finally
{restore stream position}
aStream.Seek(lSavePos, soBeginning);
end;
end; {function, StreamType}
The common denominator seems to be the Result assignments being in try/finally blocks.
Consider this code with a minimal reproduction of your scenario:
function Bar: Boolean;
begin
Result := Random<0.5;
end;
function Foo: Integer;
begin
Result := 0;
if Bar then
Result := 1
else
raise Exception.Create('');
end;
The compiler, even older versions, emits the following hint:
[dcc32 Hint]: H2077 Value assigned to 'Foo' never used
This is reasonable. The first assignment to Result is pointless and can be removed.
Now consider this variation:
function Foo: Integer;
begin
Result := 0;
try
if Bar then
Result := 1
else
raise Exception.Create('');
finally
end;
end;
Older versions of the compiler no longer emit the hint, but the latest version of the compiler does. This should be considered a compiler defect, for older versions. The two variants of Foo shown above are semantically identical. The compiler would be justified in generating identical code.
As you surmise, the assignment being inside the try/finally block is necessary to trigger the defect in previous versions.
We can conclude that the Embarcadero developers have fixed a defect in Tokyo. You can resolve the hints by removing the spurious initial assignments.
Of course, if your code is to be compiled by older versions of the compiler, as well as by new versions, then you are in a bind. With the code as it stands now, a hint is emitted by new versions of the compiler. Remove the initial assignment and a hint is emitted by old versions of the compiler.

Error: No overloaded versoin of 'IntToStr'

When compiling the following code:
procedure TMainWin.FormActivate(Sender: TObject);
var LineRaw : String;
LinesFile : TextFile;
i, i2 : integer;
tempChar : String;
CurTempCharPos : integer;
begin
AssignFile(LinesFile, 'Lines.txt');
Reset(LinesFile);
i := 0;
tempChar := '';
CurTempCharPos := 1;
while not EoF(LinesFile) do begin
i := i+1; //ticker
ReadLn(LinesFile, LineRaw);
for i2 := 0 to 4 do begin
tempChar := LineRaw[CurTempCharPos] + LineRaw[CurTempCharPos +1];
Lines[i,i2] := IntToStr(tempChar);
tempChar := '';
CurTempCharPos := CurTempCharPos + 3;
end;
end;
CloseFile(LinesFile);
end;
With Lines being defined in another form:
unit uGlobal;
interface
type
aLines = array[1..5] of integer;
aLinesFinal = array of aLines;
var
Lines : aLinesFinal;
implementation
end.
I get the following error: There is no overloaded version of 'IntToStr' that can be called with these arguments. The error points to the line:
Lines[i,i2] := IntToStr(tempChar);
Here is the declaration of tempChar:
tempChar : String;
It is a string. And here is the call that the compiler rejects:
Lines[i,i2] := IntToStr(tempChar);
The IntToStr function, which has various overloads, accepts integer input parameters and returns strings. You cannot pass a string to IntToStr. Perhaps you meant to write:
Lines[i,i2] := StrToInt(tempChar);
Some other comments:
I doesn't look like you initialised Lines. This means that whilst the code might compile, it will fail at runtime.
Since you declared aLines as array[1..5] of integer, the valid values for i2 are 1 to 5 inclusive. You use 0 to 4 inclusive. Again, that's going to bite at runtime.
You really should enable range checking as a matter of urgency, since when you start executing this code that setting will reveal the errors above, and no doubt more besides.
In my view tempChar is a poor name for something that can hold more than a single character.
As #TLama points out, OnActivate seems to be an unusual place to execute this code. This event will run multiple times. Perhaps you should be executing this code at start up. In any case, code like this should not be in an event handler and should be moved to a separate method which an event handler can call.

How can I make signaling NaNs easy to work with?

The IEEE754 standard defines two classes of NaN, the quiet NaN, QNaN, and the signaling NaN, SNaN. When an SNaN is loaded into a floating point register, an exception is raised by the floating point unit.
QNaN is available to Delphi code through the constant named NaN that is declared in Math. The definition of that constant is:
const
NaN = 0.0 / 0.0;
I would like to be able to use something similar to declare a constant that is a signaling NaN, but have not yet found a way to do that.
Naively you might write this code:
function SNaN: Double;
begin
PInt64(#Result)^ := $7FF7FFFFFFFFFFFF;//this bit pattern specifies an SNaN
end;
But the ABI for floating point return values means that the SNaN is loaded into a floating point register so that it can be returned. Naturally that leads to an exception which rather defeats the purpose.
So you are then led to writing code like this:
procedure SetToSNaN(out D: Double);
begin
PInt64(#D)^ := $7FF7FFFFFFFFFFFF;
end;
Now, this works, but it's very inconvenient. Suppose you need to pass an SNaN to another function. Ideally you would like to write:
Foo(SNaN)
but instead you have to do this:
var
SNaN: Double;
....
SetToSNaN(SNaN);
Foo(SNaN);
So, after the build-up, here's the question.
Is there any way to write x := SNaN and have the floating point variable x assigned a value that is a signaling NaN?
This declaration solves it at compile time:
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double absolute iNaN;
The compiler still treats the SNaN as a constant.
Trying to assign a value to SNaN will give a compile time error: E2064 Left side cannot be assigned to.
procedure DoSomething( var d : Double);
begin
d := 2.0;
end;
SNaN := 2.0; // <-- E2064 Left side cannot be assigned to
DoSomething( SNaN); // <--E2197 Constant object cannot be passed as var parameter
WriteLn(Math.IsNaN(SNaN)); // <-- Writes "true"
Should you have the compiler directive $WRITEABLECONSTS ON (or $J+), this could be turned off temporarily to ensure not altering SNaN.
{$IFOPT J+}
{$DEFINE UNDEFWRITEABLECONSTANTS}
{$J-}
{$ENDIF}
const
iNaN : UInt64 = $7FF7FFFFFFFFFFFF;
var
SNaN : Double ABSOLUTE iNaN;
{$IFDEF UNDEFWRITEABLECONSTANTS}
{$J+}
{$ENDIF}
Here's another workaround:
type
TFakeRecord = record
case Byte of
0: (SNaN: Double);
1: (i: Int64);
end;
const
IEEE754: TFakeRecord = ( i: $7FF7FFFFFFFFFFFF);
The debugger shows IEEE754.SNaN as +NAN, however when you access it you'll still get a floating point exception. A workaround for that could be:
type
ISet8087CW = interface
end;
TISet8087CW = class(TInterfacedObject, ISet8087CW)
protected
OldCW: Word;
public
constructor Create(const NewCW: Word);
destructor Destroy; override;
end;
TIEEE754 = record
case Byte of
0: (SNaN: Double);
1: (i: Int64);
end;
const
IEEE754: TIEEE754 = ( i: $7FF7FFFFFFFFFFFF);
{ TISet8087CW }
constructor TISet8087CW.Create(const NewCW: Word);
begin
OldCW := Get8087CW;
Set8087CW(NewCW);
inherited Create;
end;
destructor TISet8087CW.Destroy;
begin
Set8087CW(OldCW);
inherited;
end;
procedure TForm6.Button4Click(Sender: TObject);
var
CW: ISet8087CW;
begin
CW := TISet8087CW.Create($133F);
Memo1.Lines.Add(Format('SNaN: %f', [IEEE754.SNaN]));
end;
You can inline the function:
function SNaN: Double; inline;
begin
PInt64(#Result)^ := $7FF7FFFFFFFFFFFF;
end;
But it will depend on the optimization and compiler mood.
I've seen some functions not inlined, without any clear understanding from the context. I do not like either relying on inlining.
What I would better do, and which will work on all versions of Delphi, is to use a global variable:
var
SNaN: double;
Then set it in the initialization block of the unit:
const
SNaN64 = $7FF7FFFFFFFFFFFF;
initialization
PInt64(#SNaN)^ := SNaN64;
end.
Then you will be able to use SNaN just as a regular constant. That is, you can write code as expected:
var test: double;
...
test := SNaN;
In the IDE debugger, it will be shown as "test = +NAN", which is the expected result, I suppose.
Note that using this SNaN will raise an exception when it is read into the FPU stack (e.g. if test=0 then) so you have to check the value at binary level... this is the reason why I defined a SNaN64 constant, which will make very fast code by the way.
toto := SNaN;
if PInt64(#toto)^<>SNaN64 then // will run and work as expected
DoubleToString(toto);
if toto<>SNaN then // will raise an EInvalidOp at runtime
DoubleToString(toto);
You can change this behavior by changing the x87 exception register:
backup := Set8087CW($133F);
try
..
finally
Set8087CW(backup);
end;
I suppose this to be set globally for your program, in all extend of the code which will have to handle this SNaN constant.
I use a function:
Function UndefinedFloat : double
Begin
Result := Nan
End;
This then works
Var
MyFloat : double;
Begin
MyFloat := UndefinedFloat;
Here's a rather dirty way to do it, that results in very clean code for the consumer.
unit uSNaN;
interface
const
SNaN: Double=0.0;//SNaN value assigned during initialization
implementation
initialization
PInt64(#SNaN)^ := $7FF7FFFFFFFFFFFF;
end.
I was expecting the linker to put SNaN in a read-only segment of the executable but it appears not to do so. In any case, even if it did you could use VirtualProtect to get around that for the duration of the assignment.

How to obtain the line numbers of executable lines from DWScript context map or symbol table

I am writing an IDE to use with Delphi DWScript and now have a simple debuggable script. I now want to highlight the executable lines in my source (like the blue dots at the left of the Delphi source). Digging for examples / information I see that there is a program 'SymbolDictionary' where I can call 'FindSymbolUsage( suReference)' - this seems to give me positions of symbols 'being referred to', and I guess I could call this again with 'suImplementation' to get the lines where there is an assignment. This has made me realise though that I could do with understanding what the structure and purpose of the ContextMap and the SymbolDictionary actually are. Does anyone have an example of listing the executable line numbers of the script?
My fledgling code is reproduced below and is awaiting critical analysis :-)
Thanks
TExecutableLines = class( TObject )
constructor Create;
destructor Destroy; override;
PRIVATE
FLines : TBits;
function GetIsExecutable(ALineNumber: integer): boolean;
procedure SetIsExecutable(ALineNumber: integer; const Value: boolean);
PUBLIC
procedure Clear;
procedure Evaluate( AProgram : IdwsProgram; const AUnitName : string );
property IsExecutable[ALineNumber : integer] : boolean
read GetIsExecutable
write SetIsExecutable;
end;
{ TExecutableLines }
procedure TExecutableLines.Clear;
begin
FLines.Size := 0;
FLines.Size := 1024;
end;
constructor TExecutableLines.Create;
begin
inherited;
FLines := TBits.Create;
end;
destructor TExecutableLines.Destroy;
begin
FreeAndnil( FLines );
inherited;
end;
procedure TExecutableLines.Evaluate(AProgram: IdwsProgram; const AUnitName : string);
var
I : integer;
Pos : TSymbolPosition;
begin
Clear;
For I := 0 to AProgram.SymbolDictionary.Count-1 do
begin
Pos := AProgram.SymbolDictionary.FindSymbolPosList(
AProgram.SymbolDictionary[I].Symbol ).FindUsage( suReference);
if Pos <> nil then
If Pos.ScriptPos.IsMainModule then
IsExecutable[ Pos.ScriptPos.Line ] := True
else
if SameText( Pos.UnitName, AUnitName ) then
IsExecutable[ Pos.ScriptPos.Line ] := True
end;
end;
function TExecutableLines.GetIsExecutable(ALineNumber: integer): boolean;
begin
if ALineNumber = 0 then
raise Exception.Create('Lines numbers are 1..n' );
if ALineNumber < FLines.Size then
Result := FLines[ALineNumber]
else
Result := False;
end;
procedure TExecutableLines.SetIsExecutable(ALineNumber: integer;
const Value: boolean);
begin
if ALineNumber = 0 then
raise Exception.Create('Lines numbers are 1..n' );
if ALineNumber >= FLines.Size then
FLines.Size := ALineNumber+1;
FLines[ALineNumber] := Value;
end;
The TdwsSymbolDictionary serves a different purpose, mostly knowing where (if) a symbol is declared or used, as well as easing up things like rename refactoring (see http://delphitools.info/2011/02/19/spotlight-on-tsymboldictionary/).
The TdwsSourceContextMap serves to know where in the source code, code "blocks" are (f.i. where a class declaration starts and ends, where function implementation starts and ends, etc.), it's both useful to "jump" to a position in code, or to know where the cursor is in terms of symbols.
What you're after is yet another info, it's what lines correspond to compiled expressions. For that you need to look at what is compiled, TExprBase.SubExpr/SubExprCount are your workhorses, or the utility function that wraps them RecursiveEnumerateSubExprs. With that function you can look at all the expressions in your program, start from TdwsProgram.Expr and TdwsProgram.InitExpr (you can cast the IdwsProgram to a TdwsProgram to get at those properties).
These are where you can have breakpoints.
As an illustration, suppose you have
1. function Dummy : Integer;
2. var i : Integer;
3. begin
4. while i<10 do begin
5. Inc(i);
6. if i>5 then
7. break;
8. end;
9. end;
Then if I'm not mistaken (doing this out of the blue):
The symbol dictionary will list a declaration of "Dummy" at 1, a
usage of "Integer" at 1 & 2, a declaration of "i" at 2, a usage of "i" at
4, 5 & 6.
The context map will have a block for the function, the main block
and the while loop.
The lines with compiled expressions will be 2 (.InitExpr) and 4, 5,
6 & 7 (.Expr)

Loosen "Local procedure/function assigned to procedure variable" restriction gracefully

Consider the following test-case:
{ CompilerVersion = 21 }
procedure Global();
procedure Local();
begin
end;
type
TProcedure = procedure ();
var
Proc: TProcedure;
begin
Proc := Local; { E2094 Local procedure/function 'Local' assigned to procedure variable }
end;
At line 13 compiler emits message with ERROR level, prohibiting all of the cases of such local procedures usage. "Official" resolution is to promote Local symbol to the outer scope (ie: make it a sibling of Global) which would have negative impact on code "structuredness".
I'm seeking the way to circumvent it in most graceful manner, preferably causing compiler to emit WARNING level message.
Your best bet is to declare it as reference to procedure using the new anonymous methods feature and then you can keep everything nicely encapsulated.
type
TProc = reference to procedure;
procedure Outer;
var
Local: TProc;
begin
Local := procedure
begin
DoStuff;
end;
Local;
end;
This gets around the issues that Mason describes by capturing any variables local to the anonymous function.
Here's why you can't do it:
type
TProcedure = procedure ();
function Global(): TProcedure;
var
localint: integer;
procedure Local();
begin
localint := localint + 5;
end;
begin
result := Local;
end;
Local procedures have access to the outer routine's variable scope. Those variables are declared on the stack, though, and become invalid once the outer procedure returns.
However, if you're using CompilerVersion 21 (Delphi 2010), you've got anonymous methods available, which should be able to do what you're looking for; you just need a slightly different syntax.
If one really needs to use local procedures in D7 or earlier one could use this trick:
procedure GlobalProc;
var t,maxx:integer; itr,flag1,flag2:boolean; iterat10n:pointer;
//Local procs:
procedure iterat10n_01;begin {code #1 here} end;
procedure iterat10n_10;begin {code #2 here} end;
procedure iterat10n_11;begin {code #1+#2 here} end;
begin
//...
t:=ord(flag2)*$10 or ord(flag1);
if t=$11 then iterat10n:=#iterat10n_11
else if t=$10 then iterat10n:=#iterat10n_10
else if t=$01 then iterat10n:=#iterat10n_01
else iterat10n:=nil;
itr:=(iterat10n<>nil);
//...
for t:=1 to maxx do begin
//...
if(itr)then asm
push ebp;
call iterat10n;
pop ecx;
end;
//...
end;
//...
end;
However the problem is that adress-registers could differ on different machines - so it's needed to write some code using local proc call and look via breakpoint which registers are used there...
And yeah - in most real production cases this trick is just some kind of palliative.
For the records, my homebrewn closure:
{ this type looks "leaked" }
type TFunction = function (): Integer;
function MyFunction(): TFunction;
{$J+ move it outside the stack segment!}
const Answer: Integer = 42;
function Local(): Integer;
begin
Result := Answer;
{ just some side effect }
Answer := Answer + Answer div 2;
end;
begin
Result := #Local;
end;
procedure TForm1.FormClick(Sender: TObject);
var
Func: TFunction;
N: Integer;
begin
{ unfolded for clarity }
Func := MyFunction();
N := Func();
ShowMessageFmt('Answer: %d', [N]);
end;

Resources