Issue |
143129
|
Summary |
[Sema] Memory leak in `SemaOverload.cpp`
|
Labels |
new issue
|
Assignees |
|
Reporter |
lux-QAQ
|
### **Description**
Hello, I've observed a memory leak in `SemaOverload.cpp` that occurs during template overload resolution.
My investigation **suggests a possible root cause** related to the lifecycle of `DeductionFailureInfo` objects. **It appears that** these objects, created to store details about why a template deduction failed, are allocated using placement `new (Context)` but are not explicitly deallocated when their owning `OverloadCandidate` is destroyed.
**If this hypothesis is correct, it could explain why** a definite leak occurs for members that perform their own heap allocations, such as the `SmallVector` within `clang::ConstraintSatisfaction`. The destructor for `ConstraintSatisfaction` **would presumably not be called**, which would in turn orphan its internal buffer, matching the behavior shown in the ASan report.
---
### **ASan Report**
[build2.log](https://github.com/user-attachments/files/20628340/build2.log)
[build1.log](https://github.com/user-attachments/files/20628341/build1.log)
This ASan report clearly shows the leak originating from `MakeDeductionFailureInfo` during the handling of unsatisfied constraints.
```cpp
==386608==ERROR: LeakSanitizer: detected memory leaks
Direct leak of 7992 byte(s) in 37 object(s) allocated from:
#0 0x55c8fab23dc3 in malloc (...)
#1 0x7f81bb63dbcb in safe_malloc (...)
#2 0x7f81bb63dbcb in llvm::SmallVectorBase<unsigned int>::grow_pod(...)
#3 ...
#7 0x7f81c86b806f in clang::ConstraintSatisfaction::operator=(...) /home/****/llvminstall/clang/include/clang/AST/ASTConcept.h:36:7
#8 0x7f81c942c8fe in clang::MakeDeductionFailureInfo(clang::ASTContext&, ...) /home/****/llvminstall/clang/lib/Sema/SemaOverload.cpp:804:25
#9 0x7f81c9952e56 in getPatternForClassTemplateSpecialization (...)
#10 0x7f81c9952e56 in clang::Sema::InstantiateClassTemplateSpecialization(...)
...
```
---
### **Trigger Context and Build Failure**
The ASan reports are generated during the build process itself, specifically when the newly-built Clang attempts to compile tests for `libcxx`. The sequence of events is as follows:
1. `ninja` starts executing the compilation command for a specific test file, for instance:
```sh
[1980/1983] Building CXX object libcxx/test/tools/clang_tidy_checks/CMakeFiles/cxx-tidy.dir/hide_from_abi.cpp.o
/home/****/code/llvmleak/llvm-project/build/bin/clang++ --target=x86_64-pc-linux-gnu ... -c .../hide_from_abi.cpp
```
2. Upon completion of the `clang++` process, LeakSanitizer (linked into `clang++`) runs its analysis and **prints the memory leak report** to the console first.
3. After the ASan report is printed, `ninja` detects that the `clang++` subcommand has terminated with a non-zero (failure) exit code, and **then prints its `FAILED` message**:
```
FAILED: libcxx/test/tools/clang_tidy_checks/CMakeFiles/cxx-tidy.dir/hide_from_abi.cpp.o
```
4. These repeated failures cause the entire `ninja` build to halt with a final status:
```
ninja: build stopped: subcommand failed.
FAILED: runtimes/runtimes-stamps/runtimes-build /home/****/code/llvmleak/llvm-project/build/runtimes/runtimes-stamps/runtimes-build
cd /home/****/code/llvmleak/llvm-project/build/runtimes/runtimes-bins && /usr/local/bin/cmake --build .
```
---
### **Code Analysis**
[clang/lib/Sema/SemaOverload.cpp](https://github.com/llvm/llvm-project/blob/main/clang/lib/Sema/SemaOverload.cpp)
The issue appears to be a memory management mismatch. Allocation happens via the `ASTContext` arena in `MakeDeductionFailureInfo`, as noted by the `FIXME`:
*File: `clang/lib/Sema/SemaOverload.cpp`*
```cpp
//...
case TemplateDeductionResult::DeducedMismatch:
case TemplateDeductionResult::DeducedMismatchNested: {
// FIXME: Should allocate from normal heap so that we can free this later.
auto *Saved = new (Context) DFIDeducedMismatchArgs; // <--- Allocation
//...
```
But the corresponding `Destroy` method is a no-op, which also contains a `FIXME`:
*File: `clang/lib/Sema/SemaOverload.cpp`*
```cpp
void DeductionFailureInfo::Destroy() {
switch (static_cast<TemplateDeductionResult>(Result)) {
//...
case TemplateDeductionResult::ConstraintsNotSatisfied:
// FIXME: Destroy the data?
Data = "" // <--- No deallocation
break;
//...
}
}
```
---
### **Questions Regarding a Fix and CI**
I would like to contribute a PR to fix this and have two questions:
**1. Fixme:** The `FIXME` comments suggest using the normal heap. I could change `new (Context)` to a standard `new` and add a corresponding `delete` in `Destroy()`. However, I am concerned about performance implications and consistency with Clang's memory management model. What would be the architecturally correct approach?
**2. CI Coverage:** I am trying to determine if this is an issue specific to my local build environment, or if it indicates a potential gap in the CI's test coverage. The leak is consistently reproducible for me with the configuration below. Given this, I am wondering why the leak might not have been detected by the official CI pipelines.
---
### **Build Configuration**
Here is the full CMake command I used to configure the build that produced the ASan reports:
```cmake
cmake -G "Ninja" \
-S /home/****/code/llvmleak/llvm-project/llvm \
-B /home/****/code/llvmleak/llvm-project/build \
-DCMAKE_INSTALL_PREFIX=$HOME/ollvm \
-DCMAKE_INSTALL_RPATH=$HOME/ollvm/lib \
-DLLVM_ENABLE_PROJECTS="bolt;clang;clang-tools-extra;cross-project-tests;libclc;lld;lldb;mlir;pstl" \
-DLLVM_ENABLE_RUNTIMES="all" \
-DLLVM_ENABLE_Z3_SOLVER=ON \
-DLLVM_FORCE_BUILD_RUNTIME=ON \
-DCMAKE_C_COMPILER=/usr/bin/clang \
-DCMAKE_CXX_COMPILER=/usr/bin/clang++ \
-DCMAKE_CXX_COMPILER_TARGET=x86_64-pc-linux-gnu \
-DCMAKE_C_COMPILER_TARGET=x86_64-pc-linux-gnu \
-DLLVM_DEFAULT_TARGET_TRIPLE=x86_64-pc-linux-gnu \
-DCMAKE_CXX_FLAGS="-O3 -march=native" \
-DCMAKE_C_FLAGS="-O3 -march=native" \
-DCMAKE_LINKER=/usr/bin/ld.lld \
-DLLVM_LIBC_FULL_BUILD=ON \
-DLLVM_ENABLE_LLD=ON \
-DLIBCXX_USE_COMPILER_RT=ON \
-DLIBCXXABI_USE_COMPILER_RT=ON \
-DLIBCXXABI_ENABLE_STATIC_UNWINDER=ON \
-DLLVM_PROFILE_GENERATE=OFF \
-DLIBCXX_INSTALL_MODULES=ON \
-DCMAKE_AR=/usr/bin/llvm-ar \
-DCMAKE_RANLIB=/usr/bin/llvm-ranlib \
-DLLVM_ENABLE_OPENMP=ON \
-DLLVM_ENABLE_LIBUNWIND=ON \
-DBOOTSTRAP_LLVM_ENABLE_LTO="Thin" \
-DLLVM_ENABLE_LIBCXXABI=ON \
-DLLVM_BUILD_DOCS=ON \
-DLLVM_ENABLE_DOXYGEN=ON \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_BUILD_LLVM_DYLIB=ON \
-DLLVM_LINK_LLVM_DYLIB=ON \
-DLLVM_ENABLE_RTTI=ON \
-DLLVM_ENABLE_EH=ON \
-DLLVM_ENABLE_EXCEPTIONS=ON \
-DCMAKE_BUILD_WITH_INSTALL_RPATH=TRUE \
-DCMAKE_SHARED_LINKER_FLAGS="-Wl" \
-DLLVM_USE_SANITIZER=Address \
-DLLVM_TARGETS_TO_BUILD="X86"
```
---
### **System Environment**
* **OS:** `Ubuntu 24.04 LTS on Windows 10 x86_64`
* **LLVM Project Version:** `aaec9e5f5b1abb79bda62ca7cb25258acfb1acc3`
* **Host Compiler:**
``` shell
Ubuntu clang version 18.1.3 (1ubuntu1)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```
### **Steps to Reproduce**
1. **Initial Build Attempt:** Configure the build with the CMake command provided above and run `ninja`. The build will likely fail midway through with a `subcommand failed` error, because host tools cannot find newly-built shared libraries.
2. **Set Library Path:** To resolve this initial linking issue, set the dynamic library path to point to the build's output directory.
```bash
export LD_LIBRARY_PATH=/home/****/code/llvmleak/llvm-project/build/lib:$LD_LIBRARY_PATH
```
3. **Resume Build and Trigger Leak:** Run `ninja` again in the same terminal. The build will now proceed to the `runtimes` stage. It is here that the `clang++` compilation commands will first cause ASan to print leak reports, and then cause the build to fail again.
_______________________________________________
llvm-bugs mailing list
llvm-bugs@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs