<table border="1" cellspacing="0" cellpadding="8">
<tr>
<th>Issue</th>
<td>
<a href=https://github.com/llvm/llvm-project/issues/130213>130213</a>
</td>
</tr>
<tr>
<th>Summary</th>
<td>
[clang-tidy] Check request: boost-avoid-dangling-in-any-range-and-transformed-range-cooperation
</td>
</tr>
<tr>
<th>Labels</th>
<td>
clang-tidy
</td>
</tr>
<tr>
<th>Assignees</th>
<td>
</td>
</tr>
<tr>
<th>Reporter</th>
<td>
denzor200
</td>
</tr>
</table>
<pre>
Look at the snippet(you could play with the snippet [here](https://godbolt.org/z/ojPbs9vYf)):
```
#include <boost/range/adaptors.hpp>
#include <boost/range/any_range.hpp>
#include <vector>
#include <iostream>
using StringRange = boost::any_range<
std::string,
boost::forward_traversal_tag,
const std::string&,
std::ptrdiff_t
>;
StringRange create_view_to_vector(const std::vector<std::string>& vec) {
auto identity_func = [](const std::string& x) {
return x;
};
auto view_to_vector = vec | boost::adaptors::transformed(identity_func);
return StringRange(view_to_vector);
}
int main() {
std::vector<std::string> vec = {"first", "second", "third", "fourth", "fifth"};
auto range = create_view_to_vector(vec);
// Attempt to use range
for (const std::string& str : range) {
std::cout << str << " "; // BAD - UB reached
}
}
```
For the first seeing this code looks correct and innocent. But reality doesn't spare us and this code contains insidious **undefined behaviour UB** which related to `boost::any_range` and `boost::adaptors::transformed` using. So, lets figure out what's wrong here.
1. The lambda `identity_func` returns a copy of `std::string`, not a reference:
```
auto identity_func = [](const std::string& x) {
return x; // Here we return copy of a string
};
```
2. But `boost::any_range` in the mood for store a reference(`const std::string&`):
```
using IntRange = boost::any_range<
std::string,
boost::forward_traversal_tag,
const std::string&, // Here we "store" a reference
std::ptrdiff_t
>;
```
It leads to `boost::any_range` attempts to store a reference to a temporary object which deletes just after lambda's evaluation. When `view_to_vector` returns a value it's always a temporary object. That object deletes just after being used, but `boost::any_range` attemps to store a reference to it. It leads to dangling references.
### How does the problem appear itself?
In the `for` loop:
```
for (const std::string& str : range) {
std::cout << str << " ";
}
```
the variable x will refer to temporary objects that have already been destroyed. This leads to UB, and the program may output garbage, crash, or behave unpredictably.
### Fixing
To fix the problem, we need to reconcile the types. There are two options:
1. Use `boost::any_range` that stores values.
Let's change using of `boost::any_range` so that it stores values (`std::string`) instead of references:
```
#include <boost/range/adaptors.hpp>
#include <boost/range/any_range.hpp>
#include <vector>
#include <iostream>
using StringRange = boost::any_range<
std::string,
boost::forward_traversal_tag,
std::string, // Here we "store" values
std::ptrdiff_t
>;
StringRange create_view_to_vector(const std::vector<std::string>& vec) {
auto identity_func = [](const std::string& x) {
return x;
};
auto view_to_vector = vec | boost::adaptors::transformed(identity_func);
return StringRange(view_to_vector);
}
int main() {
std::vector<std::string> vec = {"first", "second", "third", "fourth", "fifth"};
auto range = create_view_to_vector(vec);
// Attempt to use range
for (const std::string& str : range) {
std::cout << str << " "; // OK - first second third fourth fiveth
}
}
```
Now `boost::any_range` stores values, and the dangling reference problem disappears.
2. Use references in lambda.
If you want `boost::any_range` to store references, the lambda should return references to objects that live longer than `boost::any_range`. For example:
```
#include <boost/range/adaptors.hpp>
#include <boost/range/any_range.hpp>
#include <vector>
#include <iostream>
using StringRange = boost::any_range<
std::string,
boost::forward_traversal_tag,
const std::string&,
std::ptrdiff_t
>;
StringRange create_view_to_vector(const std::vector<std::string>& vec) {
auto identity_func = [](const std::string& x) -> const std::string& {
return x; // Here we return a reference for an element of the vector
};
auto view_to_vector = vec | boost::adaptors::transformed(identity_func);
return StringRange(view_to_vector);
}
int main() {
std::vector<std::string> vec = {"first", "second", "third", "fourth", "fifth"};
auto range = create_view_to_vector(vec);
// Attempt to use range
for (const std::string& str : range) {
std::cout << str << " "; // OK - first second third fourth fiveth
}
}
```
Here the lambda returns references to the elements of the original vector vec, which lives longer than `boost::any_range`. This is safe.
### Resume
Needs a check that will find the problem described above in C++ code and point it out. I believe it will not be a hard task to detect it automatically(it looks like a really simple Clang Tidy check), but believe my words it's impossible to detect by simple reading - only hours in a flaky debugging :)
I suppose this check will not suggest a fixin hint(or at least 100% safe and simple suggesting to force ` boost::any_range` to store values).
</pre>
<img width="1" height="1" alt="" src="http://email.email.llvm.org/o/eJzsWV9v47gR_zTMy8CGQsV_8uAH21njFj1ci7tdFH0KKHFkcUOTKknZ0X36YkjZluMku0Bb4IDNwotY5pDz78ffcCjhvdoaxAWbrNjk4Ua0obZuIdH8aR3PspvCym7BsuWv1j6BCBBqBG9U02BgfN7ZFkrbagmNFh0cVKiHEsAmqxodsskD4_M6hMazfMn4hvHN1srC6jC2bsv45k_GN_bbPwp_v_9Xxfg9ffIly5ZsmvWfbMl4rkypW4nA8nVhrQ-Mb5wwW2R8I6RognV-XDcNyz99X950j_H7WxP2WAbrXhtR1geHYtePZcvWK7OFP4JTZvs7rQksf4CkMV-yfHlSxvI1y5YAAD7INObjNMaPA4NplXUH4eRjcGKPzgv9GMRAsrTGh-uFpmeJ01gTnFRV9RjI4PwTy1fJ8qHNpUMR8HGv8PAY7GPvP5-_UHOMy_ql4vwT41PYY8n4PbDZqrdBtMGCkmiCCt1j1ZoyRich7nr5kxfwfLkO_XMYWmfgOdlPv7DZw_khqrq0P-raYwlstr7ISA-X9BScML6yboeS8fmFsRGKRwW9_kHUGJ-_DFgvT5bFGCsTYCeUYXx-6dGPhDTZTvGarRjnlXKEYs74GhjnHktr5Pk51MoNHivbulAPnlWVHq-C5k6ofQsFKa8n4MTYx50MyxBw1wQIFlqPaaleoqIEvJNiHyhBy37OVbpPM0rbBtp5LF_3c-JXxjn9Z_nqZMxq-QAj-LoCh6KsUQ5gckrJkFOy5ca6SFoxtOARaS-HWnkorUTQ1j7RV-ewDCCMBGWMLdGEMazaQHq0Ch1Ii94wPgvgG-EQWh-FzwuV1gShjAdlvJLKth4YXzK-bI3EShmUUGAt9sq2Dr6u0hgcalXW4FCLgJJCzKbZq8QyzaK-F8NvonyaQWStMfxhCR0ag4dKbVuHQME-1CIwPvNwcNZsgWh8nOJ1O4YvNYIWu0IK0ne5XaZZv0s8CCht04GtSOpl9qcZqTU2gACHFTo0JV6T_v-KPQbMcYTKL-gQDngcOtoqoF8k4SUBfmART3l_Jw_KREDtrJVxB_hgHV64yedsmr1J3xSaV-pfqjKfTfhrlBh4GUfiI_KUduXQ2R8qRQM_PwfQKKT_HtoT7USxqwjTjwJIwDrhOrDFN9q-aTdJ1BjQw7fWBxBVQNejOQIe90K3IihrxvDPGg3Z8IINLzBO4ggq7RahD6Lzr6imTSPC0Y5XLCgi77SeKtAaivcRlnx_23UVxjAMoxRmq2n9k5TvdzPjefrAL_YQSSxit3G20LgD0TQoHKjgUVcs31B6ErrZNKtSKLS1zTVa_wvq_3Haf5XTybq9cEoUGuEZDkrr5DdF4mVeyF0RoBZ7BKEdCtlBgWhAog_Odigpc8qfY0nUvO65PQZq68QOdqIj3mzaAFvhCkE-raF0wtf0xbpE7gitaRxKVQZR6O4qCRv1nKjni4VKPQ9zQcscEAymOuCo9JdKY5QJXYM-EjOBwSGEgwXbEIp9ys3tGL56fA9UMRARTj6BOmKEEvIrJnSXdWSeRESJ1t9azNu0nnqxJLBPnM0zNl-dv0Q2fK0-3FOtDCgkKTtD96M1-C5vXy_xHlun1Hz0DB89w0_ZM_z9bzA6Hf8pOBBjAikUUKk9hvo7jcRv9vAuHQ45cFg_rivzqfZK5VP5PdZqnij8TIR00kwnlyNTf66gsy0chHn3_HA6NwxIla-jQf253tfxXqcH70BlsJeVU6s9NUlmS_W1FuYdtWOgXgufxa7Rrxz1P0j8J7zfGRFfvSnyJpO_0cMNj8HEI8IAatyhCXSCiAfD5N1HIfgoBP-fQhAROSDSY6N4SaEk0CPTH6FpndoqI3SP0bTT1n3PSjTrf5hnY8eiPHhRHa9tLtqM39G3O4rzb4gyXtTUWD4lQo_9UqXO_U0qRuhLpwqUIAq7R6o8a8ZXjK_S7RbVs8YSWlWgRmgMn6FArZBk-0WNDVBQr1oLJyEI_xR7UwzUEqsQwbQTQZVC6462Uuhv37R6Si0uDYBXVD9grYXZwhclu2R9fG2QOuej4l0HB-ukP3bnatdY7xX1hWfFxWlF6v6I6Edgje6gtq2LJVZApcVTBxKLdrslifga456aYfBt01iP_UVfDOPJWd9ut0gdPvVyykCtTGB8TsQUm3Mf4DbLGJ_ERMUY9qb0M-NVpKWdUMbW7fWaMyzoxxPG_fhGLnJ5n9-LG1zczu5u8-ye301u6sX9RHIxLzCvpmVZzGZiNp1VFebT8raQ5Xx-oxY845Msz2bZbZbdTcZVOZtls4mY55O7u5mYsrsMd0Lpsdb73di67Y3yvsXFbZ7x2_xGiwK1j--UOC8pTaOgZEebbfJw4xY0a1S0W8_uMq188Od1ggo6vo0aTJs8wDrG1eG_W4zupziMxN4qOToeoUbKjITpRjEoI2HkaMDM_a-ltQ26eLtz0zq9ePFSSoW6Lcal3TG-IZv6P6PGWTryML6JjnrGN72v-wX_TwAAAP__QSSI0g">