[Lldb-commits] [lldb] [lldb-dap] Fix global-scope evaluate always routing to command interpreter (PR #225243)

via lldb-commits lldb-commits at lists.llvm.org
Mon Sep 21 17:50:00 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lldb

Author: Nathan Fusselman (nathanfusselman)

<details>
<summary>Changes</summary>

DetectReplMode returned Command unconditionally when there was no frame, since it couldn't check FindVariable without one. A frameless evaluate (no frameId, e.g. `(int)getpid()`) was always treated as an unknown command instead of an expression.

Fall back to SBTarget::FindFirstGlobalVariable when there's no frame: DAP::target is valid independent of frame, so globals/statics can still be checked without one.

rdar://187884135

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


3 Files Affected:

- (modified) lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py (+25-5) 
- (modified) lldb/test/API/tools/lldb-dap/evaluate/main.cpp (+4) 
- (modified) lldb/tools/lldb-dap/DAP.cpp (+6-5) 


``````````diff
diff --git a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
index 2ace6eeccc228..6aef5303c102a 100644
--- a/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
+++ b/lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
@@ -54,6 +54,11 @@ def assert_eval_fails(expression: str):
                 f"expected {expression!r} to fail using {context=!r} in {frame_id=!r}"
             )
 
+        def assert_eval_fails_frameless(expression: str):
+            session.do_evaluate(expression, frameId=None, context=context).error(
+                f"expected {expression!r} to fail using {context=!r} with no frame"
+            )
+
         source = "main.cpp"
         program = self.getBuildArtifact("a.out")
         breakpoint_lines = [
@@ -225,14 +230,22 @@ def assert_eval_fails(expression: str):
         session.verify_evaluate(eval_body, matches="20")
 
         if context_parses_expressions:
-            # Access global variable without a frame
-            # Run in variable mode to avoid interpreting it as a command.
-            session.evaluate("`lldb-dap repl-mode variable", context="repl")
-
+            # Access a global variable with no frame at all (frameId omitted).
             eval_body = session.evaluate("static_int", context=context)
             session.verify_evaluate(eval_body, matches="42", type="int")
 
-            session.evaluate("`lldb-dap repl-mode auto", context="repl")
+            assert_eval_fails_frameless("var1")  # local, not global.
+            assert_eval_fails_frameless("totally_bogus_expr_xyz")
+
+            if context in ("repl", None):
+                # `help` shares its name with a real lldb command (see main.cpp);
+                # it should resolve to the global, with an ambiguity warning.
+                eval_body = session.evaluate("help", context=context)
+                session.verify_evaluate(eval_body, matches="99", type="int")
+
+                # `version` has no matching global; still runs as a command frameless.
+                eval_body = session.evaluate("version", context=context)
+                session.verify_evaluate(eval_body, matches=r"^lldb[ -]\d", has_mem_ref=False)
 
         # In a_function's own frame these names are out of scope.
         assert_eval_fails("var1")
@@ -346,6 +359,13 @@ def assert_eval_fails(expression: str):
 
         session.continue_to_exit()
 
+        if context in ("repl", None):
+            console_str = session.get_console()
+            self.assertIn(
+                "Expression 'help' is both an LLDB command and variable",
+                console_str,
+            )
+
     @skipIfWindows
     def test_generic_evaluate_expressions(self):
         # Tests context-less expression evaluations.
diff --git a/lldb/test/API/tools/lldb-dap/evaluate/main.cpp b/lldb/test/API/tools/lldb-dap/evaluate/main.cpp
index 112726677637c..8201ca0867993 100644
--- a/lldb/test/API/tools/lldb-dap/evaluate/main.cpp
+++ b/lldb/test/API/tools/lldb-dap/evaluate/main.cpp
@@ -8,6 +8,10 @@ static int static_int = 42;
 
 int non_static_int = 43;
 
+// Deliberately shares its name with the `help` lldb command, to test the
+// command/variable ambiguity warning for a frameless (global-scope) evaluate.
+int help = 99;
+
 int a_function(int list) {
   return list; // breakpoint 3
 }
diff --git a/lldb/tools/lldb-dap/DAP.cpp b/lldb/tools/lldb-dap/DAP.cpp
index 7e59541e6c834..7fef0874ddcef 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -592,10 +592,6 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame &frame, std::string &expression,
   if (repl_mode != ReplMode::Auto)
     return repl_mode;
 
-  // We cannot check if expression is a variable without a frame.
-  if (!frame)
-    return ReplMode::Command;
-
   // To determine if the expression is a command or not, check if the first
   // term is a variable or command. If it's a variable in scope we will prefer
   // that behavior and give a warning to the user if they meant to invoke the
@@ -618,7 +614,12 @@ ReplMode DAP::DetectReplMode(lldb::SBFrame &frame, std::string &expression,
   const bool is_command = interpreter.CommandExists(first_cstr) ||
                           interpreter.UserCommandExists(first_cstr) ||
                           interpreter.AliasExists(first_cstr);
-  const bool is_variable = frame.FindVariable(first_cstr).IsValid();
+  // Without a frame there are no locals to check; fall back to `target`
+  // (this DAP's own member, valid independent of `frame`) to look up a
+  // global/static of the same name instead.
+  const bool is_variable =
+      frame ? frame.FindVariable(first_cstr).IsValid()
+            : target.FindFirstGlobalVariable(first_cstr).IsValid();
 
   // If we have both a variable and command, warn the user about the conflict.
   if (!partial_expression && is_command && is_variable) {

``````````

</details>


https://github.com/llvm/llvm-project/pull/225243


More information about the lldb-commits mailing list