Skip to content

Commit f308f1a

Browse files
Always allow full filesystem access to LSP.
1 parent 49d27ea commit f308f1a

12 files changed

+339
-125
lines changed

Changelog.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ Compiler Features:
88
* Assembly-Json: Export: Include source list in `sourceList` field.
99
* Commandline Interface: option ``--pretty-json`` works also with the following options: ``--abi``, ``--asm-json``, ``--ast-compact-json``, ``--devdoc``, ``--storage-layout``, ``--userdoc``.
1010
* SMTChecker: Support ``abi.encodeCall`` taking into account the called selector.
11+
* Language Server: Allow full filesystem access to language server.
1112

1213

1314
Bugfixes:

libsolidity/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,4 @@ set(sources
176176

177177
add_library(solidity ${sources})
178178
target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only)
179+

libsolidity/lsp/FileRepository.cpp

Lines changed: 104 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,48 +17,130 @@
1717
// SPDX-License-Identifier: GPL-3.0
1818

1919
#include <libsolidity/lsp/FileRepository.h>
20+
#include <libsolidity/lsp/Utils.h>
21+
22+
#include <libsolutil/StringUtils.h>
23+
#include <libsolutil/CommonIO.h>
24+
25+
#include <range/v3/range/conversion.hpp>
26+
#include <range/v3/view/transform.hpp>
27+
28+
#include <regex>
2029

2130
using namespace std;
2231
using namespace solidity;
2332
using namespace solidity::lsp;
33+
using namespace solidity::frontend;
2434

25-
namespace
26-
{
35+
using solidity::util::readFileAsString;
36+
using solidity::util::joinHumanReadable;
2737

28-
string stripFilePrefix(string const& _path)
38+
FileRepository::FileRepository(boost::filesystem::path _basePath): m_basePath(std::move(_basePath))
2939
{
30-
if (_path.find("file://") == 0)
31-
return _path.substr(7);
32-
else
33-
return _path;
34-
}
35-
3640
}
3741

38-
string FileRepository::sourceUnitNameToClientPath(string const& _sourceUnitName) const
42+
string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const
3943
{
40-
if (m_sourceUnitNamesToClientPaths.count(_sourceUnitName))
41-
return m_sourceUnitNamesToClientPaths.at(_sourceUnitName);
44+
regex const windowsDriveLetterPath("^[a-zA-Z]:/");
45+
46+
if (m_sourceUnitNamesToUri.count(_sourceUnitName))
47+
return m_sourceUnitNamesToUri.at(_sourceUnitName);
4248
else if (_sourceUnitName.find("file://") == 0)
4349
return _sourceUnitName;
50+
else if (regex_search(_sourceUnitName, windowsDriveLetterPath))
51+
return "file:///" + _sourceUnitName;
52+
else if (_sourceUnitName.find("/") == 0)
53+
return "file://" + _sourceUnitName;
4454
else
45-
return "file://" + (m_fileReader.basePath() / _sourceUnitName).generic_string();
55+
return "file://" + m_basePath.generic_string() + "/" + _sourceUnitName;
4656
}
4757

48-
string FileRepository::clientPathToSourceUnitName(string const& _path) const
58+
string FileRepository::uriToSourceUnitName(string const& _path) const
4959
{
50-
return m_fileReader.cliPathToSourceUnitName(stripFilePrefix(_path));
60+
return stripFileUriSchemePrefix(_path);
5161
}
5262

53-
map<string, string> const& FileRepository::sourceUnits() const
63+
void FileRepository::setSourceByUri(string const& _uri, string _source)
5464
{
55-
return m_fileReader.sourceUnits();
65+
// This is needed for uris outside the base path. It can lead to collisions,
66+
// but we need to mostly rewrite this in a future version anyway.
67+
auto sourceUnitName = uriToSourceUnitName(_uri);
68+
m_sourceUnitNamesToUri.emplace(sourceUnitName, _uri);
69+
m_sourceCodes[sourceUnitName] = std::move(_source);
5670
}
5771

58-
void FileRepository::setSourceByClientPath(string const& _uri, string _text)
72+
frontend::ReadCallback::Result FileRepository::readFile(string const& _kind, string const& _sourceUnitName)
5973
{
60-
// This is needed for uris outside the base path. It can lead to collisions,
61-
// but we need to mostly rewrite this in a future version anyway.
62-
m_sourceUnitNamesToClientPaths.emplace(clientPathToSourceUnitName(_uri), _uri);
63-
m_fileReader.addOrUpdateFile(stripFilePrefix(_uri), move(_text));
74+
solAssert(
75+
_kind == ReadCallback::kindString(ReadCallback::Kind::ReadFile),
76+
"ReadFile callback used as callback kind " + _kind
77+
);
78+
79+
try
80+
{
81+
// File was read already. Use local store.
82+
if (m_sourceCodes.count(_sourceUnitName))
83+
return ReadCallback::Result{true, m_sourceCodes.at(_sourceUnitName)};
84+
85+
string const strippedSourceUnitName = stripFileUriSchemePrefix(_sourceUnitName);
86+
87+
if (
88+
boost::filesystem::path(strippedSourceUnitName).has_root_path() &&
89+
boost::filesystem::exists(strippedSourceUnitName)
90+
)
91+
{
92+
auto contents = readFileAsString(strippedSourceUnitName);
93+
solAssert(m_sourceCodes.count(_sourceUnitName) == 0, "");
94+
m_sourceCodes[_sourceUnitName] = contents;
95+
return ReadCallback::Result{true, move(contents)};
96+
}
97+
98+
vector<boost::filesystem::path> candidates;
99+
vector<reference_wrapper<boost::filesystem::path>> prefixes = {m_basePath};
100+
prefixes += (m_includePaths | ranges::to<vector<reference_wrapper<boost::filesystem::path>>>);
101+
102+
auto const pathToQuotedString = [](boost::filesystem::path const& _path) { return "\"" + _path.string() + "\""; };
103+
104+
for (auto const& prefix: prefixes)
105+
{
106+
boost::filesystem::path canonicalPath = boost::filesystem::path(prefix) / boost::filesystem::path(strippedSourceUnitName);
107+
108+
if (boost::filesystem::exists(canonicalPath))
109+
candidates.push_back(move(canonicalPath));
110+
}
111+
112+
if (candidates.empty())
113+
return ReadCallback::Result{
114+
false,
115+
"File not found. Searched the following locations: " +
116+
joinHumanReadable(prefixes | ranges::views::transform(pathToQuotedString), ", ") +
117+
"."
118+
};
119+
120+
if (candidates.size() >= 2)
121+
return ReadCallback::Result{
122+
false,
123+
"Ambiguous import. "
124+
"Multiple matching files found inside base path and/or include paths: " +
125+
joinHumanReadable(candidates | ranges::views::transform(pathToQuotedString), ", ") +
126+
"."
127+
};
128+
129+
if (!boost::filesystem::is_regular_file(candidates[0]))
130+
return ReadCallback::Result{false, "Not a valid file."};
131+
132+
auto contents = readFileAsString(candidates[0]);
133+
solAssert(m_sourceCodes.count(_sourceUnitName) == 0, "");
134+
m_sourceCodes[_sourceUnitName] = contents;
135+
return ReadCallback::Result{true, move(contents)};
136+
}
137+
catch (std::exception const& _exception)
138+
{
139+
return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)};
140+
}
141+
catch (...)
142+
{
143+
return ReadCallback::Result{false, "Unknown exception in read callback: " + boost::current_exception_diagnostic_information()};
144+
}
64145
}
146+

libsolidity/lsp/FileRepository.h

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,42 @@ namespace solidity::lsp
2828
class FileRepository
2929
{
3030
public:
31-
explicit FileRepository(boost::filesystem::path const& _basePath):
32-
m_fileReader(_basePath) {}
31+
explicit FileRepository(boost::filesystem::path _basePath);
3332

34-
boost::filesystem::path const& basePath() const { return m_fileReader.basePath(); }
33+
boost::filesystem::path const& basePath() const { return m_basePath; }
3534

3635
/// Translates a compiler-internal source unit name to an LSP client path.
37-
std::string sourceUnitNameToClientPath(std::string const& _sourceUnitName) const;
38-
/// Translates an LSP client path into a compiler-internal source unit name.
39-
std::string clientPathToSourceUnitName(std::string const& _uri) const;
36+
std::string sourceUnitNameToUri(std::string const& _sourceUnitName) const;
37+
38+
/// Translates an LSP file URI into a compiler-internal source unit name.
39+
std::string uriToSourceUnitName(std::string const& _uri) const;
4040

4141
/// @returns all sources by their compiler-internal source unit name.
42-
std::map<std::string, std::string> const& sourceUnits() const;
42+
StringMap const& sourceUnits() const noexcept { return m_sourceCodes; }
43+
4344
/// Changes the source identified by the LSP client path _uri to _text.
44-
void setSourceByClientPath(std::string const& _uri, std::string _text);
45+
void setSourceByUri(std::string const& _uri, std::string _text);
4546

46-
frontend::ReadCallback::Callback reader() { return m_fileReader.reader(); }
47+
void addOrUpdateFile(boost::filesystem::path const& _path, frontend::SourceCode _source);
48+
void setSourceUnits(StringMap _sources);
49+
frontend::ReadCallback::Result readFile(std::string const& _kind, std::string const& _sourceUnitName);
50+
frontend::ReadCallback::Callback reader()
51+
{
52+
return [this](std::string const& _kind, std::string const& _path) { return readFile(_kind, _path); };
53+
}
4754

4855
private:
49-
std::map<std::string, std::string> m_sourceUnitNamesToClientPaths;
50-
frontend::FileReader m_fileReader;
56+
/// Base path without URI scheme.
57+
boost::filesystem::path m_basePath;
58+
59+
/// Additional directories used for resolving relative paths in imports.
60+
std::vector<boost::filesystem::path> m_includePaths;
61+
62+
/// Mapping of source unit names to their URIs as understood by the client.
63+
StringMap m_sourceUnitNamesToUri;
64+
65+
/// Mapping of source unit names to their file content.
66+
StringMap m_sourceCodes;
5167
};
5268

5369
}

libsolidity/lsp/HandlerBase.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const
4747
{
4848
solAssert(_location.sourceName);
4949
Json::Value item = Json::objectValue;
50-
item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName);
50+
item["uri"] = fileRepository().sourceUnitNameToUri(*_location.sourceName);
5151
item["range"] = toRange(_location);
5252
return item;
5353
}
5454

5555
pair<string, LineColumn> HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const
5656
{
5757
string const uri = _args["textDocument"]["uri"].asString();
58-
string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri);
58+
string const sourceUnitName = fileRepository().uriToSourceUnitName(uri);
5959
if (!fileRepository().sourceUnits().count(sourceUnitName))
6060
BOOST_THROW_EXCEPTION(
6161
RequestError(ErrorCode::RequestFailed) <<

libsolidity/lsp/LanguageServer.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,6 @@
3737
#include <boost/filesystem.hpp>
3838
#include <boost/algorithm/string/predicate.hpp>
3939

40-
#include <fmt/format.h>
41-
4240
#include <ostream>
4341
#include <string>
4442

@@ -114,9 +112,9 @@ void LanguageServer::compile()
114112
swap(oldRepository, m_fileRepository);
115113

116114
for (string const& fileName: m_openFiles)
117-
m_fileRepository.setSourceByClientPath(
115+
m_fileRepository.setSourceByUri(
118116
fileName,
119-
oldRepository.sourceUnits().at(oldRepository.clientPathToSourceUnitName(fileName))
117+
oldRepository.sourceUnits().at(oldRepository.uriToSourceUnitName(fileName))
120118
);
121119

122120
// TODO: optimize! do not recompile if nothing has changed (file(s) not flagged dirty).
@@ -178,7 +176,7 @@ void LanguageServer::compileAndUpdateDiagnostics()
178176
for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit)
179177
{
180178
Json::Value params;
181-
params["uri"] = m_fileRepository.sourceUnitNameToClientPath(sourceUnitName);
179+
params["uri"] = m_fileRepository.sourceUnitNameToUri(sourceUnitName);
182180
if (!diagnostics.empty())
183181
m_nonemptyDiagnostics.insert(sourceUnitName);
184182
params["diagnostics"] = move(diagnostics);
@@ -252,13 +250,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args)
252250
ErrorCode::InvalidParams,
253251
"rootUri only supports file URI scheme."
254252
);
255-
256-
rootPath = rootPath.substr(7);
253+
rootPath = stripFileUriSchemePrefix(rootPath);
257254
}
258255
else if (Json::Value rootPath = _args["rootPath"])
259256
rootPath = rootPath.asString();
260257

261-
m_fileRepository = FileRepository(boost::filesystem::path(rootPath));
258+
m_fileRepository = FileRepository(rootPath);
262259
if (_args["initializationOptions"].isObject())
263260
changeConfiguration(_args["initializationOptions"]);
264261

@@ -309,7 +306,7 @@ void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args)
309306
string text = _args["textDocument"]["text"].asString();
310307
string uri = _args["textDocument"]["uri"].asString();
311308
m_openFiles.insert(uri);
312-
m_fileRepository.setSourceByClientPath(uri, move(text));
309+
m_fileRepository.setSourceByUri(uri, move(text));
313310
compileAndUpdateDiagnostics();
314311
}
315312

@@ -327,7 +324,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
327324
"Invalid content reference."
328325
);
329326

330-
string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri);
327+
string const sourceUnitName = m_fileRepository.uriToSourceUnitName(uri);
331328
lspAssert(
332329
m_fileRepository.sourceUnits().count(sourceUnitName),
333330
ErrorCode::RequestFailed,
@@ -348,7 +345,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args)
348345
buffer.replace(static_cast<size_t>(change->start), static_cast<size_t>(change->end - change->start), move(text));
349346
text = move(buffer);
350347
}
351-
m_fileRepository.setSourceByClientPath(uri, move(text));
348+
m_fileRepository.setSourceByUri(uri, move(text));
352349
}
353350

354351
compileAndUpdateDiagnostics();

libsolidity/lsp/LanguageServer.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class LanguageServer
9292
Transport& m_client;
9393
std::map<std::string, MessageHandler> m_handlers;
9494

95-
/// Set of files known to be open by the client.
95+
/// Set of files (names in URI form) known to be open by the client.
9696
std::set<std::string> m_openFiles;
9797
/// Set of source unit names for which we sent diagnostics to the client in the last iteration.
9898
std::set<std::string> m_nonemptyDiagnostics;

0 commit comments

Comments
 (0)