Skip to content

[lldb-dap] Improving 'variables' hover requests. #146773

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

ashgti
Copy link
Contributor

@ashgti ashgti commented Jul 2, 2025

This partially fixes #146559.

When hovering over variables while debugging with lldb-dap we are receiving hover requests that include symbols.

For example, if you have:

MyClass *foo;

and hover over 'foo', the request will contain the expression expression="*foo" and we're evaluating the dereference, so you end up with different hover results vs the variables view.

A more complete solution would be to implement an
EvaluatableExpressionProvider in the VS Code extension.

For example, if you have a declaration without any whitespace like

char*foo = "bar";

And try to hover over 'foo', the request will be expression="char*foo", which won't evaluate correctly in lldb.

This partially fixes llvm#146559.

When hovering over variables while debugging with lldb-dap we are
receiving hover requests that include symbols.

For example, if you have:

```
MyClass *foo;
```

and hover over 'foo', the request will contain the expression
`expression="*foo"` and we're evaluating the dereference, so you end up
with different hover results vs the variables view.

A more complete solution would be to implement an
[EvaluatableExpressionProvider](https://code.visualstudio.com/api/references/vscode-api#EvaluatableExpressionProvider)
in the VS Code extension.

For example, if you have a declaration without any whitespace like

```c
char*foo = "bar";
```

And try to hover over 'foo', the request will be `expression="char*foo"`.
@ashgti ashgti marked this pull request as ready for review July 2, 2025 20:38
@ashgti ashgti requested a review from JDevlieghere as a code owner July 2, 2025 20:38
@llvmbot
Copy link
Member

llvmbot commented Jul 2, 2025

@llvm/pr-subscribers-lldb

Author: John Harrison (ashgti)

Changes

This partially fixes #146559.

When hovering over variables while debugging with lldb-dap we are receiving hover requests that include symbols.

For example, if you have:

MyClass *foo;

and hover over 'foo', the request will contain the expression expression="*foo" and we're evaluating the dereference, so you end up with different hover results vs the variables view.

A more complete solution would be to implement an
EvaluatableExpressionProvider in the VS Code extension.

For example, if you have a declaration without any whitespace like

char*foo = "bar";

And try to hover over 'foo', the request will be expression="char*foo", which won't evaluate correctly in lldb.


Full diff: https://github.com/llvm/llvm-project/pull/146773.diff

2 Files Affected:

  • (modified) lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py (+1-1)
  • (modified) lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp (+10)
diff --git a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
index 55fb4a961e783..30ee7a1631e29 100644
--- a/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
+++ b/lldb/test/API/tools/lldb-dap/memory/TestDAP_memory.py
@@ -92,7 +92,7 @@ def test_readMemory(self):
         )
         self.continue_to_next_stop()
 
-        ptr_deref = self.dap_server.request_evaluate("*rawptr")["body"]
+        ptr_deref = self.dap_server.request_evaluate("*rawptr", context="repl")["body"]
         memref = ptr_deref["memoryReference"]
 
         # We can read the complete string
diff --git a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
index e1556846dff19..e2161eb999e82 100644
--- a/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
+++ b/lldb/tools/lldb-dap/Handler/EvaluateRequestHandler.cpp
@@ -181,7 +181,17 @@ void EvaluateRequestHandler::operator()(
         expression = dap.last_nonempty_var_expression;
       else
         dap.last_nonempty_var_expression = expression;
+    } else {
+      // If this isn't a REPL context, trim leading pointer/reference characters
+      // to ensure we return the actual value of the expression.
+      // This can come up if you hover over a pointer or reference declaration
+      // like 'MyType *foo;' or `void fn(std::string &arg)`, which results in
+      // the hover request sending '*foo' or `&arg`. When we're not in the REPL,
+      // we should trim these characters to get to the actual variable, which
+      // should have the proper type encoded by the compiler.
+      expression = llvm::StringRef(expression).ltrim("*&").str();
     }
+
     // Always try to get the answer from the local variables if possible. If
     // this fails, then if the context is not "hover", actually evaluate an
     // expression using the expression parser.

@vogelsgesang
Copy link
Member

A more complete solution would be to implement an
EvaluatableExpressionProvider in the VS Code extension.

What's the reason not to implement that more complete solution?

@ashgti
Copy link
Contributor Author

ashgti commented Jul 2, 2025

What's the reason not to implement that more complete solution?

It would be pretty language dependent. For example, if we want to break a .cpp file up into resolvable expressions we'd need to maybe make something in clangd for extracting that from the ast or make a helper clang-frontend tool for this.

We might be able to cheat slightly and if the language has semantic token support for its lsp (clangd/sourcekit-lsp both support this), we might be able to use that to determine how to break up terms.

I suppose could also approach this from the other side and look and see if we can get this from lldb somehow. Maybe there is enough information that we could determine from the debugger itself which parts of the file are expressions and we could make a custom request in lldb-dap for that and call that from our implementation of EvaluatableExpressionProvider.

@ashgti ashgti requested a review from vogelsgesang July 2, 2025 22:23
@labath
Copy link
Collaborator

labath commented Jul 3, 2025

I suppose could also approach this from the other side and look and see if we can get this from lldb somehow. Maybe there is enough information that we could determine from the debugger itself which parts of the file are expressions and we could make a custom request in lldb-dap for that and call that from our implementation of EvaluatableExpressionProvider.

LLDB (kind of by design) doesn't parse the source code of a program. In fact, the only reason it touches it at all is to display it to the user. If you delete the source code, every part of the debugger will work just fine, except you won't actually see the code you're debugging.

So, no, I don't think there's anything in lldb that would help with that...

@da-viper
Copy link
Contributor

da-viper commented Jul 3, 2025

the request will be expression="char*foo", which won't evaluate correctly in lldb.

We could search starting from the back for the first occurrence of either "&" or "*". I think llvm::StringRef::find_last_of() does this.

We should limit this to just the hover context because we can deference a variable in the watch window.

We also need test for it.

@jimingham
Copy link
Collaborator

I don't know how you are doing this, but if you are treating "hover over" requests as expressions and presenting the result of some real expression evaluation, you have to be quite careful about what you evaluate. For instance, you want to make sure that hovering over

i++
Doesn't actually evaluate that expression. Hover-overs really shouldn't be allowed to change program state or they will lead to all sorts of odd behaviors.

@JDevlieghere
Copy link
Member

I don't know how you are doing this, but if you are treating "hover over" requests as expressions and presenting the result of some real expression evaluation, you have to be quite careful about what you evaluate.

The corresponding DAP request is called "expression" which is somewhat confusing. The request includes a "context" (such as "repl" or "hover") which allows us to do something different. Except for "repl", we always try GetValueForVariablePath first, and only fall back to EvaluateExpression if that fails. The exception is "hover" for which we don't do the fallback. So I think we handle this correctly.

@jimingham
Copy link
Collaborator

I don't know how you are doing this, but if you are treating "hover over" requests as expressions and presenting the result of some real expression evaluation, you have to be quite careful about what you evaluate.

The corresponding DAP request is called "expression" which is somewhat confusing. The request includes a "context" (such as "repl" or "hover") which allows us to do something different. Except for "repl", we always try GetValueForVariablePath first, and only fall back to EvaluateExpression if that fails. The exception is "hover" for which we don't do the fallback. So I think we handle this correctly.

Excellent!

Comment on lines +184 to +186
} else {
// If this isn't a REPL context, trim leading pointer/reference characters
// to ensure we return the actual value of the expression.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

doesn't this also trigger in way too many other contexts? E.g., also in the watch panel? I think in the watch panel a *x should actually dereference x

Copy link
Collaborator

@jimingham jimingham Jul 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since people have to voluntarily put something in a watch panel, then in that case we should even run it as an expression. If somebody actually puts i++ in a watch window, presumably they meant for it to increment every time they stepped.
We had a silly bug in gdbtk way back in the day where we if you moused over a selection we would run the selection as an expression. But lots of people seem to select expressions so that they can focus on just the part they selected. As you can imagine, that went poorly.
The main thing - for me - is to always have running expressions be a distinct gesture.

@labath
Copy link
Collaborator

labath commented Jul 4, 2025

the request will be expression="char*foo", which won't evaluate correctly in lldb.

We could search starting from the back for the first occurrence of either "&" or "*". I think llvm::StringRef::find_last_of() does this.

That's rather specific to C. In Swift for instance, the variable name comes before the type (var i:Int).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[lldb-dap] Hover on objc declarations vs expressions is different
7 participants