diff options
author | royitaqi <royitaqi@users.noreply.github.com> | 2024-05-20 15:49:46 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-05-20 15:49:46 -0700 |
commit | e8dc8d614ada201e250fbf075241b2b6180943b5 (patch) | |
tree | 8b689fdddac43ee191b9e2942bb69242dbf940a3 /lldb/source/Interpreter/CommandInterpreter.cpp | |
parent | 8018e4c569d34b5913a4cc78f08f25f778dec866 (diff) | |
download | llvm-e8dc8d614ada201e250fbf075241b2b6180943b5.zip llvm-e8dc8d614ada201e250fbf075241b2b6180943b5.tar.gz llvm-e8dc8d614ada201e250fbf075241b2b6180943b5.tar.bz2 |
Add new Python API `SBCommandInterpreter::GetTranscript()` (#90703)
# Motivation
Currently, the user can already get the "transcript" (for "what is the
transcript", see `CommandInterpreter::SaveTranscript`). However, the
only way to obtain the transcript data as a user is to first destroy the
debugger, then read the save directory. Note that destroy-callbacks
cannot be used, because 1\ transcript data is private to the command
interpreter (see `CommandInterpreter.h`), and 2\ the writing of the
transcript is *after* the invocation of destory-callbacks (see
`Debugger::Destroy`).
So basically, there is no way to obtain the transcript:
* during the lifetime of a debugger (including the destroy-callbacks,
which often performs logging tasks, where the transcript can be useful)
* without relying on external storage
In theory, there are other ways for user to obtain transcript data
during a debugger's life cycle:
* Use Python API and intercept commands and results.
* Use CLI and record console input/output.
However, such ways rely on the client's setup and are not supported
natively by LLDB.
# Proposal
Add a new Python API `SBCommandInterpreter::GetTranscript()`.
Goals:
* It can be called at any time during the debugger's life cycle,
including in destroy-callbacks.
* It returns data in-memory.
Structured data:
* To make data processing easier, the return type is `SBStructuredData`.
See comments in code for how the data is organized.
* In the future, `SaveTranscript` can be updated to write different
formats using such data (e.g. JSON). This is probably accompanied by a
new setting (e.g. `interpreter.save-session-format`).
# Alternatives
The return type can also be `std::vector<std::pair<std::string,
SBCommandReturnObject>>`. This will make implementation easier, without
having to translate it to `SBStructuredData`. On the other hand,
`SBStructuredData` can convert to JSON easily, so it's more convenient
for user to process.
# Privacy
Both user commands and output/error in the transcript can contain
privacy data. However, as mentioned, the transcript is already available
to the user. The addition of the new API doesn't increase the level of
risk. In fact, it _lowers_ the risk of privacy data being leaked later
on, by avoiding writing such data to external storage.
Once the user (or their code) gets the transcript, it will be their
responsibility to make sure that any required privacy policies are
guaranteed.
# Tests
```
bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/python_api/interpreter/TestCommandInterpreterAPI.py
```
```
bin/llvm-lit -sv ../external/llvm-project/lldb/test/API/commands/session/save/TestSessionSave.py
```
---------
Co-authored-by: Roy Shi <royshi@meta.com>
Co-authored-by: Med Ismail Bennani <ismail@bennani.ma>
Diffstat (limited to 'lldb/source/Interpreter/CommandInterpreter.cpp')
-rw-r--r-- | lldb/source/Interpreter/CommandInterpreter.cpp | 43 |
1 files changed, 39 insertions, 4 deletions
diff --git a/lldb/source/Interpreter/CommandInterpreter.cpp b/lldb/source/Interpreter/CommandInterpreter.cpp index 4c58ecc..811726e 100644 --- a/lldb/source/Interpreter/CommandInterpreter.cpp +++ b/lldb/source/Interpreter/CommandInterpreter.cpp @@ -51,6 +51,7 @@ #include "lldb/Utility/Log.h" #include "lldb/Utility/State.h" #include "lldb/Utility/Stream.h" +#include "lldb/Utility/StructuredData.h" #include "lldb/Utility/Timer.h" #include "lldb/Host/Config.h" @@ -161,6 +162,17 @@ void CommandInterpreter::SetPromptOnQuit(bool enable) { SetPropertyAtIndex(idx, enable); } +bool CommandInterpreter::GetSaveTranscript() const { + const uint32_t idx = ePropertySaveTranscript; + return GetPropertyAtIndexAs<bool>( + idx, g_interpreter_properties[idx].default_uint_value != 0); +} + +void CommandInterpreter::SetSaveTranscript(bool enable) { + const uint32_t idx = ePropertySaveTranscript; + SetPropertyAtIndex(idx, enable); +} + bool CommandInterpreter::GetSaveSessionOnQuit() const { const uint32_t idx = ePropertySaveSessionOnQuit; return GetPropertyAtIndexAs<bool>( @@ -1889,7 +1901,16 @@ bool CommandInterpreter::HandleCommand(const char *command_line, else add_to_history = (lazy_add_to_history == eLazyBoolYes); - m_transcript_stream << "(lldb) " << command_line << '\n'; + // The same `transcript_item` will be used below to add output and error of + // the command. + StructuredData::DictionarySP transcript_item; + if (GetSaveTranscript()) { + m_transcript_stream << "(lldb) " << command_line << '\n'; + + transcript_item = std::make_shared<StructuredData::Dictionary>(); + transcript_item->AddStringItem("command", command_line); + m_transcript.AddItem(transcript_item); + } bool empty_command = false; bool comment_command = false; @@ -1994,7 +2015,7 @@ bool CommandInterpreter::HandleCommand(const char *command_line, // Take care of things like setting up the history command & calling the // appropriate Execute method on the CommandObject, with the appropriate // arguments. - + StatsDuration execute_time; if (cmd_obj != nullptr) { bool generate_repeat_command = add_to_history; // If we got here when empty_command was true, then this command is a @@ -2035,14 +2056,24 @@ bool CommandInterpreter::HandleCommand(const char *command_line, log, "HandleCommand, command line after removing command name(s): '%s'", remainder.c_str()); + ElapsedTime elapsed(execute_time); cmd_obj->Execute(remainder.c_str(), result); } LLDB_LOGF(log, "HandleCommand, command %s", (result.Succeeded() ? "succeeded" : "did not succeed")); - m_transcript_stream << result.GetOutputData(); - m_transcript_stream << result.GetErrorData(); + // To test whether or not transcript should be saved, `transcript_item` is + // used instead of `GetSaveTrasncript()`. This is because the latter will + // fail when the command is "settings set interpreter.save-transcript true". + if (transcript_item) { + m_transcript_stream << result.GetOutputData(); + m_transcript_stream << result.GetErrorData(); + + transcript_item->AddStringItem("output", result.GetOutputData()); + transcript_item->AddStringItem("error", result.GetErrorData()); + transcript_item->AddFloatItem("seconds", execute_time.get().count()); + } return result.Succeeded(); } @@ -3554,3 +3585,7 @@ llvm::json::Value CommandInterpreter::GetStatistics() { stats.try_emplace(command_usage.getKey(), command_usage.getValue()); return stats; } + +const StructuredData::Array &CommandInterpreter::GetTranscript() const { + return m_transcript; +} |