From 9a7c364c716c375cb137b8d52f8f562c7824773d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20=C5=9Aliwak?= Date: Sun, 13 Jun 2021 15:53:16 +0200 Subject: [PATCH] Process input files in Standard JSON mode just like in other modes - This makes `-` for stdin work. - `--ignore-missing` now works with `--standard-json`, though it's not very useful because there can be at most one input file. - Separate errors for situations where there are no input files on the command line (this can be detected in the parser) and where they are not present on disk. --- Changelog.md | 2 + solc/CommandLineInterface.cpp | 95 ++--- solc/CommandLineInterface.h | 8 +- solc/CommandLineParser.cpp | 47 ++- solc/CommandLineParser.h | 1 - solc/main.cpp | 2 + test/CMakeLists.txt | 3 + test/FilesystemUtils.cpp | 15 + test/FilesystemUtils.h | 5 + test/cmdlineTests/standard_file_not_found/err | 2 +- .../analysis/FunctionCallGraph.cpp | 2 +- test/solc/CommandLineInterface.cpp | 346 ++++++++++++++++++ test/solc/CommandLineParser.cpp | 32 +- test/solc/Common.cpp | 58 +++ test/solc/Common.h | 53 +++ test/yulPhaser/TestHelpers.h | 2 +- 16 files changed, 583 insertions(+), 90 deletions(-) create mode 100644 test/solc/CommandLineInterface.cpp create mode 100644 test/solc/Common.cpp create mode 100644 test/solc/Common.h diff --git a/Changelog.md b/Changelog.md index 042248fe7..490d054dd 100644 --- a/Changelog.md +++ b/Changelog.md @@ -15,6 +15,8 @@ Bugfixes: * Code Generator: Fix crash when passing an empty string literal to ``bytes.concat()``. * Code Generator: Fix internal compiler error when calling functions bound to calldata structs and arrays. * Code Generator: Fix internal compiler error when passing a 32-byte hex literal or a zero literal to ``bytes.concat()`` by disallowing such literals. + * Commandline Interface: Fix crash when a directory path is passed to ``--standard-json``. + * Commandline Interface: Read JSON from standard input when ``--standard-json`` gets ``-`` as a file name. * Standard JSON: Include source location for errors in files with empty name. * Type Checker: Fix internal error and prevent static calls to unimplemented modifiers. * Yul Code Generator: Fix internal compiler error when using a long literal with bitwise negation. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 1b7f2d089..91f2baf9a 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -377,8 +377,18 @@ void CommandLineInterface::handleGasEstimation(string const& _contract) } } -bool CommandLineInterface::readInputFilesAndConfigureFileReader() +bool CommandLineInterface::readInputFiles() { + solAssert(!m_standardJsonInput.has_value(), ""); + + m_fileReader.setBasePath(m_options.input.basePath); + + if (m_fileReader.basePath() != "" && !boost::filesystem::is_directory(m_fileReader.basePath())) + { + serr() << "Base path must be a directory: " << m_fileReader.basePath() << endl; + return false; + } + for (boost::filesystem::path const& allowedDirectory: m_options.input.allowedDirectories) m_fileReader.allowDirectory(allowedDirectory); @@ -411,16 +421,33 @@ bool CommandLineInterface::readInputFilesAndConfigureFileReader() } // NOTE: we ignore the FileNotFound exception as we manually check above - m_fileReader.setSource(infile, readFileAsString(infile.string())); - m_fileReader.allowDirectory(boost::filesystem::path(boost::filesystem::canonical(infile).string()).remove_filename()); + string fileContent = readFileAsString(infile.string()); + if (m_options.input.mode == InputMode::StandardJson) + { + solAssert(!m_standardJsonInput.has_value(), ""); + m_standardJsonInput = move(fileContent); + } + else + { + m_fileReader.setSource(infile, move(fileContent)); + m_fileReader.allowDirectory(boost::filesystem::canonical(infile).remove_filename()); + } } if (m_options.input.addStdin) - m_fileReader.setSource(g_stdinFileName, readUntilEnd(m_sin)); - - if (m_fileReader.sourceCodes().size() == 0) { - serr() << "No input files given. If you wish to use the standard input please specify \"-\" explicitly." << endl; + if (m_options.input.mode == InputMode::StandardJson) + { + solAssert(!m_standardJsonInput.has_value(), ""); + m_standardJsonInput = readUntilEnd(m_sin); + } + else + m_fileReader.setSource(g_stdinFileName, readUntilEnd(m_sin)); + } + + if (m_fileReader.sourceCodes().empty() && !m_standardJsonInput.has_value()) + { + serr() << "All specified input files either do not exist or are not regular files." << endl; return false; } @@ -502,57 +529,35 @@ bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv) bool CommandLineInterface::processInput() { - m_fileReader.setBasePath(m_options.input.basePath); - - if (m_options.input.basePath != "" && !boost::filesystem::is_directory(m_options.input.basePath)) + switch (m_options.input.mode) { - serr() << "Base path must be a directory: \"" << m_options.input.basePath << "\"\n"; - return false; - } - - if (m_options.input.mode == InputMode::StandardJson) + case InputMode::StandardJson: { - string input; - if (m_options.input.standardJsonFile.empty()) - input = readUntilEnd(m_sin); - else - { - try - { - input = readFileAsString(m_options.input.standardJsonFile); - } - catch (FileNotFound const&) - { - serr() << "File not found: " << m_options.input.standardJsonFile << endl; - return false; - } - catch (NotAFile const&) - { - serr() << "Not a regular file: " << m_options.input.standardJsonFile << endl; - return false; - } - } + solAssert(m_standardJsonInput.has_value(), ""); + StandardCompiler compiler(m_fileReader.reader(), m_options.formatting.json); - sout() << compiler.compile(std::move(input)) << endl; + sout() << compiler.compile(move(m_standardJsonInput.value())) << endl; + m_standardJsonInput.reset(); return true; } - - if (!readInputFilesAndConfigureFileReader()) - return false; - - if (m_options.input.mode == InputMode::Assembler) + case InputMode::Assembler: + { return assemble( m_options.assembly.inputLanguage, m_options.assembly.targetMachine, m_options.optimizer.enabled, m_options.optimizer.yulSteps ); - - if (m_options.input.mode == InputMode::Linker) + } + case InputMode::Linker: return link(); + case InputMode::Compiler: + case InputMode::CompilerWithASTImport: + return compile(); + } - solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); - return compile(); + solAssert(false, ""); + return false; } bool CommandLineInterface::compile() diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h index 57674355a..8e17489a7 100644 --- a/solc/CommandLineInterface.h +++ b/solc/CommandLineInterface.h @@ -53,6 +53,8 @@ public: /// Parse command line arguments and return false if we should not continue bool parseArguments(int _argc, char const* const* _argv); + /// Read the content of all input files and initialize the file reader. + bool readInputFiles(); /// Parse the files and create source code objects bool processInput(); /// Perform actions on the input depending on provided compiler arguments @@ -61,6 +63,7 @@ public: CommandLineOptions const& options() const { return m_options; } FileReader const& fileReader() const { return m_fileReader; } + std::optional const& standardJsonInput() const { return m_standardJsonInput; } private: bool compile(); @@ -95,10 +98,6 @@ private: void handleGasEstimation(std::string const& _contract); void handleStorageLayout(std::string const& _contract); - /// Reads the content of input files specified on the command line and passes them to FileReader. - /// @return false if there are no input files or input files cannot be read. - bool readInputFilesAndConfigureFileReader(); - /// Tries to read @ m_sourceCodes as a JSONs holding ASTs /// such that they can be imported into the compiler (importASTs()) /// (produced by --combined-json ast,compact-format @@ -129,6 +128,7 @@ private: bool m_hasOutput = false; bool m_error = false; ///< If true, some error occurred. FileReader m_fileReader; + std::optional m_standardJsonInput; std::unique_ptr m_compiler; CommandLineOptions m_options; }; diff --git a/solc/CommandLineParser.cpp b/solc/CommandLineParser.cpp index 864282c21..ed2f2c9b3 100644 --- a/solc/CommandLineParser.cpp +++ b/solc/CommandLineParser.cpp @@ -265,7 +265,6 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex { return input.paths == _other.input.paths && - input.standardJsonFile == _other.input.standardJsonFile && input.remappings == _other.input.remappings && input.addStdin == _other.input.addStdin && input.basePath == _other.input.basePath && @@ -301,12 +300,20 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex bool CommandLineParser::parseInputPathsAndRemappings() { m_options.input.ignoreMissingFiles = (m_args.count(g_strIgnoreMissingFiles) > 0); + if (m_args.count(g_strInputFile)) for (string path: m_args[g_strInputFile].as>()) { auto eq = find(path.begin(), path.end(), '='); if (eq != path.end()) { + if (m_options.input.mode == InputMode::StandardJson) + { + serr() << "Import remappings are not accepted on the command line in Standard JSON mode." << endl; + serr() << "Please put them under 'settings.remappings' in the JSON input." << endl; + return false; + } + if (auto r = ImportRemapper::parseRemapping(path)) m_options.input.remappings.emplace_back(std::move(*r)); else @@ -324,6 +331,25 @@ bool CommandLineParser::parseInputPathsAndRemappings() m_options.input.paths.insert(path); } + if (m_options.input.mode == InputMode::StandardJson) + { + if (m_options.input.paths.size() > 1 || (m_options.input.paths.size() == 1 && m_options.input.addStdin)) + { + serr() << "Too many input files for --" << g_strStandardJSON << "." << endl; + serr() << "Please either specify a single file name or provide its content on standard input." << endl; + return false; + } + else if (m_options.input.paths.size() == 0) + // Standard JSON mode input used to be handled separately and zero files meant "read from stdin". + // Keep it working that way for backwards-compatibility. + m_options.input.addStdin = true; + } + else if (m_options.input.paths.size() == 0 && !m_options.input.addStdin) + { + serr() << "No input files given. If you wish to use the standard input please specify \"-\" explicitly." << endl; + return false; + } + return true; } @@ -882,25 +908,12 @@ General Information)").c_str(), else m_options.input.mode = InputMode::Compiler; - if (m_options.input.mode == InputMode::StandardJson) - { - vector inputFiles; - if (m_args.count(g_strInputFile)) - inputFiles = m_args[g_strInputFile].as>(); - if (inputFiles.size() == 1) - m_options.input.standardJsonFile = inputFiles[0]; - else if (inputFiles.size() > 1) - { - serr() << "If --" << g_strStandardJSON << " is used, only zero or one input files are supported." << endl; - return false; - } - - return true; - } - if (!parseInputPathsAndRemappings()) return false; + if (m_options.input.mode == InputMode::StandardJson) + return true; + if (m_args.count(g_strLibraries)) for (string const& library: m_args[g_strLibraries].as>()) if (!parseLibraryOption(library)) diff --git a/solc/CommandLineParser.h b/solc/CommandLineParser.h index 375c961dc..176966601 100644 --- a/solc/CommandLineParser.h +++ b/solc/CommandLineParser.h @@ -107,7 +107,6 @@ struct CommandLineOptions { InputMode mode = InputMode::Compiler; std::set paths; - std::string standardJsonFile; std::vector remappings; bool addStdin = false; boost::filesystem::path basePath = ""; diff --git a/solc/main.cpp b/solc/main.cpp index 4c40f47f1..9eefc1afd 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -57,6 +57,8 @@ int main(int argc, char** argv) solidity::frontend::CommandLineInterface cli(cin, cout, cerr); if (!cli.parseArguments(argc, argv)) return 1; + if (!cli.readInputFiles()) + return 1; if (!cli.processInput()) return 1; bool success = false; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 17300c660..a70a92639 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -152,6 +152,9 @@ set(libyul_sources detect_stray_source_files("${libyul_sources}" "libyul/") set(solcli_sources + solc/Common.cpp + solc/Common.h + solc/CommandLineInterface.cpp solc/CommandLineParser.cpp ) detect_stray_source_files("${solcli_sources}" "solc/") diff --git a/test/FilesystemUtils.cpp b/test/FilesystemUtils.cpp index 08d484504..cdad58e62 100644 --- a/test/FilesystemUtils.cpp +++ b/test/FilesystemUtils.cpp @@ -24,6 +24,21 @@ using namespace std; using namespace solidity; using namespace solidity::test; +void solidity::test::createFilesWithParentDirs(set const& _paths, string const& _content) +{ + for (boost::filesystem::path const& path: _paths) + { + if (!path.parent_path().empty()) + boost::filesystem::create_directories(path.parent_path()); + + ofstream newFile(path.string()); + newFile << _content; + + if (newFile.fail() || !boost::filesystem::exists(path)) + BOOST_THROW_EXCEPTION(runtime_error("Failed to create an empty file: \"" + path.string() + "\".")); + } +} + void solidity::test::createFileWithContent(boost::filesystem::path const& _path, string const& content) { if (boost::filesystem::is_regular_file(_path)) diff --git a/test/FilesystemUtils.h b/test/FilesystemUtils.h index f8ad32044..291188114 100644 --- a/test/FilesystemUtils.h +++ b/test/FilesystemUtils.h @@ -23,11 +23,16 @@ #include +#include #include namespace solidity::test { +/// Creates all the specified files and fills them with the specifiedcontent. Creates their parent +/// directories if they do not exist. Throws an exception if any part of the operation does not succeed. +void createFilesWithParentDirs(std::set const& _paths, std::string const& _content = ""); + /// Creates a file with the exact content specified in the second argument. /// Throws an exception if the file already exists or if the parent directory of the file does not. void createFileWithContent(boost::filesystem::path const& _path, std::string const& content); diff --git a/test/cmdlineTests/standard_file_not_found/err b/test/cmdlineTests/standard_file_not_found/err index a97cc5b7a..db5590ed9 100644 --- a/test/cmdlineTests/standard_file_not_found/err +++ b/test/cmdlineTests/standard_file_not_found/err @@ -1 +1 @@ -File not found: input.sol +"input.sol" is not found. diff --git a/test/libsolidity/analysis/FunctionCallGraph.cpp b/test/libsolidity/analysis/FunctionCallGraph.cpp index e4cd3d066..de63be563 100644 --- a/test/libsolidity/analysis/FunctionCallGraph.cpp +++ b/test/libsolidity/analysis/FunctionCallGraph.cpp @@ -215,7 +215,7 @@ ostream& operator<<(ostream& _out, map> const& _map) namespace boost::test_tools::tt_detail { -// Boost won't find find the << operator unless we put it in the std namespace which is illegal. +// Boost won't find the << operator unless we put it in the std namespace which is illegal. // The recommended solution is to overload print_log_value<> struct and make it use our operator. template<> diff --git a/test/solc/CommandLineInterface.cpp b/test/solc/CommandLineInterface.cpp new file mode 100644 index 000000000..9c73c66ea --- /dev/null +++ b/test/solc/CommandLineInterface.cpp @@ -0,0 +1,346 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +/// Unit tests for solc/CommandLineInterface.h + +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace std; +using namespace solidity::frontend; +using namespace solidity::test; + +using PathSet = set; + +#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name) + +namespace +{ + +ostream& operator<<(ostream& _out, vector const& _remappings) +{ + static auto remappingToString = [](auto const& _remapping) + { + return _remapping.context + ":" + _remapping.prefix + "=" + _remapping.target; + }; + + _out << "[" << joinHumanReadable(_remappings | ranges::views::transform(remappingToString)) << "]"; + return _out; +} + +ostream& operator<<(ostream& _out, map const& _map) +{ + _out << "{" << endl; + for (auto const& [key, value]: _map) + _out << "" << key << ": " << value << "," << endl; + _out << "}"; + + return _out; +} + +ostream& operator<<(ostream& _out, PathSet const& _paths) +{ + static auto pathString = [](auto const& _path) { return _path.string(); }; + + _out << "{" << joinHumanReadable(_paths | ranges::views::transform(pathString)) << "}"; + return _out; +} + +} // namespace + +namespace boost::test_tools::tt_detail +{ + +// Boost won't find the << operator unless we put it in the std namespace which is illegal. +// The recommended solution is to overload print_log_value<> struct and make it use our operator. + +template<> +struct print_log_value> +{ + void operator()(std::ostream& _out, vector const& _value) { ::operator<<(_out, _value); } +}; + +template<> +struct print_log_value> +{ + void operator()(std::ostream& _out, map const& _value) { ::operator<<(_out, _value); } +}; + +template<> +struct print_log_value +{ + void operator()(std::ostream& _out, PathSet const& _value) { ::operator<<(_out, _value); } +}; + +} // namespace boost::test_tools::tt_detail + +namespace solidity::frontend::test +{ + +BOOST_AUTO_TEST_SUITE(CommandLineInterfaceTest) + +BOOST_AUTO_TEST_CASE(multiple_input_modes) +{ + array inputModeOptions = { + "--standard-json", + "--link", + "--assemble", + "--strict-assembly", + "--yul", + "--import-ast", + }; + string expectedMessage = + "The following options are mutually exclusive: " + "--standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. " + "Select at most one.\n"; + + for (string const& mode1: inputModeOptions) + for (string const& mode2: inputModeOptions) + if (mode1 != mode2) + { + vector commandLine = {"solc", mode1, mode2}; + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); + } +} + +BOOST_AUTO_TEST_CASE(cli_input) +{ + TemporaryDirectory tempDir1(TEST_CASE_NAME); + TemporaryDirectory tempDir2(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir1.path() / "input1.sol"}); + createFilesWithParentDirs({tempDir2.path() / "input2.sol"}); + + vector expectedRemappings = { + {"", "a", "b/c/d"}, + {"a", "b", "c/d/e/"}, + }; + map expectedSources = { + {"", "\n"}, + {(tempDir1.path() / "input1.sol").generic_string(), ""}, + {(tempDir2.path() / "input2.sol").generic_string(), ""}, + }; + PathSet expectedAllowedPaths = { + boost::filesystem::canonical(tempDir1.path()), + boost::filesystem::canonical(tempDir2.path()), + "b/c", + "c/d/e", + }; + + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ + "solc", + "a=b/c/d", + (tempDir1.path() / "input1.sol").string(), + (tempDir2.path() / "input2.sol").string(), + "a:b=c/d/e/", + "-", + }); + + BOOST_TEST(result.success); + BOOST_TEST(result.stderrContent == ""); + BOOST_TEST(result.options.input.mode == InputMode::Compiler); + BOOST_TEST(result.options.input.addStdin); + BOOST_CHECK_EQUAL(result.options.input.remappings, expectedRemappings); + BOOST_CHECK_EQUAL(result.reader.sourceCodes(), expectedSources); + BOOST_CHECK_EQUAL(result.reader.allowedDirectories(), expectedAllowedPaths); +} + +BOOST_AUTO_TEST_CASE(cli_ignore_missing_some_files_exist) +{ + TemporaryDirectory tempDir1(TEST_CASE_NAME); + TemporaryDirectory tempDir2(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir1.path() / "input1.sol"}); + + // NOTE: Allowed paths should not be added for skipped files. + map expectedSources = {{(tempDir1.path() / "input1.sol").generic_string(), ""}}; + PathSet expectedAllowedPaths = {boost::filesystem::canonical(tempDir1.path())}; + + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ + "solc", + (tempDir1.path() / "input1.sol").string(), + (tempDir2.path() / "input2.sol").string(), + "--ignore-missing", + }); + BOOST_TEST(result.success); + BOOST_TEST(result.stderrContent == "\"" + (tempDir2.path() / "input2.sol").string() + "\" is not found. Skipping.\n"); + BOOST_TEST(result.options.input.mode == InputMode::Compiler); + BOOST_TEST(!result.options.input.addStdin); + BOOST_CHECK_EQUAL(result.reader.sourceCodes(), expectedSources); + BOOST_CHECK_EQUAL(result.reader.allowedDirectories(), expectedAllowedPaths); +} + +BOOST_AUTO_TEST_CASE(cli_ignore_missing_no_files_exist) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + + string expectedMessage = + "\"" + (tempDir.path() / "input1.sol").string() + "\" is not found. Skipping.\n" + "\"" + (tempDir.path() / "input2.sol").string() + "\" is not found. Skipping.\n" + "All specified input files either do not exist or are not regular files.\n"; + + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ + "solc", + (tempDir.path() / "input1.sol").string(), + (tempDir.path() / "input2.sol").string(), + "--ignore-missing", + }); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); +} + +BOOST_AUTO_TEST_CASE(cli_not_a_file) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + + string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file.\n"; + + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); +} + +BOOST_AUTO_TEST_CASE(standard_json_base_path) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ + "solc", + "--standard-json", + "--base-path=" + tempDir.path().string(), + }); + BOOST_TEST(result.success); + BOOST_TEST(result.stderrContent == ""); + BOOST_TEST(result.options.input.mode == InputMode::StandardJson); + BOOST_TEST(result.options.input.addStdin); + BOOST_TEST(result.options.input.paths.empty()); + BOOST_TEST(result.reader.sourceCodes().empty()); + BOOST_TEST(result.reader.allowedDirectories().empty()); + BOOST_TEST(result.reader.basePath() == tempDir.path()); +} + +BOOST_AUTO_TEST_CASE(standard_json_no_input_file) +{ + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--standard-json"}); + BOOST_TEST(result.success); + BOOST_TEST(result.stderrContent == ""); + BOOST_TEST(result.options.input.mode == InputMode::StandardJson); + BOOST_TEST(result.options.input.addStdin); + BOOST_TEST(result.options.input.paths.empty()); + BOOST_TEST(result.reader.sourceCodes().empty()); + BOOST_TEST(result.reader.allowedDirectories().empty()); +} + +BOOST_AUTO_TEST_CASE(standard_json_dash) +{ + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--standard-json", "-"}); + BOOST_TEST(result.success); + BOOST_TEST(result.stderrContent == ""); + BOOST_TEST(result.options.input.mode == InputMode::StandardJson); + BOOST_TEST(result.options.input.addStdin); + BOOST_TEST(result.reader.sourceCodes().empty()); + BOOST_TEST(result.reader.allowedDirectories().empty()); +} + +BOOST_AUTO_TEST_CASE(standard_json_one_input_file) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + createFilesWithParentDirs({tempDir.path() / "input.json"}); + + vector commandLine = {"solc", "--standard-json", (tempDir.path() / "input.json").string()}; + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); + BOOST_TEST(result.success); + BOOST_TEST(result.stderrContent == ""); + BOOST_TEST(result.options.input.mode == InputMode::StandardJson); + BOOST_TEST(!result.options.input.addStdin); + BOOST_TEST(result.options.input.paths == PathSet{tempDir.path() / "input.json"}); + BOOST_TEST(result.reader.allowedDirectories().empty()); +} + +BOOST_AUTO_TEST_CASE(standard_json_two_input_files) +{ + string expectedMessage = + "Too many input files for --standard-json.\n" + "Please either specify a single file name or provide its content on standard input.\n"; + + vector commandLine = {"solc", "--standard-json", "input1.json", "input2.json"}; + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); +} + +BOOST_AUTO_TEST_CASE(standard_json_one_input_file_and_stdin) +{ + string expectedMessage = + "Too many input files for --standard-json.\n" + "Please either specify a single file name or provide its content on standard input.\n"; + + vector commandLine = {"solc", "--standard-json", "input1.json", "-"}; + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); +} + +BOOST_AUTO_TEST_CASE(standard_json_ignore_missing) +{ + TemporaryDirectory tempDir(TEST_CASE_NAME); + + // This option is pretty much useless Standard JSON mode. + string expectedMessage = + "\"" + (tempDir.path() / "input.json").string() + "\" is not found. Skipping.\n" + "All specified input files either do not exist or are not regular files.\n"; + + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ + "solc", + "--standard-json", + (tempDir.path() / "input.json").string(), + "--ignore-missing", + }); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); +} + +BOOST_AUTO_TEST_CASE(standard_json_remapping) +{ + string expectedMessage = + "Import remappings are not accepted on the command line in Standard JSON mode.\n" + "Please put them under 'settings.remappings' in the JSON input.\n"; + + vector commandLine = {"solc", "--standard-json", "a=b"}; + OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); + BOOST_TEST(!result.success); + BOOST_TEST(result.stderrContent == expectedMessage); +} + +BOOST_AUTO_TEST_SUITE_END() + +} // namespace solidity::frontend::test diff --git a/test/solc/CommandLineParser.cpp b/test/solc/CommandLineParser.cpp index 015c28b98..3f12b85fd 100644 --- a/test/solc/CommandLineParser.cpp +++ b/test/solc/CommandLineParser.cpp @@ -20,6 +20,8 @@ #include +#include + #include #include @@ -28,7 +30,6 @@ #include #include -#include #include #include @@ -46,20 +47,13 @@ using namespace solidity::yul; namespace { -optional parseCommandLine(vector const& commandLine, ostream& _stdout, ostream& _stderr) +optional parseCommandLine(vector const& _commandLine, ostream& _stdout, ostream& _stderr) { - size_t argc = commandLine.size(); - vector argv(argc + 1); - - // argv[argc] typically contains NULL - argv[argc] = nullptr; - - for (size_t i = 0; i < argc; ++i) - argv[i] = commandLine[i].c_str(); + vector argv = test::makeArgv(_commandLine); CommandLineParser cliParser(_stdout, _stderr); bool success = cliParser.parse( - static_cast(argc), + static_cast(_commandLine.size()), argv.data(), false // interactiveTerminal ); @@ -70,7 +64,6 @@ optional parseCommandLine(vector const& commandLine, return cliParser.options(); } - } // namespace namespace solidity::frontend::test @@ -99,7 +92,7 @@ BOOST_AUTO_TEST_CASE(no_options) BOOST_TEST(sout.str() == ""); BOOST_TEST(serr.str() == ""); BOOST_REQUIRE(parsedOptions.has_value()); - BOOST_TEST((parsedOptions.value() == expectedOptions)); + BOOST_TEST(parsedOptions.value() == expectedOptions); } BOOST_AUTO_TEST_CASE(help) @@ -224,7 +217,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options) BOOST_TEST(sout.str() == ""); BOOST_TEST(serr.str() == ""); BOOST_REQUIRE(parsedOptions.has_value()); - BOOST_TEST((parsedOptions.value() == expectedOptions)); + BOOST_TEST(parsedOptions.value() == expectedOptions); } } @@ -342,8 +335,7 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options) BOOST_TEST(sout.str() == ""); BOOST_TEST(serr.str() == "Warning: Yul is still experimental. Please use the output with care.\n"); BOOST_REQUIRE(parsedOptions.has_value()); - - BOOST_TEST((parsedOptions.value() == expectedOptions)); + BOOST_TEST(parsedOptions.value() == expectedOptions); } } @@ -355,7 +347,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options) "--standard-json", "--base-path=/home/user/", "--allow-paths=/tmp,/home,project,../contracts", - "--ignore-missing", // Ignored in Standard JSON mode + "--ignore-missing", "--error-recovery", // Ignored in Standard JSON mode "--output-dir=/tmp/out", // Accepted but has no effect in Standard JSON mode "--overwrite", // Accepted but has no effect in Standard JSON mode @@ -393,10 +385,10 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options) CommandLineOptions expectedOptions; expectedOptions.input.mode = InputMode::StandardJson; - expectedOptions.input.paths = {}; - expectedOptions.input.standardJsonFile = "input.json"; + expectedOptions.input.paths = {"input.json"}; expectedOptions.input.basePath = "/home/user/"; expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts"}; + expectedOptions.input.ignoreMissingFiles = true; expectedOptions.output.dir = "/tmp/out"; expectedOptions.output.overwriteFiles = true; expectedOptions.output.revertStrings = RevertStrings::Strip; @@ -419,7 +411,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options) BOOST_TEST(sout.str() == ""); BOOST_TEST(serr.str() == ""); BOOST_REQUIRE(parsedOptions.has_value()); - BOOST_TEST((parsedOptions.value() == expectedOptions)); + BOOST_TEST(parsedOptions.value() == expectedOptions); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/solc/Common.cpp b/test/solc/Common.cpp new file mode 100644 index 000000000..1bd209067 --- /dev/null +++ b/test/solc/Common.cpp @@ -0,0 +1,58 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include + +#include + +#include + +using namespace std; +using namespace solidity::frontend; + +vector test::makeArgv(vector const& _commandLine) +{ + size_t argc = _commandLine.size(); + vector argv(_commandLine.size() + 1); + + // C++ standard mandates argv[argc] to be NULL + argv[argc] = nullptr; + + for (size_t i = 0; i < argc; ++i) + argv[i] = _commandLine[i].c_str(); + + return argv; +} + +test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles( + vector const& _commandLine, + string const& _standardInputContent, + bool _processInput +) +{ + vector argv = makeArgv(_commandLine); + stringstream sin(_standardInputContent), sout, serr; + CommandLineInterface cli(sin, sout, serr); + bool success = cli.parseArguments(static_cast(_commandLine.size()), argv.data()); + if (success) + success = cli.readInputFiles(); + if (success && _processInput) + success = cli.processInput(); + + return {success, cli.options(), cli.fileReader(), cli.standardJsonInput(), sout.str(), serr.str()}; +} diff --git a/test/solc/Common.h b/test/solc/Common.h new file mode 100644 index 000000000..6d5e3875d --- /dev/null +++ b/test/solc/Common.h @@ -0,0 +1,53 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ +// SPDX-License-Identifier: GPL-3.0 + +/// Utilities shared by multiple tests for code in solc/. + +#include + +#include + +#include +#include +#include + +BOOST_TEST_DONT_PRINT_LOG_VALUE(solidity::frontend::CommandLineOptions) +BOOST_TEST_DONT_PRINT_LOG_VALUE(solidity::frontend::InputMode) + +namespace solidity::frontend::test +{ + +struct OptionsReaderAndMessages +{ + bool success; + CommandLineOptions options; + FileReader reader; + std::optional standardJsonInput; + std::string stdoutContent; + std::string stderrContent; +}; + +std::vector makeArgv(std::vector const& _commandLine); + +OptionsReaderAndMessages parseCommandLineAndReadInputFiles( + std::vector const& _commandLine, + std::string const& _standardInputContent = "", + bool _processInput = false +); + +} // namespace solidity::frontend::test diff --git a/test/yulPhaser/TestHelpers.h b/test/yulPhaser/TestHelpers.h index 31a5907f3..f9d0afa4c 100644 --- a/test/yulPhaser/TestHelpers.h +++ b/test/yulPhaser/TestHelpers.h @@ -57,7 +57,7 @@ std::ostream& operator<<(std::ostream& _output, std::tuple const& _tuple namespace boost::test_tools::tt_detail { -// Boost won't find find the << operator unless we put it in the std namespace which is illegal. +// Boost won't find the << operator unless we put it in the std namespace which is illegal. // The recommended solution is to overload print_log_value<> struct and make it use our global operator. template struct print_log_value>