Some code flattens multidimensional arrays like this:
int array[10][10];
int* flattened_array = (int*)array;
for (int i = 0; i < 10*10; ++i)
flattened_array[i] = 42;
This is, as far as I know, undefined behaviour.
I am trying to detect cases like this with gcc sanitizers, however, neither -fsanitize=address
nor -fsanitize=undefined
work.
Is there a sanitizer option that I'm missing, or perhaps a different way to detect this at run time? Or maybe I am mistaken and the code is legal?
Edit: the sanitizers detect this access as an error:
array[0][11] = 42;
but do not detect this:
int* first_element = array[0];
first_element[11] = 42;
Furthermore, clang detects the first access statically, and gives out a warning
warning: array index 11 is past the end of the array (which contains 10 elements) [-Warray-bounds]
Edit: the above does not change if int
in the declaration is replaced with char
.
Edit: There are two potential sources of UB.
- Accessing an object (of type
int[10]
) through an lvalue of an incompatible type (int
). - Out-of-bounds access with a pointer of type
int*
and an index>=10
where the size of the underlying array is 10 (rather than 100).
Sanitizers don't seem to detect the first kind of violation. There's a debate whether this is a violation at all. After all, there's also an object of type int
at the same address.
As for the second potential UB, the UB sanitizer does detect such access, but only if it is done directly via the 2D array itself and not via another variable that points to its first element, as shown above. I don't think the two accesses should differ in legality. They should be either both legal (and then ubsan has a false positive) or both illegal (and then ubsan has a false negative).
Edit: Appendix J2 says array[0][11]
should be UB, even though it is only informative.
array
has typeint[2][5]
which decays toint(*)[5].
flattened_array
has typeint*
. These types are not compatible. There are no guarantees given about conversion of a pointer to a pointer to an incompatible type, except that the result can be converted back to the original type. Access to an object should be done through an lvalue of a compatible type. Second, pointer arithmetic is only valid if there if the underlying array has sufficient size. There is no array of size 100 anywhere in sight. - n. 1.8e9-where's-my-share m.int *first_element = array[0];
first_element[11] = 42;
, the analyzer would need to prove thatfirst_element
was always pointing to an array of length < 12 in order to issue a warning. - Ian Abbott