brintos

brintos / llvm-project-archived public Read only

0
0
Text · 12.1 KiB · 31b5a89 Raw
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