370 lines · cpp
1//===--- JSONTransport.cpp - sending and receiving LSP messages over JSON -===//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-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/Support/LSP/Transport.h"10#include "llvm/ADT/SmallString.h"11#include "llvm/Support/Error.h"12#include "llvm/Support/LSP/Logging.h"13#include "llvm/Support/LSP/Protocol.h"14#include <atomic>15#include <optional>16#include <system_error>17#include <utility>18 19using namespace llvm;20using namespace llvm::lsp;21 22//===----------------------------------------------------------------------===//23// Reply24//===----------------------------------------------------------------------===//25 26namespace {27/// Function object to reply to an LSP call.28/// Each instance must be called exactly once, otherwise:29/// - if there was no reply, an error reply is sent30/// - if there were multiple replies, only the first is sent31class Reply {32public:33 Reply(const llvm::json::Value &Id, StringRef Method, JSONTransport &Transport,34 std::mutex &TransportOutputMutex);35 Reply(Reply &&Other);36 Reply &operator=(Reply &&) = delete;37 Reply(const Reply &) = delete;38 Reply &operator=(const Reply &) = delete;39 40 void operator()(llvm::Expected<llvm::json::Value> Reply);41 42private:43 std::string Method;44 std::atomic<bool> Replied = {false};45 llvm::json::Value Id;46 JSONTransport *Transport;47 std::mutex &TransportOutputMutex;48};49} // namespace50 51Reply::Reply(const llvm::json::Value &Id, llvm::StringRef Method,52 JSONTransport &Transport, std::mutex &TransportOutputMutex)53 : Method(Method), Id(Id), Transport(&Transport),54 TransportOutputMutex(TransportOutputMutex) {}55 56Reply::Reply(Reply &&Other)57 : Method(Other.Method), Replied(Other.Replied.load()),58 Id(std::move(Other.Id)), Transport(Other.Transport),59 TransportOutputMutex(Other.TransportOutputMutex) {60 Other.Transport = nullptr;61}62 63void Reply::operator()(llvm::Expected<llvm::json::Value> Reply) {64 if (Replied.exchange(true)) {65 Logger::error("Replied twice to message {0}({1})", Method, Id);66 assert(false && "must reply to each call only once!");67 return;68 }69 assert(Transport && "expected valid transport to reply to");70 71 std::lock_guard<std::mutex> TransportLock(TransportOutputMutex);72 if (Reply) {73 Logger::info("--> reply:{0}({1})", Method, Id);74 Transport->reply(std::move(Id), std::move(Reply));75 } else {76 llvm::Error Error = Reply.takeError();77 Logger::info("--> reply:{0}({1}): {2}", Method, Id, Error);78 Transport->reply(std::move(Id), std::move(Error));79 }80}81 82//===----------------------------------------------------------------------===//83// MessageHandler84//===----------------------------------------------------------------------===//85 86bool MessageHandler::onNotify(llvm::StringRef Method, llvm::json::Value Value) {87 Logger::info("--> {0}", Method);88 89 if (Method == "exit")90 return false;91 if (Method == "$cancel") {92 // TODO: Add support for cancelling requests.93 } else {94 auto It = NotificationHandlers.find(Method);95 if (It != NotificationHandlers.end())96 It->second(std::move(Value));97 }98 return true;99}100 101bool MessageHandler::onCall(llvm::StringRef Method, llvm::json::Value Params,102 llvm::json::Value Id) {103 Logger::info("--> {0}({1})", Method, Id);104 105 Reply Reply(Id, Method, Transport, TransportOutputMutex);106 107 auto It = MethodHandlers.find(Method);108 if (It != MethodHandlers.end()) {109 It->second(std::move(Params), std::move(Reply));110 } else {111 Reply(llvm::make_error<LSPError>("method not found: " + Method.str(),112 ErrorCode::MethodNotFound));113 }114 return true;115}116 117bool MessageHandler::onReply(llvm::json::Value Id,118 llvm::Expected<llvm::json::Value> Result) {119 // Find the response handler in the mapping. If it exists, move it out of the120 // mapping and erase it.121 ResponseHandlerTy ResponseHandler;122 {123 std::lock_guard<std::mutex> responseHandlersLock(ResponseHandlersMutex);124 auto It = ResponseHandlers.find(debugString(Id));125 if (It != ResponseHandlers.end()) {126 ResponseHandler = std::move(It->second);127 ResponseHandlers.erase(It);128 }129 }130 131 // If we found a response handler, invoke it. Otherwise, log an error.132 if (ResponseHandler.second) {133 Logger::info("--> reply:{0}({1})", ResponseHandler.first, Id);134 ResponseHandler.second(std::move(Id), std::move(Result));135 } else {136 Logger::error(137 "received a reply with ID {0}, but there was no such outgoing request",138 Id);139 if (!Result)140 llvm::consumeError(Result.takeError());141 }142 return true;143}144 145//===----------------------------------------------------------------------===//146// JSONTransport147//===----------------------------------------------------------------------===//148 149/// Encode the given error as a JSON object.150static llvm::json::Object encodeError(llvm::Error Error) {151 std::string Message;152 ErrorCode Code = ErrorCode::UnknownErrorCode;153 auto HandlerFn = [&](const LSPError &LspError) -> llvm::Error {154 Message = LspError.message;155 Code = LspError.code;156 return llvm::Error::success();157 };158 if (llvm::Error Unhandled = llvm::handleErrors(std::move(Error), HandlerFn))159 Message = llvm::toString(std::move(Unhandled));160 161 return llvm::json::Object{162 {"message", std::move(Message)},163 {"code", int64_t(Code)},164 };165}166 167/// Decode the given JSON object into an error.168llvm::Error decodeError(const llvm::json::Object &O) {169 StringRef Msg = O.getString("message").value_or("Unspecified error");170 if (std::optional<int64_t> Code = O.getInteger("code"))171 return llvm::make_error<LSPError>(Msg.str(), ErrorCode(*Code));172 return llvm::make_error<llvm::StringError>(llvm::inconvertibleErrorCode(),173 Msg.str());174}175 176void JSONTransport::notify(StringRef Method, llvm::json::Value Params) {177 sendMessage(llvm::json::Object{178 {"jsonrpc", "2.0"},179 {"method", Method},180 {"params", std::move(Params)},181 });182}183void JSONTransport::call(StringRef Method, llvm::json::Value Params,184 llvm::json::Value Id) {185 sendMessage(llvm::json::Object{186 {"jsonrpc", "2.0"},187 {"id", std::move(Id)},188 {"method", Method},189 {"params", std::move(Params)},190 });191}192void JSONTransport::reply(llvm::json::Value Id,193 llvm::Expected<llvm::json::Value> Result) {194 if (Result) {195 return sendMessage(llvm::json::Object{196 {"jsonrpc", "2.0"},197 {"id", std::move(Id)},198 {"result", std::move(*Result)},199 });200 }201 202 sendMessage(llvm::json::Object{203 {"jsonrpc", "2.0"},204 {"id", std::move(Id)},205 {"error", encodeError(Result.takeError())},206 });207}208 209llvm::Error JSONTransport::run(MessageHandler &Handler) {210 std::string Json;211 while (!In->isEndOfInput()) {212 if (In->hasError()) {213 return llvm::errorCodeToError(214 std::error_code(errno, std::system_category()));215 }216 217 if (succeeded(In->readMessage(Json))) {218 if (llvm::Expected<llvm::json::Value> Doc = llvm::json::parse(Json)) {219 if (!handleMessage(std::move(*Doc), Handler))220 return llvm::Error::success();221 } else {222 Logger::error("JSON parse error: {0}", llvm::toString(Doc.takeError()));223 }224 }225 }226 return llvm::errorCodeToError(std::make_error_code(std::errc::io_error));227}228 229void JSONTransport::sendMessage(llvm::json::Value Msg) {230 OutputBuffer.clear();231 llvm::raw_svector_ostream os(OutputBuffer);232 os << llvm::formatv(PrettyOutput ? "{0:2}\n" : "{0}", Msg);233 Out << "Content-Length: " << OutputBuffer.size() << "\r\n\r\n"234 << OutputBuffer;235 Out.flush();236 Logger::debug(">>> {0}\n", OutputBuffer);237}238 239bool JSONTransport::handleMessage(llvm::json::Value Msg,240 MessageHandler &Handler) {241 // Message must be an object with "jsonrpc":"2.0".242 llvm::json::Object *Object = Msg.getAsObject();243 if (!Object ||244 Object->getString("jsonrpc") != std::optional<StringRef>("2.0"))245 return false;246 247 // `id` may be any JSON value. If absent, this is a notification.248 std::optional<llvm::json::Value> Id;249 if (llvm::json::Value *I = Object->get("id"))250 Id = std::move(*I);251 std::optional<StringRef> Method = Object->getString("method");252 253 // This is a response.254 if (!Method) {255 if (!Id)256 return false;257 if (auto *Err = Object->getObject("error"))258 return Handler.onReply(std::move(*Id), decodeError(*Err));259 // result should be given, use null if not.260 llvm::json::Value Result = nullptr;261 if (llvm::json::Value *R = Object->get("result"))262 Result = std::move(*R);263 return Handler.onReply(std::move(*Id), std::move(Result));264 }265 266 // Params should be given, use null if not.267 llvm::json::Value Params = nullptr;268 if (llvm::json::Value *P = Object->get("params"))269 Params = std::move(*P);270 271 if (Id)272 return Handler.onCall(*Method, std::move(Params), std::move(*Id));273 return Handler.onNotify(*Method, std::move(Params));274}275 276/// Tries to read a line up to and including \n.277/// If failing, feof(), ferror(), or shutdownRequested() will be set.278LogicalResult readLine(std::FILE *In, SmallVectorImpl<char> &Out) {279 // Big enough to hold any reasonable header line. May not fit content lines280 // in delimited mode, but performance doesn't matter for that mode.281 static constexpr int BufSize = 128;282 size_t Size = 0;283 Out.clear();284 for (;;) {285 Out.resize_for_overwrite(Size + BufSize);286 if (!std::fgets(&Out[Size], BufSize, In))287 return failure();288 289 clearerr(In);290 291 // If the line contained null bytes, anything after it (including \n) will292 // be ignored. Fortunately this is not a legal header or JSON.293 size_t Read = std::strlen(&Out[Size]);294 if (Read > 0 && Out[Size + Read - 1] == '\n') {295 Out.resize(Size + Read);296 return success();297 }298 Size += Read;299 }300}301 302// Returns std::nullopt when:303// - ferror(), feof(), or shutdownRequested() are set.304// - Content-Length is missing or empty (protocol error)305LogicalResult306JSONTransportInputOverFile::readStandardMessage(std::string &Json) {307 // A Language Server Protocol message starts with a set of HTTP headers,308 // delimited by \r\n, and terminated by an empty line (\r\n).309 unsigned long long ContentLength = 0;310 llvm::SmallString<128> Line;311 while (true) {312 if (feof(In) || hasError() || failed(readLine(In, Line)))313 return failure();314 315 // Content-Length is a mandatory header, and the only one we handle.316 StringRef LineRef = Line;317 if (LineRef.consume_front("Content-Length: ")) {318 llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);319 } else if (!LineRef.trim().empty()) {320 // It's another header, ignore it.321 continue;322 } else {323 // An empty line indicates the end of headers. Go ahead and read the JSON.324 break;325 }326 }327 328 // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"329 if (ContentLength == 0 || ContentLength > 1 << 30)330 return failure();331 332 Json.resize(ContentLength);333 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {334 Read = std::fread(&Json[Pos], 1, ContentLength - Pos, In);335 if (Read == 0)336 return failure();337 338 // If we're done, the error was transient. If we're not done, either it was339 // transient or we'll see it again on retry.340 clearerr(In);341 Pos += Read;342 }343 return success();344}345 346/// For lit tests we support a simplified syntax:347/// - messages are delimited by '// -----' on a line by itself348/// - lines starting with // are ignored.349/// This is a testing path, so favor simplicity over performance here.350/// When returning failure: feof(), ferror(), or shutdownRequested() will be351/// set.352LogicalResult353JSONTransportInputOverFile::readDelimitedMessage(std::string &Json) {354 Json.clear();355 llvm::SmallString<128> Line;356 while (succeeded(readLine(In, Line))) {357 StringRef LineRef = Line.str().trim();358 if (LineRef.starts_with("//")) {359 // Found a delimiter for the message.360 if (LineRef == "// -----")361 break;362 continue;363 }364 365 Json += Line;366 }367 368 return failure(ferror(In));369}370