Skip to content

Commit

Permalink
[LSan] Report proper error on allocator failures instead of CHECK(0)-ing
Browse files Browse the repository at this point in the history
Summary:
Following up on and complementing D44404.

Currently many allocator specific errors (OOM, for example) are reported as
a text message and CHECK(0) termination, not stack, no details, not too
helpful nor informative. To improve the situation, detailed and
structured errors were defined and reported under the appropriate conditions.

Reviewers: eugenis

Subscribers: srhines, mgorny, delcypher, llvm-commits, #sanitizers

Differential Revision: https://reviews.llvm.org/D47645

llvm-svn: 334034
  • Loading branch information
alekseyshl committed Jun 5, 2018
1 parent 89f5293 commit 236c3f9
Show file tree
Hide file tree
Showing 10 changed files with 275 additions and 39 deletions.
42 changes: 29 additions & 13 deletions compiler-rt/lib/lsan/lsan_allocator.cc
Expand Up @@ -17,6 +17,7 @@
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_allocator_checks.h"
#include "sanitizer_common/sanitizer_allocator_interface.h"
#include "sanitizer_common/sanitizer_allocator_report.h"
#include "sanitizer_common/sanitizer_errno.h"
#include "sanitizer_common/sanitizer_internal_defs.h"
#include "sanitizer_common/sanitizer_stackdepot.h"
Expand Down Expand Up @@ -70,17 +71,27 @@ static void RegisterDeallocation(void *p) {
atomic_store(reinterpret_cast<atomic_uint8_t *>(m), 0, memory_order_relaxed);
}

static void *ReportAllocationSizeTooBig(uptr size, const StackTrace &stack) {
if (AllocatorMayReturnNull()) {
Report("WARNING: LeakSanitizer failed to allocate 0x%zx bytes\n", size);
return nullptr;
}
ReportAllocationSizeTooBig(size, kMaxAllowedMallocSize, &stack);
}

void *Allocate(const StackTrace &stack, uptr size, uptr alignment,
bool cleared) {
if (size == 0)
size = 1;
if (size > kMaxAllowedMallocSize) {
Report("WARNING: LeakSanitizer failed to allocate %zu bytes\n", size);
return ReturnNullOrDieOnFailure::OnBadRequest();
}
if (size > kMaxAllowedMallocSize)
return ReportAllocationSizeTooBig(size, stack);
void *p = allocator.Allocate(GetAllocatorCache(), size, alignment);
if (UNLIKELY(!p))
return ReturnNullOrDieOnFailure::OnOOM();
if (UNLIKELY(!p)) {
SetAllocatorOutOfMemory();
if (AllocatorMayReturnNull())
return nullptr;
ReportOutOfMemory(size, &stack);
}
// Do not rely on the allocator to clear the memory (it's slow).
if (cleared && allocator.FromPrimary(p))
memset(p, 0, size);
Expand All @@ -91,8 +102,11 @@ void *Allocate(const StackTrace &stack, uptr size, uptr alignment,
}

static void *Calloc(uptr nmemb, uptr size, const StackTrace &stack) {
if (UNLIKELY(CheckForCallocOverflow(size, nmemb)))
return ReturnNullOrDieOnFailure::OnBadRequest();
if (UNLIKELY(CheckForCallocOverflow(size, nmemb))) {
if (AllocatorMayReturnNull())
return nullptr;
ReportCallocOverflow(nmemb, size, &stack);
}
size *= nmemb;
return Allocate(stack, size, 1, true);
}
Expand All @@ -108,9 +122,8 @@ void *Reallocate(const StackTrace &stack, void *p, uptr new_size,
uptr alignment) {
RegisterDeallocation(p);
if (new_size > kMaxAllowedMallocSize) {
Report("WARNING: LeakSanitizer failed to allocate %zu bytes\n", new_size);
allocator.Deallocate(GetAllocatorCache(), p);
return ReturnNullOrDieOnFailure::OnBadRequest();
return ReportAllocationSizeTooBig(new_size, stack);
}
p = allocator.Reallocate(GetAllocatorCache(), p, new_size, alignment);
RegisterAllocation(stack, p, new_size);
Expand All @@ -131,8 +144,9 @@ uptr GetMallocUsableSize(const void *p) {
int lsan_posix_memalign(void **memptr, uptr alignment, uptr size,
const StackTrace &stack) {
if (UNLIKELY(!CheckPosixMemalignAlignment(alignment))) {
ReturnNullOrDieOnFailure::OnBadRequest();
return errno_EINVAL;
if (AllocatorMayReturnNull())
return errno_EINVAL;
ReportInvalidPosixMemalignAlignment(alignment, &stack);
}
void *ptr = Allocate(stack, size, alignment, kAlwaysClearMemory);
if (UNLIKELY(!ptr))
Expand All @@ -146,7 +160,9 @@ int lsan_posix_memalign(void **memptr, uptr alignment, uptr size,
void *lsan_memalign(uptr alignment, uptr size, const StackTrace &stack) {
if (UNLIKELY(!IsPowerOfTwo(alignment))) {
errno = errno_EINVAL;
return ReturnNullOrDieOnFailure::OnBadRequest();
if (AllocatorMayReturnNull())
return nullptr;
ReportInvalidAllocationAlignment(alignment, &stack);
}
return SetErrnoOnNull(Allocate(stack, size, alignment, kAlwaysClearMemory));
}
Expand Down
25 changes: 13 additions & 12 deletions compiler-rt/lib/lsan/lsan_interceptors.cc
Expand Up @@ -14,6 +14,7 @@

#include "interception/interception.h"
#include "sanitizer_common/sanitizer_allocator.h"
#include "sanitizer_common/sanitizer_allocator_report.h"
#include "sanitizer_common/sanitizer_atomic.h"
#include "sanitizer_common/sanitizer_common.h"
#include "sanitizer_common/sanitizer_flags.h"
Expand Down Expand Up @@ -200,21 +201,21 @@ INTERCEPTOR(int, mprobe, void *ptr) {


// TODO(alekseys): throw std::bad_alloc instead of dying on OOM.
#define OPERATOR_NEW_BODY(nothrow) \
ENSURE_LSAN_INITED; \
GET_STACK_TRACE_MALLOC; \
void *res = lsan_malloc(size, stack); \
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM(); \
#define OPERATOR_NEW_BODY(nothrow)\
ENSURE_LSAN_INITED;\
GET_STACK_TRACE_MALLOC;\
void *res = lsan_malloc(size, stack);\
if (!nothrow && UNLIKELY(!res)) ReportOutOfMemory(size, &stack);\
return res;
#define OPERATOR_NEW_BODY_ALIGN(nothrow) \
ENSURE_LSAN_INITED; \
GET_STACK_TRACE_MALLOC; \
void *res = lsan_memalign((uptr)align, size, stack); \
if (!nothrow && UNLIKELY(!res)) DieOnFailure::OnOOM(); \
#define OPERATOR_NEW_BODY_ALIGN(nothrow)\
ENSURE_LSAN_INITED;\
GET_STACK_TRACE_MALLOC;\
void *res = lsan_memalign((uptr)align, size, stack);\
if (!nothrow && UNLIKELY(!res)) ReportOutOfMemory(size, &stack);\
return res;

#define OPERATOR_DELETE_BODY \
ENSURE_LSAN_INITED; \
#define OPERATOR_DELETE_BODY\
ENSURE_LSAN_INITED;\
lsan_free(ptr);

// On OS X it's not enough to just provide our own 'operator new' and
Expand Down
2 changes: 2 additions & 0 deletions compiler-rt/lib/sanitizer_common/CMakeLists.txt
Expand Up @@ -69,6 +69,7 @@ set(SANITIZER_COVERAGE_SOURCES
sanitizer_coverage_win_sections.cc)

set(SANITIZER_SYMBOLIZER_SOURCES
sanitizer_allocator_report.cc
sanitizer_stackdepot.cc
sanitizer_stacktrace.cc
sanitizer_stacktrace_libcdep.cc
Expand Down Expand Up @@ -98,6 +99,7 @@ set(SANITIZER_HEADERS
sanitizer_allocator_local_cache.h
sanitizer_allocator_primary32.h
sanitizer_allocator_primary64.h
sanitizer_allocator_report.h
sanitizer_allocator_secondary.h
sanitizer_allocator_size_class_map.h
sanitizer_allocator_stats.h
Expand Down
126 changes: 126 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_allocator_report.cc
@@ -0,0 +1,126 @@
//===-- sanitizer_allocator_report.cc ---------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Shared allocator error reporting for ThreadSanitizer, MemorySanitizer, etc.
///
//===----------------------------------------------------------------------===//

#include "sanitizer_allocator.h"
#include "sanitizer_allocator_report.h"
#include "sanitizer_common.h"
#include "sanitizer_report_decorator.h"

namespace __sanitizer {

class ScopedAllocatorErrorReport {
public:
ScopedAllocatorErrorReport(const char *error_summary_,
const StackTrace *stack_)
: error_summary(error_summary_),
stack(stack_) {
Printf("%s", d.Error());
}
~ScopedAllocatorErrorReport() {
Printf("%s", d.Default());
stack->Print();
// TODO(alekseyshl): Define SanitizerToolOptionsEnvVarName and use it there.
PrintHintAllocatorCannotReturnNull("");
ReportErrorSummary(error_summary, stack);
}

private:
ScopedErrorReportLock lock;
const char *error_summary;
const StackTrace* const stack;
const SanitizerCommonDecorator d;
};

void NORETURN ReportCallocOverflow(uptr count, uptr size,
const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("calloc-overflow", stack);
Report("ERROR: %s: calloc parameters overflow: count * size (%zd * %zd) "
"cannot be represented in type size_t\n", SanitizerToolName, count,
size);
}
Die();
}

void NORETURN ReportPvallocOverflow(uptr size, const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("pvalloc-overflow", stack);
Report("ERROR: %s: pvalloc parameters overflow: size 0x%zx rounded up to "
"system page size 0x%zx cannot be represented in type size_t\n",
SanitizerToolName, size, GetPageSizeCached());
}
Die();
}

void NORETURN ReportInvalidAllocationAlignment(uptr alignment,
const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("invalid-allocation-alignment", stack);
Report("ERROR: %s: invalid allocation alignment: %zd, alignment must be a "
"power of two\n", SanitizerToolName, alignment);
}
Die();
}

void NORETURN ReportInvalidAlignedAllocAlignment(uptr size, uptr alignment,
const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("invalid-aligned-alloc-alignment", stack);
#if SANITIZER_POSIX
Report("ERROR: %s: invalid alignment requested in "
"aligned_alloc: %zd, alignment must be a power of two and the "
"requested size 0x%zx must be a multiple of alignment\n",
SanitizerToolName, alignment, size);
#else
Report("ERROR: %s: invalid alignment requested in aligned_alloc: %zd, "
"the requested size 0x%zx must be a multiple of alignment\n",
SanitizerToolName, alignment, size);
#endif
}
Die();
}

void NORETURN ReportInvalidPosixMemalignAlignment(uptr alignment,
const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("invalid-posix-memalign-alignment",
stack);
Report("ERROR: %s: invalid alignment requested in "
"posix_memalign: %zd, alignment must be a power of two and a "
"multiple of sizeof(void*) == %zd\n", SanitizerToolName, alignment,
sizeof(void*)); // NOLINT
}
Die();
}

void NORETURN ReportAllocationSizeTooBig(uptr user_size, uptr max_size,
const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("allocation-size-too-big", stack);
Report("ERROR: %s: requested allocation size 0x%zx exceeds maximum "
"supported size of 0x%zx\n", SanitizerToolName, user_size, max_size);
}
Die();
}

void NORETURN ReportOutOfMemory(uptr requested_size, const StackTrace *stack) {
{
ScopedAllocatorErrorReport report("out-of-memory", stack);
Report("ERROR: %s: allocator is out of memory trying to allocate 0x%zx "
"bytes\n", SanitizerToolName, requested_size);
}
Die();
}

} // namespace __sanitizer
38 changes: 38 additions & 0 deletions compiler-rt/lib/sanitizer_common/sanitizer_allocator_report.h
@@ -0,0 +1,38 @@
//===-- sanitizer_allocator_report.h ----------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// Shared allocator error reporting for ThreadSanitizer, MemorySanitizer, etc.
///
//===----------------------------------------------------------------------===//

#ifndef SANITIZER_ALLOCATOR_REPORT_H
#define SANITIZER_ALLOCATOR_REPORT_H

#include "sanitizer_internal_defs.h"
#include "sanitizer_stacktrace.h"

namespace __sanitizer {

void NORETURN ReportCallocOverflow(uptr count, uptr size,
const StackTrace *stack);
void NORETURN ReportPvallocOverflow(uptr size, const StackTrace *stack);
void NORETURN ReportInvalidAllocationAlignment(uptr alignment,
const StackTrace *stack);
void NORETURN ReportInvalidAlignedAllocAlignment(uptr size, uptr alignment,
const StackTrace *stack);
void NORETURN ReportInvalidPosixMemalignAlignment(uptr alignment,
const StackTrace *stack);
void NORETURN ReportAllocationSizeTooBig(uptr user_size, uptr max_size,
const StackTrace *stack);
void NORETURN ReportOutOfMemory(uptr requested_size, const StackTrace *stack);

} // namespace __sanitizer

#endif // SANITIZER_ALLOCATOR_REPORT_H
5 changes: 3 additions & 2 deletions compiler-rt/lib/sanitizer_common/sanitizer_report_decorator.h
Expand Up @@ -25,10 +25,11 @@ class SanitizerCommonDecorator {
// stdout, which is not the case on Windows (see SetConsoleTextAttribute()).
public:
SanitizerCommonDecorator() : ansi_(ColorizeReports()) {}
const char *Bold() const { return ansi_ ? "\033[1m" : ""; }
const char *Bold() const { return ansi_ ? "\033[1m" : ""; }
const char *Default() const { return ansi_ ? "\033[1m\033[0m" : ""; }
const char *Warning() const { return Red(); }
const char *MemoryByte() { return Magenta(); }
const char *Error() const { return Red(); }
const char *MemoryByte() const { return Magenta(); }

protected:
const char *Black() const { return ansi_ ? "\033[1m\033[30m" : ""; }
Expand Down
25 changes: 25 additions & 0 deletions compiler-rt/test/lsan/TestCases/Linux/aligned_alloc-alignment.cc
@@ -0,0 +1,25 @@
// RUN: %clangxx_lsan -O0 %s -o %t
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL

// UNSUPPORTED: android

// REQUIRES: stable-runtime

#include <stdio.h>
#include <stdlib.h>

extern void *aligned_alloc(size_t alignment, size_t size);

int main() {
void *p = aligned_alloc(17, 100);
// CHECK: {{ERROR: .*Sanitizer: invalid allocation alignment: 17}}
// CHECK: {{#0 0x.* in .*}}{{aligned_alloc|memalign}}
// CHECK: {{#1 0x.* in main .*aligned_alloc-alignment.cc:}}[[@LINE-3]]
// CHECK: {{SUMMARY: .*Sanitizer: invalid-allocation-alignment}}

printf("pointer after failed aligned_alloc: %zd\n", (size_t)p);
// CHECK-NULL: pointer after failed aligned_alloc: 0

return 0;
}
9 changes: 9 additions & 0 deletions compiler-rt/test/lsan/TestCases/Posix/lit.local.cfg
@@ -0,0 +1,9 @@
def getRoot(config):
if not config.parent:
return config
return getRoot(config.parent)

root = getRoot(config)

if root.host_os in ['Windows']:
config.unsupported = True
22 changes: 22 additions & 0 deletions compiler-rt/test/lsan/TestCases/Posix/posix_memalign-alignment.cc
@@ -0,0 +1,22 @@
// RUN: %clangxx_lsan -O0 %s -o %t
// RUN: %env_lsan_opts=allocator_may_return_null=0 not %run %t 2>&1 | FileCheck %s
// RUN: %env_lsan_opts=allocator_may_return_null=1 %run %t 2>&1 | FileCheck %s --check-prefix=CHECK-NULL

// REQUIRES: stable-runtime

#include <stdio.h>
#include <stdlib.h>

int main() {
void *p = reinterpret_cast<void*>(42);
int res = posix_memalign(&p, 17, 100);
// CHECK: {{ERROR: .*Sanitizer: invalid alignment requested in posix_memalign: 17}}
// CHECK: {{#0 0x.* in .*posix_memalign}}
// CHECK: {{#1 0x.* in main .*posix_memalign-alignment.cc:}}[[@LINE-3]]
// CHECK: {{SUMMARY: .*Sanitizer: invalid-posix-memalign-alignment}}

printf("pointer after failed posix_memalign: %zd\n", (size_t)p);
// CHECK-NULL: pointer after failed posix_memalign: 42

return 0;
}

0 comments on commit 236c3f9

Please sign in to comment.