Обнаружение оборванных ссылок на временные
Clang 3.9 очень многократно использует память, используемую временными.
Этот код UB (упрощенный код):
template <class T>
class my_optional
{
public:
bool has{ false };
T value;
const T& get_or_default(const T& def)
{
return has ? value : def;
}
};
void use(const std::string& s)
{
// ...
}
int main()
{
my_optional<std::string> m;
// ...
const std::string& s = m.get_or_default("default value");
use(s); // s is dangling if default returned
}
У нас есть тонны кода, что-то вроде выше (my_optional
- просто пример, чтобы проиллюстрировать его).
Из-за UB все компиляторы clang с 3.9 начинают повторно использовать эту память, и это законное поведение.
Возникает вопрос: как обнаружить такие оборванные ссылки во время компиляции или что-то вроде дезинфицирующего средства во время выполнения? Ни один дезинфицирующий агент может обнаружить их.
Upd. Пожалуйста, не отвечайте: "используйте std::optional
". Читайте внимательно: вопрос не об этом.
UPD2. Пожалуйста, не отвечайте: "ваш код плохой". Читайте внимательно: вопрос НЕ о дизайне кода.
Ответы
Ответ 1
Вы можете обнаружить злоупотребления этим конкретным API, добавив дополнительную перегрузку:
const T& get_or_default(T&& rvalue) = delete;
Если аргумент, присвоенный get_or_default
, является истинным значением rvalue, он будет выбран вместо этого, поэтому компиляция завершится неудачно.
Что касается обнаружения таких ошибок во время выполнения, попробуйте использовать Clang AddressSanitizer с включенным включением использования после-возврата (ASAN_OPTIONS=detect_stack_use_after_return=1
) и/или использования-после-области (-fsanitize-address-use-after-scope
).
Ответ 2
Это интересный вопрос. Фактическая причина оборванного ref заключается в том, что вы используете ссылку rvalue, как если бы она была lvalue.
Если у вас не слишком много этого кода, вы можете попытаться сделать исключение таким образом:
class my_optional
{
public:
bool has{ false };
T value;
const T& get_or_default(const T&& def)
{
throw std::invalid_argument("Received a rvalue");
}
const T& get_or_default(const T& def)
{
return has ? value : def;
}
};
Таким образом, если вы передадите ему ссылку на временную (которая действительно является rvalue), вы получите исключение, которое вы сможете поймать или, по крайней мере, скоро прервите.
В качестве альтернативы вы можете попробовать простое исправление, заставив вернуть временное значение (а не ref), если вы получили rvalue:
class my_optional
{
public:
bool has{ false };
T value;
const T get_or_default(const T&& def)
{
return get_or_default(static_cast<const T&>(def));
}
const T& get_or_default(const T& def)
{
return has ? value : def;
}
};
Другая возможность заключалась бы в том, чтобы взломать компилятор Clang, чтобы он попросил его определить, прошел ли метод значение lvalue или rvalue, с помощью I am, недостаточно используемых для этих методов...
Ответ 3
Вы можете попробовать lvalue_ref
wrapper из Explicit. Это предотвращает нежелательное связывание с временным в одном объявлении, например:
const T& get_or_default(lvalue_ref<const T> def)
{
return has ? value : def.get();
}