Search code examples
cmemory-leaksclangaddress-sanitizerleak-sanitizer

Clang LeakSanitizer memory leak detection with optimized code


1.Background

  • Let's consider a C file a.c with following content as a memory leak MRE:
#include <stdlib.h>
void Foo() { malloc(1); }
int main() { Foo();  return 0;}
  • On one hand, as expected, Clang LeakSanitizer (which is integrated into Clang AddresSanitizer) detects the memory leak if the following command is run:
$ clang++ -g -fsanitize=address a.c; ./a.out

=================================================================
==...==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 1 byte(s) in 1 object(s) allocated from:
    #0 0x... in malloc (/.../a.out+0xa114e) (BuildId: ...)
    #1 0x... in Baz() (/.../a.c:2:14
    #2 0x... in main (/.../a.c:3:14
    #3 0x... in __libc_start_call_main csu/../sysdeps/nptl/libc_start_call_main.h:58:16

SUMMARY: AddressSanitizer: 1 byte(s) leaked in 1 allocation(s).
  • On the other hand, Clang AddressSanitizer documentation Usage section mentions:

To get a reasonable performance add -O1 or higher.

2.Concern

  • Clang LeakSanitizer does not detect the memory leak if the following command is run (the code generation option -O1 has been added):
$ clang++ -O1 -g -fsanitize=address a.c; ./a.out

3.Question

Can memory leak still be detected by Clang LeakSanitizer when code optimization level 1 or higher is used?

  • If the answer is yes, please provide a minimal reproducible example (MRE) proving it.

  • If the answer is no, please explain why (link to Clang compiler source code, ...).

4.Remarks

  • Clang LeakSanitizer documentation Usage section does not explicitly mention that code optimization shall not be used.

  • Above observations were made with version 18 of Clang, installed on Linux Ubuntu 22.04.3 using LLVM automatic installation script.

  • As explained by user17732522 in a comment, the a.c file example in the Background section does not lead to memory leak detection when the -O1 option is used because:

The optimized program won't have any actual memory leak. The malloc call will be optimized away since it doesn't affect observable behavior


Solution

  • Your minimal example is too minimal -- clang was able to optimize everything away from it:

    (gdb) disas main
    Dump of assembler code for function main:
       0x0000000000401120 <+0>:     xor    %eax,%eax
       0x0000000000401122 <+2>:     ret
    

    As you can see, none of Foo or malloc remain.

    To make it a bit more realistic, try this:

    #include <stdlib.h>
    #include <stdio.h>
    
    void* Foo() { return malloc(1); }
    void Bar(void *p) { printf("p = %p\n", p); }
    int main() { Bar(Foo());  return 0;}
    
    clang -g -O0 -fsanitize=address a1.c && ./a.out
    p = 0x502000000010
    
    =================================================================
    ==191==ERROR: LeakSanitizer: detected memory leaks
    
    Direct leak of 1 byte(s) in 1 object(s) allocated from:
        #0 0x4c8822 in malloc (/tmp/a.out+0x4c8822) (BuildId: a27851507d14179bca1fa40aa114a3e5ffa99c23)
        #1 0x50644d in Foo /tmp/a1.c:4:22
        #2 0x506493 in main /tmp/a1.c:6:18
        #3 0x7f9a74195149 in __libc_start_call_main (/lib64/libc.so.6+0x28149) (BuildId: 7ea8d85df0e89b90c63ac7ed2b3578b2e7728756)
        #4 0x7f9a7419520a in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x2820a) (BuildId: 7ea8d85df0e89b90c63ac7ed2b3578b2e7728756)
        #5 0x42a324 in _start (/tmp/a.out+0x42a324) (BuildId: a27851507d14179bca1fa40aa114a3e5ffa99c23)
    
    SUMMARY: AddressSanitizer: 1 byte(s) leaked in 1 allocation(s).
    

    And now with -O1:

    clang -g -O1 -fsanitize=address a1.c && ./a.out
    p = 0x502000000010
    
    =================================================================
    ==197==ERROR: LeakSanitizer: detected memory leaks
    
    Direct leak of 1 byte(s) in 1 object(s) allocated from:
        #0 0x4c8822 in malloc (/tmp/a.out+0x4c8822) (BuildId: 1ffaa6630367e5a8f0a95663181cd0d01efdc513)
        #1 0x50646a in Foo /tmp/a1.c:4:22
        #2 0x50646a in main /tmp/a1.c:6:18
        #3 0x7f804f1c020a in __libc_start_main@GLIBC_2.2.5 (/lib64/libc.so.6+0x2820a) (BuildId: 7ea8d85df0e89b90c63ac7ed2b3578b2e7728756)
        #4 0x42a324 in _start (/tmp/a.out+0x42a324) (BuildId: 1ffaa6630367e5a8f0a95663181cd0d01efdc513)
    
    SUMMARY: AddressSanitizer: 1 byte(s) leaked in 1 allocation(s).