c++ - Detectar referencias colgantes a temporales.
c++11 undefined-behavior (3)
Clang 3.9 reutiliza extremadamente la memoria usada por los temporales.
Este código es UB (código simplificado):
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
}
Tenemos toneladas de código como el anterior ( my_optional
es solo un ejemplo simple para ilustrarlo).
Debido a UB, todo el compilador de Clang desde 3.9 comienza a reutilizar esta memoria y es un comportamiento legal.
La pregunta es: ¿cómo detectar tales referencias colgantes en tiempo de compilación o con algo como desinfectante en tiempo de ejecución? Ningún desinfectante puede detectarlos.
Actualizaciones Por favor, no responda: "use std::optional
". Lee detenidamente: la pregunta NO es sobre eso.
Upd2. Por favor, no responda: "el diseño de su código es malo". Lea detenidamente: la pregunta NO es sobre el diseño del código.
Esa es una pregunta interesante. La causa real de la referencia pendiente es que utiliza una referencia rvalue como si fuera una lvalue.
Si no tiene mucho de ese código, puede intentar lanzar una excepción de esa manera:
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;
}
};
De esa manera, si le pasa un ref a un temporal (que de hecho es un valor), obtendrá una excepción, que podrá atrapar o al menos dará un aborto pronto.
Alternativamente, puede intentar una solución simple forzando a devolver un valor temporal (y no una referencia) si le pasaron un valor de r:
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;
}
};
Otra posibilidad sería hackear el compilador Clang para pedirle que detecte si el método pasa un valor lvalue o rvalue, ya que no estoy lo suficientemente acostumbrado a esas técnicas ...
Podrías probar lvalue_ref
wrapper desde Explicit library. Evita el enlace no deseado a una declaración temporal en una declaración, como:
const T& get_or_default(lvalue_ref<const T> def)
{
return has ? value : def.get();
}
Puede detectar el uso incorrecto de esta API en particular agregando una sobrecarga adicional:
const T& get_or_default(T&& rvalue) = delete;
Si el argumento dado a get_or_default
es un valor verdadero, se elegirá en su lugar, por lo que la compilación fallará.
En cuanto a la detección de dichos errores en el tiempo de ejecución, intente usar Clang''s AddressSanitizer con use-after-return ( ASAN_OPTIONS=detect_stack_use_after_return=1
) y / o use-after-scope ( -fsanitize-address-use-after-scope
) habilitado.