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 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.

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)
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()

View File

@ -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<std::string> 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 <file.sol>
@ -129,6 +128,7 @@ private:
bool m_hasOutput = false;
bool m_error = false; ///< If true, some error occurred.
FileReader m_fileReader;
std::optional<std::string> m_standardJsonInput;
std::unique_ptr<frontend::CompilerStack> m_compiler;
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 true;
}
@ -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<vector<string>>())
{
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;
}
@ -735,7 +761,7 @@ General Information)").c_str(),
return false;
}
if (!checkMutuallyExclusive(m_args, g_strColor, g_strNoColor))
if (!checkMutuallyExclusive({g_strColor, g_strNoColor}))
return false;
array<string, 8> const conflictingWithStopAfter{
@ -750,7 +776,7 @@ General Information)").c_str(),
};
for (auto& option: conflictingWithStopAfter)
if (!checkMutuallyExclusive(m_args, g_strStopAfter, option))
if (!checkMutuallyExclusive({g_strStopAfter, option}))
return false;
if (m_args.count(g_strColor) > 0)
@ -861,42 +887,33 @@ General Information)").c_str(),
m_options.output.stopAfter = CompilerStack::State::Parsed;
}
vector<string> const exclusiveModes = {
if (!checkMutuallyExclusive({
g_strStandardJSON,
g_strLink,
g_strAssemble,
g_strStrictAssembly,
g_strYul,
g_strImportAst,
};
if (countEnabledOptions(exclusiveModes) > 1)
{
serr() << "The following options are mutually exclusive: " << joinOptionNames(exclusiveModes) << ". ";
serr() << "Select at most one." << endl;
}))
return false;
}
if (m_args.count(g_strStandardJSON))
{
if (m_args.count(g_strStandardJSON) > 0)
m_options.input.mode = InputMode::StandardJson;
vector<string> inputFiles;
if (m_args.count(g_strInputFile))
inputFiles = m_args[g_strInputFile].as<vector<string>>();
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;
}
else if (m_args.count(g_strAssemble) > 0 || m_args.count(g_strStrictAssembly) > 0 || m_args.count(g_strYul) > 0)
m_options.input.mode = InputMode::Assembler;
else if (m_args.count(g_strLink) > 0)
m_options.input.mode = InputMode::Linker;
else if (m_args.count(g_strImportAst) > 0)
m_options.input.mode = InputMode::CompilerWithASTImport;
else
m_options.input.mode = InputMode::Compiler;
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<vector<string>>())
if (!parseLibraryOption(library))
@ -914,10 +931,8 @@ General Information)").c_str(),
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 = {
// TODO: The list is not complete. Add more.
g_strOutputDir,
@ -1031,11 +1046,8 @@ General Information)").c_str(),
return false;
}
if (m_args.count(g_strLink))
{
m_options.input.mode = InputMode::Linker;
if (m_options.input.mode == InputMode::Linker)
return true;
}
if (m_args.count(g_strMetadataHash))
{
@ -1128,9 +1140,7 @@ General Information)").c_str(),
m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>();
}
if (m_args.count(g_strImportAst) > 0)
m_options.input.mode = InputMode::CompilerWithASTImport;
else
if (m_options.input.mode == InputMode::Compiler)
m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0);
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;
std::set<boost::filesystem::path> paths;
std::string standardJsonFile;
std::vector<ImportRemapper::Remapping> remappings;
bool addStdin = false;
boost::filesystem::path basePath = "";
@ -218,11 +217,7 @@ private:
/// @return false if there are any validation errors, true otherwise.
bool parseLibraryOption(std::string const& _input);
bool checkMutuallyExclusive(
boost::program_options::variables_map const& args,
std::string const& _optionA,
std::string const& _optionB
);
bool checkMutuallyExclusive(std::vector<std::string> const& _optionNames);
[[noreturn]] void printVersionAndExit();
[[noreturn]] void printLicenseAndExit();
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);
if (!cli.parseArguments(argc, argv))
return 1;
if (!cli.readInputFiles())
return 1;
if (!cli.processInput())
return 1;
bool success = false;

View File

@ -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/")

View File

@ -24,6 +24,21 @@ using namespace std;
using namespace solidity;
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)
{
if (boost::filesystem::is_regular_file(_path))

View File

@ -23,11 +23,16 @@
#include <boost/filesystem.hpp>
#include <set>
#include <string>
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.
/// 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);

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
{
// 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<>

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 <test/solc/Common.h>
#include <test/Common.h>
#include <test/libsolidity/util/SoltestErrors.h>
@ -28,7 +30,6 @@
#include <libsolidity/interface/Version.h>
#include <boost/algorithm/string.hpp>
#include <boost/test/unit_test.hpp>
#include <map>
#include <optional>
@ -46,20 +47,13 @@ using namespace solidity::yul;
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(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<char const*> argv = test::makeArgv(_commandLine);
CommandLineParser cliParser(_stdout, _stderr);
bool success = cliParser.parse(
static_cast<int>(argc),
static_cast<int>(_commandLine.size()),
argv.data(),
false // interactiveTerminal
);
@ -70,7 +64,6 @@ optional<CommandLineOptions> parseCommandLine(vector<string> 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()

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
{
// 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<typename T1,typename T2>
struct print_log_value<std::tuple<T1, T2>>