Shouldn't this give an out-of-bounds warning?

Viewed 555

I think this code should warn about an out-of-bounds array access:

int foo() {
  int x[10] = {0};
  int *p = &x[5];
  return p[~0LLU];
}

I know out-of-bounds warnings are not required by the standard but compilers do give them. I'm asking whether it would be correct for the compiler to give such a warning here.

Any reason why that code should be consider well formed?

3 Answers

I think this code should warn about an out-of-bounds array access:

A decent compiler could warn you when you're doing that on non-VLA arrays (gcc does not, but clang does: https://godbolt.org/z/lOvl5n)

For this snippet:

int foo() {
  int x[10] = {0};  
  return x[~0LLU];  // or x[40] to make it simpler, same thing
}

warning:

<source>:3:10: warning: array index -1 is past the end of the array (which contains 10 elements) [-Warray-bounds]

  return x[~0LLU];

         ^ ~~~~~

The compiler knows that this is an array, knows the size and therefore can check bounds if everything is literal (non-VLA array and literal index is the prerequesite)

In your case, what "loses" the compiler is that you're assigning to a pointer (array decays into a pointer)

After that, the compiler isn't able to tell the origin of the data, so it cannot control the bounds (even if in your case, the offset is ludicriously big / negative / whatever). A dedicated static analysis tool might find the issue.

The C language imposes no requirements on bounds checking of arrays. That is part of what makes it fast. That being said, compilers can and do perform check in some situations.

For example, if I compile with -O3 in gcc and replace return p[~0LLU]; with return p[10]; I get the following warning:

x1.c: In function ‘foo’:
x1.c:6:10: warning: ‘*((void *)&x+60)’ is used uninitialized in this function [-Wuninitialized]
   return p[10];

I get a similar warning if I use -10 as the index:

gcc -g -O3 -Wall -Wextra -Warray-bounds -o x1 x1.c
x1.c: In function ‘foo’:
x1.c:6:10: warning: ‘*((void *)&x+-20)’ is used uninitialized in this function [-Wuninitialized]
   return p[-100];

So it does seem that it can warn about invalid negative values for an array index.

In your case, it seems for this compiler that the value ~0LLU is converted to a signed value for the purposes of pointer arithmetic and is viewed as -1.

Note that this check can be fooled by putting other initialized variables around x:

int foo() {
  int y[10] = {0};
  int x[10] = {0};
  int z[10] = {0};
  int *p = &x[5];
  printf("&x=%p, &y=%p, &z=%p\n", (void *)x, (void *)y, (void *)z);
  return p[10] + y[0] + z[0];
}

This code produces no warnings even though p[10] is out of bounds.

So it's up to the implementation if it wants to perform a out-of-bounds check and how it does it.

Edit: Complete rewrite, with standard quotes:

[dcl.array] [ Note: Except where it has been declared for a class, the subscript operator [] is interpreted in such a way that E1[E2] is identical to *((E1)+(E2))

[expr.add] When an expression that has integral type is added to or subtracted from a pointer, the result has the type of the pointer operand. If the expression P points to element x[i] of an array object x with n elements, the expressions P + J and J + P (where J has the value j) point to the (possibly-hypothetical) element x[i + j] if 0 ≤ i + j ≤ n; otherwise, the behavior is undefined.

Therefore p[~0LLU] is interpreted identically to *(p + ~0LLU) (as per [dcl.array]) where the parenthesised expression points to the element x[5 + ~0LLU] - if the index is within the valid range - (as per [expr.add]). If the index isn't within range, the behaviour is undefined.

Is 5 + ~0LLU within the valid range of indices though? Given integer conversion rules of the language, the shown expression would appear to be well-defined if the type of 5 were a signed type of no larger size than unsigned long long, and in that case the pointed element would be x[4]. However, standard doesn't explicitly define the type of i and j in the expression that describes the behaviour. It should be interpreted to be a pure mathematic expression in which case the result would be an index unrepresentable by long long unsigned and certainly greater than n and thus undefined behaviour.

Given the interpretation that behaviour is undefined, it wouldn't be incorrect for the compiler to warn. Regardless, the compiler is not required to warn.

Related