-
-
Notifications
You must be signed in to change notification settings - Fork 41
What are range and overflow checks (and errors) in Pascal
- What are Range check errors? What does {$R+} do?
- What are Overflow errors? What does {$Q+} do?
Overflow and range check errors are, to put it simply, bugs.
-
Overflow (checked by {$Q+}) is when you make an arithmetic operation that results in something larger than the allowed value. For example, I have
A: Int64
that is equal right nowHigh(Int64) - 100
. I calculateA + 200
. The result isHigh(Int64) + 100
, it cannot fit within theInt64
range.I made a demo showing it: https://gist.github.com/michaliskambi/ea4399e9aeb9f23858d22dc58b1391d9
-
Range check error (checked by {$R+}) is when you have e.g.
Arr: array[0..10] of something
and you access (read or write)Arr[11]
orArr[-1]
. -
To confuse matters a bit, if you try to repeat my 1st example with Byte (8 bit unsigned integer), declaring
A: Byte
and initializingA := 100
at the beginning and you calculateA + 200
... (which is 300, and Byte can only fit values 0..255)... you will get a range check error, not overflow error as you might expect. The reason for this inconsistency is somewhat internal.(FPC calculates internally using native integer size, 32 or 64 bit on our modern machines, and only an error at this point (at 32-bit or 64-bit calculation) causes overflow; an error later, because the result doesn't fit in Byte range, causes a range check error. See Jonas comments at https://bugs.freepascal.org/view.php?id=28384 )
Fortunately, in most usecases you enable (or disable) both overflow and range checking, so it doesn't matter much which check ($Q or $R) catches which error (overflow at arithmetic operation, or accessing outside of array bounds).
These errors are detected in debug mode, and cause an exception then. But this detection takes time (quite significant in some cases, e.g. our ray-tracer is 1.9 x slower in debug mode, see https://castle-engine.sourceforge.io/manual_optimization.php#section_release_mode ). So these checks are turned off in "release" mode. In release mode we simply assume you will not make these errors.
Now what will happen if these errors occur in release mode?
-
An (unchecked) overflow means that the integer value will "wrap" around zero. It's easy to calculate what this means for unsigned values, when A is Byte and A = 200 and you do A := A + 100 then A is now equal (300 - 256) = 44. For signed values, you need to understand how negative integers are encoded in memory: https://en.wikipedia.org/wiki/Two%27s_complement . In any case, the result is probably something unexpected (you added something positive to A, but the resulting A is smaller) and can cause any logical bugs later.
-
An (unchecked) range error, when you access something outside the array bounds, is much more nasty. You are reading outside of the memory than you're supposed to.
- Maybe you're reading a memory currently allocated but unused, holding garbage (random garbage, that may even change from program execution to execution).
- Maybe you're reading a memory currently allocated and used by another of your variables. So you read or write an unrelated, "innocent" variable. This can lead to many nasty bugs later. And it can be used as a security hole in special cases: an attacker can deliberately cause this error to read some adjacent memory location (e.g. containing user's saved passing).
- Maybe you're reading outside of the allocated memory. ("Allocated" from OS, using e.g.
malloc
from libc on Unix.) In this case you get SEGFAULT (a Unix name for Pascal's EAccessViolation exception).
To summarize: you have to write your code such that it never causes an overflow or range check error. These mistakes cause clear exceptions in "debug" mode to help you with this (so that these errors don't go unnoticed). So you have to find in code what and why causes an overflow or range check error, and properly secure from it.