2017-02-20 17 views
19

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 начинают повторное использование этой памяти, и это законное поведение.

Вопрос: как обнаружить такие обвисшие ссылки во время компиляции или что-то вроде дезинфицирующего средства во время выполнения? Никакой дезинфицирующее средство для клещей не может их обнаружить.

Обновление. Пожалуйста, не отвечайте: «используйте std::optional». Читайте внимательно: вопрос НЕ об этом.
Upd2. Пожалуйста, не отвечайте: «ваш код плохой». Читайте внимательно: вопрос НЕ о дизайне кода.

+0

@Someprogrammerdude Спасибо, я знаю. Дело в том, что нет константы, но 'std :: string (« значение по умолчанию »)' построено неявно, это временно, и оно умирает после этой строки. Так '' 'на следующей строке свисает. – vladon

+0

№ Строка * временно * объект _dies_ в конце строки. Ссылка на него висит на строке 'use (s)'. – vladon

+1

@Someprogrammerdude "временная привязка к эталонному параметру в вызове функции существует до конца полного выражения, содержащего вызов этой функции: если функция возвращает ссылку, которая пережитёт полного выражения, она становится оборванной ссылкой" http://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary – vladon

ответ

22

Вы можете обнаружить злоупотреблений этого конкретного API путем добавления дополнительной перегрузки:

const T& get_or_default(T&& rvalue) = delete; 

Если аргумент, данный get_or_default является истинным Rvalue, он будет выбран вместо этого, так компиляция потерпит неудачу.

Что касается обнаружения таких ошибок во время выполнения, попробуйте использовать параметр AddressSanitizer Clang с использованием использования после-возврата (ASAN_OPTIONS=detect_stack_use_after_return=1) и/или использования после использования (-fsanitize-address-use-after-scope).

+0

да! благодаря! с этими опциями ASan обнаруживает их :-) – vladon

3

Это интересный вопрос. Фактическая причина оборванного 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) , вы получите исключение, которое вы сможете поймать или, по крайней мере, скоро прервите.

В качестве альтернативы, вы можете попробовать простой исправить, заставляя вернуть временное значение (а не реф), если вы сдали 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 спросить его обнаружение передается ли метод именующего выражения или RValue, по я я не достаточно использовать для этих методов ...

3

Вы можете попробовать lvalue_ref обертки из Explicit библиотеки. Он предотвращает нежелательное связывание с временным в одном объявлении, например:

const T& get_or_default(lvalue_ref<const T> def) 
{ 
    return has ? value : def.get(); 
}