diff --git a/.circleci/config.yml b/.circleci/config.yml index d42021373..06213ebcb 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -9,20 +9,20 @@ version: 2.1 parameters: ubuntu-2004-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-11 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:9928dc357829e475e8729c62a1c2d495dbb41cb9fe4c4b115a5568be8e1ed69e" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004-12 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:5087cc068b48787e89887804e632120b3e65bc5c25086bdf7b152be4fe5fc9ba" ubuntu-2004-clang-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-11 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:72fb9574c90e8ef908dce4c9dd9788ff4de708b504d970cd9146eed8911c313e" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu2004.clang-12 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:7f53f1bc3d89bdfb0725f604efbbec570d80ffa9b731b47a4842f4e286de0355" ubuntu-1604-clang-ossfuzz-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-16 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:fe54d8e5409827d43edb0dc8ad0d9e4232a675050ceb271c873b73e5ee267938" + # solbuildpackpusher/solidity-buildpack-deps:ubuntu1604.clang.ossfuzz-17 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:85b298c763adf5c516238246beb283640eb555e79e7ad6a8e7a6c9ed47ef6324" emscripten-docker-image: type: string - # solbuildpackpusher/solidity-buildpack-deps:emscripten-9 - default: "solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24" + # solbuildpackpusher/solidity-buildpack-deps:emscripten-10 + default: "solbuildpackpusher/solidity-buildpack-deps@sha256:bd23831e0025e35a106005b4ac06cb3618f690bfa2833a5881b573c02d35d9fc" evm-version: type: string default: london @@ -1280,6 +1280,15 @@ jobs: - run: name: "Run soltest" command: .circleci/soltest.ps1 + - run: + name: Install LSP test dependencies + command: python -m pip install --user deepdiff colorama + - run: + name: Inspect lsp.py + command: Get-Content ./test/lsp.py + - run: + name: Executing solc LSP test suite + command: python ./test/lsp.py .\build\solc\Release\solc.exe - store_test_results: *store_test_results - store_artifacts: *artifacts_test_results - gitter_notify_failure_unless_pr diff --git a/.circleci/osx_install_dependencies.sh b/.circleci/osx_install_dependencies.sh index cd0637eac..6b328e5a1 100755 --- a/.circleci/osx_install_dependencies.sh +++ b/.circleci/osx_install_dependencies.sh @@ -61,11 +61,11 @@ then ./scripts/install_obsolete_jsoncpp_1_7_4.sh # z3 - z3_version="4.8.14" + z3_version="4.8.16" z3_dir="z3-${z3_version}-x64-osx-10.16" z3_package="${z3_dir}.zip" wget "https://github.com/Z3Prover/z3/releases/download/z3-${z3_version}/${z3_package}" - validate_checksum "$z3_package" 1341671670e0c4e72da80815128a68975ee90816d50ceaf6bd820f06babe2cfd + validate_checksum "$z3_package" 71ed7b6d10c01198187df72cccb8eb6de6d9aa2bf9557b3dd052032524b598a5 unzip "$z3_package" rm "$z3_package" cp "${z3_dir}/bin/libz3.a" /usr/local/lib diff --git a/.circleci/soltest.sh b/.circleci/soltest.sh index 30bd1274c..91325f02e 100755 --- a/.circleci/soltest.sh +++ b/.circleci/soltest.sh @@ -74,7 +74,7 @@ do BOOST_TEST_ARGS_RUN=( "--color_output=no" "--show_progress=yes" - "--logger=JUNIT,error,test_results/$(get_logfile_basename "$run").xml" + "--logger=JUNIT,error,test_results/$(get_logfile_basename "$((CPUs * CIRCLE_NODE_INDEX + run))").xml" "${BOOST_TEST_ARGS[@]}" ) SOLTEST_ARGS=("--evm-version=$EVM" "${SOLTEST_FLAGS[@]}") diff --git a/CMakeLists.txt b/CMakeLists.txt index 759f69a53..a298f4044 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,7 +65,7 @@ configure_file("${CMAKE_SOURCE_DIR}/cmake/templates/license.h.in" include/licens include(EthOptions) configure_project(TESTS) -set(LATEST_Z3_VERSION "4.8.14") +set(LATEST_Z3_VERSION "4.8.16") set(MINIMUM_Z3_VERSION "4.8.0") find_package(Z3) if (${Z3_FOUND}) diff --git a/Changelog.md b/Changelog.md index 6c7f30d21..55fc62ed9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -20,11 +20,14 @@ Compiler Features: * 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``. * SMTChecker: Support ``abi.encodeCall`` taking into account the called selector. + * Language Server: Allow full filesystem access to language server. Bugfixes: + * Type Checker: Properly check restrictions of ``using ... global`` in conjunction with libraries. * Assembly-Json: Fix assembly json export to store jump types of operations in `jumpType` field instead of `value`. * TypeChecker: Convert parameters of function type to how they would be called for ``abi.encodeCall``. +* View Pure Checker: Mark ``returndatasize`` and ``returndatacopy`` as view to disallow them in inline assembly blocks in pure functions. diff --git a/docs/contracts/functions.rst b/docs/contracts/functions.rst index 890e8d177..8dbb2e275 100644 --- a/docs/contracts/functions.rst +++ b/docs/contracts/functions.rst @@ -318,12 +318,13 @@ will consume more gas than the 2300 gas stipend: - Sending Ether .. warning:: - Contracts that receive Ether directly (without a function call, i.e. using ``send`` or ``transfer``) - but do not define a receive Ether function or a payable fallback function - throw an exception, sending back the Ether (this was different - before Solidity v0.4.0). So if you want your contract to receive Ether, + When Ether is sent directly to a contract (without a function call, i.e. sender uses ``send`` or ``transfer``) + but the receiving contract does not define a receive Ether function or a payable fallback function, + an exception will be thrown, sending back the Ether (this was different + before Solidity v0.4.0). If you want your contract to receive Ether, you have to implement a receive Ether function (using payable fallback functions for receiving Ether is - not recommended, since it would not fail on interface confusions). + not recommended, since the fallback is invoked and would not fail for interface confusions + on the part of the sender). .. warning:: diff --git a/docs/examples/voting.rst b/docs/examples/voting.rst index 0e8901c3d..6e36f4e51 100644 --- a/docs/examples/voting.rst +++ b/docs/examples/voting.rst @@ -109,6 +109,7 @@ of votes. function delegate(address to) external { // assigns reference Voter storage sender = voters[msg.sender]; + require(sender.weight != 0, "You have no right to vote"); require(!sender.voted, "You already voted."); require(to != msg.sender, "Self-delegation is disallowed."); @@ -132,7 +133,7 @@ of votes. // modifies `voters[msg.sender].voted` Voter storage delegate_ = voters[to]; - // Voters cannot delegate to wallets that cannot vote. + // Voters cannot delegate to accounts that cannot vote. require(delegate_.weight >= 1); sender.voted = true; sender.delegate = to; diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp index 16c78541c..1a3b3e5da 100644 --- a/libevmasm/SemanticInformation.cpp +++ b/libevmasm/SemanticInformation.cpp @@ -481,6 +481,8 @@ bool SemanticInformation::invalidInPureFunctions(Instruction _instruction) case Instruction::EXTCODESIZE: case Instruction::EXTCODECOPY: case Instruction::EXTCODEHASH: + case Instruction::RETURNDATASIZE: + case Instruction::RETURNDATACOPY: case Instruction::BLOCKHASH: case Instruction::COINBASE: case Instruction::TIMESTAMP: diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 3d2845463..26c14976e 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -176,3 +176,4 @@ set(sources add_library(solidity ${sources}) target_link_libraries(solidity PUBLIC yul evmasm langutil smtutil solutil Boost::boost fmt::fmt-header-only) + diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 1c749b103..f0a13fbfa 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -3641,11 +3641,36 @@ void TypeChecker::endVisit(Literal const& _literal) void TypeChecker::endVisit(UsingForDirective const& _usingFor) { + if (_usingFor.global()) + { + if (m_currentContract || !_usingFor.typeName()) + { + solAssert(m_errorReporter.hasErrors()); + return; + } + solAssert(_usingFor.typeName()->annotation().type); + if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition()) + { + if (typeDefinition->scope() != m_currentSourceUnit) + m_errorReporter.typeError( + 4117_error, + _usingFor.location(), + "Can only use \"global\" with types defined in the same source unit at file level." + ); + } + else + m_errorReporter.typeError( + 8841_error, + _usingFor.location(), + "Can only use \"global\" with user-defined types." + ); + } + if (!_usingFor.usesBraces()) { solAssert(_usingFor.functionsOrLibrary().size() == 1); ContractDefinition const* library = dynamic_cast( - _usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration + _usingFor.functionsOrLibrary().front()->annotation().referencedDeclaration ); solAssert(library && library->isLibrary()); // No type checking for libraries @@ -3665,28 +3690,6 @@ void TypeChecker::endVisit(UsingForDirective const& _usingFor) ); solAssert(normalizedType); - if (_usingFor.global()) - { - if (m_currentContract) - solAssert(m_errorReporter.hasErrors()); - if (Declaration const* typeDefinition = _usingFor.typeName()->annotation().type->typeDefinition()) - { - if (typeDefinition->scope() != m_currentSourceUnit) - m_errorReporter.typeError( - 4117_error, - _usingFor.location(), - "Can only use \"global\" with types defined in the same source unit at file level." - ); - } - else - m_errorReporter.typeError( - 8841_error, - _usingFor.location(), - "Can only use \"global\" with user-defined types." - ); - } - - for (ASTPointer const& path: _usingFor.functionsOrLibrary()) { solAssert(path->annotation().referencedDeclaration); diff --git a/libsolidity/lsp/FileRepository.cpp b/libsolidity/lsp/FileRepository.cpp index 9c7f72e0c..e82343973 100644 --- a/libsolidity/lsp/FileRepository.cpp +++ b/libsolidity/lsp/FileRepository.cpp @@ -17,48 +17,130 @@ // SPDX-License-Identifier: GPL-3.0 #include +#include + +#include +#include + +#include +#include + +#include using namespace std; using namespace solidity; 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::sourceUnitNameToClientPath(string const& _sourceUnitName) const +string FileRepository::sourceUnitNameToUri(string const& _sourceUnitName) const { - if (m_sourceUnitNamesToClientPaths.count(_sourceUnitName)) - return m_sourceUnitNamesToClientPaths.at(_sourceUnitName); + regex const windowsDriveLetterPath("^[a-zA-Z]:/"); + + if (m_sourceUnitNamesToUri.count(_sourceUnitName)) + return m_sourceUnitNamesToUri.at(_sourceUnitName); else if (_sourceUnitName.find("file://") == 0) return _sourceUnitName; + else if (regex_search(_sourceUnitName, windowsDriveLetterPath)) + return "file:///" + _sourceUnitName; + else if (_sourceUnitName.find("/") == 0) + return "file://" + _sourceUnitName; 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 const& FileRepository::sourceUnits() const -{ - return m_fileReader.sourceUnits(); -} - -void FileRepository::setSourceByClientPath(string const& _uri, string _text) +void FileRepository::setSourceByUri(string const& _uri, string _source) { // 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. - m_sourceUnitNamesToClientPaths.emplace(clientPathToSourceUnitName(_uri), _uri); - m_fileReader.addOrUpdateFile(stripFilePrefix(_uri), move(_text)); + auto sourceUnitName = uriToSourceUnitName(_uri); + 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 candidates; + vector> prefixes = {m_basePath}; + prefixes += (m_includePaths | ranges::to>>); + + 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()}; + } +} + diff --git a/libsolidity/lsp/FileRepository.h b/libsolidity/lsp/FileRepository.h index b6aa5ee08..152c1d4be 100644 --- a/libsolidity/lsp/FileRepository.h +++ b/libsolidity/lsp/FileRepository.h @@ -28,26 +28,42 @@ namespace solidity::lsp class FileRepository { public: - explicit FileRepository(boost::filesystem::path const& _basePath): - m_fileReader(_basePath) {} + explicit FileRepository(boost::filesystem::path _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. - std::string sourceUnitNameToClientPath(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; + std::string sourceUnitNameToUri(std::string const& _sourceUnitName) 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. - std::map const& sourceUnits() const; - /// Changes the source identified by the LSP client path _uri to _text. - void setSourceByClientPath(std::string const& _uri, std::string _text); + StringMap const& sourceUnits() const noexcept { return m_sourceCodes; } - 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: - std::map m_sourceUnitNamesToClientPaths; - frontend::FileReader m_fileReader; + /// Base path without URI scheme. + boost::filesystem::path m_basePath; + + /// Additional directories used for resolving relative paths in imports. + std::vector 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; }; } diff --git a/libsolidity/lsp/HandlerBase.cpp b/libsolidity/lsp/HandlerBase.cpp index 0da19aad3..f40f189ab 100644 --- a/libsolidity/lsp/HandlerBase.cpp +++ b/libsolidity/lsp/HandlerBase.cpp @@ -47,7 +47,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const { solAssert(_location.sourceName); Json::Value item = Json::objectValue; - item["uri"] = fileRepository().sourceUnitNameToClientPath(*_location.sourceName); + item["uri"] = fileRepository().sourceUnitNameToUri(*_location.sourceName); item["range"] = toRange(_location); return item; } @@ -55,7 +55,7 @@ Json::Value HandlerBase::toJson(SourceLocation const& _location) const pair HandlerBase::extractSourceUnitNameAndLineColumn(Json::Value const& _args) const { string const uri = _args["textDocument"]["uri"].asString(); - string const sourceUnitName = fileRepository().clientPathToSourceUnitName(uri); + string const sourceUnitName = fileRepository().uriToSourceUnitName(uri); if (!fileRepository().sourceUnits().count(sourceUnitName)) BOOST_THROW_EXCEPTION( RequestError(ErrorCode::RequestFailed) << diff --git a/libsolidity/lsp/LanguageServer.cpp b/libsolidity/lsp/LanguageServer.cpp index 8a42ca104..44b0cf041 100644 --- a/libsolidity/lsp/LanguageServer.cpp +++ b/libsolidity/lsp/LanguageServer.cpp @@ -37,8 +37,6 @@ #include #include -#include - #include #include @@ -114,9 +112,9 @@ void LanguageServer::compile() swap(oldRepository, m_fileRepository); for (string const& fileName: m_openFiles) - m_fileRepository.setSourceByClientPath( + m_fileRepository.setSourceByUri( 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). @@ -178,7 +176,7 @@ void LanguageServer::compileAndUpdateDiagnostics() for (auto&& [sourceUnitName, diagnostics]: diagnosticsBySourceUnit) { Json::Value params; - params["uri"] = m_fileRepository.sourceUnitNameToClientPath(sourceUnitName); + params["uri"] = m_fileRepository.sourceUnitNameToUri(sourceUnitName); if (!diagnostics.empty()) m_nonemptyDiagnostics.insert(sourceUnitName); params["diagnostics"] = move(diagnostics); @@ -252,13 +250,12 @@ void LanguageServer::handleInitialize(MessageID _id, Json::Value const& _args) ErrorCode::InvalidParams, "rootUri only supports file URI scheme." ); - - rootPath = rootPath.substr(7); + rootPath = stripFileUriSchemePrefix(rootPath); } else if (Json::Value rootPath = _args["rootPath"]) rootPath = rootPath.asString(); - m_fileRepository = FileRepository(boost::filesystem::path(rootPath)); + m_fileRepository = FileRepository(rootPath); if (_args["initializationOptions"].isObject()) changeConfiguration(_args["initializationOptions"]); @@ -309,7 +306,7 @@ void LanguageServer::handleTextDocumentDidOpen(Json::Value const& _args) string text = _args["textDocument"]["text"].asString(); string uri = _args["textDocument"]["uri"].asString(); m_openFiles.insert(uri); - m_fileRepository.setSourceByClientPath(uri, move(text)); + m_fileRepository.setSourceByUri(uri, move(text)); compileAndUpdateDiagnostics(); } @@ -327,7 +324,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args) "Invalid content reference." ); - string const sourceUnitName = m_fileRepository.clientPathToSourceUnitName(uri); + string const sourceUnitName = m_fileRepository.uriToSourceUnitName(uri); lspAssert( m_fileRepository.sourceUnits().count(sourceUnitName), ErrorCode::RequestFailed, @@ -348,7 +345,7 @@ void LanguageServer::handleTextDocumentDidChange(Json::Value const& _args) buffer.replace(static_cast(change->start), static_cast(change->end - change->start), move(text)); text = move(buffer); } - m_fileRepository.setSourceByClientPath(uri, move(text)); + m_fileRepository.setSourceByUri(uri, move(text)); } compileAndUpdateDiagnostics(); diff --git a/libsolidity/lsp/LanguageServer.h b/libsolidity/lsp/LanguageServer.h index e827f6259..2a7951fb0 100644 --- a/libsolidity/lsp/LanguageServer.h +++ b/libsolidity/lsp/LanguageServer.h @@ -92,7 +92,7 @@ private: Transport& m_client; std::map 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 m_openFiles; /// Set of source unit names for which we sent diagnostics to the client in the last iteration. std::set m_nonemptyDiagnostics; diff --git a/libsolidity/lsp/Transport.cpp b/libsolidity/lsp/Transport.cpp index b82b34ec4..aa85fd6b1 100644 --- a/libsolidity/lsp/Transport.cpp +++ b/libsolidity/lsp/Transport.cpp @@ -22,31 +22,25 @@ #include #include +#include + #include #include #include +#include + + +#if defined(_WIN32) +#include +#include +#endif using namespace std; using namespace solidity::lsp; -IOStreamTransport::IOStreamTransport(istream& _in, ostream& _out): - m_input{_in}, - m_output{_out} -{ -} - -IOStreamTransport::IOStreamTransport(): - IOStreamTransport(cin, cout) -{ -} - -bool IOStreamTransport::closed() const noexcept -{ - return m_input.eof(); -} - -optional IOStreamTransport::receive() +// {{{ Transport +optional Transport::receive() { auto const headers = parseHeaders(); if (!headers) @@ -61,7 +55,7 @@ optional IOStreamTransport::receive() 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; string jsonParsingErrors; @@ -75,29 +69,6 @@ optional IOStreamTransport::receive() 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(_code); - json["error"]["message"] = move(_message); - send(move(json), _id); -} - void Transport::trace(std::string _message, Json::Value _extra) { 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) -{ - 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> IOStreamTransport::parseHeaders() +optional> Transport::parseHeaders() { map headers; while (true) { - string line; - getline(m_input, line); + auto line = getline(); if (boost::trim_copy(line).empty()) break; @@ -141,13 +95,127 @@ optional> IOStreamTransport::parseHeaders() if (delimiterPos == string::npos) return nullopt; - string name = boost::to_lower_copy(line.substr(0, delimiterPos)); - string value = line.substr(delimiterPos + 1); - if (!headers.emplace( - boost::trim_copy(name), - boost::trim_copy(value) - ).second) + auto const name = boost::to_lower_copy(line.substr(0, delimiterPos)); + auto const value = line.substr(delimiterPos + 1); + if (!headers.emplace(boost::trim_copy(name), boost::trim_copy(value)).second) return nullopt; } 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(_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(_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); +} +// }}} diff --git a/libsolidity/lsp/Transport.h b/libsolidity/lsp/Transport.h index d84edf49d..8aa89b3d2 100644 --- a/libsolidity/lsp/Transport.h +++ b/libsolidity/lsp/Transport.h @@ -91,20 +91,44 @@ class Transport public: virtual ~Transport() = default; + std::optional 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 std::optional 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); TraceValue traceValue() const noexcept { return m_logTrace; } void setTrace(TraceValue _value) noexcept { m_logTrace = _value; } - private: TraceValue m_logTrace = TraceValue::Off; + +protected: + /// Reads from the transport and parses the headers until the beginning + /// of the contents. + std::optional> 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) IOStreamTransport(std::istream& _in, std::ostream& _out); - // Constructs a JSON transport using standard I/O streams. - IOStreamTransport(); - bool closed() const noexcept override; - std::optional 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: - /// 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); - - /// Parses header section from the client including message-delimiting empty line. - std::optional> parseHeaders(); + std::string readBytes(size_t _byteCount) override; + std::string getline() override; + void writeBytes(std::string_view _data) override; + void flushOutput() override; private: std::istream& m_input; 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; +}; + } diff --git a/libsolidity/lsp/Utils.cpp b/libsolidity/lsp/Utils.cpp index 624d1f150..ef19b4c2e 100644 --- a/libsolidity/lsp/Utils.cpp +++ b/libsolidity/lsp/Utils.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include namespace solidity::lsp @@ -115,4 +115,15 @@ optional parseRange(FileRepository const& _fileRepository, strin 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; +} + } diff --git a/libsolidity/lsp/Utils.h b/libsolidity/lsp/Utils.h index 3594efba2..c6d40213e 100644 --- a/libsolidity/lsp/Utils.h +++ b/libsolidity/lsp/Utils.h @@ -64,6 +64,13 @@ std::optional parseRange( 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. /// /// This may for example be the type declaration of an identifier, diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index a1b49a81f..7bf0de045 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -142,7 +142,7 @@ ASTPointer Parser::parse(CharStream& _charStream) expectToken(Token::Semicolon); } else - fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition."); + fatalParserError(7858_error, "Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition."); } } solAssert(m_recursionDepth == 0, ""); diff --git a/libsolutil/Visitor.h b/libsolutil/Visitor.h index 497ed2f29..1147f9579 100644 --- a/libsolutil/Visitor.h +++ b/libsolutil/Visitor.h @@ -56,6 +56,17 @@ struct VisitorFallback { template R operator()(T&&) const { retur template<> struct VisitorFallback<> { template void operator()(T&&) const {} }; -template struct GenericVisitor: Visitors... { using Visitors::operator()...; }; +// MSVC. Empty base class optimization does not happen in some scenarios. +// Enforcing it with __declspec(empty_bases) avoids MSVC Debug test crash +// (Run-Time Check Failure #2 - Stack around the variable '....' was corrupted). +// See https://docs.microsoft.com/en-us/cpp/cpp/empty-bases, +// https://developercommunity.visualstudio.com/t/10005513. +#if defined(_MSC_VER) +#define SOLC_EMPTY_BASES __declspec(empty_bases) +#else +#define SOLC_EMPTY_BASES +#endif + +template struct SOLC_EMPTY_BASES GenericVisitor: Visitors... { using Visitors::operator()...; }; template GenericVisitor(Visitors...) -> GenericVisitor; } diff --git a/libyul/optimiser/StructuralSimplifier.cpp b/libyul/optimiser/StructuralSimplifier.cpp index 0b80c42cd..ca8f94239 100644 --- a/libyul/optimiser/StructuralSimplifier.cpp +++ b/libyul/optimiser/StructuralSimplifier.cpp @@ -93,31 +93,26 @@ void StructuralSimplifier::operator()(Block& _block) void StructuralSimplifier::simplify(std::vector& _statements) { - // Explicit local variables ifLambda, switchLambda, forLoopLambda are created to avoid MSVC C++17 Debug test crash - // (Run-Time Check Failure #2 - Stack around the variable '....' was corrupted). - // As soon as the issue is fixed, this workaround can be removed. - auto ifLambda = [&](If& _ifStmt) -> OptionalStatements - { - if (expressionAlwaysTrue(*_ifStmt.condition)) - return {std::move(_ifStmt.body.statements)}; - else if (expressionAlwaysFalse(*_ifStmt.condition)) - return {vector{}}; - return {}; + util::GenericVisitor visitor{ + util::VisitorFallback{}, + [&](If& _ifStmt) -> OptionalStatements { + if (expressionAlwaysTrue(*_ifStmt.condition)) + return {std::move(_ifStmt.body.statements)}; + else if (expressionAlwaysFalse(*_ifStmt.condition)) + return {vector{}}; + return {}; + }, + [&](Switch& _switchStmt) -> OptionalStatements { + if (std::optional const constExprVal = hasLiteralValue(*_switchStmt.expression)) + return replaceConstArgSwitch(_switchStmt, constExprVal.value()); + return {}; + }, + [&](ForLoop& _forLoop) -> OptionalStatements { + if (expressionAlwaysFalse(*_forLoop.condition)) + return {std::move(_forLoop.pre.statements)}; + return {}; + } }; - auto switchLambda = [&](Switch& _switchStmt) -> OptionalStatements - { - if (std::optional const constExprVal = hasLiteralValue(*_switchStmt.expression)) - return replaceConstArgSwitch(_switchStmt, constExprVal.value()); - return {}; - }; - auto forLoopLambda = [&](ForLoop& _forLoop) -> OptionalStatements - { - if (expressionAlwaysFalse(*_forLoop.condition)) - return {std::move(_forLoop.pre.statements)}; - return {}; - }; - - util::GenericVisitor visitor{util::VisitorFallback{}, ifLambda, switchLambda, forLoopLambda}; util::iterateReplacing( _statements, diff --git a/scripts/build_emscripten.sh b/scripts/build_emscripten.sh index 16e9078f3..09d421e5a 100755 --- a/scripts/build_emscripten.sh +++ b/scripts/build_emscripten.sh @@ -34,7 +34,7 @@ else BUILD_DIR="$1" fi -# solbuildpackpusher/solidity-buildpack-deps:emscripten-9 +# solbuildpackpusher/solidity-buildpack-deps:emscripten-10 docker run -v "$(pwd):/root/project" -w /root/project \ - solbuildpackpusher/solidity-buildpack-deps@sha256:d51534dfdd05ece86f69ed7beafd68c15b88606da00a4b7fe2873ccfbd0dce24\ + solbuildpackpusher/solidity-buildpack-deps@sha256:bd23831e0025e35a106005b4ac06cb3618f690bfa2833a5881b573c02d35d9fc\ ./scripts/ci/build_emscripten.sh "$BUILD_DIR" diff --git a/scripts/deps-ppa/static_z3.sh b/scripts/deps-ppa/static_z3.sh index 353a8a8d9..79393221c 100755 --- a/scripts/deps-ppa/static_z3.sh +++ b/scripts/deps-ppa/static_z3.sh @@ -25,9 +25,9 @@ set -ev keyid=70D110489D66E2F6 email=builds@ethereum.org packagename=z3-static -version=4.8.14 +version=4.8.16 -DISTRIBUTIONS="focal hirsute impish jammy" +DISTRIBUTIONS="focal impish jammy" for distribution in $DISTRIBUTIONS do diff --git a/scripts/tests.sh b/scripts/tests.sh index 5515a6869..80559f5e2 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -84,7 +84,7 @@ printTask "Testing Python scripts..." "$REPO_ROOT/test/pyscriptTests.py" printTask "Testing LSP..." -"$REPO_ROOT/scripts/test_solidity_lsp.py" "${SOLIDITY_BUILD_DIR}/solc/solc" +"$REPO_ROOT/test/lsp.py" "${SOLIDITY_BUILD_DIR}/solc/solc" printTask "Running commandline tests..." # Only run in parallel if this is run on CI infrastructure diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 40de51b52..273e47a43 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -912,7 +912,7 @@ void CommandLineInterface::handleAst() void CommandLineInterface::serveLSP() { - lsp::IOStreamTransport transport; + lsp::StdioTransport transport; if (!lsp::LanguageServer{transport}.run()) solThrow(CommandLineExecutionError, "LSP terminated abnormally."); } diff --git a/test/cmdlineTests/model_checker_invariants_all/err b/test/cmdlineTests/model_checker_invariants_all/err index 2f028037b..65228e880 100644 --- a/test/cmdlineTests/model_checker_invariants_all/err +++ b/test/cmdlineTests/model_checker_invariants_all/err @@ -5,10 +5,8 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Contract invariant(s) for model_checker_invariants_all/input.sol:test: -(x <= 0) +((x <= 0) || true) Reentrancy property(ies) for model_checker_invariants_all/input.sol:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) + = 1 -> Assertion failed at assert(x == 0) diff --git a/test/cmdlineTests/model_checker_invariants_all/input.sol b/test/cmdlineTests/model_checker_invariants_all/input.sol index f8601cf4f..8b90cb3c1 100644 --- a/test/cmdlineTests/model_checker_invariants_all/input.sol +++ b/test/cmdlineTests/model_checker_invariants_all/input.sol @@ -4,9 +4,6 @@ contract test { uint x; function f(address _a) public { _a.call(""); - assert(x < 10); + assert(x == 0); } - function g() public view { - assert(x < 10); - } } \ No newline at end of file diff --git a/test/cmdlineTests/model_checker_invariants_contract/err b/test/cmdlineTests/model_checker_invariants_contract/err index a5277bebf..e3058cca5 100644 --- a/test/cmdlineTests/model_checker_invariants_contract/err +++ b/test/cmdlineTests/model_checker_invariants_contract/err @@ -1,2 +1,2 @@ Info: Contract invariant(s) for model_checker_invariants_contract/input.sol:test: -(x <= 0) +((x <= 0) || true) diff --git a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err index fad76665e..855521836 100644 --- a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err +++ b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/err @@ -5,10 +5,8 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Contract invariant(s) for model_checker_invariants_contract_reentrancy/input.sol:test: -(x <= 0) +((x <= 0) || true) Reentrancy property(ies) for model_checker_invariants_contract_reentrancy/input.sol:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) + = 1 -> Assertion failed at assert(x == 0) diff --git a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol index f8601cf4f..8b90cb3c1 100644 --- a/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol +++ b/test/cmdlineTests/model_checker_invariants_contract_reentrancy/input.sol @@ -4,9 +4,6 @@ contract test { uint x; function f(address _a) public { _a.call(""); - assert(x < 10); + assert(x == 0); } - function g() public view { - assert(x < 10); - } } \ No newline at end of file diff --git a/test/cmdlineTests/model_checker_invariants_reentrancy/err b/test/cmdlineTests/model_checker_invariants_reentrancy/err index a2fd6075a..dc939704b 100644 --- a/test/cmdlineTests/model_checker_invariants_reentrancy/err +++ b/test/cmdlineTests/model_checker_invariants_reentrancy/err @@ -5,6 +5,6 @@ Warning: Return value of low-level calls not used. | ^^^^^^^^^^^ Info: Reentrancy property(ies) for model_checker_invariants_reentrancy/input.sol:test: -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) diff --git a/test/cmdlineTests/recovery_ast_empty_contract/err b/test/cmdlineTests/recovery_ast_empty_contract/err index f7324da63..fed054470 100644 --- a/test/cmdlineTests/recovery_ast_empty_contract/err +++ b/test/cmdlineTests/recovery_ast_empty_contract/err @@ -1,4 +1,4 @@ -Error: Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +Error: Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. --> recovery_ast_empty_contract/input.sol:3:1: | 3 | c diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract/output.json b/test/cmdlineTests/standard_model_checker_invariants_contract/output.json index 1cd236baf..385fcb31f 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract/output.json @@ -1,7 +1,7 @@ {"errors":[{"component":"general","errorCode":"1180","formattedMessage":"Info: Contract invariant(s) for A:test: -(x <= 0) +((x <= 0) || true) ","message":"Contract invariant(s) for A:test: -(x <= 0) +((x <= 0) || true) ","severity":"info","type":"Info"}],"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json index 56139e6a5..38f658653 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/input.json @@ -8,10 +8,7 @@ uint x; function f(address _a) public { _a.call(\"\"); - assert(x < 10); - } - function g() public view { - assert(x < 10); + assert(x == 10); } }" } diff --git a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json index f5ced7253..64d63c46c 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_contract_reentrancy/output.json @@ -4,22 +4,28 @@ 7 | \t\t\t\t\t\t_a.call(\"\"); | \t\t\t\t\t\t^^^^^^^^^^^ -","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"1180","formattedMessage":"Info: Contract invariant(s) for A:test: -(x <= 0) -Reentrancy property(ies) for A:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) - = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) +","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"6328","formattedMessage":"Warning: CHC: Assertion violation happens here. +Counterexample: +x = 0 +_a = 0x0 +Transaction trace: +test.constructor() +State: x = 0 +test.f(0x0) + _a.call(\"\") -- untrusted external call + --> A:8:7: + | +8 | \t\t\t\t\t\tassert(x == 10); + | \t\t\t\t\t\t^^^^^^^^^^^^^^^ -","message":"Contract invariant(s) for A:test: -(x <= 0) -Reentrancy property(ies) for A:test: -((!(x <= 0) || !( >= 3)) && (!(x <= 0) || (x' <= 0))) -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) - = 0 -> no errors - = 1 -> Assertion failed at assert(x < 10) - = 3 -> Assertion failed at assert(x < 10) -","severity":"info","type":"Info"}],"sources":{"A":{"id":0}}} +","message":"CHC: Assertion violation happens here. +Counterexample: +x = 0 +_a = 0x0 + +Transaction trace: +test.constructor() +State: x = 0 +test.f(0x0) + _a.call(\"\") -- untrusted external call","severity":"warning","sourceLocation":{"end":166,"file":"A","start":151},"type":"Warning"}],"sources":{"A":{"id":0}}} diff --git a/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json b/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json index 4158c3ae1..d7cb43429 100644 --- a/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json +++ b/test/cmdlineTests/standard_model_checker_invariants_reentrancy/output.json @@ -5,13 +5,13 @@ | \t\t\t\t\t\t^^^^^^^^^^^ ","message":"Return value of low-level calls not used.","severity":"warning","sourceLocation":{"end":143,"file":"A","start":132},"type":"Warning"},{"component":"general","errorCode":"1180","formattedMessage":"Info: Reentrancy property(ies) for A:test: -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) ","message":"Reentrancy property(ies) for A:test: -((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) +(((!(x <= 0) || (x' <= 0)) && (!(x <= 0) || ( <= 0))) || true) = 0 -> no errors = 1 -> Assertion failed at assert(x < 10) ","severity":"info","type":"Info"}],"sources":{"A":{"id":0}}} diff --git a/test/libsolidity/SMTCheckerTest.cpp b/test/libsolidity/SMTCheckerTest.cpp index 3b6b8e1b8..10084b014 100644 --- a/test/libsolidity/SMTCheckerTest.cpp +++ b/test/libsolidity/SMTCheckerTest.cpp @@ -69,7 +69,17 @@ SMTCheckerTest::SMTCheckerTest(string const& _filename): SyntaxTest(_filename, E else BOOST_THROW_EXCEPTION(runtime_error("Invalid SMT counterexample choice.")); - auto const& ignoreInv = m_reader.stringSetting("SMTIgnoreInv", "no"); + static auto removeInv = [](vector&& errors) { + vector filtered; + for (auto&& e: errors) + if (e.errorId != 1180_error) + filtered.emplace_back(e); + return filtered; + }; + if (m_modelCheckerSettings.invariants.invariants.empty()) + m_expectations = removeInv(move(m_expectations)); + + auto const& ignoreInv = m_reader.stringSetting("SMTIgnoreInv", "yes"); if (ignoreInv == "no") m_modelCheckerSettings.invariants = ModelCheckerInvariants::All(); else if (ignoreInv == "yes") diff --git a/test/libsolidity/lsp/didOpen_with_import.sol b/test/libsolidity/lsp/didOpen_with_import.sol index a335df759..93880e2cb 100644 --- a/test/libsolidity/lsp/didOpen_with_import.sol +++ b/test/libsolidity/lsp/didOpen_with_import.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: UNLICENSED pragma solidity >=0.8.0; -import './lib.sol'; +import './goto/lib.sol'; contract C { diff --git a/test/libsolidity/lsp/goto/goto_definition.sol b/test/libsolidity/lsp/goto/goto_definition.sol new file mode 100644 index 000000000..7415d3866 --- /dev/null +++ b/test/libsolidity/lsp/goto/goto_definition.sol @@ -0,0 +1,207 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +import "./lib.sol"; +// ^ @importDirective + +interface I +{ + function f(uint x) external returns (uint); + // ^ @functionF +} + +contract IA is I + // ^^ @IASymbol +{ + function f(uint x) public pure override returns (uint) { return x + 1; } +} + +contract IB is I +{ + function f(uint x) public pure override returns (uint) { return x + 2; } +} + +library IntLib +{ + function add(int self, int b) public pure returns (int) { return self + b; } + // ^^^ @IntLibAdd +} + +contract C +{ + I obj; + function virtual_inheritance() public payable + { + obj = new IA(); + // ^ @usingIASymbol + obj.f(1); // goto-definition should jump to definition of interface. + // ^ @virtualFunctionLookup + } + + using IntLib for *; + function using_for(int i) pure public + { + i.add(5); + // ^ @usingIntAdd + 14.add(4); + } + + function useLib(uint n) public payable returns (uint) + { + return Lib.add(n, 1); + // ^ @LibSymbol + // ^ @LibAddSymbol + } + + function enums(Color c) public pure returns (Color d) + // ^ @ColorSymbolInParameter + { + Color e = Color.Red; + // ^ @eVariableDeclaration + // ^ @RedEnumMemberAccess + if (c == e) + // ^ @eVariableAccess + d = Color.Green; + else + d = c; + } + + type Price is uint128; + // ^^^^^ @PriceDeclaration + function udlTest() public pure returns (uint128) + { + Price p = Price.wrap(128); + // ^ @PriceSymbol + // ^ @PriceInWrap + return Price.unwrap(p); + } + + function structCtorTest(uint8 v) public pure returns (uint8 result) + { + RGBColor memory c = RGBColor(v, 2 * v, 3 * v); + // ^ @RGBColorCursor + result = c.red; + int a; +// ^^^^^ @unusedLocalVar + } +} +// ---- +// goto_definition: @unusedLocalVar 2072 +// lib: @diagnostics 2072 +// -> textDocument/definition { +// "position": @importDirective +// } +// <- [ +// { +// "range": { +// "end": { +// "character": 0, +// "line": 0 +// }, +// "start": { +// "character": 0, +// "line": 0 +// } +// }, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @usingIASymbol +// } +// <- [ +// { +// "range": @IASymbol, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @virtualFunctionLookup +// } +// <- [ +// { +// "range": @functionF, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @usingIntAdd +// } +// <- [ +// { +// "range": @IntLibAdd, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @LibSymbol +// } +// <- [ +// { +// "range": @LibLibrary, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @LibAddSymbol +// } +// <- [ +// { +// "range": @addSymbol, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ColorSymbolInParameter +// } +// <- [ +// { +// "range": @ColorEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @RedEnumMemberAccess +// } +// <- [ +// { +// "range": @EnumMemberRed, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @eVariableAccess +// } +// <- [ +// { +// "range": @eVariableDeclaration, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @PriceSymbol +// } +// <- [ +// { +// "range": @PriceDeclaration, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @PriceInWrap +// } +// <- [ +// { +// "range": @PriceDeclaration, +// "uri": "goto_definition.sol" +// } +// ] +// -> textDocument/definition { +// "position": @RGBColorCursor +// } +// <- [ +// { +// "range": @RGBColorStruct, +// "uri": "lib.sol" +// } +// ] diff --git a/test/libsolidity/lsp/goto/goto_definition_imports.sol b/test/libsolidity/lsp/goto/goto_definition_imports.sol new file mode 100644 index 000000000..dd0267d71 --- /dev/null +++ b/test/libsolidity/lsp/goto/goto_definition_imports.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity >=0.8.0; + +import {Weather as Wetter} from "./lib.sol"; +// ^ @wheatherImportCursor +import "./lib.sol" as That; +// ^^^^ @ThatImport + +contract C +{ + function test_symbol_alias() public pure returns (Wetter result) + // ^ @WetterCursor + { + result = Wetter.Sunny; + } + + function test_library_alias() public pure returns (That.Color result) + // ^ @ThatCursor + { + That.Color color = That.Color.Red; +// ^ @ThatVarCursor ^ @ThatExpressionCursor + result = color; + } +} +// ---- +// lib: @diagnostics 2072 +// -> textDocument/definition { +// "position": @wheatherImportCursor +// } +// <- [ +// { +// "range": @whetherEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @WetterCursor +// } +// <- [ +// { +// "range": @whetherEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ThatCursor +// } +// <- [ +// { +// "range": @ColorEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ThatVarCursor +// } +// <- [ +// { +// "range": @ColorEnum, +// "uri": "lib.sol" +// } +// ] +// -> textDocument/definition { +// "position": @ThatExpressionCursor +// } +// <- [ +// { +// "range": @ThatImport, +// "uri": "goto_definition_imports.sol" +// } +// ] diff --git a/test/libsolidity/lsp/lib.sol b/test/libsolidity/lsp/goto/lib.sol similarity index 77% rename from test/libsolidity/lsp/lib.sol rename to test/libsolidity/lsp/goto/lib.sol index 031cf19ad..c476f2dc2 100644 --- a/test/libsolidity/lsp/lib.sol +++ b/test/libsolidity/lsp/goto/lib.sol @@ -5,6 +5,7 @@ pragma solidity >=0.8.0; error E(uint, uint); enum Weather { +// ^^^^^^^ @whetherEnum Sunny, Cloudy, Rainy @@ -12,8 +13,10 @@ enum Weather { /// Some custom Color enum type holding 3 colors. enum Color { +// ^^^^^ @ColorEnum /// Red color. Red, +// ^^^ @EnumMemberRed /// Green color. Green, /// Blue color. @@ -21,9 +24,11 @@ enum Color { } library Lib +// @ ^^^ @LibLibrary { function add(uint a, uint b) public pure returns (uint result) // ^( @addFunction +// ^^^ @addSymbol { result = a + b; } @@ -37,8 +42,11 @@ library Lib } struct RGBColor +// ^^^^^^^^ @RGBColorStruct { uint8 red; uint8 green; uint8 blue; } +// ---- +// lib: @diagnostics 2072 diff --git a/test/libsolidity/lsp/publish_diagnostics_1.sol b/test/libsolidity/lsp/goto/publish_diagnostics_1.sol similarity index 80% rename from test/libsolidity/lsp/publish_diagnostics_1.sol rename to test/libsolidity/lsp/goto/publish_diagnostics_1.sol index be15a4090..357de7a35 100644 --- a/test/libsolidity/lsp/publish_diagnostics_1.sol +++ b/test/libsolidity/lsp/goto/publish_diagnostics_1.sol @@ -19,3 +19,5 @@ contract D // ^^^^^^^^^^^^ @unusedContractVariable } } +// ---- +// publish_diagnostics_1: @unusedReturnVariable 6321 @unusedVariable 2072 @unusedContractVariable 2072 diff --git a/test/libsolidity/lsp/publish_diagnostics_2.sol b/test/libsolidity/lsp/goto/publish_diagnostics_2.sol similarity index 81% rename from test/libsolidity/lsp/publish_diagnostics_2.sol rename to test/libsolidity/lsp/goto/publish_diagnostics_2.sol index 65b4df585..cf052e7c9 100644 --- a/test/libsolidity/lsp/publish_diagnostics_2.sol +++ b/test/libsolidity/lsp/goto/publish_diagnostics_2.sol @@ -22,3 +22,5 @@ contract D // ^^^^^^^^^^^^^^^^^^^^^ @wrongArgumentsCount } } +// ---- +// publish_diagnostics_2: @conversionError 9574 @argumentsRequired 6777 @wrongArgumentsCount 6160 diff --git a/test/libsolidity/lsp/goto_definition.sol b/test/libsolidity/lsp/goto_definition.sol deleted file mode 100644 index 675c1297c..000000000 --- a/test/libsolidity/lsp/goto_definition.sol +++ /dev/null @@ -1,68 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.0; - -import "./lib.sol"; - -interface I -{ - function f(uint x) external returns (uint); -} - -contract IA is I -{ - function f(uint x) public pure override returns (uint) { return x + 1; } -} - -contract IB is I -{ - function f(uint x) public pure override returns (uint) { return x + 2; } -} - -library IntLib -{ - function add(int self, int b) public pure returns (int) { return self + b; } -} - -contract C -{ - I obj; - function virtual_inheritance() public payable - { - obj = new IA(); - obj.f(1); // goto-definition should jump to definition of interface. - } - - using IntLib for *; - function using_for(int i) pure public - { - i.add(5); - 14.add(4); - } - - function useLib(uint n) public payable returns (uint) - { - return Lib.add(n, 1); - } - - function enums(Color c) public pure returns (Color d) - { - Color e = Color.Red; - if (c == e) - d = Color.Green; - else - d = c; - } - - type Price is uint128; - function udlTest() public pure returns (uint128) - { - Price p = Price.wrap(128); - return Price.unwrap(p); - } - - function structCtorTest(uint8 v) public pure returns (uint8 result) - { - RGBColor memory c = RGBColor(v, 2 * v, 3 * v); - result = c.red; - } -} diff --git a/test/libsolidity/lsp/goto_definition_imports.sol b/test/libsolidity/lsp/goto_definition_imports.sol deleted file mode 100644 index b3df921fe..000000000 --- a/test/libsolidity/lsp/goto_definition_imports.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.8.0; - -import {Weather as Wetter} from "./lib.sol"; -import "./lib.sol" as That; - -contract C -{ - function test_symbol_alias() public pure returns (Wetter result) - { - result = Wetter.Sunny; - } - - function test_library_alias() public pure returns (That.Color result) - { - That.Color color = That.Color.Red; - result = color; - } -} diff --git a/test/libsolidity/semanticTests/using/library_on_interface.sol b/test/libsolidity/semanticTests/using/library_on_interface.sol new file mode 100644 index 000000000..ff716dadb --- /dev/null +++ b/test/libsolidity/semanticTests/using/library_on_interface.sol @@ -0,0 +1,18 @@ +using L for I; +interface I { function f() external pure returns (uint); } +library L { + function execute(I i) internal pure returns (uint) { + return i.f(); + } +} +contract C is I { + function x() public view returns (uint) { + I i = this; + return i.execute(); + } + function f() public pure returns (uint) { return 7; } +} +// ==== +// compileViaYul: also +// ---- +// x() -> 7 diff --git a/test/libsolidity/semanticTests/using/using_global_all_the_types.sol b/test/libsolidity/semanticTests/using/using_global_all_the_types.sol new file mode 100644 index 000000000..b4f071fa2 --- /dev/null +++ b/test/libsolidity/semanticTests/using/using_global_all_the_types.sol @@ -0,0 +1,38 @@ +==== Source: A ==== +enum E {A, B} +struct S { uint x; } +type T is uint; +using L for E global; +using L for S global; +using L for T global; +library L { + function f(E e) internal pure returns (uint) { + return uint(e); + } + function f(S memory s) internal pure returns (uint) { + return s.x; + } + function f(T t) internal pure returns (uint) { + return T.unwrap(t); + } +} + +==== Source: B ==== +contract C { + function f() public pure returns (uint a, uint b, uint c) { + E e = E.B; + a = e.f(); + S memory s; + s.x = 7; + b = s.f(); + T t = T.wrap(9); + c = t.f(); + } +} + +import {E, S, T} from "A"; + +// ==== +// compileViaYul: also +// ---- +// f() -> 1, 7, 9 diff --git a/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol b/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol index ca1b6b40a..f60d5f52b 100644 --- a/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol +++ b/test/libsolidity/smtCheckerTests/abi/abi_encode_with_selector_hash.sol @@ -3,7 +3,8 @@ contract C { require(a == b); bytes memory b1 = abi.encodeWithSelector(sel, a, a, a, a); bytes memory b2 = abi.encodeWithSelector(sel, b, a, b, a); - assert(keccak256(b1) == keccak256(b2)); + // Disabled because of OSX nondeterminism + //assert(keccak256(b1) == keccak256(b2)); bytes memory b3 = abi.encodeWithSelector(0xcafecafe, a, a, a, a); assert(keccak256(b1) == keccak256(b3)); // should fail @@ -13,9 +14,10 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 1218: (333-371): CHC: Error trying to invoke SMT solver. -// Warning 1218: (390-428): CHC: Error trying to invoke SMT solver. -// Warning 6328: (333-371): CHC: Assertion violation might happen here. -// Warning 6328: (390-428): CHC: Assertion violation might happen here. -// Warning 4661: (333-371): BMC: Assertion violation happens here. -// Warning 4661: (390-428): BMC: Assertion violation happens here. +// Warning 2072: (161-176): Unused local variable. +// Warning 1218: (379-417): CHC: Error trying to invoke SMT solver. +// Warning 1218: (436-474): CHC: Error trying to invoke SMT solver. +// Warning 6328: (379-417): CHC: Assertion violation might happen here. +// Warning 6328: (436-474): CHC: Assertion violation might happen here. +// Warning 4661: (379-417): BMC: Assertion violation happens here. +// Warning 4661: (436-474): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol index 0d16306b7..b38963b59 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_1d.sol @@ -19,4 +19,4 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (199-229): CHC: Assertion violation happens here.\nCounterexample:\nb = [1]\n\nTransaction trace:\nC.constructor()\nState: b = []\nC.g() +// Warning 6328: (199-229): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol index 7bd74f6dc..31eca9e92 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes.sol @@ -18,5 +18,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- // Warning 6328: (265-310): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol index 7151d9304..fcceeebfd 100644 --- a/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol +++ b/test/libsolidity/smtCheckerTests/array_members/push_as_lhs_bytes_2d.sol @@ -22,5 +22,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- // Warning 6328: (435-508): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol index 4fa420ff1..cd6e1ef9b 100644 --- a/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol +++ b/test/libsolidity/smtCheckerTests/blockchain_state/balance_receive_4.sol @@ -11,13 +11,13 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // SMTIgnoreOS: macos // ---- // Warning 4984: (82-85): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. // Warning 4984: (154-160): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. // Warning 4984: (212-218): CHC: Overflow (resulting value larger than 2**256 - 1) might happen here. -// Warning 6328: (180-219): CHC: Assertion violation happens here.\nCounterexample:\nc = 1\n\nTransaction trace:\nC.constructor()\nState: c = 0\nC.f(){ msg.value: 11 }\nState: c = 1\nC.inv() -// Info 1180: Contract invariant(s) for :C:\n(((11 * c) + ((- 1) * (:var 1).balances[address(this)])) <= 0)\n +// Warning 6328: (180-219): CHC: Assertion violation happens here. // Warning 2661: (82-85): BMC: Overflow (resulting value larger than 2**256 - 1) happens here. // Warning 2661: (154-160): BMC: Overflow (resulting value larger than 2**256 - 1) happens here. // Warning 2661: (212-218): BMC: Overflow (resulting value larger than 2**256 - 1) happens here. diff --git a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol index 98b551c17..f08a26bf7 100644 --- a/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol +++ b/test/libsolidity/smtCheckerTests/control_flow/branches_with_return/branches_in_modifiers_2.sol @@ -45,5 +45,5 @@ contract C { // SMTIgnoreOS: macos // ---- // Warning 6328: (255-269): CHC: Assertion violation happens here.\nCounterexample:\nx = 0\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.test()\n C.reset_if_overflow() -- internal call -// Warning 6328: (502-519): CHC: Assertion violation happens here. +// Warning 6328: (502-519): CHC: Assertion violation happens here.\nCounterexample:\nx = 2\noldx = 1\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.set(1)\nState: x = 1\nC.test()\n C.reset_if_overflow() -- internal call // Warning 6328: (615-629): CHC: Assertion violation happens here.\nCounterexample:\nx = 1\n\nTransaction trace:\nC.constructor()\nState: x = 0\nC.set(10)\nState: x = 10\nC.test()\n C.reset_if_overflow() -- internal call diff --git a/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol b/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol index fd3b55a8c..b89867c39 100644 --- a/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol +++ b/test/libsolidity/smtCheckerTests/crypto/crypto_functions_same_input_over_state_same_output_fail.sol @@ -48,12 +48,10 @@ contract C { // Warning 1218: (693-712): CHC: Error trying to invoke SMT solver. // Warning 1218: (716-735): CHC: Error trying to invoke SMT solver. // Warning 1218: (739-758): CHC: Error trying to invoke SMT solver. -// Warning 1218: (762-781): CHC: Error trying to invoke SMT solver. // Warning 6328: (693-712): CHC: Assertion violation might happen here. // Warning 6328: (716-735): CHC: Assertion violation might happen here. // Warning 6328: (739-758): CHC: Assertion violation might happen here. -// Warning 6328: (762-781): CHC: Assertion violation might happen here. +// Warning 6328: (762-781): CHC: Assertion violation happens here. // Warning 4661: (693-712): BMC: Assertion violation happens here. // Warning 4661: (716-735): BMC: Assertion violation happens here. // Warning 4661: (739-758): BMC: Assertion violation happens here. -// Warning 4661: (762-781): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol index 813185adf..f9293f693 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_1.sol @@ -12,5 +12,5 @@ contract C { // ---- // Warning 9302: (96-117): Return value of low-level calls not used. // Warning 6328: (121-156): CHC: Assertion violation might happen here. -// Warning 6328: (175-211): CHC: Assertion violation happens here. +// Warning 6328: (175-211): CHC: Assertion violation happens here.\nCounterexample:\n\ni = 0x0\n\nTransaction trace:\nC.constructor()\nC.g(0x0)\n i.call{value: 10}("") -- untrusted external call // Warning 4661: (121-156): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol index c38658080..968095bb2 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/call_with_value_2.sol @@ -11,5 +11,5 @@ contract C { // ---- // Warning 9302: (96-116): Return value of low-level calls not used. // Warning 6328: (120-156): CHC: Assertion violation might happen here. -// Warning 6328: (175-210): CHC: Assertion violation happens here.\nCounterexample:\n\ni = 0x0\n\nTransaction trace:\nC.constructor()\nC.g(0x0)\n i.call{value: 0}("") -- untrusted external call +// Warning 6328: (175-210): CHC: Assertion violation happens here. // Warning 4661: (120-156): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol b/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol index eb5cd97dd..9d27d6d3b 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_call_from_constructor_3.sol @@ -20,4 +20,4 @@ contract C { // SMTEngine: all // ---- // Warning 6328: (69-85): CHC: Assertion violation happens here.\nCounterexample:\n\n_x = 100\n = 0\n\nTransaction trace:\nState.constructor()\nState.f(100) -// Warning 6328: (203-217): CHC: Assertion violation happens here.\nCounterexample:\ns = 0, z = 3\n\nTransaction trace:\nC.constructor()\nState: s = 0, z = 3\nC.f() +// Warning 6328: (203-217): CHC: Assertion violation happens here.\nCounterexample:\ns = 0, z = 0\n\nTransaction trace:\nC.constructor()\nState: s = 0, z = 0\nC.f() diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol index be9f698e1..37509269b 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_2.sol @@ -42,4 +42,6 @@ contract C { // SMTIgnoreOS: macos // ---- // Warning 2018: (33-88): Function state mutability can be restricted to view -// Warning 6328: (367-381): CHC: Assertion violation happens here. +// Warning 1218: (367-381): CHC: Error trying to invoke SMT solver. +// Warning 6328: (367-381): CHC: Assertion violation might happen here. +// Warning 4661: (367-381): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol index ea9764e17..bd779b56a 100644 --- a/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol +++ b/test/libsolidity/smtCheckerTests/external_calls/external_hash_known_code_state_reentrancy_indirect.sol @@ -44,7 +44,5 @@ contract C { // SMTIgnoreCex: yes // SMTIgnoreOS: macos // ---- -// Warning 1218: (437-463): CHC: Error trying to invoke SMT solver. // Warning 6328: (419-433): CHC: Assertion violation happens here. -// Warning 6328: (437-463): CHC: Assertion violation might happen here. -// Warning 4661: (437-463): BMC: Assertion violation happens here. +// Warning 6328: (437-463): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol b/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol index 421c4fd4f..d44d15f28 100644 --- a/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol +++ b/test/libsolidity/smtCheckerTests/functions/getters/array_1.sol @@ -14,6 +14,7 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (187-201): CHC: Assertion violation happens here.\nCounterexample:\na = [0, 0, 0, 0]\ny = 0\n\nTransaction trace:\nC.constructor()\nState: a = [0, 0, 0, 0]\nC.f() +// Warning 6328: (187-201): CHC: Assertion violation happens here. // Info 1180: Contract invariant(s) for :C:\n!(a.length <= 2)\n diff --git a/test/libsolidity/smtCheckerTests/functions/payable_2.sol b/test/libsolidity/smtCheckerTests/functions/payable_2.sol index 98717b133..04833ae02 100644 --- a/test/libsolidity/smtCheckerTests/functions/payable_2.sol +++ b/test/libsolidity/smtCheckerTests/functions/payable_2.sol @@ -24,4 +24,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (261-283): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.g2(){ msg.value: 35 }\n C.i() -- internal call +// Warning 6328: (261-283): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.g2(){ msg.value: 1 }\n C.i() -- internal call diff --git a/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol b/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol index fe43db0de..180d85adc 100644 --- a/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol +++ b/test/libsolidity/smtCheckerTests/loops/while_nested_continue_fail.sol @@ -27,7 +27,5 @@ contract C // ==== // SMTEngine: all // ---- -// Warning 1218: (290-305): CHC: Error trying to invoke SMT solver. -// Warning 6328: (290-305): CHC: Assertion violation might happen here. +// Warning 6328: (290-305): CHC: Assertion violation happens here.\nCounterexample:\n\nx = 0\ny = 15\nb = false\nc = false\n\nTransaction trace:\nC.constructor()\nC.f(0, 0, false, false) // Warning 6328: (329-344): CHC: Assertion violation happens here.\nCounterexample:\n\nx = 15\ny = 0\nb = true\nc = false\n\nTransaction trace:\nC.constructor()\nC.f(0, 0, true, false) -// Warning 4661: (290-305): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol b/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol index 9b27a277b..edd36047c 100644 --- a/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol +++ b/test/libsolidity/smtCheckerTests/operators/compound_bitwise_or_uint_2.sol @@ -19,4 +19,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Info 1180: Contract invariant(s) for :C:\n!(s.x.length <= 2)\n +// Warning 6368: (196-202): CHC: Out of bounds access might happen here. diff --git a/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol b/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol index 9c0db9007..c6f421a0c 100644 --- a/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol +++ b/test/libsolidity/smtCheckerTests/operators/slice_default_end.sol @@ -3,8 +3,9 @@ contract C { require(b.length == 30); require(b[10] == 0xff); require(b[b.length - 1] == 0xaa); - assert(bytes(b[10:]).length == 20); - assert(bytes(b[10:])[0] == 0xff); + assert(bytes(b[10:]).length == 20); // should hold + // Disabled because of Spacer's nondeterminism. + //assert(bytes(b[10:])[0] == 0xff); // should hold //assert(bytes(b[10:])[5] == 0xff); // Removed because of Spacer's nondeterminism //assert(bytes(b[10:])[19] == 0xaa); // Removed because of Spacer nondeterminism } @@ -12,5 +13,3 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (188-220): CHC: Assertion violation might happen here. -// Warning 4661: (188-220): BMC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol b/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol index 0a6d85a7e..e809e041c 100644 --- a/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol +++ b/test/libsolidity/smtCheckerTests/overflow/unsigned_mul_overflow.sol @@ -5,5 +5,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 4984: (80-85): CHC: Overflow (resulting value larger than 2**256 - 1) happens here.\nCounterexample:\n\nx = 57896044618658097711785492504343953926634992332820282019728792003956564819968\ny = 2\n = 0\n\nTransaction trace:\nC.constructor()\nC.f(57896044618658097711785492504343953926634992332820282019728792003956564819968, 2) +// Warning 4984: (80-85): CHC: Overflow (resulting value larger than 2**256 - 1) happens here. diff --git a/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol b/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol index 2ebf4d326..6e4f5a620 100644 --- a/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol +++ b/test/libsolidity/smtCheckerTests/special/block_vars_chc_internal.sol @@ -32,4 +32,4 @@ contract C { // ==== // SMTEngine: chc // ---- -// Warning 6328: (770-799): CHC: Assertion violation happens here.\nCounterexample:\ncoin = 0x0, dif = 0, gas = 0, number = 0, timestamp = 0\n\nTransaction trace:\nC.constructor()\nState: coin = 0x0, dif = 0, gas = 0, number = 0, timestamp = 0\nC.f(){ block.coinbase: 0x0, block.difficulty: 0, block.gaslimit: 0, block.number: 0, block.timestamp: 0 }\n C.g() -- internal call +// Warning 6328: (770-799): CHC: Assertion violation happens here.\nCounterexample:\ncoin = 0x1e28, dif = 0, gas = 0, number = 0, timestamp = 0\n\nTransaction trace:\nC.constructor()\nState: coin = 0x0, dif = 0, gas = 0, number = 0, timestamp = 0\nC.f(){ block.coinbase: 0x1e28, block.difficulty: 0, block.gaslimit: 0, block.number: 0, block.timestamp: 0 }\n C.g() -- internal call diff --git a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol index 7729cebb9..f1fed73c0 100644 --- a/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol +++ b/test/libsolidity/smtCheckerTests/special/tx_vars_reentrancy_1.sol @@ -13,4 +13,4 @@ contract C { // SMTEngine: all // SMTIgnoreOS: macos // ---- -// Warning 6328: (135-169): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 2997\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 2803 }\n _i.f() -- untrusted external call, synthesized as:\n C.g(0){ msg.value: 2446 } -- reentrant call\n _i.f() -- untrusted external call +// Warning 6328: (135-169): CHC: Assertion violation happens here.\nCounterexample:\n\n_i = 0\nx = 9726\n\nTransaction trace:\nC.constructor()\nC.g(0){ msg.value: 2070 }\n _i.f() -- untrusted external call, synthesized as:\n C.g(0){ msg.value: 0 } -- reentrant call\n _i.f() -- untrusted external call diff --git a/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol b/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol index 98a19731b..5c15fb54f 100644 --- a/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol +++ b/test/libsolidity/smtCheckerTests/try_catch/try_nested_2.sol @@ -19,5 +19,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (342-362): CHC: Assertion violation happens here.\nCounterexample:\n\nchoice = 3\n\nTransaction trace:\nC.constructor()\nC.f() +// Warning 6328: (342-362): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol b/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol index dd0d8e004..cd1d77d8d 100644 --- a/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol +++ b/test/libsolidity/smtCheckerTests/types/array_mapping_aliasing_2.sol @@ -36,11 +36,12 @@ contract C // SMTIgnoreCex: yes // ---- // Warning 6368: (439-453): CHC: Out of bounds access happens here. +// Warning 6368: (465-480): CHC: Out of bounds access might happen here. // Warning 6368: (492-508): CHC: Out of bounds access happens here. // Warning 6368: (492-511): CHC: Out of bounds access happens here. // Warning 6368: (622-636): CHC: Out of bounds access happens here. +// Warning 6368: (737-752): CHC: Out of bounds access might happen here. // Warning 6368: (850-866): CHC: Out of bounds access happens here. // Warning 6368: (850-869): CHC: Out of bounds access happens here. // Warning 6328: (936-956): CHC: Assertion violation happens here. // Warning 6368: (1029-1043): CHC: Out of bounds access might happen here. -// Info 1180: Contract invariant(s) for :C:\n!(severalMaps8.length <= 0)\n diff --git a/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol b/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol index f9aa3a650..ca1a58018 100644 --- a/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol +++ b/test/libsolidity/smtCheckerTests/types/array_static_mapping_aliasing_2.sol @@ -30,7 +30,8 @@ contract C // SMTEngine: all // SMTIgnoreCex: yes // ---- +// Warning 6368: (340-355): CHC: Out of bounds access might happen here. +// Warning 6368: (612-627): CHC: Out of bounds access might happen here. // Warning 6328: (860-880): CHC: Assertion violation happens here. // Warning 6368: (936-952): CHC: Out of bounds access might happen here. // Warning 6368: (936-955): CHC: Out of bounds access might happen here. -// Info 1180: Contract invariant(s) for :C:\n!(severalMaps8.length <= 1)\n diff --git a/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol b/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol index 109fee7dc..67f56d8c6 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/array_struct_array_struct_storage_safe.sol @@ -53,3 +53,4 @@ contract C { // ==== // SMTEngine: all // ---- +// Warning 6368: (212-217): CHC: Out of bounds access might happen here. diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol index 698262382..9d4623ae0 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_2.sol @@ -20,4 +20,4 @@ contract C { // ==== // SMTEngine: all // ---- -// Warning 6328: (289-322): CHC: Assertion violation happens here.\nCounterexample:\ns = {innerM, sum: 11}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: s = {innerM, sum: 11}\nC.g(){ msg.sender: 0x0985 } +// Warning 6328: (289-322): CHC: Assertion violation happens here.\nCounterexample:\ns = {innerM, sum: 10}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: s = {innerM, sum: 10}\nC.g(){ msg.sender: 0x0985 } diff --git a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol index fcf30555e..f9ae95e7c 100644 --- a/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol +++ b/test/libsolidity/smtCheckerTests/types/struct/struct_aliasing_parameter_storage_3.sol @@ -25,5 +25,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 6328: (307-327): CHC: Assertion violation happens here.\nCounterexample:\nt = {x: 11, s: {innerM, sum: 21239}}\n\nTransaction trace:\nC.constructor(0){ msg.sender: 0x6dc4 }\nState: t = {x: 11, s: {innerM, sum: 21239}}\nC.g() +// Warning 6328: (307-327): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol b/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol index cb795f9c0..9d8df15eb 100644 --- a/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol +++ b/test/libsolidity/smtCheckerTests/unchecked/checked_called_by_unchecked.sol @@ -9,5 +9,6 @@ contract C { } // ==== // SMTEngine: all +// SMTIgnoreCex: yes // ---- -// Warning 4984: (96-101): CHC: Overflow (resulting value larger than 65535) happens here.\nCounterexample:\n\na = 1\nb = 65535\n = 0\n\nTransaction trace:\nC.constructor()\nC.add(1, 65535) +// Warning 4984: (96-101): CHC: Overflow (resulting value larger than 65535) happens here. diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion.sol deleted file mode 100644 index cad0233a7..000000000 --- a/test/libsolidity/smtCheckerTests/userTypes/conversion.sol +++ /dev/null @@ -1,87 +0,0 @@ -pragma abicoder v2; - -type MyUInt8 is uint8; -type MyInt8 is int8; -type MyUInt16 is uint16; - -contract C { - function f(uint a) internal pure returns(MyUInt8) { - return MyUInt8.wrap(uint8(a)); - } - function g(uint a) internal pure returns(MyInt8) { - return MyInt8.wrap(int8(int(a))); - } - function h(MyUInt8 a) internal pure returns (MyInt8) { - return MyInt8.wrap(int8(MyUInt8.unwrap(a))); - } - function i(MyUInt8 a) internal pure returns(MyUInt16) { - return MyUInt16.wrap(MyUInt8.unwrap(a)); - } - function j(MyUInt8 a) internal pure returns (uint) { - return MyUInt8.unwrap(a); - } - function k(MyUInt8 a) internal pure returns (MyUInt16) { - return MyUInt16.wrap(MyUInt8.unwrap(a)); - } - function m(MyUInt16 a) internal pure returns (MyUInt8) { - return MyUInt8.wrap(uint8(MyUInt16.unwrap(a))); - } - - function p() public pure { - assert(MyUInt8.unwrap(f(1)) == 1); - assert(MyUInt8.unwrap(f(2)) == 2); - assert(MyUInt8.unwrap(f(257)) == 1); - assert(MyUInt8.unwrap(f(257)) == 257); // should fail - } - - function q() public pure { - assert(MyInt8.unwrap(g(1)) == 1); - assert(MyInt8.unwrap(g(2)) == 2); - assert(MyInt8.unwrap(g(255)) == -1); - assert(MyInt8.unwrap(g(257)) == 1); - assert(MyInt8.unwrap(g(257)) == -1); // should fail - } - - function r() public pure { - assert(MyInt8.unwrap(h(MyUInt8.wrap(1))) == 1); - assert(MyInt8.unwrap(h(MyUInt8.wrap(2))) == 2); - assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == -1); - assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == 1); // should fail - } - - function s() public pure { - assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 250); - assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 0); // should fail - } - - function t() public pure { - assert(j(MyUInt8.wrap(1)) == 1); - assert(j(MyUInt8.wrap(2)) == 2); - assert(j(MyUInt8.wrap(255)) == 0xff); - assert(j(MyUInt8.wrap(255)) == 1); // should fail - } - - function v() public pure { - assert(MyUInt16.unwrap(k(MyUInt8.wrap(1))) == 1); - assert(MyUInt16.unwrap(k(MyUInt8.wrap(2))) == 2); - assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 0xff); - assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 1); // should fail - } - - function w() public pure { - assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1); - assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2); - assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff); - assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1); // should fail - } -} -// ==== -// SMTEngine: all -// ---- -// Warning 6328: (937-974): CHC: Assertion violation happens here. -// Warning 6328: (1174-1209): CHC: Assertion violation happens here. -// Warning 6328: (1413-1461): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.r()\n C.h(1) -- internal call\n C.h(2) -- internal call\n C.h(255) -- internal call\n C.h(255) -- internal call -// Warning 6328: (1568-1618): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.s()\n C.i(250) -- internal call\n C.i(250) -- internal call -// Warning 6328: (1779-1812): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.t()\n C.j(1) -- internal call\n C.j(2) -- internal call\n C.j(255) -- internal call\n C.j(255) -- internal call -// Warning 6328: (2024-2074): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.v()\n C.k(1) -- internal call\n C.k(2) -- internal call\n C.k(255) -- internal call\n C.k(255) -- internal call -// Warning 6328: (2286-2336): CHC: Assertion violation happens here. diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol new file mode 100644 index 000000000..fa56e11ab --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_1.sol @@ -0,0 +1,35 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function f(uint a) internal pure returns(MyUInt8) { + return MyUInt8.wrap(uint8(a)); + } + function g(uint a) internal pure returns(MyInt8) { + return MyInt8.wrap(int8(int(a))); + } + + function p() public pure { + assert(MyUInt8.unwrap(f(1)) == 1); + assert(MyUInt8.unwrap(f(2)) == 2); + assert(MyUInt8.unwrap(f(257)) == 1); + assert(MyUInt8.unwrap(f(257)) == 257); // should fail + } + + function q() public pure { + assert(MyInt8.unwrap(g(1)) == 1); + assert(MyInt8.unwrap(g(2)) == 2); + assert(MyInt8.unwrap(g(255)) == -1); + assert(MyInt8.unwrap(g(257)) == 1); + assert(MyInt8.unwrap(g(257)) == -1); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (428-465): CHC: Assertion violation happens here. +// Warning 6328: (665-700): CHC: Assertion violation happens here. +// Info 1180: Contract invariant(s) for :C:\n(true || true || true || true || true)\nReentrancy property(ies) for :C:\n(true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true)\n(true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true)\n(true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true)\n(true || true || true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyUInt8.unwrap(f(1)) == 1)\n = 2 -> Assertion failed at assert(MyUInt8.unwrap(f(2)) == 2)\n = 3 -> Assertion failed at assert(MyUInt8.unwrap(f(257)) == 1)\n = 4 -> Assertion failed at assert(MyUInt8.unwrap(f(257)) == 257)\n = 6 -> Assertion failed at assert(MyInt8.unwrap(g(1)) == 1)\n = 7 -> Assertion failed at assert(MyInt8.unwrap(g(2)) == 2)\n = 8 -> Assertion failed at assert(MyInt8.unwrap(g(255)) == -1)\n = 9 -> Assertion failed at assert(MyInt8.unwrap(g(257)) == 1)\n = 10 -> Assertion failed at assert(MyInt8.unwrap(g(257)) == -1)\n diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol new file mode 100644 index 000000000..9b4f72d33 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_2.sol @@ -0,0 +1,32 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function h(MyUInt8 a) internal pure returns (MyInt8) { + return MyInt8.wrap(int8(MyUInt8.unwrap(a))); + } + function i(MyUInt8 a) internal pure returns(MyUInt16) { + return MyUInt16.wrap(MyUInt8.unwrap(a)); + } + + function r() public pure { + assert(MyInt8.unwrap(h(MyUInt8.wrap(1))) == 1); + assert(MyInt8.unwrap(h(MyUInt8.wrap(2))) == 2); + assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == -1); + assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == 1); // should fail + } + + function s() public pure { + assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 250); + assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 0); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (497-545): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.r()\n C.h(1) -- internal call\n C.h(2) -- internal call\n C.h(255) -- internal call\n C.h(255) -- internal call +// Warning 6328: (652-702): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.s()\n C.i(250) -- internal call\n C.i(250) -- internal call +// Info 1180: Contract invariant(s) for :C:\n(true || true || true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true || true)\n(true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true)\n(true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(1))) == 1)\n = 2 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(2))) == 2)\n = 3 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == -1)\n = 4 -> Assertion failed at assert(MyInt8.unwrap(h(MyUInt8.wrap(255))) == 1)\n = 6 -> Assertion failed at assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 250)\n = 7 -> Assertion failed at assert(MyUInt16.unwrap(i(MyUInt8.wrap(250))) == 0)\n diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol new file mode 100644 index 000000000..ea8e9804b --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_3.sol @@ -0,0 +1,34 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function j(MyUInt8 a) internal pure returns (uint) { + return MyUInt8.unwrap(a); + } + function k(MyUInt8 a) internal pure returns (MyUInt16) { + return MyUInt16.wrap(MyUInt8.unwrap(a)); + } + + function t() public pure { + assert(j(MyUInt8.wrap(1)) == 1); + assert(j(MyUInt8.wrap(2)) == 2); + assert(j(MyUInt8.wrap(255)) == 0xff); + assert(j(MyUInt8.wrap(255)) == 1); // should fail + } + + function v() public pure { + assert(MyUInt16.unwrap(k(MyUInt8.wrap(1))) == 1); + assert(MyUInt16.unwrap(k(MyUInt8.wrap(2))) == 2); + assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 0xff); + assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 1); // should fail + } +} +// ==== +// SMTEngine: all +// ---- +// Warning 6328: (434-467): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.t()\n C.j(1) -- internal call\n C.j(2) -- internal call\n C.j(255) -- internal call\n C.j(255) -- internal call +// Warning 6328: (679-729): CHC: Assertion violation happens here.\nCounterexample:\n\n\nTransaction trace:\nC.constructor()\nC.v()\n C.k(1) -- internal call\n C.k(2) -- internal call\n C.k(255) -- internal call\n C.k(255) -- internal call +// Info 1180: Contract invariant(s) for :C:\n(true || true || true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true || true || true || true)\n(true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true || true)\n(true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))) || true)\n(true || true || true || true || true || true || true || true || (( = 0) && ((:var 0) = (:var 1))))\n = 0 -> no errors\n = 1 -> Assertion failed at assert(j(MyUInt8.wrap(1)) == 1)\n = 2 -> Assertion failed at assert(j(MyUInt8.wrap(2)) == 2)\n = 3 -> Assertion failed at assert(j(MyUInt8.wrap(255)) == 0xff)\n = 4 -> Assertion failed at assert(j(MyUInt8.wrap(255)) == 1)\n = 6 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(1))) == 1)\n = 7 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(2))) == 2)\n = 8 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 0xff)\n = 9 -> Assertion failed at assert(MyUInt16.unwrap(k(MyUInt8.wrap(255))) == 1)\n diff --git a/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol b/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol new file mode 100644 index 000000000..699d22097 --- /dev/null +++ b/test/libsolidity/smtCheckerTests/userTypes/conversion_4.sol @@ -0,0 +1,24 @@ +pragma abicoder v2; + +type MyUInt8 is uint8; +type MyInt8 is int8; +type MyUInt16 is uint16; + +contract C { + function m(MyUInt16 a) internal pure returns (MyUInt8) { + return MyUInt8.wrap(uint8(MyUInt16.unwrap(a))); + } + + function w() public pure { + assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1); + assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2); + assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff); + assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1); // should fail + } +} +// ==== +// SMTEngine: all +// SMTIgnoreCex: yes +// ---- +// Warning 6328: (407-457): CHC: Assertion violation happens here. +// Info 1180: Contract invariant(s) for :C:\n(true || true || true)\nReentrancy property(ies) for :C:\n((( = 0) && ((:var 0) = (:var 1))) || true || true || true || true)\n(true || true || (( = 0) && ((:var 0) = (:var 1))) || true || true)\n = 0 -> no errors\n = 1 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(1))) == 1)\n = 2 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(2))) == 2)\n = 3 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 0xff)\n = 4 -> Assertion failed at assert(MyUInt8.unwrap(m(MyUInt16.wrap(255))) == 1)\n diff --git a/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol b/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol index e44356dc3..787898357 100644 --- a/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol +++ b/test/libsolidity/syntaxTests/freeFunctions/free_constructor.sol @@ -1,3 +1,3 @@ constructor() {} // ---- -// ParserError 7858: (0-11): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-11): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol b/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol index e813e58ee..897ed5964 100644 --- a/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol +++ b/test/libsolidity/syntaxTests/freeFunctions/free_fallback.sol @@ -1,3 +1,3 @@ fallback(){} // ---- -// ParserError 7858: (0-8): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-8): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol b/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol index d1e790d7c..83dfee446 100644 --- a/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol +++ b/test/libsolidity/syntaxTests/freeFunctions/free_receive.sol @@ -1,3 +1,3 @@ receive() {} // ---- -// ParserError 7858: (0-7): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-7): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol index b08172ebe..28d336577 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium.sol @@ -1,5 +1,5 @@ contract C { - function f() pure external { + function f() view external { assembly { let s := returndatasize() returndatacopy(0, 0, s) diff --git a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol index 74316c526..5698f990f 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/evm_byzantium_on_homestead.sol @@ -1,5 +1,5 @@ contract C { - function f() pure external { + function f() view external { assembly { let s := returndatasize() returndatacopy(0, 0, s) diff --git a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol index f959537b6..403022846 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_call_post_byzantium.sol.sol @@ -1,5 +1,5 @@ contract C { - function f() public pure { + function f() public view { uint returndatasize; returndatasize; assembly { diff --git a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol index 44a12aa06..903da7223 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_post_byzantium.sol @@ -1,5 +1,5 @@ contract C { - function f() public pure { + function f() public view { uint returndatasize; returndatasize; assembly { diff --git a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol index 07bae8ac8..250a0755f 100644 --- a/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol +++ b/test/libsolidity/syntaxTests/inlineAssembly/returndatasize_as_variable_pre_byzantium.sol @@ -1,4 +1,4 @@ -contract C { function f() public pure { uint returndatasize; returndatasize; assembly { pop(returndatasize()) }}} +contract C { function f() public view { uint returndatasize; returndatasize; assembly { pop(returndatasize()) }}} // ==== // EVMVersion: =homestead // ---- diff --git a/test/libsolidity/syntaxTests/unexpected.sol b/test/libsolidity/syntaxTests/unexpected.sol index 1ffa40c21..c6fae414c 100644 --- a/test/libsolidity/syntaxTests/unexpected.sol +++ b/test/libsolidity/syntaxTests/unexpected.sol @@ -1,3 +1,3 @@ unexpected // ---- -// ParserError 7858: (0-10): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function definition. +// ParserError 7858: (0-10): Expected pragma, import directive or contract/interface/library/struct/enum/constant/function/error definition. diff --git a/test/libsolidity/syntaxTests/using/global_inside_contract.sol b/test/libsolidity/syntaxTests/using/global_inside_contract.sol index edaeea922..54c01f99c 100644 --- a/test/libsolidity/syntaxTests/using/global_inside_contract.sol +++ b/test/libsolidity/syntaxTests/using/global_inside_contract.sol @@ -4,4 +4,3 @@ contract C { function f(uint) pure{} // ---- // SyntaxError 3367: (17-43): "global" can only be used at file level. -// TypeError 8841: (17-43): Can only use "global" with user-defined types. diff --git a/test/libsolidity/syntaxTests/using/global_library_for_builtin.sol b/test/libsolidity/syntaxTests/using/global_library_for_builtin.sol new file mode 100644 index 000000000..7ff956941 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_for_builtin.sol @@ -0,0 +1,9 @@ +using L for uint global; +using L for uint[] global; +using L for function() returns (uint) global; +library L { +} +// ---- +// TypeError 8841: (0-24): Can only use "global" with user-defined types. +// TypeError 8841: (25-51): Can only use "global" with user-defined types. +// TypeError 8841: (52-97): Can only use "global" with user-defined types. diff --git a/test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol b/test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol new file mode 100644 index 000000000..f3f65890f --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_for_defined_elsewhere.sol @@ -0,0 +1,6 @@ +using L for L.S global; +library L { + struct S { uint x; } +} +// ---- +// TypeError 4117: (0-23): Can only use "global" with types defined in the same source unit at file level. diff --git a/test/libsolidity/syntaxTests/using/global_library_for_interface.sol b/test/libsolidity/syntaxTests/using/global_library_for_interface.sol new file mode 100644 index 000000000..06a644634 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_for_interface.sol @@ -0,0 +1,6 @@ +interface I {} +using L for I global; +library L { +} +// ---- +// TypeError 8841: (15-36): Can only use "global" with user-defined types. diff --git a/test/libsolidity/syntaxTests/using/global_library_foreign.sol b/test/libsolidity/syntaxTests/using/global_library_foreign.sol new file mode 100644 index 000000000..9eacc2c4b --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_foreign.sol @@ -0,0 +1,8 @@ +==== Source: A ==== +struct S { uint x; } +==== Source: B ==== +library L {} +using L for S global; +import {S} from "A"; +// ---- +// TypeError 4117: (B:13-34): Can only use "global" with types defined in the same source unit at file level. diff --git a/test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol b/test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol new file mode 100644 index 000000000..49ac6824d --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_library_with_asterisk.sol @@ -0,0 +1,5 @@ +using L for * global; +library L {} +// ---- +// SyntaxError 8118: (0-21): The type has to be specified explicitly at file level (cannot use '*'). +// SyntaxError 2854: (0-21): Can only globally bind functions to specific types. diff --git a/test/libsolidity/syntaxTests/using/global_with_asterisk.sol b/test/libsolidity/syntaxTests/using/global_with_asterisk.sol new file mode 100644 index 000000000..b5f6d8827 --- /dev/null +++ b/test/libsolidity/syntaxTests/using/global_with_asterisk.sol @@ -0,0 +1,5 @@ +using {f} for * global; +function f(uint) pure{} +// ---- +// SyntaxError 8118: (0-23): The type has to be specified explicitly at file level (cannot use '*'). +// SyntaxError 2854: (0-23): Can only globally bind functions to specific types. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol new file mode 100644 index 000000000..8b634c219 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed.sol @@ -0,0 +1,92 @@ +contract C { + function f() public { + assembly { + stop() + pop(add(0, 1)) + pop(sub(0, 1)) + pop(mul(0, 1)) + pop(div(0, 1)) + pop(sdiv(0, 1)) + pop(mod(0, 1)) + pop(smod(0, 1)) + pop(exp(0, 1)) + pop(not(0)) + pop(lt(0, 1)) + pop(gt(0, 1)) + pop(slt(0, 1)) + pop(sgt(0, 1)) + pop(eq(0, 1)) + pop(iszero(0)) + pop(and(0, 1)) + pop(or(0, 1)) + pop(xor(0, 1)) + pop(byte(0, 1)) + pop(shl(0, 1)) + pop(shr(0, 1)) + pop(sar(0, 1)) + pop(addmod(0, 1, 2)) + pop(mulmod(0, 1, 2)) + pop(signextend(0, 1)) + pop(keccak256(0, 1)) + pop(0) + pop(mload(0)) + mstore(0, 1) + mstore8(0, 1) + pop(sload(0)) + sstore(0, 1) + pop(gas()) + pop(address()) + pop(balance(0)) + pop(selfbalance()) + pop(caller()) + pop(callvalue()) + pop(calldataload(0)) + pop(calldatasize()) + calldatacopy(0, 1, 2) + pop(codesize()) + codecopy(0, 1, 2) + pop(extcodesize(0)) + extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) + pop(extcodehash(0)) + pop(create(0, 1, 2)) + pop(create2(0, 1, 2, 3)) + pop(call(0, 1, 2, 3, 4, 5, 6)) + pop(callcode(0, 1, 2, 3, 4, 5, 6)) + pop(delegatecall(0, 1, 2, 3, 4, 5)) + pop(staticcall(0, 1, 2, 3, 4, 5)) + return(0, 1) + revert(0, 1) + selfdestruct(0) + invalid() + log0(0, 1) + log1(0, 1, 2) + log2(0, 1, 2, 3) + log3(0, 1, 2, 3, 4) + log4(0, 1, 2, 3, 4, 5) + pop(chainid()) + pop(basefee()) + pop(origin()) + pop(gasprice()) + pop(blockhash(0)) + pop(coinbase()) + pop(timestamp()) + pop(number()) + pop(difficulty()) + pop(gaslimit()) + + // NOTE: msize() is allowed only with optimizer disabled + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (89-1716): Unreachable code. +// Warning 5740: (1729-1741): Unreachable code. +// Warning 5740: (1754-1769): Unreachable code. +// Warning 5740: (1782-1791): Unreachable code. +// Warning 5740: (1804-2215): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol new file mode 100644 index 000000000..9c09bcc37 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_pure.sol @@ -0,0 +1,90 @@ +contract C { + function f() public pure { + assembly { + stop() + pop(add(0, 1)) + pop(sub(0, 1)) + pop(mul(0, 1)) + pop(div(0, 1)) + pop(sdiv(0, 1)) + pop(mod(0, 1)) + pop(smod(0, 1)) + pop(exp(0, 1)) + pop(not(0)) + pop(lt(0, 1)) + pop(gt(0, 1)) + pop(slt(0, 1)) + pop(sgt(0, 1)) + pop(eq(0, 1)) + pop(iszero(0)) + pop(and(0, 1)) + pop(or(0, 1)) + pop(xor(0, 1)) + pop(byte(0, 1)) + pop(shl(0, 1)) + pop(shr(0, 1)) + pop(sar(0, 1)) + pop(addmod(0, 1, 2)) + pop(mulmod(0, 1, 2)) + pop(signextend(0, 1)) + pop(keccak256(0, 1)) + pop(0) + pop(mload(0)) + mstore(0, 1) + mstore8(0, 1) + //pop(sload(0)) + //sstore(0, 1) + //pop(gas()) + //pop(address()) + //pop(balance(0)) + //pop(selfbalance()) + //pop(caller()) + //pop(callvalue()) + pop(calldataload(0)) + pop(calldatasize()) + calldatacopy(0, 1, 2) + pop(codesize()) + codecopy(0, 1, 2) + //pop(extcodesize(0)) + //extcodecopy(0, 1, 2, 3) + //pop(returndatasize()) + //returndatacopy(0, 1, 2) + //pop(extcodehash(0)) + //pop(create(0, 1, 2)) + //pop(create2(0, 1, 2, 3)) + //pop(call(0, 1, 2, 3, 4, 5, 6)) + //pop(callcode(0, 1, 2, 3, 4, 5, 6)) + //pop(delegatecall(0, 1, 2, 3, 4, 5)) + //pop(staticcall(0, 1, 2, 3, 4, 5)) + return(0, 1) + revert(0, 1) + //selfdestruct(0) + invalid() + //log0(0, 1) + //log1(0, 1, 2) + //log2(0, 1, 2, 3) + //log3(0, 1, 2, 3, 4) + //log4(0, 1, 2, 3, 4, 5) + //pop(chainid()) + //pop(basefee()) + //pop(origin()) + //pop(gasprice()) + //pop(blockhash(0)) + //pop(coinbase()) + //pop(timestamp()) + //pop(number()) + //pop(difficulty()) + //pop(gaslimit()) + + // NOTE: msize() is allowed only with optimizer disabled + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (94-1759): Unreachable code. +// Warning 5740: (1772-1784): Unreachable code. +// Warning 5740: (1827-1836): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol new file mode 100644 index 000000000..af30bc21a --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_allowed_view.sol @@ -0,0 +1,91 @@ +contract C { + function f() public view { + assembly { + stop() + pop(add(0, 1)) + pop(sub(0, 1)) + pop(mul(0, 1)) + pop(div(0, 1)) + pop(sdiv(0, 1)) + pop(mod(0, 1)) + pop(smod(0, 1)) + pop(exp(0, 1)) + pop(not(0)) + pop(lt(0, 1)) + pop(gt(0, 1)) + pop(slt(0, 1)) + pop(sgt(0, 1)) + pop(eq(0, 1)) + pop(iszero(0)) + pop(and(0, 1)) + pop(or(0, 1)) + pop(xor(0, 1)) + pop(byte(0, 1)) + pop(shl(0, 1)) + pop(shr(0, 1)) + pop(sar(0, 1)) + pop(addmod(0, 1, 2)) + pop(mulmod(0, 1, 2)) + pop(signextend(0, 1)) + pop(keccak256(0, 1)) + pop(0) + pop(mload(0)) + mstore(0, 1) + mstore8(0, 1) + pop(sload(0)) + //sstore(0, 1) + pop(gas()) + pop(address()) + pop(balance(0)) + pop(selfbalance()) + pop(caller()) + pop(callvalue()) + pop(calldataload(0)) + pop(calldatasize()) + calldatacopy(0, 1, 2) + pop(codesize()) + codecopy(0, 1, 2) + pop(extcodesize(0)) + extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) + pop(extcodehash(0)) + //pop(create(0, 1, 2)) + //pop(create2(0, 1, 2, 3)) + //pop(call(0, 1, 2, 3, 4, 5, 6)) + //pop(callcode(0, 1, 2, 3, 4, 5, 6)) + //pop(delegatecall(0, 1, 2, 3, 4, 5)) + pop(staticcall(0, 1, 2, 3, 4, 5)) + return(0, 1) + revert(0, 1) + //selfdestruct(0) + invalid() + //log0(0, 1) + //log1(0, 1, 2) + //log2(0, 1, 2, 3) + //log3(0, 1, 2, 3, 4) + //log4(0, 1, 2, 3, 4, 5) + pop(chainid()) + pop(basefee()) + pop(origin()) + pop(gasprice()) + pop(blockhash(0)) + pop(coinbase()) + pop(timestamp()) + pop(number()) + pop(difficulty()) + pop(gaslimit()) + + // NOTE: msize() is allowed only with optimizer disabled + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (94-1733): Unreachable code. +// Warning 5740: (1746-1758): Unreachable code. +// Warning 5740: (1801-1810): Unreachable code. +// Warning 5740: (1978-2244): Unreachable code. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol new file mode 100644 index 000000000..992a51ad2 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed.sol @@ -0,0 +1,28 @@ +contract C { + function f() public { + assembly { + datasize(0) + dataoffset(0) + datacopy(0, 1, 2) + setimmutable(0, "x", 1) + loadimmutable("x") + linkersymbol("x") + memoryguard(0) + verbatim_1i_1o(hex"600202", 0) + + pop(msize()) + pop(pc()) + } + } +} +// ---- +// SyntaxError 6553: (47-362): The msize instruction cannot be used when the Yul optimizer is activated because it can change its semantics. Either disable the Yul optimizer or do not use the instruction. +// DeclarationError 4619: (70-78): Function "datasize" not found. +// DeclarationError 4619: (94-104): Function "dataoffset" not found. +// DeclarationError 4619: (120-128): Function "datacopy" not found. +// DeclarationError 4619: (150-162): Function "setimmutable" not found. +// DeclarationError 4619: (186-199): Function "loadimmutable" not found. +// DeclarationError 4619: (217-229): Function "linkersymbol" not found. +// DeclarationError 4619: (247-258): Function "memoryguard" not found. +// DeclarationError 4619: (274-288): Function "verbatim_1i_1o" not found. +// SyntaxError 2450: (347-349): PC instruction is a low-level EVM feature. Because of that PC is disallowed in strict assembly. diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol new file mode 100644 index 000000000..8da17390d --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_pure.sol @@ -0,0 +1,84 @@ +contract C { + function f() public pure { + assembly { + pop(sload(0)) + sstore(0, 1) + pop(gas()) + pop(address()) + pop(balance(0)) + pop(selfbalance()) + pop(caller()) + pop(callvalue()) + pop(extcodesize(0)) + extcodecopy(0, 1, 2, 3) + pop(returndatasize()) + returndatacopy(0, 1, 2) + pop(extcodehash(0)) + pop(create(0, 1, 2)) + pop(create2(0, 1, 2, 3)) + pop(call(0, 1, 2, 3, 4, 5, 6)) + pop(callcode(0, 1, 2, 3, 4, 5, 6)) + pop(delegatecall(0, 1, 2, 3, 4, 5)) + pop(staticcall(0, 1, 2, 3, 4, 5)) + selfdestruct(0) + log0(0, 1) + log1(0, 1, 2) + log2(0, 1, 2, 3) + log3(0, 1, 2, 3, 4) + log4(0, 1, 2, 3, 4, 5) + pop(chainid()) + pop(basefee()) + pop(origin()) + pop(gasprice()) + pop(blockhash(0)) + pop(coinbase()) + pop(timestamp()) + pop(number()) + pop(difficulty()) + pop(gaslimit()) + + // These two are disallowed too but the error suppresses other errors. + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (742-1153): Unreachable code. +// TypeError 2527: (79-87): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (101-113): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (130-135): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (153-162): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (180-190): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (208-221): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (239-247): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (265-276): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (294-308): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (322-345): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (362-378): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (392-415): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (432-446): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (464-479): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (497-516): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (534-559): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (577-606): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (624-654): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (672-700): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 8961: (714-729): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (742-752): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (765-778): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (791-807): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (820-839): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 8961: (852-874): Function cannot be declared as pure because this expression (potentially) modifies the state. +// TypeError 2527: (891-900): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (918-927): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (945-953): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (971-981): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (999-1011): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1029-1039): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1057-1068): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1086-1094): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1112-1124): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". +// TypeError 2527: (1142-1152): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". diff --git a/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol new file mode 100644 index 000000000..5daf91742 --- /dev/null +++ b/test/libsolidity/syntaxTests/viewPureChecker/inline_assembly_instructions_disallowed_view.sol @@ -0,0 +1,38 @@ +contract C { + function f() public view { + assembly { + sstore(0, 1) + pop(create(0, 1, 2)) + pop(create2(0, 1, 2, 3)) + pop(call(0, 1, 2, 3, 4, 5, 6)) + pop(callcode(0, 1, 2, 3, 4, 5, 6)) + pop(delegatecall(0, 1, 2, 3, 4, 5)) + selfdestruct(0) + log0(0, 1) + log1(0, 1, 2) + log2(0, 1, 2, 3) + log3(0, 1, 2, 3, 4) + log4(0, 1, 2, 3, 4, 5) + + // These two are disallowed too but the error suppresses other errors. + //pop(msize()) + //pop(pc()) + } + } +} +// ==== +// EVMVersion: >=london +// ---- +// Warning 5740: (336-468): Unreachable code. +// TypeError 8961: (75-87): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (104-119): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (137-156): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (174-199): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (217-246): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (264-294): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (308-323): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (336-346): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (359-372): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (385-401): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (414-433): Function cannot be declared as view because this expression (potentially) modifies the state. +// TypeError 8961: (446-468): Function cannot be declared as view because this expression (potentially) modifies the state. diff --git a/test/lsp.py b/test/lsp.py index 8b7398cf2..9a4335d73 100755 --- a/test/lsp.py +++ b/test/lsp.py @@ -1,26 +1,147 @@ #!/usr/bin/env python3 # pragma pylint: disable=too-many-lines +# test line 1 import argparse import fnmatch +import functools import json import os +import re import subprocess import sys import traceback -import re - -from typing import Any, List, Optional, Tuple, Union +from collections import namedtuple +from copy import deepcopy from enum import Enum, auto +from itertools import islice +from pathlib import PurePath +from typing import Any, List, Optional, Tuple, Union -import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows. +import colorama # Enables the use of SGR & CUP terminal VT sequences on Windows. from deepdiff import DeepDiff +if os.name == 'nt': + # pragma pylint: disable=import-error + import msvcrt +else: + import tty + # Turn off user input buffering so we get the input immediately, + # not only after a line break + tty.setcbreak(sys.stdin.fileno()) + + +def escape_string(text: str) -> str: + """ + Trivially escapes given input string's \r \n and \\. + """ + return text.translate(str.maketrans({ + "\r": r"\r", + "\n": r"\n", + "\\": r"\\" + })) + + +def getCharFromStdin(): + """ + Gets a single character from stdin without line-buffering. + """ + if os.name == 'nt': + # pragma pylint: disable=import-error + return msvcrt.getch().decode("utf-8") + else: + return sys.stdin.buffer.read(1) + + +""" +Named tuple that holds various regexes used to parse the test specification. +""" +TestRegexesTuple = namedtuple("TestRegexesTuple", [ + "sendRequest", # regex to find requests to be sent & tested + "findQuotedTag", # regex to find tags wrapped in quotes + "findTag", # regex to find tags + "fileDiagnostics", # regex to find diagnostic expectations for a file + "diagnostic" # regex to find a single diagnostic within the file expectations +]) +""" +Instance of the named tuple holding the regexes +""" +TEST_REGEXES = TestRegexesTuple( + re.compile(R'^// -> (?P[\w\/]+) {'), + re.compile(R'(?P"@\w+")'), + re.compile(R'(?P@\w+)'), + re.compile(R'// (?P\w+):[ ]?(?P[\w @]*)'), + re.compile(R'(?P@\w+) (?P\d\d\d\d)') +) + +""" +Named tuple holding regexes to find tags in the solidity code +""" +TagRegexesTuple = namedtuple("TagRegexestuple", ["simpleRange", "multilineRange"]) +TAG_REGEXES = TagRegexesTuple( + re.compile(R"(?P[\^]+) (?P@\w+)"), + re.compile(R"\^(?P[()]{1,2}) (?P@\w+)$") +) + +def split_path(path): + """ + Return the test name and the subdir path of the given path. + """ + sub_dir_separator = path.find("/") + + if sub_dir_separator == -1: + return (path, None) + + return (path[sub_dir_separator+1:], path[:sub_dir_separator]) + +def count_index(lines, start=0): + """ + Takes an iterable of lines and adds the current byte index so it's available + when iterating or looping. + """ + n = start + for elem in lines: + yield n, elem + n += 1 + len(elem) + +def tags_only(lines, start=0): + """ + Filter the lines for tag comments and report line number that tags refer to. + """ + n = start + numCommentLines = 0 + + def hasTag(line): + if line.find("// ") != -1: + for _, regex in TAG_REGEXES._asdict().items(): + if regex.search(line[len("// "):]) is not None: + return True + return False + + for line in lines: + if hasTag(line): + numCommentLines += 1 + yield n - numCommentLines, line + else: + numCommentLines = 0 + + n += 1 + + +def prepend_comments(sequence): + """ + Prepends a comment indicator to each element + """ + result = "" + for line in sequence.splitlines(True): + result = result + "// " + line + return result + + # {{{ JsonRpcProcess class BadHeader(Exception): def __init__(self, msg: str): super().__init__("Bad header: " + msg) - class JsonRpcProcess: exe_path: str exe_args: List[str] @@ -59,14 +180,16 @@ class JsonRpcProcess: while True: # read header line = self.process.stdout.readline() - if line == '': + if len(line) == 0: # server quit return None line = line.decode("utf-8") + if self.trace_io: + print(f"Received header-line: {escape_string(line)}") if not line.endswith("\r\n"): raise BadHeader("missing newline") - # remove the "\r\n" - line = line[:-2] + # Safely remove the "\r\n". + line = line.rstrip("\r\n") if line == '': break # done with the headers if line.startswith(CONTENT_LENGTH_HEADER): @@ -118,12 +241,30 @@ SGR_STATUS_OKAY = '\033[1;32m' SGR_STATUS_FAIL = '\033[1;31m' class ExpectationFailed(Exception): - def __init__(self, actual, expected): - self.actual = json.dumps(actual, sort_keys=True) - self.expected = json.dumps(expected, sort_keys=True) - diff = json.dumps(DeepDiff(actual, expected), indent=4) + class Part(Enum): + Diagnostics = auto() + Methods = auto() + + def __init__(self, reason: str, part): + self.part = part + super().__init__(reason) + +class JSONExpectationFailed(ExpectationFailed): + def __init__(self, actual, expected, part): + self.actual = actual + self.expected = expected + + expected_pretty = "" + + if expected is not None: + expected_pretty = json.dumps(expected, sort_keys=True) + + diff = DeepDiff(actual, expected) + super().__init__( - f"\n\tExpected {self.expected}\n\tbut got {self.actual}.\n\t{diff}" + f"\n\tExpected {expected_pretty}" + \ + f"\n\tbut got {json.dumps(actual, sort_keys=True)}.\n\t{diff}", + part ) @@ -180,15 +321,424 @@ class Counter: failed: int = 0 -class Marker(Enum): - SimpleRange = auto() - MultilineRange = auto() - - # Returns the given marker with the end extended by 'amount' def extendEnd(marker, amount=1): - marker["end"]["character"] += amount - return marker + newMarker = deepcopy(marker) + newMarker["end"]["character"] += amount + return newMarker + +class TestParserException(Exception): + def __init__(self, incompleteResult, msg: str): + self.result = incompleteResult + super().__init__("Failed to parse test specification: " + msg) + +class TestParser: + """ + Parses test specifications. + Usage example: + + parsed_testcases = TestParser(content).parse() + + # First diagnostics are yielded + expected_diagnostics = next(parsed_testcases) + ... + # Now each request/response pair in the test definition + for testcase in self.parsed_testcases: + ... + """ + RequestAndResponse = namedtuple('RequestAndResponse', + "method, request, response, responseBegin, responseEnd", + defaults=(None, None, None, None) + ) + Diagnostics = namedtuple('Diagnostics', 'tests start end has_header') + Diagnostic = namedtuple('Diagnostic', 'marker code') + + TEST_START = "// ----" + + def __init__(self, content: str): + self.content = content + self.lines = None + self.current_line_tuple = None + + def parse(self): + """ + Starts parsing the test specifications. + Will first yield with the diagnostics expectations as type 'Diagnostics'. + After that, it will yield once for every Request/Response pair found in + the file, each time as type 'RequestAndResponse'. + + """ + testDefStartIdx = self.content.rfind(f"\n{self.TEST_START}\n") + + if testDefStartIdx == -1: + # Set start/end to end of file if there is no test section + yield self.Diagnostics({}, len(self.content), len(self.content), False) + return + + self.lines = islice( + count_index(self.content[testDefStartIdx+1:].splitlines(), testDefStartIdx+1), + 1, + None + ) + self.next_line() + + yield self.parseDiagnostics() + + while not self.at_end(): + yield self.RequestAndResponse(**self.parseRequestAndResponse()) + self.next_line() + + + def parseDiagnostics(self): + """ + Parse diagnostic expectations specified in the file. + Returns a named tuple instance of "Diagnostics" + """ + diagnostics = { "tests": {}, "has_header": True } + + diagnostics["start"] = self.position() + + while not self.at_end(): + fileDiagMatch = TEST_REGEXES.fileDiagnostics.match(self.current_line()) + if fileDiagMatch is None: + break + + testDiagnostics = [] + + for diagnosticMatch in TEST_REGEXES.diagnostic.finditer(fileDiagMatch.group("diagnostics")): + testDiagnostics.append(self.Diagnostic( + diagnosticMatch.group("tag"), + int(diagnosticMatch.group("code")) + )) + + diagnostics["tests"][fileDiagMatch.group("testname")] = testDiagnostics + + self.next_line() + + diagnostics["end"] = self.position() + return self.Diagnostics(**diagnostics) + + + def parseRequestAndResponse(self): + RESPONSE_START = "// <- " + REQUEST_END = "// }" + COMMENT_PREFIX = "// " + + ret = {} + start_character = None + + # Parse request header + requestResult = TEST_REGEXES.sendRequest.match(self.current_line()) + if requestResult is None: + raise TestParserException(ret, "Method for request not found on line " + self.current_line()) + + ret["method"] = requestResult.group("method") + ret["request"] = "{\n" + + self.next_line() + + # Search for request block end + while not self.at_end(): + line = self.current_line() + ret["request"] += line[len(COMMENT_PREFIX):] + "\n" + + self.next_line() + + if line.startswith(REQUEST_END): + break + + # Reached end without finding request_end. Abort. + if self.at_end(): + raise TestParserException(ret, "Request body not found") + + + # Parse response header + if self.current_line().startswith(RESPONSE_START): + start_character = self.current_line()[len(RESPONSE_START)] + if start_character not in ("{", "["): + raise TestParserException(ret, "Response header malformed") + ret["response"] = self.current_line()[len(RESPONSE_START):] + "\n" + ret["responseBegin"] = self.position() + else: + raise TestParserException(ret, "Response header not found") + + self.next_line() + + end_character = "}" if start_character == "{" else "]" + + # Search for request block end + while not self.at_end(): + ret["response"] += self.current_line()[len(COMMENT_PREFIX):] + "\n" + + if self.current_line().startswith(f"// {end_character}"): + ret["responseEnd"] = self.position() + len(self.current_line()) + break + + self.next_line() + + # Reached end without finding block_end. Abort. + if self.at_end(): + raise TestParserException(ret, "Response footer not found") + + return ret + + def next_line(self): + self.current_line_tuple = next(self.lines, None) + + def current_line(self): + return self.current_line_tuple[1] + + def position(self): + """ + Returns current byte position + """ + if self.current_line_tuple is None: + return len(self.content) + return self.current_line_tuple[0] + + def at_end(self): + """ + Returns True if we exhausted the lines + """ + return self.current_line_tuple is None + +class FileTestRunner: + """ + Runs all tests in a given file. + It is required to call test_diagnostics() before calling test_methods(). + + When a test fails, asks the user how to proceed. + Offers automatic test expectation updates and rerunning of the tests. + """ + + class TestResult(Enum): + SuccessOrIgnored = auto() + Reparse = auto() + + def __init__(self, test_name, sub_dir, solc, suite): + self.test_name = test_name + self.sub_dir = sub_dir + self.suite = suite + self.solc = solc + self.open_tests = [] + self.content = self.suite.get_test_file_contents(self.test_name, self.sub_dir) + self.markers = self.suite.get_file_tags(self.test_name, self.sub_dir) + self.parsed_testcases = None + self.expected_diagnostics = None + + def test_diagnostics(self): + """ + Test that the expected diagnostics match the actual diagnostics + """ + try: + self.parsed_testcases = TestParser(self.content).parse() + + # Process diagnostics first + self.expected_diagnostics = next(self.parsed_testcases) + assert isinstance(self.expected_diagnostics, TestParser.Diagnostics) is True + + tests = self.expected_diagnostics.tests + + # Add our own test diagnostics if they didn't exist + if self.test_name not in tests: + tests[self.test_name] = [] + + published_diagnostics = \ + self.suite.open_file_and_wait_for_diagnostics(self.solc, self.test_name, self.sub_dir) + + for diagnostics in published_diagnostics: + if not diagnostics["uri"].startswith(self.suite.project_root_uri + "/"): + raise Exception( + f"'{self.test_name}.sol' imported file outside of test directory: '{diagnostics['uri']}'" + ) + self.open_tests.append(diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")]) + + self.suite.expect_equal( + len(published_diagnostics), + len(tests), + description="Amount of reports does not match!") + + for diagnostics in published_diagnostics: + testname_and_subdir = diagnostics["uri"].replace(self.suite.project_root_uri + "/", "")[:-len(".sol")] + testname, sub_dir = split_path(testname_and_subdir) + + expected_diagnostics = tests[testname] + self.suite.expect_equal( + len(diagnostics["diagnostics"]), + len(expected_diagnostics), + description="Unexpected amount of diagnostics" + ) + markers = self.suite.get_file_tags(testname, sub_dir) + for actual_diagnostic in diagnostics["diagnostics"]: + expected_diagnostic = next((diagnostic for diagnostic in + expected_diagnostics if actual_diagnostic['range'] == + markers[diagnostic.marker]), None) + + if expected_diagnostic is None: + raise ExpectationFailed( + f"Unexpected diagnostic: {json.dumps(actual_diagnostic, indent=4, sort_keys=True)}", + ExpectationFailed.Part.Diagnostics + ) + + self.suite.expect_diagnostic( + actual_diagnostic, + code=expected_diagnostic.code, + marker=markers[expected_diagnostic.marker] + ) + + except Exception: + self.close_all_open_files() + raise + + def close_all_open_files(self): + for testpath in self.open_tests: + test, sub_dir = split_path(testpath) + + self.solc.send_message( + 'textDocument/didClose', + { 'textDocument': { 'uri': self.suite.get_test_file_uri(test, sub_dir) }} + ) + self.suite.wait_for_diagnostics(self.solc) + + self.open_tests.clear() + + def test_methods(self) -> bool: + """ + Test all methods. Returns False if a reparsing is required, else True + """ + try: + # Now handle each request/response pair in the test definition + for testcase in self.parsed_testcases: + try: + self.run_testcase(testcase) + except JSONExpectationFailed as e: + result = self.user_interaction_failed_method_test(testcase, e.actual, e.expected) + + if result == self.TestResult.Reparse: + return False + + return True + except TestParserException as e: + print(e) + print(e.result) + raise + finally: + self.close_all_open_files() + + def user_interaction_failed_method_test(self, testcase, actual, expected): + actual_pretty = self.suite.replace_ranges_with_tags(actual, self.sub_dir) + + if expected is None: + print("Failed to parse expected response, received:\n" + actual) + else: + print("Expected:\n" + \ + self.suite.replace_ranges_with_tags(expected, self.sub_dir) + \ + "\nbut got:\n" + actual_pretty + ) + + while True: + print("(u)pdate/(r)etry/(i)gnore?") + user_response = getCharFromStdin() + if user_response == "i": + return self.TestResult.SuccessOrIgnored + + if user_response == "u": + actual = actual["result"] + self.content = self.content[:testcase.responseBegin] + \ + prepend_comments( + "<- " + \ + self.suite.replace_ranges_with_tags(actual, self.sub_dir)) + \ + self.content[testcase.responseEnd:] + + with open(self.suite.get_test_file_path(\ + self.test_name, self.sub_dir), \ + mode="w", \ + encoding="utf-8", \ + newline='') as f: + f.write(self.content) + return self.TestResult.Reparse + if user_response == "r": + return self.TestResult.Reparse + + print("Invalid response.") + + + def run_testcase(self, testcase: TestParser.RequestAndResponse): + """ + Runs the given testcase. + """ + requestBodyJson = self.parse_json_with_tags(testcase.request, self.markers) + # add textDocument/uri if missing + if 'textDocument' not in requestBodyJson: + requestBodyJson['textDocument'] = { 'uri': self.suite.get_test_file_uri(self.test_name, self.sub_dir) } + actualResponseJson = self.solc.call_method(testcase.method, requestBodyJson) + + # simplify response + for result in actualResponseJson["result"]: + if "uri" in result: + result["uri"] = result["uri"].replace(self.suite.project_root_uri + "/" + self.sub_dir + "/", "") + if "jsonrpc" in actualResponseJson: + actualResponseJson.pop("jsonrpc") + + try: + expectedResponseJson = self.parse_json_with_tags(testcase.response, self.markers) + except json.decoder.JSONDecodeError: + expectedResponseJson = None + + expectedResponseJson = { "result": expectedResponseJson } + + self.suite.expect_equal( + actualResponseJson, + expectedResponseJson, + f"Request failed: \n{testcase.request}", + ExpectationFailed.Part.Methods + ) + + + def parse_json_with_tags(self, content, markersFallback): + """ + Replaces any tags with their actual content and parsers the result as + json to return it. + """ + split_by_tag = TEST_REGEXES.findTag.split(content) + + # add quotes so we can parse it as json + contentReplaced = '"'.join(split_by_tag) + contentJson = json.loads(contentReplaced) + + def replace_tag(data, markers): + + if isinstance(data, list): + for el in data: + replace_tag(el, markers) + return data + + if not isinstance(data, dict): + return data + + # Check if we need markers from a specific file + # Needs to be done before the loop or it might be called only after + # we found "range" or "position" + if "uri" in data: + markers = self.suite.get_file_tags(data["uri"][:-len(".sol")], self.sub_dir) + + for key, val in data.items(): + if key == "range": + for tag, tagRange in markers.items(): + if tag == val: + data[key] = tagRange + elif key == "position": + for tag, tagRange in markers.items(): + if tag == val: + data[key] = tagRange["start"] + elif isinstance(val, dict): + replace_tag(val, markers) + elif isinstance(val, list): + for el in val: + replace_tag(el, markers) + return data + + return replace_tag(contentJson, markersFallback) class SolidityLSPTestSuite: # {{{ @@ -198,22 +748,17 @@ class SolidityLSPTestSuite: # {{{ trace_io: bool = False fail_fast: bool = False test_pattern: str - marker_regexes: {} def __init__(self): colorama.init() args = create_cli_parser().parse_args() self.solc_path = args.solc_path self.project_root_dir = os.path.realpath(args.project_root_dir) + "/test/libsolidity/lsp" - self.project_root_uri = "file://" + self.project_root_dir + self.project_root_uri = PurePath(self.project_root_dir).as_uri() self.print_assertions = args.print_assertions self.trace_io = args.trace_io self.test_pattern = args.test_pattern self.fail_fast = args.fail_fast - self.marker_regexes = { - Marker.SimpleRange: re.compile(R"(?P[\^]+) (?P@\w+)"), - Marker.MultilineRange: re.compile(R"\^(?P[()]) (?P@\w+)$") - } print(f"{SGR_NOTICE}test pattern: {self.test_pattern}{SGR_RESET}") @@ -283,22 +828,27 @@ class SolidityLSPTestSuite: # {{{ params['rootUri'] = None lsp.call_method('initialize', params) lsp.send_notification('initialized') + # Enable traces to receive the amount of expected diagnostics before + # actually receiving them. + lsp.send_message("$/setTrace", { 'value': 'messages' }) # {{{ helpers - def get_test_file_path(self, test_case_name): + def get_test_file_path(self, test_case_name, sub_dir=None): + if sub_dir: + return f"{self.project_root_dir}/{sub_dir}/{test_case_name}.sol" return f"{self.project_root_dir}/{test_case_name}.sol" - def get_test_file_uri(self, test_case_name): - return "file://" + self.get_test_file_path(test_case_name) + def get_test_file_uri(self, test_case_name, sub_dir=None): + return PurePath(self.get_test_file_path(test_case_name, sub_dir)).as_uri() - def get_test_file_contents(self, test_case_name): + def get_test_file_contents(self, test_case_name, sub_dir=None): """ Reads the file contents from disc for a given test case. The `test_case_name` will be the basename of the file - in the test path (test/libsolidity/lsp). + in the test path (test/libsolidity/lsp/{sub_dir}). """ - with open(self.get_test_file_path(test_case_name), mode="r", encoding="utf-8", newline='') as f: - return f.read() + with open(self.get_test_file_path(test_case_name, sub_dir), mode="r", encoding="utf-8", newline='') as f: + return f.read().replace("\r\n", "\n") def require_params_for_method(self, method_name: str, message: dict) -> Any: """ @@ -314,50 +864,109 @@ class SolidityLSPTestSuite: # {{{ raise RuntimeError(f"Error {code} received. {text}") if 'method' not in message.keys(): raise RuntimeError("No method received but something else.") - self.expect_equal(message['method'], method_name, "Ensure expected method name") + self.expect_equal(message['method'], method_name, description="Ensure expected method name") return message['params'] - def wait_for_diagnostics(self, solc: JsonRpcProcess, count: int) -> List[dict]: + def wait_for_diagnostics(self, solc: JsonRpcProcess) -> List[dict]: """ - Return `count` number of published diagnostic reports sorted by file URI. + Return all published diagnostic reports sorted by file URI. """ reports = [] - for _ in range(0, count): + + num_files = solc.receive_message()["params"]["openFileCount"] + + for _ in range(0, num_files): message = solc.receive_message() + assert message is not None # This can happen if the server aborts early. + reports.append( self.require_params_for_method( 'textDocument/publishDiagnostics', message, ) ) + return sorted(reports, key=lambda x: x['uri']) + def fetch_and_format_diagnostics(self, solc: JsonRpcProcess, test, sub_dir=None): + expectations = "" + + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, test, sub_dir) + + for diagnostics in published_diagnostics: + testname = diagnostics["uri"].replace(f"{self.project_root_uri}/{sub_dir}/", "")[:-len(".sol")] + + # Skip empty diagnostics within the same file + if len(diagnostics["diagnostics"]) == 0 and testname == test: + continue + + expectations += f"// {testname}:" + + for diagnostic in diagnostics["diagnostics"]: + tag = self.find_tag_with_range(testname, sub_dir, diagnostic['range']) + + if tag is None: + raise Exception(f"No tag found for diagnostic range {diagnostic['range']}") + + expectations += f" {tag} {diagnostic['code']}" + expectations += "\n" + + return expectations + + def update_diagnostics_in_file( + self, + solc: JsonRpcProcess, + test, + sub_dir, + content, + current_diagnostics: TestParser.Diagnostics + ): + test_header = "" + + if not current_diagnostics.has_header: + test_header = f"{TestParser.TEST_START}\n" + + content = content[:current_diagnostics.start] + \ + test_header + \ + self.fetch_and_format_diagnostics(solc, test, sub_dir) + \ + content[current_diagnostics.end:] + + with open(self.get_test_file_path(test, sub_dir), mode="w", encoding="utf-8", newline='') as f: + f.write(content) + + return content + def open_file_and_wait_for_diagnostics( self, solc_process: JsonRpcProcess, test_case_name: str, - max_diagnostic_reports: int = 1 + sub_dir=None ) -> List[Any]: """ Opens file for given test case and waits for diagnostics to be published. """ - assert max_diagnostic_reports > 0 solc_process.send_message( 'textDocument/didOpen', { 'textDocument': { - 'uri': self.get_test_file_uri(test_case_name), + 'uri': self.get_test_file_uri(test_case_name, sub_dir), 'languageId': 'Solidity', 'version': 1, - 'text': self.get_test_file_contents(test_case_name) + 'text': self.get_test_file_contents(test_case_name, sub_dir) } } ) - return self.wait_for_diagnostics(solc_process, max_diagnostic_reports) + return self.wait_for_diagnostics(solc_process) - def expect_equal(self, actual, expected, description="Equality") -> None: + def expect_equal( + self, + actual, + expected, + description="Equality", + part=ExpectationFailed.Part.Diagnostics + ) -> None: self.assertion_counter.total += 1 prefix = f"[{self.assertion_counter.total}] {SGR_ASSERT_BEGIN}{description}: " diff = DeepDiff(actual, expected) @@ -370,7 +979,7 @@ class SolidityLSPTestSuite: # {{{ # Failed assertions are always printed. self.assertion_counter.failed += 1 print(prefix + SGR_STATUS_FAIL + 'FAILED' + SGR_RESET) - raise ExpectationFailed(actual, expected) + raise JSONExpectationFailed(actual, expected, part) def expect_empty_diagnostics(self, published_diagnostics: List[dict]) -> None: self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") @@ -384,10 +993,21 @@ class SolidityLSPTestSuite: # {{{ startEndColumns: Tuple[int, int] = None, marker: {} = None ): - self.expect_equal(diagnostic['code'], code, f'diagnostic: {code}') + self.expect_equal( + diagnostic['code'], + code, + ExpectationFailed.Part.Diagnostics, + f'diagnostic: {code}' + ) if marker: - self.expect_equal(diagnostic['range'], marker, "diagnostic: check range") + self.expect_equal( + diagnostic['range'], + marker, + ExpectationFailed.Part.Diagnostics, + "diagnostic: check range" + ) + else: assert len(startEndColumns) == 2 [startColumn, endColumn] = startEndColumns @@ -397,6 +1017,7 @@ class SolidityLSPTestSuite: # {{{ 'start': {'character': startColumn, 'line': lineNo}, 'end': {'character': endColumn, 'line': lineNo} }, + ExpectationFailed.Part.Diagnostics, "diagnostic: check range" ) @@ -446,44 +1067,103 @@ class SolidityLSPTestSuite: # {{{ message = "Goto definition (" + description + ")" self.expect_equal(len(response['result']), 1, message) self.expect_location(response['result'][0], expected_uri, expected_lineNo, expected_startEndColumns) + + + def find_tag_with_range(self, test, sub_dir, target_range): + """ + Find and return the tag that represents the requested range otherwise + return None. + """ + markers = self.get_file_tags(test, sub_dir) + + for tag, tag_range in markers.items(): + if tag_range == target_range: + return str(tag) + + return None + + def replace_ranges_with_tags(self, content, sub_dir): + """ + Replace matching ranges with "@". + """ + + def recursive_iter(obj): + if isinstance(obj, dict): + yield obj + for item in obj.values(): + yield from recursive_iter(item) + elif any(isinstance(obj, t) for t in (list, tuple)): + for item in obj: + yield from recursive_iter(item) + + for item in recursive_iter(content): + if "uri" in item and "range" in item: + markers = self.get_file_tags(item["uri"][:-len(".sol")], sub_dir) + for tag, tagRange in markers.items(): + if tagRange == item["range"]: + item["range"] = str(tag) + + # Convert JSON to string and split it at the quoted tags + split_by_tag = TEST_REGEXES.findQuotedTag.split(json.dumps(content, indent=4, sort_keys=True)) + + # remove the quotes and return result + return "".join(map(lambda p: p[1:-1] if p.startswith('"@') else p, split_by_tag)) + + def user_interaction_failed_diagnostics( + self, + solc: JsonRpcProcess, + test, + sub_dir, + content, + current_diagnostics: TestParser.Diagnostics + ): + """ + Asks the user how to proceed after an error. + Returns True if the test/file should be ignored, otherwise False + """ + while True: + print("(u)pdate/(r)etry/(s)kip file?") + user_response = getCharFromStdin() + if user_response == "u": + while True: + try: + self.update_diagnostics_in_file(solc, test, sub_dir, content, current_diagnostics) + return False + # pragma pylint: disable=broad-except + except Exception as e: + print(e) + if self.user_interaction_failed_autoupdate(test, sub_dir): + return True + elif user_response == 's': + return True + elif user_response == 'r': + return False + + def user_interaction_failed_autoupdate(self, test, sub_dir): + print("(e)dit/(r)etry/(s)kip file?") + user_response = getCharFromStdin() + if user_response == "r": + print("retrying...") + # pragma pylint: disable=no-member + self.get_file_tags.cache_clear() + return False + if user_response == "e": + editor = os.environ.get('VISUAL', os.environ.get('EDITOR', 'vi')) + subprocess.run( + f'{editor} {self.get_test_file_path(test, sub_dir)}', + shell=True, + check=True + ) + # pragma pylint: disable=no-member + self.get_file_tags.cache_clear() + elif user_response == "s": + print("skipping...") + + return True + # }}} # {{{ actual tests - def test_publish_diagnostics_warnings(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - TEST_NAME = 'publish_diagnostics_1' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) - - self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message") - report = published_diagnostics[0] - - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") - diagnostics = report['diagnostics'] - - markers = self.get_file_tags(TEST_NAME) - - self.expect_equal(len(diagnostics), 3, "3 diagnostic messages") - self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"]) - self.expect_diagnostic(diagnostics[1], code=2072, marker=markers["@unusedVariable"]) - self.expect_diagnostic(diagnostics[2], code=2072, marker=markers["@unusedContractVariable"]) - - def test_publish_diagnostics_errors(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - TEST_NAME = 'publish_diagnostics_2' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) - - self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message") - report = published_diagnostics[0] - - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") - diagnostics = report['diagnostics'] - - markers = self.get_file_tags(TEST_NAME) - - self.expect_equal(len(diagnostics), 3, "3 diagnostic messages") - self.expect_diagnostic(diagnostics[0], code=9574, marker=markers["@conversionError"]) - self.expect_diagnostic(diagnostics[1], code=6777, marker=markers["@argumentsRequired"]) - self.expect_diagnostic(diagnostics[2], code=6160, marker=markers["@wrongArgumentsCount"]) def test_publish_diagnostics_errors_multiline(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) @@ -510,7 +1190,7 @@ class SolidityLSPTestSuite: # {{{ def test_textDocument_didOpen_with_relative_import(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) TEST_NAME = 'didOpen_with_import' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, 2) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files") @@ -519,15 +1199,15 @@ class SolidityLSPTestSuite: # {{{ self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") self.expect_equal(len(report['diagnostics']), 0, "no diagnostics") - # imported file (./lib.sol): + # imported file (goto/lib.sol): report = published_diagnostics[1] - self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI") self.expect_equal(len(report['diagnostics']), 1, "one diagnostic") - marker = self.get_file_tags("lib")["@diagnostics"] + marker = self.get_file_tags("lib", "goto")["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) - - def get_file_tags(self, test_name: str, verbose=False): + @functools.lru_cache() # pragma pylint: disable=lru-cache-decorating-method + def get_file_tags(self, test_name: str, sub_dir=None, verbose=False): """ Finds all tags (e.g. @tagname) in the given test and returns them as a dictionary having the following structure: { @@ -537,18 +1217,16 @@ class SolidityLSPTestSuite: # {{{ } } """ - content = self.get_test_file_contents(test_name) + content = self.get_test_file_contents(test_name, sub_dir) markers = {} - for lineNum, line in enumerate(content.splitlines(), start=-1): + for lineNum, line in tags_only(content.splitlines()): commentStart = line.find("//") - if commentStart == -1: - continue - for kind, regex in self.marker_regexes.items(): + for kind, regex in TAG_REGEXES._asdict().items(): for match in regex.finditer(line[commentStart:]): - if kind == Marker.SimpleRange: + if kind == "simpleRange": markers[match.group("tag")] = { "start": { "line": lineNum, @@ -558,7 +1236,7 @@ class SolidityLSPTestSuite: # {{{ "line": lineNum, "character": match.end("range") + commentStart }} - elif kind == Marker.MultilineRange: + elif kind == "multilineRange": if match.group("delimiter") == "(": markers[match.group("tag")] = \ { "start": { "line": lineNum, "character": 0 } } @@ -574,14 +1252,14 @@ class SolidityLSPTestSuite: # {{{ def test_didChange_in_A_causing_error_in_B(self, solc: JsonRpcProcess) -> None: # Reusing another test but now change some file that generates an error in the other. self.test_textDocument_didOpen_with_relative_import(solc) - marker = self.get_file_tags("lib")["@addFunction"] - self.open_file_and_wait_for_diagnostics(solc, 'lib', 2) + marker = self.get_file_tags("lib", "goto")["@addFunction"] + self.open_file_and_wait_for_diagnostics(solc, 'lib', "goto") solc.send_message( 'textDocument/didChange', { 'textDocument': { - 'uri': self.get_test_file_uri('lib') + 'uri': self.get_test_file_uri('lib', 'goto') }, 'contentChanges': [ @@ -592,7 +1270,7 @@ class SolidityLSPTestSuite: # {{{ ] } ) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 2, "Diagnostic reports for 2 files") # Main file now contains a new diagnostic @@ -605,14 +1283,14 @@ class SolidityLSPTestSuite: # {{{ # The modified file retains the same diagnostics. report = published_diagnostics[1] - self.expect_equal(report['uri'], self.get_test_file_uri('lib')) + self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto')) self.expect_equal(len(report['diagnostics']), 0) # The warning went away because the compiler aborts further processing after the error. def test_textDocument_didOpen_with_relative_import_without_project_url(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc, expose_project_root=False) TEST_NAME = 'didOpen_with_import' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, 2) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) self.verify_didOpen_with_import_diagnostics(published_diagnostics) def verify_didOpen_with_import_diagnostics( @@ -627,25 +1305,64 @@ class SolidityLSPTestSuite: # {{{ self.expect_equal(report['uri'], self.get_test_file_uri(main_file_name), "Correct file URI") self.expect_equal(len(report['diagnostics']), 0, "one diagnostic") - # imported file (./lib.sol): + # imported file (./goto/lib.sol): report = published_diagnostics[1] - self.expect_equal(report['uri'], self.get_test_file_uri('lib'), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI") self.expect_equal(len(report['diagnostics']), 1, "one diagnostic") - marker = self.get_file_tags('lib')["@diagnostics"] + markers = self.get_file_tags('lib', 'goto') + marker = markers["@diagnostics"] self.expect_diagnostic(report['diagnostics'][0], code=2072, marker=marker) + def test_generic(self, solc: JsonRpcProcess) -> None: + self.setup_lsp(solc) + + sub_dirs = filter( + lambda filepath: filepath.is_dir(), + os.scandir(self.project_root_dir) + ) + + for sub_dir in map(lambda filepath: filepath.name, sub_dirs): + tests = map( + lambda filename: filename[:-len(".sol")], + os.listdir(f"{self.project_root_dir}/{sub_dir}") + ) + + print(f"Running tests in subdirectory '{sub_dir}'...") + for test in tests: + try_again = True + print(f"\t{test}") + + while try_again: + runner = FileTestRunner(test, sub_dir, solc, self) + + try: + runner.test_diagnostics() + try_again = not runner.test_methods() + except ExpectationFailed as e: + print(e) + + if e.part == e.Part.Diagnostics: + try_again = not self.user_interaction_failed_diagnostics( + solc, + test, + sub_dir, + runner.content, + runner.expected_diagnostics + ) + else: + raise def test_textDocument_didChange_updates_diagnostics(self, solc: JsonRpcProcess) -> None: self.setup_lsp(solc) TEST_NAME = 'publish_diagnostics_1' - published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME) + published_diagnostics = self.open_file_and_wait_for_diagnostics(solc, TEST_NAME, "goto") self.expect_equal(len(published_diagnostics), 1, "One published_diagnostics message") report = published_diagnostics[0] - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME, "goto"), "Correct file URI") diagnostics = report['diagnostics'] self.expect_equal(len(diagnostics), 3, "3 diagnostic messages") - markers = self.get_file_tags(TEST_NAME) + markers = self.get_file_tags(TEST_NAME, "goto") self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"]) self.expect_diagnostic(diagnostics[1], code=2072, marker=markers["@unusedVariable"]) self.expect_diagnostic(diagnostics[2], code=2072, marker=markers["@unusedContractVariable"]) @@ -654,7 +1371,7 @@ class SolidityLSPTestSuite: # {{{ 'textDocument/didChange', { 'textDocument': { - 'uri': self.get_test_file_uri(TEST_NAME) + 'uri': self.get_test_file_uri(TEST_NAME, "goto") }, 'contentChanges': [ { @@ -664,10 +1381,10 @@ class SolidityLSPTestSuite: # {{{ ] } ) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1) report = published_diagnostics[0] - self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME), "Correct file URI") + self.expect_equal(report['uri'], self.get_test_file_uri(TEST_NAME, "goto"), "Correct file URI") diagnostics = report['diagnostics'] self.expect_equal(len(diagnostics), 2) self.expect_diagnostic(diagnostics[0], code=6321, marker=markers["@unusedReturnVariable"]) @@ -676,9 +1393,9 @@ class SolidityLSPTestSuite: # {{{ def test_textDocument_didChange_delete_line_and_close(self, solc: JsonRpcProcess) -> None: # Reuse this test to prepare and ensure it is as expected self.test_textDocument_didOpen_with_relative_import(solc) - self.open_file_and_wait_for_diagnostics(solc, 'lib', 2) + self.open_file_and_wait_for_diagnostics(solc, 'lib', 'goto') - marker = self.get_file_tags('lib')["@diagnostics"] + marker = self.get_file_tags('lib', 'goto')["@diagnostics"] # lib.sol: Fix the unused variable message by removing it. solc.send_message( @@ -686,7 +1403,7 @@ class SolidityLSPTestSuite: # {{{ { 'textDocument': { - 'uri': self.get_test_file_uri('lib') + 'uri': self.get_test_file_uri('lib', 'goto') }, 'contentChanges': # delete the in-body statement: `uint unused;` [ @@ -697,22 +1414,22 @@ class SolidityLSPTestSuite: # {{{ ] } ) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 2, "published diagnostics count") report1 = published_diagnostics[0] self.expect_equal(report1['uri'], self.get_test_file_uri('didOpen_with_import'), "Correct file URI") self.expect_equal(len(report1['diagnostics']), 0, "no diagnostics in didOpen_with_import.sol") report2 = published_diagnostics[1] - self.expect_equal(report2['uri'], self.get_test_file_uri('lib'), "Correct file URI") + self.expect_equal(report2['uri'], self.get_test_file_uri('lib', 'goto'), "Correct file URI") self.expect_equal(len(report2['diagnostics']), 0, "no diagnostics in lib.sol") # Now close the file and expect the warning to re-appear solc.send_message( 'textDocument/didClose', - { 'textDocument': { 'uri': self.get_test_file_uri('lib') }} + { 'textDocument': { 'uri': self.get_test_file_uri('lib', 'goto') }} ) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.verify_didOpen_with_import_diagnostics(published_diagnostics) def test_textDocument_opening_two_new_files_edit_and_close(self, solc: JsonRpcProcess) -> None: @@ -734,7 +1451,7 @@ class SolidityLSPTestSuite: # {{{ ]) } }) - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, "one publish diagnostics notification") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") @@ -750,7 +1467,7 @@ class SolidityLSPTestSuite: # {{{ ]) } }) - reports = self.wait_for_diagnostics(solc, 2) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, "one publish diagnostics notification") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") self.expect_equal(len(reports[1]['diagnostics']), 0, "should not contain diagnostics") @@ -769,7 +1486,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - reports = self.wait_for_diagnostics(solc, 2) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, "one publish diagnostics notification") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") self.expect_equal(len(reports[1]['diagnostics']), 0, "should not contain diagnostics") @@ -779,7 +1496,7 @@ class SolidityLSPTestSuite: # {{{ { 'textDocument': { 'uri': FILE_B_URI }} ) # We only get one diagnostics message since the diagnostics for b.sol was empty. - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, "one publish diagnostics notification") self.expect_diagnostic(reports[0]['diagnostics'][0], 6275, 2, (0, 17)) # a.sol: File B not found self.expect_equal(reports[0]['uri'], FILE_A_URI, "Correct uri") @@ -792,7 +1509,7 @@ class SolidityLSPTestSuite: # {{{ """ self.setup_lsp(solc) - FILE_A_URI = f'file://{self.project_root_dir}/a.sol' + FILE_A_URI = f'{self.project_root_uri}/a.sol' solc.send_message('textDocument/didOpen', { 'textDocument': { 'uri': FILE_A_URI, @@ -801,14 +1518,14 @@ class SolidityLSPTestSuite: # {{{ 'text': '// SPDX-License-Identifier: UNLICENSED\n' 'pragma solidity >=0.8.0;\n' - 'import "./lib.sol";\n' + 'import "./goto/lib.sol";\n' } }) - reports = self.wait_for_diagnostics(solc, 2) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 2, '') self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") - marker = self.get_file_tags("lib")["@diagnostics"] + marker = self.get_file_tags("lib", 'goto')["@diagnostics"] # unused variable in lib.sol self.expect_diagnostic(reports[1]['diagnostics'][0], code=2072, marker=marker) @@ -818,12 +1535,11 @@ class SolidityLSPTestSuite: # {{{ 'textDocument/didClose', { 'textDocument': { 'uri': FILE_A_URI }} ) - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1, '') - self.expect_equal(reports[0]['uri'], f'file://{self.project_root_dir}/lib.sol', "") + self.expect_equal(reports[0]['uri'], f'{self.project_root_uri}/goto/lib.sol', "") self.expect_equal(len(reports[0]['diagnostics']), 0, "should not contain diagnostics") - def test_textDocument_didChange_at_eol(self, solc: JsonRpcProcess) -> None: """ Append at one line and insert a new one below. @@ -839,7 +1555,7 @@ class SolidityLSPTestSuite: # {{{ 'text': self.get_test_file_contents(FILE_NAME) } }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "no diagnostics") solc.send_message('textDocument/didChange', { @@ -856,7 +1572,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report2 = published_diagnostics[0] self.expect_equal(report2['uri'], FILE_URI, "Correct file URI") @@ -875,238 +1591,13 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report3 = published_diagnostics[0] self.expect_equal(report3['uri'], FILE_URI, "Correct file URI") self.expect_equal(len(report3['diagnostics']), 1, "one diagnostic") self.expect_diagnostic(report3['diagnostics'][0], 4126, 6, (1, 23)) - def test_textDocument_definition(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - FILE_NAME = 'goto_definition' - FILE_URI = self.get_test_file_uri(FILE_NAME) - LIB_URI = self.get_test_file_uri('lib') - solc.send_message('textDocument/didOpen', { - 'textDocument': { - 'uri': FILE_URI, - 'languageId': 'Solidity', - 'version': 1, - 'text': self.get_test_file_contents(FILE_NAME) - } - }) - published_diagnostics = self.wait_for_diagnostics(solc, 2) - self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files") - self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0) - self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1) - self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 33, (8, 19)) # unused variable in lib.sol - - # import directive - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(3, 9), # symbol `"./lib.sol"` in `import "./lib.sol"` - expected_uri=LIB_URI, - expected_lineNo=0, - expected_startEndColumns=(0, 0), - description="import directive" - ) - - # type symbol to jump to type defs (error, contract, enum, ...) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(30, 19), # symbol `IA` in `new IA()` - expected_uri=FILE_URI, - expected_lineNo=10, - expected_startEndColumns=(9, 11), - description="type symbol to jump to definition" - ) - - # virtual function lookup? - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(31, 12), # symbol `f`, jumps to interface definition - expected_uri=FILE_URI, - expected_lineNo=7, - expected_startEndColumns=(13, 14), - description="virtual function lookup" - ) - - # using for - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(37, 10), # symbol `add` in `i.add(5)` - expected_uri=FILE_URI, - expected_lineNo=22, - expected_startEndColumns=(13, 16), - description="using for" - ) - - # library - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(43, 15), # symbol `Lib` in `Lib.add(n, 1)` - expected_uri=LIB_URI, - expected_lineNo=22, - expected_startEndColumns=(8, 11), - description="Library symbol from different file" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(43, 19), # symbol `add` in `Lib.add(n, 1)` - expected_uri=LIB_URI, - expected_lineNo=24, - expected_startEndColumns=(13, 16), - description="Library member symbol from different file" - ) - - # enum type symbol and enum values - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(46, 19), # symbol `Color` in function signature's parameter - expected_uri=LIB_URI, - expected_lineNo=13, - expected_startEndColumns=(5, 10), - description="Enum type" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(48, 24), # symbol `Red` in `Color.Red` - expected_uri=LIB_URI, - expected_lineNo=15, - expected_startEndColumns=(4, 7), - description="Enum value" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(48, 24), # symbol `Red` in `Color.Red` - expected_uri=LIB_URI, - expected_lineNo=15, - expected_startEndColumns=(4, 7), - description="Enum value" - ) - - # local variable declarations - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(49, 17), # symbol `e` in `(c == e)` - expected_uri=FILE_URI, - expected_lineNo=48, - expected_startEndColumns=(14, 15), - description="local variable declaration" - ) - - # User defined type - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(58, 8), # symbol `Price` in `Price p ...` - expected_uri=FILE_URI, - expected_lineNo=55, - expected_startEndColumns=(9, 14), - description="User defined type on left hand side" - ) - - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(58, 18), # symbol `Price` in `Price.wrap()` expected_uri=FILE_URI, - expected_uri=FILE_URI, - expected_lineNo=55, - expected_startEndColumns=(9, 14), - description="User defined type on right hand side." - ) - - # struct constructor also properly jumps to the struct's declaration. - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(64, 33), # symbol `RGBColor` right hand side expression. - expected_uri=LIB_URI, - expected_lineNo=38, - expected_startEndColumns=(7, 15), - description="Struct constructor." - ) - - def test_textDocument_definition_imports(self, solc: JsonRpcProcess) -> None: - self.setup_lsp(solc) - FILE_NAME = 'goto_definition_imports' - FILE_URI = self.get_test_file_uri(FILE_NAME) - LIB_URI = self.get_test_file_uri('lib') - solc.send_message('textDocument/didOpen', { - 'textDocument': { - 'uri': FILE_URI, - 'languageId': 'Solidity', - 'version': 1, - 'text': self.get_test_file_contents(FILE_NAME) - } - }) - published_diagnostics = self.wait_for_diagnostics(solc, 2) - self.expect_equal(len(published_diagnostics), 2, "publish diagnostics for 2 files") - self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0) - self.expect_equal(len(published_diagnostics[1]['diagnostics']), 1) - self.expect_diagnostic(published_diagnostics[1]['diagnostics'][0], 2072, 33, (8, 19)) # unused variable in lib.sol - - # import directive: test symbol alias - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(3, 9), # in `Weather` of `import {Weather as Wetter} from "./lib.sol"` - expected_uri=LIB_URI, - expected_lineNo=6, - expected_startEndColumns=(5, 12), - description="goto definition of symbol in symbol alias import directive" - ) - - # import directive: test symbol alias - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(8, 55), # `Wetter` in return type declaration - expected_uri=LIB_URI, - expected_lineNo=6, - expected_startEndColumns=(5, 12), - description="goto definition of symbol in symbol alias import directive" - ) - - # That.Color tests with `That` being the aliased library to be imported. - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(13, 55), # `That` in return type declaration - expected_uri=LIB_URI, - expected_lineNo=13, - expected_startEndColumns=(5, 10), - description="goto definition of symbol in symbol alias import directive" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(15, 8), - expected_uri=LIB_URI, - expected_lineNo=13, - expected_startEndColumns=(5, 10), - description="`That` in LHS variable assignment" - ) - self.expect_goto_definition_location( - solc=solc, - document_uri=FILE_URI, - document_position=(15, 27), - expected_uri=FILE_URI, - expected_lineNo=4, - expected_startEndColumns=(22, 26), - description="`That` in expression" - ) - def test_textDocument_didChange_empty_file(self, solc: JsonRpcProcess) -> None: """ Starts with an empty file and changes it to look like @@ -1126,7 +1617,7 @@ class SolidityLSPTestSuite: # {{{ 'text': '' } }) - reports = self.wait_for_diagnostics(solc, 1) + reports = self.wait_for_diagnostics(solc) self.expect_equal(len(reports), 1) report = reports[0] published_diagnostics = report['diagnostics'] @@ -1147,7 +1638,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 2) + published_diagnostics = self.wait_for_diagnostics(solc) self.verify_didOpen_with_import_diagnostics(published_diagnostics, 'a_new_file') def test_textDocument_didChange_multi_line(self, solc: JsonRpcProcess) -> None: @@ -1166,7 +1657,7 @@ class SolidityLSPTestSuite: # {{{ 'text': self.get_test_file_contents(FILE_NAME) } }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") self.expect_equal(len(published_diagnostics[0]['diagnostics']), 0, "no diagnostics") solc.send_message('textDocument/didChange', { @@ -1181,7 +1672,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report2 = published_diagnostics[0] self.expect_equal(report2['uri'], FILE_URI, "Correct file URI") @@ -1201,7 +1692,7 @@ class SolidityLSPTestSuite: # {{{ } ] }) - published_diagnostics = self.wait_for_diagnostics(solc, 1) + published_diagnostics = self.wait_for_diagnostics(solc) self.expect_equal(len(published_diagnostics), 1, "one publish diagnostics notification") report3 = published_diagnostics[0] self.expect_equal(report3['uri'], FILE_URI, "Correct file URI") @@ -1232,6 +1723,7 @@ class SolidityLSPTestSuite: # {{{ # }}} # }}} + if __name__ == "__main__": suite = SolidityLSPTestSuite() exit_code = suite.main()