[Lldb-commits] [lldb] [lldb] Add extended variable support to Get*VariableList. (PR #181501)

Aman LaChapelle via lldb-commits lldb-commits at lists.llvm.org
Wed Apr 15 22:52:32 PDT 2026


https://github.com/bzcheeseman updated https://github.com/llvm/llvm-project/pull/181501

>From 0dee745bdc3ac4c0b371995d1ef523308df803d7 Mon Sep 17 00:00:00 2001
From: bzcheeseman <aman.lachapelle at gmail.com>
Date: Wed, 15 Apr 2026 22:49:41 -0700
Subject: [PATCH 1/3] [lldb] Reformat OptionGroupVariable.{h,cpp}, NFC.

This patch runs clang-format on OptionGroupVariable.{h,cpp}.

stack-info: PR: https://github.com/llvm/llvm-project/pull/192395, branch: users/bzcheeseman/stack/10
---
 .../lldb/Interpreter/OptionGroupVariable.h    |   6 +-
 .../Interpreter/OptionGroupVariable.cpp       | 105 ++++++++++++++----
 2 files changed, 87 insertions(+), 24 deletions(-)

diff --git a/lldb/include/lldb/Interpreter/OptionGroupVariable.h b/lldb/include/lldb/Interpreter/OptionGroupVariable.h
index c9f1283d4de20..c1fbeb8e78b26 100644
--- a/lldb/include/lldb/Interpreter/OptionGroupVariable.h
+++ b/lldb/include/lldb/Interpreter/OptionGroupVariable.h
@@ -30,9 +30,9 @@ class OptionGroupVariable : public OptionGroup {
   void OptionParsingStarting(ExecutionContext *execution_context) override;
 
   bool include_frame_options : 1,
-      show_args : 1,    // Frame option only (include_frame_options == true)
-      show_recognized_args : 1,  // Frame option only (include_frame_options ==
-                                 // true)
+      show_args : 1, // Frame option only (include_frame_options == true)
+      show_recognized_args : 1, // Frame option only (include_frame_options ==
+                                // true)
       show_locals : 1,  // Frame option only (include_frame_options == true)
       show_globals : 1, // Frame option only (include_frame_options == true)
       use_regex : 1, show_scope : 1, show_decl : 1;
diff --git a/lldb/source/Interpreter/OptionGroupVariable.cpp b/lldb/source/Interpreter/OptionGroupVariable.cpp
index 9bffe1dba85e7..065b70a460c2f 100644
--- a/lldb/source/Interpreter/OptionGroupVariable.cpp
+++ b/lldb/source/Interpreter/OptionGroupVariable.cpp
@@ -20,33 +20,96 @@ using namespace lldb_private;
 // if you add any options here, remember to update the counters in
 // OptionGroupVariable::GetNumDefinitions()
 static constexpr OptionDefinition g_variable_options[] = {
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "no-args", 'a',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "no-args",
+     'a',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
      "Omit function arguments."},
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "no-recognized-args", 't',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "no-recognized-args",
+     't',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
      "Omit recognized function arguments."},
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "no-locals", 'l',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "no-locals",
+     'l',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
      "Omit local variables."},
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "show-globals", 'g',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "show-globals",
+     'g',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
      "Show the current frame source file global and static variables."},
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "show-declaration", 'c',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "show-declaration",
+     'c',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
      "Show variable declaration information (source file and line where the "
      "variable was declared)."},
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "regex", 'r',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeRegularExpression,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "regex",
+     'r',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeRegularExpression,
      "The <variable-name> argument for name lookups are regular expressions."},
-    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2, false, "scope", 's',
-     OptionParser::eNoArgument, nullptr, {}, 0, eArgTypeNone,
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "scope",
+     's',
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
      "Show variable scope (argument, local, global, static)."},
-    {LLDB_OPT_SET_1, false, "summary", 'y', OptionParser::eRequiredArgument,
-     nullptr, {}, 0, eArgTypeName,
+    {LLDB_OPT_SET_1,
+     false,
+     "summary",
+     'y',
+     OptionParser::eRequiredArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeName,
      "Specify the summary that the variable output should use."},
-    {LLDB_OPT_SET_2, false, "summary-string", 'z',
-     OptionParser::eRequiredArgument, nullptr, {}, 0, eArgTypeName,
+    {LLDB_OPT_SET_2,
+     false,
+     "summary-string",
+     'z',
+     OptionParser::eRequiredArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeName,
      "Specify a summary string to use to format the variable output."},
 };
 
@@ -125,10 +188,10 @@ OptionGroupVariable::SetOptionValue(uint32_t option_idx,
 
 void OptionGroupVariable::OptionParsingStarting(
     ExecutionContext *execution_context) {
-  show_args = true;     // Frame option only
+  show_args = true;            // Frame option only
   show_recognized_args = true; // Frame option only
-  show_locals = true;   // Frame option only
-  show_globals = false; // Frame option only
+  show_locals = true;          // Frame option only
+  show_globals = false;        // Frame option only
   show_decl = false;
   use_regex = false;
   show_scope = false;

>From 694a0dd25dfd485f5ff2da5a08429d18b74c3bfb Mon Sep 17 00:00:00 2001
From: bzcheeseman <aman.lachapelle at gmail.com>
Date: Sat, 14 Feb 2026 10:55:50 -0800
Subject: [PATCH 2/3] [lldb] Scaffolding for synthetic variable support.

This patch handles most of the scaffolding for synthetic variable support that isn't directly tied to functional changes. This patch will be used by one following patch that actually modifies the lldb_private::StackFrame API to allow us to fetch synthetic variables.

There were a couple important/interesting decisions made in this patch that should be noted:
- Any value type may be synthetic, which is why it's a mask applied over the top of another value type.
- When printing frame variables with `fr v`, default to showing synthetic variables.

This new value type mask makes some of the ValueType handling more interesting, but since nothing generates objects with this mask until the next patch, we can land the concept in this patch in some amount of isolation.

stack-info: PR: https://github.com/llvm/llvm-project/pull/181500, branch: users/bzcheeseman/stack/8
---
 lldb/include/lldb/API/SBVariablesOptions.h    |  4 +++
 .../lldb/Interpreter/OptionGroupVariable.h    |  5 +--
 lldb/include/lldb/Utility/ValueType.h         | 32 +++++++++++++++++++
 lldb/include/lldb/lldb-enumerations.h         |  6 ++++
 lldb/source/API/SBVariablesOptions.cpp        | 21 ++++++++++--
 .../Interpreter/OptionGroupVariable.cpp       | 19 +++++++++--
 6 files changed, 81 insertions(+), 6 deletions(-)
 create mode 100644 lldb/include/lldb/Utility/ValueType.h

diff --git a/lldb/include/lldb/API/SBVariablesOptions.h b/lldb/include/lldb/API/SBVariablesOptions.h
index 53ab4b7e14f2f..87680012d4a1a 100644
--- a/lldb/include/lldb/API/SBVariablesOptions.h
+++ b/lldb/include/lldb/API/SBVariablesOptions.h
@@ -46,6 +46,10 @@ class LLDB_API SBVariablesOptions {
 
   void SetIncludeStatics(bool);
 
+  bool GetIncludeSynthetic() const;
+
+  void SetIncludeSynthetic(bool);
+
   bool GetInScopeOnly() const;
 
   void SetInScopeOnly(bool);
diff --git a/lldb/include/lldb/Interpreter/OptionGroupVariable.h b/lldb/include/lldb/Interpreter/OptionGroupVariable.h
index c1fbeb8e78b26..ec158a9f6ffc4 100644
--- a/lldb/include/lldb/Interpreter/OptionGroupVariable.h
+++ b/lldb/include/lldb/Interpreter/OptionGroupVariable.h
@@ -33,8 +33,9 @@ class OptionGroupVariable : public OptionGroup {
       show_args : 1, // Frame option only (include_frame_options == true)
       show_recognized_args : 1, // Frame option only (include_frame_options ==
                                 // true)
-      show_locals : 1,  // Frame option only (include_frame_options == true)
-      show_globals : 1, // Frame option only (include_frame_options == true)
+      show_locals : 1,    // Frame option only (include_frame_options == true)
+      show_globals : 1,   // Frame option only (include_frame_options == true)
+      show_synthetic : 1, // Frame option only (include_frame_options == true)
       use_regex : 1, show_scope : 1, show_decl : 1;
   OptionValueString summary;        // the name of a named summary
   OptionValueString summary_string; // a summary string
diff --git a/lldb/include/lldb/Utility/ValueType.h b/lldb/include/lldb/Utility/ValueType.h
new file mode 100644
index 0000000000000..e286eeab1b0b1
--- /dev/null
+++ b/lldb/include/lldb/Utility/ValueType.h
@@ -0,0 +1,32 @@
+//===-- State.h -------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_UTILITY_VALUETYPE_H
+#define LLDB_UTILITY_VALUETYPE_H
+
+#include "lldb/lldb-enumerations.h"
+
+namespace lldb_private {
+/// Get the base value type - for when we don't care if the value is synthetic
+/// or not, or when we've already handled that case.
+constexpr lldb::ValueType GetBaseValueType(lldb::ValueType vt) {
+  return lldb::ValueType(vt & ~lldb::ValueTypeSyntheticMask);
+}
+
+/// Given a base value type, return a version that carries the synthetic bit.
+constexpr lldb::ValueType GetSyntheticValueType(lldb::ValueType base) {
+  return lldb::ValueType(base | lldb::ValueTypeSyntheticMask);
+}
+
+/// Return true if vt represents a synthetic value, false if not.
+constexpr bool IsSyntheticValueType(lldb::ValueType vt) {
+  return vt & lldb::ValueTypeSyntheticMask;
+}
+} // namespace lldb_private
+
+#endif // LLDB_UTILITY_VALUETYPE_H
diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h
index 4cbbabbf879ad..fa2e548bbdfaa 100644
--- a/lldb/include/lldb/lldb-enumerations.h
+++ b/lldb/include/lldb/lldb-enumerations.h
@@ -340,6 +340,12 @@ enum ValueType {
   eValueTypeVTableEntry = 10, ///< function pointer in virtual function table
 };
 
+/// A mask that we can use to check if the value type is synthetic or not.
+// NOTE: This limits the number of value types to 31, but that's 3x more than
+// what we currently have now. See lldb/Utility/ValueType.h for helpers for
+// working with synthetic value types.
+static constexpr unsigned ValueTypeSyntheticMask = 0x20;
+
 /// Token size/granularities for Input Readers.
 
 enum InputReaderGranularity {
diff --git a/lldb/source/API/SBVariablesOptions.cpp b/lldb/source/API/SBVariablesOptions.cpp
index 989d159139cca..4d4eba0c635ce 100644
--- a/lldb/source/API/SBVariablesOptions.cpp
+++ b/lldb/source/API/SBVariablesOptions.cpp
@@ -20,8 +20,8 @@ class VariablesOptionsImpl {
 public:
   VariablesOptionsImpl()
       : m_include_arguments(false), m_include_locals(false),
-        m_include_statics(false), m_in_scope_only(false),
-        m_include_runtime_support_values(false) {}
+        m_include_statics(false), m_include_synthetic(false),
+        m_in_scope_only(false), m_include_runtime_support_values(false) {}
 
   VariablesOptionsImpl(const VariablesOptionsImpl &) = default;
 
@@ -51,6 +51,10 @@ class VariablesOptionsImpl {
 
   void SetIncludeStatics(bool b) { m_include_statics = b; }
 
+  bool GetIncludeSynthetic() const { return m_include_synthetic; }
+
+  void SetIncludeSynthetic(bool b) { m_include_synthetic = b; }
+
   bool GetInScopeOnly() const { return m_in_scope_only; }
 
   void SetInScopeOnly(bool b) { m_in_scope_only = b; }
@@ -71,6 +75,7 @@ class VariablesOptionsImpl {
   bool m_include_arguments : 1;
   bool m_include_locals : 1;
   bool m_include_statics : 1;
+  bool m_include_synthetic : 1;
   bool m_in_scope_only : 1;
   bool m_include_runtime_support_values : 1;
   LazyBool m_include_recognized_arguments =
@@ -157,6 +162,18 @@ void SBVariablesOptions::SetIncludeStatics(bool statics) {
   m_opaque_up->SetIncludeStatics(statics);
 }
 
+bool SBVariablesOptions::GetIncludeSynthetic() const {
+  LLDB_INSTRUMENT_VA(this);
+
+  return m_opaque_up->GetIncludeSynthetic();
+}
+
+void SBVariablesOptions::SetIncludeSynthetic(bool synthetic) {
+  LLDB_INSTRUMENT_VA(this, synthetic);
+
+  m_opaque_up->SetIncludeSynthetic(synthetic);
+}
+
 bool SBVariablesOptions::GetInScopeOnly() const {
   LLDB_INSTRUMENT_VA(this);
 
diff --git a/lldb/source/Interpreter/OptionGroupVariable.cpp b/lldb/source/Interpreter/OptionGroupVariable.cpp
index 065b70a460c2f..0024b4294791e 100644
--- a/lldb/source/Interpreter/OptionGroupVariable.cpp
+++ b/lldb/source/Interpreter/OptionGroupVariable.cpp
@@ -60,6 +60,16 @@ static constexpr OptionDefinition g_variable_options[] = {
      0,
      eArgTypeNone,
      "Show the current frame source file global and static variables."},
+    {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
+     false,
+     "no-synthetic",
+     'e', // Use 'e' for synthEtic - s and y are both taken.
+     OptionParser::eNoArgument,
+     nullptr,
+     {},
+     0,
+     eArgTypeNone,
+     "Omit extended variables."},
     {LLDB_OPT_SET_1 | LLDB_OPT_SET_2,
      false,
      "show-declaration",
@@ -140,8 +150,9 @@ static Status ValidateSummaryString(const char *str, void *) {
 OptionGroupVariable::OptionGroupVariable(bool show_frame_options)
     : include_frame_options(show_frame_options), show_args(false),
       show_recognized_args(false), show_locals(false), show_globals(false),
-      use_regex(false), show_scope(false), show_decl(false),
-      summary(ValidateNamedSummary), summary_string(ValidateSummaryString) {}
+      show_synthetic(true), use_regex(false), show_scope(false),
+      show_decl(false), summary(ValidateNamedSummary),
+      summary_string(ValidateSummaryString) {}
 
 Status
 OptionGroupVariable::SetOptionValue(uint32_t option_idx,
@@ -164,6 +175,9 @@ OptionGroupVariable::SetOptionValue(uint32_t option_idx,
   case 'g':
     show_globals = true;
     break;
+  case 'e':
+    show_synthetic = false;
+    break;
   case 'c':
     show_decl = true;
     break;
@@ -192,6 +206,7 @@ void OptionGroupVariable::OptionParsingStarting(
   show_recognized_args = true; // Frame option only
   show_locals = true;          // Frame option only
   show_globals = false;        // Frame option only
+  show_synthetic = true;       // Frame option only
   show_decl = false;
   use_regex = false;
   show_scope = false;

>From 664608da9dc162c36898dc5b19a74d72ed8c2743 Mon Sep 17 00:00:00 2001
From: bzcheeseman <aman.lachapelle at gmail.com>
Date: Sat, 14 Feb 2026 11:00:57 -0800
Subject: [PATCH 3/3] [lldb] Add synthetic variable support to
 Get*VariableList.

This patch adds a new flag to the lldb_private::StackFrame API to get variable lists: `include_synthetic_vars`.  This allows ScriptedFrame (and other future synthetic frames) to construct 'fake' variables and return them in the VariableList, so that commands like `fr v` and `SBFrame::GetVariables` can show them to the user as requested.

This patch includes all changes necessary to call the API the new way - I tried to use my best judgement on when to include synthetic variables or not and leave comments explaining the decision.

As a consequence of producing synthetic variables, this patch means that ScriptedFrame can produce Variable objects with ValueType that contains a ValueTypeExtendedMask in a high bit. This necessarily complicates some of the switch/case handling in places where we would expect to find such variables, and this patch makes best effort to address all such cases as well. From experience, they tend to show up whenever we're dealing with checking if a Variable is in a specified scope, which means we basically have to check the high bit against some user input saying "yes/no synthetic variables".

stack-info: PR: https://github.com/llvm/llvm-project/pull/181501, branch: users/bzcheeseman/stack/9
---
 lldb/include/lldb/Target/BorrowedStackFrame.h |  2 +
 lldb/include/lldb/Target/StackFrame.h         | 16 ++++++
 lldb/source/API/SBFrame.cpp                   | 52 +++++++++++++++----
 lldb/source/Commands/CommandObjectFrame.cpp   | 42 +++++++++++----
 lldb/source/Core/IOHandlerCursesGUI.cpp       |  3 +-
 .../Clang/ClangExpressionDeclMap.cpp          | 13 +++--
 .../Process/scripted/ScriptedFrame.cpp        | 51 +++++++++++++++---
 .../Plugins/Process/scripted/ScriptedFrame.h  | 15 ++++--
 lldb/source/Symbol/Variable.cpp               | 10 ++--
 lldb/source/Target/BorrowedStackFrame.cpp     |  9 ++--
 lldb/source/Target/StackFrame.cpp             | 18 ++++++-
 .../TestScriptedFrameProvider.py              | 34 +++++++++---
 .../test_frame_providers.py                   |  2 +
 13 files changed, 213 insertions(+), 54 deletions(-)

diff --git a/lldb/include/lldb/Target/BorrowedStackFrame.h b/lldb/include/lldb/Target/BorrowedStackFrame.h
index 72e7777961da7..1baa35effdec5 100644
--- a/lldb/include/lldb/Target/BorrowedStackFrame.h
+++ b/lldb/include/lldb/Target/BorrowedStackFrame.h
@@ -78,10 +78,12 @@ class BorrowedStackFrame : public StackFrame {
   lldb::RegisterContextSP GetRegisterContext() override;
 
   VariableList *GetVariableList(bool get_file_globals,
+                                bool include_synthetic_vars,
                                 Status *error_ptr) override;
 
   lldb::VariableListSP
   GetInScopeVariableList(bool get_file_globals,
+                         bool include_synthetic_vars = true,
                          bool must_have_valid_location = false) override;
 
   lldb::ValueObjectSP GetValueForVariableExpressionPath(
diff --git a/lldb/include/lldb/Target/StackFrame.h b/lldb/include/lldb/Target/StackFrame.h
index 46922448d6e59..23e4ab20db3b2 100644
--- a/lldb/include/lldb/Target/StackFrame.h
+++ b/lldb/include/lldb/Target/StackFrame.h
@@ -262,6 +262,12 @@ class StackFrame : public ExecutionContextScope,
   ///     that are visible to the entire compilation unit (e.g. file
   ///     static in C, globals that are homed in this CU).
   ///
+  /// \param[in] include_synthetic_vars
+  ///     Whether to also include synthetic variables from other
+  ///     sources. For example, synthetic frames can produce
+  ///     variables that aren't strictly 'variables', but can still
+  ///     be displayed with their values.
+  ///
   /// \param [out] error_ptr
   ///   If there is an error in the debug information that prevents variables
   ///   from being fetched. \see SymbolFile::GetFrameVariableError() for full
@@ -270,6 +276,7 @@ class StackFrame : public ExecutionContextScope,
   /// \return
   ///     A pointer to a list of variables.
   virtual VariableList *GetVariableList(bool get_file_globals,
+                                        bool include_synthetic_vars,
                                         Status *error_ptr);
 
   /// Retrieve the list of variables that are in scope at this StackFrame's
@@ -284,6 +291,14 @@ class StackFrame : public ExecutionContextScope,
   ///     that are visible to the entire compilation unit (e.g. file
   ///     static in C, globals that are homed in this CU).
   ///
+  /// \param[in] include_synthetic_vars
+  ///     Whether to also include extended variables from other
+  ///     sources. For example, synthetic frames can produce
+  ///     variables that aren't strictly 'variables', but can still
+  ///     be displayed with their values. Defaults to `true` because
+  ///     we are assuming that if a user's context has extended variables,
+  ///     they want them shown.
+  ///
   /// \param[in] must_have_valid_location
   ///     Whether to filter variables whose location is not available at this
   ///     StackFrame's pc.
@@ -291,6 +306,7 @@ class StackFrame : public ExecutionContextScope,
   ///     A pointer to a list of variables.
   virtual lldb::VariableListSP
   GetInScopeVariableList(bool get_file_globals,
+                         bool include_synthetic_vars = true,
                          bool must_have_valid_location = false);
 
   /// Create a ValueObject for a variable name / pathname, possibly including
diff --git a/lldb/source/API/SBFrame.cpp b/lldb/source/API/SBFrame.cpp
index 31947a054aaaf..69357cde4f9ec 100644
--- a/lldb/source/API/SBFrame.cpp
+++ b/lldb/source/API/SBFrame.cpp
@@ -12,6 +12,8 @@
 
 #include "lldb/API/SBFrame.h"
 
+#include "lldb/Utility/ValueType.h"
+#include "lldb/lldb-enumerations.h"
 #include "lldb/lldb-types.h"
 
 #include "Utils.h"
@@ -498,7 +500,11 @@ SBValue SBFrame::FindValue(const char *name, ValueType value_type,
 
   VariableList variable_list;
 
-  switch (value_type) {
+  bool include_synthetic_vars = IsSyntheticValueType(value_type);
+  // Switch on the value_type without the mask, but keep it in the value type so
+  // we can use it later when we look for variables in the list.
+  auto base_value_type = GetBaseValueType(value_type);
+  switch (base_value_type) {
   case eValueTypeVariableGlobal:        // global variable
   case eValueTypeVariableStatic:        // static variable
   case eValueTypeVariableArgument:      // function argument variables
@@ -514,14 +520,17 @@ SBValue SBFrame::FindValue(const char *name, ValueType value_type,
       sc.block->AppendVariables(
           can_create, get_parent_variables, stop_if_block_is_inlined_function,
           [frame](Variable *v) { return v->IsInScope(frame); }, &variable_list);
-    if (value_type == eValueTypeVariableGlobal ||
-        value_type == eValueTypeVariableStatic) {
+    // Fetch variables from the frame if we need to get globals/statics/extended
+    // variables.
+    if (base_value_type == eValueTypeVariableGlobal ||
+        base_value_type == eValueTypeVariableStatic || include_synthetic_vars) {
       const bool get_file_globals = true;
-      VariableList *frame_vars =
-          frame->GetVariableList(get_file_globals, nullptr);
+      VariableList *frame_vars = frame->GetVariableList(
+          get_file_globals, include_synthetic_vars, nullptr);
       if (frame_vars)
         frame_vars->AppendVariablesIfUnique(variable_list);
     }
+
     ConstString const_name(name);
     VariableSP variable_sp(variable_list.FindVariable(const_name, value_type));
     if (variable_sp) {
@@ -686,8 +695,22 @@ lldb::SBValueList SBFrame::GetVariables(bool arguments, bool locals,
 
 /// Returns true if the variable is in any of the requested scopes.
 static bool IsInRequestedScope(bool statics, bool arguments, bool locals,
-                               Variable &var) {
-  switch (var.GetScope()) {
+                               bool synthetic, Variable &var) {
+  auto value_type = var.GetScope();
+  // Check if the variable is synthetic first.
+  bool is_synthetic = IsSyntheticValueType(value_type)
+  if (is_synthetic) {
+    // If the variable is extended but we don't want those, then it's
+    // automatically out of scope.
+    if (!synthetic)
+      return false;
+
+    // Clear the ValueTypeExtendedMask bit so the rest of the switch works
+    // correctly.
+    value_type = GetBaseValueType(value_type);
+  }
+
+  switch (value_type) {
   case eValueTypeVariableGlobal:
   case eValueTypeVariableStatic:
   case eValueTypeVariableThreadLocal:
@@ -702,7 +725,13 @@ static bool IsInRequestedScope(bool statics, bool arguments, bool locals,
   default:
     break;
   }
-  return false;
+
+  // The default for all other value types is !is_synthetic. At this point, if
+  // we didn't want extended variables we'd have exited by now anyway, so we
+  // must want them. Aside from the modifiers above that should apply equally to
+  // extended and normal variables, any other extended variable we should
+  // default to showing.
+  return !is_synthetic;
 }
 
 enum WasInterrupted { Yes, No };
@@ -718,13 +747,16 @@ static std::pair<WasInterrupted, Status> FetchVariablesUnlessInterrupted(
   const bool statics = options.GetIncludeStatics();
   const bool arguments = options.GetIncludeArguments();
   const bool locals = options.GetIncludeLocals();
+  const bool synthetic = options.GetIncludeSynthetic();
   const bool in_scope_only = options.GetInScopeOnly();
   const bool include_runtime_support_values =
       options.GetIncludeRuntimeSupportValues();
   const lldb::DynamicValueType use_dynamic = options.GetUseDynamic();
 
   Status var_error;
-  VariableList *variable_list = frame.GetVariableList(true, &var_error);
+  // Fetch all variables available and filter them later.
+  VariableList *variable_list = frame.GetVariableList(
+      /*get_file_globals=*/true, /*include_synthetic_vars=*/true, &var_error);
 
   std::set<VariableSP> variable_set;
 
@@ -734,7 +766,7 @@ static std::pair<WasInterrupted, Status> FetchVariablesUnlessInterrupted(
   size_t num_produced = 0;
   for (const VariableSP &variable_sp : *variable_list) {
     if (!variable_sp ||
-        !IsInRequestedScope(statics, arguments, locals, *variable_sp))
+        !IsInRequestedScope(statics, arguments, locals, synthetic, *variable_sp))
       continue;
 
     if (INTERRUPT_REQUESTED(
diff --git a/lldb/source/Commands/CommandObjectFrame.cpp b/lldb/source/Commands/CommandObjectFrame.cpp
index 9133359fbf537..8517146baa95c 100644
--- a/lldb/source/Commands/CommandObjectFrame.cpp
+++ b/lldb/source/Commands/CommandObjectFrame.cpp
@@ -29,7 +29,9 @@
 #include "lldb/Target/Target.h"
 #include "lldb/Target/Thread.h"
 #include "lldb/Utility/Args.h"
+#include "lldb/Utility/ValueType.h"
 #include "lldb/ValueObject/ValueObject.h"
+#include "lldb/lldb-enumerations.h"
 
 #include <memory>
 #include <optional>
@@ -337,9 +339,9 @@ class CommandObjectFrameSelect : public CommandObjectParsed {
           // The request went past the stack, so handle that case:
           const uint32_t num_frames = thread->GetStackFrameCount();
           if (static_cast<int32_t>(num_frames - frame_idx) >
-              *m_options.relative_frame_offset)
-          frame_idx += *m_options.relative_frame_offset;
-          else {
+              *m_options.relative_frame_offset) {
+            frame_idx += *m_options.relative_frame_offset;
+          } else {
             if (frame_idx == num_frames - 1) {
               // If we are already at the top of the stack, just warn and don't
               // reset the frame.
@@ -439,17 +441,23 @@ may even involve JITing and running code in the target program.)");
     if (!var_sp)
       return llvm::StringRef();
 
-    switch (var_sp->GetScope()) {
+    auto vt = var_sp->GetScope();
+    bool is_synthetic = IsSyntheticValueType(vt);
+    // Clear the bit so the rest works correctly.
+    if (is_synthetic)
+      vt = GetBaseValueType(vt);
+
+    switch (vt) {
     case eValueTypeVariableGlobal:
-      return "GLOBAL: ";
+      return is_synthetic ? "(ext) GLOBAL: " : "GLOBAL: ";
     case eValueTypeVariableStatic:
-      return "STATIC: ";
+      return is_synthetic ? "(ext) STATIC: " : "STATIC: ";
     case eValueTypeVariableArgument:
-      return "ARG: ";
+      return is_synthetic ? "(ext) ARG: " : "ARG: ";
     case eValueTypeVariableLocal:
-      return "LOCAL: ";
+      return is_synthetic ? "(ext) LOCAL: " : "LOCAL: ";
     case eValueTypeVariableThreadLocal:
-      return "THREAD: ";
+      return is_synthetic ? "(ext) THREAD: " : "THREAD: ";
     default:
       break;
     }
@@ -459,6 +467,14 @@ may even involve JITing and running code in the target program.)");
 
   /// Returns true if `scope` matches any of the options in `m_option_variable`.
   bool ScopeRequested(lldb::ValueType scope) {
+    // If it's an extended variable, check if we want to show those first.
+    bool is_synthetic = IsSyntheticValueType(scope);
+    if (is_synthetic) {
+      if (!m_option_variable.show_synthetic)
+        return false;
+
+      scope = GetBaseValueType(scope);
+    }
     switch (scope) {
     case eValueTypeVariableGlobal:
     case eValueTypeVariableStatic:
@@ -474,7 +490,10 @@ may even involve JITing and running code in the target program.)");
     case eValueTypeVariableThreadLocal:
     case eValueTypeVTable:
     case eValueTypeVTableEntry:
-      return false;
+      // The default for all other value types is !is_extended. Aside from the
+      // modifiers above that should apply equally to extended and normal
+      // variables, any other extended variable we should default to showing.
+      return !is_synthetic;
     }
     llvm_unreachable("Unexpected scope value");
   }
@@ -521,7 +540,8 @@ may even involve JITing and running code in the target program.)");
 
     Status error;
     VariableList *variable_list =
-        frame->GetVariableList(m_option_variable.show_globals, &error);
+        frame->GetVariableList(m_option_variable.show_globals,
+                               m_option_variable.show_synthetic, &error);
 
     if (error.Fail() && (!variable_list || variable_list->GetSize() == 0)) {
       result.AppendError(error.AsCString());
diff --git a/lldb/source/Core/IOHandlerCursesGUI.cpp b/lldb/source/Core/IOHandlerCursesGUI.cpp
index 47ba35877651a..b206558cf5014 100644
--- a/lldb/source/Core/IOHandlerCursesGUI.cpp
+++ b/lldb/source/Core/IOHandlerCursesGUI.cpp
@@ -5934,7 +5934,8 @@ class FrameVariablesWindowDelegate : public ValueObjectListDelegate {
       if (m_frame_block != frame_block) {
         m_frame_block = frame_block;
 
-        VariableList *locals = frame->GetVariableList(true, nullptr);
+        VariableList *locals = frame->GetVariableList(
+            /*get_file_globals=*/true, /*include_synthetic_vars=*/true, nullptr);
         if (locals) {
           const DynamicValueType use_dynamic = eDynamicDontRunTarget;
           for (const VariableSP &local_sp : *locals) {
diff --git a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
index 664bcba0017a7..7bbf3035dab02 100644
--- a/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
+++ b/lldb/source/Plugins/ExpressionParser/Clang/ClangExpressionDeclMap.cpp
@@ -868,8 +868,11 @@ void ClangExpressionDeclMap::LookUpLldbClass(NameSearchContext &context) {
   // creates decls for function templates by attaching them to the TU instead
   // of a class context. So we can actually have template methods scoped
   // outside of a class. Once we fix that, we can remove this code-path.
-
-  VariableList *vars = frame->GetVariableList(false, nullptr);
+  // Additionally, we exclude synthetic variables from here. Clang-based
+  // languages are unlikely candidates for synthetic variables anyway, and
+  // especially in this case, we're looking for something specific to C++.
+  VariableList *vars = frame->GetVariableList(
+      /*get_file_globals=*/false, /*include_synthetic_vars=*/false, nullptr);
 
   lldb::VariableSP this_var = vars->FindVariable(ConstString("this"));
 
@@ -955,7 +958,11 @@ void ClangExpressionDeclMap::LookUpLldbObjCClass(NameSearchContext &context) {
   // In that case, just look up the "self" variable in the current scope
   // and use its type.
 
-  VariableList *vars = frame->GetVariableList(false, nullptr);
+  // We exclude synthetic variables from here. Like above, it's highly unlikely
+  // we care about synthetic variables here, and indeed this code is looking for
+  // an obj-C specific construct.
+  VariableList *vars = frame->GetVariableList(
+      /*get_file_globals=*/false, /*include_synthetic_vars=*/false, nullptr);
 
   lldb::VariableSP self_var = vars->FindVariable(ConstString("self"));
 
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
index 7462c467eb7da..e738d245d7264 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.cpp
@@ -14,6 +14,7 @@
 #include "lldb/Core/Debugger.h"
 #include "lldb/Core/Module.h"
 #include "lldb/Core/ModuleList.h"
+#include "lldb/Expression/DWARFExpressionList.h"
 #include "lldb/Host/FileSystem.h"
 #include "lldb/Interpreter/Interfaces/ScriptedFrameInterface.h"
 #include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
@@ -30,8 +31,14 @@
 #include "lldb/Utility/LLDBLog.h"
 #include "lldb/Utility/Log.h"
 #include "lldb/Utility/StructuredData.h"
+#include "lldb/Utility/ValueType.h"
 #include "lldb/ValueObject/ValueObject.h"
 #include "lldb/ValueObject/ValueObjectList.h"
+#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
+#include "llvm/Support/ErrorHandling.h"
+
+#include <memory>
 
 using namespace lldb;
 using namespace lldb_private;
@@ -271,19 +278,22 @@ lldb::RegisterContextSP ScriptedFrame::GetRegisterContext() {
 }
 
 VariableList *ScriptedFrame::GetVariableList(bool get_file_globals,
+                                             bool include_synthetic_vars,
                                              Status *error_ptr) {
-  PopulateVariableListFromInterface();
+  PopulateVariableListFromInterface(include_synthetic_vars);
   return m_variable_list_sp.get();
 }
 
 lldb::VariableListSP
 ScriptedFrame::GetInScopeVariableList(bool get_file_globals,
+                                      bool include_synthetic_vars,
                                       bool must_have_valid_location) {
-  PopulateVariableListFromInterface();
+  PopulateVariableListFromInterface(include_synthetic_vars);
   return m_variable_list_sp;
 }
 
-void ScriptedFrame::PopulateVariableListFromInterface() {
+void ScriptedFrame::PopulateVariableListFromInterface(
+    bool include_synthetic_vars) {
   // Fetch values from the interface.
   ValueObjectListSP value_list_sp = GetInterface()->GetVariables();
   if (!value_list_sp)
@@ -297,12 +307,28 @@ void ScriptedFrame::PopulateVariableListFromInterface() {
       continue;
 
     VariableSP var = v->GetVariable();
-    // TODO: We could in theory ask the scripted frame to *produce* a
-    //       variable for this value object.
-    if (!var)
-      continue;
+    if (!var && include_synthetic_vars) {
+      // Construct the value type as an synthetic verison of what the value type
+      // is. That'll allow the user to tell the scope and the 'synthetic-ness' of
+      // the variable.
+      lldb::ValueType vt = GetSyntheticValueType(v->GetValueType());
+
+      // Just make up a variable - the frame variable dumper just passes it
+      // back in to GetValueObjectForFrameVariable, so we really just need to
+      // make sure the name and type are correct. We create IDs based on
+      // value_list_sp in order to make sure they're unique.
+      var = std::make_shared<lldb_private::Variable>(
+          (lldb::user_id_t)value_list_sp->GetSize() + i, v->GetName().GetCString(),
+          v->GetName().GetCString(), nullptr, vt,
+          /*owner_scope=*/nullptr,
+          /*scope_range=*/Variable::RangeList{},
+          /*decl=*/nullptr, DWARFExpressionList{}, /*external=*/false,
+          /*artificial=*/true, /*location_is_constant_data=*/false);
+    }
 
-    m_variable_list_sp->AddVariable(var);
+    // Only append the variable if we have one (had already, or just created).
+    if (var)
+      m_variable_list_sp->AddVariable(var);
   }
 }
 
@@ -316,6 +342,15 @@ lldb::ValueObjectSP ScriptedFrame::GetValueObjectForFrameVariable(
   return values->FindValueObjectByValueName(variable_sp->GetName().AsCString());
 }
 
+lldb::ValueObjectSP ScriptedFrame::FindVariable(ConstString name) {
+  // Fetch values from the interface.
+  ValueObjectListSP values = m_scripted_frame_interface_sp->GetVariables();
+  if (!values)
+    return {};
+
+  return values->FindValueObjectByValueName(name.AsCString());
+}
+
 lldb::ValueObjectSP ScriptedFrame::GetValueForVariableExpressionPath(
     llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
     uint32_t options, lldb::VariableSP &var_sp, Status &error) {
diff --git a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
index fe154792c745b..f969401004f1e 100644
--- a/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
+++ b/lldb/source/Plugins/Process/scripted/ScriptedFrame.h
@@ -64,16 +64,19 @@ class ScriptedFrame : public lldb_private::StackFrame {
   lldb::RegisterContextSP GetRegisterContext() override;
 
   VariableList *GetVariableList(bool get_file_globals,
+                                bool include_synthetic_vars,
                                 lldb_private::Status *error_ptr) override;
 
   lldb::VariableListSP
-  GetInScopeVariableList(bool get_file_globals,
+  GetInScopeVariableList(bool get_file_globals, bool include_synthetic_vars,
                          bool must_have_valid_location = false) override;
 
   lldb::ValueObjectSP
   GetValueObjectForFrameVariable(const lldb::VariableSP &variable_sp,
                                  lldb::DynamicValueType use_dynamic) override;
 
+  lldb::ValueObjectSP FindVariable(ConstString name) override;
+
   lldb::ValueObjectSP GetValueForVariableExpressionPath(
       llvm::StringRef var_expr, lldb::DynamicValueType use_dynamic,
       uint32_t options, lldb::VariableSP &var_sp, Status &error) override;
@@ -90,10 +93,12 @@ class ScriptedFrame : public lldb_private::StackFrame {
   CreateRegisterContext(ScriptedFrameInterface &interface, Thread &thread,
                         lldb::user_id_t frame_id);
 
-  // Populate m_variable_list_sp from the scripted frame interface. Right now
-  // this doesn't take any options because the implementation can't really do
-  // anything with those options anyway, so there's no point.
-  void PopulateVariableListFromInterface();
+  // Populate m_variable_list_sp from the scripted frame interface. The boolean
+  // controls if we should try to fabricate Variable objects for each of the
+  // ValueObjects that we have. This defaults to 'true' because this is a
+  // scripted frame, so kind of the whole point is to provide synthetic variables
+  // to the user.
+  void PopulateVariableListFromInterface(bool include_synthetic_vars = true);
 
   ScriptedFrame(const ScriptedFrame &) = delete;
   const ScriptedFrame &operator=(const ScriptedFrame &) = delete;
diff --git a/lldb/source/Symbol/Variable.cpp b/lldb/source/Symbol/Variable.cpp
index 45c9c0a843837..cdfd62546c52c 100644
--- a/lldb/source/Symbol/Variable.cpp
+++ b/lldb/source/Symbol/Variable.cpp
@@ -590,9 +590,10 @@ static void PrivateAutoComplete(
     } else {
       if (frame) {
         const bool get_file_globals = true;
+        const bool include_synthetic_vars = true;
 
-        VariableList *variable_list = frame->GetVariableList(get_file_globals,
-                                                             nullptr);
+        VariableList *variable_list = frame->GetVariableList(
+            get_file_globals, include_synthetic_vars, nullptr);
 
         if (variable_list) {
           for (const VariableSP &var_sp : *variable_list)
@@ -686,9 +687,10 @@ static void PrivateAutoComplete(
         } else if (frame) {
           // We haven't found our variable yet
           const bool get_file_globals = true;
+          const bool include_synthetic_vars = true;
 
-          VariableList *variable_list =
-              frame->GetVariableList(get_file_globals, nullptr);
+          VariableList *variable_list = frame->GetVariableList(
+              get_file_globals, include_synthetic_vars, nullptr);
 
           if (!variable_list)
             break;
diff --git a/lldb/source/Target/BorrowedStackFrame.cpp b/lldb/source/Target/BorrowedStackFrame.cpp
index 5afadf21fde03..27ec62a5fe94c 100644
--- a/lldb/source/Target/BorrowedStackFrame.cpp
+++ b/lldb/source/Target/BorrowedStackFrame.cpp
@@ -86,15 +86,18 @@ RegisterContextSP BorrowedStackFrame::GetRegisterContext() {
 }
 
 VariableList *BorrowedStackFrame::GetVariableList(bool get_file_globals,
+                                                  bool include_synthetic_vars,
                                                   Status *error_ptr) {
-  return m_borrowed_frame_sp->GetVariableList(get_file_globals, error_ptr);
+  return m_borrowed_frame_sp->GetVariableList(get_file_globals,
+                                              include_synthetic_vars, error_ptr);
 }
 
 VariableListSP
 BorrowedStackFrame::GetInScopeVariableList(bool get_file_globals,
+                                           bool include_synthetic_vars,
                                            bool must_have_valid_location) {
-  return m_borrowed_frame_sp->GetInScopeVariableList(get_file_globals,
-                                                     must_have_valid_location);
+  return m_borrowed_frame_sp->GetInScopeVariableList(
+      get_file_globals, include_synthetic_vars, must_have_valid_location);
 }
 
 ValueObjectSP BorrowedStackFrame::GetValueForVariableExpressionPath(
diff --git a/lldb/source/Target/StackFrame.cpp b/lldb/source/Target/StackFrame.cpp
index 340607e14abed..115b3b59bed45 100644
--- a/lldb/source/Target/StackFrame.cpp
+++ b/lldb/source/Target/StackFrame.cpp
@@ -439,7 +439,11 @@ StackFrame::GetSymbolContext(SymbolContextItem resolve_scope) {
 }
 
 VariableList *StackFrame::GetVariableList(bool get_file_globals,
+                                          bool include_synthetic_vars,
                                           Status *error_ptr) {
+  // We don't have 'synthetic variables' in the base stack frame.
+  (void)include_synthetic_vars;
+
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
   if (m_flags.IsClear(RESOLVED_VARIABLES)) {
     m_flags.Set(RESOLVED_VARIABLES);
@@ -490,7 +494,11 @@ VariableList *StackFrame::GetVariableList(bool get_file_globals,
 
 VariableListSP
 StackFrame::GetInScopeVariableList(bool get_file_globals,
+                                   bool include_synthetic_vars,
                                    bool must_have_valid_location) {
+  // We don't have synthetic variables in the base stack frame.
+  (void)include_synthetic_vars;
+
   std::lock_guard<std::recursive_mutex> guard(m_mutex);
   // We can't fetch variable information for a history stack frame.
   if (IsHistorical())
@@ -1235,7 +1243,8 @@ StackFrame::GetValueObjectForFrameVariable(const VariableSP &variable_sp,
     if (IsHistorical()) {
       return valobj_sp;
     }
-    VariableList *var_list = GetVariableList(true, nullptr);
+    VariableList *var_list = GetVariableList(
+        /*get_file_globals=*/true, /*include_synthetic_vars=*/true, nullptr);
     if (var_list) {
       // Make sure the variable is a frame variable
       const uint32_t var_idx =
@@ -1856,7 +1865,12 @@ lldb::ValueObjectSP StackFrame::GuessValueForRegisterAndOffset(ConstString reg,
   }
 
   const bool get_file_globals = false;
-  VariableList *variables = GetVariableList(get_file_globals, nullptr);
+  // Keep this as 'false' here because if we're inspecting a register, it's
+  // HIGHLY unlikely that we have an synthetic variable. Indeed, since we're not
+  // in a synthetic frame, it's probably actually impossible here.
+  const bool include_synthetic_vars = false;
+  VariableList *variables =
+      GetVariableList(get_file_globals, include_synthetic_vars, nullptr);
 
   if (!variables) {
     return ValueObjectSP();
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
index 7dd74013b90f8..eea0143355753 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/TestScriptedFrameProvider.py
@@ -769,17 +769,37 @@ def test_get_values(self):
         # Check that we can get variables from this frame.
         frame0 = thread.GetFrameAtIndex(0)
         self.assertIsNotNone(frame0)
-        # Get every variable visible at this point
-        variables = frame0.GetVariables(True, True, True, False)
-        self.assertTrue(variables.IsValid() and variables.GetSize() == 1)
+
+        # Ensure that we can get extended variables with `SetIncludeExtended`.
+        options = lldb.SBVariablesOptions()
+        options.SetIncludeExtended(True)
+        variables = frame0.GetVariables(options)
+        self.assertTrue(variables.IsValid())
+        self.assertTrue(variables.GetValueAtIndex(0).name == "_handler_one")
+
+        # Check the `frame variable` command(s) handle extended variables the
+        # way we expect by printing them.
+        self.expect("frame var", substrs=["variable_in_main", "_handler_one"])
+
+        # Then, try and run it without extended variables and ensure we don't
+        # get any, but we still get the others.
+        interp = self.dbg.GetCommandInterpreter()
+        command_result = lldb.SBCommandReturnObject()
+        result = interp.HandleCommand("frame var -e", command_result)
+        self.assertEqual(
+            result, lldb.eReturnStatusSuccessFinishResult, "frame var -e didn't succeed"
+        )
+        output = command_result.GetOutput()
+        self.assertIn("variable_in_main", output, "Didn't find a regular variable")
+        self.assertNotIn("_handler_one", output, "Found an extended variable")
 
         # Check that we can get values from paths. `_handler_one` is a special
         # value we provide through only our expression handler in the frame
-        # implementation.
+        # implementation. We can't evaluate expressions on the special value
+        # just because the test implementation doesn't handle it, and we
+        # delegate all expression handling to the implementation.
         one = frame0.GetValueForVariablePath("_handler_one")
         self.assertEqual(one.unsigned, 1)
-        var = frame0.GetValueForVariablePath("variable_in_main")
-        # The names won't necessarily match, but the values should (the frame renames the SBValue)
-        self.assertEqual(var.unsigned, variables.GetValueAtIndex(0).unsigned)
+        # Ensure I can still access and do arithmetic on regular variables.
         varp1 = frame0.GetValueForVariablePath("variable_in_main + 1")
         self.assertEqual(varp1.unsigned, 124)
diff --git a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
index 3a30e4fa96d6e..efdbe4cc4bf59 100644
--- a/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
+++ b/lldb/test/API/functionalities/scripted_frame_provider/test_frame_providers.py
@@ -500,6 +500,8 @@ def get_variables(self):
         """"""
         out = lldb.SBValueList()
         out.Append(self.variable)
+        # Produce a fake value to be displayed.
+        out.Append(self.variable.CreateValueFromExpression("_handler_one", "(uint32_t)1"))
         return out
 
     def get_value_for_variable_expression(self, expr, options, error: lldb.SBError):



More information about the lldb-commits mailing list