How to detect mid-function value changes to const parameter?

Viewed 55

I ran into a nasty bug in some of my code. Here's the simplified version:

#include <iostream>

class A
{
public:
    std::string s;
    
    void run(const std::string& x)
    {
        // do some "read-only" stuff with "x"
        std::cout << "x = " << x << std::endl;

        // Since I passed X as a const referece, I expected string never to change
        // but actually, it does get changed by clear() function
        clear();

        // trying to do something else with "x", 
        // but now it has a different value although I declared it as 
        // "const". This killed the code logic.
        std::cout << "x = " << x << std::endl;

        // is there some way to detect possible change of X here during compile-time?
    }
    
    void clear()
    {
        // in my actual code, this doesn't even happen here, but 3 levels deep on some other code that gets called
        s.clear();
    }
};

int main()
{
    A a;
    a.s = "test";
    a.run(a.s);
    
    return 0;
}

Basically, the code that calls a.run() use to be used for all kinds of strings in the past and at one point, I needed the exact value that object "a.s" had, so I just put a.s in there and then some time later noticed program behaving weird. I tracked it down to this.

Now, I understand why this is happening, but it looks like one of those really hard to trace and detect bugs. You see the parameter declared as const & and suddenly it's value changes.

Is there some way to detect this during compile-time? I'm using CLang and MSVC.

Thanks.

1 Answers

Is there some way to detect this during compile-time?

I don't think so. There is nothing inherently wrong about modifying a member variable that is referred by a const reference, so there is no reason for the compiler to warn about it. The compiler cannot read your mind to find out what your expectations are.

There are some usages where such wrong assumption could result in definite bugs such as undefined behaviour that could be diagnosed if identified. I suspect that identifying such cases in general would be quite expensive computationally, so I wouldn't rely on it.


Redesigning the interface could make that situation impossible For example following:

struct wrapper {
    std::string str;
};


void run(const wrapper& x);

x.str will not alias the member because the member is not inside a wrapper.

Related