llvm / llvm/llvm-project

[clang][analyzer] False positive core.NullDereference after updating list head through pointer-to-pointer backlink

Open
#215,755 6 comments 0 reactions 0 assignees View on GitHub
clang:static analyzer false-positive
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

**Description:**
The analyzer reports a null-pointer dereference on an impossible second loop iteration. The non-returning path in `reproducer()` expects a valid one-element list (`head -> [node] -> NULL`), and when updated inside `unlink_node()`, line `*node->previous = node->next;` is equivalent to `*(&head) = NULL`, so `head` becomes a `NULL` and the loop body execute only once.

**Reproducer:**
```c
#define NULL ((void*)0)

void clang_analyzer_eval(int);
void clang_analyzer_numTimesReached(void);

typedef struct Node Node;
struct Node {
Node *next;
Node **previous;
};

static Node *head;

void unlink_node(Node *node)
{
*node->previous = node->next;

if (node->next != NULL)
node->next->previous = node->previous;

node->previous = NULL;
node->next = NULL;
}

void reproducer(void)
{
if (head == NULL || head->next != NULL || head->previous != &head)
return;

clang_analyzer_eval(head->previous == &head);

while (head != 0) {
clang_analyzer_numTimesReached();

Node *node = head;
unlink_node(node);

clang_analyzer_eval(head == NULL);
}
}
```

**Output:**
```sh
mre.c:18:21: warning: Dereference of null pointer (loaded from field 'previous') [core.NullDereference]
18 | *node->previous = node->next;
| ~~~~~~~~ ^
mre.c:32:5: warning: UNKNOWN [debug.ExprInspection]
32 | clang_analyzer_eval(head->previous == &head);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mre.c:35:2: warning: 2 [debug.ExprInspection]
35 | clang_analyzer_numTimesReached();
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mre.c:40:2: warning: FALSE [debug.ExprInspection]
40 | clang_analyzer_eval(head == NULL);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
However, the analyzer:
- reports UNKNOWN for `head->previous == &head` (should be TRUE);
- reports FALSE for `head == NULL` after unlinking (should be TRUE);
- reaches the loop body twice (should be only once);
- reports a null dereference during the second iteration.

**Clang version:**
```sh
clang version 24.0.0git (https://github.com/llvm/llvm-project.git d71427f70210606cc41a460b4a1101b99e5f882f)
Target: x86_64-unknown-linux-gnu
Thread model: posix
Build config: +assertions
```

Contributor guide

Open the contributing guide

Research direction

Start by running the supplied reproducer() with the stated Clang version and compare the analyzer output with the expected pointer updates. Trace unlink_node(), especially the pointer-to-pointer assignment and the calls to clang_analyzer_eval() and clang_analyzer_numTimesReached(); done means the assertions report TRUE, the loop reaches once, and no null-dereference warning is emitted.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.