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

via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 22 10:54:30 PDT 2026


Author: Nathan Fusselman
Date: 2026-09-22T18:54:23+01:00
New Revision: 2643205b93aa72864b2ac0cd0ae8047bc8048baf

URL: https://github.com/llvm/llvm-project/commit/2643205b93aa72864b2ac0cd0ae8047bc8048baf
DIFF: https://github.com/llvm/llvm-project/commit/2643205b93aa72864b2ac0cd0ae8047bc8048baf.diff

LOG: [lldb-dap] Fix global-scope evaluate always routing to command interpreter (#225243)

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

Added: 
    

Modified: 
    lldb/test/API/tools/lldb-dap/evaluate/TestDAP_evaluate.py
    lldb/test/API/tools/lldb-dap/evaluate/main.cpp
    lldb/tools/lldb-dap/DAP.cpp

Removed: 
    


################################################################################
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..49d8967340e40 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,37 @@ 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).
+                # With no frame, the global should win and emit an ambiguity warning.
+                event_before_help_eval = session.last_event()
+                eval_body = session.evaluate("help", context=context)
+                session.verify_evaluate(eval_body, matches="99", type="int")
+                session.collect_console(
+                    until="Expression 'help' is both an LLDB command and variable",
+                    after=event_before_help_eval,
+                )
+
+                # A valid frame should add local lookup without preventing the same
+                # global from taking precedence over the LLDB command.
+                eval_body = a_frame.evaluate("help", context=context)
+                session.verify_evaluate(eval_body, matches="99", type="int")
+
+                # Use a uniquely named alias with deterministic output to verify that a
+                # command with no matching variable is still run as an LLDB command.
+                session.evaluate(
+                    "command alias lldb_dap_test_command expression -- 12345",
+                    context=context,
+                )
+                eval_body = session.evaluate("lldb_dap_test_command", context=context)
+                session.verify_evaluate(eval_body, matches="12345", has_mem_ref=False)
 
         # In a_function's own frame these names are out of scope.
         assert_eval_fails("var1")

diff  --git a/lldb/test/API/tools/lldb-dap/evaluate/main.cpp b/lldb/test/API/tools/lldb-dap/evaluate/main.cpp
index 112726677637c..97ed7bac90750 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
+// command/variable ambiguity handling for a global variable.
+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 c76ea99caaa4f..cb87c314e4665 100644
--- a/lldb/tools/lldb-dap/DAP.cpp
+++ b/lldb/tools/lldb-dap/DAP.cpp
@@ -492,10 +492,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
@@ -518,7 +514,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();
+  // Check both variables visible in the current frame and globals/statics.
+  // A valid frame should not prevent a global from taking precedence over an
+  // LLDB command with the same name.
+  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) {


        


More information about the lldb-commits mailing list