<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/152898>152898</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
Stack unwinding memory leak.
</td>
</tr>
<tr>
<th>Labels</th>
<td>
new issue
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
ThePatrickCrab
</td>
</tr>
</table>
<pre>
## Problem Statement
Improper destruction order during stack unwinding can cause a constructed object to never be destructed. If the object manages resources, these resources will never be free'd resulting in a memory leak (see [memory leak example](https://godbolt.org/z/zMo37eaK8)). The platform and version that I found this issue on is listed at the bottom of this issue, however the compiler explorer example linked shows the same memory leak on `x86-64 clang (trunk)`.
## C++ Standard Reference
Consider the following snippet from [C++ Standard Working Draft (generated 2025-08-03) Section 14.3 [except.ctor]](https://eel.is/c++draft/except.ctor) Stack unwinding.
```
struct A { };
struct Y { ~Y() noexcept(false) { throw 0; } };
A f() {
try {
A a;
Y y;
A b;
return {}; // #1
} catch (...) {
}
return {}; // #2
}
```
"At <span>#</span>1, the returned object of type A is constructed. Then, the local variable b is destroyed ([stmt.jump]). Next, the local variable y is destroyed, causing stack unwinding, resulting in the destruction of the returned object, followed by the destruction of the local variable a. Finally, the returned object is constructed again at <span>#</span>2. — end example]"
## Stack Unwinding Behavior
With a modified version of the example above, I observed unexpected behavior:
```
#include <iostream>
struct A {
A(char c_) : c(c_) { std::cout << "A::ctor(" << this << "): " << this->c << std::endl; }
~A() { std::cout << "A::dtor(" << this << "): " << this->c << std::endl; }
char c;
};
struct Y
{
Y() { std::cout << "Y::ctor(" << this << ")" << std::endl; }
~Y() noexcept(false)
{
std::cout << "Y::dtor(" << this << ")" << std::endl;
throw 0;
}
};
A foo()
{
try {
A a('a');
Y y;
A b('b');
return { 'c' }; // #1
} catch (...) {
}
return { 'd' }; // #2
}
int main()
{
foo();
}
```
Destruction According to standard:
- object `b` dtor call
- object `y` dtor call (throws, causing stack unwinding),
- unnamed object created on marked line `// #1` dtor call
- object `a` dtor call
Behavior observed using clang ([18.1.3](#local-clang-version)):
- object `b` dtor call
- object `y` dtor call (throws, causing stack unwinding),
- object `a` dtor call
Behavior observed using gcc ([13.3.0](#local-gcc-version)):
- object `b` dtor call
- object `y` dtor call (throws, causing stack unwinding),
- object `a` dtor call
- unnamed object created on marked line `// #1` dtor call
Neither clang nor gcc behaved as described in the standard, but clang (and apparently msvc? I haven't checked this myself) appears to be missing a destructor call for the unnamed object created on marked line <span>#</span>. IMO this isn't a huge deal for the examples provided so far, but what happens if our object manages resources and this destructor really isn't being called? From my testing, a memory leak is being exposed here due to the missing destructor call.
## Memory Leak
Updated snippet adding resources to the object ((open in compiler explorer)[https://godbolt.org/z/zMo37eaK8]):
```
#include <array>
#include <memory>
struct A {
using data_type = std::array<char, 4096>;
A() : c(), some_data(std::make_unique<data_type>()) {}
A(char c_) : c(c_), some_data(std::make_unique<data_type>()) {}
~A() = default;
char c;
std::unique_ptr<data_type> some_data;
};
struct Y { ~Y() noexcept(false) { throw 0; } };
static A foo() {
try {
A a('a');
Y y;
A b('b');
return { 'c' }; // #1
} catch (...) {
}
return { 'd' }; // #2
}
int main()
{
foo();
}
```
By adding dynamic memory allocation to `struct A` we now have a function `foo()` that leaks memory every time it is called. This memory leak is a direct result of the divergence between the behavior described in the standard, and the behavior observed in the clang-compiled binary. Adding a loop to infinitely call `foo()` causes virtual memory usage to increase until an eventual core dump. The same code compiled with gcc produces a binary that runs forever at a constant virtual memory usage because it actually deletes the object being returned before the exception is thrown.
Compiling with `-fsanitize=address,undefined` does produce a binary that detects the memory leak, but this memory leak should not exist in this program. If stack unwinding was performed as described in the standard, then there would be no memory leak in this code.
## Local Environment Information
### General
Running on WSL ubuntu:
```
$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 24.04.2 LTS
Release: 24.04
Codename: noble
```
### Local Clang Version
```
clang++ --version
Ubuntu clang version 18.1.3 (1ubuntu1)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin
```
Installed using:
```
sudo apt install clang
```
### Local GCC Version
```
$ g++ --version
g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
Copyright (C) 2023 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
```
</pre>
<img width="1" height="1" alt="" src="http://email.email.llvm.org/o/eJzMWU1z4zjO_jXMBWWVTPkrhxwUpz2v6-2v6qR3qk8pSoRtTiRSS1J2PIf57VsgJX91ku7ZndnaVHclFiEQAIEHD2jhnFprxBs2vmXjuyvR-o2xNw8b_Cy8VeXT3IriqjByf8N4xngGn60pKqzh3guPNWrP0nxZN9Y0aEGi87YtvTIajJX0pLVKr8F5UT5Bq3dKS_pcCg2laB2CgNLo-BZKMMVvWHrwBjRu0UKBB50oE1iuwG-wl6qFFmt0YNGZ1pboGJ_TusPjI9ipqjoqW1lExqeSBNrKkylKg4Aaa2P3UKF4AsZnDhHY-Pb0KT6LuqmQje8Yn228bxzLcsYXjC_WRham8omxa8YXv9P_Dyabovj_GePXjF8n8LBBaCrhV8bWILSELVpHUfIb4WEJK9NqCX6jHCjnWgSjQTmolKOoCB_cLoz3pgazOhEklzdmF_wjmdLUjarQAj43lbHhj2A4VEo_oQS3MTsXRJ2o8cxxo4FN0ufZZDAZQVkJvaZYeNvqJ_JjkiYszelfzIQ547eM31ImaCmshC-4Qou6RJbmc6Odkp1RK1NVZhfyQKumQQ8ra2qK8Hc6fjX2iQTvrFh52n6NGq2gKPCUjwfpbJBmjF_DPcY0G46SjDThc4mNT0pvLJ3RC8eEWCXKMb4o46aStqDnJ2-S4vNU7X2epN2_NI_5CDmw6S2w6R3LbqNQt_AtLPzxjXE6f9Am7sD4bCUqh_SMBPzGmh2kLAtKzhTlsOpeZlN6BODt_vA3QA4iytKHb7A_fsihOH6w6Furw3tBOYSfGA1gPBsGOdq8FL7cULiTJDndlt6j3y9rOtXFyfIgfRqqkC0898CyuWuEZtk7yp5szvii-zzsqrbb4wgClOf7BiGnSjgBiVBNun-rMqWoYCusEkWFUJBwgAyzR0kesfGt87VPfmvrJqTFdQIf8dm_omB_poCECKdewDBaOkMRUnYGgKuX3KLXYj2ghGL_2lsXVokEFkqLqtq_Fq3zGIFYCwK2twLPE2DvOJul7HoEqOUZxPGzUo818fUA37e4EVtlLEvzX5XfEIAaqVYKj8DW-dGjjyjMNoDVEkzh0G5RQqvxucFgbtErzC6LjfFM6bJqJZIryjhvUdTkzmnJ5afFwfis3AgL5WNI5SyHkh499oXnvKR9srw0bQgQy-ZAado9DUAwY5z3awFuj3IEhlkOFwIDlr0r-weHLVDLqqvwzrw_8mNp_8gU-feaEqPUoc4LOBYeH2DmJ8z-9icieFx8K1hvgGgncrSQfn5g2U8F9HXLTjY6ovfBjruLMOawMibafxbJcyzv8ZzkpoLxaTjT09UzgO9BPogXL4kfsRoYn5aMT-EA2pfY_zb6w8lJnGuVZ1pf7QRprjTxNKVfCMMxOIcE_K553J0gY16Wxgb48YbAOBCGCBiDHgXZJC3YJAU6ZyhFVV0s7s8WA72hc3Rv4vw14_PO5gG0Wov6CLulxUBOjIZaWCJYldJIW52G-sKiqOlolbgUYGneQ-wJXAbrDryMjW-Hs2SYZJHrMJ6FnjEIAoMOhiP__K8H6d9zbV2WvWNZkiXphWPrsvzfdeuvSw6W5h9R-Q3a7qy1sSE0oUdSaw_8pLSqQNnzjkM58DkUrT9mCc0aommERe2rPdRuW7JsAUsgXZrxqYdygyXZFpCw3jusVgQComlQWEfFViDUyoUAiQNZ6eO4MpHk_6Tvr9KRBJYfPvWTTbRMwKZdEz0Sx206QuGgsWarJE00BlbC9p7vaKDakPHagVqBae2r82KYxMKOJ05ZJJp1sKHAOK9WFUqK3IIml3oPHp3vWOD5-Khc9w4-N8ahhA1aBNkiRZI86EN5Ecjz4epD1PgexRNL86-NDMHsxychAw4eHelU9wlKVTMzDWrKj-9GQsrq8e3Pz7CBNP-ImQlrxb6jZWcLMTZvEbZYelJ48RgIP8vujp230zsnrkLBHqXXE1LWN9me8Z1SvVi24EyNj6SWJvpeXy2e8LHV6p8tsmx-2DQkZDeu92POj-nkX7fLkRVmdyBxJdrKn_l4ytXOiE7c5bHx9mKnE8teZXj_8aTqvPCqhBOq88bI-jLF-W5-fZna_Bla8-dG2pcIzd9DZ273fenKvRa1KnvkEBX1uEBzvKEG0ZcJdYcdgja7gNggYNXqyIfYJD3uOUnjVRJBkOu14hbtHryqEVScEAOM0QCt3CVoCZDKEnrEobYf4KTaol2jLhEK9DvE2HD6ge3tVhTx9UT60PE74UhWOoSSUCgt7D6BPAZJQGVMQxFReqW08ljtu-Z94Xu4S3SwVda3oupda51YY3yd2pGjJuVVBUJTaHQQLU1A57qJl3ThUqw08nCTJmFHEy414MYa2Ya20RkaQ25b7ag9hTs44fsbTaH9y_YUGG8-lQdR0nK1B4kVenSnIB6byGHML5C26FogFamKV4ShNnXXPObBZnovGM0m6WDlhFZe_Y4suxNSWnREeFotcaU0ysA-TGyn5NyFbxI9lj7adZIvfbP1l3nkNqatJGjjAZ-V8_GcVVC_tqIOV7iXl8E74aBBuzK2_gl64zcxBS3CLmxWUHmcZ3O3KZ3jeVt9H65V3umtskbXqD0sNe0bSu8gRpK_hHtHImRfWq3JTKPh1_v30Bat9u1L7XAElSseLVYh1QaCmJyB9_e3UBvZEmcRFkFshapEUQXL7pTzVhUtkYDlHfWWr1F9GH1Kq8I503OAbgn4KElHCYf3D_dkXdyuE6GfsB6SQSJRsuOSNkWF33fxs9jMA3P8R8ezz2VDtXY3toMDF0_zzrBIOvsLoDicEAAPY8iGETEfhF2jJ6OeZ5PHyWjQlINK6fZ5sNbk98PGopAUMqxIqjFOPbM0X1JNEXzdKRvvPBats4wvCnVp50E0Uovvz8q10oBoKD2DZDT9B5H5ZT5_JS509C_FpX_G-KwLURxsBpMYEf5HPCt-3S2EU2v2Vq03gcXNaYmnPIOFRYR7s_I7yqGFabWMOcvnsNRlEgIXqHP4jgNcJ0r9zGHEjUgWA5MuTbMPvNZoqUiPS4DwzyJp-PiJpfmOmJf21JtjPW9Rh3c_vPsy_7_840N-u3y_fPgGxsJi-fDx3f09LD59gRw-518elvOv7_Mv8Pnrl8-f7t8lpwG7kjeZvM6uxRXeDKfj0WyYpePrq82NzEazEYppMZWz8UxOxJBP5awQxWRYcDESV-qGp3yczoZpOktHw-tkiFwIWY7GMkM5kmM2SrEWqkqqalsTl70KX5LcDMd8dj27qkSBlQvfdXGucdd_hcLZ-O7K3tBLg6JdOzZKK-W8O6rxyld4c_HVwCnmJFetrW4uOLXym7ZISlMzviBV3a9BY028Cl4EAxzji87C7Q3_VwAAAP__2Q5hsQ">