summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorSergey Matveev <earthdok@google.com>2013-06-06 14:17:56 +0000
committerSergey Matveev <earthdok@google.com>2013-06-06 14:17:56 +0000
commitcd571e07fd1179383188c70338fa0dc1c452cb19 (patch)
treeb83b2c886c931d18b6542013180cc4b0170af411 /lib
parent722f2e6a6125a0d5c9d453278b0f292e3410124d (diff)
[lsan] Implement __lsan_ignore_object().
Leak annotation similar to HeapChecker's IgnoreObject(). git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@183412 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib')
-rw-r--r--lib/asan/asan_allocator2.cc14
-rw-r--r--lib/lsan/lit_tests/TestCases/ignore_object.cc30
-rw-r--r--lib/lsan/lit_tests/TestCases/ignore_object_errors.cc22
-rw-r--r--lib/lsan/lsan_allocator.cc15
-rw-r--r--lib/lsan/lsan_common.cc28
-rw-r--r--lib/lsan/lsan_common.h11
-rw-r--r--lib/sanitizer_common/sanitizer_allocator.h18
7 files changed, 127 insertions, 11 deletions
diff --git a/lib/asan/asan_allocator2.cc b/lib/asan/asan_allocator2.cc
index ea4181d17..c1f012492 100644
--- a/lib/asan/asan_allocator2.cc
+++ b/lib/asan/asan_allocator2.cc
@@ -792,6 +792,20 @@ template void ForEachChunk<MarkIndirectlyLeakedCb>(
template void ForEachChunk<CollectSuppressedCb>(
CollectSuppressedCb const &callback);
#endif // CAN_SANITIZE_LEAKS
+
+IgnoreObjectResult IgnoreObjectLocked(const void *p) {
+ uptr addr = reinterpret_cast<uptr>(p);
+ __asan::AsanChunk *m = __asan::GetAsanChunkByAddr(addr);
+ if (!m) return kIgnoreObjectInvalid;
+ if ((m->chunk_state == __asan::CHUNK_ALLOCATED) && m->AddrIsInside(addr)) {
+ if (m->lsan_tag == kSuppressed)
+ return kIgnoreObjectAlreadyIgnored;
+ m->lsan_tag = __lsan::kSuppressed;
+ return kIgnoreObjectSuccess;
+ } else {
+ return kIgnoreObjectInvalid;
+ }
+}
} // namespace __lsan
extern "C" {
diff --git a/lib/lsan/lit_tests/TestCases/ignore_object.cc b/lib/lsan/lit_tests/TestCases/ignore_object.cc
new file mode 100644
index 000000000..b01619853
--- /dev/null
+++ b/lib/lsan/lit_tests/TestCases/ignore_object.cc
@@ -0,0 +1,30 @@
+// Test for __lsan_ignore_object().
+// RUN: LSAN_BASE="report_blocks=1:use_registers=0:use_stacks=0:use_globals=0:use_tls=0:verbosity=2"
+// RUN: %clangxx_lsan -I %p/../../../../include %s -o %t
+// RUN: LSAN_OPTIONS=$LSAN_BASE %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sanitizer/lsan_interface.h"
+
+int main() {
+ {
+ // The first malloc call can cause an allocation in libdl. Ignore it here so
+ // it doesn't show up in our output.
+ __lsan::ScopedDisabler d;
+ malloc(1);
+ }
+ // Explicitly ignored block.
+ void **p = new void *;
+ // Transitively ignored block.
+ *p = malloc(666);
+ // Non-ignored block.
+ volatile void *q = malloc(1337);
+ fprintf(stderr, "Test alloc: %p.\n", p);
+ __lsan_ignore_object(p);
+ return 0;
+}
+// CHECK: Test alloc: [[ADDR:.*]].
+// CHECK: ignoring heap object at [[ADDR]]
+// CHECK: SUMMARY: LeakSanitizer: 1337 byte(s) leaked in 1 allocation(s)
diff --git a/lib/lsan/lit_tests/TestCases/ignore_object_errors.cc b/lib/lsan/lit_tests/TestCases/ignore_object_errors.cc
new file mode 100644
index 000000000..4b0e5d928
--- /dev/null
+++ b/lib/lsan/lit_tests/TestCases/ignore_object_errors.cc
@@ -0,0 +1,22 @@
+// Test for incorrect use of __lsan_ignore_object().
+// RUN: LSAN_BASE="verbosity=1"
+// RUN: %clangxx_lsan -I %p/../../../../include %s -o %t
+// RUN: LSAN_OPTIONS=$LSAN_BASE %t 2>&1 | FileCheck %s
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "sanitizer/lsan_interface.h"
+
+int main() {
+ void *p = malloc(1337);
+ fprintf(stderr, "Test alloc: %p.\n", p);
+ __lsan_ignore_object(p);
+ __lsan_ignore_object(p);
+ free(p);
+ __lsan_ignore_object(p);
+ return 0;
+}
+// CHECK: Test alloc: [[ADDR:.*]].
+// CHECK: heap object at [[ADDR]] is already being ignored
+// CHECK: no heap object found at [[ADDR]]
diff --git a/lib/lsan/lsan_allocator.cc b/lib/lsan/lsan_allocator.cc
index eca739943..cbfaa1572 100644
--- a/lib/lsan/lsan_allocator.cc
+++ b/lib/lsan/lsan_allocator.cc
@@ -190,6 +190,21 @@ template void ForEachChunk<MarkIndirectlyLeakedCb>(
MarkIndirectlyLeakedCb const &callback);
template void ForEachChunk<CollectSuppressedCb>(
CollectSuppressedCb const &callback);
+
+IgnoreObjectResult IgnoreObjectLocked(const void *p) {
+ void *chunk = allocator.GetBlockBegin(p);
+ if (!chunk || p < chunk) return kIgnoreObjectInvalid;
+ ChunkMetadata *m = Metadata(chunk);
+ CHECK(m);
+ if (m->allocated && (uptr)p < (uptr)chunk + m->requested_size) {
+ if (m->tag == kSuppressed)
+ return kIgnoreObjectAlreadyIgnored;
+ m->tag = kSuppressed;
+ return kIgnoreObjectSuccess;
+ } else {
+ return kIgnoreObjectInvalid;
+ }
+}
} // namespace __lsan
extern "C" {
diff --git a/lib/lsan/lsan_common.cc b/lib/lsan/lsan_common.cc
index ae1fca740..a42469b03 100644
--- a/lib/lsan/lsan_common.cc
+++ b/lib/lsan/lsan_common.cc
@@ -23,6 +23,9 @@
#if CAN_SANITIZE_LEAKS
namespace __lsan {
+// This mutex is used to prevent races between DoLeakCheck and SuppressObject.
+BlockingMutex global_mutex(LINKER_INITIALIZED);
+
Flags lsan_flags;
static void InitializeFlags() {
@@ -37,6 +40,7 @@ static void InitializeFlags() {
f->use_stacks = true;
f->use_tls = true;
f->use_unaligned = false;
+ f->verbosity = 0;
f->log_pointers = false;
f->log_threads = false;
@@ -52,6 +56,7 @@ static void InitializeFlags() {
CHECK_GE(&f->resolution, 0);
ParseFlag(options, &f->max_leaks, "max_leaks");
CHECK_GE(&f->max_leaks, 0);
+ ParseFlag(options, &f->verbosity, "verbosity");
ParseFlag(options, &f->log_pointers, "log_pointers");
ParseFlag(options, &f->log_threads, "log_threads");
ParseFlag(options, &f->exitcode, "exitcode");
@@ -311,12 +316,13 @@ static void DoLeakCheckCallback(const SuspendedThreadsList &suspended_threads,
}
void DoLeakCheck() {
+ BlockingMutexLock l(&global_mutex);
static bool already_done;
+ CHECK(!already_done);
+ already_done = true;
LeakCheckResult result = kFatalError;
LockThreadRegistry();
LockAllocator();
- CHECK(!already_done);
- already_done = true;
StopTheWorld(DoLeakCheckCallback, &result);
UnlockAllocator();
UnlockThreadRegistry();
@@ -394,4 +400,22 @@ void LeakReport::PrintSummary() {
}
} // namespace __lsan
+
+using namespace __lsan; // NOLINT
+
+extern "C" {
+void __lsan_ignore_object(const void *p) {
+ // Cannot use PointsIntoChunk or LsanMetadata here, since the allocator is not
+ // locked.
+ BlockingMutexLock l(&global_mutex);
+ IgnoreObjectResult res = IgnoreObjectLocked(p);
+ if (res == kIgnoreObjectInvalid && flags()->verbosity >= 1)
+ Report("__lsan_ignore_object(): no heap object found at %p", p);
+ if (res == kIgnoreObjectAlreadyIgnored && flags()->verbosity >= 1)
+ Report("__lsan_ignore_object(): "
+ "heap object at %p is already being ignored\n", p);
+ if (res == kIgnoreObjectSuccess && flags()->verbosity >= 2)
+ Report("__lsan_ignore_object(): ignoring heap object at %p\n", p);
+}
+} // extern "C"
#endif // CAN_SANITIZE_LEAKS
diff --git a/lib/lsan/lsan_common.h b/lib/lsan/lsan_common.h
index 18660ba68..4cd6082be 100644
--- a/lib/lsan/lsan_common.h
+++ b/lib/lsan/lsan_common.h
@@ -64,6 +64,9 @@ struct Flags {
// Consider unaligned pointers valid.
bool use_unaligned;
+ // User-visible verbosity.
+ int verbosity;
+
// Debug logging.
bool log_pointers;
bool log_threads;
@@ -153,6 +156,12 @@ class CollectSuppressedCb {
InternalVector<uptr> *frontier_;
};
+enum IgnoreObjectResult {
+ kIgnoreObjectSuccess,
+ kIgnoreObjectAlreadyIgnored,
+ kIgnoreObjectInvalid
+};
+
// The following must be implemented in the parent tool.
template<typename Callable> void ForEachChunk(Callable const &callback);
@@ -172,6 +181,8 @@ bool GetThreadRangesLocked(uptr os_id, uptr *stack_begin, uptr *stack_end,
void *PointsIntoChunk(void *p);
// Return address of user-visible chunk contained in this allocator chunk.
void *GetUserBegin(void *p);
+// Helper for __lsan_ignore_object().
+IgnoreObjectResult IgnoreObjectLocked(const void *p);
// Wrapper for chunk metadata operations.
class LsanMetadata {
public:
diff --git a/lib/sanitizer_common/sanitizer_allocator.h b/lib/sanitizer_common/sanitizer_allocator.h
index d24f42b3c..6d61392f9 100644
--- a/lib/sanitizer_common/sanitizer_allocator.h
+++ b/lib/sanitizer_common/sanitizer_allocator.h
@@ -344,15 +344,15 @@ class SizeClassAllocator64 {
region->n_freed += b->count;
}
- static bool PointerIsMine(void *p) {
+ static bool PointerIsMine(const void *p) {
return reinterpret_cast<uptr>(p) / kSpaceSize == kSpaceBeg / kSpaceSize;
}
- static uptr GetSizeClass(void *p) {
+ static uptr GetSizeClass(const void *p) {
return (reinterpret_cast<uptr>(p) / kRegionSize) % kNumClassesRounded;
}
- void *GetBlockBegin(void *p) {
+ void *GetBlockBegin(const void *p) {
uptr class_id = GetSizeClass(p);
uptr size = SizeClassMap::Size(class_id);
if (!size) return 0;
@@ -671,15 +671,15 @@ class SizeClassAllocator32 {
sci->free_list.push_front(b);
}
- bool PointerIsMine(void *p) {
+ bool PointerIsMine(const void *p) {
return GetSizeClass(p) != 0;
}
- uptr GetSizeClass(void *p) {
+ uptr GetSizeClass(const void *p) {
return possible_regions[ComputeRegionId(reinterpret_cast<uptr>(p))];
}
- void *GetBlockBegin(void *p) {
+ void *GetBlockBegin(const void *p) {
CHECK(PointerIsMine(p));
uptr mem = reinterpret_cast<uptr>(p);
uptr beg = ComputeRegionBeg(mem);
@@ -1006,7 +1006,7 @@ class LargeMmapAllocator {
return res;
}
- bool PointerIsMine(void *p) {
+ bool PointerIsMine(const void *p) {
return GetBlockBegin(p) != 0;
}
@@ -1021,7 +1021,7 @@ class LargeMmapAllocator {
return GetHeader(p) + 1;
}
- void *GetBlockBegin(void *ptr) {
+ void *GetBlockBegin(const void *ptr) {
uptr p = reinterpret_cast<uptr>(ptr);
SpinMutexLock l(&mutex_);
uptr nearest_chunk = 0;
@@ -1231,7 +1231,7 @@ class CombinedAllocator {
return secondary_.GetMetaData(p);
}
- void *GetBlockBegin(void *p) {
+ void *GetBlockBegin(const void *p) {
if (primary_.PointerIsMine(p))
return primary_.GetBlockBegin(p);
return secondary_.GetBlockBegin(p);