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
