diff mbox

LeakSanitizer testsuite

Message ID 532A892E.4010607@partner.samsung.com
State New
Headers show

Commit Message

max March 20, 2014, 6:22 a.m. UTC
Hi,

This patch adds initial set of tests and dg infrastructure for 
LeakSanitizer runtime.

Tested on x86_64.

Ok to commit?

-Maxim

Comments

Jakub Jelinek March 24, 2014, 8:50 a.m. UTC | #1
On Thu, Mar 20, 2014 at 10:22:38AM +0400, Maxim Ostapenko wrote:
> Hi,
> 
> This patch adds initial set of tests and dg infrastructure for
> LeakSanitizer runtime.
> 
> Tested on x86_64.
> 
> Ok to commit?

Are you sure we need testsuite for something that doesn't have a GCC code
generation counterpart at all?

In any case, this isn't ok for 4.9, we are too late in the development
cycle.
Perhaps it could go in during next stage1.

> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/lsan/fork.c
> @@ -0,0 +1,23 @@
> +// Test that thread local data is handled correctly after forking without exec().
> +/* { dg-do run } */
> +
> +#include <assert.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <sys/wait.h>
> +#include <unistd.h>
> +
> +__thread void *thread_local_var;

Tests using __thread should be guarded with
/* { dg-require-effective-target tls_runtime } */
Also, I wonder given the explicit uses of unistd.h if you just shouldn't
limit the test to *-*-linux* or similar.
> --- /dev/null
> +++ b/gcc/testsuite/c-c++-common/lsan/swapcontext-1.c
> @@ -0,0 +1,41 @@
> +// We can't unwind stack if we're running coroutines on heap-allocated
> +// memory. Make sure we don't report these leaks.

I guess setcontext/getcontext/makecontext is supported even on far fewer
OSes.

	Jakub
Yury Gribov April 11, 2014, 6:03 a.m. UTC | #2
>> This patch adds initial set of tests and dg infrastructure for
>> LeakSanitizer runtime.
>
> Are you sure we need testsuite for something that doesn't have a GCC code
> generation counterpart at all?

That's a valid point. We want this in GCC because
(1) support for cross-compilation and cross-testing in LLVM LSan is much 
less mature so testing non-Intel platforms there is PITA
(2) even though the code itself is the same, the build scripts and 
compilation options are GCC-specific so it may make sense to test these

As an example of (2), we currently don't have lsan_preinit.o so static 
liblsan isn't fully supported.

We understand that these may or may not be important for other community 
members so will accept either decision.

-Y
diff mbox

Patch

2014-03-20  Max Ostapenko  <m.ostapenko@partner.samsung.com>

	* c-c++-common/lsan/fork.c: New test.
	* c-c++-common/lsan/ignore_object.c: New test.
	* c-c++-common/lsan/ignore_object_errors.c: New test.
	* c-c++-common/lsan/large_allocation_leak.c: New test.
	* c-c++-common/lsan/leak_check_at_exit-1.c: New test.
	* c-c++-common/lsan/leak_check_at_exit-2.c: New test.
	* c-c++-common/lsan/link_turned_off.c: New test.
	* c-c++-common/lsan/pointer_to_self.c: New test.
	* c-c++-common/lsan/suppressions_default.c: New test.
	* c-c++-common/lsan/swapcontext-1.c: New test.
	* c-c++-common/lsan/swapcontext-2.c: New test.
	* c-c++-common/lsan/use_after_return.c: New test.
	* c-c++-common/lsan/use_globals_initialized.c: New test.
	* c-c++-common/lsan/use_globals_uninitialized.c: New test.
	* c-c++-common/lsan/use_stacks.c: New test.
	* c-c++-common/lsan/use_tls_static.c: New test.
	* c-c++-common/lsan/use_unaligned.c: New test.
	* g++.dg/lsan/lsan.exp: New file.
	* gcc.dg/lsan/lsan.exp: New file.
	* lib/lsan-dg.exp: New file.


diff --git a/gcc/testsuite/c-c++-common/lsan/fork.c b/gcc/testsuite/c-c++-common/lsan/fork.c
new file mode 100644
index 0000000..4dc9d4b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/fork.c
@@ -0,0 +1,23 @@ 
+// Test that thread local data is handled correctly after forking without exec().
+/* { dg-do run } */
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+__thread void *thread_local_var;
+
+int main() {
+  int status = 0;
+  thread_local_var = malloc(1337);
+  pid_t pid = fork();
+  assert(pid >= 0);
+  if (pid > 0) {
+    waitpid(pid, &status, 0);
+    assert(WIFEXITED(status));
+    return WEXITSTATUS(status);
+  }
+  return 0;
+}
diff --git a/gcc/testsuite/c-c++-common/lsan/ignore_object.c b/gcc/testsuite/c-c++-common/lsan/ignore_object.c
new file mode 100644
index 0000000..d73f08b
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/ignore_object.c
@@ -0,0 +1,31 @@ 
+// Test for __lsan_ignore_object().
+
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_registers=0:use_stacks=0:use_globals=0:use_tls=0:verbosity=3" } */
+/* { dg-set-target-env-var ASAN_OPTIONS "verbosity=3" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void __lsan_ignore_object(void **p);
+
+int main() {
+  // Explicitly ignored object.
+  void **p = (void **) malloc(sizeof(void **));
+  // Transitively ignored object.
+  *p = malloc(666);
+  // Non-ignored object.
+  volatile void *q = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", p);
+  fprintf(stderr, "Test alloc_2: %p.\n", q);
+  __lsan_ignore_object(p);
+  return 0;
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "ignoring heap object at .*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer: 1337 byte\\(s\\) leaked in 1 allocation\\(s\\).*" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/ignore_object_errors.c b/gcc/testsuite/c-c++-common/lsan/ignore_object_errors.c
new file mode 100644
index 0000000..47a1cd1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/ignore_object_errors.c
@@ -0,0 +1,25 @@ 
+// Test for incorrect use of __lsan_ignore_object().
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "verbosity=2" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void __lsan_ignore_object(const void *p);
+
+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;
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "heap object at .* is already being ignored.*" } */
+/* { dg-output "no heap object found at .*" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/large_allocation_leak.c b/gcc/testsuite/c-c++-common/lsan/large_allocation_leak.c
new file mode 100644
index 0000000..36511d3
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/large_allocation_leak.c
@@ -0,0 +1,19 @@ 
+// Test that LargeMmapAllocator's chunks aren't reachable via some internal data structure.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  // maxsize in primary allocator is always less than this (1 << 25).
+  void *large_alloc = malloc(33554432);
+  fprintf(stderr, "Test alloc: %p.\n", large_alloc);
+  return 0;
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 33554432 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/leak_check_at_exit-1.c b/gcc/testsuite/c-c++-common/lsan/leak_check_at_exit-1.c
new file mode 100644
index 0000000..443a2b1
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/leak_check_at_exit-1.c
@@ -0,0 +1,24 @@ 
+// Test for the leak_check_at_exit flag.
+
+/* { dg-do run } */
+/* { dg-options "-fno-builtin" } */
+/* { dg-set-target-env-var LSAN_OPTIONS "verbosity=1:leak_check_at_exit=0" } */
+/* { dg-set-target-env-var ASAN_OPTIONS "leak_check_at_exit=0" } */
+
+#ifdef __cplusplus
+extern "C"
+#endif
+void __lsan_do_leak_check();
+
+#ifdef __cplusplus
+extern "C"
+#endif
+int printf (const char *format, ...);
+
+int main(int argc, char *argv[]) {
+  printf("printf to break optimization\n");
+  __lsan_do_leak_check();
+  return 0;
+}
+
+/* { dg-output "(Leak|Address)Sanitizer: .*" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/leak_check_at_exit-2.c b/gcc/testsuite/c-c++-common/lsan/leak_check_at_exit-2.c
new file mode 100644
index 0000000..8ae955a
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/leak_check_at_exit-2.c
@@ -0,0 +1,18 @@ 
+// Test for the leak_check_at_exit flag.
+
+/* { dg-do run } */
+/* { dg-options "-fno-builtin" } */
+/* { dg-set-target-env-var LSAN_OPTIONS "verbosity=1:leak_check_at_exit=0" } */
+/* { dg-set-target-env-var ASAN_OPTIONS "leak_check_at_exit=0" } */
+
+#ifdef __cplusplus
+extern "C"
+#endif
+int printf (const char *format, ...);
+
+int main(int argc, char *argv[]) {
+  printf("printf to break optimization\n");
+  return 0;
+}
+
+/* { dg-prune-output "(Leak|Address)Sanitizer: .*" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/link_turned_off.c b/gcc/testsuite/c-c++-common/lsan/link_turned_off.c
new file mode 100644
index 0000000..9e7e428
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/link_turned_off.c
@@ -0,0 +1,21 @@ 
+// Test for disabling LSan at link-time.
+
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "use_stacks=0:use_registers=0" } */
+
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C"
+#endif
+int __lsan_is_turned_off() {
+  return 1;
+}
+
+int main(int argc, char *argv[]) {
+  volatile int *x = (volatile int *) malloc(sizeof(int));
+  *x = 42;
+  return 0;
+}
+
+/* { dg-prune-output "SUMMARY: (Leak|Address)Sanitizer: 4 byte\\(s\\) leaked in 1 allocation\\(s\\)" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/pointer_to_self.c b/gcc/testsuite/c-c++-common/lsan/pointer_to_self.c
new file mode 100644
index 0000000..5cfa5a4
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/pointer_to_self.c
@@ -0,0 +1,18 @@ 
+// Regression test: pointers to self should not confuse LSan into thinking the
+// object is indirectly leaked. Only external pointers count.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  void *p = malloc(1337);
+  *((void **) p) = p;
+  fprintf(stderr, "Test alloc: %p.\n", p);
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/suppressions_default.c b/gcc/testsuite/c-c++-common/lsan/suppressions_default.c
new file mode 100644
index 0000000..8d41e75
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/suppressions_default.c
@@ -0,0 +1,29 @@ 
+// Test for ScopedDisabler.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "use_stacks=0:use_registers=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef __cplusplus
+extern "C"
+#endif
+const char *__lsan_default_suppressions() {
+  return "leak:*LSanTestLeakingFunc*";
+}
+
+void LSanTestLeakingFunc() {
+  void *p = malloc(666);
+  fprintf(stderr, "Test alloc: %p.\n", p);
+}
+
+int main() {
+  LSanTestLeakingFunc();
+  void *q = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", q);
+  return 0;
+}
+/* { dg-output "Suppressions used:.*" } */
+/* { dg-output "1.*666 \\*LSanTestLeakingFunc\\*.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer: 1337 byte\\(s\\) leaked in 1 allocation\\(s\\).*" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/swapcontext-1.c b/gcc/testsuite/c-c++-common/lsan/swapcontext-1.c
new file mode 100644
index 0000000..e16ef15
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/swapcontext-1.c
@@ -0,0 +1,41 @@ 
+// We can't unwind stack if we're running coroutines on heap-allocated
+// memory. Make sure we don't report these leaks.
+
+/* { dg-do run } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <ucontext.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+const int kStackSize = 1 << 20;
+
+void Child() {
+  int child_stack;
+  printf("Child: %p\n", &child_stack);
+  volatile int *leaked = (volatile int *) malloc(666 * sizeof(int));
+  leaked[0] = 1;
+  printf("leaked[0] = %d\n", leaked[0]);
+}
+
+int main(int argc, char *argv[]) {
+  char stack_memory[kStackSize + 1];
+  char *child_stack = stack_memory;
+
+  printf("Child stack: %p\n", child_stack);
+  ucontext_t orig_context;
+  ucontext_t child_context;
+  getcontext(&child_context);
+  child_context.uc_stack.ss_sp = child_stack;
+  child_context.uc_stack.ss_size = kStackSize / 2;
+  child_context.uc_link = &orig_context;
+  makecontext(&child_context, Child, 0);
+  if (swapcontext(&orig_context, &child_context) < 0) {
+    perror("swapcontext");
+    return 1;
+  }
+  return 0;
+}
+
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer: 2664 byte\\(s\\) leaked in 1 allocation\\(s\\)" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/swapcontext-2.c b/gcc/testsuite/c-c++-common/lsan/swapcontext-2.c
new file mode 100644
index 0000000..9b9bcd6
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/swapcontext-2.c
@@ -0,0 +1,39 @@ 
+// We can't unwind stack if we're running coroutines on heap-allocated
+// memory. Make sure we don't report these leaks.
+
+/* { dg-do run } */
+
+#include <stdio.h>
+#include <ucontext.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+const int kStackSize = 1 << 20;
+
+void Child() {
+  int child_stack;
+  printf("Child: %p\n", &child_stack);
+  int *leaked = (int *) malloc(666 * sizeof(int));
+}
+
+int main(int argc, char *argv[]) {
+  char *heap_memory = (char *) malloc(kStackSize + 1);
+  char *child_stack = heap_memory;
+
+  printf("Child stack: %p\n", child_stack);
+  ucontext_t orig_context;
+  ucontext_t child_context;
+  getcontext(&child_context);
+  child_context.uc_stack.ss_sp = child_stack;
+  child_context.uc_stack.ss_size = kStackSize / 2;
+  child_context.uc_link = &orig_context;
+  makecontext(&child_context, Child, 0);
+  if (swapcontext(&orig_context, &child_context) < 0) {
+    perror("swapcontext");
+    return 1;
+  }
+  free(heap_memory);
+  return 0;
+}
+
+/* { dg-prune-output "SUMMARY: (Leak|Address)Sanitizer: 2664 byte\\(s\\) leaked in 1 allocation\\(s\\)" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/use_after_return.c b/gcc/testsuite/c-c++-common/lsan/use_after_return.c
new file mode 100644
index 0000000..9373e48
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/use_after_return.c
@@ -0,0 +1,22 @@ 
+// Test that fake stack (introduced by ASan's use-after-return mode) is included
+// in the root set.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0" } */
+/* { dg-set-target-env-var ASAN_OPTIONS "detect_stack_use_after_return=1" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  void *stack_var = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", stack_var);
+  // Take pointer to variable, to ensure it's not optimized into a register.
+  fprintf(stderr, "Stack var at: %p.\n", &stack_var);
+  // Do not return from main to prevent the pointer from going out of scope.
+  exit(0);
+}
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 1337 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/use_globals_initialized.c b/gcc/testsuite/c-c++-common/lsan/use_globals_initialized.c
new file mode 100644
index 0000000..f6647c8
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/use_globals_initialized.c
@@ -0,0 +1,19 @@ 
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0:use_globals=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void *data_var = (void *) 1;
+
+int main() {
+  data_var = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", data_var);
+  return 0;
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 1337 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/use_globals_uninitialized.c b/gcc/testsuite/c-c++-common/lsan/use_globals_uninitialized.c
new file mode 100644
index 0000000..538c546
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/use_globals_uninitialized.c
@@ -0,0 +1,19 @@ 
+// Test that uninitialized globals are included in the root set.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0:use_globals=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+void *bss_var;
+
+int main() {
+  bss_var = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", bss_var);
+  return 0;
+}
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 1337 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/use_stacks.c b/gcc/testsuite/c-c++-common/lsan/use_stacks.c
new file mode 100644
index 0000000..abf0aee
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/use_stacks.c
@@ -0,0 +1,19 @@ 
+// Test that stack of main thread is included in the root set.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+int main() {
+  void *stack_var = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", stack_var);
+  // Do not return from main to prevent the pointer from going out of scope.
+  exit(0);
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 1337 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/use_tls_static.c b/gcc/testsuite/c-c++-common/lsan/use_tls_static.c
new file mode 100644
index 0000000..dd27883
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/use_tls_static.c
@@ -0,0 +1,20 @@ 
+// Test that statically allocated TLS space is included in the root set.
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0:use_tls=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+__thread void *tls_var;
+
+int main() {
+  tls_var = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", tls_var);
+  return 0;
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 1337 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/c-c++-common/lsan/use_unaligned.c b/gcc/testsuite/c-c++-common/lsan/use_unaligned.c
new file mode 100644
index 0000000..1aff6a9
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/lsan/use_unaligned.c
@@ -0,0 +1,24 @@ 
+// Test that unaligned pointers are detected correctly.
+
+/* { dg-do run } */
+/* { dg-set-target-env-var LSAN_OPTIONS "report_objects=1:use_stacks=0:use_registers=0:use_unaligned=0" } */
+/* { dg-shouldfail "lsan" } */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+void *arr[2];
+
+int main() {
+  void *p = malloc(1337);
+  fprintf(stderr, "Test alloc: %p.\n", p);
+  char *char_arr = (char *)arr;
+  memcpy(char_arr + 1, &p, sizeof(p));
+  return 0;
+}
+
+/* { dg-output "Test alloc: .*" } */
+/* { dg-output "Directly leaked 1337 byte object at .*" } */
+/* { dg-output "LeakSanitizer: detected memory leaks.*" } */
+/* { dg-output "SUMMARY: (Leak|Address)Sanitizer" } */
diff --git a/gcc/testsuite/g++.dg/lsan/lsan.exp b/gcc/testsuite/g++.dg/lsan/lsan.exp
new file mode 100644
index 0000000..f531a44
--- /dev/null
+++ b/gcc/testsuite/g++.dg/lsan/lsan.exp
@@ -0,0 +1,38 @@ 
+# Copyright (C) 2012-2014 Free Software Foundation, Inc.
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GCC is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Load support procs.
+load_lib g++-dg.exp
+load_lib lsan-dg.exp
+
+if ![check_effective_target_fleak_sanitizer] {
+  return
+}
+
+# Initialize `dg'.
+dg-init
+if [lsan_init] {
+
+# Main loop.
+gcc-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.C $srcdir/c-c++-common/lsan/*.c]] ""
+
+}
+
+# All done.
+lsan_finish
+dg-finish
diff --git a/gcc/testsuite/gcc.dg/lsan/lsan.exp b/gcc/testsuite/gcc.dg/lsan/lsan.exp
new file mode 100644
index 0000000..d5f5749
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/lsan/lsan.exp
@@ -0,0 +1,40 @@ 
+# Copyright (C) 2012-2014 Free Software Foundation, Inc.
+#
+# This file is part of GCC.
+#
+# GCC is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3, or (at your option)
+# any later version.
+#
+# GCC is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# GCC testsuite that uses the `dg.exp' driver.
+
+# Load support procs.
+load_lib gcc-dg.exp
+load_lib lsan-dg.exp
+
+if ![check_effective_target_fleak_sanitizer] {
+  return
+}
+
+# Initialize `dg'.
+dg-init
+if [lsan_init] {
+
+# Main loop.
+gcc-dg-runtest [lsort [glob -nocomplain $srcdir/$subdir/*.c $srcdir/c-c++-common/lsan/*.c]] ""
+
+}
+
+# All done.
+lsan_finish
+dg-finish
diff --git a/gcc/testsuite/lib/lsan-dg.exp b/gcc/testsuite/lib/lsan-dg.exp
new file mode 100644
index 0000000..143b71c
--- /dev/null
+++ b/gcc/testsuite/lib/lsan-dg.exp
@@ -0,0 +1,113 @@ 
+# Copyright (C) 2012-2014 Free Software Foundation, Inc.
+
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GCC; see the file COPYING3.  If not see
+# <http://www.gnu.org/licenses/>.
+
+# Return 1 if compilation with -fsanitize=leak is error-free for trivial
+# code, 0 otherwise.
+
+proc check_effective_target_fleak_sanitizer {} {
+    return [check_no_compiler_messages fleak_sanitizer object {
+	void foo (void) { }
+    } "-fsanitize=leak"]
+}
+
+#
+# lsan_link_flags -- compute library path and flags to find liblsan.
+# (originally from g++.exp)
+#
+
+proc lsan_link_flags { paths } {
+    global srcdir
+    global ld_library_path
+    global shlib_ext
+
+    set gccpath ${paths}
+    set flags ""
+
+    set shlib_ext [get_shlib_extension]
+
+    if { $gccpath != "" } {
+      if { [file exists "${gccpath}/libsanitizer/lsan/.libs/liblsan.a"]
+	   || [file exists "${gccpath}/libsanitizer/lsan/.libs/liblsan.${shlib_ext}"] } {
+	  append flags " -B${gccpath}/libsanitizer/ "
+	  append flags " -B${gccpath}/libsanitizer/lsan/ "
+	  append flags " -L${gccpath}/libsanitizer/lsan/.libs "
+	  append ld_library_path ":${gccpath}/libsanitizer/lsan/.libs"
+      }
+    } else {
+      global tool_root_dir
+
+      set liblsan [lookfor_file ${tool_root_dir} liblsan]
+      if { $liblsan != "" } {
+	  append flags "-L${liblsan} "
+	  append ld_library_path ":${liblsan}"
+      }
+    }
+    set_ld_library_path_env_vars
+    return "$flags"
+}
+
+#
+# lsan_init -- called at the start of each subdir of tests
+#
+
+proc lsan_init { args } {
+    global TEST_ALWAYS_FLAGS
+    global ALWAYS_CXXFLAGS
+    global TOOL_OPTIONS
+    global lsan_saved_TEST_ALWAYS_FLAGS
+    set link_flags ""
+    if ![is_remote host] {
+	if [info exists TOOL_OPTIONS] {
+	    set link_flags "[lsan_link_flags [get_multilibs ${TOOL_OPTIONS}]]"
+	} else {
+	    set link_flags "[lsan_link_flags [get_multilibs]]"
+	}
+    }
+
+    if [info exists TEST_ALWAYS_FLAGS] {
+	set lsan_saved_TEST_ALWAYS_FLAGS $TEST_ALWAYS_FLAGS
+    }
+    if [info exists ALWAYS_CXXFLAGS] {
+	set ALWAYS_CXXFLAGS [concat "{ldflags=$link_flags}" $ALWAYS_CXXFLAGS]
+	set ALWAYS_CXXFLAGS [concat "{additional_flags=-fsanitize=leak -g}" $ALWAYS_CXXFLAGS]
+    } else {
+	if [info exists TEST_ALWAYS_FLAGS] {
+	    set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=leak -g $TEST_ALWAYS_FLAGS"
+	} else {
+	    set TEST_ALWAYS_FLAGS "$link_flags -fsanitize=leak -g"
+	}
+    }
+    if { $link_flags != "" } {
+	return 1
+    }
+    return 0
+}
+
+#
+# lsan_finish -- called at the start of each subdir of tests
+#
+
+proc lsan_finish { args } {
+    global TEST_ALWAYS_FLAGS
+    global lsan_saved_TEST_ALWAYS_FLAGS
+
+    if [info exists lsan_saved_TEST_ALWAYS_FLAGS] {
+	set TEST_ALWAYS_FLAGS $lsan_saved_TEST_ALWAYS_FLAGS
+    } else {
+	unset TEST_ALWAYS_FLAGS
+    }
+}
+