mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
LSP: Fixes URL decoding incoming file names.
This commit is contained in:
parent
2cc6610e40
commit
a2de5d4b45
@ -9,6 +9,7 @@ Compiler Features:
|
|||||||
* Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode.
|
* Standard JSON: Add a boolean field `settings.metadata.appendCBOR` that skips CBOR metadata from getting appended at the end of the bytecode.
|
||||||
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
|
* Yul Optimizer: Allow replacing the previously hard-coded cleanup sequence by specifying custom steps after a colon delimiter (``:``) in the sequence string.
|
||||||
* Language Server: Add basic document hover support.
|
* Language Server: Add basic document hover support.
|
||||||
|
* Language Server: Fixes URL decoding of incoming file names.
|
||||||
|
|
||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
@ -23,6 +24,7 @@ Important Bugfixes:
|
|||||||
|
|
||||||
Compiler Features:
|
Compiler Features:
|
||||||
* Code Generator: More efficient overflow checks for multiplication.
|
* Code Generator: More efficient overflow checks for multiplication.
|
||||||
|
* Yul Optimizer: Simplify the starting offset of zero-length operations to zero.
|
||||||
* Language Server: Analyze all files in a project by default (can be customized by setting ``'file-load-strategy'`` to ``'directly-opened-and-on-import'`` in LSP settings object).
|
* Language Server: Analyze all files in a project by default (can be customized by setting ``'file-load-strategy'`` to ``'directly-opened-and-on-import'`` in LSP settings object).
|
||||||
* Yul Optimizer: Simplify the starting offset of zero-length operations to zero.
|
* Yul Optimizer: Simplify the starting offset of zero-length operations to zero.
|
||||||
|
|
||||||
|
@ -73,23 +73,23 @@ string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const
|
|||||||
else if (_sourceUnitName.find("file://") == 0)
|
else if (_sourceUnitName.find("file://") == 0)
|
||||||
return _sourceUnitName;
|
return _sourceUnitName;
|
||||||
else if (regex_search(_sourceUnitName, windowsDriveLetterPath))
|
else if (regex_search(_sourceUnitName, windowsDriveLetterPath))
|
||||||
return "file:///" + _sourceUnitName;
|
return "file:///" + util::encodeURI(_sourceUnitName);
|
||||||
else if (
|
else if (
|
||||||
auto const resolvedPath = tryResolvePath(_sourceUnitName);
|
auto const resolvedPath = tryResolvePath(_sourceUnitName);
|
||||||
resolvedPath.message().empty()
|
resolvedPath.message().empty()
|
||||||
)
|
)
|
||||||
return "file://" + ensurePathIsUnixLike(resolvedPath.get().generic_string());
|
return "file://" + util::encodeURI(ensurePathIsUnixLike(resolvedPath.get().generic_string()));
|
||||||
else if (m_basePath.generic_string() != "/")
|
else if (m_basePath.generic_string() != "/")
|
||||||
return "file://" + m_basePath.generic_string() + "/" + _sourceUnitName;
|
return "file://" + util::encodeURI(m_basePath.generic_string() + "/" + _sourceUnitName);
|
||||||
else
|
else
|
||||||
// Avoid double-/ in case base-path itself is simply a UNIX root filesystem root.
|
// Avoid double-/ in case base-path itself is simply a UNIX root filesystem root.
|
||||||
return "file:///" + _sourceUnitName;
|
return "file:///" + util::encodeURI(_sourceUnitName);
|
||||||
}
|
}
|
||||||
|
|
||||||
string FileRepository::uriToSourceUnitName(string const& _path) const
|
string FileRepository::uriToSourceUnitName(string const& _path) const
|
||||||
{
|
{
|
||||||
lspRequire(boost::algorithm::starts_with(_path, "file://"), ErrorCode::InternalError, "URI must start with file://");
|
lspRequire(boost::algorithm::starts_with(_path, "file://"), ErrorCode::InternalError, "URI must start with file://");
|
||||||
return stripFileUriSchemePrefix(_path);
|
return stripFileUriSchemePrefix(util::decodeURI(_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
void FileRepository::setSourceByUri(string const& _uri, string _source)
|
void FileRepository::setSourceByUri(string const& _uri, string _source)
|
||||||
|
@ -396,7 +396,7 @@ 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 = stripFileUriSchemePrefix(util::decodeURI(rootPath));
|
||||||
}
|
}
|
||||||
else if (Json::Value rootPath = _args["rootPath"])
|
else if (Json::Value rootPath = _args["rootPath"])
|
||||||
rootPath = rootPath.asString();
|
rootPath = rootPath.asString();
|
||||||
@ -432,11 +432,11 @@ void LanguageServer::handleInitialized(MessageID, Json::Value const&)
|
|||||||
|
|
||||||
void LanguageServer::semanticTokensFull(MessageID _id, Json::Value const& _args)
|
void LanguageServer::semanticTokensFull(MessageID _id, Json::Value const& _args)
|
||||||
{
|
{
|
||||||
auto uri = _args["textDocument"]["uri"];
|
auto uri = _args["textDocument"]["uri"].asString();
|
||||||
|
|
||||||
compile();
|
compile();
|
||||||
|
|
||||||
auto const sourceName = m_fileRepository.uriToSourceUnitName(uri.as<string>());
|
auto const sourceName = m_fileRepository.uriToSourceUnitName(uri);
|
||||||
SourceUnit const& ast = m_compilerStack.ast(sourceName);
|
SourceUnit const& ast = m_compilerStack.ast(sourceName);
|
||||||
m_compilerStack.charStream(sourceName);
|
m_compilerStack.charStream(sourceName);
|
||||||
Json::Value data = SemanticTokensBuilder().build(ast, m_compilerStack.charStream(sourceName));
|
Json::Value data = SemanticTokensBuilder().build(ast, m_compilerStack.charStream(sourceName));
|
||||||
|
@ -43,7 +43,7 @@ set(sources
|
|||||||
)
|
)
|
||||||
|
|
||||||
add_library(solutil ${sources})
|
add_library(solutil ${sources})
|
||||||
target_link_libraries(solutil PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::system range-v3)
|
target_link_libraries(solutil PUBLIC jsoncpp Boost::boost Boost::filesystem Boost::system range-v3 fmt::fmt-header-only)
|
||||||
target_include_directories(solutil PUBLIC "${CMAKE_SOURCE_DIR}")
|
target_include_directories(solutil PUBLIC "${CMAKE_SOURCE_DIR}")
|
||||||
add_dependencies(solutil solidity_BuildInfo.h)
|
add_dependencies(solutil solidity_BuildInfo.h)
|
||||||
|
|
||||||
|
@ -25,8 +25,10 @@
|
|||||||
#include <libsolutil/StringUtils.h>
|
#include <libsolutil/StringUtils.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace std::string_view_literals;
|
||||||
using namespace solidity::util;
|
using namespace solidity::util;
|
||||||
|
|
||||||
bool solidity::util::stringWithinDistance(string const& _str1, string const& _str2, size_t _maxDistance, size_t _lenThreshold)
|
bool solidity::util::stringWithinDistance(string const& _str1, string const& _str2, size_t _maxDistance, size_t _lenThreshold)
|
||||||
@ -113,3 +115,50 @@ string solidity::util::suffixedVariableNameList(string const& _baseName, size_t
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string solidity::util::decodeURI(string const& _uri)
|
||||||
|
{
|
||||||
|
string decodedString;
|
||||||
|
for (size_t i = 0; i < _uri.size(); ++i)
|
||||||
|
{
|
||||||
|
char const ch = _uri[i];
|
||||||
|
if (ch != '%')
|
||||||
|
decodedString.push_back(ch);
|
||||||
|
else if (i + 2 < _uri.size())
|
||||||
|
{
|
||||||
|
char const buf[3] = { _uri[i + 1], _uri[i + 2], '\0' };
|
||||||
|
char* end = nullptr;
|
||||||
|
auto const numericValue = std::strtol(buf, &end, 16);
|
||||||
|
if (end == buf + 2)
|
||||||
|
{
|
||||||
|
decodedString.push_back(static_cast<char>(numericValue));
|
||||||
|
i += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return decodedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
string solidity::util::encodeURI(string const& _uri)
|
||||||
|
{
|
||||||
|
string encodedString;
|
||||||
|
for (char const ch: _uri)
|
||||||
|
{
|
||||||
|
// see https://en.wikipedia.org/wiki/Percent-encoding#Types_of_URI_characters
|
||||||
|
static auto constexpr allowedCharacters = string_view(
|
||||||
|
// unreserved characters
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz"
|
||||||
|
"0123456789"
|
||||||
|
"-_.~"
|
||||||
|
// reserved characters (special meaning depending on URI component)
|
||||||
|
"/:"
|
||||||
|
);
|
||||||
|
if (allowedCharacters.find(ch) != string_view::npos)
|
||||||
|
encodedString += ch;
|
||||||
|
else
|
||||||
|
encodedString += fmt::format("%{:02X}", static_cast<unsigned>(ch));
|
||||||
|
}
|
||||||
|
return encodedString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -50,6 +50,13 @@ std::string quotedAlternativesList(std::vector<std::string> const& suggestions);
|
|||||||
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
|
/// If @a _startSuffix == @a _endSuffix, the empty string is returned.
|
||||||
std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
|
std::string suffixedVariableNameList(std::string const& _baseName, size_t _startSuffix, size_t _endSuffix);
|
||||||
|
|
||||||
|
/// Decodes a URI with respect to %XX notation.
|
||||||
|
/// No URI-validity verification is performed but simply the URI decoded into non-escaping characters.
|
||||||
|
std::string decodeURI(std::string const& _uri);
|
||||||
|
|
||||||
|
/// Encodes a string into a URI conform notation.
|
||||||
|
std::string encodeURI(std::string const& _uri);
|
||||||
|
|
||||||
/// Joins collection of strings into one string with separators between, last separator can be different.
|
/// Joins collection of strings into one string with separators between, last separator can be different.
|
||||||
/// @param _list collection of strings to join
|
/// @param _list collection of strings to join
|
||||||
/// @param _separator defaults to ", "
|
/// @param _separator defaults to ", "
|
||||||
|
@ -210,6 +210,38 @@ BOOST_AUTO_TEST_CASE(test_format_number_readable_signed)
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(test_encodeURI)
|
||||||
|
{
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI(""), "");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI(" "), "%20");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI("%"), "%25");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI("Hello World."), "Hello%20World.");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI("\x01\x02\x7F"), "%01%02%7F");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI("C:\\readme.md"), "C:%5Creadme.md");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI("C:/readme.md"), "C:/readme.md");
|
||||||
|
BOOST_CHECK_EQUAL(util::encodeURI("/var/log"), "/var/log");
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(test_decodeURI)
|
||||||
|
{
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI(""), "");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI(""), "");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI(" ")," ");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%25"), "%");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("Hello%20World."), "Hello World.");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("Hello World."), "Hello World.");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%01%02%7F"), "\x01\x02\x7F");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("C%3A%5Creadme.md"), "C:\\readme.md");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("C:/readme.md"), "C:/readme.md");
|
||||||
|
|
||||||
|
// Decoding failure cases (there's not really a standard for that).
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%"), "");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%ZZ"), "ZZ");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%7Ge"), "7Ge");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%2F%2%2F"), "/2/");
|
||||||
|
BOOST_CHECK_EQUAL(util::decodeURI("%1G/%7G/%FG"), "1G/7G/FG");
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_SUITE_END()
|
BOOST_AUTO_TEST_SUITE_END()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
19
test/lsp.py
19
test/lsp.py
@ -11,6 +11,7 @@ import re
|
|||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
import urllib.parse
|
||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
@ -358,6 +359,10 @@ def extendEnd(marker, amount=1):
|
|||||||
newMarker["end"]["character"] += amount
|
newMarker["end"]["character"] += amount
|
||||||
return newMarker
|
return newMarker
|
||||||
|
|
||||||
|
# Convenience method to intuitively decode a URI.
|
||||||
|
def decodeURI(text: str) -> str:
|
||||||
|
return urllib.parse.unquote(text)
|
||||||
|
|
||||||
class TestParserException(Exception):
|
class TestParserException(Exception):
|
||||||
def __init__(self, incompleteResult, msg: str):
|
def __init__(self, incompleteResult, msg: str):
|
||||||
self.result = incompleteResult
|
self.result = incompleteResult
|
||||||
@ -590,11 +595,11 @@ class FileTestRunner:
|
|||||||
self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name, self.sub_dir)
|
self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name, self.sub_dir)
|
||||||
|
|
||||||
for diagnostics in published_diagnostics:
|
for diagnostics in published_diagnostics:
|
||||||
if not diagnostics["uri"].startswith(self.suite.project_root_uri + "/"):
|
if not decodeURI(decodeURI(diagnostics["uri"])).startswith(self.suite.project_root_uri + "/"):
|
||||||
raise Exception(
|
raise Exception(
|
||||||
f"'{self.test_name}.sol' imported file outside of test directory: '{diagnostics['uri']}'"
|
f"'{self.test_name}.sol' imported file outside of test directory: '{diagnostics['uri']}'"
|
||||||
)
|
)
|
||||||
self.open_tests.append(self.suite.normalizeUri(diagnostics["uri"]))
|
self.open_tests.append(self.suite.normalizeUri(decodeURI(diagnostics["uri"])))
|
||||||
|
|
||||||
self.suite.expect_equal(
|
self.suite.expect_equal(
|
||||||
len(published_diagnostics),
|
len(published_diagnostics),
|
||||||
@ -747,7 +752,9 @@ class FileTestRunner:
|
|||||||
if isinstance(actualResponseJson["result"], list):
|
if isinstance(actualResponseJson["result"], list):
|
||||||
for result in actualResponseJson["result"]:
|
for result in actualResponseJson["result"]:
|
||||||
if "uri" in result:
|
if "uri" in result:
|
||||||
result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/" + self.sub_dir + "/", "")
|
result["uri"] = decodeURI(result["uri"]).replace(
|
||||||
|
self.suite.project_root_uri + "/" + self.sub_dir + "/", ""
|
||||||
|
)
|
||||||
|
|
||||||
elif isinstance(actualResponseJson["result"], dict):
|
elif isinstance(actualResponseJson["result"], dict):
|
||||||
if "changes" in actualResponseJson["result"]:
|
if "changes" in actualResponseJson["result"]:
|
||||||
@ -813,7 +820,7 @@ class FileTestRunner:
|
|||||||
# Needs to be done before the loop or it might be called only after
|
# Needs to be done before the loop or it might be called only after
|
||||||
# we found "range" or "position"
|
# we found "range" or "position"
|
||||||
if "uri" in data:
|
if "uri" in data:
|
||||||
markers = self.suite.get_test_tags(data["uri"][:-len(".sol")], self.sub_dir)
|
markers = self.suite.get_test_tags(decodeURI(data["uri"])[:-len(".sol")], self.sub_dir)
|
||||||
|
|
||||||
for key, val in data.items():
|
for key, val in data.items():
|
||||||
if key == "range":
|
if key == "range":
|
||||||
@ -1020,7 +1027,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, test, sub_dir)
|
published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, test, sub_dir)
|
||||||
|
|
||||||
for file_diagnostics in published_diagnostics:
|
for file_diagnostics in published_diagnostics:
|
||||||
testname, local_sub_dir = split_path(self.normalizeUri(file_diagnostics["uri"]))
|
testname, local_sub_dir = split_path(self.normalizeUri(decodeURI(file_diagnostics["uri"])))
|
||||||
|
|
||||||
# Skip empty diagnostics within the same file
|
# Skip empty diagnostics within the same file
|
||||||
if len(file_diagnostics["diagnostics"]) == 0 and testname == test:
|
if len(file_diagnostics["diagnostics"]) == 0 and testname == test:
|
||||||
@ -1242,7 +1249,7 @@ class SolidityLSPTestSuite: # {{{
|
|||||||
for item in recursive_iter(content):
|
for item in recursive_iter(content):
|
||||||
if "uri" in item and "range" in item:
|
if "uri" in item and "range" in item:
|
||||||
try:
|
try:
|
||||||
markers = self.get_test_tags(item["uri"][:-len(".sol")], sub_dir)
|
markers = self.get_test_tags(decodeURI(item["uri"])[:-len(".sol")], sub_dir)
|
||||||
replace_range(item, markers)
|
replace_range(item, markers)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
# Skip over errors as this is user provided input that can
|
# Skip over errors as this is user provided input that can
|
||||||
|
Loading…
Reference in New Issue
Block a user