Merge pull request #11800 from spiyer99/separate-control-flow-from-error-reporting-in-CommandLineParser

Separate control flow from error reporting in command line parser
This commit is contained in:
chriseth 2021-12-06 16:50:27 +01:00 committed by GitHub
commit 9e9225eb60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 472 additions and 521 deletions

View File

@ -54,6 +54,10 @@ struct Exception: virtual std::exception, virtual boost::exception
::boost::throw_line(__LINE__) \
)
/// Defines an exception type that's meant to signal a specific condition and be caught rather than
/// unwind the stack all the way to the top-level exception handler and interrupt the program.
/// As such it does not carry a message - the code catching it is expected to handle it without
/// letting it escape.
#define DEV_SIMPLE_EXCEPTION(X) struct X: virtual ::solidity::util::Exception { const char* what() const noexcept override { return #X; } }
DEV_SIMPLE_EXCEPTION(InvalidAddress);

View File

@ -1,6 +1,7 @@
set(libsolcli_sources
CommandLineInterface.cpp CommandLineInterface.h
CommandLineParser.cpp CommandLineParser.h
Exceptions.h
)
add_library(solcli ${libsolcli_sources})

View File

@ -23,6 +23,8 @@
*/
#include <solc/CommandLineInterface.h>
#include <solc/Exceptions.h>
#include "license.h"
#include "solidity/BuildInfo.h"
@ -402,7 +404,7 @@ void CommandLineInterface::handleGasEstimation(string const& _contract)
}
}
bool CommandLineInterface::readInputFiles()
void CommandLineInterface::readInputFiles()
{
solAssert(!m_standardJsonInput.has_value(), "");
@ -411,23 +413,17 @@ bool CommandLineInterface::readInputFiles()
m_options.input.mode == InputMode::License ||
m_options.input.mode == InputMode::Version
)
return true;
return;
m_fileReader.setBasePath(m_options.input.basePath);
if (m_fileReader.basePath() != "")
{
if (!boost::filesystem::exists(m_fileReader.basePath()))
{
serr() << "Base path does not exist: " << m_fileReader.basePath() << endl;
return false;
}
solThrow(CommandLineValidationError, "Base path does not exist: \"" + m_fileReader.basePath().string() + '"');
if (!boost::filesystem::is_directory(m_fileReader.basePath()))
{
serr() << "Base path is not a directory: " << m_fileReader.basePath() << endl;
return false;
}
solThrow(CommandLineValidationError, "Base path is not a directory: \"" + m_fileReader.basePath().string() + '"');
}
for (boost::filesystem::path const& includePath: m_options.input.includePaths)
@ -442,16 +438,18 @@ bool CommandLineInterface::readInputFiles()
{
auto pathToQuotedString = [](boost::filesystem::path const& _path){ return "\"" + _path.string() + "\""; };
serr() << "Source unit name collision detected. ";
serr() << "The specified values of base path and/or include paths would result in multiple ";
serr() << "input files being assigned the same source unit name:" << endl;
string message =
"Source unit name collision detected. "
"The specified values of base path and/or include paths would result in multiple "
"input files being assigned the same source unit name:\n";
for (auto const& [sourceUnitName, normalizedInputPaths]: collisions)
{
serr() << sourceUnitName << " matches: ";
serr() << joinHumanReadable(normalizedInputPaths | ranges::views::transform(pathToQuotedString)) << endl;
message += sourceUnitName + " matches: ";
message += joinHumanReadable(normalizedInputPaths | ranges::views::transform(pathToQuotedString)) + "\n";
}
return false;
solThrow(CommandLineValidationError, message);
}
for (boost::filesystem::path const& infile: m_options.input.paths)
@ -459,10 +457,7 @@ bool CommandLineInterface::readInputFiles()
if (!boost::filesystem::exists(infile))
{
if (!m_options.input.ignoreMissingFiles)
{
serr() << infile << " is not found." << endl;
return false;
}
solThrow(CommandLineValidationError, '"' + infile.string() + "\" is not found.");
else
serr() << infile << " is not found. Skipping." << endl;
@ -472,10 +467,7 @@ bool CommandLineInterface::readInputFiles()
if (!boost::filesystem::is_regular_file(infile))
{
if (!m_options.input.ignoreMissingFiles)
{
serr() << infile << " is not a valid file." << endl;
return false;
}
solThrow(CommandLineValidationError, '"' + infile.string() + "\" is not a valid file.");
else
serr() << infile << " is not a valid file. Skipping." << endl;
@ -508,12 +500,7 @@ bool CommandLineInterface::readInputFiles()
}
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 true;
solThrow(CommandLineValidationError, "All specified input files either do not exist or are not regular files.");
}
map<string, Json::Value> CommandLineInterface::parseAstFromInput()
@ -559,19 +546,12 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da
string pathName = (m_options.output.dir / _fileName).string();
if (fs::exists(pathName) && !m_options.output.overwriteFiles)
{
serr() << "Refusing to overwrite existing file \"" << pathName << "\" (use --overwrite to force)." << endl;
m_outputFailed = true;
return;
}
solThrow(CommandLineOutputError, "Refusing to overwrite existing file \"" + pathName + "\" (use --overwrite to force).");
ofstream outFile(pathName);
outFile << _data;
if (!outFile)
{
serr() << "Could not write to file \"" << pathName << "\"." << endl;
m_outputFailed = true;
return;
}
solThrow(CommandLineOutputError, "Could not write to file \"" + pathName + "\".");
}
void CommandLineInterface::createJson(string const& _fileName, string const& _json)
@ -579,9 +559,33 @@ void CommandLineInterface::createJson(string const& _fileName, string const& _js
createFile(boost::filesystem::basename(_fileName) + string(".json"), _json);
}
bool CommandLineInterface::run(int _argc, char const* const* _argv)
{
try
{
if (!parseArguments(_argc, _argv))
return false;
readInputFiles();
processInput();
return true;
}
catch (CommandLineError const& _exception)
{
m_hasOutput = true;
// There might be no message in the exception itself if the error output is bulky and has
// already been printed to stderr (this happens e.g. for compiler errors).
if (_exception.what() != ""s)
serr() << _exception.what() << endl;
return false;
}
}
bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv)
{
CommandLineParser parser(serr(/* _markAsUsed */ false));
CommandLineParser parser;
if (isatty(fileno(stdin)) && _argc == 1)
{
@ -592,16 +596,13 @@ bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv)
return false;
}
bool success = parser.parse(_argc, _argv);
if (!success)
return false;
m_hasOutput = m_hasOutput || parser.hasOutput();
parser.parse(_argc, _argv);
m_options = parser.options();
return true;
}
bool CommandLineInterface::processInput()
void CommandLineInterface::processInput()
{
switch (m_options.input.mode)
{
@ -624,22 +625,17 @@ bool CommandLineInterface::processInput()
break;
}
case InputMode::Assembler:
if (!assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine))
return false;
assemble(m_options.assembly.inputLanguage, m_options.assembly.targetMachine);
break;
case InputMode::Linker:
if (!link())
return false;
link();
writeLinkedFiles();
break;
case InputMode::Compiler:
case InputMode::CompilerWithASTImport:
if (!compile())
return false;
compile();
outputCompilationResults();
}
return !m_outputFailed;
}
void CommandLineInterface::printVersion()
@ -655,7 +651,7 @@ void CommandLineInterface::printLicense()
sout() << licenseText << endl;
}
bool CommandLineInterface::compile()
void CommandLineInterface::compile()
{
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport, "");
@ -718,8 +714,9 @@ bool CommandLineInterface::compile()
}
catch (Exception const& _exc)
{
serr() << string("Failed to import AST: ") << _exc.what() << endl;
return false;
// FIXME: AST import is missing proper validations. This hack catches failing
// assertions and presents them as if they were compiler errors.
solThrow(CommandLineExecutionError, "Failed to import AST: "s + _exc.what());
}
}
else
@ -736,29 +733,29 @@ bool CommandLineInterface::compile()
formatter.printErrorInformation(*error);
}
if (!successful)
return m_options.input.errorRecovery;
if (!successful && !m_options.input.errorRecovery)
solThrow(CommandLineExecutionError, "");
}
catch (CompilerError const& _exception)
{
m_hasOutput = true;
formatter.printExceptionInformation(_exception, "Compiler error");
return false;
solThrow(CommandLineExecutionError, "");
}
catch (Error const& _error)
{
if (_error.type() == Error::Type::DocstringParsingError)
serr() << "Documentation parsing error: " << *boost::get_error_info<errinfo_comment>(_error) << endl;
{
serr() << *boost::get_error_info<errinfo_comment>(_error);
solThrow(CommandLineExecutionError, "Documentation parsing failed.");
}
else
{
m_hasOutput = true;
formatter.printExceptionInformation(_error, _error.typeName());
solThrow(CommandLineExecutionError, "");
}
return false;
}
return true;
}
void CommandLineInterface::handleCombinedJSON()
@ -887,7 +884,7 @@ void CommandLineInterface::handleAst()
}
}
bool CommandLineInterface::link()
void CommandLineInterface::link()
{
solAssert(m_options.input.mode == InputMode::Linker, "");
@ -925,11 +922,11 @@ bool CommandLineInterface::link()
*(it + placeholderSize - 2) != '_' ||
*(it + placeholderSize - 1) != '_'
)
{
serr() << "Error in binary object file " << src.first << " at position " << (it - src.second.begin()) << endl;
serr() << '"' << string(it, it + min(placeholderSize, static_cast<int>(end - it))) << "\" is not a valid link reference." << endl;
return false;
}
solThrow(
CommandLineExecutionError,
"Error in binary object file " + src.first + " at position " + to_string(it - src.second.begin()) + "\n" +
'"' + string(it, it + min(placeholderSize, static_cast<int>(end - it))) + "\" is not a valid link reference."
);
string foundPlaceholder(it, it + placeholderSize);
if (librariesReplacements.count(foundPlaceholder))
@ -948,8 +945,6 @@ bool CommandLineInterface::link()
src.second.resize(src.second.size() - 1);
}
m_fileReader.setSources(move(sourceCodes));
return true;
}
void CommandLineInterface::writeLinkedFiles()
@ -964,11 +959,7 @@ void CommandLineInterface::writeLinkedFiles()
ofstream outFile(src.first);
outFile << src.second;
if (!outFile)
{
serr() << "Could not write to file " << src.first << ". Aborting." << endl;
m_outputFailed = true;
return;
}
solThrow(CommandLineOutputError, "Could not write to file " + src.first + ". Aborting.");
}
sout() << "Linking completed." << endl;
}
@ -990,10 +981,12 @@ string CommandLineInterface::objectWithLinkRefsHex(evmasm::LinkerObject const& _
return out;
}
bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine)
void CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine)
{
solAssert(m_options.input.mode == InputMode::Assembler, "");
serr() << "Warning: Yul is still experimental. Please use the output with care." << endl;
bool successful = true;
map<string, yul::AssemblyStack> assemblyStacks;
for (auto const& src: m_fileReader.sourceCodes())
@ -1031,7 +1024,10 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
}
if (!successful)
return false;
{
solAssert(m_hasOutput);
solThrow(CommandLineExecutionError, "");
}
for (auto const& src: m_fileReader.sourceCodes())
{
@ -1089,8 +1085,6 @@ bool CommandLineInterface::assemble(yul::AssemblyStack::Language _language, yul:
serr() << "No text representation found." << endl;
}
}
return true;
}
void CommandLineInterface::outputCompilationResults()
@ -1127,13 +1121,9 @@ void CommandLineInterface::outputCompilationResults()
ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes());
if (!m_options.output.dir.empty())
{
createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret);
}
else
{
sout() << "EVM assembly:" << endl << ret << endl;
}
}
if (m_options.compiler.estimateGas)

View File

@ -51,12 +51,28 @@ public:
m_options(_options)
{}
/// Parse command line arguments and return false if we should not continue
/// Parses command-line arguments, executes the requested operation and handles validation and
/// execution errors.
/// @returns false if it catches a @p CommandLineValidationError or if the application is
/// expected to exit with a non-zero exit code despite there being no error.
bool run(int _argc, char const* const* _argv);
/// Parses command line arguments and stores the result in @p m_options.
/// @throws CommandLineValidationError if command-line arguments are invalid.
/// @returns false if the application is expected to exit with a non-zero exit code despite
/// there being no error.
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, create source code objects, print the output.
bool processInput();
/// Reads the content of all input files and initializes the file reader.
/// @throws CommandLineValidationError if it fails to read the input files (invalid paths,
/// non-existent files, not enough or too many input files, etc.).
void readInputFiles();
/// Executes the requested operation (compilation, assembling, standard JSON, etc.) and prints
/// results to the terminal.
/// @throws CommandLineExecutionError if execution fails due to errors in the input files.
/// @throws CommandLineOutputError if creating output files or writing to them fails.
void processInput();
CommandLineOptions const& options() const { return m_options; }
FileReader const& fileReader() const { return m_fileReader; }
@ -65,15 +81,15 @@ public:
private:
void printVersion();
void printLicense();
bool compile();
bool link();
void compile();
void link();
void writeLinkedFiles();
/// @returns the ``// <identifier> -> name`` hint for library placeholders.
static std::string libraryPlaceholderHint(std::string const& _libraryName);
/// @returns the full object with library placeholder hints in hex.
static std::string objectWithLinkRefsHex(evmasm::LinkerObject const& _obj);
bool assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine);
void assemble(yul::AssemblyStack::Language _language, yul::AssemblyStack::Machine _targetMachine);
void outputCompilationResults();
@ -120,7 +136,6 @@ private:
std::ostream& m_sout;
std::ostream& m_serr;
bool m_hasOutput = false;
bool m_outputFailed = false; ///< If true, creation or write to some of the output files failed.
FileReader m_fileReader;
std::optional<std::string> m_standardJsonInput;
std::unique_ptr<frontend::CompilerStack> m_compiler;

View File

@ -17,7 +17,11 @@
// SPDX-License-Identifier: GPL-3.0
#include <solc/CommandLineParser.h>
#include <solc/Exceptions.h>
#include <libyul/optimiser/Suite.h>
#include <liblangutil/EVMVersion.h>
#include <boost/algorithm/string.hpp>
@ -34,14 +38,6 @@ namespace po = boost::program_options;
namespace solidity::frontend
{
ostream& CommandLineParser::serr()
{
m_hasOutput = true;
return m_serr;
}
#define cerr
static string const g_strAllowPaths = "allow-paths";
static string const g_strBasePath = "base-path";
static string const g_strIncludePath = "include-path";
@ -131,6 +127,9 @@ static set<string> const g_metadataHashArgs
};
static map<InputMode, string> const g_inputModeName = {
{InputMode::Help, "help"},
{InputMode::License, "license"},
{InputMode::Version, "version"},
{InputMode::Compiler, "compiler"},
{InputMode::CompilerWithASTImport, "compiler (AST import)"},
{InputMode::Assembler, "assembler"},
@ -138,15 +137,16 @@ static map<InputMode, string> const g_inputModeName = {
{InputMode::Linker, "linker"},
};
bool CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
void CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
{
if (countEnabledOptions(_optionNames) > 1)
{
serr() << "The following options are mutually exclusive: " << joinOptionNames(_optionNames) << ". ";
serr() << "Select at most one." << endl;
return false;
solThrow(
CommandLineValidationError,
"The following options are mutually exclusive: " + joinOptionNames(_optionNames) + ". " +
"Select at most one."
);
}
return true;
}
bool CompilerOutputs::operator==(CompilerOutputs const& _other) const noexcept
@ -268,17 +268,13 @@ OptimiserSettings CommandLineOptions::optimiserSettings() const
return settings;
}
bool CommandLineParser::parse(int _argc, char const* const* _argv)
void CommandLineParser::parse(int _argc, char const* const* _argv)
{
m_hasOutput = false;
if (!parseArgs(_argc, _argv))
return false;
return processArgs();
parseArgs(_argc, _argv);
processArgs();
}
bool CommandLineParser::parseInputPathsAndRemappings()
void CommandLineParser::parseInputPathsAndRemappings()
{
m_options.input.ignoreMissingFiles = (m_args.count(g_strIgnoreMissingFiles) > 0);
@ -289,17 +285,14 @@ bool CommandLineParser::parseInputPathsAndRemappings()
{
optional<ImportRemapper::Remapping> remapping = ImportRemapper::parseRemapping(positionalArg);
if (!remapping.has_value())
{
serr() << "Invalid remapping: \"" << positionalArg << "\"." << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid remapping: \"" + positionalArg + "\".");
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;
}
solThrow(
CommandLineValidationError,
"Import remappings are not accepted on the command line in Standard JSON mode.\n"
"Please put them under 'settings.remappings' in the JSON input."
);
if (!remapping->target.empty())
{
@ -324,26 +317,24 @@ bool CommandLineParser::parseInputPathsAndRemappings()
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;
}
solThrow(
CommandLineValidationError,
"Too many input files for --" + g_strStandardJSON + ".\n"
"Please either specify a single file name or provide its content on standard input."
);
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;
solThrow(
CommandLineValidationError,
"No input files given. If you wish to use the standard input please specify \"-\" explicitly."
);
}
bool CommandLineParser::parseLibraryOption(string const& _input)
void CommandLineParser::parseLibraryOption(string const& _input)
{
namespace fs = boost::filesystem;
string data = _input;
@ -378,71 +369,71 @@ bool CommandLineParser::parseLibraryOption(string const& _input)
{
separator = lib.rfind(':');
if (separator == string::npos)
{
serr() << "Equal sign separator missing in library address specifier \"" << lib << "\"" << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Equal sign separator missing in library address specifier \"" + lib + "\""
);
else
isSeparatorEqualSign = false; // separator is colon
}
else
if (lib.rfind('=') != lib.find('='))
{
serr() << "Only one equal sign \"=\" is allowed in the address string \"" << lib << "\"." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Only one equal sign \"=\" is allowed in the address string \"" + lib + "\"."
);
string libName(lib.begin(), lib.begin() + static_cast<ptrdiff_t>(separator));
boost::trim(libName);
if (m_options.linker.libraries.count(libName))
{
serr() << "Address specified more than once for library \"" << libName << "\"." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Address specified more than once for library \"" + libName + "\"."
);
string addrString(lib.begin() + static_cast<ptrdiff_t>(separator) + 1, lib.end());
boost::trim(addrString);
if (addrString.empty())
{
serr() << "Empty address provided for library \"" << libName << "\"." << endl;
serr() << "Note that there should not be any whitespace after the " << (isSeparatorEqualSign ? "equal sign" : "colon") << "." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Empty address provided for library \"" + libName + "\".\n"
"Note that there should not be any whitespace after the " +
(isSeparatorEqualSign ? "equal sign" : "colon") + "."
);
if (addrString.substr(0, 2) == "0x")
addrString = addrString.substr(2);
else
{
serr() << "The address " << addrString << " is not prefixed with \"0x\"." << endl;
serr() << "Note that the address must be prefixed with \"0x\"." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"The address " + addrString + " is not prefixed with \"0x\".\n"
"Note that the address must be prefixed with \"0x\"."
);
if (addrString.length() != 40)
{
serr() << "Invalid length for address for library \"" << libName << "\": " << addrString.length() << " instead of 40 characters." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Invalid length for address for library \"" + libName + "\": " +
to_string(addrString.length()) + " instead of 40 characters."
);
if (!passesAddressChecksum(addrString, false))
{
serr() << "Invalid checksum on address for library \"" << libName << "\": " << addrString << endl;
serr() << "The correct checksum is " << getChecksummedAddress(addrString) << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Invalid checksum on address for library \"" + libName + "\": " + addrString + "\n"
"The correct checksum is " + getChecksummedAddress(addrString)
);
bytes binAddr = fromHex(addrString);
h160 address(binAddr, h160::AlignRight);
if (binAddr.size() > 20 || address == h160())
{
serr() << "Invalid address for library \"" << libName << "\": " << addrString << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Invalid address for library \"" + libName + "\": " + addrString
);
m_options.linker.libraries[libName] = address;
}
return true;
}
bool CommandLineParser::parseOutputSelection()
void CommandLineParser::parseOutputSelection()
{
static auto outputSupported = [](InputMode _mode, string_view _outputName)
{
@ -498,13 +489,11 @@ bool CommandLineParser::parseOutputSelection()
unsupportedOutputs.push_back(optionName);
if (!unsupportedOutputs.empty())
{
serr() << "The following outputs are not supported in " << g_inputModeName.at(m_options.input.mode) << " mode: ";
serr() << joinOptionNames(unsupportedOutputs) << ".";
return false;
}
return true;
solThrow(
CommandLineValidationError,
"The following outputs are not supported in " + g_inputModeName.at(m_options.input.mode) + " mode: " +
joinOptionNames(unsupportedOutputs) + "."
);
}
po::options_description CommandLineParser::optionsDescription()
@ -843,7 +832,7 @@ po::positional_options_description CommandLineParser::positionalOptionsDescripti
return filesPositions;
}
bool CommandLineParser::parseArgs(int _argc, char const* const* _argv)
void CommandLineParser::parseArgs(int _argc, char const* const* _argv)
{
po::options_description allOptions = optionsDescription();
po::positional_options_description filesPositions = positionalOptionsDescription();
@ -858,18 +847,15 @@ bool CommandLineParser::parseArgs(int _argc, char const* const* _argv)
}
catch (po::error const& _exception)
{
serr() << _exception.what() << endl;
return false;
solThrow(CommandLineValidationError, _exception.what());
}
po::notify(m_args);
return true;
}
bool CommandLineParser::processArgs()
void CommandLineParser::processArgs()
{
if (!checkMutuallyExclusive({
checkMutuallyExclusive({
g_strHelp,
g_strLicense,
g_strVersion,
@ -879,8 +865,7 @@ bool CommandLineParser::processArgs()
g_strStrictAssembly,
g_strYul,
g_strImportAst,
}))
return false;
});
if (m_args.count(g_strHelp) > 0)
m_options.input.mode = InputMode::Help;
@ -904,7 +889,7 @@ bool CommandLineParser::processArgs()
m_options.input.mode == InputMode::License ||
m_options.input.mode == InputMode::Version
)
return true;
return;
map<string, set<InputMode>> validOptionInputModeCombinations = {
// TODO: This should eventually contain all options.
@ -919,13 +904,13 @@ bool CommandLineParser::processArgs()
}
if (!invalidOptionsForCurrentInputMode.empty())
{
serr() << "The following options are not supported in the current input mode: " << joinOptionNames(invalidOptionsForCurrentInputMode) << endl;
return false;
}
solThrow(
CommandLineValidationError,
"The following options are not supported in the current input mode: " +
joinOptionNames(invalidOptionsForCurrentInputMode)
);
if (!checkMutuallyExclusive({g_strColor, g_strNoColor}))
return false;
checkMutuallyExclusive({g_strColor, g_strNoColor});
array<string, 9> const conflictingWithStopAfter{
CompilerOutputs::componentName(&CompilerOutputs::binary),
@ -940,8 +925,7 @@ bool CommandLineParser::processArgs()
};
for (auto& option: conflictingWithStopAfter)
if (!checkMutuallyExclusive({g_strStopAfter, option}))
return false;
checkMutuallyExclusive({g_strStopAfter, option});
if (
m_options.input.mode != InputMode::Compiler &&
@ -950,23 +934,23 @@ bool CommandLineParser::processArgs()
)
{
if (!m_args[g_strOptimizeRuns].defaulted())
{
serr() << "Option --" << g_strOptimizeRuns << " is only valid in compiler and assembler modes." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Option --" + g_strOptimizeRuns + " is only valid in compiler and assembler modes."
);
for (string const& option: {g_strOptimize, g_strNoOptimizeYul, g_strOptimizeYul, g_strYulOptimizations})
if (m_args.count(option) > 0)
{
serr() << "Option --" << option << " is only valid in compiler and assembler modes." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Option --" + option + " is only valid in compiler and assembler modes."
);
if (!m_args[g_strDebugInfo].defaulted())
{
serr() << "Option --" << g_strDebugInfo << " is only valid in compiler and assembler modes." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Option --" + g_strDebugInfo + " is only valid in compiler and assembler modes."
);
}
if (m_args.count(g_strColor) > 0)
@ -981,15 +965,15 @@ bool CommandLineParser::processArgs()
string revertStringsString = m_args[g_strRevertStrings].as<string>();
std::optional<RevertStrings> revertStrings = revertStringsFromString(revertStringsString);
if (!revertStrings)
{
serr() << "Invalid option for --" << g_strRevertStrings << ": " << revertStringsString << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Invalid option for --" + g_strRevertStrings + ": " + revertStringsString
);
if (*revertStrings == RevertStrings::VerboseDebug)
{
serr() << "Only \"default\", \"strip\" and \"debug\" are implemented for --" << g_strRevertStrings << " for now." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"Only \"default\", \"strip\" and \"debug\" are implemented for --" + g_strRevertStrings + " for now."
);
m_options.output.revertStrings = *revertStrings;
}
@ -998,20 +982,13 @@ bool CommandLineParser::processArgs()
string optionValue = m_args[g_strDebugInfo].as<string>();
m_options.output.debugInfoSelection = DebugInfoSelection::fromString(optionValue);
if (!m_options.output.debugInfoSelection.has_value())
{
serr() << "Invalid value for --" << g_strDebugInfo << " option: " << optionValue << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid value for --" + g_strDebugInfo + " option: " + optionValue);
if (m_options.output.debugInfoSelection->snippet && !m_options.output.debugInfoSelection->location)
{
serr() << "To use 'snippet' with --" << g_strDebugInfo << " you must select also 'location'." << endl;
return false;
}
solThrow(CommandLineValidationError, "To use 'snippet' with --" + g_strDebugInfo + " you must select also 'location'.");
}
if (!parseCombinedJsonOption())
return false;
parseCombinedJsonOption();
if (m_args.count(g_strOutputDir))
m_options.output.dir = m_args.at(g_strOutputDir).as<string>();
@ -1028,8 +1005,7 @@ bool CommandLineParser::processArgs()
m_options.formatting.json.indent = m_args[g_strJsonIndent].as<uint32_t>();
}
if (!parseOutputSelection())
return false;
parseOutputSelection();
m_options.compiler.estimateGas = (m_args.count(g_strGas) > 0);
@ -1039,18 +1015,13 @@ bool CommandLineParser::processArgs()
if (m_args.count(g_strIncludePath) > 0)
{
if (m_options.input.basePath.empty())
{
serr() << "--" << g_strIncludePath << " option requires a non-empty base path." << endl;
return false;
}
solThrow(CommandLineValidationError, "--" + g_strIncludePath + " option requires a non-empty base path.");
for (string const& includePath: m_args[g_strIncludePath].as<vector<string>>())
{
if (includePath.empty())
{
serr() << "Empty values are not allowed in --" << g_strIncludePath << "." << endl;
return false;
}
solThrow(CommandLineValidationError, "Empty values are not allowed in --" + g_strIncludePath + ".");
m_options.input.includePaths.push_back(includePath);
}
}
@ -1066,37 +1037,29 @@ bool CommandLineParser::processArgs()
if (m_args.count(g_strStopAfter))
{
if (m_args[g_strStopAfter].as<string>() != "parsing")
{
serr() << "Valid options for --" << g_strStopAfter << " are: \"parsing\".\n";
return false;
}
solThrow(CommandLineValidationError, "Valid options for --" + g_strStopAfter + " are: \"parsing\".\n");
else
m_options.output.stopAfter = CompilerStack::State::Parsed;
}
if (!parseInputPathsAndRemappings())
return false;
parseInputPathsAndRemappings();
if (m_options.input.mode == InputMode::StandardJson)
return true;
return;
if (m_args.count(g_strLibraries))
for (string const& library: m_args[g_strLibraries].as<vector<string>>())
if (!parseLibraryOption(library))
return false;
parseLibraryOption(library);
if (m_options.input.mode == InputMode::Linker)
return true;
return;
if (m_args.count(g_strEVMVersion))
{
string versionOptionStr = m_args[g_strEVMVersion].as<string>();
std::optional<langutil::EVMVersion> versionOption = langutil::EVMVersion::fromString(versionOptionStr);
if (!versionOption)
{
serr() << "Invalid option for --" << g_strEVMVersion << ": " << versionOptionStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strEVMVersion + ": " + versionOptionStr);
m_options.output.evmVersion = *versionOption;
}
@ -1109,10 +1072,7 @@ bool CommandLineParser::processArgs()
{
OptimiserSettings optimiserSettings = m_options.optimiserSettings();
if (!optimiserSettings.runYulOptimiser)
{
serr() << "--" << g_strYulOptimizations << " is invalid if Yul optimizer is disabled" << endl;
return false;
}
solThrow(CommandLineValidationError, "--" + g_strYulOptimizations + " is invalid if Yul optimizer is disabled");
try
{
@ -1120,8 +1080,10 @@ bool CommandLineParser::processArgs()
}
catch (yul::OptimizerException const& _exception)
{
serr() << "Invalid optimizer step sequence in --" << g_strYulOptimizations << ": " << _exception.what() << endl;
return false;
solThrow(
CommandLineValidationError,
"Invalid optimizer step sequence in --" + g_strYulOptimizations + ": " + _exception.what()
);
}
m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>();
@ -1142,12 +1104,11 @@ bool CommandLineParser::processArgs()
auto optionEnabled = [&](string const& name){ return m_args.count(name) > 0; };
auto enabledOptions = nonAssemblyModeOptions | ranges::views::filter(optionEnabled) | ranges::to_vector;
serr() << "The following options are invalid in assembly mode: ";
serr() << joinOptionNames(enabledOptions) << ".";
string message = "The following options are invalid in assembly mode: " + joinOptionNames(enabledOptions) + ".";
if (m_args.count(g_strOptimizeYul) || m_args.count(g_strNoOptimizeYul))
serr() << " Optimization is disabled by default and can be enabled with --" << g_strOptimize << "." << endl;
serr() << endl;
return false;
message += " Optimization is disabled by default and can be enabled with --" + g_strOptimize + ".";
solThrow(CommandLineValidationError, message);
}
// switch to assembly mode
@ -1163,10 +1124,7 @@ bool CommandLineParser::processArgs()
else if (machine == g_strEwasm)
m_options.assembly.targetMachine = Machine::Ewasm;
else
{
serr() << "Invalid option for --" << g_strMachine << ": " << machine << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strMachine + ": " + machine);
}
if (m_options.assembly.targetMachine == Machine::Ewasm && m_options.assembly.inputLanguage == Input::StrictAssembly)
m_options.assembly.inputLanguage = Input::Ewasm;
@ -1179,45 +1137,33 @@ bool CommandLineParser::processArgs()
{
m_options.assembly.inputLanguage = Input::Ewasm;
if (m_options.assembly.targetMachine != Machine::Ewasm)
{
serr() << "If you select Ewasm as --" << g_strYulDialect << ", ";
serr() << "--" << g_strMachine << " has to be Ewasm as well." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"If you select Ewasm as --" + g_strYulDialect + ", "
"--" + g_strMachine + " has to be Ewasm as well."
);
}
else
{
serr() << "Invalid option for --" << g_strYulDialect << ": " << dialect << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strYulDialect + ": " + dialect);
}
if (m_options.optimizer.enabled && (m_options.assembly.inputLanguage != Input::StrictAssembly && m_options.assembly.inputLanguage != Input::Ewasm))
{
serr() <<
"Optimizer can only be used for strict assembly. Use --" <<
g_strStrictAssembly <<
"." <<
endl;
return false;
}
solThrow(
CommandLineValidationError,
"Optimizer can only be used for strict assembly. Use --" + g_strStrictAssembly + "."
);
if (m_options.assembly.targetMachine == Machine::Ewasm && m_options.assembly.inputLanguage != Input::StrictAssembly && m_options.assembly.inputLanguage != Input::Ewasm)
{
serr() << "The selected input language is not directly supported when targeting the Ewasm machine ";
serr() << "and automatic translation is not available." << endl;
return false;
}
serr() <<
"Warning: Yul is still experimental. Please use the output with care." <<
endl;
return true;
solThrow(
CommandLineValidationError,
"The selected input language is not directly supported when targeting the Ewasm machine "
"and automatic translation is not available."
);
return;
}
else if (countEnabledOptions({g_strYulDialect, g_strMachine}) >= 1)
{
serr() << "--" << g_strYulDialect << " and --" << g_strMachine << " ";
serr() << "are only valid in assembly mode." << endl;
return false;
}
solThrow(
CommandLineValidationError,
"--" + g_strYulDialect + " and --" + g_strMachine + " are only valid in assembly mode."
);
if (m_args.count(g_strMetadataHash))
{
@ -1229,10 +1175,7 @@ bool CommandLineParser::processArgs()
else if (hashStr == g_strNone)
m_options.metadata.hash = CompilerStack::MetadataHash::None;
else
{
serr() << "Invalid option for --" << g_strMetadataHash << ": " << hashStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strMetadataHash + ": " + hashStr);
}
if (m_args.count(g_strModelCheckerContracts))
@ -1240,10 +1183,7 @@ bool CommandLineParser::processArgs()
string contractsStr = m_args[g_strModelCheckerContracts].as<string>();
optional<ModelCheckerContracts> contracts = ModelCheckerContracts::fromString(contractsStr);
if (!contracts)
{
serr() << "Invalid option for --" << g_strModelCheckerContracts << ": " << contractsStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerContracts + ": " + contractsStr);
m_options.modelChecker.settings.contracts = move(*contracts);
}
@ -1255,10 +1195,7 @@ bool CommandLineParser::processArgs()
string engineStr = m_args[g_strModelCheckerEngine].as<string>();
optional<ModelCheckerEngine> engine = ModelCheckerEngine::fromString(engineStr);
if (!engine)
{
serr() << "Invalid option for --" << g_strModelCheckerEngine << ": " << engineStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerEngine + ": " + engineStr);
m_options.modelChecker.settings.engine = *engine;
}
@ -1267,10 +1204,7 @@ bool CommandLineParser::processArgs()
string invsStr = m_args[g_strModelCheckerInvariants].as<string>();
optional<ModelCheckerInvariants> invs = ModelCheckerInvariants::fromString(invsStr);
if (!invs)
{
serr() << "Invalid option for --" << g_strModelCheckerInvariants << ": " << invsStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerInvariants + ": " + invsStr);
m_options.modelChecker.settings.invariants = *invs;
}
@ -1282,10 +1216,7 @@ bool CommandLineParser::processArgs()
string solversStr = m_args[g_strModelCheckerSolvers].as<string>();
optional<smtutil::SMTSolverChoice> solvers = smtutil::SMTSolverChoice::fromString(solversStr);
if (!solvers)
{
serr() << "Invalid option for --" << g_strModelCheckerSolvers << ": " << solversStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerSolvers + ": " + solversStr);
m_options.modelChecker.settings.solvers = *solvers;
}
@ -1294,10 +1225,7 @@ bool CommandLineParser::processArgs()
string targetsStr = m_args[g_strModelCheckerTargets].as<string>();
optional<ModelCheckerTargets> targets = ModelCheckerTargets::fromString(targetsStr);
if (!targets)
{
serr() << "Invalid option for --" << g_strModelCheckerTargets << ": " << targetsStr << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerTargets + ": " + targetsStr);
m_options.modelChecker.settings.targets = *targets;
}
@ -1319,27 +1247,21 @@ bool CommandLineParser::processArgs()
m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0);
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport);
return true;
}
bool CommandLineParser::parseCombinedJsonOption()
void CommandLineParser::parseCombinedJsonOption()
{
if (!m_args.count(g_strCombinedJson))
return true;
return;
set<string> requests;
for (string const& item: boost::split(requests, m_args[g_strCombinedJson].as<string>(), boost::is_any_of(",")))
if (CombinedJsonRequests::componentMap().count(item) == 0)
{
serr() << "Invalid option to --" << g_strCombinedJson << ": " << item << endl;
return false;
}
solThrow(CommandLineValidationError, "Invalid option to --" + g_strCombinedJson + ": " + item);
m_options.compiler.combinedJsonRequests = CombinedJsonRequests{};
for (auto&& [componentName, component]: CombinedJsonRequests::componentMap())
m_options.compiler.combinedJsonRequests.value().*component = (requests.count(componentName) > 0);
return true;
}
size_t CommandLineParser::countEnabledOptions(vector<string> const& _optionNames) const

View File

@ -234,27 +234,17 @@ struct CommandLineOptions
};
/// Parses the command-line arguments and produces a filled-out CommandLineOptions structure.
/// Validates provided values and prints error messages in case of errors.
/// Validates provided values and reports errors by throwing @p CommandLineValidationErrors.
class CommandLineParser
{
public:
explicit CommandLineParser(std::ostream& _serr):
m_serr(_serr)
{}
/// Parses the command-line arguments and fills out the internal CommandLineOptions structure.
/// Performs validation and prints error messages.
/// @return true if there were no validation errors when parsing options and the
/// CommandLineOptions structure has been fully initialized. false if there were errors - in
/// this case CommandLineOptions may be only partially filled out. May also return false if
/// there is not further processing necessary and the program should just exit.
bool parse(int _argc, char const* const* _argv);
/// @throws CommandLineValidationError if the arguments cannot be properly parsed or are invalid.
/// When an exception is thrown, the @p CommandLineOptions may be only partially filled out.
void parse(int _argc, char const* const* _argv);
CommandLineOptions const& options() const { return m_options; }
/// Returns true if the parser has written anything to any of its output streams.
bool hasOutput() const { return m_hasOutput; }
static void printHelp(std::ostream& _out) { _out << optionsDescription(); }
private:
@ -269,40 +259,32 @@ private:
/// Uses boost::program_options to parse the command-line arguments and leaves the result in @a m_args.
/// Also handles the arguments that result in information being printed followed by immediate exit.
/// @returns false if parsing fails due to syntactical errors or the arguments not matching the description.
bool parseArgs(int _argc, char const* const* _argv);
void parseArgs(int _argc, char const* const* _argv);
/// Validates parsed arguments stored in @a m_args and fills out the internal CommandLineOptions
/// structure.
/// @return false if there are any validation errors, true otherwise.
bool processArgs();
/// @throws CommandLineValidationError in case of validation errors.
void processArgs();
/// Parses the value supplied to --combined-json.
/// @return false if there are any validation errors, true otherwise.
bool parseCombinedJsonOption();
/// @throws CommandLineValidationError in case of validation errors.
void parseCombinedJsonOption();
/// Parses the names of the input files, remappings for all modes except for Standard JSON.
/// Does not check if files actually exist.
/// @return false if there are any validation errors, true otherwise.
bool parseInputPathsAndRemappings();
/// Parses the names of the input files, remappings. Does not check if the files actually exist.
/// @throws CommandLineValidationError in case of validation errors.
void parseInputPathsAndRemappings();
/// Tries to read from the file @a _input or interprets @a _input literally if that fails.
/// It then tries to parse the contents and appends to m_options.libraries.
/// @return false if there are any validation errors, true otherwise.
bool parseLibraryOption(std::string const& _input);
/// It then tries to parse the contents and appends to @a m_options.libraries.
/// @throws CommandLineValidationError in case of validation errors.
void parseLibraryOption(std::string const& _input);
bool parseOutputSelection();
void parseOutputSelection();
bool checkMutuallyExclusive(std::vector<std::string> const& _optionNames);
void checkMutuallyExclusive(std::vector<std::string> const& _optionNames);
size_t countEnabledOptions(std::vector<std::string> const& _optionNames) const;
static std::string joinOptionNames(std::vector<std::string> const& _optionNames, std::string _separator = ", ");
/// Returns the stream that should receive error output. Sets m_hasOutput to true if the
/// stream has ever been used.
std::ostream& serr();
std::ostream& m_serr;
bool m_hasOutput = false;
CommandLineOptions m_options;
/// Map of command-line arguments produced by boost::program_options.

34
solc/Exceptions.h Normal file
View File

@ -0,0 +1,34 @@
/*
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
/**
* Exceptions used by the command-line interface.
*/
#pragma once
#include <liblangutil/Exceptions.h>
namespace solidity::frontend
{
struct CommandLineError: virtual util::Exception {};
struct CommandLineExecutionError: virtual CommandLineError {};
struct CommandLineValidationError: virtual CommandLineError {};
struct CommandLineOutputError: virtual CommandLineError {};
}

View File

@ -62,12 +62,7 @@ int main(int argc, char** argv)
{
setDefaultOrCLocale();
solidity::frontend::CommandLineInterface cli(cin, cout, cerr);
bool success =
cli.parseArguments(argc, argv) &&
cli.readInputFiles() &&
cli.processInput();
return success ? 0 : 1;
return cli.run(argc, argv) ? 0 : 1;
}
catch (smtutil::SMTLogicError const& _exception)
{

View File

@ -19,6 +19,7 @@
/// Unit tests for solc/CommandLineInterface.h
#include <solc/CommandLineInterface.h>
#include <solc/Exceptions.h>
#include <test/solc/Common.h>
@ -114,7 +115,7 @@ BOOST_AUTO_TEST_SUITE(CommandLineInterfaceTest)
BOOST_AUTO_TEST_CASE(help)
{
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--help"}, "", /* _processInput */ true);
OptionsReaderAndMessages result = runCLI({"solc", "--help"}, "");
BOOST_TEST(result.success);
BOOST_TEST(boost::starts_with(result.stdoutContent, "solc, the Solidity commandline compiler."));
@ -124,7 +125,7 @@ BOOST_AUTO_TEST_CASE(help)
BOOST_AUTO_TEST_CASE(license)
{
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--license"}, "", /* _processInput */ true);
OptionsReaderAndMessages result = runCLI({"solc", "--license"}, "");
BOOST_TEST(result.success);
BOOST_TEST(boost::starts_with(result.stdoutContent, "Most of the code is licensed under GPLv3"));
@ -134,7 +135,7 @@ BOOST_AUTO_TEST_CASE(license)
BOOST_AUTO_TEST_CASE(version)
{
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--version"}, "", /* _processInput */ true);
OptionsReaderAndMessages result = runCLI({"solc", "--version"}, "");
BOOST_TEST(result.success);
BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n"));
@ -158,17 +159,16 @@ BOOST_AUTO_TEST_CASE(multiple_input_modes)
string expectedMessage =
"The following options are mutually exclusive: "
"--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. "
"Select at most one.\n";
"Select at most one.";
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_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({"solc", mode1, mode2}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
BOOST_AUTO_TEST_CASE(cli_input)
@ -253,7 +253,7 @@ BOOST_AUTO_TEST_CASE(cli_ignore_missing_no_files_exist)
"\"" + (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({
OptionsReaderAndMessages result = runCLI({
"solc",
(tempDir.path() / "input1.sol").string(),
(tempDir.path() / "input2.sol").string(),
@ -267,11 +267,13 @@ BOOST_AUTO_TEST_CASE(cli_not_a_file)
{
TemporaryDirectory tempDir(TEST_CASE_NAME);
string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file.\n";
string expectedMessage = "\"" + tempDir.path().string() + "\" is not a valid file.";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()});
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
BOOST_AUTO_TEST_CASE(standard_json_base_path)
@ -336,24 +338,26 @@ 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";
"Please either specify a single file name or provide its content on standard input.";
vector<string> commandLine = {"solc", "--standard-json", "input1.json", "input2.json"};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "input2.json"}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
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";
"Please either specify a single file name or provide its content on standard input.";
vector<string> commandLine = {"solc", "--standard-json", "input1.json", "-"};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "-"}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
BOOST_AUTO_TEST_CASE(standard_json_ignore_missing)
@ -362,29 +366,31 @@ BOOST_AUTO_TEST_CASE(standard_json_ignore_missing)
// 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";
"All specified input files either do not exist or are not regular files.";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({
"solc",
"--standard-json",
(tempDir.path() / "input.json").string(),
"--ignore-missing",
});
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc",
"--standard-json",
(tempDir.path() / "input.json").string(),
"--ignore-missing",
}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
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";
"Please put them under 'settings.remappings' in the JSON input.";
vector<string> commandLine = {"solc", "--standard-json", "a=b"};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({"solc", "--standard-json", "a=b"}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path)
@ -997,11 +1003,7 @@ BOOST_AUTO_TEST_CASE(cli_include_paths)
canonicalWorkDir / "lib",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(
commandLine,
"",
true /* _processInput */
);
OptionsReaderAndMessages result = runCLI(commandLine, "");
BOOST_TEST(result.stderrContent == "");
BOOST_TEST(result.stdoutContent == "");
@ -1087,11 +1089,7 @@ BOOST_AUTO_TEST_CASE(standard_json_include_paths)
FileReader::FileSystemPathSet expectedAllowedDirectories = {};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(
commandLine,
standardJsonInput,
true /* _processInput */
);
OptionsReaderAndMessages result = runCLI(commandLine, standardJsonInput);
Json::Value parsedStdout;
string jsonParsingErrors;
@ -1119,18 +1117,19 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_empty_path)
TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "base/main.sol"});
string expectedMessage = "Empty values are not allowed in --include-path.\n";
string expectedMessage = "Empty values are not allowed in --include-path.";
vector<string> commandLine = {
"solc",
"--base-path=base/",
"--include-path", "include/",
"--include-path", "",
"base/main.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc",
"--base-path=base/",
"--include-path", "include/",
"--include-path", "",
"base/main.sol",
}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path)
@ -1139,12 +1138,13 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path)
TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "contract.sol"});
string expectedMessage = "--include-path option requires a non-empty base path.\n";
string expectedMessage = "--include-path option requires a non-empty base path.";
vector<string> commandLine = {"solc", "--include-path", "include/", "contract.sol"};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(!result.success);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({"solc", "--include-path", "include/", "contract.sol"}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions)
@ -1173,35 +1173,37 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions
{
// import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = {
"solc",
"--base-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir2/contract1.sol",
"dir1/contract2.sol",
"dir2/contract2.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc",
"--base-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir2/contract1.sol",
"dir1/contract2.sol",
"dir2/contract2.sol",
}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
{
// import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = {
"solc",
"--base-path=dir3/",
"--include-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir2/contract1.sol",
"dir1/contract2.sol",
"dir2/contract2.sol",
};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success);
BOOST_CHECK_EXCEPTION(
parseCommandLineAndReadInputFiles({
"solc",
"--base-path=dir3/",
"--include-path=dir1/",
"--include-path=dir2/",
"dir1/contract1.sol",
"dir2/contract1.sol",
"dir1/contract2.sol",
"dir2/contract2.sol",
}),
CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
}
{
@ -1316,12 +1318,7 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_ambiguous_import)
"3 | import \"contract.sol\";\n"
" | ^^^^^^^^^^^^^^^^^^^^^^\n\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(
commandLine,
mainContractSource,
true /* _processInput */
);
OptionsReaderAndMessages result = runCLI(commandLine, mainContractSource);
BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success);
}

View File

@ -95,11 +95,7 @@ ImportCheck checkImport(
"pragma solidity >=0.0;\n" +
_import + ";";
test::OptionsReaderAndMessages cliResult = test::parseCommandLineAndReadInputFiles(
commandLine,
standardInputContent,
true /* processInput */
);
test::OptionsReaderAndMessages cliResult = test::runCLI(commandLine, standardInputContent);
if (cliResult.success)
return ImportCheck::OK();

View File

@ -19,6 +19,7 @@
/// Unit tests for solc/CommandLineParser.h
#include <solc/CommandLineParser.h>
#include <solc/Exceptions.h>
#include <test/solc/Common.h>
@ -46,17 +47,13 @@ using namespace solidity::yul;
namespace
{
optional<CommandLineOptions> parseCommandLine(vector<string> const& _commandLine, ostream& _stderr)
CommandLineOptions parseCommandLine(vector<string> const& _commandLine)
{
vector<char const*> argv = test::makeArgv(_commandLine);
CommandLineParser cliParser(_stderr);
bool success = cliParser.parse(static_cast<int>(_commandLine.size()), argv.data());
if (!success)
return nullopt;
else
return cliParser.options();
CommandLineParser cliParser;
cliParser.parse(static_cast<int>(_commandLine.size()), argv.data());
return cliParser.options();
}
} // namespace
@ -75,12 +72,9 @@ BOOST_AUTO_TEST_CASE(no_options)
expectedOptions.modelChecker.initialize = true;
expectedOptions.modelChecker.settings = {};
stringstream serr;
optional<CommandLineOptions> parsedOptions = parseCommandLine(commandLine, serr);
CommandLineOptions parsedOptions = parseCommandLine(commandLine);
BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value());
BOOST_TEST(parsedOptions.value() == expectedOptions);
BOOST_TEST(parsedOptions == expectedOptions);
}
BOOST_AUTO_TEST_CASE(help_license_version)
@ -93,15 +87,12 @@ BOOST_AUTO_TEST_CASE(help_license_version)
for (auto const& [option, expectedMode]: expectedModePerOption)
{
stringstream serr;
optional<CommandLineOptions> parsedOptions = parseCommandLine({"solc", option}, serr);
CommandLineOptions parsedOptions = parseCommandLine({"solc", option});
CommandLineOptions expectedOptions;
expectedOptions.input.mode = expectedMode;
BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value());
BOOST_TEST(parsedOptions.value() == expectedOptions);
BOOST_TEST(parsedOptions == expectedOptions);
}
}
@ -226,12 +217,9 @@ BOOST_AUTO_TEST_CASE(cli_mode_options)
5,
};
stringstream serr;
optional<CommandLineOptions> parsedOptions = parseCommandLine(commandLine, serr);
CommandLineOptions parsedOptions = parseCommandLine(commandLine);
BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value());
BOOST_TEST(parsedOptions.value() == expectedOptions);
BOOST_TEST(parsedOptions == expectedOptions);
}
}
@ -352,12 +340,9 @@ BOOST_AUTO_TEST_CASE(assembly_mode_options)
expectedOptions.optimizer.expectedExecutionsPerDeployment = 1000;
}
stringstream serr;
optional<CommandLineOptions> parsedOptions = parseCommandLine(commandLine, serr);
CommandLineOptions parsedOptions = parseCommandLine(commandLine);
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 == expectedOptions);
}
}
@ -420,12 +405,9 @@ BOOST_AUTO_TEST_CASE(standard_json_mode_options)
expectedOptions.compiler.combinedJsonRequests->abi = true;
expectedOptions.compiler.combinedJsonRequests->binary = true;
stringstream serr;
optional<CommandLineOptions> parsedOptions = parseCommandLine(commandLine, serr);
CommandLineOptions parsedOptions = parseCommandLine(commandLine);
BOOST_TEST(serr.str() == "");
BOOST_REQUIRE(parsedOptions.has_value());
BOOST_TEST(parsedOptions.value() == expectedOptions);
BOOST_TEST(parsedOptions == expectedOptions);
}
BOOST_AUTO_TEST_CASE(invalid_options_input_modes_combinations)
@ -441,10 +423,11 @@ BOOST_AUTO_TEST_CASE(invalid_options_input_modes_combinations)
{
stringstream serr;
vector<string> commandLine = {"solc", optionName, "file", inputMode};
optional<CommandLineOptions> parsedOptions = parseCommandLine(commandLine, serr);
BOOST_TEST(serr.str() == "The following options are not supported in the current input mode: " + optionName + "\n");
BOOST_REQUIRE(!parsedOptions.has_value());
string expectedMessage = "The following options are not supported in the current input mode: " + optionName;
auto hasCorrectMessage = [&](CommandLineValidationError const& _exception) { return _exception.what() == expectedMessage; };
BOOST_CHECK_EXCEPTION(parseCommandLine(commandLine), CommandLineValidationError, hasCorrectMessage);
}
}

View File

@ -41,18 +41,34 @@ vector<char const*> test::makeArgv(vector<string> const& _commandLine)
test::OptionsReaderAndMessages test::parseCommandLineAndReadInputFiles(
vector<string> const& _commandLine,
string const& _standardInputContent,
bool _processInput
string const& _standardInputContent
)
{
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();
cli.readInputFiles();
return {
success,
cli.options(),
cli.fileReader(),
cli.standardJsonInput(),
sout.str(),
stripPreReleaseWarning(serr.str()),
};
}
test::OptionsReaderAndMessages test::runCLI(
vector<string> const& _commandLine,
string const& _standardInputContent
)
{
vector<char const*> argv = makeArgv(_commandLine);
stringstream sin(_standardInputContent), sout, serr;
CommandLineInterface cli(sin, sout, serr);
bool success = cli.run(static_cast<int>(_commandLine.size()), argv.data());
return {
success,

View File

@ -44,10 +44,26 @@ struct OptionsReaderAndMessages
std::vector<char const*> makeArgv(std::vector<std::string> const& _commandLine);
/// Runs only command-line parsing, without compilation, assembling or any other input processing.
/// Lets through any @a CommandLineErrors throw by the CLI.
/// Note: This uses the @a CommandLineInterface class and does not actually spawn a new process.
/// @param _commandLine Arguments in the form of strings that would be specified on the command-line.
/// You must specify the program name as the first item.
/// @param _standardInputContent Content that the CLI will be able to read from its standard input.
OptionsReaderAndMessages parseCommandLineAndReadInputFiles(
std::vector<std::string> const& _commandLine,
std::string const& _standardInputContent = "",
bool _processInput = false
std::string const& _standardInputContent = ""
);
/// Runs all stages of command-line interface processing, including error handling.
/// Never throws @a CommandLineError - validation errors are included in the returned stderr content.
/// Note: This uses the @a CommandLineInterface class and does not actually spawn a new process.
/// @param _commandLine Arguments in the form of strings that would be specified on the command-line.
/// You must specify the program name as the first item.
/// @param _standardInputContent Content that the CLI will be able to read from its standard input.
OptionsReaderAndMessages runCLI(
std::vector<std::string> const& _commandLine,
std::string const& _standardInputContent = ""
);
std::string stripPreReleaseWarning(std::string const& _stderrContent);