Search code examples
delphidelphi-7

Can a Range check error be generated without being specifically enabled?


A customer has reported a Range check error on a machine embedded in a factory running an older Delphi 7 application. We have not (yet) been able to reproduce the error. They sent us a photo:

Range check error

The software can operate correctly for days on end, and we currently only have vague clues as to how this might be generated or reliably reproduced. My understanding is that this error occurs in two general scenarios:

(1) An array or string has been accessed outside its bounds
(2) The variable is assigned a value out-of-range for its type

An example of (1) is accessing array[50] if there are only 40 elements. An example of (2) is assigning the value "300" to an an unsigned BYTE.

This comes from SO question 1 and SO question 2. I've got lots of careful checking to do to try and identify the offending lines!

My question is how this error is generated in the first place. Both the above-mentioned questions refer to the {$R+} compiler directive. In Project Options > Compiler > Runtime errors, Range checking is off, and nowhere in the code is {$R+} (nor {$R-}) used. How does this error occur? Shouldn't the application crash or generate a different exception?


Solution

  • I'll answer the question:

    Can a Range check error be generated without being specifically enabled?

    However, the problem you're experiencing at your client's site will need further investigation for you to resolve it. Typically this kind of thing requires a combination of:

    • Exception logging: tools that produce a stack trace when exceptions occur to determine exactly what code was being called.
    • Tracing: logging messages that provide clues to the state of the application to assist in your investigation.

    Yes, range check error can be generated without being specifically enabled.

    1. Range check errors can be raised at any time with raise ERangeError.Create(...);. If called, this will raise the error regardless of the state of the range-check setting.

    Note code can if so written, also honour the setting as follows:

    {$IFOPT R+}
      raise ERangeError.Create(...);
    {$ENDIF}
    

    But the point is that as soon as raise <SomeClass>.Create(...) is called, an exception will be raised. If you search the Delphi source code, you'll find a few places where ERangeError is raised. Loki's answer provides an example.

    1. The second important thing to note is that the {$R} setting is not global. If you compile a project with this setting turn off, but link in DCUs which were compiled with the option turned on: then the setting will still be on for those DCUs.

    Furthermore, the setting can be locally changed for specific sections of code. E.g.

    {$IFOPT R-} {$R+} {$DEFINE TOGGLE_ROFF} {$ENDIF}
    { Ensure range-checking is on, but turn off again if it was already off.}
    procedure MustUseRangeChecking(...);
    begin
      ...
    end;
    {$IFDEF TOGGLE_ROFF} {$R-} {$ENDIF}
    

    NOTE: You can use {$IFOPT} in your own code to check the state of the range-checking directive.