Skip to content

[lldb] Fix dwim-print to not delete non-result persistent variables #85152

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

Conversation

kastiglione
Copy link
Contributor

@kastiglione kastiglione commented Mar 13, 2024

EvaluateExpression does not always create a new persistent result. If the expression is a bare persistent variable, then a new persistent result is not created. This means the caller can't assume a new persistent result is created for each evaluation. However, dwim-print was doing exactly that: assuming a new persistent result for each evaluation. This resulted in a bug:

(lldb) p int $j = 23
(lldb) p $j
(lldb) p $j

The first p $j would not create a persistent result, and so dwim-print would inadvertently delete $j. The second p $j would fail.

The fix is to try expr as a persistent variable, after trying expr as a frame variable. For persistent variables, this avoids calling EvaluateExpression.

Resolves #84806

rdar://124688427

@llvmbot
Copy link
Member

llvmbot commented Mar 13, 2024

@llvm/pr-subscribers-lldb

Author: Dave Lee (kastiglione)

Changes

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

2 Files Affected:

  • (modified) lldb/source/Commands/CommandObjectDWIMPrint.cpp (+10-2)
  • (modified) lldb/test/API/commands/dwim-print/TestDWIMPrint.py (+12)
diff --git a/lldb/source/Commands/CommandObjectDWIMPrint.cpp b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
index b183cb423111fb..5c043dfd101be6 100644
--- a/lldb/source/Commands/CommandObjectDWIMPrint.cpp
+++ b/lldb/source/Commands/CommandObjectDWIMPrint.cpp
@@ -170,6 +170,14 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
     ExpressionResults expr_result = target.EvaluateExpression(
         expr, exe_scope, valobj_sp, eval_options, &fixed_expression);
 
+    auto persistent_name = valobj_sp->GetName();
+    // EvaluateExpression doesn't generate a new persistent result (`$0`) when
+    // the expression is already just a persistent variable (`$var`). Instead,
+    // the same persistent variable is reused. Take note of when a persistent
+    // result is created, to prevent unintentional deletion of a user's
+    // persistent variable.
+    bool did_persist_result = persistent_name != expr;
+
     // Only mention Fix-Its if the expression evaluator applied them.
     // Compiler errors refer to the final expression after applying Fix-It(s).
     if (!fixed_expression.empty() && target.GetEnableNotifyAboutFixIts()) {
@@ -199,9 +207,9 @@ void CommandObjectDWIMPrint::DoExecute(StringRef command,
         }
       }
 
-      if (suppress_result)
+      if (did_persist_result && suppress_result)
         if (auto result_var_sp =
-                target.GetPersistentVariable(valobj_sp->GetName())) {
+                target.GetPersistentVariable(persistent_name)) {
           auto language = valobj_sp->GetPreferredDisplayLanguage();
           if (auto *persistent_state =
                   target.GetPersistentExpressionStateForLanguage(language))
diff --git a/lldb/test/API/commands/dwim-print/TestDWIMPrint.py b/lldb/test/API/commands/dwim-print/TestDWIMPrint.py
index 040632096c70e7..c650b1e3533e08 100644
--- a/lldb/test/API/commands/dwim-print/TestDWIMPrint.py
+++ b/lldb/test/API/commands/dwim-print/TestDWIMPrint.py
@@ -146,3 +146,15 @@ def test_void_result(self):
             self, "// break here", lldb.SBFileSpec("main.c")
         )
         self.expect("dwim-print (void)15", matching=False, patterns=["(?i)error"])
+
+    def test_preserves_persistent_variables(self):
+        """Test dwim-print does not delete persistent variables."""
+        self.build()
+        lldbutil.run_to_source_breakpoint(
+            self, "// break here", lldb.SBFileSpec("main.c")
+        )
+        self.expect("dwim-print int $i = 15")
+        # Run the same expression twice and verify success. This ensures the
+        # first command does not delete the persistent variable.
+        for _ in range(2):
+            self.expect("dwim-print $i", startstr="(int) 15")

@kastiglione kastiglione requested a review from jimingham March 14, 2024 00:08
// the same persistent variable is reused. Take note of when a persistent
// result is created, to prevent unintentional deletion of a user's
// persistent variable.
bool did_persist_result = persistent_name != expr;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This seems like a really fragile way to test what you want to test, which is whether the result of the expression was a new expression result variable. It might be better to add a "PeekNextPersistentVariableName" that just prints what the next persistent variable would be called but doesn't increment the counter, then assert that the variable you produced is NOT called that?

Copy link
Contributor Author

@kastiglione kastiglione Mar 14, 2024

Choose a reason for hiding this comment

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

I agree that it's not ideal, but I felt it was sufficient.

what you want to test, which is whether the result of the expression was a new expression result variable

exactly. However I think peeking at the result variables is also not ideal. Both my initial stab at this, and your suggestion are deducing whether a new expression result variable was created. If I'm to change the API, I think it'd be ideal to make the API have some means to communicate this explicitly.

There's another approach I considered taking. Before calling EvaluateExpression, try the expression as a persistent variable and check in the target to see if it exists, and if it does exist, use it (and avoid EvaluateExpression altogether). This is similar to how expressions are first treated as frame variables (and as @felipepiovezan has requested, we should try target variable too).

Copy link
Collaborator

Choose a reason for hiding this comment

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

It would be good to have EvaluateExpression tell you whether it created a result variable or not. For instance, calling a void function produces no result. It would be nice to know that happened directly rather than attempting to guess from the returned ValueObject. We definitely know this...
That would definitely be a better way to do this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Jim, I have updated the PR to try the expression as a persistent variable – prior to expression evaluation. My thinking is, instead of evaluating first, then asking if a persistent variable was created, it's more straight to the point to first ask "is this a persistent variable".

I like the idea of trying the various kinds of variables in sequence: frame, target, persistent.

@kastiglione kastiglione requested a review from jimingham March 15, 2024 18:05
@jimingham
Copy link
Collaborator

That's an okay approach if the only time an expression produces a result variable dwim-print should not delete is when a persistent variable is explicitly mentioned. But anyway, since that's the case we're fixing, and checking as a persistent variable as a short-cut early on isn't a bad idea, it is fine for these purposes.

I think you need to get the persistent variables for whatever the language of the expression happens to be, not just get all the persistent variables. Otherwise, if you define a C persistent variable, then stop in a swift frame and do:

(lldb) p $works_in_c
10
(lldb) p $works_in_c + 10
unknown identifier "$works_in_c"

which would be confusing.

@kastiglione
Copy link
Contributor Author

I'll change this to scope lookup of persistent variables to the expected language.

However, there's an argument to be made that dwim-print $my_var should work anywhere. I think that follows the spirit of "do what I mean". Maybe in the future I'll propose that in a separate change.

@jimingham
Copy link
Collaborator

jimingham commented Mar 15, 2024 via email

@kastiglione
Copy link
Contributor Author

I don't consider it a failure to not be able to perform mixed language expressions. But it would have to be presented to the user in a way that is understandable, which could be a challenge.

Anyway, I pushed the change that scopes the lookup to the expected language.

thank you for the reviews

@jimingham
Copy link
Collaborator

jimingham commented Mar 15, 2024

I don't consider it a failure to not be able to perform mixed language expressions. But it would have to be presented to the user in a way that is understandable, which could be a challenge.

Anyway, I pushed the change that scopes the lookup to the expected language.

thank you for the reviews

I'm not sure what you mean by "mixed language expressions". What I'm trying to avoid is, if stopped in a swift frame, you get:

(lldb) p cpp_object_ptr
<get a value>
(lldb) p cpp_object_ptr->cpp_method()
<unknown identifier cpp_object_ptr>

That's not a mixed language expression, that's a perfectly good C++ expression. I don't think it makes sense to present simple variables of all language types, but not evaluate expressions using all the available expression parsers, starting with the obvious one first.

Copy link
Collaborator

@jimingham jimingham left a comment

Choose a reason for hiding this comment

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

LGTM

@kastiglione
Copy link
Contributor Author

kastiglione commented Mar 15, 2024

I'm not sure what you mean by "mixed language expressions".

I was thinking about the case where a user attempts p $some_c_var + 1 in Swift frame – which is valid swift as far as the debugger is concerned. I said mixed because it's got a C var inside a Swift expression.

@kastiglione kastiglione merged commit 4da2b54 into llvm:main Mar 15, 2024
@kastiglione kastiglione deleted the lldb-Fix-dwim-print-to-not-delete-non-result-persistent-variables branch March 15, 2024 23:09
kastiglione added a commit to swiftlang/llvm-project that referenced this pull request Mar 15, 2024
…lvm#85152)

`EvaluateExpression` does not always create a new persistent result. If the expression
is a bare persistent variable, then a new persistent result is not created. This means
the caller can't assume a new persistent result is created for each evaluation.
However, `dwim-print` was doing exactly that: assuming a new persistent result for each
evaluation. This resulted in a bug:

```
(lldb) p int $j = 23
(lldb) p $j
(lldb) p $j
```

The first `p $j` would not create a persistent result, and so `dwim-print` would
inadvertently delete `$j`. The second `p $j` would fail.

The fix is to try `expr` as a persistent variable, after trying `expr` as a frame
variable. For persistent variables, this avoids calling `EvaluateExpression`.

Resolves llvm#84806

rdar://124688427
(cherry picked from commit 4da2b54)
@jimingham
Copy link
Collaborator

jimingham commented Mar 15, 2024

I'm not sure what you mean by "mixed language expressions".

I was thinking about the case where a user attempts p $some_c_var + 1 in Swift frame – which is valid swift as far as the debugger is concerned. I said mixed because it's got a C var inside a Swift expression.

The important thing is not whether this is abstractly a valid swift expression, but rather that is an expression that would execute correctly in the Swift Expression parser. That won't happen with this expression because the swift compiler can't find $some_c_var and and even if it could it wouldn't know how to use it.

My take is that if I do p some_var and you return some_var successfully, then when I do p some_var->some_method() you will use the language in which you found that symbol to evaluate that expression that uses it. I don't see how you will know that directly, I think the best you can do is try them all in some reasonable order.

dwim-print has a language option because it's really trying to be the expression parser but done as efficiently as possible. Having it just ignore whatever that language option is supposed to instruct it to do seems wrong, and having it sometimes ignore the language and other times not is even more wrong, IMO.

kastiglione added a commit to swiftlang/llvm-project that referenced this pull request Mar 16, 2024
…lvm#85152)

`EvaluateExpression` does not always create a new persistent result. If the expression
is a bare persistent variable, then a new persistent result is not created. This means
the caller can't assume a new persistent result is created for each evaluation.
However, `dwim-print` was doing exactly that: assuming a new persistent result for each
evaluation. This resulted in a bug:

```
(lldb) p int $j = 23
(lldb) p $j
(lldb) p $j
```

The first `p $j` would not create a persistent result, and so `dwim-print` would
inadvertently delete `$j`. The second `p $j` would fail.

The fix is to try `expr` as a persistent variable, after trying `expr` as a frame
variable. For persistent variables, this avoids calling `EvaluateExpression`.

Resolves llvm#84806

rdar://124688427
(cherry picked from commit 4da2b54)
kastiglione added a commit to swiftlang/llvm-project that referenced this pull request Mar 18, 2024
…lvm#85152)

`EvaluateExpression` does not always create a new persistent result. If the expression
is a bare persistent variable, then a new persistent result is not created. This means
the caller can't assume a new persistent result is created for each evaluation.
However, `dwim-print` was doing exactly that: assuming a new persistent result for each
evaluation. This resulted in a bug:

```
(lldb) p int $j = 23
(lldb) p $j
(lldb) p $j
```

The first `p $j` would not create a persistent result, and so `dwim-print` would
inadvertently delete `$j`. The second `p $j` would fail.

The fix is to try `expr` as a persistent variable, after trying `expr` as a frame
variable. For persistent variables, this avoids calling `EvaluateExpression`.

Resolves llvm#84806

rdar://124688427
(cherry picked from commit 4da2b54)

(cherry-picked from commit 38b55e1)
kastiglione added a commit to swiftlang/llvm-project that referenced this pull request Mar 18, 2024
…lvm#85152) (#8428)

`EvaluateExpression` does not always create a new persistent result. If the expression
is a bare persistent variable, then a new persistent result is not created. This means
the caller can't assume a new persistent result is created for each evaluation.
However, `dwim-print` was doing exactly that: assuming a new persistent result for each
evaluation. This resulted in a bug:

```
(lldb) p int $j = 23
(lldb) p $j
(lldb) p $j
```

The first `p $j` would not create a persistent result, and so `dwim-print` would
inadvertently delete `$j`. The second `p $j` would fail.

The fix is to try `expr` as a persistent variable, after trying `expr` as a frame
variable. For persistent variables, this avoids calling `EvaluateExpression`.

Resolves llvm#84806

rdar://124688427
(cherry picked from commit 4da2b54)

(cherry-picked from commit 38b55e1)
kastiglione added a commit to swiftlang/llvm-project that referenced this pull request Apr 2, 2024
…lvm#85152)

`EvaluateExpression` does not always create a new persistent result. If the expression
is a bare persistent variable, then a new persistent result is not created. This means
the caller can't assume a new persistent result is created for each evaluation.
However, `dwim-print` was doing exactly that: assuming a new persistent result for each
evaluation. This resulted in a bug:

```
(lldb) p int $j = 23
(lldb) p $j
(lldb) p $j
```

The first `p $j` would not create a persistent result, and so `dwim-print` would
inadvertently delete `$j`. The second `p $j` would fail.

The fix is to try `expr` as a persistent variable, after trying `expr` as a frame
variable. For persistent variables, this avoids calling `EvaluateExpression`.

Resolves llvm#84806

rdar://124688427
(cherry picked from commit 4da2b54)

(cherry-picked from commit 38b55e1)
adrian-prantl added a commit to swiftlang/llvm-project that referenced this pull request Apr 4, 2024
…m-print-to-not-delete-non-result-persistent-variables-85152

[lldb] Fix dwim-print to not delete non-result persistent variables (llvm#85152)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

LLDB: Inconsistant existance of expression global variable (use of undeclared identifier)
3 participants