diff options
24 files changed, 395 insertions, 330 deletions
diff --git a/docs/index.rst b/docs/index.rst index 37d17348..b60ea06d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -46,139 +46,6 @@ with varying degrees of completeness and up-to-dateness. The English version sta * `Korean <http://solidity-kr.readthedocs.io>`_ (in progress) * `French <http://solidity-fr.readthedocs.io>`_ (in progress) - -Useful links ------------- - -General -~~~~~~~ - -* `Ethereum <https://ethereum.org>`_ - -* `Changelog <https://github.com/ethereum/solidity/blob/develop/Changelog.md>`_ - -* `Source Code <https://github.com/ethereum/solidity/>`_ - -* `Ethereum Stackexchange <https://ethereum.stackexchange.com/>`_ - -* `Language Users Chat <https://gitter.im/ethereum/solidity/>`_ - -* `Compiler Developers Chat <https://gitter.im/ethereum/solidity-dev/>`_ - -Available Solidity Integrations -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* Generic: - - * `EthFiddle <https://ethfiddle.com/>`_ - Solidity IDE in the Browser. Write and share your solidity code. Uses server-side components. - - * `Remix <https://remix.ethereum.org/>`_ - Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. - - * `Solium <https://github.com/duaraghav8/Solium/>`_ - Linter to identify and fix style and security issues in Solidity. - - * `Solhint <https://github.com/protofire/solhint>`_ - Solidity linter that provides security, style guide and best practice rules for smart contract validation. - -* Atom: - - * `Etheratom <https://github.com/0mkara/etheratom>`_ - Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (Backend node & VM compatible). - - * `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_ - Plugin for the Atom editor that provides Solidity linting. - - * `Atom Solium Linter <https://atom.io/packages/linter-solium>`_ - Configurable Solidty linter for Atom using Solium as a base. - -* Eclipse: - - * `YAKINDU Solidity Tools <https://yakindu.github.io/solidity-ide/>`_ - Eclipse based IDE. Features context sensitive code completion and help, code navigation, syntax coloring, built in compiler, quick fixes and templates. - -* Emacs: - - * `Emacs Solidity <https://github.com/ethereum/emacs-solidity/>`_ - Plugin for the Emacs editor providing syntax highlighting and compilation error reporting. - -* IntelliJ: - - * `IntelliJ IDEA plugin <https://plugins.jetbrains.com/plugin/9475-intellij-solidity>`_ - Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs) - -* Sublime: - - * `Package for SublimeText — Solidity language syntax <https://packagecontrol.io/packages/Ethereum/>`_ - Solidity syntax highlighting for SublimeText editor. - -* Vim: - - * `Vim Solidity <https://github.com/tomlion/vim-solidity/>`_ - Plugin for the Vim editor providing syntax highlighting. - - * `Vim Syntastic <https://github.com/scrooloose/syntastic>`_ - Plugin for the Vim editor providing compile checking. - -* Visual Studio Code: - - * `Visual Studio Code extension <http://juan.blanco.ws/solidity-contracts-in-visual-studio-code/>`_ - Solidity plugin for Microsoft Visual Studio Code that includes syntax highlighting and the Solidity compiler. - -Discontinued: - -* `Mix IDE <https://github.com/ethereum/mix/>`_ - Qt based IDE for designing, debugging and testing solidity smart contracts. - -* `Ethereum Studio <https://live.ether.camp/>`_ - Specialized web IDE that also provides shell access to a complete Ethereum environment. - -* `Visual Studio Extension <https://visualstudiogallery.msdn.microsoft.com/96221853-33c4-4531-bdd5-d2ea5acc4799/>`_ - Solidity plugin for Microsoft Visual Studio that includes the Solidity compiler. - -Solidity Tools -~~~~~~~~~~~~~~ - -* `Dapp <https://dapp.tools/dapp/>`_ - Build tool, package manager, and deployment assistant for Solidity. - -* `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_ - Try Solidity instantly with a command-line Solidity console. - -* `solgraph <https://github.com/raineorshine/solgraph>`_ - Visualize Solidity control flow and highlight potential security vulnerabilities. - -* `Doxity <https://github.com/DigixGlobal/doxity>`_ - Documentation Generator for Solidity. - -* `evmdis <https://github.com/Arachnid/evmdis>`_ - EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations. - -* `ABI to solidity interface converter <https://gist.github.com/chriseth/8f533d133fa0c15b0d6eaf3ec502c82b>`_ - A script for generating contract interfaces from the ABI of a smart contract. - -* `Securify <https://securify.ch/>`_ - Fully automated online static analyzer for smart contracts, providing a security report based on vulnerability patterns. - -* `Sūrya <https://github.com/ConsenSys/surya/>`_ - Utility tool for smart contract systems, offering a number of visual outputs and information about the contracts' structure. Also supports querying the function call graph. - -* `EVM Lab <https://github.com/ethereum/evmlab/>`_ - Rich tool package to interact with the EVM. Includes a VM, Etherchain API, and a trace-viewer with gas cost display. - -.. note:: - Information like variable names, comments, and source code formatting is lost in the compilation process and it is not possible to completely recover the original source code. Decompiling smart contracts to view the original source code might not be possible, or the end result that useful. - -Third-Party Solidity Parsers and Grammars -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -* `solidity-parser <https://github.com/ConsenSys/solidity-parser>`_ - Solidity parser for JavaScript - -* `Solidity Grammar for ANTLR 4 <https://github.com/federicobond/solidity-antlr4>`_ - Solidity grammar for the ANTLR 4 parser generator - Language Documentation ---------------------- @@ -211,6 +78,7 @@ Contents solidity-by-example.rst solidity-in-depth.rst security-considerations.rst + resources.rst using-the-compiler.rst metadata.rst abi-spec.rst diff --git a/docs/resources.rst b/docs/resources.rst new file mode 100644 index 00000000..e9e65692 --- /dev/null +++ b/docs/resources.rst @@ -0,0 +1,132 @@ +Resources +--------- + +General +~~~~~~~ + +* `Ethereum <https://ethereum.org>`_ + +* `Changelog <https://github.com/ethereum/solidity/blob/develop/Changelog.md>`_ + +* `Source Code <https://github.com/ethereum/solidity/>`_ + +* `Ethereum Stackexchange <https://ethereum.stackexchange.com/>`_ + +* `Language Users Chat <https://gitter.im/ethereum/solidity/>`_ + +* `Compiler Developers Chat <https://gitter.im/ethereum/solidity-dev/>`_ + +Solidity Integrations +~~~~~~~~~~~~~~~~~~~~~ + +* Generic: + + + * `EthFiddle <https://ethfiddle.com/>`_ + Solidity IDE in the Browser. Write and share your solidity code. Uses server-side components. + + * `Remix <https://remix.ethereum.org/>`_ + Browser-based IDE with integrated compiler and Solidity runtime environment without server-side components. + + * `Solium <https://github.com/duaraghav8/Solium/>`_ + Linter to identify and fix style and security issues in Solidity. + + * `Solhint <https://github.com/protofire/solhint>`_ + Solidity linter that provides security, style guide and best practice rules for smart contract validation. + +* Atom: + + * `Etheratom <https://github.com/0mkara/etheratom>`_ + Plugin for the Atom editor that features syntax highlighting, compilation and a runtime environment (Backend node & VM compatible). + + * `Atom Solidity Linter <https://atom.io/packages/linter-solidity>`_ + Plugin for the Atom editor that provides Solidity linting. + + * `Atom Solium Linter <https://atom.io/packages/linter-solium>`_ + Configurable Solidty linter for Atom using Solium as a base. + +* Eclipse: + + * `YAKINDU Solidity Tools <https://yakindu.github.io/solidity-ide/>`_ + Eclipse based IDE. Features context sensitive code completion and help, code navigation, syntax coloring, built in compiler, quick fixes and templates. + +* Emacs: + + * `Emacs Solidity <https://github.com/ethereum/emacs-solidity/>`_ + Plugin for the Emacs editor providing syntax highlighting and compilation error reporting. + +* IntelliJ: + + * `IntelliJ IDEA plugin <https://plugins.jetbrains.com/plugin/9475-intellij-solidity>`_ + Solidity plugin for IntelliJ IDEA (and all other JetBrains IDEs) + +* Sublime: + + * `Package for SublimeText - Solidity language syntax <https://packagecontrol.io/packages/Ethereum/>`_ + Solidity syntax highlighting for SublimeText editor. + +* Vim: + + * `Vim Solidity <https://github.com/tomlion/vim-solidity/>`_ + Plugin for the Vim editor providing syntax highlighting. + + * `Vim Syntastic <https://github.com/scrooloose/syntastic>`_ + Plugin for the Vim editor providing compile checking. + +* Visual Studio Code: + + * `Visual Studio Code extension <http://juan.blanco.ws/solidity-contracts-in-visual-studio-code/>`_ + Solidity plugin for Microsoft Visual Studio Code that includes syntax highlighting and the Solidity compiler. + +Discontinued: + +* `Mix IDE <https://github.com/ethereum/mix/>`_ + Qt based IDE for designing, debugging and testing solidity smart contracts. + +* `Ethereum Studio <https://live.ether.camp/>`_ + Specialized web IDE that also provides shell access to a complete Ethereum environment. + +* `Visual Studio Extension <https://visualstudiogallery.msdn.microsoft.com/96221853-33c4-4531-bdd5-d2ea5acc4799/>`_ + Solidity plugin for Microsoft Visual Studio that includes the Solidity compiler. + +Solidity Tools +~~~~~~~~~~~~~~ + +* `Dapp <https://dapp.tools/dapp/>`_ + Build tool, package manager, and deployment assistant for Solidity. + +* `Solidity REPL <https://github.com/raineorshine/solidity-repl>`_ + Try Solidity instantly with a command-line Solidity console. + +* `solgraph <https://github.com/raineorshine/solgraph>`_ + Visualize Solidity control flow and highlight potential security vulnerabilities. + +* `Doxity <https://github.com/DigixGlobal/doxity>`_ + Documentation Generator for Solidity. + +* `evmdis <https://github.com/Arachnid/evmdis>`_ + EVM Disassembler that performs static analysis on the bytecode to provide a higher level of abstraction than raw EVM operations. + +* `ABI to solidity interface converter <https://gist.github.com/chriseth/8f533d133fa0c15b0d6eaf3ec502c82b>`_ + A script for generating contract interfaces from the ABI of a smart contract. + +* `Securify <https://securify.ch/>`_ + Fully automated online static analyzer for smart contracts, providing a security report based on vulnerability patterns. + +* `Sūrya <https://github.com/ConsenSys/surya/>`_ + Utility tool for smart contract systems, offering a number of visual outputs and information about the contracts' structure. Also supports querying the function call graph. + +* `EVM Lab <https://github.com/ethereum/evmlab/>`_ + Rich tool package to interact with the EVM. Includes a VM, Etherchain API, and a trace-viewer with gas cost display. + +.. note:: + Information like variable names, comments, and source code formatting is lost in the compilation process and it is not possible to completely recover the original source code. Decompiling smart contracts to view the original source code might not be possible, or the end result that useful. + +Third-Party Solidity Parsers and Grammars +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +* `solidity-parser <https://github.com/ConsenSys/solidity-parser>`_ + Solidity parser for JavaScript + +* `Solidity Grammar for ANTLR 4 <https://github.com/federicobond/solidity-antlr4>`_ + Solidity grammar for the ANTLR 4 parser generator diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 1aa3504c..cc730575 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -80,45 +80,6 @@ string dev::readStandardInput() return ret; } -void dev::writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename) -{ - namespace fs = boost::filesystem; - if (_writeDeleteRename) - { - fs::path tempPath = fs::unique_path(_file + "-%%%%%%"); - writeFile(tempPath.string(), _data, false); - // will delete _file if it exists - fs::rename(tempPath, _file); - } - else - { - // create directory if not existent - fs::path p(_file); - if (!p.parent_path().empty() && !fs::exists(p.parent_path())) - { - fs::create_directories(p.parent_path()); - try - { - fs::permissions(p.parent_path(), fs::owner_all); - } - catch (...) - { - } - } - - ofstream s(_file, ios::trunc | ios::binary); - s.write(reinterpret_cast<char const*>(_data.data()), _data.size()); - assertThrow(s, FileError, "Could not write to file: " + _file); - try - { - fs::permissions(_file, fs::owner_read|fs::owner_write); - } - catch (...) - { - } - } -} - #if defined(_WIN32) class DisableConsoleBuffering { diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 928b6d15..b9f941ea 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -41,14 +41,6 @@ std::string readStandardInput(); /// Retrieve and returns a character from standard input (without waiting for EOL). int readStandardInputChar(); -/// Write the given binary data into the given file, replacing the file if it pre-exists. -/// Throws exception on error. -/// @param _writeDeleteRename useful not to lose any data: If set, first writes to another file in -/// the same directory and then moves that file. -void writeFile(std::string const& _file, bytesConstRef _data, bool _writeDeleteRename = false); -/// Write the given binary data into the given file, replacing the file if it pre-exists. -inline void writeFile(std::string const& _file, bytes const& _data, bool _writeDeleteRename = false) { writeFile(_file, bytesConstRef(&_data), _writeDeleteRename); } -inline void writeFile(std::string const& _file, std::string const& _data, bool _writeDeleteRename = false) { writeFile(_file, bytesConstRef(_data), _writeDeleteRename); } /// Converts arbitrary value to string representation using std::stringstream. template <class _T> std::string toString(_T const& _t) diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 27440289..749739ce 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -568,12 +568,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case FunctionType::Kind::External: case FunctionType::Kind::DelegateCall: case FunctionType::Kind::BareCall: - case FunctionType::Kind::BareCallCode: case FunctionType::Kind::BareDelegateCall: case FunctionType::Kind::BareStaticCall: _functionCall.expression().accept(*this); appendExternalFunctionCall(function, arguments); break; + case FunctionType::Kind::BareCallCode: + solAssert(false, "Callcode has been removed."); case FunctionType::Kind::Creation: { _functionCall.expression().accept(*this); @@ -1328,8 +1329,6 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) m_context << Instruction::CALLVALUE; else if (member == "origin") m_context << Instruction::ORIGIN; - else if (member == "gas") - m_context << Instruction::GAS; else if (member == "gasprice") m_context << Instruction::GASPRICE; else if (member == "data") @@ -1337,9 +1336,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) else if (member == "sig") m_context << u256(0) << Instruction::CALLDATALOAD << (u256(0xffffffff) << (256 - 32)) << Instruction::AND; + else if (member == "gas") + solAssert(false, "Gas has been removed."); else if (member == "blockhash") - { - } + solAssert(false, "Blockhash has been removed."); else solAssert(false, "Unknown magic member."); break; @@ -1844,8 +1844,9 @@ void ExpressionCompiler::appendExternalFunctionCall( solAssert(funKind != FunctionType::Kind::BareStaticCall || m_context.evmVersion().hasStaticCall(), ""); - bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareCallCode || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; - bool isCallCode = funKind == FunctionType::Kind::BareCallCode; + solAssert(funKind != FunctionType::Kind::BareCallCode, "Callcode has been removed."); + + bool returnSuccessConditionAndReturndata = funKind == FunctionType::Kind::BareCall || funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::BareStaticCall; bool isDelegateCall = funKind == FunctionType::Kind::BareDelegateCall || funKind == FunctionType::Kind::DelegateCall; bool useStaticCall = funKind == FunctionType::Kind::BareStaticCall || (_functionType.stateMutability() <= StateMutability::View && m_context.evmVersion().hasStaticCall()); @@ -1930,7 +1931,7 @@ void ExpressionCompiler::appendExternalFunctionCall( parameterTypes, _functionType.padArguments(), _functionType.takesArbitraryParameters() || _functionType.isBareCall(), - isCallCode || isDelegateCall + isDelegateCall ); // Stack now: @@ -2001,8 +2002,6 @@ void ExpressionCompiler::appendExternalFunctionCall( // Order is important here, STATICCALL might overlap with DELEGATECALL. if (isDelegateCall) m_context << Instruction::DELEGATECALL; - else if (isCallCode) - m_context << Instruction::CALLCODE; else if (useStaticCall) m_context << Instruction::STATICCALL; else diff --git a/libsolidity/formal/SMTChecker.cpp b/libsolidity/formal/SMTChecker.cpp index 671c2049..e5648eb3 100644 --- a/libsolidity/formal/SMTChecker.cpp +++ b/libsolidity/formal/SMTChecker.cpp @@ -458,6 +458,12 @@ void SMTChecker::endVisit(Identifier const& _identifier) { // Will be translated as part of the node that requested the lvalue. } + else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) + { + if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require) + return; + createExpr(_identifier); + } else if (isSupportedType(_identifier.annotation().type->category())) { if (VariableDeclaration const* decl = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration)) @@ -469,25 +475,15 @@ void SMTChecker::endVisit(Identifier const& _identifier) "Assertion checker does not yet support the type of this variable." ); } - else if (FunctionType const* fun = dynamic_cast<FunctionType const*>(_identifier.annotation().type.get())) - { - if (fun->kind() == FunctionType::Kind::Assert || fun->kind() == FunctionType::Kind::Require) - return; - createExpr(_identifier); - } } void SMTChecker::endVisit(Literal const& _literal) { Type const& type = *_literal.annotation().type; - if (type.category() == Type::Category::Integer || type.category() == Type::Category::Address || type.category() == Type::Category::RationalNumber) - { - if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(&type)) - solAssert(!rational->isFractional(), ""); + if (isNumber(type.category())) defineExpr(_literal, smt::Expression(type.literalValue(&_literal))); - } - else if (type.category() == Type::Category::Bool) + else if (isBool(type.category())) defineExpr(_literal, smt::Expression(_literal.token() == Token::TrueLiteral ? true : false)); else m_errorReporter.warning( @@ -917,13 +913,10 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) if (hasVariable(_varDecl)) return true; auto const& type = _varDecl.type(); - if (isSupportedType(type->category())) - { - solAssert(m_variables.count(&_varDecl) == 0, ""); - m_variables.emplace(&_varDecl, newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_interface)); - return true; - } - else + solAssert(m_variables.count(&_varDecl) == 0, ""); + auto result = newSymbolicVariable(*type, _varDecl.name() + "_" + to_string(_varDecl.id()), *m_interface); + m_variables.emplace(&_varDecl, result.second); + if (result.first) { m_errorReporter.warning( _varDecl.location(), @@ -931,6 +924,7 @@ bool SMTChecker::createVariable(VariableDeclaration const& _varDecl) ); return false; } + return true; } string SMTChecker::uniqueSymbol(Expression const& _expr) @@ -980,7 +974,7 @@ smt::Expression SMTChecker::expr(Expression const& _e) m_errorReporter.warning(_e.location(), "Internal error: Expression undefined for SMT solver." ); createExpr(_e); } - return prev(m_expressions.upper_bound(&_e))->second; + return m_expressions.at(&_e)->currentValue(); } bool SMTChecker::hasExpr(Expression const& _e) const @@ -996,33 +990,17 @@ bool SMTChecker::hasVariable(VariableDeclaration const& _var) const void SMTChecker::createExpr(Expression const& _e) { solAssert(_e.annotation().type, ""); - string exprSymbol = uniqueSymbol(_e) + "_" + to_string(m_expressions.count(&_e)); - switch (_e.annotation().type->category()) - { - case Type::Category::RationalNumber: + if (hasExpr(_e)) + m_expressions.at(&_e)->increaseIndex(); + else { - if (RationalNumberType const* rational = dynamic_cast<RationalNumberType const*>(_e.annotation().type.get())) - solAssert(!rational->isFractional(), ""); - m_expressions.emplace(&_e, m_interface->newInteger(exprSymbol)); - break; - } - case Type::Category::Address: - case Type::Category::Integer: - m_expressions.emplace(&_e, m_interface->newInteger(exprSymbol)); - break; - case Type::Category::Bool: - m_expressions.emplace(&_e, m_interface->newBool(exprSymbol)); - break; - case Type::Category::Function: - // This should be replaced by a `non-deterministic` type in the future. - m_expressions.emplace(&_e, m_interface->newInteger(exprSymbol)); - break; - default: - m_expressions.emplace(&_e, m_interface->newInteger(exprSymbol)); - m_errorReporter.warning( - _e.location(), - "Assertion checker does not yet implement this type." - ); + auto result = newSymbolicVariable(*_e.annotation().type, "expr_" + to_string(_e.id()), *m_interface); + m_expressions.emplace(&_e, result.second); + if (result.first) + m_errorReporter.warning( + _e.location(), + "Assertion checker does not yet implement this type." + ); } } diff --git a/libsolidity/formal/SMTChecker.h b/libsolidity/formal/SMTChecker.h index 0b078556..f66693d2 100644 --- a/libsolidity/formal/SMTChecker.h +++ b/libsolidity/formal/SMTChecker.h @@ -25,7 +25,6 @@ #include <libsolidity/interface/ReadFile.h> -#include <map> #include <unordered_map> #include <string> #include <vector> @@ -186,7 +185,7 @@ private: bool m_loopExecutionHappened = false; /// An Expression may have multiple smt::Expression due to /// repeated calls to the same function. - std::multimap<Expression const*, smt::Expression> m_expressions; + std::unordered_map<Expression const*, std::shared_ptr<SymbolicVariable>> m_expressions; std::unordered_map<VariableDeclaration const*, std::shared_ptr<SymbolicVariable>> m_variables; std::vector<smt::Expression> m_pathConditions; ErrorReporter& m_errorReporter; diff --git a/libsolidity/formal/SymbolicTypes.cpp b/libsolidity/formal/SymbolicTypes.cpp index 2d993865..d47869db 100644 --- a/libsolidity/formal/SymbolicTypes.cpp +++ b/libsolidity/formal/SymbolicTypes.cpp @@ -30,27 +30,44 @@ using namespace dev::solidity; bool dev::solidity::isSupportedType(Type::Category _category) { - return isInteger(_category) || - isAddress(_category) || - isBool(_category); + return isNumber(_category) || + isBool(_category) || + isFunction(_category); } -shared_ptr<SymbolicVariable> dev::solidity::newSymbolicVariable( +pair<bool, shared_ptr<SymbolicVariable>> dev::solidity::newSymbolicVariable( Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver ) { + bool abstract = false; + shared_ptr<SymbolicVariable> var; if (!isSupportedType(_type)) - return nullptr; - if (isBool(_type.category())) - return make_shared<SymbolicBoolVariable>(_type, _uniqueName, _solver); + { + abstract = true; + var = make_shared<SymbolicIntVariable>(IntegerType{256}, _uniqueName, _solver); + } + else if (isBool(_type.category())) + var = make_shared<SymbolicBoolVariable>(_type, _uniqueName, _solver); + else if (isFunction(_type.category())) + var = make_shared<SymbolicIntVariable>(IntegerType{256}, _uniqueName, _solver); else if (isInteger(_type.category())) - return make_shared<SymbolicIntVariable>(_type, _uniqueName, _solver); + var = make_shared<SymbolicIntVariable>(_type, _uniqueName, _solver); else if (isAddress(_type.category())) - return make_shared<SymbolicAddressVariable>(_type, _uniqueName, _solver); + var = make_shared<SymbolicAddressVariable>(_type, _uniqueName, _solver); + else if (isRational(_type.category())) + { + auto rational = dynamic_cast<RationalNumberType const*>(&_type); + solAssert(rational, ""); + if (rational->isFractional()) + var = make_shared<SymbolicIntVariable>(IntegerType{256}, _uniqueName, _solver); + else + var = make_shared<SymbolicIntVariable>(_type, _uniqueName, _solver); + } else solAssert(false, ""); + return make_pair(abstract, var); } bool dev::solidity::isSupportedType(Type const& _type) @@ -63,9 +80,9 @@ bool dev::solidity::isInteger(Type::Category _category) return _category == Type::Category::Integer; } -bool dev::solidity::isInteger(Type const& _type) +bool dev::solidity::isRational(Type::Category _category) { - return isInteger(_type.category()); + return _category == Type::Category::RationalNumber; } bool dev::solidity::isAddress(Type::Category _category) @@ -73,30 +90,21 @@ bool dev::solidity::isAddress(Type::Category _category) return _category == Type::Category::Address; } -bool dev::solidity::isAddress(Type const& _type) -{ - return isAddress(_type.category()); -} - bool dev::solidity::isNumber(Type::Category _category) { return isInteger(_category) || + isRational(_category) || isAddress(_category); } -bool dev::solidity::isNumber(Type const& _type) -{ - return isNumber(_type.category()); -} - bool dev::solidity::isBool(Type::Category _category) { return _category == Type::Category::Bool; } -bool dev::solidity::isBool(Type const& _type) +bool dev::solidity::isFunction(Type::Category _category) { - return isBool(_type.category()); + return _category == Type::Category::Function; } smt::Expression dev::solidity::minValue(IntegerType const& _type) diff --git a/libsolidity/formal/SymbolicTypes.h b/libsolidity/formal/SymbolicTypes.h index 77822fed..0887fa41 100644 --- a/libsolidity/formal/SymbolicTypes.h +++ b/libsolidity/formal/SymbolicTypes.h @@ -34,18 +34,16 @@ bool isSupportedType(Type::Category _category); bool isSupportedType(Type const& _type); bool isInteger(Type::Category _category); -bool isInteger(Type const& _type); - +bool isRational(Type::Category _category); bool isAddress(Type::Category _category); -bool isAddress(Type const& _type); - bool isNumber(Type::Category _category); -bool isNumber(Type const& _type); - bool isBool(Type::Category _category); -bool isBool(Type const& _type); +bool isFunction(Type::Category _category); -std::shared_ptr<SymbolicVariable> newSymbolicVariable(Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver); +/// Returns a new symbolic variable, according to _type. +/// Also returns whether the type is abstract or not, +/// which is true for unsupported types. +std::pair<bool, std::shared_ptr<SymbolicVariable>> newSymbolicVariable(Type const& _type, std::string const& _uniqueName, smt::SolverInterface& _solver); smt::Expression minValue(IntegerType const& _type); smt::Expression maxValue(IntegerType const& _type); diff --git a/libyul/optimiser/CommonSubexpressionEliminator.cpp b/libyul/optimiser/CommonSubexpressionEliminator.cpp index 23d15cad..51737097 100644 --- a/libyul/optimiser/CommonSubexpressionEliminator.cpp +++ b/libyul/optimiser/CommonSubexpressionEliminator.cpp @@ -50,8 +50,8 @@ void CommonSubexpressionEliminator::visit(Expression& _e) if (m_value.at(name)->type() == typeid(Identifier)) { string const& value = boost::get<Identifier>(*m_value.at(name)).name; - if (inScope(value)) - _e = Identifier{locationOf(_e), value}; + assertThrow(inScope(value), OptimizerException, ""); + _e = Identifier{locationOf(_e), value}; } } } @@ -61,6 +61,7 @@ void CommonSubexpressionEliminator::visit(Expression& _e) for (auto const& var: m_value) { assertThrow(var.second, OptimizerException, ""); + assertThrow(inScope(var.first), OptimizerException, ""); if (SyntacticalEqualityChecker::equal(_e, *var.second)) { _e = Identifier{locationOf(_e), var.first}; diff --git a/libyul/optimiser/DataFlowAnalyzer.cpp b/libyul/optimiser/DataFlowAnalyzer.cpp index ca1e5153..7ac42c30 100644 --- a/libyul/optimiser/DataFlowAnalyzer.cpp +++ b/libyul/optimiser/DataFlowAnalyzer.cpp @@ -84,19 +84,19 @@ void DataFlowAnalyzer::operator()(Switch& _switch) void DataFlowAnalyzer::operator()(FunctionDefinition& _fun) { - m_variableScopes.emplace_back(true); + pushScope(true); for (auto const& parameter: _fun.parameters) m_variableScopes.back().variables.insert(parameter.name); for (auto const& var: _fun.returnVariables) m_variableScopes.back().variables.insert(var.name); ASTModifier::operator()(_fun); - m_variableScopes.pop_back(); + popScope(); } void DataFlowAnalyzer::operator()(ForLoop& _for) { // Special scope handling of the pre block. - m_variableScopes.emplace_back(false); + pushScope(false); for (auto& statement: _for.pre.statements) visit(statement); @@ -110,16 +110,15 @@ void DataFlowAnalyzer::operator()(ForLoop& _for) (*this)(_for.post); clearValues(assignments.names()); - - m_variableScopes.pop_back(); + popScope(); } void DataFlowAnalyzer::operator()(Block& _block) { size_t numScopes = m_variableScopes.size(); - m_variableScopes.emplace_back(false); + pushScope(false); ASTModifier::operator()(_block); - m_variableScopes.pop_back(); + popScope(); assertThrow(numScopes == m_variableScopes.size(), OptimizerException, ""); } @@ -148,7 +147,18 @@ void DataFlowAnalyzer::handleAssignment(set<string> const& _variables, Expressio } } -void DataFlowAnalyzer::clearValues(set<string> const& _variables) +void DataFlowAnalyzer::pushScope(bool _functionScope) +{ + m_variableScopes.emplace_back(_functionScope); +} + +void DataFlowAnalyzer::popScope() +{ + clearValues(std::move(m_variableScopes.back().variables)); + m_variableScopes.pop_back(); +} + +void DataFlowAnalyzer::clearValues(set<string> _variables) { // All variables that reference variables to be cleared also have to be // cleared, but not recursively, since only the value of the original @@ -163,16 +173,15 @@ void DataFlowAnalyzer::clearValues(set<string> const& _variables) // This cannot be easily tested since the substitutions will be done // one by one on the fly, and the last line will just be add(1, 1) - set<string> variables = _variables; // Clear variables that reference variables to be cleared. - for (auto const& name: variables) + for (auto const& name: _variables) for (auto const& ref: m_referencedBy[name]) - variables.insert(ref); + _variables.insert(ref); // Clear the value and update the reference relation. - for (auto const& name: variables) + for (auto const& name: _variables) m_value.erase(name); - for (auto const& name: variables) + for (auto const& name: _variables) { for (auto const& ref: m_references[name]) m_referencedBy[ref].erase(name); diff --git a/libyul/optimiser/DataFlowAnalyzer.h b/libyul/optimiser/DataFlowAnalyzer.h index f998eadf..95671467 100644 --- a/libyul/optimiser/DataFlowAnalyzer.h +++ b/libyul/optimiser/DataFlowAnalyzer.h @@ -56,9 +56,15 @@ protected: /// Registers the assignment. void handleAssignment(std::set<std::string> const& _names, Expression* _value); + /// Creates a new inner scope. + void pushScope(bool _functionScope); + + /// Removes the innermost scope and clears all variables in it. + void popScope(); + /// Clears information about the values assigned to the given variables, /// for example at points where control flow is merged. - void clearValues(std::set<std::string> const& _names); + void clearValues(std::set<std::string> _names); /// Returns true iff the variable is in scope. bool inScope(std::string const& _variableName) const; diff --git a/libyul/optimiser/Disambiguator.h b/libyul/optimiser/Disambiguator.h index e16ebfbf..74a491ab 100644 --- a/libyul/optimiser/Disambiguator.h +++ b/libyul/optimiser/Disambiguator.h @@ -47,9 +47,8 @@ public: solidity::assembly::AsmAnalysisInfo const& _analysisInfo, std::set<std::string> const& _externallyUsedIdentifiers = {} ): - m_info(_analysisInfo), m_externallyUsedIdentifiers(_externallyUsedIdentifiers) + m_info(_analysisInfo), m_externallyUsedIdentifiers(_externallyUsedIdentifiers), m_nameDispenser(m_externallyUsedIdentifiers) { - m_nameDispenser.m_usedNames = m_externallyUsedIdentifiers; } protected: diff --git a/libyul/optimiser/FullInliner.cpp b/libyul/optimiser/FullInliner.cpp index aa069506..280d625a 100644 --- a/libyul/optimiser/FullInliner.cpp +++ b/libyul/optimiser/FullInliner.cpp @@ -40,13 +40,9 @@ using namespace dev; using namespace dev::yul; using namespace dev::solidity; -FullInliner::FullInliner(Block& _ast): - m_ast(_ast) +FullInliner::FullInliner(Block& _ast, NameDispenser& _dispenser): + m_ast(_ast), m_nameDispenser(_dispenser) { - assertThrow(m_ast.statements.size() >= 1, OptimizerException, ""); - assertThrow(m_ast.statements.front().type() == typeid(Block), OptimizerException, ""); - m_nameDispenser.m_usedNames = NameCollector(m_ast).names(); - // Determine constants SSAValueTracker tracker; tracker(m_ast); @@ -55,10 +51,11 @@ FullInliner::FullInliner(Block& _ast): m_constants.insert(ssaValue.first); map<string, size_t> references = ReferencesCounter::countReferences(m_ast); - for (size_t i = 1; i < m_ast.statements.size(); ++i) + for (auto& statement: m_ast.statements) { - assertThrow(m_ast.statements.at(i).type() == typeid(FunctionDefinition), OptimizerException, ""); - FunctionDefinition& fun = boost::get<FunctionDefinition>(m_ast.statements.at(i)); + if (statement.type() != typeid(FunctionDefinition)) + continue; + FunctionDefinition& fun = boost::get<FunctionDefinition>(statement); m_functions[fun.name] = &fun; // Always inline functions that are only called once. if (references[fun.name] == 1) @@ -69,9 +66,10 @@ FullInliner::FullInliner(Block& _ast): void FullInliner::run() { - assertThrow(m_ast.statements[0].type() == typeid(Block), OptimizerException, ""); + for (auto& statement: m_ast.statements) + if (statement.type() == typeid(Block)) + handleBlock("", boost::get<Block>(statement)); - handleBlock("", boost::get<Block>(m_ast.statements[0])); // TODO it might be good to determine a visiting order: // first handle functions that are called from many places. for (auto const& fun: m_functions) @@ -157,7 +155,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC // helper function to create a new variable that is supposed to model // an existing variable. auto newVariable = [&](TypedName const& _existingVariable, Expression* _value) { - string newName = m_nameDispenser.newName(function.name + "_" + _existingVariable.name); + string newName = m_nameDispenser.newName(_existingVariable.name, function.name); variableReplacements[_existingVariable.name] = newName; VariableDeclaration varDecl{_funCall.location, {{_funCall.location, newName, _existingVariable.type}}, {}}; if (_value) @@ -170,7 +168,7 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC for (auto const& var: function.returnVariables) newVariable(var, nullptr); - Statement newBody = BodyCopier(m_nameDispenser, function.name + "_", variableReplacements)(function.body); + Statement newBody = BodyCopier(m_nameDispenser, function.name, variableReplacements)(function.body); newStatements += std::move(boost::get<Block>(newBody).statements); boost::apply_visitor(GenericFallbackVisitor<Assignment, VariableDeclaration>{ @@ -203,15 +201,10 @@ vector<Statement> InlineModifier::performInline(Statement& _statement, FunctionC return newStatements; } -string InlineModifier::newName(string const& _prefix) -{ - return m_nameDispenser.newName(_prefix); -} - Statement BodyCopier::operator()(VariableDeclaration const& _varDecl) { for (auto const& var: _varDecl.variables) - m_variableReplacements[var.name] = m_nameDispenser.newName(m_varNamePrefix + var.name); + m_variableReplacements[var.name] = m_nameDispenser.newName(var.name, m_varNamePrefix); return ASTCopier::operator()(_varDecl); } diff --git a/libyul/optimiser/FullInliner.h b/libyul/optimiser/FullInliner.h index 513ffc93..5ecfb57a 100644 --- a/libyul/optimiser/FullInliner.h +++ b/libyul/optimiser/FullInliner.h @@ -71,7 +71,7 @@ class NameCollector; class FullInliner: public ASTModifier { public: - explicit FullInliner(Block& _ast); + explicit FullInliner(Block& _ast, NameDispenser& _dispenser); void run(); @@ -94,7 +94,7 @@ private: /// Variables that are constants (used for inlining heuristic) std::set<std::string> m_constants; std::map<std::string, size_t> m_functionSizes; - NameDispenser m_nameDispenser; + NameDispenser& m_nameDispenser; }; /** @@ -116,8 +116,6 @@ private: boost::optional<std::vector<Statement>> tryInlineStatement(Statement& _statement); std::vector<Statement> performInline(Statement& _statement, FunctionCall& _funCall); - std::string newName(std::string const& _prefix); - std::string m_currentFunction; FullInliner& m_driver; NameDispenser& m_nameDispenser; diff --git a/libyul/optimiser/NameDispenser.cpp b/libyul/optimiser/NameDispenser.cpp index f7385471..d3f10bc2 100644 --- a/libyul/optimiser/NameDispenser.cpp +++ b/libyul/optimiser/NameDispenser.cpp @@ -20,18 +20,43 @@ #include <libyul/optimiser/NameDispenser.h> +#include <libyul/optimiser/NameCollector.h> + +#include <libsolidity/inlineasm/AsmData.h> + using namespace std; using namespace dev; using namespace dev::yul; -string NameDispenser::newName(string const& _prefix) +NameDispenser::NameDispenser(Block const& _ast): + NameDispenser(NameCollector(_ast).names()) +{ +} + +NameDispenser::NameDispenser(set<string> _usedNames): + m_usedNames(std::move(_usedNames)) +{ +} + +string NameDispenser::newName(string const& _nameHint, string const& _context) +{ + // Shortening rules: Use a suffix of _prefix and a prefix of _context. + string prefix = _nameHint; + + if (!_context.empty()) + prefix = _context.substr(0, 10) + "_" + prefix; + + return newNameInternal(prefix); +} + +string NameDispenser::newNameInternal(string const& _nameHint) { - string name = _prefix; size_t suffix = 0; + string name = _nameHint; while (name.empty() || m_usedNames.count(name)) { suffix++; - name = _prefix + "_" + to_string(suffix); + name = _nameHint + "_" + to_string(suffix); } m_usedNames.insert(name); return name; diff --git a/libyul/optimiser/NameDispenser.h b/libyul/optimiser/NameDispenser.h index 64ec318f..5fbf5f8e 100644 --- a/libyul/optimiser/NameDispenser.h +++ b/libyul/optimiser/NameDispenser.h @@ -19,6 +19,8 @@ */ #pragma once +#include <libyul/ASTDataForward.h> + #include <set> #include <string> @@ -27,9 +29,29 @@ namespace dev namespace yul { -struct NameDispenser +/** + * Optimizer component that can be used to generate new names that + * do not conflict with existing names. + * + * Tries to keep names short and appends decimals to disambiguate. + */ +class NameDispenser { - std::string newName(std::string const& _prefix); +public: + /// Initialize the name dispenser with all the names used in the given AST. + explicit NameDispenser(Block const& _ast); + /// Initialize the name dispenser with the given used names. + explicit NameDispenser(std::set<std::string> _usedNames); + + /// @returns a currently unused name that should be similar to _nameHint + /// and prefixed by _context if present. + /// If the resulting name would be too long, trims the context at the end + /// and the name hint at the start. + std::string newName(std::string const& _nameHint, std::string const& _context = {}); + +private: + std::string newNameInternal(std::string const& _nameHint); + std::set<std::string> m_usedNames; }; diff --git a/libyul/optimiser/Rematerialiser.cpp b/libyul/optimiser/Rematerialiser.cpp index dd6653ea..a99db0b6 100644 --- a/libyul/optimiser/Rematerialiser.cpp +++ b/libyul/optimiser/Rematerialiser.cpp @@ -38,16 +38,11 @@ void Rematerialiser::visit(Expression& _e) if (m_value.count(identifier.name)) { string name = identifier.name; - bool expressionValid = true; for (auto const& ref: m_references[name]) - if (!inScope(ref)) - { - expressionValid = false; - break; - } + assertThrow(inScope(ref), OptimizerException, ""); assertThrow(m_value.at(name), OptimizerException, ""); auto const& value = *m_value.at(name); - if (expressionValid && CodeSize::codeSize(value) <= 7) + if (CodeSize::codeSize(value) <= 7) _e = (ASTCopier{}).translate(value); } } diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index e0c6a2b6..844cef90 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -1118,7 +1118,15 @@ void CommandLineInterface::writeLinkedFiles() if (src.first == g_stdinFileName) cout << src.second << endl; else - writeFile(src.first, src.second); + { + ofstream outFile(src.first); + outFile << src.second; + if (!outFile) + { + cerr << "Could not write to file " << src.first << ". Aborting." << endl; + return; + } + } } string CommandLineInterface::libraryPlaceholderHint(string const& _libraryName) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7a496e64..133387c8 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -14010,6 +14010,21 @@ BOOST_AUTO_TEST_CASE(test_underscore_in_hex) ABI_CHECK(callContractFunction("f(bool)", false), encodeArgs(u256(0x1234abcd1234))); } +BOOST_AUTO_TEST_CASE(flipping_sign_tests) +{ + char const* sourceCode = R"( + contract test { + function f() public returns (bool){ + int x = -2**255; + assert(-x == x); + return true; + } + } + )"; + compileAndRun(sourceCode); + ABI_CHECK(callContractFunction("f()"), encodeArgs(true)); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libyul/YulOptimizerTest.cpp b/test/libyul/YulOptimizerTest.cpp index 8e4771c8..fabc05ca 100644 --- a/test/libyul/YulOptimizerTest.cpp +++ b/test/libyul/YulOptimizerTest.cpp @@ -106,8 +106,7 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con } else if (m_optimizerStep == "expressionSplitter") { - NameDispenser nameDispenser; - nameDispenser.m_usedNames = NameCollector(*m_ast).names(); + NameDispenser nameDispenser(*m_ast); ExpressionSplitter{nameDispenser}(*m_ast); } else if (m_optimizerStep == "functionGrouper") @@ -130,10 +129,9 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con disambiguate(); (FunctionHoister{})(*m_ast); (FunctionGrouper{})(*m_ast); - NameDispenser nameDispenser; - nameDispenser.m_usedNames = NameCollector(*m_ast).names(); + NameDispenser nameDispenser(*m_ast); ExpressionSplitter{nameDispenser}(*m_ast); - FullInliner(*m_ast).run(); + FullInliner(*m_ast, nameDispenser).run(); ExpressionJoiner::run(*m_ast); } else if (m_optimizerStep == "mainFunction") @@ -155,8 +153,7 @@ bool YulOptimizerTest::run(ostream& _stream, string const& _linePrefix, bool con else if (m_optimizerStep == "fullSimplify") { disambiguate(); - NameDispenser nameDispenser; - nameDispenser.m_usedNames = NameCollector(*m_ast).names(); + NameDispenser nameDispenser(*m_ast); ExpressionSplitter{nameDispenser}(*m_ast); CommonSubexpressionEliminator{}(*m_ast); ExpressionSimplifier::run(*m_ast); diff --git a/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul new file mode 100644 index 00000000..49b4c916 --- /dev/null +++ b/test/libyul/yulOptimizerTests/commonSubexpressionEliminator/scopes.yul @@ -0,0 +1,25 @@ +{ + let a := 10 + let x := 20 + { + let b := calldataload(0) + let d := calldataload(1) + x := d + } + // We had a bug where "calldataload(0)" was incorrectly replaced by "b" + mstore(0, calldataload(0)) + mstore(0, x) +} +// ---- +// commonSubexpressionEliminator +// { +// let a := 10 +// let x := 20 +// { +// let b := calldataload(0) +// let d := calldataload(1) +// x := d +// } +// mstore(0, calldataload(0)) +// mstore(0, x) +// } diff --git a/test/libyul/yulOptimizerTests/disambiguator/long_names.yul b/test/libyul/yulOptimizerTests/disambiguator/long_names.yul new file mode 100644 index 00000000..933e1e8f --- /dev/null +++ b/test/libyul/yulOptimizerTests/disambiguator/long_names.yul @@ -0,0 +1,12 @@ +// yul +{ { let aanteuhdaoneudbrgkjiuaothduiathudaoeuh:u256 } { let aanteuhdaoneudbrgkjiuaothduiathudaoeuh:u256 } } +// ---- +// disambiguator +// { +// { +// let aanteuhdaoneudbrgkjiuaothduiathudaoeuh:u256 +// } +// { +// let aanteuhdaoneudbrgkjiuaothduiathudaoeuh_1:u256 +// } +// } diff --git a/test/libyul/yulOptimizerTests/fullInliner/long_names.yul b/test/libyul/yulOptimizerTests/fullInliner/long_names.yul new file mode 100644 index 00000000..644e9126 --- /dev/null +++ b/test/libyul/yulOptimizerTests/fullInliner/long_names.yul @@ -0,0 +1,25 @@ +{ + function verylongfunctionname(verylongvariablename) -> verylongvariablename2 { + verylongvariablename2 := add(verylongvariablename, verylongvariablename) + } + // same long name + let verylongvariablename2 := 3 + mstore(0, verylongfunctionname(verylongvariablename2)) + mstore(1, verylongvariablename2) +} +// ---- +// fullInliner +// { +// { +// let verylongvariablename2_1 := 3 +// let verylongfu_verylongvariablename := verylongvariablename2_1 +// let verylongfu_verylongvariablename2 +// verylongfu_verylongvariablename2 := add(verylongfu_verylongvariablename, verylongfu_verylongvariablename) +// mstore(0, verylongfu_verylongvariablename2) +// mstore(1, verylongvariablename2_1) +// } +// function verylongfunctionname(verylongvariablename) -> verylongvariablename2 +// { +// verylongvariablename2 := add(verylongvariablename, verylongvariablename) +// } +// } |