| //===-- OptionValueDictionary.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/OptionValueDictionary.h" | |
| #include "lldb/DataFormatters/FormatManager.h" | |
| #include "lldb/Interpreter/OptionValue.h" | |
| #include "lldb/Interpreter/OptionValueEnumeration.h" | |
| #include "lldb/Interpreter/OptionValueString.h" | |
| #include "lldb/Utility/Args.h" | |
| #include "lldb/Utility/State.h" | |
| #include "llvm/ADT/StringRef.h" | |
| using namespace lldb; | |
| using namespace lldb_private; | |
| void OptionValueDictionary::DumpValue(const ExecutionContext *exe_ctx, | |
| Stream &strm, uint32_t dump_mask) { | |
| const Type dict_type = ConvertTypeMaskToType(m_type_mask); | |
| if (dump_mask & eDumpOptionType) { | |
| if (m_type_mask != eTypeInvalid) | |
| strm.Printf("(%s of %ss)", GetTypeAsCString(), | |
| GetBuiltinTypeAsCString(dict_type)); | |
| else | |
| strm.Printf("(%s)", GetTypeAsCString()); | |
| } | |
| if (dump_mask & eDumpOptionValue) { | |
| const bool one_line = dump_mask & eDumpOptionCommand; | |
| if (dump_mask & (eDumpOptionType | eDumpOptionDefaultValue)) { | |
| strm.PutCString(" ="); | |
| if (dump_mask & eDumpOptionDefaultValue && !m_values.empty()) { | |
| DefaultValueFormat label(strm); | |
| strm.PutCString("empty"); | |
| } | |
| } | |
| if (!one_line) | |
| strm.IndentMore(); | |
| // m_values is not guaranteed to be sorted alphabetically, so for | |
| // consistentcy we will sort them here before dumping | |
| std::map<llvm::StringRef, OptionValue *> sorted_values; | |
| for (const auto &value : m_values) { | |
| sorted_values[value.first()] = value.second.get(); | |
| } | |
| for (const auto &value : sorted_values) { | |
| OptionValue *option_value = value.second; | |
| if (one_line) | |
| strm << ' '; | |
| else | |
| strm.EOL(); | |
| strm.Indent(value.first); | |
| const uint32_t extra_dump_options = m_raw_value_dump ? eDumpOptionRaw : 0; | |
| switch (dict_type) { | |
| default: | |
| case eTypeArray: | |
| case eTypeDictionary: | |
| case eTypeProperties: | |
| case eTypeFileSpecList: | |
| case eTypePathMap: | |
| strm.PutChar(' '); | |
| option_value->DumpValue(exe_ctx, strm, dump_mask | extra_dump_options); | |
| break; | |
| case eTypeBoolean: | |
| case eTypeChar: | |
| case eTypeEnum: | |
| case eTypeFileLineColumn: | |
| case eTypeFileSpec: | |
| case eTypeFormat: | |
| case eTypeSInt64: | |
| case eTypeString: | |
| case eTypeUInt64: | |
| case eTypeUUID: | |
| // No need to show the type for dictionaries of simple items | |
| strm.PutCString("="); | |
| option_value->DumpValue(exe_ctx, strm, | |
| (dump_mask & (~eDumpOptionType)) | | |
| extra_dump_options); | |
| break; | |
| } | |
| } | |
| if (!one_line) | |
| strm.IndentLess(); | |
| } | |
| } | |
| llvm::json::Value | |
| OptionValueDictionary::ToJSON(const ExecutionContext *exe_ctx) const { | |
| llvm::json::Object dict; | |
| for (const auto &value : m_values) { | |
| dict.try_emplace(value.first(), value.second->ToJSON(exe_ctx)); | |
| } | |
| return dict; | |
| } | |
| size_t OptionValueDictionary::GetArgs(Args &args) const { | |
| args.Clear(); | |
| for (const auto &value : m_values) { | |
| StreamString strm; | |
| strm.Printf("%s=", value.first().data()); | |
| value.second->DumpValue(nullptr, strm, eDumpOptionValue | eDumpOptionRaw); | |
| args.AppendArgument(strm.GetString()); | |
| } | |
| return args.GetArgumentCount(); | |
| } | |
| Status OptionValueDictionary::SetArgs(const Args &args, | |
| VarSetOperationType op) { | |
| Status error; | |
| const size_t argc = args.GetArgumentCount(); | |
| switch (op) { | |
| case eVarSetOperationClear: | |
| Clear(); | |
| break; | |
| case eVarSetOperationAppend: | |
| case eVarSetOperationReplace: | |
| case eVarSetOperationAssign: | |
| if (argc == 0) { | |
| error = Status::FromErrorString( | |
| "assign operation takes one or more key=value arguments"); | |
| return error; | |
| } | |
| for (const auto &entry : args) { | |
| if (entry.ref().empty()) { | |
| error = Status::FromErrorString("empty argument"); | |
| return error; | |
| } | |
| if (!entry.ref().contains('=')) { | |
| error = Status::FromErrorString( | |
| "assign operation takes one or more key=value arguments"); | |
| return error; | |
| } | |
| llvm::StringRef key, value; | |
| std::tie(key, value) = entry.ref().split('='); | |
| bool key_valid = false; | |
| if (key.empty()) { | |
| error = Status::FromErrorString("empty dictionary key"); | |
| return error; | |
| } | |
| if (key.front() == '[') { | |
| // Key name starts with '[', so the key value must be in single or | |
| // double quotes like: ['<key>'] ["<key>"] | |
| if ((key.size() > 2) && (key.back() == ']')) { | |
| // Strip leading '[' and trailing ']' | |
| key = key.substr(1, key.size() - 2); | |
| const char quote_char = key.front(); | |
| if ((quote_char == '\'') || (quote_char == '"')) { | |
| if ((key.size() > 2) && (key.back() == quote_char)) { | |
| // Strip the quotes | |
| key = key.substr(1, key.size() - 2); | |
| key_valid = true; | |
| } | |
| } else { | |
| // square brackets, no quotes | |
| key_valid = true; | |
| } | |
| } | |
| } else { | |
| // No square brackets or quotes | |
| key_valid = true; | |
| } | |
| if (!key_valid) { | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid key \"%s\", the key must be a bare string or " | |
| "surrounded by brackets with optional quotes: [<key>] or " | |
| "['<key>'] or [\"<key>\"]", | |
| key.str().c_str()); | |
| return error; | |
| } | |
| if (m_type_mask == 1u << eTypeEnum) { | |
| auto enum_value = | |
| std::make_shared<OptionValueEnumeration>(m_enum_values, 0); | |
| error = enum_value->SetValueFromString(value); | |
| if (error.Fail()) | |
| return error; | |
| m_value_was_set = true; | |
| SetValueForKey(key, enum_value, true); | |
| } else { | |
| lldb::OptionValueSP value_sp(CreateValueFromCStringForTypeMask( | |
| value.str().c_str(), m_type_mask, error)); | |
| if (value_sp) { | |
| if (error.Fail()) | |
| return error; | |
| m_value_was_set = true; | |
| SetValueForKey(key, value_sp, true); | |
| } else { | |
| error = Status::FromErrorString( | |
| "dictionaries that can contain multiple types " | |
| "must subclass OptionValueArray"); | |
| } | |
| } | |
| } | |
| break; | |
| case eVarSetOperationRemove: | |
| if (argc > 0) { | |
| for (size_t i = 0; i < argc; ++i) { | |
| llvm::StringRef key(args.GetArgumentAtIndex(i)); | |
| if (!DeleteValueForKey(key)) { | |
| error = Status::FromErrorStringWithFormat( | |
| "no value found named '%s', aborting remove operation", | |
| key.data()); | |
| break; | |
| } | |
| } | |
| } else { | |
| error = Status::FromErrorString( | |
| "remove operation takes one or more key arguments"); | |
| } | |
| break; | |
| case eVarSetOperationInsertBefore: | |
| case eVarSetOperationInsertAfter: | |
| case eVarSetOperationInvalid: | |
| error = OptionValue::SetValueFromString(llvm::StringRef(), op); | |
| break; | |
| } | |
| return error; | |
| } | |
| Status OptionValueDictionary::SetValueFromString(llvm::StringRef value, | |
| VarSetOperationType op) { | |
| Args args(value.str()); | |
| Status error = SetArgs(args, op); | |
| if (error.Success()) | |
| NotifyValueChanged(); | |
| return error; | |
| } | |
| lldb::OptionValueSP | |
| OptionValueDictionary::GetSubValue(const ExecutionContext *exe_ctx, | |
| llvm::StringRef name, Status &error) const { | |
| lldb::OptionValueSP value_sp; | |
| if (name.empty()) | |
| return nullptr; | |
| llvm::StringRef left, temp; | |
| std::tie(left, temp) = name.split('['); | |
| if (left.size() == name.size()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid value path '%s', %s values only " | |
| "support '[<key>]' subvalues where <key> " | |
| "a string value optionally delimited by " | |
| "single or double quotes", | |
| name.str().c_str(), GetTypeAsCString()); | |
| return nullptr; | |
| } | |
| assert(!temp.empty()); | |
| llvm::StringRef key, quote_char; | |
| if (temp[0] == '\"' || temp[0] == '\'') { | |
| quote_char = temp.take_front(); | |
| temp = temp.drop_front(); | |
| } | |
| llvm::StringRef sub_name; | |
| std::tie(key, sub_name) = temp.split(']'); | |
| if (!key.consume_back(quote_char) || key.empty()) { | |
| error = Status::FromErrorStringWithFormat( | |
| "invalid value path '%s', " | |
| "key names must be formatted as ['<key>'] where <key> " | |
| "is a string that doesn't contain quotes and the quote" | |
| " char is optional", | |
| name.str().c_str()); | |
| return nullptr; | |
| } | |
| value_sp = GetValueForKey(key); | |
| if (!value_sp) { | |
| error = Status::FromErrorStringWithFormat( | |
| "dictionary does not contain a value for the key name '%s'", | |
| key.str().c_str()); | |
| return nullptr; | |
| } | |
| if (sub_name.empty()) | |
| return value_sp; | |
| return value_sp->GetSubValue(exe_ctx, sub_name, error); | |
| } | |
| Status OptionValueDictionary::SetSubValue(const ExecutionContext *exe_ctx, | |
| VarSetOperationType op, | |
| llvm::StringRef name, | |
| llvm::StringRef value) { | |
| Status error; | |
| lldb::OptionValueSP value_sp(GetSubValue(exe_ctx, name, error)); | |
| if (value_sp) | |
| error = value_sp->SetValueFromString(value, op); | |
| else { | |
| if (error.AsCString() == nullptr) | |
| error = Status::FromErrorStringWithFormat("invalid value path '%s'", | |
| name.str().c_str()); | |
| } | |
| return error; | |
| } | |
| lldb::OptionValueSP | |
| OptionValueDictionary::GetValueForKey(llvm::StringRef key) const { | |
| lldb::OptionValueSP value_sp; | |
| auto pos = m_values.find(key); | |
| if (pos != m_values.end()) | |
| value_sp = pos->second; | |
| return value_sp; | |
| } | |
| bool OptionValueDictionary::SetValueForKey(llvm::StringRef key, | |
| const lldb::OptionValueSP &value_sp, | |
| bool can_replace) { | |
| // Make sure the value_sp object is allowed to contain values of the type | |
| // passed in... | |
| if (value_sp && (m_type_mask & value_sp->GetTypeAsMask())) { | |
| if (!can_replace) { | |
| auto pos = m_values.find(key); | |
| if (pos != m_values.end()) | |
| return false; | |
| } | |
| m_values[key] = value_sp; | |
| return true; | |
| } | |
| return false; | |
| } | |
| bool OptionValueDictionary::DeleteValueForKey(llvm::StringRef key) { | |
| auto pos = m_values.find(key); | |
| if (pos != m_values.end()) { | |
| m_values.erase(pos); | |
| return true; | |
| } | |
| return false; | |
| } | |
| OptionValueSP | |
| OptionValueDictionary::DeepCopy(const OptionValueSP &new_parent) const { | |
| auto copy_sp = OptionValue::DeepCopy(new_parent); | |
| // copy_sp->GetAsDictionary cannot be used here as it doesn't work for derived | |
| // types that override GetType returning a different value. | |
| auto *dict_value_ptr = static_cast<OptionValueDictionary *>(copy_sp.get()); | |
| lldbassert(dict_value_ptr); | |
| for (auto &value : dict_value_ptr->m_values) | |
| value.second = value.second->DeepCopy(copy_sp); | |
| return copy_sp; | |
| } |