/*
	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
/**
 * @author Lefteris <lefteris@ethdev.com>
 * @author Gav Wood <g@ethdev.com>
 * @date 2014
 * Solidity command line interface.
 */
#include <solc/CommandLineInterface.h>

#include "solidity/BuildInfo.h"
#include "license.h"

#include <libsolidity/interface/FileReader.h>
#include <libsolidity/interface/Version.h>
#include <libsolidity/parsing/Parser.h>
#include <libsolidity/ast/ASTJsonConverter.h>
#include <libsolidity/ast/ASTJsonImporter.h>
#include <libsolidity/analysis/NameAndTypeResolver.h>
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/StandardCompiler.h>
#include <libsolidity/interface/GasEstimator.h>
#include <libsolidity/interface/DebugSettings.h>
#include <libsolidity/interface/ImportRemapper.h>
#include <libsolidity/interface/StorageLayout.h>

#include <libyul/AssemblyStack.h>
#include <libyul/optimiser/Suite.h>

#include <libevmasm/Instruction.h>
#include <libevmasm/GasMeter.h>

#include <liblangutil/Exceptions.h>
#include <liblangutil/Scanner.h>
#include <liblangutil/SourceReferenceFormatter.h>

#include <libsmtutil/Exceptions.h>

#include <libsolutil/Common.h>
#include <libsolutil/CommonData.h>
#include <libsolutil/CommonIO.h>
#include <libsolutil/JSON.h>

#include <algorithm>
#include <memory>

#include <range/v3/view/map.hpp>

#include <boost/filesystem.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/algorithm/string.hpp>

#include <range/v3/view/transform.hpp>

#ifdef _WIN32 // windows
	#include <io.h>
	#define isatty _isatty
	#define fileno _fileno
#else // unix
	#include <unistd.h>
#endif

#include <string>
#include <iostream>
#include <fstream>

#if !defined(STDERR_FILENO)
	#define STDERR_FILENO 2
#endif

using namespace std;
using namespace solidity;
using namespace solidity::util;
using namespace solidity::langutil;

namespace po = boost::program_options;

namespace solidity::frontend
{

bool g_hasOutput = false;

namespace
{

std::ostream& sout()
{
	g_hasOutput = true;
	return cout;
}

std::ostream& serr(bool _used = true)
{
	if (_used)
		g_hasOutput = true;
	return cerr;
}

}

#define cout
#define cerr

static string const g_stdinFileNameStr = "<stdin>";
static string const g_strAbi = "abi";
static string const g_strAllowPaths = "allow-paths";
static string const g_strBasePath = "base-path";
static string const g_strAsm = "asm";
static string const g_strAsmJson = "asm-json";
static string const g_strAssemble = "assemble";
static string const g_strAst = "ast";
static string const g_strAstCompactJson = "ast-compact-json";
static string const g_strBinary = "bin";
static string const g_strBinaryRuntime = "bin-runtime";
static string const g_strCombinedJson = "combined-json";
static string const g_strCompactJSON = "compact-format";
static string const g_strContracts = "contracts";
static string const g_strErrorRecovery = "error-recovery";
static string const g_strEVM = "evm";
static string const g_strEVMVersion = "evm-version";
static string const g_strEwasm = "ewasm";
static string const g_strExperimentalViaIR = "experimental-via-ir";
static string const g_strGeneratedSources = "generated-sources";
static string const g_strGeneratedSourcesRuntime = "generated-sources-runtime";
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_strInterface = "interface";
static string const g_strYul = "yul";
static string const g_strYulDialect = "yul-dialect";
static string const g_strIR = "ir";
static string const g_strIROptimized = "ir-optimized";
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_strMachine = "machine";
static string const g_strMetadata = "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_strModelCheckerEngine = "model-checker-engine";
static string const g_strModelCheckerTargets = "model-checker-targets";
static string const g_strModelCheckerTimeout = "model-checker-timeout";
static string const g_strNatspecDev = "devdoc";
static string const g_strNatspecUser = "userdoc";
static string const g_strNone = "none";
static string const g_strNoOptimizeYul = "no-optimize-yul";
static string const g_strOpcodes = "opcodes";
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_strStorageLayout = "storage-layout";
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_strSignatureHashes = "hashes";
static string const g_strSources = "sources";
static string const g_strSourceList = "sourceList";
static string const g_strSrcMap = "srcmap";
static string const g_strSrcMapRuntime = "srcmap-runtime";
static string const g_strFunDebug = "function-debug";
static string const g_strFunDebugRuntime = "function-debug-runtime";
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_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";

static string const g_argAbi = g_strAbi;
static string const g_argPrettyJson = g_strPrettyJson;
static string const g_argAllowPaths = g_strAllowPaths;
static string const g_argBasePath = g_strBasePath;
static string const g_argAsm = g_strAsm;
static string const g_argAsmJson = g_strAsmJson;
static string const g_argAssemble = g_strAssemble;
static string const g_argAstCompactJson = g_strAstCompactJson;
static string const g_argBinary = g_strBinary;
static string const g_argBinaryRuntime = g_strBinaryRuntime;
static string const g_argCombinedJson = g_strCombinedJson;
static string const g_argCompactJSON = g_strCompactJSON;
static string const g_argErrorRecovery = g_strErrorRecovery;
static string const g_argGas = g_strGas;
static string const g_argHelp = g_strHelp;
static string const g_argImportAst = g_strImportAst;
static string const g_argInputFile = g_strInputFile;
static string const g_argYul = g_strYul;
static string const g_argIR = g_strIR;
static string const g_argIROptimized = g_strIROptimized;
static string const g_argEwasm = g_strEwasm;
static string const g_argExperimentalViaIR = g_strExperimentalViaIR;
static string const g_argLibraries = g_strLibraries;
static string const g_argLink = g_strLink;
static string const g_argMachine = g_strMachine;
static string const g_argMetadata = g_strMetadata;
static string const g_argMetadataHash = g_strMetadataHash;
static string const g_argMetadataLiteral = g_strMetadataLiteral;
static string const g_argModelCheckerContracts = g_strModelCheckerContracts;
static string const g_argModelCheckerEngine = g_strModelCheckerEngine;
static string const g_argModelCheckerTargets = g_strModelCheckerTargets;
static string const g_argModelCheckerTimeout = g_strModelCheckerTimeout;
static string const g_argNatspecDev = g_strNatspecDev;
static string const g_argNatspecUser = g_strNatspecUser;
static string const g_argOpcodes = g_strOpcodes;
static string const g_argOptimize = g_strOptimize;
static string const g_argOptimizeRuns = g_strOptimizeRuns;
static string const g_argOutputDir = g_strOutputDir;
static string const g_argSignatureHashes = g_strSignatureHashes;
static string const g_argStandardJSON = g_strStandardJSON;
static string const g_argStorageLayout = g_strStorageLayout;
static string const g_argStrictAssembly = g_strStrictAssembly;
static string const g_argVersion = g_strVersion;
static string const g_stdinFileName = g_stdinFileNameStr;
static string const g_argIgnoreMissingFiles = g_strIgnoreMissingFiles;
static string const g_argColor = g_strColor;
static string const g_argNoColor = g_strNoColor;
static string const g_argErrorIds = g_strErrorIds;

/// Possible arguments to for --combined-json
static set<string> const g_combinedJsonArgs
{
	g_strAbi,
	g_strAsm,
	g_strAst,
	g_strBinary,
	g_strBinaryRuntime,
	g_strCompactJSON,
	g_strFunDebug,
	g_strFunDebugRuntime,
	g_strGeneratedSources,
	g_strGeneratedSourcesRuntime,
	g_strInterface,
	g_strMetadata,
	g_strNatspecUser,
	g_strNatspecDev,
	g_strOpcodes,
	g_strSignatureHashes,
	g_strSrcMap,
	g_strSrcMapRuntime,
	g_strStorageLayout
};

/// Possible arguments to for --machine
static set<string> const g_machineArgs
{
	g_strEVM,
	g_strEwasm
};

/// Possible arguments to for --yul-dialect
static set<string> const g_yulDialectArgs
{
	g_strEVM,
	g_strEwasm
};

/// Possible arguments to for --metadata-hash
static set<string> const g_metadataHashArgs
{
	g_strIPFS,
	g_strSwarm,
	g_strNone
};

static void version()
{
	sout() <<
		"solc, the solidity compiler commandline interface" <<
		endl <<
		"Version: " <<
		solidity::frontend::VersionString <<
		endl;
	exit(0);
}

static void license()
{
	sout() << otherLicenses << endl;
	// This is a static variable generated by cmake from LICENSE.txt
	sout() << licenseText << endl;
	exit(0);
}

static bool needsHumanTargetedStdout(po::variables_map const& _args)
{
	if (_args.count(g_argGas))
		return true;
	if (_args.count(g_argOutputDir))
		return false;
	for (string const& arg: {
		g_argAbi,
		g_argAsm,
		g_argAsmJson,
		g_argBinary,
		g_argBinaryRuntime,
		g_argMetadata,
		g_argNatspecUser,
		g_argNatspecDev,
		g_argOpcodes,
		g_argSignatureHashes,
		g_argStorageLayout
	})
		if (_args.count(arg))
			return true;
	return false;
}

namespace
{

bool checkMutuallyExclusive(boost::program_options::variables_map const& args, std::string const& _optionA, std::string const& _optionB)
{
	if (args.count(_optionA) && args.count(_optionB))
	{
		serr() << "Option " << _optionA << " and " << _optionB << " are mutually exclusive." << endl;
		return false;
	}

	return true;
}

}

void CommandLineInterface::handleBinary(string const& _contract)
{
	if (m_args.count(g_argBinary))
	{
		if (m_args.count(g_argOutputDir))
			createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", objectWithLinkRefsHex(m_compiler->object(_contract)));
		else
		{
			sout() << "Binary:" << endl;
			sout() << objectWithLinkRefsHex(m_compiler->object(_contract)) << endl;
		}
	}
	if (m_args.count(g_argBinaryRuntime))
	{
		if (m_args.count(g_argOutputDir))
			createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", objectWithLinkRefsHex(m_compiler->runtimeObject(_contract)));
		else
		{
			sout() << "Binary of the runtime part:" << endl;
			sout() << objectWithLinkRefsHex(m_compiler->runtimeObject(_contract)) << endl;
		}
	}
}

void CommandLineInterface::handleOpcode(string const& _contract)
{
	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", evmasm::disassemble(m_compiler->object(_contract).bytecode));
	else
	{
		sout() << "Opcodes:" << endl;
		sout() << std::uppercase << evmasm::disassemble(m_compiler->object(_contract).bytecode);
		sout() << endl;
	}
}

void CommandLineInterface::handleIR(string const& _contractName)
{
	if (!m_args.count(g_argIR))
		return;

	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName));
	else
	{
		sout() << "IR:" << endl;
		sout() << m_compiler->yulIR(_contractName) << endl;
	}
}

void CommandLineInterface::handleIROptimized(string const& _contractName)
{
	if (!m_args.count(g_argIROptimized))
		return;

	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", m_compiler->yulIROptimized(_contractName));
	else
	{
		sout() << "Optimized IR:" << endl;
		sout() << m_compiler->yulIROptimized(_contractName) << endl;
	}
}

void CommandLineInterface::handleEwasm(string const& _contractName)
{
	if (!m_args.count(g_argEwasm))
		return;

	if (m_args.count(g_argOutputDir))
	{
		createFile(m_compiler->filesystemFriendlyName(_contractName) + ".wast", m_compiler->ewasm(_contractName));
		createFile(
			m_compiler->filesystemFriendlyName(_contractName) + ".wasm",
			asString(m_compiler->ewasmObject(_contractName).bytecode)
		);
	}
	else
	{
		sout() << "Ewasm text:" << endl;
		sout() << m_compiler->ewasm(_contractName) << endl;
		sout() << "Ewasm binary (hex): " << m_compiler->ewasmObject(_contractName).toHex() << endl;
	}
}

void CommandLineInterface::handleBytecode(string const& _contract)
{
	if (m_args.count(g_argOpcodes))
		handleOpcode(_contract);
	if (m_args.count(g_argBinary) || m_args.count(g_argBinaryRuntime))
		handleBinary(_contract);
}

void CommandLineInterface::handleSignatureHashes(string const& _contract)
{
	if (!m_args.count(g_argSignatureHashes))
		return;

	Json::Value methodIdentifiers = m_compiler->methodIdentifiers(_contract);
	string out;
	for (auto const& name: methodIdentifiers.getMemberNames())
		out += methodIdentifiers[name].asString() + ": " + name + "\n";

	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out);
	else
		sout() << "Function signatures:" << endl << out;
}

void CommandLineInterface::handleMetadata(string const& _contract)
{
	if (!m_args.count(g_argMetadata))
		return;

	string data = m_compiler->metadata(_contract);
	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contract) + "_meta.json", data);
	else
		sout() << "Metadata:" << endl << data << endl;
}

void CommandLineInterface::handleABI(string const& _contract)
{
	if (!m_args.count(g_argAbi))
		return;

	string data = jsonCompactPrint(removeNullMembers(m_compiler->contractABI(_contract)));
	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contract) + ".abi", data);
	else
		sout() << "Contract JSON ABI" << endl << data << endl;
}

void CommandLineInterface::handleStorageLayout(string const& _contract)
{
	if (!m_args.count(g_argStorageLayout))
		return;

	string data = jsonCompactPrint(removeNullMembers(m_compiler->storageLayout(_contract)));
	if (m_args.count(g_argOutputDir))
		createFile(m_compiler->filesystemFriendlyName(_contract) + "_storage.json", data);
	else
		sout() << "Contract Storage Layout:" << endl << data << endl;
}

void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contract)
{
	std::string argName;
	std::string suffix;
	std::string title;

	if (_natspecDev)
	{
		argName = g_argNatspecDev;
		suffix = ".docdev";
		title = "Developer Documentation";
	}
	else
	{
		argName = g_argNatspecUser;
		suffix = ".docuser";
		title = "User Documentation";
	}

	if (m_args.count(argName))
	{
		std::string output = jsonPrettyPrint(
			removeNullMembers(
				_natspecDev ?
				m_compiler->natspecDev(_contract) :
				m_compiler->natspecUser(_contract)
			)
		);

		if (m_args.count(g_argOutputDir))
			createFile(m_compiler->filesystemFriendlyName(_contract) + suffix, output);
		else
		{
			sout() << title << endl;
			sout() << output << endl;
		}

	}
}

void CommandLineInterface::handleGasEstimation(string const& _contract)
{
	Json::Value estimates = m_compiler->gasEstimates(_contract);
	sout() << "Gas estimation:" << endl;

	if (estimates["creation"].isObject())
	{
		Json::Value creation = estimates["creation"];
		sout() << "construction:" << endl;
		sout() << "   " << creation["executionCost"].asString();
		sout() << " + " << creation["codeDepositCost"].asString();
		sout() << " = " << creation["totalCost"].asString() << endl;
	}

	if (estimates["external"].isObject())
	{
		Json::Value externalFunctions = estimates["external"];
		sout() << "external:" << endl;
		for (auto const& name: externalFunctions.getMemberNames())
		{
			if (name.empty())
				sout() << "   fallback:\t";
			else
				sout() << "   " << name << ":\t";
			sout() << externalFunctions[name].asString() << endl;
		}
	}

	if (estimates["internal"].isObject())
	{
		Json::Value internalFunctions = estimates["internal"];
		sout() << "internal:" << endl;
		for (auto const& name: internalFunctions.getMemberNames())
		{
			sout() << "   " << name << ":\t";
			sout() << internalFunctions[name].asString() << endl;
		}
	}
}

bool CommandLineInterface::readInputFilesAndConfigureRemappings()
{
	bool ignoreMissing = m_args.count(g_argIgnoreMissingFiles);
	bool addStdin = false;
	if (m_args.count(g_argInputFile))
		for (string path: m_args[g_argInputFile].as<vector<string>>())
		{
			auto eq = find(path.begin(), path.end(), '=');
			if (eq != path.end())
			{
				if (auto r = ImportRemapper::parseRemapping(path))
					m_remappings.emplace_back(std::move(*r));
				else
				{
					serr() << "Invalid remapping: \"" << path << "\"." << endl;
					return false;
				}

				string remappingTarget(eq + 1, path.end());
				m_fileReader.allowDirectory(boost::filesystem::path(remappingTarget).remove_filename());
			}
			else if (path == "-")
				addStdin = true;
			else
			{
				auto infile = boost::filesystem::path(path);
				if (!boost::filesystem::exists(infile))
				{
					if (!ignoreMissing)
					{
						serr() << infile << " is not found." << endl;
						return false;
					}
					else
						serr() << infile << " is not found. Skipping." << endl;

					continue;
				}

				if (!boost::filesystem::is_regular_file(infile))
				{
					if (!ignoreMissing)
					{
						serr() << infile << " is not a valid file." << endl;
						return false;
					}
					else
						serr() << infile << " is not a valid file. Skipping." << endl;

					continue;
				}

				// NOTE: we ignore the FileNotFound exception as we manually check above
				m_fileReader.setSource(infile, readFileAsString(infile.string()));
				m_fileReader.allowDirectory(boost::filesystem::path(boost::filesystem::canonical(infile).string()).remove_filename());
			}
		}

	if (addStdin)
		m_fileReader.setSource(g_stdinFileName, readStandardInput());

	if (m_fileReader.sourceCodes().size() == 0)
	{
		serr() << "No input files given. If you wish to use the standard input please specify \"-\" explicitly." << endl;
		return false;
	}

	return true;
}

bool CommandLineInterface::parseLibraryOption(string const& _input)
{
	namespace fs = boost::filesystem;
	string data = _input;
	try
	{
		if (fs::is_regular_file(_input))
			data = readFileAsString(_input);
	}
	catch (fs::filesystem_error const&)
	{
		// Thrown e.g. if path is too long.
	}
	catch (FileNotFound 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)
				{
					serr() << "Equal sign separator missing in library address specifier \"" << lib << "\"" << endl;
					return false;
				}
				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;
				}

			string libName(lib.begin(), lib.begin() + static_cast<ptrdiff_t>(separator));
			boost::trim(libName);
			if (m_libraries.count(libName))
			{
				serr() << "Address specified more than once for library \"" << libName << "\"." << endl;
				return false;
			}

			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;
			}

			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;
			}

			if (addrString.length() != 40)
			{
				serr() << "Invalid length for address for library \"" << libName << "\": " << addrString.length() << " instead of 40 characters." << endl;
				return false;
			}
			if (!passesAddressChecksum(addrString, false))
			{
				serr() << "Invalid checksum on address for library \"" << libName << "\": " << addrString << endl;
				serr() << "The correct checksum is " << getChecksummedAddress(addrString) << endl;
				return false;
			}
			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;
			}
			m_libraries[libName] = address;
		}

	return true;
}

map<string, Json::Value> CommandLineInterface::parseAstFromInput()
{
	map<string, Json::Value> sourceJsons;
	map<string, string> tmpSources;

	for (SourceCode const& sourceCode: m_fileReader.sourceCodes() | ranges::views::values)
	{
		Json::Value ast;
		astAssert(jsonParseStrict(sourceCode, ast), "Input file could not be parsed to JSON");
		astAssert(ast.isMember("sources"), "Invalid Format for import-JSON: Must have 'sources'-object");

		for (auto& src: ast["sources"].getMemberNames())
		{
			std::string astKey = ast["sources"][src].isMember("ast") ? "ast" : "AST";

			astAssert(ast["sources"][src].isMember(astKey), "astkey is not member");
			astAssert(ast["sources"][src][astKey]["nodeType"].asString() == "SourceUnit",  "Top-level node should be a 'SourceUnit'");
			astAssert(sourceJsons.count(src) == 0, "All sources must have unique names");
			sourceJsons.emplace(src, move(ast["sources"][src][astKey]));
			tmpSources[src] = util::jsonCompactPrint(ast);
		}
	}

	m_fileReader.setSources(tmpSources);

	return sourceJsons;
}

void CommandLineInterface::createFile(string const& _fileName, string const& _data)
{
	namespace fs = boost::filesystem;

	fs::path outputDir(m_args.at(g_argOutputDir).as<string>());

	// NOTE: create_directories() raises an exception if the path consists solely of '.' or '..'
	// (or equivalent such as './././.'). Paths like 'a/b/.' and 'a/b/..' are fine though.
	// The simplest workaround is to use an absolute path.
	fs::create_directories(fs::absolute(outputDir));

	string pathName = (outputDir / _fileName).string();
	if (fs::exists(pathName) && !m_args.count(g_strOverwrite))
	{
		serr() << "Refusing to overwrite existing file \"" << pathName << "\" (use --" << g_strOverwrite << " to force)." << endl;
		m_error = true;
		return;
	}
	ofstream outFile(pathName);
	outFile << _data;
	if (!outFile)
	{
		serr() << "Could not write to file \"" << pathName << "\"." << endl;
		m_error = true;
		return;
	}
}

void CommandLineInterface::createJson(string const& _fileName, string const& _json)
{
	createFile(boost::filesystem::basename(_fileName) + string(".json"), _json);
}

bool CommandLineInterface::parseArguments(int _argc, char** _argv)
{
	g_hasOutput = false;

	// 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 none given or
"-" 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 --)" + g_argBinary + 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_argHelp.c_str(), "Show help message and exit.")
		(g_argVersion.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_argBasePath.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_argAllowPaths.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_argIgnoreMissingFiles.c_str(),
			"Ignore missing files."
		)
		(
			g_argErrorRecovery.c_str(),
			"Enables additional parser error recovery."
		)
	;
	desc.add(inputOptions);

	po::options_description outputOptions("Output Options");
	outputOptions.add_options()
		(
			(g_argOutputDir + ",o").c_str(),
			po::value<string>()->value_name("path"),
			"If given, creates one file per 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"),
			"Select desired EVM version. Either homestead, tangerineWhistle, spuriousDragon, "
			"byzantium, constantinople, petersburg, istanbul (default) or berlin."
		)
		(
			g_strExperimentalViaIR.c_str(),
			"Turn on experimental compilation mode via the IR (EXPERIMENTAL)."
		)
		(
			g_strRevertStrings.c_str(),
			po::value<string>()->value_name(boost::join(g_revertStringsArgs, ",")),
			"Strip revert (and require) reason strings or add additional debugging information."
		)
		(
			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_argStandardJSON.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_argLink.c_str(),
			("Switch to linker mode, ignoring all options apart from --" + g_argLibraries + " "
			"and modify binaries in place.").c_str()
		)
		(
			g_argAssemble.c_str(),
			("Switch to assembly mode, ignoring all options except "
			"--" + g_argMachine + ", --" + g_strYulDialect + ", --" + g_argOptimize + " and --" + g_strYulOptimizations + " "
			"and assumes input is assembly.").c_str()
		)
		(
			g_argYul.c_str(),
			("Switch to Yul mode, ignoring all options except "
			"--" + g_argMachine + ", --" + g_strYulDialect + ", --" + g_argOptimize + " and --" + g_strYulOptimizations + " "
			"and assumes input is Yul.").c_str()
		)
		(
			g_argStrictAssembly.c_str(),
			("Switch to strict assembly mode, ignoring all options except "
			"--" + g_argMachine + ", --" + g_strYulDialect + ", --" + g_argOptimize + " and --" + g_strYulOptimizations + " "
			"and assumes input is strict assembly.").c_str()
		)
		(
			g_argImportAst.c_str(),
			("Import ASTs to be compiled, assumes input holds the AST in compact JSON format. "
			"Supported Inputs is the output of the --" + g_argStandardJSON + " or the one produced by "
			"--" + g_argCombinedJson + " " + g_strAst + "," + g_strCompactJSON).c_str()
		)
	;
	desc.add(alternativeInputModes);

	po::options_description assemblyModeOptions("Assembly Mode Options");
	assemblyModeOptions.add_options()
		(
			g_argMachine.c_str(),
			po::value<string>()->value_name(boost::join(g_machineArgs, ",")),
			"Target machine in assembly or Yul mode."
		)
		(
			g_strYulDialect.c_str(),
			po::value<string>()->value_name(boost::join(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_argLibraries.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_argPrettyJson.c_str(),
			"Output JSON in pretty format. Currently it only works with the combined JSON output."
		)
		(
			g_argColor.c_str(),
			"Force colored output."
		)
		(
			g_argNoColor.c_str(),
			"Explicitly disable colored output, disabling terminal auto-detection."
		)
		(
			g_argErrorIds.c_str(),
			"Output error codes."
		)
	;
	desc.add(outputFormatting);

	po::options_description outputComponents("Output Components");
	outputComponents.add_options()
		(g_argAstCompactJson.c_str(), "AST of all source files in a compact JSON format.")
		(g_argAsm.c_str(), "EVM assembly of the contracts.")
		(g_argAsmJson.c_str(), "EVM assembly of the contracts in JSON format.")
		(g_argOpcodes.c_str(), "Opcodes of the contracts.")
		(g_argBinary.c_str(), "Binary of the contracts in hex.")
		(g_argBinaryRuntime.c_str(), "Binary of the runtime part of the contracts in hex.")
		(g_argAbi.c_str(), "ABI specification of the contracts.")
		(g_argIR.c_str(), "Intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
		(g_argIROptimized.c_str(), "Optimized intermediate Representation (IR) of all contracts (EXPERIMENTAL).")
		(g_argEwasm.c_str(), "Ewasm text representation of all contracts (EXPERIMENTAL).")
		(g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.")
		(g_argNatspecUser.c_str(), "Natspec user documentation of all contracts.")
		(g_argNatspecDev.c_str(), "Natspec developer documentation of all contracts.")
		(g_argMetadata.c_str(), "Combined Metadata JSON whose Swarm hash is stored on-chain.")
		(g_argStorageLayout.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_argGas.c_str(),
			"Print an estimate of the maximal gas usage for each function."
		)
		(
			g_argCombinedJson.c_str(),
			po::value<string>()->value_name(boost::join(g_combinedJsonArgs, ",")),
			"Output a single json document containing the specified information."
		)
	;
	desc.add(extraOutput);

	po::options_description metadataOptions("Metadata Options");
	metadataOptions.add_options()
		(
			g_argMetadataHash.c_str(),
			po::value<string>()->value_name(boost::join(g_metadataHashArgs, ",")),
			"Choose hash method for the bytecode metadata or disable it."
		)
		(
			g_argMetadataLiteral.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_argOptimize.c_str(),
			"Enable bytecode optimizer."
		)
		(
			g_argOptimizeRuns.c_str(),
			po::value<unsigned>()->value_name("n")->default_value(200),
			"Set for how many contract runs to optimize. "
			"Lower values will optimize more for initial deployment cost, higher values will optimize more for high-frequency usage."
		)
		(
			g_strOptimizeYul.c_str(),
			("Legacy option, ignored. Use the general --" + g_argOptimize + " to enable Yul optimizer.").c_str()
		)
		(
			g_strNoOptimizeYul.c_str(),
			"Disable Yul optimizer in Solidity."
		)
		(
			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_strModelCheckerEngine.c_str(),
			po::value<string>()->value_name("all,bmc,chc,none")->default_value("none"),
			"Select model checker engine."
		)
		(
			g_strModelCheckerTargets.c_str(),
			po::value<string>()->value_name("default,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."
		)
		(
			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."
		)
	;
	desc.add(smtCheckerOptions);

	po::options_description allOptions = desc;
	allOptions.add_options()(g_argInputFile.c_str(), po::value<vector<string>>(), "input file");

	// All positional options should be interpreted as input files
	po::positional_options_description filesPositions;
	filesPositions.add(g_argInputFile.c_str(), -1);

	// 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)
	{
		serr() << _exception.what() << endl;
		return false;
	}

	if (!checkMutuallyExclusive(m_args, g_argColor, g_argNoColor))
		return false;

	static vector<string> const conflictingWithStopAfter{
		g_argBinary,
		g_argIR,
		g_argIROptimized,
		g_argEwasm,
		g_argGas,
		g_argAsm,
		g_argAsmJson,
		g_argOpcodes
	};

	for (auto& option: conflictingWithStopAfter)
		if (!checkMutuallyExclusive(m_args, g_strStopAfter, option))
			return false;

	m_coloredOutput = !m_args.count(g_argNoColor) && (isatty(STDERR_FILENO) || m_args.count(g_argColor));

	m_withErrorIds = m_args.count(g_argErrorIds);

	if (m_args.count(g_argHelp) || (isatty(fileno(stdin)) && _argc == 1))
	{
		sout() << desc;
		return false;
	}

	if (m_args.count(g_argVersion))
	{
		version();
		return false;
	}

	if (m_args.count(g_strLicense))
	{
		license();
		return false;
	}

	if (m_args.count(g_strRevertStrings))
	{
		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;
		}
		if (*revertStrings == RevertStrings::VerboseDebug)
		{
			serr() << "Only \"default\", \"strip\" and \"debug\" are implemented for --" << g_strRevertStrings << " for now." << endl;
			return false;
		}
		m_revertStrings = *revertStrings;
	}

	if (m_args.count(g_argCombinedJson))
	{
		vector<string> requests;
		for (string const& item: boost::split(requests, m_args[g_argCombinedJson].as<string>(), boost::is_any_of(",")))
			if (!g_combinedJsonArgs.count(item))
			{
				serr() << "Invalid option to --" << g_argCombinedJson << ": " << item << endl;
				return false;
			}
	}
	po::notify(m_args);

	return true;
}

bool CommandLineInterface::processInput()
{
	if (m_args.count(g_argBasePath))
	{
		boost::filesystem::path const fspath{m_args[g_argBasePath].as<string>()};
		if (!boost::filesystem::is_directory(fspath))
		{
			serr() << "Base path must be a directory: \"" << fspath << "\"\n";
			return false;
		}
		m_fileReader.setBasePath(fspath);
	}

	if (m_args.count(g_argAllowPaths))
	{
		vector<string> paths;
		for (string const& path: boost::split(paths, m_args[g_argAllowPaths].as<string>(), boost::is_any_of(",")))
		{
			auto filesystem_path = boost::filesystem::path(path);
			// If the given path had a trailing slash, the Boost filesystem
			// path will have it's last component set to '.'. This breaks
			// path comparison in later parts of the code, so we need to strip
			// it.
			if (filesystem_path.filename() == ".")
				filesystem_path.remove_filename();
			m_fileReader.allowDirectory(filesystem_path);
		}
	}

	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;
		}
		else
			m_stopAfter = CompilerStack::State::Parsed;
	}

	vector<string> const exclusiveModes = {
		g_argStandardJSON,
		g_argLink,
		g_argAssemble,
		g_argStrictAssembly,
		g_argYul,
		g_argImportAst,
	};
	if (countEnabledOptions(exclusiveModes) > 1)
	{
		serr() << "The following options are mutually exclusive: " << joinOptionNames(exclusiveModes) << ". ";
		serr() << "Select at most one." << endl;
		return false;
	}

	if (m_args.count(g_argStandardJSON))
	{
		vector<string> inputFiles;
		string jsonFile;
		if (m_args.count(g_argInputFile))
			inputFiles = m_args[g_argInputFile].as<vector<string>>();
		if (inputFiles.size() == 1)
			jsonFile = inputFiles[0];
		else if (inputFiles.size() > 1)
		{
			serr() << "If --" << g_argStandardJSON << " is used, only zero or one input files are supported." << endl;
			return false;
		}
		string input;
		if (jsonFile.empty())
			input = readStandardInput();
		else
		{
			try
			{
				input = readFileAsString(jsonFile);
			}
			catch (FileNotFound const&)
			{
				serr() << "File not found: " << jsonFile << endl;
				return false;
			}
		}
		StandardCompiler compiler(m_fileReader.reader());
		sout() << compiler.compile(std::move(input)) << endl;
		return true;
	}

	if (!readInputFilesAndConfigureRemappings())
		return false;

	if (m_args.count(g_argLibraries))
		for (string const& library: m_args[g_argLibraries].as<vector<string>>())
			if (!parseLibraryOption(library))
				return false;

	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;
		}
		m_evmVersion = *versionOption;
	}

	if (m_args.count(g_argAssemble) || m_args.count(g_argStrictAssembly) || m_args.count(g_argYul))
	{
		vector<string> const nonAssemblyModeOptions = {
			// TODO: The list is not complete. Add more.
			g_argOutputDir,
			g_argGas,
			g_argCombinedJson,
			g_strOptimizeYul,
			g_strNoOptimizeYul,
		};
		if (countEnabledOptions(nonAssemblyModeOptions) >= 1)
		{
			auto optionEnabled = [&](string const& name){ return m_args.count(name) > 0; };
			auto enabledOptions = boost::copy_range<vector<string>>(nonAssemblyModeOptions | boost::adaptors::filtered(optionEnabled));

			serr() << "The following options are invalid in assembly mode: ";
			serr() << 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_argOptimize << "." << endl;
			serr() << endl;
			return false;
		}

		// switch to assembly mode
		m_onlyAssemble = true;
		using Input = yul::AssemblyStack::Language;
		using Machine = yul::AssemblyStack::Machine;
		Input inputLanguage = m_args.count(g_argYul) ? Input::Yul : (m_args.count(g_argStrictAssembly) ? Input::StrictAssembly : Input::Assembly);
		Machine targetMachine = Machine::EVM;
		bool optimize = m_args.count(g_argOptimize);

		optional<string> yulOptimiserSteps;
		if (m_args.count(g_strYulOptimizations))
		{
			if (!optimize)
			{
				serr() << "--" << g_strYulOptimizations << " is invalid if Yul optimizer is disabled" << endl;
				return false;
			}

			try
			{
				yul::OptimiserSuite::validateSequence(m_args[g_strYulOptimizations].as<string>());
			}
			catch (yul::OptimizerException const& _exception)
			{
				serr() << "Invalid optimizer step sequence in --" << g_strYulOptimizations << ": " << _exception.what() << endl;
				return false;
			}

			yulOptimiserSteps = m_args[g_strYulOptimizations].as<string>();
		}

		if (m_args.count(g_argMachine))
		{
			string machine = m_args[g_argMachine].as<string>();
			if (machine == g_strEVM)
				targetMachine = Machine::EVM;
			else if (machine == g_strEwasm)
				targetMachine = Machine::Ewasm;
			else
			{
				serr() << "Invalid option for --" << g_argMachine << ": " << machine << endl;
				return false;
			}
		}
		if (targetMachine == Machine::Ewasm && inputLanguage == Input::StrictAssembly)
			inputLanguage = Input::Ewasm;
		if (m_args.count(g_strYulDialect))
		{
			string dialect = m_args[g_strYulDialect].as<string>();
			if (dialect == g_strEVM)
				inputLanguage = Input::StrictAssembly;
			else if (dialect == g_strEwasm)
			{
				inputLanguage = Input::Ewasm;
				if (targetMachine != Machine::Ewasm)
				{
					serr() << "If you select Ewasm as --" << g_strYulDialect << ", ";
					serr() << "--" << g_argMachine << " has to be Ewasm as well." << endl;
					return false;
				}
			}
			else
			{
				serr() << "Invalid option for --" << g_strYulDialect << ": " << dialect << endl;
				return false;
			}
		}
		if (optimize && (inputLanguage != Input::StrictAssembly && inputLanguage != Input::Ewasm))
		{
			serr() <<
				"Optimizer can only be used for strict assembly. Use --" <<
				g_strStrictAssembly <<
				"." <<
				endl;
			return false;
		}
		if (targetMachine == Machine::Ewasm && inputLanguage != Input::StrictAssembly && 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 assemble(inputLanguage, targetMachine, optimize, yulOptimiserSteps);
	}
	else if (countEnabledOptions({g_strYulDialect, g_argMachine}) >= 1)
	{
		serr() << "--" << g_strYulDialect << " and --" << g_argMachine << " ";
		serr() << "are only valid in assembly mode." << endl;
		return false;
	}

	if (m_args.count(g_argLink))
	{
		// switch to linker mode
		m_onlyLink = true;
		return link();
	}

	if (m_args.count(g_argMetadataHash))
	{
		string hashStr = m_args[g_argMetadataHash].as<string>();
		if (hashStr == g_strIPFS)
			m_metadataHash = CompilerStack::MetadataHash::IPFS;
		else if (hashStr == g_strSwarm)
			m_metadataHash = CompilerStack::MetadataHash::Bzzr1;
		else if (hashStr == g_strNone)
			m_metadataHash = CompilerStack::MetadataHash::None;
		else
		{
			serr() << "Invalid option for --" << g_argMetadataHash << ": " << hashStr << endl;
			return false;
		}
	}

	if (m_args.count(g_argModelCheckerContracts))
	{
		string contractsStr = m_args[g_argModelCheckerContracts].as<string>();
		optional<ModelCheckerContracts> contracts = ModelCheckerContracts::fromString(contractsStr);
		if (!contracts)
		{
			serr() << "Invalid option for --" << g_argModelCheckerContracts << ": " << contractsStr << endl;
			return false;
		}
		m_modelCheckerSettings.contracts = move(*contracts);
	}


	if (m_args.count(g_argModelCheckerEngine))
	{
		string engineStr = m_args[g_argModelCheckerEngine].as<string>();
		optional<ModelCheckerEngine> engine = ModelCheckerEngine::fromString(engineStr);
		if (!engine)
		{
			serr() << "Invalid option for --" << g_argModelCheckerEngine << ": " << engineStr << endl;
			return false;
		}
		m_modelCheckerSettings.engine = *engine;
	}

	if (m_args.count(g_argModelCheckerTargets))
	{
		string targetsStr = m_args[g_argModelCheckerTargets].as<string>();
		optional<ModelCheckerTargets> targets = ModelCheckerTargets::fromString(targetsStr);
		if (!targets)
		{
			serr() << "Invalid option for --" << g_argModelCheckerTargets << ": " << targetsStr << endl;
			return false;
		}
		m_modelCheckerSettings.targets = *targets;
	}

	if (m_args.count(g_argModelCheckerTimeout))
		m_modelCheckerSettings.timeout = m_args[g_argModelCheckerTimeout].as<unsigned>();

	m_compiler = make_unique<CompilerStack>(m_fileReader.reader());

	SourceReferenceFormatter formatter(serr(false), m_coloredOutput, m_withErrorIds);

	try
	{
		if (m_args.count(g_argMetadataLiteral) > 0)
			m_compiler->useMetadataLiteralSources(true);
		if (m_args.count(g_argMetadataHash))
			m_compiler->setMetadataHash(m_metadataHash);
		if (
			m_args.count(g_argModelCheckerContracts) ||
			m_args.count(g_argModelCheckerEngine) ||
			m_args.count(g_argModelCheckerTargets) ||
			m_args.count(g_argModelCheckerTimeout)
		)
			m_compiler->setModelCheckerSettings(m_modelCheckerSettings);
		if (m_args.count(g_argInputFile))
			m_compiler->setRemappings(m_remappings);

		if (m_args.count(g_argLibraries))
			m_compiler->setLibraries(m_libraries);
		if (m_args.count(g_argExperimentalViaIR))
			m_compiler->setViaIR(true);
		m_compiler->setEVMVersion(m_evmVersion);
		m_compiler->setRevertStringBehaviour(m_revertStrings);
		// TODO: Perhaps we should not compile unless requested

		m_compiler->enableIRGeneration(m_args.count(g_argIR) || m_args.count(g_argIROptimized));
		m_compiler->enableEwasmGeneration(m_args.count(g_argEwasm));

		OptimiserSettings settings = m_args.count(g_argOptimize) ? OptimiserSettings::standard() : OptimiserSettings::minimal();
		settings.expectedExecutionsPerDeployment = m_args[g_argOptimizeRuns].as<unsigned>();
		if (m_args.count(g_strNoOptimizeYul))
			settings.runYulOptimiser = false;
		if (m_args.count(g_strYulOptimizations))
		{
			if (!settings.runYulOptimiser)
			{
				serr() << "--" << g_strYulOptimizations << " is invalid if Yul optimizer is disabled" << endl;
				return false;
			}

			try
			{
				yul::OptimiserSuite::validateSequence(m_args[g_strYulOptimizations].as<string>());
			}
			catch (yul::OptimizerException const& _exception)
			{
				serr() << "Invalid optimizer step sequence in --" << g_strYulOptimizations << ": " << _exception.what() << endl;
				return false;
			}

			settings.yulOptimiserSteps = m_args[g_strYulOptimizations].as<string>();
		}
		settings.optimizeStackAllocation = settings.runYulOptimiser;
		m_compiler->setOptimiserSettings(settings);

		if (m_args.count(g_argImportAst))
		{
			try
			{
				m_compiler->importASTs(parseAstFromInput());

				if (!m_compiler->analyze())
				{
					for (auto const& error: m_compiler->errors())
						formatter.printErrorInformation(*error);
					astAssert(false, "Analysis of the AST failed");
				}
			}
			catch (Exception const& _exc)
			{
				serr() << string("Failed to import AST: ") << _exc.what() << endl;
				return false;
			}
		}
		else
		{
			m_compiler->setSources(m_fileReader.sourceCodes());
			if (m_args.count(g_argErrorRecovery))
				m_compiler->setParserErrorRecovery(true);
		}

		bool successful = m_compiler->compile(m_stopAfter);

		for (auto const& error: m_compiler->errors())
		{
			g_hasOutput = true;
			formatter.printErrorInformation(*error);
		}

		if (!successful)
		{
			if (m_args.count(g_argErrorRecovery))
				return true;
			else
				return false;
		}
	}
	catch (CompilerError const& _exception)
	{
		g_hasOutput = true;
		formatter.printExceptionInformation(_exception, "Compiler error");
		return false;
	}
	catch (InternalCompilerError const& _exception)
	{
		serr() <<
			"Internal compiler error during compilation:" <<
			endl <<
			boost::diagnostic_information(_exception);
		return false;
	}
	catch (UnimplementedFeatureError const& _exception)
	{
		serr() <<
			"Unimplemented feature:" <<
			endl <<
			boost::diagnostic_information(_exception);
		return false;
	}
	catch (smtutil::SMTLogicError const& _exception)
	{
		serr() <<
			"SMT logic error during analysis:" <<
			endl <<
			boost::diagnostic_information(_exception);
		return false;
	}
	catch (Error const& _error)
	{
		if (_error.type() == Error::Type::DocstringParsingError)
			serr() << "Documentation parsing error: " << *boost::get_error_info<errinfo_comment>(_error) << endl;
		else
		{
			g_hasOutput = true;
			formatter.printExceptionInformation(_error, _error.typeName());
		}

		return false;
	}
	catch (Exception const& _exception)
	{
		serr() << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl;
		return false;
	}
	catch (std::exception const& _e)
	{
		serr() << "Unknown exception during compilation" << (
			_e.what() ? ": " + string(_e.what()) : "."
		) << endl;
		return false;
	}
	catch (...)
	{
		serr() << "Unknown exception during compilation." << endl;
		return false;
	}

	return true;
}

void CommandLineInterface::handleCombinedJSON()
{
	if (!m_args.count(g_argCombinedJson))
		return;

	Json::Value output(Json::objectValue);

	output[g_strVersion] = frontend::VersionString;
	set<string> requests;
	boost::split(requests, m_args[g_argCombinedJson].as<string>(), boost::is_any_of(","));
	vector<string> contracts = m_compiler->contractNames();

	if (!contracts.empty())
		output[g_strContracts] = Json::Value(Json::objectValue);
	for (string const& contractName: contracts)
	{
		Json::Value& contractData = output[g_strContracts][contractName] = Json::objectValue;
		if (requests.count(g_strAbi))
			contractData[g_strAbi] = m_compiler->contractABI(contractName);
		if (requests.count("metadata"))
			contractData["metadata"] = m_compiler->metadata(contractName);
		if (requests.count(g_strBinary) && m_compiler->compilationSuccessful())
			contractData[g_strBinary] = m_compiler->object(contractName).toHex();
		if (requests.count(g_strBinaryRuntime) && m_compiler->compilationSuccessful())
			contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex();
		if (requests.count(g_strOpcodes) && m_compiler->compilationSuccessful())
			contractData[g_strOpcodes] = evmasm::disassemble(m_compiler->object(contractName).bytecode);
		if (requests.count(g_strAsm) && m_compiler->compilationSuccessful())
			contractData[g_strAsm] = m_compiler->assemblyJSON(contractName);
		if (requests.count(g_strStorageLayout) && m_compiler->compilationSuccessful())
			contractData[g_strStorageLayout] = m_compiler->storageLayout(contractName);
		if (requests.count(g_strGeneratedSources) && m_compiler->compilationSuccessful())
			contractData[g_strGeneratedSources] = m_compiler->generatedSources(contractName, false);
		if (requests.count(g_strGeneratedSourcesRuntime) && m_compiler->compilationSuccessful())
			contractData[g_strGeneratedSourcesRuntime] = m_compiler->generatedSources(contractName, true);
		if (requests.count(g_strSrcMap) && m_compiler->compilationSuccessful())
		{
			auto map = m_compiler->sourceMapping(contractName);
			contractData[g_strSrcMap] = map ? *map : "";
		}
		if (requests.count(g_strSrcMapRuntime) && m_compiler->compilationSuccessful())
		{
			auto map = m_compiler->runtimeSourceMapping(contractName);
			contractData[g_strSrcMapRuntime] = map ? *map : "";
		}
		if (requests.count(g_strFunDebug) && m_compiler->compilationSuccessful())
			contractData[g_strFunDebug] = StandardCompiler::formatFunctionDebugData(
				m_compiler->object(contractName).functionDebugData
			);
		if (requests.count(g_strFunDebugRuntime) && m_compiler->compilationSuccessful())
			contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData(
				m_compiler->runtimeObject(contractName).functionDebugData
			);
		if (requests.count(g_strSignatureHashes))
			contractData[g_strSignatureHashes] = m_compiler->methodIdentifiers(contractName);
		if (requests.count(g_strNatspecDev))
			contractData[g_strNatspecDev] = m_compiler->natspecDev(contractName);
		if (requests.count(g_strNatspecUser))
			contractData[g_strNatspecUser] = m_compiler->natspecUser(contractName);
	}

	bool needsSourceList = requests.count(g_strAst) || requests.count(g_strSrcMap) || requests.count(g_strSrcMapRuntime);
	if (needsSourceList)
	{
		// Indices into this array are used to abbreviate source names in source locations.
		output[g_strSourceList] = Json::Value(Json::arrayValue);

		for (auto const& source: m_compiler->sourceNames())
			output[g_strSourceList].append(source);
	}

	if (requests.count(g_strAst))
	{
		output[g_strSources] = Json::Value(Json::objectValue);
		for (auto const& sourceCode: m_fileReader.sourceCodes())
		{
			ASTJsonConverter converter(m_compiler->state(), m_compiler->sourceIndices());
			output[g_strSources][sourceCode.first] = Json::Value(Json::objectValue);
			output[g_strSources][sourceCode.first]["AST"] = converter.toJson(m_compiler->ast(sourceCode.first));
		}
	}

	string json = m_args.count(g_argPrettyJson) ? jsonPrettyPrint(removeNullMembers(std::move(output))) :
		jsonCompactPrint(removeNullMembers(std::move(output)));

	if (m_args.count(g_argOutputDir))
		createJson("combined", json);
	else
		sout() << json << endl;
}

void CommandLineInterface::handleAst()
{
	if (!m_args.count(g_argAstCompactJson))
		return;

	vector<ASTNode const*> asts;
	for (auto const& sourceCode: m_fileReader.sourceCodes())
		asts.push_back(&m_compiler->ast(sourceCode.first));

	if (m_args.count(g_argOutputDir))
	{
		for (auto const& sourceCode: m_fileReader.sourceCodes())
		{
			stringstream data;
			string postfix = "";
			ASTJsonConverter(m_compiler->state(), m_compiler->sourceIndices()).print(data, m_compiler->ast(sourceCode.first));
			postfix += "_json";
			boost::filesystem::path path(sourceCode.first);
			createFile(path.filename().string() + postfix + ".ast", data.str());
		}
	}
	else
	{
		sout() << "JSON AST (compact format):" << endl << endl;
		for (auto const& sourceCode: m_fileReader.sourceCodes())
		{
			sout() << endl << "======= " << sourceCode.first << " =======" << endl;
			ASTJsonConverter(m_compiler->state(), m_compiler->sourceIndices()).print(sout(), m_compiler->ast(sourceCode.first));
		}
	}
}

bool CommandLineInterface::actOnInput()
{
	if (m_args.count(g_argStandardJSON) || m_onlyAssemble)
		// Already done in "processInput" phase.
		return true;
	else if (m_onlyLink)
		writeLinkedFiles();
	else
		outputCompilationResults();
	return !m_error;
}

bool CommandLineInterface::link()
{
	// Map from how the libraries will be named inside the bytecode to their addresses.
	map<string, h160> librariesReplacements;
	int const placeholderSize = 40; // 20 bytes or 40 hex characters
	for (auto const& library: m_libraries)
	{
		string const& name = library.first;
		// Library placeholders are 40 hex digits (20 bytes) that start and end with '__'.
		// This leaves 36 characters for the library identifier. The identifier used to
		// be just the cropped or '_'-padded library name, but this changed to
		// the cropped hex representation of the hash of the library name.
		// We support both ways of linking here.
		librariesReplacements["__" + evmasm::LinkerObject::libraryPlaceholder(name) + "__"] = library.second;

		string replacement = "__";
		for (size_t i = 0; i < placeholderSize - 4; ++i)
			replacement.push_back(i < name.size() ? name[i] : '_');
		replacement += "__";
		librariesReplacements[replacement] = library.second;
	}

	FileReader::StringMap sourceCodes = m_fileReader.sourceCodes();
	for (auto& src: sourceCodes)
	{
		auto end = src.second.end();
		for (auto it = src.second.begin(); it != end;)
		{
			while (it != end && *it != '_') ++it;
			if (it == end) break;
			if (
				end - it < placeholderSize ||
				*(it + 1) != '_' ||
				*(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;
			}

			string foundPlaceholder(it, it + placeholderSize);
			if (librariesReplacements.count(foundPlaceholder))
			{
				string hexStr(toHex(librariesReplacements.at(foundPlaceholder).asBytes()));
				copy(hexStr.begin(), hexStr.end(), it);
			}
			else
				serr() << "Reference \"" << foundPlaceholder << "\" in file \"" << src.first << "\" still unresolved." << endl;
			it += placeholderSize;
		}
		// Remove hints for resolved libraries.
		for (auto const& library: m_libraries)
			boost::algorithm::erase_all(src.second, "\n" + libraryPlaceholderHint(library.first));
		while (!src.second.empty() && *prev(src.second.end()) == '\n')
			src.second.resize(src.second.size() - 1);
	}
	m_fileReader.setSources(move(sourceCodes));

	return true;
}

void CommandLineInterface::writeLinkedFiles()
{
	for (auto const& src: m_fileReader.sourceCodes())
		if (src.first == g_stdinFileName)
			sout() << src.second << endl;
		else
		{
			ofstream outFile(src.first);
			outFile << src.second;
			if (!outFile)
			{
				serr() << "Could not write to file " << src.first << ". Aborting." << endl;
				return;
			}
		}
	sout() << "Linking completed." << endl;
}

string CommandLineInterface::libraryPlaceholderHint(string const& _libraryName)
{
	return "// " + evmasm::LinkerObject::libraryPlaceholder(_libraryName) + " -> " + _libraryName;
}

string CommandLineInterface::objectWithLinkRefsHex(evmasm::LinkerObject const& _obj)
{
	string out = _obj.toHex();
	if (!_obj.linkReferences.empty())
	{
		out += "\n";
		for (auto const& linkRef: _obj.linkReferences)
			out += "\n" + libraryPlaceholderHint(linkRef.second);
	}
	return out;
}

bool CommandLineInterface::assemble(
	yul::AssemblyStack::Language _language,
	yul::AssemblyStack::Machine _targetMachine,
	bool _optimize,
	optional<string> _yulOptimiserSteps
)
{
	solAssert(_optimize || !_yulOptimiserSteps.has_value(), "");

	bool successful = true;
	map<string, yul::AssemblyStack> assemblyStacks;
	for (auto const& src: m_fileReader.sourceCodes())
	{
		OptimiserSettings settings = _optimize ? OptimiserSettings::full() : OptimiserSettings::minimal();
		if (_yulOptimiserSteps.has_value())
			settings.yulOptimiserSteps = _yulOptimiserSteps.value();

		auto& stack = assemblyStacks[src.first] = yul::AssemblyStack(m_evmVersion, _language, settings);
		try
		{
			if (!stack.parseAndAnalyze(src.first, src.second))
				successful = false;
			else
				stack.optimize();
		}
		catch (Exception const& _exception)
		{
			serr() << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl;
			return false;
		}
		catch (std::exception const& _e)
		{
			serr() <<
				"Unknown exception during compilation" <<
				(_e.what() ? ": " + string(_e.what()) : ".") <<
				endl;
			return false;
		}
		catch (...)
		{
			serr() << "Unknown exception in assembler." << endl;
			return false;
		}
	}

	for (auto const& sourceAndStack: assemblyStacks)
	{
		auto const& stack = sourceAndStack.second;
		SourceReferenceFormatter formatter(serr(false), m_coloredOutput, m_withErrorIds);

		for (auto const& error: stack.errors())
		{
			g_hasOutput = true;
			formatter.printErrorInformation(*error);
		}
		if (!Error::containsOnlyWarnings(stack.errors()))
			successful = false;
	}

	if (!successful)
		return false;

	for (auto const& src: m_fileReader.sourceCodes())
	{
		string machine =
			_targetMachine == yul::AssemblyStack::Machine::EVM ? "EVM" :
			"Ewasm";
		sout() << endl << "======= " << src.first << " (" << machine << ") =======" << endl;

		yul::AssemblyStack& stack = assemblyStacks[src.first];

		sout() << endl << "Pretty printed source:" << endl;
		sout() << stack.print() << endl;

		if (_language != yul::AssemblyStack::Language::Ewasm && _targetMachine == yul::AssemblyStack::Machine::Ewasm)
		{
			try
			{
				stack.translate(yul::AssemblyStack::Language::Ewasm);
				stack.optimize();
			}
			catch (Exception const& _exception)
			{
				serr() << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl;
				return false;
			}
			catch (std::exception const& _e)
			{
				serr() <<
					"Unknown exception during compilation" <<
					(_e.what() ? ": " + string(_e.what()) : ".") <<
					endl;
				return false;
			}
			catch (...)
			{
				serr() << "Unknown exception in assembler." << endl;
				return false;
			}

			sout() << endl << "==========================" << endl;
			sout() << endl << "Translated source:" << endl;
			sout() << stack.print() << endl;
		}

		yul::MachineAssemblyObject object;
		try
		{
			object = stack.assemble(_targetMachine);
			object.bytecode->link(m_libraries);
		}
		catch (Exception const& _exception)
		{
			serr() << "Exception while assembling: " << boost::diagnostic_information(_exception) << endl;
			return false;
		}
		catch (std::exception const& _e)
		{
			serr() << "Unknown exception during compilation" << (
				_e.what() ? ": " + string(_e.what()) : "."
			) << endl;
			return false;
		}
		catch (...)
		{
			serr() << "Unknown exception while assembling." << endl;
			return false;
		}

		sout() << endl << "Binary representation:" << endl;
		if (object.bytecode)
			sout() << object.bytecode->toHex() << endl;
		else
			serr() << "No binary representation found." << endl;

		sout() << endl << "Text representation:" << endl;
		if (!object.assembly.empty())
			sout() << object.assembly << endl;
		else
			serr() << "No text representation found." << endl;
	}

	return true;
}

void CommandLineInterface::outputCompilationResults()
{
	handleCombinedJSON();

	// do we need AST output?
	handleAst();

	if (
		!m_compiler->compilationSuccessful() &&
		m_stopAfter == CompilerStack::State::CompilationSuccessful
	)
	{
		serr() << endl << "Compilation halted after AST generation due to errors." << endl;
		return;
	}

	vector<string> contracts = m_compiler->contractNames();
	for (string const& contract: contracts)
	{
		if (needsHumanTargetedStdout(m_args))
			sout() << endl << "======= " << contract << " =======" << endl;

		// do we need EVM assembly?
		if (m_args.count(g_argAsm) || m_args.count(g_argAsmJson))
		{
			string ret;
			if (m_args.count(g_argAsmJson))
				ret = jsonPrettyPrint(removeNullMembers(m_compiler->assemblyJSON(contract)));
			else
				ret = m_compiler->assemblyString(contract, m_fileReader.sourceCodes());

			if (m_args.count(g_argOutputDir))
			{
				createFile(m_compiler->filesystemFriendlyName(contract) + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), ret);
			}
			else
			{
				sout() << "EVM assembly:" << endl << ret << endl;
			}
		}

		if (m_args.count(g_argGas))
			handleGasEstimation(contract);

		handleBytecode(contract);
		handleIR(contract);
		handleIROptimized(contract);
		handleEwasm(contract);
		handleSignatureHashes(contract);
		handleMetadata(contract);
		handleABI(contract);
		handleStorageLayout(contract);
		handleNatspec(true, contract);
		handleNatspec(false, contract);
	} // end of contracts iteration

	if (!g_hasOutput)
	{
		if (m_args.count(g_argOutputDir))
			sout() << "Compiler run successful. Artifact(s) can be found in directory " << m_args.at(g_argOutputDir).as<string>() << "." << endl;
		else
			serr() << "Compiler run successful, no output requested." << endl;
	}
}

size_t CommandLineInterface::countEnabledOptions(vector<string> const& _optionNames) const
{
	size_t count = 0;
	for (string const& _option: _optionNames)
		count += m_args.count(_option);

	return count;
}

string CommandLineInterface::joinOptionNames(vector<string> const& _optionNames, string _separator)
{
	return boost::algorithm::join(
		_optionNames | ranges::views::transform([](string const& _option){ return "--" + _option; }),
		_separator
	);
}

}
