1 //===-- LSPClient.cpp - Helper for ClangdLSPServer tests ------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "LSPClient.h" 10 #include "Protocol.h" 11 #include "TestFS.h" 12 #include "Transport.h" 13 #include "support/Logger.h" 14 #include "support/Threading.h" 15 #include "llvm/Support/Path.h" 16 #include "llvm/Support/raw_ostream.h" 17 #include "gtest/gtest.h" 18 #include <condition_variable> 19 #include <queue> 20 21 namespace clang { 22 namespace clangd { 23 24 llvm::Expected<llvm::json::Value> clang::clangd::LSPClient::CallResult::take() { 25 std::unique_lock<std::mutex> Lock(Mu); 26 if (!clangd::wait(Lock, CV, timeoutSeconds(10), 27 [this] { return Value.has_value(); })) { 28 ADD_FAILURE() << "No result from call after 10 seconds!"; 29 return llvm::json::Value(nullptr); 30 } 31 auto Res = std::move(*Value); 32 Value.reset(); 33 return Res; 34 } 35 36 llvm::json::Value LSPClient::CallResult::takeValue() { 37 auto ExpValue = take(); 38 if (!ExpValue) { 39 ADD_FAILURE() << "takeValue(): " << llvm::toString(ExpValue.takeError()); 40 return llvm::json::Value(nullptr); 41 } 42 return std::move(*ExpValue); 43 } 44 45 void LSPClient::CallResult::set(llvm::Expected<llvm::json::Value> V) { 46 std::lock_guard<std::mutex> Lock(Mu); 47 if (Value) { 48 ADD_FAILURE() << "Multiple replies"; 49 llvm::consumeError(V.takeError()); 50 return; 51 } 52 Value = std::move(V); 53 CV.notify_all(); 54 } 55 56 LSPClient::CallResult::~CallResult() { 57 if (Value && !*Value) { 58 ADD_FAILURE() << llvm::toString(Value->takeError()); 59 } 60 } 61 62 static void logBody(llvm::StringRef Method, llvm::json::Value V, bool Send) { 63 // We invert <<< and >>> as the combined log is from the server's viewpoint. 64 vlog("{0} {1}: {2:2}", Send ? "<<<" : ">>>", Method, V); 65 } 66 67 class LSPClient::TransportImpl : public Transport { 68 public: 69 std::pair<llvm::json::Value, CallResult *> addCallSlot() { 70 std::lock_guard<std::mutex> Lock(Mu); 71 unsigned ID = CallResults.size(); 72 CallResults.emplace_back(); 73 return {ID, &CallResults.back()}; 74 } 75 76 // A null action causes the transport to shut down. 77 void enqueue(std::function<void(MessageHandler &)> Action) { 78 std::lock_guard<std::mutex> Lock(Mu); 79 Actions.push(std::move(Action)); 80 CV.notify_all(); 81 } 82 83 std::vector<llvm::json::Value> takeNotifications(llvm::StringRef Method) { 84 std::vector<llvm::json::Value> Result; 85 { 86 std::lock_guard<std::mutex> Lock(Mu); 87 std::swap(Result, Notifications[Method]); 88 } 89 return Result; 90 } 91 92 private: 93 void reply(llvm::json::Value ID, 94 llvm::Expected<llvm::json::Value> V) override { 95 if (V) // Nothing additional to log for error. 96 logBody("reply", *V, /*Send=*/false); 97 std::lock_guard<std::mutex> Lock(Mu); 98 if (auto I = ID.getAsInteger()) { 99 if (*I >= 0 && *I < static_cast<int64_t>(CallResults.size())) { 100 CallResults[*I].set(std::move(V)); 101 return; 102 } 103 } 104 ADD_FAILURE() << "Invalid reply to ID " << ID; 105 llvm::consumeError(std::move(V).takeError()); 106 } 107 108 void notify(llvm::StringRef Method, llvm::json::Value V) override { 109 logBody(Method, V, /*Send=*/false); 110 std::lock_guard<std::mutex> Lock(Mu); 111 Notifications[Method].push_back(std::move(V)); 112 } 113 114 void call(llvm::StringRef Method, llvm::json::Value Params, 115 llvm::json::Value ID) override { 116 logBody(Method, Params, /*Send=*/false); 117 ADD_FAILURE() << "Unexpected server->client call " << Method; 118 } 119 120 llvm::Error loop(MessageHandler &H) override { 121 std::unique_lock<std::mutex> Lock(Mu); 122 while (true) { 123 CV.wait(Lock, [&] { return !Actions.empty(); }); 124 if (!Actions.front()) // Stop! 125 return llvm::Error::success(); 126 auto Action = std::move(Actions.front()); 127 Actions.pop(); 128 Lock.unlock(); 129 Action(H); 130 Lock.lock(); 131 } 132 } 133 134 std::mutex Mu; 135 std::deque<CallResult> CallResults; 136 std::queue<std::function<void(Transport::MessageHandler &)>> Actions; 137 std::condition_variable CV; 138 llvm::StringMap<std::vector<llvm::json::Value>> Notifications; 139 }; 140 141 LSPClient::LSPClient() : T(std::make_unique<TransportImpl>()) {} 142 LSPClient::~LSPClient() = default; 143 144 LSPClient::CallResult &LSPClient::call(llvm::StringRef Method, 145 llvm::json::Value Params) { 146 auto Slot = T->addCallSlot(); 147 T->enqueue([ID(Slot.first), Method(Method.str()), 148 Params(std::move(Params))](Transport::MessageHandler &H) { 149 logBody(Method, Params, /*Send=*/true); 150 H.onCall(Method, std::move(Params), ID); 151 }); 152 return *Slot.second; 153 } 154 155 void LSPClient::notify(llvm::StringRef Method, llvm::json::Value Params) { 156 T->enqueue([Method(Method.str()), 157 Params(std::move(Params))](Transport::MessageHandler &H) { 158 logBody(Method, Params, /*Send=*/true); 159 H.onNotify(Method, std::move(Params)); 160 }); 161 } 162 163 std::vector<llvm::json::Value> 164 LSPClient::takeNotifications(llvm::StringRef Method) { 165 return T->takeNotifications(Method); 166 } 167 168 void LSPClient::stop() { T->enqueue(nullptr); } 169 170 Transport &LSPClient::transport() { return *T; } 171 172 using Obj = llvm::json::Object; 173 174 llvm::json::Value LSPClient::uri(llvm::StringRef Path) { 175 std::string Storage; 176 if (!llvm::sys::path::is_absolute(Path)) 177 Path = Storage = testPath(Path); 178 return toJSON(URIForFile::canonicalize(Path, Path)); 179 } 180 llvm::json::Value LSPClient::documentID(llvm::StringRef Path) { 181 return Obj{{"uri", uri(Path)}}; 182 } 183 184 void LSPClient::didOpen(llvm::StringRef Path, llvm::StringRef Content) { 185 notify( 186 "textDocument/didOpen", 187 Obj{{"textDocument", 188 Obj{{"uri", uri(Path)}, {"text", Content}, {"languageId", "cpp"}}}}); 189 } 190 void LSPClient::didChange(llvm::StringRef Path, llvm::StringRef Content) { 191 notify("textDocument/didChange", 192 Obj{{"textDocument", documentID(Path)}, 193 {"contentChanges", llvm::json::Array{Obj{{"text", Content}}}}}); 194 } 195 void LSPClient::didClose(llvm::StringRef Path) { 196 notify("textDocument/didClose", Obj{{"textDocument", documentID(Path)}}); 197 } 198 199 void LSPClient::sync() { call("sync", nullptr).takeValue(); } 200 201 llvm::Optional<std::vector<llvm::json::Value>> 202 LSPClient::diagnostics(llvm::StringRef Path) { 203 sync(); 204 auto Notifications = takeNotifications("textDocument/publishDiagnostics"); 205 for (const auto &Notification : llvm::reverse(Notifications)) { 206 if (const auto *PubDiagsParams = Notification.getAsObject()) { 207 auto U = PubDiagsParams->getString("uri"); 208 auto *D = PubDiagsParams->getArray("diagnostics"); 209 if (!U || !D) { 210 ADD_FAILURE() << "Bad PublishDiagnosticsParams: " << PubDiagsParams; 211 continue; 212 } 213 if (*U == uri(Path)) 214 return std::vector<llvm::json::Value>(D->begin(), D->end()); 215 } 216 } 217 return {}; 218 } 219 220 } // namespace clangd 221 } // namespace clang 222