Files
clang-p2996/clang/test/Analysis/NewDeleteLeaks.cpp
Kristóf Umann d832078904 [analyzer] Improve NoOwnershipChangeVisitor's understanding of deallocators
The problem with leak bug reports is that the most interesting event in the code
is likely the one that did not happen -- lack of ownership change and lack of
deallocation, which is often present within the same function that the analyzer
inlined anyway, but not on the path of execution on which the bug occured. We
struggle to understand that a function was responsible for freeing the memory,
but failed.

D105819 added a new visitor to improve memory leak bug reports. In addition to
inspecting the ExplodedNodes of the bug pat, the visitor tries to guess whether
the function was supposed to free memory, but failed to. Initially (in D108753),
this was done by checking whether a CXXDeleteExpr is present in the function. If
so, we assume that the function was at least party responsible, and prevent the
analyzer from pruning bug report notes in it. This patch improves this heuristic
by recognizing all deallocator functions that MallocChecker itself recognizes,
by reusing MallocChecker::isFreeingCall.

Differential Revision: https://reviews.llvm.org/D118880
2022-03-03 11:27:56 +01:00

195 lines
6.8 KiB
C++

// RUN: %clang_analyze_cc1 -verify -analyzer-output=text %s \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus \
// RUN: -analyzer-checker=unix \
// RUN: -analyzer-config \
// RUN: unix.DynamicMemoryModeling:AddNoOwnershipChangeNotes=false
// RUN: %clang_analyze_cc1 -verify=expected,ownership -analyzer-output=text %s \
// RUN: -analyzer-checker=core \
// RUN: -analyzer-checker=cplusplus \
// RUN: -analyzer-checker=unix \
// RUN: -analyzer-config \
// RUN: unix.DynamicMemoryModeling:AddNoOwnershipChangeNotes=true
#include "Inputs/system-header-simulator-for-malloc.h"
//===----------------------------------------------------------------------===//
// Report for which we expect NoOwnershipChangeVisitor to add a new note.
//===----------------------------------------------------------------------===//
bool coin();
// TODO: AST analysis of sink would reveal that it doesn't intent to free the
// allocated memory, but in this instance, its also the only function with
// the ability to do so, we should see a note here.
namespace memory_allocated_in_fn_call {
void sink(int *P) {
}
void foo() {
sink(new int(5)); // expected-note {{Memory is allocated}}
} // expected-warning {{Potential memory leak [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential memory leak}}
} // namespace memory_allocated_in_fn_call
// Realize that sink() intends to deallocate memory, assume that it should've
// taken care of the leaked object as well.
namespace memory_passed_to_fn_call_delete {
void sink(int *P) {
if (coin()) // ownership-note {{Assuming the condition is false}}
// ownership-note@-1 {{Taking false branch}}
delete P;
} // ownership-note {{Returning without deallocating memory or storing the pointer for later deallocation}}
void foo() {
int *ptr = new int(5); // expected-note {{Memory is allocated}}
sink(ptr); // ownership-note {{Calling 'sink'}}
// ownership-note@-1 {{Returning from 'sink'}}
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_passed_to_fn_call_delete
namespace memory_passed_to_fn_call_free {
void sink(int *P) {
if (coin()) // ownership-note {{Assuming the condition is false}}
// ownership-note@-1 {{Taking false branch}}
free(P);
} // ownership-note {{Returning without deallocating memory or storing the pointer for later deallocation}}
void foo() {
int *ptr = (int *)malloc(sizeof(int)); // expected-note {{Memory is allocated}}
sink(ptr); // ownership-note {{Calling 'sink'}}
// ownership-note@-1 {{Returning from 'sink'}}
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [unix.Malloc]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_passed_to_fn_call_free
// Function pointers cannot be resolved syntactically.
namespace memory_passed_to_fn_call_free_through_fn_ptr {
void (*freeFn)(void *) = free;
void sink(int *P) {
if (coin())
freeFn(P);
}
void foo() {
int *ptr = (int *)malloc(sizeof(int)); // expected-note {{Memory is allocated}}
sink(ptr);
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [unix.Malloc]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_passed_to_fn_call_free_through_fn_ptr
namespace memory_shared_with_ptr_of_shorter_lifetime {
void sink(int *P) {
int *Q = P;
if (coin()) // ownership-note {{Assuming the condition is false}}
// ownership-note@-1 {{Taking false branch}}
delete P;
(void)Q;
} // ownership-note {{Returning without deallocating memory or storing the pointer for later deallocation}}
void foo() {
int *ptr = new int(5); // expected-note {{Memory is allocated}}
sink(ptr); // ownership-note {{Calling 'sink'}}
// ownership-note@-1 {{Returning from 'sink'}}
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_shared_with_ptr_of_shorter_lifetime
//===----------------------------------------------------------------------===//
// Report for which we *do not* expect NoOwnershipChangeVisitor add a new note,
// nor do we want it to.
//===----------------------------------------------------------------------===//
namespace memory_not_passed_to_fn_call {
void sink(int *P) {
if (coin())
delete P;
}
void foo() {
int *ptr = new int(5); // expected-note {{Memory is allocated}}
int *q = nullptr;
sink(q);
(void)ptr;
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_not_passed_to_fn_call
namespace memory_shared_with_ptr_of_same_lifetime {
void sink(int *P, int **Q) {
// NOTE: Not a job of NoOwnershipChangeVisitor, but maybe this could be
// highlighted still?
*Q = P;
}
void foo() {
int *ptr = new int(5); // expected-note {{Memory is allocated}}
int *q = nullptr;
sink(ptr, &q);
} // expected-warning {{Potential leak of memory pointed to by 'q' [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_shared_with_ptr_of_same_lifetime
namespace memory_passed_into_fn_that_doesnt_intend_to_free {
void sink(int *P) {
}
void foo() {
int *ptr = new int(5); // expected-note {{Memory is allocated}}
sink(ptr);
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_passed_into_fn_that_doesnt_intend_to_free
namespace memory_passed_into_fn_that_doesnt_intend_to_free2 {
void bar();
void sink(int *P) {
// Correctly realize that calling bar() doesn't mean that this function would
// like to deallocate anything.
bar();
}
void foo() {
int *ptr = new int(5); // expected-note {{Memory is allocated}}
sink(ptr);
} // expected-warning {{Potential leak of memory pointed to by 'ptr' [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential leak}}
} // namespace memory_passed_into_fn_that_doesnt_intend_to_free2
namespace refkind_from_unoallocated_to_allocated {
// RefKind of the symbol changed from nothing to Allocated. We don't want to
// emit notes when the RefKind changes in the stack frame.
static char *malloc_wrapper_ret() {
return (char *)malloc(12); // expected-note {{Memory is allocated}}
}
void use_ret() {
char *v;
v = malloc_wrapper_ret(); // expected-note {{Calling 'malloc_wrapper_ret'}}
// expected-note@-1 {{Returned allocated memory}}
} // expected-warning {{Potential leak of memory pointed to by 'v' [unix.Malloc]}}
// expected-note@-1 {{Potential leak of memory pointed to by 'v'}}
} // namespace refkind_from_unoallocated_to_allocated