Detect dangling references to temporary
Asked Answered
P

3

21

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.

Pb answered 20/2, 2017 at 8:54 Comment(11)
@Someprogrammerdude Thank you, I know. The point is: there not a constant, but std::string("default value") constructed implicitly, it is a temporary, and it dies after that line. So s on next line is dangling.Pb
No. String temporary object dies on the end of the line. Reference to it is dangling on the line use(s).Pb
@Someprogrammerdude "a temporary bound to a reference parameter in a function call exists until the end of the full expression containing that function call: if the function returns a reference, which outlives the full expression, it becomes a dangling reference" en.cppreference.com/w/cpp/language/…Pb
You're right, I was wrong. Sorry.Chitin
In general terms, it is not necessarily possible to detect a dangling reference at all (short of resorting to techniques that are highly specific to the compiler, or rely on knowledge of low-down details of what the compiler does). The usual strategy is to design your program in a way that lifetime of objects is controlled - so dangling references or pointers don't exist.Claudette
@Claudette Thank you for your comment. But it does not help. The code already IS exists. Legacy code. Tons of legacy code. Please don't be Captain Obvious here.Pb
The fact you asked the question suggests it was not obvious to you there is no solution. In any event, the only solution is the old-fashioned way - step through with a debugger, isolate code that doesn't contribute to the problem, keep going until the code left is small enough that you can find the cause. Sometimes there is no alternative to working for your supper.Claudette
None of Asan, UBsan or Valgrind catch this which is surprising and unfortunate.Brader
@Claudette I don't want to debug when problem occurs. I want to prevent the problems caused by UB code like this.Pb
@Pb - I understand that. However, one of the common reasons that something is undefined behaviour is that it results from something that cannot reliably be detected. If you can't reliably detect something, it is not possible to prevent it, short of redesign to prevent it. The fact you want otherwise doesn't change that. And, given a reference, it is not generally possible to detect that the object it refers to ceases to exist.Claudette
@Claudette I think it is possible at compiler level (LLVM level or something like it)Pb
A
27

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.

Antarctic answered 20/2, 2017 at 10:43 Comment(1)
yeah! thanks! with these options ASan detects them :-)Pb
V
4

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();
}
Valorous answered 20/2, 2017 at 11:48 Comment(0)
W
3

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...

Wristband answered 20/2, 2017 at 10:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.