Add SBDebugger::GetSetting() public APIs

This patch adds new SBDebugger::GetSetting() API which
enables client to access settings as SBStructedData.

Implementation wise, a new ToJSON() virtual function is added to OptionValue
class so that each concrete child class can override and provides its
own JSON representation. This patch aims to define the APIs and implement
a common set of OptionValue child classes, leaving the remaining for
future patches.

This patch is used later by auto deduce source map from source line breakpoint
feature for testing generated source map entries.

Differential Revision: https://reviews.llvm.org/D133038
This commit is contained in:
Jeffrey Tan 2022-09-08 11:00:22 -07:00
parent 844f6c5d03
commit d5f5475104
32 changed files with 279 additions and 15 deletions

View File

@ -225,6 +225,8 @@ public:
}
}
lldb::SBStructuredData GetSetting(const char *setting = nullptr);
SBError
SetInputString (const char* data);

View File

@ -115,6 +115,21 @@ public:
void Clear();
/// Getting a specific setting value into SBStructuredData format.
/// Client can specify empty string or null to get all settings.
///
/// Example usages:
/// lldb::SBStructuredData settings = debugger.GetSetting();
/// lldb::SBStructuredData settings = debugger.GetSetting(nullptr);
/// lldb::SBStructuredData settings = debugger.GetSetting("");
/// lldb::SBStructuredData settings = debugger.GetSetting("target.arg0");
/// lldb::SBStructuredData settings = debugger.GetSetting("target");
///
/// \param[out] setting
/// Property setting path to retrieve values. e.g "target.source-map"
///
lldb::SBStructuredData GetSetting(const char *setting = nullptr);
void SetAsync(bool b);
bool GetAsync();

View File

@ -57,10 +57,11 @@ public:
virtual Status DumpPropertyValue(const ExecutionContext *exe_ctx,
Stream &strm, llvm::StringRef property_path,
uint32_t dump_mask);
uint32_t dump_mask, bool is_json = false);
virtual void DumpAllPropertyValues(const ExecutionContext *exe_ctx,
Stream &strm, uint32_t dump_mask);
Stream &strm, uint32_t dump_mask,
bool is_json = false);
virtual void DumpAllDescriptions(CommandInterpreter &interpreter,
Stream &strm) const;

View File

@ -17,6 +17,7 @@
#include "lldb/lldb-defines.h"
#include "lldb/lldb-private-enumerations.h"
#include "lldb/lldb-private-interfaces.h"
#include "llvm/Support/JSON.h"
namespace lldb_private {
@ -82,6 +83,16 @@ public:
virtual void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) = 0;
// TODO: make this function pure virtual after implementing it in all
// child classes.
virtual llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) {
// Return nullptr which will create a llvm::json::Value() that is a NULL
// value. No setting should ever really have a NULL value in JSON. This
// indicates an error occurred and if/when we add a FromJSON() it will know
// to fail if someone tries to set it with a NULL JSON value.
return nullptr;
}
virtual Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign);

View File

@ -29,6 +29,8 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -29,6 +29,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_current_value;
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -30,6 +30,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_current_value;
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -34,6 +34,8 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -35,6 +35,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_current_value.GetPath();
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -31,6 +31,8 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -28,6 +28,8 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -33,6 +33,8 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -29,6 +29,8 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -47,11 +47,13 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override;
ConstString GetName() const override { return m_name; }
virtual Status DumpPropertyValue(const ExecutionContext *exe_ctx,
Stream &strm, llvm::StringRef property_path,
uint32_t dump_mask);
uint32_t dump_mask, bool is_json = false);
virtual void DumpAllDescriptions(CommandInterpreter &interpreter,
Stream &strm) const;

View File

@ -28,6 +28,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_regex.GetText();
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -35,6 +35,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_current_value;
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -69,6 +69,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_current_value;
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;
@ -109,7 +113,7 @@ public:
bool IsCurrentValueEmpty() const { return m_current_value.empty(); }
bool IsDefaultValueEmpty() const { return m_default_value.empty(); }
void SetValidator(ValidatorCallback validator, void *baton = nullptr) {
m_validator = validator;
m_validator_baton = baton;

View File

@ -38,6 +38,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_current_value;
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -29,6 +29,10 @@ public:
void DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
uint32_t dump_mask) override;
llvm::json::Value ToJSON(const ExecutionContext *exe_ctx) override {
return m_uuid.GetAsString();
}
Status
SetValueFromString(llvm::StringRef value,
VarSetOperationType op = eVarSetOperationAssign) override;

View File

@ -9,10 +9,11 @@
#ifndef LLDB_TARGET_PATHMAPPINGLIST_H
#define LLDB_TARGET_PATHMAPPINGLIST_H
#include <map>
#include <vector>
#include "lldb/Utility/ConstString.h"
#include "lldb/Utility/Status.h"
#include "llvm/Support/JSON.h"
#include <map>
#include <vector>
namespace lldb_private {
@ -41,6 +42,8 @@ public:
// By default, dump all pairs.
void Dump(Stream *s, int pair_index = -1);
llvm::json::Value ToJSON();
bool IsEmpty() const { return m_pairs.empty(); }
size_t GetSize() const { return m_pairs.size(); }

View File

@ -436,6 +436,29 @@ SBError SBDebugger::SetErrorFile(SBFile file) {
return error;
}
lldb::SBStructuredData SBDebugger::GetSetting(const char *setting) {
LLDB_INSTRUMENT_VA(this, setting);
SBStructuredData data;
if (!m_opaque_sp)
return data;
StreamString json_strm;
ExecutionContext exe_ctx(
m_opaque_sp->GetCommandInterpreter().GetExecutionContext());
if (setting && strlen(setting) > 0)
m_opaque_sp->DumpPropertyValue(&exe_ctx, json_strm, setting,
/*dump_mask*/ 0,
/*is_json*/ true);
else
m_opaque_sp->DumpAllPropertyValues(&exe_ctx, json_strm, /*dump_mask*/ 0,
/*is_json*/ true);
data.m_impl_up->SetObjectSP(
StructuredData::ParseJSON(json_strm.GetString().str()));
return data;
}
FILE *SBDebugger::GetInputFileHandle() {
LLDB_INSTRUMENT_VA(this);
if (m_opaque_sp) {

View File

@ -53,10 +53,17 @@ Status Properties::SetPropertyValue(const ExecutionContext *exe_ctx,
}
void Properties::DumpAllPropertyValues(const ExecutionContext *exe_ctx,
Stream &strm, uint32_t dump_mask) {
Stream &strm, uint32_t dump_mask,
bool is_json) {
OptionValuePropertiesSP properties_sp(GetValueProperties());
if (properties_sp)
return properties_sp->DumpValue(exe_ctx, strm, dump_mask);
if (!properties_sp)
return;
if (is_json) {
llvm::json::Value json = properties_sp->ToJSON(exe_ctx);
strm.Printf("%s", llvm::formatv("{0:2}", json).str().c_str());
} else
properties_sp->DumpValue(exe_ctx, strm, dump_mask);
}
void Properties::DumpAllDescriptions(CommandInterpreter &interpreter,
@ -71,11 +78,11 @@ void Properties::DumpAllDescriptions(CommandInterpreter &interpreter,
Status Properties::DumpPropertyValue(const ExecutionContext *exe_ctx,
Stream &strm,
llvm::StringRef property_path,
uint32_t dump_mask) {
uint32_t dump_mask, bool is_json) {
OptionValuePropertiesSP properties_sp(GetValueProperties());
if (properties_sp) {
return properties_sp->DumpPropertyValue(exe_ctx, strm, property_path,
dump_mask);
dump_mask, is_json);
}
Status error;
error.SetErrorString("empty property list");

View File

@ -75,6 +75,14 @@ void OptionValueArray::DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
}
}
llvm::json::Value OptionValueArray::ToJSON(const ExecutionContext *exe_ctx) {
llvm::json::Array json_array;
const uint32_t size = m_values.size();
for (uint32_t i = 0; i < size; ++i)
json_array.emplace_back(m_values[i]->ToJSON(exe_ctx));
return json_array;
}
Status OptionValueArray::SetValueFromString(llvm::StringRef value,
VarSetOperationType op) {
Args args(value.str());

View File

@ -83,6 +83,15 @@ void OptionValueDictionary::DumpValue(const ExecutionContext *exe_ctx,
}
}
llvm::json::Value
OptionValueDictionary::ToJSON(const ExecutionContext *exe_ctx) {
llvm::json::Object dict;
for (const auto &value : m_values) {
dict.try_emplace(value.first.GetCString(), value.second->ToJSON(exe_ctx));
}
return dict;
}
size_t OptionValueDictionary::GetArgs(Args &args) const {
args.Clear();
collection::const_iterator pos, end = m_values.end();

View File

@ -26,6 +26,10 @@ void OptionValueFormat::DumpValue(const ExecutionContext *exe_ctx, Stream &strm,
}
}
llvm::json::Value OptionValueFormat::ToJSON(const ExecutionContext *exe_ctx) {
return FormatManager::GetFormatAsCString(m_current_value);
}
Status OptionValueFormat::SetValueFromString(llvm::StringRef value,
VarSetOperationType op) {
Status error;

View File

@ -60,6 +60,13 @@ void OptionValueFormatEntity::DumpValue(const ExecutionContext *exe_ctx,
}
}
llvm::json::Value
OptionValueFormatEntity::ToJSON(const ExecutionContext *exe_ctx) {
std::string escaped;
EscapeBackticks(m_current_format, escaped);
return escaped;
}
Status OptionValueFormatEntity::SetValueFromString(llvm::StringRef value_str,
VarSetOperationType op) {
Status error;

View File

@ -29,6 +29,10 @@ void OptionValueLanguage::DumpValue(const ExecutionContext *exe_ctx,
}
}
llvm::json::Value OptionValueLanguage::ToJSON(const ExecutionContext *exe_ctx) {
return Language::GetNameForLanguageType(m_current_value);
}
Status OptionValueLanguage::SetValueFromString(llvm::StringRef value,
VarSetOperationType op) {
Status error;

View File

@ -34,6 +34,11 @@ void OptionValuePathMappings::DumpValue(const ExecutionContext *exe_ctx,
}
}
llvm::json::Value
OptionValuePathMappings::ToJSON(const ExecutionContext *exe_ctx) {
return m_path_mappings.ToJSON();
}
Status OptionValuePathMappings::SetValueFromString(llvm::StringRef value,
VarSetOperationType op) {
Status error;

View File

@ -545,10 +545,26 @@ void OptionValueProperties::DumpValue(const ExecutionContext *exe_ctx,
}
}
llvm::json::Value
OptionValueProperties::ToJSON(const ExecutionContext *exe_ctx) {
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(exe_ctx, false, i);
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) {
uint32_t dump_mask, bool is_json) {
Status error;
const bool will_modify = false;
lldb::OptionValueSP value_sp(
@ -560,7 +576,10 @@ Status OptionValueProperties::DumpPropertyValue(const ExecutionContext *exe_ctx,
if (dump_mask & ~eDumpOptionName)
strm.PutChar(' ');
}
value_sp->DumpValue(exe_ctx, strm, dump_mask);
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;
}

View File

@ -131,6 +131,16 @@ void PathMappingList::Dump(Stream *s, int pair_index) {
}
}
llvm::json::Value PathMappingList::ToJSON() {
llvm::json::Array entries;
for (const auto &pair : m_pairs) {
llvm::json::Array entry{pair.first.GetStringRef().str(),
pair.second.GetStringRef().str()};
entries.emplace_back(std::move(entry));
}
return entries;
}
void PathMappingList::Clear(bool notify) {
if (!m_pairs.empty())
++m_mod_id;

View File

@ -3,7 +3,7 @@ Test lldb settings command.
"""
import json
import os
import re
import lldb
@ -274,7 +274,7 @@ class SettingsCommandTestCase(TestBase):
self.assertEqual(launch_info.GetArgumentAtIndex(0), "A")
self.assertEqual(launch_info.GetArgumentAtIndex(1), "B")
self.assertEqual(launch_info.GetArgumentAtIndex(2), "C")
self.expect(
'target show-launch-environment',
substrs=["MY_ENV_VAR=YES"])
@ -787,3 +787,59 @@ class SettingsCommandTestCase(TestBase):
# A known option should fail if its argument is invalid.
self.expect("settings set auto-confirm bogus", error=True)
def get_setting_json(self, setting_path = None):
settings_data = self.dbg.GetSetting(setting_path)
stream = lldb.SBStream()
settings_data.GetAsJSON(stream)
return json.loads(stream.GetData())
def verify_setting_value_json(self, setting_path, setting_value):
self.runCmd("settings set %s %s" % (setting_path, setting_value))
settings_json = self.get_setting_json(setting_path)
self.assertEqual(settings_json, setting_value)
def test_settings_api(self):
"""
Test that ensures SBDebugger::GetSetting() APIs
can correctly fetch settings.
"""
# Test basic values and embedding special JSON escaping characters.
self.runCmd("settings set auto-confirm true")
self.runCmd("settings set tab-size 2")
arg_value = "hello \"world\""
self.runCmd('settings set target.arg0 %s' % arg_value)
settings_json = self.get_setting_json()
self.assertEqual(settings_json["auto-confirm"], True)
self.assertEqual(settings_json["tab-size"], 2)
self.assertEqual(settings_json["target"]["arg0"], arg_value)
settings_data = self.get_setting_json("target.arg0")
self.assertEqual(settings_data, arg_value)
# Test OptionValueFileSpec
self.verify_setting_value_json("platform.module-cache-directory", self.get_process_working_directory())
# Test OptionValueArray
setting_path = "target.run-args"
setting_value = ["value1", "value2", "value3"]
self.runCmd("settings set %s %s" % (setting_path, " ".join(setting_value)))
settings_json = self.get_setting_json(setting_path)
self.assertEqual(settings_json, setting_value)
# Test OptionValueFormatEntity
setting_value = """thread #${thread.index}{, name = \\'${thread.name}\\
'}{, queue = ${ansi.fg.green}\\'${thread.queue}\\'${ansi.normal}}{,
activity = ${ansi.fg.green}\\'${thread.info.activity.name}\\'${ansi.normal}}
{, ${thread.info.trace_messages} messages}{, stop reason = ${ansi.fg.red}$
{thread.stop-reason}${ansi.normal}}{\\\\nReturn value: ${thread.return-value}}
{\\\\nCompleted expression: ${thread.completed-expression}}\\\\n"""
self.verify_setting_value_json("thread-stop-format", setting_value)
# Test OptionValueRegex
self.verify_setting_value_json("target.process.thread.step-avoid-regexp", "^std::")
# Test OptionValueLanguage
self.verify_setting_value_json("repl-lang", "c++")

View File

@ -1,11 +1,45 @@
import lldb
from lldbsuite.test.lldbtest import *
from lldbsuite.test.decorators import *
import json
import os
class TestTargetSourceMap(TestBase):
@no_debug_info_test
def test_source_map_via_setting_api(self):
"""
Test that ensures SBDebugger::GetSetting("target.source-map") API
can correctly fetch source mapping entries.
"""
# Set the target soure map to map "./" to the current test directory
src_dir = self.getSourceDir()
source_map_setting_path = "target.source-map"
initial_source_map = self.dbg.GetSetting(source_map_setting_path)
self.assertEquals(initial_source_map.GetSize(), 0,
"Initial source map should be empty")
src_dir = self.getSourceDir()
self.runCmd('settings set %s . "%s"' % (source_map_setting_path, src_dir))
source_map = self.dbg.GetSetting(source_map_setting_path)
self.assertEquals(source_map.GetSize(), 1,
"source map should be have one appended entry")
stream = lldb.SBStream()
source_map.GetAsJSON(stream)
serialized_source_map = json.loads(stream.GetData())
self.assertEquals(len(serialized_source_map[0]), 2,
"source map entry should have two parts")
self.assertEquals(serialized_source_map[0][0], ".",
"source map entry's first part does not match")
self.assertEquals(serialized_source_map[0][1], src_dir,
"source map entry's second part does not match")
@no_debug_info_test
def test_source_map(self):
"""Test target.source-map' functionality."""