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__) \ ::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; } } #define DEV_SIMPLE_EXCEPTION(X) struct X: virtual ::solidity::util::Exception { const char* what() const noexcept override { return #X; } }
DEV_SIMPLE_EXCEPTION(InvalidAddress); DEV_SIMPLE_EXCEPTION(InvalidAddress);

View File

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

View File

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

View File

@ -51,12 +51,28 @@ public:
m_options(_options) 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); bool parseArguments(int _argc, char const* const* _argv);
/// Read the content of all input files and initialize the file reader.
bool readInputFiles(); /// Reads the content of all input files and initializes the file reader.
/// Parse the files, create source code objects, print the output. /// @throws CommandLineValidationError if it fails to read the input files (invalid paths,
bool processInput(); /// 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; } CommandLineOptions const& options() const { return m_options; }
FileReader const& fileReader() const { return m_fileReader; } FileReader const& fileReader() const { return m_fileReader; }
@ -65,15 +81,15 @@ public:
private: private:
void printVersion(); void printVersion();
void printLicense(); void printLicense();
bool compile(); void compile();
bool link(); void link();
void writeLinkedFiles(); void writeLinkedFiles();
/// @returns the ``// <identifier> -> name`` hint for library placeholders. /// @returns the ``// <identifier> -> name`` hint for library placeholders.
static std::string libraryPlaceholderHint(std::string const& _libraryName); static std::string libraryPlaceholderHint(std::string const& _libraryName);
/// @returns the full object with library placeholder hints in hex. /// @returns the full object with library placeholder hints in hex.
static std::string objectWithLinkRefsHex(evmasm::LinkerObject const& _obj); 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(); void outputCompilationResults();
@ -120,7 +136,6 @@ private:
std::ostream& m_sout; std::ostream& m_sout;
std::ostream& m_serr; std::ostream& m_serr;
bool m_hasOutput = false; bool m_hasOutput = false;
bool m_outputFailed = false; ///< If true, creation or write to some of the output files failed.
FileReader m_fileReader; FileReader m_fileReader;
std::optional<std::string> m_standardJsonInput; std::optional<std::string> m_standardJsonInput;
std::unique_ptr<frontend::CompilerStack> m_compiler; std::unique_ptr<frontend::CompilerStack> m_compiler;

View File

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

View File

@ -19,6 +19,7 @@
/// Unit tests for solc/CommandLineInterface.h /// Unit tests for solc/CommandLineInterface.h
#include <solc/CommandLineInterface.h> #include <solc/CommandLineInterface.h>
#include <solc/Exceptions.h>
#include <test/solc/Common.h> #include <test/solc/Common.h>
@ -114,7 +115,7 @@ BOOST_AUTO_TEST_SUITE(CommandLineInterfaceTest)
BOOST_AUTO_TEST_CASE(help) BOOST_AUTO_TEST_CASE(help)
{ {
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--help"}, "", /* _processInput */ true); OptionsReaderAndMessages result = runCLI({"solc", "--help"}, "");
BOOST_TEST(result.success); BOOST_TEST(result.success);
BOOST_TEST(boost::starts_with(result.stdoutContent, "solc, the Solidity commandline compiler.")); 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) BOOST_AUTO_TEST_CASE(license)
{ {
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--license"}, "", /* _processInput */ true); OptionsReaderAndMessages result = runCLI({"solc", "--license"}, "");
BOOST_TEST(result.success); BOOST_TEST(result.success);
BOOST_TEST(boost::starts_with(result.stdoutContent, "Most of the code is licensed under GPLv3")); 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) BOOST_AUTO_TEST_CASE(version)
{ {
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({"solc", "--version"}, "", /* _processInput */ true); OptionsReaderAndMessages result = runCLI({"solc", "--version"}, "");
BOOST_TEST(result.success); BOOST_TEST(result.success);
BOOST_TEST(boost::ends_with(result.stdoutContent, "Version: " + solidity::frontend::VersionString + "\n")); 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 = string expectedMessage =
"The following options are mutually exclusive: " "The following options are mutually exclusive: "
"--help, --license, --version, --standard-json, --link, --assemble, --strict-assembly, --yul, --import-ast. " "--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& mode1: inputModeOptions)
for (string const& mode2: inputModeOptions) for (string const& mode2: inputModeOptions)
if (mode1 != mode2) if (mode1 != mode2)
{ BOOST_CHECK_EXCEPTION(
vector<string> commandLine = {"solc", mode1, mode2}; parseCommandLineAndReadInputFiles({"solc", mode1, mode2}),
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); CommandLineValidationError,
BOOST_TEST(!result.success); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
BOOST_TEST(result.stderrContent == expectedMessage); );
}
} }
BOOST_AUTO_TEST_CASE(cli_input) 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" "\"" + (tempDir.path() / "input2.sol").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.\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ OptionsReaderAndMessages result = runCLI({
"solc", "solc",
(tempDir.path() / "input1.sol").string(), (tempDir.path() / "input1.sol").string(),
(tempDir.path() / "input2.sol").string(), (tempDir.path() / "input2.sol").string(),
@ -267,11 +267,13 @@ BOOST_AUTO_TEST_CASE(cli_not_a_file)
{ {
TemporaryDirectory tempDir(TEST_CASE_NAME); 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_CHECK_EXCEPTION(
BOOST_TEST(!result.success); parseCommandLineAndReadInputFiles({"solc", tempDir.path().string()}),
BOOST_TEST(result.stderrContent == expectedMessage); CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_base_path) BOOST_AUTO_TEST_CASE(standard_json_base_path)
@ -336,24 +338,26 @@ BOOST_AUTO_TEST_CASE(standard_json_two_input_files)
{ {
string expectedMessage = string expectedMessage =
"Too many input files for --standard-json.\n" "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"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "input2.json"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_one_input_file_and_stdin) BOOST_AUTO_TEST_CASE(standard_json_one_input_file_and_stdin)
{ {
string expectedMessage = string expectedMessage =
"Too many input files for --standard-json.\n" "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", "-"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--standard-json", "input1.json", "-"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_ignore_missing) 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. // This option is pretty much useless Standard JSON mode.
string expectedMessage = 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.";
"All specified input files either do not exist or are not regular files.\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles({ BOOST_CHECK_EXCEPTION(
"solc", parseCommandLineAndReadInputFiles({
"--standard-json", "solc",
(tempDir.path() / "input.json").string(), "--standard-json",
"--ignore-missing", (tempDir.path() / "input.json").string(),
}); "--ignore-missing",
BOOST_TEST(!result.success); }),
BOOST_TEST(result.stderrContent == expectedMessage); CommandLineValidationError,
[&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(standard_json_remapping) BOOST_AUTO_TEST_CASE(standard_json_remapping)
{ {
string expectedMessage = string expectedMessage =
"Import remappings are not accepted on the command line in Standard JSON mode.\n" "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"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--standard-json", "a=b"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(cli_paths_to_source_unit_names_no_base_path) 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", canonicalWorkDir / "lib",
}; };
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( OptionsReaderAndMessages result = runCLI(commandLine, "");
commandLine,
"",
true /* _processInput */
);
BOOST_TEST(result.stderrContent == ""); BOOST_TEST(result.stderrContent == "");
BOOST_TEST(result.stdoutContent == ""); BOOST_TEST(result.stdoutContent == "");
@ -1087,11 +1089,7 @@ BOOST_AUTO_TEST_CASE(standard_json_include_paths)
FileReader::FileSystemPathSet expectedAllowedDirectories = {}; FileReader::FileSystemPathSet expectedAllowedDirectories = {};
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( OptionsReaderAndMessages result = runCLI(commandLine, standardJsonInput);
commandLine,
standardJsonInput,
true /* _processInput */
);
Json::Value parsedStdout; Json::Value parsedStdout;
string jsonParsingErrors; string jsonParsingErrors;
@ -1119,18 +1117,19 @@ BOOST_AUTO_TEST_CASE(cli_include_paths_empty_path)
TemporaryWorkingDirectory tempWorkDir(tempDir); TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "base/main.sol"}); 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 = { BOOST_CHECK_EXCEPTION(
"solc", parseCommandLineAndReadInputFiles({
"--base-path=base/", "solc",
"--include-path", "include/", "--base-path=base/",
"--include-path", "", "--include-path", "include/",
"base/main.sol", "--include-path", "",
}; "base/main.sol",
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); }),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(cli_include_paths_without_base_path) 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); TemporaryWorkingDirectory tempWorkDir(tempDir);
createFilesWithParentDirs({tempDir.path() / "contract.sol"}); 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"}; BOOST_CHECK_EXCEPTION(
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); parseCommandLineAndReadInputFiles({"solc", "--include-path", "include/", "contract.sol"}),
BOOST_TEST(!result.success); CommandLineValidationError,
BOOST_TEST(result.stderrContent == expectedMessage); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
BOOST_AUTO_TEST_CASE(cli_include_paths_should_detect_source_unit_name_collisions) 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: // import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = { BOOST_CHECK_EXCEPTION(
"solc", parseCommandLineAndReadInputFiles({
"--base-path=dir1/", "solc",
"--include-path=dir2/", "--base-path=dir1/",
"dir1/contract1.sol", "--include-path=dir2/",
"dir2/contract1.sol", "dir1/contract1.sol",
"dir1/contract2.sol", "dir2/contract1.sol",
"dir2/contract2.sol", "dir1/contract2.sol",
}; "dir2/contract2.sol",
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); }),
BOOST_TEST(result.stderrContent == expectedMessage); CommandLineValidationError,
BOOST_REQUIRE(!result.success); [&](auto const& _exception) { BOOST_TEST(_exception.what() == expectedMessage); return true; }
);
} }
{ {
// import "contract1.sol" and import "contract2.sol" would be ambiguous: // import "contract1.sol" and import "contract2.sol" would be ambiguous:
vector<string> commandLine = { BOOST_CHECK_EXCEPTION(
"solc", parseCommandLineAndReadInputFiles({
"--base-path=dir3/", "solc",
"--include-path=dir1/", "--base-path=dir3/",
"--include-path=dir2/", "--include-path=dir1/",
"dir1/contract1.sol", "--include-path=dir2/",
"dir2/contract1.sol", "dir1/contract1.sol",
"dir1/contract2.sol", "dir2/contract1.sol",
"dir2/contract2.sol", "dir1/contract2.sol",
}; "dir2/contract2.sol",
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles(commandLine); }),
BOOST_TEST(result.stderrContent == expectedMessage); CommandLineValidationError,
BOOST_REQUIRE(!result.success); [&](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" "3 | import \"contract.sol\";\n"
" | ^^^^^^^^^^^^^^^^^^^^^^\n\n"; " | ^^^^^^^^^^^^^^^^^^^^^^\n\n";
OptionsReaderAndMessages result = parseCommandLineAndReadInputFiles( OptionsReaderAndMessages result = runCLI(commandLine, mainContractSource);
commandLine,
mainContractSource,
true /* _processInput */
);
BOOST_TEST(result.stderrContent == expectedMessage); BOOST_TEST(result.stderrContent == expectedMessage);
BOOST_REQUIRE(!result.success); BOOST_REQUIRE(!result.success);
} }

View File

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

View File

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

View File

@ -44,10 +44,26 @@ struct OptionsReaderAndMessages
std::vector<char const*> makeArgv(std::vector<std::string> const& _commandLine); 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( OptionsReaderAndMessages parseCommandLineAndReadInputFiles(
std::vector<std::string> const& _commandLine, std::vector<std::string> const& _commandLine,
std::string const& _standardInputContent = "", std::string const& _standardInputContent = ""
bool _processInput = false );
/// 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); std::string stripPreReleaseWarning(std::string const& _stderrContent);