brintos

brintos / llvm-project-archived public Read only

0
0
Text · 11.4 KiB · 3c0e198 Raw
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