In #168245, I attempted to dump the available settings to Markdown. That required a full build of LLDB. However, to build the docs, only the swig wrappers should need to be compiled. The comment was that we should be able to use the definitions from the TableGen files. Currently, the property definitions in don't have information about the path where they will be available. They only contain a `Definition` which groups properties, so they can be added to `OptionValueProperties`. With this PR, I'm adding the path for each property definition. For example, `symbols.enable-external-lookup` would have `Name = enable-external-lookup, Path = symbols`. In LLDB itself, we don't need this path, we only need it for the documentation. To avoid mismatches between the actual path and the declared one, I added a debug-only check when a property group is added to a parent (`OptionValueProperties::AppendProperty`). The TableGen emitter for the properties now additionally emits `g_{definition}_properties_def`, which includes both the array of properties and the expected path. This constant has to be used to initialize a `OptionValueProperties`. I couldn't test this for everything (e.g. IntelPT or ProcessKDP), but the necessary changes are simple: (1) set the `Path` in the TableGen file, (2) update `initialize` to use `_def`.
529 lines
17 KiB
C++
529 lines
17 KiB
C++
//===-- 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::Clear() {
|
|
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);
|
|
const bool transparent_value = option_value->ValueIsTransparent();
|
|
property->Dump(exe_ctx, strm, dump_mask);
|
|
if (!transparent_value)
|
|
strm.EOL();
|
|
}
|
|
}
|
|
}
|
|
|
|
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.Printf(
|
|
"%s",
|
|
llvm::formatv("{0:2}", value_sp->ToJSON(exe_ctx)).str().c_str());
|
|
} 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) const {
|
|
const size_t num_properties = m_properties.size();
|
|
StreamString strm;
|
|
for (size_t i = 0; i < num_properties; ++i) {
|
|
const Property *property = ProtectedGetPropertyAtIndex(i);
|
|
if (property) {
|
|
const OptionValueProperties *properties =
|
|
property->GetValue()->GetAsProperties();
|
|
if (properties) {
|
|
properties->Apropos(keyword, matching_properties);
|
|
} else {
|
|
bool match = false;
|
|
llvm::StringRef name = property->GetName();
|
|
if (name.contains_insensitive(keyword))
|
|
match = true;
|
|
else {
|
|
llvm::StringRef desc = property->GetDescription();
|
|
if (desc.contains_insensitive(keyword))
|
|
match = true;
|
|
}
|
|
if (match) {
|
|
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;
|
|
}
|