diff --git a/CMakeLists.txt b/CMakeLists.txt index 6213b5b6..9a649784 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -180,6 +180,7 @@ else() configure_native_kernel("/share/jupyter/kernels/xcpp17/") configure_native_kernel("/share/jupyter/kernels/xcpp20/") configure_native_kernel("/share/jupyter/kernels/xcpp23/") + configure_native_kernel("/share/jupyter/kernels/xcpp17-debugger/") endif() # Source files @@ -196,6 +197,7 @@ set(XEUS_CPP_HEADERS include/xeus-cpp/xmagics.hpp include/xeus-cpp/xoptions.hpp include/xeus-cpp/xpreamble.hpp + include/xeus-cpp/xdebugger.hpp #src/xinspect.hpp #src/xsystem.hpp #src/xparser.hpp @@ -210,6 +212,9 @@ set(XEUS_CPP_SRC src/xparser.cpp src/xutils.cpp src/xmagics/os.cpp + src/xdebugger.cpp + src/xdebuglldb_client.cpp + src/xinternal_utils.cpp ) if(NOT EMSCRIPTEN) diff --git a/include/xeus-cpp/xdebugger.hpp b/include/xeus-cpp/xdebugger.hpp new file mode 100644 index 00000000..4408c46f --- /dev/null +++ b/include/xeus-cpp/xdebugger.hpp @@ -0,0 +1,105 @@ +/************************************************************************************ + * Copyright (c) 2023, xeus-cpp contributors * + * Copyright (c) 2023, Johan Mabille, Loic Gouarin, Sylvain Corlay, Wolf Vollprecht * + * * + * Distributed under the terms of the BSD 3-Clause License. * + * * + * The full license is in the file LICENSE, distributed with this software. * + ************************************************************************************/ + +#ifndef XEUS_CPP_DEBUGGER_HPP +#define XEUS_CPP_DEBUGGER_HPP + +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wattributes" +#endif + +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "xeus-zmq/xdebugger_base.hpp" +#include "xeus_cpp_config.hpp" +#include "xeus-cpp/xinterpreter.hpp" + +namespace xcpp +{ + class xdebuglldb_client; + + class XEUS_CPP_API debugger : public xeus::xdebugger_base + { + public: + + using base_type = xeus::xdebugger_base; + + debugger(xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config); + + virtual ~debugger(); + + private: + + nl::json inspect_variables_request(const nl::json& message); + nl::json stack_trace_request(const nl::json& message); + nl::json attach_request(const nl::json& message); + nl::json configuration_done_request(const nl::json& message); + nl::json set_breakpoints_request(const nl::json& message); + nl::json dump_cell_request(const nl::json& message); + nl::json rich_inspect_variables_request(const nl::json& message); + nl::json copy_to_globals_request(const nl::json& message); + nl::json source_request(const nl::json& message); + + bool get_variable_info_from_lldb(const std::string& var_name, int frame_id, nl::json& data, nl::json& metadata, int sequence); + void get_container_info(const std::string& var_name, int frame_id, + const std::string& var_type, nl::json& data, nl::json& metadata, int sequence, int size); + bool is_container_type(const std::string& type) const; + + bool start_lldb(); + bool start() override; + void stop() override; + xeus::xdebugger_info get_debugger_info() const override; + virtual std::string get_cell_temporary_file(const std::string& code) const override; + + bool connect_to_lldb_tcp(); + std::string send_dap_message(const nl::json& message); + std::string receive_dap_response(); + + xdebuglldb_client* p_debuglldb_client; + std::string m_lldb_host; + std::string m_lldb_port; + nl::json m_debugger_config; + bool m_is_running; + int m_tcp_socket; + bool m_tcp_connected; + pid_t jit_process_pid; + + using breakpoint_list_t = std::map>; + breakpoint_list_t m_breakpoint_list; + + xcpp::interpreter* m_interpreter; + + std::unordered_map> m_hash_to_sequential; + std::unordered_map m_sequential_to_hash; + + bool m_copy_to_globals_available; + }; + + XEUS_CPP_API + std::unique_ptr make_cpp_debugger(xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config); +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif + +#endif \ No newline at end of file diff --git a/include/xeus-cpp/xinterpreter.hpp b/include/xeus-cpp/xinterpreter.hpp index 25eeb049..7fe49ac3 100644 --- a/include/xeus-cpp/xinterpreter.hpp +++ b/include/xeus-cpp/xinterpreter.hpp @@ -25,6 +25,8 @@ #include "xbuffer.hpp" #include "xeus_cpp_config.hpp" #include "xmanager.hpp" +#include + namespace nl = nlohmann; @@ -40,6 +42,28 @@ namespace xcpp void publish_stdout(const std::string&); void publish_stderr(const std::string&); + static pid_t get_current_pid(); + + std::vector get_execution_count(const std::string& code) const + { + auto it = m_code_to_execution_count_map.find(code); + if (it != m_code_to_execution_count_map.end()) + { + return it->second; + } + return {}; + } + + std::string get_code_from_execution_count(int execution_count) const + { + auto it = m_execution_count_to_code_map.find(execution_count); + if(it != m_execution_count_to_code_map.end()) + { + return it->second; + } + return ""; + } + private: void configure_impl() override; @@ -85,6 +109,9 @@ namespace xcpp xoutput_buffer m_cout_buffer; xoutput_buffer m_cerr_buffer; + + std::map> m_code_to_execution_count_map; + std::map m_execution_count_to_code_map; }; } diff --git a/share/jupyter/kernels/xcpp17-debugger/kernel.json.in b/share/jupyter/kernels/xcpp17-debugger/kernel.json.in new file mode 100644 index 00000000..a1e96cb4 --- /dev/null +++ b/share/jupyter/kernels/xcpp17-debugger/kernel.json.in @@ -0,0 +1,21 @@ +{ + "display_name": "C++17-Debugger", + "env": { + "PATH":"@XEUS_CPP_PATH@", + "LD_LIBRARY_PATH":"@XEUS_CPP_LD_LIBRARY_PATH@" + }, + "argv": [ + "@XEUS_CPP_KERNELSPEC_PATH@xcpp", + "-f", + "{connection_file}", + "-g", + "-O0", + "--use-oop-jit", + "-resource-dir", "@XEUS_CPP_RESOURCE_DIR@", + "-I", "@XEUS_CPP_INCLUDE_DIR@", + "-std=c++17" + ], + "language": "cpp", + "metadata": {"debugger": true + } +} diff --git a/share/jupyter/kernels/xcpp17-debugger/logo-32x32.png b/share/jupyter/kernels/xcpp17-debugger/logo-32x32.png new file mode 100644 index 00000000..c09c4585 Binary files /dev/null and b/share/jupyter/kernels/xcpp17-debugger/logo-32x32.png differ diff --git a/share/jupyter/kernels/xcpp17-debugger/logo-64x64.png b/share/jupyter/kernels/xcpp17-debugger/logo-64x64.png new file mode 100644 index 00000000..396c2446 Binary files /dev/null and b/share/jupyter/kernels/xcpp17-debugger/logo-64x64.png differ diff --git a/share/jupyter/kernels/xcpp17-debugger/logo-svg.svg b/share/jupyter/kernels/xcpp17-debugger/logo-svg.svg new file mode 100644 index 00000000..5e117077 --- /dev/null +++ b/share/jupyter/kernels/xcpp17-debugger/logo-svg.svg @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + diff --git a/share/jupyter/kernels/xcpp17-omp/kernel.json.in b/share/jupyter/kernels/xcpp17-omp/kernel.json.in index f74379f1..6a3443b8 100644 --- a/share/jupyter/kernels/xcpp17-omp/kernel.json.in +++ b/share/jupyter/kernels/xcpp17-omp/kernel.json.in @@ -13,6 +13,6 @@ "-std=c++17"@XEUS_CPP_OMP@ ], "language": "cpp", - "metadata": {"debugger": false + "metadata": {"debugger": true } } diff --git a/share/jupyter/kernels/xcpp17/wasm_kernel.json.in b/share/jupyter/kernels/xcpp17/wasm_kernel.json.in index fc2a3d61..d08bff0c 100644 --- a/share/jupyter/kernels/xcpp17/wasm_kernel.json.in +++ b/share/jupyter/kernels/xcpp17/wasm_kernel.json.in @@ -8,7 +8,7 @@ ], "language": "cpp", "metadata": { - "debugger": false, + "debugger": true, "shared": { "libxeus.so": "lib/libxeus.so", "libclangCppInterOp.so": "lib/libclangCppInterOp.so" diff --git a/src/main.cpp b/src/main.cpp index 00d52d28..1a236dc1 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -19,16 +19,19 @@ #include #endif -#include "xeus/xhelper.hpp" +#include "nlohmann/json.hpp" +#include #include #include - #include "xeus-zmq/xzmq_context.hpp" #include - +#include "xeus-zmq/xserver_zmq_split.hpp" #include "xeus-cpp/xeus_cpp_config.hpp" #include "xeus-cpp/xinterpreter.hpp" #include "xeus-cpp/xutils.hpp" +#include "xeus-cpp/xdebugger.hpp" + +namespace nl = nlohmann; int main(int argc, char* argv[]) { @@ -62,6 +65,12 @@ int main(int argc, char* argv[]) auto interpreter = std::make_unique(argc, argv); std::unique_ptr context = xeus::make_zmq_context(); + nl::json debugger_config; + debugger_config["lldb"]["initCommands"] = { + "settings set plugin.jit-loader.gdb.enable on" + }; + debugger_config["interpreter_ptr"] = reinterpret_cast(interpreter.get()); + if (!file_name.empty()) { xeus::xconfiguration config = xeus::load_configuration(file_name); @@ -72,18 +81,19 @@ int main(int argc, char* argv[]) xeus::get_user_name(), std::move(context), std::move(interpreter), - xeus::make_xserver_default, + xeus::make_xserver_control_main, xeus::make_in_memory_history_manager(), xeus::make_console_logger( xeus::xlogger::msg_type, xeus::make_file_logger(xeus::xlogger::content, "xeus.log") - ) + ), + xcpp::make_cpp_debugger, + debugger_config ); std::clog << "Starting xcpp kernel...\n\n" "If you want to connect to this kernel from an other client, you can use" - " the " - + file_name + " file." + " the " + file_name + " file." << std::endl; kernel.start(); @@ -94,12 +104,14 @@ int main(int argc, char* argv[]) xeus::get_user_name(), std::move(context), std::move(interpreter), - xeus::make_xserver_default, + xeus::make_xserver_control_main, xeus::make_in_memory_history_manager(), xeus::make_console_logger( xeus::xlogger::msg_type, xeus::make_file_logger(xeus::xlogger::content, "xeus.log") - ) + ), + xcpp::make_cpp_debugger, + debugger_config ); std::cout << "Getting config" << std::endl; @@ -109,37 +121,20 @@ int main(int argc, char* argv[]) " and paste the following content inside of a `kernel.json` file. And then run for example:\n\n" "# jupyter console --existing kernel.json\n\n" "kernel.json\n```\n{\n" - " \"transport\": \"" - + config.m_transport - + "\",\n" - " \"ip\": \"" - + config.m_ip - + "\",\n" - " \"control_port\": " - + config.m_control_port - + ",\n" - " \"shell_port\": " - + config.m_shell_port - + ",\n" - " \"stdin_port\": " - + config.m_stdin_port - + ",\n" - " \"iopub_port\": " - + config.m_iopub_port - + ",\n" - " \"hb_port\": " - + config.m_hb_port - + ",\n" - " \"signature_scheme\": \"" - + config.m_signature_scheme - + "\",\n" - " \"key\": \"" - + config.m_key - + "\"\n" - "}\n```\n"; + " \"transport\": \"" + config.m_transport + "\",\n" + " \"ip\": \"" + config.m_ip + "\",\n" + " \"control_port\": " + config.m_control_port + ",\n" + " \"shell_port\": " + config.m_shell_port + ",\n" + " \"stdin_port\": " + config.m_stdin_port + ",\n" + " \"iopub_port\": " + config.m_iopub_port + ",\n" + " \"hb_port\": " + config.m_hb_port + ",\n" + " \"signature_scheme\": \"" + config.m_signature_scheme + "\",\n" + " \"key\": \"" + config.m_key + "\"\n" + "}\n```" + << std::endl; kernel.start(); } return 0; -} +} \ No newline at end of file diff --git a/src/xdebugger.cpp b/src/xdebugger.cpp new file mode 100644 index 00000000..89000276 --- /dev/null +++ b/src/xdebugger.cpp @@ -0,0 +1,813 @@ +#include "xeus-cpp/xdebugger.hpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "xdebuglldb_client.hpp" +#include "xeus-cpp/xinterpreter.hpp" +#include "xeus-zmq/xmiddleware.hpp" +#include "xeus/xsystem.hpp" +#include "xinternal_utils.hpp" + +using namespace std::placeholders; + +// ***** ONLY FOR DEBUGGING PURPOSES. NOT TO BE COMMITTED ***** +static std::ofstream log_stream("/Users/abhinavkumar/Desktop/Coding/CERN_HSF_COMPILER_RESEARCH/xeus-cpp/build/xeus-cpp-logs.log", std::ios::app); + +void log_debug(const std::string& msg) { + log_stream << "[xeus-cpp] " << msg << std::endl; + log_stream.flush(); // Ensure immediate write +} +// ******* (END) ONLY FOR DEBUGGING PURPOSES. NOT TO BE COMMITTED ******* + +namespace xcpp +{ + debugger::debugger( + xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config + ) + : xdebugger_base(context) + , p_debuglldb_client(new xdebuglldb_client( + context, + config, + xeus::get_socket_linger(), + xdap_tcp_configuration(xeus::dap_tcp_type::client, xeus::dap_init_type::parallel, user_name, session_id), + get_event_callback() + )) + , m_lldb_host("127.0.0.1") + , m_lldb_port("") + , m_debugger_config(debugger_config) + { + // Register request handlers + register_request_handler( + "inspectVariables", + std::bind(&debugger::inspect_variables_request, this, _1), + false + ); + register_request_handler("attach", std::bind(&debugger::attach_request, this, _1), true); + register_request_handler( + "configurationDone", + std::bind(&debugger::configuration_done_request, this, _1), + true + ); + register_request_handler("richInspectVariables", std::bind(&debugger::rich_inspect_variables_request, this, _1), true); + register_request_handler("setBreakpoints", std::bind(&debugger::set_breakpoints_request, this, _1), true); + register_request_handler("dumpCell", std::bind(&debugger::dump_cell_request, this, _1), true); + register_request_handler("copyToGlobals", std::bind(&debugger::copy_to_globals_request, this, _1), true); + register_request_handler("stackTrace", std::bind(&debugger::stack_trace_request, this, _1), true); + register_request_handler("source", std::bind(&debugger::source_request, this, _1), true); + + m_interpreter = reinterpret_cast( + debugger_config["interpreter_ptr"].get() + ); + } + + debugger::~debugger() + { + delete p_debuglldb_client; + p_debuglldb_client = nullptr; + } + + bool debugger::start_lldb() + { + jit_process_pid = interpreter::get_current_pid(); + + m_lldb_port = xeus::find_free_port(100, 9999, 10099); + if (m_lldb_port.empty()) + { + std::cerr << "Failed to find a free port for LLDB-DAP" << std::endl; + return false; + } + + if (std::getenv("XEUS_LOG")) + { + std::ofstream log("xeus.log", std::ios::app); + log << "===== DEBUGGER CONFIG =====\n"; + log << m_debugger_config.dump(4) << '\n'; + } + + // std::vector lldb_args = {"/Users/abhinavkumar/Desktop/Coding/CERN_HSF_COMPILER_RESEARCH/llvm-project-lldb/build/bin/lldb-dap", "--connection", "listen://localhost:" + m_lldb_port}; + std::vector lldb_args = { + "lldb-dap", + "--port", + m_lldb_port + }; + + std::string log_dir = xeus::get_temp_directory_path() + "/xcpp_debug_logs_" + + std::to_string(xeus::get_current_pid()); + xeus::create_directory(log_dir); + std::string log_file = log_dir + "/lldb-dap.log"; + + // std::cout << "Log file for LLDB-DAP: " << log_file << std::endl; + + pid_t pid = fork(); + if (pid == 0) + { + // ***** ONLY FOR DEBUGGING PURPOSES. NOT TO BE COMMITTED ***** + int fd = open(log_file.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0644); + if (fd != -1) + { + dup2(fd, STDOUT_FILENO); + dup2(fd, STDERR_FILENO); + close(fd); + } + // ******* (END) ONLY FOR DEBUGGING PURPOSES. NOT TO BE COMMITTED ****** + + int null_fd = open("/dev/null", O_RDONLY); + if (null_fd != -1) + { + dup2(null_fd, STDIN_FILENO); + close(null_fd); + } + + std::vector argv; + for (auto& arg : lldb_args) + { + argv.push_back(const_cast(arg.c_str())); + } + argv.push_back(nullptr); + + execvp("lldb-dap", argv.data()); + + std::cerr << "Failed to execute lldb-dap" << std::endl; + std::exit(1); + } + else if (pid > 0) + { + int status; + if (waitpid(pid, &status, WNOHANG) != 0) + { + std::cerr << "LLDB-DAP process exited prematurely." << std::endl; + return false; + } + m_is_running = true; + m_copy_to_globals_available = true; + return true; + } + else + { + std::cerr << "fork() failed" << std::endl; + return false; + } + } + + bool debugger::start() + { + bool lldb_started = start_lldb(); + if (!lldb_started) + { + std::cerr << "Failed to start LLDB-DAP" << std::endl; + return false; + } + + std::string controller_end_point = xeus::get_controller_end_point("debugger"); + std::string controller_header_end_point = xeus::get_controller_end_point("debugger_header"); + std::string publisher_end_point = xeus::get_publisher_end_point(); + bind_sockets(controller_header_end_point, controller_end_point); + + std::string lldb_endpoint = "tcp://" + m_lldb_host + ":" + m_lldb_port; + std::thread client( + &xdap_tcp_client::start_debugger, + p_debuglldb_client, + lldb_endpoint, + publisher_end_point, + controller_end_point, + controller_header_end_point + ); + client.detach(); + + send_recv_request("REQ"); + + std::string tmp_folder = get_tmp_prefix(); + xeus::create_directory(tmp_folder); + + return true; + } + + nl::json debugger::attach_request(const nl::json& message) + { + // Placeholder DAP response + nl::json attach_request = { + {"seq", message["seq"]}, + {"type", "request"}, + {"command", "attach"}, + {"arguments", + { + {"pid", jit_process_pid}, + {"initCommands", {"settings set plugin.jit-loader.gdb.enable on"}} + } + } + }; + log_debug("[debugger::attach_request] Attach request sent:\n" + attach_request.dump(4)); + nl::json reply = forward_message(attach_request); + return reply; + } + + nl::json debugger::inspect_variables_request(const nl::json& message) + { + log_debug("[debugger::inspect_variables_request] Inspect variable not implemented."); + nl::json inspect_request = { + {"seq", message["seq"]}, + {"type", "request"}, + {"command", "variables"}, + {"arguments", {{"variablesReference", 0}}} + }; + nl::json dummy_reply = forward_message(inspect_request); + return dummy_reply; + } + + nl::json debugger::set_breakpoints_request(const nl::json& message) + { + std::string source = message["arguments"]["source"]["path"].get(); + m_breakpoint_list.erase(source); + nl::json bp_json = message["arguments"]["breakpoints"]; + std::vector bp_list(bp_json.begin(), bp_json.end()); + std::string sequential_source = m_hash_to_sequential[source][0]; + m_breakpoint_list.insert(std::make_pair(std::move(source), std::move(bp_list))); + nl::json mod_message = message; + mod_message["arguments"]["source"]["path"] = sequential_source; + log_debug("[debugger::set_breakpoints_request] Set breakpoints request sent:\n" + mod_message.dump(4)); + nl::json breakpoint_reply = forward_message(mod_message); + if (breakpoint_reply.contains("body") && breakpoint_reply["body"].contains("breakpoints")) + { + for (auto& bp : breakpoint_reply["body"]["breakpoints"]) + { + if (bp.contains("source") && bp["source"].contains("path")) + { + std::string seq_path = bp["source"]["path"].get(); + if (m_sequential_to_hash.find(seq_path) != m_sequential_to_hash.end()) + { + bp["source"]["path"] = m_sequential_to_hash[seq_path]; + } + } + } + } + log_debug("[debugger::set_breakpoints_request] Set breakpoints reply received:\n" + breakpoint_reply.dump(4)); + return breakpoint_reply; + } + + nl::json debugger::copy_to_globals_request(const nl::json& message) + { + // This request cannot be processed if the version of debugpy is lower than 1.6.5. + log_debug("[debugger::copy_to_globals_request] Copy to globals request received:\n" + message.dump(4)); + if (!m_copy_to_globals_available) + { + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", false}, + {"command", message["command"]}, + {"body", "The debugpy version must be greater than or equal 1.6.5 to allow copying a variable to the global scope."} + }; + return reply; + } + + std::string src_var_name = message["arguments"]["srcVariableName"].get(); + std::string dst_var_name = message["arguments"]["dstVariableName"].get(); + int src_frame_id = message["arguments"]["srcFrameId"].get(); + + // It basically runs a setExpression in the globals dictionary of Python. + int seq = message["seq"].get(); + std::string expression = "globals()['" + dst_var_name + "']"; + nl::json request = { + {"type", "request"}, + {"command", "setExpression"}, + {"seq", seq+1}, + {"arguments", { + {"expression", expression}, + {"value", src_var_name}, + {"frameId", src_frame_id} + }} + }; + return forward_message(request); + } + + + nl::json debugger::configuration_done_request(const nl::json& message) + { + log_debug("[debugger::configuration_done_request] Configuration done request received:\n" + message.dump(4)); + if(!base_type::get_stopped_threads().empty()) { + // Return a dummy reply as per user request. + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", true}, + {"command", message["command"]}, + {"body", {{"info", "Dummy reply: continue request not sent as requested."}}} + }; + log_debug("[debugger::configuration_done_request] Returning dummy reply instead of sending continue request."); + return reply; + } + nl::json reply = forward_message(message); + log_debug("[debugger::configuration_done_request] Configuration done reply sent:\n" + reply.dump(4)); + return reply; + } + + void debugger::get_container_info(const std::string& var_name, int frame_id, + const std::string& var_type, nl::json& data, nl::json& metadata, int sequence, int size) + { + // Create a tree structure for the container + nl::json container_tree = { + {"type", "container"}, + {"name", var_name}, + {"containerType", var_type}, + {"size", size}, + {"children", nl::json::array()}, + {"expanded", false} + }; + + // Add elements as children + for (int i = 0; i < size; ++i) { + nl::json elem_request = { + {"type", "request"}, + {"command", "evaluate"}, + {"seq", sequence++}, + {"arguments", { + {"expression", var_name + "[" + std::to_string(i) + "]"}, + {"frameId", frame_id}, + {"context", "watch"} + }} + }; + + nl::json elem_reply = forward_message(elem_request); + if (elem_reply["success"].get()) { + std::string elem_value = elem_reply["body"]["result"].get(); + std::string elem_type = elem_reply["body"].contains("type") ? + elem_reply["body"]["type"].get() : "unknown"; + + nl::json child_node = { + {"type", "element"}, + {"name", "[" + std::to_string(i) + "]"}, + {"value", elem_value}, + {"valueType", elem_type}, + {"index", i}, + {"leaf", true} + }; + + // Check if element is also a container + if (is_container_type(elem_type)) { + child_node["leaf"] = false; + child_node["hasChildren"] = true; + // Size extraction for nested containers + int nested_size = 0; + std::smatch match; + std::regex size_regex(R"(size\s*=\s*(\d+))"); + if (std::regex_search(elem_value, match, size_regex) && match.size() > 1) { + nested_size = std::stoi(match[1]); + } + child_node["size"] = nested_size; + } + + container_tree["children"].push_back(child_node); + } + } + + // Store the tree structure in JSON format + data["application/json"] = container_tree; + } + + bool debugger::is_container_type(const std::string& type) const + { + return type.find("std::vector") != std::string::npos || + type.find("std::array") != std::string::npos || + type.find("std::list") != std::string::npos || + type.find("std::deque") != std::string::npos || + type.find("std::map") != std::string::npos || + type.find("std::set") != std::string::npos || + type.find("std::unordered_map") != std::string::npos || + type.find("std::unordered_set") != std::string::npos; + } + + bool debugger::get_variable_info_from_lldb(const std::string& var_name, int frame_id, nl::json& data, nl::json& metadata, int sequence) { + try { + nl::json eval_request = { + {"type", "request"}, + {"command", "evaluate"}, + {"seq", sequence}, + {"arguments", { + {"expression", var_name}, + {"frameId", frame_id}, + {"context", "watch"} + }} + }; + + nl::json eval_reply = forward_message(eval_request); + if (!eval_reply["success"].get()) { + return false; + } + + std::string var_value = eval_reply["body"]["result"].get(); + std::string var_type = eval_reply["body"].contains("type") ? + eval_reply["body"]["type"].get() : "unknown"; + + // Create a unified JSON structure for all variable types + nl::json variable_info = { + {"type", "variable"}, + {"name", var_name}, + {"value", var_value}, + {"valueType", var_type}, + {"frameId", frame_id}, + {"timestamp", std::time(nullptr)} + }; + + if(is_container_type(var_type)) { + // Extract size from var_value, e.g., "size=2" + int size = 0; + std::smatch match; + std::regex size_regex(R"(size\s*=\s*(\d+))"); + if (std::regex_search(var_value, match, size_regex) && match.size() > 1) { + size = std::stoi(match[1]); + } + + variable_info["isContainer"] = true; + variable_info["size"] = size; + variable_info["leaf"] = false; + variable_info["hasChildren"] = size > 0; + + // Get detailed container info + get_container_info(var_name, frame_id, var_type, data, metadata, sequence + 1, size); + + // Add container summary to the main variable info + if (data["application/json"].contains("children")) { + variable_info["children"] = data["application/json"]["children"]; + variable_info["expanded"] = false; + } + } else { + variable_info["isContainer"] = false; + variable_info["leaf"] = true; + variable_info["hasChildren"] = false; + } + + // Store everything in application/json format + data["application/json"] = variable_info; + + // Metadata for the frontend renderer + metadata["application/json"] = { + {"version", "1.0"}, + {"renderer", "variable-inspector"}, + {"expandable", variable_info["hasChildren"]}, + {"rootVariable", var_name}, + {"capabilities", { + {"tree", true}, + {"search", true}, + {"filter", true}, + {"export", true} + }} + }; + + return true; + } catch (const std::exception& e) { + log_debug("[get_variable_info_from_lldb] Exception: " + std::string(e.what())); + return false; + } + } + + nl::json debugger::rich_inspect_variables_request(const nl::json& message) + { + log_debug("[debugger::rich_inspect_variables_request] Rich inspect variables request received:\n" + message.dump(4)); + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", false}, + {"command", message["command"]} + }; + + std::string var_name; + try { + var_name = message["arguments"]["variableName"].get(); + } catch (const nl::json::exception& e) { + reply["body"] = { + {"data", { + {"application/json", { + {"type", "error"}, + {"message", "Invalid variable name in request"}, + {"details", std::string(e.what())} + }} + }}, + {"metadata", { + {"application/json", { + {"error", true}, + {"errorType", "invalid_request"} + }} + }} + }; + return reply; + } + + int frame_id = 0; + if(message["arguments"].contains("frameId")) { + frame_id = message["arguments"]["frameId"].get(); + } + + nl::json var_data, var_metadata; + bool success = false; + + auto stopped_threads = base_type::get_stopped_threads(); + if(!stopped_threads.empty()) { + success = get_variable_info_from_lldb(var_name, frame_id, var_data, var_metadata, message["seq"].get() + 1); + } else { + // When there is no breakpoint hit, return a placeholder structure + var_data["application/json"] = { + {"type", "unavailable"}, + {"name", var_name}, + {"message", "Variable not accessible - no active debugging session"}, + {"suggestions", { + "Set a breakpoint and run the program", + "Ensure the variable is in scope", + "Check if the program is currently stopped" + }} + }; + var_metadata["application/json"] = { + {"available", false}, + {"reason", "no_stopped_threads"} + }; + success = true; + } + + if(success) { + reply["body"] = { + {"data", var_data}, + {"metadata", var_metadata} + }; + reply["success"] = true; + } else { + reply["body"] = { + {"data", { + {"application/json", { + {"type", "error"}, + {"name", var_name}, + {"message", "Variable '" + var_name + "' not found or not accessible"}, + {"frameId", frame_id}, + {"suggestions", { + "Check variable name spelling", + "Ensure variable is in current scope", + "Verify the frame ID is correct" + }} + }} + }}, + {"metadata", { + {"application/json", { + {"error", true}, + {"errorType", "variable_not_found"} + }} + }} + }; + } + + log_debug("[debugger::rich_inspect_variables_request] Reply:\n" + reply.dump(4)); + return reply; + } + + nl::json debugger::stack_trace_request(const nl::json& message) + { + int requested_thread_id = message["arguments"]["threadId"]; + + auto stopped_threads = base_type::get_stopped_threads(); + + if (stopped_threads.empty()) { + nl::json reply = forward_message(message); + return reply; + } + + int actual_thread_id; + nl::json modified_message = message; + + if (requested_thread_id == 1 && !stopped_threads.empty()) { + actual_thread_id = *stopped_threads.begin(); + modified_message["arguments"]["threadId"] = actual_thread_id; + std::clog << "XDEBUGGER: Mapping client thread 1 to actual thread " << actual_thread_id << std::endl; + } + else if (stopped_threads.find(requested_thread_id) != stopped_threads.end()) { + actual_thread_id = requested_thread_id; + } + else { + actual_thread_id = *stopped_threads.begin(); + modified_message["arguments"]["threadId"] = actual_thread_id; + std::clog << "XDEBUGGER: Thread " << requested_thread_id + << " not found in stopped threads, using " << actual_thread_id << std::endl; + } + + for (auto x : stopped_threads) + { + log_debug("Stopped thread ID: " + std::to_string(x)); + } + + log_debug("[debugger::stack_trace_request]:\n" + modified_message.dump(4)); + + nl::json reply = forward_message(modified_message); + + if (!reply.contains("body") || !reply["body"].contains("stackFrames")) { + return reply; + } + + auto& stack_frames = reply["body"]["stackFrames"]; + nl::json filtered_frames = nl::json::array(); + + for (auto& frame : stack_frames) + { + if (frame.contains("source") && frame["source"].contains("path")) + { + std::string path = frame["source"]["path"]; + std::string name = frame["source"]["name"]; + // Check if path is in the format input_line_ + if (name.find("input_line_") != std::string::npos) + { + // Map sequential filename to hash if mapping exists + auto it = m_sequential_to_hash.find(name); + if (it != m_sequential_to_hash.end()) + { + frame["source"]["path"] = it->second; + // Set name to last part of path (filename) + std::string filename = it->second.substr(it->second.find_last_of("/\\") + 1); + frame["source"]["name"] = filename; + } else { + std::string code, hash_file_name; + // Extract execution count number from "input_line_" + int exec_count = -1; + std::string prefix = "input_line_"; + if (name.compare(0, prefix.size(), prefix) == 0) + { + try + { + exec_count = std::stoi(name.substr(prefix.size())); + } + catch (...) + { + exec_count = 0; + } + } + + if (exec_count != -1) { + code = m_interpreter->get_code_from_execution_count(exec_count); + hash_file_name = get_cell_temporary_file(code); + frame["source"]["path"] = hash_file_name; + frame["source"]["name"] = hash_file_name.substr(hash_file_name.find_last_of("/\\") + 1); + // Update mappings if not already present + m_hash_to_sequential[hash_file_name].push_back(name); + m_sequential_to_hash[name] = hash_file_name; + } + } + filtered_frames.push_back(frame); + } + } + } + + reply["body"]["stackFrames"] = filtered_frames; + + #ifdef WIN32 + for (auto& frame : reply["body"]["stackFrames"]) { + if (frame.contains("source") && frame["source"].contains("path")) { + std::string path = frame["source"]["path"]; + std::replace(path.begin(), path.end(), '\\', '/'); + frame["source"]["path"] = path; + } + } + #endif + + log_debug("Stack trace response: " + reply.dump(4)); + + return reply; + } + + void debugger::stop() + { + std::string controller_end_point = xeus::get_controller_end_point("debugger"); + std::string controller_header_end_point = xeus::get_controller_end_point("debugger_header"); + unbind_sockets(controller_header_end_point, controller_end_point); + } + + xeus::xdebugger_info debugger::get_debugger_info() const + { + // Placeholder debugger info + log_debug("[debugger::get_debugger_info] Returning debugger info"); + return xeus::xdebugger_info( + xeus::get_tmp_hash_seed(), + get_tmp_prefix(), + get_tmp_suffix(), + true, // Supports exceptions + {"C++ Exceptions"}, + true // Supports breakpoints + ); + } + + nl::json debugger::dump_cell_request(const nl::json& message) + { + log_debug("[debugger::dump_cell_request] Dump cell request received:\n" + message.dump(4)); + std::string code; + try + { + code = message["arguments"]["code"].get(); + // std::cout << "Execution Count is: " << m_interpreter->get_execution_count(code)[0] << std::endl; + } + catch (nl::json::type_error& e) + { + std::clog << e.what() << std::endl; + } + catch (...) + { + std::clog << "XDEBUGGER: Unknown issue" << std::endl; + } + std::string hash_file_name = get_cell_temporary_file(code); + for(auto& exec_count: m_interpreter->get_execution_count(code)) + { + m_hash_to_sequential[hash_file_name].push_back("input_line_" + std::to_string(exec_count)); + m_sequential_to_hash["input_line_" + std::to_string(exec_count)] = hash_file_name; + } + + // std::string next_file_name = "input_line_" + std::to_string(m_interpreter->get_execution_count(code).size() ? m_interpreter->get_execution_count(code)[0] : 0); + + std::fstream fs(hash_file_name, std::ios::in); + if (!fs.is_open()) + { + fs.clear(); + fs.open(hash_file_name, std::ios::out); + fs << code; + } + + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", true}, + {"command", message["command"]}, + {"body", {{"sourcePath", hash_file_name}}} + }; + return reply; + } + + nl::json debugger::source_request(const nl::json& message) + { + std::string sourcePath; + try + { + sourcePath = message["arguments"]["source"]["path"].get(); + } + catch(nl::json::type_error& e) + { + std::clog << e.what() << std::endl; + } + catch(...) + { + std::clog << "XDEBUGGER: Unknown issue" << std::endl; + } + + std::ifstream ifs(sourcePath, std::ios::in); + if(!ifs.is_open()) + { + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", false}, + {"command", message["command"]}, + {"message", "source unavailable"}, + {"body", {{}}} + }; + return reply; + } + + std::string content((std::istreambuf_iterator(ifs)), std::istreambuf_iterator()); + + nl::json reply = { + {"type", "response"}, + {"request_seq", message["seq"]}, + {"success", true}, + {"command", message["command"]}, + {"body", { + {"content", content} + }} + }; + log_debug("[debugger::source_request reply]:\n" + reply.dump(4)); + return reply; + } + + std::string debugger::get_cell_temporary_file(const std::string& code) const + { + return get_cell_tmp_file(code); + } + + std::unique_ptr make_cpp_debugger( + xeus::xcontext& context, + const xeus::xconfiguration& config, + const std::string& user_name, + const std::string& session_id, + const nl::json& debugger_config + ) + { + return std::unique_ptr( + new debugger(context, config, user_name, session_id, debugger_config) + ); + } +} \ No newline at end of file diff --git a/src/xdebuglldb_client.cpp b/src/xdebuglldb_client.cpp new file mode 100644 index 00000000..6b446181 --- /dev/null +++ b/src/xdebuglldb_client.cpp @@ -0,0 +1,77 @@ +#include "xdebuglldb_client.hpp" + +#include +#include +#include +#include +#include +#include +#include + +#include "nlohmann/json.hpp" +#include "xeus/xmessage.hpp" +#include +#include + +namespace nl = nlohmann; + +// ***** ONLY FOR DEBUGGING PURPOSES. NOT TO BE COMMITTED ***** +static std::ofstream log_stream_client("/Users/abhinavkumar/Desktop/Coding/CERN_HSF_COMPILER_RESEARCH/xeus-cpp/build/xeus-cpp-xdebuglldb_client-logs.log", std::ios::app); + +void log_debug_client(const std::string& msg) { + log_stream_client << "[xeus-cpp-debuglldb_client] " << msg << std::endl; + log_stream_client.flush(); // Ensure immediate write +} +// ******* (END) ONLY FOR DEBUGGING PURPOSES. NOT TO BE COMMITTED ******* + +void log_debug(const std::string& msg); + + +namespace xcpp +{ + xdebuglldb_client::xdebuglldb_client( + xeus::xcontext& context, + const xeus::xconfiguration& config, + int socket_linger, + const xdap_tcp_configuration& dap_config, + const event_callback& cb + ) + : base_type(context, config, socket_linger, dap_config, cb) + { + std::cout << "xdebuglldb_client initialized" << std::endl; + } + + + void xdebuglldb_client::handle_event(nl::json message) + { + // Forward DAP events to the base class (e.g., "stopped" events from LLDB-DAP) + log_debug_client("[xdebuglldb_client::handle_event] Handling event:\n" + message.dump(4)); + forward_event(std::move(message)); + } + + nl::json xdebuglldb_client::get_stack_frames(int thread_id, int seq) + { + // Construct a DAP stackTrace request + log_debug_client("Requesting stack frames for thread ID: " + std::to_string(thread_id) + " with sequence number: " + std::to_string(seq)); + // std::cout << "Requesting stack frames for thread ID: " << thread_id << " with sequence number: " << seq << std::endl; + nl::json request = { + {"type", "request"}, + {"seq", seq}, + {"command", "stackTrace"}, + {"arguments", {{"threadId", thread_id}}} + }; + + // Send the request + send_dap_request(std::move(request)); + + // Wait for the response + nl::json reply = wait_for_message( + [](const nl::json& message) + { + return message["type"] == "response" && message["command"] == "stackTrace"; + } + ); + + return reply["body"]["stackFrames"]; + } +} \ No newline at end of file diff --git a/src/xdebuglldb_client.hpp b/src/xdebuglldb_client.hpp new file mode 100644 index 00000000..3ea029d6 --- /dev/null +++ b/src/xdebuglldb_client.hpp @@ -0,0 +1,33 @@ +#ifndef XEUS_CPP_DEBUGLLDB_CLIENT_HPP +#define XEUS_CPP_DEBUGLLDB_CLIENT_HPP + +#include "xeus-zmq/xdap_tcp_client.hpp" + +namespace xcpp +{ + using xeus::xdap_tcp_client; + using xeus::xdap_tcp_configuration; + + class xdebuglldb_client : public xdap_tcp_client + { + public: + + using base_type = xdap_tcp_client; + using event_callback = base_type::event_callback; + + xdebuglldb_client(xeus::xcontext& context, + const xeus::xconfiguration& config, + int socket_linger, + const xdap_tcp_configuration& dap_config, + const event_callback& cb); + + virtual ~xdebuglldb_client() = default; + + private: + + void handle_event(nl::json message) override; + nl::json get_stack_frames(int thread_id, int seq); + }; +} + +#endif \ No newline at end of file diff --git a/src/xinternal_utils.cpp b/src/xinternal_utils.cpp new file mode 100644 index 00000000..ae00b332 --- /dev/null +++ b/src/xinternal_utils.cpp @@ -0,0 +1,72 @@ +#include "xinternal_utils.hpp" + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace xcpp +{ + std::string red_text(const std::string& text) + { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + SetConsoleTextAttribute(hConsole, FOREGROUND_RED); + std::string result = text; + SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes); + return result; +#else + return "\033[0;31m" + text + "\033[0m"; +#endif + } + + std::string green_text(const std::string& text) + { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + SetConsoleTextAttribute(hConsole, FOREGROUND_GREEN); + std::string result = text; + SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes); + return result; +#else + return "\033[0;32m" + text + "\033[0m"; +#endif + } + + std::string blue_text(const std::string& text) + { +#ifdef _WIN32 + HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO consoleInfo; + GetConsoleScreenBufferInfo(hConsole, &consoleInfo); + SetConsoleTextAttribute(hConsole, FOREGROUND_BLUE); + std::string result = text; + SetConsoleTextAttribute(hConsole, consoleInfo.wAttributes); + return result; +#else + return "\033[0;34m" + text + "\033[0m"; +#endif + } + + std::string get_tmp_prefix() + { + return xeus::get_tmp_prefix("xcpp"); + } + + std::string get_tmp_suffix() + { + return ".cpp"; + } + + std::string get_cell_tmp_file(const std::string& content) + { + return xeus::get_cell_tmp_file(get_tmp_prefix(), + content, + get_tmp_suffix()); + } +} \ No newline at end of file diff --git a/src/xinternal_utils.hpp b/src/xinternal_utils.hpp new file mode 100644 index 00000000..0d5a67d9 --- /dev/null +++ b/src/xinternal_utils.hpp @@ -0,0 +1,21 @@ +#ifndef XEUS_CPP_INTERNAL_UTILS_HPP +#define XEUS_CPP_INTERNAL_UTILS_HPP + +#include + +#include "xeus/xsystem.hpp" + +namespace xcpp +{ + // Colorize text for terminal output + std::string red_text(const std::string& text); + std::string green_text(const std::string& text); + std::string blue_text(const std::string& text); + + // Temporary file utilities for Jupyter cells + std::string get_tmp_prefix(); + std::string get_tmp_suffix(); + std::string get_cell_tmp_file(const std::string& content); +} + +#endif \ No newline at end of file diff --git a/src/xinterpreter.cpp b/src/xinterpreter.cpp index 14972324..398af434 100644 --- a/src/xinterpreter.cpp +++ b/src/xinterpreter.cpp @@ -19,6 +19,8 @@ #include "xinspect.hpp" #include "xmagics/os.hpp" #include +#include +#include #ifndef EMSCRIPTEN #include "xmagics/xassist.hpp" #endif @@ -27,28 +29,30 @@ using Args = std::vector; -void* createInterpreter(const Args &ExtraArgs = {}) { - Args ClangArgs = {/*"-xc++"*/"-v"}; - if (std::find_if(ExtraArgs.begin(), ExtraArgs.end(), [](const std::string& s) { - return s == "-resource-dir";}) == ExtraArgs.end()) { - std::string resource_dir = Cpp::DetectResourceDir(); - if (!resource_dir.empty()) { - ClangArgs.push_back("-resource-dir"); - ClangArgs.push_back(resource_dir.c_str()); - } else { - std::cerr << "Failed to detect the resource-dir\n"; +void* createInterpreter(const Args& ExtraArgs = {}) +{ + Args ClangArgs = {/*"-xc++"*/"-v"}; + if (std::find_if(ExtraArgs.begin(), ExtraArgs.end(), [](const std::string& s) { + return s == "-resource-dir";}) == ExtraArgs.end()) { + std::string resource_dir = Cpp::DetectResourceDir(); + if (!resource_dir.empty()) { + ClangArgs.push_back("-resource-dir"); + ClangArgs.push_back(resource_dir.c_str()); + } else { + std::cerr << "Failed to detect the resource-dir\n"; + } } - } - std::vector CxxSystemIncludes; - Cpp::DetectSystemCompilerIncludePaths(CxxSystemIncludes); - for (const std::string& CxxInclude : CxxSystemIncludes) { - ClangArgs.push_back("-isystem"); - ClangArgs.push_back(CxxInclude.c_str()); - } - ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end()); - // FIXME: We should process the kernel input options and conditionally pass - // the gpu args here. - return Cpp::CreateInterpreter(ClangArgs/*, {"-cuda"}*/); + std::vector CxxSystemIncludes; + Cpp::DetectSystemCompilerIncludePaths(CxxSystemIncludes); + for (const std::string& CxxInclude : CxxSystemIncludes) { + ClangArgs.push_back("-isystem"); + ClangArgs.push_back(CxxInclude.c_str()); + } + ClangArgs.insert(ClangArgs.end(), ExtraArgs.begin(), ExtraArgs.end()); + + // FIXME: We should process the kernel input options and conditionally pass + // the gpu args here. + return Cpp::CreateInterpreter(ClangArgs /*, {"-cuda"}*/); } using namespace std::placeholders; @@ -73,6 +77,11 @@ namespace xcpp xeus::register_interpreter(this); } + pid_t interpreter::get_current_pid() + { + return Cpp::GetExecutorPID(); + } + static std::string get_stdopt() { // We need to find what's the C++ version the interpreter runs with. @@ -100,16 +109,16 @@ __get_cxx_version () return std::to_string(cxx_version); } - interpreter::interpreter(int argc, const char* const* argv) : + interpreter::interpreter(int argc, const char* const* argv): xmagics() , p_cout_strbuf(nullptr) , p_cerr_strbuf(nullptr) , m_cout_buffer(std::bind(&interpreter::publish_stdout, this, _1)) , m_cerr_buffer(std::bind(&interpreter::publish_stderr, this, _1)) { - //NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic) + // NOLINTNEXTLINE (cppcoreguidelines-pro-bounds-pointer-arithmetic) createInterpreter(Args(argv ? argv + 1 : argv, argv + argc)); - m_version = get_stdopt(); + // m_version = get_stdopt(); redirect_output(); init_preamble(); init_magic(); @@ -122,7 +131,7 @@ __get_cxx_version () void interpreter::execute_request_impl( send_reply_callback cb, - int /*execution_count*/, + int execution_count, const std::string& code, xeus::execute_request_config config, nl::json /*user_expressions*/ @@ -164,9 +173,13 @@ __get_cxx_version () std::string err; + m_code_to_execution_count_map[code].push_back(execution_count); + m_execution_count_to_code_map[execution_count] = code; + // Attempt normal evaluation try { + StreamRedirectRAII R(err); compilation_result = Cpp::Process(code.c_str()); } @@ -213,7 +226,7 @@ __get_cxx_version () if (evalue.size() < 4) { ename = " "; } - std::vector traceback({ename + evalue}); + std::vector traceback({ename + evalue}); if (!config.silent) { publish_execution_error(ename, evalue, traceback); @@ -277,13 +290,17 @@ __get_cxx_version () nl::json interpreter::is_complete_request_impl(const std::string& code) { - if (!code.empty() && code[code.size() - 1] == '\\') { + if (!code.empty() && code[code.size() - 1] == '\\') + { auto found = code.rfind('\n'); if (found == std::string::npos) + { found = -1; + } auto found1 = found++; - while (isspace(code[++found1])) ; - return xeus::create_is_complete_reply("incomplete", code.substr(found, found1-found)); + while (isspace(code[++found1])) + ; + return xeus::create_is_complete_reply("incomplete", code.substr(found, found1 - found)); } return xeus::create_is_complete_reply("complete"); @@ -312,6 +329,7 @@ __get_cxx_version () "\n" " xeus-cpp: a C++ Jupyter kernel - based on Clang-repl\n"; result["banner"] = banner; + result["debugger"] = true; result["language_info"]["name"] = "C++"; result["language_info"]["version"] = m_version; result["language_info"]["mimetype"] = "text/x-c++src";