clang-analyzer-security.ArrayBound and clang-analyzer-security.ArrayBound false positives
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
`clang-analyzer-core.UndefinedBinaryOperatorResult` and `clang-analyzer-security.ArrayBound` both report a false-positive "garbage value" / out-of-bounds read on a `malloc`'d array that is fully initialized by a preceding `for` loop over the same bound. The false positive only appears when the pointer supplying that bound is also passed to an unrelated opaque/external function call between the initializing loop and the later read of the array. Removing that single call argument makes both warnings disappear, even though the argument cannot affect the array's contents. The analyzer's own path notes confirm it walked the first loop (`for (i = 0; i < info->count; i++) done[i] = 0;`) to completion, correctly reasoning `i >= info->count` at loop exit — i.e. it did model `done[]` as fully initialized — yet in the final loop it still treats `done[i]` as holding a garbage/uninitialized value.
I've reproduced this locally on v20.1.8 with the following code as well as on Compiler Explorer ([here](https://godbolt.org/z/8xeWYefq7)), which uses v24.0.0 as of today.
**Minimal reproducer**
```c
#include
typedef struct { int count; } info_type;
extern void log_impl(const char *fmt, ...);
extern int process_item(int index);
int process_all(info_type const *info) {
int *done = (int *)malloc(sizeof(int) * info->count);
int status = 0;
int i;
for (i = 0; i < info->count; i++) {
done[i] = 0;
}
status = process_item(0);
log_impl("processing, info: %p", info); /* removing this call silences both warnings */
if (status != 0) {
for (i = 0; i < info->count; i++) {
if (done[i] == 1) { /* false-positive garbage-value read reported here */
}
}
}
free(done);
return status;
}
```
yields:
```
:22:11: warning: Out of bound access to memory after the end of the heap area [clang-analyzer-security.ArrayBound]
22 | if (done[i] == 1) { /* false-positive garbage-value read reported here */
| ^~~~~~~
:13:15: note: Assuming 'i' is < field 'count'
13 | for (i = 0; i < info->count; i++) {
| ^~~~~~~~~~~~~~~
:13:3: note: Loop condition is true. Entering loop body
13 | for (i = 0; i < info->count; i++) {
| ^
:13:15: note: Assuming 'i' is >= field 'count'
13 | for (i = 0; i < info->count; i++) {
| ^~~~~~~~~~~~~~~
:13:3: note: Loop condition is false. Execution continues on line 17
13 | for (i = 0; i < info->count; i++) {
| ^
:20:7: note: Assuming 'status' is not equal to 0
20 | if (status != 0) {
| ^~~~~~~~~~~
:20:3: note: Taking true branch
20 | if (status != 0) {
| ^
:21:17: note: Assuming 'i' is < field 'count'
21 | for (i = 0; i < info->count; i++) {
| ^~~~~~~~~~~~~~~
:21:5: note: Loop condition is true. Entering loop body
21 | for (i = 0; i < info->count; i++) {
| ^
:22:7: note: Taking false branch
22 | if (done[i] == 1) { /* false-positive garbage-value read reported here */
| ^
:21:17: note: Assuming 'i' is < field 'count'
21 | for (i = 0; i < info->count; i++) {
| ^~~~~~~~~~~~~~~
:21:5: note: Loop condition is true. Entering loop body
21 | for (i = 0; i < info->count; i++) {
| ^
:22:11: note: Access of 'int' element in the heap area at index 1
22 | if (done[i] == 1) { /* false-positive garbage-value read reported here */
| ^~~~~~~
:22:19: warning: The left operand of '==' is a garbage value [clang-analyzer-core.UndefinedBinaryOperatorResult]
22 | if (done[i] == 1) { /* false-positive garbage-value read reported here */
| ~~~~~~~ ^
:9:22: note: Storing uninitialized value
9 | int *done = (int *)malloc(sizeof(int) * info->count);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
:13:15: note: Assuming 'i' is >= field 'count'
13 | for (i = 0; i < info->count; i++) {
| ^~~~~~~~~~~~~~~
:13:3: note: Loop condition is false. Execution continues on line 17
13 | for (i = 0; i < info->count; i++) {
| ^
:20:7: note: Assuming 'status' is not equal to 0
20 | if (status != 0) {
| ^~~~~~~~~~~
:20:3: note: Taking true branch
20 | if (status != 0) {
| ^
:21:10: note: The value 0 is assigned to 'i'
21 | for (i = 0; i < info->count; i++) {
| ^~~~~
:21:17: note: Assuming 'i' is < field 'count'
21 | for (i = 0; i < info->count; i++) {
| ^~~~~~~~~~~~~~~
:21:5: note: Loop condition is true. Entering loop body
21 | for (i = 0; i < info->count; i++) {
| ^
:22:19: note: The left operand of '==' is a garbage value
22 | if (done[i] == 1) { /* false-positive garbage-value read reported here */
| ~~~~~~~ ^
```
Contributor guide
Assessment
This issue has not been assessed yet.