if (i < 0 || buffer+i-128 >= buffer)
... instead of if ((i < 0) || (i >= sizeof(buffer))
? if (i < 0 || buffer+i-128 >= buffer)
... instead of if ((i < 0) || (i >= sizeof(buffer))
?Instead, consider it from the point of view of probability.
Even if probability of any single programmer writing an exceedingly stupid piece of code is vanishingly small, the number of programmers writing new code daily is so big, that mistakes of vanishingly small probability end up being quite common indeed.
To given a real example: consider that Apple undoubtably employs only the finest programmers for their OS group and yet every iOS release is jailbroken quite quickly due to a piece of code which had a security bug.
Even within a much smaller scope of a single application, all web browser from Microsoft, Apple, Mozilla (and sometimes Google) are regularly compromised via security bugs despite huge investments from all those companies into security.
Toss in the fact that many programmers actually like doing things in unnecessarily clever or novel ways and inevitably the clever code is more likely to be buggy than boring code.
Bugs and crappy code is a statistical certainty, especially in languages like C and C++ that give you so many ways to shoot yourself in the foot.
Unrelated to the issue, but the superfluous parenthesis added in the second example are interesting. I think every C coder has a level of comfort with the 15 sets in the operator precedence rules, and beyond that they throw parenthesis at it. I'm fine with the rules until I use a <<, >>, &, ^, or |[1]. Then I drag out the parenthesis.
[1] I swear the positions of the ones I named are placed alphabetically rather than in relation to their mathematical function.
:(
Therefore, buffer through buffer+1 would be valid pointers, but unless they can prove that i-128 is <=1 they can safely assume that the final pointer can be more than 1 past the buffer.
If it was just a pointer (not an array) they could do the same analysis if the pointer was mallocced right then in the same function with a constant size. More likely though, the pointer would be a function argument and then they would not know where the object boundaries were without doing interprocedural analysis (which I assume is outside of the scope of this post) and some other additional propagation (so technically within the C99 spec, but I doubt you would see an optimization that actually would do that).
- macro expansions
- the author took an example from a larger function and simplified it into a minimal test case. For example, there might be a
#define BUFSIZE 128;
...
j = buffer+i;
...
if( (i < 0) || (i-BUFSIZE >= buffer))
Still weird (i - BUFSIZE could underflow), but more believable.