Skip to content

[DRAFT] [pulse] do not keep the arguments of pure calls alive through their result - #2178

Draft
VladimirMakaev wants to merge 1 commit into
facebook:mainfrom
VladimirMakaev:pulse-dead-function-terms
Draft

VladimirMakaev wants to merge 1 commit into
facebook:mainfrom
VladimirMakaev:pulse-dead-function-terms

Conversation

@VladimirMakaev

@VladimirMakaev VladimirMakaev commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Pulse records calls to unknown functions that cannot write through their arguments as v = f(x).
Summaries kept x alive through this equality whenever v was live, which hid leaks of x and
made summaries grow exponentially along trees of such calls.

#include <stdlib.h>

int read_through_const_pointer(const int* p); // unknown

int leak_bad() {
  int* p = malloc(sizeof(int));
  if (p == NULL) {
    return 0;
  }
  return read_through_const_pointer(p); // Infer missed: memory leak
}

Now the result and the other arguments no longer keep a dead argument alive, unless callers can
compute it again, like g() in f(g()); the equalities that mention it are dropped.

This exposed a bug: inlining the initializer of a global constant argument, as in
exchange(fd_, kInvalid), could overwrite another argument of the call. It no longer does.

Limitations: this applies to all allocations (malloc, new, file descriptors, CoreFoundation
objects, C# resources):

  • unknown functions that close a descriptor or return their pointer argument now cause leak reports,
    e.g. return my_close(fd);. On libuv 1.48, this adds 2 PULSE_RESOURCE_LEAK false positives, at
    src/unix/pipe.c:130 (uv_pipe_bind2) and src/unix/tcp.c:78 (new_socket), where a helper
    closes the descriptor or keeps it; main already reports 2 of this kind in new_socket.
  • results of calls sharing a dead argument are no longer related.

Test plan

New leak tests in c/pulse/memory_leak.c, c/pulse/resource_leak.c and cpp/pulse/leaks.cpp,
with FP_ tests for the limitations, and ground pure call and global constant tests. A depth-16
tree of combine(f(), f()) calls over a local buffer took 6.7 s and a 14 MB results database
before, 0.1 s and 0.2 MB after. Codetoanalyze tests pass.

…esult

Pulse records calls to unknown functions that cannot write through their arguments as `v = f(x)`.
Summaries kept `x` alive through this equality whenever `v` was live, which hid leaks of `x` and
made summaries grow exponentially along trees of such calls.

```c
#include <stdlib.h>

int read_through_const_pointer(const int* p); // unknown

int leak_bad() {
  int* p = malloc(sizeof(int));
  if (p == NULL) {
    return 0;
  }
  return read_through_const_pointer(p); // Infer missed: memory leak
}
```

Now the result and the other arguments no longer keep a dead argument alive, unless callers can
compute it again, like `g()` in `f(g())`; the equalities that mention it are dropped.

This exposed a bug: inlining the initializer of a global constant argument, as in
`exchange(fd_, kInvalid)`, could overwrite another argument of the call. It no longer does.

Limitations: this applies to all allocations (malloc, `new`, file descriptors, CoreFoundation
objects, C# resources):
- unknown functions that close a descriptor or return their pointer argument now cause leak
  reports, e.g. `return my_close(fd);`
- results of calls sharing a dead argument are no longer related.

## Test plan

New leak tests in `c/pulse/memory_leak.c`, `c/pulse/resource_leak.c` and `cpp/pulse/leaks.cpp`,
with `FP_` tests for the limitations, and ground pure call and global constant tests. A depth-16
tree of `combine(f(), f())` calls over a local buffer took 6.7 s and a 14 MB results database
before, 0.1 s and 0.2 MB after. Codetoanalyze tests pass.
@VladimirMakaev
VladimirMakaev force-pushed the pulse-dead-function-terms branch from a99fb6f to 199e333 Compare October 5, 2026 06:53
VladimirMakaev added a commit to VladimirMakaev/infer that referenced this pull request Oct 5, 2026
…esult

Pulse records calls to unknown functions that cannot write through their arguments as `v = f(x)`.
Summaries kept `x` alive through this equality whenever `v` was live, which hid leaks of `x` and
made summaries grow exponentially along trees of such calls.

Expectation conflicts with earlier frontier commits were resolved automatically in: infer/tests/codetoanalyze/cpp/pulse/issues.exp, infer/tests/codetoanalyze/cpp/pulse/issues.exp-11

Expected test output regenerated for the combination with earlier frontier commits (no new or lost report): infer/tests/codetoanalyze/java/pulse/issues.exp (line numbers only)

Upstream-PR: facebook#2178
PR-Head: 199e333
PR-Base: 9cc2641

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant