3 ms·
to_string_t as shown is bait for undefined behavior, when combined with auto: #include <string> #include <string_view> #include <iostream> #include <ss
by MaulingMonkey 6y ago
to_string_t as shown is bait for undefined behavior, when combined with auto:
#include <string>
#include <string_view>
#include <iostream>
#include <sstream>
struct to_string_t {
std::string_view s;
// I'm too lazy to download boost::lexical_cast:
operator int() const { std::stringstream ss; ss << s; int r = 0; ss >> r; return r; }
operator bool() const { std::stringstream ss; ss << s; bool r = false; ss >> r; return r; }
};
to_string_t from_string(std::string_view s) { return to_string_t{s}; }
void takes_int(int i) {
std::cout << i << "\n";
}
int main() {
std::string foo = "1";
takes_int(from_string(foo + "0")); // OK
const auto a = from_string(foo + "0"); // temporary string
takes_int(a); // use after free bug, the temporary string was freed
}
MSVC's debug heap seems to have trouble catching it (small string optimizations?) and prints "10" followed by "0" (not another "10"!), but address sanitizer at least has my back:
/mnt/c/local/evil$ g++ -fsanitize=address -std=c++17 main.cpp -o main && ./main
10
=================================================================
==154==ERROR: AddressSanitizer: stack-use-after-scope on address 0x7ffd0d828fb1 at pc 0x7f737d8cd733 bp 0x7ffd0d828ad0 sp 0x7ffd0d828278
...
- ot 6y agoIt could be made safe by making to_string_t a template and forwarding from_string's argument into its storage, if things are not complicated enough already :) Something like this template <class String> struct to_string_t { const String s; ... } template <class String> auto from_string(String&& s) { return to_string_t<String>{std::forward<String>(s)}; } Then the temporary's ownership would be passed to to_string_t. Would still have surprising behavior if a the argument is a non-const reference though.
- MaulingMonkey 6y agoIt'd help with the edge case I showed... "safe" is a strong word though, doesn't handle this one: std::string json() { return "\"123\""; } std::string_view unquote(std::string_view s) { return { s.data()+1, s.size()-2 }; } auto i = from_string(unquote(json())); It's a fundamental problem of subtly & unexpectedly deferred action.
- ot 6y agoI meant to write "safer" but the "r" fell through :)