mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
1397 lines
52 KiB
C++
1397 lines
52 KiB
C++
/*
|
|
This file is part of solidity.
|
|
|
|
solidity is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
solidity is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with solidity. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
// SPDX-License-Identifier: GPL-3.0
|
|
|
|
#include <solc/CommandLineParser.h>
|
|
|
|
#include <solc/Exceptions.h>
|
|
|
|
#include <libyul/optimiser/Suite.h>
|
|
|
|
#include <liblangutil/EVMVersion.h>
|
|
|
|
#include <boost/algorithm/string.hpp>
|
|
|
|
#include <range/v3/view/transform.hpp>
|
|
#include <range/v3/view/filter.hpp>
|
|
#include <range/v3/range/conversion.hpp>
|
|
|
|
using namespace std;
|
|
using namespace solidity::langutil;
|
|
|
|
namespace po = boost::program_options;
|
|
|
|
namespace solidity::frontend
|
|
{
|
|
|
|
static string const g_strAllowPaths = "allow-paths";
|
|
static string const g_strBasePath = "base-path";
|
|
static string const g_strIncludePath = "include-path";
|
|
static string const g_strAssemble = "assemble";
|
|
static string const g_strCombinedJson = "combined-json";
|
|
static string const g_strErrorRecovery = "error-recovery";
|
|
static string const g_strEVM = "evm";
|
|
static string const g_strEVMVersion = "evm-version";
|
|
static string const g_strEOFVersion = "experimental-eof-version";
|
|
static string const g_strViaIR = "via-ir";
|
|
static string const g_strExperimentalViaIR = "experimental-via-ir";
|
|
static string const g_strGas = "gas";
|
|
static string const g_strHelp = "help";
|
|
static string const g_strImportAst = "import-ast";
|
|
static string const g_strInputFile = "input-file";
|
|
static string const g_strYul = "yul";
|
|
static string const g_strYulDialect = "yul-dialect";
|
|
static string const g_strDebugInfo = "debug-info";
|
|
static string const g_strIPFS = "ipfs";
|
|
static string const g_strLicense = "license";
|
|
static string const g_strLibraries = "libraries";
|
|
static string const g_strLink = "link";
|
|
static string const g_strLSP = "lsp";
|
|
static string const g_strMachine = "machine";
|
|
static string const g_strNoCBORMetadata = "no-cbor-metadata";
|
|
static string const g_strMetadataHash = "metadata-hash";
|
|
static string const g_strMetadataLiteral = "metadata-literal";
|
|
static string const g_strModelCheckerContracts = "model-checker-contracts";
|
|
static string const g_strModelCheckerDivModNoSlacks = "model-checker-div-mod-no-slacks";
|
|
static string const g_strModelCheckerEngine = "model-checker-engine";
|
|
static string const g_strModelCheckerExtCalls = "model-checker-ext-calls";
|
|
static string const g_strModelCheckerInvariants = "model-checker-invariants";
|
|
static string const g_strModelCheckerShowProvedSafe = "model-checker-show-proved-safe";
|
|
static string const g_strModelCheckerShowUnproved = "model-checker-show-unproved";
|
|
static string const g_strModelCheckerShowUnsupported = "model-checker-show-unsupported";
|
|
static string const g_strModelCheckerSolvers = "model-checker-solvers";
|
|
static string const g_strModelCheckerTargets = "model-checker-targets";
|
|
static string const g_strModelCheckerTimeout = "model-checker-timeout";
|
|
static string const g_strModelCheckerBMCLoopIterations = "model-checker-bmc-loop-iterations";
|
|
static string const g_strNone = "none";
|
|
static string const g_strNoOptimizeYul = "no-optimize-yul";
|
|
static string const g_strOptimize = "optimize";
|
|
static string const g_strOptimizeRuns = "optimize-runs";
|
|
static string const g_strOptimizeYul = "optimize-yul";
|
|
static string const g_strYulOptimizations = "yul-optimizations";
|
|
static string const g_strOutputDir = "output-dir";
|
|
static string const g_strOverwrite = "overwrite";
|
|
static string const g_strRevertStrings = "revert-strings";
|
|
static string const g_strStopAfter = "stop-after";
|
|
static string const g_strParsing = "parsing";
|
|
|
|
/// Possible arguments to for --revert-strings
|
|
static set<string> const g_revertStringsArgs
|
|
{
|
|
revertStringsToString(RevertStrings::Default),
|
|
revertStringsToString(RevertStrings::Strip),
|
|
revertStringsToString(RevertStrings::Debug),
|
|
revertStringsToString(RevertStrings::VerboseDebug)
|
|
};
|
|
|
|
static string const g_strSources = "sources";
|
|
static string const g_strSourceList = "sourceList";
|
|
static string const g_strStandardJSON = "standard-json";
|
|
static string const g_strStrictAssembly = "strict-assembly";
|
|
static string const g_strSwarm = "swarm";
|
|
static string const g_strPrettyJson = "pretty-json";
|
|
static string const g_strJsonIndent = "json-indent";
|
|
static string const g_strVersion = "version";
|
|
static string const g_strIgnoreMissingFiles = "ignore-missing";
|
|
static string const g_strColor = "color";
|
|
static string const g_strNoColor = "no-color";
|
|
static string const g_strErrorIds = "error-codes";
|
|
|
|
/// Possible arguments to for --machine
|
|
static set<string> const g_machineArgs
|
|
{
|
|
g_strEVM
|
|
};
|
|
|
|
/// Possible arguments to for --yul-dialect
|
|
static set<string> const g_yulDialectArgs
|
|
{
|
|
g_strEVM
|
|
};
|
|
|
|
/// Possible arguments to for --metadata-hash
|
|
static set<string> const g_metadataHashArgs
|
|
{
|
|
g_strIPFS,
|
|
g_strSwarm,
|
|
g_strNone
|
|
};
|
|
|
|
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"},
|
|
{InputMode::StandardJson, "standard JSON"},
|
|
{InputMode::Linker, "linker"},
|
|
{InputMode::LanguageServer, "language server (LSP)"},
|
|
};
|
|
|
|
void CommandLineParser::checkMutuallyExclusive(vector<string> const& _optionNames)
|
|
{
|
|
if (countEnabledOptions(_optionNames) > 1)
|
|
{
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"The following options are mutually exclusive: " + joinOptionNames(_optionNames) + ". " +
|
|
"Select at most one."
|
|
);
|
|
}
|
|
}
|
|
|
|
bool CompilerOutputs::operator==(CompilerOutputs const& _other) const noexcept
|
|
{
|
|
for (bool CompilerOutputs::* member: componentMap() | ranges::views::values)
|
|
if (this->*member != _other.*member)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
ostream& operator<<(ostream& _out, CompilerOutputs const& _selection)
|
|
{
|
|
vector<string> serializedSelection;
|
|
for (auto&& [componentName, component]: CompilerOutputs::componentMap())
|
|
if (_selection.*component)
|
|
serializedSelection.push_back(CompilerOutputs::componentName(component));
|
|
|
|
return _out << util::joinHumanReadable(serializedSelection, ",");
|
|
}
|
|
|
|
string const& CompilerOutputs::componentName(bool CompilerOutputs::* _component)
|
|
{
|
|
solAssert(_component, "");
|
|
|
|
// NOTE: Linear search is not optimal but it's simpler than getting pointers-to-members to work as map keys.
|
|
for (auto const& [componentName, component]: CompilerOutputs::componentMap())
|
|
if (component == _component)
|
|
return componentName;
|
|
|
|
solAssert(false, "");
|
|
}
|
|
|
|
bool CombinedJsonRequests::operator==(CombinedJsonRequests const& _other) const noexcept
|
|
{
|
|
for (bool CombinedJsonRequests::* member: componentMap() | ranges::views::values)
|
|
if (this->*member != _other.*member)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
|
|
ostream& operator<<(ostream& _out, CombinedJsonRequests const& _requests)
|
|
{
|
|
vector<string> serializedRequests;
|
|
for (auto&& [componentName, component]: CombinedJsonRequests::componentMap())
|
|
if (_requests.*component)
|
|
serializedRequests.push_back(CombinedJsonRequests::componentName(component));
|
|
|
|
return _out << util::joinHumanReadable(serializedRequests, ",");
|
|
}
|
|
|
|
string const& CombinedJsonRequests::componentName(bool CombinedJsonRequests::* _component)
|
|
{
|
|
solAssert(_component, "");
|
|
|
|
for (auto const& [componentName, component]: CombinedJsonRequests::componentMap())
|
|
if (component == _component)
|
|
return componentName;
|
|
|
|
solAssert(false, "");
|
|
}
|
|
|
|
bool CommandLineOptions::operator==(CommandLineOptions const& _other) const noexcept
|
|
{
|
|
return
|
|
input.paths == _other.input.paths &&
|
|
input.remappings == _other.input.remappings &&
|
|
input.addStdin == _other.input.addStdin &&
|
|
input.basePath == _other.input.basePath &&
|
|
input.includePaths == _other.input.includePaths &&
|
|
input.allowedDirectories == _other.input.allowedDirectories &&
|
|
input.ignoreMissingFiles == _other.input.ignoreMissingFiles &&
|
|
input.errorRecovery == _other.input.errorRecovery &&
|
|
output.dir == _other.output.dir &&
|
|
output.overwriteFiles == _other.output.overwriteFiles &&
|
|
output.evmVersion == _other.output.evmVersion &&
|
|
output.viaIR == _other.output.viaIR &&
|
|
output.revertStrings == _other.output.revertStrings &&
|
|
output.debugInfoSelection == _other.output.debugInfoSelection &&
|
|
output.stopAfter == _other.output.stopAfter &&
|
|
output.eofVersion == _other.output.eofVersion &&
|
|
input.mode == _other.input.mode &&
|
|
assembly.targetMachine == _other.assembly.targetMachine &&
|
|
assembly.inputLanguage == _other.assembly.inputLanguage &&
|
|
linker.libraries == _other.linker.libraries &&
|
|
formatting.json == _other.formatting.json &&
|
|
formatting.coloredOutput == _other.formatting.coloredOutput &&
|
|
formatting.withErrorIds == _other.formatting.withErrorIds &&
|
|
compiler.outputs == _other.compiler.outputs &&
|
|
compiler.estimateGas == _other.compiler.estimateGas &&
|
|
compiler.combinedJsonRequests == _other.compiler.combinedJsonRequests &&
|
|
metadata.format == _other.metadata.format &&
|
|
metadata.hash == _other.metadata.hash &&
|
|
metadata.literalSources == _other.metadata.literalSources &&
|
|
optimizer.optimizeEvmasm == _other.optimizer.optimizeEvmasm &&
|
|
optimizer.optimizeYul == _other.optimizer.optimizeYul &&
|
|
optimizer.expectedExecutionsPerDeployment == _other.optimizer.expectedExecutionsPerDeployment &&
|
|
optimizer.yulSteps == _other.optimizer.yulSteps &&
|
|
modelChecker.initialize == _other.modelChecker.initialize &&
|
|
modelChecker.settings == _other.modelChecker.settings;
|
|
}
|
|
|
|
OptimiserSettings CommandLineOptions::optimiserSettings() const
|
|
{
|
|
OptimiserSettings settings;
|
|
|
|
if (optimizer.optimizeEvmasm)
|
|
settings = OptimiserSettings::standard();
|
|
else
|
|
settings = OptimiserSettings::minimal();
|
|
|
|
settings.runYulOptimiser = optimizer.optimizeYul;
|
|
if (optimizer.optimizeYul)
|
|
// NOTE: Standard JSON disables optimizeStackAllocation by default when yul optimizer is disabled.
|
|
// --optimize --no-optimize-yul on the CLI does not have that effect.
|
|
settings.optimizeStackAllocation = true;
|
|
|
|
if (optimizer.expectedExecutionsPerDeployment.has_value())
|
|
settings.expectedExecutionsPerDeployment = optimizer.expectedExecutionsPerDeployment.value();
|
|
|
|
if (optimizer.yulSteps.has_value())
|
|
{
|
|
string const fullSequence = optimizer.yulSteps.value();
|
|
auto const delimiterPos = fullSequence.find(":");
|
|
settings.yulOptimiserSteps = fullSequence.substr(0, delimiterPos);
|
|
|
|
if (delimiterPos != string::npos)
|
|
settings.yulOptimiserCleanupSteps = fullSequence.substr(delimiterPos + 1);
|
|
else
|
|
solAssert(settings.yulOptimiserCleanupSteps == OptimiserSettings::DefaultYulOptimiserCleanupSteps);
|
|
}
|
|
|
|
return settings;
|
|
}
|
|
|
|
void CommandLineParser::parse(int _argc, char const* const* _argv)
|
|
{
|
|
parseArgs(_argc, _argv);
|
|
processArgs();
|
|
}
|
|
|
|
void CommandLineParser::parseInputPathsAndRemappings()
|
|
{
|
|
m_options.input.ignoreMissingFiles = (m_args.count(g_strIgnoreMissingFiles) > 0);
|
|
|
|
if (m_args.count(g_strInputFile))
|
|
for (string const& positionalArg: m_args[g_strInputFile].as<vector<string>>())
|
|
{
|
|
if (ImportRemapper::isRemapping(positionalArg))
|
|
{
|
|
optional<ImportRemapper::Remapping> remapping = ImportRemapper::parseRemapping(positionalArg);
|
|
if (!remapping.has_value())
|
|
solThrow(CommandLineValidationError, "Invalid remapping: \"" + positionalArg + "\".");
|
|
|
|
if (m_options.input.mode == InputMode::StandardJson)
|
|
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())
|
|
{
|
|
// If the target is a directory, whitelist it. Otherwise whitelist containing dir.
|
|
// NOTE: /a/b/c/ is a directory while /a/b/c is not.
|
|
boost::filesystem::path remappingDir = remapping->target;
|
|
if (remappingDir.filename() != "..")
|
|
// As an exception we'll treat /a/b/c/.. as a directory too. It would be
|
|
// unintuitive to whitelist /a/b/c when the target is equivalent to /a/b/.
|
|
remappingDir.remove_filename();
|
|
m_options.input.allowedDirectories.insert(remappingDir.empty() ? "." : remappingDir);
|
|
}
|
|
|
|
m_options.input.remappings.emplace_back(std::move(remapping.value()));
|
|
}
|
|
else if (positionalArg == "-")
|
|
m_options.input.addStdin = true;
|
|
else
|
|
m_options.input.paths.insert(positionalArg);
|
|
}
|
|
|
|
if (m_options.input.mode == InputMode::StandardJson)
|
|
{
|
|
if (m_options.input.paths.size() > 1 || (m_options.input.paths.size() == 1 && m_options.input.addStdin))
|
|
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)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"No input files given. If you wish to use the standard input please specify \"-\" explicitly."
|
|
);
|
|
}
|
|
|
|
void CommandLineParser::parseLibraryOption(string const& _input)
|
|
{
|
|
namespace fs = boost::filesystem;
|
|
string data = _input;
|
|
try
|
|
{
|
|
if (fs::is_regular_file(_input))
|
|
data = util::readFileAsString(_input);
|
|
}
|
|
catch (fs::filesystem_error const&)
|
|
{
|
|
// Thrown e.g. if path is too long.
|
|
}
|
|
catch (util::FileNotFound const&)
|
|
{
|
|
// Should not happen if `fs::is_regular_file` is correct.
|
|
}
|
|
catch (util::NotAFile const&)
|
|
{
|
|
// Should not happen if `fs::is_regular_file` is correct.
|
|
}
|
|
|
|
vector<string> libraries;
|
|
boost::split(libraries, data, boost::is_space() || boost::is_any_of(","), boost::token_compress_on);
|
|
for (string const& lib: libraries)
|
|
if (!lib.empty())
|
|
{
|
|
//search for equal sign or last colon in string as our binaries output placeholders in the form of file=Name or file:Name
|
|
//so we need to search for `=` or `:` in the string
|
|
auto separator = lib.rfind('=');
|
|
bool isSeparatorEqualSign = true;
|
|
if (separator == string::npos)
|
|
{
|
|
separator = lib.rfind(':');
|
|
if (separator == string::npos)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Equal sign separator missing in library address specifier \"" + lib + "\""
|
|
);
|
|
else
|
|
isSeparatorEqualSign = false; // separator is colon
|
|
}
|
|
else
|
|
if (lib.rfind('=') != lib.find('='))
|
|
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))
|
|
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())
|
|
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
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"The address " + addrString + " is not prefixed with \"0x\".\n"
|
|
"Note that the address must be prefixed with \"0x\"."
|
|
);
|
|
|
|
if (addrString.length() != 40)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Invalid length for address for library \"" + libName + "\": " +
|
|
to_string(addrString.length()) + " instead of 40 characters."
|
|
);
|
|
if (!util::passesAddressChecksum(addrString, false))
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Invalid checksum on address for library \"" + libName + "\": " + addrString + "\n"
|
|
"The correct checksum is " + util::getChecksummedAddress(addrString)
|
|
);
|
|
bytes binAddr = util::fromHex(addrString);
|
|
util::h160 address(binAddr, util::h160::AlignRight);
|
|
if (binAddr.size() > 20 || address == util::h160())
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Invalid address for library \"" + libName + "\": " + addrString
|
|
);
|
|
m_options.linker.libraries[libName] = address;
|
|
}
|
|
}
|
|
|
|
void CommandLineParser::parseOutputSelection()
|
|
{
|
|
static auto outputSupported = [](InputMode _mode, string_view _outputName)
|
|
{
|
|
static set<string> const compilerModeOutputs = (
|
|
CompilerOutputs::componentMap() |
|
|
ranges::views::keys |
|
|
ranges::to<set>()
|
|
);
|
|
static set<string> const assemblerModeOutputs = {
|
|
CompilerOutputs::componentName(&CompilerOutputs::asm_),
|
|
CompilerOutputs::componentName(&CompilerOutputs::binary),
|
|
CompilerOutputs::componentName(&CompilerOutputs::irOptimized),
|
|
CompilerOutputs::componentName(&CompilerOutputs::astCompactJson),
|
|
};
|
|
|
|
switch (_mode)
|
|
{
|
|
case InputMode::Help:
|
|
case InputMode::License:
|
|
case InputMode::Version:
|
|
case InputMode::LanguageServer:
|
|
solAssert(false);
|
|
case InputMode::Compiler:
|
|
case InputMode::CompilerWithASTImport:
|
|
return util::contains(compilerModeOutputs, _outputName);
|
|
case InputMode::Assembler:
|
|
return util::contains(assemblerModeOutputs, _outputName);
|
|
case InputMode::StandardJson:
|
|
case InputMode::Linker:
|
|
return false;
|
|
}
|
|
|
|
solAssert(false, "");
|
|
};
|
|
|
|
for (auto&& [optionName, outputComponent]: CompilerOutputs::componentMap())
|
|
m_options.compiler.outputs.*outputComponent = (m_args.count(optionName) > 0);
|
|
|
|
if (m_options.input.mode == InputMode::Assembler && m_options.compiler.outputs == CompilerOutputs{})
|
|
{
|
|
// In assembly mode keep the default outputs enabled for backwards-compatibility.
|
|
// TODO: Remove this (must be done in a breaking release).
|
|
m_options.compiler.outputs.asm_ = true;
|
|
m_options.compiler.outputs.binary = true;
|
|
m_options.compiler.outputs.irOptimized = true;
|
|
}
|
|
|
|
vector<string> unsupportedOutputs;
|
|
for (auto&& [optionName, outputComponent]: CompilerOutputs::componentMap())
|
|
if (m_options.compiler.outputs.*outputComponent && !outputSupported(m_options.input.mode, optionName))
|
|
unsupportedOutputs.push_back(optionName);
|
|
|
|
if (!unsupportedOutputs.empty())
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"The following outputs are not supported in " + g_inputModeName.at(m_options.input.mode) + " mode: " +
|
|
joinOptionNames(unsupportedOutputs) + "."
|
|
);
|
|
|
|
// TODO: restrict EOF version to correct EVM version.
|
|
}
|
|
|
|
po::options_description CommandLineParser::optionsDescription(bool _forHelp)
|
|
{
|
|
// Declare the supported options.
|
|
po::options_description desc((R"(solc, the Solidity commandline compiler.
|
|
|
|
This program comes with ABSOLUTELY NO WARRANTY. This is free software, and you
|
|
are welcome to redistribute it under certain conditions. See 'solc --)" + g_strLicense + R"('
|
|
for details.
|
|
|
|
Usage: solc [options] [input_file...]
|
|
Compiles the given Solidity input files (or the standard input if "-" is
|
|
used as a file name) and outputs the components specified in the options
|
|
at standard output or in files in the output directory, if specified.
|
|
Imports are automatically read from the filesystem, but it is also possible to
|
|
remap paths using the context:prefix=path syntax.
|
|
Example:
|
|
solc --)" + CompilerOutputs::componentName(&CompilerOutputs::binary) + R"( -o /tmp/solcoutput dapp-bin=/usr/local/lib/dapp-bin contract.sol
|
|
|
|
General Information)").c_str(),
|
|
po::options_description::m_default_line_length,
|
|
po::options_description::m_default_line_length - 23
|
|
);
|
|
desc.add_options()
|
|
(g_strHelp.c_str(), "Show help message and exit.")
|
|
(g_strVersion.c_str(), "Show version and exit.")
|
|
(g_strLicense.c_str(), "Show licensing information and exit.")
|
|
;
|
|
|
|
po::options_description inputOptions("Input Options");
|
|
inputOptions.add_options()
|
|
(
|
|
g_strBasePath.c_str(),
|
|
po::value<string>()->value_name("path"),
|
|
"Use the given path as the root of the source tree instead of the root of the filesystem."
|
|
)
|
|
(
|
|
g_strIncludePath.c_str(),
|
|
po::value<vector<string>>()->value_name("path"),
|
|
"Make an additional source directory available to the default import callback. "
|
|
"Use this option if you want to import contracts whose location is not fixed in relation "
|
|
"to your main source tree, e.g. third-party libraries installed using a package manager. "
|
|
"Can be used multiple times. "
|
|
"Can only be used if base path has a non-empty value."
|
|
)
|
|
(
|
|
g_strAllowPaths.c_str(),
|
|
po::value<string>()->value_name("path(s)"),
|
|
"Allow a given path for imports. A list of paths can be supplied by separating them with a comma."
|
|
)
|
|
(
|
|
g_strIgnoreMissingFiles.c_str(),
|
|
"Ignore missing files."
|
|
)
|
|
(
|
|
g_strErrorRecovery.c_str(),
|
|
"Enables additional parser error recovery."
|
|
)
|
|
;
|
|
desc.add(inputOptions);
|
|
|
|
po::options_description outputOptions("Output Options");
|
|
outputOptions.add_options()
|
|
(
|
|
(g_strOutputDir + ",o").c_str(),
|
|
po::value<string>()->value_name("path"),
|
|
"If given, creates one file per output component and contract/file at the specified directory."
|
|
)
|
|
(
|
|
g_strOverwrite.c_str(),
|
|
"Overwrite existing files (used together with -o)."
|
|
)
|
|
(
|
|
g_strEVMVersion.c_str(),
|
|
po::value<string>()->value_name("version")->default_value(EVMVersion{}.name()),
|
|
"Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, "
|
|
"byzantium, constantinople, petersburg, istanbul, berlin, london, paris or shanghai."
|
|
)
|
|
;
|
|
if (!_forHelp) // Note: We intentionally keep this undocumented for now.
|
|
outputOptions.add_options()
|
|
(
|
|
g_strEOFVersion.c_str(),
|
|
// Declared as uint64_t, since uint8_t will be parsed as character by boost.
|
|
po::value<uint64_t>()->value_name("version")->implicit_value(1),
|
|
"Select desired EOF version. Currently the only valid value is 1. "
|
|
"If not specified, legacy non-EOF bytecode will be generated."
|
|
)
|
|
;
|
|
outputOptions.add_options()
|
|
(
|
|
g_strExperimentalViaIR.c_str(),
|
|
"Deprecated synonym of --via-ir."
|
|
)
|
|
(
|
|
g_strViaIR.c_str(),
|
|
"Turn on compilation mode via the IR."
|
|
)
|
|
(
|
|
g_strRevertStrings.c_str(),
|
|
po::value<string>()->value_name(util::joinHumanReadable(g_revertStringsArgs, ",")),
|
|
"Strip revert (and require) reason strings or add additional debugging information."
|
|
)
|
|
(
|
|
g_strDebugInfo.c_str(),
|
|
po::value<string>()->default_value(util::toString(DebugInfoSelection::Default())),
|
|
("Debug info components to be included in the produced EVM assembly and Yul code. "
|
|
"Value can be all, none or a comma-separated list containing one or more of the "
|
|
"following components: " + util::joinHumanReadable(DebugInfoSelection::componentMap() | ranges::views::keys) + ".").c_str()
|
|
)
|
|
(
|
|
g_strStopAfter.c_str(),
|
|
po::value<string>()->value_name("stage"),
|
|
"Stop execution after the given compiler stage. Valid options: \"parsing\"."
|
|
)
|
|
;
|
|
desc.add(outputOptions);
|
|
|
|
po::options_description alternativeInputModes("Alternative Input Modes");
|
|
alternativeInputModes.add_options()
|
|
(
|
|
g_strStandardJSON.c_str(),
|
|
"Switch to Standard JSON input / output mode, ignoring all options. "
|
|
"It reads from standard input, if no input file was given, otherwise it reads from the provided input file. The result will be written to standard output."
|
|
)
|
|
(
|
|
g_strLink.c_str(),
|
|
("Switch to linker mode, ignoring all options apart from --" + g_strLibraries + " "
|
|
"and modify binaries in place.").c_str()
|
|
)
|
|
(
|
|
g_strAssemble.c_str(),
|
|
"Switch to assembly mode and assume input is assembly."
|
|
)
|
|
(
|
|
g_strYul.c_str(),
|
|
"Switch to Yul mode and assume input is Yul."
|
|
)
|
|
(
|
|
g_strStrictAssembly.c_str(),
|
|
"Switch to strict assembly mode and assume input is strict assembly."
|
|
)
|
|
(
|
|
g_strImportAst.c_str(),
|
|
("Import ASTs to be compiled, assumes input holds the AST in compact JSON format. "
|
|
"Supported Inputs is the output of the --" + g_strStandardJSON + " or the one produced by "
|
|
"--" + g_strCombinedJson + " " + CombinedJsonRequests::componentName(&CombinedJsonRequests::ast)).c_str()
|
|
)
|
|
(
|
|
g_strLSP.c_str(),
|
|
"Switch to language server mode (\"LSP\"). Allows the compiler to be used as an analysis backend "
|
|
"for your favourite IDE."
|
|
)
|
|
;
|
|
desc.add(alternativeInputModes);
|
|
|
|
po::options_description assemblyModeOptions("Assembly Mode Options");
|
|
assemblyModeOptions.add_options()
|
|
(
|
|
g_strMachine.c_str(),
|
|
po::value<string>()->value_name(util::joinHumanReadable(g_machineArgs, ",")),
|
|
"Target machine in assembly or Yul mode."
|
|
)
|
|
(
|
|
g_strYulDialect.c_str(),
|
|
po::value<string>()->value_name(util::joinHumanReadable(g_yulDialectArgs, ",")),
|
|
"Input dialect to use in assembly or yul mode."
|
|
)
|
|
;
|
|
desc.add(assemblyModeOptions);
|
|
|
|
po::options_description linkerModeOptions("Linker Mode Options");
|
|
linkerModeOptions.add_options()
|
|
(
|
|
g_strLibraries.c_str(),
|
|
po::value<vector<string>>()->value_name("libs"),
|
|
"Direct string or file containing library addresses. Syntax: "
|
|
"<libraryName>=<address> [, or whitespace] ...\n"
|
|
"Address is interpreted as a hex string prefixed by 0x."
|
|
)
|
|
;
|
|
desc.add(linkerModeOptions);
|
|
|
|
po::options_description outputFormatting("Output Formatting");
|
|
outputFormatting.add_options()
|
|
(
|
|
g_strPrettyJson.c_str(),
|
|
"Output JSON in pretty format."
|
|
)
|
|
(
|
|
g_strJsonIndent.c_str(),
|
|
po::value<uint32_t>()->value_name("N")->default_value(util::JsonFormat::defaultIndent),
|
|
"Indent pretty-printed JSON with N spaces. Enables '--pretty-json' automatically."
|
|
)
|
|
(
|
|
g_strColor.c_str(),
|
|
"Force colored output."
|
|
)
|
|
(
|
|
g_strNoColor.c_str(),
|
|
"Explicitly disable colored output, disabling terminal auto-detection."
|
|
)
|
|
(
|
|
g_strErrorIds.c_str(),
|
|
"Output error codes."
|
|
)
|
|
;
|
|
desc.add(outputFormatting);
|
|
|
|
po::options_description outputComponents("Output Components");
|
|
outputComponents.add_options()
|
|
(CompilerOutputs::componentName(&CompilerOutputs::astCompactJson).c_str(), "AST of all source files in a compact JSON format.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::asm_).c_str(), "EVM assembly of the contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::asmJson).c_str(), "EVM assembly of the contracts in JSON format.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::opcodes).c_str(), "Opcodes of the contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::binary).c_str(), "Binary of the contracts in hex.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::binaryRuntime).c_str(), "Binary of the runtime part of the contracts in hex.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::abi).c_str(), "ABI specification of the contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::ir).c_str(), "Intermediate Representation (IR) of all contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::irAstJson).c_str(), "AST of Intermediate Representation (IR) of all contracts in a compact JSON format.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::irOptimized).c_str(), "Optimized Intermediate Representation (IR) of all contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::irOptimizedAstJson).c_str(), "AST of optimized Intermediate Representation (IR) of all contracts in a compact JSON format.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::signatureHashes).c_str(), "Function signature hashes of the contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::natspecUser).c_str(), "Natspec user documentation of all contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::natspecDev).c_str(), "Natspec developer documentation of all contracts.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::metadata).c_str(), "Combined Metadata JSON whose IPFS hash is stored on-chain.")
|
|
(CompilerOutputs::componentName(&CompilerOutputs::storageLayout).c_str(), "Slots, offsets and types of the contract's state variables.")
|
|
;
|
|
desc.add(outputComponents);
|
|
|
|
po::options_description extraOutput("Extra Output");
|
|
extraOutput.add_options()
|
|
(
|
|
g_strGas.c_str(),
|
|
"Print an estimate of the maximal gas usage for each function."
|
|
)
|
|
(
|
|
g_strCombinedJson.c_str(),
|
|
po::value<string>()->value_name(util::joinHumanReadable(CombinedJsonRequests::componentMap() | ranges::views::keys, ",")),
|
|
"Output a single json document containing the specified information."
|
|
)
|
|
;
|
|
desc.add(extraOutput);
|
|
|
|
po::options_description metadataOptions("Metadata Options");
|
|
metadataOptions.add_options()
|
|
(
|
|
g_strNoCBORMetadata.c_str(),
|
|
"Do not append CBOR metadata to the end of the bytecode."
|
|
)
|
|
(
|
|
g_strMetadataHash.c_str(),
|
|
po::value<string>()->value_name(util::joinHumanReadable(g_metadataHashArgs, ",")),
|
|
"Choose hash method for the bytecode metadata or disable it."
|
|
)
|
|
(
|
|
g_strMetadataLiteral.c_str(),
|
|
"Store referenced sources as literal data in the metadata output."
|
|
)
|
|
;
|
|
desc.add(metadataOptions);
|
|
|
|
po::options_description optimizerOptions("Optimizer Options");
|
|
optimizerOptions.add_options()
|
|
(
|
|
g_strOptimize.c_str(),
|
|
"Enable optimizer."
|
|
)
|
|
(
|
|
g_strOptimizeRuns.c_str(),
|
|
// TODO: The type in OptimiserSettings is size_t but we only accept values up to 2**32-1
|
|
// on the CLI and in Standard JSON. We should just switch to uint32_t everywhere.
|
|
po::value<unsigned>()->value_name("n")->default_value(static_cast<unsigned>(OptimiserSettings{}.expectedExecutionsPerDeployment)),
|
|
"The number of runs specifies roughly how often each opcode of the deployed code will be executed across the lifetime of the contract. "
|
|
"Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage."
|
|
)
|
|
(
|
|
g_strOptimizeYul.c_str(),
|
|
("Enable Yul optimizer (independently of the EVM assembly optimizer). "
|
|
"The general --" + g_strOptimize + " option automatically enables this unless --" +
|
|
g_strNoOptimizeYul + " is specified.").c_str()
|
|
)
|
|
(
|
|
g_strNoOptimizeYul.c_str(),
|
|
"Disable Yul optimizer (independently of the EVM assembly optimizer)."
|
|
)
|
|
(
|
|
g_strYulOptimizations.c_str(),
|
|
po::value<string>()->value_name("steps"),
|
|
"Forces Yul optimizer to use the specified sequence of optimization steps instead of the built-in one."
|
|
)
|
|
;
|
|
desc.add(optimizerOptions);
|
|
|
|
po::options_description smtCheckerOptions("Model Checker Options");
|
|
smtCheckerOptions.add_options()
|
|
(
|
|
g_strModelCheckerContracts.c_str(),
|
|
po::value<string>()->value_name("default,<source>:<contract>")->default_value("default"),
|
|
"Select which contracts should be analyzed using the form <source>:<contract>."
|
|
"Multiple pairs <source>:<contract> can be selected at the same time, separated by a comma "
|
|
"and no spaces."
|
|
)
|
|
(
|
|
g_strModelCheckerDivModNoSlacks.c_str(),
|
|
"Encode division and modulo operations with their precise operators"
|
|
" instead of multiplication with slack variables."
|
|
)
|
|
(
|
|
g_strModelCheckerEngine.c_str(),
|
|
po::value<string>()->value_name("all,bmc,chc,none")->default_value("none"),
|
|
"Select model checker engine."
|
|
)
|
|
(
|
|
g_strModelCheckerExtCalls.c_str(),
|
|
po::value<string>()->value_name("untrusted,trusted")->default_value("untrusted"),
|
|
"Select whether to assume (trusted) that external calls always invoke"
|
|
" the code given by the type of the contract, if that code is available."
|
|
)
|
|
(
|
|
g_strModelCheckerInvariants.c_str(),
|
|
po::value<string>()->value_name("default,all,contract,reentrancy")->default_value("default"),
|
|
"Select whether to report inferred contract inductive invariants."
|
|
" Multiple types of invariants can be selected at the same time, separated by a comma and no spaces."
|
|
" By default no invariants are reported."
|
|
)
|
|
(
|
|
g_strModelCheckerShowProvedSafe.c_str(),
|
|
"Show all targets that were proved safe separately."
|
|
)
|
|
(
|
|
g_strModelCheckerShowUnproved.c_str(),
|
|
"Show all unproved targets separately."
|
|
)
|
|
(
|
|
g_strModelCheckerShowUnsupported.c_str(),
|
|
"Show all unsupported language features separately."
|
|
)
|
|
(
|
|
g_strModelCheckerSolvers.c_str(),
|
|
po::value<string>()->value_name("cvc4,eld,z3,smtlib2")->default_value("z3"),
|
|
"Select model checker solvers."
|
|
)
|
|
(
|
|
g_strModelCheckerTargets.c_str(),
|
|
po::value<string>()->value_name("default,all,constantCondition,underflow,overflow,divByZero,balance,assert,popEmptyArray,outOfBounds")->default_value("default"),
|
|
"Select model checker verification targets."
|
|
"Multiple targets can be selected at the same time, separated by a comma and no spaces."
|
|
" By default all targets except underflow and overflow are selected."
|
|
)
|
|
(
|
|
g_strModelCheckerTimeout.c_str(),
|
|
po::value<unsigned>()->value_name("ms"),
|
|
"Set model checker timeout per query in milliseconds."
|
|
"The default is a deterministic resource limit."
|
|
"A timeout of 0 means no resource/time restrictions for any query."
|
|
)
|
|
(
|
|
g_strModelCheckerBMCLoopIterations.c_str(),
|
|
po::value<unsigned>(),
|
|
"Set loop unrolling depth for BMC engine."
|
|
"Default is 1."
|
|
)
|
|
;
|
|
desc.add(smtCheckerOptions);
|
|
|
|
desc.add_options()(g_strInputFile.c_str(), po::value<vector<string>>(), "input file");
|
|
return desc;
|
|
}
|
|
|
|
po::positional_options_description CommandLineParser::positionalOptionsDescription()
|
|
{
|
|
// All positional options should be interpreted as input files
|
|
po::positional_options_description filesPositions;
|
|
filesPositions.add(g_strInputFile.c_str(), -1);
|
|
return filesPositions;
|
|
}
|
|
|
|
void CommandLineParser::parseArgs(int _argc, char const* const* _argv)
|
|
{
|
|
po::options_description allOptions = optionsDescription();
|
|
po::positional_options_description filesPositions = positionalOptionsDescription();
|
|
|
|
// parse the compiler arguments
|
|
try
|
|
{
|
|
po::command_line_parser cmdLineParser(_argc, _argv);
|
|
cmdLineParser.style(po::command_line_style::default_style & (~po::command_line_style::allow_guessing));
|
|
cmdLineParser.options(allOptions).positional(filesPositions);
|
|
po::store(cmdLineParser.run(), m_args);
|
|
}
|
|
catch (po::error const& _exception)
|
|
{
|
|
solThrow(CommandLineValidationError, _exception.what());
|
|
}
|
|
|
|
po::notify(m_args);
|
|
}
|
|
|
|
void CommandLineParser::processArgs()
|
|
{
|
|
checkMutuallyExclusive({
|
|
g_strHelp,
|
|
g_strLicense,
|
|
g_strVersion,
|
|
g_strStandardJSON,
|
|
g_strLink,
|
|
g_strAssemble,
|
|
g_strStrictAssembly,
|
|
g_strYul,
|
|
g_strImportAst,
|
|
g_strLSP,
|
|
});
|
|
|
|
if (m_args.count(g_strHelp) > 0)
|
|
m_options.input.mode = InputMode::Help;
|
|
else if (m_args.count(g_strLicense) > 0)
|
|
m_options.input.mode = InputMode::License;
|
|
else if (m_args.count(g_strVersion) > 0)
|
|
m_options.input.mode = InputMode::Version;
|
|
else if (m_args.count(g_strStandardJSON) > 0)
|
|
m_options.input.mode = InputMode::StandardJson;
|
|
else if (m_args.count(g_strLSP))
|
|
m_options.input.mode = InputMode::LanguageServer;
|
|
else if (m_args.count(g_strAssemble) > 0 || m_args.count(g_strStrictAssembly) > 0 || m_args.count(g_strYul) > 0)
|
|
m_options.input.mode = InputMode::Assembler;
|
|
else if (m_args.count(g_strLink) > 0)
|
|
m_options.input.mode = InputMode::Linker;
|
|
else if (m_args.count(g_strImportAst) > 0)
|
|
m_options.input.mode = InputMode::CompilerWithASTImport;
|
|
else
|
|
m_options.input.mode = InputMode::Compiler;
|
|
|
|
if (
|
|
m_options.input.mode == InputMode::Help ||
|
|
m_options.input.mode == InputMode::License ||
|
|
m_options.input.mode == InputMode::Version
|
|
)
|
|
return;
|
|
|
|
map<string, set<InputMode>> validOptionInputModeCombinations = {
|
|
// TODO: This should eventually contain all options.
|
|
{g_strErrorRecovery, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strExperimentalViaIR, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strViaIR, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strMetadataLiteral, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strNoCBORMetadata, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strMetadataHash, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerShowProvedSafe, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerShowUnproved, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerShowUnsupported, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerDivModNoSlacks, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerEngine, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerInvariants, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerSolvers, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerTimeout, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerBMCLoopIterations, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerContracts, {InputMode::Compiler, InputMode::CompilerWithASTImport}},
|
|
{g_strModelCheckerTargets, {InputMode::Compiler, InputMode::CompilerWithASTImport}}
|
|
};
|
|
vector<string> invalidOptionsForCurrentInputMode;
|
|
for (auto const& [optionName, inputModes]: validOptionInputModeCombinations)
|
|
{
|
|
if (
|
|
m_args.count(optionName) > 0 &&
|
|
inputModes.count(m_options.input.mode) == 0 &&
|
|
!m_args[optionName].defaulted()
|
|
)
|
|
invalidOptionsForCurrentInputMode.push_back(optionName);
|
|
}
|
|
|
|
if (!invalidOptionsForCurrentInputMode.empty())
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"The following options are not supported in the current input mode: " +
|
|
joinOptionNames(invalidOptionsForCurrentInputMode)
|
|
);
|
|
|
|
if (m_options.input.mode == InputMode::LanguageServer)
|
|
return;
|
|
|
|
checkMutuallyExclusive({g_strColor, g_strNoColor});
|
|
checkMutuallyExclusive({g_strStopAfter, g_strGas});
|
|
|
|
for (string const& option: CompilerOutputs::componentMap() | ranges::views::keys)
|
|
if (option != CompilerOutputs::componentName(&CompilerOutputs::astCompactJson))
|
|
checkMutuallyExclusive({g_strStopAfter, option});
|
|
|
|
if (
|
|
m_options.input.mode != InputMode::Compiler &&
|
|
m_options.input.mode != InputMode::CompilerWithASTImport &&
|
|
m_options.input.mode != InputMode::Assembler
|
|
)
|
|
{
|
|
if (!m_args[g_strOptimizeRuns].defaulted())
|
|
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)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Option --" + option + " is only valid in compiler and assembler modes."
|
|
);
|
|
|
|
if (!m_args[g_strDebugInfo].defaulted())
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Option --" + g_strDebugInfo + " is only valid in compiler and assembler modes."
|
|
);
|
|
}
|
|
|
|
if (m_args.count(g_strColor) > 0)
|
|
m_options.formatting.coloredOutput = true;
|
|
else if (m_args.count(g_strNoColor) > 0)
|
|
m_options.formatting.coloredOutput = false;
|
|
|
|
m_options.formatting.withErrorIds = m_args.count(g_strErrorIds);
|
|
|
|
if (m_args.count(g_strRevertStrings))
|
|
{
|
|
string revertStringsString = m_args[g_strRevertStrings].as<string>();
|
|
optional<RevertStrings> revertStrings = revertStringsFromString(revertStringsString);
|
|
if (!revertStrings)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Invalid option for --" + g_strRevertStrings + ": " + revertStringsString
|
|
);
|
|
if (*revertStrings == RevertStrings::VerboseDebug)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Only \"default\", \"strip\" and \"debug\" are implemented for --" + g_strRevertStrings + " for now."
|
|
);
|
|
m_options.output.revertStrings = *revertStrings;
|
|
}
|
|
|
|
if (!m_args[g_strDebugInfo].defaulted())
|
|
{
|
|
string optionValue = m_args[g_strDebugInfo].as<string>();
|
|
m_options.output.debugInfoSelection = DebugInfoSelection::fromString(optionValue);
|
|
if (!m_options.output.debugInfoSelection.has_value())
|
|
solThrow(CommandLineValidationError, "Invalid value for --" + g_strDebugInfo + " option: " + optionValue);
|
|
|
|
if (m_options.output.debugInfoSelection->snippet && !m_options.output.debugInfoSelection->location)
|
|
solThrow(CommandLineValidationError, "To use 'snippet' with --" + g_strDebugInfo + " you must select also 'location'.");
|
|
}
|
|
|
|
parseCombinedJsonOption();
|
|
|
|
if (m_args.count(g_strOutputDir))
|
|
m_options.output.dir = m_args.at(g_strOutputDir).as<string>();
|
|
|
|
m_options.output.overwriteFiles = (m_args.count(g_strOverwrite) > 0);
|
|
|
|
if (m_args.count(g_strPrettyJson) > 0)
|
|
{
|
|
m_options.formatting.json.format = util::JsonFormat::Pretty;
|
|
}
|
|
if (!m_args[g_strJsonIndent].defaulted())
|
|
{
|
|
m_options.formatting.json.format = util::JsonFormat::Pretty;
|
|
m_options.formatting.json.indent = m_args[g_strJsonIndent].as<uint32_t>();
|
|
}
|
|
|
|
parseOutputSelection();
|
|
|
|
m_options.compiler.estimateGas = (m_args.count(g_strGas) > 0);
|
|
|
|
if (m_args.count(g_strBasePath))
|
|
m_options.input.basePath = m_args[g_strBasePath].as<string>();
|
|
|
|
if (m_args.count(g_strIncludePath) > 0)
|
|
{
|
|
if (m_options.input.basePath.empty())
|
|
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())
|
|
solThrow(CommandLineValidationError, "Empty values are not allowed in --" + g_strIncludePath + ".");
|
|
|
|
m_options.input.includePaths.push_back(includePath);
|
|
}
|
|
}
|
|
|
|
if (m_args.count(g_strAllowPaths))
|
|
{
|
|
vector<string> paths;
|
|
for (string const& allowedPath: boost::split(paths, m_args[g_strAllowPaths].as<string>(), boost::is_any_of(",")))
|
|
if (!allowedPath.empty())
|
|
m_options.input.allowedDirectories.insert(allowedPath);
|
|
}
|
|
|
|
if (m_args.count(g_strStopAfter))
|
|
{
|
|
if (m_args[g_strStopAfter].as<string>() != "parsing")
|
|
solThrow(CommandLineValidationError, "Valid options for --" + g_strStopAfter + " are: \"parsing\".\n");
|
|
else
|
|
m_options.output.stopAfter = CompilerStack::State::Parsed;
|
|
}
|
|
|
|
parseInputPathsAndRemappings();
|
|
|
|
if (m_options.input.mode == InputMode::StandardJson)
|
|
return;
|
|
|
|
if (m_args.count(g_strLibraries))
|
|
for (string const& library: m_args[g_strLibraries].as<vector<string>>())
|
|
parseLibraryOption(library);
|
|
|
|
if (m_options.input.mode == InputMode::Linker)
|
|
return;
|
|
|
|
if (m_args.count(g_strEVMVersion))
|
|
{
|
|
string versionOptionStr = m_args[g_strEVMVersion].as<string>();
|
|
optional<langutil::EVMVersion> versionOption = langutil::EVMVersion::fromString(versionOptionStr);
|
|
if (!versionOption)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strEVMVersion + ": " + versionOptionStr);
|
|
m_options.output.evmVersion = *versionOption;
|
|
}
|
|
|
|
if (m_args.count(g_strEOFVersion))
|
|
{
|
|
// Request as uint64_t, since uint8_t will be parsed as character by boost.
|
|
uint64_t versionOption = m_args[g_strEOFVersion].as<uint64_t>();
|
|
if (versionOption != 1)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strEOFVersion + ": " + to_string(versionOption));
|
|
m_options.output.eofVersion = 1;
|
|
}
|
|
|
|
if (m_args.count(g_strNoOptimizeYul) > 0 && m_args.count(g_strOptimizeYul) > 0)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Options --" + g_strOptimizeYul + " and --" + g_strNoOptimizeYul + " cannot be used together."
|
|
);
|
|
|
|
m_options.optimizer.optimizeEvmasm = (m_args.count(g_strOptimize) > 0);
|
|
m_options.optimizer.optimizeYul = (
|
|
(m_args.count(g_strOptimize) > 0 && m_args.count(g_strNoOptimizeYul) == 0) ||
|
|
m_args.count(g_strOptimizeYul) > 0
|
|
);
|
|
if (!m_args[g_strOptimizeRuns].defaulted())
|
|
m_options.optimizer.expectedExecutionsPerDeployment = m_args.at(g_strOptimizeRuns).as<unsigned>();
|
|
|
|
if (m_args.count(g_strYulOptimizations))
|
|
{
|
|
OptimiserSettings optimiserSettings = m_options.optimiserSettings();
|
|
if (!optimiserSettings.runYulOptimiser)
|
|
solThrow(CommandLineValidationError, "--" + g_strYulOptimizations + " is invalid if Yul optimizer is disabled");
|
|
|
|
try
|
|
{
|
|
yul::OptimiserSuite::validateSequence(m_args[g_strYulOptimizations].as<string>());
|
|
}
|
|
catch (yul::OptimizerException const& _exception)
|
|
{
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Invalid optimizer step sequence in --" + g_strYulOptimizations + ": " + _exception.what()
|
|
);
|
|
}
|
|
|
|
m_options.optimizer.yulSteps = m_args[g_strYulOptimizations].as<string>();
|
|
}
|
|
|
|
if (m_options.input.mode == InputMode::Assembler)
|
|
{
|
|
vector<string> const nonAssemblyModeOptions = {
|
|
// TODO: The list is not complete. Add more.
|
|
g_strOutputDir,
|
|
g_strGas,
|
|
g_strCombinedJson,
|
|
};
|
|
if (countEnabledOptions(nonAssemblyModeOptions) >= 1)
|
|
{
|
|
auto optionEnabled = [&](string const& name){ return m_args.count(name) > 0; };
|
|
auto enabledOptions = nonAssemblyModeOptions | ranges::views::filter(optionEnabled) | ranges::to_vector;
|
|
|
|
string message = "The following options are invalid in assembly mode: " + joinOptionNames(enabledOptions) + ".";
|
|
solThrow(CommandLineValidationError, message);
|
|
}
|
|
|
|
// switch to assembly mode
|
|
using Input = yul::YulStack::Language;
|
|
using Machine = yul::YulStack::Machine;
|
|
m_options.assembly.inputLanguage = m_args.count(g_strYul) ? Input::Yul : (m_args.count(g_strStrictAssembly) ? Input::StrictAssembly : Input::Assembly);
|
|
|
|
if (m_args.count(g_strMachine))
|
|
{
|
|
string machine = m_args[g_strMachine].as<string>();
|
|
if (machine == g_strEVM)
|
|
m_options.assembly.targetMachine = Machine::EVM;
|
|
else
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strMachine + ": " + machine);
|
|
}
|
|
if (m_args.count(g_strYulDialect))
|
|
{
|
|
string dialect = m_args[g_strYulDialect].as<string>();
|
|
if (dialect == g_strEVM)
|
|
m_options.assembly.inputLanguage = Input::StrictAssembly;
|
|
else
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strYulDialect + ": " + dialect);
|
|
}
|
|
if (
|
|
(m_options.optimizer.optimizeEvmasm || m_options.optimizer.optimizeYul) &&
|
|
m_options.assembly.inputLanguage != Input::StrictAssembly
|
|
)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Optimizer can only be used for strict assembly. Use --" + g_strStrictAssembly + "."
|
|
);
|
|
return;
|
|
}
|
|
else if (countEnabledOptions({g_strYulDialect, g_strMachine}) >= 1)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"--" + g_strYulDialect + " and --" + g_strMachine + " are only valid in assembly mode."
|
|
);
|
|
|
|
if (m_args.count(g_strMetadataHash))
|
|
{
|
|
string hashStr = m_args[g_strMetadataHash].as<string>();
|
|
if (hashStr == g_strIPFS)
|
|
m_options.metadata.hash = CompilerStack::MetadataHash::IPFS;
|
|
else if (hashStr == g_strSwarm)
|
|
m_options.metadata.hash = CompilerStack::MetadataHash::Bzzr1;
|
|
else if (hashStr == g_strNone)
|
|
m_options.metadata.hash = CompilerStack::MetadataHash::None;
|
|
else
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strMetadataHash + ": " + hashStr);
|
|
}
|
|
|
|
if (m_args.count(g_strNoCBORMetadata))
|
|
{
|
|
if (
|
|
m_args.count(g_strMetadataHash) &&
|
|
m_options.metadata.hash != CompilerStack::MetadataHash::None
|
|
)
|
|
solThrow(
|
|
CommandLineValidationError,
|
|
"Cannot specify a metadata hashing method when --" +
|
|
g_strNoCBORMetadata + " is set."
|
|
);
|
|
|
|
m_options.metadata.format = CompilerStack::MetadataFormat::NoMetadata;
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerContracts))
|
|
{
|
|
string contractsStr = m_args[g_strModelCheckerContracts].as<string>();
|
|
optional<ModelCheckerContracts> contracts = ModelCheckerContracts::fromString(contractsStr);
|
|
if (!contracts)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerContracts + ": " + contractsStr);
|
|
m_options.modelChecker.settings.contracts = std::move(*contracts);
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerDivModNoSlacks))
|
|
m_options.modelChecker.settings.divModNoSlacks = true;
|
|
|
|
if (m_args.count(g_strModelCheckerEngine))
|
|
{
|
|
string engineStr = m_args[g_strModelCheckerEngine].as<string>();
|
|
optional<ModelCheckerEngine> engine = ModelCheckerEngine::fromString(engineStr);
|
|
if (!engine)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerEngine + ": " + engineStr);
|
|
m_options.modelChecker.settings.engine = *engine;
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerExtCalls))
|
|
{
|
|
string mode = m_args[g_strModelCheckerExtCalls].as<string>();
|
|
optional<ModelCheckerExtCalls> extCallsMode = ModelCheckerExtCalls::fromString(mode);
|
|
if (!extCallsMode)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerExtCalls + ": " + mode);
|
|
m_options.modelChecker.settings.externalCalls = *extCallsMode;
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerInvariants))
|
|
{
|
|
string invsStr = m_args[g_strModelCheckerInvariants].as<string>();
|
|
optional<ModelCheckerInvariants> invs = ModelCheckerInvariants::fromString(invsStr);
|
|
if (!invs)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerInvariants + ": " + invsStr);
|
|
m_options.modelChecker.settings.invariants = *invs;
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerShowProvedSafe))
|
|
m_options.modelChecker.settings.showProvedSafe = true;
|
|
|
|
if (m_args.count(g_strModelCheckerShowUnproved))
|
|
m_options.modelChecker.settings.showUnproved = true;
|
|
|
|
if (m_args.count(g_strModelCheckerShowUnsupported))
|
|
m_options.modelChecker.settings.showUnsupported = true;
|
|
|
|
if (m_args.count(g_strModelCheckerSolvers))
|
|
{
|
|
string solversStr = m_args[g_strModelCheckerSolvers].as<string>();
|
|
optional<smtutil::SMTSolverChoice> solvers = smtutil::SMTSolverChoice::fromString(solversStr);
|
|
if (!solvers)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerSolvers + ": " + solversStr);
|
|
m_options.modelChecker.settings.solvers = *solvers;
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerTargets))
|
|
{
|
|
string targetsStr = m_args[g_strModelCheckerTargets].as<string>();
|
|
optional<ModelCheckerTargets> targets = ModelCheckerTargets::fromString(targetsStr);
|
|
if (!targets)
|
|
solThrow(CommandLineValidationError, "Invalid option for --" + g_strModelCheckerTargets + ": " + targetsStr);
|
|
m_options.modelChecker.settings.targets = *targets;
|
|
}
|
|
|
|
if (m_args.count(g_strModelCheckerTimeout))
|
|
m_options.modelChecker.settings.timeout = m_args[g_strModelCheckerTimeout].as<unsigned>();
|
|
|
|
if (m_args.count(g_strModelCheckerBMCLoopIterations))
|
|
{
|
|
if (!m_options.modelChecker.settings.engine.bmc)
|
|
solThrow(CommandLineValidationError, "BMC loop unrolling requires the BMC engine to be enabled");
|
|
m_options.modelChecker.settings.bmcLoopIterations = m_args[g_strModelCheckerBMCLoopIterations].as<unsigned>();
|
|
}
|
|
|
|
m_options.metadata.literalSources = (m_args.count(g_strMetadataLiteral) > 0);
|
|
m_options.modelChecker.initialize =
|
|
m_args.count(g_strModelCheckerContracts) ||
|
|
m_args.count(g_strModelCheckerDivModNoSlacks) ||
|
|
m_args.count(g_strModelCheckerEngine) ||
|
|
m_args.count(g_strModelCheckerExtCalls) ||
|
|
m_args.count(g_strModelCheckerInvariants) ||
|
|
m_args.count(g_strModelCheckerShowProvedSafe) ||
|
|
m_args.count(g_strModelCheckerShowUnproved) ||
|
|
m_args.count(g_strModelCheckerShowUnsupported) ||
|
|
m_args.count(g_strModelCheckerSolvers) ||
|
|
m_args.count(g_strModelCheckerTargets) ||
|
|
m_args.count(g_strModelCheckerTimeout);
|
|
m_options.output.viaIR = (m_args.count(g_strExperimentalViaIR) > 0 || m_args.count(g_strViaIR) > 0);
|
|
if (m_options.input.mode == InputMode::Compiler)
|
|
m_options.input.errorRecovery = (m_args.count(g_strErrorRecovery) > 0);
|
|
|
|
solAssert(m_options.input.mode == InputMode::Compiler || m_options.input.mode == InputMode::CompilerWithASTImport);
|
|
}
|
|
|
|
void CommandLineParser::parseCombinedJsonOption()
|
|
{
|
|
if (!m_args.count(g_strCombinedJson))
|
|
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)
|
|
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);
|
|
}
|
|
|
|
size_t CommandLineParser::countEnabledOptions(vector<string> const& _optionNames) const
|
|
{
|
|
size_t count = 0;
|
|
for (string const& _option: _optionNames)
|
|
count += m_args.count(_option);
|
|
|
|
return count;
|
|
}
|
|
|
|
string CommandLineParser::joinOptionNames(vector<string> const& _optionNames, string _separator)
|
|
{
|
|
return util::joinHumanReadable(
|
|
_optionNames | ranges::views::transform([](string const& _option){ return "--" + _option; }),
|
|
_separator
|
|
);
|
|
}
|
|
|
|
} // namespace solidity::frontend
|