Author: Adrian Prantl
Date: 2026-07-08T15:50:14-07:00
New Revision: 349375a83b85c481c8126ac2f3081b51bf4725d2

URL: 
https://github.com/llvm/llvm-project/commit/349375a83b85c481c8126ac2f3081b51bf4725d2
DIFF: 
https://github.com/llvm/llvm-project/commit/349375a83b85c481c8126ac2f3081b51bf4725d2.diff

LOG: [LLDB] Add an API to check whether a variable is writable (#208042)

IDEs may offer functionality to set a variable to a specific value.
There are many situations where this isn't actually possible, for
example, if the variable's value is a constant or the result of a
complex DWARF expression. Instead of offering to change a value only to
have it fail with an error, this API lets the IDE query whether setting
a value is generally feasible so it can hide the action where it isn't
applicable.

rdar://142358140

Assisted-by: claude

Added: 
    

Modified: 
    lldb/bindings/interface/SBValueDocstrings.i
    lldb/include/lldb/API/SBValue.h
    lldb/include/lldb/Expression/DWARFExpression.h
    lldb/include/lldb/Expression/DWARFExpressionList.h
    lldb/include/lldb/ValueObject/ValueObject.h
    lldb/include/lldb/ValueObject/ValueObjectVariable.h
    lldb/source/API/SBValue.cpp
    lldb/source/Expression/DWARFExpression.cpp
    lldb/source/Expression/DWARFExpressionList.cpp
    lldb/source/ValueObject/ValueObject.cpp
    lldb/source/ValueObject/ValueObjectVariable.cpp
    lldb/test/API/python_api/value/change_values/TestChangeValueAPI.py
    lldb/unittests/Expression/DWARFExpressionTest.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/bindings/interface/SBValueDocstrings.i 
b/lldb/bindings/interface/SBValueDocstrings.i
index 59fa807f5ec95..bedb553e94c3a 100644
--- a/lldb/bindings/interface/SBValueDocstrings.i
+++ b/lldb/bindings/interface/SBValueDocstrings.i
@@ -207,3 +207,13 @@ linked list."
 
 %feature("docstring", "Returns an expression path for this value."
 ) lldb::SBValue::GetExpressionPath;
+
+%feature("docstring", "
+    Returns whether this value can be modified through SetValueFromCString()
+    or SetData().
+
+    Returns False when the value is not writable. An example would be a
+    variable values reconstructed from debug info via a computation or a 
constant.
+    A True result does not guarantee a write will succeed; other
+    runtime conditions may still prevent a successful write."
+) lldb::SBValue::CanSetValue;

diff  --git a/lldb/include/lldb/API/SBValue.h b/lldb/include/lldb/API/SBValue.h
index 23a58897bc110..16a9f0837454c 100644
--- a/lldb/include/lldb/API/SBValue.h
+++ b/lldb/include/lldb/API/SBValue.h
@@ -116,6 +116,14 @@ class LLDB_API SBValue {
 
   bool SetValueFromCString(const char *value_str, lldb::SBError &error);
 
+  /// Returns false if this value cannot be modified through
+  /// SetValueFromCString() or SetData(), for instance because it
+  /// exists in the target, but has no writable storage (for example a
+  /// constant or variable value that was reconstructed from debug
+  /// info as the result of a computation). A true result does not
+  /// guarantee a write will succeed.
+  bool CanSetValue();
+
   lldb::SBTypeFormat GetTypeFormat();
 
   lldb::SBTypeSummary GetTypeSummary();

diff  --git a/lldb/include/lldb/Expression/DWARFExpression.h 
b/lldb/include/lldb/Expression/DWARFExpression.h
index b1895690985a7..feecd50fc9c22 100644
--- a/lldb/include/lldb/Expression/DWARFExpression.h
+++ b/lldb/include/lldb/Expression/DWARFExpression.h
@@ -96,6 +96,12 @@ class DWARFExpression {
 
   bool ContainsThreadLocalStorage(const Delegate *dwarf_cu) const;
 
+  /// Return true if this expression produces a DWARF implicit or
+  /// composite location description that LLDB cannot write a new
+  /// value back to. Scans the opcodes without evaluating the
+  /// expression.
+  bool IsImplicit(const Delegate *dwarf_cu) const;
+
   bool LinkThreadLocalStorage(
       const Delegate *dwarf_cu,
       std::function<lldb::addr_t(lldb::addr_t file_addr)> const

diff  --git a/lldb/include/lldb/Expression/DWARFExpressionList.h 
b/lldb/include/lldb/Expression/DWARFExpressionList.h
index d303ad834b354..b83b82bd8edb2 100644
--- a/lldb/include/lldb/Expression/DWARFExpressionList.h
+++ b/lldb/include/lldb/Expression/DWARFExpressionList.h
@@ -126,7 +126,20 @@ class DWARFExpressionList {
                                  const Value *initial_value_ptr,
                                  const Value *object_address_ptr) const;
 
+  /// Return true if the expression in scope at the current PC produces an
+  /// implicit location, or if no location is in scope. See
+  /// DWARFExpression::IsImplicit. \p exe_ctx and \p reg_ctx may be null for an
+  /// always-valid single expression.
+  bool IsImplicit(ExecutionContext *exe_ctx, RegisterContext *reg_ctx,
+                  lldb::addr_t func_load_addr) const;
+
 private:
+  /// Return the expression in scope at the current PC, or an error if no
+  /// location is in scope.
+  llvm::Expected<const DWARFExpression *>
+  GetExpressionAtPC(ExecutionContext *exe_ctx, RegisterContext *reg_ctx,
+                    lldb::addr_t func_load_addr) const;
+
   // RangeDataVector requires a comparator for DWARFExpression, but it doesn't
   // make sense to do so.
   struct DWARFExpressionCompare {

diff  --git a/lldb/include/lldb/ValueObject/ValueObject.h 
b/lldb/include/lldb/ValueObject/ValueObject.h
index 07f7fa434171f..6e4d93b815761 100644
--- a/lldb/include/lldb/ValueObject/ValueObject.h
+++ b/lldb/include/lldb/ValueObject/ValueObject.h
@@ -851,6 +851,14 @@ class ValueObject {
 
   virtual bool GetIsConstant() const { return m_update_point.IsConstant(); }
 
+  /// Returns false when this value cannot be modified through
+  /// SetValueFromCString() or SetData() because it exists in the
+  /// target but has no writable storage, e.g., a constant or a
+  /// computed variable value.  A true result does not guarantee a
+  /// write will succeed; other runtime conditions can still cause
+  /// SetValue* to fail.
+  virtual bool CanSetValue() { return !GetIsConstant(); }
+
   bool NeedsUpdating() {
     const bool accept_invalid_exe_ctx =
         (CanUpdateWithInvalidExecutionContext() == eLazyBoolYes);

diff  --git a/lldb/include/lldb/ValueObject/ValueObjectVariable.h 
b/lldb/include/lldb/ValueObject/ValueObjectVariable.h
index 5a791a0f1d83a..a4668c4291054 100644
--- a/lldb/include/lldb/ValueObject/ValueObjectVariable.h
+++ b/lldb/include/lldb/ValueObject/ValueObjectVariable.h
@@ -64,6 +64,8 @@ class ValueObjectVariable : public ValueObject {
 
   bool SetData(DataExtractor &data, Status &error) override;
 
+  bool CanSetValue() override;
+
   lldb::VariableSP GetVariable() override { return m_variable_sp; }
 
 protected:
@@ -80,6 +82,11 @@ class ValueObjectVariable : public ValueObject {
   /// it up.
   Value m_resolved_value;
 
+  /// True when the resolved value has no writable storage in the inferior (an
+  /// implicit location, a constant, or an unresolved location) and so cannot 
be
+  /// the target of SetValueFromCString() or SetData(). Set by UpdateValue().
+  bool m_resolved_value_is_implicit = false;
+
 private:
   ValueObjectVariable(ExecutionContextScope *exe_scope,
                       ValueObjectManager &manager,

diff  --git a/lldb/source/API/SBValue.cpp b/lldb/source/API/SBValue.cpp
index 9b84fb975c302..de660577b3a9b 100644
--- a/lldb/source/API/SBValue.cpp
+++ b/lldb/source/API/SBValue.cpp
@@ -310,6 +310,17 @@ bool SBValue::SetValueFromCString(const char *value_str, 
lldb::SBError &error) {
   return success;
 }
 
+bool SBValue::CanSetValue() {
+  LLDB_INSTRUMENT_VA(this);
+
+  ValueLocker locker;
+  lldb::ValueObjectSP value_sp(GetSP(locker));
+  if (!value_sp)
+    return false;
+
+  return value_sp->CanSetValue();
+}
+
 lldb::SBTypeFormat SBValue::GetTypeFormat() {
   LLDB_INSTRUMENT_VA(this);
 

diff  --git a/lldb/source/Expression/DWARFExpression.cpp 
b/lldb/source/Expression/DWARFExpression.cpp
index 985a2dfecd38f..c4c86b408accd 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -567,6 +567,36 @@ bool DWARFExpression::ContainsThreadLocalStorage(
   }
   return false;
 }
+
+bool DWARFExpression::IsImplicit(
+    const DWARFExpression::Delegate *dwarf_cu) const {
+  lldb::offset_t offset = 0;
+  while (m_data.ValidOffset(offset)) {
+    const LocationAtom op = static_cast<LocationAtom>(m_data.GetU8(&offset));
+
+    switch (op) {
+    // Implicit locations have no storage in the inferior. Composite locations
+    // might, but we conservatively treat them as non-writable because LLDB
+    // does not write their pieces back.
+    case DW_OP_stack_value:
+    case DW_OP_implicit_value:
+    case DW_OP_implicit_pointer:
+    case DW_OP_piece:
+    case DW_OP_bit_piece:
+      return true;
+    default:
+      break;
+    }
+
+    const lldb::offset_t op_arg_size =
+        GetOpcodeDataSize(m_data, offset, op, dwarf_cu);
+    if (op_arg_size == LLDB_INVALID_OFFSET)
+      return false;
+    offset += op_arg_size;
+  }
+  return false;
+}
+
 bool DWARFExpression::LinkThreadLocalStorage(
     const DWARFExpression::Delegate *dwarf_cu,
     std::function<lldb::addr_t(lldb::addr_t file_addr)> const

diff  --git a/lldb/source/Expression/DWARFExpressionList.cpp 
b/lldb/source/Expression/DWARFExpressionList.cpp
index 91c1740136036..14943f8ff269e 100644
--- a/lldb/source/Expression/DWARFExpressionList.cpp
+++ b/lldb/source/Expression/DWARFExpressionList.cpp
@@ -229,43 +229,64 @@ void DWARFExpressionList::GetDescription(Stream *s,
   }
 }
 
+llvm::Expected<const DWARFExpression *>
+DWARFExpressionList::GetExpressionAtPC(ExecutionContext *exe_ctx,
+                                       RegisterContext *reg_ctx,
+                                       lldb::addr_t func_load_addr) const {
+  if (const DWARFExpression *always = GetAlwaysValidExpr())
+    return always;
+
+  Address pc;
+  StackFrame *frame = nullptr;
+  if (!reg_ctx || !reg_ctx->GetPCForSymbolication(pc)) {
+    if (exe_ctx)
+      frame = exe_ctx->GetFramePtr();
+    if (!frame)
+      return llvm::createStringError("no frame");
+    RegisterContextSP reg_ctx_sp = frame->GetRegisterContext();
+    if (!reg_ctx_sp)
+      return llvm::createStringError("no register context");
+    reg_ctx_sp->GetPCForSymbolication(pc);
+  }
+
+  if (!pc.IsValid())
+    return llvm::createStringError("invalid PC in frame");
+
+  addr_t pc_load_addr = pc.GetLoadAddress(exe_ctx->GetTargetPtr());
+  const DWARFExpression *entry =
+      GetExpressionAtAddress(func_load_addr, pc_load_addr);
+  if (!entry)
+    return llvm::createStringError("variable not available");
+  return entry;
+}
+
 llvm::Expected<Value> DWARFExpressionList::Evaluate(
     ExecutionContext *exe_ctx, RegisterContext *reg_ctx,
     lldb::addr_t func_load_addr, const Value *initial_value_ptr,
     const Value *object_address_ptr) const {
+  llvm::Expected<const DWARFExpression *> expr =
+      GetExpressionAtPC(exe_ctx, reg_ctx, func_load_addr);
+  if (!expr)
+    return expr.takeError();
+
   ModuleSP module_sp = m_module_wp.lock();
   DataExtractor data;
-  RegisterKind reg_kind;
-  DWARFExpression expr;
-  if (IsAlwaysValidSingleExpr()) {
-    expr = m_exprs.Back()->data;
-  } else {
-    Address pc;
-    StackFrame *frame = nullptr;
-    if (!reg_ctx || !reg_ctx->GetPCForSymbolication(pc)) {
-      if (exe_ctx)
-        frame = exe_ctx->GetFramePtr();
-      if (!frame)
-        return llvm::createStringError("no frame");
-      RegisterContextSP reg_ctx_sp = frame->GetRegisterContext();
-      if (!reg_ctx_sp)
-        return llvm::createStringError("no register context");
-      reg_ctx_sp->GetPCForSymbolication(pc);
-    }
-
-    if (!pc.IsValid()) {
-      return llvm::createStringError("invalid PC in frame");
-    }
-    addr_t pc_load_addr = pc.GetLoadAddress(exe_ctx->GetTargetPtr());
-    const DWARFExpression *entry =
-        GetExpressionAtAddress(func_load_addr, pc_load_addr);
-    if (!entry)
-      return llvm::createStringError("variable not available");
-    expr = *entry;
-  }
-  expr.GetExpressionData(data);
-  reg_kind = expr.GetRegisterKind();
+  (*expr)->GetExpressionData(data);
+  RegisterKind reg_kind = (*expr)->GetRegisterKind();
   return DWARFExpression::Evaluate(exe_ctx, reg_ctx, module_sp, data,
                                    m_dwarf_cu, reg_kind, initial_value_ptr,
                                    object_address_ptr);
 }
+
+bool DWARFExpressionList::IsImplicit(ExecutionContext *exe_ctx,
+                                     RegisterContext *reg_ctx,
+                                     lldb::addr_t func_load_addr) const {
+  llvm::Expected<const DWARFExpression *> expr =
+      GetExpressionAtPC(exe_ctx, reg_ctx, func_load_addr);
+  if (!expr) {
+    // No location in scope, so nothing to write back to.
+    llvm::consumeError(expr.takeError());
+    return true;
+  }
+  return (*expr)->IsImplicit(m_dwarf_cu);
+}

diff  --git a/lldb/source/ValueObject/ValueObject.cpp 
b/lldb/source/ValueObject/ValueObject.cpp
index 32332c2962e87..cac4933c64325 100644
--- a/lldb/source/ValueObject/ValueObject.cpp
+++ b/lldb/source/ValueObject/ValueObject.cpp
@@ -811,6 +811,10 @@ uint64_t ValueObject::GetData(DataExtractor &data, Status 
&error) {
 
 bool ValueObject::SetData(DataExtractor &data, Status &error) {
   error.Clear();
+  if (GetIsConstant()) {
+    error = Status::FromErrorString("Cannot change the value of a constant");
+    return false;
+  }
   // Make sure our value is up to date first so that our location and location
   // type is valid.
   if (!UpdateValueIfNeeded(false)) {
@@ -1702,6 +1706,10 @@ static const char *ConvertBoolean(lldb::LanguageType 
language_type,
 
 bool ValueObject::SetValueFromCString(const char *value_str, Status &error) {
   error.Clear();
+  if (GetIsConstant()) {
+    error = Status::FromErrorString("Cannot change the value of a constant");
+    return false;
+  }
   // Make sure our value is up to date first so that our location and location
   // type is valid.
   if (!UpdateValueIfNeeded(false)) {

diff  --git a/lldb/source/ValueObject/ValueObjectVariable.cpp 
b/lldb/source/ValueObject/ValueObjectVariable.cpp
index 6d9e6d31327f4..198d667bba17f 100644
--- a/lldb/source/ValueObject/ValueObjectVariable.cpp
+++ b/lldb/source/ValueObject/ValueObjectVariable.cpp
@@ -140,6 +140,7 @@ bool ValueObjectVariable::UpdateValue() {
       m_error = Status::FromErrorString("empty constant data");
     // constant bytes can't be edited - sorry
     m_resolved_value.SetContext(Value::ContextType::Invalid, nullptr);
+    m_resolved_value_is_implicit = true;
   } else {
     lldb::addr_t loclist_base_load_addr = LLDB_INVALID_ADDRESS;
     ExecutionContext exe_ctx(GetExecutionContextRef());
@@ -164,6 +165,8 @@ bool ValueObjectVariable::UpdateValue() {
     if (maybe_value) {
       m_value = *maybe_value;
       m_resolved_value = m_value;
+      m_resolved_value_is_implicit =
+          expr_list.IsImplicit(&exe_ctx, nullptr, loclist_base_load_addr);
       m_value.SetContext(Value::ContextType::Variable, variable);
 
       CompilerType compiler_type = GetCompilerType();
@@ -244,6 +247,7 @@ bool ValueObjectVariable::UpdateValue() {
       m_error = Status::FromError(maybe_value.takeError());
       // could not find location, won't allow editing
       m_resolved_value.SetContext(Value::ContextType::Invalid, nullptr);
+      m_resolved_value_is_implicit = true;
     }
   }
 
@@ -352,6 +356,12 @@ const char *ValueObjectVariable::GetLocationAsCString() {
     return ValueObject::GetLocationAsCString();
 }
 
+bool ValueObjectVariable::CanSetValue() {
+  // Refresh the resolved location so m_resolved_value_is_implicit is current.
+  UpdateValueIfNeeded();
+  return !m_resolved_value_is_implicit && ValueObject::CanSetValue();
+}
+
 bool ValueObjectVariable::SetValueFromCString(const char *value_str,
                                               Status &error) {
   if (!UpdateValueIfNeeded()) {
@@ -359,6 +369,11 @@ bool ValueObjectVariable::SetValueFromCString(const char 
*value_str,
     return false;
   }
 
+  if (m_resolved_value_is_implicit) {
+    error = Status::FromErrorString("Cannot change the value of a constant");
+    return false;
+  }
+
   if (m_resolved_value.GetContextType() == Value::ContextType::RegisterInfo) {
     RegisterInfo *reg_info = m_resolved_value.GetRegisterInfo();
     ExecutionContext exe_ctx(GetExecutionContextRef());
@@ -388,6 +403,11 @@ bool ValueObjectVariable::SetData(DataExtractor &data, 
Status &error) {
     return false;
   }
 
+  if (m_resolved_value_is_implicit) {
+    error = Status::FromErrorString("Cannot change the value of a constant");
+    return false;
+  }
+
   if (m_resolved_value.GetContextType() == Value::ContextType::RegisterInfo) {
     RegisterInfo *reg_info = m_resolved_value.GetRegisterInfo();
     ExecutionContext exe_ctx(GetExecutionContextRef());

diff  --git 
a/lldb/test/API/python_api/value/change_values/TestChangeValueAPI.py 
b/lldb/test/API/python_api/value/change_values/TestChangeValueAPI.py
index a669cff9ff597..d3c8935e2978d 100644
--- a/lldb/test/API/python_api/value/change_values/TestChangeValueAPI.py
+++ b/lldb/test/API/python_api/value/change_values/TestChangeValueAPI.py
@@ -75,6 +75,19 @@ def test_change_value(self):
         self.assertSuccess(error, "Got a changed value from val")
         self.assertEqual(actual_value, 12345, "Got the right changed value 
from val")
 
+        # A normal variable backed by memory reports that it can be modified.
+        self.assertTrue(val_value.CanSetValue(), "val can be modified")
+
+        # A constant expression result has no writable storage, so it cannot be
+        # modified and CanSetValue() is False.
+        const_value = frame0.EvaluateExpression("val + 1")
+        self.assertTrue(const_value.IsValid(), "Got a valid expression result")
+        self.assertFalse(const_value.CanSetValue(), "A constant cannot be 
modified")
+        error.Clear()
+        result = const_value.SetValueFromCString("0", error)
+        self.assertFalse(result, "SetValueFromCString on a constant failed")
+        self.assertIn("constant", error.GetCString())
+
         # Now check that we can set a structure element:
 
         mine_value = frame0.FindVariable("mine")

diff  --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp 
b/lldb/unittests/Expression/DWARFExpressionTest.cpp
index 75162ca0b5f3e..042045f35552c 100644
--- a/lldb/unittests/Expression/DWARFExpressionTest.cpp
+++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp
@@ -593,6 +593,26 @@ TEST(DWARFExpression, DW_OP_stack_value) {
   EXPECT_THAT_EXPECTED(Evaluate({DW_OP_stack_value}), llvm::Failed());
 }
 
+TEST(DWARFExpression, IsImplicit) {
+  auto is_implicit = [](llvm::ArrayRef<uint8_t> expr) {
+    DataExtractor extractor(expr.data(), expr.size(), lldb::eByteOrderLittle,
+                            /*addr_size=*/4);
+    return DWARFExpression(extractor).IsImplicit(/*dwarf_cu=*/nullptr);
+  };
+
+  // Implicit and composite locations have no writable storage.
+  EXPECT_TRUE(is_implicit({DW_OP_lit1, DW_OP_stack_value}));
+  EXPECT_TRUE(is_implicit({DW_OP_implicit_value, 0x01, 0x11}));
+  EXPECT_TRUE(is_implicit({DW_OP_reg0, DW_OP_piece, 0x02}));
+  EXPECT_TRUE(is_implicit({DW_OP_reg0, DW_OP_bit_piece, 0x04, 0x00}));
+
+  // Memory and register locations are writable.
+  EXPECT_FALSE(is_implicit({DW_OP_addr, 0x10, 0x20, 0x30, 0x40}));
+  EXPECT_FALSE(is_implicit({DW_OP_reg0}));
+  EXPECT_FALSE(is_implicit({DW_OP_breg0, 0x00}));
+  EXPECT_FALSE(is_implicit({DW_OP_fbreg, 0x00}));
+}
+
 // This test shows that the dwarf version is used by the expression evaluation.
 // Note that the 
diff erent behavior tested here is not meant to imply that this
 // is the correct interpretation of dwarf2 vs. dwarf5, but rather it was picked


        
_______________________________________________
lldb-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to