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
195 lines
6.8 KiB
C++
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
|