Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion lldb/test/API/tools/lldb-dap/coreFile/TestDAP_coreFile.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def test_core_file(self):
self.assertEqual(self.get_stackFrames(), expected_frames)

@skipIfLLVMTargetMissing("X86")
def test_core_file_source_mapping(self):
def test_core_file_source_mapping_array(self):
"""Test that sourceMap property is correctly applied when loading a core"""
current_dir = os.path.dirname(__file__)
exe_file = os.path.join(current_dir, "linux-x86_64.out")
Expand All @@ -70,3 +70,17 @@ def test_core_file_source_mapping(self):
self.attach(exe_file, coreFile=core_file, sourceMap=source_map)

self.assertIn(current_dir, self.get_stackFrames()[0]["source"]["path"])

@skipIfLLVMTargetMissing("X86")
def test_core_file_source_mapping_object(self):
"""Test that sourceMap property is correctly applied when loading a core"""
current_dir = os.path.dirname(__file__)
exe_file = os.path.join(current_dir, "linux-x86_64.out")
core_file = os.path.join(current_dir, "linux-x86_64.core")

self.create_debug_adaptor()

source_map = {"/home/labath/test": current_dir}
self.attach(exe_file, coreFile=core_file, sourceMap=source_map)

self.assertIn(current_dir, self.get_stackFrames()[0]["source"]["path"])
37 changes: 36 additions & 1 deletion lldb/test/API/tools/lldb-dap/launch/TestDAP_launch.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,12 +224,47 @@ def test_args(self):
'arg[%i] "%s" not in "%s"' % (i + 1, quoted_arg, lines[i]),
)

def test_environment(self):
def test_environment_with_object(self):
"""
Tests launch of a simple program with environment variables
"""
program = self.getBuildArtifact("a.out")
env = {
"NO_VALUE": "",
"WITH_VALUE": "BAR",
"EMPTY_VALUE": "",
"SPACE": "Hello World",
}

self.build_and_launch(program, env=env)
self.continue_to_exit()

# Now get the STDOUT and verify our arguments got passed correctly
output = self.get_stdout()
self.assertTrue(output and len(output) > 0, "expect program output")
lines = output.splitlines()
# Skip the all arguments so we have only environment vars left
while len(lines) and lines[0].startswith("arg["):
lines.pop(0)
# Make sure each environment variable in "env" is actually set in the
# program environment that was printed to STDOUT
for var in env:
found = False
for program_var in lines:
if var in program_var:
found = True
break
self.assertTrue(
found, '"%s" must exist in program environment (%s)' % (var, lines)
)

def test_environment_with_array(self):
"""
Tests launch of a simple program with environment variables
"""
program = self.getBuildArtifact("a.out")
env = ["NO_VALUE", "WITH_VALUE=BAR", "EMPTY_VALUE=", "SPACE=Hello World"]

self.build_and_launch(program, env=env)
self.continue_to_exit()

Expand Down
40 changes: 34 additions & 6 deletions lldb/tools/lldb-dap/JSONUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,31 @@ std::vector<std::string> GetStrings(const llvm::json::Object *obj,
return strs;
}

std::unordered_map<std::string, std::string>
GetStringMap(const llvm::json::Object &obj, llvm::StringRef key) {
std::unordered_map<std::string, std::string> strs;
const auto *const json_object = obj.getObject(key);
if (!json_object)
return strs;

for (const auto &[key, value] : *json_object) {
switch (value.kind()) {
case llvm::json::Value::String:
strs.emplace(key.str(), value.getAsString()->str());
break;
case llvm::json::Value::Number:
case llvm::json::Value::Boolean:
strs.emplace(key.str(), llvm::to_string(value));
break;
case llvm::json::Value::Null:
case llvm::json::Value::Object:
case llvm::json::Value::Array:
break;
}
}
return strs;
}

static bool IsClassStructOrUnionType(lldb::SBType t) {
return (t.GetTypeClass() & (lldb::eTypeClassUnion | lldb::eTypeClassStruct |
lldb::eTypeClassArray)) != 0;
Expand Down Expand Up @@ -1370,13 +1395,16 @@ CreateRunInTerminalReverseRequest(const llvm::json::Object &launch_request,
if (!cwd.empty())
run_in_terminal_args.try_emplace("cwd", cwd);

// We need to convert the input list of environments variables into a
// dictionary
std::vector<std::string> envs = GetStrings(launch_request_arguments, "env");
std::unordered_map<std::string, std::string> envMap =
GetStringMap(*launch_request_arguments, "env");
llvm::json::Object environment;
for (const std::string &env : envs) {
size_t index = env.find('=');
environment.try_emplace(env.substr(0, index), env.substr(index + 1));
for (const auto &[key, value] : envMap) {
if (key.empty())
g_dap.SendOutput(OutputType::Stderr,
"empty environment variable for value: \"" + value +
'\"');
else
environment.try_emplace(key, value);
}
run_in_terminal_args.try_emplace("env",
llvm::json::Value(std::move(environment)));
Expand Down
22 changes: 22 additions & 0 deletions lldb/tools/lldb-dap/JSONUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "llvm/Support/JSON.h"
#include <cstdint>
#include <optional>
#include <unordered_map>

namespace lldb_dap {

Expand Down Expand Up @@ -152,6 +153,27 @@ bool ObjectContainsKey(const llvm::json::Object &obj, llvm::StringRef key);
std::vector<std::string> GetStrings(const llvm::json::Object *obj,
llvm::StringRef key);

/// Extract an object of key value strings for the specified key from an object.
///
/// String values in the object will be extracted without any quotes
/// around them. Numbers and Booleans will be converted into
/// strings. Any NULL, array or objects values in the array will be
/// ignored.
///
/// \param[in] obj
/// A JSON object that we will attempt to extract the array from
///
/// \param[in] key
/// The key to use when extracting the value
///
/// \return
/// An object of key value strings for the specified \a key, or
/// \a fail_value if there is no key that matches or if the
/// value is not an object or key and values in the object are not
/// strings, numbers or booleans.
std::unordered_map<std::string, std::string>
GetStringMap(const llvm::json::Object &obj, llvm::StringRef key);

/// Fill a response object given the request object.
///
/// The \a response object will get its "type" set to "response",
Expand Down
5 changes: 4 additions & 1 deletion lldb/tools/lldb-dap/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,10 @@ adds `FOO=1` and `bar` to the environment:
"name": "Debug",
"program": "/tmp/a.out",
"args": [ "one", "two", "three" ],
"env": [ "FOO=1", "BAR" ],
"env": {
"FOO": "1"
"BAR": ""
}
}
```

Expand Down
56 changes: 45 additions & 11 deletions lldb/tools/lldb-dap/lldb-dap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
#include <thread>
#include <vector>

#include "lldb/API/SBEnvironment.h"
#include "lldb/API/SBStream.h"
#include "lldb/Host/Config.h"
#include "llvm/ADT/ArrayRef.h"
Expand Down Expand Up @@ -184,6 +185,33 @@ std::vector<const char *> MakeArgv(const llvm::ArrayRef<std::string> &strs) {
return argv;
}

// Gets all the environment variables from the json object depending on if the
// kind is an object or an array.
lldb::SBEnvironment
GetEnvironmentFromArguments(const llvm::json::Object &arguments) {
lldb::SBEnvironment envs{};
constexpr llvm::StringRef env_key = "env";
const auto *env_type = arguments.get(env_key);

if (!env_type)
return envs;

if (env_type->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 (env_type->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;
}

// Send a "exited" event to indicate the process has exited.
void SendProcessExitedEvent(lldb::SBProcess &process) {
llvm::json::Object event(CreateEventObject("exited"));
Expand Down Expand Up @@ -605,25 +633,32 @@ void SetSourceMapFromArguments(const llvm::json::Object &arguments) {
std::string sourceMapCommand;
llvm::raw_string_ostream strm(sourceMapCommand);
strm << "settings set target.source-map ";
auto sourcePath = GetString(arguments, "sourcePath");
const auto sourcePath = GetString(arguments, "sourcePath");

// sourceMap is the new, more general form of sourcePath and overrides it.
auto sourceMap = arguments.getArray("sourceMap");
if (sourceMap) {
for (const auto &value : *sourceMap) {
auto mapping = value.getAsArray();
constexpr llvm::StringRef sourceMapKey = "sourceMap";

if (const auto *sourceMapArray = arguments.getArray(sourceMapKey)) {
for (const auto &value : *sourceMapArray) {
const auto *mapping = value.getAsArray();
if (mapping == nullptr || mapping->size() != 2 ||
(*mapping)[0].kind() != llvm::json::Value::String ||
(*mapping)[1].kind() != llvm::json::Value::String) {
g_dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp));
return;
}
auto mapFrom = GetAsString((*mapping)[0]);
auto mapTo = GetAsString((*mapping)[1]);
const auto mapFrom = GetAsString((*mapping)[0]);
const auto mapTo = GetAsString((*mapping)[1]);
strm << "\"" << mapFrom << "\" \"" << mapTo << "\" ";
}
} else if (const auto *sourceMapObj = arguments.getObject(sourceMapKey)) {
for (const auto &[key, value] : *sourceMapObj) {
if (value.kind() == llvm::json::Value::String) {
strm << "\"" << key.str() << "\" \"" << GetAsString(value) << "\" ";
}
}
} else {
if (ObjectContainsKey(arguments, "sourceMap")) {
if (ObjectContainsKey(arguments, sourceMapKey)) {
g_dap.SendOutput(OutputType::Console, llvm::StringRef(sourceMapHelp));
return;
}
Expand Down Expand Up @@ -1831,9 +1866,8 @@ lldb::SBError LaunchProcess(const llvm::json::Object &request) {
launch_info.SetArguments(MakeArgv(args).data(), true);

// Pass any environment variables along that the user specified.
auto envs = GetStrings(arguments, "env");
if (!envs.empty())
launch_info.SetEnvironmentEntries(MakeArgv(envs).data(), true);
const auto envs = GetEnvironmentFromArguments(*arguments);
launch_info.SetEnvironment(envs, true);

auto flags = launch_info.GetLaunchFlags();

Expand Down
81 changes: 71 additions & 10 deletions lldb/tools/lldb-dap/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,26 @@
"default": "${workspaceRoot}"
},
"env": {
"type": "array",
"description": "Additional environment variables to set when launching the program. This is an array of strings that contains the variable name followed by an optional '=' character and the environment variable's value.",
"default": []
"anyOf": [
{
"type": "object",
"description": "Additional environment variables to set when launching the program. E.g. `{ \"FOO\": \"1\" }`",
"patternProperties": {
".*": {
"type": "string"
}
},
"default": {}
},
{
"type": "array",
"description": "Additional environment variables to set when launching the program. E.g. `[\"FOO=1\", \"BAR\"]`",
"items": {
"type": "string"
},
"default": {}
}
]
},
"stopOnEntry": {
"type": "boolean",
Expand Down Expand Up @@ -194,9 +211,31 @@
"description": "Specify a source path to remap \"./\" to allow full paths to be used when setting breakpoints in binaries that have relative source paths."
},
"sourceMap": {
"type": "array",
"description": "Specify an array of path remappings; each element must itself be a two element array containing a source and destination path name. Overrides sourcePath.",
"default": []
"anyOf": [
{
"type": "object",
"description": "Specify an object of path remappings; each entry has a key containing the source path and a value containing the destination path. E.g `{ \"/the/source/path\": \"/the/destination/path\" }`. Overrides sourcePath.",
"patternProperties": {
".*": {
"type": "string"
}
},
"default": {}
},
{
"type": "array",
"description": "Specify an array of path remappings; each element must itself be a two element array containing a source and destination path name. Overrides sourcePath.",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "string"
}
},
"default": []
}
]
},
"debuggerRoot": {
"type": "string",
Expand Down Expand Up @@ -299,9 +338,31 @@
"description": "Specify a source path to remap \"./\" to allow full paths to be used when setting breakpoints in binaries that have relative source paths."
},
"sourceMap": {
"type": "array",
"description": "Specify an array of path remappings; each element must itself be a two element array containing a source and destination path name. Overrides sourcePath.",
"default": []
"anyOf": [
{
"type": "object",
"description": "Specify an object of path remappings; each entry has a key containing the source path and a value containing the destination path. E.g `{ \"/the/source/path\": \"/the/destination/path\" }`. Overrides sourcePath.",
"patternProperties": {
".*": {
"type": "string"
}
},
"default": {}
},
{
"type": "array",
"description": "Specify an array of path remappings; each element must itself be a two element array containing a source and destination path name. Overrides sourcePath.",
"items": {
"type": "array",
"minItems": 2,
"maxItems": 2,
"items": {
"type": "string"
}
},
"default": []
}
]
},
"debuggerRoot": {
"type": "string",
Expand Down Expand Up @@ -421,4 +482,4 @@
}
]
}
}
}