337 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#include "Protocol.h" // For LSPError9#include "Transport.h"10#include "support/Cancellation.h"11#include "support/Logger.h"12#include "support/Shutdown.h"13#include "support/ThreadCrashReporter.h"14#include "llvm/ADT/SmallString.h"15#include "llvm/Support/Error.h"16#include <optional>17#include <system_error>18 19namespace clang {20namespace clangd {21namespace {22 23llvm::json::Object encodeError(llvm::Error E) {24 std::string Message;25 ErrorCode Code = ErrorCode::UnknownErrorCode;26 // FIXME: encode cancellation errors using RequestCancelled or ContentModified27 // as appropriate.28 if (llvm::Error Unhandled = llvm::handleErrors(29 std::move(E),30 [&](const CancelledError &C) -> llvm::Error {31 switch (C.Reason) {32 case static_cast<int>(ErrorCode::ContentModified):33 Code = ErrorCode::ContentModified;34 Message = "Request cancelled because the document was modified";35 break;36 default:37 Code = ErrorCode::RequestCancelled;38 Message = "Request cancelled";39 break;40 }41 return llvm::Error::success();42 },43 [&](const LSPError &L) -> llvm::Error {44 Message = L.Message;45 Code = L.Code;46 return llvm::Error::success();47 }))48 Message = llvm::toString(std::move(Unhandled));49 50 return llvm::json::Object{51 {"message", std::move(Message)},52 {"code", int64_t(Code)},53 };54}55 56llvm::Error decodeError(const llvm::json::Object &O) {57 llvm::StringRef Msg = O.getString("message").value_or("Unspecified error");58 if (auto Code = O.getInteger("code"))59 return llvm::make_error<LSPError>(Msg.str(), ErrorCode(*Code));60 return error(Msg.str());61}62 63class JSONTransport : public Transport {64public:65 JSONTransport(std::FILE *In, llvm::raw_ostream &Out,66 llvm::raw_ostream *InMirror, bool Pretty, JSONStreamStyle Style)67 : In(In), Out(Out), InMirror(InMirror ? *InMirror : llvm::nulls()),68 Pretty(Pretty), Style(Style) {}69 70 void notify(llvm::StringRef Method, llvm::json::Value Params) override {71 sendMessage(llvm::json::Object{72 {"jsonrpc", "2.0"},73 {"method", Method},74 {"params", std::move(Params)},75 });76 }77 void call(llvm::StringRef Method, llvm::json::Value Params,78 llvm::json::Value ID) override {79 sendMessage(llvm::json::Object{80 {"jsonrpc", "2.0"},81 {"id", std::move(ID)},82 {"method", Method},83 {"params", std::move(Params)},84 });85 }86 void reply(llvm::json::Value ID,87 llvm::Expected<llvm::json::Value> Result) override {88 if (Result) {89 sendMessage(llvm::json::Object{90 {"jsonrpc", "2.0"},91 {"id", std::move(ID)},92 {"result", std::move(*Result)},93 });94 } else {95 sendMessage(llvm::json::Object{96 {"jsonrpc", "2.0"},97 {"id", std::move(ID)},98 {"error", encodeError(Result.takeError())},99 });100 }101 }102 103 llvm::Error loop(MessageHandler &Handler) override {104 std::string JSON; // Messages may be large, reuse same big buffer.105 while (!feof(In)) {106 if (shutdownRequested())107 return error(std::make_error_code(std::errc::operation_canceled),108 "Got signal, shutting down");109 if (ferror(In))110 return llvm::errorCodeToError(llvm::errnoAsErrorCode());111 if (readRawMessage(JSON)) {112 ThreadCrashReporter ScopedReporter([&JSON]() {113 auto &OS = llvm::errs();114 OS << "Signalled while processing message:\n";115 OS << JSON << "\n";116 });117 if (auto Doc = llvm::json::parse(JSON)) {118 vlog(Pretty ? "<<< {0:2}\n" : "<<< {0}\n", *Doc);119 if (!handleMessage(std::move(*Doc), Handler))120 return llvm::Error::success(); // we saw the "exit" notification.121 } else {122 // Parse error. Log the raw message.123 vlog("<<< {0}\n", JSON);124 elog("JSON parse error: {0}", llvm::toString(Doc.takeError()));125 }126 }127 }128 return llvm::errorCodeToError(std::make_error_code(std::errc::io_error));129 }130 131private:132 // Dispatches incoming message to Handler onNotify/onCall/onReply.133 bool handleMessage(llvm::json::Value Message, MessageHandler &Handler);134 // Writes outgoing message to Out stream.135 void sendMessage(llvm::json::Value Message) {136 OutputBuffer.clear();137 llvm::raw_svector_ostream OS(OutputBuffer);138 OS << llvm::formatv(Pretty ? "{0:2}" : "{0}", Message);139 Out << "Content-Length: " << OutputBuffer.size() << "\r\n\r\n"140 << OutputBuffer;141 Out.flush();142 vlog(">>> {0}\n", OutputBuffer);143 }144 145 // Read raw string messages from input stream.146 bool readRawMessage(std::string &JSON) {147 return Style == JSONStreamStyle::Delimited ? readDelimitedMessage(JSON)148 : readStandardMessage(JSON);149 }150 bool readDelimitedMessage(std::string &JSON);151 bool readStandardMessage(std::string &JSON);152 153 llvm::SmallVector<char, 0> OutputBuffer;154 std::FILE *In;155 llvm::raw_ostream &Out;156 llvm::raw_ostream &InMirror;157 bool Pretty;158 JSONStreamStyle Style;159};160 161bool JSONTransport::handleMessage(llvm::json::Value Message,162 MessageHandler &Handler) {163 // Message must be an object with "jsonrpc":"2.0".164 auto *Object = Message.getAsObject();165 if (!Object ||166 Object->getString("jsonrpc") != std::optional<llvm::StringRef>("2.0")) {167 elog("Not a JSON-RPC 2.0 message: {0:2}", Message);168 return false;169 }170 // ID may be any JSON value. If absent, this is a notification.171 std::optional<llvm::json::Value> ID;172 if (auto *I = Object->get("id"))173 ID = std::move(*I);174 auto Method = Object->getString("method");175 if (!Method) { // This is a response.176 if (!ID) {177 elog("No method and no response ID: {0:2}", Message);178 return false;179 }180 if (auto *Err = Object->getObject("error"))181 return Handler.onReply(std::move(*ID), decodeError(*Err));182 // Result should be given, use null if not.183 llvm::json::Value Result = nullptr;184 if (auto *R = Object->get("result"))185 Result = std::move(*R);186 return Handler.onReply(std::move(*ID), std::move(Result));187 }188 // Params should be given, use null if not.189 llvm::json::Value Params = nullptr;190 if (auto *P = Object->get("params"))191 Params = std::move(*P);192 193 if (ID)194 return Handler.onCall(*Method, std::move(Params), std::move(*ID));195 return Handler.onNotify(*Method, std::move(Params));196}197 198// Tries to read a line up to and including \n.199// If failing, feof(), ferror(), or shutdownRequested() will be set.200bool readLine(std::FILE *In, llvm::SmallVectorImpl<char> &Out) {201 // Big enough to hold any reasonable header line. May not fit content lines202 // in delimited mode, but performance doesn't matter for that mode.203 static constexpr int BufSize = 128;204 size_t Size = 0;205 Out.clear();206 for (;;) {207 Out.resize_for_overwrite(Size + BufSize);208 // Handle EINTR which is sent when a debugger attaches on some platforms.209 if (!retryAfterSignalUnlessShutdown(210 nullptr, [&] { return std::fgets(&Out[Size], BufSize, In); }))211 return false;212 clearerr(In);213 // If the line contained null bytes, anything after it (including \n) will214 // be ignored. Fortunately this is not a legal header or JSON.215 size_t Read = std::strlen(&Out[Size]);216 if (Read > 0 && Out[Size + Read - 1] == '\n') {217 Out.resize(Size + Read);218 return true;219 }220 Size += Read;221 }222}223 224// Returns None when:225// - ferror(), feof(), or shutdownRequested() are set.226// - Content-Length is missing or empty (protocol error)227bool JSONTransport::readStandardMessage(std::string &JSON) {228 // A Language Server Protocol message starts with a set of HTTP headers,229 // delimited by \r\n, and terminated by an empty line (\r\n).230 unsigned long long ContentLength = 0;231 llvm::SmallString<128> Line;232 while (true) {233 if (feof(In) || ferror(In) || !readLine(In, Line))234 return false;235 InMirror << Line;236 237 llvm::StringRef LineRef = Line;238 239 // We allow comments in headers. Technically this isn't part240 241 // of the LSP specification, but makes writing tests easier.242 if (LineRef.starts_with("#"))243 continue;244 245 // Content-Length is a mandatory header, and the only one we handle.246 if (LineRef.consume_front("Content-Length: ")) {247 if (ContentLength != 0) {248 elog("Warning: Duplicate Content-Length header received. "249 "The previous value for this message ({0}) was ignored.",250 ContentLength);251 }252 llvm::getAsUnsignedInteger(LineRef.trim(), 0, ContentLength);253 continue;254 }255 256 // An empty line indicates the end of headers.257 // Go ahead and read the JSON.258 if (LineRef.trim().empty())259 break;260 261 // It's another header, ignore it.262 }263 264 // The fuzzer likes crashing us by sending "Content-Length: 9999999999999999"265 if (ContentLength > 1 << 30) { // 1024M266 elog("Refusing to read message with long Content-Length: {0}. "267 "Expect protocol errors",268 ContentLength);269 return false;270 }271 if (ContentLength == 0) {272 log("Warning: Missing Content-Length header, or zero-length message.");273 return false;274 }275 276 JSON.resize(ContentLength);277 for (size_t Pos = 0, Read; Pos < ContentLength; Pos += Read) {278 // Handle EINTR which is sent when a debugger attaches on some platforms.279 Read = retryAfterSignalUnlessShutdown(0, [&]{280 return std::fread(&JSON[Pos], 1, ContentLength - Pos, In);281 });282 if (Read == 0) {283 elog("Input was aborted. Read only {0} bytes of expected {1}.", Pos,284 ContentLength);285 return false;286 }287 InMirror << llvm::StringRef(&JSON[Pos], Read);288 clearerr(In); // If we're done, the error was transient. If we're not done,289 // either it was transient or we'll see it again on retry.290 Pos += Read;291 }292 return true;293}294 295// For lit tests we support a simplified syntax:296// - messages are delimited by '---' on a line by itself297// - lines starting with # are ignored.298// This is a testing path, so favor simplicity over performance here.299// When returning false: feof(), ferror(), or shutdownRequested() will be set.300bool JSONTransport::readDelimitedMessage(std::string &JSON) {301 JSON.clear();302 llvm::SmallString<128> Line;303 while (readLine(In, Line)) {304 InMirror << Line;305 auto LineRef = Line.str().trim();306 if (LineRef.starts_with("#")) // comment307 continue;308 309 // found a delimiter310 if (LineRef.rtrim() == "---")311 break;312 313 JSON += Line;314 }315 316 if (shutdownRequested())317 return false;318 if (ferror(In)) {319 elog("Input error while reading message!");320 return false;321 }322 return true; // Including at EOF323}324 325} // namespace326 327std::unique_ptr<Transport> newJSONTransport(std::FILE *In,328 llvm::raw_ostream &Out,329 llvm::raw_ostream *InMirror,330 bool Pretty,331 JSONStreamStyle Style) {332 return std::make_unique<JSONTransport>(In, Out, InMirror, Pretty, Style);333}334 335} // namespace clangd336} // namespace clang337