Delphi: always watch the compiler Warnings
Posted by jpluimers on 2014/08/28
Quiz questions:
- Does the below unit test succeed or fail?
- Why?
procedure TestHResult.Test_EOleException;
var
OleException: EOleException;
IResult: LongInt; // == HResult
UResult: Cardinal; // == DWord
begin
IResult := $800A11FD;
UResult := $800A11FD;
try
OleException := EOleException.Create('message', $800A11FD, 'source', 'helpfile', -1);
raise OleException;
except
on E: EOleException do
begin
if E.ErrorCode = $800A11FD then
Exit;
Self.CheckEquals(E.ErrorCode, $800A11FD, 'E.ErrorCode should equal $800A11FD');
end; // E: EOleException
end;
end;
I partially gave away the answers in the title of the title of the post.
But first some background information:
The above code is inspired by code I see in a lot of applications at clients: tons of compiler warnings (and even more hints).
That is not the way you should program your applications (be it Delphi or any other): always make them Warning free. And try to make them Hint free too.
This particular test came of an application that tried to do handling of EOleException using the ErrorCode field which is of type HResult which – like for instance the .NET ExternalException.ErrorCode – is a 32-bit signed Integer (the 32-bit unsigned data type is Cardinal).
HResult is the Delphi equivalent of the Windows (and in the past OS/2) HRESULT. Most documentation will give you error codes in hexadecimal, is it is much easier to formulate the bit fields in HRESULT using hexadecimal values than decimal ones.
Now for the Warnings and why they are caused.
The assignment of the hexadecimal literal value to IResult will give the first warning: the literal is larger than the High(Integer) value (which is the same MaxInt). It won’t fit in a 4-byte Integer, but the compiler – despite the warning – will make it fit. If you ask the debugger for the hexadecimal value of IResult, it will happily return $800A11FD.
The signed decimal equivalent that gets assigned to UResult is 2148143613: no warning as it will fit.
// [DCC Warning] TestHResultUnit.pas(35): W1012 Constant expression violates subrange bounds IResult := $800A11FD; // Does not fit, but will cast 4-byte Cardinal into a 4-byte Integer with value -2146823683 ($800A11FD) UResult := $800A11FD; // Fits, will have value 2148143613 ($800A11FD)
The second warning is the same one as the first. Which means that OleException.ErrorCode will get the same value as IResult:
// [DCC Warning] TestHResultUnit.pas(41): W1012 Constant expression violates subrange bounds
OleException := EOleException.Create('message', $800A11FD, 'source', 'helpfile', -1);
The difference btween IResult and UResult also explain the next two warnings: they basically come down to comparing -2146823683 (the stored value in E.ErrorCode) and 2148143613 (the signed equivalent of $800A11FD).
Since $800A11FD is bigger than MaxInt, the comparison will always be false.
// [DCC Warning] TestHResultUnit.pas(48): W1021 Comparison always evaluates to False
// [DCC Warning] TestHResultUnit.pas(48): W1023 Comparing signed and unsigned types - widened both operands
if E.ErrorCode = $800A11FD then // Integer can never match a Cardinal larger than High(Integer);
Exit;
You can workaround these warnings in two ways – either cast to HResult or to Cardinal:
if E.ErrorCode = HResult($800A11FD) then
Exit; // Succeed
if Cardinal(E.ErrorCode) = $800A11FD then
Exit; // Succeed
Finally no warning, but still a failure: both E.ErrorCode and $800A11FD is now passed as Int64 because there are no better overloads for TTestCase.CheckEquals in the TestFramework unit of DUnit.
Which again means that -2146823683 is compared to 2148143613. Which fails the test case.
// No warning, but both are passed as Int64, so comparison fails
Self.CheckEquals(E.ErrorCode, $800A11FD, 'E.ErrorCode should equal $800A11FD');
To answer the questions:
- Does the below unit test succeed or fail?
Fail. - Why?
Because of compiler warnings, and theTTestCase.CheckEqualsoverload chosen by the compiler.
–jeroen
unit TestHResultUnit;
interface
uses
TestFramework, System.SysUtils;
type
TestHResult = class(TTestCase)
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure Test_EOleException;
end;
implementation
uses
System.Win.ComObj, Winapi.Windows;
// 1 Fool the optimizer
procedure Touch(var X);
begin
end;
procedure TestHResult.Test_EOleException;
var
OleException: EOleException;
IResult: LongInt; // == HResult
UResult: Cardinal; // == DWord
begin
// [DCC Warning] TestHResultUnit.pas(35): W1012 Constant expression violates subrange bounds
IResult := $800A11FD; // Does not fit, but will cast 4-byte Cardinal into a 4-byte Integer with value -2146823683 ($800A11FD)
UResult := $800A11FD; // Fits, will have value 2148143613 ($800A11FD)
Touch(IResult);
Touch(UResult);
try
// [DCC Warning] TestHResultUnit.pas(41): W1012 Constant expression violates subrange bounds
OleException := EOleException.Create('message', $800A11FD, 'source', 'helpfile', -1);
raise OleException;
except
on E: EOleException do
begin
// [DCC Warning] TestHResultUnit.pas(48): W1021 Comparison always evaluates to False
// [DCC Warning] TestHResultUnit.pas(48): W1023 Comparing signed and unsigned types - widened both operands
if E.ErrorCode = $800A11FD then // Integer can never match a Cardinal larger than High(Integer);
Exit; // Succeed
// No warning, but both are passed as Int64, so comparison fails
Self.CheckEquals(E.ErrorCode, $800A11FD, 'E.ErrorCode should equal $800A11FD');
end; // E: EOleException
end;
end;
procedure TestHResult.SetUp;
begin
end;
procedure TestHResult.TearDown;
begin
end;
initialization
RegisterTest(TestHResult.Suite);
end.






Thaddy said
Change it into a comp…;) Because that has the proper range ( -2146823683.. 2148143614). Although comp is deprecated, it is useful since it is a processor native type on intel/amd.
meyerdesign said
Gosh, you mean those hints and warnings aren’t just decoration? I have come into projects in the past where the list of hints and warnings was over 1500 lines. In passing, it would be nice, since this sort of thing is not that rare, if there were a search tool on the list.
Really, I have never understood those who ignore these things. starting from square one, it is so easy to handle them, and so helpful to have none. And of course, people who let them lie then wonder at the amount of debugging they have to do. ;)