From f2701db0aa71f271b53476b568102363f7bf9fe4 Mon Sep 17 00:00:00 2001 From: Erik Kundt Date: Mon, 9 Dec 2019 17:01:31 +0100 Subject: [PATCH] Adds documentation for Solidity source upgrader. --- .circleci/config.yml | 7 + CMakeLists.txt | 1 + docs/using-the-compiler.rst | 237 ++++++++ libsolidity/analysis/OverrideChecker.cpp | 4 +- libsolidity/analysis/OverrideChecker.h | 15 +- libsolidity/ast/AST.h | 2 +- tools/CMakeLists.txt | 14 + tools/solidityUpgrade/SourceTransform.h | 159 ++++++ tools/solidityUpgrade/SourceUpgrade.cpp | 525 ++++++++++++++++++ tools/solidityUpgrade/SourceUpgrade.h | 158 ++++++ tools/solidityUpgrade/Upgrade050.cpp | 58 ++ tools/solidityUpgrade/Upgrade050.h | 58 ++ tools/solidityUpgrade/Upgrade060.cpp | 212 +++++++ tools/solidityUpgrade/Upgrade060.h | 69 +++ tools/solidityUpgrade/UpgradeChange.cpp | 73 +++ tools/solidityUpgrade/UpgradeChange.h | 82 +++ tools/solidityUpgrade/UpgradeSuite.h | 90 +++ .../contracts/DocsExamplePass.sol | 23 + tools/solidityUpgrade/contracts/Test.sol | 18 + .../contracts/TestMultiline.sol | 21 + .../contracts/TestNonFixable.sol | 15 + tools/solidityUpgrade/main.cpp | 81 +++ 22 files changed, 1911 insertions(+), 11 deletions(-) create mode 100644 tools/CMakeLists.txt create mode 100644 tools/solidityUpgrade/SourceTransform.h create mode 100644 tools/solidityUpgrade/SourceUpgrade.cpp create mode 100644 tools/solidityUpgrade/SourceUpgrade.h create mode 100644 tools/solidityUpgrade/Upgrade050.cpp create mode 100644 tools/solidityUpgrade/Upgrade050.h create mode 100644 tools/solidityUpgrade/Upgrade060.cpp create mode 100644 tools/solidityUpgrade/Upgrade060.h create mode 100644 tools/solidityUpgrade/UpgradeChange.cpp create mode 100644 tools/solidityUpgrade/UpgradeChange.h create mode 100644 tools/solidityUpgrade/UpgradeSuite.h create mode 100644 tools/solidityUpgrade/contracts/DocsExamplePass.sol create mode 100644 tools/solidityUpgrade/contracts/Test.sol create mode 100644 tools/solidityUpgrade/contracts/TestMultiline.sol create mode 100644 tools/solidityUpgrade/contracts/TestNonFixable.sol create mode 100644 tools/solidityUpgrade/main.cpp diff --git a/.circleci/config.yml b/.circleci/config.yml index a211edab0..c9fc35abc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -62,6 +62,11 @@ defaults: path: build/solc/solc destination: solc + # compiled tool executable target + - artifacts_tools: &artifacts_tools + path: build/tools/solidity-upgrade + destination: solidity-upgrade + # compiled executable targets - artifacts_executables: &artifacts_executables root: build @@ -324,6 +329,7 @@ jobs: - checkout - run: *run_build - store_artifacts: *artifacts_solc + - store_artifacts: *artifacts_tools - persist_to_workspace: *artifacts_executables b_ubu_release: &build_ubuntu1904_release @@ -455,6 +461,7 @@ jobs: - /usr/local/Homebrew - run: *run_build - store_artifacts: *artifacts_solc + - store_artifacts: *artifacts_tools - persist_to_workspace: *artifacts_build_dir t_osx_soltest: diff --git a/CMakeLists.txt b/CMakeLists.txt index 408b42024..1ab3e3741 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ add_subdirectory(libevmasm) add_subdirectory(libyul) add_subdirectory(libsolidity) add_subdirectory(libsolc) +add_subdirectory(tools) if (NOT EMSCRIPTEN) add_subdirectory(solc) diff --git a/docs/using-the-compiler.rst b/docs/using-the-compiler.rst index c91e4e505..fcb611f60 100644 --- a/docs/using-the-compiler.rst +++ b/docs/using-the-compiler.rst @@ -474,3 +474,240 @@ Error types 11. ``CompilerError``: Invalid use of the compiler stack - this should be reported as an issue. 12. ``FatalError``: Fatal error not processed correctly - this should be reported as an issue. 13. ``Warning``: A warning, which didn't stop the compilation, but should be addressed if possible. + + +.. _compiler-tools: + +Compiler tools +************** + +solidity-upgrade +---------------- + +``solidity-upgrade`` can help you to semi-automatically upgrade your contracts +to breaking language changes. While it does not and cannot implement all +required changes for every breaking release, it still supports the ones, that +would need plenty of repetitive manual adjustments otherwise. + +.. note:: + + ``solidity-upgrade`` carries out a large part of the work, but your + contracts will most likely need further manual adjustments. We recommend + using a version control system for your files. This helps reviewing and + eventually rolling back the changes made. + +.. warning:: + + ``solidity-upgrade`` is not considered to be complete or free from bugs, so + please use with care. + +How it works +~~~~~~~~~~~~ + +You can pass (a) Solidity source file(s) to ``solidity-upgrade [files]``. If +these make use of ``import`` statement which refer to files outside the +current source file's directory, you need to specify directories that +are allowed to read and import files from, by passing +``--allow-paths [directory]``. You can ignore missing files by passing +``--ignore-missing``. + +``solidity-upgrade`` is based on ``libsolidity`` and can parse, compile and +analyse your source files, and might find applicable source upgrades in them. + +Source upgrades are considered to be small textual changes to your source code. +They are applied to an in-memory representation of the source files +given. The corresponding source file is updated by default, but you can pass +``--dry-run`` to simulate to whole upgrade process without writing to any file. + +The upgrade process itself has two phases. In the first phase source files are +parsed, and since it is not possible to upgrade source code on that level, +errors are collected and can be logged by passing ``--verbose``. No source +upgrades available at this point. + +In the second phase, all sources are compiled and all activated upgrade analysis +modules are run alongside compilation. By default, all available modules are +activated. Please read the documentation on +:ref:`available modules ` for further details. + + +This can result in compilation errors that may +be fixed by source upgrades. If no errors occur, no source upgrades are being +reported and you're done. +If errors occur and some upgrade module reported a source upgrade, the first +reported one gets applied and compilation is triggered again for all given +source files. The previous step is repeated as long as source upgrades are +reported. If errors still occur, you can log them by passing ``--verbose``. +If no errors occur, your contracts are up to date and can be compiled with +the latest version of the compiler. + +.. _upgrade-modules: + +Available upgrade modules +~~~~~~~~~~~~~~~~~~~~~~~~~ + ++-----------------+---------+--------------------------------------------------+ +| Module | Version | Description | ++=================+=========+==================================================+ +| ``constructor`` | 0.5.0 | Constructors must now be defined using the | +| | | ``constructor`` keyword. | ++-----------------+---------+--------------------------------------------------+ +| ``visibility`` | 0.5.0 | Explicit function visibility is now mandatory, | +| | | defaults to ``public``. | ++-----------------+---------+--------------------------------------------------+ +| ``abstract`` | 0.6.0 | The keyword ``abstract`` has to be used if a | +| | | contract does not implement all its functions. | ++-----------------+---------+--------------------------------------------------+ +| ``virtual`` | 0.6.0 | Functions without implementation outside an | +| | | interface have to be marked ``virtual``. | ++-----------------+---------+--------------------------------------------------+ +| ``override`` | 0.6.0 | When overriding a function or modifier, the new | +| | | keyword ``override`` must be used. | ++-----------------+---------+--------------------------------------------------+ + +Please read :doc:`0.5.0 release notes <050-breaking-changes>` and +:doc:`0.6.0 release notes <060-breaking-changes>` for further details. + +Synopsis +~~~~~~~~ + +.. code-block:: none + + Usage: solidity-upgrade [options] contract.sol + + Allowed options: + --help Show help message and exit. + --version Show version and exit. + --allow-paths path(s) + Allow a given path for imports. A list of paths can be + supplied by separating them with a comma. + --ignore-missing Ignore missing files. + --modules module(s) Only activate a specific upgrade module. A list of + modules can be supplied by separating them with a comma. + --dry-run Apply changes in-memory only and don't write to input + file. + --verbose Print logs, errors and changes. Shortens output of + upgrade patches. + --unsafe Accept *unsafe* changes. + + + +Bug Reports / Feature requests +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +If you found a bug or if you have a feature request, please +`file an issue `_ on Github. + + +Example +~~~~~~~ + +Assume you have the following contracts you want to update declared in ``Source.sol``: + +.. code-block:: none + + // This will not compile + pragma solidity >0.4.23; + + contract Updateable { + function run() public view returns (bool); + function update() public; + } + + contract Upgradable { + function run() public view returns (bool); + function upgrade(); + } + + contract Source is Updateable, Upgradable { + function Source() public {} + + function run() + public + view + returns (bool) {} + + function update() {} + function upgrade() {} + } + + +Required changes +^^^^^^^^^^^^^^^^ + +To bring the contracts up to date with the current Solidity version, the +following upgrade modules have to be executed: ``constructor``, +``visibility``, ``abstract``, ``override`` and ``virtual``. Please read the +documentation on :ref:`available modules ` for further details. + +Running the upgrade +^^^^^^^^^^^^^^^^^^^ + +In this example, all modules needed to upgrade the contracts above, +are available and all of them are activated by default. Therefore you +do not need to specify the ``--modules`` option. + +.. code-block:: none + + $ solidity-upgrade Source.sol --dry-run + +.. code-block:: none + + Running analysis (and upgrade) on given source files. + .............. + + After upgrade: + + Found 0 errors. + Found 0 upgrades. + +The above performs a dry-ran upgrade on the given file and logs statistics after all. +In this case, the upgrade was successful and no further adjustments are needed. + +Finally, you can run the upgrade and also write to the source file. + +.. code-block:: none + + $ solidity-upgrade Source.sol + +.. code-block:: none + + Running analysis (and upgrade) on given source files. + .............. + + After upgrade: + + Found 0 errors. + Found 0 upgrades. + + +Review changes +^^^^^^^^^^^^^^ + +The command above applies all changes as shown below. Please review them carefully. + +.. code-block:: none + + pragma solidity >0.4.23; + + abstract contract Updateable { + function run() public view virtual returns (bool); + function update() public virtual; + } + + abstract contract Upgradable { + function run() public view virtual returns (bool); + function upgrade() public virtual; + } + + contract Source is Updateable, Upgradable { + constructor() public {} + + function run() + public + view + override(Updateable,Upgradable) + returns (bool) {} + + function update() public override {} + function upgrade() public override {} + } diff --git a/libsolidity/analysis/OverrideChecker.cpp b/libsolidity/analysis/OverrideChecker.cpp index fc2c8df62..8367f9f58 100644 --- a/libsolidity/analysis/OverrideChecker.cpp +++ b/libsolidity/analysis/OverrideChecker.cpp @@ -144,8 +144,8 @@ vector resolveDirectBaseContracts(ContractDefinition Declaration const* baseDecl = specifier->name().annotation().referencedDeclaration; auto contract = dynamic_cast(baseDecl); - solAssert(contract, "contract is null"); - resolvedContracts.emplace_back(contract); + if (contract) + resolvedContracts.emplace_back(contract); } return resolvedContracts; diff --git a/libsolidity/analysis/OverrideChecker.h b/libsolidity/analysis/OverrideChecker.h index 48fa54314..d4b96663c 100644 --- a/libsolidity/analysis/OverrideChecker.h +++ b/libsolidity/analysis/OverrideChecker.h @@ -129,6 +129,7 @@ private: class OverrideChecker { public: + using OverrideProxyBySignatureMultiSet = std::multiset; /// @param _errorReporter provides the error logging functionality. explicit OverrideChecker(langutil::ErrorReporter& _errorReporter): @@ -137,12 +138,17 @@ public: void check(ContractDefinition const& _contract); -private: struct CompareByID { bool operator()(ContractDefinition const* _a, ContractDefinition const* _b) const; }; + /// Returns all functions of bases (including public state variables) that have not yet been overwritten. + /// May contain the same function multiple times when used with shared bases. + OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const; + OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const; + +private: void checkIllegalOverrides(ContractDefinition const& _contract); /// Performs various checks related to @a _overriding overriding @a _super like /// different return type, invalid visibility change, etc. @@ -174,15 +180,8 @@ private: /// Resolves an override list of UserDefinedTypeNames to a list of contracts. std::set resolveOverrideList(OverrideSpecifier const& _overrides) const; - using OverrideProxyBySignatureMultiSet = std::multiset; - void checkOverrideList(OverrideProxy _item, OverrideProxyBySignatureMultiSet const& _inherited); - /// Returns all functions of bases (including public state variables) that have not yet been overwritten. - /// May contain the same function multiple times when used with shared bases. - OverrideProxyBySignatureMultiSet const& inheritedFunctions(ContractDefinition const& _contract) const; - OverrideProxyBySignatureMultiSet const& inheritedModifiers(ContractDefinition const& _contract) const; - langutil::ErrorReporter& m_errorReporter; /// Cache for inheritedFunctions(). diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 4bb9a0570..c0c29be7d 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -797,7 +797,7 @@ public: { return CallableDeclaration::virtualSemantics() || - annotation().contract->isInterface(); + (annotation().contract && annotation().contract->isInterface()); } private: StateMutability m_stateMutability; diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 000000000..bdcf615af --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,14 @@ +add_executable(solidity-upgrade + solidityUpgrade/main.cpp + solidityUpgrade/UpgradeChange.h + solidityUpgrade/UpgradeChange.cpp + solidityUpgrade/UpgradeSuite.h + solidityUpgrade/Upgrade050.cpp + solidityUpgrade/Upgrade060.cpp + solidityUpgrade/SourceTransform.h + solidityUpgrade/SourceUpgrade.cpp +) +target_link_libraries(solidity-upgrade PRIVATE solidity Boost::boost Boost::program_options Boost::system) + +include(GNUInstallDirs) +install(TARGETS solidity-upgrade DESTINATION "${CMAKE_INSTALL_BINDIR}") diff --git a/tools/solidityUpgrade/SourceTransform.h b/tools/solidityUpgrade/SourceTransform.h new file mode 100644 index 000000000..2b42471c1 --- /dev/null +++ b/tools/solidityUpgrade/SourceTransform.h @@ -0,0 +1,159 @@ +/* + 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 . +*/ + +#include + +#include + +#include + +namespace solidity::tools +{ + +/** + * Helper that provides functions which analyze certain source locations + * on a textual base. They utilize regular expression to search for + * keywords or to determine formatting. + */ +class SourceAnalysis { +public: + static bool isMultilineKeyword( + langutil::SourceLocation const& _location, + std::string const& _keyword + ) + { + return regex_search( + _location.text(), + std::regex{"(\\b" + _keyword + "\\b\\n|\\r|\\r\\n)"} + ); + } + + static bool hasMutabilityKeyword(langutil::SourceLocation const& _location) + { + return regex_search( + _location.text(), + std::regex{"(\\b(pure|view|nonpayable|payable)\\b)"} + ); + } + + static bool hasVirtualKeyword(langutil::SourceLocation const& _location) + { + return regex_search(_location.text(), std::regex{"(\\b(virtual)\\b)"}); + } + + static bool hasVisibilityKeyword(langutil::SourceLocation const& _location) + { + return regex_search(_location.text(), std::regex{"\\bpublic\\b"}); + } +}; + +/** + * Helper that provides functions which can analyse declarations and + * generate source snippets based on the information retrieved. + */ +class SourceGeneration +{ +public: + using CompareFunction = frontend::OverrideChecker::CompareByID; + using Contracts = std::set; + + /// Generates an `override` declaration for single overrides + /// or `override(...)` with contract list for multiple overrides. + static std::string functionOverride(Contracts const& _contracts) + { + if (_contracts.size() <= 1) + return "override"; + + std::string overrideList; + for (auto inheritedContract: _contracts) + overrideList += inheritedContract->name() + ","; + + return "override(" + overrideList.substr(0, overrideList.size() - 1) + ")"; + } +}; + +/** + * Helper that provides functions which apply changes to Solidity source code + * on a textual base. In general, these utilize regular expressions applied + * to the given source location. + */ +class SourceTransform +{ +public: + /// Searches for the keyword given and prepends the expression. + /// E.g. `function f() view;` -> `function f() public view;` + static std::string insertBeforeKeyword( + langutil::SourceLocation const& _location, + std::string const& _keyword, + std::string const& _expression + ) + { + return regex_replace( + _location.text(), + std::regex{"(\\b" + _keyword + "\\b)"}, + _expression + " " + _keyword + ); + } + + /// Searches for the keyword given and appends the expression. + /// E.g. `function f() public {}` -> `function f() public override {}` + static std::string insertAfterKeyword( + langutil::SourceLocation const& _location, + std::string const& _keyword, + std::string const& _expression + ) + { + bool isMultiline = SourceAnalysis::isMultilineKeyword(_location, _keyword); + std::string toAppend = isMultiline ? ("\n " + _expression) : (" " + _expression); + std::regex keyword{"(\\b" + _keyword + "\\b)"}; + + return regex_replace(_location.text(), keyword, _keyword + toAppend); + } + + /// Searches for the first right parenthesis and appends the expression + /// given. + /// E.g. `function f() {}` -> `function f() public {}` + static std::string insertAfterRightParenthesis( + langutil::SourceLocation const& _location, + std::string const& _expression + ) + { + return regex_replace( + _location.text(), + std::regex{"(\\))"}, + ") " + _expression + ); + } + + /// Searches for the `function` keyword and its identifier and replaces + /// both by the expression given. + /// E.g. `function Storage() {}` -> `constructor() {}` + static std::string replaceFunctionName( + langutil::SourceLocation const& _location, + std::string const& _name, + std::string const& _expression + ) + { + return regex_replace( + _location.text(), + std::regex{"(\\bfunction\\s*" + _name + "\\b)"}, + _expression + ); + } +}; + +} diff --git a/tools/solidityUpgrade/SourceUpgrade.cpp b/tools/solidityUpgrade/SourceUpgrade.cpp new file mode 100644 index 000000000..82d8fecf0 --- /dev/null +++ b/tools/solidityUpgrade/SourceUpgrade.cpp @@ -0,0 +1,525 @@ +/* + 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 . +*/ +#include + +#include +#include + +#include + +#include +#include +#include + +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +using namespace solidity; +using namespace solidity::langutil; +using namespace solidity::tools; +using namespace solidity::util; +using namespace solidity::frontend; +using namespace std; + + +static string const g_argHelp = "help"; +static string const g_argVersion = "version"; +static string const g_argInputFile = "input-file"; +static string const g_argModules = "modules"; +static string const g_argDryRun = "dry-run"; +static string const g_argUnsafe = "unsafe"; +static string const g_argVerbose = "verbose"; +static string const g_argIgnoreMissingFiles = "ignore-missing"; +static string const g_argAllowPaths = "allow-paths"; + +namespace +{ + +ostream& out() +{ + return cout; +} + +AnsiColorized log() +{ + return AnsiColorized(cout, true, {}); +} + +AnsiColorized success() +{ + return AnsiColorized(cout, true, {formatting::CYAN}); +} + +AnsiColorized warning() +{ + return AnsiColorized(cout, true, {formatting::YELLOW}); +} + +AnsiColorized error() +{ + return AnsiColorized(cout, true, {formatting::MAGENTA}); +} + +void logVersion() +{ + /// TODO Replace by variable that can be set during build. + out() << "0.1.0" << endl; +} + +void logProgress() +{ + out() << "."; + out().flush(); +} + +} + +bool SourceUpgrade::parseArguments(int _argc, char** _argv) +{ + po::options_description desc(R"(solidity-upgrade, the Solidity upgrade assistant. + +The solidity-upgrade tool can help upgrade smart contracts to breaking language features. + +It does not support all breaking changes for each version, but will hopefully assist +upgrading your contracts to the desired Solidity version. + +solidity-upgrade is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY. Please be careful when running upgrades on +your contracts. + +Usage: solidity-upgrade [options] contract.sol + +Allowed options)", + 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_argAllowPaths.c_str(), + po::value()->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_argModules.c_str(), + po::value()->value_name("module(s)"), + "Only activate a specific upgrade module. A list of " + "modules can be supplied by separating them with a comma." + ) + (g_argDryRun.c_str(), "Apply changes in-memory only and don't write to input file.") + (g_argVerbose.c_str(), "Print logs, errors and changes. Shortens output of upgrade patches.") + (g_argUnsafe.c_str(), "Accept *unsafe* changes."); + + + po::options_description allOptions = desc; + allOptions.add_options()("input-file", po::value>(), "input file"); + + po::positional_options_description filesPositions; + filesPositions.add("input-file", -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) + { + error() << _exception.what() << endl; + return false; + } + + if (m_args.count(g_argHelp) || (isatty(fileno(stdin)) && _argc == 1)) + { + out() << endl; + log() << desc; + return false; + } + + if (m_args.count(g_argVersion)) + { + logVersion(); + return false; + } + + if (m_args.count(g_argModules)) + { + vector moduleArgs; + auto modules = boost::split( + moduleArgs, m_args[g_argModules].as(), boost::is_any_of(",") + ); + + /// All modules are activated by default. Clear them before activating single ones. + m_suite.deactivateModules(); + + for (string const& module: modules) + { + if (module == "constructor") + m_suite.activateModule(Module::ConstructorKeyword); + else if (module == "visibility") + m_suite.activateModule(Module::VisibilitySpecifier); + else if (module == "abstract") + m_suite.activateModule(Module::AbstractContract); + else if (module == "override") + m_suite.activateModule(Module::OverridingFunction); + else if (module == "virtual") + m_suite.activateModule(Module::VirtualFunction); + else + { + error() << "Unknown upgrade module \"" + module + "\"" << endl; + return false; + } + } + } + + /// TODO Share with solc commandline interface. + if (m_args.count(g_argAllowPaths)) + { + vector paths; + auto allowedPaths = boost::split( + paths, m_args[g_argAllowPaths].as(), boost::is_any_of(",") + ); + for (string const& path: allowedPaths) + { + 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_allowedDirectories.push_back(filesystem_path); + } + } + + + return true; +} + +void SourceUpgrade::printPrologue() +{ + out() << endl; + out() << endl; + + log() << + "solidity-upgrade does not support all breaking changes for each version." << + endl << + "Please run `solidity-upgrade --help` and get a list of implemented upgrades." << + endl << + endl; + + log() << + "Running analysis (and upgrade) on given source files." << + endl; +} + +bool SourceUpgrade::processInput() +{ + if (!readInputFiles()) + return false; + + resetCompiler(fileReader()); + + tryCompile(); + runUpgrade(); + + printStatistics(); + + return true; +} + +void SourceUpgrade::tryCompile() const +{ + bool verbose = m_args.count(g_argVerbose); + + if (verbose) + log() << "Running compilation phases." << endl << endl; + else + logProgress(); + + try + { + if (m_compiler->parse()) + { + if (m_compiler->analyze()) + m_compiler->compile(); + else + if (verbose) + { + error() << + "Compilation errors that solidity-upgrade may resolve occurred." << + endl << + endl; + + printErrors(); + } + } + else + if (verbose) + { + error() << + "Compilation errors that solidity-upgrade cannot resolve occurred." << + endl << + endl; + + printErrors(); + } + } + catch (Exception const& _exception) + { + error() << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl; + } + catch (std::exception const& _e) + { + error() << (_e.what() ? ": " + string(_e.what()) : ".") << endl; + } + catch (...) + { + error() << "Unknown exception during compilation." << endl; + } +} + +void SourceUpgrade::runUpgrade() +{ + bool recompile = true; + + while (recompile && !m_compiler->errors().empty()) + { + for (auto& sourceCode: m_sourceCodes) + { + recompile = analyzeAndUpgrade(sourceCode); + if (recompile) + break; + } + + if (recompile) + { + m_suite.reset(); + resetCompiler(); + tryCompile(); + } + } +} + +bool SourceUpgrade::analyzeAndUpgrade(pair const& _sourceCode) +{ + bool applyUnsafe = m_args.count(g_argUnsafe); + bool verbose = m_args.count(g_argVerbose); + + if (verbose) + log() << "Analyzing and upgrading " << _sourceCode.first << "." << endl; + + if (m_compiler->state() >= CompilerStack::State::AnalysisPerformed) + m_suite.analyze(m_compiler->ast(_sourceCode.first)); + + if (!m_suite.changes().empty()) + { + auto& change = m_suite.changes().front(); + + if (verbose) + change.log(true); + + if (change.level() == UpgradeChange::Level::Safe) + { + applyChange(_sourceCode, change); + return true; + } + else if (change.level() == UpgradeChange::Level::Unsafe) + { + if (applyUnsafe) + { + applyChange(_sourceCode, change); + return true; + } + } + } + + return false; +} + +void SourceUpgrade::applyChange( + pair const& _sourceCode, + UpgradeChange& _change +) +{ + bool dryRun = m_args.count(g_argDryRun); + bool verbose = m_args.count(g_argVerbose); + + if (verbose) + { + log() << "Applying change to " << _sourceCode.first << endl << endl; + log() << _change.patch(); + } + + _change.apply(); + m_sourceCodes[_sourceCode.first] = _change.source(); + + if (!dryRun) + writeInputFile(_sourceCode.first, _change.source()); +} + +void SourceUpgrade::printErrors() const +{ + auto formatter = make_unique(cout, true); + + for (auto const& error: m_compiler->errors()) + if (error->type() != langutil::Error::Type::Warning) + formatter->printErrorInformation(*error); +} + +void SourceUpgrade::printStatistics() const +{ + out() << endl; + out() << endl; + out() << "After upgrade:" << endl; + out() << endl; + error() << "Found " << m_compiler->errors().size() << " errors." << endl; + success() << "Found " << m_suite.changes().size() << " upgrades." << endl; +} + +bool SourceUpgrade::readInputFiles() +{ + bool ignoreMissing = m_args.count(g_argIgnoreMissingFiles); + + /// TODO Share with solc commandline interface. + if (m_args.count(g_argInputFile)) + for (string path: m_args[g_argInputFile].as>()) + { + auto infile = boost::filesystem::path(path); + if (!boost::filesystem::exists(infile)) + { + if (!ignoreMissing) + { + error() << infile << " is not found." << endl; + return false; + } + else + error() << infile << " is not found. Skipping." << endl; + + continue; + } + + if (!boost::filesystem::is_regular_file(infile)) + { + if (!ignoreMissing) + { + error() << infile << " is not a valid file." << endl; + return false; + } + else + error() << infile << " is not a valid file. Skipping." << endl; + + continue; + } + + m_sourceCodes[infile.generic_string()] = readFileAsString(infile.string()); + path = boost::filesystem::canonical(infile).string(); + } + + if (m_sourceCodes.size() == 0) + { + warning() << "No input files given. If you wish to use the standard input please specify \"-\" explicitly." << endl; + return false; + } + + return true; +} + +bool SourceUpgrade::writeInputFile(string const& _path, string const& _source) +{ + bool verbose = m_args.count(g_argVerbose); + + if (verbose) + { + out() << endl; + log() << "Writing to input file " << _path << "." << endl; + } + + ofstream file(_path, ios::trunc); + file << _source; + + return true; +} + +ReadCallback::Callback SourceUpgrade::fileReader() +{ + /// TODO Share with solc commandline interface. + ReadCallback::Callback fileReader = [this](string const&, string const& _path) + { + try + { + auto path = boost::filesystem::path(_path); + auto canonicalPath = boost::filesystem::weakly_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 ReadCallback::Result{false, "File outside of allowed directories."}; + + if (!boost::filesystem::exists(canonicalPath)) + return ReadCallback::Result{false, "File not found."}; + + if (!boost::filesystem::is_regular_file(canonicalPath)) + return ReadCallback::Result{false, "Not a valid file."}; + + auto contents = readFileAsString(canonicalPath.string()); + m_sourceCodes[path.generic_string()] = contents; + return ReadCallback::Result{true, contents}; + } + catch (Exception const& _exception) + { + return ReadCallback::Result{false, "Exception in read callback: " + boost::diagnostic_information(_exception)}; + } + catch (...) + { + return ReadCallback::Result{false, "Unknown exception in read callback."}; + } + }; + + return fileReader; +} + +void SourceUpgrade::resetCompiler() +{ + m_compiler->reset(); + m_compiler->setSources(m_sourceCodes); + m_compiler->setParserErrorRecovery(true); +} + +void SourceUpgrade::resetCompiler(ReadCallback::Callback const& _callback) +{ + m_compiler.reset(new CompilerStack(_callback)); + m_compiler->setSources(m_sourceCodes); + m_compiler->setParserErrorRecovery(true); +} diff --git a/tools/solidityUpgrade/SourceUpgrade.h b/tools/solidityUpgrade/SourceUpgrade.h new file mode 100644 index 000000000..9ace60e57 --- /dev/null +++ b/tools/solidityUpgrade/SourceUpgrade.h @@ -0,0 +1,158 @@ +/* + 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 . +*/ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +#include + +namespace solidity::tools +{ + +/** + * The Solidity source upgrade tool. It supplies a command line interface + * and connects this to a compiler stack that the upgrade logic is facilitated + * with. + */ +class SourceUpgrade +{ +public: + /// Parse command line arguments and return false in case of a failure. + bool parseArguments(int _argc, char** _argv); + /// Prints additional information on the upgrade tool. + void printPrologue(); + /// Parse / compile files and runs upgrade analysis on them. + bool processInput(); + +private: + /// All available upgrade modules + enum class Module + { + ConstructorKeyword, + VisibilitySpecifier, + AbstractContract, + OverridingFunction, + VirtualFunction + }; + + /// Upgrade suite that hosts all available modules. + class Suite: public UpgradeSuite + { + public: + void analyze(frontend::SourceUnit const& _sourceUnit) + { + /// Solidity 0.5.0 + if (isActivated(Module::ConstructorKeyword)) + ConstructorKeyword{m_changes}.analyze(_sourceUnit); + if (isActivated(Module::VisibilitySpecifier)) + VisibilitySpecifier{m_changes}.analyze(_sourceUnit); + + /// Solidity 0.6.0 + if (isActivated(Module::AbstractContract)) + AbstractContract{m_changes}.analyze(_sourceUnit); + if (isActivated(Module::OverridingFunction)) + OverridingFunction{m_changes}.analyze(_sourceUnit); + if (isActivated(Module::VirtualFunction)) + VirtualFunction{m_changes}.analyze(_sourceUnit); + } + + void activateModule(Module _module) { m_modules.insert(_module); } + void deactivateModules() { m_modules.clear(); } + + private: + bool isActivated(Module _module) const + { + return m_modules.find(_module) != m_modules.end(); + } + + /// All modules are activated by default. Clear them before activating + /// single ones. + std::set m_modules = { + Module::ConstructorKeyword, + Module::VisibilitySpecifier, + Module::AbstractContract, + Module::OverridingFunction, + Module::VirtualFunction + }; + }; + + /// Parses the current sources and runs analyses as well as compilation on + /// them if parsing was successful. + void tryCompile() const; + /// Analyses and upgrades the sources given. The upgrade happens in a loop, + /// applying one change at a time, which is run until no applicable changes + /// are found any more. Only one change is done at a time and all sources + /// are being compiled again after each change. + void runUpgrade(); + /// Runs upgrade analysis on source and applies upgrades changes to it. + /// Returns `true` if there're still changes that can be applied, + /// `false` otherwise. + bool analyzeAndUpgrade( + std::pair const& _sourceCode + ); + + /// Applies the change given to its source code. If no `--dry-run` was + /// passed via the commandline, the upgraded source code is written back + /// to its file. + void applyChange( + std::pair const& _sourceCode, + UpgradeChange& _change + ); + + /// Prints all errors (excluding warnings) the compiler currently reported. + void printErrors() const; + /// Prints error and upgrade overview at the end of each full run. + void printStatistics() const; + + /// Reads all input files given and stores sources in the internal data + /// structure. Reports errors if files cannot be found. + bool readInputFiles(); + /// Writes source to file given. + bool writeInputFile(std::string const& _path, std::string const& _source); + /// Returns a file reader function that fills `m_sources`. + frontend::ReadCallback::Callback fileReader(); + + /// Resets the compiler stack and configures sources to compile. + /// Also enables error recovery. + void resetCompiler(); + /// Resets the compiler stack and configures sources to compile. + /// Also enables error recovery. Passes read callback to the compiler stack. + void resetCompiler(frontend::ReadCallback::Callback const& _callback); + + /// Compiler arguments variable map + boost::program_options::variables_map m_args; + /// Map of input files to source code strings + std::map m_sourceCodes; + /// Solidity compiler stack + std::unique_ptr m_compiler; + /// List of allowed directories to read files from + std::vector m_allowedDirectories; + /// Holds all upgrade modules and source upgrades. + Suite m_suite; +}; + +} diff --git a/tools/solidityUpgrade/Upgrade050.cpp b/tools/solidityUpgrade/Upgrade050.cpp new file mode 100644 index 000000000..ac939c2a1 --- /dev/null +++ b/tools/solidityUpgrade/Upgrade050.cpp @@ -0,0 +1,58 @@ +/* + 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 . +*/ +#include +#include + +#include + +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::tools; + +void ConstructorKeyword::endVisit(ContractDefinition const& _contract) +{ + for (auto const* function: _contract.definedFunctions()) + if (function->name() == _contract.name()) + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + function->location(), + SourceTransform::replaceFunctionName( + function->location(), + function->name(), + "constructor" + ) + } + ); +} + +void VisibilitySpecifier::endVisit(FunctionDefinition const& _function) +{ + if (_function.noVisibilitySpecified()) + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + _function.location(), + SourceTransform::insertAfterRightParenthesis(_function.location(), "public") + } + ); +} diff --git a/tools/solidityUpgrade/Upgrade050.h b/tools/solidityUpgrade/Upgrade050.h new file mode 100644 index 000000000..d85b69912 --- /dev/null +++ b/tools/solidityUpgrade/Upgrade050.h @@ -0,0 +1,58 @@ +/* + 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 . +*/ +#pragma once + +#include +#include + +#include + +namespace solidity::tools +{ + +/** + * Module that performs analysis on the AST. It visits all contract + * definitions and its defined functions and reports a source upgrade, + * if one of the declared functions is the constructor but does not + * use the `constructor` keyword. + */ +class ConstructorKeyword: public AnalysisUpgrade +{ +public: + using AnalysisUpgrade::AnalysisUpgrade; + + void analyze(frontend::SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); } +private: + void endVisit(frontend::ContractDefinition const& _contract); +}; + +/** + * Module that performs analysis on the AST. It visits function definitions + * and reports a source upgrade, if this function's visibility is `public`, + * but not marked explicitly as such. + */ +class VisibilitySpecifier: public AnalysisUpgrade +{ +public: + using AnalysisUpgrade::AnalysisUpgrade; + + void analyze(frontend::SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); } +private: + void endVisit(frontend::FunctionDefinition const& _function); +}; + +} diff --git a/tools/solidityUpgrade/Upgrade060.cpp b/tools/solidityUpgrade/Upgrade060.cpp new file mode 100644 index 000000000..9cd996f53 --- /dev/null +++ b/tools/solidityUpgrade/Upgrade060.cpp @@ -0,0 +1,212 @@ +/* + 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 . +*/ +#include +#include + +#include + +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::frontend; +using namespace solidity::tools; + +using Contracts = set; + +namespace +{ + +inline string appendOverride( + FunctionDefinition const& _function, + Contracts const& _expectedContracts +) +{ + auto location = _function.location(); + string upgradedCode; + string overrideExpression = SourceGeneration::functionOverride(_expectedContracts); + + if (SourceAnalysis::hasVirtualKeyword(location)) + upgradedCode = SourceTransform::insertAfterKeyword( + location, + "virtual", + overrideExpression + ); + else if (SourceAnalysis::hasMutabilityKeyword(location)) + upgradedCode = SourceTransform::insertAfterKeyword( + location, + stateMutabilityToString(_function.stateMutability()), + overrideExpression + ); + else if (SourceAnalysis::hasVisibilityKeyword(location)) + upgradedCode = SourceTransform::insertAfterKeyword( + location, + Declaration::visibilityToString(_function.visibility()), + overrideExpression + ); + else + upgradedCode = SourceTransform::insertAfterRightParenthesis( + location, + overrideExpression + ); + + return upgradedCode; +} + +inline string appendVirtual(FunctionDefinition const& _function) +{ + auto location = _function.location(); + string upgradedCode; + + if (SourceAnalysis::hasMutabilityKeyword(location)) + upgradedCode = SourceTransform::insertAfterKeyword( + location, + stateMutabilityToString(_function.stateMutability()), + "virtual" + ); + else if (SourceAnalysis::hasVisibilityKeyword(location)) + upgradedCode = SourceTransform::insertAfterKeyword( + location, + Declaration::visibilityToString(_function.visibility()), + "virtual" + ); + else + upgradedCode = SourceTransform::insertAfterRightParenthesis( + _function.location(), + "virtual" + ); + + return upgradedCode; +} + +} + +void AbstractContract::endVisit(ContractDefinition const& _contract) +{ + bool isFullyImplemented = _contract.annotation().unimplementedFunctions.empty(); + + if ( + !isFullyImplemented && + !_contract.abstract() && + !_contract.isInterface() + ) + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + _contract.location(), + SourceTransform::insertBeforeKeyword(_contract.location(), "contract", "abstract") + } + ); +} + +void OverridingFunction::endVisit(ContractDefinition const& _contract) +{ + auto const& inheritedFunctions = m_overrideChecker.inheritedFunctions(_contract); + + for (auto const* function: _contract.definedFunctions()) + { + Contracts expectedContracts; + OverrideProxy proxy{function}; + + if (!function->isConstructor()) + { + /// Build list of contracts expected to be mentioned in the override list (if any). + for (auto [begin, end] = inheritedFunctions.equal_range(proxy); begin != end; begin++) + expectedContracts.insert(&begin->contract()); + + /// Add override with contract list, if needed. + if (!function->overrides() && expectedContracts.size() > 1) + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + function->location(), + appendOverride(*function, expectedContracts) + } + ); + + for (auto [begin, end] = inheritedFunctions.equal_range(proxy); begin != end; begin++) + { + auto& super = (*begin); + auto functionType = FunctionType(*function).asCallableFunction(false); + auto superType = super.functionType()->asCallableFunction(false); + + if (functionType && functionType->hasEqualParameterTypes(*superType)) + { + /// If function does not specify override and no override with + /// contract list was added before. + if (!function->overrides() && expectedContracts.size() <= 1) + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + function->location(), + appendOverride(*function, expectedContracts) + } + ); + } + } + } + } +} + +void VirtualFunction::endVisit(ContractDefinition const& _contract) +{ + auto const& inheritedFunctions = m_overrideChecker.inheritedFunctions(_contract); + + for (FunctionDefinition const* function: _contract.definedFunctions()) + { + OverrideProxy proxy{function}; + + if (!function->isConstructor()) + { + if ( + !function->markedVirtual() && + !function->isImplemented() && + !function->virtualSemantics() && + function->visibility() > Visibility::Private + ) + { + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + function->location(), + appendVirtual(*function) + } + ); + } + + for (auto [begin, end] = inheritedFunctions.equal_range(proxy); begin != end; begin++) + { + auto& super = (*begin); + if ( + !function->markedVirtual() && + !super.virtualSemantics() + ) + { + m_changes.push_back( + UpgradeChange{ + UpgradeChange::Level::Safe, + function->location(), + appendVirtual(*function) + } + ); + } + } + } + } +} diff --git a/tools/solidityUpgrade/Upgrade060.h b/tools/solidityUpgrade/Upgrade060.h new file mode 100644 index 000000000..ea29d3a7b --- /dev/null +++ b/tools/solidityUpgrade/Upgrade060.h @@ -0,0 +1,69 @@ +/* + 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 . +*/ +#pragma once + +#include +#include + +#include + +namespace solidity::tools +{ + +/** + * Module that performs analysis on the AST. Finds abstract contracts that are + * not marked as such and adds the `abstract` keyword. + */ +class AbstractContract: public AnalysisUpgrade +{ +public: + using AnalysisUpgrade::AnalysisUpgrade; + + void analyze(frontend::SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); } +private: + void endVisit(frontend::ContractDefinition const& _contract); +}; + +/** + * Module that performs analysis on the AST. Finds functions that need to be + * marked `override` and adds the keyword to the function header. + */ +class OverridingFunction: public AnalysisUpgrade +{ +public: + using AnalysisUpgrade::AnalysisUpgrade; + + void analyze(frontend::SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); } +private: + void endVisit(frontend::ContractDefinition const& _contract); +}; + +/** + * Module that performs analysis on the AST. Finds functions that need to be + * marked `virtual` and adds the keyword to the function header. + */ +class VirtualFunction: public AnalysisUpgrade +{ +public: + using AnalysisUpgrade::AnalysisUpgrade; + + void analyze(frontend::SourceUnit const& _sourceUnit) { _sourceUnit.accept(*this); } +private: + void endVisit(frontend::ContractDefinition const& _function); +}; + +} diff --git a/tools/solidityUpgrade/UpgradeChange.cpp b/tools/solidityUpgrade/UpgradeChange.cpp new file mode 100644 index 000000000..8bbd6214c --- /dev/null +++ b/tools/solidityUpgrade/UpgradeChange.cpp @@ -0,0 +1,73 @@ +/* + 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 . +*/ +#include + +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::langutil; +using namespace solidity::util; +using namespace solidity::tools; + +void UpgradeChange::apply() +{ + m_source.replace(m_location.start, m_location.end - m_location.start, m_patch); +} + +void UpgradeChange::log(bool const _shorten) const +{ + stringstream os; + SourceReferenceFormatterHuman formatter{os, true}; + + string start = to_string(m_location.start); + string end = to_string(m_location.end); + + auto color = m_level == Level::Unsafe ? formatting::MAGENTA : formatting::CYAN; + auto level = m_level == Level::Unsafe ? "unsafe" : "safe"; + + os << endl; + AnsiColorized(os, true, {formatting::BOLD, color}) << "Upgrade change (" << level << ")" << endl; + os << "=======================" << endl; + formatter.printSourceLocation(SourceReferenceExtractor::extract(&m_location)); + os << endl; + + LineColumn lineEnd = m_location.source->translatePositionToLineColumn(m_location.end); + int const leftpad = static_cast(log10(max(lineEnd.line, 1))) + 2; + + stringstream output; + output << (_shorten ? shortenSource(m_patch) : m_patch); + + string line; + while (getline(output, line)) + { + os << string(leftpad, ' '); + AnsiColorized(os, true, {formatting::BOLD, formatting::BLUE}) << "| "; + AnsiColorized(os, true, {}) << line << endl; + } + cout << os.str(); + cout << endl; +} + +string UpgradeChange::shortenSource(string const& _source) +{ + size_t constexpr maxSourceLength = 1000; + if (_source.size() > maxSourceLength) + return _source.substr(0, min(_source.size(), maxSourceLength)) + "..."; + return _source; +} diff --git a/tools/solidityUpgrade/UpgradeChange.h b/tools/solidityUpgrade/UpgradeChange.h new file mode 100644 index 000000000..4d279fa60 --- /dev/null +++ b/tools/solidityUpgrade/UpgradeChange.h @@ -0,0 +1,82 @@ +/* + 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 . +*/ +#pragma once + +#include + +#include + +#include + +namespace solidity::tools +{ + +/** + * Models a single source code change, based on the initial source location + * and a patch, which needs to be applied. + * It implements the concept of level of confidence in the change and distiguishes + * safe from unsafe changes. A "safe" change is considered to not break + * compilation or change semantics. An "unsafe" change is considered to potentially + * change semantics or require further manual management. + */ +class UpgradeChange +{ +public: + enum class Level + { + Safe, + Unsafe + }; + + UpgradeChange( + Level _level, + langutil::SourceLocation _location, + std::string _patch + ) + : + m_location(_location), + m_source(_location.source->source()), + m_patch(_patch), + m_level(_level) {} + + ~UpgradeChange() {} + + langutil::SourceLocation const& location() { return m_location; } + std::string source() const { return m_source; } + std::string patch() { return m_patch; } + Level level() const { return m_level; } + + /// Does the actual replacement of code under at current source location. + /// The change is applied on the upgrade-specific copy of source code. + /// The altered code is then requested by the upgrade routine later on. + void apply(); + /// Does a pretty-print of this upgrade change. It uses a source formatter + /// provided by the compiler in order to print affected code. Since the patch + /// can contain a lot of code lines, it can be shortened, which is signaled + /// by setting the flag. + void log(bool const _shorten = true) const; +private: + langutil::SourceLocation m_location; + std::string m_source; + std::string m_patch; + Level m_level; + + /// Shortens the given source to a constant limit. + static std::string shortenSource(std::string const& _source); +}; + +} diff --git a/tools/solidityUpgrade/UpgradeSuite.h b/tools/solidityUpgrade/UpgradeSuite.h new file mode 100644 index 000000000..b9b094e70 --- /dev/null +++ b/tools/solidityUpgrade/UpgradeSuite.h @@ -0,0 +1,90 @@ +/* + 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 . +*/ +#pragma once + +#include + +#include + +#include +#include + +#include + +namespace solidity::tools +{ + +/** + * The base upgrade module that can be inherited from. Doing so + * creates a basic upgrade module that facilitates access to + * change reporting. + */ +class Upgrade +{ +public: + Upgrade(std::vector& _changes): m_changes(_changes) {} + +protected: + /// A reference to a suite-specific set of changes. + /// It is passed to all upgrade modules and meant to collect + /// reported changes. + std::vector& m_changes; +}; + +/** + * A specific upgrade module meant to be run after the analysis phase + * of the compiler. + */ +class AnalysisUpgrade: public Upgrade, public frontend::ASTConstVisitor +{ +public: + AnalysisUpgrade(std::vector& _changes): + Upgrade(_changes), + m_errorReporter(m_errors), + m_overrideChecker(m_errorReporter) + {} + /// Interface function for all upgrade modules that are meant + /// be run after the analysis phase of the compiler. + void analyze(frontend::SourceUnit const&) {} +protected: + langutil::ErrorList m_errors; + langutil::ErrorReporter m_errorReporter; + frontend::OverrideChecker m_overrideChecker; +}; + +/** + * The generic upgrade suite. Should be inherited from for each set of + * desired upgrade modules. + */ +class UpgradeSuite +{ +public: + /// The base interface function that needs to be implemented for each + /// suite. It should create suite-specific upgrade modules and trigger + /// their analysis. + void analyze(frontend::SourceUnit const& _sourceUnit); + /// Resets all changes collected so far. + void reset() { m_changes.clear(); } + + std::vector& changes() { return m_changes; } + std::vector const& changes() const { return m_changes; } + +protected: + std::vector m_changes; +}; + +} diff --git a/tools/solidityUpgrade/contracts/DocsExamplePass.sol b/tools/solidityUpgrade/contracts/DocsExamplePass.sol new file mode 100644 index 000000000..d8e4dc8e0 --- /dev/null +++ b/tools/solidityUpgrade/contracts/DocsExamplePass.sol @@ -0,0 +1,23 @@ +pragma solidity >0.4.23; + +contract Updateable { + function run() public view returns (bool); + function update() public; +} + +contract Upgradable { + function run() public view returns (bool); + function upgrade(); +} + +contract Source is Updateable, Upgradable { + function Source() public {} + + function run() + public + view + returns (bool) {} + + function update() {} + function upgrade() {} +} \ No newline at end of file diff --git a/tools/solidityUpgrade/contracts/Test.sol b/tools/solidityUpgrade/contracts/Test.sol new file mode 100644 index 000000000..8958a091a --- /dev/null +++ b/tools/solidityUpgrade/contracts/Test.sol @@ -0,0 +1,18 @@ +pragma solidity >0.4.23; + +contract Storage { + function Storage() public {} + function start(); + function state() public view returns (bool); + function stop() public; +} + +contract Observable { + function state() public view returns (bool); +} + +contract VolatileStorage is Storage, Observable { + function start() {} + function state() public view returns (bool) {} + function stop() {} +} \ No newline at end of file diff --git a/tools/solidityUpgrade/contracts/TestMultiline.sol b/tools/solidityUpgrade/contracts/TestMultiline.sol new file mode 100644 index 000000000..7083da4ea --- /dev/null +++ b/tools/solidityUpgrade/contracts/TestMultiline.sol @@ -0,0 +1,21 @@ +pragma solidity >0.4.23; + +contract Storage { + function Storage() {} + function init() public; + function idle(); + function destroy() public view; +} + +contract VolatileStorage is Storage { + function init() + public + {} + + function idle() {} + + function destroy() + public + view + {} +} \ No newline at end of file diff --git a/tools/solidityUpgrade/contracts/TestNonFixable.sol b/tools/solidityUpgrade/contracts/TestNonFixable.sol new file mode 100644 index 000000000..0a5c2f00e --- /dev/null +++ b/tools/solidityUpgrade/contracts/TestNonFixable.sol @@ -0,0 +1,15 @@ +pragma solidity >0.4.23; + +contract Storage { + function Storage() {} + function init() public; + function idle(); + function destroy() public view; +} + +contract VolatileStorage is Storage { + uint[] array; + function init() public { array.length = 3; } + function idle() {} + function destroy() public view {} +} \ No newline at end of file diff --git a/tools/solidityUpgrade/main.cpp b/tools/solidityUpgrade/main.cpp new file mode 100644 index 000000000..0f2b7a473 --- /dev/null +++ b/tools/solidityUpgrade/main.cpp @@ -0,0 +1,81 @@ +/* + 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 . +*/ +#include + +#include +#include + +//#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#include +#endif + +using namespace solidity; +using namespace std; +namespace po = boost::program_options; +namespace fs = boost::filesystem; + +namespace +{ + +void setupTerminal() +{ +#if defined(_WIN32) && defined(ENABLE_VIRTUAL_TERMINAL_PROCESSING) + // Set output mode to handle virtual terminal (ANSI escape sequences) + // ignore any error, as this is just a "nice-to-have" + // only windows needs to be taken care of, as other platforms (Linux/OSX) support them natively. + HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); + if (hOut == INVALID_HANDLE_VALUE) + return; + + DWORD dwMode = 0; + if (!GetConsoleMode(hOut, &dwMode)) + return; + + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (!SetConsoleMode(hOut, dwMode)) + return; +#endif +} + +} + +int main(int argc, char** argv) +{ + setupTerminal(); + + tools::SourceUpgrade upgrade; + if (!upgrade.parseArguments(argc, argv)) + return 1; + upgrade.printPrologue(); + if (!upgrade.processInput()) + return 1; + + return 0; +}