/* 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 . */ // SPDX-License-Identifier: GPL-3.0 /** * @author Lefteris * @author Gav Wood * @date 2014 * Solidity command line interface. */ #include #include #include "license.h" #include "solidity/BuildInfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef _WIN32 // windows #include #define isatty _isatty #define fileno _fileno #else // unix #include #endif #include #if !defined(STDERR_FILENO) #define STDERR_FILENO 2 #endif using namespace std; using namespace solidity; using namespace solidity::util; using namespace solidity::langutil; namespace { set const CompilerInputModes{ frontend::InputMode::Compiler, frontend::InputMode::CompilerWithASTImport, }; } // anonymous namespace namespace solidity::frontend { ostream& CommandLineInterface::sout(bool _markAsUsed) { if (_markAsUsed) m_hasOutput = true; return m_sout; } ostream& CommandLineInterface::serr(bool _markAsUsed) { if (_markAsUsed) m_hasOutput = true; return m_serr; } #define cin #define cout #define cerr static string const g_stdinFileName = ""; static string const g_strAbi = "abi"; static string const g_strAsm = "asm"; static string const g_strAst = "ast"; static string const g_strBinary = "bin"; static string const g_strBinaryRuntime = "bin-runtime"; static string const g_strContracts = "contracts"; static string const g_strFunDebug = "function-debug"; static string const g_strFunDebugRuntime = "function-debug-runtime"; static string const g_strGeneratedSources = "generated-sources"; static string const g_strGeneratedSourcesRuntime = "generated-sources-runtime"; static string const g_strNatspecDev = "devdoc"; static string const g_strNatspecUser = "userdoc"; static string const g_strOpcodes = "opcodes"; static string const g_strSignatureHashes = "hashes"; static string const g_strSourceList = "sourceList"; static string const g_strSources = "sources"; static string const g_strSrcMap = "srcmap"; static string const g_strSrcMapRuntime = "srcmap-runtime"; static string const g_strStorageLayout = "storage-layout"; static string const g_strVersion = "version"; static bool needsHumanTargetedStdout(CommandLineOptions const& _options) { if (_options.compiler.estimateGas) return true; if (!_options.output.dir.empty()) return false; return _options.compiler.outputs.abi || _options.compiler.outputs.asm_ || _options.compiler.outputs.asmJson || _options.compiler.outputs.binary || _options.compiler.outputs.binaryRuntime || _options.compiler.outputs.metadata || _options.compiler.outputs.natspecUser || _options.compiler.outputs.natspecDev || _options.compiler.outputs.opcodes || _options.compiler.outputs.signatureHashes || _options.compiler.outputs.storageLayout; } static bool coloredOutput(CommandLineOptions const& _options) { return (!_options.formatting.coloredOutput.has_value() && isatty(STDERR_FILENO)) || (_options.formatting.coloredOutput.has_value() && _options.formatting.coloredOutput.value()); } void CommandLineInterface::handleBinary(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (m_options.compiler.outputs.binary) { if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", objectWithLinkRefsHex(m_compiler->object(_contract))); else { sout() << "Binary:" << endl; sout() << objectWithLinkRefsHex(m_compiler->object(_contract)) << endl; } } if (m_options.compiler.outputs.binaryRuntime) { if (!m_options.output.dir.empty()) 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) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + ".opcode", evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion)); else { sout() << "Opcodes:" << endl; sout() << uppercase << evmasm::disassemble(m_compiler->object(_contract).bytecode, m_options.output.evmVersion); sout() << endl; } } void CommandLineInterface::handleIR(string const& _contractName) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.ir) return; if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contractName) + ".yul", m_compiler->yulIR(_contractName)); else { sout() << "IR:" << endl; sout() << m_compiler->yulIR(_contractName) << endl; } } void CommandLineInterface::handleIRAst(string const& _contractName) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.irAstJson) return; if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_yul_ast.json", util::jsonPrint( m_compiler->yulIRAst(_contractName), m_options.formatting.json ) ); else { sout() << "IR AST:" << endl; sout() << util::jsonPrint( m_compiler->yulIRAst(_contractName), m_options.formatting.json ) << endl; } } void CommandLineInterface::handleIROptimized(string const& _contractName) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.irOptimized) return; if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_opt.yul", m_compiler->yulIROptimized(_contractName) ); else { sout() << "Optimized IR:" << endl; sout() << m_compiler->yulIROptimized(_contractName) << endl; } } void CommandLineInterface::handleIROptimizedAst(string const& _contractName) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.irOptimizedAstJson) return; if (!m_options.output.dir.empty()) createFile( m_compiler->filesystemFriendlyName(_contractName) + "_opt_yul_ast.json", util::jsonPrint( m_compiler->yulIROptimizedAst(_contractName), m_options.formatting.json ) ); else { sout() << "Optimized IR AST:" << endl; sout() << util::jsonPrint( m_compiler->yulIROptimizedAst(_contractName), m_options.formatting.json ) << endl; } } void CommandLineInterface::handleBytecode(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (m_options.compiler.outputs.opcodes) handleOpcode(_contract); if (m_options.compiler.outputs.binary || m_options.compiler.outputs.binaryRuntime) handleBinary(_contract); } void CommandLineInterface::handleSignatureHashes(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.signatureHashes) return; Json::Value interfaceSymbols = m_compiler->interfaceSymbols(_contract); string out = "Function signatures:\n"; for (auto const& name: interfaceSymbols["methods"].getMemberNames()) out += interfaceSymbols["methods"][name].asString() + ": " + name + "\n"; if (interfaceSymbols.isMember("errors")) { out += "\nError signatures:\n"; for (auto const& name: interfaceSymbols["errors"].getMemberNames()) out += interfaceSymbols["errors"][name].asString() + ": " + name + "\n"; } if (interfaceSymbols.isMember("events")) { out += "\nEvent signatures:\n"; for (auto const& name: interfaceSymbols["events"].getMemberNames()) out += interfaceSymbols["events"][name].asString() + ": " + name + "\n"; } if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + ".signatures", out); else sout() << out; } void CommandLineInterface::handleMetadata(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.metadata) return; string data = m_compiler->metadata(_contract); if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + "_meta.json", data); else sout() << "Metadata:" << endl << data << endl; } void CommandLineInterface::handleABI(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.abi) return; string data = jsonPrint(removeNullMembers(m_compiler->contractABI(_contract)), m_options.formatting.json); if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + ".abi", data); else sout() << "Contract JSON ABI" << endl << data << endl; } void CommandLineInterface::handleStorageLayout(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.storageLayout) return; string data = jsonPrint(removeNullMembers(m_compiler->storageLayout(_contract)), m_options.formatting.json); if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + "_storage.json", data); else sout() << "Contract Storage Layout:" << endl << data << endl; } void CommandLineInterface::handleNatspec(bool _natspecDev, string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); bool enabled = false; string suffix; string title; if (_natspecDev) { enabled = m_options.compiler.outputs.natspecDev; suffix = ".docdev"; title = "Developer Documentation"; } else { enabled = m_options.compiler.outputs.natspecUser; suffix = ".docuser"; title = "User Documentation"; } if (enabled) { string output = jsonPrint( removeNullMembers( _natspecDev ? m_compiler->natspecDev(_contract) : m_compiler->natspecUser(_contract) ), m_options.formatting.json ); if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(_contract) + suffix, output); else { sout() << title << endl; sout() << output << endl; } } } void CommandLineInterface::handleGasEstimation(string const& _contract) { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); 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; } } } void CommandLineInterface::readInputFiles() { solAssert(!m_standardJsonInput.has_value()); static set const noInputFiles{ frontend::InputMode::Help, frontend::InputMode::License, frontend::InputMode::Version }; if (noInputFiles.count(m_options.input.mode) == 1) return; m_fileReader.setBasePath(m_options.input.basePath); if (m_fileReader.basePath() != "") { if (!boost::filesystem::exists(m_fileReader.basePath())) solThrow(CommandLineValidationError, "Base path does not exist: \"" + m_fileReader.basePath().string() + '"'); if (!boost::filesystem::is_directory(m_fileReader.basePath())) solThrow(CommandLineValidationError, "Base path is not a directory: \"" + m_fileReader.basePath().string() + '"'); } for (boost::filesystem::path const& includePath: m_options.input.includePaths) m_fileReader.addIncludePath(includePath); for (boost::filesystem::path const& allowedDirectory: m_options.input.allowedDirectories) m_fileReader.allowDirectory(allowedDirectory); map> collisions = m_fileReader.detectSourceUnitNameCollisions(m_options.input.paths); if (!collisions.empty()) { auto pathToQuotedString = [](boost::filesystem::path const& _path){ return "\"" + _path.string() + "\""; }; string message = "Source unit name collision detected. " "The specified values of base path and/or include paths would result in multiple " "input files being assigned the same source unit name:\n"; for (auto const& [sourceUnitName, normalizedInputPaths]: collisions) { message += sourceUnitName + " matches: "; message += util::joinHumanReadable(normalizedInputPaths | ranges::views::transform(pathToQuotedString)) + "\n"; } solThrow(CommandLineValidationError, message); } for (boost::filesystem::path const& infile: m_options.input.paths) { if (!boost::filesystem::exists(infile)) { if (!m_options.input.ignoreMissingFiles) solThrow(CommandLineValidationError, '"' + infile.string() + "\" is not found."); else serr() << infile << " is not found. Skipping." << endl; continue; } if (!boost::filesystem::is_regular_file(infile)) { if (!m_options.input.ignoreMissingFiles) solThrow(CommandLineValidationError, '"' + infile.string() + "\" is not a valid file."); else serr() << infile << " is not a valid file. Skipping." << endl; continue; } // NOTE: we ignore the FileNotFound exception as we manually check above string fileContent = readFileAsString(infile); if (m_options.input.mode == InputMode::StandardJson) { solAssert(!m_standardJsonInput.has_value()); m_standardJsonInput = std::move(fileContent); } else { m_fileReader.addOrUpdateFile(infile, std::move(fileContent)); m_fileReader.allowDirectory(boost::filesystem::canonical(infile).remove_filename()); } } if (m_options.input.addStdin) { if (m_options.input.mode == InputMode::StandardJson) { solAssert(!m_standardJsonInput.has_value()); m_standardJsonInput = readUntilEnd(m_sin); } else m_fileReader.setStdin(readUntilEnd(m_sin)); } if ( m_options.input.mode != InputMode::LanguageServer && m_fileReader.sourceUnits().empty() && !m_standardJsonInput.has_value() ) solThrow(CommandLineValidationError, "All specified input files either do not exist or are not regular files."); } map CommandLineInterface::parseAstFromInput() { solAssert(m_options.input.mode == InputMode::CompilerWithASTImport); map sourceJsons; map tmpSources; for (SourceCode const& sourceCode: m_fileReader.sourceUnits() | 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()) { 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, std::move(ast["sources"][src][astKey])); tmpSources[src] = util::jsonCompactPrint(ast); } } m_fileReader.setSourceUnits(tmpSources); return sourceJsons; } void CommandLineInterface::createFile(string const& _fileName, string const& _data) { namespace fs = boost::filesystem; solAssert(!m_options.output.dir.empty()); // 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(m_options.output.dir)); string pathName = (m_options.output.dir / _fileName).string(); if (fs::exists(pathName) && !m_options.output.overwriteFiles) solThrow(CommandLineOutputError, "Refusing to overwrite existing file \"" + pathName + "\" (use --overwrite to force)."); ofstream outFile(pathName); outFile << _data; if (!outFile) solThrow(CommandLineOutputError, "Could not write to file \"" + pathName + "\"."); } void CommandLineInterface::createJson(string const& _fileName, string const& _json) { createFile(boost::filesystem::path(_fileName).stem().string() + string(".json"), _json); } bool CommandLineInterface::run(int _argc, char const* const* _argv) { try { if (!parseArguments(_argc, _argv)) return false; readInputFiles(); processInput(); return true; } catch (CommandLineError const& _exception) { m_hasOutput = true; // There might be no message in the exception itself if the error output is bulky and has // already been printed to stderr (this happens e.g. for compiler errors). if (_exception.what() != ""s) serr() << _exception.what() << endl; return false; } } bool CommandLineInterface::parseArguments(int _argc, char const* const* _argv) { CommandLineParser parser; if (isatty(fileno(stdin)) && _argc == 1) { // If the terminal is taking input from the user, provide more user-friendly output. CommandLineParser::printHelp(sout()); // In this case we want to exit with an error but not display any error message. return false; } parser.parse(_argc, _argv); m_options = parser.options(); return true; } void CommandLineInterface::processInput() { switch (m_options.input.mode) { case InputMode::Help: CommandLineParser::printHelp(sout()); break; case InputMode::License: printLicense(); break; case InputMode::Version: printVersion(); break; case InputMode::StandardJson: { solAssert(m_standardJsonInput.has_value()); StandardCompiler compiler(m_universalCallback.callback(), m_options.formatting.json); sout() << compiler.compile(std::move(m_standardJsonInput.value())) << endl; m_standardJsonInput.reset(); break; } case InputMode::LanguageServer: serveLSP(); break; case InputMode::Assembler: assembleYul(m_options.assembly.inputLanguage, m_options.assembly.targetMachine); break; case InputMode::Linker: link(); writeLinkedFiles(); break; case InputMode::Compiler: case InputMode::CompilerWithASTImport: compile(); outputCompilationResults(); break; } } void CommandLineInterface::printVersion() { sout() << "solc, the solidity compiler commandline interface" << endl; sout() << "Version: " << solidity::frontend::VersionString << endl; } void CommandLineInterface::printLicense() { sout() << otherLicenses << endl; // This is a static variable generated by cmake from LICENSE.txt sout() << licenseText << endl; } void CommandLineInterface::compile() { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); m_compiler = make_unique(m_universalCallback.callback()); SourceReferenceFormatter formatter(serr(false), *m_compiler, coloredOutput(m_options), m_options.formatting.withErrorIds); try { if (m_options.metadata.literalSources) m_compiler->useMetadataLiteralSources(true); m_compiler->setMetadataFormat(m_options.metadata.format); m_compiler->setMetadataHash(m_options.metadata.hash); if (m_options.modelChecker.initialize) m_compiler->setModelCheckerSettings(m_options.modelChecker.settings); m_compiler->setRemappings(m_options.input.remappings); m_compiler->setLibraries(m_options.linker.libraries); m_compiler->setViaIR(m_options.output.viaIR); m_compiler->setEVMVersion(m_options.output.evmVersion); m_compiler->setEOFVersion(m_options.output.eofVersion); m_compiler->setRevertStringBehaviour(m_options.output.revertStrings); if (m_options.output.debugInfoSelection.has_value()) m_compiler->selectDebugInfo(m_options.output.debugInfoSelection.value()); // TODO: Perhaps we should not compile unless requested m_compiler->enableIRGeneration( m_options.compiler.outputs.ir || m_options.compiler.outputs.irOptimized || m_options.compiler.outputs.irAstJson || m_options.compiler.outputs.irOptimizedAstJson ); m_compiler->enableEvmBytecodeGeneration( m_options.compiler.estimateGas || m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson || m_options.compiler.outputs.opcodes || m_options.compiler.outputs.binary || m_options.compiler.outputs.binaryRuntime || (m_options.compiler.combinedJsonRequests && ( m_options.compiler.combinedJsonRequests->binary || m_options.compiler.combinedJsonRequests->binaryRuntime || m_options.compiler.combinedJsonRequests->opcodes || m_options.compiler.combinedJsonRequests->asm_ || m_options.compiler.combinedJsonRequests->generatedSources || m_options.compiler.combinedJsonRequests->generatedSourcesRuntime || m_options.compiler.combinedJsonRequests->srcMap || m_options.compiler.combinedJsonRequests->srcMapRuntime || m_options.compiler.combinedJsonRequests->funDebug || m_options.compiler.combinedJsonRequests->funDebugRuntime )) ); m_compiler->setOptimiserSettings(m_options.optimiserSettings()); if (m_options.input.mode == InputMode::CompilerWithASTImport) { try { m_compiler->importASTs(parseAstFromInput()); if (!m_compiler->analyze()) { formatter.printErrorInformation(m_compiler->errors()); astAssert(false, "Analysis of the AST failed"); } } catch (Exception const& _exc) { // FIXME: AST import is missing proper validations. This hack catches failing // assertions and presents them as if they were compiler errors. solThrow(CommandLineExecutionError, "Failed to import AST: "s + _exc.what()); } } else { m_compiler->setSources(m_fileReader.sourceUnits()); m_compiler->setParserErrorRecovery(m_options.input.errorRecovery); } bool successful = m_compiler->compile(m_options.output.stopAfter); for (auto const& error: m_compiler->errors()) { m_hasOutput = true; formatter.printErrorInformation(*error); } if (!successful && !m_options.input.errorRecovery) solThrow(CommandLineExecutionError, ""); } catch (CompilerError const& _exception) { m_hasOutput = true; formatter.printExceptionInformation( _exception, Error::errorSeverity(Error::Type::CompilerError) ); solThrow(CommandLineExecutionError, ""); } catch (Error const& _error) { if (_error.type() == Error::Type::DocstringParsingError) { serr() << *boost::get_error_info(_error); solThrow(CommandLineExecutionError, "Documentation parsing failed."); } else { m_hasOutput = true; formatter.printErrorInformation(_error); solThrow(CommandLineExecutionError, ""); } } } void CommandLineInterface::handleCombinedJSON() { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.combinedJsonRequests.has_value()) return; Json::Value output(Json::objectValue); output[g_strVersion] = frontend::VersionString; vector 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 (m_options.compiler.combinedJsonRequests->abi) contractData[g_strAbi] = m_compiler->contractABI(contractName); if (m_options.compiler.combinedJsonRequests->metadata) contractData["metadata"] = m_compiler->metadata(contractName); if (m_options.compiler.combinedJsonRequests->binary && m_compiler->compilationSuccessful()) contractData[g_strBinary] = m_compiler->object(contractName).toHex(); if (m_options.compiler.combinedJsonRequests->binaryRuntime && m_compiler->compilationSuccessful()) contractData[g_strBinaryRuntime] = m_compiler->runtimeObject(contractName).toHex(); if (m_options.compiler.combinedJsonRequests->opcodes && m_compiler->compilationSuccessful()) contractData[g_strOpcodes] = evmasm::disassemble(m_compiler->object(contractName).bytecode, m_options.output.evmVersion); if (m_options.compiler.combinedJsonRequests->asm_ && m_compiler->compilationSuccessful()) contractData[g_strAsm] = m_compiler->assemblyJSON(contractName); if (m_options.compiler.combinedJsonRequests->storageLayout && m_compiler->compilationSuccessful()) contractData[g_strStorageLayout] = m_compiler->storageLayout(contractName); if (m_options.compiler.combinedJsonRequests->generatedSources && m_compiler->compilationSuccessful()) contractData[g_strGeneratedSources] = m_compiler->generatedSources(contractName, false); if (m_options.compiler.combinedJsonRequests->generatedSourcesRuntime && m_compiler->compilationSuccessful()) contractData[g_strGeneratedSourcesRuntime] = m_compiler->generatedSources(contractName, true); if (m_options.compiler.combinedJsonRequests->srcMap && m_compiler->compilationSuccessful()) { auto map = m_compiler->sourceMapping(contractName); contractData[g_strSrcMap] = map ? *map : ""; } if (m_options.compiler.combinedJsonRequests->srcMapRuntime && m_compiler->compilationSuccessful()) { auto map = m_compiler->runtimeSourceMapping(contractName); contractData[g_strSrcMapRuntime] = map ? *map : ""; } if (m_options.compiler.combinedJsonRequests->funDebug && m_compiler->compilationSuccessful()) contractData[g_strFunDebug] = StandardCompiler::formatFunctionDebugData( m_compiler->object(contractName).functionDebugData ); if (m_options.compiler.combinedJsonRequests->funDebugRuntime && m_compiler->compilationSuccessful()) contractData[g_strFunDebugRuntime] = StandardCompiler::formatFunctionDebugData( m_compiler->runtimeObject(contractName).functionDebugData ); if (m_options.compiler.combinedJsonRequests->signatureHashes) contractData[g_strSignatureHashes] = m_compiler->interfaceSymbols(contractName)["methods"]; if (m_options.compiler.combinedJsonRequests->natspecDev) contractData[g_strNatspecDev] = m_compiler->natspecDev(contractName); if (m_options.compiler.combinedJsonRequests->natspecUser) contractData[g_strNatspecUser] = m_compiler->natspecUser(contractName); } bool needsSourceList = m_options.compiler.combinedJsonRequests->ast || m_options.compiler.combinedJsonRequests->srcMap || m_options.compiler.combinedJsonRequests->srcMapRuntime; 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 (m_options.compiler.combinedJsonRequests->ast) { output[g_strSources] = Json::Value(Json::objectValue); for (auto const& sourceCode: m_fileReader.sourceUnits()) { output[g_strSources][sourceCode.first] = Json::Value(Json::objectValue); output[g_strSources][sourceCode.first]["AST"] = ASTJsonExporter( m_compiler->state(), m_compiler->sourceIndices() ).toJson(m_compiler->ast(sourceCode.first)); output[g_strSources][sourceCode.first]["id"] = m_compiler->sourceIndices().at(sourceCode.first); } } string json = jsonPrint(removeNullMembers(std::move(output)), m_options.formatting.json); if (!m_options.output.dir.empty()) createJson("combined", json); else sout() << json << endl; } void CommandLineInterface::handleAst() { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); if (!m_options.compiler.outputs.astCompactJson) return; vector asts; for (auto const& sourceCode: m_fileReader.sourceUnits()) asts.push_back(&m_compiler->ast(sourceCode.first)); if (!m_options.output.dir.empty()) { for (auto const& sourceCode: m_fileReader.sourceUnits()) { stringstream data; string postfix = ""; ASTJsonExporter(m_compiler->state(), m_compiler->sourceIndices()).print(data, m_compiler->ast(sourceCode.first), m_options.formatting.json); 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.sourceUnits()) { sout() << endl << "======= " << sourceCode.first << " =======" << endl; ASTJsonExporter(m_compiler->state(), m_compiler->sourceIndices()).print(sout(), m_compiler->ast(sourceCode.first), m_options.formatting.json); } } } void CommandLineInterface::serveLSP() { lsp::StdioTransport transport; if (!lsp::LanguageServer{transport}.run()) solThrow(CommandLineExecutionError, "LSP terminated abnormally."); } void CommandLineInterface::link() { solAssert(m_options.input.mode == InputMode::Linker); // Map from how the libraries will be named inside the bytecode to their addresses. map librariesReplacements; int const placeholderSize = 40; // 20 bytes or 40 hex characters for (auto const& library: m_options.linker.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.sourceUnits(); 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) != '_' ) solThrow( CommandLineExecutionError, "Error in binary object file " + src.first + " at position " + to_string(it - src.second.begin()) + "\n" + '"' + string(it, it + min(placeholderSize, static_cast(end - it))) + "\" is not a valid link reference." ); string foundPlaceholder(it, it + placeholderSize); if (librariesReplacements.count(foundPlaceholder)) { string hexStr(util::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_options.linker.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.setSourceUnits(std::move(sourceCodes)); } void CommandLineInterface::writeLinkedFiles() { solAssert(m_options.input.mode == InputMode::Linker); for (auto const& src: m_fileReader.sourceUnits()) if (src.first == g_stdinFileName) sout() << src.second << endl; else { ofstream outFile(src.first); outFile << src.second; if (!outFile) solThrow(CommandLineOutputError, "Could not write to file " + src.first + ". Aborting."); } 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; } void CommandLineInterface::assembleYul(yul::YulStack::Language _language, yul::YulStack::Machine _targetMachine) { solAssert(m_options.input.mode == InputMode::Assembler); bool successful = true; map yulStacks; for (auto const& src: m_fileReader.sourceUnits()) { auto& stack = yulStacks[src.first] = yul::YulStack( m_options.output.evmVersion, m_options.output.eofVersion, _language, m_options.optimiserSettings(), m_options.output.debugInfoSelection.has_value() ? m_options.output.debugInfoSelection.value() : DebugInfoSelection::Default() ); if (!stack.parseAndAnalyze(src.first, src.second)) successful = false; else stack.optimize(); } for (auto const& sourceAndStack: yulStacks) { auto const& stack = sourceAndStack.second; SourceReferenceFormatter formatter(serr(false), stack, coloredOutput(m_options), m_options.formatting.withErrorIds); for (auto const& error: stack.errors()) { m_hasOutput = true; formatter.printErrorInformation(*error); } if (Error::containsErrors(stack.errors())) successful = false; } if (!successful) { solAssert(m_hasOutput); solThrow(CommandLineExecutionError, ""); } for (auto const& src: m_fileReader.sourceUnits()) { solAssert(_targetMachine == yul::YulStack::Machine::EVM); string machine = "EVM"; sout() << endl << "======= " << src.first << " (" << machine << ") =======" << endl; yul::YulStack& stack = yulStacks[src.first]; if (m_options.compiler.outputs.irOptimized) { // NOTE: This actually outputs unoptimized code when the optimizer is disabled but // 'ir' output in StandardCompiler works the same way. sout() << endl << "Pretty printed source:" << endl; sout() << stack.print() << endl; } yul::MachineAssemblyObject object; object = stack.assemble(_targetMachine); object.bytecode->link(m_options.linker.libraries); if (m_options.compiler.outputs.binary) { sout() << endl << "Binary representation:" << endl; if (object.bytecode) sout() << object.bytecode->toHex() << endl; else serr() << "No binary representation found." << endl; } if (m_options.compiler.outputs.astCompactJson) { sout() << "AST:" << endl << endl; sout() << util::jsonPrint(stack.astJson(), m_options.formatting.json) << endl; } solAssert(_targetMachine == yul::YulStack::Machine::EVM, ""); if (m_options.compiler.outputs.asm_) { sout() << endl << "Text representation:" << endl; if (!object.assembly.empty()) sout() << object.assembly << endl; else serr() << "No text representation found." << endl; } } } void CommandLineInterface::outputCompilationResults() { solAssert(CompilerInputModes.count(m_options.input.mode) == 1); handleCombinedJSON(); // do we need AST output? handleAst(); if ( !m_compiler->compilationSuccessful() && m_options.output.stopAfter == CompilerStack::State::CompilationSuccessful ) { serr() << endl << "Compilation halted after AST generation due to errors." << endl; return; } vector contracts = m_compiler->contractNames(); for (string const& contract: contracts) { if (needsHumanTargetedStdout(m_options)) sout() << endl << "======= " << contract << " =======" << endl; // do we need EVM assembly? if (m_options.compiler.outputs.asm_ || m_options.compiler.outputs.asmJson) { string ret; if (m_options.compiler.outputs.asmJson) ret = util::jsonPrint(removeNullMembers(m_compiler->assemblyJSON(contract)), m_options.formatting.json); else ret = m_compiler->assemblyString(contract, m_fileReader.sourceUnits()); if (!m_options.output.dir.empty()) createFile(m_compiler->filesystemFriendlyName(contract) + (m_options.compiler.outputs.asmJson ? "_evm.json" : ".evm"), ret); else sout() << "EVM assembly:" << endl << ret << endl; } if (m_options.compiler.estimateGas) handleGasEstimation(contract); handleBytecode(contract); handleIR(contract); handleIRAst(contract); handleIROptimized(contract); handleIROptimizedAst(contract); handleSignatureHashes(contract); handleMetadata(contract); handleABI(contract); handleStorageLayout(contract); handleNatspec(true, contract); handleNatspec(false, contract); } // end of contracts iteration if (!m_hasOutput) { if (!m_options.output.dir.empty()) sout() << "Compiler run successful. Artifact(s) can be found in directory " << m_options.output.dir << "." << endl; else if (contracts.empty()) sout() << "Compiler run successful. No contracts to compile." << endl; else sout() << "Compiler run successful. No output generated." << endl; } } }