Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detect dangling references to temporary

Clang 3.9 extremely reuses memory used by temporaries.

This code is UB (simplified code):

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
}

We have tons of code something like above (my_optional is just a simple example to illustrate it).

Because of UB all clang compiler since 3.9 starts to reuse this memory, and it is lawful behavior.

The question is: how to detect such dangling references at compile time or with something like sanitizer at runtime? No clang sanitizer can detect them.

Upd. Please do not answer: "use std::optional". Read carefully: question is NOT about it.
Upd2. Please do not answer: "your code design is bad". Read carefully: question is NOT about code design.

like image 529
vladon Avatar asked Feb 20 '17 08:02

vladon


3 Answers

You can detect misuses of this particular API by adding an additional overload:

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

If the argument given to get_or_default is a true rvalue, it will be chosen instead, so compilation will fail.

As for detecting such errors at runtime, try using Clang's AddressSanitizer with use-after-return (ASAN_OPTIONS=detect_stack_use_after_return=1) and/or use-after-scope (-fsanitize-address-use-after-scope) detection enabled.

like image 151
Sebastian Redl Avatar answered Nov 05 '22 08:11

Sebastian Redl


You could try out lvalue_ref wrapper from Explicit library. It prevents the unwanted binding to a temporary in one declaration, like:

const T& get_or_default(lvalue_ref<const T> def)
{
    return has ? value : def.get();
}
like image 4
Andrzej Avatar answered Nov 05 '22 08:11

Andrzej


That is an interesting question. The actual cause of the dangling ref is that you use an rvalue reference as if it was an lvalue one.

If you have not too much of that code, you can try to throw an exception that way:

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

That way, if you pass it a ref to a temporary (which is indeed an rvalue), you will get an exception, that you will be able to catch or at least will give a soon abort.

Alternatively, you could try a simple fix by forcing to return a temporary value (and not a ref) if you were passed an 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;
    }
};

Another possibility would be to hack the Clang compiler to ask it to detect whether the method is passed an lvalue or an rvalue, by I am not enough used to those techniques...

like image 3
Serge Ballesta Avatar answered Nov 05 '22 10:11

Serge Ballesta