Harden plugin runtime: TLS verify, LSP deadlock, path traversal, ABI exception safety (W14)
W14 addresses the five most critical findings from the W13 plugin audits: - W14.1 network: enable ssl::verify_peer + SSL_set1_host SNI hostname verification (fixes TLS bypass, W13.3 CVSS 7.4); add steady_timer DNS timeout and bottom-up catch(...) hardening (engineer-zhou) - W14.2 lsp: fix reader_loop/stop mutex deadlock via stop_nolock/stop_locked split (W13.4); wrap 11 vtable/entry functions in try/catch with cv notification on reader exit (engineer-sun) - W14.3 tools: add is_safe_path() rejecting empty/absolute/.. paths before file_io calls (fixes path traversal, W13.5 CVSS 7.5); guard g_tools and g_session/g_history under mutex; 9 vtable try/catch (security-cao) - W14.4 host: add fallback plugin search (../plugins/) so binaries run from build/tests/ load current DLLs, resolving the W13.6 R2 stale-DLL false alarm (architect-lin) - W14.5 anthropic+deepseek: wrap 12 ABI boundary functions in try/catch with log-guard, preventing exceptions from crossing the C ABI (engineer-chen) Verified: cmake build 0 error 0 warning, ctest 4/4 pass, smoke R2 now passes naturally. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -373,42 +373,48 @@ static void send_notification(const std::string& method, const json::object& par
|
||||
// ============================================================================
|
||||
|
||||
static void handle_message(const std::string& body) {
|
||||
json::value val;
|
||||
try { val = json::parse(body); }
|
||||
catch (...) { return; }
|
||||
|
||||
json::object msg;
|
||||
try { msg = val.as_object(); }
|
||||
catch (...) { return; }
|
||||
|
||||
if (msg.contains("id") && !msg.contains("method")) {
|
||||
// 响应 (有 id, 无 method)
|
||||
int id = static_cast<int>(msg["id"].as_int64());
|
||||
std::lock_guard<std::mutex> lock(g_lsp.mutex);
|
||||
g_lsp.pending_responses[id] = body;
|
||||
g_lsp.cv.notify_all();
|
||||
|
||||
} else if (msg.contains("method") && !msg.contains("id")) {
|
||||
// 通知 (有 method, 无 id)
|
||||
std::string method;
|
||||
try { method = json::value_to<std::string>(msg["method"]); }
|
||||
try {
|
||||
json::value val;
|
||||
try { val = json::parse(body); }
|
||||
catch (...) { return; }
|
||||
|
||||
if (method == "textDocument/publishDiagnostics") {
|
||||
if (!msg.contains("params")) return;
|
||||
auto params = msg["params"].as_object();
|
||||
if (!params.contains("uri")) return;
|
||||
|
||||
std::string uri = json::value_to<std::string>(params["uri"]);
|
||||
std::string diag_json;
|
||||
if (params.contains("diagnostics"))
|
||||
diag_json = json::serialize(params["diagnostics"]);
|
||||
else
|
||||
diag_json = "[]";
|
||||
json::object msg;
|
||||
try { msg = val.as_object(); }
|
||||
catch (...) { return; }
|
||||
|
||||
if (msg.contains("id") && !msg.contains("method")) {
|
||||
// 响应 (有 id, 无 method)
|
||||
int id = static_cast<int>(msg["id"].as_int64());
|
||||
std::lock_guard<std::mutex> lock(g_lsp.mutex);
|
||||
g_lsp.diagnostics[uri] = diag_json;
|
||||
g_lsp.pending_responses[id] = body;
|
||||
g_lsp.cv.notify_all();
|
||||
|
||||
} else if (msg.contains("method") && !msg.contains("id")) {
|
||||
// 通知 (有 method, 无 id)
|
||||
std::string method;
|
||||
try { method = json::value_to<std::string>(msg["method"]); }
|
||||
catch (...) { return; }
|
||||
|
||||
if (method == "textDocument/publishDiagnostics") {
|
||||
if (!msg.contains("params")) return;
|
||||
auto params = msg["params"].as_object();
|
||||
if (!params.contains("uri")) return;
|
||||
|
||||
std::string uri = json::value_to<std::string>(params["uri"]);
|
||||
std::string diag_json;
|
||||
if (params.contains("diagnostics"))
|
||||
diag_json = json::serialize(params["diagnostics"]);
|
||||
else
|
||||
diag_json = "[]";
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_lsp.mutex);
|
||||
g_lsp.diagnostics[uri] = diag_json;
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] handle_message: %s", e.what());
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] handle_message: unknown exception");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,40 +423,46 @@ static void handle_message(const std::string& body) {
|
||||
// ============================================================================
|
||||
|
||||
static void reader_loop() {
|
||||
while (g_lsp.running) {
|
||||
int content_length = -1;
|
||||
bool pipe_ok = true;
|
||||
try {
|
||||
while (g_lsp.running) {
|
||||
int content_length = -1;
|
||||
bool pipe_ok = true;
|
||||
|
||||
// 状态机式读取 header 块:循环 read_line 直到读到空行
|
||||
// LSP 3.17: header 块以空行(\r\n)结束,允许 Content-Type 等其他 header
|
||||
while (pipe_ok) {
|
||||
std::string line;
|
||||
if (!g_lsp.proc.read_line(line)) {
|
||||
pipe_ok = false;
|
||||
break;
|
||||
// 状态机式读取 header 块:循环 read_line 直到读到空行
|
||||
// LSP 3.17: header 块以空行(\r\n)结束,允许 Content-Type 等其他 header
|
||||
while (pipe_ok) {
|
||||
std::string line;
|
||||
if (!g_lsp.proc.read_line(line)) {
|
||||
pipe_ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// header 块以空行结束
|
||||
auto sv = trim(std::string_view(line));
|
||||
if (sv.empty()) break;
|
||||
|
||||
// 累积 Content-Length;遇到其他 header 不丢弃,继续读取下一行
|
||||
int len = parse_content_length(line);
|
||||
if (len >= 0) content_length = len;
|
||||
}
|
||||
|
||||
// header 块以空行结束
|
||||
auto sv = trim(std::string_view(line));
|
||||
if (sv.empty()) break;
|
||||
if (!pipe_ok) break;
|
||||
|
||||
// 累积 Content-Length;遇到其他 header 不丢弃,继续读取下一行
|
||||
int len = parse_content_length(line);
|
||||
if (len >= 0) content_length = len;
|
||||
// 空行前都没读到 Content-Length,协议错误——记日志并跳过这一帧
|
||||
if (content_length < 0) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] Invalid LSP frame: missing Content-Length header");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string body;
|
||||
if (!g_lsp.proc.read_bytes(body, content_length)) break;
|
||||
|
||||
handle_message(body);
|
||||
}
|
||||
|
||||
if (!pipe_ok) break;
|
||||
|
||||
// 空行前都没读到 Content-Length,协议错误——记日志并跳过这一帧
|
||||
if (content_length < 0) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] Invalid LSP frame: missing Content-Length header");
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string body;
|
||||
if (!g_lsp.proc.read_bytes(body, content_length)) break;
|
||||
|
||||
handle_message(body);
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] reader_loop: %s", e.what());
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] reader_loop: unknown exception");
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_lsp.mutex);
|
||||
@@ -463,106 +475,131 @@ static void reader_loop() {
|
||||
// ============================================================================
|
||||
|
||||
static void g_lsp_impl_stop();
|
||||
static void g_lsp_impl_stop_nolock();
|
||||
static void g_lsp_impl_stop_locked(std::unique_lock<std::mutex>& lock);
|
||||
|
||||
static int g_lsp_impl_start(const char* server_cmd, const char* language) {
|
||||
if (!server_cmd || !server_cmd[0]) return -1;
|
||||
|
||||
// 如果已在运行, 先停止
|
||||
if (g_lsp.running) {
|
||||
g_lsp_impl_stop();
|
||||
}
|
||||
|
||||
g_lsp.language = language ? language : "";
|
||||
|
||||
// 启动进程
|
||||
if (!g_lsp.proc.start(server_cmd)) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] failed to start: %s", server_cmd);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 重置 ID 计数器
|
||||
g_lsp.next_id = 1;
|
||||
|
||||
// 启动读取线程
|
||||
g_lsp.running = true;
|
||||
g_lsp.reader_thread = std::thread(reader_loop);
|
||||
|
||||
// 构建 initialize 参数
|
||||
json::object text_doc_caps;
|
||||
{
|
||||
json::object hover;
|
||||
hover["dynamicRegistration"] = false;
|
||||
text_doc_caps["hover"] = hover;
|
||||
|
||||
json::object completion;
|
||||
completion["dynamicRegistration"] = false;
|
||||
text_doc_caps["completion"] = completion;
|
||||
|
||||
json::object diagnostic;
|
||||
diagnostic["dynamicRegistration"] = false;
|
||||
text_doc_caps["diagnostic"] = diagnostic;
|
||||
}
|
||||
|
||||
json::object capabilities;
|
||||
capabilities["textDocument"] = text_doc_caps;
|
||||
|
||||
json::object init_params;
|
||||
init_params["processId"] = nullptr;
|
||||
init_params["rootUri"] = nullptr;
|
||||
init_params["capabilities"] = capabilities;
|
||||
|
||||
// 发送 initialize 请求
|
||||
int init_id = send_request("initialize", init_params);
|
||||
|
||||
// 等待 initialize 响应 (最多 10 秒)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [init_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(init_id) > 0;
|
||||
});
|
||||
|
||||
if (!got || !g_lsp.running) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] initialize timed out");
|
||||
try {
|
||||
// 如果已在运行, 先停止
|
||||
if (g_lsp.running) {
|
||||
g_lsp_impl_stop();
|
||||
}
|
||||
|
||||
g_lsp.language = language ? language : "";
|
||||
|
||||
// 启动进程
|
||||
if (!g_lsp.proc.start(server_cmd)) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] failed to start: %s", server_cmd);
|
||||
return -1;
|
||||
}
|
||||
g_lsp.pending_responses.erase(init_id);
|
||||
|
||||
// 重置 ID 计数器
|
||||
g_lsp.next_id = 1;
|
||||
|
||||
// 启动读取线程
|
||||
g_lsp.running = true;
|
||||
g_lsp.reader_thread = std::thread(reader_loop);
|
||||
|
||||
// 构建 initialize 参数
|
||||
json::object text_doc_caps;
|
||||
{
|
||||
json::object hover;
|
||||
hover["dynamicRegistration"] = false;
|
||||
text_doc_caps["hover"] = hover;
|
||||
|
||||
json::object completion;
|
||||
completion["dynamicRegistration"] = false;
|
||||
text_doc_caps["completion"] = completion;
|
||||
|
||||
json::object diagnostic;
|
||||
diagnostic["dynamicRegistration"] = false;
|
||||
text_doc_caps["diagnostic"] = diagnostic;
|
||||
}
|
||||
|
||||
json::object capabilities;
|
||||
capabilities["textDocument"] = text_doc_caps;
|
||||
|
||||
json::object init_params;
|
||||
init_params["processId"] = nullptr;
|
||||
init_params["rootUri"] = nullptr;
|
||||
init_params["capabilities"] = capabilities;
|
||||
|
||||
// 发送 initialize 请求
|
||||
int init_id = send_request("initialize", init_params);
|
||||
|
||||
// 等待 initialize 响应 (最多 10 秒)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [init_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(init_id) > 0;
|
||||
});
|
||||
|
||||
if (!got || !g_lsp.running) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] initialize timed out");
|
||||
g_lsp_impl_stop_locked(lock);
|
||||
return -1;
|
||||
}
|
||||
g_lsp.pending_responses.erase(init_id);
|
||||
}
|
||||
|
||||
// 发送 initialized 通知
|
||||
send_notification("initialized", json::object{});
|
||||
|
||||
if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] server started: %s", server_cmd);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] start: %s", e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] start: unknown exception");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
// 发送 initialized 通知
|
||||
send_notification("initialized", json::object{});
|
||||
static void g_lsp_impl_stop_nolock() {
|
||||
try {
|
||||
if (!g_lsp.running) return;
|
||||
|
||||
if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] server started: %s", server_cmd);
|
||||
return 0;
|
||||
// 发送 shutdown 请求
|
||||
int shutdown_id = send_request("shutdown", json::object{});
|
||||
|
||||
// 等待 shutdown 响应 (最多 2 秒)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
g_lsp.cv.wait_for(lock, std::chrono::seconds(2), [shutdown_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(shutdown_id) > 0;
|
||||
});
|
||||
g_lsp.pending_responses.clear();
|
||||
}
|
||||
|
||||
// 发送 exit 通知
|
||||
send_notification("exit", json::object{});
|
||||
|
||||
// 停止读取线程
|
||||
g_lsp.running = false;
|
||||
g_lsp.proc.stop();
|
||||
|
||||
if (g_lsp.reader_thread.joinable())
|
||||
g_lsp.reader_thread.join();
|
||||
|
||||
g_lsp.diagnostics.clear();
|
||||
if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] server stopped");
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] stop: %s", e.what());
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] stop: unknown exception");
|
||||
}
|
||||
}
|
||||
|
||||
static void g_lsp_impl_stop() {
|
||||
if (!g_lsp.running) return;
|
||||
g_lsp_impl_stop_nolock();
|
||||
}
|
||||
|
||||
// 发送 shutdown 请求
|
||||
int shutdown_id = send_request("shutdown", json::object{});
|
||||
|
||||
// 等待 shutdown 响应 (最多 2 秒)
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
g_lsp.cv.wait_for(lock, std::chrono::seconds(2), [shutdown_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(shutdown_id) > 0;
|
||||
});
|
||||
g_lsp.pending_responses.clear();
|
||||
}
|
||||
|
||||
// 发送 exit 通知
|
||||
send_notification("exit", json::object{});
|
||||
|
||||
// 停止读取线程
|
||||
g_lsp.running = false;
|
||||
g_lsp.proc.stop();
|
||||
|
||||
if (g_lsp.reader_thread.joinable())
|
||||
g_lsp.reader_thread.join();
|
||||
|
||||
g_lsp.diagnostics.clear();
|
||||
if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] server stopped");
|
||||
static void g_lsp_impl_stop_locked(std::unique_lock<std::mutex>& lock) {
|
||||
lock.unlock();
|
||||
g_lsp_impl_stop_nolock();
|
||||
}
|
||||
|
||||
static int g_lsp_impl_open_document(const char* uri, const char* content,
|
||||
@@ -570,131 +607,177 @@ static int g_lsp_impl_open_document(const char* uri, const char* content,
|
||||
if (!g_lsp.running) return -1;
|
||||
if (!uri || !content || !lang_id) return -1;
|
||||
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
text_doc["languageId"] = lang_id;
|
||||
text_doc["version"] = 1;
|
||||
text_doc["text"] = content;
|
||||
try {
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
text_doc["languageId"] = lang_id;
|
||||
text_doc["version"] = 1;
|
||||
text_doc["text"] = content;
|
||||
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
|
||||
send_notification("textDocument/didOpen", params);
|
||||
return 0;
|
||||
send_notification("textDocument/didOpen", params);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] open_document: %s", e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] open_document: unknown exception");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int g_lsp_impl_close_document(const char* uri) {
|
||||
if (!g_lsp.running) return -1;
|
||||
if (!uri) return -1;
|
||||
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
try {
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
|
||||
send_notification("textDocument/didClose", params);
|
||||
return 0;
|
||||
send_notification("textDocument/didClose", params);
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] close_document: %s", e.what());
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] close_document: unknown exception");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
static int g_lsp_impl_get_diagnostics(const char* uri, char** json_out) {
|
||||
if (!g_lsp.running) return -1;
|
||||
if (!uri || !json_out) return -1;
|
||||
|
||||
std::lock_guard<std::mutex> lock(g_lsp.mutex);
|
||||
auto it = g_lsp.diagnostics.find(uri);
|
||||
if (it == g_lsp.diagnostics.end()) {
|
||||
*json_out = g_host->strdup("[]");
|
||||
} else {
|
||||
*json_out = g_host->strdup(it->second.c_str());
|
||||
try {
|
||||
std::lock_guard<std::mutex> lock(g_lsp.mutex);
|
||||
auto it = g_lsp.diagnostics.find(uri);
|
||||
if (it == g_lsp.diagnostics.end()) {
|
||||
*json_out = g_host->strdup("[]");
|
||||
} else {
|
||||
*json_out = g_host->strdup(it->second.c_str());
|
||||
}
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_diagnostics: %s", e.what());
|
||||
*json_out = nullptr;
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_diagnostics: unknown exception");
|
||||
*json_out = nullptr;
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int g_lsp_impl_get_hover(const char* uri, int line, int col, char** json_out) {
|
||||
if (!g_lsp.running) return -1;
|
||||
if (!uri || !json_out) return -1;
|
||||
|
||||
json::object position;
|
||||
position["line"] = line;
|
||||
position["character"] = col;
|
||||
try {
|
||||
json::object position;
|
||||
position["line"] = line;
|
||||
position["character"] = col;
|
||||
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
params["position"] = position;
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
params["position"] = position;
|
||||
|
||||
int req_id = send_request("textDocument/hover", params);
|
||||
int req_id = send_request("textDocument/hover", params);
|
||||
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [req_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(req_id) > 0;
|
||||
});
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [req_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(req_id) > 0;
|
||||
});
|
||||
|
||||
if (!got || !g_lsp.running || g_lsp.pending_responses.count(req_id) == 0) {
|
||||
if (!got || !g_lsp.running || g_lsp.pending_responses.count(req_id) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string response_body = g_lsp.pending_responses[req_id];
|
||||
g_lsp.pending_responses.erase(req_id);
|
||||
|
||||
json::value val;
|
||||
try { val = json::parse(response_body); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
json::object resp;
|
||||
try { resp = val.as_object(); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
if (!resp.contains("result")) return -1;
|
||||
|
||||
*json_out = g_host->strdup(json::serialize(resp["result"]).c_str());
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_hover: %s", e.what());
|
||||
*json_out = nullptr;
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_hover: unknown exception");
|
||||
*json_out = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string response_body = g_lsp.pending_responses[req_id];
|
||||
g_lsp.pending_responses.erase(req_id);
|
||||
|
||||
json::value val;
|
||||
try { val = json::parse(response_body); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
json::object resp;
|
||||
try { resp = val.as_object(); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
if (!resp.contains("result")) return -1;
|
||||
|
||||
*json_out = g_host->strdup(json::serialize(resp["result"]).c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int g_lsp_impl_get_completion(const char* uri, int line, int col, char** json_out) {
|
||||
if (!g_lsp.running) return -1;
|
||||
if (!uri || !json_out) return -1;
|
||||
|
||||
json::object position;
|
||||
position["line"] = line;
|
||||
position["character"] = col;
|
||||
try {
|
||||
json::object position;
|
||||
position["line"] = line;
|
||||
position["character"] = col;
|
||||
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
json::object text_doc;
|
||||
text_doc["uri"] = uri;
|
||||
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
params["position"] = position;
|
||||
json::object params;
|
||||
params["textDocument"] = text_doc;
|
||||
params["position"] = position;
|
||||
|
||||
int req_id = send_request("textDocument/completion", params);
|
||||
int req_id = send_request("textDocument/completion", params);
|
||||
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [req_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(req_id) > 0;
|
||||
});
|
||||
std::unique_lock<std::mutex> lock(g_lsp.mutex);
|
||||
bool got = g_lsp.cv.wait_for(lock, std::chrono::seconds(10), [req_id]() {
|
||||
return !g_lsp.running || g_lsp.pending_responses.count(req_id) > 0;
|
||||
});
|
||||
|
||||
if (!got || !g_lsp.running || g_lsp.pending_responses.count(req_id) == 0) {
|
||||
if (!got || !g_lsp.running || g_lsp.pending_responses.count(req_id) == 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string response_body = g_lsp.pending_responses[req_id];
|
||||
g_lsp.pending_responses.erase(req_id);
|
||||
|
||||
json::value val;
|
||||
try { val = json::parse(response_body); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
json::object resp;
|
||||
try { resp = val.as_object(); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
if (!resp.contains("result")) return -1;
|
||||
|
||||
*json_out = g_host->strdup(json::serialize(resp["result"]).c_str());
|
||||
return 0;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_completion: %s", e.what());
|
||||
*json_out = nullptr;
|
||||
return -1;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] get_completion: unknown exception");
|
||||
*json_out = nullptr;
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string response_body = g_lsp.pending_responses[req_id];
|
||||
g_lsp.pending_responses.erase(req_id);
|
||||
|
||||
json::value val;
|
||||
try { val = json::parse(response_body); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
json::object resp;
|
||||
try { resp = val.as_object(); }
|
||||
catch (...) { return -1; }
|
||||
|
||||
if (!resp.contains("result")) return -1;
|
||||
|
||||
*json_out = g_host->strdup(json::serialize(resp["result"]).c_str());
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
@@ -722,11 +805,19 @@ static int on_init(const dstalk_host_api_t* host) {
|
||||
}
|
||||
|
||||
static void on_shutdown() {
|
||||
if (g_lsp.running) {
|
||||
g_lsp_impl_stop();
|
||||
try {
|
||||
if (g_lsp.running) {
|
||||
g_lsp_impl_stop();
|
||||
}
|
||||
if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] shutdown");
|
||||
g_host = nullptr;
|
||||
} catch (const std::exception& e) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] on_shutdown: %s", e.what());
|
||||
g_host = nullptr;
|
||||
} catch (...) {
|
||||
if (g_host) g_host->log(DSTALK_LOG_ERROR, "[lsp] on_shutdown: unknown exception");
|
||||
g_host = nullptr;
|
||||
}
|
||||
if (g_host) g_host->log(DSTALK_LOG_INFO, "[lsp] shutdown");
|
||||
g_host = nullptr;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user