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

Nathan Fusselman via lldb-commits lldb-commits at lists.llvm.org
Tue Sep 22 07:46:32 PDT 2026


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

>From d31ded40aceab92008cee10951bdf42b30290aaf Mon Sep 17 00:00:00 2001
From: Nathan Fusselman <nfusselman at apple.com>
Date: Mon, 21 Sep 2026 17:26:36 -0700
Subject: [PATCH] [lldb-dap] Fix global-scope evaluate always routing to
 command interpreter

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
---
 .../lldb-dap/evaluate/TestDAP_evaluate.py     | 42 ++++++++++++++++---
 .../test/API/tools/lldb-dap/evaluate/main.cpp |  4 ++
 lldb/tools/lldb-dap/DAP.cpp                   | 11 ++---
 3 files changed, 47 insertions(+), 10 deletions(-)

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..639b32a81d519 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,41 @@ 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 7e59541e6c834..f9f9e3d7c14f2 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();
+  // 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