1

After some changes the output of an application is no longer valid in some cases. Some output values are wrong. The values used to compute those outputs are correct, and at some point in the complicated processing things take a wrong turn.

Is there a tool to track the origin of a C++ variable's value? I've used valgrind before to track NULL values, but what I would like is something more general. Is there a more general tool that shows the chain (or tree) of assignments that led to a variable to have its value at a certain point in time?

PS: The code is, as almost all legacy code, hard to follow, has no unit tests, etc.

EDIT: Data breakpoints on the variable would only show me the endpoint in the chain. A bit more than that would be nice to have.

pau.estalella
  • 2,197
  • 1
  • 15
  • 20
  • visual studio lets you condition a break point on variable change: http://stackoverflow.com/questions/160045/break-when-a-value-changes-using-the-visual-studio-debugger – thang Sep 18 '14 at 08:14
  • 2
    Why is "legacy code" so hard to follow? Are language comments a new feature? – Bathsheba Sep 18 '14 at 08:14
  • 1
    Well, in a sense, that variable's value is potentially dependent on the entire previous history of your program. Anything that could calculate such a tree could produce some very large results... – Oliver Charlesworth Sep 18 '14 at 08:15
  • @OliverCharlesworth That's why I would love to find the exact sequence of assignments that led to the value. Without effort, and without having to review all the code. – pau.estalella Sep 18 '14 at 08:16
  • @Bathsheba: Does it matter? You didn't do it and now it's yours. Who did it is dead. Blaming it doesn't change anything. – Emilio Garavaglia Sep 18 '14 at 08:17
  • Just set a watchpoint on the variable so that you drop into the debugger whenever it changes. – Paul R Sep 18 '14 at 08:18
  • 2
    It's an odd phenomenon. Once in a while you get a headhunter call explaining that some investment bank or other has a "whole load of legacy code" that needs refactoring. What this really means is that it's been hacked together by a bunch of folk who have learned the language in the gutter. I conject that a lot of code written today will be of that ilk in years to come. That phone will continue to ring, for sure. Let's all stop writing legacy code! – Bathsheba Sep 18 '14 at 08:19
  • 1
    Depending on the type of application, this is anywhere between "easy" to "terribly hard". There are so many different ways that a variable can change, and if you write something to "track a variable", I can almost guarantee that I can write something that makes your application NOT find out what is going on, and you come up with something cleverer, and I come up with something cleverer. There is no simple answer, only way out is to trace your calculations, using a debugger - document as you go along, either separately or in the code. – Mats Petersson Sep 18 '14 at 08:21
  • @MatsPetersson of course you can create adversarial code. Just like you can create hard-to-debug self-modifying code. But that does not negate the usefulness of a debugger. – pau.estalella Sep 18 '14 at 08:24
  • 1
    @pau.estalella My point is that "exact sequence of assignments" could be an **enormous** graph, which would be of very little actual value. – Oliver Charlesworth Sep 18 '14 at 08:28
  • @OliverCharlesworth I agree that it could be enormous. A step by step procedure to move around the graph? I don't really want the *whole* graph. Just having the ability to move inside it would be great. – pau.estalella Sep 18 '14 at 08:32
  • @OliverCharlesworth I can even imagine how it would go: Set up a data breakpoint on the variable. Then, when you reach the breakpoint, you are able to jump to the place where the values used in the assignment expression where defined. Recursively. – pau.estalella Sep 18 '14 at 08:46
  • @pau.estalella: That sounds a lot like a reverse-execution debugger (http://stackoverflow.com/questions/1878352/which-debuggers-support-step-back-time-machine-back-in-time-feature)... – Oliver Charlesworth Sep 18 '14 at 09:33
  • check out the updated answer with a link to ideone. With appropriate slim wrappers you might get an idea how the data flows – Dmitry Ledentsov Sep 18 '14 at 09:43
  • 1
    @OliverCharlesworth Kind of. But reverse-execution debugging is an overkill. I would like a more data-oriented approach. I don't really care about the executed instructions. I would like to follow the data trail. Meh, I guess the answer to my question is: no, there's no such tool. – pau.estalella Sep 23 '14 at 12:20
  • They're pretty much the same thing though, in this case. There is a sequence of assignments that led to the variable's current value, that sequence is intrinsically related to the particular sequence of instructions that your program executed. – Oliver Charlesworth Sep 23 '14 at 12:41

1 Answers1

2

What you could do is wrap your variables of interest with a family of generic wrappers that would log a stacktrace and the values on each call. Something like (omitting some details):

template <typename T>
class TracingValue
{
private:
 T m_Val;
 ...    
 void LogStackTrace() {...}

public:

 // write
 TracingValue& operator= (const T& val) {
    LogStackTrace();
    m_Val=val;
    return *this;
 }

 // read     
 operator T () const { return m_Val; }

 // "connect" to other values
 TracingValue& operator=(const TracingValue &other) {
   LogStackTrace();
   m_Val = other.m_Val;
   std::cout << "id: " << this->Id() << " new value: " << m_Val
             << " from id: " << other.Id() << std::endl;
   return *this;
 }

};

Logging the stack traces will be slow and may generate too much data, but if you use it sparingly, you might get a better picture what is happening in your software. You can then put breakpoints in the wrapper to catch the modifications when they're happening.

That should work for the trivial cases. In case serialization and other operations are involved, it might need to be refined further.

Tracing value changes and constructions from other wrapped values is possible. See →Ideone for an example:

TracingValue<double> d;
d = 3.;
d = 42.;
double x = d - 2.;
std::cout << x << std::endl;
TracingValue<double> other_d(d);
TracingValue<double> another_d;
another_d = other_d;  

outputting

id: 1 constructed with value: 0
id: 1 new value: 3
id: 1 new value: 42
40
id: 2 constructed with value: 42
id: 2 constructed from id: 1
id: 3 constructed with value: 0
id: 3 new value: 42 from id: 2
Dmitry Ledentsov
  • 3,620
  • 18
  • 28
  • I'm not sure the OP wants to know the stack trace, they want to know the dependency graph behind that value. – Oliver Charlesworth Sep 18 '14 at 08:29
  • That could be the case, indeed. I think, starting small, and gradually creating helpers to trace the object graph and its evolution could help in any case. Imagine a further wrapper that would log its reference of some kind into the target value. But, of course, just reading and step-by-step debugging the code might be a better solution to the overall problem. ["Working Effectively With Legacy Code"](http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052) should give a good reference how to go about a task as well. – Dmitry Ledentsov Sep 18 '14 at 08:38
  • Thanks for the effort. I may use that in the future. But I was looking to ready to use tools to trace the origin of a value in an existing code base. – pau.estalella Sep 23 '14 at 12:17