Merge pull request #11544 from ethereum/standard-json-cli-common-input-file-processing

Common input file processing for CLI and Standard JSON
This commit is contained in:
Christian Parpart 2021-07-27 16:34:03 +02:00 committed by GitHub
commit de2e72b868
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 604 additions and 118 deletions

View File

@ -15,6 +15,8 @@ Bugfixes:
* Code Generator: Fix crash when passing an empty string literal to ``bytes.concat()``. * 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 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. * 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. * Standard JSON: Include source location for errors in files with empty name.
* Type Checker: Fix internal error and prevent static calls to unimplemented modifiers. * 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. * Yul Code Generator: Fix internal compiler error when using a long literal with bitwise negation.

View File

@ -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) for (boost::filesystem::path const& allowedDirectory: m_options.input.allowedDirectories)
m_fileReader.allowDirectory(allowedDirectory); m_fileReader.allowDirectory(allowedDirectory);
@ -411,16 +421,33 @@ bool CommandLineInterface::readInputFilesAndConfigureFileReader()
} }
// NOTE: we ignore the FileNotFound exception as we manually check above // NOTE: we ignore the FileNotFound exception as we manually check above
m_fileReader.setSource(infile, readFileAsString(infile.string())); string fileContent = readFileAsString(infile.string());
m_fileReader.allowDirectory(boost::filesystem::path(boost::filesystem::canonical(infile).string()).remove_filename()); 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) 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; return false;
} }
@ -502,57 +529,35 @@ bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv)
bool CommandLineInterface::processInput() bool CommandLineInterface::processInput()
{ {
m_fileReader.setBasePath(m_options.input.basePath); switch (m_options.input.mode)
if (m_options.input.basePath != "" && !boost::filesystem::is_directory(m_options.input.basePath))
{ {
serr() << "Base path must be a directory: \"" << m_options.input.basePath << "\"\n"; case InputMode::StandardJson:
return false;
}
if (m_options.input.mode == InputMode::StandardJson)
{ {
string input; solAssert(m_standardJsonInput.has_value(), "");
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;
}
}
StandardCompiler compiler(m_fileReader.reader(), m_options.formatting.json); 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; return true;
} }
case InputMode::Assembler:
if (!readInputFilesAndConfigureFileReader()) {
return false;
if (m_options.input.mode == InputMode::Assembler)
return assemble( return assemble(
m_options.assembly.inputLanguage, m_options.assembly.inputLanguage,
m_options.assembly.targetMachine, m_options.assembly.targetMachine,
m_options.optimizer.enabled, m_options.optimizer.enabled,
m_options.optimizer.yulSteps m_options.optimizer.yulSteps
); );
}
if (m_options.input.mode == InputMode::Linker) case InputMode::Linker:
return link(); return link();
case InputMode::Compiler:
case InputMode::CompilerWithASTImport:
return compile();
}
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); solAssert(false, "");
return compile(); return false;
} }
bool CommandLineInterface::compile() bool CommandLineInterface::compile()

View File

@ -53,6 +53,8 @@ public:
/// Parse command line arguments and return false if we should not continue /// Parse command line arguments and return false if we should not continue
bool parseArguments(int _argc, char const* const* _argv); 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 /// Parse the files and create source code objects
bool processInput(); bool processInput();
/// Perform actions on the input depending on provided compiler arguments /// Perform actions on the input depending on provided compiler arguments
@ -61,6 +63,7 @@ public:
CommandLineOptions const& options() const { return m_options; } CommandLineOptions const& options() const { return m_options; }
FileReader const& fileReader() const { return m_fileReader; } FileReader const& fileReader() const { return m_fileReader; }
std::optional<std::string> const& standardJsonInput() const { return m_standardJsonInput; }
private: private:
bool compile(); bool compile();
@ -95,10 +98,6 @@ private:
void handleGasEstimation(std::string const& _contract); void handleGasEstimation(std::string const& _contract);
void handleStorageLayout(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 /// Tries to read @ m_sourceCodes as a JSONs holding ASTs
/// such that they can be imported into the compiler (importASTs()) /// such that they can be imported into the compiler (importASTs())
/// (produced by --combined-json ast,compact-format <file.sol> /// (produced by --combined-json ast,compact-format <file.sol>
@ -129,6 +128,7 @@ private:
bool m_hasOutput = false; bool m_hasOutput = false;
bool m_error = false; ///< If true, some error occurred. bool m_error = false; ///< If true, some error occurred.
FileReader m_fileReader; FileReader m_fileReader;
std::optional<std::string> m_standardJsonInput;
std::unique_ptr<frontend::CompilerStack> m_compiler; std::unique_ptr<frontend::CompilerStack> m_compiler;
CommandLineOptions m_options; CommandLineOptions m_options;
}; };

View File

@ -198,14 +198,14 @@ void CommandLineParser::printLicenseAndExit()
} }
bool CommandLineParser::checkMutuallyExclusive(boost::program_options::variables_map const& args, string const& _optionA, string const& _optionB) bool CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
{ {
if (args.count(_optionA) && args.count(_optionB)) if (countEnabledOptions(_optionNames) > 1)
{ {
serr() << "Option " << _optionA << " and " << _optionB << " are mutually exclusive." << endl; serr() << "The following options are mutually exclusive: " << joinOptionNames(_optionNames) << ". ";
serr() << "Select at most one." << endl;
return false; return false;
} }
return true; return true;
} }
@ -265,7 +265,6 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex
{ {
return return
input.paths == _other.input.paths && input.paths == _other.input.paths &&
input.standardJsonFile == _other.input.standardJsonFile &&
input.remappings == _other.input.remappings && input.remappings == _other.input.remappings &&
input.addStdin == _other.input.addStdin && input.addStdin == _other.input.addStdin &&
input.basePath == _other.input.basePath && input.basePath == _other.input.basePath &&
@ -301,12 +300,20 @@ bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noex
bool CommandLineParser::parseInputPathsAndRemappings() bool CommandLineParser::parseInputPathsAndRemappings()
{ {
m_options.input.ignoreMissingFiles = (m_args.count(g_strIgnoreMissingFiles) > 0); m_options.input.ignoreMissingFiles = (m_args.count(g_strIgnoreMissingFiles) > 0);
if (m_args.count(g_strInputFile)) if (m_args.count(g_strInputFile))
for (string path: m_args[g_strInputFile].as<vector<string>>()) for (string path: m_args[g_strInputFile].as<vector<string>>())
{ {
auto eq = find(path.begin(), path.end(), '='); auto eq = find(path.begin(), path.end(), '=');
if (eq != 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)) if (auto r = ImportRemapper::parseRemapping(path))
m_options.input.remappings.emplace_back(std::move(*r)); m_options.input.remappings.emplace_back(std::move(*r));
else else
@ -324,6 +331,25 @@ bool CommandLineParser::parseInputPathsAndRemappings()
m_options.input.paths.insert(path); 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; return true;
} }
@ -735,7 +761,7 @@ General Information)").c_str(),
return false; return false;
} }
if (!checkMutuallyExclusive(m_args, g_strColor, g_strNoColor)) if (!checkMutuallyExclusive({g_strColor, g_strNoColor}))
return false; return false;
array<string, 8> const conflictingWithStopAfter{ array<string, 8> const conflictingWithStopAfter{
@ -750,7 +776,7 @@ General Information)").c_str(),
}; };
for (auto& option: conflictingWithStopAfter) for (auto& option: conflictingWithStopAfter)
if (!checkMutuallyExclusive(m_args, g_strStopAfter, option)) if (!checkMutuallyExclusive({g_strStopAfter, option}))
return false; return false;
if (m_args.count(g_strColor) > 0) if (m_args.count(g_strColor) > 0)
@ -861,42 +887,33 @@ General Information)").c_str(),
m_options.output.stopAfter = CompilerStack::State::Parsed; m_options.output.stopAfter = CompilerStack::State::Parsed;
} }
vector<string> const exclusiveModes = { if (!checkMutuallyExclusive({
g_strStandardJSON, g_strStandardJSON,
g_strLink, g_strLink,
g_strAssemble, g_strAssemble,
g_strStrictAssembly, g_strStrictAssembly,
g_strYul, g_strYul,
g_strImportAst, g_strImportAst,
}; }))
if (countEnabledOptions(exclusiveModes) > 1)
{
serr() << "The following options are mutually exclusive: " << joinOptionNames(exclusiveModes) << ". ";
serr() << "Select at most one." << endl;
return false; return false;
}
if (m_args.count(g_strStandardJSON)) if (m_args.count(g_strStandardJSON) > 0)
{
m_options.input.mode = InputMode::StandardJson; m_options.input.mode = InputMode::StandardJson;
else if (m_args.count(g_strAssemble) > 0 || m_args.count(g_strStrictAssembly) > 0 || m_args.count(g_strYul) > 0)
vector<string> inputFiles; m_options.input.mode = InputMode::Assembler;
if (m_args.count(g_strInputFile)) else if (m_args.count(g_strLink) > 0)
inputFiles = m_args[g_strInputFile].as<vector<string>>(); m_options.input.mode = InputMode::Linker;
if (inputFiles.size() == 1) else if (m_args.count(g_strImportAst) > 0)
m_options.input.standardJsonFile = inputFiles[0]; m_options.input.mode = InputMode::CompilerWithASTImport;
else if (inputFiles.size() > 1) else
{ m_options.input.mode = InputMode::Compiler;
serr() << "If --" << g_strStandardJSON << " is used, only zero or one input files are supported." << endl;
return false;
}
return true;
}
if (!parseInputPathsAndRemappings()) if (!parseInputPathsAndRemappings())
return false; return false;
if (m_options.input.mode == InputMode::StandardJson)
return true;
if (m_args.count(g_strLibraries)) if (m_args.count(g_strLibraries))
for (string const& library: m_args[g_strLibraries].as<vector<string>>()) for (string const& library: m_args[g_strLibraries].as<vector<string>>())
if (!parseLibraryOption(library)) if (!parseLibraryOption(library))
@ -914,10 +931,8 @@ General Information)").c_str(),
m_options.output.evmVersion = *versionOption; m_options.output.evmVersion = *versionOption;
} }
if (m_args.count(g_strAssemble) || m_args.count(g_strStrictAssembly) || m_args.count(g_strYul)) if (m_options.input.mode == InputMode::Assembler)
{ {
m_options.input.mode = InputMode::Assembler;
vector<string> const nonAssemblyModeOptions = { vector<string> const nonAssemblyModeOptions = {
// TODO: The list is not complete. Add more. // TODO: The list is not complete. Add more.
g_strOutputDir, g_strOutputDir,
@ -1031,11 +1046,8 @@ General Information)").c_str(),
return false; return false;
} }
if (m_args.count(g_strLink)) if (m_options.input.mode == InputMode::Linker)
{
m_options.input.mode = InputMode::Linker;
return true; return true;
}
if (m_args.count(g_strMetadataHash)) if (m_args.count(g_strMetadataHash))
{ {
@ -1128,9 +1140,7 @@ General Information)").c_str(),
m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>(); m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>();
} }
if (m_args.count(g_strImportAst) > 0) if (m_options.input.mode == InputMode::Compiler)
m_options.input.mode = InputMode::CompilerWithASTImport;
else
m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0); m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0);
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, ""); solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, "");

View File

@ -107,7 +107,6 @@ struct CommandLineOptions
{ {
InputMode mode = InputMode::Compiler; InputMode mode = InputMode::Compiler;
std::set<boost::filesystem::path> paths; std::set<boost::filesystem::path> paths;
std::string standardJsonFile;
std::vector<ImportRemapper::Remapping> remappings; std::vector<ImportRemapper::Remapping> remappings;
bool addStdin = false; bool addStdin = false;
boost::filesystem::path basePath = ""; boost::filesystem::path basePath = "";
@ -218,11 +217,7 @@ private:
/// @return false if there are any validation errors, true otherwise. /// @return false if there are any validation errors, true otherwise.
bool parseLibraryOption(std::string const& _input); bool parseLibraryOption(std::string const& _input);
bool checkMutuallyExclusive( bool checkMutuallyExclusive(std::vector<std::string> const& _optionNames);
boost::program_options::variables_map const& args,
std::string const& _optionA,
std::string const& _optionB
);
[[noreturn]] void printVersionAndExit(); [[noreturn]] void printVersionAndExit();
[[noreturn]] void printLicenseAndExit(); [[noreturn]] void printLicenseAndExit();
size_t countEnabledOptions(std::vector<std::string> const& _optionNames) const; size_t countEnabledOptions(std::vector<std::string> const& _optionNames) const;

View File

@ -57,6 +57,8 @@ int main(int argc, char** argv)
solidity::frontend::CommandLineInterface cli(cin, cout, cerr); solidity::frontend::CommandLineInterface cli(cin, cout, cerr);
if (!cli.parseArguments(argc, argv)) if (!cli.parseArguments(argc, argv))
return 1; return 1;
if (!cli.readInputFiles())
return 1;
if (!cli.processInput()) if (!cli.processInput())
return 1; return 1;
bool success = false; bool success = false;

View File

@ -152,6 +152,9 @@ set(libyul_sources
detect_stray_source_files("${libyul_sources}" "libyul/") detect_stray_source_files("${libyul_sources}" "libyul/")
set(solcli_sources set(solcli_sources
solc/Common.cpp
solc/Common.h
solc/CommandLineInterface.cpp
solc/CommandLineParser.cpp solc/CommandLineParser.cpp
) )
detect_stray_source_files("${solcli_sources}" "solc/") detect_stray_source_files("${solcli_sources}" "solc/")

View File

@ -24,6 +24,21 @@ using namespace std;
using namespace solidity; using namespace solidity;
using namespace solidity::test; using namespace solidity::test;
void solidity::test::createFilesWithParentDirs(set<boost::filesystem::path> 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) void solidity::test::createFileWithContent(boost::filesystem::path const& _path, string const& content)
{ {
if (boost::filesystem::is_regular_file(_path)) if (boost::filesystem::is_regular_file(_path))

View File

@ -23,11 +23,16 @@
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <set>
#include <string> #include <string>
namespace solidity::test 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<boost::filesystem::path> const& _paths, std::string const& _content = "");
/// Creates a file with the exact content specified in the second argument. /// 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. /// 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); void createFileWithContent(boost::filesystem::path const& _path, std::string const& content);

View File

@ -1 +1 @@
File not found: input.sol "input.sol" is not found.

View File

@ -215,7 +215,7 @@ ostream& operator<<(ostream& _out, map<string, set<string>> const& _map)
namespace boost::test_tools::tt_detail 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. // The recommended solution is to overload print_log_value<> struct and make it use our operator.
template<> template<>

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/// Unit tests for solc/CommandLineInterface.h
#include <solc/CommandLineInterface.h>
#include <test/solc/Common.h>
#include <test/Common.h>
#include <test/libsolidity/util/SoltestErrors.h>
#include <test/FilesystemUtils.h>
#include <test/TemporaryDirectory.h>
#include <range/v3/view/transform.hpp>
#include <map>
#include <ostream>
#include <set>
#include <string>
#include <vector>
using namespace std;
using namespace solidity::frontend;
using namespace solidity::test;
using PathSet = set<boost::filesystem::path>;
#define TEST_CASE_NAME (boost::unit_test::framework::current_test_case().p_name)
namespace
{
ostream& operator<<(ostream& _out, vector<ImportRemapper::Remapping> 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<string, string> 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<vector<ImportRemapper::Remapping>>
{
void operator()(std::ostream& _out, vector<ImportRemapper::Remapping> const& _value) { ::operator<<(_out, _value); }
};
template<>
struct print_log_value<map<string, string>>
{
void operator()(std::ostream& _out, map<string, string> const& _value) { ::operator<<(_out, _value); }
};
template<>
struct print_log_value<PathSet>
{
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<string, 6> 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<string> 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<ImportRemapper::Remapping> expectedRemappings = {
{"", "a", "b/c/d"},
{"a", "b", "c/d/e/"},
};
map<string, string> expectedSources = {
{"<stdin>", "\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<string, string> 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<string> 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<string> 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<string> 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<string> 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

View File

@ -20,6 +20,8 @@
#include <solc/CommandLineParser.h> #include <solc/CommandLineParser.h>
#include <test/solc/Common.h>
#include <test/Common.h> #include <test/Common.h>
#include <test/libsolidity/util/SoltestErrors.h> #include <test/libsolidity/util/SoltestErrors.h>
@ -28,7 +30,6 @@
#include <libsolidity/interface/Version.h> #include <libsolidity/interface/Version.h>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>
#include <map> #include <map>
#include <optional> #include <optional>
@ -46,20 +47,13 @@ using namespace solidity::yul;
namespace namespace
{ {
optional<CommandLineOptions> parseCommandLine(vector<string> const& commandLine, ostream& _stdout, ostream& _stderr) optional<CommandLineOptions> parseCommandLine(vector<string> const& _commandLine, ostream& _stdout, ostream& _stderr)
{ {
size_t argc = commandLine.size(); vector<char const*> argv = test::makeArgv(_commandLine);
vector<char const*> 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();
CommandLineParser cliParser(_stdout, _stderr); CommandLineParser cliParser(_stdout, _stderr);
bool success = cliParser.parse( bool success = cliParser.parse(
static_cast<int>(argc), static_cast<int>(_commandLine.size()),
argv.data(), argv.data(),
false // interactiveTerminal false // interactiveTerminal
); );
@ -70,7 +64,6 @@ optional<CommandLineOptions> parseCommandLine(vector<string> const& commandLine,
return cliParser.options(); return cliParser.options();
} }
} // namespace } // namespace
namespace solidity::frontend::test namespace solidity::frontend::test
@ -99,7 +92,7 @@ BOOST_AUTO_TEST_CASE(no_options)
BOOST_TEST(sout.str() == ""); BOOST_TEST(sout.str() == "");
BOOST_TEST(serr.str() == ""); BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value()); BOOST_REQUIRE(parsedOptions.has_value());
BOOST_TEST((parsedOptions.value() == expectedOptions)); BOOST_TEST(parsedOptions.value() == expectedOptions);
} }
BOOST_AUTO_TEST_CASE(help) BOOST_AUTO_TEST_CASE(help)
@ -224,7 +217,7 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
BOOST_TEST(sout.str() == ""); BOOST_TEST(sout.str() == "");
BOOST_TEST(serr.str() == ""); BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value()); 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(sout.str() == "");
BOOST_TEST(serr.str() == "Warning: Yul is still experimental. Please use the output with care.\n"); BOOST_TEST(serr.str() == "Warning: Yul is still experimental. Please use the output with care.\n");
BOOST_REQUIRE(parsedOptions.has_value()); 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", "--standard-json",
"--base-path=/home/user/", "--base-path=/home/user/",
"--allow-paths=/tmp,/home,project,../contracts", "--allow-paths=/tmp,/home,project,../contracts",
"--ignore-missing", // Ignored in Standard JSON mode "--ignore-missing",
"--error-recovery", // Ignored in Standard JSON mode "--error-recovery", // Ignored in Standard JSON mode
"--output-dir=/tmp/out", // Accepted but has no effect 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 "--overwrite", // Accepted but has no effect in Standard JSON mode
@ -393,10 +385,10 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
CommandLineOptions expectedOptions; CommandLineOptions expectedOptions;
expectedOptions.input.mode = InputMode::StandardJson; expectedOptions.input.mode = InputMode::StandardJson;
expectedOptions.input.paths = {}; expectedOptions.input.paths = {"input.json"};
expectedOptions.input.standardJsonFile = "input.json";
expectedOptions.input.basePath = "/home/user/"; expectedOptions.input.basePath = "/home/user/";
expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts"}; expectedOptions.input.allowedDirectories = {"/tmp", "/home", "project", "../contracts"};
expectedOptions.input.ignoreMissingFiles = true;
expectedOptions.output.dir = "/tmp/out"; expectedOptions.output.dir = "/tmp/out";
expectedOptions.output.overwriteFiles = true; expectedOptions.output.overwriteFiles = true;
expectedOptions.output.revertStrings = RevertStrings::Strip; expectedOptions.output.revertStrings = RevertStrings::Strip;
@ -419,7 +411,7 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
BOOST_TEST(sout.str() == ""); BOOST_TEST(sout.str() == "");
BOOST_TEST(serr.str() == ""); BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value()); BOOST_REQUIRE(parsedOptions.has_value());
BOOST_TEST((parsedOptions.value() == expectedOptions)); BOOST_TEST(parsedOptions.value() == expectedOptions);
} }
BOOST_AUTO_TEST_SUITE_END() BOOST_AUTO_TEST_SUITE_END()

58
test/solc/Common.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
#include <test/solc/Common.h>
#include <solc/CommandLineInterface.h>
#include <sstream>
using namespace std;
using namespace solidity::frontend;
vector<char const*> test::makeArgv(vector<string> const& _commandLine)
{
size_t argc = _commandLine.size();
vector<char const*> 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<string> const& _commandLine,
string const& _standardInputContent,
bool _processInput
)
{
vector<char const*> argv = makeArgv(_commandLine);
stringstream sin(_standardInputContent), sout, serr;
CommandLineInterface cli(sin, sout, serr);
bool success = cli.parseArguments(static_cast<int>(_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()};
}

53
test/solc/Common.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
// SPDX-License-Identifier: GPL-3.0
/// Utilities shared by multiple tests for code in solc/.
#include <solc/CommandLineParser.h>
#include <boost/test/unit_test.hpp>
#include <optional>
#include <string>
#include <vector>
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<std::string> standardJsonInput;
std::string stdoutContent;
std::string stderrContent;
};
std::vector<char const*> makeArgv(std::vector<std::string> const& _commandLine);
OptionsReaderAndMessages parseCommandLineAndReadInputFiles(
std::vector<std::string> const& _commandLine,
std::string const& _standardInputContent = "",
bool _processInput = false
);
} // namespace solidity::frontend::test

View File

@ -57,7 +57,7 @@ std::ostream& operator<<(std::ostream& _output, std::tuple<T1, T2> const& _tuple
namespace boost::test_tools::tt_detail 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. // The recommended solution is to overload print_log_value<> struct and make it use our global operator.
template<typename T1,typename T2> template<typename T1,typename T2>
struct print_log_value<std::tuple<T1, T2>> struct print_log_value<std::tuple<T1, T2>>