The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,708 other followers

Delphi: always watch the compiler Warnings

Posted by jpluimers on 2014/08/28

Quiz questions:

  1. Does the below unit test succeed or fail?
  2. 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:

  1. Does the below unit test succeed or fail?
    Fail.
  2. Why?
    Because of compiler warnings, and the TTestCase.CheckEquals overload 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.

2 Responses to “Delphi: always watch the compiler Warnings”

  1. 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.

  2. 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. ;)

Leave a Reply to Thaddy Cancel reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

 
%d bloggers like this: