lldb-dap currently crashes when the first character is non ascii. This is because we assume that the request column is ascii based instead of UTF16 code units, and end up in the middle of a character code point. causing an assertion since we cannot not send invalid UTF-8 values. This also handles the case in multilines and the column is outside the range of the text. Move completion description to the `CompletionItem.detail` property.
316 lines
10 KiB
C++
316 lines
10 KiB
C++
//===-- LLDBUtils.cpp -------------------------------------------*- C++ -*-===//
|
|
//
|
|
// 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 "LLDBUtils.h"
|
|
#include "DAPError.h"
|
|
#include "JSONUtils.h"
|
|
#include "lldb/API/SBCommandInterpreter.h"
|
|
#include "lldb/API/SBCommandReturnObject.h"
|
|
#include "lldb/API/SBDebugger.h"
|
|
#include "lldb/API/SBFrame.h"
|
|
#include "lldb/API/SBStringList.h"
|
|
#include "lldb/API/SBStructuredData.h"
|
|
#include "lldb/API/SBThread.h"
|
|
#include "lldb/lldb-defines.h"
|
|
#include "lldb/lldb-enumerations.h"
|
|
#include "llvm/ADT/ArrayRef.h"
|
|
#include "llvm/Support/ConvertUTF.h"
|
|
#include "llvm/Support/Error.h"
|
|
#include "llvm/Support/JSON.h"
|
|
#include "llvm/Support/raw_ostream.h"
|
|
|
|
#include <cstdint>
|
|
#include <cstring>
|
|
#include <mutex>
|
|
#include <system_error>
|
|
|
|
namespace lldb_dap {
|
|
|
|
bool RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix,
|
|
const llvm::ArrayRef<std::string> &commands,
|
|
llvm::raw_ostream &strm, bool parse_command_directives,
|
|
bool echo_commands) {
|
|
if (commands.empty())
|
|
return true;
|
|
|
|
bool did_print_prefix = false;
|
|
|
|
// We only need the prompt when echoing commands.
|
|
std::string prompt_string;
|
|
if (echo_commands) {
|
|
prompt_string = "(lldb) ";
|
|
|
|
// Get the current prompt from settings.
|
|
if (const lldb::SBStructuredData prompt = debugger.GetSetting("prompt")) {
|
|
const size_t prompt_length = prompt.GetStringValue(nullptr, 0);
|
|
|
|
if (prompt_length != 0) {
|
|
prompt_string.resize(prompt_length + 1);
|
|
prompt.GetStringValue(prompt_string.data(), prompt_string.length());
|
|
}
|
|
}
|
|
}
|
|
|
|
lldb::SBCommandInterpreter interp = debugger.GetCommandInterpreter();
|
|
for (llvm::StringRef command : commands) {
|
|
lldb::SBCommandReturnObject result;
|
|
bool quiet_on_success = false;
|
|
bool check_error = false;
|
|
|
|
while (parse_command_directives) {
|
|
if (command.starts_with("?")) {
|
|
command = command.drop_front();
|
|
quiet_on_success = true;
|
|
} else if (command.starts_with("!")) {
|
|
command = command.drop_front();
|
|
check_error = true;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
{
|
|
// Prevent simultaneous calls to HandleCommand, e.g. EventThreadFunction
|
|
// may asynchronously call RunExitCommands when we are already calling
|
|
// RunTerminateCommands.
|
|
static std::mutex handle_command_mutex;
|
|
std::lock_guard<std::mutex> locker(handle_command_mutex);
|
|
interp.HandleCommand(command.str().c_str(), result,
|
|
/*add_to_history=*/true);
|
|
}
|
|
|
|
const bool got_error = !result.Succeeded();
|
|
// The if statement below is assuming we always print out `!` prefixed
|
|
// lines. The only time we don't print is when we have `quiet_on_success ==
|
|
// true` and we don't have an error.
|
|
if (quiet_on_success ? got_error : true) {
|
|
if (!did_print_prefix && !prefix.empty()) {
|
|
strm << prefix << "\n";
|
|
did_print_prefix = true;
|
|
}
|
|
|
|
if (echo_commands)
|
|
strm << prompt_string.c_str() << command << '\n';
|
|
|
|
auto output_len = result.GetOutputSize();
|
|
if (output_len) {
|
|
const char *output = result.GetOutput();
|
|
strm << output;
|
|
}
|
|
auto error_len = result.GetErrorSize();
|
|
if (error_len) {
|
|
const char *error = result.GetError();
|
|
strm << error;
|
|
}
|
|
}
|
|
if (check_error && got_error)
|
|
return false; // Stop running commands.
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string RunLLDBCommands(lldb::SBDebugger &debugger, llvm::StringRef prefix,
|
|
const llvm::ArrayRef<std::string> &commands,
|
|
bool &required_command_failed,
|
|
bool parse_command_directives, bool echo_commands) {
|
|
required_command_failed = false;
|
|
std::string s;
|
|
llvm::raw_string_ostream strm(s);
|
|
required_command_failed =
|
|
!RunLLDBCommands(debugger, prefix, commands, strm,
|
|
parse_command_directives, echo_commands);
|
|
return s;
|
|
}
|
|
|
|
bool ThreadHasStopReason(lldb::SBThread &thread) {
|
|
switch (thread.GetStopReason()) {
|
|
case lldb::eStopReasonTrace:
|
|
case lldb::eStopReasonPlanComplete:
|
|
case lldb::eStopReasonWatchpoint:
|
|
case lldb::eStopReasonInstrumentation:
|
|
case lldb::eStopReasonSignal:
|
|
case lldb::eStopReasonException:
|
|
case lldb::eStopReasonExec:
|
|
case lldb::eStopReasonProcessorTrace:
|
|
case lldb::eStopReasonFork:
|
|
case lldb::eStopReasonVFork:
|
|
case lldb::eStopReasonVForkDone:
|
|
case lldb::eStopReasonInterrupt:
|
|
case lldb::eStopReasonHistoryBoundary:
|
|
return true;
|
|
case lldb::eStopReasonBreakpoint: {
|
|
// Stop reason data for breakpoints consists of breakpoint ID and location
|
|
// ID pairs. Internal breakpoints (identified by their ID) are not
|
|
// considered valid stop reasons.
|
|
const uint64_t data_count = thread.GetStopReasonDataCount();
|
|
if (data_count == 0)
|
|
return true;
|
|
for (uint64_t i = 0; i < data_count; i += 2) {
|
|
const lldb::break_id_t bp_id = thread.GetStopReasonDataAtIndex(i);
|
|
if (!LLDB_BREAK_ID_IS_INTERNAL(bp_id))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
case lldb::eStopReasonThreadExiting:
|
|
case lldb::eStopReasonInvalid:
|
|
case lldb::eStopReasonNone:
|
|
break;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static uint32_t constexpr THREAD_INDEX_SHIFT = 19;
|
|
|
|
uint32_t GetLLDBThreadIndexID(uint64_t dap_frame_id) {
|
|
return dap_frame_id >> THREAD_INDEX_SHIFT;
|
|
}
|
|
|
|
uint32_t GetLLDBFrameID(uint64_t dap_frame_id) {
|
|
return dap_frame_id & ((1u << THREAD_INDEX_SHIFT) - 1);
|
|
}
|
|
|
|
uint64_t MakeDAPFrameID(lldb::SBFrame &frame) {
|
|
return ((uint64_t)frame.GetThread().GetIndexID() << THREAD_INDEX_SHIFT) |
|
|
frame.GetFrameID();
|
|
}
|
|
|
|
lldb::SBEnvironment
|
|
GetEnvironmentFromArguments(const llvm::json::Object &arguments) {
|
|
lldb::SBEnvironment envs{};
|
|
constexpr llvm::StringRef env_key = "env";
|
|
const llvm::json::Value *raw_json_env = arguments.get(env_key);
|
|
|
|
if (!raw_json_env)
|
|
return envs;
|
|
|
|
if (raw_json_env->kind() == llvm::json::Value::Object) {
|
|
auto env_map = GetStringMap(arguments, env_key);
|
|
for (const auto &[key, value] : env_map)
|
|
envs.Set(key.c_str(), value.c_str(), true);
|
|
|
|
} else if (raw_json_env->kind() == llvm::json::Value::Array) {
|
|
const auto envs_strings = GetStrings(&arguments, env_key);
|
|
lldb::SBStringList entries{};
|
|
for (const auto &env : envs_strings)
|
|
entries.AppendString(env.c_str());
|
|
|
|
envs.SetEntries(entries, true);
|
|
}
|
|
return envs;
|
|
}
|
|
|
|
lldb::StopDisassemblyType
|
|
GetStopDisassemblyDisplay(lldb::SBDebugger &debugger) {
|
|
lldb::StopDisassemblyType result =
|
|
lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo;
|
|
lldb::SBStructuredData string_result =
|
|
debugger.GetSetting("stop-disassembly-display");
|
|
const size_t result_length = string_result.GetStringValue(nullptr, 0);
|
|
if (result_length > 0) {
|
|
std::string result_string(result_length, '\0');
|
|
string_result.GetStringValue(result_string.data(), result_length + 1);
|
|
|
|
result =
|
|
llvm::StringSwitch<lldb::StopDisassemblyType>(result_string)
|
|
.Case("never", lldb::StopDisassemblyType::eStopDisassemblyTypeNever)
|
|
.Case("always",
|
|
lldb::StopDisassemblyType::eStopDisassemblyTypeAlways)
|
|
.Case("no-source",
|
|
lldb::StopDisassemblyType::eStopDisassemblyTypeNoSource)
|
|
.Case("no-debuginfo",
|
|
lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo)
|
|
.Default(
|
|
lldb::StopDisassemblyType::eStopDisassemblyTypeNoDebugInfo);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
llvm::Error ToError(const lldb::SBError &error, bool show_user) {
|
|
if (error.Success())
|
|
return llvm::Error::success();
|
|
|
|
return llvm::make_error<DAPError>(
|
|
/*message=*/error.GetCString(),
|
|
/*EC=*/std::error_code(error.GetError(), std::generic_category()),
|
|
/*show_user=*/show_user);
|
|
}
|
|
|
|
std::string GetStringValue(const lldb::SBStructuredData &data) {
|
|
if (!data.IsValid())
|
|
return "";
|
|
|
|
const size_t str_length = data.GetStringValue(nullptr, 0);
|
|
if (!str_length)
|
|
return "";
|
|
|
|
std::string str(str_length, 0);
|
|
data.GetStringValue(str.data(), str_length + 1);
|
|
return str;
|
|
}
|
|
|
|
ScopeSyncMode::ScopeSyncMode(lldb::SBDebugger &debugger)
|
|
: m_debugger(debugger), m_async(m_debugger.GetAsync()) {
|
|
m_debugger.SetAsync(false);
|
|
}
|
|
|
|
ScopeSyncMode::~ScopeSyncMode() { m_debugger.SetAsync(m_async); }
|
|
|
|
std::string GetSBFileSpecPath(const lldb::SBFileSpec &file_spec) {
|
|
const auto directory_length = ::strlen(file_spec.GetDirectory());
|
|
const auto file_name_length = ::strlen(file_spec.GetFilename());
|
|
|
|
std::string path(directory_length + file_name_length + 1, '\0');
|
|
file_spec.GetPath(path.data(), path.length() + 1);
|
|
return path;
|
|
}
|
|
|
|
lldb::SBLineEntry GetLineEntryForAddress(lldb::SBTarget &target,
|
|
const lldb::SBAddress &address) {
|
|
lldb::SBSymbolContext sc = target.ResolveSymbolContextForAddress(
|
|
address, lldb::eSymbolContextLineEntry);
|
|
return sc.GetLineEntry();
|
|
}
|
|
|
|
std::optional<size_t> UTF16CodeunitToBytes(llvm::StringRef line,
|
|
uint32_t utf16_codeunits) {
|
|
size_t bytes_count = 0;
|
|
size_t utf16_seen_cu = 0;
|
|
size_t idx = 0;
|
|
const size_t line_size = line.size();
|
|
|
|
while (idx < line_size && utf16_seen_cu < utf16_codeunits) {
|
|
const char first_char = line[idx];
|
|
const auto num_bytes = llvm::getNumBytesForUTF8(first_char);
|
|
|
|
if (num_bytes == 4) {
|
|
utf16_seen_cu += 2;
|
|
} else if (num_bytes < 4) {
|
|
utf16_seen_cu += 1;
|
|
} else {
|
|
// getNumBytesForUTF8 may return bytes greater than 4 this is not valid
|
|
// UTF8
|
|
return std::nullopt;
|
|
}
|
|
|
|
idx += num_bytes;
|
|
if (utf16_seen_cu <= utf16_codeunits) {
|
|
bytes_count = idx;
|
|
} else {
|
|
// We are in the middle of a codepoint or the utf16_codeunits ends in the
|
|
// middle of a codepoint.
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
|
|
return bytes_count;
|
|
}
|
|
|
|
} // namespace lldb_dap
|