llvm / llvm/llvm-project

clang-analyzer-security.ArrayBound and clang-analyzer-security.ArrayBound false positives

Open
#222,102 6 comments 0 reactions 1 assignee Claimed by @benedekaibas View on GitHub
clang:static analyzer false-positive
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.