mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
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:
commit
9e9225eb60
@ -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);
|
||||
|
@ -1,6 +1,7 @@
|
||||
set(libsolcli_sources
|
||||
CommandLineInterface.cpp CommandLineInterface.h
|
||||
CommandLineParser.cpp CommandLineParser.h
|
||||
Exceptions.h
|
||||
)
|
||||
|
||||
add_library(solcli ${libsolcli_sources})
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
34
solc/Exceptions.h
Normal 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 {};
|
||||
|
||||
}
|
@ -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)
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user