Bug 341731 - Show values returned from function calls after a step-return
Summary: Show values returned from function calls after a step-return
Status: RESOLVED FIXED
Alias: None
Product: CDT
Classification: Tools
Component: cdt-debug (show other bugs)
Version: 8.0   Edit
Hardware: PC All
: P3 enhancement (vote)
Target Milestone: 8.4.0   Edit
Assignee: Marc Khouzam CLA
QA Contact: Ken Ryall CLA
URL:
Whiteboard:
Keywords:
Depends on: 422701 378729 418710 420366
Blocks:
  Show dependency tree
 
Reported: 2011-04-03 16:32 EDT by Sergey Prigogin CLA
Modified: 2014-02-13 15:16 EST (History)
8 users (show)

See Also:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Sergey Prigogin CLA 2011-04-03 16:32:49 EDT
It would be nice to display values returned from function calls in the Variables view.

Visual Studio displays values returned from function calls in its Autos view.

When stepping over

  foo(bar());

two lines appear in the Autos view:

bar returned   <value1>
foo returned   <value2>

The same thing happens when stepping out of a function.
Comment 1 Axel Mueller CLA 2011-09-05 03:47:35 EDT
There was a patch proposed in Bug #302320 (for CDI version). It would be cool to have this feature.
Comment 2 Marc Khouzam CLA 2013-07-11 09:43:22 EDT
(In reply to comment #1)
> There was a patch proposed in Bug #302320 (for CDI version). It would be
> cool to have this feature.

Looking at the patch in bug 302320, I saw the *stopped event after an -exec-finish contained the return value of the method.  For example:

stopped,reason="function-finished",frame={addr="0x0000000000401270",func="main",args=
[{name="argc",value="1"},{name="argv",value="0x7fffffffde68"}],file="../src/DSFTestApp.cpp",fullname="DSFTestApp.cpp",line="262"},
gdb-result-var="$4",return-value="11",
thread-id="1",stopped-threads=["1"],core="0"

We could do that relatively easily for DSF.

I was also thinking that we could go beyond the step-finish and, if possible, support the following:
- step-finish and showing the resulting value automatically
- stepping over a line with a method call and showing the resulting value automatically
- stepping over a line with multiple method calls and showing all resulting values

I haven't dived into it, but I'm thinking we could force a step-into and a step-finish to obtain the return value of the call, for each step over.  I think performance would be ok, but we'll have to see.  One tweak would be that when we are fast stepping, we don't provide the return value, and avoid this extra step.
Comment 3 Sergey Prigogin CLA 2013-07-11 13:44:46 EDT
(In reply to comment #2)
It would be nice to display the return value in the Variables view similar to how VS does it.
Comment 4 Marc Khouzam CLA 2013-08-12 13:10:09 EDT
When a step-finish is done, GDB stores the return value of the method in a convenience var such as $1.  Currently, when trying to look at $1 we get an error because we automatically try to fetch its address, which is not valid.  This is tracked by Bug 378729.
Comment 5 Marc Khouzam CLA 2013-08-12 14:35:00 EDT
I've decided to divide this effort into logical updates.

The first step is to take advantage of the fact that after a step-finish operation, GDB stores the return value in a convenience variable (e.g., $1).  I've update the MIStack service to keep track of such return values and provide them to the variables view.  I've pushed an patch to gerrit for that part:
https://git.eclipse.org/r/15377.  If you step-return out of a method, you should now see the return value at the top of the variables view (the label must be updated though, see below).

Note that the patch is based on the fix to Bug 378729.

What remains:
1- provide proper label. So, instead of showing $1 which does not mean anything to the user, we must show "<method> returned" or something of the sort.
2- deal with the "View in memory" action when right-clicking on these new return values in the variable view, which currently causes an error (because convenience variables don't have an address).
3- show return values of methods for any step-over operation instead of only when step-returning.  I have a prototype of this, which simulates a step-over operation by doing repetitive step-in/step-return operations.  It works well but I'm nervous about replacing GDB's actual step-over operation.  I'll post the prototype anyway, and we can see what to do about it.
Comment 6 Marc Khouzam CLA 2013-08-13 13:55:37 EDT
(In reply to comment #5)

> What remains:
> 1- provide proper label. So, instead of showing $1 which does not mean
> anything to the user, we must show "<method> returned" or something of the
> sort.

I've posted patchset 2 at https://git.eclipse.org/r/15377.

That version updates the label properly.  For simplicity, instead of 
  "foo() returned" 
I just put
  "foo()"
but I'm open to suggestions.

The label logic was a bit tricky, due to implementation details.  The requirement is that an expression needs to support an 'alias'.  So, $1 would have alias "foo()".

The patch supports this specifically for the return value use case by having the Expression service take care of the alias.  It does that by keeping track of where a thread is stopped, and then, when it gets an MIFunctionFinishedEvent, it creates the alias based on the name of that previous method.

My first instinct was that the stack service should be the one taking care of determining the method name that returned.  However, the stack service would then need to pass that information to the expression service.  That would mean new APIs:
- IStack.IVariableDMData2 to provide a new getAlias() method
- a new IExpressions4 to provide createExpression(ctx, expression, alias) to pass that alias to the expression service

and the expression service would still have to delete the aliases when appropriate, so it would still need to manage them a bit.  The alternative would be to add yet another API for the stack service to delete them from the expression service.

To avoid all those APIs, I put the logic directly in the MIExpressions service class, but I'm conflicted about it.
Comment 7 Marc Khouzam CLA 2013-08-13 14:22:45 EDT
I ran into a race condition where I received an MIStoppedEvent before the _preceding_ IResumed event.  This can happen because an IResumed event is sent a little later once IRunControl has finished processing MIRunningEvent, while an MIStopped event is sent as soon as it is received from GDB.

The impact what that an alias could be cleared before it was actually used.

I pushed patchset 3 to only work with IResumed and ISuspended event, which is the proper way to do things.
Comment 8 Marc Khouzam CLA 2013-08-15 11:28:14 EDT
I've rebased and pushed patchset 4.

Mikhail, can you have a look when you have a moment?

I think the only thing missing for this first part (assuming the implementation decision is ok), is to look into disabling the "show in memory" action from the variables view for those return values.  I'll look at that when I come back from vacation.
Comment 9 Nobody - feel free to take it CLA 2013-09-17 14:24:52 EDT
Marc,

Did you leave the "Watch" menu enabled intentionally? It adds $1 to the Expressions view but it doesn't seem to be very useful.
Comment 10 Marc Khouzam CLA 2013-09-17 14:33:30 EDT
(In reply to Mikhail Khodjaiants from comment #9)
> Marc,
> 
> Did you leave the "Watch" menu enabled intentionally? It adds $1 to the
> Expressions view but it doesn't seem to be very useful.

I think I missed that.  I'll have a look.
Comment 11 Marc Khouzam CLA 2013-09-18 14:00:17 EDT
(In reply to Mikhail Khodjaiants from comment #9)
> Did you leave the "Watch" menu enabled intentionally? It adds $1 to the
> Expressions view but it doesn't seem to be very useful.

$1 in this case is where GDB stored the returned value, and this expression remains valid throughout the rest of the debug session.  However I agree that it will normally mean nothing to the user.  Furthermore, that value will never change again, so there is not much point is showing it in the expressions view.

I'll try to disable the "watch" on those entries.
Comment 12 Marc Khouzam CLA 2013-09-18 16:40:57 EDT
(In reply to Marc Khouzam from comment #11)

> I'll try to disable the "watch" on those entries.

This is not trivial.  Here is what I believe needs to be done to achieve this:

1-the MIStack service must create a special type of variable for $1, say IConvenienceVariableDMContext (new API #1)
2- step 1 probably should not be in MIStack but in a GdbStack specialized class (new API #2)
3- GdbVariableVMNode must then check for IConvenienceVariableDMContext and choose to create a special expression for such a context.  This will require a new IExpressions.createConvenienceExpression() (new API #3)
4- but since we can't augment IExpressions, we'll need a new IExpressions4 or an IGdbExpressions (new API #4)
4- this IGdbExpressions.createConvenienceExpression() will need to return a new IConvenienceExpressionDMC (new API #5)
5- GdbVariableVMNode will then map IConvenienceExpressionDMC to a new GdbConvenienceVariableExpressionVMC (new API #6)
6- And finally, now that GdbVariableVMNode will know it is dealing with a GdbConvenienceVariableExpressionVMC, it will be able to turn off the "Watch" action by using a new GdbVariableExpressionFactory that can properly implement IWatchExpressionFactoryAdapter2 (new API #7)

So, a pretty convoluted path requiring at least 7 new APIs, one new service extension and one new interface.

On the one hand, if this is what is needed to get this done, we just have to do it.  On the other, how annoying is having the "Watch" action enabled?

Mikhail, what do you think?  Is it worth the code changes?  I'm not trying to sway you either way, I just rather discuss this before coding the changes than after, just in case :) (although I'm close to done anyway).
Comment 13 Nobody - feel free to take it CLA 2013-09-18 17:59:03 EDT
(In reply to Marc Khouzam from comment #12)
> (In reply to Marc Khouzam from comment #11)
> 
> > I'll try to disable the "watch" on those entries.
> 
> This is not trivial.  Here is what I believe needs to be done to achieve
> this:
> 
> 1-the MIStack service must create a special type of variable for $1, say
> IConvenienceVariableDMContext (new API #1)
> 2- step 1 probably should not be in MIStack but in a GdbStack specialized
> class (new API #2)
> 3- GdbVariableVMNode must then check for IConvenienceVariableDMContext and
> choose to create a special expression for such a context.  This will require
> a new IExpressions.createConvenienceExpression() (new API #3)
> 4- but since we can't augment IExpressions, we'll need a new IExpressions4
> or an IGdbExpressions (new API #4)
> 4- this IGdbExpressions.createConvenienceExpression() will need to return a
> new IConvenienceExpressionDMC (new API #5)
> 5- GdbVariableVMNode will then map IConvenienceExpressionDMC to a new
> GdbConvenienceVariableExpressionVMC (new API #6)
> 6- And finally, now that GdbVariableVMNode will know it is dealing with a
> GdbConvenienceVariableExpressionVMC, it will be able to turn off the "Watch"
> action by using a new GdbVariableExpressionFactory that can properly
> implement IWatchExpressionFactoryAdapter2 (new API #7)
> 
> So, a pretty convoluted path requiring at least 7 new APIs, one new service
> extension and one new interface.
> 
> On the one hand, if this is what is needed to get this done, we just have to
> do it.  On the other, how annoying is having the "Watch" action enabled?
> 
> Mikhail, what do you think?  Is it worth the code changes?  I'm not trying
> to sway you either way, I just rather discuss this before coding the changes
> than after, just in case :) (although I'm close to done anyway).

I think it is better to leave the patch as it is, at least for now. If something else comes up we'll take another look at it.
Comment 14 Marc Khouzam CLA 2013-09-18 20:34:43 EDT
(In reply to Mikhail Khodjaiants from comment #13)

> I think it is better to leave the patch as it is, at least for now. If
> something else comes up we'll take another look at it.

Thanks.
While in traffic though, I had the idea that maybe I could avoid all this API mess by simply checking if the expression starts with a '$' at the time we need to know if "watch" should be disabled.  I'll try that out tomorrow to see if it is possible.
Comment 15 Nobody - feel free to take it CLA 2013-09-18 23:58:19 EDT
(In reply to Marc Khouzam from comment #14)
> While in traffic though, I had the idea that maybe I could avoid all this
> API mess by simply checking if the expression starts with a '$' at the time
> we need to know if "watch" should be disabled.  I'll try that out tomorrow
> to see if it is possible.

I thought about it too. Wouldn't it affect watching a register? What about the expression groups? Maybe we should check whether the expression is '$<number>'.
Comment 16 Marc Khouzam CLA 2013-09-19 09:49:07 EDT
(In reply to Mikhail Khodjaiants from comment #15)
> (In reply to Marc Khouzam from comment #14)
> > While in traffic though, I had the idea that maybe I could avoid all this
> > API mess by simply checking if the expression starts with a '$' at the time
> > we need to know if "watch" should be disabled.  I'll try that out tomorrow
> > to see if it is possible.
> 
> I thought about it too. Wouldn't it affect watching a register? 

It's good you mentioned it, I kept thinking of convenience variables and trace variables and forgot about registers.
In the Registers view, a register is of type RegisterVMC instead of VariableExpressionVMC so it won't be affect.  Also, a reg in the reg view does not start with a $ anyway.  And there won't be registers in the variables view.

To my surprise, the "watch" action is enabled in the expressions view, so registers in there would be affected.  I guess the "watch" action can be useful in the Expressions view as it can be applied to children elements.

> What about the expression groups? 

Yes, since "watch" works in the Expressions view, that would be a problem if the first element of the group is a register.

> Maybe we should check whether the expression is '$<number>'.

I agree.  That would be safe for all above cases.
Thanks!

I have posted an updated version on gerrit.
Comment 17 Marc-André Laperle CLA 2013-09-23 17:33:49 EDT
Hi Marc. I tried the current patch, very promising! It looks like this only works with non-stop enabled, is this intentional?

gdb 7.5.91.20130417-cvs-ubuntu
Comment 18 Marc Khouzam CLA 2013-11-29 08:33:37 EST
I've been working on and off on this feature.  I've finally pushed the new version to gerrit.

Here what has improved:

1- all-stop was broken (thanks Marc-Andre/Mikhail for noticing!).  Fixed in next patch.

2- "View memory" was still enabled in the context menu although it does not make sense for a return value.  This is fixed by the patch posted to bug 418710, which is a dependency to this fix.

3- I enabled the MIExpressions JUnit tests to also run with non-stop mode (they only ran in all-stop before)

4- I added two JUnit tests for return values, one to verify a simple return value, and one to verify a complex return value

5- Casting a return value to may cause errors in the detail pane.  This is tracked by bug 422701.  It happens when casting a child of a return value, which is a rare enough case that I don't think we should block this fix because of it.

6- When a return value was a pointer, expanding that pointer in the view would make the $1 string visible E.g., 
   foo() returned        int *     0xabcdef
     *$1                 int       12345
Same issue when casting a return value to an array:
   foo() returned       int[1]     0xabcdef
     $1[0]              int        12345

I wasn't sure what would be best to show instead, so I went with the most obvious solution:
   foo() returned        int *     0xabcdef
     *(foo() returned)   int       12345
Same issue when casting a return value to an array:
   foo() returned        int[1]    0xabcdef
     (foo() returned)[0] int       12345

7- Enhanced expressions are not really impacted by return values.  Currently, the only way to show a return value using enhanced expressions is by using '=*' or '*' which will show that return value, along with all other variables.  What I thought would be nice is that the user could create an enhanced expression such as "=*returned" to always show the return value of the selected thread, if there is one.  I think this is an improvement that should be handled in a separate bug it we feel it is useful.
Comment 19 Marc Khouzam CLA 2014-02-07 16:40:26 EST
Marc-Andre noticed that the methods GdbVariableVMNode.isConvenienceVariable() and GdbVariableVMNode.isRegisterExpression() can give false positives for variables containing a $ sign (but not starting with one).  Variables like 'ca$h' or 'a$12a".  Those variables will not have the "View In Memory" button and sometimes the "Watch" button.

Let's discuss if that is ok or not.

If we allow those buttons all the time, so as to avoid false positives what will happen?  Using "View In Memory" for a return value will pop-up an error dialogue saying "Can't view memory on ($1)", while "Watch" will add the expression $1 to the Expression view.  The $1 expression is not a big deal although kind of cryptic.  The error dialogue annoys me a little more, I find it less friendly.

If we look at likeliness of the user being impacted, we need to compare how often there will be variables with a $ defined in the program vs how often the user will see a return value in CDT.  I personally think return-values will be more common, especially if we starts supporting them for step-over operations.

So the question comes down to: what is worse, an error dialog when trying to see the memory of a return value (more common case), or a missing "view memory/watch" option for a variable containing a $ (rarer case).

We could figure out a way to solve both problems in the code, but from what I had previously investigated, I believe this will be larger effort that the benefit it will bring.

I'm leaning towards accepting the limitation for variables containing a $.

What do people think?
Comment 20 Marc-André Laperle CLA 2014-02-07 17:37:57 EST
(In reply to Marc Khouzam from comment #19)
> So the question comes down to: what is worse, an error dialog when trying to
> see the memory of a return value (more common case), or a missing "view
> memory/watch" option for a variable containing a $ (rarer case).
> 
> We could figure out a way to solve both problems in the code, but from what
> I had previously investigated, I believe this will be larger effort that the
> benefit it will bring.
> 
> I'm leaning towards accepting the limitation for variables containing a $.
> 
> What do people think?

I don't know how common it is to have $ in a variable but I've never seen is personally. So I'm assuming this is very rare therefore I agree with you.
Comment 21 Marc Khouzam CLA 2014-02-13 15:16:21 EST
I've opened bug 428140 to add support to show return values after a step over operation.

I'm changing this bug title to reflect the fact that we now have support to show return values after a step-return operation.

I committed this feature from Gerrit just now.

Thanks Marc-Andre and Mikhail for the reviews.