| //===-- OptionValueProperties.cpp -----------------------------------------===// | |
| // | |
| // 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "lldb/Interpreter/OptionValueProperties.h" | |
| #include "lldb/Utility/Flags.h" | |
| #include "lldb/Core/UserSettingsController.h" | |
| #include "lldb/Interpreter/OptionValues.h" | |
| #include "lldb/Interpreter/Property.h" | |
| #include "lldb/Utility/Args.h" | |
| #include "lldb/Utility/Stream.h" | |
| #include "lldb/Utility/StringList.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| OptionValueProperties::OptionValueProperties(llvm::StringRef name) | |
| : m_name(name.str()) {} | |
| void OptionValueProperties::Initialize( | |
| const PropertyCollectionDefinition &defs) { | |
| for (const auto &definition : defs.definitions) { | |
| Property property(definition); | |
| assert(property.IsValid()); | |
| m_name_to_index.insert({property.GetName(), m_properties.size()}); | |
| property.GetValue()->SetParent(shared_from_this()); | |
| m_properties.push_back(property); | |
| } | |
| SetExpectedPath(defs.expected_path.str()); | |
| } | |
| void OptionValueProperties::SetExpectedPath(std::string path) { | |
| assert(m_expected_path.empty() || m_expected_path == path); | |
| m_expected_path = path; | |
| } | |
| void OptionValueProperties::SetValueChangedCallback( | |
| size_t property_idx, std::function<void()> callback) { | |
| Property *property = ProtectedGetPropertyAtIndex(property_idx); | |
| if (property) | |
| property->SetValueChangedCallback(std::move(callback)); | |
| } | |
| void OptionValueProperties::AppendProperty(llvm::StringRef name, | |
| llvm::StringRef desc, bool is_global, | |
| const OptionValueSP &value_sp) { | |
| Property property(name, desc, is_global, value_sp); | |
| m_name_to_index.insert({name, m_properties.size()}); | |
| m_properties.push_back(property); | |
| value_sp->SetParent(shared_from_this()); | |
| #ifndef NDEBUG | |
| OptionValueProperties *properties = value_sp->GetAsProperties(); | |
| if (properties) { | |
| assert(value_sp->GetName() == name); | |
| assert(properties->VerifyPath() && | |
| "Mismatch between parents from TableGen and actual parents"); | |
| } | |
| #endif | |
| } | |
| lldb::OptionValueSP | |
| OptionValueProperties::GetValueForKey(const ExecutionContext *exe_ctx, | |
| llvm::StringRef key) const { | |
| auto iter = m_name_to_index.find(key); | |
| if (iter == m_name_to_index.end()) | |
| return OptionValueSP(); | |
| const size_t idx = iter->second; | |
| if (idx >= m_properties.size()) | |
| return OptionValueSP(); | |
| return GetPropertyAtIndex(idx, exe_ctx)->GetValue(); | |
| } | |
| lldb::OptionValueSP | |
| OptionValueProperties::GetSubValue(const ExecutionContext *exe_ctx, | |
| llvm::StringRef name, Status &error) const { | |
| lldb::OptionValueSP value_sp; | |
| if (name.empty()) | |
| return OptionValueSP(); | |
| llvm::StringRef sub_name; | |
| llvm::StringRef key; | |
| size_t key_len = name.find_first_of(".[{"); | |
| if (key_len != llvm::StringRef::npos) { | |
| key = name.take_front(key_len); | |
| sub_name = name.drop_front(key_len); | |
| } else | |
| key = name; | |
| value_sp = GetValueForKey(exe_ctx, key); | |
| if (sub_name.empty() || !value_sp) | |
| return value_sp; | |
| switch (sub_name[0]) { | |
| case '.': { | |
| lldb::OptionValueSP return_val_sp; | |
| return_val_sp = | |
| value_sp->GetSubValue(exe_ctx, sub_name.drop_front(), error); | |
| if (!return_val_sp) { | |
| if (Properties::IsSettingExperimental(sub_name.drop_front())) { | |
| const size_t experimental_len = | |
| Properties::GetExperimentalSettingsName().size(); | |
| if (sub_name[experimental_len + 1] == '.') | |
| return_val_sp = value_sp->GetSubValue( | |
| exe_ctx, sub_name.drop_front(experimental_len + 2), error); | |
| // It isn't an error if an experimental setting is not present. | |
| if (!return_val_sp) | |
| error.Clear(); | |
| } | |
| } | |
| return return_val_sp; | |
| } | |
| case '[': | |
| // Array or dictionary access for subvalues like: "[12]" -- access | |
| // 12th array element "['hello']" -- dictionary access of key named hello | |
| return value_sp->GetSubValue(exe_ctx, sub_name, error); | |
| default: | |
| value_sp.reset(); | |
| break; | |
| } | |
| return value_sp; | |
| } | |
| Status OptionValueProperties::SetSubValue(const ExecutionContext *exe_ctx, | |
| VarSetOperationType op, | |
| llvm::StringRef name, | |
| llvm::StringRef value) { | |
| Status error; | |
| llvm::SmallVector<llvm::StringRef, 8> components; | |
| name.split(components, '.'); | |
| bool name_contains_experimental = false; | |
| for (const auto &part : components) | |
| if (Properties::IsSettingExperimental(part)) | |
| name_contains_experimental = true; | |
| lldb::OptionValueSP value_sp(GetSubValue(exe_ctx, name, error)); | |
| if (value_sp) | |
| error = value_sp->SetValueFromString(value, op); | |
| else { | |
| // Don't set an error if the path contained .experimental. - those are | |
| // allowed to be missing and should silently fail. | |
| if (!name_contains_experimental && error.AsCString() == nullptr) { | |
| error = Status::FromErrorStringWithFormat("invalid value path '%s'", | |
| name.str().c_str()); | |
| } | |
| } | |
| return error; | |
| } | |
| size_t OptionValueProperties::GetPropertyIndex(llvm::StringRef name) const { | |
| auto iter = m_name_to_index.find(name); | |
| if (iter == m_name_to_index.end()) | |
| return SIZE_MAX; | |
| return iter->second; | |
| } | |
| const Property * | |
| OptionValueProperties::GetProperty(llvm::StringRef name, | |
| const ExecutionContext *exe_ctx) const { | |
| auto iter = m_name_to_index.find(name); | |
| if (iter == m_name_to_index.end()) | |
| return nullptr; | |
| return GetPropertyAtIndex(iter->second, exe_ctx); | |
| } | |
| lldb::OptionValueSP OptionValueProperties::GetPropertyValueAtIndex( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| const Property *setting = GetPropertyAtIndex(idx, exe_ctx); | |
| if (setting) | |
| return setting->GetValue(); | |
| return OptionValueSP(); | |
| } | |
| OptionValuePathMappings * | |
| OptionValueProperties::GetPropertyAtIndexAsOptionValuePathMappings( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| OptionValueSP value_sp(GetPropertyValueAtIndex(idx, exe_ctx)); | |
| if (value_sp) | |
| return value_sp->GetAsPathMappings(); | |
| return nullptr; | |
| } | |
| OptionValueFileSpecList * | |
| OptionValueProperties::GetPropertyAtIndexAsOptionValueFileSpecList( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| OptionValueSP value_sp(GetPropertyValueAtIndex(idx, exe_ctx)); | |
| if (value_sp) | |
| return value_sp->GetAsFileSpecList(); | |
| return nullptr; | |
| } | |
| bool OptionValueProperties::GetPropertyAtIndexAsArgs( | |
| size_t idx, Args &args, const ExecutionContext *exe_ctx) const { | |
| const Property *property = GetPropertyAtIndex(idx, exe_ctx); | |
| if (!property) | |
| return false; | |
| OptionValue *value = property->GetValue().get(); | |
| if (!value) | |
| return false; | |
| const OptionValueArgs *arguments = value->GetAsArgs(); | |
| if (arguments) { | |
| arguments->GetArgs(args); | |
| return true; | |
| } | |
| const OptionValueArray *array = value->GetAsArray(); | |
| if (array) { | |
| array->GetArgs(args); | |
| return true; | |
| } | |
| const OptionValueDictionary *dict = value->GetAsDictionary(); | |
| if (dict) { | |
| dict->GetArgs(args); | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool OptionValueProperties::SetPropertyAtIndexFromArgs( | |
| size_t idx, const Args &args, const ExecutionContext *exe_ctx) { | |
| const Property *property = GetPropertyAtIndex(idx, exe_ctx); | |
| if (!property) | |
| return false; | |
| OptionValue *value = property->GetValue().get(); | |
| if (!value) | |
| return false; | |
| OptionValueArgs *arguments = value->GetAsArgs(); | |
| if (arguments) | |
| return arguments->SetArgs(args, eVarSetOperationAssign).Success(); | |
| OptionValueArray *array = value->GetAsArray(); | |
| if (array) | |
| return array->SetArgs(args, eVarSetOperationAssign).Success(); | |
| OptionValueDictionary *dict = value->GetAsDictionary(); | |
| if (dict) | |
| return dict->SetArgs(args, eVarSetOperationAssign).Success(); | |
| return false; | |
| } | |
| OptionValueDictionary * | |
| OptionValueProperties::GetPropertyAtIndexAsOptionValueDictionary( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| const Property *property = GetPropertyAtIndex(idx, exe_ctx); | |
| if (property) | |
| return property->GetValue()->GetAsDictionary(); | |
| return nullptr; | |
| } | |
| OptionValueFileSpec * | |
| OptionValueProperties::GetPropertyAtIndexAsOptionValueFileSpec( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| const Property *property = GetPropertyAtIndex(idx, exe_ctx); | |
| if (property) { | |
| OptionValue *value = property->GetValue().get(); | |
| if (value) | |
| return value->GetAsFileSpec(); | |
| } | |
| return nullptr; | |
| } | |
| OptionValueSInt64 *OptionValueProperties::GetPropertyAtIndexAsOptionValueSInt64( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| const Property *property = GetPropertyAtIndex(idx, exe_ctx); | |
| if (property) { | |
| OptionValue *value = property->GetValue().get(); | |
| if (value) | |
| return value->GetAsSInt64(); | |
| } | |
| return nullptr; | |
| } | |
| OptionValueUInt64 *OptionValueProperties::GetPropertyAtIndexAsOptionValueUInt64( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| const Property *property = GetPropertyAtIndex(idx, exe_ctx); | |
| if (property) { | |
| OptionValue *value = property->GetValue().get(); | |
| if (value) | |
| return value->GetAsUInt64(); | |
| } | |
| return nullptr; | |
| } | |
| OptionValueString *OptionValueProperties::GetPropertyAtIndexAsOptionValueString( | |
| size_t idx, const ExecutionContext *exe_ctx) const { | |
| OptionValueSP value_sp(GetPropertyValueAtIndex(idx, exe_ctx)); | |
| if (value_sp) | |
| return value_sp->GetAsString(); | |
| return nullptr; | |
| } | |
| void OptionValueProperties::ClearImpl() { | |
| const size_t num_properties = m_properties.size(); | |
| for (size_t i = 0; i < num_properties; ++i) | |
| m_properties[i].GetValue()->Clear(); | |
| } | |
| Status OptionValueProperties::SetValueFromString(llvm::StringRef value, | |
| VarSetOperationType op) { | |
| Status error; | |
| // Args args(value_cstr); | |
| // const size_t argc = args.GetArgumentCount(); | |
| switch (op) { | |
| case eVarSetOperationClear: | |
| Clear(); | |
| break; | |
| case eVarSetOperationReplace: | |
| case eVarSetOperationAssign: | |
| case eVarSetOperationRemove: | |
| case eVarSetOperationInsertBefore: | |
| case eVarSetOperationInsertAfter: | |
| case eVarSetOperationAppend: | |
| case eVarSetOperationInvalid: | |
| error = OptionValue::SetValueFromString(value, op); | |
| break; | |
| } | |
| return error; | |
| } | |
| void OptionValueProperties::DumpValue(const ExecutionContext *exe_ctx, | |
| Stream &strm, uint32_t dump_mask) { | |
| const size_t num_properties = m_properties.size(); | |
| for (size_t i = 0; i < num_properties; ++i) { | |
| const Property *property = GetPropertyAtIndex(i, exe_ctx); | |
| if (property) { | |
| OptionValue *option_value = property->GetValue().get(); | |
| assert(option_value); | |
| if ((dump_mask & eDumpOptionOnlyChanged) && option_value->IsDefault()) | |
| continue; | |
| const bool transparent_value = option_value->ValueIsTransparent(); | |
| property->Dump(exe_ctx, strm, dump_mask); | |
| if (!transparent_value) | |
| strm.EOL(); | |
| } | |
| } | |
| } | |
| bool OptionValueProperties::IsDefault() const { | |
| for (const Property &property : m_properties) { | |
| if (OptionValue *value = property.GetValue().get()) | |
| if (!value->IsDefault()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| llvm::json::Value | |
| OptionValueProperties::ToJSON(const ExecutionContext *exe_ctx) const { | |
| llvm::json::Object json_properties; | |
| const size_t num_properties = m_properties.size(); | |
| for (size_t i = 0; i < num_properties; ++i) { | |
| const Property *property = GetPropertyAtIndex(i, exe_ctx); | |
| if (property) { | |
| OptionValue *option_value = property->GetValue().get(); | |
| assert(option_value); | |
| json_properties.try_emplace(property->GetName(), | |
| option_value->ToJSON(exe_ctx)); | |
| } | |
| } | |
| return json_properties; | |
| } | |
| Status OptionValueProperties::DumpPropertyValue(const ExecutionContext *exe_ctx, | |
| Stream &strm, | |
| llvm::StringRef property_path, | |
| uint32_t dump_mask, | |
| bool is_json) { | |
| Status error; | |
| lldb::OptionValueSP value_sp(GetSubValue(exe_ctx, property_path, error)); | |
| if (value_sp) { | |
| if (!value_sp->ValueIsTransparent()) { | |
| if (dump_mask & eDumpOptionName) | |
| strm.PutCString(property_path); | |
| if (dump_mask & ~eDumpOptionName) | |
| strm.PutChar(' '); | |
| } | |
| if (is_json) | |
| strm << llvm::formatv("{0:2}", value_sp->ToJSON(exe_ctx)); | |
| else | |
| value_sp->DumpValue(exe_ctx, strm, dump_mask); | |
| } | |
| return error; | |
| } | |
| OptionValuePropertiesSP | |
| OptionValueProperties::CreateLocalCopy(const Properties &global_properties) { | |
| auto global_props_sp = global_properties.GetValueProperties(); | |
| lldbassert(global_props_sp); | |
| auto copy_sp = global_props_sp->DeepCopy(global_props_sp->GetParent()); | |
| return std::static_pointer_cast<OptionValueProperties>(copy_sp); | |
| } | |
| OptionValueSP | |
| OptionValueProperties::DeepCopy(const OptionValueSP &new_parent) const { | |
| auto copy_sp = OptionValue::DeepCopy(new_parent); | |
| // copy_sp->GetAsProperties cannot be used here as it doesn't work for derived | |
| // types that override GetType returning a different value. | |
| auto *props_value_ptr = static_cast<OptionValueProperties *>(copy_sp.get()); | |
| lldbassert(props_value_ptr); | |
| for (auto &property : props_value_ptr->m_properties) { | |
| // Duplicate any values that are not global when constructing properties | |
| // from a global copy. | |
| if (!property.IsGlobal()) { | |
| auto value_sp = property.GetValue()->DeepCopy(copy_sp); | |
| property.SetOptionValue(value_sp); | |
| } | |
| } | |
| return copy_sp; | |
| } | |
| const Property * | |
| OptionValueProperties::GetPropertyAtPath(const ExecutionContext *exe_ctx, | |
| llvm::StringRef name) const { | |
| if (name.empty()) | |
| return nullptr; | |
| const Property *property = nullptr; | |
| llvm::StringRef sub_name; | |
| llvm::StringRef key; | |
| size_t key_len = name.find_first_of(".[{"); | |
| if (key_len != llvm::StringRef::npos) { | |
| key = name.take_front(key_len); | |
| sub_name = name.drop_front(key_len); | |
| } else | |
| key = name; | |
| property = GetProperty(key, exe_ctx); | |
| if (sub_name.empty() || !property) | |
| return property; | |
| if (sub_name[0] == '.') { | |
| OptionValueProperties *sub_properties = | |
| property->GetValue()->GetAsProperties(); | |
| if (sub_properties) | |
| return sub_properties->GetPropertyAtPath(exe_ctx, sub_name.drop_front()); | |
| } | |
| return nullptr; | |
| } | |
| void OptionValueProperties::DumpAllDescriptions(CommandInterpreter &interpreter, | |
| Stream &strm) const { | |
| size_t max_name_len = 0; | |
| const size_t num_properties = m_properties.size(); | |
| for (size_t i = 0; i < num_properties; ++i) { | |
| const Property *property = ProtectedGetPropertyAtIndex(i); | |
| if (property) | |
| max_name_len = std::max<size_t>(property->GetName().size(), max_name_len); | |
| } | |
| for (size_t i = 0; i < num_properties; ++i) { | |
| const Property *property = ProtectedGetPropertyAtIndex(i); | |
| if (property) | |
| property->DumpDescription(interpreter, strm, max_name_len, false); | |
| } | |
| } | |
| void OptionValueProperties::Apropos( | |
| llvm::StringRef keyword, std::vector<const Property *> &matching_properties, | |
| std::vector<const Property *> &matching_property_paths) const { | |
| const size_t num_properties = m_properties.size(); | |
| for (size_t i = 0; i < num_properties; ++i) { | |
| const Property *property = ProtectedGetPropertyAtIndex(i); | |
| if (!property) | |
| continue; | |
| const OptionValueProperties *properties = | |
| property->GetValue()->GetAsProperties(); | |
| if (properties) | |
| properties->Apropos(keyword, matching_properties, | |
| matching_property_paths); | |
| bool matched = false; | |
| if (llvm::StringRef name = property->GetName(); | |
| !matched && name.contains_insensitive(keyword)) | |
| matched = true; | |
| if (llvm::StringRef desc = property->GetDescription(); | |
| !matched && desc.contains_insensitive(keyword)) | |
| matched = true; | |
| if (!matched) | |
| continue; | |
| if (properties) { | |
| matching_property_paths.push_back(property); | |
| } else { | |
| matching_properties.push_back(property); | |
| } | |
| } | |
| } | |
| lldb::OptionValuePropertiesSP | |
| OptionValueProperties::GetSubProperty(const ExecutionContext *exe_ctx, | |
| llvm::StringRef name) { | |
| lldb::OptionValueSP option_value_sp(GetValueForKey(exe_ctx, name)); | |
| if (option_value_sp) { | |
| OptionValueProperties *ov_properties = option_value_sp->GetAsProperties(); | |
| if (ov_properties) | |
| return ov_properties->shared_from_this(); | |
| } | |
| return lldb::OptionValuePropertiesSP(); | |
| } | |
| bool OptionValueProperties::VerifyPath() { | |
| OptionValueSP parent = GetParent(); | |
| if (!parent) { | |
| // Only the top level value should have an empty path. | |
| return m_expected_path.empty(); | |
| } | |
| OptionValueProperties *parent_properties = parent->GetAsProperties(); | |
| if (!parent_properties) | |
| return false; | |
| auto [prefix, expected_name] = llvm::StringRef(m_expected_path).rsplit('.'); | |
| if (expected_name.empty()) { | |
| // There is no dot, so the parent should be the top-level (core properties). | |
| return parent_properties->m_expected_path.empty() && GetName() == prefix; | |
| } | |
| return parent_properties->m_expected_path == prefix && | |
| GetName() == expected_name; | |
| } |