Always allow full filesystem access to LSP.

This commit is contained in:
Christian Parpart 2022-04-25 15:35:41 +02:00
parent 49d27eaa5d
commit f308f1a1f8
12 changed files with 350 additions and 136 deletions

View File

@ -8,6 +8,7 @@ Compiler Features:
* Assembly-Json: Export: Include source list in `sourceList` field. * Assembly-Json: Export: Include source list in `sourceList` field.
* Commandline Interface: option ``--pretty-json`` works also with the following options: ``--abi``, ``--asm-json``, ``--ast-compact-json``, ``--devdoc``, ``--storage-layout``, ``--userdoc``. * Commandline Interface: option ``--pretty-json`` works also with the following options: ``--abi``, ``--asm-json``, ``--ast-compact-json``, ``--devdoc``, ``--storage-layout``, ``--userdoc``.
* SMTChecker: Support ``abi.encodeCall`` taking into account the called selector. * SMTChecker: Support ``abi.encodeCall`` taking into account the called selector.
* Language Server: Allow full filesystem access to language server.
Bugfixes: Bugfixes:

View File

@ -176,3 +176,4 @@ set(sources
add_library(solidity ${sources}) add_library(solidity ${sources})
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only) target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only)

View File

@ -17,48 +17,130 @@
// SPDX-License-Identifier: GPL-3.0 // SPDX-License-Identifier: GPL-3.0
#include <libsolidity/lsp/FileRepository.h> #include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/Utils.h>
#include <libsolutil/StringUtils.h>
#include <libsolutil/CommonIO.h>
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/transform.hpp>
#include <regex>
using namespace std; using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::lsp; using namespace solidity::lsp;
using namespace solidity::frontend;
namespace using solidity::util::readFileAsString;
{ using solidity::util::joinHumanReadable;
string stripFilePrefix(string const& _path) FileRepository::FileRepository(boost::filesystem::path _basePath): m_basePath(std::move(_basePath))
{ {
if (_path.find("file://") == 0)
return _path.substr(7);
else
return _path;
} }
} string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const
string FileRepository::sourceUnitNameToClientPath(string const& _sourceUnitName) const
{ {
if (m_sourceUnitNamesToClientPaths.count(_sourceUnitName)) regex const windowsDriveLetterPath("^[a-zA-Z]:/");
return m_sourceUnitNamesToClientPaths.at(_sourceUnitName);
if (m_sourceUnitNamesToUri.count(_sourceUnitName))
return m_sourceUnitNamesToUri.at(_sourceUnitName);
else if (_sourceUnitName.find("file://") == 0) else if (_sourceUnitName.find("file://") == 0)
return _sourceUnitName; return _sourceUnitName;
else if (regex_search(_sourceUnitName, windowsDriveLetterPath))
return "file:///" + _sourceUnitName;
else if (_sourceUnitName.find("/") == 0)
return "file://" + _sourceUnitName;
else else
return "file://" + (m_fileReader.basePath() / _sourceUnitName).generic_string(); return "file://" + m_basePath.generic_string() + "/" + _sourceUnitName;
} }
string FileRepository::clientPathToSourceUnitName(string const& _path) const string FileRepository::uriToSourceUnitName(string const& _path) const
{ {
return m_fileReader.cliPathToSourceUnitName(stripFilePrefix(_path)); return stripFileUriSchemePrefix(_path);
} }
map<string, string> const& FileRepository::sourceUnits() const void FileRepository::setSourceByUri(string const& _uri, string _source)
{
return m_fileReader.sourceUnits();
}
void FileRepository::setSourceByClientPath(string const& _uri, string _text)
{ {
// This is needed for uris outside the base path. It can lead to collisions, // This is needed for uris outside the base path. It can lead to collisions,
// but we need to mostly rewrite this in a future version anyway. // but we need to mostly rewrite this in a future version anyway.
m_sourceUnitNamesToClientPaths.emplace(clientPathToSourceUnitName(_uri), _uri); auto sourceUnitName = uriToSourceUnitName(_uri);
m_fileReader.addOrUpdateFile(stripFilePrefix(_uri), move(_text)); m_sourceUnitNamesToUri.emplace(sourceUnitName, _uri);
m_sourceCodes[sourceUnitName] = std::move(_source);
} }
frontend::ReadCallback::Result FileRepository::readFile(string const& _kind, string const& _sourceUnitName)
{
solAssert(
_kind == ReadCallback::kindString(ReadCallback::Kind::ReadFile),
"ReadFile callback used as callback kind " + _kind
);
try
{
// File was read already. Use local store.
if (m_sourceCodes.count(_sourceUnitName))
return ReadCallback::Result{true, m_sourceCodes.at(_sourceUnitName)};
string const strippedSourceUnitName = stripFileUriSchemePrefix(_sourceUnitName);
if (
boost::filesystem::path(strippedSourceUnitName).has_root_path() &&
boost::filesystem::exists(strippedSourceUnitName)
)
{
auto contents = readFileAsString(strippedSourceUnitName);
solAssert(m_sourceCodes.count(_sourceUnitName) == 0, "");
m_sourceCodes[_sourceUnitName] = contents;
return ReadCallback::Result{true, move(contents)};
}
vector<boost::filesystem::path> candidates;
vector<reference_wrapper<boost::filesystem::path>> prefixes = {m_basePath};
prefixes += (m_includePaths | ranges::to<vector<reference_wrapper<boost::filesystem::path>>>);
auto const pathToQuotedString = [](boost::filesystem::path const& _path) { return "\"" + _path.string() + "\""; };
for (auto const& prefix: prefixes)
{
boost::filesystem::path canonicalPath = boost::filesystem::path(prefix) / boost::filesystem::path(strippedSourceUnitName);
if (boost::filesystem::exists(canonicalPath))
candidates.push_back(move(canonicalPath));
}
if (candidates.empty())
return ReadCallback::Result{
false,
"File not found. Searched the following locations: " +
joinHumanReadable(prefixes | ranges::views::transform(pathToQuotedString), ", ") +
"."
};
if (candidates.size() >= 2)
return ReadCallback::Result{
false,
"Ambiguous import. "
"Multiple matching files found inside base path and/or include paths: " +
joinHumanReadable(candidates | ranges::views::transform(pathToQuotedString), ", ") +
"."
};
if (!boost::filesystem::is_regular_file(candidates[0]))
return ReadCallback::Result{false, "Not a valid file."};
auto contents = readFileAsString(candidates[0]);
solAssert(m_sourceCodes.count(_sourceUnitName) == 0, "");
m_sourceCodes[_sourceUnitName] = contents;
return ReadCallback::Result{true, move(contents)};
}
catch (std::exception const& _exception)
{
return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
}
catch (...)
{
return ReadCallback::Result{false, "Unknown exception in read callback: " + boost::current_exception_diagnostic_information()};
}
}

View File

@ -28,26 +28,42 @@ namespace solidity::lsp
class FileRepository class FileRepository
{ {
public: public:
explicit FileRepository(boost::filesystem::path const& _basePath): explicit FileRepository(boost::filesystem::path _basePath);
m_fileReader(_basePath) {}
boost::filesystem::path const& basePath() const { return m_fileReader.basePath(); } boost::filesystem::path const& basePath() const { return m_basePath; }
/// Translates a compiler-internal source unit name to an LSP client path. /// Translates a compiler-internal source unit name to an LSP client path.
std::string sourceUnitNameToClientPath(std::string const& _sourceUnitName) const; std::string sourceUnitNameToUri(std::string const& _sourceUnitName) const;
/// Translates an LSP client path into a compiler-internal source unit name.
std::string clientPathToSourceUnitName(std::string const& _uri) const; /// Translates an LSP file URI into a compiler-internal source unit name.
std::string uriToSourceUnitName(std::string const& _uri) const;
/// @returns all sources by their compiler-internal source unit name. /// @returns all sources by their compiler-internal source unit name.
std::map<std::string, std::string> const& sourceUnits() const; StringMap const& sourceUnits() const noexcept { return m_sourceCodes; }
/// Changes the source identified by the LSP client path _uri to _text.
void setSourceByClientPath(std::string const& _uri, std::string _text);
frontend::ReadCallback::Callback reader() { return m_fileReader.reader(); } /// Changes the source identified by the LSP client path _uri to _text.
void setSourceByUri(std::string const& _uri, std::string _text);
void addOrUpdateFile(boost::filesystem::path const& _path, frontend::SourceCode _source);
void setSourceUnits(StringMap _sources);
frontend::ReadCallback::Result readFile(std::string const& _kind, std::string const& _sourceUnitName);
frontend::ReadCallback::Callback reader()
{
return [this](std::string const& _kind, std::string const& _path) { return readFile(_kind, _path); };
}
private: private:
std::map<std::string, std::string> m_sourceUnitNamesToClientPaths; /// Base path without URI scheme.
frontend::FileReader m_fileReader; boost::filesystem::path m_basePath;
/// Additional directories used for resolving relative paths in imports.
std::vector<boost::filesystem::path> m_includePaths;
/// Mapping of source unit names to their URIs as understood by the client.
StringMap m_sourceUnitNamesToUri;
/// Mapping of source unit names to their file content.
StringMap m_sourceCodes;
}; };
} }

View File

@ -47,7 +47,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
{ {
solAssert(_location.sourceName); solAssert(_location.sourceName);
Json::Value item = Json::objectValue; Json::Value item = Json::objectValue;
item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName); item["uri"] = fileRepository().sourceUnitNameToUri(*_location.sourceName);
item["range"] = toRange(_location); item["range"] = toRange(_location);
return item; return item;
} }
@ -55,7 +55,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
{ {
string const uri = _args["textDocument"]["uri"].asString(); string const uri = _args["textDocument"]["uri"].asString();
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri); string const sourceUnitName = fileRepository().uriToSourceUnitName(uri);
if (!fileRepository().sourceUnits().count(sourceUnitName)) if (!fileRepository().sourceUnits().count(sourceUnitName))
BOOST_THROW_EXCEPTION( BOOST_THROW_EXCEPTION(
RequestError(ErrorCode::RequestFailed) << RequestError(ErrorCode::RequestFailed) <<

View File

@ -37,8 +37,6 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/algorithm/string/predicate.hpp> #include <boost/algorithm/string/predicate.hpp>
#include <fmt/format.h>
#include <ostream> #include <ostream>
#include <string> #include <string>
@ -114,9 +112,9 @@ void LanguageServer::compile()
swap(oldRepository, m_fileRepository); swap(oldRepository, m_fileRepository);
for (string const& fileName: m_openFiles) for (string const& fileName: m_openFiles)
m_fileRepository.setSourceByClientPath( m_fileRepository.setSourceByUri(
fileName, fileName,
oldRepository.sourceUnits().at(oldRepository.clientPathToSourceUnitName(fileName)) oldRepository.sourceUnits().at(oldRepository.uriToSourceUnitName(fileName))
); );
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty). // TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
@ -178,7 +176,7 @@ void LanguageServer::compileAndUpdateDiagnostics()
for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit) for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit)
{ {
Json::Value params; Json::Value params;
params["uri"] = m_fileRepository.sourceUnitNameToClientPath(sourceUnitName); params["uri"] = m_fileRepository.sourceUnitNameToUri(sourceUnitName);
if (!diagnostics.empty()) if (!diagnostics.empty())
m_nonemptyDiagnostics.insert(sourceUnitName); m_nonemptyDiagnostics.insert(sourceUnitName);
params["diagnostics"] = move(diagnostics); params["diagnostics"] = move(diagnostics);
@ -252,13 +250,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
ErrorCode::InvalidParams, ErrorCode::InvalidParams,
"rootUri only supports file URI scheme." "rootUri only supports file URI scheme."
); );
rootPath = stripFileUriSchemePrefix(rootPath);
rootPath = rootPath.substr(7);
} }
else if (Json::Value rootPath = _args["rootPath"]) else if (Json::Value rootPath = _args["rootPath"])
rootPath = rootPath.asString(); rootPath = rootPath.asString();
m_fileRepository = FileRepository(boost::filesystem::path(rootPath)); m_fileRepository = FileRepository(rootPath);
if (_args["initializationOptions"].isObject()) if (_args["initializationOptions"].isObject())
changeConfiguration(_args["initializationOptions"]); changeConfiguration(_args["initializationOptions"]);
@ -309,7 +306,7 @@ void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args)
string text = _args["textDocument"]["text"].asString(); string text = _args["textDocument"]["text"].asString();
string uri = _args["textDocument"]["uri"].asString(); string uri = _args["textDocument"]["uri"].asString();
m_openFiles.insert(uri); m_openFiles.insert(uri);
m_fileRepository.setSourceByClientPath(uri, move(text)); m_fileRepository.setSourceByUri(uri, move(text));
compileAndUpdateDiagnostics(); compileAndUpdateDiagnostics();
} }
@ -327,7 +324,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
"Invalid content reference." "Invalid content reference."
); );
string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri); string const sourceUnitName = m_fileRepository.uriToSourceUnitName(uri);
lspAssert( lspAssert(
m_fileRepository.sourceUnits().count(sourceUnitName), m_fileRepository.sourceUnits().count(sourceUnitName),
ErrorCode::RequestFailed, ErrorCode::RequestFailed,
@ -348,7 +345,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text)); buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text));
text = move(buffer); text = move(buffer);
} }
m_fileRepository.setSourceByClientPath(uri, move(text)); m_fileRepository.setSourceByUri(uri, move(text));
} }
compileAndUpdateDiagnostics(); compileAndUpdateDiagnostics();

View File

@ -92,7 +92,7 @@ private:
Transport& m_client; Transport& m_client;
std::map<std::string, MessageHandler> m_handlers; std::map<std::string, MessageHandler> m_handlers;
/// Set of files known to be open by the client. /// Set of files (names in URI form) known to be open by the client.
std::set<std::string> m_openFiles; std::set<std::string> m_openFiles;
/// Set of source unit names for which we sent diagnostics to the client in the last iteration. /// Set of source unit names for which we sent diagnostics to the client in the last iteration.
std::set<std::string> m_nonemptyDiagnostics; std::set<std::string> m_nonemptyDiagnostics;

View File

@ -22,31 +22,25 @@
#include <libsolutil/CommonIO.h> #include <libsolutil/CommonIO.h>
#include <liblangutil/Exceptions.h> #include <liblangutil/Exceptions.h>
#include <fmt/format.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <iostream> #include <iostream>
#include <sstream> #include <sstream>
#include <string>
#if defined(_WIN32)
#include <io.h>
#include <fcntl.h>
#endif
using namespace std; using namespace std;
using namespace solidity::lsp; using namespace solidity::lsp;
IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out): // {{{ Transport
m_input{_in}, optional<Json::Value> Transport::receive()
m_output{_out}
{
}
IOStreamTransport::IOStreamTransport():
IOStreamTransport(cin, cout)
{
}
bool IOStreamTransport::closed() const noexcept
{
return m_input.eof();
}
optional<Json::Value> IOStreamTransport::receive()
{ {
auto const headers = parseHeaders(); auto const headers = parseHeaders();
if (!headers) if (!headers)
@ -61,7 +55,7 @@ optional<Json::Value> IOStreamTransport::receive()
return nullopt; return nullopt;
} }
string const data = util::readBytes(m_input, stoul(headers->at("content-length"))); string const data = readBytes(stoul(headers->at("content-length")));
Json::Value jsonMessage; Json::Value jsonMessage;
string jsonParsingErrors; string jsonParsingErrors;
@ -75,29 +69,6 @@ optional<Json::Value> IOStreamTransport::receive()
return {move(jsonMessage)}; return {move(jsonMessage)};
} }
void IOStreamTransport::notify(string _method, Json::Value _message)
{
Json::Value json;
json["method"] = move(_method);
json["params"] = move(_message);
send(move(json));
}
void IOStreamTransport::reply(MessageID _id, Json::Value _message)
{
Json::Value json;
json["result"] = move(_message);
send(move(json), _id);
}
void IOStreamTransport::error(MessageID _id, ErrorCode _code, string _message)
{
Json::Value json;
json["error"]["code"] = static_cast<int>(_code);
json["error"]["message"] = move(_message);
send(move(json), _id);
}
void Transport::trace(std::string _message, Json::Value _extra) void Transport::trace(std::string _message, Json::Value _extra)
{ {
if (m_logTrace != TraceValue::Off) if (m_logTrace != TraceValue::Off)
@ -110,30 +81,13 @@ void Transport::trace(std::string _message, Json::Value _extra)
} }
} }
void IOStreamTransport::send(Json::Value _json, MessageID _id) optional<map<string, string>> Transport::parseHeaders()
{
solAssert(_json.isObject());
_json["jsonrpc"] = "2.0";
if (_id != Json::nullValue)
_json["id"] = _id;
string const jsonString = solidity::util::jsonCompactPrint(_json);
m_output << "Content-Length: " << jsonString.size() << "\r\n";
m_output << "\r\n";
m_output << jsonString;
m_output.flush();
}
optional<map<string, string>> IOStreamTransport::parseHeaders()
{ {
map<string, string> headers; map<string, string> headers;
while (true) while (true)
{ {
string line; auto line = getline();
getline(m_input, line);
if (boost::trim_copy(line).empty()) if (boost::trim_copy(line).empty())
break; break;
@ -141,13 +95,127 @@ optional<map<string, string>> IOStreamTransport::parseHeaders()
if (delimiterPos == string::npos) if (delimiterPos == string::npos)
return nullopt; return nullopt;
string name = boost::to_lower_copy(line.substr(0, delimiterPos)); auto const name = boost::to_lower_copy(line.substr(0, delimiterPos));
string value = line.substr(delimiterPos + 1); auto const value = line.substr(delimiterPos + 1);
if (!headers.emplace( if (!headers.emplace(boost::trim_copy(name), boost::trim_copy(value)).second)
boost::trim_copy(name),
boost::trim_copy(value)
).second)
return nullopt; return nullopt;
} }
return {move(headers)}; return {move(headers)};
} }
void Transport::notify(string _method, Json::Value _message)
{
Json::Value json;
json["method"] = move(_method);
json["params"] = move(_message);
send(move(json));
}
void Transport::reply(MessageID _id, Json::Value _message)
{
Json::Value json;
json["result"] = move(_message);
send(move(json), _id);
}
void Transport::error(MessageID _id, ErrorCode _code, string _message)
{
Json::Value json;
json["error"]["code"] = static_cast<int>(_code);
json["error"]["message"] = move(_message);
send(move(json), _id);
}
void Transport::send(Json::Value _json, MessageID _id)
{
solAssert(_json.isObject());
_json["jsonrpc"] = "2.0";
if (_id != Json::nullValue)
_json["id"] = _id;
// Trailing CRLF only for easier readability.
string const jsonString = solidity::util::jsonCompactPrint(_json);
writeBytes(fmt::format("Content-Length: {}\r\n\r\n", jsonString.size()));
writeBytes(jsonString);
flushOutput();
}
// }}}
// {{{ IOStreamTransport
IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out):
m_input{_in},
m_output{_out}
{
}
bool IOStreamTransport::closed() const noexcept
{
return m_input.eof();
}
std::string IOStreamTransport::readBytes(size_t _length)
{
return util::readBytes(m_input, _length);
}
std::string IOStreamTransport::getline()
{
string line;
std::getline(m_input, line);
return line;
}
void IOStreamTransport::writeBytes(std::string_view _data)
{
m_output.write(_data.data(), static_cast<std::streamsize>(_data.size()));
}
void IOStreamTransport::flushOutput()
{
m_output.flush();
}
// }}}
// {{{ StdioTransport
StdioTransport::StdioTransport()
{
#if defined(_WIN32)
// Attempt to change the modes of stdout from text to binary.
setmode(fileno(stdout), O_BINARY);
#endif
}
bool StdioTransport::closed() const noexcept
{
return feof(stdin);
}
std::string StdioTransport::readBytes(size_t _byteCount)
{
std::string buffer;
buffer.resize(_byteCount);
auto const n = fread(buffer.data(), 1, _byteCount, stdin);
if (n < _byteCount)
buffer.resize(n);
return buffer;
}
std::string StdioTransport::getline()
{
std::string line;
std::getline(std::cin, line);
return line;
}
void StdioTransport::writeBytes(std::string_view _data)
{
auto const bytesWritten = fwrite(_data.data(), 1, _data.size(), stdout);
solAssert(bytesWritten == _data.size());
}
void StdioTransport::flushOutput()
{
fflush(stdout);
}
// }}}

View File

@ -91,20 +91,44 @@ class Transport
public: public:
virtual ~Transport() = default; virtual ~Transport() = default;
std::optional<Json::Value> receive();
void notify(std::string _method, Json::Value _params);
void reply(MessageID _id, Json::Value _result);
void error(MessageID _id, ErrorCode _code, std::string _message);
virtual bool closed() const noexcept = 0; virtual bool closed() const noexcept = 0;
virtual std::optional<Json::Value> receive() = 0;
virtual void notify(std::string _method, Json::Value _params) = 0;
virtual void reply(MessageID _id, Json::Value _result) = 0;
virtual void error(MessageID _id, ErrorCode _code, std::string _message) = 0;
void trace(std::string _message, Json::Value _extra = Json::nullValue); void trace(std::string _message, Json::Value _extra = Json::nullValue);
TraceValue traceValue() const noexcept { return m_logTrace; } TraceValue traceValue() const noexcept { return m_logTrace; }
void setTrace(TraceValue _value) noexcept { m_logTrace = _value; } void setTrace(TraceValue _value) noexcept { m_logTrace = _value; }
private: private:
TraceValue m_logTrace = TraceValue::Off; TraceValue m_logTrace = TraceValue::Off;
protected:
/// Reads from the transport and parses the headers until the beginning
/// of the contents.
std::optional<std::map<std::string, std::string>> parseHeaders();
/// Consumes exactly @p _byteCount bytes, as needed for consuming
/// the message body from the transport line.
virtual std::string readBytes(size_t _byteCount) = 0;
// Mimmicks std::getline() on this Transport API.
virtual std::string getline() = 0;
/// Writes the given payload @p _data to transport.
/// This call may or may not buffer.
virtual void writeBytes(std::string_view _data) = 0;
/// Ensures transport output is flushed.
virtual void flushOutput() = 0;
/// Sends an arbitrary raw message to the client.
///
/// Used by the notify/reply/error function family.
virtual void send(Json::Value _message, MessageID _id = Json::nullValue);
}; };
/** /**
@ -119,27 +143,34 @@ public:
/// @param _out for example std::cout (stdout) /// @param _out for example std::cout (stdout)
IOStreamTransport(std::istream& _in, std::ostream& _out); IOStreamTransport(std::istream& _in, std::ostream& _out);
// Constructs a JSON transport using standard I/O streams.
IOStreamTransport();
bool closed() const noexcept override; bool closed() const noexcept override;
std::optional<Json::Value> receive() override;
void notify(std::string _method, Json::Value _params) override;
void reply(MessageID _id, Json::Value _result) override;
void error(MessageID _id, ErrorCode _code, std::string _message) override;
protected: protected:
/// Sends an arbitrary raw message to the client. std::string readBytes(size_t _byteCount) override;
/// std::string getline() override;
/// Used by the notify/reply/error function family. void writeBytes(std::string_view _data) override;
virtual void send(Json::Value _message, MessageID _id = Json::nullValue); void flushOutput() override;
/// Parses header section from the client including message-delimiting empty line.
std::optional<std::map<std::string, std::string>> parseHeaders();
private: private:
std::istream& m_input; std::istream& m_input;
std::ostream& m_output; std::ostream& m_output;
}; };
/**
* Standard I/O transport Layer utilizing stdin/stdout for communication.
*/
class StdioTransport: public Transport
{
public:
StdioTransport();
bool closed() const noexcept override;
protected:
std::string readBytes(size_t _byteCount) override;
std::string getline() override;
void writeBytes(std::string_view _data) override;
void flushOutput() override;
};
} }

View File

@ -22,7 +22,7 @@
#include <libsolidity/lsp/FileRepository.h> #include <libsolidity/lsp/FileRepository.h>
#include <libsolidity/lsp/Utils.h> #include <libsolidity/lsp/Utils.h>
#include <fmt/format.h> #include <regex>
#include <fstream> #include <fstream>
namespace solidity::lsp namespace solidity::lsp
@ -115,4 +115,15 @@ optional<SourceLocation> parseRange(FileRepository const& _fileRepository, strin
return start; return start;
} }
string stripFileUriSchemePrefix(string const& _path)
{
regex const windowsDriveLetterPath("^file:///[a-zA-Z]:/");
if (regex_search(_path, windowsDriveLetterPath))
return _path.substr(8);
if (_path.find("file://") == 0)
return _path.substr(7);
else
return _path;
}
} }

View File

@ -64,6 +64,13 @@ std::optional<langutil::SourceLocation> parseRange(
Json::Value const& _range Json::Value const& _range
); );
/// Strips the file:// URI prefix off the given path, if present,
/// also taking special care of Windows-drive-letter paths.
///
/// So file:///path/to/some/file.txt returns /path/to/some/file.txt, as well as,
/// file:///C:/file.txt will return C:/file.txt (forward-slash is okay on Windows).
std::string stripFileUriSchemePrefix(std::string const& _path);
/// Extracts the resolved declaration of the given expression AST node. /// Extracts the resolved declaration of the given expression AST node.
/// ///
/// This may for example be the type declaration of an identifier, /// This may for example be the type declaration of an identifier,

View File

@ -912,7 +912,7 @@ void CommandLineInterface::handleAst()
void CommandLineInterface::serveLSP() void CommandLineInterface::serveLSP()
{ {
lsp::IOStreamTransport transport; lsp::StdioTransport transport;
if (!lsp::LanguageServer{transport}.run()) if (!lsp::LanguageServer{transport}.run())
solThrow(CommandLineExecutionError, "LSP terminated abnormally."); solThrow(CommandLineExecutionError, "LSP terminated abnormally.");
} }