Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libsolidity/interface/StandardCompiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,8 @@ std::optional<Json> checkMetadataKeys(Json const& _input)
return formatFatalError(Error::Type::JSONError, "\"settings.metadata.appendCBOR\" must be Boolean");
if (_input.contains("useLiteralContent") && !_input["useLiteralContent"].is_boolean())
return formatFatalError(Error::Type::JSONError, "\"settings.metadata.useLiteralContent\" must be Boolean");
if (_input.contains("bytecodeHash") && !_input["bytecodeHash"].is_string())
return formatFatalError(Error::Type::JSONError, "\"settings.metadata.bytecodeHash\" must be a string");

static std::set<std::string> hashes{"ipfs", "bzzr1", "none"};
if (_input.contains("bytecodeHash") && !hashes.count(_input["bytecodeHash"].get<std::string>()))
Expand Down
18 changes: 16 additions & 2 deletions libsolidity/lsp/LanguageServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ namespace fs = boost::filesystem;
namespace
{

bool isFilesystemRoot(fs::path const& _path)
{
auto const normalizedPath = _path.lexically_normal();
return normalizedPath.has_root_path() && normalizedPath == normalizedPath.root_path();
}

bool resolvesToRegularFile(boost::filesystem::path _path, int maxRecursionDepth = 10)
{
fs::file_status fileStatus = fs::status(_path);
Expand Down Expand Up @@ -217,14 +223,20 @@ void LanguageServer::changeConfiguration(Json const& _settings)
std::vector<boost::filesystem::path> LanguageServer::allSolidityFilesFromProject() const
{
std::vector<fs::path> collectedPaths{};
auto const basePath = m_fileRepository.basePath().lexically_normal();

// The filesystem root is used as a fallback when the client does not provide a workspace root.
// Keep full filesystem access available for explicit imports, but never scan the entire filesystem eagerly.
if (isFilesystemRoot(basePath))
return collectedPaths;

// We explicitly decided against including all files from include paths but leave the possibility
// open for a future PR to enable such a feature to be optionally enabled (default disabled).
// Note: Newer versions of boost have deprecated symlink_option::recurse
#if (BOOST_VERSION < 107200)
auto directoryIterator = fs::recursive_directory_iterator(m_fileRepository.basePath(), fs::symlink_option::recurse);
auto directoryIterator = fs::recursive_directory_iterator(basePath, fs::symlink_option::recurse);
#else
auto directoryIterator = fs::recursive_directory_iterator(m_fileRepository.basePath(), fs::directory_options::follow_directory_symlink);
auto directoryIterator = fs::recursive_directory_iterator(basePath, fs::directory_options::follow_directory_symlink);
#endif
for (fs::directory_entry const& dirEntry: directoryIterator)
if (
Expand Down Expand Up @@ -412,6 +424,8 @@ void LanguageServer::handleInitialize(MessageID _id, Json const& _args)
m_fileRepository = FileRepository(rootPath, {});
if (_args.contains("initializationOptions") && _args["initializationOptions"].is_object())
changeConfiguration(_args["initializationOptions"]);
if (m_fileLoadStrategy == FileLoadStrategy::ProjectDirectory && isFilesystemRoot(m_fileRepository.basePath()))
m_fileLoadStrategy = FileLoadStrategy::DirectlyOpenedAndOnImported;

Json replyArgs;
replyArgs["serverInfo"]["name"] = "solc";
Expand Down
29 changes: 24 additions & 5 deletions scripts/regressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ def parseCmdLine(cls, description, args):
def run_cmd(command, logfile=None, env=None):
"""
Args:
command (str): command to run
command (list[str]): command and arguments to run
logfile (str): log file name
env (dict): dictionary holding key-value pairs for bash environment
variables
Expand All @@ -69,13 +69,33 @@ def run_cmd(command, logfile=None, env=None):
env = os.environ.copy()

with open(logfile, 'w', encoding='utf8') as logfh:
with subprocess.Popen(command, shell=True, executable='/bin/bash',
env=env, stdout=logfh,
with subprocess.Popen(command, env=env, stdout=logfh,
stderr=subprocess.STDOUT) as proc:
ret = proc.wait()
logfh.close()
return ret

@staticmethod
def run_corpus(fuzzer, corpus_dir, logfile, env=None):
if not env:
env = os.environ.copy()

corpus_files = []
for root, _, filenames in os.walk(corpus_dir):
corpus_files.extend(os.path.join(root, filename) for filename in filenames)

with open(logfile, 'w', encoding='utf8') as logfh:
for corpus_file in sorted(corpus_files):
with subprocess.Popen(
[fuzzer, corpus_file],
env=env,
stdout=logfh,
stderr=subprocess.STDOUT
) as proc:
if proc.wait() != 0:
return 255
return 0

def process_log(self, logfile):
"""
Args:
Expand Down Expand Up @@ -106,8 +126,7 @@ def run(self):
basename = os.path.basename(fuzzer)
logfile = os.path.join(self._logpath, f"{basename}.log")
corpus_dir = f"/tmp/solidity-fuzzing-corpus/{basename}_seed_corpus"
cmd = f"find {corpus_dir} -type f | xargs -n1 sh -c '{fuzzer} $0 || exit 255'"
self.run_cmd(cmd, logfile=logfile)
self.run_corpus(fuzzer, corpus_dir, logfile)
ret = self.process_log(logfile)
if not ret:
print(
Expand Down
17 changes: 14 additions & 3 deletions scripts/splitSources.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
# - 'false' if the file only had one source

import sys
import os
import traceback
from pathlib import Path


def uncaught_exception_hook(exc_type, exc_value, exc_traceback):
Expand All @@ -37,9 +37,20 @@ def writeSourceToFile(lines):
filePath, srcName = extractSourceName(lines[0])
# print("sourceName is ", srcName)
# print("filePath is", filePath)
outputRoot = Path.cwd().resolve()
sourcePath = Path(srcName)
if sourcePath.is_absolute():
raise ValueError("Source name must be a relative path: " + srcName)

outputPath = (outputRoot / sourcePath).resolve()
try:
outputPath.relative_to(outputRoot)
except ValueError:
raise ValueError("Source name escapes the output directory: " + srcName)

if filePath:
os.system("mkdir -p " + filePath)
with open(srcName, mode='a+', encoding='utf8', newline='') as f:
outputPath.parent.mkdir(parents=True, exist_ok=True)
with outputPath.open(mode='a+', encoding='utf8', newline='') as f:
for idx, line in enumerate(lines[1:]):
# write to file
if line[:12] != "==== Source:":
Expand Down
13 changes: 13 additions & 0 deletions test/cmdlineTests/standard_wrong_type_bytecodeHash/input.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"language": "Solidity",
"sources": {
"A.sol": {
"content": "pragma solidity >=0.0; contract A {}"
}
},
"settings": {
"metadata": {
"bytecodeHash": true
}
}
}
11 changes: 11 additions & 0 deletions test/cmdlineTests/standard_wrong_type_bytecodeHash/output.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"errors": [
{
"component": "general",
"formattedMessage": "\"settings.metadata.bytecodeHash\" must be a string",
"message": "\"settings.metadata.bytecodeHash\" must be a string",
"severity": "error",
"type": "JSONError"
}
]
}
16 changes: 16 additions & 0 deletions test/lsp.py
Original file line number Diff line number Diff line change
Expand Up @@ -1330,6 +1330,22 @@ def user_interaction_failed_autoupdate(self, test, sub_dir):
# }}}

# {{{ actual tests
def test_project_directory_without_workspace_root(self, solc: JsonRpcProcess) -> None:
"""
A missing workspace root falls back to the filesystem root internally. Project-directory
loading must not interpret that fallback as a request to scan the entire filesystem.
"""
self.setup_lsp(
solc,
expose_project_root=False,
file_load_strategy=FileLoadStrategy.ProjectDirectory
)
TEST_NAME = 'publish_diagnostics_3'
published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME)

self.expect_equal(len(published_diagnostics), 1, "Only the directly opened file is analyzed")
self.expect_equal(published_diagnostics[0]['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI")

def test_analyze_all_project_files_flat(self, solc: JsonRpcProcess) -> None:
"""
Tests the option (default) to analyze all .sol project files even when they have not been actively
Expand Down