Подтвердить что ты не робот

Обнаружение оборванных ссылок на временные

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. Пожалуйста, не отвечайте: "ваш код плохой". Читайте внимательно: вопрос НЕ о дизайне кода.

4b9b3361

Ответ 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();
}