检测对临时文件的悬挂引用

Detect dangling references to temporary

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 all clang compiler since 3.9 starts to reuse this memory, and it is a lawful behavior.

问题是:如何在编译时 检测 此类悬空引用或在运行时使用诸如消毒剂之类的东西?没有 clang 消毒剂可以检测到它们。

更新。请不要回答:"use std::optional"。仔细阅读:问题不是关于它。
更新2。请不要回答:"your code design is bad"。仔细阅读:问题与代码设计无关。

您可以通过添加额外的重载来检测对这个特定 API 的滥用:

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

如果给 get_or_default 的参数是一个真正的右值,它将被选择,因此编译将失败。

至于在运行时检测此类错误,请尝试使用启用了 use-after-return (ASAN_OPTIONS=detect_stack_use_after_return=1) and/or use-after-scope (-fsanitize-address-use-after-scope) 检测的 Clang 的 AddressSanitizer。

这是个有趣的问题。悬空引用的实际原因是您使用右值引用,就好像它是左值引用一样。

如果你没有太多的代码,你可以尝试以这种方式抛出异常:

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;
    }
};

那样的话,如果你将一个 ref 传递给一个临时对象(这确实是一个右值),你会得到一个异常,你将能够捕获或者至少会很快中止。

或者,如果您传递了一个右值,您可以通过强制 return 一个临时值(而不是 ref)来尝试一个简单的修复:

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_ref wrapper from Explicit 库。它可以防止在一个声明中不必要地绑定到临时文件,例如:

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