diff options
Diffstat (limited to 'solc/CommandLineInterface.cpp')
-rw-r--r-- | solc/CommandLineInterface.cpp | 835 |
1 files changed, 533 insertions, 302 deletions
diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 6e59099a..0222ccb0 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -22,28 +22,9 @@ */ #include "CommandLineInterface.h" -#ifdef _WIN32 // windows - #include <io.h> - #define isatty _isatty - #define fileno _fileno -#else // unix - #include <unistd.h> -#endif -#include <string> -#include <iostream> -#include <fstream> - -#include <boost/filesystem.hpp> -#include <boost/filesystem/operations.hpp> -#include <boost/algorithm/string.hpp> - #include "solidity/BuildInfo.h" -#include <libdevcore/Common.h> -#include <libdevcore/CommonData.h> -#include <libdevcore/CommonIO.h> -#include <libdevcore/JSON.h> -#include <libevmasm/Instruction.h> -#include <libevmasm/GasMeter.h> +#include "license.h" + #include <libsolidity/interface/Version.h> #include <libsolidity/parsing/Scanner.h> #include <libsolidity/parsing/Parser.h> @@ -52,10 +33,33 @@ #include <libsolidity/analysis/NameAndTypeResolver.h> #include <libsolidity/interface/Exceptions.h> #include <libsolidity/interface/CompilerStack.h> +#include <libsolidity/interface/StandardCompiler.h> #include <libsolidity/interface/SourceReferenceFormatter.h> #include <libsolidity/interface/GasEstimator.h> -#include <libsolidity/inlineasm/AsmParser.h> -#include <libsolidity/formal/Why3Translator.h> +#include <libsolidity/interface/AssemblyStack.h> + +#include <libevmasm/Instruction.h> +#include <libevmasm/GasMeter.h> + +#include <libdevcore/Common.h> +#include <libdevcore/CommonData.h> +#include <libdevcore/CommonIO.h> +#include <libdevcore/JSON.h> + +#include <boost/filesystem.hpp> +#include <boost/filesystem/operations.hpp> +#include <boost/algorithm/string.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> using namespace std; namespace po = boost::program_options; @@ -65,38 +69,112 @@ namespace dev namespace solidity { -static string const g_argAbiStr = "abi"; -static string const g_argSignatureHashes = "hashes"; -static string const g_argGas = "gas"; -static string const g_argAsmStr = "asm"; -static string const g_argAsmJsonStr = "asm-json"; -static string const g_argAstStr = "ast"; -static string const g_argAstJson = "ast-json"; -static string const g_argBinaryStr = "bin"; -static string const g_argRuntimeBinaryStr = "bin-runtime"; -static string const g_argCloneBinaryStr = "clone-bin"; -static string const g_argOpcodesStr = "opcodes"; -static string const g_argNatspecDevStr = "devdoc"; -static string const g_argNatspecUserStr = "userdoc"; -static string const g_argMetadata = "metadata"; -static string const g_argAddStandard = "add-std"; -static string const g_stdinFileName = "<stdin>"; +static string const g_stdinFileNameStr = "<stdin>"; +static string const g_strAbi = "abi"; +static string const g_strAddStandard = "add-std"; +static string const g_strAllowPaths = "allow-paths"; +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_strAstJson = "ast-json"; +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_strCloneBinary = "clone-bin"; +static string const g_strCombinedJson = "combined-json"; +static string const g_strCompactJSON = "compact-format"; +static string const g_strContracts = "contracts"; +static string const g_strEVM = "evm"; +static string const g_strEVM15 = "evm15"; +static string const g_streWasm = "ewasm"; +static string const g_strFormal = "formal"; +static string const g_strGas = "gas"; +static string const g_strHelp = "help"; +static string const g_strInputFile = "input-file"; +static string const g_strInterface = "interface"; +static string const g_strJulia = "julia"; +static string const g_strLicense = "license"; +static string const g_strLibraries = "libraries"; +static string const g_strLink = "link"; +static string const g_strMetadata = "metadata"; +static string const g_strMetadataLiteral = "metadata-literal"; +static string const g_strNatspecDev = "devdoc"; +static string const g_strNatspecUser = "userdoc"; +static string const g_strOpcodes = "opcodes"; +static string const g_strOptimize = "optimize"; +static string const g_strOptimizeRuns = "optimize-runs"; +static string const g_strOutputDir = "output-dir"; +static string const g_strOverwrite = "overwrite"; +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_strStandardJSON = "standard-json"; +static string const g_strVersion = "version"; + +static string const g_argAbi = g_strAbi; +static string const g_argAddStandard = g_strAddStandard; +static string const g_argAllowPaths = g_strAllowPaths; +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_argAst = g_strAst; +static string const g_argAstCompactJson = g_strAstCompactJson; +static string const g_argAstJson = g_strAstJson; +static string const g_argBinary = g_strBinary; +static string const g_argBinaryRuntime = g_strBinaryRuntime; +static string const g_argCloneBinary = g_strCloneBinary; +static string const g_argCombinedJson = g_strCombinedJson; +static string const g_argCompactJSON = g_strCompactJSON; +static string const g_argFormal = g_strFormal; +static string const g_argGas = g_strGas; +static string const g_argHelp = g_strHelp; +static string const g_argInputFile = g_strInputFile; +static string const g_argJulia = "julia"; +static string const g_argLibraries = g_strLibraries; +static string const g_argLink = g_strLink; +static string const g_argMachine = "machine"; +static string const g_argMetadata = g_strMetadata; +static string const g_argMetadataLiteral = g_strMetadataLiteral; +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_argVersion = g_strVersion; +static string const g_stdinFileName = g_stdinFileNameStr; /// Possible arguments to for --combined-json -static set<string> const g_combinedJsonArgs{ - "bin", - "bin-runtime", - "clone-bin", - "srcmap", - "srcmap-runtime", - "opcodes", - "abi", - "interface", - "metadata", - "asm", - "ast", - "userdoc", - "devdoc" +static set<string> const g_combinedJsonArgs +{ + g_strAbi, + g_strAsm, + g_strAst, + g_strBinary, + g_strBinaryRuntime, + g_strCloneBinary, + g_strCompactJSON, + g_strInterface, + g_strMetadata, + g_strNatspecUser, + g_strNatspecDev, + g_strOpcodes, + g_strSignatureHashes, + g_strSrcMap, + g_strSrcMapRuntime +}; + +/// Possible arguments to for --machine +static set<string> const g_machineArgs +{ + g_strEVM, + g_strEVM15, + g_streWasm }; static void version() @@ -110,26 +188,34 @@ static void version() exit(0); } +static void license() +{ + cout << otherLicenses << endl; + // This is a static variable generated by cmake from LICENSE.txt + cout << licenseText << endl; + exit(0); +} + static bool needsHumanTargetedStdout(po::variables_map const& _args) { if (_args.count(g_argGas)) return true; - if (_args.count("output-dir")) + if (_args.count(g_argOutputDir)) return false; for (string const& arg: { - g_argAbiStr, - g_argSignatureHashes, - g_argMetadata, - g_argNatspecUserStr, + g_argAbi, + g_argAsm, + g_argAsmJson, g_argAstJson, - g_argNatspecDevStr, - g_argAsmStr, - g_argAsmJsonStr, - g_argOpcodesStr, - g_argBinaryStr, - g_argRuntimeBinaryStr, - g_argCloneBinaryStr, - string("formal") + g_argBinary, + g_argBinaryRuntime, + g_argCloneBinary, + g_argFormal, + g_argMetadata, + g_argNatspecUser, + g_argNatspecDev, + g_argOpcodes, + g_argSignatureHashes }) if (_args.count(arg)) return true; @@ -138,30 +224,30 @@ static bool needsHumanTargetedStdout(po::variables_map const& _args) void CommandLineInterface::handleBinary(string const& _contract) { - if (m_args.count(g_argBinaryStr)) + if (m_args.count(g_argBinary)) { - if (m_args.count("output-dir")) - createFile(_contract + ".bin", m_compiler->object(_contract).toHex()); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", m_compiler->object(_contract).toHex()); else { cout << "Binary: " << endl; cout << m_compiler->object(_contract).toHex() << endl; } } - if (m_args.count(g_argCloneBinaryStr)) + if (m_args.count(g_argCloneBinary)) { - if (m_args.count("output-dir")) - createFile(_contract + ".clone_bin", m_compiler->cloneObject(_contract).toHex()); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + ".clone_bin", m_compiler->cloneObject(_contract).toHex()); else { cout << "Clone Binary: " << endl; cout << m_compiler->cloneObject(_contract).toHex() << endl; } } - if (m_args.count(g_argRuntimeBinaryStr)) + if (m_args.count(g_argBinaryRuntime)) { - if (m_args.count("output-dir")) - createFile(_contract + ".bin-runtime", m_compiler->runtimeObject(_contract).toHex()); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin-runtime", m_compiler->runtimeObject(_contract).toHex()); else { cout << "Binary of the runtime part: " << endl; @@ -172,8 +258,8 @@ void CommandLineInterface::handleBinary(string const& _contract) void CommandLineInterface::handleOpcode(string const& _contract) { - if (m_args.count("output-dir")) - createFile(_contract + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode)); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", solidity::disassemble(m_compiler->object(_contract).bytecode)); else { cout << "Opcodes: " << endl; @@ -184,9 +270,9 @@ void CommandLineInterface::handleOpcode(string const& _contract) void CommandLineInterface::handleBytecode(string const& _contract) { - if (m_args.count(g_argOpcodesStr)) + if (m_args.count(g_argOpcodes)) handleOpcode(_contract); - if (m_args.count(g_argBinaryStr) || m_args.count(g_argCloneBinaryStr) || m_args.count(g_argRuntimeBinaryStr)) + if (m_args.count(g_argBinary) || m_args.count(g_argCloneBinary) || m_args.count(g_argBinaryRuntime)) handleBinary(_contract); } @@ -195,12 +281,13 @@ 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& it: m_compiler->contractDefinition(_contract).interfaceFunctions()) - out += toHex(it.first.ref()) + ": " + it.second->externalSignature() + "\n"; + for (auto const& name: methodIdentifiers.getMemberNames()) + out += methodIdentifiers[name].asString() + ": " + name + "\n"; - if (m_args.count("output-dir")) - createFile(_contract + ".signatures", out); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out); else cout << "Function signatures: " << endl << out; } @@ -211,31 +298,38 @@ void CommandLineInterface::handleOnChainMetadata(string const& _contract) return; string data = m_compiler->onChainMetadata(_contract); - if (m_args.count("output-dir")) - createFile(_contract + "_meta.json", data); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + "_meta.json", data); else cout << "Metadata: " << endl << data << endl; } -void CommandLineInterface::handleMeta(DocumentationType _type, string const& _contract) +void CommandLineInterface::handleABI(string const& _contract) +{ + if (!m_args.count(g_argAbi)) + return; + + string data = dev::jsonCompactPrint(m_compiler->contractABI(_contract)); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + ".abi", data); + else + cout << "Contract JSON ABI " << endl << data << endl; +} + +void CommandLineInterface::handleNatspec(DocumentationType _type, string const& _contract) { std::string argName; std::string suffix; std::string title; switch(_type) { - case DocumentationType::ABIInterface: - argName = g_argAbiStr; - suffix = ".abi"; - title = "Contract JSON ABI"; - break; case DocumentationType::NatspecUser: - argName = g_argNatspecUserStr; + argName = g_argNatspecUser; suffix = ".docuser"; title = "User Documentation"; break; case DocumentationType::NatspecDev: - argName = g_argNatspecDevStr; + argName = g_argNatspecDev; suffix = ".docdev"; title = "Developer Documentation"; break; @@ -246,14 +340,10 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co if (m_args.count(argName)) { - std::string output; - if (_type == DocumentationType::ABIInterface) - output = dev::jsonCompactPrint(m_compiler->metadata(_contract, _type)); - else - output = dev::jsonPrettyPrint(m_compiler->metadata(_contract, _type)); + std::string output = dev::jsonPrettyPrint(m_compiler->natspec(_contract, _type)); - if (m_args.count("output-dir")) - createFile(_contract + suffix, output); + if (m_args.count(g_argOutputDir)) + createFile(m_compiler->filesystemFriendlyName(_contract) + suffix, output); else { cout << title << endl; @@ -265,72 +355,51 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co void CommandLineInterface::handleGasEstimation(string const& _contract) { - using Gas = GasEstimator::GasConsumption; - if (!m_compiler->assemblyItems(_contract) && !m_compiler->runtimeAssemblyItems(_contract)) - return; + Json::Value estimates = m_compiler->gasEstimates(_contract); cout << "Gas estimation:" << endl; - if (eth::AssemblyItems const* items = m_compiler->assemblyItems(_contract)) + + if (estimates["creation"].isObject()) { - Gas gas = GasEstimator::functionalEstimation(*items); - u256 bytecodeSize(m_compiler->runtimeObject(_contract).bytecode.size()); + Json::Value creation = estimates["creation"]; cout << "construction:" << endl; - cout << " " << gas << " + " << (bytecodeSize * eth::GasCosts::createDataGas) << " = "; - gas += bytecodeSize * eth::GasCosts::createDataGas; - cout << gas << endl; + cout << " " << creation["executionCost"].asString(); + cout << " + " << creation["codeDepositCost"].asString(); + cout << " = " << creation["totalCost"].asString() << endl; } - if (eth::AssemblyItems const* items = m_compiler->runtimeAssemblyItems(_contract)) + + if (estimates["external"].isObject()) { - ContractDefinition const& contract = m_compiler->contractDefinition(_contract); + Json::Value externalFunctions = estimates["external"]; cout << "external:" << endl; - for (auto it: contract.interfaceFunctions()) + for (auto const& name: externalFunctions.getMemberNames()) { - string sig = it.second->externalSignature(); - GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, sig); - cout << " " << sig << ":\t" << gas << endl; - } - if (contract.fallbackFunction()) - { - GasEstimator::GasConsumption gas = GasEstimator::functionalEstimation(*items, "INVALID"); - cout << " fallback:\t" << gas << endl; + if (name.empty()) + cout << " fallback:\t"; + else + cout << " " << name << ":\t"; + cout << externalFunctions[name].asString() << endl; } + } + + if (estimates["internal"].isObject()) + { + Json::Value internalFunctions = estimates["internal"]; cout << "internal:" << endl; - for (auto const& it: contract.definedFunctions()) + for (auto const& name: internalFunctions.getMemberNames()) { - if (it->isPartOfExternalInterface() || it->isConstructor()) - continue; - size_t entry = m_compiler->functionEntryPoint(_contract, *it); - GasEstimator::GasConsumption gas = GasEstimator::GasConsumption::infinite(); - if (entry > 0) - gas = GasEstimator::functionalEstimation(*items, entry, *it); - FunctionType type(*it); - cout << " " << it->name() << "("; - auto paramTypes = type.parameterTypes(); - for (auto it = paramTypes.begin(); it != paramTypes.end(); ++it) - cout << (*it)->toString() << (it + 1 == paramTypes.end() ? "" : ","); - cout << "):\t" << gas << endl; + cout << " " << name << ":\t"; + cout << internalFunctions[name].asString() << endl; } } } -void CommandLineInterface::handleFormal() -{ - if (!m_args.count("formal")) - return; - - if (m_args.count("output-dir")) - createFile("solidity.mlw", m_compiler->formalTranslation()); - else - cout << "Formal version:" << endl << m_compiler->formalTranslation() << endl; -} - void CommandLineInterface::readInputFilesAndConfigureRemappings() { - vector<string> inputFiles; bool addStdin = false; - if (!m_args.count("input-file")) + if (!m_args.count(g_argInputFile)) addStdin = true; else - for (string path: m_args["input-file"].as<vector<string>>()) + for (string path: m_args[g_argInputFile].as<vector<string>>()) { auto eq = find(path.begin(), path.end(), '='); if (eq != path.end()) @@ -371,14 +440,25 @@ void CommandLineInterface::readInputFilesAndConfigureRemappings() bool CommandLineInterface::parseLibraryOption(string const& _input) { namespace fs = boost::filesystem; - string data = fs::is_regular_file(_input) ? contentsString(_input) : _input; + string data = _input; + try + { + if (fs::is_regular_file(_input)) + data = contentsString(_input); + } + catch (fs::filesystem_error const&) + { + // Thrown e.g. if path is too long. + } 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()) { - auto colon = lib.find(':'); + //search for last colon in string as our binaries output placeholders in the form of file:Name + //so we need to search for the second `:` in the string + auto colon = lib.rfind(':'); if (colon == string::npos) { cerr << "Colon separator missing in library address specifier \"" << lib << "\"" << endl; @@ -388,6 +468,11 @@ bool CommandLineInterface::parseLibraryOption(string const& _input) string addrString(lib.begin() + colon + 1, lib.end()); boost::trim(libName); boost::trim(addrString); + if (!passesAddressChecksum(addrString, false)) + { + cerr << "Invalid checksum on library address \"" << libName << "\": " << addrString << endl; + return false; + } bytes binAddr = fromHex(addrString); h160 address(binAddr, h160::AlignRight); if (binAddr.size() > 20 || address == h160()) @@ -405,9 +490,17 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da { namespace fs = boost::filesystem; // create directory if not existent - fs::path p(m_args.at("output-dir").as<string>()); - fs::create_directories(p); + fs::path p(m_args.at(g_argOutputDir).as<string>()); + // Do not try creating the directory if the first item is . or .. + if (p.filename() != "." && p.filename() != "..") + fs::create_directories(p); string pathName = (p / _fileName).string(); + if (fs::exists(pathName) && !m_args.count(g_strOverwrite)) + { + cerr << "Refusing to overwrite existing file \"" << pathName << "\" (use --overwrite to force)." << endl; + m_error = true; + return; + } ofstream outFile(pathName); outFile << _data; if (!outFile) @@ -417,8 +510,12 @@ void CommandLineInterface::createFile(string const& _fileName, string const& _da bool CommandLineInterface::parseArguments(int _argc, char** _argv) { // Declare the supported options. - po::options_description desc( - R"(solc, the Solidity commandline compiler. + 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 --license' +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 @@ -430,68 +527,92 @@ Example: Allowed options)", po::options_description::m_default_line_length, - po::options_description::m_default_line_length - 23); + po::options_description::m_default_line_length - 23 + ); desc.add_options() - ("help", "Show help message and exit.") - ("version", "Show version and exit.") - ("optimize", "Enable bytecode optimizer.") + (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.") + (g_argOptimize.c_str(), "Enable bytecode optimizer.") ( - "optimize-runs", + g_argOptimizeRuns.c_str(), po::value<unsigned>()->value_name("n")->default_value(200), "Estimated number of contract runs for optimizer tuning." ) (g_argAddStandard.c_str(), "Add standard contracts.") ( - "libraries", + 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 optionally prefixed by 0x." ) ( - "output-dir,o", + (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).") ( - "combined-json", + g_argCombinedJson.c_str(), po::value<string>()->value_name(boost::join(g_combinedJsonArgs, ",")), "Output a single json document containing the specified information." ) (g_argGas.c_str(), "Print an estimate of the maximal gas usage for each function.") ( - "assemble", - "Switch to assembly mode, ignoring all options and assumes input is assembly." + g_argStandardJSON.c_str(), + "Switch to Standard JSON input / output mode, ignoring all options. " + "It reads from standard input and provides the result on the standard output." ) ( - "link", + g_argAssemble.c_str(), + "Switch to assembly mode, ignoring all options except --machine and assumes input is assembly." + ) + ( + g_argJulia.c_str(), + "Switch to JULIA mode, ignoring all options except --machine and assumes input is JULIA." + ) + ( + g_argMachine.c_str(), + po::value<string>()->value_name(boost::join(g_machineArgs, ",")), + "Target machine in assembly or JULIA mode." + ) + ( + g_argLink.c_str(), "Switch to linker mode, ignoring all options apart from --libraries " "and modify binaries in place." + ) + (g_argMetadataLiteral.c_str(), "Store referenced sources are literal data in the metadata output.") + ( + 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." ); po::options_description outputComponents("Output Components"); outputComponents.add_options() - (g_argAstStr.c_str(), "AST of all source files.") + (g_argAst.c_str(), "AST of all source files.") (g_argAstJson.c_str(), "AST of all source files in JSON format.") - (g_argAsmStr.c_str(), "EVM assembly of the contracts.") - (g_argAsmJsonStr.c_str(), "EVM assembly of the contracts in JSON format.") - (g_argOpcodesStr.c_str(), "Opcodes of the contracts.") - (g_argBinaryStr.c_str(), "Binary of the contracts in hex.") - (g_argRuntimeBinaryStr.c_str(), "Binary of the runtime part of the contracts in hex.") - (g_argCloneBinaryStr.c_str(), "Binary of the clone contracts in hex.") - (g_argAbiStr.c_str(), "ABI specification of the contracts.") + (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_argCloneBinary.c_str(), "Binary of the clone contracts in hex.") + (g_argAbi.c_str(), "ABI specification of the contracts.") (g_argSignatureHashes.c_str(), "Function signature hashes of the contracts.") - (g_argNatspecUserStr.c_str(), "Natspec user documentation of all contracts.") - (g_argNatspecDevStr.c_str(), "Natspec developer documentation of all 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.") - ("formal", "Translated source suitable for formal analysis."); + (g_argFormal.c_str(), "Translated source suitable for formal analysis."); desc.add(outputComponents); po::options_description allOptions = desc; - allOptions.add_options()("input-file", po::value<vector<string>>(), "input file"); + 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("input-file", -1); + filesPositions.add(g_argInputFile.c_str(), -1); // parse the compiler arguments try @@ -506,22 +627,28 @@ Allowed options)", return false; } - if (m_args.count("help") || (isatty(fileno(stdin)) && _argc == 1)) + if (m_args.count(g_argHelp) || (isatty(fileno(stdin)) && _argc == 1)) { cout << desc; return false; } - if (m_args.count("version")) + if (m_args.count(g_argVersion)) { version(); return false; } - if (m_args.count("combined-json")) + if (m_args.count(g_strLicense)) + { + license(); + return false; + } + + if (m_args.count(g_argCombinedJson)) { vector<string> requests; - for (string const& item: boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(","))) + for (string const& item: boost::split(requests, m_args[g_argCombinedJson].as<string>(), boost::is_any_of(","))) if (!g_combinedJsonArgs.count(item)) { cerr << "Invalid option to --combined-json: " << item << endl; @@ -535,74 +662,123 @@ Allowed options)", bool CommandLineInterface::processInput() { + ReadFile::Callback fileReader = [this](string const& _path) + { + try + { + auto path = boost::filesystem::path(_path); + auto canonicalPath = boost::filesystem::canonical(path); + bool isAllowed = false; + for (auto const& allowedDir: m_allowedDirectories) + { + // If dir is a prefix of boostPath, we are fine. + if ( + std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) && + std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin()) + ) + { + isAllowed = true; + break; + } + } + if (!isAllowed) + return ReadFile::Result{false, "File outside of allowed directories."}; + else if (!boost::filesystem::exists(path)) + return ReadFile::Result{false, "File not found."}; + else if (!boost::filesystem::is_regular_file(canonicalPath)) + return ReadFile::Result{false, "Not a valid file."}; + else + { + auto contents = dev::contentsString(canonicalPath.string()); + m_sourceCodes[path.string()] = contents; + return ReadFile::Result{true, contents}; + } + } + catch (Exception const& _exception) + { + return ReadFile::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)}; + } + catch (...) + { + return ReadFile::Result{false, "Unknown exception in read callback."}; + } + }; + + 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(","))) + m_allowedDirectories.push_back(boost::filesystem::path(path)); + } + + if (m_args.count(g_argStandardJSON)) + { + string input; + while (!cin.eof()) + { + string tmp; + getline(cin, tmp); + input.append(tmp + "\n"); + } + StandardCompiler compiler(fileReader); + cout << compiler.compile(input) << endl; + return true; + } + readInputFilesAndConfigureRemappings(); - if (m_args.count("libraries")) - for (string const& library: m_args["libraries"].as<vector<string>>()) + 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("assemble")) + if (m_args.count(g_argAssemble) || m_args.count(g_argJulia)) { // switch to assembly mode m_onlyAssemble = true; - return assemble(); + using Input = AssemblyStack::Language; + using Machine = AssemblyStack::Machine; + Input inputLanguage = m_args.count(g_argJulia) ? Input::JULIA : Input::Assembly; + Machine targetMachine = Machine::EVM; + 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_strEVM15) + targetMachine = Machine::EVM15; + else if (machine == g_streWasm) + targetMachine = Machine::eWasm; + else + { + cerr << "Invalid option for --machine: " << machine << endl; + return false; + } + } + return assemble(inputLanguage, targetMachine); } - if (m_args.count("link")) + if (m_args.count(g_argLink)) { // switch to linker mode m_onlyLink = true; return link(); } - CompilerStack::ReadFileCallback fileReader = [this](string const& _path) - { - auto path = boost::filesystem::path(_path); - if (!boost::filesystem::exists(path)) - return CompilerStack::ReadFileResult{false, "File not found."}; - auto canonicalPath = boost::filesystem::canonical(path); - bool isAllowed = false; - for (auto const& allowedDir: m_allowedDirectories) - { - // If dir is a prefix of boostPath, we are fine. - if ( - std::distance(allowedDir.begin(), allowedDir.end()) <= std::distance(canonicalPath.begin(), canonicalPath.end()) && - std::equal(allowedDir.begin(), allowedDir.end(), canonicalPath.begin()) - ) - { - isAllowed = true; - break; - } - } - if (!isAllowed) - return CompilerStack::ReadFileResult{false, "File outside of allowed directories."}; - else if (!boost::filesystem::is_regular_file(canonicalPath)) - return CompilerStack::ReadFileResult{false, "Not a valid file."}; - else - { - auto contents = dev::contentsString(canonicalPath.string()); - m_sourceCodes[path.string()] = contents; - return CompilerStack::ReadFileResult{true, contents}; - } - }; - m_compiler.reset(new CompilerStack(fileReader)); auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); }; try { - if (m_args.count("input-file")) - m_compiler->setRemappings(m_args["input-file"].as<vector<string>>()); + if (m_args.count(g_argMetadataLiteral) > 0) + m_compiler->useMetadataLiteralSources(true); + if (m_args.count(g_argInputFile)) + m_compiler->setRemappings(m_args[g_argInputFile].as<vector<string>>()); for (auto const& sourceCode: m_sourceCodes) m_compiler->addSource(sourceCode.first, sourceCode.second); // TODO: Perhaps we should not compile unless requested - bool optimize = m_args.count("optimize") > 0; - unsigned runs = m_args["optimize-runs"].as<unsigned>(); + bool optimize = m_args.count(g_argOptimize) > 0; + unsigned runs = m_args[g_argOptimizeRuns].as<unsigned>(); bool successful = m_compiler->compile(optimize, runs, m_libraries); - if (successful && m_args.count("formal")) - if (!m_compiler->prepareFormalAnalysis()) - successful = false; - for (auto const& error: m_compiler->errors()) SourceReferenceFormatter::printExceptionInformation( cerr, @@ -656,73 +832,76 @@ bool CommandLineInterface::processInput() void CommandLineInterface::handleCombinedJSON() { - if (!m_args.count("combined-json")) + if (!m_args.count(g_argCombinedJson)) return; Json::Value output(Json::objectValue); - output["version"] = ::dev::solidity::VersionString; + output[g_strVersion] = ::dev::solidity::VersionString; set<string> requests; - boost::split(requests, m_args["combined-json"].as<string>(), boost::is_any_of(",")); + boost::split(requests, m_args[g_argCombinedJson].as<string>(), boost::is_any_of(",")); vector<string> contracts = m_compiler->contractNames(); if (!contracts.empty()) - output["contracts"] = Json::Value(Json::objectValue); + output[g_strContracts] = Json::Value(Json::objectValue); for (string const& contractName: contracts) { Json::Value contractData(Json::objectValue); - if (requests.count("abi")) - contractData["abi"] = dev::jsonCompactPrint(m_compiler->interface(contractName)); + if (requests.count(g_strAbi)) + contractData[g_strAbi] = dev::jsonCompactPrint(m_compiler->contractABI(contractName)); if (requests.count("metadata")) contractData["metadata"] = m_compiler->onChainMetadata(contractName); - if (requests.count("bin")) - contractData["bin"] = m_compiler->object(contractName).toHex(); - if (requests.count("bin-runtime")) - contractData["bin-runtime"] = m_compiler->runtimeObject(contractName).toHex(); - if (requests.count("clone-bin")) - contractData["clone-bin"] = m_compiler->cloneObject(contractName).toHex(); - if (requests.count("opcodes")) - contractData["opcodes"] = solidity::disassemble(m_compiler->object(contractName).bytecode); - if (requests.count("asm")) + if (requests.count(g_strBinary)) + contractData[g_strBinary] = m_compiler->object(contractName).toHex(); + if (requests.count(g_strBinaryRuntime)) + contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex(); + if (requests.count(g_strCloneBinary)) + contractData[g_strCloneBinary] = m_compiler->cloneObject(contractName).toHex(); + if (requests.count(g_strOpcodes)) + contractData[g_strOpcodes] = solidity::disassemble(m_compiler->object(contractName).bytecode); + if (requests.count(g_strAsm)) { ostringstream unused; - contractData["asm"] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true); + contractData[g_strAsm] = m_compiler->streamAssembly(unused, contractName, m_sourceCodes, true); } - if (requests.count("srcmap")) + if (requests.count(g_strSrcMap)) { auto map = m_compiler->sourceMapping(contractName); - contractData["srcmap"] = map ? *map : ""; + contractData[g_strSrcMap] = map ? *map : ""; } - if (requests.count("srcmap-runtime")) + if (requests.count(g_strSrcMapRuntime)) { auto map = m_compiler->runtimeSourceMapping(contractName); - contractData["srcmap-runtime"] = map ? *map : ""; + contractData[g_strSrcMapRuntime] = map ? *map : ""; } - if (requests.count("devdoc")) - contractData["devdoc"] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecDev)); - if (requests.count("userdoc")) - contractData["userdoc"] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecUser)); - output["contracts"][contractName] = contractData; + if (requests.count(g_strSignatureHashes)) + contractData[g_strSignatureHashes] = m_compiler->methodIdentifiers(contractName); + if (requests.count(g_strNatspecDev)) + contractData[g_strNatspecDev] = dev::jsonCompactPrint(m_compiler->natspec(contractName, DocumentationType::NatspecDev)); + if (requests.count(g_strNatspecUser)) + contractData[g_strNatspecUser] = dev::jsonCompactPrint(m_compiler->natspec(contractName, DocumentationType::NatspecUser)); + output[g_strContracts][contractName] = contractData; } - bool needsSourceList = requests.count("ast") || requests.count("srcmap") || requests.count("srcmap-runtime"); + 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["sourceList"] = Json::Value(Json::arrayValue); + output[g_strSourceList] = Json::Value(Json::arrayValue); for (auto const& source: m_compiler->sourceNames()) - output["sourceList"].append(source); + output[g_strSourceList].append(source); } - if (requests.count("ast")) + if (requests.count(g_strAst)) { - output["sources"] = Json::Value(Json::objectValue); + bool legacyFormat = !requests.count(g_strCompactJSON); + output[g_strSources] = Json::Value(Json::objectValue); for (auto const& sourceCode: m_sourceCodes) { - ASTJsonConverter converter(m_compiler->ast(sourceCode.first), m_compiler->sourceIndices()); - output["sources"][sourceCode.first] = Json::Value(Json::objectValue); - output["sources"][sourceCode.first]["AST"] = converter.json(); + ASTJsonConverter converter(legacyFormat, 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)); } } cout << dev::jsonCompactPrint(output) << endl; @@ -732,10 +911,12 @@ void CommandLineInterface::handleAst(string const& _argStr) { string title; - if (_argStr == g_argAstStr) + if (_argStr == g_argAst) title = "Syntax trees:"; else if (_argStr == g_argAstJson) title = "JSON AST:"; + else if (_argStr == g_argAstCompactJson) + title = "JSON AST (compact format):"; else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Illegal argStr for AST")); @@ -752,21 +933,21 @@ void CommandLineInterface::handleAst(string const& _argStr) asts ); - if (m_args.count("output-dir")) + bool legacyFormat = !m_args.count(g_argAstCompactJson); + if (m_args.count(g_argOutputDir)) { for (auto const& sourceCode: m_sourceCodes) { stringstream data; string postfix = ""; - if (_argStr == g_argAstStr) + if (_argStr == g_argAst) { ASTPrinter printer(m_compiler->ast(sourceCode.first), sourceCode.second); printer.print(data); } else { - ASTJsonConverter converter(m_compiler->ast(sourceCode.first)); - converter.print(data); + ASTJsonConverter(legacyFormat, m_compiler->sourceIndices()).print(data, m_compiler->ast(sourceCode.first)); postfix += "_json"; } boost::filesystem::path path(sourceCode.first); @@ -779,7 +960,7 @@ void CommandLineInterface::handleAst(string const& _argStr) for (auto const& sourceCode: m_sourceCodes) { cout << endl << "======= " << sourceCode.first << " =======" << endl; - if (_argStr == g_argAstStr) + if (_argStr == g_argAst) { ASTPrinter printer( m_compiler->ast(sourceCode.first), @@ -789,23 +970,22 @@ void CommandLineInterface::handleAst(string const& _argStr) printer.print(cout); } else - { - ASTJsonConverter converter(m_compiler->ast(sourceCode.first)); - converter.print(cout); - } + ASTJsonConverter(legacyFormat, m_compiler->sourceIndices()).print(cout, m_compiler->ast(sourceCode.first)); } } } } -void CommandLineInterface::actOnInput() +bool CommandLineInterface::actOnInput() { - if (m_onlyAssemble) - outputAssembly(); + 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() @@ -861,42 +1041,91 @@ void CommandLineInterface::writeLinkedFiles() writeFile(src.first, src.second); } -bool CommandLineInterface::assemble() +bool CommandLineInterface::assemble( + AssemblyStack::Language _language, + AssemblyStack::Machine _targetMachine +) { - //@TODO later, we will use the convenience interface and should also remove the include above bool successful = true; - map<string, shared_ptr<Scanner>> scanners; + map<string, AssemblyStack> assemblyStacks; for (auto const& src: m_sourceCodes) { - auto scanner = make_shared<Scanner>(CharStream(src.second), src.first); - scanners[src.first] = scanner; - if (!m_assemblyStacks[src.first].parse(scanner)) - successful = false; - else - //@TODO we should not just throw away the result here - m_assemblyStacks[src.first].assemble(); + auto& stack = assemblyStacks[src.first] = AssemblyStack(_language); + try + { + if (!stack.parseAndAnalyze(src.first, src.second)) + successful = false; + } + catch (Exception const& _exception) + { + cerr << "Exception in assembler: " << boost::diagnostic_information(_exception) << endl; + return false; + } + catch (...) + { + cerr << "Unknown exception in assembler." << endl; + return false; + } } - for (auto const& stack: m_assemblyStacks) - for (auto const& error: stack.second.errors()) + for (auto const& sourceAndStack: assemblyStacks) + { + auto const& stack = sourceAndStack.second; + for (auto const& error: stack.errors()) SourceReferenceFormatter::printExceptionInformation( cerr, *error, (error->type() == Error::Type::Warning) ? "Warning" : "Error", - [&](string const& _source) -> Scanner const& { return *scanners.at(_source); } + [&](string const&) -> Scanner const& { return stack.scanner(); } ); + if (!Error::containsOnlyWarnings(stack.errors())) + successful = false; + } - return successful; -} + if (!successful) + return false; -void CommandLineInterface::outputAssembly() -{ for (auto const& src: m_sourceCodes) { - cout << endl << "======= " << src.first << " =======" << endl; - eth::Assembly assembly = m_assemblyStacks[src.first].assemble(); - cout << assembly.assemble().toHex() << endl; - assembly.stream(cout, "", m_sourceCodes); + string machine = + _targetMachine == AssemblyStack::Machine::EVM ? "EVM" : + _targetMachine == AssemblyStack::Machine::EVM15 ? "EVM 1.5" : + "eWasm"; + cout << endl << "======= " << src.first << " (" << machine << ") =======" << endl; + AssemblyStack& stack = assemblyStacks[src.first]; + + cout << endl << "Pretty printed source:" << endl; + cout << stack.print() << endl; + + MachineAssemblyObject object; + try + { + object = stack.assemble(_targetMachine); + } + catch (Exception const& _exception) + { + cerr << "Exception while assembling: " << boost::diagnostic_information(_exception) << endl; + return false; + } + catch (...) + { + cerr << "Unknown exception while assembling." << endl; + return false; + } + + cout << endl << "Binary representation:" << endl; + if (object.bytecode) + cout << object.bytecode->toHex() << endl; + else + cerr << "No binary representation found." << endl; + + cout << endl << "Text representation:" << endl; + if (!object.assembly.empty()) + cout << object.assembly << endl; + else + cerr << "No text representation found." << endl; } + + return true; } void CommandLineInterface::outputCompilationResults() @@ -904,8 +1133,9 @@ void CommandLineInterface::outputCompilationResults() handleCombinedJSON(); // do we need AST output? - handleAst(g_argAstStr); + handleAst(g_argAst); handleAst(g_argAstJson); + handleAst(g_argAstCompactJson); vector<string> contracts = m_compiler->contractNames(); for (string const& contract: contracts) @@ -914,18 +1144,18 @@ void CommandLineInterface::outputCompilationResults() cout << endl << "======= " << contract << " =======" << endl; // do we need EVM assembly? - if (m_args.count(g_argAsmStr) || m_args.count(g_argAsmJsonStr)) + if (m_args.count(g_argAsm) || m_args.count(g_argAsmJson)) { - if (m_args.count("output-dir")) + if (m_args.count(g_argOutputDir)) { stringstream data; - m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr)); - createFile(contract + (m_args.count(g_argAsmJsonStr) ? "_evm.json" : ".evm"), data.str()); + m_compiler->streamAssembly(data, contract, m_sourceCodes, m_args.count(g_argAsmJson)); + createFile(m_compiler->filesystemFriendlyName(contract) + (m_args.count(g_argAsmJson) ? "_evm.json" : ".evm"), data.str()); } else { cout << "EVM assembly:" << endl; - m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJsonStr)); + m_compiler->streamAssembly(cout, contract, m_sourceCodes, m_args.count(g_argAsmJson)); } } @@ -935,12 +1165,13 @@ void CommandLineInterface::outputCompilationResults() handleBytecode(contract); handleSignatureHashes(contract); handleOnChainMetadata(contract); - handleMeta(DocumentationType::ABIInterface, contract); - handleMeta(DocumentationType::NatspecDev, contract); - handleMeta(DocumentationType::NatspecUser, contract); + handleABI(contract); + handleNatspec(DocumentationType::NatspecDev, contract); + handleNatspec(DocumentationType::NatspecUser, contract); } // end of contracts iteration - handleFormal(); + if (m_args.count(g_argFormal)) + cerr << "Support for the Why3 output was removed." << endl; } } |