Обнаружить висячие ссылки на временные

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

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

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

3 ответа

Решение

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

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

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

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

Вы можете попробовать lvalue_ref обертка из явной библиотеки. Это предотвращает нежелательную привязку к временному в одном объявлении, например:

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

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

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

Другие вопросы по тегам