Files
clang-p2996/clang/test/Analysis/NewDeleteLeaks.cpp
Kristóf Umann 2d3668c997 [analyzer] MallocChecker: Add a visitor to leave a note on functions that could have, but did not change ownership on leaked memory
This is a rather common feedback we get from out leak checkers: bug reports are
really short, and are contain barely any usable information on what the analyzer
did to conclude that a leak actually happened.

This happens because of our bug report minimizing effort. We construct bug
reports by inspecting the ExplodedNodes that lead to the error from the bottom
up (from the error node all the way to the root of the exploded graph), and mark
entities that were the cause of a bug, or have interacted with it as
interesting. In order to make the bug report a bit less verbose, whenever we
find an entire function call (from CallEnter to CallExitEnd) that didn't talk
about any interesting entity, we prune it (click here for more info on bug
report generation). Even if the event to highlight is exactly this lack of
interaction with interesting entities.

D105553 generalized the visitor that creates notes for these cases. This patch
adds a new kind of NoStateChangeVisitor that leaves notes in functions that
took a piece of dynamically allocated memory that later leaked as parameter,
and didn't change its ownership status.

Differential Revision: https://reviews.llvm.org/D105553
2021-08-16 16:19:00 +02:00

143 lines
5.3 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();
namespace memory_allocated_in_fn_call {
void sink(int *P) {
} // ownership-note {{Returning without deallocating memory or storing the pointer for later deallocation}}
void foo() {
sink(new int(5)); // expected-note {{Memory is allocated}}
// ownership-note@-1 {{Calling 'sink'}}
// ownership-note@-2 {{Returning from 'sink'}}
} // expected-warning {{Potential memory leak [cplusplus.NewDeleteLeaks]}}
// expected-note@-1 {{Potential memory leak}}
} // namespace memory_allocated_in_fn_call
namespace memory_passed_to_fn_call {
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
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
// TODO: We don't want a note here. sink() doesn't seem like a function that
// even attempts to take care of any memory ownership problems.
namespace memory_passed_into_fn_that_doesnt_intend_to_free {
void sink(int *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_into_fn_that_doesnt_intend_to_free
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