From 4bde0a2d36297c4b3fa17c7dac2bb1681e1e7f75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Thu, 20 Oct 2016 11:58:25 +0200 Subject: [PATCH 001/125] Build jsoncpp from source using jsoncpp.cmake script --- .travis.yml | 3 +- CMakeLists.txt | 1 + cmake/FindJsoncpp.cmake | 50 ------------------- cmake/UseJsoncpp.cmake | 10 ---- cmake/UseSolidity.cmake | 3 +- deps | 2 +- libevmasm/CMakeLists.txt | 3 +- liblll/CMakeLists.txt | 3 -- scripts/install_deps.sh | 10 +--- scripts/release_ppa.sh | 20 +------- scripts/travis-emscripten/build_emscripten.sh | 16 ------ scripts/travis-emscripten/install_deps.sh | 1 - 12 files changed, 10 insertions(+), 112 deletions(-) delete mode 100644 cmake/FindJsoncpp.cmake delete mode 100644 cmake/UseJsoncpp.cmake diff --git a/.travis.yml b/.travis.yml index 1f35a509a..143255671 100644 --- a/.travis.yml +++ b/.travis.yml @@ -138,7 +138,6 @@ cache: - cryptopp - boost_1_57_0 - build - - jsoncpp install: - test $TRAVIS_INSTALL_DEPS != On || ./scripts/install_deps.sh @@ -164,7 +163,7 @@ script: # in Solidity's RPC setup and some will be in 'eth'. It seems unlikely that Solidity # itself is broken from the failure messages which we are seeing. # - # More details on known issues at https://github.com/ethereum/solidity/issues/769 + # More details on known issues at https://github.com/ethereum/solidity/issues/769 - test $TRAVIS_TESTS != On || (cd $TRAVIS_BUILD_DIR && (./scripts/tests.sh || ./scripts/tests.sh || ./scripts/tests.sh) ) env: global: diff --git a/CMakeLists.txt b/CMakeLists.txt index 624402657..efde0f911 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,6 +13,7 @@ project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies include(EthDependencies) +include(deps/jsoncpp.cmake) # Figure out what compiler and system are we using include(EthCompilerSettings) diff --git a/cmake/FindJsoncpp.cmake b/cmake/FindJsoncpp.cmake deleted file mode 100644 index e8258b71c..000000000 --- a/cmake/FindJsoncpp.cmake +++ /dev/null @@ -1,50 +0,0 @@ -# Find jsoncpp -# -# Find the jsoncpp includes and library -# -# if you nee to add a custom library search path, do it via via CMAKE_PREFIX_PATH -# -# This module defines -# JSONCPP_INCLUDE_DIRS, where to find header, etc. -# JSONCPP_LIBRARIES, the libraries needed to use jsoncpp. -# JSONCPP_FOUND, If false, do not try to use jsoncpp. - -# only look in default directories -find_path( - JSONCPP_INCLUDE_DIR - NAMES json/json.h - PATH_SUFFIXES jsoncpp - DOC "jsoncpp include dir" -) - -find_library( - JSONCPP_LIBRARY - NAMES jsoncpp - DOC "jsoncpp library" -) - -set(JSONCPP_INCLUDE_DIRS ${JSONCPP_INCLUDE_DIR}) -set(JSONCPP_LIBRARIES ${JSONCPP_LIBRARY}) - -# debug library on windows -# same naming convention as in qt (appending debug library with d) -# boost is using the same "hack" as us with "optimized" and "debug" -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") - - find_library( - JSONCPP_LIBRARY_DEBUG - NAMES jsoncppd - DOC "jsoncpp debug library" - ) - - set(JSONCPP_LIBRARIES optimized ${JSONCPP_LIBRARIES} debug ${JSONCPP_LIBRARY_DEBUG}) - -endif() - -# handle the QUIETLY and REQUIRED arguments and set JSONCPP_FOUND to TRUE -# if all listed variables are TRUE, hide their existence from configuration view -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(jsoncpp DEFAULT_MSG - JSONCPP_LIBRARY JSONCPP_INCLUDE_DIR) -mark_as_advanced (JSONCPP_INCLUDE_DIR JSONCPP_LIBRARY) - diff --git a/cmake/UseJsoncpp.cmake b/cmake/UseJsoncpp.cmake deleted file mode 100644 index 6f6052833..000000000 --- a/cmake/UseJsoncpp.cmake +++ /dev/null @@ -1,10 +0,0 @@ -function(eth_apply TARGET REQUIRED) - find_package (Jsoncpp 0.60) - eth_show_dependency(JSONCPP JsonCpp) - if (JSONCPP_FOUND) - target_include_directories(${TARGET} SYSTEM BEFORE PUBLIC ${JSONCPP_INCLUDE_DIRS}) - target_link_libraries(${TARGET} ${JSONCPP_LIBRARIES}) - elseif (NOT ${REQUIRED} STREQUAL "OPTIONAL") - message(FATAL_ERROR "Jsoncpp library not found") - endif() -endfunction() diff --git a/cmake/UseSolidity.cmake b/cmake/UseSolidity.cmake index 9780464cb..5080f71b4 100644 --- a/cmake/UseSolidity.cmake +++ b/cmake/UseSolidity.cmake @@ -15,8 +15,7 @@ function(eth_apply TARGET REQUIRED SUBMODULE) target_include_directories(${TARGET} PUBLIC ${Solidity_INCLUDE_DIRS}) if (${SUBMODULE} STREQUAL "solevmasm") - eth_use(${TARGET} ${REQUIRED} Jsoncpp) - target_link_libraries(${TARGET} ${Solidity_SOLEVMASM_LIBRARIES}) + target_link_libraries(${TARGET} ${Solidity_SOLEVMASM_LIBRARIES} jsoncpp) endif() if (${SUBMODULE} STREQUAL "lll") diff --git a/deps b/deps index f2ede70f3..ff0c567cc 160000 --- a/deps +++ b/deps @@ -1 +1 @@ -Subproject commit f2ede70f33633b26a27299ff39995914db2c6923 +Subproject commit ff0c567cc0fd57049b5c29c3b3428097e6ca4644 diff --git a/libevmasm/CMakeLists.txt b/libevmasm/CMakeLists.txt index 14800f418..9cc3e93e9 100644 --- a/libevmasm/CMakeLists.txt +++ b/libevmasm/CMakeLists.txt @@ -8,6 +8,7 @@ file(GLOB HEADERS "*.h") include_directories(BEFORE ..) add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) -eth_use(${EXECUTABLE} REQUIRED Jsoncpp Dev::soldevcore) +eth_use(${EXECUTABLE} REQUIRED Dev::soldevcore) +target_link_libraries(${EXECUTABLE} jsoncpp) install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) diff --git a/liblll/CMakeLists.txt b/liblll/CMakeLists.txt index b9d220ab7..db90025a9 100644 --- a/liblll/CMakeLists.txt +++ b/liblll/CMakeLists.txt @@ -5,9 +5,6 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") aux_source_directory(. SRC_LIST) -#include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) -#include_directories(${Boost_INCLUDE_DIRS}) - set(EXECUTABLE lll) file(GLOB HEADERS "*.h") diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh index 334c62d4b..c9f827699 100755 --- a/scripts/install_deps.sh +++ b/scripts/install_deps.sh @@ -105,7 +105,6 @@ case $(uname -s) in brew install boost brew install cmake - brew install jsoncpp # We should really 'brew install' our eth client here, but at the time of writing # the bottle is known broken, so we will just cheat and use a hardcoded ZIP for @@ -164,7 +163,7 @@ case $(uname -s) in # See https://pkgs.alpinelinux.org/ apk update - apk add boost-dev build-base cmake jsoncpp-dev + apk add boost-dev build-base cmake ;; @@ -219,7 +218,6 @@ case $(uname -s) in gcc \ git \ libboost-all-dev \ - libjsoncpp-dev \ unzip ;; @@ -321,8 +319,7 @@ case $(uname -s) in build-essential \ cmake \ git \ - libboost-all-dev \ - libjsoncpp-dev + libboost-all-dev # Install 'eth', for use in the Solidity Tests-over-IPC. sudo add-apt-repository -y ppa:ethereum/ethereum @@ -363,9 +360,6 @@ case $(uname -s) in sudo yum -y remove boost-devel sudo wget http://repo.enetres.net/enetres.repo -O /etc/yum.repos.d/enetres.repo sudo yum install boost-devel - - # And finally jsoncpp - sudo yum -y install jsoncpp-devel else echo "Aborted CentOS Solidity Dependency Installation"; exit 1 diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 7231f582c..1aaa2a421 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -76,21 +76,6 @@ cp /tmp/${packagename}_${debversion}.orig.tar.gz ../ # Create debian package information -case $distribution in - trusty) - jsoncpplib=libjsoncpp0 - ;; - vivid) - jsoncpplib=libjsoncpp0 - ;; - wily) - jsoncpplib=libjsoncpp0v5 - ;; - *) - jsoncpplib=libjsoncpp1 - ;; -esac - mkdir debian echo 9 > debian/compat cat < debian/control @@ -107,8 +92,7 @@ Build-Depends: debhelper (>= 9.0.0), libboost-all-dev, automake, libtool, - scons, - libjsoncpp-dev + scons Standards-Version: 3.9.5 Homepage: https://ethereum.org Vcs-Git: git://github.com/ethereum/solidity.git @@ -117,7 +101,7 @@ Vcs-Browser: https://github.com/ethereum/solidity Package: solc Architecture: any-i386 any-amd64 Multi-Arch: same -Depends: \${shlibs:Depends}, \${misc:Depends}, $jsoncpplib +Depends: \${shlibs:Depends}, \${misc:Depends} Replaces: lllc (<< 1:0.3.6) Conflicts: libethereum (<= 1.2.9) Description: Solidity compiler. diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 285c6338c..17a40e1c9 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -52,20 +52,6 @@ rm -rf .git ) echo -en 'travis_fold:end:compiling_cryptopp\\r' -# Json-CPP -echo -en 'travis_fold:start:compiling_jsoncpp\\r' -cd "$WORKSPACE/jsoncpp" -# if .git exists, it is a fresh checkout, otherwise it comes from the cache -# and is already compiled -test -e .git && ( -emcmake cmake -DJSONCPP_LIB_BUILD_STATIC=ON -DJSONCPP_LIB_BUILD_SHARED=OFF \ - -DJSONCPP_WITH_TESTS=OFF -DJSONCPP_WITH_POST_BUILD_UNITTEST=OFF \ - -G "Unix Makefiles" . -emmake make -j 4 -rm -rf .git -) -echo -en 'travis_fold:end:compiling_jsoncpp\\r' - # Boost echo -en 'travis_fold:start:compiling_boost\\r' cd "$WORKSPACE"/boost_1_57_0 @@ -112,8 +98,6 @@ emcmake cmake \ -DBoost_THREAD_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_thread.a \ -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_unit_test_framework.a \ -DBoost_UNIT_TEST_FRAMEWORK_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_unit_test_framework.a \ - -DJSONCPP_LIBRARY="$WORKSPACE"/jsoncpp/src/lib_json/libjsoncpp.a \ - -DJSONCPP_INCLUDE_DIR="$WORKSPACE"/jsoncpp/include/ \ -DCRYPTOPP_LIBRARY="$WORKSPACE"/cryptopp/src/libcryptlib.a \ -DCRYPTOPP_INCLUDE_DIR="$WORKSPACE"/cryptopp/src/ \ -DDev_DEVCORE_LIBRARY="$WORKSPACE"/solidity/build/libdevcore/libsoldevcore.a \ diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index 2c0e9f26f..d85337c01 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -31,7 +31,6 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' test -e cryptopp -a -e cryptopp/src || git clone https://github.com/mmoss/cryptopp.git -test -e jsoncpp -a -e jsoncpp/include || git clone https://github.com/open-source-parsers/jsoncpp.git test -e boost_1_57_0 -a -e boost_1_57_0/boost || ( wget 'http://downloads.sourceforge.net/project/boost/boost/'\ '1.57.0/boost_1_57_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2F'\ From b269202b73dc6a22c0f6adc849d8fc063119e346 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 26 Oct 2016 13:47:32 +0100 Subject: [PATCH 002/125] LLL: support passing error reasons --- liblll/CodeFragment.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index e0d48ab7b..637d169fb 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -51,6 +51,11 @@ private: void finalise(CompilerState const& _cs); template void error() const { BOOST_THROW_EXCEPTION(T() ); } + template void error(std::string const& reason) const { + auto err = T(); + err << errinfo_comment(reason); + BOOST_THROW_EXCEPTION(err); + } void constructOperation(sp::utree const& _t, CompilerState& _s); bool m_finalised = false; From 9b65a79cb35860f4de6c653ded0e6ab58cf6d57e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 26 Oct 2016 13:48:13 +0100 Subject: [PATCH 003/125] LLL: report back unsupported keywords --- liblll/CodeFragment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 0f8f26061..0ba072a4c 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -587,7 +587,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_") == string::npos) m_asm.append((u256)varAddress(s)); else - error(); + error("Unsupported keyword: '" + us + "'"); } } From b24eed1c3e2f4527444b33cf1bba418480934f64 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 26 Oct 2016 14:27:39 +0100 Subject: [PATCH 004/125] LLL: remove unneeded includes --- liblll/CodeFragment.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 0ba072a4c..ac9eac37a 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -35,9 +35,6 @@ using namespace std; using namespace dev; using namespace dev::eth; -namespace qi = boost::spirit::qi; -namespace px = boost::phoenix; -namespace sp = boost::spirit; void CodeFragment::finalise(CompilerState const& _cs) { From 5be1996ea5bc73ef37df55cdd28504de5ec357c4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 26 Oct 2016 14:28:19 +0100 Subject: [PATCH 005/125] LLL: update exception message --- liblll/Compiler.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index 684991061..4008022f8 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -34,8 +34,7 @@ bytes dev::eth::compileLLL(string const& _src, bool _opt, vector* _error { CompilerState cs; cs.populateStandard(); - auto f = CodeFragment::compile(_src, cs); - bytes ret = f.assembly(cs).optimise(_opt).assemble().bytecode; + bytes ret = CodeFragment::compile(_src, cs).assembly(cs).optimise(_opt).assemble().bytecode; for (auto i: cs.treesToKill) killBigints(i); return ret; @@ -59,7 +58,7 @@ bytes dev::eth::compileLLL(string const& _src, bool _opt, vector* _error catch (...) { if (_errors) - _errors->push_back("Internal parse exception."); + _errors->push_back("Internal compiler exception."); } return bytes(); } @@ -93,7 +92,7 @@ std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::v catch (...) { if (_errors) - _errors->push_back("Internal parse exception."); + _errors->push_back("Internal compiler exception."); } return string(); } From b92bb41be78c4277a16974353ec2e21ea8cd5f7b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 26 Oct 2016 14:29:08 +0100 Subject: [PATCH 006/125] LLL: catch and display spirit::qi errors --- liblll/Parser.cpp | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index 2754e9f59..7d594e31e 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -135,10 +135,19 @@ void dev::eth::parseTreeLLL(string const& _s, sp::utree& o_out) s.push_back(i); } auto ret = s.cbegin(); - qi::phrase_parse(ret, s.cend(), element, space, qi::skip_flag::dont_postskip, o_out); + try + { + qi::phrase_parse(ret, s.cend(), element, space, qi::skip_flag::dont_postskip, o_out); + } + catch (qi::expectation_failure const& e) + { + std::string fragment(e.first, e.last); + std::string loc = std::to_string(std::distance(s.cbegin(), e.first) - 1); + std::string reason("Lexer failure at " + loc + ": '" + fragment + "'"); + BOOST_THROW_EXCEPTION(ParserException() << errinfo_comment(reason)); + } for (auto i = ret; i != s.cend(); ++i) if (!isspace(*i)) { BOOST_THROW_EXCEPTION(ParserException() << errinfo_comment("Non-whitespace left in parser")); } } - From 4f9741c0cf54a9fb86e901b29a87c2642f4dbab7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 1 Nov 2016 09:58:02 +0100 Subject: [PATCH 007/125] Version update in develop. --- CMakeLists.txt | 2 +- Changelog.md | 2 ++ docs/conf.py | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 7e38083b5..4a3437dfd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include(EthPolicy) eth_policy() # project name and version should be set after cmake_policy CMP0048 -set(PROJECT_VERSION "0.4.4") +set(PROJECT_VERSION "0.4.5") project(solidity VERSION ${PROJECT_VERSION}) # Let's find our dependencies diff --git a/Changelog.md b/Changelog.md index c83bcffad..ee1060471 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,5 @@ +### 0.4.5 (unreleased) + ### 0.4.4 (2016-10-31) Bugfixes: diff --git a/docs/conf.py b/docs/conf.py index ebc771246..e17d5fd8f 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -56,9 +56,9 @@ copyright = '2016, Ethereum' # built documents. # # The short X.Y version. -version = '0.4.4' +version = '0.4.5' # The full version, including alpha/beta/rc tags. -release = '0.4.4-develop' +release = '0.4.5-develop' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. From dc5e05681dfbacfcdbd875ce48c8839fcab20271 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 1 Nov 2016 01:01:35 +0000 Subject: [PATCH 008/125] LLL: simplify integer parsing --- liblll/Parser.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index 7d594e31e..219d4f54b 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -101,8 +101,8 @@ void dev::eth::parseTreeLLL(string const& _s, sp::utree& o_out) qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; - qi::rule integer = intstr; - qi::rule atom = integer[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; + qi::rule integer = intstr[qi::_val = px::construct(px::new_(qi::_1))]; + qi::rule atom = integer[qi::_val = qi::_1] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; qi::rule seq = '{' > *element > '}'; qi::rule mload = '@' > element; qi::rule sload = qi::lit("@@") > element; From ac3c8a553a0b943984742f6b12d9d0a3cabcb877 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 1 Nov 2016 02:06:40 +0000 Subject: [PATCH 009/125] LLL: properly support dashes (-) as part of variable names --- liblll/CodeFragment.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index ac9eac37a..bc53d7777 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -87,7 +87,7 @@ CodeFragment::CodeFragment(sp::utree const& _t, CompilerState& _s, bool _allowAS m_asm.append(_s.args.at(s).m_asm); else if (_s.outers.count(s)) m_asm.append(_s.outers.at(s).m_asm); - else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_") == string::npos) + else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) { auto it = _s.vars.find(s); if (it == _s.vars.end()) @@ -581,7 +581,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) { m_asm.appendJump(m_asm.errorTag()); } - else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_") == string::npos) + else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) m_asm.append((u256)varAddress(s)); else error("Unsupported keyword: '" + us + "'"); From 77a7bafb2d78c3e08c47fd5c02ba0ae1753c9ce9 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 1 Nov 2016 12:02:08 +0000 Subject: [PATCH 010/125] LLL: include version number in lllc (verbatim copy of solc) --- lllc/main.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lllc/main.cpp b/lllc/main.cpp index f8677be02..9763e820e 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -27,11 +27,18 @@ #include #include #include +#include + using namespace std; using namespace dev; using namespace dev::solidity; using namespace dev::eth; +static string const VersionString = + string(ETH_PROJECT_VERSION) + + (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) + + (string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO)); + void help() { cout @@ -50,7 +57,7 @@ void help() void version() { cout << "LLLC, the Lovely Little Language Compiler " << endl; - cout << " By Gav Wood, (c) 2014." << endl; + cout << "Version: " << VersionString << endl; exit(0); } From 200cbc08dd52b4853612d7e726724a554face650 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Sun, 23 Oct 2016 13:51:41 +0100 Subject: [PATCH 011/125] LLL: add bytecodesize keyword to push resulting bytecode size --- liblll/CodeFragment.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index bc53d7777..39b6376c7 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -581,6 +581,10 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) { m_asm.appendJump(m_asm.errorTag()); } + else if (us == "BYTECODESIZE") + { + m_asm.appendProgramSize(); + } else if (us.find_first_of("1234567890") != 0 && us.find_first_not_of("QWERTYUIOPASDFGHJKLZXCVBNM1234567890_-") == string::npos) m_asm.append((u256)varAddress(s)); else From 739ee88fa72739d432f626cd862b796bc0ee797c Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Tue, 1 Nov 2016 20:03:20 -0300 Subject: [PATCH 012/125] Add clarification for block.blockhash --- docs/units-and-global-variables.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/units-and-global-variables.rst b/docs/units-and-global-variables.rst index 3499bc71b..dd3d4be8d 100644 --- a/docs/units-and-global-variables.rst +++ b/docs/units-and-global-variables.rst @@ -50,7 +50,7 @@ namespace and are mainly used to provide information about the blockchain. Block and Transaction Properties -------------------------------- -- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks +- ``block.blockhash(uint blockNumber) returns (bytes32)``: hash of the given block - only works for 256 most recent blocks excluding current - ``block.coinbase`` (``address``): current block miner's address - ``block.difficulty`` (``uint``): current block difficulty - ``block.gaslimit`` (``uint``): current block gaslimit From 4c105dba0714564c2a31dac5ded8e994d0625dd1 Mon Sep 17 00:00:00 2001 From: varunagarwal315 Date: Thu, 3 Nov 2016 13:02:25 +0530 Subject: [PATCH 013/125] Update solidity-by-example.rst Might be trivial, but makes more sense to be able to directly return the name of the winner for the election. If the position of the winner on the proposal[] array is returned, then people still don't know the name of the person who won. --- docs/solidity-by-example.rst | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index 2e53b78c6..dd208ea36 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -170,6 +170,22 @@ of votes. } } } + + function winnerName() constant + returns (bytes32 winnerName) + { + //Init a for loop that compares all the votes + //one at a time. If a higher count is found, the + //value is updated. p represents position of the + //proposed person's name in the array + uint winningVoteCount = 0; + for (uint p = 0; p < proposals.length; p++) { + if (proposals[p].voteCount > winningVoteCount) { + winningVoteCount = proposals[p].voteCount; + winnerName = proposals[p].name; + } + } + } } Possible Improvements From 6cf63e2874ca4c5d5e2ae4a1f14808732c15867b Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 3 Nov 2016 10:48:54 +0100 Subject: [PATCH 014/125] Trying to attach artifacts to tags only. --- appveyor.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index b45e9f114..5d6dcb204 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -29,7 +29,7 @@ branches: only: - - master + - release - develop os: Visual Studio 2015 configuration: @@ -83,14 +83,13 @@ artifacts: # below. deploy: - description: 'Development build of solidity at commit $(APPVEYOR_REPO_COMMIT).\n\n$(APPVEYOR_REPO_COMMIT_MESSAGE)\n\nCommitted by $(APPVEYOR_REPO_COMMIT_AUTHOR), $(APPVEYOR_REPO_COMMIT_TIMESTAMP).' - prerelease: true provider: GitHub auth_token: secure: HPjiugbDSCsEFTphj/qwHuSw80/BV1xWoSvj95CPmtb16Ukh2VQbLVB7iFtZSans artifact: solidity-windows-zip on: branch: release + appveyor_repo_tag: true notifications: - provider: GitHubPullRequest From 38e0b0a5faf75e3392fbfdd371b27db728026853 Mon Sep 17 00:00:00 2001 From: varunagarwal315 Date: Thu, 3 Nov 2016 17:42:05 +0530 Subject: [PATCH 015/125] Update solidity-by-example.rst made changes suggested to simplify code. Hope this is enough Thanks --- docs/solidity-by-example.rst | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index dd208ea36..f5f109fde 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -171,20 +171,13 @@ of votes. } } + //Calls winningProposal() function to get the index + //of the winner contained in the proposals array and then + //returns the name of the winner function winnerName() constant returns (bytes32 winnerName) { - //Init a for loop that compares all the votes - //one at a time. If a higher count is found, the - //value is updated. p represents position of the - //proposed person's name in the array - uint winningVoteCount = 0; - for (uint p = 0; p < proposals.length; p++) { - if (proposals[p].voteCount > winningVoteCount) { - winningVoteCount = proposals[p].voteCount; - winnerName = proposals[p].name; - } - } + winnerName = proposals[winningProposal()].name; } } From 02416d4460ed2b4ff2c0311181abb6f836bebce3 Mon Sep 17 00:00:00 2001 From: yann300 Date: Thu, 3 Nov 2016 16:36:38 +0100 Subject: [PATCH 016/125] add payable to AST --- libsolidity/ast/ASTJsonConverter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 0ea5e0939..b573feda7 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -174,7 +174,8 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node) addJsonNode(_node, "FunctionDefinition", { make_pair("name", _node.name()), make_pair("public", _node.isPublic()), - make_pair("constant", _node.isDeclaredConst()) + make_pair("constant", _node.isDeclaredConst()), + make_pair("payable", _node.isPayable()) }, true); return true; } From 364698255aef1638e8d3e917dfc5a577312e6d07 Mon Sep 17 00:00:00 2001 From: varunagarwal315 Date: Thu, 3 Nov 2016 22:25:19 +0530 Subject: [PATCH 017/125] Update solidity-by-example.rst added the space. Sorry, just slipped my mind. --- docs/solidity-by-example.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/solidity-by-example.rst b/docs/solidity-by-example.rst index f5f109fde..915cfa765 100644 --- a/docs/solidity-by-example.rst +++ b/docs/solidity-by-example.rst @@ -171,9 +171,9 @@ of votes. } } - //Calls winningProposal() function to get the index - //of the winner contained in the proposals array and then - //returns the name of the winner + // Calls winningProposal() function to get the index + // of the winner contained in the proposals array and then + // returns the name of the winner function winnerName() constant returns (bytes32 winnerName) { From b78d4d67f51d6d5b65fff595ddbf06f13fa85bc2 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 7 Nov 2016 14:29:12 +0100 Subject: [PATCH 018/125] test: add a test case about SourceLocation --- test/CMakeLists.txt | 1 + test/libevmasm/SourceLocation.cpp | 50 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 test/libevmasm/SourceLocation.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 2f001b218..e67a04d4d 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -3,6 +3,7 @@ cmake_policy(SET CMP0015 NEW) aux_source_directory(. SRC_LIST) aux_source_directory(contracts SRC_LIST) aux_source_directory(libsolidity SRC_LIST) +aux_source_directory(libevmasm SRC_LIST) get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) diff --git a/test/libevmasm/SourceLocation.cpp b/test/libevmasm/SourceLocation.cpp new file mode 100644 index 000000000..64237a4e1 --- /dev/null +++ b/test/libevmasm/SourceLocation.cpp @@ -0,0 +1,50 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Yoichi Hirai + * @date 2016 + * Unit tests for the SourceLocation class. + */ + +#include + +#include "../TestHelper.h" + +namespace dev +{ +namespace solidity +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(SourceLocationTest) + +BOOST_AUTO_TEST_CASE(test_fail) +{ + BOOST_CHECK(SourceLocation() == SourceLocation()); + BOOST_CHECK(SourceLocation(0, 3, std::make_shared("sourceA")) != SourceLocation(0, 3, std::make_shared("sourceB"))); + BOOST_CHECK(SourceLocation(0, 3, std::make_shared("source")) == SourceLocation(0, 3, std::make_shared("source"))); + BOOST_CHECK(SourceLocation(3, 7, std::make_shared("source")).contains(SourceLocation(4, 6, std::make_shared("source")))); + BOOST_CHECK(!SourceLocation(3, 7, std::make_shared("sourceA")).contains(SourceLocation(4, 6, std::make_shared("sourceB")))); + BOOST_CHECK(SourceLocation(3, 7, std::make_shared("sourceA")) < SourceLocation(4, 6, std::make_shared("sourceB"))); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} +} // end namespaces From 10019d4a5e9ae063f7f4fe3b70a096e554e89805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Mon, 7 Nov 2016 22:52:31 +0100 Subject: [PATCH 019/125] Update jsoncpp.cmake from cpp-dependencies repo --- deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps b/deps index ff0c567cc..03f3b6382 160000 --- a/deps +++ b/deps @@ -1 +1 @@ -Subproject commit ff0c567cc0fd57049b5c29c3b3428097e6ca4644 +Subproject commit 03f3b63826b8b6e88355b30b725aa1b8df760f24 From 598154ed17f4155406ec1c047f3294c316ebd658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 8 Nov 2016 01:16:21 +0100 Subject: [PATCH 020/125] Drop CryptoPP leftovers Especially, do not compile CryptoPP for Emscripten. --- .travis.yml | 1 - libdevcore/Common.h | 1 - scripts/release_ppa.sh | 1 - scripts/travis-emscripten/build_emscripten.sh | 14 -------------- scripts/travis-emscripten/install_deps.sh | 1 - 5 files changed, 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index 143255671..2748c460b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -135,7 +135,6 @@ git: cache: ccache: true directories: - - cryptopp - boost_1_57_0 - build diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 43ae71627..d65cfeacd 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -70,7 +70,6 @@ #include "vector_ref.h" -// CryptoPP defines byte in the global namespace, so must we. using byte = uint8_t; // Quote a given token stream to turn it into a string. diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 1aaa2a421..39003795b 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -84,7 +84,6 @@ Section: science Priority: extra Maintainer: Christian (Buildserver key) Build-Depends: debhelper (>= 9.0.0), - libcryptopp-dev, cmake, g++-4.8, git, diff --git a/scripts/travis-emscripten/build_emscripten.sh b/scripts/travis-emscripten/build_emscripten.sh index 17a40e1c9..f5374a330 100755 --- a/scripts/travis-emscripten/build_emscripten.sh +++ b/scripts/travis-emscripten/build_emscripten.sh @@ -40,18 +40,6 @@ apt-get -y install git-core export WORKSPACE=/src -# CryptoPP -echo -en 'travis_fold:start:compiling_cryptopp\\r' -cd "$WORKSPACE/cryptopp" -# if .git exists, it is a fresh checkout, otherwise it comes from the cache -# and is already compiled -test -e .git && ( -emcmake cmake -DCRYPTOPP_LIBRARY_TYPE=STATIC -DCRYPTOPP_RUNTIME_TYPE=STATIC && emmake make -j 4 -ln -s . src/cryptopp || true -rm -rf .git -) -echo -en 'travis_fold:end:compiling_cryptopp\\r' - # Boost echo -en 'travis_fold:start:compiling_boost\\r' cd "$WORKSPACE"/boost_1_57_0 @@ -98,8 +86,6 @@ emcmake cmake \ -DBoost_THREAD_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_thread.a \ -DBoost_UNIT_TEST_FRAMEWORK_LIBRARY="$WORKSPACE"/boost_1_57_0/libboost_unit_test_framework.a \ -DBoost_UNIT_TEST_FRAMEWORK_LIBRARIES="$WORKSPACE"/boost_1_57_0/libboost_unit_test_framework.a \ - -DCRYPTOPP_LIBRARY="$WORKSPACE"/cryptopp/src/libcryptlib.a \ - -DCRYPTOPP_INCLUDE_DIR="$WORKSPACE"/cryptopp/src/ \ -DDev_DEVCORE_LIBRARY="$WORKSPACE"/solidity/build/libdevcore/libsoldevcore.a \ -DEth_EVMASM_LIBRARY="$WORKSPACE"/solidity/build/libevmasm/libsolevmasm.a \ -DETH_STATIC=1 -DTESTS=0 \ diff --git a/scripts/travis-emscripten/install_deps.sh b/scripts/travis-emscripten/install_deps.sh index d85337c01..252c74b07 100755 --- a/scripts/travis-emscripten/install_deps.sh +++ b/scripts/travis-emscripten/install_deps.sh @@ -30,7 +30,6 @@ set -ev echo -en 'travis_fold:start:installing_dependencies\\r' -test -e cryptopp -a -e cryptopp/src || git clone https://github.com/mmoss/cryptopp.git test -e boost_1_57_0 -a -e boost_1_57_0/boost || ( wget 'http://downloads.sourceforge.net/project/boost/boost/'\ '1.57.0/boost_1_57_0.tar.bz2?r=http%3A%2F%2Fsourceforge.net%2F'\ From 2e929666845300217f3725f67e48cd04a567a6f2 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 7 Nov 2016 14:12:30 +0100 Subject: [PATCH 021/125] libevmasm: fix comparison of SourceLocations --- libevmasm/SourceLocation.h | 15 ++++++++++----- test/libsolidity/Assembly.cpp | 4 +++- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/libevmasm/SourceLocation.h b/libevmasm/SourceLocation.h index 05304d146..b8f073bb9 100644 --- a/libevmasm/SourceLocation.h +++ b/libevmasm/SourceLocation.h @@ -55,7 +55,11 @@ struct SourceLocation return *this; } - bool operator==(SourceLocation const& _other) const { return start == _other.start && end == _other.end;} + bool operator==(SourceLocation const& _other) const + { + return start == _other.start && end == _other.end && + ((!sourceName && !_other.sourceName) || (sourceName && _other.sourceName && *sourceName == *_other.sourceName)); + } bool operator!=(SourceLocation const& _other) const { return !operator==(_other); } inline bool operator<(SourceLocation const& _other) const; inline bool contains(SourceLocation const& _other) const; @@ -79,20 +83,21 @@ inline std::ostream& operator<<(std::ostream& _out, SourceLocation const& _locat bool SourceLocation::operator<(SourceLocation const& _other) const { if (!sourceName || !_other.sourceName) - return int(!!sourceName) < int(!!_other.sourceName); - return make_tuple(*sourceName, start, end) < make_tuple(*_other.sourceName, _other.start, _other.end); + return std::make_tuple(int(!!sourceName), start, end) < std::make_tuple(int(!!_other.sourceName), _other.start, _other.end); + else + return std::make_tuple(*sourceName, start, end) < std::make_tuple(*_other.sourceName, _other.start, _other.end); } bool SourceLocation::contains(SourceLocation const& _other) const { - if (isEmpty() || _other.isEmpty() || !sourceName || !_other.sourceName || *sourceName != *_other.sourceName) + if (isEmpty() || _other.isEmpty() || ((!sourceName || !_other.sourceName || *sourceName != *_other.sourceName) && (sourceName || _other.sourceName))) return false; return start <= _other.start && _other.end <= end; } bool SourceLocation::intersects(SourceLocation const& _other) const { - if (isEmpty() || _other.isEmpty() || !sourceName || !_other.sourceName || *sourceName != *_other.sourceName) + if (isEmpty() || _other.isEmpty() || ((!sourceName || !_other.sourceName || *sourceName != *_other.sourceName) && (sourceName || _other.sourceName))) return false; return _other.start < end && start < _other.end; } diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index 8d7a3540f..eddba5e1c 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -91,8 +91,10 @@ void checkAssemblyLocations(AssemblyItems const& _items, vector BOOST_CHECK_MESSAGE( _items[i].location() == _locations[i], "Location mismatch for assembly item " + to_string(i) + ". Found: " + + (_items[i].location().sourceName ? *_items[i].location().sourceName + ":" : "(null source name)") + to_string(_items[i].location().start) + "-" + to_string(_items[i].location().end) + ", expected: " + + (_locations[i].sourceName ? *_locations[i].sourceName + ":" : "(null source name)") + to_string(_locations[i].start) + "-" + to_string(_locations[i].end)); } @@ -111,7 +113,7 @@ BOOST_AUTO_TEST_CASE(location_test) } } )"; - shared_ptr n = make_shared("source"); + shared_ptr n = make_shared(""); AssemblyItems items = compileContract(sourceCode); vector locations = vector(18, SourceLocation(2, 75, n)) + From 69556666dba6a372f4e7e7754bb791e5127f1e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Bylica?= Date: Tue, 8 Nov 2016 14:28:59 +0100 Subject: [PATCH 022/125] Update jsoncpp.cmake from cpp-dependencies repo This change will keep the downloaded jsoncpp archive in the source dir and allow PPA builds from tarballs. --- deps | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps b/deps index 03f3b6382..b3db89058 160000 --- a/deps +++ b/deps @@ -1 +1 @@ -Subproject commit 03f3b63826b8b6e88355b30b725aa1b8df760f24 +Subproject commit b3db8905894eafb74a436b702de78ba235f3a3b1 From dc8a5f4ef5505f2aeb017dfa4c9aca77a9fd93aa Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 8 Nov 2016 16:41:25 +0100 Subject: [PATCH 023/125] Fetch jsoncpp tarball during ppa release. --- scripts/release_ppa.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/release_ppa.sh b/scripts/release_ppa.sh index 39003795b..86288c47f 100755 --- a/scripts/release_ppa.sh +++ b/scripts/release_ppa.sh @@ -49,6 +49,10 @@ cd $distribution git clone --recursive https://github.com/ethereum/solidity.git -b "$branch" mv solidity solc +# Fetch jsoncpp dependency +mkdir -p ./solc/deps/downloads/ 2>/dev/null || true +wget -O ./solc/deps/downloads/jsoncpp-1.7.7.tar.gz https://github.com/open-source-parsers/jsoncpp/archive/1.7.7.tar.gz + # Determine version cd solc version=`grep -oP "PROJECT_VERSION \"?\K[0-9.]+(?=\")"? CMakeLists.txt` From 88547a1c660f330614ad1a9621f06a93d989bd09 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 8 Nov 2016 17:09:24 +0100 Subject: [PATCH 024/125] test: fix a typo in calling_payable test --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 640cc1080..832925016 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4015,8 +4015,8 @@ BOOST_AUTO_TEST_CASE(calling_payable) char const* text = R"( contract receiver { function pay() payable {} } contract test { - funciton f() { (new receiver()).pay.value(10)(); } - recevier r = new receiver(); + function f() { (new receiver()).pay.value(10)(); } + receiver r = new receiver(); function g() { r.pay.value(10)(); } } )"; From 63bde109a1dfa0e4726f9b479923db305aa25a2a Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 10:48:45 +0100 Subject: [PATCH 025/125] Fail if parsing fails in type checker tests. --- test/libsolidity/SolidityNameAndTypeResolution.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 832925016..099c3c007 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -57,7 +57,7 @@ parseAnalyseAndReturnError(string const& _source, bool _reportWarnings = false, { sourceUnit = parser.parse(std::make_shared(CharStream(source))); if(!sourceUnit) - return make_pair(sourceUnit, nullptr); + BOOST_FAIL("Parsing failed in type checker test."); SyntaxChecker syntaxChecker(errors); if (!syntaxChecker.checkSyntax(*sourceUnit)) From d22ed31e4bbf89dab73b3c213544460fb1cfc36b Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 15:04:16 +0100 Subject: [PATCH 026/125] Windows build fix. Thanks for the find, @slothbag --- libdevcore/CommonData.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index b27271cf8..062d1b29c 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -20,9 +20,6 @@ */ #include "CommonData.h" -#if defined(_MSC_VER) -#pragma warning(pop) -#endif #include "Exceptions.h" using namespace std; using namespace dev; From 4524ad08701939cc22d28494c57dda1cdfba9e10 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Sat, 30 Jul 2016 00:13:05 -0700 Subject: [PATCH 027/125] Add support for do/while loops This commit adds support for a standard do while ; form of statement. While loops were already being supported; supporting a do/while loop mostly involves reusing code from while loops but putting the conditional checking last. --- Changelog.md | 3 +++ docs/control-structures.rst | 4 ++-- libsolidity/ast/AST.h | 8 +++++-- libsolidity/ast/ASTJsonConverter.cpp | 6 ++++- libsolidity/ast/ASTPrinter.cpp | 2 +- libsolidity/codegen/ContractCompiler.cpp | 19 ++++++++++++--- libsolidity/formal/Why3Translator.cpp | 10 ++++++++ libsolidity/grammar.txt | 3 ++- libsolidity/parsing/Parser.cpp | 19 ++++++++++++++- libsolidity/parsing/Parser.h | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 28 +++++++++++++++++++++++ 11 files changed, 92 insertions(+), 11 deletions(-) diff --git a/Changelog.md b/Changelog.md index ee1060471..1ae2f57ee 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,8 @@ ### 0.4.5 (unreleased) +Features: + * Do-while loops: support for a C-style do{}while(); control structure + ### 0.4.4 (2016-10-31) Bugfixes: diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 597829d31..51f430150 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -2,14 +2,14 @@ Expressions and Control Structures ################################## -.. index:: if, else, while, for, break, continue, return, switch, goto +.. index:: if, else, while, do/while, for, break, continue, return, switch, goto Control Structures =================== Most of the control structures from C or JavaScript are available in Solidity except for ``switch`` and ``goto``. So -there is: ``if``, ``else``, ``while``, ``for``, ``break``, ``continue``, ``return``, ``? :``, with +there is: ``if``, ``else``, ``while``, ``do``, ``for``, ``break``, ``continue``, ``return``, ``? :``, with the usual semantics known from C or JavaScript. Parentheses can *not* be omitted for conditionals, but curly brances can be omitted diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 7ed4ddce8..6c3f52bcc 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -1005,18 +1005,22 @@ public: SourceLocation const& _location, ASTPointer const& _docString, ASTPointer const& _condition, - ASTPointer const& _body + ASTPointer const& _body, + bool _isDoWhile ): - BreakableStatement(_location, _docString), m_condition(_condition), m_body(_body) {} + BreakableStatement(_location, _docString), m_condition(_condition), m_body(_body), + m_isDoWhile(_isDoWhile) {} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; Expression const& condition() const { return *m_condition; } Statement const& body() const { return *m_body; } + bool isDoWhile() const { return m_isDoWhile; } private: ASTPointer m_condition; ASTPointer m_body; + bool m_isDoWhile; }; /** diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index b573feda7..3fce11804 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -264,7 +264,11 @@ bool ASTJsonConverter::visit(IfStatement const& _node) bool ASTJsonConverter::visit(WhileStatement const& _node) { - addJsonNode(_node, "WhileStatement", {}, true); + addJsonNode( + _node, + _node.isDoWhile() ? "DoWhileStatement" : "WhileStatement", + {}, + true); return true; } diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index a9de457a7..272669688 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -208,7 +208,7 @@ bool ASTPrinter::visit(IfStatement const& _node) bool ASTPrinter::visit(WhileStatement const& _node) { - writeLine("WhileStatement"); + writeLine(_node.isDoWhile() ? "DoWhileStatement" : "WhileStatement"); printSourcePart(_node); return goDeeper(); } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index ebb84784e..1404963f1 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -611,12 +611,25 @@ bool ContractCompiler::visit(WhileStatement const& _whileStatement) m_breakTags.push_back(loopEnd); m_context << loopStart; - compileExpression(_whileStatement.condition()); - m_context << Instruction::ISZERO; - m_context.appendConditionalJumpTo(loopEnd); + + // While loops have the condition prepended + if (!_whileStatement.isDoWhile()) + { + compileExpression(_whileStatement.condition()); + m_context << Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + } _whileStatement.body().accept(*this); + // Do-while loops have the condition appended + if (_whileStatement.isDoWhile()) + { + compileExpression(_whileStatement.condition()); + m_context << Instruction::ISZERO; + m_context.appendConditionalJumpTo(loopEnd); + } + m_context.appendJumpTo(loopStart); m_context << loopEnd; diff --git a/libsolidity/formal/Why3Translator.cpp b/libsolidity/formal/Why3Translator.cpp index 813fa3abe..5934d5936 100644 --- a/libsolidity/formal/Why3Translator.cpp +++ b/libsolidity/formal/Why3Translator.cpp @@ -410,6 +410,16 @@ bool Why3Translator::visit(WhileStatement const& _node) { addSourceFromDocStrings(_node.annotation()); + // Why3 does not appear to support do-while loops, + // so we will simulate them by performing a while + // loop with the body prepended once. + + if (_node.isDoWhile()) + { + visitIndentedUnlessBlock(_node.body()); + newLine(); + } + add("while "); _node.condition().accept(*this); newLine(); diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index d84ee10c6..586c41e95 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -41,7 +41,7 @@ ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' StorageLocation = 'memory' | 'storage' Block = '{' Statement* '}' -Statement = IfStatement | WhileStatement | ForStatement | Block | +Statement = IfStatement | WhileStatement | DoWhileStatement | ForStatement | Block | ( PlaceholderStatement | Continue | Break | Return | Throw | SimpleStatement ) ';' @@ -51,6 +51,7 @@ WhileStatement = 'while' '(' Expression ')' Statement PlaceholderStatement = '_' SimpleStatement = VariableDefinition | ExpressionStatement ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement +DoWhileStatement = 'do' Statement 'while' '(' Expression ')' ';' Continue = 'continue' Break = 'break' Return = 'return' Expression? diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 0e99d1e73..52b536196 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -722,6 +722,8 @@ ASTPointer Parser::parseStatement() return parseIfStatement(docString); case Token::While: return parseWhileStatement(docString); + case Token::Do: + return parseDoWhileStatement(docString); case Token::For: return parseForStatement(docString); case Token::LBrace: @@ -816,9 +818,24 @@ ASTPointer Parser::parseWhileStatement(ASTPointer con expectToken(Token::RParen); ASTPointer body = parseStatement(); nodeFactory.setEndPositionFromNode(body); - return nodeFactory.createNode(_docString, condition, body); + return nodeFactory.createNode(_docString, condition, body, false); } +ASTPointer Parser::parseDoWhileStatement(ASTPointer const& _docString) +{ + ASTNodeFactory nodeFactory(*this); + expectToken(Token::Do); + ASTPointer body = parseStatement(); + expectToken(Token::While); + expectToken(Token::LParen); + ASTPointer condition = parseExpression(); + expectToken(Token::RParen); + nodeFactory.markEndPosition(); + expectToken(Token::Semicolon); + return nodeFactory.createNode(_docString, condition, body, true); +} + + ASTPointer Parser::parseForStatement(ASTPointer const& _docString) { ASTNodeFactory nodeFactory(*this); diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 9c30cf608..26f347cb5 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -85,6 +85,7 @@ private: ASTPointer parseInlineAssembly(ASTPointer const& _docString = {}); ASTPointer parseIfStatement(ASTPointer const& _docString); ASTPointer parseWhileStatement(ASTPointer const& _docString); + ASTPointer parseDoWhileStatement(ASTPointer const& _docString); ASTPointer parseForStatement(ASTPointer const& _docString); /// A "simple statement" can be a variable declaration statement or an expression statement. ASTPointer parseSimpleStatement(ASTPointer const& _docString); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8600443d7..a1430b02c 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -353,6 +353,34 @@ BOOST_AUTO_TEST_CASE(while_loop) testSolidityAgainstCppOnRange("f(uint256)", while_loop_cpp, 0, 5); } + +BOOST_AUTO_TEST_CASE(do_while_loop) +{ + char const* sourceCode = "contract test {\n" + " function f(uint n) returns(uint nfac) {\n" + " nfac = 1;\n" + " var i = 2;\n" + " do { nfac *= i++; } while (i <= n);\n" + " }\n" + "}\n"; + compileAndRun(sourceCode); + + auto do_while_loop_cpp = [](u256 const& n) -> u256 + { + u256 nfac = 1; + u256 i = 2; + do + { + nfac *= i++; + } + while (i <= n); + + return nfac; + }; + + testSolidityAgainstCppOnRange("f(uint256)", do_while_loop_cpp, 0, 5); +} + BOOST_AUTO_TEST_CASE(nested_loops) { // tests that break and continue statements in nested loops jump to the correct place From 1e845c279b3a86c87b56e1007406a96d55004a9c Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 09:33:19 +0100 Subject: [PATCH 028/125] Fix semicolons --- libsolidity/grammar.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 586c41e95..3edb4eba6 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -41,8 +41,8 @@ ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' StorageLocation = 'memory' | 'storage' Block = '{' Statement* '}' -Statement = IfStatement | WhileStatement | DoWhileStatement | ForStatement | Block | - ( PlaceholderStatement | Continue | Break | Return | +Statement = IfStatement | WhileStatement | ForStatement | Block | + ( DoWhileStatement | PlaceholderStatement | Continue | Break | Return | Throw | SimpleStatement ) ';' ExpressionStatement = Expression @@ -51,7 +51,7 @@ WhileStatement = 'while' '(' Expression ')' Statement PlaceholderStatement = '_' SimpleStatement = VariableDefinition | ExpressionStatement ForStatement = 'for' '(' (SimpleStatement)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement -DoWhileStatement = 'do' Statement 'while' '(' Expression ')' ';' +DoWhileStatement = 'do' Statement 'while' '(' Expression ')' Continue = 'continue' Break = 'break' Return = 'return' Expression? From 33590d513e278526bdfbf4bd18ea5236a8911bb0 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 26 Oct 2016 15:17:26 +0200 Subject: [PATCH 029/125] test: add a test for #621 --- test/libsolidity/SolidityEndToEndTest.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a1430b02c..bab54fab5 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -6353,6 +6353,20 @@ BOOST_AUTO_TEST_CASE(decayed_tuple) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); } +BOOST_AUTO_TEST_CASE(inline_tuple_with_rational_numbers) +{ + char const* sourceCode = R"( + contract c { + function f() returns (int8) { + int8[5] memory foo3 = [int8(1), -1, 0, 0, 0]; + return foo3[0]; + } + } + )"; + compileAndRun(sourceCode); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1))); +} + BOOST_AUTO_TEST_CASE(destructuring_assignment) { char const* sourceCode = R"( From 6c15757618924a0da4491a243740c7bf0f70f60f Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 26 Oct 2016 15:57:42 +0200 Subject: [PATCH 030/125] Type checker: move the burden of computing mobile type to commonType This solves #621 --- libsolidity/analysis/TypeChecker.cpp | 4 ++-- libsolidity/ast/Types.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index 46f4f7f6d..f934b2c8d 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -996,9 +996,9 @@ bool TypeChecker::visit(TupleExpression const& _tuple) fatalTypeError(components[i]->location(), "Invalid mobile type."); if (i == 0) - inlineArrayType = types[i]->mobileType(); + inlineArrayType = types[i]; else if (inlineArrayType) - inlineArrayType = Type::commonType(inlineArrayType, types[i]->mobileType()); + inlineArrayType = Type::commonType(inlineArrayType, types[i]); } } else diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 7fe97fa76..0e077b32c 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -200,10 +200,10 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) { if (!_a || !_b) return TypePointer(); - else if (_b->isImplicitlyConvertibleTo(*_a)) - return _a; - else if (_a->isImplicitlyConvertibleTo(*_b)) - return _b; + else if (_b->isImplicitlyConvertibleTo(*_a->mobileType())) + return _a->mobileType(); + else if (_a->isImplicitlyConvertibleTo(*_b->mobileType())) + return _b->mobileType(); else return TypePointer(); } From 41170d55075fc056a688dc8fb29021e23b645cc0 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 11:58:48 +0100 Subject: [PATCH 031/125] Changelog: add a point about #1293 --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index 1ae2f57ee..d76303ab4 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * Do-while loops: support for a C-style do{}while(); control structure + * Type checker: now more eagerly searches for a common type of an inline array with mixed types ### 0.4.4 (2016-10-31) From e6098f0039868222c758e4d10f23ace59d1a3195 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 7 Nov 2016 18:15:59 +0100 Subject: [PATCH 032/125] ast: add EnumType::numberOfMembers() --- libsolidity/ast/Types.cpp | 7 ++++++- libsolidity/ast/Types.h | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 0e077b32c..a134a0bba 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1561,7 +1561,7 @@ bool EnumType::operator==(Type const& _other) const unsigned EnumType::storageBytes() const { - size_t elements = m_enum.members().size(); + size_t elements = numberOfMembers(); if (elements <= 1) return 1; else @@ -1578,6 +1578,11 @@ string EnumType::canonicalName(bool) const return m_enum.annotation().canonicalName; } +size_t EnumType::numberOfMembers() const +{ + return m_enum.members().size(); +}; + bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const { return _convertTo.category() == category() || _convertTo.category() == Category::Integer; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 3f94d11a6..082e16a6a 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -738,6 +738,7 @@ public: EnumDefinition const& enumDefinition() const { return m_enum; } /// @returns the value that the string has in the Enum unsigned int memberValue(ASTString const& _member) const; + size_t numberOfMembers() const; private: EnumDefinition const& m_enum; From 8856adce8f1829cecf7353c64c8cf07058da6f4f Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 8 Nov 2016 11:37:44 +0100 Subject: [PATCH 033/125] test: add tests that witness issue #1311 --- test/libsolidity/SolidityEndToEndTest.cpp | 24 +++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index bab54fab5..90547ad90 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3342,6 +3342,30 @@ BOOST_AUTO_TEST_CASE(using_enums) BOOST_CHECK(callContractFunction("getChoice()") == encodeArgs(2)); } +BOOST_AUTO_TEST_CASE(enum_explicit_overflow) +{ + char const* sourceCode = R"( + contract test { + enum ActionChoices { GoLeft, GoRight, GoStraight } + function test() + { + } + function getChoiceExp(uint x) returns (uint d) + { + choice = ActionChoices(x); + d = uint256(choice); + } + ActionChoices choice; + } + )"; + compileAndRun(sourceCode); + // These should throw + BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 3) == encodeArgs()); + // These should work + BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 2) == encodeArgs(2)); + BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 0) == encodeArgs(0)); +} + BOOST_AUTO_TEST_CASE(using_contract_enums_with_explicit_contract_name) { char const* sourceCode = R"( From 98dcd883e4ee1b3d62a1b9e7d4e5d4e038ea0434 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 8 Nov 2016 13:37:59 +0100 Subject: [PATCH 034/125] codegen: check the value range after converting something to an enum element --- Changelog.md | 1 + libsolidity/codegen/CompilerUtils.cpp | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/Changelog.md b/Changelog.md index d76303ab4..b4456a86d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -3,6 +3,7 @@ Features: * Do-while loops: support for a C-style do{}while(); control structure * Type checker: now more eagerly searches for a common type of an inline array with mixed types + * Code generator: generates a runtime error when an out-of-range value is converted into an enum type. ### 0.4.4 (2016-10-31) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index e064c1a64..5e0459968 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -656,6 +656,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); break; } + + // Check the conversion result fits in a range. + if (targetTypeCategory == Type::Category::Enum) + { + EnumType const& enumType = dynamic_cast(_targetType); + m_context << u256(enumType.numberOfMembers()) << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } } void CompilerUtils::pushZeroValue(Type const& _type) From 1af3c4f754be05c82a0aee4d309a3e681387eaed Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 10:47:54 +0100 Subject: [PATCH 035/125] docs: document the new overflow exception during conversion into enum --- docs/control-structures.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 51f430150..bbb90e6a2 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -329,9 +329,10 @@ Currently, there are situations, where exceptions happen automatically in Solidi 3. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``. 4. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly"). 5. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``). -6. If you perform an external function call targeting a contract that contains no code. -7. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). -8. If your contract receives Ether via a public accessor function. +6. If you convert a value too big or negative into an enum type. +7. If you perform an external function call targeting a contract that contains no code. +8. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function). +9. If your contract receives Ether via a public accessor function. Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect. From 08a889a90863ff11cca9cb0b8c141ff5ca11a42e Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 11:40:52 +0100 Subject: [PATCH 036/125] test: add a test case for #1343 The test witnesses that #1334 fixes #1343. --- test/libsolidity/SolidityEndToEndTest.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 90547ad90..09c3b681f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3355,12 +3355,18 @@ BOOST_AUTO_TEST_CASE(enum_explicit_overflow) choice = ActionChoices(x); d = uint256(choice); } + function getChoiceFromSigned(int x) returns (uint d) + { + choice = ActionChoices(x); + d = uint256(choice); + } ActionChoices choice; } )"; compileAndRun(sourceCode); // These should throw BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 3) == encodeArgs()); + BOOST_CHECK(callContractFunction("getChoiceFromSigned(int256)", -1) == encodeArgs()); // These should work BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 2) == encodeArgs(2)); BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 0) == encodeArgs(0)); From 457daecba1f7e267cf7578b702945bb6532aa2af Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 11:50:09 +0100 Subject: [PATCH 037/125] test: add a test converting -1 as a literal into an enum This shows #1334 fixes #1344 --- test/libsolidity/SolidityEndToEndTest.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 09c3b681f..5582e4a12 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -3360,6 +3360,11 @@ BOOST_AUTO_TEST_CASE(enum_explicit_overflow) choice = ActionChoices(x); d = uint256(choice); } + function getChoiceFromNegativeLiteral() returns (uint d) + { + choice = ActionChoices(-1); + d = uint256(choice); + } ActionChoices choice; } )"; @@ -3367,6 +3372,7 @@ BOOST_AUTO_TEST_CASE(enum_explicit_overflow) // These should throw BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 3) == encodeArgs()); BOOST_CHECK(callContractFunction("getChoiceFromSigned(int256)", -1) == encodeArgs()); + BOOST_CHECK(callContractFunction("getChoiceFromNegativeLiteral()") == encodeArgs()); // These should work BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 2) == encodeArgs(2)); BOOST_CHECK(callContractFunction("getChoiceExp(uint256)", 0) == encodeArgs(0)); From eee629652e3ee851419805ad1cb54d168fa77763 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 14:08:51 +0100 Subject: [PATCH 038/125] parsing: ban empty enum definition. --- Changelog.md | 4 ++++ libsolidity/parsing/Parser.cpp | 2 ++ test/libsolidity/ASTJSON.cpp | 14 -------------- test/libsolidity/SolidityParser.cpp | 2 +- 4 files changed, 7 insertions(+), 15 deletions(-) diff --git a/Changelog.md b/Changelog.md index b4456a86d..4bed85ea9 100644 --- a/Changelog.md +++ b/Changelog.md @@ -5,6 +5,10 @@ Features: * Type checker: now more eagerly searches for a common type of an inline array with mixed types * Code generator: generates a runtime error when an out-of-range value is converted into an enum type. +Bugfixes: + + * Parser: disallow empty enum definitions. + ### 0.4.4 (2016-10-31) Bugfixes: diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 52b536196..df3ed7b23 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -406,6 +406,8 @@ ASTPointer Parser::parseEnumDefinition() if (m_scanner->currentToken() != Token::Identifier) fatalParserError(string("Expected Identifier after ','")); } + if (members.size() == 0) + parserError({"enum with no members is not allowed."}); nodeFactory.markEndPosition(); expectToken(Token::RBrace); diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index a0fc5dd7e..6c062ee88 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -94,20 +94,6 @@ BOOST_AUTO_TEST_CASE(using_for_directive) BOOST_CHECK_EQUAL(usingFor["children"][1]["attributes"]["name"], "uint"); } -BOOST_AUTO_TEST_CASE(enum_definition) -{ - CompilerStack c; - c.addSource("a", "contract C { enum E {} }"); - c.parse(); - map sourceIndices; - sourceIndices["a"] = 1; - Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); - Json::Value enumDefinition = astJson["children"][0]["children"][0]; - BOOST_CHECK_EQUAL(enumDefinition["name"], "EnumDefinition"); - BOOST_CHECK_EQUAL(enumDefinition["attributes"]["name"], "E"); - BOOST_CHECK_EQUAL(enumDefinition["src"], "13:9:1"); -} - BOOST_AUTO_TEST_CASE(enum_value) { CompilerStack c; diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index a81a98284..ec23d5fd5 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -824,7 +824,7 @@ BOOST_AUTO_TEST_CASE(empty_enum_declaration) contract c { enum foo { } })"; - BOOST_CHECK(successParse(text)); + BOOST_CHECK(!successParse(text)); } BOOST_AUTO_TEST_CASE(malformed_enum_declaration) From effca182507bb736c8f52b167bae470c06dc6c36 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 14:13:57 +0100 Subject: [PATCH 039/125] docs: udpate description of enums about #1334 --- docs/types.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 9e7d9b4ac..9ec9e526b 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -237,7 +237,8 @@ Enums ===== Enums are one way to create a user-defined type in Solidity. They are explicitly convertible -to and from all integer types but implicit conversion is not allowed. +to and from all integer types but implicit conversion is not allowed. The explicit conversions +check the value ranges at runtime and a failure causes an exception. Enums needs at least one member. :: From 0a6c937dcbb3671e2fa5f6d7b50b5a909cf522d0 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 14:14:18 +0100 Subject: [PATCH 040/125] codegen: shorten the overflow checking when converting into enums --- libsolidity/codegen/CompilerUtils.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 5e0459968..ad155b13f 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -661,7 +661,8 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp if (targetTypeCategory == Type::Category::Enum) { EnumType const& enumType = dynamic_cast(_targetType); - m_context << u256(enumType.numberOfMembers()) << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; + solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); + m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context.appendConditionalJumpTo(m_context.errorTag()); } } From 4b6e7e0677c9d5de50258fab39ed62e48778ac7a Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 15:53:00 +0100 Subject: [PATCH 041/125] test: add a test conerting an enum into another enum, which should fail --- .../libsolidity/SolidityNameAndTypeResolution.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 099c3c007..f498a0b9a 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -1535,6 +1535,21 @@ BOOST_AUTO_TEST_CASE(enum_implicit_conversion_is_not_okay) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(enum_to_enum_conversion_is_not_okay) +{ + char const* text = R"( + contract test { + enum Paper { Up, Down, Left, Right } + enum Ground { North, South, West, East } + function test() + { + Ground(Paper.Up); + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + BOOST_AUTO_TEST_CASE(enum_duplicate_values) { char const* text = R"( From 20c2ca39922f4230b504888644d04f4bc8d6b8f3 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Wed, 9 Nov 2016 17:02:25 +0100 Subject: [PATCH 042/125] ast, codegen: disallow conversion between different enum types --- Changelog.md | 1 + libsolidity/ast/Types.cpp | 2 +- libsolidity/codegen/CompilerUtils.cpp | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Changelog.md b/Changelog.md index 4bed85ea9..851f39a01 100644 --- a/Changelog.md +++ b/Changelog.md @@ -8,6 +8,7 @@ Features: Bugfixes: * Parser: disallow empty enum definitions. + * Type checker: disallow conversion between different enum types. ### 0.4.4 (2016-10-31) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index a134a0bba..f09953931 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1585,7 +1585,7 @@ size_t EnumType::numberOfMembers() const bool EnumType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - return _convertTo.category() == category() || _convertTo.category() == Category::Integer; + return _convertTo == *this || _convertTo.category() == Category::Integer; } unsigned EnumType::memberValue(ASTString const& _member) const diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index ad155b13f..2f30f53ed 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -348,7 +348,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp } break; case Type::Category::Enum: - solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); + solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); break; case Type::Category::FixedPoint: solAssert(false, "Not yet implemented - FixedPointType."); From 061b50ae19b0f8b9dcffc8dab5f08c4e26e884fb Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 2 Nov 2016 12:32:55 +0100 Subject: [PATCH 043/125] Add tests for the std files. --- scripts/tests.sh | 17 +++++++++++++++-- std/StandardToken.sol | 2 ++ std/Token.sol | 2 ++ std/mortal.sol | 2 ++ std/owned.sol | 2 ++ std/std.sol | 2 ++ 6 files changed, 25 insertions(+), 2 deletions(-) diff --git a/scripts/tests.sh b/scripts/tests.sh index 93afd2d20..5fa75d4d1 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -28,8 +28,21 @@ set -e -# There is an implicit assumption here that we HAVE to run from root directory. -REPO_ROOT=$(pwd) +REPO_ROOT="$(dirname "$0")"/.. + + # Compile all files in std and examples. + +for f in "$REPO_ROOT"/std/*.sol +do + echo "Compiling $f..." + set +e + output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1) + failed=$? + output=$(echo "$output" | grep -v 'pre-release') + echo "$output" + set -e + test -z "$output" -a "$failed" -eq 0 +done # This conditional is only needed because we don't have a working Homebrew # install for `eth` at the time of writing, so we unzip the ZIP file locally diff --git a/std/StandardToken.sol b/std/StandardToken.sol index 41f2d709a..4ff1b8f92 100644 --- a/std/StandardToken.sol +++ b/std/StandardToken.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.0; + import "./Token.sol"; contract StandardToken is Token { diff --git a/std/Token.sol b/std/Token.sol index 396dbf9e5..59566f26f 100644 --- a/std/Token.sol +++ b/std/Token.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.0; + contract Token { event Transfer(address indexed _from, address indexed _to, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value); diff --git a/std/mortal.sol b/std/mortal.sol index 8de019abb..f0a6f4ce0 100644 --- a/std/mortal.sol +++ b/std/mortal.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.0; + import "./owned.sol"; contract mortal is owned { diff --git a/std/owned.sol b/std/owned.sol index 3d7674f58..bbb8d957d 100644 --- a/std/owned.sol +++ b/std/owned.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.0; + contract owned { address owner; diff --git a/std/std.sol b/std/std.sol index c3f66b1b7..4d65bef2a 100644 --- a/std/std.sol +++ b/std/std.sol @@ -1,3 +1,5 @@ +pragma solidity ^0.4.0; + import "./owned.sol"; import "./mortal.sol"; import "./Token.sol"; From 04eb6e85f22d21b3def8ed448f59aaec44457cc9 Mon Sep 17 00:00:00 2001 From: ethers Date: Sat, 12 Nov 2016 20:02:43 -0800 Subject: [PATCH 044/125] Remove named return Named returns are not explained in this introduction; they also provide little value in these examples. --- docs/introduction-to-smart-contracts.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/introduction-to-smart-contracts.rst b/docs/introduction-to-smart-contracts.rst index eeea85a70..4a3de441b 100644 --- a/docs/introduction-to-smart-contracts.rst +++ b/docs/introduction-to-smart-contracts.rst @@ -25,7 +25,7 @@ Storage storedData = x; } - function get() constant returns (uint retVal) { + function get() constant returns (uint) { return storedData; } } @@ -136,7 +136,7 @@ like this one. The accessor function created by the ``public`` keyword is a bit more complex in this case. It roughly looks like the following:: - function balances(address _account) returns (uint balance) { + function balances(address _account) returns (uint) { return balances[_account]; } From 81f5734cbe029b93aa143e5eb7f57869ab63af7b Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 14 Nov 2016 11:11:39 +0100 Subject: [PATCH 045/125] codegen: move the enum overflow checking closer to the conversion into enums --- libsolidity/codegen/CompilerUtils.cpp | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 2f30f53ed..dd133aea6 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -315,6 +315,8 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp Type::Category stackTypeCategory = _typeOnStack.category(); Type::Category targetTypeCategory = _targetType.category(); + bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum); + switch (stackTypeCategory) { case Type::Category::FixedBytes: @@ -349,6 +351,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp break; case Type::Category::Enum: solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); + if (enumOverflowCheckPending) + { + EnumType const& enumType = dynamic_cast(_targetType); + solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); + m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; + m_context.appendConditionalJumpTo(m_context.errorTag()); + enumOverflowCheckPending = false; + } break; case Type::Category::FixedPoint: solAssert(false, "Not yet implemented - FixedPointType."); @@ -372,6 +382,11 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp solAssert(_typeOnStack.mobileType(), ""); // just clean convertType(_typeOnStack, *_typeOnStack.mobileType(), true); + EnumType const& enumType = dynamic_cast(_targetType); + solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); + m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; + m_context.appendConditionalJumpTo(m_context.errorTag()); + enumOverflowCheckPending = false; } else if (targetTypeCategory == Type::Category::FixedPoint) { @@ -657,14 +672,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp break; } - // Check the conversion result fits in a range. - if (targetTypeCategory == Type::Category::Enum) - { - EnumType const& enumType = dynamic_cast(_targetType); - solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); - m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + solAssert(!enumOverflowCheckPending, "enum overflow checking missing."); } void CompilerUtils::pushZeroValue(Type const& _type) From 176c06f386ef096f119dd09ae0f6e16f389894ae Mon Sep 17 00:00:00 2001 From: Walter Weinmann Date: Mon, 14 Nov 2016 13:22:25 +0100 Subject: [PATCH 046/125] #1362: As it stands currently, the >>> operator will not be implemented. --- libsolidity/grammar.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 3edb4eba6..6ad8994aa 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -65,7 +65,7 @@ Expression = | Expression '**' Expression | Expression ('*' | '/' | '%') Expression | Expression ('+' | '-') Expression - | Expression ('<<' | '>>' | '>>>') + | Expression ('<<' | '>>') | Expression '&' Expression | Expression '^' Expression | Expression '|' Expression From 6db9fd498ac707828e63518c07ed5543efe22fd4 Mon Sep 17 00:00:00 2001 From: Alex Sinyagin Date: Mon, 14 Nov 2016 13:39:46 +0000 Subject: [PATCH 047/125] Print canonical names of structs and enums in AST --- libsolidity/ast/Types.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index f09953931..6ad74d289 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1451,7 +1451,7 @@ u256 StructType::storageSize() const string StructType::toString(bool _short) const { - string ret = "struct " + m_struct.name(); + string ret = "struct " + m_struct.annotation().canonicalName; if (!_short) ret += " " + stringForReferencePart(); return ret; @@ -1570,7 +1570,7 @@ unsigned EnumType::storageBytes() const string EnumType::toString(bool) const { - return string("enum ") + m_enum.name(); + return string("enum ") + m_enum.annotation().canonicalName; } string EnumType::canonicalName(bool) const From c2c39239d6f1d51addad97c0c5128983ef58011f Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 16:02:57 +0100 Subject: [PATCH 048/125] Report infinite gas for calls. --- libevmasm/GasMeter.cpp | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 51f3cf1de..51dc34f47 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -128,24 +128,28 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item) case Instruction::CALLCODE: case Instruction::DELEGATECALL: { - gas = GasCosts::callGas; - if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) - gas += (*value); - else - gas = GasConsumption::infinite(); - if (_item.instruction() == Instruction::CALL) - gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. - int valueSize = _item.instruction() == Instruction::DELEGATECALL ? 0 : 1; - if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize))) - gas += GasCosts::callValueTransferGas; - gas += memoryGas(-2 - valueSize, -3 - valueSize); - gas += memoryGas(-4 - valueSize, -5 - valueSize); +// We assume that we do not know the target contract and thus, the consumption is infinite. + gas = GasConsumption::infinite(); +// gas = GasCosts::callGas; +// if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) +// gas += (*value); +// else +// gas = GasConsumption::infinite(); +// if (_item.instruction() == Instruction::CALL) +// gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. +// int valueSize = _item.instruction() == Instruction::DELEGATECALL ? 0 : 1; +// if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize))) +// gas += GasCosts::callValueTransferGas; +// gas += memoryGas(-2 - valueSize, -3 - valueSize); +// gas += memoryGas(-4 - valueSize, -5 - valueSize); break; } case Instruction::CREATE: - gas = GasCosts::createGas; - gas += memoryGas(-1, -2); - break; +// We assume that we do not know the target contract and thus, the consumption is infinite. +// gas = GasConsumption::infinite(); +// gas = GasCosts::createGas; +// gas += memoryGas(-1, -2); +// break; case Instruction::EXP: gas = GasCosts::expGas; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) From 1ff67b492a821054e5a3990f281500ebc0e56e83 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 14 Nov 2016 16:14:59 +0100 Subject: [PATCH 049/125] codegen: add a missing `break;` --- libsolidity/codegen/CompilerUtils.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index dd133aea6..546452890 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -666,6 +666,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp solAssert(_targetType == _typeOnStack, "Invalid conversion for bool."); if (_cleanupNeeded) m_context << Instruction::ISZERO << Instruction::ISZERO; + break; default: // All other types should not be convertible to non-equal types. solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); From 454e7618c8ebd35d442c43a66b2f95b36a4cd1d5 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 14 Nov 2016 16:44:04 +0100 Subject: [PATCH 050/125] test: add tests about returning invalid enum values from interface functions --- test/libsolidity/SolidityEndToEndTest.cpp | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 5582e4a12..74cf53cff 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4499,6 +4499,40 @@ BOOST_AUTO_TEST_CASE(external_types_in_calls) BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(9))); } +BOOST_AUTO_TEST_CASE(invalid_enum_as_external_ret) +{ + char const* sourceCode = R"( + contract C { + enum X { A, B } + + function test_return() returns (X) { + X garbled; + assembly { + garbled := 5 + } + return garbled; + } + function test_inline_assignment() returns (X _ret) { + assembly { + _ret := 5 + } + } + function test_assignment() returns (X _ret) { + X tmp; + assembly { + tmp := 5 + } + _ret = tmp; + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + // both should throw + BOOST_CHECK(callContractFunction("test_return()") == encodeArgs()); + BOOST_CHECK(callContractFunction("test_inline_assignment()") == encodeArgs()); + BOOST_CHECK(callContractFunction("test_assignment()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(proper_order_of_overwriting_of_attributes) { // bug #1798 From dbcbfafda8e44f56a7952993fd9f6699822395d6 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 14 Nov 2016 17:09:53 +0100 Subject: [PATCH 051/125] codegen: overflow checking also during conversion from enums --- libsolidity/codegen/CompilerUtils.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index dd133aea6..9472aac55 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -315,7 +315,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp Type::Category stackTypeCategory = _typeOnStack.category(); Type::Category targetTypeCategory = _targetType.category(); - bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum); + bool enumOverflowCheckPending = (targetTypeCategory == Type::Category::Enum || stackTypeCategory == Type::Category::Enum); switch (stackTypeCategory) { @@ -353,7 +353,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp solAssert(_targetType == _typeOnStack || targetTypeCategory == Type::Category::Integer, ""); if (enumOverflowCheckPending) { - EnumType const& enumType = dynamic_cast(_targetType); + EnumType const& enumType = dynamic_cast(_typeOnStack); solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error."); m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT; m_context.appendConditionalJumpTo(m_context.errorTag()); From e6247195ddc38b437b8b22b4f5fcd80f25a960a7 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Mon, 14 Nov 2016 15:15:09 +0100 Subject: [PATCH 052/125] test: add a testcase about using an invalid enum value as an external call argument --- test/libsolidity/SolidityEndToEndTest.cpp | 27 +++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 74cf53cff..739a2efde 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4533,6 +4533,33 @@ BOOST_AUTO_TEST_CASE(invalid_enum_as_external_ret) BOOST_CHECK(callContractFunction("test_assignment()") == encodeArgs()); } +BOOST_AUTO_TEST_CASE(invalid_enum_as_external_arg) +{ + char const* sourceCode = R"( + contract C { + enum X { A, B } + + function tested (X x) returns (uint) { + return 1; + } + + function test() returns (uint) { + X garbled; + + assembly { + garbled := 5 + } + + return this.tested(garbled); + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + // should throw + BOOST_CHECK(callContractFunction("test()") == encodeArgs()); +} + + BOOST_AUTO_TEST_CASE(proper_order_of_overwriting_of_attributes) { // bug #1798 From 58e75c7a48f8166cca41e9017dad351113952ab5 Mon Sep 17 00:00:00 2001 From: Rhett Aultman Date: Mon, 14 Nov 2016 12:41:58 -0800 Subject: [PATCH 053/125] Unimplemented features moved to their own exception (#1361) Unimplemented features moved to their own exception InternalCompilerError is an exception that really should be reserved for actual internal errors of the compiler. Unimplemented features can now use either solUnimplemented( ) or, if it should be conditional, then solUnimplementedAssert( ). * Revert some unimplemented exceptions, add handlers The jsonCompiler and CommandLineInterface needed handlers for the new UnimplementedFeatureException, and some cases I had moved on to the new exception were better treated as real internal compiler errors. * Standardize on "Unimplemented feature" message --- libsolidity/codegen/ArrayUtils.cpp | 2 +- libsolidity/codegen/CompilerUtils.cpp | 10 +++++----- libsolidity/codegen/ContractCompiler.cpp | 4 ++-- libsolidity/codegen/ExpressionCompiler.cpp | 14 +++++++------- libsolidity/codegen/LValue.cpp | 4 ++-- libsolidity/interface/Exceptions.h | 1 + libsolidity/interface/Utils.h | 6 ++++++ solc/CommandLineInterface.cpp | 6 ++++++ solc/jsonCompiler.cpp | 4 ++++ 9 files changed, 34 insertions(+), 17 deletions(-) diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp index e7e8a98e5..b5f5cd8e6 100644 --- a/libsolidity/codegen/ArrayUtils.cpp +++ b/libsolidity/codegen/ArrayUtils.cpp @@ -270,7 +270,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const { - solAssert( + solUnimplementedAssert( !_sourceType.baseType()->isDynamicallySized(), "Nested dynamic arrays not implemented here." ); diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index db4aac34d..58d1caa90 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -138,7 +138,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); if (numBytes > 0) { - solAssert( + solUnimplementedAssert( _type.sizeOnStack() == 1, "Memory store of types with stack size != 1 not implemented." ); @@ -161,7 +161,7 @@ void CompilerUtils::encodeToMemory( solAssert(targetTypes.size() == _givenTypes.size(), ""); for (TypePointer& t: targetTypes) { - solAssert( + solUnimplementedAssert( t->mobileType() && t->mobileType()->interfaceType(_encodeAsLibraryTypes) && t->mobileType()->interfaceType(_encodeAsLibraryTypes)->encodingType(), @@ -361,7 +361,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp } break; case Type::Category::FixedPoint: - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); case Type::Category::Integer: case Type::Category::Contract: case Type::Category::RationalNumber: @@ -401,7 +401,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp if (auto typeOnStack = dynamic_cast(&_typeOnStack)) if (targetFixedPointType.integerBits() > typeOnStack->numBits()) cleanHigherOrderBits(*typeOnStack); - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); } else { @@ -414,7 +414,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp RationalNumberType const& constType = dynamic_cast(_typeOnStack); // We know that the stack is clean, we only have to clean for a narrowing conversion // where cleanup is forced. - solAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); + solUnimplementedAssert(!constType.isFractional(), "Not yet implemented - FixedPointType."); if (targetType.numBits() < constType.integerType()->numBits() && _cleanupNeeded) cleanHigherOrderBits(targetType); } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 1404963f1..2aec3055f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -296,10 +296,10 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast(*type); - solAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); + solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); if (_fromMemory) { - solAssert( + solUnimplementedAssert( arrayType.baseType()->isValueType(), "Nested memory arrays not yet implemented here." ); diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 9a096e2d0..e3f05c21c 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -99,7 +99,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& if (auto mappingType = dynamic_cast(returnType.get())) { solAssert(CompilerUtils::freeMemoryPointer >= 0x40, ""); - solAssert( + solUnimplementedAssert( !paramTypes[i]->isDynamicallySized(), "Accessors for mapping with dynamically-sized keys not yet implemented." ); @@ -211,7 +211,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) Token::Value op = _assignment.assignmentOperator(); if (op != Token::Assign) // compound assignment { - solAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types."); + solUnimplementedAssert(_assignment.annotation().type->isValueType(), "Compound operators not implemented for non-value types."); unsigned lvalueSize = m_currentLValue->sizeOnStack(); unsigned itemSize = _assignment.annotation().type->sizeOnStack(); if (lvalueSize > 0) @@ -312,7 +312,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) if (!_unaryOperation.isPrefixOperation()) { // store value for later - solAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); + solUnimplementedAssert(_unaryOperation.annotation().type->sizeOnStack() == 1, "Stack size != 1 not implemented."); m_context << Instruction::DUP1; if (m_currentLValue->sizeOnStack() > 0) for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) @@ -1141,7 +1141,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) break; case DataLocation::CallData: //@todo if we implement this, the value in calldata has to be added to the base offset - solAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); + solUnimplementedAssert(!arrayType.baseType()->isDynamicallySized(), "Nested arrays not yet implemented."); if (arrayType.baseType()->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic( *arrayType.baseType(), @@ -1318,7 +1318,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty bool const c_isSigned = type.isSigned(); if (_type.category() == Type::Category::FixedPoint) - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); switch (_operator) { @@ -1372,7 +1372,7 @@ void ExpressionCompiler::appendBitOperatorCode(Token::Value _operator) void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator) { - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Shift operators not yet implemented.")); + BOOST_THROW_EXCEPTION(UnimplementedFeatureError() << errinfo_comment("Shift operators not yet implemented.")); switch (_operator) { case Token::SHL: @@ -1634,7 +1634,7 @@ void ExpressionCompiler::appendExternalFunctionCall( void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression) { - solAssert(_expectedType.isValueType(), "Not implemented for non-value types."); + solUnimplementedAssert(_expectedType.isValueType(), "Not implemented for non-value types."); _expression.accept(*this); utils().convertType(*_expression.annotation().type, _expectedType, true); utils().storeInMemoryDynamic(_expectedType); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index c1e057923..69a80b6a8 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -120,7 +120,7 @@ void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool } else { - solAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); + solUnimplementedAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory."); solAssert(m_dataType->sizeOnStack() == 1, ""); if (!_move) @@ -181,7 +181,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; if (m_dataType->category() == Type::Category::FixedPoint) // implementation should be very similar to the integer case. - solAssert(false, "Not yet implemented - FixedPointType."); + solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; else if ( diff --git a/libsolidity/interface/Exceptions.h b/libsolidity/interface/Exceptions.h index 078353200..c651548af 100644 --- a/libsolidity/interface/Exceptions.h +++ b/libsolidity/interface/Exceptions.h @@ -37,6 +37,7 @@ using ErrorList = std::vector>; struct CompilerError: virtual Exception {}; struct InternalCompilerError: virtual Exception {}; struct FatalError: virtual Exception {}; +struct UnimplementedFeatureError: virtual Exception{}; class Error: virtual public Exception { diff --git a/libsolidity/interface/Utils.h b/libsolidity/interface/Utils.h index 738669aca..eef8c917a 100644 --- a/libsolidity/interface/Utils.h +++ b/libsolidity/interface/Utils.h @@ -30,6 +30,7 @@ namespace dev namespace solidity { struct InternalCompilerError; +struct UnimplementedFeatureError; } } @@ -37,3 +38,8 @@ struct InternalCompilerError; #define solAssert(CONDITION, DESCRIPTION) \ assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) +#define solUnimplementedAssert(CONDITION, DESCRIPTION) \ + assertThrow(CONDITION, ::dev::solidity::UnimplementedFeatureError, DESCRIPTION) + +#define solUnimplemented(DESCRIPTION) \ + solUnimplementedAssert(false, DESCRIPTION) diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 84cc25341..83168f86f 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -604,6 +604,12 @@ bool CommandLineInterface::processInput() << boost::diagnostic_information(_exception); return false; } + catch (UnimplementedFeatureError const& _exception) + { + cerr << "Unimplemented feature:" << endl + << boost::diagnostic_information(_exception); + return false; + } catch (Error const& _error) { if (_error.type() == Error::Type::DocstringParsingError) diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index ef69105e6..e5be8404d 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -189,6 +189,10 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback { errors.append(formatError(exception, "Internal compiler error", scannerFromSourceName)); } + catch (UnimplementedFeatureError const& exception) + { + errors.append(formatError(exception, "Unimplemented feature", scannerFromSourceName)); + } catch (Exception const& exception) { errors.append("Exception during compilation: " + boost::diagnostic_information(exception)); From 35def4735e53429edf0767b2c4f74dc7bb2617f4 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Mon, 14 Nov 2016 21:43:28 +0000 Subject: [PATCH 054/125] Compile with -Og in debug mode (by @chfast) --- cmake/EthCompilerSettings.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index af6ae9281..0c3072888 100644 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -71,7 +71,7 @@ if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MA add_compile_options(-fPIC) # Configuration-specific compiler settings. - set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_DEBUG "-Og -g -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g") From 3dbf2830ca96922a3aec954ea9d7251f2bc02eed Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:04:43 +0100 Subject: [PATCH 055/125] Update tests.sh --- scripts/tests.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/tests.sh b/scripts/tests.sh index 5fa75d4d1..5da427d44 100755 --- a/scripts/tests.sh +++ b/scripts/tests.sh @@ -38,6 +38,7 @@ do set +e output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1) failed=$? + # Remove the pre-release warning from the compiler output output=$(echo "$output" | grep -v 'pre-release') echo "$output" set -e From 2f83a4557729753d6da28eddd28d54bfc18bf5e1 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 14:24:19 +0100 Subject: [PATCH 056/125] Swarm hash. --- libdevcore/SHA3.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/libdevcore/SHA3.h b/libdevcore/SHA3.h index c481bfc92..e3ff1335c 100644 --- a/libdevcore/SHA3.h +++ b/libdevcore/SHA3.h @@ -53,4 +53,12 @@ inline std::string keccak256(std::string const& _input, bool _isNibbles) { retur /// Calculate SHA3-256 MAC inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); } +h256 swarmHash(bytes const& _data) +{ + bytes size(8); + for (size_t i = 0; i < 8; ++i) + size[i] = (_data.size() >> (8 * i)) & 0xff; + + return keccak256(size + _data); +} } From 8944b092f8de074ec2c98434cb4c76d804146bd6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 21:35:24 +0100 Subject: [PATCH 057/125] Multi-level swarm hash. --- libdevcore/SHA3.cpp | 43 +++++++++++++++++++++++++++ libdevcore/SHA3.h | 9 +----- test/CMakeLists.txt | 5 ++-- test/libdevcore/SwarmHash.cpp | 56 +++++++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+), 10 deletions(-) create mode 100644 test/libdevcore/SwarmHash.cpp diff --git a/libdevcore/SHA3.cpp b/libdevcore/SHA3.cpp index 3b12f39f3..96c7b7641 100644 --- a/libdevcore/SHA3.cpp +++ b/libdevcore/SHA3.cpp @@ -238,4 +238,47 @@ bool keccak256(bytesConstRef _input, bytesRef o_output) return true; } +bytes toLittleEndian(size_t _size) +{ + bytes encoded(8); + for (size_t i = 0; i < 8; ++i) + encoded[i] = (_size >> (8 * i)) & 0xff; + return encoded; +} + +h256 swarmHashSimple(bytesConstRef _data, size_t _size) +{ + return keccak256(toLittleEndian(_size) + _data.toBytes()); +} + +h256 swarmHash(bytes const& _input) +{ + bytes data = _input; + size_t lastChunkSize = 0; + size_t level = 0; + do + { + bytes innerNodes; + size_t i = 0; + do + { + size_t bytes = std::min(0x1000, data.size() - i); + size_t size = bytes << (7 * level); + if (i + 0x1000 >= data.size()) + { + // last node + size = level == 0 ? bytes : ((bytes - 32) << (7 * level)) + lastChunkSize; + lastChunkSize = size; + } + innerNodes += swarmHashSimple(bytesConstRef(_input.data() + i, bytes), size).asBytes(); + i += 0x1000; + } + while (i < data.size()); + data = std::move(innerNodes); + level++; + } + while (data.size() > 32); + return h256(data); +} + } diff --git a/libdevcore/SHA3.h b/libdevcore/SHA3.h index e3ff1335c..ea0c761d7 100644 --- a/libdevcore/SHA3.h +++ b/libdevcore/SHA3.h @@ -53,12 +53,5 @@ inline std::string keccak256(std::string const& _input, bool _isNibbles) { retur /// Calculate SHA3-256 MAC inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); } -h256 swarmHash(bytes const& _data) -{ - bytes size(8); - for (size_t i = 0; i < 8; ++i) - size[i] = (_data.size() >> (8 * i)) & 0xff; - - return keccak256(size + _data); -} +h256 swarmHash(bytes const& _data); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e67a04d4d..33af9981e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,9 +1,10 @@ cmake_policy(SET CMP0015 NEW) aux_source_directory(. SRC_LIST) -aux_source_directory(contracts SRC_LIST) -aux_source_directory(libsolidity SRC_LIST) +aux_source_directory(libdevcore SRC_LIST) aux_source_directory(libevmasm SRC_LIST) +aux_source_directory(libsolidity SRC_LIST) +aux_source_directory(contracts SRC_LIST) get_filename_component(TESTS_DIR "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) diff --git a/test/libdevcore/SwarmHash.cpp b/test/libdevcore/SwarmHash.cpp new file mode 100644 index 000000000..47b2baa7d --- /dev/null +++ b/test/libdevcore/SwarmHash.cpp @@ -0,0 +1,56 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * Unit tests for the swarm hash computation routine. + */ + +#include + +#include "../TestHelper.h" + +using namespace std; + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(SwarmHash) + +string swarmHashHex(bytes const& _input) +{ + return toHex(swarmHash(_input).asBytes()); +} + +BOOST_AUTO_TEST_CASE(test_zeros) +{ + BOOST_CHECK_EQUAL(swarmHashHex(bytes()), string("011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000 - 1, 0)), string("32f0faabc4265ac238cd945087133ce3d7e9bb2e536053a812b5373c54043adb")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000, 0)), string("411dd45de7246e94589ff5888362c41e85bd3e582a92d0fda8f0e90b76439bec")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000 + 1, 0)), string("970b0b1fe0bf90549af9aba54e8ce18884cce97d63069efe92f73fd50037c3e2")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000 - 1, 0)), string("1e7bd4d46836b63a2e7c313b68d24ad6be56ed6c8b30634005fd213bb580b0b4")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000, 0)), string("ab184c15eee316dd54e8887614a535f589478e99b922a1ede30ec418344c8324")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000 + 1, 0)), string("c8915d9244ad5d7428a41b207e083de6eafffac629b45ad462062ea8dc5ac4a3")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x80000, 0)), string("d0bf003bbc34a68f608f8bc88b497c19ce4ad61f38436ee3120b33db9cc9a116")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x80020, 0)), string("d001908f9942ad56d5343574e33bd74b30092115b1c29a67c20869a9ddbbedc3")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x800020, 0)), string("cdeedeccfe3eaa1c4095713af579b9c2b151d2b3f45ce6652ba2f5cd537a60ba")); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} From 2ecf348a9d00af8757efd1310b71c7c7e8f34cb7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:12:14 +0100 Subject: [PATCH 058/125] Move swarmHash to its own file. --- libdevcore/SHA3.cpp | 43 ---------------------- libdevcore/SHA3.h | 1 - libdevcore/SwarmHash.cpp | 69 +++++++++++++++++++++++++++++++++++ libdevcore/SwarmHash.h | 31 ++++++++++++++++ test/libdevcore/SwarmHash.cpp | 2 +- 5 files changed, 101 insertions(+), 45 deletions(-) create mode 100644 libdevcore/SwarmHash.cpp create mode 100644 libdevcore/SwarmHash.h diff --git a/libdevcore/SHA3.cpp b/libdevcore/SHA3.cpp index 96c7b7641..3b12f39f3 100644 --- a/libdevcore/SHA3.cpp +++ b/libdevcore/SHA3.cpp @@ -238,47 +238,4 @@ bool keccak256(bytesConstRef _input, bytesRef o_output) return true; } -bytes toLittleEndian(size_t _size) -{ - bytes encoded(8); - for (size_t i = 0; i < 8; ++i) - encoded[i] = (_size >> (8 * i)) & 0xff; - return encoded; -} - -h256 swarmHashSimple(bytesConstRef _data, size_t _size) -{ - return keccak256(toLittleEndian(_size) + _data.toBytes()); -} - -h256 swarmHash(bytes const& _input) -{ - bytes data = _input; - size_t lastChunkSize = 0; - size_t level = 0; - do - { - bytes innerNodes; - size_t i = 0; - do - { - size_t bytes = std::min(0x1000, data.size() - i); - size_t size = bytes << (7 * level); - if (i + 0x1000 >= data.size()) - { - // last node - size = level == 0 ? bytes : ((bytes - 32) << (7 * level)) + lastChunkSize; - lastChunkSize = size; - } - innerNodes += swarmHashSimple(bytesConstRef(_input.data() + i, bytes), size).asBytes(); - i += 0x1000; - } - while (i < data.size()); - data = std::move(innerNodes); - level++; - } - while (data.size() > 32); - return h256(data); -} - } diff --git a/libdevcore/SHA3.h b/libdevcore/SHA3.h index ea0c761d7..c481bfc92 100644 --- a/libdevcore/SHA3.h +++ b/libdevcore/SHA3.h @@ -53,5 +53,4 @@ inline std::string keccak256(std::string const& _input, bool _isNibbles) { retur /// Calculate SHA3-256 MAC inline void keccak256mac(bytesConstRef _secret, bytesConstRef _plain, bytesRef _output) { keccak256(_secret.toBytes() + _plain.toBytes()).ref().populate(_output); } -h256 swarmHash(bytes const& _data); } diff --git a/libdevcore/SwarmHash.cpp b/libdevcore/SwarmHash.cpp new file mode 100644 index 000000000..583d84b1f --- /dev/null +++ b/libdevcore/SwarmHash.cpp @@ -0,0 +1,69 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file SwarmHash.cpp + */ + +#include + +#include + +using namespace std; +using namespace dev; + + +bytes toLittleEndian(size_t _size) +{ + bytes encoded(8); + for (size_t i = 0; i < 8; ++i) + encoded[i] = (_size >> (8 * i)) & 0xff; + return encoded; +} + +h256 swarmHashSimple(bytesConstRef _data, size_t _size) +{ + return keccak256(toLittleEndian(_size) + _data.toBytes()); +} + +h256 dev::swarmHash(bytes const& _input) +{ + bytes data = _input; + size_t lastChunkSize = 0; + size_t level = 0; + do + { + bytes innerNodes; + size_t i = 0; + do + { + size_t bytes = std::min(0x1000, data.size() - i); + size_t size = bytes << (7 * level); + if (i + 0x1000 >= data.size()) + { + // last node + size = level == 0 ? bytes : ((bytes - 32) << (7 * level)) + lastChunkSize; + lastChunkSize = size; + } + innerNodes += swarmHashSimple(bytesConstRef(_input.data() + i, bytes), size).asBytes(); + i += 0x1000; + } + while (i < data.size()); + data = std::move(innerNodes); + level++; + } + while (data.size() > 32); + return h256(data); +} diff --git a/libdevcore/SwarmHash.h b/libdevcore/SwarmHash.h new file mode 100644 index 000000000..925509ab5 --- /dev/null +++ b/libdevcore/SwarmHash.h @@ -0,0 +1,31 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file SwarmHash.h + */ + +#pragma once + +#include +#include + +namespace dev +{ + +/// Compute the "swarm hash" of @a _data +h256 swarmHash(bytes const& _data); + +} diff --git a/test/libdevcore/SwarmHash.cpp b/test/libdevcore/SwarmHash.cpp index 47b2baa7d..1f0c2ea5e 100644 --- a/test/libdevcore/SwarmHash.cpp +++ b/test/libdevcore/SwarmHash.cpp @@ -18,7 +18,7 @@ * Unit tests for the swarm hash computation routine. */ -#include +#include #include "../TestHelper.h" From bf5b0dc2d27a3c4139894daf627cf63b8cfb1864 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:28:26 +0100 Subject: [PATCH 059/125] Keep old code. --- libevmasm/GasMeter.cpp | 48 ++++++++++++++++++++++++------------------ libevmasm/GasMeter.h | 3 ++- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp index 51dc34f47..da8b41e32 100644 --- a/libevmasm/GasMeter.cpp +++ b/libevmasm/GasMeter.cpp @@ -39,7 +39,7 @@ GasMeter::GasConsumption& GasMeter::GasConsumption::operator+=(GasConsumption co return *this; } -GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item) +GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item, bool _includeExternalCosts) { GasConsumption gas; switch (_item.type()) @@ -128,28 +128,36 @@ GasMeter::GasConsumption GasMeter::estimateMax(AssemblyItem const& _item) case Instruction::CALLCODE: case Instruction::DELEGATECALL: { -// We assume that we do not know the target contract and thus, the consumption is infinite. - gas = GasConsumption::infinite(); -// gas = GasCosts::callGas; -// if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) -// gas += (*value); -// else -// gas = GasConsumption::infinite(); -// if (_item.instruction() == Instruction::CALL) -// gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. -// int valueSize = _item.instruction() == Instruction::DELEGATECALL ? 0 : 1; -// if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize))) -// gas += GasCosts::callValueTransferGas; -// gas += memoryGas(-2 - valueSize, -3 - valueSize); -// gas += memoryGas(-4 - valueSize, -5 - valueSize); + if (_includeExternalCosts) + // We assume that we do not know the target contract and thus, the consumption is infinite. + gas = GasConsumption::infinite(); + else + { + gas = GasCosts::callGas; + if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(0))) + gas += (*value); + else + gas = GasConsumption::infinite(); + if (_item.instruction() == Instruction::CALL) + gas += GasCosts::callNewAccountGas; // We very rarely know whether the address exists. + int valueSize = _item.instruction() == Instruction::DELEGATECALL ? 0 : 1; + if (!classes.knownZero(m_state->relativeStackElement(-1 - valueSize))) + gas += GasCosts::callValueTransferGas; + gas += memoryGas(-2 - valueSize, -3 - valueSize); + gas += memoryGas(-4 - valueSize, -5 - valueSize); + } break; } case Instruction::CREATE: -// We assume that we do not know the target contract and thus, the consumption is infinite. -// gas = GasConsumption::infinite(); -// gas = GasCosts::createGas; -// gas += memoryGas(-1, -2); -// break; + if (_includeExternalCosts) + // We assume that we do not know the target contract and thus, the consumption is infinite. + gas = GasConsumption::infinite(); + else + { + gas = GasCosts::createGas; + gas += memoryGas(-1, -2); + } + break; case Instruction::EXP: gas = GasCosts::expGas; if (u256 const* value = classes.knownConstant(m_state->relativeStackElement(-1))) diff --git a/libevmasm/GasMeter.h b/libevmasm/GasMeter.h index 1a607a9fc..ff1279096 100644 --- a/libevmasm/GasMeter.h +++ b/libevmasm/GasMeter.h @@ -102,7 +102,8 @@ public: /// @returns an upper bound on the gas consumed by the given instruction and updates /// the state. - GasConsumption estimateMax(AssemblyItem const& _item); + /// @param _inculdeExternalCosts if true, include costs caused by other contracts in calls. + GasConsumption estimateMax(AssemblyItem const& _item, bool _includeExternalCosts = true); u256 const& largestMemoryAccess() const { return m_largestMemoryAccess; } From ac46834d7d9e756388803e418e0e6eea336e66ba Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 28 Jul 2016 22:06:30 +0100 Subject: [PATCH 060/125] Do not include a trailing new line in the ABI JSON output --- Changelog.md | 1 + libsolidity/interface/InterfaceHandler.cpp | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 851f39a01..98dababdc 100644 --- a/Changelog.md +++ b/Changelog.md @@ -9,6 +9,7 @@ Bugfixes: * Parser: disallow empty enum definitions. * Type checker: disallow conversion between different enum types. + * Interface JSON: do not include trailing new line. ### 0.4.4 (2016-10-31) diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index de16a3725..1686f9ea6 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -103,7 +103,10 @@ string InterfaceHandler::abiInterface(ContractDefinition const& _contractDef) event["inputs"] = params; abi.append(event); } - return Json::FastWriter().write(abi); + + Json::FastWriter writer; + writer.omitEndingLineFeed(); + return writer.write(abi); } string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDef) From dad33f80dda1824c87e64ad857f9f075e97a01c8 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 02:53:19 +0000 Subject: [PATCH 061/125] Fix inline assembly stack warnings when using variables --- Changelog.md | 1 + libsolidity/inlineasm/AsmCodeGen.cpp | 16 +++++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Changelog.md b/Changelog.md index 851f39a01..64db4af8b 100644 --- a/Changelog.md +++ b/Changelog.md @@ -7,6 +7,7 @@ Features: Bugfixes: + * Inline assembly: calculate stack height warning correctly even when local variables are used. * Parser: disallow empty enum definitions. * Type checker: disallow conversion between different enum types. diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 5d920cb75..76c710486 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -216,10 +216,18 @@ public: size_t numVariables = m_state.variables.size(); int deposit = m_state.assembly.deposit(); std::for_each(_block.statements.begin(), _block.statements.end(), boost::apply_visitor(*this)); - deposit = m_state.assembly.deposit() - deposit; + + // pop variables + while (m_state.variables.size() > numVariables) + { + m_state.assembly.append(solidity::Instruction::POP); + m_state.variables.pop_back(); + } m_state.assembly.setSourceLocation(_block.location); + deposit = m_state.assembly.deposit() - deposit; + // issue warnings for stack height discrepancies if (deposit < 0) { @@ -238,12 +246,6 @@ public: ); } - // pop variables - while (m_state.variables.size() > numVariables) - { - m_state.assembly.append(solidity::Instruction::POP); - m_state.variables.pop_back(); - } } private: From dce80911e198fe5d6317a9a4d155f929789fdea6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 15 Nov 2016 09:16:33 +0100 Subject: [PATCH 062/125] Add tests --- test/libsolidity/InlineAssembly.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index 6c04367f7..a80a44a20 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -41,7 +41,7 @@ namespace test namespace { -bool successParse(std::string const& _source, bool _assemble = false) +bool successParse(std::string const& _source, bool _assemble = false, bool _allowWarnings = true) { assembly::InlineAssemblyStack stack; try @@ -51,8 +51,9 @@ bool successParse(std::string const& _source, bool _assemble = false) if (_assemble) { stack.assemble(); - if (!stack.errors().empty() && !Error::containsOnlyWarnings(stack.errors())) - return false; + if (!stack.errors().empty()) + if (!_allowWarnings || !Error::containsOnlyWarnings(stack.errors())) + return false; } } catch (FatalError const&) @@ -67,9 +68,9 @@ bool successParse(std::string const& _source, bool _assemble = false) return true; } -bool successAssemble(string const& _source) +bool successAssemble(string const& _source, bool _allowWarnings = true) { - return successParse(_source, true); + return successParse(_source, true, _allowWarnings); } } @@ -169,6 +170,13 @@ BOOST_AUTO_TEST_CASE(magic_variables) BOOST_CHECK(successAssemble("{ let ecrecover := 1 ecrecover }")); } +BOOST_AUTO_TEST_CASE(imbalanced_stack) +{ + BOOST_CHECK(successAssemble("{ 1 2 mul pop }", false)); + BOOST_CHECK(!successAssemble("{ 1 }", false)); + BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); +} + BOOST_AUTO_TEST_SUITE_END() } From 91367234d946266b73a5ace8071b0fd931f3a74b Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Oct 2016 12:30:04 +0100 Subject: [PATCH 063/125] Support ErrorTag as a jump label in inline assembly --- Changelog.md | 1 + libsolidity/inlineasm/AsmCodeGen.cpp | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/Changelog.md b/Changelog.md index 4d68fc69f..d5f8cea9d 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ Features: * Do-while loops: support for a C-style do{}while(); control structure + * Inline assembly: support ``ErrorTag`` as a jump label. * Type checker: now more eagerly searches for a common type of an inline array with mixed types * Code generator: generates a runtime error when an out-of-range value is converted into an enum type. diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 76c710486..1b789c5f0 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -81,7 +81,11 @@ struct GeneratorState class LabelOrganizer: public boost::static_visitor<> { public: - LabelOrganizer(GeneratorState& _state): m_state(_state) {} + LabelOrganizer(GeneratorState& _state): m_state(_state) + { + // Make the Solidity ErrorTag available to inline assembly + m_state.labels.insert(make_pair("ErrorTag", m_state.assembly.errorTag())); + } template void operator()(T const& /*_item*/) { } From bee926bf3f2d9f56103547d1add5463ab21c5f8e Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 20 Oct 2016 12:30:10 +0100 Subject: [PATCH 064/125] Add tests for the ErrorTag --- test/libsolidity/InlineAssembly.cpp | 5 +++++ test/libsolidity/SolidityEndToEndTest.cpp | 15 +++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index a80a44a20..a26e9470f 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -177,6 +177,11 @@ BOOST_AUTO_TEST_CASE(imbalanced_stack) BOOST_CHECK(successAssemble("{ let x := 4 7 add }", false)); } +BOOST_AUTO_TEST_CASE(error_tag) +{ + BOOST_CHECK(successAssemble("{ ErrorTag }")); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 739a2efde..e50c2a85f 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7692,6 +7692,21 @@ BOOST_AUTO_TEST_CASE(packed_storage_overflow) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x1234), u256(0), u256(0), u256(0xfffe))); } +BOOST_AUTO_TEST_CASE(inline_assembly_errortag) +{ + char const* sourceCode = R"( + contract C { + function f() { + assembly { + jump(ErrorTag) + } + } + } + )"; + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); +} + BOOST_AUTO_TEST_SUITE_END() } From 702ab4cb4f9344ce6d8fb1311db7c9fc94431386 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Wed, 9 Nov 2016 01:18:10 +0000 Subject: [PATCH 065/125] Document inline assembly ErrorTag --- docs/control-structures.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index bbb90e6a2..351e4c916 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -716,6 +716,10 @@ will have a wrong impression about the stack height at label ``two``: three: } +.. note:: + + ``ErrorTag`` is a pre-defined label. Jumping to this location will always + result in an invalid jump, effectively aborting execution of the code. Declaring Assembly-Local Variables ---------------------------------- From ae8403ed08cf3b2b5bec1d3f8da0c6c7425a4d5a Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 10:12:03 +0000 Subject: [PATCH 066/125] Rename ErrorTag to invalidJumpLabel in inline assembly --- Changelog.md | 2 +- docs/control-structures.rst | 2 +- libsolidity/inlineasm/AsmCodeGen.cpp | 2 +- test/libsolidity/InlineAssembly.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Changelog.md b/Changelog.md index d5f8cea9d..a392ec013 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,7 +2,7 @@ Features: * Do-while loops: support for a C-style do{}while(); control structure - * Inline assembly: support ``ErrorTag`` as a jump label. + * Inline assembly: support ``invalidJumpLabel`` as a jump label. * Type checker: now more eagerly searches for a common type of an inline array with mixed types * Code generator: generates a runtime error when an out-of-range value is converted into an enum type. diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 351e4c916..7e1c690d2 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -718,7 +718,7 @@ will have a wrong impression about the stack height at label ``two``: .. note:: - ``ErrorTag`` is a pre-defined label. Jumping to this location will always + ``invalidJumpLabel`` is a pre-defined label. Jumping to this location will always result in an invalid jump, effectively aborting execution of the code. Declaring Assembly-Local Variables diff --git a/libsolidity/inlineasm/AsmCodeGen.cpp b/libsolidity/inlineasm/AsmCodeGen.cpp index 1b789c5f0..771f10425 100644 --- a/libsolidity/inlineasm/AsmCodeGen.cpp +++ b/libsolidity/inlineasm/AsmCodeGen.cpp @@ -84,7 +84,7 @@ public: LabelOrganizer(GeneratorState& _state): m_state(_state) { // Make the Solidity ErrorTag available to inline assembly - m_state.labels.insert(make_pair("ErrorTag", m_state.assembly.errorTag())); + m_state.labels.insert(make_pair("invalidJumpLabel", m_state.assembly.errorTag())); } template diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp index a26e9470f..185a62158 100644 --- a/test/libsolidity/InlineAssembly.cpp +++ b/test/libsolidity/InlineAssembly.cpp @@ -179,7 +179,7 @@ BOOST_AUTO_TEST_CASE(imbalanced_stack) BOOST_AUTO_TEST_CASE(error_tag) { - BOOST_CHECK(successAssemble("{ ErrorTag }")); + BOOST_CHECK(successAssemble("{ invalidJumpLabel }")); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index e50c2a85f..d89242500 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7692,13 +7692,13 @@ BOOST_AUTO_TEST_CASE(packed_storage_overflow) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(0x1234), u256(0), u256(0), u256(0xfffe))); } -BOOST_AUTO_TEST_CASE(inline_assembly_errortag) +BOOST_AUTO_TEST_CASE(inline_assembly_invalidjumplabel) { char const* sourceCode = R"( contract C { function f() { assembly { - jump(ErrorTag) + jump(invalidJumpLabel) } } } From 518fe2aab7b96dc9cd259049dec80be509860a43 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 15 Nov 2016 14:55:19 +0100 Subject: [PATCH 067/125] Correct implementation of swarm hash. --- libdevcore/SwarmHash.cpp | 38 +++++++++++++++-------------------- test/libdevcore/SwarmHash.cpp | 15 +++++++------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/libdevcore/SwarmHash.cpp b/libdevcore/SwarmHash.cpp index 583d84b1f..e7b844ebf 100644 --- a/libdevcore/SwarmHash.cpp +++ b/libdevcore/SwarmHash.cpp @@ -38,32 +38,26 @@ h256 swarmHashSimple(bytesConstRef _data, size_t _size) return keccak256(toLittleEndian(_size) + _data.toBytes()); } -h256 dev::swarmHash(bytes const& _input) +h256 swarmHashIntermediate(bytes const& _input, size_t _offset, size_t _length) { - bytes data = _input; - size_t lastChunkSize = 0; - size_t level = 0; - do + if (_length <= 0x1000) + return swarmHashSimple(bytesConstRef(_input.data() + _offset, _length), _length); + else { bytes innerNodes; - size_t i = 0; - do + size_t maxRepresentedSize = 0x1000; + while (maxRepresentedSize * (0x1000 / 32) < _length) + maxRepresentedSize *= (0x1000 / 32); + for (size_t i = 0; i < _length; i += maxRepresentedSize) { - size_t bytes = std::min(0x1000, data.size() - i); - size_t size = bytes << (7 * level); - if (i + 0x1000 >= data.size()) - { - // last node - size = level == 0 ? bytes : ((bytes - 32) << (7 * level)) + lastChunkSize; - lastChunkSize = size; - } - innerNodes += swarmHashSimple(bytesConstRef(_input.data() + i, bytes), size).asBytes(); - i += 0x1000; + size_t size = std::min(maxRepresentedSize, _length - i); + innerNodes += swarmHashIntermediate(_input, _offset + i, size).asBytes(); } - while (i < data.size()); - data = std::move(innerNodes); - level++; + return swarmHashSimple(bytesConstRef(&innerNodes), _length); } - while (data.size() > 32); - return h256(data); +} + +h256 dev::swarmHash(bytes const& _input) +{ + return swarmHashIntermediate(_input, 0, _input.size()); } diff --git a/test/libdevcore/SwarmHash.cpp b/test/libdevcore/SwarmHash.cpp index 1f0c2ea5e..a23c36fd8 100644 --- a/test/libdevcore/SwarmHash.cpp +++ b/test/libdevcore/SwarmHash.cpp @@ -41,13 +41,14 @@ BOOST_AUTO_TEST_CASE(test_zeros) BOOST_CHECK_EQUAL(swarmHashHex(bytes()), string("011b4d03dd8c01f1049143cf9c4c817e4b167f1d1b83e5c6f0f10d89ba1e7bce")); BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000 - 1, 0)), string("32f0faabc4265ac238cd945087133ce3d7e9bb2e536053a812b5373c54043adb")); BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000, 0)), string("411dd45de7246e94589ff5888362c41e85bd3e582a92d0fda8f0e90b76439bec")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000 + 1, 0)), string("970b0b1fe0bf90549af9aba54e8ce18884cce97d63069efe92f73fd50037c3e2")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000 - 1, 0)), string("1e7bd4d46836b63a2e7c313b68d24ad6be56ed6c8b30634005fd213bb580b0b4")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000, 0)), string("ab184c15eee316dd54e8887614a535f589478e99b922a1ede30ec418344c8324")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000 + 1, 0)), string("c8915d9244ad5d7428a41b207e083de6eafffac629b45ad462062ea8dc5ac4a3")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x80000, 0)), string("d0bf003bbc34a68f608f8bc88b497c19ce4ad61f38436ee3120b33db9cc9a116")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x80020, 0)), string("d001908f9942ad56d5343574e33bd74b30092115b1c29a67c20869a9ddbbedc3")); - BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x800020, 0)), string("cdeedeccfe3eaa1c4095713af579b9c2b151d2b3f45ce6652ba2f5cd537a60ba")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x1000 + 1, 0)), string("69754a0098432bbc2e84fe1205276870748a61a065ab6ef44d6a2e7b13ce044d")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000 - 1, 0)), string("69ad3c581043404f775ffa8d6f1b25ad4a9ee812971190e90209c0966116a321")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000, 0)), string("f00222373ff82d0a178dc6271c78953e9c88f74130a52d401f5ec51475f63c43")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x2000 + 1, 0)), string("86d6773e79e02fd8145ee1aedba89ace0c15f2566db1249654000039a9a134bf")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x80000, 0)), string("cc0854fe2c6b98e920d5c14b1a88e6d4223e55b8f78883f60939aa2485e361bf")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x80020, 0)), string("ee9ffca246e70d3704740ba4df450fa6988d14a1c2439c7e734c7a77a4eb6fd3")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(0x800020, 0)), string("78b90b20c90559fb904535181a7c28929ea2f30a2329dbc25232de579709f12f")); + BOOST_CHECK_EQUAL(swarmHashHex(bytes(2095104, 0)), string("a9958184589fc11b4027a4c233e777ebe2e99c66f96b74aef2a0638a94dd5439")); } BOOST_AUTO_TEST_SUITE_END() From 9719cf38e662e428ace8f3ebce9774a5338f0ce5 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 01:04:00 +0000 Subject: [PATCH 068/125] Move InterfaceHandler from string to JSON --- libsolidity/ast/AST.cpp | 8 +++--- libsolidity/ast/AST.h | 13 +++++----- libsolidity/interface/CompilerStack.cpp | 8 +++--- libsolidity/interface/CompilerStack.h | 14 +++++----- libsolidity/interface/InterfaceHandler.cpp | 17 +++++------- libsolidity/interface/InterfaceHandler.h | 16 ++++++------ solc/CommandLineInterface.cpp | 30 +++++++++++++++++----- solc/jsonCompiler.cpp | 13 +++++++--- 8 files changed, 71 insertions(+), 48 deletions(-) diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp index 695d98819..305668e7e 100644 --- a/libsolidity/ast/AST.cpp +++ b/libsolidity/ast/AST.cpp @@ -160,22 +160,22 @@ vector, FunctionTypePointer>> const& ContractDefinition::inter return *m_interfaceFunctionList; } -string const& ContractDefinition::devDocumentation() const +Json::Value const& ContractDefinition::devDocumentation() const { return m_devDocumentation; } -string const& ContractDefinition::userDocumentation() const +Json::Value const& ContractDefinition::userDocumentation() const { return m_userDocumentation; } -void ContractDefinition::setDevDocumentation(string const& _devDocumentation) +void ContractDefinition::setDevDocumentation(Json::Value const& _devDocumentation) { m_devDocumentation = _devDocumentation; } -void ContractDefinition::setUserDocumentation(string const& _userDocumentation) +void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentation) { m_userDocumentation = _userDocumentation; } diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 6c3f52bcc..1b42c499f 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -35,6 +35,7 @@ #include #include #include +#include namespace dev { @@ -343,11 +344,11 @@ public: /// Returns the fallback function or nullptr if no fallback function was specified. FunctionDefinition const* fallbackFunction() const; - std::string const& userDocumentation() const; - void setUserDocumentation(std::string const& _userDocumentation); + Json::Value const& userDocumentation() const; + void setUserDocumentation(Json::Value const& _userDocumentation); - std::string const& devDocumentation() const; - void setDevDocumentation(std::string const& _devDocumentation); + Json::Value const& devDocumentation() const; + void setDevDocumentation(Json::Value const& _devDocumentation); virtual TypePointer type() const override; @@ -359,8 +360,8 @@ private: bool m_isLibrary; // parsed Natspec documentation of the contract. - std::string m_userDocumentation; - std::string m_devDocumentation; + Json::Value m_userDocumentation; + Json::Value m_devDocumentation; std::vector m_linearizedBaseContracts; mutable std::unique_ptr, FunctionTypePointer>>> m_interfaceFunctionList; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index efbbd237d..519027bcd 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -345,17 +345,17 @@ map CompilerStack::sourceIndices() const return indices; } -string const& CompilerStack::interface(string const& _contractName) const +Json::Value const& CompilerStack::interface(string const& _contractName) const { return metadata(_contractName, DocumentationType::ABIInterface); } -string const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const +Json::Value const& CompilerStack::metadata(string const& _contractName, DocumentationType _type) const { if (!m_parseSuccessful) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Parsing was not successful.")); - std::unique_ptr* doc; + std::unique_ptr* doc; Contract const& currentContract = contract(_contractName); // checks wheather we already have the documentation @@ -376,7 +376,7 @@ string const& CompilerStack::metadata(string const& _contractName, Documentation // caches the result if (!*doc) - doc->reset(new string(InterfaceHandler::documentation(*currentContract.contract, _type))); + doc->reset(new Json::Value(InterfaceHandler::documentation(*currentContract.contract, _type))); return *(*doc); } diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h index da4796380..1fd30c4db 100644 --- a/libsolidity/interface/CompilerStack.h +++ b/libsolidity/interface/CompilerStack.h @@ -162,14 +162,14 @@ public: /// @returns a mapping assigning each source name its index inside the vector returned /// by sourceNames(). std::map sourceIndices() const; - /// @returns a string representing the contract interface in JSON. + /// @returns a JSON representing the contract interface. /// Prerequisite: Successful call to parse or compile. - std::string const& interface(std::string const& _contractName = "") const; - /// @returns a string representing the contract's documentation in JSON. + Json::Value const& interface(std::string const& _contractName = "") const; + /// @returns a JSON representing the contract's documentation. /// Prerequisite: Successful call to parse or compile. /// @param type The type of the documentation to get. /// Can be one of 4 types defined at @c DocumentationType - std::string const& metadata(std::string const& _contractName, DocumentationType _type) const; + Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const; /// @returns the previously used scanner, useful for counting lines during error reporting. Scanner const& scanner(std::string const& _sourceName = "") const; @@ -213,9 +213,9 @@ private: eth::LinkerObject object; eth::LinkerObject runtimeObject; eth::LinkerObject cloneObject; - mutable std::unique_ptr interface; - mutable std::unique_ptr userDocumentation; - mutable std::unique_ptr devDocumentation; + mutable std::unique_ptr interface; + mutable std::unique_ptr userDocumentation; + mutable std::unique_ptr devDocumentation; mutable std::unique_ptr sourceMapping; mutable std::unique_ptr runtimeSourceMapping; }; diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 1686f9ea6..5705856c9 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -8,7 +8,7 @@ using namespace std; using namespace dev; using namespace dev::solidity; -string InterfaceHandler::documentation( +Json::Value InterfaceHandler::documentation( ContractDefinition const& _contractDef, DocumentationType _type ) @@ -24,10 +24,9 @@ string InterfaceHandler::documentation( } BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown documentation type")); - return ""; } -string InterfaceHandler::abiInterface(ContractDefinition const& _contractDef) +Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDef) { Json::Value abi(Json::arrayValue); @@ -104,12 +103,10 @@ string InterfaceHandler::abiInterface(ContractDefinition const& _contractDef) abi.append(event); } - Json::FastWriter writer; - writer.omitEndingLineFeed(); - return writer.write(abi); + return abi; } -string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDef) +Json::Value InterfaceHandler::userDocumentation(ContractDefinition const& _contractDef) { Json::Value doc; Json::Value methods(Json::objectValue); @@ -129,10 +126,10 @@ string InterfaceHandler::userDocumentation(ContractDefinition const& _contractDe } doc["methods"] = methods; - return Json::StyledWriter().write(doc); + return doc; } -string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef) +Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef) { Json::Value doc; Json::Value methods(Json::objectValue); @@ -178,7 +175,7 @@ string InterfaceHandler::devDocumentation(ContractDefinition const& _contractDef } doc["methods"] = methods; - return Json::StyledWriter().write(doc); + return doc; } string InterfaceHandler::extractDoc(multimap const& _tags, string const& _name) diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h index 54199e4e7..d4f2eaf4f 100644 --- a/libsolidity/interface/InterfaceHandler.h +++ b/libsolidity/interface/InterfaceHandler.h @@ -64,24 +64,24 @@ public: /// @param _contractDef The contract definition /// @param _type The type of the documentation. Can be one of the /// types provided by @c DocumentationType - /// @return A string with the json representation of provided type - static std::string documentation( + /// @return A JSON representation of provided type + static Json::Value documentation( ContractDefinition const& _contractDef, DocumentationType _type ); /// Get the ABI Interface of the contract /// @param _contractDef The contract definition - /// @return A string with the json representation of the contract's ABI Interface - static std::string abiInterface(ContractDefinition const& _contractDef); + /// @return A JSONrepresentation of the contract's ABI Interface + static Json::Value abiInterface(ContractDefinition const& _contractDef); /// Get the User documentation of the contract /// @param _contractDef The contract definition - /// @return A string with the json representation of the contract's user documentation - static std::string userDocumentation(ContractDefinition const& _contractDef); + /// @return A JSON representation of the contract's user documentation + static Json::Value userDocumentation(ContractDefinition const& _contractDef); /// Genereates the Developer's documentation of the contract /// @param _contractDef The contract definition - /// @return A string with the json representation + /// @return A JSON representation /// of the contract's developer documentation - static std::string devDocumentation(ContractDefinition const& _contractDef); + static Json::Value devDocumentation(ContractDefinition const& _contractDef); private: /// @returns concatenation of all content under the given tag name. diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 83168f86f..5509a414e 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -107,6 +107,18 @@ static void version() exit(0); } +string jsonPrettyPrint(Json::Value const& input) +{ + return Json::StyledWriter().write(input); +} + +string jsonCompactPrint(Json::Value const& input) +{ + Json::FastWriter writer; + writer.omitEndingLineFeed(); + return writer.write(input); +} + static bool needsHumanTargetedStdout(po::variables_map const& _args) { if (_args.count(g_argGas)) @@ -230,12 +242,18 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co if (m_args.count(argName)) { + std::string output; + if (_type == DocumentationType::ABIInterface) + output = jsonCompactPrint(m_compiler->metadata(_contract, _type)); + else + output = jsonPrettyPrint(m_compiler->metadata(_contract, _type)); + if (m_args.count("output-dir")) - createFile(_contract + suffix, m_compiler->metadata(_contract, _type)); + createFile(_contract + suffix, output); else { cout << title << endl; - cout << m_compiler->metadata(_contract, _type) << endl; + cout << output << endl; } } @@ -651,7 +669,7 @@ void CommandLineInterface::handleCombinedJSON() { Json::Value contractData(Json::objectValue); if (requests.count("abi")) - contractData["abi"] = m_compiler->interface(contractName); + contractData["abi"] = jsonCompactPrint(m_compiler->interface(contractName)); if (requests.count("bin")) contractData["bin"] = m_compiler->object(contractName).toHex(); if (requests.count("bin-runtime")) @@ -676,9 +694,9 @@ void CommandLineInterface::handleCombinedJSON() contractData["srcmap-runtime"] = map ? *map : ""; } if (requests.count("devdoc")) - contractData["devdoc"] = m_compiler->metadata(contractName, DocumentationType::NatspecDev); + contractData["devdoc"] = jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecDev)); if (requests.count("userdoc")) - contractData["userdoc"] = m_compiler->metadata(contractName, DocumentationType::NatspecUser); + contractData["userdoc"] = jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecUser)); output["contracts"][contractName] = contractData; } @@ -702,7 +720,7 @@ void CommandLineInterface::handleCombinedJSON() output["sources"][sourceCode.first]["AST"] = converter.json(); } } - cout << Json::FastWriter().write(output) << endl; + cout << jsonCompactPrint(output) << endl; } void CommandLineInterface::handleAst(string const& _argStr) diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index e5be8404d..52f796a5c 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -125,6 +125,13 @@ Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) return gasEstimates; } +string jsonCompactPrint(Json::Value const& input) +{ + Json::FastWriter writer; + writer.omitEndingLineFeed(); + return writer.write(input); +} + string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback) { Json::Value output(Json::objectValue); @@ -213,7 +220,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback for (string const& contractName: compiler.contractNames()) { Json::Value contractData(Json::objectValue); - contractData["interface"] = compiler.interface(contractName); + contractData["interface"] = jsonCompactPrint(compiler.interface(contractName)); contractData["bytecode"] = compiler.object(contractName).toHex(); contractData["runtimeBytecode"] = compiler.runtimeObject(contractName).toHex(); contractData["opcodes"] = solidity::disassemble(compiler.object(contractName).bytecode); @@ -274,7 +281,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback try { - return Json::FastWriter().write(output); + return jsonCompactPrint(output); } catch (...) { @@ -292,7 +299,7 @@ string compileMulti(string const& _input, bool _optimize, CStyleReadFileCallback errors.append("Error parsing input JSON: " + reader.getFormattedErrorMessages()); Json::Value output(Json::objectValue); output["errors"] = errors; - return Json::FastWriter().write(output); + return jsonCompactPrint(output); } else { From 9205662de9416ab160db7327a6022ea8b1fba3e1 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 17:20:24 +0000 Subject: [PATCH 069/125] Update tests to use JSON --- test/libsolidity/SolidityABIJSON.cpp | 4 +--- test/libsolidity/SolidityNatspecJSON.cpp | 10 ++++------ 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 073d7d97f..0566f2538 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -40,9 +40,7 @@ public: void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString) { ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing contract failed"); - std::string generatedInterfaceString = m_compilerStack.metadata("", DocumentationType::ABIInterface); - Json::Value generatedInterface; - m_reader.parse(generatedInterfaceString, generatedInterface); + Json::Value generatedInterface = m_compilerStack.metadata("", DocumentationType::ABIInterface); Json::Value expectedInterface; m_reader.parse(_expectedInterfaceString, expectedInterface); BOOST_CHECK_MESSAGE( diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index 1f74e928f..facfcda7e 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -45,21 +45,19 @@ public: bool _userDocumentation ) { - std::string generatedDocumentationString; + Json::Value generatedDocumentation; ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing failed"); if (_userDocumentation) - generatedDocumentationString = m_compilerStack.metadata("", DocumentationType::NatspecUser); + generatedDocumentation = m_compilerStack.metadata("", DocumentationType::NatspecUser); else - generatedDocumentationString = m_compilerStack.metadata("", DocumentationType::NatspecDev); - Json::Value generatedDocumentation; - m_reader.parse(generatedDocumentationString, generatedDocumentation); + generatedDocumentation = m_compilerStack.metadata("", DocumentationType::NatspecDev); Json::Value expectedDocumentation; m_reader.parse(_expectedDocumentationString, expectedDocumentation); BOOST_CHECK_MESSAGE( expectedDocumentation == generatedDocumentation, "Expected " << _expectedDocumentationString << - "\n but got:\n" << generatedDocumentationString + "\n but got:\n" << Json::StyledWriter().write(generatedDocumentation) ); } From 81c50143f2bff6f589ab1237d68c8820107f18b9 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 17:33:28 +0000 Subject: [PATCH 070/125] Move JSON helpers to libdevcore/json --- libdevcore/JSON.h | 44 ++++++++++++++++++++++++ solc/CommandLineInterface.cpp | 25 ++++---------- solc/jsonCompiler.cpp | 14 +++----- test/libsolidity/SolidityNatspecJSON.cpp | 3 +- 4 files changed, 57 insertions(+), 29 deletions(-) create mode 100644 libdevcore/JSON.h diff --git a/libdevcore/JSON.h b/libdevcore/JSON.h new file mode 100644 index 000000000..7876dfb21 --- /dev/null +++ b/libdevcore/JSON.h @@ -0,0 +1,44 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** @file JSON.h + * @date 2016 + * + * JSON related helpers + */ + +#pragma once + +#include + +namespace dev +{ + +/// Serialise the JSON object (@a _input) with identation +std::string jsonPrettyPrint(Json::Value const& _input) +{ + return Json::StyledWriter().write(_input); +} + +/// Serialise theJ SON object (@a _input) without identation +std::string jsonCompactPrint(Json::Value const& _input) +{ + Json::FastWriter writer; + writer.omitEndingLineFeed(); + return writer.write(_input); +} + +} diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp index 5509a414e..7b23f886b 100644 --- a/solc/CommandLineInterface.cpp +++ b/solc/CommandLineInterface.cpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include @@ -107,18 +108,6 @@ static void version() exit(0); } -string jsonPrettyPrint(Json::Value const& input) -{ - return Json::StyledWriter().write(input); -} - -string jsonCompactPrint(Json::Value const& input) -{ - Json::FastWriter writer; - writer.omitEndingLineFeed(); - return writer.write(input); -} - static bool needsHumanTargetedStdout(po::variables_map const& _args) { if (_args.count(g_argGas)) @@ -244,9 +233,9 @@ void CommandLineInterface::handleMeta(DocumentationType _type, string const& _co { std::string output; if (_type == DocumentationType::ABIInterface) - output = jsonCompactPrint(m_compiler->metadata(_contract, _type)); + output = dev::jsonCompactPrint(m_compiler->metadata(_contract, _type)); else - output = jsonPrettyPrint(m_compiler->metadata(_contract, _type)); + output = dev::jsonPrettyPrint(m_compiler->metadata(_contract, _type)); if (m_args.count("output-dir")) createFile(_contract + suffix, output); @@ -669,7 +658,7 @@ void CommandLineInterface::handleCombinedJSON() { Json::Value contractData(Json::objectValue); if (requests.count("abi")) - contractData["abi"] = jsonCompactPrint(m_compiler->interface(contractName)); + contractData["abi"] = dev::jsonCompactPrint(m_compiler->interface(contractName)); if (requests.count("bin")) contractData["bin"] = m_compiler->object(contractName).toHex(); if (requests.count("bin-runtime")) @@ -694,9 +683,9 @@ void CommandLineInterface::handleCombinedJSON() contractData["srcmap-runtime"] = map ? *map : ""; } if (requests.count("devdoc")) - contractData["devdoc"] = jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecDev)); + contractData["devdoc"] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecDev)); if (requests.count("userdoc")) - contractData["userdoc"] = jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecUser)); + contractData["userdoc"] = dev::jsonCompactPrint(m_compiler->metadata(contractName, DocumentationType::NatspecUser)); output["contracts"][contractName] = contractData; } @@ -720,7 +709,7 @@ void CommandLineInterface::handleCombinedJSON() output["sources"][sourceCode.first]["AST"] = converter.json(); } } - cout << jsonCompactPrint(output) << endl; + cout << dev::jsonCompactPrint(output) << endl; } void CommandLineInterface::handleAst(string const& _argStr) diff --git a/solc/jsonCompiler.cpp b/solc/jsonCompiler.cpp index 52f796a5c..771f0df8c 100644 --- a/solc/jsonCompiler.cpp +++ b/solc/jsonCompiler.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -125,13 +126,6 @@ Json::Value estimateGas(CompilerStack const& _compiler, string const& _contract) return gasEstimates; } -string jsonCompactPrint(Json::Value const& input) -{ - Json::FastWriter writer; - writer.omitEndingLineFeed(); - return writer.write(input); -} - string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback _readCallback) { Json::Value output(Json::objectValue); @@ -220,7 +214,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback for (string const& contractName: compiler.contractNames()) { Json::Value contractData(Json::objectValue); - contractData["interface"] = jsonCompactPrint(compiler.interface(contractName)); + contractData["interface"] = dev::jsonCompactPrint(compiler.interface(contractName)); contractData["bytecode"] = compiler.object(contractName).toHex(); contractData["runtimeBytecode"] = compiler.runtimeObject(contractName).toHex(); contractData["opcodes"] = solidity::disassemble(compiler.object(contractName).bytecode); @@ -281,7 +275,7 @@ string compile(StringMap const& _sources, bool _optimize, CStyleReadFileCallback try { - return jsonCompactPrint(output); + return dev::jsonCompactPrint(output); } catch (...) { @@ -299,7 +293,7 @@ string compileMulti(string const& _input, bool _optimize, CStyleReadFileCallback errors.append("Error parsing input JSON: " + reader.getFormattedErrorMessages()); Json::Value output(Json::objectValue); output["errors"] = errors; - return jsonCompactPrint(output); + return dev::jsonCompactPrint(output); } else { diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index facfcda7e..f05542b16 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace dev { @@ -57,7 +58,7 @@ public: BOOST_CHECK_MESSAGE( expectedDocumentation == generatedDocumentation, "Expected " << _expectedDocumentationString << - "\n but got:\n" << Json::StyledWriter().write(generatedDocumentation) + "\n but got:\n" << dev::jsonPrettyPrint(generatedDocumentation) ); } From 227f6aab4f96003e0f7c99194a9ea1095041970f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 17:37:18 +0000 Subject: [PATCH 071/125] Change natspec/abi JSON expected message to be the same --- test/libsolidity/SolidityABIJSON.cpp | 4 +++- test/libsolidity/SolidityNatspecJSON.cpp | 7 +++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 0566f2538..0ad9e928e 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -40,12 +40,14 @@ public: void checkInterface(std::string const& _code, std::string const& _expectedInterfaceString) { ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing contract failed"); + Json::Value generatedInterface = m_compilerStack.metadata("", DocumentationType::ABIInterface); Json::Value expectedInterface; m_reader.parse(_expectedInterfaceString, expectedInterface); BOOST_CHECK_MESSAGE( expectedInterface == generatedInterface, - "Expected:\n" << expectedInterface.toStyledString() << "\n but got:\n" << generatedInterface.toStyledString() + "Expected:\n" << expectedInterface.toStyledString() << + "\n but got:\n" << generatedInterface.toStyledString() ); } diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp index f05542b16..49844f157 100644 --- a/test/libsolidity/SolidityNatspecJSON.cpp +++ b/test/libsolidity/SolidityNatspecJSON.cpp @@ -26,7 +26,6 @@ #include #include #include -#include namespace dev { @@ -46,9 +45,9 @@ public: bool _userDocumentation ) { - Json::Value generatedDocumentation; ETH_TEST_REQUIRE_NO_THROW(m_compilerStack.parse("pragma solidity >=0.0;\n" + _code), "Parsing failed"); + Json::Value generatedDocumentation; if (_userDocumentation) generatedDocumentation = m_compilerStack.metadata("", DocumentationType::NatspecUser); else @@ -57,8 +56,8 @@ public: m_reader.parse(_expectedDocumentationString, expectedDocumentation); BOOST_CHECK_MESSAGE( expectedDocumentation == generatedDocumentation, - "Expected " << _expectedDocumentationString << - "\n but got:\n" << dev::jsonPrettyPrint(generatedDocumentation) + "Expected " << expectedDocumentation.toStyledString() << + "\n but got:\n" << generatedDocumentation.toStyledString() ); } From cc8583ec7d6fd86ca7e129475fde32b76d102e79 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 27 Sep 2016 21:37:32 +0200 Subject: [PATCH 072/125] Function types. --- libsolidity/analysis/ReferencesResolver.cpp | 17 +++ libsolidity/analysis/ReferencesResolver.h | 1 + libsolidity/ast/AST.h | 35 +++++ libsolidity/ast/ASTForward.h | 1 + libsolidity/ast/ASTJsonConverter.cpp | 14 ++ libsolidity/ast/ASTJsonConverter.h | 2 + libsolidity/ast/ASTPrinter.cpp | 12 ++ libsolidity/ast/ASTPrinter.h | 2 + libsolidity/ast/ASTVisitor.h | 4 + libsolidity/ast/AST_accept.h | 20 +++ libsolidity/ast/Types.cpp | 65 ++++++++- libsolidity/ast/Types.h | 10 +- libsolidity/parsing/Parser.cpp | 127 ++++++++++-------- libsolidity/parsing/Parser.h | 14 ++ test/libsolidity/SolidityEndToEndTest.cpp | 18 +++ .../SolidityNameAndTypeResolution.cpp | 49 +++++++ test/libsolidity/SolidityParser.cpp | 51 +++++++ 17 files changed, 381 insertions(+), 61 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index a7b9e8b8d..41cad922d 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -83,6 +83,23 @@ void ReferencesResolver::endVisit(UserDefinedTypeName const& _typeName) fatalTypeError(_typeName.location(), "Name has to refer to a struct, enum or contract."); } +void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) +{ + switch (_typeName.visibility()) + { + case VariableDeclaration::Visibility::Default: + case VariableDeclaration::Visibility::Internal: + case VariableDeclaration::Visibility::External: + break; + default: + typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); + } + + // Do we allow storage references for external functions? + + _typeName.annotation().type = make_shared(_typeName); +} + void ReferencesResolver::endVisit(Mapping const& _typeName) { TypePointer keyType = _typeName.keyType().annotation().type; diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h index 1986b2bb2..bfaef2e1f 100644 --- a/libsolidity/analysis/ReferencesResolver.h +++ b/libsolidity/analysis/ReferencesResolver.h @@ -62,6 +62,7 @@ private: virtual bool visit(Identifier const& _identifier) override; virtual bool visit(ElementaryTypeName const& _typeName) override; virtual void endVisit(UserDefinedTypeName const& _typeName) override; + virtual void endVisit(FunctionTypeName const& _typeName) override; virtual void endVisit(Mapping const& _typeName) override; virtual void endVisit(ArrayTypeName const& _typeName) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h index 1b42c499f..a2b70fe9c 100644 --- a/libsolidity/ast/AST.h +++ b/libsolidity/ast/AST.h @@ -822,6 +822,41 @@ private: std::vector m_namePath; }; +/** + * A literal function type. Its source form is "function (paramType1, paramType2) internal / external returns (retType1, retType2)" + */ +class FunctionTypeName: public TypeName +{ +public: + FunctionTypeName( + SourceLocation const& _location, + ASTPointer const& _parameterTypes, + ASTPointer const& _returnTypes, + Declaration::Visibility _visibility, + bool _isDeclaredConst, + bool _isPayable + ): + TypeName(_location), m_parameterTypes(_parameterTypes), m_returnTypes(_returnTypes), + m_visibility(_visibility), m_isDeclaredConst(_isDeclaredConst), m_isPayable(_isPayable) + {} + virtual void accept(ASTVisitor& _visitor) override; + virtual void accept(ASTConstVisitor& _visitor) const override; + + std::vector> const& parameterTypes() const { return m_parameterTypes->parameters(); } + std::vector> const& returnParameterTypes() const { return m_returnTypes->parameters(); } + + Declaration::Visibility visibility() const { return m_visibility; } + bool isDeclaredConst() const { return m_isDeclaredConst; } + bool isPayable() const { return m_isPayable; } + +private: + ASTPointer m_parameterTypes; + ASTPointer m_returnTypes; + Declaration::Visibility m_visibility; + bool m_isDeclaredConst; + bool m_isPayable; +}; + /** * A mapping type. Its source form is "mapping('keyType' => 'valueType')" */ diff --git a/libsolidity/ast/ASTForward.h b/libsolidity/ast/ASTForward.h index 59fc1b570..52bbf3968 100644 --- a/libsolidity/ast/ASTForward.h +++ b/libsolidity/ast/ASTForward.h @@ -54,6 +54,7 @@ class MagicVariableDeclaration; class TypeName; class ElementaryTypeName; class UserDefinedTypeName; +class FunctionTypeName; class Mapping; class ArrayTypeName; class Statement; diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 3fce11804..717a80ee1 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -226,6 +226,15 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) return true; } +bool ASTJsonConverter::visit(FunctionTypeName const& _node) +{ + addJsonNode(_node, "FunctionTypeName", { + make_pair("payable", _node.isPayable()), + make_pair("constant", _node.isDeclaredConst()) + }); + return true; +} + bool ASTJsonConverter::visit(Mapping const& _node) { addJsonNode(_node, "Mapping", {}, true); @@ -507,6 +516,11 @@ void ASTJsonConverter::endVisit(UserDefinedTypeName const&) { } +void ASTJsonConverter::endVisit(FunctionTypeName const&) +{ + goUp(); +} + void ASTJsonConverter::endVisit(Mapping const&) { goUp(); diff --git a/libsolidity/ast/ASTJsonConverter.h b/libsolidity/ast/ASTJsonConverter.h index 7c7b37f8c..0a71779c9 100644 --- a/libsolidity/ast/ASTJsonConverter.h +++ b/libsolidity/ast/ASTJsonConverter.h @@ -69,6 +69,7 @@ public: bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; + bool visit(FunctionTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(InlineAssembly const& _node) override; @@ -114,6 +115,7 @@ public: void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; + void endVisit(FunctionTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; void endVisit(InlineAssembly const&) override; diff --git a/libsolidity/ast/ASTPrinter.cpp b/libsolidity/ast/ASTPrinter.cpp index 272669688..053b9b822 100644 --- a/libsolidity/ast/ASTPrinter.cpp +++ b/libsolidity/ast/ASTPrinter.cpp @@ -164,6 +164,13 @@ bool ASTPrinter::visit(UserDefinedTypeName const& _node) return goDeeper(); } +bool ASTPrinter::visit(FunctionTypeName const& _node) +{ + writeLine("FunctionTypeName"); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(Mapping const& _node) { writeLine("Mapping"); @@ -442,6 +449,11 @@ void ASTPrinter::endVisit(UserDefinedTypeName const&) m_indentation--; } +void ASTPrinter::endVisit(FunctionTypeName const&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(Mapping const&) { m_indentation--; diff --git a/libsolidity/ast/ASTPrinter.h b/libsolidity/ast/ASTPrinter.h index f0ab10982..9f88a1fdc 100644 --- a/libsolidity/ast/ASTPrinter.h +++ b/libsolidity/ast/ASTPrinter.h @@ -63,6 +63,7 @@ public: bool visit(TypeName const& _node) override; bool visit(ElementaryTypeName const& _node) override; bool visit(UserDefinedTypeName const& _node) override; + bool visit(FunctionTypeName const& _node) override; bool visit(Mapping const& _node) override; bool visit(ArrayTypeName const& _node) override; bool visit(InlineAssembly const& _node) override; @@ -106,6 +107,7 @@ public: void endVisit(TypeName const&) override; void endVisit(ElementaryTypeName const&) override; void endVisit(UserDefinedTypeName const&) override; + void endVisit(FunctionTypeName const&) override; void endVisit(Mapping const&) override; void endVisit(ArrayTypeName const&) override; void endVisit(InlineAssembly const&) override; diff --git a/libsolidity/ast/ASTVisitor.h b/libsolidity/ast/ASTVisitor.h index 3a1b55d33..e72afe697 100644 --- a/libsolidity/ast/ASTVisitor.h +++ b/libsolidity/ast/ASTVisitor.h @@ -61,6 +61,7 @@ public: virtual bool visit(TypeName& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName& _node) { return visitNode(_node); } + virtual bool visit(FunctionTypeName& _node) { return visitNode(_node); } virtual bool visit(Mapping& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName& _node) { return visitNode(_node); } virtual bool visit(InlineAssembly& _node) { return visitNode(_node); } @@ -106,6 +107,7 @@ public: virtual void endVisit(TypeName& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionTypeName& _node) { endVisitNode(_node); } virtual void endVisit(Mapping& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName& _node) { endVisitNode(_node); } virtual void endVisit(InlineAssembly& _node) { endVisitNode(_node); } @@ -163,6 +165,7 @@ public: virtual bool visit(TypeName const& _node) { return visitNode(_node); } virtual bool visit(ElementaryTypeName const& _node) { return visitNode(_node); } virtual bool visit(UserDefinedTypeName const& _node) { return visitNode(_node); } + virtual bool visit(FunctionTypeName const& _node) { return visitNode(_node); } virtual bool visit(Mapping const& _node) { return visitNode(_node); } virtual bool visit(ArrayTypeName const& _node) { return visitNode(_node); } virtual bool visit(Block const& _node) { return visitNode(_node); } @@ -208,6 +211,7 @@ public: virtual void endVisit(TypeName const& _node) { endVisitNode(_node); } virtual void endVisit(ElementaryTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(UserDefinedTypeName const& _node) { endVisitNode(_node); } + virtual void endVisit(FunctionTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(Mapping const& _node) { endVisitNode(_node); } virtual void endVisit(ArrayTypeName const& _node) { endVisitNode(_node); } virtual void endVisit(Block const& _node) { endVisitNode(_node); } diff --git a/libsolidity/ast/AST_accept.h b/libsolidity/ast/AST_accept.h index b5a3806b7..f521e0929 100644 --- a/libsolidity/ast/AST_accept.h +++ b/libsolidity/ast/AST_accept.h @@ -327,6 +327,26 @@ void UserDefinedTypeName::accept(ASTConstVisitor& _visitor) const _visitor.endVisit(*this); } +void FunctionTypeName::accept(ASTVisitor& _visitor) +{ + if (_visitor.visit(*this)) + { + m_parameterTypes->accept(_visitor); + m_returnTypes->accept(_visitor); + } + _visitor.endVisit(*this); +} + +void FunctionTypeName::accept(ASTConstVisitor& _visitor) const +{ + if (_visitor.visit(*this)) + { + m_parameterTypes->accept(_visitor); + m_returnTypes->accept(_visitor); + } + _visitor.endVisit(*this); +} + void Mapping::accept(ASTVisitor& _visitor) { if (_visitor.visit(*this)) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 6ad74d289..808b0c55e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1805,6 +1805,23 @@ FunctionType::FunctionType(EventDefinition const& _event): swap(paramNames, m_parameterNames); } +FunctionType::FunctionType(FunctionTypeName const& _typeName): + m_location(_typeName.visibility() == VariableDeclaration::Visibility::External ? Location::External : Location::Internal), + m_isConstant(_typeName.isDeclaredConst()), + m_isPayable(_typeName.isPayable()) +{ + for (auto const& t: _typeName.parameterTypes()) + { + solAssert(t->annotation().type, "Type not set for parameter."); + m_parameterTypes.push_back(t->annotation().type); + } + for (auto const& t: _typeName.returnParameterTypes()) + { + solAssert(t->annotation().type, "Type not set for return parameter."); + m_returnParameterTypes.push_back(t->annotation().type); + } +} + FunctionTypePointer FunctionType::newExpressionType(ContractDefinition const& _contract) { FunctionDefinition const* constructor = _contract.constructor(); @@ -1885,17 +1902,47 @@ string FunctionType::toString(bool _short) const string name = "function ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); - name += ") returns ("; + name += ") "; + if (m_isConstant) + name += "constant "; + if (m_isPayable) + name += "payable "; + if (m_location == Location::External) + name += "external "; + name += "returns ("; for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); return name + ")"; } +unsigned FunctionType::calldataEncodedSize(bool _padded) const +{ + unsigned size = storageBytes(); + if (_padded) + size = ((size + 31) / 32) * 32; + return size; +} + u256 FunctionType::storageSize() const { - BOOST_THROW_EXCEPTION( - InternalCompilerError() - << errinfo_comment("Storage size of non-storable function type requested.")); + if (m_location == Location::External || m_location == Location::Internal) + return 1; + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + +unsigned FunctionType::storageBytes() const +{ + if (m_location == Location::External) + return 20 + 4; + else if (m_location == Location::Internal) + return 8; // it should really not be possible to create larger programs + else + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); } unsigned FunctionType::sizeOnStack() const @@ -2018,6 +2065,16 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con } } +TypePointer FunctionType::interfaceType(bool _inLibrary) const +{ + if (m_location != Location::External && m_location != Location::Internal) + return TypePointer(); + if (_inLibrary) + return shared_from_this(); + else + return make_shared(storageBytes()); +} + bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const { solAssert(!bound() || _selfType, ""); diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 082e16a6a..358c7efcb 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -821,6 +821,8 @@ public: explicit FunctionType(VariableDeclaration const& _varDecl); /// Creates the function type of an event. explicit FunctionType(EventDefinition const& _event); + /// Creates the type of a function type name. + explicit FunctionType(FunctionTypeName const& _typeName); /// Function type constructor to be used for a plain type (not derived from a declaration). FunctionType( strings const& _parameterTypes, @@ -891,11 +893,15 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString(bool _short) const override; - virtual bool canBeStored() const override { return false; } + virtual unsigned calldataEncodedSize(bool _padded) const override; + virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } virtual u256 storageSize() const override; - virtual bool canLiveOutsideStorage() const override { return false; } + virtual unsigned storageBytes() const override; + virtual bool isValueType() const override { return true; } + virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + virtual TypePointer interfaceType(bool _inLibrary) const override; /// @returns TypePointer of a new FunctionType object. All input/return parameters are an /// appropriate external types (i.e. the interfaceType()s) of input/return parameters of diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index df3ed7b23..421e358f6 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -288,6 +288,52 @@ Declaration::Visibility Parser::parseVisibilitySpecifier(Token::Value _token) return visibility; } +Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers) +{ + FunctionHeaderParserResult result; + expectToken(Token::Function); + if (_forceEmptyName || m_scanner->currentToken() == Token::LParen) + result.name = make_shared(); // anonymous function + else + result.name = expectIdentifierToken(); + VarDeclParserOptions options; + options.allowLocationSpecifier = true; + result.parameters = parseParameterList(options); + while (true) + { + Token::Value token = m_scanner->currentToken(); + if (token == Token::Const) + { + result.isDeclaredConst = true; + m_scanner->next(); + } + else if (m_scanner->currentToken() == Token::Payable) + { + result.isPayable = true; + m_scanner->next(); + } + else if (_allowModifiers && token == Token::Identifier) + result.modifiers.push_back(parseModifierInvocation()); + else if (Token::isVisibilitySpecifier(token)) + { + if (result.visibility != Declaration::Visibility::Default) + fatalParserError(string("Multiple visibility specifiers.")); + result.visibility = parseVisibilitySpecifier(token); + } + else + break; + } + if (m_scanner->currentToken() == Token::Returns) + { + bool const permitEmptyParameterList = false; + m_scanner->next(); + result.returnParameters = parseParameterList(options, permitEmptyParameterList); + } + else + result.returnParameters = createEmptyParameterList(); + return result; +} + ASTPointer Parser::parseFunctionDefinition(ASTString const* _contractName) { ASTNodeFactory nodeFactory(*this); @@ -295,52 +341,8 @@ ASTPointer Parser::parseFunctionDefinition(ASTString const* if (m_scanner->currentCommentLiteral() != "") docstring = make_shared(m_scanner->currentCommentLiteral()); - expectToken(Token::Function); - ASTPointer name; - if (m_scanner->currentToken() == Token::LParen) - name = make_shared(); // anonymous function - else - name = expectIdentifierToken(); - VarDeclParserOptions options; - options.allowLocationSpecifier = true; - ASTPointer parameters(parseParameterList(options)); - bool isDeclaredConst = false; - bool isPayable = false; - Declaration::Visibility visibility(Declaration::Visibility::Default); - vector> modifiers; - while (true) - { - Token::Value token = m_scanner->currentToken(); - if (token == Token::Const) - { - isDeclaredConst = true; - m_scanner->next(); - } - else if (m_scanner->currentToken() == Token::Payable) - { - isPayable = true; - m_scanner->next(); - } - else if (token == Token::Identifier) - modifiers.push_back(parseModifierInvocation()); - else if (Token::isVisibilitySpecifier(token)) - { - if (visibility != Declaration::Visibility::Default) - fatalParserError(string("Multiple visibility specifiers.")); - visibility = parseVisibilitySpecifier(token); - } - else - break; - } - ASTPointer returnParameters; - if (m_scanner->currentToken() == Token::Returns) - { - bool const permitEmptyParameterList = false; - m_scanner->next(); - returnParameters = parseParameterList(options, permitEmptyParameterList); - } - else - returnParameters = createEmptyParameterList(); + FunctionHeaderParserResult header = parseFunctionHeader(false, true); + ASTPointer block = ASTPointer(); nodeFactory.markEndPosition(); if (m_scanner->currentToken() != Token::Semicolon) @@ -350,17 +352,17 @@ ASTPointer Parser::parseFunctionDefinition(ASTString const* } else m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *name == *_contractName); + bool const c_isConstructor = (_contractName && *header.name == *_contractName); return nodeFactory.createNode( - name, - visibility, + header.name, + header.visibility, c_isConstructor, docstring, - parameters, - isDeclaredConst, - modifiers, - returnParameters, - isPayable, + header.parameters, + header.isDeclaredConst, + header.modifiers, + header.returnParameters, + header.isPayable, block ); } @@ -631,6 +633,8 @@ ASTPointer Parser::parseTypeName(bool _allowVar) fatalParserError(string("Expected explicit type name.")); m_scanner->next(); } + else if (token == Token::Function) + type = parseFunctionType(); else if (token == Token::Mapping) type = parseMapping(); else if (token == Token::Identifier) @@ -653,6 +657,19 @@ ASTPointer Parser::parseTypeName(bool _allowVar) return type; } +ASTPointer Parser::parseFunctionType() +{ + ASTNodeFactory nodeFactory(*this); + FunctionHeaderParserResult header = parseFunctionHeader(true, false); + return nodeFactory.createNode( + header.parameters, + header.returnParameters, + header.visibility, + header.isDeclaredConst, + header.isPayable + ); +} + ASTPointer Parser::parseMapping() { ASTNodeFactory nodeFactory(*this); @@ -1278,7 +1295,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const Token::Value token(m_scanner->currentToken()); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); - if (token == Token::Mapping || token == Token::Var) + if (token == Token::Mapping || token == Token::Function || token == Token::Var) return LookAheadInfo::VariableDeclarationStatement; if (mightBeTypeName) { diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index 26f347cb5..a59d26883 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -53,6 +53,18 @@ private: bool allowLocationSpecifier = false; }; + /// This struct is shared for parsing a function header and a function type. + struct FunctionHeaderParserResult + { + ASTPointer name; + ASTPointer parameters; + ASTPointer returnParameters; + Declaration::Visibility visibility = Declaration::Visibility::Default; + bool isDeclaredConst = false; + bool isPayable = false; + std::vector> modifiers; + }; + ///@{ ///@name Parsing functions for the AST nodes ASTPointer parsePragmaDirective(); @@ -60,6 +72,7 @@ private: ASTPointer parseContractDefinition(bool _isLibrary); ASTPointer parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); + FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); ASTPointer parseFunctionDefinition(ASTString const* _contractName); ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); @@ -75,6 +88,7 @@ private: ASTPointer parseIdentifier(); ASTPointer parseUserDefinedTypeName(); ASTPointer parseTypeName(bool _allowVar); + ASTPointer parseFunctionType(); ASTPointer parseMapping(); ASTPointer parseParameterList( VarDeclParserOptions const& _options, diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index d89242500..c90976637 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7606,6 +7606,24 @@ BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) BOOST_CHECK(callContractFunction("f(address)", cAddrOpt) == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(pass_function_types_internally) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + return eval(g, x); + } + function eval(function(uint) returns (uint) x, uint a) returns (uint) { + return x(a); + } + function g(uint x) returns (uint) { return x + 1; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); +} + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index f498a0b9a..7f12cd867 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4132,6 +4132,55 @@ BOOST_AUTO_TEST_CASE(using_directive_for_missing_selftype) BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) returns (uint) x; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_parameter) +{ + char const* text = R"( + contract C { + function f(function(uint) returns (uint) g) returns (function(uint) returns (uint)) { + return g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(private_function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) private returns (uint) x; + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(public_function_type) +{ + char const* text = R"( + contract C { + function f() { + function(uint) public returns (uint) x; + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index ec23d5fd5..69b8d0f0e 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1241,6 +1241,57 @@ BOOST_AUTO_TEST_CASE(payable_accessor) BOOST_CHECK(!successParse(text)); } +BOOST_AUTO_TEST_CASE(function_type_in_expression) +{ + char const* text = R"( + contract test { + function f(uint x, uint y) returns (uint a) {} + function g() { + function (uint, uint) internal returns (uint) f1 = f; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable) +{ + // TODO disambiguate from fallback function + char const* text = R"( + contract test { + function f(uint x, uint y) returns (uint a) {} + function (uint, uint) internal returns (uint) f1 = f; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_in_struct) +{ + char const* text = R"( + contract test { + struct S { + function (uint x, uint y) internal returns (uint a) f; + function (uint, uint) external returns (uint) g; + uint d; + } + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_parameter) +{ + char const* text = R"( + contract test { + function f(function(uint) external returns (uint) g) internal returns (uint a) { + return g(1); + } + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() } From dd173f83e3a6a9046d1aa7e64cb171598a73b272 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 28 Sep 2016 19:22:23 +0200 Subject: [PATCH 073/125] Code generator for function types. --- libsolidity/ast/Types.cpp | 12 ++++- libsolidity/ast/Types.h | 1 + libsolidity/codegen/CompilerUtils.cpp | 34 ++++++++++++- libsolidity/codegen/ContractCompiler.cpp | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 49 ++++++++++++++++++- .../SolidityNameAndTypeResolution.cpp | 33 +++++++++++++ 6 files changed, 127 insertions(+), 3 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 808b0c55e..19a1b9d1f 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2065,6 +2065,16 @@ MemberList::MemberMap FunctionType::nativeMembers(ContractDefinition const*) con } } +TypePointer FunctionType::encodingType() const +{ + // Only external functions can be encoded, internal functions cannot leave code boundaries. + if (m_location == Location::External) + // This looks like bytes24, but bytes24 is stored differently on the stack. + return shared_from_this(); + else + return TypePointer(); +} + TypePointer FunctionType::interfaceType(bool _inLibrary) const { if (m_location != Location::External && m_location != Location::Internal) @@ -2072,7 +2082,7 @@ TypePointer FunctionType::interfaceType(bool _inLibrary) const if (_inLibrary) return shared_from_this(); else - return make_shared(storageBytes()); + return make_shared(8 * storageBytes()); } bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 358c7efcb..691ddf297 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -901,6 +901,7 @@ public: virtual bool canLiveOutsideStorage() const override { return m_location == Location::Internal || m_location == Location::External; } virtual unsigned sizeOnStack() const override; virtual MemberList::MemberMap nativeMembers(ContractDefinition const* _currentScope) const override; + virtual TypePointer encodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; /// @returns TypePointer of a new FunctionType object. All input/return parameters are an diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 58d1caa90..38a7a23bd 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -133,6 +133,17 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound m_context << u256(str->value().size()); m_context << Instruction::ADD; } + else if ( + _type.category() == Type::Category::Function && + dynamic_cast(_type).location() == FunctionType::Location::External + ) + { + solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); + m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; + m_context << Instruction::DUP2 << Instruction::MSTORE; + m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; + } else { unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); @@ -206,7 +217,8 @@ void CompilerUtils::encodeToMemory( else if ( _givenTypes[i]->dataStoredIn(DataLocation::Storage) || _givenTypes[i]->dataStoredIn(DataLocation::CallData) || - _givenTypes[i]->category() == Type::Category::StringLiteral + _givenTypes[i]->category() == Type::Category::StringLiteral || + _givenTypes[i]->category() == Type::Category::Function ) type = _givenTypes[i]; // delay conversion else @@ -678,6 +690,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp void CompilerUtils::pushZeroValue(Type const& _type) { + if (auto const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + m_context << m_context.errorTag(); + return; + } + } auto const* referenceType = dynamic_cast(&_type); if (!referenceType || referenceType->location() == DataLocation::Storage) { @@ -839,6 +859,18 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda } } + if (auto const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::External) + { + // We have to split the right-aligned
into two stack slots: + // address (right aligned), function identifier (right aligned) + m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; + m_context << u256(0xffffffffUL) << Instruction::AND; + } + } + return numBytes; } diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 2aec3055f..9cd893e8f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -293,6 +293,7 @@ void ContractCompiler::appendCalldataUnpacker(TypePointers const& _typeParameter { // stack: v1 v2 ... v(k-1) base_offset current_offset TypePointer type = parameterType->decodingType(); + solAssert(type, "No decoding type found."); if (type->category() == Type::Category::Array) { auto const& arrayType = dynamic_cast(*type); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c90976637..76df19705 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7606,6 +7606,29 @@ BOOST_AUTO_TEST_CASE(mem_resize_is_not_paid_at_call) BOOST_CHECK(callContractFunction("f(address)", cAddrOpt) == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(calling_uninitialized_function) +{ + char const* sourceCode = R"( + contract C { + function intern() returns (uint) { + function (uint) internal returns (uint) x; + x(); + return 7; + } + function extern() returns (uint) { + function (uint) external returns (uint) x; + x(); + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + // This should throw exceptions + BOOST_CHECK(callContractFunction("intern()") == encodeArgs()); + BOOST_CHECK(callContractFunction("extern()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(pass_function_types_internally) { char const* sourceCode = R"( @@ -7613,7 +7636,7 @@ BOOST_AUTO_TEST_CASE(pass_function_types_internally) function f(uint x) returns (uint) { return eval(g, x); } - function eval(function(uint) returns (uint) x, uint a) returns (uint) { + function eval(function(uint) returns (uint) x, uint a) internal returns (uint) { return x(a); } function g(uint x) returns (uint) { return x + 1; } @@ -7624,6 +7647,30 @@ BOOST_AUTO_TEST_CASE(pass_function_types_internally) BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); } +BOOST_AUTO_TEST_CASE(pass_function_types_externally) +{ + char const* sourceCode = R"( + contract C { + function f(uint x) returns (uint) { + return this.eval(this.g, x); + } + function f2(uint x) returns (uint) { + return eval(this.g, x); + } + function eval(function(uint) external returns (uint) x, uint a) returns (uint) { + return x(a); + } + function g(uint x) returns (uint) { return x + 1; } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256)", 7) == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); +} + +// TODO: storage, arrays + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 7f12cd867..2d469cdc6 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4180,6 +4180,39 @@ BOOST_AUTO_TEST_CASE(public_function_type) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) +{ + // It should not be possible to give internal functions + // as parameters to external functions. + char const* text = R"( + contract C { + function f(function(uint) returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal) +{ + char const* text = R"( + library L { + function f(function(uint) returns (uint) x) internal { + } + } + )"; + BOOST_CHECK(success(text)); +} +BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external) +{ + char const* text = R"( + library L { + function f(function(uint) returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { From 97a3588701edafe9112f35272b5d4c6e23e574b9 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 10 Oct 2016 23:06:44 +0200 Subject: [PATCH 074/125] Function type state variables. --- libsolidity/parsing/Parser.cpp | 97 +++++++++++++++-------- libsolidity/parsing/Parser.h | 2 + test/libsolidity/SolidityEndToEndTest.cpp | 28 ++++++- test/libsolidity/SolidityParser.cpp | 21 ++++- 4 files changed, 115 insertions(+), 33 deletions(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index 421e358f6..e844861b1 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -217,7 +217,9 @@ ASTPointer Parser::parseContractDefinition(bool _isLibrary) if (currentTokenValue == Token::RBrace) break; else if (currentTokenValue == Token::Function) - subNodes.push_back(parseFunctionDefinition(name.get())); + // This can be a function or a state variable of function type (especially + // complicated to distinguish fallback function from function type state variable) + subNodes.push_back(parseFunctionDefinitionOrFunctionTypeStateVariable(name.get())); else if (currentTokenValue == Token::Struct) subNodes.push_back(parseStructDefinition()); else if (currentTokenValue == Token::Enum) @@ -334,7 +336,7 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN return result; } -ASTPointer Parser::parseFunctionDefinition(ASTString const* _contractName) +ASTPointer Parser::parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName) { ASTNodeFactory nodeFactory(*this); ASTPointer docstring; @@ -343,28 +345,55 @@ ASTPointer Parser::parseFunctionDefinition(ASTString const* FunctionHeaderParserResult header = parseFunctionHeader(false, true); - ASTPointer block = ASTPointer(); - nodeFactory.markEndPosition(); - if (m_scanner->currentToken() != Token::Semicolon) + if ( + !header.modifiers.empty() || + !header.name->empty() || + m_scanner->currentToken() == Token::Semicolon || + m_scanner->currentToken() == Token::LBrace + ) { - block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); + // this has to be a function + ASTPointer block = ASTPointer(); + nodeFactory.markEndPosition(); + if (m_scanner->currentToken() != Token::Semicolon) + { + block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + } + else + m_scanner->next(); // just consume the ';' + bool const c_isConstructor = (_contractName && *header.name == *_contractName); + return nodeFactory.createNode( + header.name, + header.visibility, + c_isConstructor, + docstring, + header.parameters, + header.isDeclaredConst, + header.modifiers, + header.returnParameters, + header.isPayable, + block + ); } else - m_scanner->next(); // just consume the ';' - bool const c_isConstructor = (_contractName && *header.name == *_contractName); - return nodeFactory.createNode( - header.name, - header.visibility, - c_isConstructor, - docstring, - header.parameters, - header.isDeclaredConst, - header.modifiers, - header.returnParameters, - header.isPayable, - block - ); + { + // this has to be a state variable + ASTPointer type = nodeFactory.createNode( + header.parameters, + header.returnParameters, + header.visibility, + header.isDeclaredConst, + header.isPayable + ); + type = parseTypeNameSuffix(type, nodeFactory); + VarDeclParserOptions options; + options.isStateVariable = true; + options.allowInitialValue = true; + auto node = parseVariableDeclaration(options, type); + expectToken(Token::Semicolon); + return node; + } } ASTPointer Parser::parseStructDefinition() @@ -613,6 +642,21 @@ ASTPointer Parser::parseUserDefinedTypeName() return nodeFactory.createNode(identifierPath); } +ASTPointer Parser::parseTypeNameSuffix(ASTPointer type, ASTNodeFactory& nodeFactory) +{ + while (m_scanner->currentToken() == Token::LBrack) + { + m_scanner->next(); + ASTPointer length; + if (m_scanner->currentToken() != Token::RBrack) + length = parseExpression(); + nodeFactory.markEndPosition(); + expectToken(Token::RBrack); + type = nodeFactory.createNode(type, length); + } + return type; +} + ASTPointer Parser::parseTypeName(bool _allowVar) { ASTNodeFactory nodeFactory(*this); @@ -644,16 +688,7 @@ ASTPointer Parser::parseTypeName(bool _allowVar) if (type) // Parse "[...]" postfixes for arrays. - while (m_scanner->currentToken() == Token::LBrack) - { - m_scanner->next(); - ASTPointer length; - if (m_scanner->currentToken() != Token::RBrack) - length = parseExpression(); - nodeFactory.markEndPosition(); - expectToken(Token::RBrack); - type = nodeFactory.createNode(type, length); - } + type = parseTypeNameSuffix(type, nodeFactory); return type; } diff --git a/libsolidity/parsing/Parser.h b/libsolidity/parsing/Parser.h index a59d26883..9295a7fa4 100644 --- a/libsolidity/parsing/Parser.h +++ b/libsolidity/parsing/Parser.h @@ -73,6 +73,7 @@ private: ASTPointer parseInheritanceSpecifier(); Declaration::Visibility parseVisibilitySpecifier(Token::Value _token); FunctionHeaderParserResult parseFunctionHeader(bool _forceEmptyName, bool _allowModifiers); + ASTPointer parseFunctionDefinitionOrFunctionTypeStateVariable(ASTString const* _contractName); ASTPointer parseFunctionDefinition(ASTString const* _contractName); ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); @@ -87,6 +88,7 @@ private: ASTPointer parseModifierInvocation(); ASTPointer parseIdentifier(); ASTPointer parseUserDefinedTypeName(); + ASTPointer parseTypeNameSuffix(ASTPointer type, ASTNodeFactory& nodeFactory); ASTPointer parseTypeName(bool _allowVar); ASTPointer parseFunctionType(); ASTPointer parseMapping(); diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 76df19705..0f392cab7 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7669,7 +7669,33 @@ BOOST_AUTO_TEST_CASE(pass_function_types_externally) BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); } -// TODO: storage, arrays +BOOST_AUTO_TEST_CASE(store_function) +{ + char const* sourceCode = R"( + contract Other { + function addTwo(uint x) returns (uint) { return x + 2; } + } + contract C { + function (unction (uint) external returns (uint)) returns (uint) ev = eval; + function (uint) external returns (uint) x; + function store(function(uint) external returns (uint) y) { + x = y; + } + function eval(function(uint) external returns (uint) y) returns (uint) { + return y(7); + } + function t() returns (uint) { + this.store((new Other()).addTwo); + return ev(x); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); +} + +// TODO: public function state variables, arrays BOOST_AUTO_TEST_CASE(shift_constant_left) { diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 69b8d0f0e..496f47031 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1256,7 +1256,26 @@ BOOST_AUTO_TEST_CASE(function_type_in_expression) BOOST_AUTO_TEST_CASE(function_type_as_storage_variable) { - // TODO disambiguate from fallback function + char const* text = R"( + contract test { + function (uint, uint) internal returns (uint) f1; + } + )"; + BOOST_CHECK(successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_modifiers) +{ + char const* text = R"( + contract test { + function (uint, uint) modifier1() returns (uint) f1; + } + )"; + BOOST_CHECK(!successParse(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_as_storage_variable_with_assignment) +{ char const* text = R"( contract test { function f(uint x, uint y) returns (uint a) {} From 6f19559de02e0bf2b53e743678d53a4ea0414eae Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 13 Oct 2016 17:51:46 +0200 Subject: [PATCH 075/125] Fix some type checks and tests for internal / external function parameters. --- libsolidity/ast/Types.cpp | 6 ++-- test/libsolidity/SolidityEndToEndTest.cpp | 8 ++--- .../SolidityNameAndTypeResolution.cpp | 32 ++++++++++++++++--- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 19a1b9d1f..3afbee138 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2075,12 +2075,12 @@ TypePointer FunctionType::encodingType() const return TypePointer(); } -TypePointer FunctionType::interfaceType(bool _inLibrary) const +TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_location != Location::External && m_location != Location::Internal) return TypePointer(); - if (_inLibrary) - return shared_from_this(); + if (m_location != Location::External) + return TypePointer(); else return make_shared(8 * storageBytes()); } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 0f392cab7..709e63b2b 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7612,12 +7612,12 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function) contract C { function intern() returns (uint) { function (uint) internal returns (uint) x; - x(); + x(2); return 7; } function extern() returns (uint) { function (uint) external returns (uint) x; - x(); + x(2); return 7; } } @@ -7676,7 +7676,7 @@ BOOST_AUTO_TEST_CASE(store_function) function addTwo(uint x) returns (uint) { return x + 2; } } contract C { - function (unction (uint) external returns (uint)) returns (uint) ev = eval; + function (function (uint) external returns (uint)) returns (uint) ev = eval; function (uint) external returns (uint) x; function store(function(uint) external returns (uint) y) { x = y; @@ -7695,7 +7695,7 @@ BOOST_AUTO_TEST_CASE(store_function) BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); } -// TODO: public function state variables, arrays +// TODO: arrays, libraries BOOST_AUTO_TEST_CASE(shift_constant_left) { diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 2d469cdc6..8b8c3aeb0 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4148,7 +4148,19 @@ BOOST_AUTO_TEST_CASE(function_type_parameter) { char const* text = R"( contract C { - function f(function(uint) returns (uint) g) returns (function(uint) returns (uint)) { + function f(function(uint) external returns (uint) g) returns (function(uint) external returns (uint)) { + return g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(function_type_returned) +{ + char const* text = R"( + contract C { + function f() returns (function(uint) external returns (uint) g) { return g; } } @@ -4186,7 +4198,19 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) // as parameters to external functions. char const* text = R"( contract C { - function f(function(uint) returns (uint) x) { + function f(function(uint) internal returns (uint) x) { + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(internal_function_returned_from_public_function) +{ + // It should not be possible to return internal functions from external functions. + char const* text = R"( + contract C { + function f() returns (function(uint) internal returns (uint) x) { } } )"; @@ -4197,7 +4221,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal { char const* text = R"( library L { - function f(function(uint) returns (uint) x) internal { + function f(function(uint) internal returns (uint) x) internal { } } )"; @@ -4207,7 +4231,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external { char const* text = R"( library L { - function f(function(uint) returns (uint) x) { + function f(function(uint) internal returns (uint) x) { } } )"; From 95d7555e3c0e8fc4826114a336e0e717fe7a1a2d Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 14 Oct 2016 12:27:46 +0200 Subject: [PATCH 076/125] External functions in storage. --- libsolidity/codegen/CompilerUtils.cpp | 28 ++++++++++++-------- libsolidity/codegen/CompilerUtils.h | 7 +++++ libsolidity/codegen/LValue.cpp | 32 ++++++++++++++++++----- test/libsolidity/SolidityEndToEndTest.cpp | 28 +++++++++++++++++++- 4 files changed, 76 insertions(+), 19 deletions(-) diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 38a7a23bd..8a06268c1 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -139,8 +139,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound ) { solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); - m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; - m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; + combineExternalFunctionType(); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; } @@ -316,6 +315,21 @@ void CompilerUtils::memoryCopy() m_context << Instruction::POP; // ignore return value } +void CompilerUtils::splitExternalFunctionType() +{ + // We have to split the right-aligned
into two stack slots: + // address (right aligned), function identifier (right aligned) + m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; + m_context << u256(0xffffffffUL) << Instruction::AND; +} + +void CompilerUtils::combineExternalFunctionType() +{ + m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; +} + void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { // For a type extension, we need to remove all higher-order bits that we might have ignored in @@ -860,16 +874,8 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda } if (auto const* funType = dynamic_cast(&_type)) - { if (funType->location() == FunctionType::Location::External) - { - // We have to split the right-aligned
into two stack slots: - // address (right aligned), function identifier (right aligned) - m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; - m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; - m_context << u256(0xffffffffUL) << Instruction::AND; - } - } + splitExternalFunctionType(); return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index da74dc90c..0c9adf29b 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -114,6 +114,13 @@ public: /// Stack post: void memoryCopy(); + /// Converts the combined and right-aligned external function type + ///
into two stack slots: + /// address (right aligned), function identifier (right aligned) + void splitExternalFunctionType(); + /// Performs the opposite operation of splitExternalFunctionType() + void combineExternalFunctionType(); + /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory /// if a reference type is converted from calldata or storage to memory. diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 69a80b6a8..98ab6d412 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -153,7 +153,8 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): { if (m_dataType->isValueType()) { - solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); + if (m_dataType->category() != Type::Category::Function) + solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), ""); solAssert(m_dataType->storageSize() == 1, "Invalid storage size."); } } @@ -189,8 +190,16 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const dynamic_cast(*m_dataType).isSigned() ) m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; + else if ( + m_dataType->category() == Type::Category::Function && + dynamic_cast(*m_dataType).location() == FunctionType::Location::External + ) + CompilerUtils(m_context).splitExternalFunctionType(); else + { + solAssert(m_dataType->sizeOnStack() == 1, ""); m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND; + } } } @@ -204,6 +213,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size."); if (m_dataType->storageBytes() == 32) { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size."); // offset should be zero m_context << Instruction::POP; if (!_move) @@ -222,16 +232,23 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc m_context << Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::MUL; - m_context << Instruction::NOT << Instruction::AND; - // stack: value storage_ref multiplier cleared_value - m_context - << Instruction::SWAP1 << Instruction::DUP4; + m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1; + // stack: value storage_ref cleared_value multiplier + utils.copyToStackTop(4, m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value - if (m_dataType->category() == Type::Category::FixedBytes) + if ( + m_dataType->category() == Type::Category::Function && + dynamic_cast(*m_dataType).location() == FunctionType::Location::External + ) + // Combine the two-item function type into a single stack slot. + utils.combineExternalFunctionType(); + else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) << Instruction::SWAP1 << Instruction::DIV; else + { + solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type."); // remove the higher order bits m_context << (u256(1) << (8 * (32 - m_dataType->storageBytes()))) @@ -239,11 +256,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::DUP2 << Instruction::MUL << Instruction::DIV; + } m_context << Instruction::MUL << Instruction::OR; // stack: value storage_ref updated_value m_context << Instruction::SWAP1 << Instruction::SSTORE; if (_move) - m_context << Instruction::POP; + utils.popStackElement(*m_dataType); } } else diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 709e63b2b..b1529f8f8 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7695,7 +7695,33 @@ BOOST_AUTO_TEST_CASE(store_function) BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); } -// TODO: arrays, libraries +BOOST_AUTO_TEST_CASE(function_type_library_internal) +{ + char const* sourceCode = R"( + library Utils { + function reduce(uint[] memory array, function(uint, uint) returns (uint) f, uint init) internal returns (uint) { + for (uint i = 0; i < array.length; i++) { + init = f(array[i], init); + } + return init; + } + function sum(uint a, uint b) internal returns (uint) { + return a + b; + } + } + contract C { + function f(uint[] x) returns (uint) { + return Utils.reduce(x, Utils.sum, 0); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f(uint256[])", 0x20, 3, u256(1), u256(7), u256(3)) == encodeArgs(u256(11))); +} + + +// TODO: arrays, libraries with external functions BOOST_AUTO_TEST_CASE(shift_constant_left) { From ab3d1b024db6e208e4b63e2ecb07754af1540d6f Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 14 Oct 2016 17:32:13 +0200 Subject: [PATCH 077/125] Add tests around calling functions returning functions returning functions --- test/libsolidity/SolidityEndToEndTest.cpp | 30 +++++++++++++++++++++++ test/libsolidity/SolidityParser.cpp | 15 ++++++++++++ 2 files changed, 45 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index b1529f8f8..79dfd90ed 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7721,6 +7721,36 @@ BOOST_AUTO_TEST_CASE(function_type_library_internal) } +BOOST_AUTO_TEST_CASE(call_function_returning_function) +{ + char const* sourceCode = R"( + contract test { + function f0() returns (uint) { + return 2; + } + function f1() returns (function() returns (uint)) { + returns f0; + } + function f2() returns (function() returns (function () returns (uint))) { + returns f1; + } + function f3() returns (function() returns (function () returns (function () returns (uint)))) + { + returns f2; + } + function f() returns (uint) { + function() returns(function() returns(function() returns(function() returns(uint)))) x; + x = f3; + return x()()()(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); +} + + // TODO: arrays, libraries with external functions BOOST_AUTO_TEST_CASE(shift_constant_left) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 496f47031..d8c6fa107 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1311,6 +1311,21 @@ BOOST_AUTO_TEST_CASE(function_type_as_parameter) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(calling_function) +{ + char const* text = R"( + contract test { + function f() { + function() returns(function() returns(function() returns(function() returns(uint)))) x; + uint y; + y = x()()()(); + } + } + )"; + BOOST_CHECK(successParse(text)); +} + + BOOST_AUTO_TEST_SUITE_END() } From 708b7b35adc337500b9c42832f4a5461e7579e55 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 14 Oct 2016 17:46:48 +0200 Subject: [PATCH 078/125] Add a parser test for arrays containing functions --- test/libsolidity/SolidityParser.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index d8c6fa107..b1b1d8589 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1325,6 +1325,16 @@ BOOST_AUTO_TEST_CASE(calling_function) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(array_of_functions) +{ + char const* text = R"( + contract test { + mapping (address => function() internal returns ()) stages; + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() From 6172590b870832d7aa132209afb890125f301c15 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 14 Oct 2016 17:54:12 +0200 Subject: [PATCH 079/125] Add a test around storing functions in an array --- test/libsolidity/SolidityEndToEndTest.cpp | 39 +++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 79dfd90ed..704fc1a1c 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7750,6 +7750,45 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); } +BOOST_AUTO_TEST_CASE(array_of_functions) +{ + char const* sourceCode = R"( + contract Flow { + bool success; + function checkSuccess() returns(bool) { + return success; + } + + mapping (address => function () internal returns()) stages; + + function stage0() internal { + stages[msg.sender] = stage1; + } + + function stage1() internal { + stages[msg.sender] = stage2; + } + + function stage2() internal { + success = true; + } + + function f () { + if (0 == steps[msg.sender]) + stages[msg.sender] = stage0; + stages[msg.sender](); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); + callContractFunction("f()"); + callContractFunction("f()"); + BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); + callContractFunction("f()"); + BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(true)); +} // TODO: arrays, libraries with external functions From 62492b67e783dd19c67850e2b8ae107aeeb83217 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 16:39:16 +0200 Subject: [PATCH 080/125] Changelog entry and small fixes. --- Changelog.md | 1 + libsolidity/ast/ASTJsonConverter.cpp | 5 +++++ libsolidity/ast/Types.cpp | 8 +++----- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Changelog.md b/Changelog.md index a392ec013..ce3343d8c 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,6 +1,7 @@ ### 0.4.5 (unreleased) Features: + * Function types * Do-while loops: support for a C-style do{}while(); control structure * Inline assembly: support ``invalidJumpLabel`` as a jump label. * Type checker: now more eagerly searches for a common type of an inline array with mixed types diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index 717a80ee1..d6aca175a 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -228,8 +228,13 @@ bool ASTJsonConverter::visit(UserDefinedTypeName const& _node) bool ASTJsonConverter::visit(FunctionTypeName const& _node) { + string visibility = "internal"; + if (_node.visibility() == Declaration::Visibility::External) + visibility = "external"; + addJsonNode(_node, "FunctionTypeName", { make_pair("payable", _node.isPayable()), + make_pair("visibility", visibility), make_pair("constant", _node.isDeclaredConst()) }); return true; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3afbee138..15747a8b2 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2077,12 +2077,10 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { - if (m_location != Location::External && m_location != Location::Internal) - return TypePointer(); - if (m_location != Location::External) - return TypePointer(); - else + if (m_location == Location::External) return make_shared(8 * storageBytes()); + else + return TypePointer(); } bool FunctionType::canTakeArguments(TypePointers const& _argumentTypes, TypePointer const& _selfType) const From 679ea2820fd6bb76ddd294101ef548bab6cd6425 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 18:43:07 +0200 Subject: [PATCH 081/125] Part of the documentation. --- docs/types.rst | 42 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 9ec9e526b..2e1cbee9e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -234,7 +234,7 @@ Hexademical Literals behave like String Literals and have the same convertibilit .. _enums: Enums -===== +----- Enums are one way to create a user-defined type in Solidity. They are explicitly convertible to and from all integer types but implicit conversion is not allowed. The explicit conversions @@ -267,6 +267,46 @@ check the value ranges at runtime and a failure causes an exception. Enums need } } +.. index:: ! function type, ! type; function + +.. _function_types: + +Function Types +-------------- + +Functions can be assigned to variables and passed on together with function calls. +These types come in two flavours: *internal* and *external* functions. + +Internal functions can only be used inside the current contract (more specifically, +inside the current code unit, which also includes internal library functions) +because they cannot be executed outside of the +context of the current function. Calling an internal function is realized +by jumping to its entry label, just like when calling an function of the current +contract internally. + +External functions consist of an address and a function signature and they can +be passed via and returned from external function calls. + +Function types are notated as follows: + + function () internal / external returns () + +As always, the ``returns ()`` is optional. + +By default, function types are internal, so the ``internal`` keyword can be +omitted. + +If a function type variable is not initialized, calling it will result +in an exception. + +If external function types are used outside of the context of Solidity, +they are converted into the ``bytes24`` type. + +Example usage: + + library ArrayUtils { + + .. index:: ! type;reference, ! reference type, storage, memory, location, array, struct Reference Types From ff3553a34895c70c473a27c29464ebfc15375416 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 18:43:31 +0200 Subject: [PATCH 082/125] Change alignment. --- libsolidity/ast/Types.cpp | 2 +- libsolidity/codegen/CompilerUtils.cpp | 62 ++++++++++++++++----------- libsolidity/codegen/CompilerUtils.h | 10 ++--- libsolidity/codegen/LValue.cpp | 4 +- 4 files changed, 45 insertions(+), 33 deletions(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 15747a8b2..8cc125fe1 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -2078,7 +2078,7 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_location == Location::External) - return make_shared(8 * storageBytes()); + return make_shared(storageBytes()); else return TypePointer(); } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 8a06268c1..dedb53e7c 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -139,7 +139,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound ) { solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); - combineExternalFunctionType(); + combineExternalFunctionType(true); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; } @@ -315,19 +315,29 @@ void CompilerUtils::memoryCopy() m_context << Instruction::POP; // ignore return value } -void CompilerUtils::splitExternalFunctionType() +void CompilerUtils::splitExternalFunctionType(bool _leftAligned) { - // We have to split the right-aligned
into two stack slots: + // We have to split the left-aligned
into two stack slots: // address (right aligned), function identifier (right aligned) + if (_leftAligned) + m_context << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; - m_context << u256(0xffffffffUL) << Instruction::AND; + if (!_leftAligned) + m_context << u256(0xffffffffUL) << Instruction::AND; } -void CompilerUtils::combineExternalFunctionType() +void CompilerUtils::combineExternalFunctionType(bool _leftAligned) { - m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160) << Instruction::MUL << Instruction::SWAP1; - m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::OR; + if (_leftAligned) + m_context << (u256(1) << 224); + else + m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160); + m_context << Instruction::MUL << Instruction::SWAP1; + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + if (_leftAligned) + m_context << (u256(1) << 64) << Instruction::MUL; + m_context << Instruction::OR; } void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) @@ -856,26 +866,28 @@ void CompilerUtils::storeStringData(bytesConstRef _data) unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned numBytes = _type.calldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.category() == Type::Category::FixedBytes; - if (numBytes == 0) - m_context << Instruction::POP << u256(0); - else - { - solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); - m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); - if (numBytes != 32) - { - // add leading or trailing zeros by dividing/multiplying depending on alignment - u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); - m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; - if (leftAligned) - m_context << shiftFactor << Instruction::MUL; - } - } - + bool isExternalFunctionType = false; if (auto const* funType = dynamic_cast(&_type)) if (funType->location() == FunctionType::Location::External) - splitExternalFunctionType(); + isExternalFunctionType = true; + if (numBytes == 0) + { + m_context << Instruction::POP << u256(0); + return numBytes; + } + solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested."); + m_context << (_fromCalldata ? Instruction::CALLDATALOAD : Instruction::MLOAD); + if (isExternalFunctionType) + splitExternalFunctionType(true); + else if (numBytes != 32) + { + bool leftAligned = _type.category() == Type::Category::FixedBytes; + // add leading or trailing zeros by dividing/multiplying depending on alignment + u256 shiftFactor = u256(1) << ((32 - numBytes) * 8); + m_context << shiftFactor << Instruction::SWAP1 << Instruction::DIV; + if (leftAligned) + m_context << shiftFactor << Instruction::MUL; + } return numBytes; } diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 0c9adf29b..690452f9a 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -114,12 +114,12 @@ public: /// Stack post: void memoryCopy(); - /// Converts the combined and right-aligned external function type - ///
into two stack slots: + /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) + /// external function type
into two stack slots: /// address (right aligned), function identifier (right aligned) - void splitExternalFunctionType(); - /// Performs the opposite operation of splitExternalFunctionType() - void combineExternalFunctionType(); + void splitExternalFunctionType(bool _rightAligned); + /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) + void combineExternalFunctionType(bool _rightAligned); /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 98ab6d412..cb7cbbe3b 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -194,7 +194,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_dataType->category() == Type::Category::Function && dynamic_cast(*m_dataType).location() == FunctionType::Location::External ) - CompilerUtils(m_context).splitExternalFunctionType(); + CompilerUtils(m_context).splitExternalFunctionType(false); else { solAssert(m_dataType->sizeOnStack() == 1, ""); @@ -241,7 +241,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc dynamic_cast(*m_dataType).location() == FunctionType::Location::External ) // Combine the two-item function type into a single stack slot. - utils.combineExternalFunctionType(); + utils.combineExternalFunctionType(false); else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) From 87b148494bcf1dd4814fafe658dd81fef79cf8b4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 19 Oct 2016 18:43:42 +0200 Subject: [PATCH 083/125] Tests. --- test/libsolidity/SolidityEndToEndTest.cpp | 198 +++++++++++++++--- .../SolidityNameAndTypeResolution.cpp | 18 ++ test/libsolidity/SolidityParser.cpp | 7 +- 3 files changed, 197 insertions(+), 26 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 704fc1a1c..ef64ad5a1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7669,6 +7669,42 @@ BOOST_AUTO_TEST_CASE(pass_function_types_externally) BOOST_CHECK(callContractFunction("f2(uint256)", 7) == encodeArgs(u256(8))); } +BOOST_AUTO_TEST_CASE(receive_external_function_type) +{ + char const* sourceCode = R"( + contract C { + function g() returns (uint) { return 7; } + function f(function() external returns (uint) g) returns (uint) { + return g(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction( + "f(bytes24)", + FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + ) == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(return_external_function_type) +{ + char const* sourceCode = R"( + contract C { + function g() {} + function f() returns (function() external) { + return this.g; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK( + callContractFunction("f()") == + FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + ); +} + BOOST_AUTO_TEST_CASE(store_function) { char const* sourceCode = R"( @@ -7676,7 +7712,7 @@ BOOST_AUTO_TEST_CASE(store_function) function addTwo(uint x) returns (uint) { return x + 2; } } contract C { - function (function (uint) external returns (uint)) returns (uint) ev = eval; + function (function (uint) external returns (uint)) returns (uint) ev; function (uint) external returns (uint) x; function store(function(uint) external returns (uint) y) { x = y; @@ -7685,6 +7721,7 @@ BOOST_AUTO_TEST_CASE(store_function) return y(7); } function t() returns (uint) { + ev = eval; this.store((new Other()).addTwo); return ev(x); } @@ -7728,15 +7765,15 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) function f0() returns (uint) { return 2; } - function f1() returns (function() returns (uint)) { - returns f0; + function f1() internal returns (function() returns (uint)) { + return f0; } - function f2() returns (function() returns (function () returns (uint))) { - returns f1; + function f2() internal returns (function() returns (function () returns (uint))) { + return f1; } - function f3() returns (function() returns (function () returns (function () returns (uint)))) + function f3() internal returns (function() returns (function () returns (function () returns (uint)))) { - returns f2; + return f2; } function f() returns (uint) { function() returns(function() returns(function() returns(function() returns(uint)))) x; @@ -7746,51 +7783,164 @@ BOOST_AUTO_TEST_CASE(call_function_returning_function) } )"; - compileAndRun(sourceCode, 0, "C"); + compileAndRun(sourceCode, 0, "test"); BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(2))); } -BOOST_AUTO_TEST_CASE(array_of_functions) +BOOST_AUTO_TEST_CASE(mapping_of_functions) { char const* sourceCode = R"( contract Flow { - bool success; - function checkSuccess() returns(bool) { - return success; - } + bool public success; - mapping (address => function () internal returns()) stages; + mapping (address => function () internal) stages; function stage0() internal { - stages[msg.sender] = stage1; + stages[msg.sender] = stage1; } function stage1() internal { - stages[msg.sender] = stage2; + stages[msg.sender] = stage2; } function stage2() internal { success = true; } - function f () { - if (0 == steps[msg.sender]) - stages[msg.sender] = stage0; + function Flow() { + stages[msg.sender] = stage0; + } + + function f() { stages[msg.sender](); } } )"; - compileAndRun(sourceCode, 0, "C"); + compileAndRun(sourceCode, 0, "Flow"); BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - callContractFunction("f()"); - callContractFunction("f()"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - callContractFunction("f()"); + BOOST_CHECK(callContractFunction("f()") == encodeArgs()); BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(true)); } -// TODO: arrays, libraries with external functions +BOOST_AUTO_TEST_CASE(packed_functions) +{ + char const* sourceCode = R"( + contract C { + // these should take the same slot + function() returns (uint) a; + function() external returns (uint) b; + uint8 public x; + + function set() { + x = 2; + a = g; + b = h; + } + function t1() returns (uint) { + return a(); + } + function t2() returns (uint) { + return b(); + } + function g() returns (uint) { + return 7; + } + function h() returns (uint) { + return 8; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("set()") == encodeArgs()); + BOOST_CHECK(callContractFunction("t1()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(2))); +} + +BOOST_AUTO_TEST_CASE(function_memory_array) +{ + char const* sourceCode = R"( + contract C { + function a(uint x) returns (uint) { return x + 1; } + function b(uint x) returns (uint) { return x + 2; } + function c(uint x) returns (uint) { return x + 3; } + function d(uint x) returns (uint) { return x + 5; } + function e(uint x) returns (uint) { return x + 8; } + function test(uint x, uint i) returns (uint) { + function(uint) internal returns (uint)[] arr = + new function(uint) internal returns (uint)[](10); + arr[0] = a; + arr[1] = b; + arr[2] = c; + arr[3] = d; + arr[4] = e; + return arr[i](x); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(0)) == encodeArgs(u256(11))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(1)) == encodeArgs(u256(12))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(2)) == encodeArgs(u256(13))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(3)) == encodeArgs(u256(15))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(4)) == encodeArgs(u256(18))); + BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(5)) == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(function_delete) +{ + char const* sourceCode = R"( + contract C { + function a() returns (uint) { return 7; } + function() internal returns (uint) y; + function set() returns (uint) { + y = a; + return y(); + } + funciton d() returns (uint) { + delete y; + return 1; + } + function ca() returns (uint) { + return y(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("set()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("ca()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("d()") == encodeArgs(u256(1))); + BOOST_CHECK(callContractFunction("ca()") == encodeArgs()); +} + +BOOST_AUTO_TEST_CASE(copy_function_storage_array) +{ + char const* sourceCode = R"( + contract C { + function() internal returns (uint)[] x; + function() internal returns (uint)[] y; + function test() returns (uint) { + x.length = 10; + x[9] = a; + y = x; + return y[9](); + } + function a() returns (uint) { + return 7; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); +} BOOST_AUTO_TEST_CASE(shift_constant_left) { diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 8b8c3aeb0..e85d0fe6b 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4227,6 +4227,7 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_internal )"; BOOST_CHECK(success(text)); } + BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external) { char const* text = R"( @@ -4238,6 +4239,23 @@ BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter_in_library_external BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(function_type_arrays) +{ + char const* text = R"( + contract C { + function(uint) external returns (uint)[] public x; + function(uint) internal returns (uint)[10] y; + function f() { + function(uint) returns (uint)[10] memory a; + function(uint) returns (uint)[10] storage b = y; + function(uint) external returns (uint)[] memory c; + c = new function(uint) external returns (uint)[](200); + } + } + )"; + BOOST_CHECK(success(text)); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index b1b1d8589..796da7827 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1325,11 +1325,14 @@ BOOST_AUTO_TEST_CASE(calling_function) BOOST_CHECK(successParse(text)); } -BOOST_AUTO_TEST_CASE(array_of_functions) +BOOST_AUTO_TEST_CASE(mapping_and_array_of_functions) { char const* text = R"( contract test { - mapping (address => function() internal returns ()) stages; + mapping (address => function() internal returns (uint)) a; + mapping (address => function() external) b; + mapping (address => function() external[]) c; + function() external[] d; } )"; BOOST_CHECK(successParse(text)); From 502cc319d79c28cf398baa737e96c54563b9aafa Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 26 Oct 2016 18:01:08 +0200 Subject: [PATCH 084/125] Documentation examples. --- docs/types.rst | 74 +++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index 2e1cbee9e..cac8b774d 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -302,10 +302,82 @@ in an exception. If external function types are used outside of the context of Solidity, they are converted into the ``bytes24`` type. -Example usage: +Example that shows how to use internal function types: library ArrayUtils { + // internal functions can be used in internal library functions because + // they will be part of the same code context + function map(uint[] memory self, function (uint) returns (uint) f) + returns (uint[] memory r) + { + r = new uint[](self.length); + for (uint i = 0; i < self.length; i++) { + r[i] = f(self[i]); + } + } + function reduce( + uint[] memory self, + function (uint) returns (uint) f + ) + returns (uint r) + { + r = self[0]; + for (uint i = 1; i < self.length; i++) { + r = f(r, self[i]); + } + } + function range(uint length) returns (uint[] memory r) { + r = new uint[](length); + for (uint i = 0; i < r.length; i++) { + r[i] = i; + } + } + } + contract Pyramid { + using ArrayUtils for *; + function pyramid(uint l) return (uint) { + return ArrayUtils.range(l).map(square).reduce(sum); + } + function square(uint x) internal returns (uint) { + return x * x; + } + function sum(uint x, uint y) internal returns (uint) { + return x + y; + } + } + +Another example that uses external function types: + + contract Oracle { + struct Request { + bytes data; + function(bytes) external callback; + } + Request[] requests; + event NewRequest(uint); + function query(bytes data, function(bytes) external callback) { + requests.push(Request(data, callback)); + NewRequest(requests.length - 1); + } + function reply(uint requestID, bytes response) { + // Here goes the check that the reply comes from a trusted source + requests[requestID].callback(response); + } + } + + contract OracleUser { + Oracle constant oracle = 0x1234567; // known contract + function buySomething() { + oracle.query("USD", oracleResponse); + } + function oracleResponse(bytes response) { + if (msg.sender != oracle) throw; + // Use the data + } + } + +Note that lambda or inline functions are planned but not yet supported. .. index:: ! type;reference, ! reference type, storage, memory, location, array, struct From cc847df3c20982372d601016382b0a93266467a4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 28 Oct 2016 17:30:56 +0200 Subject: [PATCH 085/125] Bugfix in code generator. --- libsolidity/codegen/LValue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index cb7cbbe3b..d2c75445f 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -234,7 +234,7 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc << Instruction::MUL; m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1; // stack: value storage_ref cleared_value multiplier - utils.copyToStackTop(4, m_dataType->sizeOnStack()); + utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value if ( m_dataType->category() == Type::Category::Function && From 3158a8ea7b06888472b09ac4bc5f6a5a2f7ae2ce Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 4 Nov 2016 15:23:48 +0100 Subject: [PATCH 086/125] test: add a test for storing an internal function in the constructor and then using the stored function in runtime --- test/libsolidity/SolidityEndToEndTest.cpp | 46 +++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ef64ad5a1..7dbadc48d 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7732,6 +7732,52 @@ BOOST_AUTO_TEST_CASE(store_function) BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(9))); } +BOOST_AUTO_TEST_CASE(store_function_in_constructor) +{ + char const* sourceCode = R"( + contract C { + uint result_in_constructor; + function (uint) internal returns (uint) x; + function C () { + x = double; + result_in_constructor = use(2); + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + function use(uint _arg) returns (uint) { + return x(_arg); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("use(uint256)", encodeArgs(u256(3))) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("result_in_constructor()") == encodeArgs(u256(4))); +} + +BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) +{ + char const* sourceCode = R"( + contract C { + uint public initial; + function C() { + initial = double(2); + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + function runtime(uint _arg) returns (uint) { + return double(_arg); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("runtime(uint256)", encodeArgs(u256(3))) == encodeArgs(u256(6))); + BOOST_CHECK(callContractFunction("initial()") == encodeArgs(u256(4))); +} + BOOST_AUTO_TEST_CASE(function_type_library_internal) { char const* sourceCode = R"( From b6992d740a849d28f7c2a52bef0ba6b3740c9179 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 7 Nov 2016 20:07:55 +0100 Subject: [PATCH 087/125] Tests for uninitialized storage functions. --- test/libsolidity/SolidityEndToEndTest.cpp | 54 ++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 7dbadc48d..44dba9b26 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7629,6 +7629,29 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function) BOOST_CHECK(callContractFunction("extern()") == encodeArgs()); } +BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) +{ + // Storage default value of zero would be correct jump dest, this tests that + // that is properly handled. + char const* sourceCode = R"( + contract C { + function() internal returns (uint) x; + int mutex; + function t() returns (uint) { + if (mutex > 0) + return 7; + mutex = 1; + // If this test fails, it will jump to "0" and re-execute this function. + x(); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(pass_function_types_internally) { char const* sourceCode = R"( @@ -7949,7 +7972,7 @@ BOOST_AUTO_TEST_CASE(function_delete) y = a; return y(); } - funciton d() returns (uint) { + function d() returns (uint) { delete y; return 1; } @@ -7988,6 +8011,35 @@ BOOST_AUTO_TEST_CASE(copy_function_storage_array) BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) +{ + // This has to apply NOT to the functions because encoding in storage + // is different than encoding in memory. + char const* sourceCode = R"( + contract C { + function() internal returns (uint)[20] x; + int mutex; + function one() returns (uint) { + function() internal returns (uint)[20] xmem; + x = xmem; + return 3; + } + function two() returns (uint) { + if (mutex > 0) + return 7; + mutex = 1; + // If this test fails, it will jump to "0" and re-execute this function. + x[0](); + return 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("one()") == encodeArgs(u256(3))); + BOOST_CHECK(callContractFunction("two()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(shift_constant_left) { char const* sourceCode = R"( From 47794c1da406a28f0e8a10e3e57cd935f5cc7f3d Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 7 Nov 2016 20:08:05 +0100 Subject: [PATCH 088/125] Implement uninitialized storage functions. --- libevmasm/Assembly.cpp | 1 + libsolidity/codegen/CompilerContext.h | 2 ++ libsolidity/codegen/LValue.cpp | 26 +++++++++++++---------- test/libsolidity/SolidityEndToEndTest.cpp | 4 +--- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index e881c1e26..e19b6b0dd 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -485,6 +485,7 @@ LinkerObject const& Assembly::assemble() const break; case Tag: tagPos[(unsigned)i.data()] = ret.bytecode.size(); + assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); assertThrow(i.data() != 0, AssemblyException, ""); ret.bytecode.push_back((byte)Instruction::JUMPDEST); break; diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 0c1500b04..6c509685a 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -44,6 +44,8 @@ namespace solidity { class CompilerContext { public: + bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } + void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index d2c75445f..66449fc46 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -190,11 +190,11 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const dynamic_cast(*m_dataType).isSigned() ) m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; - else if ( - m_dataType->category() == Type::Category::Function && - dynamic_cast(*m_dataType).location() == FunctionType::Location::External - ) - CompilerUtils(m_context).splitExternalFunctionType(false); + else if (FunctionType const* fun = dynamic_cast(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + CompilerUtils(m_context).splitExternalFunctionType(false); + } else { solAssert(m_dataType->sizeOnStack() == 1, ""); @@ -236,12 +236,16 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc // stack: value storage_ref cleared_value multiplier utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack()); // stack: value storage_ref cleared_value multiplier value - if ( - m_dataType->category() == Type::Category::Function && - dynamic_cast(*m_dataType).location() == FunctionType::Location::External - ) - // Combine the two-item function type into a single stack slot. - utils.combineExternalFunctionType(false); + if (FunctionType const* fun = dynamic_cast(m_dataType)) + { + if (fun->location() == FunctionType::Location::External) + // Combine the two-item function type into a single stack slot. + utils.combineExternalFunctionType(false); + else + m_context << + ((u256(1) << (8 * m_dataType->storageBytes())) - 1) << + Instruction::AND; + } else if (m_dataType->category() == Type::Category::FixedBytes) m_context << (u256(0x1) << (256 - 8 * dynamic_cast(*m_dataType).numBytes())) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 44dba9b26..c665b050e 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7631,8 +7631,6 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function) BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) { - // Storage default value of zero would be correct jump dest, this tests that - // that is properly handled. char const* sourceCode = R"( contract C { function() internal returns (uint) x; @@ -7641,7 +7639,7 @@ BOOST_AUTO_TEST_CASE(calling_uninitialized_function_in_detail) if (mutex > 0) return 7; mutex = 1; - // If this test fails, it will jump to "0" and re-execute this function. + // Avoid re-executing this function if we jump somewhere. x(); return 2; } From 0e5507c78c5b1602ff0f9e5530ab05f0a35ecc0d Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 8 Nov 2016 11:43:42 +0100 Subject: [PATCH 089/125] Updates to the documentation. --- docs/types.rst | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index cac8b774d..1673c30d0 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -274,14 +274,16 @@ check the value ranges at runtime and a failure causes an exception. Enums need Function Types -------------- -Functions can be assigned to variables and passed on together with function calls. -These types come in two flavours: *internal* and *external* functions. +Function types can be used to assign functions to variables and passing them +to or return them from function calls. Such variables and parameters have +to have function types. These types come in two flavours: *internal* and *external* +functions. Internal functions can only be used inside the current contract (more specifically, -inside the current code unit, which also includes internal library functions) -because they cannot be executed outside of the -context of the current function. Calling an internal function is realized -by jumping to its entry label, just like when calling an function of the current +inside the current code unit, which also includes internal library functions +and inherited functions) because they cannot be executed outside of the +context of the current contract. Calling an internal function is realized +by jumping to its entry label, just like when calling a function of the current contract internally. External functions consist of an address and a function signature and they can @@ -289,9 +291,11 @@ be passed via and returned from external function calls. Function types are notated as follows: - function () internal / external returns () + function () {internal|external} [constant] [returns ()] -As always, the ``returns ()`` is optional. +In contrast to the parameter types, the return types cannot be empty - if the +function type should not return anything, the whole ``returns ()`` +part has to be omitted. By default, function types are internal, so the ``internal`` keyword can be omitted. From 5011d6339a185a55d6cdc3e952dea7bdd14a2ff7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 8 Nov 2016 11:48:28 +0100 Subject: [PATCH 090/125] Added function types to the grammar. --- libsolidity/grammar.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index 6ad8994aa..cfa779eca 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -31,13 +31,16 @@ EnumDefinition = 'enum' Identifier '{' EnumValue? (',' EnumValue)* '}' IndexedParameterList = '(' ( TypeName 'indexed'? Identifier? (',' TypeName 'indexed'? Identifier?)* )? ')' ParameterList = '(' ( TypeName Identifier? (',' TypeName Identifier?)* )? ')' +TypeNameList = '(' ( TypeName (',' TypeName )* )? ')' // semantic restriction: mappings and structs (recursively) containing mappings // are not allowed in argument lists VariableDeclaration = TypeName Identifier -TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName +TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName | FunctionTypeName Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' +FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' )* + ( 'returns' TypeNameList )? StorageLocation = 'memory' | 'storage' Block = '{' Statement* '}' From c9f9b2ab4d421e99055425ace5deeb76d8f4fdd2 Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Tue, 8 Nov 2016 15:25:32 +0100 Subject: [PATCH 091/125] codegen: add a compilation mode and a runtime context to CompilerContext --- libsolidity/codegen/Compiler.cpp | 6 +++--- libsolidity/codegen/Compiler.h | 4 +++- libsolidity/codegen/CompilerContext.h | 15 +++++++++++++++ libsolidity/codegen/ContractCompiler.h | 4 ++-- test/libsolidity/SolidityExpressionCompiler.cpp | 2 +- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index 1ae5dda9c..bb8211ad8 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,10 +33,10 @@ void Compiler::compileContract( std::map const& _contracts ) { - ContractCompiler runtimeCompiler(m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(CompilationMode::Runtime, nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(m_context, m_optimize); + ContractCompiler creationCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts); if (m_optimize) @@ -54,7 +54,7 @@ void Compiler::compileClone( map const& _contracts ) { - ContractCompiler cloneCompiler(m_context, m_optimize); + ContractCompiler cloneCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); if (m_optimize) diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index fccb68a95..56849ea00 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -35,7 +35,9 @@ class Compiler public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), - m_optimizeRuns(_runs) + m_optimizeRuns(_runs), + m_context(CompilationMode::Creation, &m_runtimeContext), + m_runtimeContext(CompilationMode::Runtime) { } void compileContract( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 6c509685a..8b95c9f5b 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -37,6 +37,10 @@ namespace dev { namespace solidity { +/// Depending on the compilation is on the runtime code or the creation code, +/// the interpretation of internal function values differ. +enum class CompilationMode { Runtime, Creation }; + /** * Context to be shared by all units that compile the same contract. * It stores the generated bytecode and the position of identifiers in memory and on the stack. @@ -44,6 +48,13 @@ namespace solidity { class CompilerContext { public: + CompilerContext(CompilationMode _mode, CompilerContext* _runtimeContext = nullptr) : + m_mode(_mode), m_runtimeContext(_runtimeContext) + { + solAssert(m_mode != CompilationMode::Runtime || !m_runtimeContext, "runtime but another runtime context provided"); + solAssert(m_mode != CompilationMode::Creation || m_runtimeContext, "creation but no runtime context provided"); + } + bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } void addMagicGlobal(MagicVariableDeclaration const& _declaration); @@ -230,6 +241,10 @@ private: std::vector m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack m_visitedNodes; + /// The current mode of the compilation + CompilationMode m_mode; + /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. + CompilerContext *m_runtimeContext; }; } diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 0799a5431..fecf6f5ad 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,11 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilerContext& _context, bool _optimise): + explicit ContractCompiler(CompilationMode _mode, CompilerContext* _runtimeContext, CompilerContext& _context, bool _optimise): m_optimise(_optimise), m_context(_context) { - m_context = CompilerContext(); + m_context = CompilerContext(_mode, _runtimeContext); } void compileContract( diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index e9a05745e..09d556a51 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -136,7 +136,7 @@ bytes compileFirstExpression( FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.expression() != nullptr); - CompilerContext context; + CompilerContext context { CompilationMode::Runtime /* probably simpler */ }; context.resetVisitedNodes(contract); context.setInheritanceHierarchy(inheritanceHierarchy); unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack From f21f794f3c380c9382ace4908c38d0f6c776ae17 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 09:36:38 +0100 Subject: [PATCH 092/125] delete for function types --- libsolidity/ast/Types.cpp | 7 +++ libsolidity/ast/Types.h | 1 + test/libsolidity/SolidityEndToEndTest.cpp | 19 +++++++- .../SolidityNameAndTypeResolution.cpp | 45 +++++++++++++++++++ 4 files changed, 71 insertions(+), 1 deletion(-) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 8cc125fe1..395faf556 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1897,6 +1897,13 @@ bool FunctionType::operator==(Type const& _other) const return true; } +TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const +{ + if (_operator == Token::Value::Delete) + return make_shared(); + return TypePointer(); +} + string FunctionType::toString(bool _short) const { string name = "function ("; diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 691ddf297..9831bc42e 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -892,6 +892,7 @@ public: TypePointer selfType() const; virtual bool operator==(Type const& _other) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c665b050e..a12368036 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7960,7 +7960,7 @@ BOOST_AUTO_TEST_CASE(function_memory_array) BOOST_CHECK(callContractFunction("test(uint256,uint256)", u256(10), u256(5)) == encodeArgs()); } -BOOST_AUTO_TEST_CASE(function_delete) +BOOST_AUTO_TEST_CASE(function_delete_storage) { char const* sourceCode = R"( contract C { @@ -7987,6 +7987,23 @@ BOOST_AUTO_TEST_CASE(function_delete) BOOST_CHECK(callContractFunction("ca()") == encodeArgs()); } +BOOST_AUTO_TEST_CASE(function_delete_stack) +{ + char const* sourceCode = R"( + contract C { + function a() returns (uint) { return 7; } + function test() returns (uint) { + y = a; + delete y; + y(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs()); +} + BOOST_AUTO_TEST_CASE(copy_function_storage_array) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index e85d0fe6b..62fb55f75 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4256,6 +4256,51 @@ BOOST_AUTO_TEST_CASE(function_type_arrays) BOOST_CHECK(success(text)); } +BOOST_AUTO_TEST_CASE(delete_function_type) +{ + char const* text = R"( + contract C { + function(uint) external returns (uint) x; + function(uint) internal returns (uint) y; + function f() { + delete x; + var a = y; + delete a; + delete y; + var c = f; + delete c; + function(uint) internal returns (uint) g; + delete g; + } + } + )"; + BOOST_CHECK(success(text)); +} + +BOOST_AUTO_TEST_CASE(delete_function_type_invalid) +{ + char const* text = R"( + contract C { + function f() { + delete f; + } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(delete_external_function_type_invalid) +{ + char const* text = R"( + contract C { + function f() { + delete this.f; + } + } + )"; + BOOST_CHECK(expectError(text, false) == Error::Type::TypeError); +} + BOOST_AUTO_TEST_CASE(invalid_fixed_point_literal) { char const* text = R"( From e1df3bd77f78d5564fc173474015e5d84b192824 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 13:34:51 +0100 Subject: [PATCH 093/125] Fix tests. --- libsolidity/codegen/LValue.cpp | 12 ++++++++- test/libsolidity/SolidityEndToEndTest.cpp | 31 ++++++++++++++++------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/libsolidity/codegen/LValue.cpp b/libsolidity/codegen/LValue.cpp index 66449fc46..2ec7f8005 100644 --- a/libsolidity/codegen/LValue.cpp +++ b/libsolidity/codegen/LValue.cpp @@ -177,6 +177,7 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const m_context << Instruction::POP << Instruction::SLOAD; else { + bool cleaned = false; m_context << Instruction::SWAP1 << Instruction::SLOAD << Instruction::SWAP1 << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV; @@ -184,18 +185,27 @@ void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const // implementation should be very similar to the integer case. solUnimplemented("Not yet implemented - FixedPointType."); if (m_dataType->category() == Type::Category::FixedBytes) + { m_context << (u256(0x1) << (256 - 8 * m_dataType->storageBytes())) << Instruction::MUL; + cleaned = true; + } else if ( m_dataType->category() == Type::Category::Integer && dynamic_cast(*m_dataType).isSigned() ) + { m_context << u256(m_dataType->storageBytes() - 1) << Instruction::SIGNEXTEND; + cleaned = true; + } else if (FunctionType const* fun = dynamic_cast(m_dataType)) { if (fun->location() == FunctionType::Location::External) + { CompilerUtils(m_context).splitExternalFunctionType(false); + cleaned = true; + } } - else + if (!cleaned) { solAssert(m_dataType->sizeOnStack() == 1, ""); m_context << ((u256(0x1) << (8 * m_dataType->storageBytes())) - 1) << Instruction::AND; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index a12368036..8f9edadf1 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7878,19 +7878,20 @@ BOOST_AUTO_TEST_CASE(mapping_of_functions) stages[msg.sender] = stage0; } - function f() { + function f() returns (uint) { stages[msg.sender](); + return 7; } } )"; compileAndRun(sourceCode, 0, "Flow"); - BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(false)); - BOOST_CHECK(callContractFunction("f()") == encodeArgs()); - BOOST_CHECK(callContractFunction("checkSuccess()") == encodeArgs(true)); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(false)); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(false)); + BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("success()") == encodeArgs(true)); } BOOST_AUTO_TEST_CASE(packed_functions) @@ -7900,12 +7901,16 @@ BOOST_AUTO_TEST_CASE(packed_functions) // these should take the same slot function() returns (uint) a; function() external returns (uint) b; + function() external returns (uint) c; + function() returns (uint) d; uint8 public x; function set() { x = 2; + d = g; + c = this.h; + b = this.h; a = g; - b = h; } function t1() returns (uint) { return a(); @@ -7913,6 +7918,12 @@ BOOST_AUTO_TEST_CASE(packed_functions) function t2() returns (uint) { return b(); } + function t3() returns (uint) { + return a(); + } + function t4() returns (uint) { + return b(); + } function g() returns (uint) { return 7; } @@ -7926,6 +7937,8 @@ BOOST_AUTO_TEST_CASE(packed_functions) BOOST_CHECK(callContractFunction("set()") == encodeArgs()); BOOST_CHECK(callContractFunction("t1()") == encodeArgs(u256(7))); BOOST_CHECK(callContractFunction("t2()") == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("t3()") == encodeArgs(u256(7))); + BOOST_CHECK(callContractFunction("t4()") == encodeArgs(u256(8))); BOOST_CHECK(callContractFunction("x()") == encodeArgs(u256(2))); } @@ -7939,7 +7952,7 @@ BOOST_AUTO_TEST_CASE(function_memory_array) function d(uint x) returns (uint) { return x + 5; } function e(uint x) returns (uint) { return x + 8; } function test(uint x, uint i) returns (uint) { - function(uint) internal returns (uint)[] arr = + function(uint) internal returns (uint)[] memory arr = new function(uint) internal returns (uint)[](10); arr[0] = a; arr[1] = b; From f7a62c1e69e98cc612b7ed98497260b38234a162 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 14:15:01 +0100 Subject: [PATCH 094/125] Mention "payable" in the documentation. --- docs/types.rst | 9 +++++++-- libsolidity/grammar.txt | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 1673c30d0..7f4570edd 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -291,7 +291,7 @@ be passed via and returned from external function calls. Function types are notated as follows: - function () {internal|external} [constant] [returns ()] + function () {internal|external} [constant] [payable] [returns ()] In contrast to the parameter types, the return types cannot be empty - if the function type should not return anything, the whole ``returns ()`` @@ -300,8 +300,13 @@ part has to be omitted. By default, function types are internal, so the ``internal`` keyword can be omitted. +There are two ways to access a function in the current contract: Either directly +by its name, ``f``, or using ``this.f``. The former will result in an internal +function, the latter in an external function. + If a function type variable is not initialized, calling it will result -in an exception. +in an exception. The same happens if you call a function after using ``delete`` +on it. If external function types are used outside of the context of Solidity, they are converted into the ``bytes24`` type. diff --git a/libsolidity/grammar.txt b/libsolidity/grammar.txt index cfa779eca..c8bc3aed0 100644 --- a/libsolidity/grammar.txt +++ b/libsolidity/grammar.txt @@ -22,7 +22,7 @@ StructDefinition = 'struct' Identifier '{' ( VariableDeclaration ';' (VariableDeclaration ';')* )? '}' ModifierDefinition = 'modifier' Identifier ParameterList? Block FunctionDefinition = 'function' Identifier? ParameterList - ( FunctionCall | Identifier | 'constant' | 'external' | 'public' | 'internal' | 'private' )* + ( FunctionCall | Identifier | 'constant' |' payable' | 'external' | 'public' | 'internal' | 'private' )* ( 'returns' ParameterList )? Block EventDefinition = 'event' Identifier IndexedParameterList 'anonymous'? ';' @@ -39,7 +39,7 @@ VariableDeclaration = TypeName Identifier TypeName = ElementaryTypeName | Identifier StorageLocation? | Mapping | ArrayTypeName | FunctionTypeName Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')' ArrayTypeName = TypeName StorageLocation? '[' Expression? ']' -FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' )* +FunctionTypeName = 'function' TypeNameList ( 'internal' | 'external' | 'constant' | 'payable' )* ( 'returns' TypeNameList )? StorageLocation = 'memory' | 'storage' From 925d6741466a423b58cb519b6cc400863426607e Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 15:14:16 +0100 Subject: [PATCH 095/125] Disallow payable internal functions. --- libsolidity/analysis/ReferencesResolver.cpp | 3 +- libsolidity/ast/Types.cpp | 4 ++- .../SolidityNameAndTypeResolution.cpp | 36 +++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index 41cad922d..c49af013a 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -95,7 +95,8 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) typeError(_typeName.location(), "Invalid visibility, can only be \"external\" or \"internal\"."); } - // Do we allow storage references for external functions? + if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) + fatalTypeError(_typeName.location(), "Only external function types can be payable."); _typeName.annotation().type = make_shared(_typeName); } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 395faf556..e18735d96 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1704,7 +1704,7 @@ TypePointer TupleType::closestTemporaryType(TypePointer const& _targetType) cons FunctionType::FunctionType(FunctionDefinition const& _function, bool _isInternal): m_location(_isInternal ? Location::Internal : Location::External), m_isConstant(_function.isDeclaredConst()), - m_isPayable(_function.isPayable()), + m_isPayable(_isInternal ? false : _function.isPayable()), m_declaration(&_function) { TypePointers params; @@ -1810,6 +1810,8 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): m_isConstant(_typeName.isDeclaredConst()), m_isPayable(_typeName.isPayable()) { + if (_typeName.isPayable()) + solAssert(m_location == Location::External, "Internal payable function type used."); for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 62fb55f75..5916ed10f 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4192,6 +4192,42 @@ BOOST_AUTO_TEST_CASE(public_function_type) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(payable_internal_function_type) +{ + char const* text = R"( + contract C { + function (uint) internal payable returns (uint) x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(call_value_on_non_payable_function_type) +{ + char const* text = R"( + contract C { + function (uint) external returns (uint) x; + function f() { + x.value(2)(); + } + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(call_value_on_payable_function_type) +{ + char const* text = R"( + contract C { + function (uint) external payable returns (uint) x; + function f() { + x.value(2)(1); + } + } + )"; + BOOST_CHECK(success(text)); +} + BOOST_AUTO_TEST_CASE(internal_function_as_external_parameter) { // It should not be possible to give internal functions From 08763a206d4391f94546ef32a1d0f1495eeb99e4 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 15:58:45 +0100 Subject: [PATCH 096/125] Test passing functions as arrays to other contracts. --- test/libsolidity/SolidityEndToEndTest.cpp | 43 +++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8f9edadf1..5054e2756 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8039,10 +8039,47 @@ BOOST_AUTO_TEST_CASE(copy_function_storage_array) BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(7))); } +BOOST_AUTO_TEST_CASE(function_array_cross_calls) +{ + char const* sourceCode = R"( + contract D { + function f(function() external returns (function() external returns (uint))[] x) + returns (function() external returns (uint)[3] r) + { + r[0] = x[0](); + r[1] = x[1](); + r[2] = x[2](); + } + } + contract C { + function test() returns (uint, uint, uint) { + function() external returns (function() external returns (uint))[] memory x = + new function() external returns (function() external returns (uint))[](10); + for (uint i = 0; i < x.length; i ++) + x[i] = this.h; + x[0] = this.htwo; + var y = (new D()).f(x); + return (y[0](), y[1](), y[2]()); + } + function e() returns (uint) { return 5; } + function f() returns (uint) { return 6; } + function g() returns (uint) { return 7; } + uint counter; + function h() returns (function() external returns (uint)) { + return counter++ == 0 ? this.f : this.g; + } + function htwo() returns (function() external returns (uint)) { + return this.e; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(u256(5), u256(6), u256(7))); +} + BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) { - // This has to apply NOT to the functions because encoding in storage - // is different than encoding in memory. char const* sourceCode = R"( contract C { function() internal returns (uint)[20] x; @@ -8056,7 +8093,7 @@ BOOST_AUTO_TEST_CASE(copy_internal_function_array_to_storage) if (mutex > 0) return 7; mutex = 1; - // If this test fails, it will jump to "0" and re-execute this function. + // If this test fails, it might re-execute this function. x[0](); return 2; } From 746266b8fc9c94e1a21aa18ad646dda90643a1f7 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 17:51:28 +0100 Subject: [PATCH 097/125] ABI: Use external function. --- libsolidity/interface/InterfaceHandler.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 5705856c9..0f7b6c359 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -66,7 +66,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe { Json::Value method; method["type"] = "constructor"; - auto externalFunction = FunctionType(*_contractDef.constructor()).interfaceFunctionType(); + auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); method["inputs"] = populateParameters( externalFunction->parameterNames(), @@ -76,7 +76,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe } if (_contractDef.fallbackFunction()) { - auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction()).interfaceFunctionType(); + auto externalFunctionType = FunctionType(*_contractDef.fallbackFunction(), false).interfaceFunctionType(); solAssert(!!externalFunctionType, ""); Json::Value method; method["type"] = "fallback"; From ee3efa67a8d3eb4077786fd745c1925a916419f5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 9 Nov 2016 17:51:48 +0100 Subject: [PATCH 098/125] Fix tests. --- test/libsolidity/SolidityABIJSON.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 0ad9e928e..c01ff11b0 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -684,7 +684,7 @@ BOOST_AUTO_TEST_CASE(payable_function) checkInterface(sourceCode, interface); } -BOOST_AUTO_TEST_CASE(payable_fallback_unction) +BOOST_AUTO_TEST_CASE(payable_fallback_function) { char const* sourceCode = R"( contract test { diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 5054e2756..b05316dd4 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -8006,7 +8006,7 @@ BOOST_AUTO_TEST_CASE(function_delete_stack) contract C { function a() returns (uint) { return 7; } function test() returns (uint) { - y = a; + var y = a; delete y; y(); } From e543bd34c0b4884b5a27555f698f50af6a1c0b81 Mon Sep 17 00:00:00 2001 From: chriseth Date: Thu, 10 Nov 2016 18:16:21 +0100 Subject: [PATCH 099/125] Stored combined creation and runtime tags. Includes a change to Assembly to allow tags from sub-assemblies to be used. Sorry, this get a bit bigger than I thought. --- libevmasm/Assembly.cpp | 102 +++++++++++------- libevmasm/Assembly.h | 6 +- libevmasm/AssemblyItem.cpp | 23 ++++ libevmasm/AssemblyItem.h | 8 ++ libevmasm/BlockDeduplicator.cpp | 37 +++++-- libevmasm/BlockDeduplicator.h | 16 +++ libevmasm/ControlFlowGraph.h | 5 + liblll/CodeFragment.cpp | 4 +- libsolidity/codegen/Compiler.cpp | 11 +- libsolidity/codegen/Compiler.h | 8 +- libsolidity/codegen/CompilerContext.cpp | 16 +-- libsolidity/codegen/CompilerContext.h | 45 ++++---- libsolidity/codegen/CompilerUtils.cpp | 13 +++ libsolidity/codegen/CompilerUtils.h | 4 + libsolidity/codegen/ContractCompiler.cpp | 43 ++++++-- libsolidity/codegen/ContractCompiler.h | 10 +- libsolidity/codegen/ExpressionCompiler.cpp | 31 ++++-- libsolidity/interface/CompilerStack.cpp | 17 ++- test/libsolidity/SolidityEndToEndTest.cpp | 63 +++++++++++ .../SolidityExpressionCompiler.cpp | 2 +- 20 files changed, 347 insertions(+), 117 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index e19b6b0dd..23a8180b6 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -308,9 +308,20 @@ void Assembly::injectStart(AssemblyItem const& _i) Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { - if (!_enable) - return *this; + if (_enable) + optimiseInternal(_isCreation, _runs); + return *this; +} +map Assembly::optimiseInternal(bool _isCreation, size_t _runs) +{ + for (size_t subId = 0; subId < m_subs.size(); ++subId) + { + map subTagReplacements = m_subs[subId].optimiseInternal(false, _runs); + BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); + } + + map tagReplacements; unsigned total = 0; for (unsigned count = 1; count > 0; total += count) { @@ -319,30 +330,39 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) // This only modifies PushTags, we have to run again to actually remove code. BlockDeduplicator dedup(m_items); if (dedup.deduplicate()) + { + tagReplacements.insert(dedup.replacedTags().begin(), dedup.replacedTags().end()); count++; + } { - // Control flow graph that resets knowledge at path joins. - ControlFlowGraph cfg(m_items, false); + // Control flow graph optimization has been here before but is disabled because it + // assumes we only jump to tags that are pushed. This is not the case anymore with + // function types that can be stored in storage. AssemblyItems optimisedItems; - for (BasicBlock const& block: cfg.optimisedBlocks()) + + auto iter = m_items.begin(); + while (iter != m_items.end()) { - // We used to start with the block's initial state but it caused - // too many inconsistencies. + auto end = iter; + while (end != m_items.end()) + if (SemanticInformation::altersControlFlow(*end++)) + break; + KnownState emptyState; CommonSubexpressionEliminator eliminator(emptyState); - auto iter = m_items.begin() + block.begin; - auto const end = m_items.begin() + block.end; - while (iter < end) + auto blockIter = iter; + auto const blockEnd = end; + while (blockIter < blockEnd) { - auto orig = iter; - iter = eliminator.feedItems(iter, end); + auto orig = blockIter; + blockIter = eliminator.feedItems(blockIter, blockEnd); bool shouldReplace = false; AssemblyItems optimisedChunk; try { optimisedChunk = eliminator.getOptimizedItems(); - shouldReplace = (optimisedChunk.size() < size_t(iter - orig)); + shouldReplace = (optimisedChunk.size() < size_t(blockIter - orig)); } catch (StackTooDeepException const&) { @@ -361,15 +381,11 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) optimisedItems += optimisedChunk; } else - copy(orig, iter, back_inserter(optimisedItems)); + copy(orig, blockIter, back_inserter(optimisedItems)); } + iter = end; } - if (optimisedItems.size() < m_items.size()) - { - m_items = move(optimisedItems); - count++; - } } } @@ -380,10 +396,7 @@ Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) m_items ); - for (auto& sub: m_subs) - sub.optimise(true, false, _runs); - - return *this; + return tagReplacements; } LinkerObject const& Assembly::assemble() const @@ -394,8 +407,8 @@ LinkerObject const& Assembly::assemble() const LinkerObject& ret = m_assembledObject; unsigned totalBytes = bytesRequired(); - vector tagPos(m_usedTags); - map tagRef; + m_tagPositionsInBytecode = vector(m_usedTags, -1); + map> tagRef; multimap dataRef; multimap subRef; vector sizeRef; ///< Pointers to code locations where the size of the program is inserted @@ -413,8 +426,8 @@ LinkerObject const& Assembly::assemble() const for (AssemblyItem const& i: m_items) { // store position of the invalid jump destination - if (i.type() != Tag && tagPos[0] == 0) - tagPos[0] = ret.bytecode.size(); + if (i.type() != Tag && m_tagPositionsInBytecode[0] == size_t(-1)) + m_tagPositionsInBytecode[0] = ret.bytecode.size(); switch (i.type()) { @@ -446,7 +459,7 @@ LinkerObject const& Assembly::assemble() const case PushTag: { ret.bytecode.push_back(tagPush); - tagRef[ret.bytecode.size()] = (unsigned)i.data(); + tagRef[ret.bytecode.size()] = i.splitForeignPushTag(); ret.bytecode.resize(ret.bytecode.size() + bytesPerTag); break; } @@ -484,26 +497,16 @@ LinkerObject const& Assembly::assemble() const ret.bytecode.resize(ret.bytecode.size() + 20); break; case Tag: - tagPos[(unsigned)i.data()] = ret.bytecode.size(); - assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); assertThrow(i.data() != 0, AssemblyException, ""); + assertThrow(i.splitForeignPushTag().first == size_t(-1), AssemblyException, "Foreign tag."); + assertThrow(ret.bytecode.size() < 0xffffffffL, AssemblyException, "Tag too large."); + m_tagPositionsInBytecode[size_t(i.data())] = ret.bytecode.size(); ret.bytecode.push_back((byte)Instruction::JUMPDEST); break; default: BOOST_THROW_EXCEPTION(InvalidOpcode()); } } - for (auto const& i: tagRef) - { - bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); - auto tag = i.second; - if (tag >= tagPos.size()) - tag = 0; - if (tag == 0) - assertThrow(tagPos[tag] != 0, AssemblyException, ""); - - toBigEndian(tagPos[tag], r); - } if (!dataRef.empty() && !subRef.empty()) ret.bytecode.push_back(0); @@ -519,6 +522,23 @@ LinkerObject const& Assembly::assemble() const } ret.append(m_subs[i].assemble()); } + for (auto const& i: tagRef) + { + size_t subId; + size_t tagId; + tie(subId, tagId) = i.second; + assertThrow(subId == size_t(-1) || subId < m_subs.size(), AssemblyException, "Invalid sub id"); + std::vector const& tagPositions = + subId == size_t(-1) ? + m_tagPositionsInBytecode : + m_subs[subId].m_tagPositionsInBytecode; + assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); + size_t pos = tagPositions[tagId]; + assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position."); + assertThrow(dev::bytesRequired(pos) <= bytesPerTag, AssemblyException, "Tag too large for reserved space."); + bytesRef r(ret.bytecode.data() + i.first, bytesPerTag); + toBigEndian(pos, r); + } for (auto const& dataItem: m_data) { auto references = dataRef.equal_range(dataItem.first); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index dae1e1daa..0ae81e1d5 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -53,7 +53,6 @@ public: AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem newPushLibraryAddress(std::string const& _identifier); - AssemblyItem append() { return append(newTag()); } void append(Assembly const& _a); void append(Assembly const& _a, int _deposit); AssemblyItem const& append(AssemblyItem const& _i); @@ -110,6 +109,10 @@ public: ) const; protected: + /// Does the same operations as @a optimise, but should only be applied to a sub and + /// returns the replaced tags. + std::map optimiseInternal(bool _isCreation, size_t _runs); + std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } unsigned bytesRequired() const; @@ -129,6 +132,7 @@ protected: std::map m_libraries; ///< Identifiers of libraries to be linked. mutable LinkerObject m_assembledObject; + mutable std::vector m_tagPositionsInBytecode; int m_deposit = 0; int m_baseDeposit = 0; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 599ded855..2eae20aff 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -26,6 +26,29 @@ using namespace std; using namespace dev; using namespace dev::eth; +AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const +{ + assertThrow(m_data < (u256(1) << 64), Exception, "Tag already has subassembly set."); + + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + AssemblyItem r = *this; + r.m_type = PushTag; + r.setPushTagSubIdAndTag(_subId, size_t(m_data)); + return r; +} + +pair AssemblyItem::splitForeignPushTag() const +{ + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + return make_pair(size_t(m_data / (u256(1) << 64)) - 1, size_t(m_data)); +} + +void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) +{ + assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); + setData(_tag + ((u256(_subId) + 1) << 64)); +} + unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const { switch (m_type) diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h index 1c3d9789e..1a2fb1e62 100644 --- a/libevmasm/AssemblyItem.h +++ b/libevmasm/AssemblyItem.h @@ -69,6 +69,14 @@ public: AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } + /// Converts the tag to a subassembly tag. This has to be called in order to move a tag across assemblies. + /// @param _subId the identifier of the subassembly the tag is taken from. + AssemblyItem toSubAssemblyTag(size_t _subId) const; + /// @returns splits the data of the push tag into sub assembly id and actual tag id. + /// The sub assembly id of non-foreign push tags is -1. + std::pair splitForeignPushTag() const; + /// Sets sub-assembly part and tag for a push tag. + void setPushTagSubIdAndTag(size_t _subId, size_t _tag); AssemblyItemType type() const { return m_type; } u256 const& data() const { return m_data; } diff --git a/libevmasm/BlockDeduplicator.cpp b/libevmasm/BlockDeduplicator.cpp index 3bb7a7978..18b595cd2 100644 --- a/libevmasm/BlockDeduplicator.cpp +++ b/libevmasm/BlockDeduplicator.cpp @@ -77,7 +77,6 @@ bool BlockDeduplicator::deduplicate() { //@todo this should probably be optimized. set> blocksSeen(comparator); - map tagReplacement; for (size_t i = 0; i < m_items.size(); ++i) { if (m_items.at(i).type() != Tag) @@ -86,22 +85,40 @@ bool BlockDeduplicator::deduplicate() if (it == blocksSeen.end()) blocksSeen.insert(i); else - tagReplacement[m_items.at(i).data()] = m_items.at(*it).data(); + m_replacedTags[m_items.at(i).data()] = m_items.at(*it).data(); } - bool changed = false; - for (AssemblyItem& item: m_items) - if (item.type() == PushTag && tagReplacement.count(item.data())) - { - changed = true; - item.setData(tagReplacement.at(item.data())); - } - if (!changed) + if (!applyTagReplacement(m_items, m_replacedTags)) break; } return iterations > 0; } +bool BlockDeduplicator::applyTagReplacement( + AssemblyItems& _items, + map const& _replacements, + size_t _subId +) +{ + bool changed = false; + for (AssemblyItem& item: _items) + if (item.type() == PushTag) + { + size_t subId; + size_t tagId; + tie(subId, tagId) = item.splitForeignPushTag(); + if (subId != _subId) + continue; + auto it = _replacements.find(tagId); + if (it != _replacements.end()) + { + changed = true; + item.setPushTagSubIdAndTag(subId, size_t(it->second)); + } + } + return changed; +} + BlockDeduplicator::BlockIterator& BlockDeduplicator::BlockIterator::operator++() { if (it == end) diff --git a/libevmasm/BlockDeduplicator.h b/libevmasm/BlockDeduplicator.h index c48835fd4..5ecab7f3e 100644 --- a/libevmasm/BlockDeduplicator.h +++ b/libevmasm/BlockDeduplicator.h @@ -23,9 +23,12 @@ #pragma once +#include + #include #include #include +#include namespace dev { @@ -45,6 +48,18 @@ public: BlockDeduplicator(AssemblyItems& _items): m_items(_items) {} /// @returns true if something was changed bool deduplicate(); + /// @returns the tags that were replaced. + std::map const& replacedTags() const { return m_replacedTags; } + + /// Replaces all PushTag operations insied @a _items that match a key in + /// @a _replacements by the respective value. If @a _subID is not -1, only + /// apply the replacement for foreign tags from this sub id. + /// @returns true iff a replacement was performed. + static bool applyTagReplacement( + AssemblyItems& _items, + std::map const& _replacements, + size_t _subID = size_t(-1) + ); private: /// Iterator that skips tags and skips to the end if (all branches of) the control @@ -70,6 +85,7 @@ private: AssemblyItem const* replaceWith; }; + std::map m_replacedTags; AssemblyItems& m_items; }; diff --git a/libevmasm/ControlFlowGraph.h b/libevmasm/ControlFlowGraph.h index 03a1f717c..789982627 100644 --- a/libevmasm/ControlFlowGraph.h +++ b/libevmasm/ControlFlowGraph.h @@ -89,6 +89,11 @@ struct BasicBlock using BasicBlocks = std::vector; +/** + * Control flow graph optimizer. + * ASSUMES THAT WE ONLY JUMP TO TAGS THAT WERE PREVIOUSLY PUSHED. THIS IS NOT TRUE ANYMORE + * NOW THAT FUNCTION TAGS CAN BE STORED IN STORAGE. + */ class ControlFlowGraph { public: diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 39b6376c7..73a09aad7 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -474,7 +474,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireSize(2); requireDeposit(0, 1); - auto begin = m_asm.append(); + auto begin = m_asm.append(m_asm.newTag()); m_asm.append(code[0].m_asm); if (us == "WHILE") m_asm.append(Instruction::ISZERO); @@ -489,7 +489,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireDeposit(1, 1); m_asm.append(code[0].m_asm, 0); - auto begin = m_asm.append(); + auto begin = m_asm.append(m_asm.newTag()); m_asm.append(code[1].m_asm); m_asm.append(Instruction::ISZERO); auto end = m_asm.appendJumpI(); diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index bb8211ad8..eefa50c5f 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -33,11 +33,13 @@ void Compiler::compileContract( std::map const& _contracts ) { - ContractCompiler runtimeCompiler(CompilationMode::Runtime, nullptr, m_runtimeContext, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); runtimeCompiler.compileContract(_contract, _contracts); - ContractCompiler creationCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); - m_runtimeSub = creationCompiler.compileConstructor(m_runtimeContext, _contract, _contracts); + // This might modify m_runtimeContext because it can access runtime functions at + // creation time. + ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); + m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); if (m_optimize) m_context.optimise(m_optimizeRuns); @@ -54,7 +56,8 @@ void Compiler::compileClone( map const& _contracts ) { - ContractCompiler cloneCompiler(CompilationMode::Creation, &m_runtimeContext, m_context, m_optimize); + ContractCompiler runtimeCompiler(nullptr, m_runtimeContext, m_optimize); + ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); if (m_optimize) diff --git a/libsolidity/codegen/Compiler.h b/libsolidity/codegen/Compiler.h index 56849ea00..4a87de0ea 100644 --- a/libsolidity/codegen/Compiler.h +++ b/libsolidity/codegen/Compiler.h @@ -36,8 +36,8 @@ public: explicit Compiler(bool _optimize = false, unsigned _runs = 200): m_optimize(_optimize), m_optimizeRuns(_runs), - m_context(CompilationMode::Creation, &m_runtimeContext), - m_runtimeContext(CompilationMode::Runtime) + m_runtimeContext(), + m_context(&m_runtimeContext) { } void compileContract( @@ -71,9 +71,9 @@ public: private: bool const m_optimize; unsigned const m_optimizeRuns; - CompilerContext m_context; - size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. CompilerContext m_runtimeContext; + size_t m_runtimeSub = size_t(-1); ///< Identifier of the runtime sub-assembly, if present. + CompilerContext m_context; }; } diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index 3ac5bd3c9..f1d306ce0 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -92,22 +92,22 @@ eth::AssemblyItem CompilerContext::functionEntryLabelIfExists(Declaration const& return m_functionCompilationQueue.entryLabelIfExists(_declaration); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel(FunctionDefinition const& _function) +FunctionDefinition const& CompilerContext::resolveVirtualFunction(FunctionDefinition const& _function) { // Libraries do not allow inheritance and their functions can be inlined, so we should not // search the inheritance hierarchy (which will be the wrong one in case the function // is inlined). if (auto scope = dynamic_cast(_function.scope())) if (scope->isLibrary()) - return functionEntryLabel(_function); + return _function; solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, m_inheritanceHierarchy.begin()); + return resolveVirtualFunction(_function, m_inheritanceHierarchy.begin()); } -eth::AssemblyItem CompilerContext::superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base) +FunctionDefinition const& CompilerContext::superFunction(FunctionDefinition const& _function, ContractDefinition const& _base) { solAssert(!m_inheritanceHierarchy.empty(), "No inheritance hierarchy set."); - return virtualFunctionEntryLabel(_function, superContract(_base)); + return resolveVirtualFunction(_function, superContract(_base)); } FunctionDefinition const* CompilerContext::nextConstructor(ContractDefinition const& _contract) const @@ -227,7 +227,7 @@ void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) sub.injectStart(fromBigEndian(binaryVersion())); } -eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( +FunctionDefinition const& CompilerContext::resolveVirtualFunction( FunctionDefinition const& _function, vector::const_iterator _searchStart ) @@ -242,9 +242,9 @@ eth::AssemblyItem CompilerContext::virtualFunctionEntryLabel( !function->isConstructor() && FunctionType(*function).hasEqualArgumentTypes(functionType) ) - return functionEntryLabel(*function); + return *function; solAssert(false, "Super function " + name + " not found."); - return m_asm.newTag(); // not reached + return _function; // not reached } vector::const_iterator CompilerContext::superContract(ContractDefinition const& _contract) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index 8b95c9f5b..c4724ee01 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -37,10 +37,6 @@ namespace dev { namespace solidity { -/// Depending on the compilation is on the runtime code or the creation code, -/// the interpretation of internal function values differ. -enum class CompilationMode { Runtime, Creation }; - /** * Context to be shared by all units that compile the same contract. * It stores the generated bytecode and the position of identifiers in memory and on the stack. @@ -48,15 +44,13 @@ enum class CompilationMode { Runtime, Creation }; class CompilerContext { public: - CompilerContext(CompilationMode _mode, CompilerContext* _runtimeContext = nullptr) : - m_mode(_mode), m_runtimeContext(_runtimeContext) + CompilerContext(CompilerContext* _runtimeContext = nullptr) : + m_runtimeContext(_runtimeContext) { - solAssert(m_mode != CompilationMode::Runtime || !m_runtimeContext, "runtime but another runtime context provided"); - solAssert(m_mode != CompilationMode::Creation || m_runtimeContext, "creation but no runtime context provided"); + if (m_runtimeContext) + m_runtimeSub = registerSubroutine(m_runtimeContext->assembly()); } - bool isCreationPhase() const { return m_mode == CompilationMode::Creation; } - void addMagicGlobal(MagicVariableDeclaration const& _declaration); void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); @@ -80,10 +74,10 @@ public: eth::AssemblyItem functionEntryLabelIfExists(Declaration const& _declaration) const; void setInheritanceHierarchy(std::vector const& _hierarchy) { m_inheritanceHierarchy = _hierarchy; } /// @returns the entry label of the given function and takes overrides into account. - eth::AssemblyItem virtualFunctionEntryLabel(FunctionDefinition const& _function); - /// @returns the entry label of a function that overrides the given declaration from the most derived class just + FunctionDefinition const& resolveVirtualFunction(FunctionDefinition const& _function); + /// @returns the function that overrides the given declaration from the most derived class just /// above _base in the current inheritance hierarchy. - eth::AssemblyItem superFunctionEntryLabel(FunctionDefinition const& _function, ContractDefinition const& _base); + FunctionDefinition const& superFunction(FunctionDefinition const& _function, ContractDefinition const& _base); FunctionDefinition const* nextConstructor(ContractDefinition const& _contract) const; /// @returns the next function in the queue of functions that are still to be compiled @@ -123,11 +117,15 @@ public: eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data eth::AssemblyItem newTag() { return m_asm.newTag(); } + /// Adds a subroutine to the code (in the data section) + /// @returns the assembly item corresponding to the pushed subroutine, i.e. its offset in the list. + size_t registerSubroutine(eth::Assembly const& _assembly) { return size_t(m_asm.newSub(_assembly).data()); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) - /// on the stack. @returns the assembly item corresponding to the pushed subroutine, i.e. its offset. - eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { return m_asm.appendSubSize(_assembly); } + /// on the stack. @returns the pushsub assembly item. + eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { auto sub = m_asm.newSub(_assembly); m_asm.append(m_asm.newPushSubSize(size_t(sub.data()))); return sub; } + void appendSubroutineSize(size_t const& _subRoutine) { m_asm.append(m_asm.newPushSubSize(_subRoutine)); } /// Pushes the size of the final program - void appendProgramSize() { return m_asm.appendProgramSize(); } + void appendProgramSize() { m_asm.appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. @@ -159,6 +157,11 @@ public: void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. + CompilerContext* runtimeContext() { return m_runtimeContext; } + /// @returns the identifier of the runtime subroutine. + size_t runtimeSub() const { return m_runtimeSub; } + eth::Assembly const& assembly() const { return m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. @@ -185,9 +188,9 @@ public: }; private: - /// @returns the entry label of the given function - searches the inheritance hierarchy - /// startig from the given point towards the base. - eth::AssemblyItem virtualFunctionEntryLabel( + /// Searches the inheritance hierarchy towards the base starting from @a _searchStart and returns + /// the first function definition that is overwritten by _function. + FunctionDefinition const& resolveVirtualFunction( FunctionDefinition const& _function, std::vector::const_iterator _searchStart ); @@ -241,10 +244,10 @@ private: std::vector m_inheritanceHierarchy; /// Stack of current visited AST nodes, used for location attachment std::stack m_visitedNodes; - /// The current mode of the compilation - CompilationMode m_mode; /// The runtime context if in Creation mode, this is used for generating tags that would be stored into the storage and then used at runtime. CompilerContext *m_runtimeContext; + /// The index of the runtime subroutine. + size_t m_runtimeSub = -1; }; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index dedb53e7c..1e21c0208 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -340,6 +340,19 @@ void CompilerUtils::combineExternalFunctionType(bool _leftAligned) m_context << Instruction::OR; } +void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) +{ + m_context << m_context.functionEntryLabel(_function).pushTag(); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + m_context << + (u256(1) << 32) << + Instruction::MUL << + rtc->functionEntryLabel(_function).toSubAssemblyTag(m_context.runtimeSub()) << + Instruction::OR; +} + void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { // For a type extension, we need to remove all higher-order bits that we might have ignored in diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 690452f9a..2ebec81aa 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -120,6 +120,10 @@ public: void splitExternalFunctionType(bool _rightAligned); /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) void combineExternalFunctionType(bool _rightAligned); + /// Appends code that combines the construction-time (if available) and runtime function + /// entry label of the given function into a single stack slot. + /// Note: This might cause the compilation queue of the runtime context to be extended. + void pushCombinedFunctionEntryLabel(Declaration const& _function); /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 9cd893e8f..79987af67 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -60,14 +60,13 @@ void ContractCompiler::compileContract( } size_t ContractCompiler::compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map const& _contracts ) { CompilerContext::LocationSetter locationSetter(m_context, _contract); initializeContext(_contract, _contracts); - return packIntoContractCreator(_contract, _runtimeContext); + return packIntoContractCreator(_contract); } size_t ContractCompiler::compileClone( @@ -141,21 +140,31 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendBaseConstructor(*c); } -size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext) +size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) { + solAssert(!!m_runtimeCompiler, ""); + appendInitAndConstructorCode(_contract); - eth::AssemblyItem runtimeSub = m_context.addSubroutine(_runtimeContext.assembly()); + // We jump to the deploy routine because we first have to append all missing functions, + // which can cause further functions to be added to the runtime context. + eth::AssemblyItem deployRoutine = m_context.appendJumpToNew(); + + // We have to include copies of functions in the construction time and runtime context + // because of absolute jumps. + appendMissingFunctions(); + m_runtimeCompiler->appendMissingFunctions(); + + m_context << deployRoutine; + + solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); + m_context.appendSubroutineSize(m_context.runtimeSub()); // stack contains sub size - m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; + m_context << Instruction::DUP1 << m_context.runtimeSub() << u256(0) << Instruction::CODECOPY; m_context << u256(0) << Instruction::RETURN; - // note that we have to include the functions again because of absolute jump labels - appendMissingFunctions(); - - solAssert(runtimeSub.data() < numeric_limits::max(), ""); - return size_t(runtimeSub.data()); + return m_context.runtimeSub(); } void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _constructor) @@ -516,7 +525,19 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly) { solAssert(!!decl->type(), "Type of declaration required but not yet determined."); if (FunctionDefinition const* functionDef = dynamic_cast(decl)) - _assembly.append(m_context.virtualFunctionEntryLabel(*functionDef).pushTag()); + { + functionDef = &m_context.resolveVirtualFunction(*functionDef); + _assembly.append(m_context.functionEntryLabel(*functionDef).pushTag()); + // If there is a runtime context, we have to merge both labels into the same + // stack slot in case we store it in storage. + if (CompilerContext* rtc = m_context.runtimeContext()) + { + _assembly.append(u256(1) << 32); + _assembly.append(Instruction::MUL); + _assembly.append(rtc->functionEntryLabel(*functionDef).toSubAssemblyTag(m_context.runtimeSub())); + _assembly.append(Instruction::OR); + } + } else if (auto variable = dynamic_cast(decl)) { solAssert(!variable->isConstant(), ""); diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index fecf6f5ad..9e24523c0 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -38,11 +38,12 @@ namespace solidity { class ContractCompiler: private ASTConstVisitor { public: - explicit ContractCompiler(CompilationMode _mode, CompilerContext* _runtimeContext, CompilerContext& _context, bool _optimise): + explicit ContractCompiler(ContractCompiler* _runtimeCompiler, CompilerContext& _context, bool _optimise): m_optimise(_optimise), + m_runtimeCompiler(_runtimeCompiler), m_context(_context) { - m_context = CompilerContext(_mode, _runtimeContext); + m_context = CompilerContext(_runtimeCompiler ? &_runtimeCompiler->m_context : nullptr); } void compileContract( @@ -52,7 +53,6 @@ public: /// Compiles the constructor part of the contract. /// @returns the identifier of the runtime sub-assembly. size_t compileConstructor( - CompilerContext const& _runtimeContext, ContractDefinition const& _contract, std::map const& _contracts ); @@ -74,7 +74,7 @@ private: /// Adds the code that is run at creation time. Should be run after exchanging the run-time context /// with a new and initialized context. Adds the constructor code. /// @returns the identifier of the runtime sub assembly - size_t packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext); + size_t packIntoContractCreator(ContractDefinition const& _contract); /// Appends state variable initialisation and constructor code. void appendInitAndConstructorCode(ContractDefinition const& _contract); void appendBaseConstructor(FunctionDefinition const& _constructor); @@ -117,6 +117,8 @@ private: static eth::Assembly cloneRuntime(); bool const m_optimise; + /// Pointer to the runtime compiler in case this is a creation compiler. + ContractCompiler* m_runtimeCompiler = nullptr; CompilerContext& m_context; std::vector m_breakTags; ///< tag to jump to for a "break" statement std::vector m_continueTags; ///< tag to jump to for a "continue" statement diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index e3f05c21c..83c3a2c4f 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -488,6 +488,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) parameterSize += function.selfType()->sizeOnStack(); } + if (m_context.runtimeContext()) + // We have a runtime context, so we need the creation part. + m_context << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + else + // Extract the runtime part. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); m_context << returnLabel; @@ -845,9 +852,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) ); if (funType->location() == FunctionType::Location::Internal) { - m_context << m_context.functionEntryLabel( - dynamic_cast(funType->declaration()) - ).pushTag(); + FunctionDefinition const& funDef = dynamic_cast(funType->declaration()); + utils().pushCombinedFunctionEntryLabel(funDef); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1); } else @@ -883,7 +889,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) // us to link against it although we actually do not need it. auto const* function = dynamic_cast(_memberAccess.annotation().referencedDeclaration); solAssert(!!function, "Function not found in member access"); - m_context << m_context.functionEntryLabel(*function).pushTag(); + utils().pushCombinedFunctionEntryLabel(*function); } } else if (dynamic_cast(_memberAccess.annotation().type.get())) @@ -915,10 +921,10 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess) if (type.isSuper()) { solAssert(!!_memberAccess.annotation().referencedDeclaration, "Referenced declaration not resolved."); - m_context << m_context.superFunctionEntryLabel( + utils().pushCombinedFunctionEntryLabel(m_context.superFunction( dynamic_cast(*_memberAccess.annotation().referencedDeclaration), type.contractDefinition() - ).pushTag(); + )); } else { @@ -1203,7 +1209,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) } } else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) - m_context << m_context.virtualFunctionEntryLabel(*functionDef).pushTag(); + utils().pushCombinedFunctionEntryLabel(m_context.resolveVirtualFunction(*functionDef)); else if (auto variable = dynamic_cast(declaration)) appendVariable(*variable, static_cast(_identifier)); else if (auto contract = dynamic_cast(declaration)) @@ -1266,6 +1272,17 @@ void ExpressionCompiler::appendCompareOperatorCode(Token::Value _operator, Type { if (_operator == Token::Equal || _operator == Token::NotEqual) { + if (FunctionType const* funType = dynamic_cast(&_type)) + { + if (funType->location() == FunctionType::Location::Internal) + { + // We have to remove the upper bits (construction time value) because they might + // be "unknown" in one of the operands and not in the other. + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + m_context << Instruction::SWAP1; + m_context << ((u256(1) << 32) - 1) << Instruction::AND; + } + } m_context << Instruction::EQ; if (_operator == Token::NotEqual) m_context << Instruction::ISZERO; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index 519027bcd..c86de43fe 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -37,6 +37,7 @@ #include #include +#include #include #include @@ -590,9 +591,19 @@ void CompilerStack::compileContract( compiledContract.runtimeObject = compiler->runtimeObject(); _compiledContracts[compiledContract.contract] = &compiler->assembly(); - Compiler cloneCompiler(_optimize, _runs); - cloneCompiler.compileClone(_contract, _compiledContracts); - compiledContract.cloneObject = cloneCompiler.assembledObject(); + try + { + Compiler cloneCompiler(_optimize, _runs); + cloneCompiler.compileClone(_contract, _compiledContracts); + compiledContract.cloneObject = cloneCompiler.assembledObject(); + } + catch (eth::AssemblyException const& _e) + { + // In some cases (if the constructor requests a runtime function), it is not + // possible to compile the clone. + + // TODO: Report error / warning + } } std::string CompilerStack::defaultContractName() const diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index b05316dd4..8f49b9c46 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7777,6 +7777,48 @@ BOOST_AUTO_TEST_CASE(store_function_in_constructor) BOOST_CHECK(callContractFunction("result_in_constructor()") == encodeArgs(u256(4))); } +// TODO: store bound internal library functions + +BOOST_AUTO_TEST_CASE(store_internal_unused_function_in_constructor) +{ + char const* sourceCode = R"( + contract C { + function () internal returns (uint) x; + function C () { + x = unused; + } + function unused() internal returns (uint) { + return 7; + } + function t() returns (uint) { + return x(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7))); +} + +BOOST_AUTO_TEST_CASE(store_internal_unused_library_function_in_constructor) +{ + char const* sourceCode = R"( + library L { function x() internal returns (uint) { return 7; } } + contract C { + function () internal returns (uint) x; + function C () { + x = L.x; + } + function t() returns (uint) { + return x(); + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("t()") == encodeArgs(u256(7))); +} + BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) { char const* sourceCode = R"( @@ -7799,6 +7841,27 @@ BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime) BOOST_CHECK(callContractFunction("initial()") == encodeArgs(u256(4))); } +BOOST_AUTO_TEST_CASE(same_function_in_construction_and_runtime_equality_check) +{ + char const* sourceCode = R"( + contract C { + function (uint) internal returns (uint) x; + function C() { + x = double; + } + function test() returns (bool) { + return x == double; + } + function double(uint _arg) returns (uint _ret) { + _ret = _arg * 2; + } + } + )"; + + compileAndRun(sourceCode, 0, "C"); + BOOST_CHECK(callContractFunction("test()") == encodeArgs(true)); +} + BOOST_AUTO_TEST_CASE(function_type_library_internal) { char const* sourceCode = R"( diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index 09d556a51..e9a05745e 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -136,7 +136,7 @@ bytes compileFirstExpression( FirstExpressionExtractor extractor(*contract); BOOST_REQUIRE(extractor.expression() != nullptr); - CompilerContext context { CompilationMode::Runtime /* probably simpler */ }; + CompilerContext context; context.resetVisitedNodes(contract); context.setInheritanceHierarchy(inheritanceHierarchy); unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack From e51f852504556f952ae1350c070409e3c4981cc0 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 11:41:50 +0100 Subject: [PATCH 100/125] Converted sub assembly to smart pointer. --- libevmasm/Assembly.cpp | 54 ++++++++++++------- libevmasm/Assembly.h | 33 ++++++------ libevmasm/AssemblyItem.cpp | 12 +++-- liblll/CodeFragment.cpp | 3 +- libsolidity/codegen/CompilerContext.cpp | 14 ++--- libsolidity/codegen/CompilerContext.h | 62 +++++++++++----------- libsolidity/codegen/ContractCompiler.cpp | 15 +++--- libsolidity/codegen/ContractCompiler.h | 2 +- libsolidity/codegen/ExpressionCompiler.cpp | 5 +- test/libsolidity/SolidityEndToEndTest.cpp | 2 +- test/libsolidity/SolidityOptimizer.cpp | 18 +++++++ 11 files changed, 133 insertions(+), 87 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 23a8180b6..0ee3f4216 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -75,17 +75,17 @@ string Assembly::out() const return ret.str(); } -unsigned Assembly::bytesRequired() const +unsigned Assembly::bytesRequired(unsigned subTagSize) const { - for (unsigned br = 1;; ++br) + for (unsigned tagSize = subTagSize;; ++tagSize) { unsigned ret = 1; for (auto const& i: m_data) ret += i.second.size(); for (AssemblyItem const& i: m_items) - ret += i.bytesRequired(br); - if (dev::bytesRequired(ret) <= br) + ret += i.bytesRequired(tagSize); + if (dev::bytesRequired(ret) <= tagSize) return ret; } } @@ -132,13 +132,19 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con if (i.data() == 0) _out << " PUSH [ErrorTag]"; else - _out << " PUSH [tag" << dec << i.data() << "]"; + { + size_t subId = i.splitForeignPushTag().first; + if (subId == size_t(-1)) + _out << " PUSH [tag" << dec << i.splitForeignPushTag().second << "]"; + else + _out << " PUSH [tag" << dec << subId << ":" << i.splitForeignPushTag().second << "]"; + } break; case PushSub: - _out << " PUSH [$" << h256(i.data()).abridgedMiddle() << "]"; + _out << " PUSH [$" << size_t(i.data()) << "]"; break; case PushSubSize: - _out << " PUSH #[$" << h256(i.data()).abridgedMiddle() << "]"; + _out << " PUSH #[$" << size_t(i.data()) << "]"; break; case PushProgramSize: _out << " PUSHSIZE"; @@ -167,7 +173,7 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con for (size_t i = 0; i < m_subs.size(); ++i) { _out << _prefix << " " << hex << i << ": " << endl; - m_subs[i].stream(_out, _prefix + " ", _sourceCodes); + m_subs[i]->stream(_out, _prefix + " ", _sourceCodes); } } return _out; @@ -266,7 +272,7 @@ Json::Value Assembly::streamAsmJson(ostream& _out, StringMap const& _sourceCodes { std::stringstream hexStr; hexStr << hex << i; - data[hexStr.str()] = m_subs[i].stream(_out, "", _sourceCodes, true); + data[hexStr.str()] = m_subs[i]->stream(_out, "", _sourceCodes, true); } root[".data"] = data; _out << root; @@ -317,7 +323,7 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) { for (size_t subId = 0; subId < m_subs.size(); ++subId) { - map subTagReplacements = m_subs[subId].optimiseInternal(false, _runs); + map subTagReplacements = m_subs[subId]->optimiseInternal(false, _runs); BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); } @@ -385,7 +391,11 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) } iter = end; } - + if (optimisedItems.size() < m_items.size()) + { + m_items = move(optimisedItems); + count++; + } } } @@ -404,20 +414,28 @@ LinkerObject const& Assembly::assemble() const if (!m_assembledObject.bytecode.empty()) return m_assembledObject; + size_t subTagSize = 1; + for (auto const& sub: m_subs) + { + sub->assemble(); + if (!sub->m_tagPositionsInBytecode.empty()) + subTagSize = max(subTagSize, *max_element(sub->m_tagPositionsInBytecode.begin(), sub->m_tagPositionsInBytecode.end())); + } + LinkerObject& ret = m_assembledObject; - unsigned totalBytes = bytesRequired(); + size_t bytesRequiredForCode = bytesRequired(subTagSize); m_tagPositionsInBytecode = vector(m_usedTags, -1); map> tagRef; multimap dataRef; multimap subRef; vector sizeRef; ///< Pointers to code locations where the size of the program is inserted - unsigned bytesPerTag = dev::bytesRequired(totalBytes); + unsigned bytesPerTag = dev::bytesRequired(bytesRequiredForCode); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; - unsigned bytesRequiredIncludingData = bytesRequired(); + unsigned bytesRequiredIncludingData = bytesRequiredForCode + 1; for (auto const& sub: m_subs) - bytesRequiredIncludingData += sub.assemble().bytecode.size(); + bytesRequiredIncludingData += sub->assemble().bytecode.size(); unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData); byte dataRefPush = (byte)Instruction::PUSH1 - 1 + bytesPerDataRef; @@ -475,7 +493,7 @@ LinkerObject const& Assembly::assemble() const break; case PushSubSize: { - auto s = m_subs.at(size_t(i.data())).assemble().bytecode.size(); + auto s = m_subs.at(size_t(i.data()))->assemble().bytecode.size(); i.setPushedValue(u256(s)); byte b = max(1, dev::bytesRequired(s)); ret.bytecode.push_back((byte)Instruction::PUSH1 - 1 + b); @@ -520,7 +538,7 @@ LinkerObject const& Assembly::assemble() const bytesRef r(ret.bytecode.data() + ref->second, bytesPerDataRef); toBigEndian(ret.bytecode.size(), r); } - ret.append(m_subs[i].assemble()); + ret.append(m_subs[i]->assemble()); } for (auto const& i: tagRef) { @@ -531,7 +549,7 @@ LinkerObject const& Assembly::assemble() const std::vector const& tagPositions = subId == size_t(-1) ? m_tagPositionsInBytecode : - m_subs[subId].m_tagPositionsInBytecode; + m_subs[subId]->m_tagPositionsInBytecode; assertThrow(tagId < tagPositions.size(), AssemblyException, "Reference to non-existing tag."); size_t pos = tagPositions[tagId]; assertThrow(pos != size_t(-1), AssemblyException, "Reference to tag without position."); diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 0ae81e1d5..3ce82ccee 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -14,30 +14,32 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file Assembly.h - * @author Gav Wood - * @date 2014 - */ #pragma once -#include -#include -#include -#include -#include #include #include #include #include -#include "Exceptions.h" +#include + +#include +#include +#include + #include +#include +#include +#include + namespace dev { namespace eth { +using AssemblyPointer = std::shared_ptr; + class Assembly { public: @@ -46,9 +48,9 @@ public: AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newData(bytes const& _data) { h256 h(dev::keccak256(asString(_data))); m_data[h] = _data; return AssemblyItem(PushData, h); } - AssemblyItem newSub(Assembly const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } - Assembly const& sub(size_t _sub) const { return m_subs.at(_sub); } - Assembly& sub(size_t _sub) { return m_subs.at(_sub); } + AssemblyItem newSub(AssemblyPointer const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } + Assembly const& sub(size_t _sub) const { return *m_subs.at(_sub); } + Assembly& sub(size_t _sub) { return *m_subs.at(_sub); } AssemblyItem newPushString(std::string const& _data) { h256 h(dev::keccak256(_data)); m_strings[h] = _data; return AssemblyItem(PushString, h); } AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem newPushLibraryAddress(std::string const& _identifier); @@ -58,7 +60,6 @@ public: AssemblyItem const& append(AssemblyItem const& _i); AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } - AssemblyItem appendSubSize(Assembly const& _a) { auto ret = newSub(_a); append(newPushSubSize(ret.data())); return ret; } /// Pushes the final size of the current assembly itself. Use this when the code is modified /// after compilation and CODESIZE is not an option. void appendProgramSize() { append(AssemblyItem(PushProgramSize)); } @@ -115,7 +116,7 @@ protected: std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } - unsigned bytesRequired() const; + unsigned bytesRequired(unsigned subTagSize) const; private: Json::Value streamAsmJson(std::ostream& _out, StringMap const& _sourceCodes) const; @@ -127,7 +128,7 @@ protected: unsigned m_usedTags = 1; AssemblyItems m_items; std::map m_data; - std::vector m_subs; + std::vector> m_subs; std::map m_strings; std::map m_libraries; ///< Identifiers of libraries to be linked. diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index 2eae20aff..bc8c87aa1 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -127,8 +127,14 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushString" << hex << (unsigned)_item.data(); break; case PushTag: - _out << " PushTag " << _item.data(); + { + size_t subId = _item.splitForeignPushTag().first; + if (subId == size_t(-1)) + _out << " PushTag " << _item.splitForeignPushTag().second; + else + _out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second; break; + } case Tag: _out << " Tag " << _item.data(); break; @@ -136,10 +142,10 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushData " << hex << (unsigned)_item.data(); break; case PushSub: - _out << " PushSub " << hex << h256(_item.data()).abridgedMiddle(); + _out << " PushSub " << hex << size_t(_item.data()); break; case PushSubSize: - _out << " PushSubSize " << hex << h256(_item.data()).abridgedMiddle(); + _out << " PushSubSize " << hex << size_t(_item.data()); break; case PushProgramSize: _out << " PushProgramSize"; diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 73a09aad7..d757dcf1d 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -520,7 +520,8 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireMaxSize(3); requireDeposit(1, 1); - auto subPush = m_asm.appendSubSize(code[0].assembly(ns)); + auto subPush = m_asm.newSub(make_shared(code[0].assembly(ns))); + m_asm.append(m_asm.newPushSubSize(subPush.data())); m_asm.append(Instruction::DUP1); if (code.size() == 3) { diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp index f1d306ce0..c76db2a64 100644 --- a/libsolidity/codegen/CompilerContext.cpp +++ b/libsolidity/codegen/CompilerContext.cpp @@ -60,8 +60,8 @@ void CompilerContext::startFunction(Declaration const& _function) void CompilerContext::addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent) { - solAssert(m_asm.deposit() >= 0 && unsigned(m_asm.deposit()) >= _offsetToCurrent, ""); - m_localVariables[&_declaration] = unsigned(m_asm.deposit()) - _offsetToCurrent; + solAssert(m_asm->deposit() >= 0 && unsigned(m_asm->deposit()) >= _offsetToCurrent, ""); + m_localVariables[&_declaration] = unsigned(m_asm->deposit()) - _offsetToCurrent; } void CompilerContext::removeVariable(VariableDeclaration const& _declaration) @@ -145,12 +145,12 @@ unsigned CompilerContext::baseStackOffsetOfVariable(Declaration const& _declarat unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const { - return m_asm.deposit() - _baseOffset - 1; + return m_asm->deposit() - _baseOffset - 1; } unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const { - return m_asm.deposit() - _offset - 1; + return m_asm->deposit() - _offset - 1; } pair CompilerContext::storageLocationOfVariable(const Declaration& _declaration) const @@ -217,12 +217,12 @@ void CompilerContext::appendInlineAssembly( return true; }; - solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, m_asm, identifierAccess), ""); + solAssert(assembly::InlineAssemblyStack().parseAndAssemble(*assembly, *m_asm, identifierAccess), ""); } void CompilerContext::injectVersionStampIntoSub(size_t _subIndex) { - eth::Assembly& sub = m_asm.sub(_subIndex); + eth::Assembly& sub = m_asm->sub(_subIndex); sub.injectStart(Instruction::POP); sub.injectStart(fromBigEndian(binaryVersion())); } @@ -257,7 +257,7 @@ vector::const_iterator CompilerContext::superContract void CompilerContext::updateSourceLocation() { - m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); + m_asm->setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->location()); } eth::AssemblyItem CompilerContext::FunctionCompilationQueue::entryLabel( diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index c4724ee01..ee3fb0685 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -44,11 +44,12 @@ namespace solidity { class CompilerContext { public: - CompilerContext(CompilerContext* _runtimeContext = nullptr) : + CompilerContext(CompilerContext* _runtimeContext = nullptr): + m_asm(std::make_shared()), m_runtimeContext(_runtimeContext) { if (m_runtimeContext) - m_runtimeSub = registerSubroutine(m_runtimeContext->assembly()); + m_runtimeSub = size_t(m_asm->newSub(m_runtimeContext->m_asm).data()); } void addMagicGlobal(MagicVariableDeclaration const& _declaration); @@ -59,9 +60,9 @@ public: void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } eth::Assembly const& compiledContract(ContractDefinition const& _contract) const; - void setStackOffset(int _offset) { m_asm.setDeposit(_offset); } - void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); } - unsigned stackHeight() const { solAssert(m_asm.deposit() >= 0, ""); return unsigned(m_asm.deposit()); } + void setStackOffset(int _offset) { m_asm->setDeposit(_offset); } + void adjustStackOffset(int _adjustment) { m_asm->adjustDeposit(_adjustment); } + unsigned stackHeight() const { solAssert(m_asm->deposit() >= 0, ""); return unsigned(m_asm->deposit()); } bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration) != 0; } bool isLocalVariable(Declaration const* _declaration) const; @@ -102,34 +103,33 @@ public: std::pair storageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag - eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } + eth::AssemblyItem appendConditionalJump() { return m_asm->appendJumpI().tag(); } /// Appends a JUMPI instruction to @a _tag - CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJumpI(_tag); return *this; } + CompilerContext& appendConditionalJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJumpI(_tag); return *this; } /// Appends a JUMP to a new tag and @returns the tag - eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } + eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); } /// Appends a JUMP to a tag already on the stack CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Returns an "ErrorTag" - eth::AssemblyItem errorTag() { return m_asm.errorTag(); } + eth::AssemblyItem errorTag() { return m_asm->errorTag(); } /// Appends a JUMP to a specific tag - CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } + CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm->appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. - eth::AssemblyItem pushNewTag() { return m_asm.append(m_asm.newPushTag()).tag(); } + eth::AssemblyItem pushNewTag() { return m_asm->append(m_asm->newPushTag()).tag(); } /// @returns a new tag without pushing any opcodes or data - eth::AssemblyItem newTag() { return m_asm.newTag(); } - /// Adds a subroutine to the code (in the data section) - /// @returns the assembly item corresponding to the pushed subroutine, i.e. its offset in the list. - size_t registerSubroutine(eth::Assembly const& _assembly) { return size_t(m_asm.newSub(_assembly).data()); } + eth::AssemblyItem newTag() { return m_asm->newTag(); } /// Adds a subroutine to the code (in the data section) and pushes its size (via a tag) /// on the stack. @returns the pushsub assembly item. - eth::AssemblyItem addSubroutine(eth::Assembly const& _assembly) { auto sub = m_asm.newSub(_assembly); m_asm.append(m_asm.newPushSubSize(size_t(sub.data()))); return sub; } - void appendSubroutineSize(size_t const& _subRoutine) { m_asm.append(m_asm.newPushSubSize(_subRoutine)); } + eth::AssemblyItem addSubroutine(eth::AssemblyPointer const& _assembly) { auto sub = m_asm->newSub(_assembly); m_asm->append(m_asm->newPushSubSize(size_t(sub.data()))); return sub; } + void pushSubroutineSize(size_t _subRoutine) { m_asm->append(m_asm->newPushSubSize(_subRoutine)); } + /// Pushes the offset of the subroutine. + void pushSubroutineOffset(size_t _subRoutine) { m_asm->append(eth::AssemblyItem(eth::PushSub, _subRoutine)); } /// Pushes the size of the final program - void appendProgramSize() { m_asm.appendProgramSize(); } + void appendProgramSize() { m_asm->appendProgramSize(); } /// Adds data to the data section, pushes a reference to the stack - eth::AssemblyItem appendData(bytes const& _data) { return m_asm.append(_data); } + eth::AssemblyItem appendData(bytes const& _data) { return m_asm->append(_data); } /// Appends the address (virtual, will be filled in by linker) of a library. - void appendLibraryAddress(std::string const& _identifier) { m_asm.appendLibraryAddress(_identifier); } + void appendLibraryAddress(std::string const& _identifier) { m_asm->appendLibraryAddress(_identifier); } /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes @@ -138,10 +138,10 @@ public: void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } - CompilerContext& operator<<(Instruction _instruction) { m_asm.append(_instruction); return *this; } - CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } - CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm->append(_item); return *this; } + CompilerContext& operator<<(Instruction _instruction) { m_asm->append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm->append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm->append(_data); return *this; } /// Appends inline assembly. @a _replacements are string-matching replacements that are performed /// prior to parsing the inline assembly. @@ -155,27 +155,27 @@ public: /// Prepends "PUSH POP" void injectVersionStampIntoSub(size_t _subIndex); - void optimise(unsigned _runs = 200) { m_asm.optimise(true, true, _runs); } + void optimise(unsigned _runs = 200) { m_asm->optimise(true, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() { return m_runtimeContext; } /// @returns the identifier of the runtime subroutine. size_t runtimeSub() const { return m_runtimeSub; } - eth::Assembly const& assembly() const { return m_asm; } + eth::Assembly const& assembly() const { return *m_asm; } /// @returns non-const reference to the underlying assembly. Should be avoided in favour of /// wrappers in this class. - eth::Assembly& nonConstAssembly() { return m_asm; } + eth::Assembly& nonConstAssembly() { return *m_asm; } /// @arg _sourceCodes is the map of input files to source code strings /// @arg _inJsonFormat shows whether the out should be in Json format Json::Value streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap(), bool _inJsonFormat = false) const { - return m_asm.stream(_stream, "", _sourceCodes, _inJsonFormat); + return m_asm->stream(_stream, "", _sourceCodes, _inJsonFormat); } - eth::LinkerObject const& assembledObject() { return m_asm.assemble(); } - eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm.sub(_subIndex).assemble(); } + eth::LinkerObject const& assembledObject() { return m_asm->assemble(); } + eth::LinkerObject const& assembledRuntimeObject(size_t _subIndex) { return m_asm->sub(_subIndex).assemble(); } /** * Helper class to pop the visited nodes stack when a scope closes @@ -231,7 +231,7 @@ private: mutable std::queue m_functionsToCompile; } m_functionCompilationQueue; - eth::Assembly m_asm; + eth::AssemblyPointer m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 79987af67..2e3f5d7fe 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -79,7 +79,7 @@ size_t ContractCompiler::compileClone( appendInitAndConstructorCode(_contract); //@todo determine largest return size of all runtime functions - eth::AssemblyItem runtimeSub = m_context.addSubroutine(cloneRuntime()); + auto runtimeSub = m_context.addSubroutine(cloneRuntime()); // stack contains sub size m_context << Instruction::DUP1 << runtimeSub << u256(0) << Instruction::CODECOPY; @@ -87,7 +87,6 @@ size_t ContractCompiler::compileClone( appendMissingFunctions(); - solAssert(runtimeSub.data() < numeric_limits::max(), ""); return size_t(runtimeSub.data()); } @@ -158,10 +157,10 @@ size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _cont m_context << deployRoutine; solAssert(m_context.runtimeSub() != size_t(-1), "Runtime sub not registered"); - m_context.appendSubroutineSize(m_context.runtimeSub()); - - // stack contains sub size - m_context << Instruction::DUP1 << m_context.runtimeSub() << u256(0) << Instruction::CODECOPY; + m_context.pushSubroutineSize(m_context.runtimeSub()); + m_context << Instruction::DUP1; + m_context.pushSubroutineOffset(m_context.runtimeSub()); + m_context << u256(0) << Instruction::CODECOPY; m_context << u256(0) << Instruction::RETURN; return m_context.runtimeSub(); @@ -893,7 +892,7 @@ void ContractCompiler::compileExpression(Expression const& _expression, TypePoin CompilerUtils(m_context).convertType(*_expression.annotation().type, *_targetType); } -eth::Assembly ContractCompiler::cloneRuntime() +eth::AssemblyPointer ContractCompiler::cloneRuntime() { eth::Assembly a; a << Instruction::CALLDATASIZE; @@ -911,5 +910,5 @@ eth::Assembly ContractCompiler::cloneRuntime() a.appendJumpI(a.errorTag()); //@todo adjust for larger return values, make this dynamic. a << u256(0x20) << u256(0) << Instruction::RETURN; - return a; + return make_shared(a); } diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 9e24523c0..2244a3be5 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -114,7 +114,7 @@ private: void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); /// @returns the runtime assembly for clone contracts. - static eth::Assembly cloneRuntime(); + static eth::AssemblyPointer cloneRuntime(); bool const m_optimise; /// Pointer to the runtime compiler in case this is a creation compiler. diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp index 83c3a2c4f..7a3285282 100644 --- a/libsolidity/codegen/ExpressionCompiler.cpp +++ b/libsolidity/codegen/ExpressionCompiler.cpp @@ -528,8 +528,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) // copy the contract's code into memory eth::Assembly const& assembly = m_context.compiledContract(contract); utils().fetchFreeMemoryPointer(); + // TODO we create a copy here, which is actually what we want. + // This should be revisited at the point where we fix + // https://github.com/ethereum/solidity/issues/1092 // pushes size - eth::AssemblyItem subroutine = m_context.addSubroutine(assembly); + auto subroutine = m_context.addSubroutine(make_shared(assembly)); m_context << Instruction::DUP1 << subroutine; m_context << Instruction::DUP4 << Instruction::CODECOPY; diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 8f49b9c46..c01d11d2c 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7757,7 +7757,7 @@ BOOST_AUTO_TEST_CASE(store_function_in_constructor) { char const* sourceCode = R"( contract C { - uint result_in_constructor; + uint public result_in_constructor; function (uint) internal returns (uint) x; function C () { x = double; diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 562b78590..976976b26 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -1238,6 +1238,24 @@ BOOST_AUTO_TEST_CASE(inconsistency) compareVersions("trigger()"); } +BOOST_AUTO_TEST_CASE(dead_code_elimination_across_assemblies) +{ + // This tests that a runtime-function that is stored in storage in the constructor + // is not removed as part of dead code elimination. + char const* sourceCode = R"( + contract DCE { + function () internal returns (uint) stored; + function DCE() { + stored = f; + } + function f() internal returns (uint) { return 7; } + function test() returns (uint) { return stored(); } + } + )"; + compileBothVersions(sourceCode); + compareVersions("test()"); +} + BOOST_AUTO_TEST_SUITE_END() } From f3d0433ec3aca99f6d9212da944c6fc51cb9292a Mon Sep 17 00:00:00 2001 From: Yoichi Hirai Date: Fri, 11 Nov 2016 12:02:32 +0100 Subject: [PATCH 101/125] test: add a test about external function type taking/returning internal functions --- .../SolidityNameAndTypeResolution.cpp | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp index 5916ed10f..865eb7cec 100644 --- a/test/libsolidity/SolidityNameAndTypeResolution.cpp +++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp @@ -4215,6 +4215,26 @@ BOOST_AUTO_TEST_CASE(call_value_on_non_payable_function_type) BOOST_CHECK(expectError(text) == Error::Type::TypeError); } +BOOST_AUTO_TEST_CASE(external_function_type_returning_internal) +{ + char const* text = R"( + contract C { + function() external returns (function () internal) x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + +BOOST_AUTO_TEST_CASE(external_function_type_taking_internal) +{ + char const* text = R"( + contract C { + function(function () internal) external x; + } + )"; + BOOST_CHECK(expectError(text) == Error::Type::TypeError); +} + BOOST_AUTO_TEST_CASE(call_value_on_payable_function_type) { char const* text = R"( From 22b4d1b29a17c6a6360d6d6e42860bd621a7bfd3 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 12:07:30 +0100 Subject: [PATCH 102/125] Check that no internals are used in any external function type. --- libsolidity/analysis/ReferencesResolver.cpp | 9 +++++++- libsolidity/analysis/TypeChecker.cpp | 8 +++++++ libsolidity/analysis/TypeChecker.h | 1 + libsolidity/ast/Types.cpp | 24 +++++++++++++++++++++ libsolidity/ast/Types.h | 4 ++++ 5 files changed, 45 insertions(+), 1 deletion(-) diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp index c49af013a..a0768a341 100644 --- a/libsolidity/analysis/ReferencesResolver.cpp +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -96,7 +96,14 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName) } if (_typeName.isPayable() && _typeName.visibility() != VariableDeclaration::Visibility::External) - fatalTypeError(_typeName.location(), "Only external function types can be payable."); + fatalTypeError(_typeName.location(), "Only external function types can be payable."); + if (_typeName.visibility() == VariableDeclaration::Visibility::External) + for (auto const& t: _typeName.parameterTypes() + _typeName.returnParameterTypes()) + { + solAssert(t->annotation().type, "Type not set for parameter."); + if (!t->annotation().type->canBeUsedExternally(false)) + fatalTypeError(t->location(), "Internal type cannot be used for external function type."); + } _typeName.annotation().type = make_shared(_typeName); } diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp index f934b2c8d..8b6d45e2f 100644 --- a/libsolidity/analysis/TypeChecker.cpp +++ b/libsolidity/analysis/TypeChecker.cpp @@ -574,6 +574,14 @@ bool TypeChecker::visit(EventDefinition const& _eventDef) return false; } +void TypeChecker::endVisit(FunctionTypeName const& _funType) +{ + FunctionType const& fun = dynamic_cast(*_funType.annotation().type); + if (fun.location() == FunctionType::Location::External) + if (!fun.canBeUsedExternally(false)) + typeError(_funType.location(), "External function type uses internal types."); +} + bool TypeChecker::visit(InlineAssembly const& _inlineAssembly) { // Inline assembly does not have its own type-checking phase, so we just run the diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h index be1e02be5..5874bb502 100644 --- a/libsolidity/analysis/TypeChecker.h +++ b/libsolidity/analysis/TypeChecker.h @@ -87,6 +87,7 @@ private: /// case this is a base constructor call. void visitManually(ModifierInvocation const& _modifier, std::vector const& _bases); virtual bool visit(EventDefinition const& _eventDef) override; + virtual void endVisit(FunctionTypeName const& _funType) override; virtual bool visit(InlineAssembly const& _inlineAssembly) override; virtual bool visit(IfStatement const& _ifStatement) override; virtual bool visit(WhileStatement const& _whileStatement) override; diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index e18735d96..c1ae183e9 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1265,6 +1265,7 @@ TypePointer ArrayType::decodingType() const TypePointer ArrayType::interfaceType(bool _inLibrary) const { + // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) if (_inLibrary && location() == DataLocation::Storage) return shared_from_this(); @@ -1282,6 +1283,21 @@ TypePointer ArrayType::interfaceType(bool _inLibrary) const return make_shared(DataLocation::Memory, baseExt, m_length); } +bool ArrayType::canBeUsedExternally(bool _inLibrary) const +{ + // Note: This has to fulfill canBeUsedExternally(_inLibrary) == !!interfaceType(_inLibrary) + if (_inLibrary && location() == DataLocation::Storage) + return true; + else if (m_arrayKind != ArrayKind::Ordinary) + return true; + else if (!m_baseType->canBeUsedExternally(_inLibrary)) + return false; + else if (m_baseType->category() == Category::Array && m_baseType->isDynamicallySized()) + return false; + else + return true; +} + u256 ArrayType::memorySize() const { solAssert(!isDynamicallySized(), ""); @@ -1815,11 +1831,19 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); + solAssert( + m_location != Location::External || t->annotation().type->canBeUsedExternally(false), + "Internal type used as parameter for external function." + ); m_parameterTypes.push_back(t->annotation().type); } for (auto const& t: _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for return parameter."); + solAssert( + m_location != Location::External || t->annotation().type->canBeUsedExternally(false), + "Internal type used as return parameter for external function." + ); m_returnParameterTypes.push_back(t->annotation().type); } } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 9831bc42e..84e566635 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -254,6 +254,9 @@ public: /// @param _inLibrary if set, returns types as used in a library, e.g. struct and contract types /// are returned without modification. virtual TypePointer interfaceType(bool /*_inLibrary*/) const { return TypePointer(); } + /// @returns true iff this type can be passed on via calls (to libraries if _inLibrary is true), + /// should be have identical to !!interfaceType(_inLibrary) but might do optimizations. + virtual bool canBeUsedExternally(bool _inLibrary) const { return !!interfaceType(_inLibrary); } private: /// @returns a member list containing all members added to this type by `using for` directives. @@ -580,6 +583,7 @@ public: virtual TypePointer encodingType() const override; virtual TypePointer decodingType() const override; virtual TypePointer interfaceType(bool _inLibrary) const override; + virtual bool canBeUsedExternally(bool _inLibrary) const override; /// @returns true if this is a byte array or a string bool isByteArray() const { return m_arrayKind != ArrayKind::Ordinary; } From 0335ed4cb476ece63224a96c8ab660116ff08c3a Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 14:11:07 +0100 Subject: [PATCH 103/125] Simple peephole optimizer that is activated even if not requested. --- libevmasm/Assembly.cpp | 33 ++++-- libevmasm/Assembly.h | 3 +- libevmasm/PeepholeOptimiser.cpp | 146 ++++++++++++++++++++++++++ libevmasm/PeepholeOptimiser.h | 53 ++++++++++ libsolidity/codegen/Compiler.cpp | 6 +- libsolidity/codegen/CompilerContext.h | 2 +- 6 files changed, 226 insertions(+), 17 deletions(-) create mode 100644 libevmasm/PeepholeOptimiser.cpp create mode 100644 libevmasm/PeepholeOptimiser.h diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 0ee3f4216..a813a3a78 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -20,13 +20,17 @@ */ #include "Assembly.h" -#include + #include #include +#include #include #include #include + +#include #include + using namespace std; using namespace dev; using namespace dev::eth; @@ -314,16 +318,15 @@ void Assembly::injectStart(AssemblyItem const& _i) Assembly& Assembly::optimise(bool _enable, bool _isCreation, size_t _runs) { - if (_enable) - optimiseInternal(_isCreation, _runs); + optimiseInternal(_enable, _isCreation, _runs); return *this; } -map Assembly::optimiseInternal(bool _isCreation, size_t _runs) +map Assembly::optimiseInternal(bool _enable, bool _isCreation, size_t _runs) { for (size_t subId = 0; subId < m_subs.size(); ++subId) { - map subTagReplacements = m_subs[subId]->optimiseInternal(false, _runs); + map subTagReplacements = m_subs[subId]->optimiseInternal(_enable, false, _runs); BlockDeduplicator::applyTagReplacement(m_items, subTagReplacements, subId); } @@ -333,6 +336,13 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) { count = 0; + PeepholeOptimiser peepOpt(m_items); + if (peepOpt.optimise()) + count++; + + if (!_enable) + continue; + // This only modifies PushTags, we have to run again to actually remove code. BlockDeduplicator dedup(m_items); if (dedup.deduplicate()) @@ -399,12 +409,13 @@ map Assembly::optimiseInternal(bool _isCreation, size_t _runs) } } - total += ConstantOptimisationMethod::optimiseConstants( - _isCreation, - _isCreation ? 1 : _runs, - *this, - m_items - ); + if (_enable) + total += ConstantOptimisationMethod::optimiseConstants( + _isCreation, + _isCreation ? 1 : _runs, + *this, + m_items + ); return tagReplacements; } diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h index 3ce82ccee..f3c56610b 100644 --- a/libevmasm/Assembly.h +++ b/libevmasm/Assembly.h @@ -101,6 +101,7 @@ public: /// execution gas usage is optimised. @a _isCreation should be true for the top-level assembly. /// @a _runs specifes an estimate on how often each opcode in this assembly will be executed, /// i.e. use a small value to optimise for size and a large value to optimise for runtime. + /// If @a _enable is not set, will perform some simple peephole optimizations. Assembly& optimise(bool _enable, bool _isCreation = true, size_t _runs = 200); Json::Value stream( std::ostream& _out, @@ -112,7 +113,7 @@ public: protected: /// Does the same operations as @a optimise, but should only be applied to a sub and /// returns the replaced tags. - std::map optimiseInternal(bool _isCreation, size_t _runs); + std::map optimiseInternal(bool _enable, bool _isCreation, size_t _runs); std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); } diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp new file mode 100644 index 000000000..91b0ece18 --- /dev/null +++ b/libevmasm/PeepholeOptimiser.cpp @@ -0,0 +1,146 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @file PeepholeOptimiser.h + * Performs local optimising code changes to assembly. + */ + +#include "PeepholeOptimiser.h" + +#include +#include + +using namespace std; +using namespace dev::eth; +using namespace dev; + +// TODO: Extend this to use the tools from ExpressionClasses.cpp + +struct Identity +{ + static size_t windowSize() { return 1; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + *_out = *_in; + return true; + } +}; + +struct PushPop +{ + static size_t windowSize() { return 2; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator) + { + auto t = _in[0].type(); + if (_in[1] == Instruction::POP && ( + SemanticInformation::isDupInstruction(_in[0]) || + t == Push || t == PushString || t == PushTag || t == PushSub || + t == PushSubSize || t == PushProgramSize || t == PushData || t == PushLibraryAddress + )) + return true; + else + return false; + } +}; + +struct DoubleSwap +{ + static size_t windowSize() { return 2; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator) + { + if (_in[0] == _in[1] && SemanticInformation::isSwapInstruction(_in[0])) + return true; + else + return false; + } +}; + +struct JumpToNext +{ + static size_t windowSize() { return 3; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + if ( + _in[0].type() == PushTag && + (_in[1] == Instruction::JUMP || _in[1] == Instruction::JUMPI) && + _in[2].type() == Tag && + _in[0].data() == _in[2].data() + ) + { + *_out = _in[2]; + return true; + } + else + return false; + } +}; + +struct TagConjunctions +{ + static size_t windowSize() { return 3; } + static bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out) + { + if ( + _in[0].type() == PushTag && + _in[2] == Instruction::AND && + _in[1].type() == Push && + (_in[1].data() & u256(0xFFFFFFFF)) == u256(0xFFFFFFFF) + ) + { + *_out = _in[0]; + return true; + } + else + return false; + } +}; + +struct OptimiserState +{ + AssemblyItems const& items; + size_t i; + std::back_insert_iterator out; +}; + +void applyMethods(OptimiserState&) +{ + assertThrow(false, OptimizerException, "Peephole optimizer failed to apply identity."); +} + +template +void applyMethods(OptimiserState& _state, Method, OtherMethods... _other) +{ + if (_state.i + Method::windowSize() <= _state.items.size() && Method::apply(_state.items.begin() + _state.i, _state.out)) + _state.i += Method::windowSize(); + else + applyMethods(_state, _other...); +} + +bool PeepholeOptimiser::optimise() +{ + OptimiserState state {m_items, 0, std::back_inserter(m_optimisedItems)}; + while (state.i < m_items.size()) + applyMethods(state, PushPop(), DoubleSwap(), JumpToNext(), TagConjunctions(), Identity()); + if (m_optimisedItems.size() < m_items.size()) + { + m_items = std::move(m_optimisedItems); + return true; + } + else + return false; + +} diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h new file mode 100644 index 000000000..dfc9a03b8 --- /dev/null +++ b/libevmasm/PeepholeOptimiser.h @@ -0,0 +1,53 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @file PeepholeOptimiser.h + * Performs local optimising code changes to assembly. + */ +#pragma once + +#include +#include + +namespace dev +{ +namespace eth +{ +class AssemblyItem; +using AssemblyItems = std::vector; + +class PeepholeOptimisationMethod +{ +public: + virtual size_t windowSize() const; + virtual bool apply(AssemblyItems::const_iterator _in, std::back_insert_iterator _out); +}; + +class PeepholeOptimiser +{ +public: + explicit PeepholeOptimiser(AssemblyItems& _items): m_items(_items) {} + + bool optimise(); + +private: + AssemblyItems& m_items; + AssemblyItems m_optimisedItems; +}; + +} +} diff --git a/libsolidity/codegen/Compiler.cpp b/libsolidity/codegen/Compiler.cpp index eefa50c5f..54639515f 100644 --- a/libsolidity/codegen/Compiler.cpp +++ b/libsolidity/codegen/Compiler.cpp @@ -41,8 +41,7 @@ void Compiler::compileContract( ContractCompiler creationCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = creationCompiler.compileConstructor(_contract, _contracts); - if (m_optimize) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); if (_contract.isLibrary()) { @@ -60,8 +59,7 @@ void Compiler::compileClone( ContractCompiler cloneCompiler(&runtimeCompiler, m_context, m_optimize); m_runtimeSub = cloneCompiler.compileClone(_contract, _contracts); - if (m_optimize) - m_context.optimise(m_optimizeRuns); + m_context.optimise(m_optimize, m_optimizeRuns); } eth::AssemblyItem Compiler::functionEntryLabel(FunctionDefinition const& _function) const diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h index ee3fb0685..8ccbddfd8 100644 --- a/libsolidity/codegen/CompilerContext.h +++ b/libsolidity/codegen/CompilerContext.h @@ -155,7 +155,7 @@ public: /// Prepends "PUSH POP" void injectVersionStampIntoSub(size_t _subIndex); - void optimise(unsigned _runs = 200) { m_asm->optimise(true, true, _runs); } + void optimise(bool _fullOptimsation, unsigned _runs = 200) { m_asm->optimise(_fullOptimsation, true, _runs); } /// @returns the runtime context if in creation mode and runtime context is set, nullptr otherwise. CompilerContext* runtimeContext() { return m_runtimeContext; } From 7a292c9a05eb38f10f6e619db0805105433fda30 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 15:23:50 +0100 Subject: [PATCH 104/125] Fix parser for function type disambiguity. --- libsolidity/parsing/Parser.cpp | 13 ++++++++++++- test/libsolidity/SolidityParser.cpp | 11 +++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/libsolidity/parsing/Parser.cpp b/libsolidity/parsing/Parser.cpp index e844861b1..02b7d5e0e 100644 --- a/libsolidity/parsing/Parser.cpp +++ b/libsolidity/parsing/Parser.cpp @@ -315,7 +315,18 @@ Parser::FunctionHeaderParserResult Parser::parseFunctionHeader(bool _forceEmptyN m_scanner->next(); } else if (_allowModifiers && token == Token::Identifier) - result.modifiers.push_back(parseModifierInvocation()); + { + // This can either be a modifier (function declaration) or the name of the + // variable (function type name plus variable). + if ( + m_scanner->peekNextToken() == Token::Semicolon || + m_scanner->peekNextToken() == Token::Assign + ) + // Variable declaration, break here. + break; + else + result.modifiers.push_back(parseModifierInvocation()); + } else if (Token::isVisibilitySpecifier(token)) { if (result.visibility != Declaration::Visibility::Default) diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp index 796da7827..914dbc305 100644 --- a/test/libsolidity/SolidityParser.cpp +++ b/test/libsolidity/SolidityParser.cpp @@ -1338,6 +1338,17 @@ BOOST_AUTO_TEST_CASE(mapping_and_array_of_functions) BOOST_CHECK(successParse(text)); } +BOOST_AUTO_TEST_CASE(function_type_state_variable) +{ + char const* text = R"( + contract test { + function() x; + function() y = x; + } + )"; + BOOST_CHECK(successParse(text)); +} + BOOST_AUTO_TEST_SUITE_END() From 739dabff8ba8b4a91100c1612a0ac2faf0ef78b5 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 15:24:08 +0100 Subject: [PATCH 105/125] Fix tests. --- test/libsolidity/Assembly.cpp | 6 +++--- test/libsolidity/SolidityOptimizer.cpp | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp index eddba5e1c..e5ce691b8 100644 --- a/test/libsolidity/Assembly.cpp +++ b/test/libsolidity/Assembly.cpp @@ -116,10 +116,10 @@ BOOST_AUTO_TEST_CASE(location_test) shared_ptr n = make_shared(""); AssemblyItems items = compileContract(sourceCode); vector locations = - vector(18, SourceLocation(2, 75, n)) + - vector(31, SourceLocation(20, 72, n)) + + vector(16, SourceLocation(2, 75, n)) + + vector(27, SourceLocation(20, 72, n)) + vector{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} + - vector(4, SourceLocation(58, 67, n)) + + vector(2, SourceLocation(58, 67, n)) + vector(3, SourceLocation(20, 72, n)); checkAssemblyLocations(items, locations); } diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 976976b26..4698e88cf 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -370,7 +370,7 @@ BOOST_AUTO_TEST_CASE(successor_not_found_bug) // This bug was caused because MSVC chose to use the u256->bool conversion // instead of u256->unsigned char const* sourceCode = R"( - contract greeter { function greeter() {} } + contract greeter { function greeter() { uint x = 2; x ++; } } )"; compileBothVersions(sourceCode); } From 390ba085b63e3968b555eeab0ddf8ef429e7c0ee Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 15:38:56 +0100 Subject: [PATCH 106/125] fixup! Simple peephole optimizer that is activated even if not requested. --- libevmasm/PeepholeOptimiser.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp index 91b0ece18..f42dba487 100644 --- a/libevmasm/PeepholeOptimiser.cpp +++ b/libevmasm/PeepholeOptimiser.cpp @@ -81,6 +81,8 @@ struct JumpToNext _in[0].data() == _in[2].data() ) { + if (_in[1] == Instruction::JUMPI) + *_out = AssemblyItem(Instruction::POP, _in[1].location()); *_out = _in[2]; return true; } From cb000a55328d0d1980f2a645e06d4f125ef89e47 Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 16:35:13 +0100 Subject: [PATCH 107/125] Fix setting the tag. --- libevmasm/AssemblyItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index bc8c87aa1..7bd93eaf7 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -46,7 +46,7 @@ pair AssemblyItem::splitForeignPushTag() const void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); - setData(_tag + ((u256(_subId) + 1) << 64)); + setData(_tag + (u256(_subId + 1) << 64)); } unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const From a8e7ed37a10f9dd43bfc57db7733412503c1a24e Mon Sep 17 00:00:00 2001 From: chriseth Date: Fri, 11 Nov 2016 18:43:34 +0100 Subject: [PATCH 108/125] Disable broken tests that are not useful. --- .../SolidityExpressionCompiler.cpp | 37 +------------------ test/libsolidity/SolidityOptimizer.cpp | 10 ----- 2 files changed, 2 insertions(+), 45 deletions(-) diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp index e9a05745e..91edfefd2 100644 --- a/test/libsolidity/SolidityExpressionCompiler.cpp +++ b/test/libsolidity/SolidityExpressionCompiler.cpp @@ -325,12 +325,12 @@ BOOST_AUTO_TEST_CASE(arithmetics) byte(Instruction::ADD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x0, byte(Instruction::JUMPI), byte(Instruction::MOD), byte(Instruction::DUP2), byte(Instruction::ISZERO), - byte(Instruction::PUSH1), 0x2, + byte(Instruction::PUSH1), 0x0, byte(Instruction::JUMPI), byte(Instruction::DIV), byte(Instruction::MUL)}); @@ -425,39 +425,6 @@ BOOST_AUTO_TEST_CASE(assignment) BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); } -BOOST_AUTO_TEST_CASE(function_call) -{ - char const* sourceCode = "contract test {\n" - " function f(uint a, uint b) { a += g(a + 1, b) * 2; }\n" - " function g(uint a, uint b) returns (uint c) {}\n" - "}\n"; - bytes code = compileFirstExpression(sourceCode, {{"test", "g"}}, - {{"test", "f", "a"}, {"test", "f", "b"}}); - - // Stack: a, b - bytes expectation({byte(Instruction::PUSH1), 0x02, - byte(Instruction::PUSH1), 0x0c, - byte(Instruction::PUSH1), 0x01, - byte(Instruction::DUP5), - byte(Instruction::ADD), - // Stack here: a b 2 (a+1) - byte(Instruction::DUP4), - byte(Instruction::PUSH1), 0x13, - byte(Instruction::JUMP), - byte(Instruction::JUMPDEST), - // Stack here: a b 2 g(a+1, b) - byte(Instruction::MUL), - // Stack here: a b g(a+1, b)*2 - byte(Instruction::DUP3), - byte(Instruction::ADD), - // Stack here: a b a+g(a+1, b)*2 - byte(Instruction::SWAP2), - byte(Instruction::POP), - byte(Instruction::DUP2), - byte(Instruction::JUMPDEST)}); - BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end()); -} - BOOST_AUTO_TEST_CASE(negative_literals_8bits) { char const* sourceCode = "contract test {\n" diff --git a/test/libsolidity/SolidityOptimizer.cpp b/test/libsolidity/SolidityOptimizer.cpp index 4698e88cf..4991cf24f 100644 --- a/test/libsolidity/SolidityOptimizer.cpp +++ b/test/libsolidity/SolidityOptimizer.cpp @@ -365,16 +365,6 @@ BOOST_AUTO_TEST_CASE(store_tags_as_unions) // BOOST_CHECK_EQUAL(2, numSHA3s); } -BOOST_AUTO_TEST_CASE(successor_not_found_bug) -{ - // This bug was caused because MSVC chose to use the u256->bool conversion - // instead of u256->unsigned - char const* sourceCode = R"( - contract greeter { function greeter() { uint x = 2; x ++; } } - )"; - compileBothVersions(sourceCode); -} - BOOST_AUTO_TEST_CASE(incorrect_storage_access_bug) { // This bug appeared because a sha3 operation with too low sequence number was used, From ec31d08775021de0f3279dbeb115b3e688c5997e Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 13:13:37 +0100 Subject: [PATCH 109/125] Change encoding to address-funid and add "function" as ABI type. --- libevmasm/PeepholeOptimiser.h | 1 + libsolidity/ast/ASTJsonConverter.cpp | 2 +- libsolidity/ast/Types.cpp | 9 +++++-- libsolidity/ast/Types.h | 1 + libsolidity/codegen/CompilerUtils.cpp | 31 +++++++++++++---------- libsolidity/codegen/CompilerUtils.h | 2 +- libsolidity/interface/CompilerStack.cpp | 2 +- test/libsolidity/SolidityEndToEndTest.cpp | 6 ++--- 8 files changed, 33 insertions(+), 21 deletions(-) diff --git a/libevmasm/PeepholeOptimiser.h b/libevmasm/PeepholeOptimiser.h index dfc9a03b8..372e49c5b 100644 --- a/libevmasm/PeepholeOptimiser.h +++ b/libevmasm/PeepholeOptimiser.h @@ -22,6 +22,7 @@ #include #include +#include namespace dev { diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp index d6aca175a..37dfd3c68 100644 --- a/libsolidity/ast/ASTJsonConverter.cpp +++ b/libsolidity/ast/ASTJsonConverter.cpp @@ -236,7 +236,7 @@ bool ASTJsonConverter::visit(FunctionTypeName const& _node) make_pair("payable", _node.isPayable()), make_pair("visibility", visibility), make_pair("constant", _node.isDeclaredConst()) - }); + }, true); return true; } diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index c1ae183e9..3a8fc075e 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1930,6 +1930,12 @@ TypePointer FunctionType::unaryOperatorResult(Token::Value _operator) const return TypePointer(); } +string FunctionType::canonicalName(bool) const +{ + solAssert(m_location == Location::External, ""); + return "function"; +} + string FunctionType::toString(bool _short) const { string name = "function ("; @@ -2102,7 +2108,6 @@ TypePointer FunctionType::encodingType() const { // Only external functions can be encoded, internal functions cannot leave code boundaries. if (m_location == Location::External) - // This looks like bytes24, but bytes24 is stored differently on the stack. return shared_from_this(); else return TypePointer(); @@ -2111,7 +2116,7 @@ TypePointer FunctionType::encodingType() const TypePointer FunctionType::interfaceType(bool /*_inLibrary*/) const { if (m_location == Location::External) - return make_shared(storageBytes()); + return shared_from_this(); else return TypePointer(); } diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h index 84e566635..34fcfc823 100644 --- a/libsolidity/ast/Types.h +++ b/libsolidity/ast/Types.h @@ -897,6 +897,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual std::string canonicalName(bool /*_addDataLocation*/) const override; virtual std::string toString(bool _short) const override; virtual unsigned calldataEncodedSize(bool _padded) const override; virtual bool canBeStored() const override { return m_location == Location::Internal || m_location == Location::External; } diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 1e21c0208..1e819ed45 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -317,27 +317,32 @@ void CompilerUtils::memoryCopy() void CompilerUtils::splitExternalFunctionType(bool _leftAligned) { - // We have to split the left-aligned
into two stack slots: + // We have to split the left-aligned
into two stack slots: // address (right aligned), function identifier (right aligned) if (_leftAligned) - m_context << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; - m_context << Instruction::DUP1 << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; - m_context << (u256(1) << 160) << Instruction::SWAP1 << Instruction::DIV; - if (!_leftAligned) - m_context << u256(0xffffffffUL) << Instruction::AND; + { + m_context << Instruction::DUP1 << (u256(1) << (64 + 32)) << Instruction::SWAP1 << Instruction::DIV; + //
+ m_context << Instruction::SWAP1 << (u256(1) << 64) << Instruction::SWAP1 << Instruction::DIV; + } + else + { + m_context << Instruction::DUP1 << (u256(1) << 32) << Instruction::SWAP1 << Instruction::DIV; + m_context << ((u256(1) << 160) - 1) << Instruction::AND << Instruction::SWAP1; + } + m_context << u256(0xffffffffUL) << Instruction::AND; } void CompilerUtils::combineExternalFunctionType(bool _leftAligned) { - if (_leftAligned) - m_context << (u256(1) << 224); - else - m_context << u256(0xffffffffUL) << Instruction::AND << (u256(1) << 160); - m_context << Instruction::MUL << Instruction::SWAP1; - m_context << ((u256(1) << 160) - 1) << Instruction::AND; + //
+ m_context << u256(0xffffffffUL) << Instruction::AND << Instruction::SWAP1; + if (!_leftAligned) + m_context << ((u256(1) << 160) - 1) << Instruction::AND; + m_context << (u256(1) << 32) << Instruction::MUL; + m_context << Instruction::OR; if (_leftAligned) m_context << (u256(1) << 64) << Instruction::MUL; - m_context << Instruction::OR; } void CompilerUtils::pushCombinedFunctionEntryLabel(Declaration const& _function) diff --git a/libsolidity/codegen/CompilerUtils.h b/libsolidity/codegen/CompilerUtils.h index 2ebec81aa..8e4033d63 100644 --- a/libsolidity/codegen/CompilerUtils.h +++ b/libsolidity/codegen/CompilerUtils.h @@ -115,7 +115,7 @@ public: void memoryCopy(); /// Converts the combined and left-aligned (right-aligned if @a _rightAligned is true) - /// external function type
into two stack slots: + /// external function type
into two stack slots: /// address (right aligned), function identifier (right aligned) void splitExternalFunctionType(bool _rightAligned); /// Performs the opposite operation of splitExternalFunctionType(_rightAligned) diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index c86de43fe..f1eb2614b 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -597,7 +597,7 @@ void CompilerStack::compileContract( cloneCompiler.compileClone(_contract, _compiledContracts); compiledContract.cloneObject = cloneCompiler.assembledObject(); } - catch (eth::AssemblyException const& _e) + catch (eth::AssemblyException const&) { // In some cases (if the constructor requests a runtime function), it is not // possible to compile the clone. diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index c01d11d2c..ed95d6877 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -7703,8 +7703,8 @@ BOOST_AUTO_TEST_CASE(receive_external_function_type) compileAndRun(sourceCode, 0, "C"); BOOST_CHECK(callContractFunction( - "f(bytes24)", - FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + "f(function)", + m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g()")).asBytes() + bytes(32 - 4 - 20, 0) ) == encodeArgs(u256(7))); } @@ -7722,7 +7722,7 @@ BOOST_AUTO_TEST_CASE(return_external_function_type) compileAndRun(sourceCode, 0, "C"); BOOST_CHECK( callContractFunction("f()") == - FixedHash<4>(dev::keccak256("g()")).asBytes() + m_contractAddress.asBytes() + bytes(32 - 4 - 20, 0) + m_contractAddress.asBytes() + FixedHash<4>(dev::keccak256("g()")).asBytes() + bytes(32 - 4 - 20, 0) ); } From 830f14c3a3c7bc991ad34c1b0299a31368853d97 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 21:51:41 +0100 Subject: [PATCH 110/125] Fix documentation formatting. --- docs/types.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index 7f4570edd..c9e2e6fa1 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -311,7 +311,7 @@ on it. If external function types are used outside of the context of Solidity, they are converted into the ``bytes24`` type. -Example that shows how to use internal function types: +Example that shows how to use internal function types:: library ArrayUtils { // internal functions can be used in internal library functions because @@ -356,7 +356,7 @@ Example that shows how to use internal function types: } } -Another example that uses external function types: +Another example that uses external function types:: contract Oracle { struct Request { From e1fec9b2877492658b079d3278a702310213fe2a Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:37:19 +0100 Subject: [PATCH 111/125] JSON tests. --- test/libsolidity/ASTJSON.cpp | 14 ++++++++++++++ test/libsolidity/SolidityABIJSON.cpp | 23 +++++++++++++++++++++++ 2 files changed, 37 insertions(+) diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index 6c062ee88..0180c4ac3 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -197,6 +197,20 @@ BOOST_AUTO_TEST_CASE(non_utf8) BOOST_CHECK(literal["attributes"]["type"].asString().find("invalid") != string::npos); } +BOOST_AUTO_TEST_CASE(function_type) +{ + CompilerStack c; + c.addSource("a", "contract C { function f(function() external payable constant returns (uint) x) {} }"); + c.parse(); + map sourceIndices; + sourceIndices["a"] = 1; + Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); + Json::Value event = astJson["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(event["name"], "EventDefinition"); + BOOST_CHECK_EQUAL(event["attributes"]["name"], "E"); + BOOST_CHECK_EQUAL(event["src"], "13:10:1"); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index c01ff11b0..f77ad5fc3 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -703,6 +703,29 @@ BOOST_AUTO_TEST_CASE(payable_fallback_function) checkInterface(sourceCode, interface); } +BOOST_AUTO_TEST_CASE(function_type) +{ + char const* sourceCode = R"( + contract test { + function g(function(uint) external returns (uint)) {} + } + )"; + + char const* interface = R"( + [ + { + "constant" : false, + "payable": true, + "inputs": ["function"], + "name": "g", + "outputs": [], + "type" : "function" + } + ] + )"; + checkInterface(sourceCode, interface); +} + BOOST_AUTO_TEST_SUITE_END() } From b3eeb5fcf9a8efe1fc2a715fbd1a03c421824d72 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:52:07 +0100 Subject: [PATCH 112/125] Some more tests. --- test/libsolidity/ASTJSON.cpp | 14 ++++++++++---- test/libsolidity/SolidityABIJSON.cpp | 9 ++++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index 0180c4ac3..fd7c21b8d 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -205,10 +205,16 @@ BOOST_AUTO_TEST_CASE(function_type) map sourceIndices; sourceIndices["a"] = 1; Json::Value astJson = ASTJsonConverter(c.ast("a"), sourceIndices).json(); - Json::Value event = astJson["children"][0]["children"][0]; - BOOST_CHECK_EQUAL(event["name"], "EventDefinition"); - BOOST_CHECK_EQUAL(event["attributes"]["name"], "E"); - BOOST_CHECK_EQUAL(event["src"], "13:10:1"); + Json::Value fun = astJson["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(fun["name"], "FunctionDefinition"); + Json::Value argument = fun["children"][0]["children"][0]; + BOOST_CHECK_EQUAL(argument["name"], "VariableDeclaration"); + BOOST_CHECK_EQUAL(argument["attributes"]["name"], "x"); + BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () constant payable external returns (uint256)"); + Json::Value funType = argument["children"][0]; + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["payable"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index f77ad5fc3..5cabb7fa8 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -707,7 +707,7 @@ BOOST_AUTO_TEST_CASE(function_type) { char const* sourceCode = R"( contract test { - function g(function(uint) external returns (uint)) {} + function g(function(uint) external returns (uint) x) {} } )"; @@ -715,8 +715,11 @@ BOOST_AUTO_TEST_CASE(function_type) [ { "constant" : false, - "payable": true, - "inputs": ["function"], + "payable": false, + "inputs": [{ + "name": "x", + "type": "function" + }], "name": "g", "outputs": [], "type" : "function" From eeae91c2a2897c6fd155890d654485fc8cf4dfc6 Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 14 Nov 2016 23:54:44 +0100 Subject: [PATCH 113/125] Update documentation. --- docs/types.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/types.rst b/docs/types.rst index c9e2e6fa1..ae27ee31e 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -309,7 +309,8 @@ in an exception. The same happens if you call a function after using ``delete`` on it. If external function types are used outside of the context of Solidity, -they are converted into the ``bytes24`` type. +they are treated as the ``function`` type, which encodes the address +followed by the function identifier together in a single ``bytes24`` type. Example that shows how to use internal function types:: From 2defe4dcefb6f0e17d7f87231233ff637dad55a8 Mon Sep 17 00:00:00 2001 From: chriseth Date: Tue, 15 Nov 2016 11:43:46 +0100 Subject: [PATCH 114/125] Documentation: Style update --- docs/types.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/types.rst b/docs/types.rst index ae27ee31e..b22ad7d4c 100644 --- a/docs/types.rst +++ b/docs/types.rst @@ -274,10 +274,10 @@ check the value ranges at runtime and a failure causes an exception. Enums need Function Types -------------- -Function types can be used to assign functions to variables and passing them -to or return them from function calls. Such variables and parameters have -to have function types. These types come in two flavours: *internal* and *external* -functions. +Function types are the types of functions. Variables of function type +can be assigned from functions and function parameters of function type +can be used to pass functions to and return functions from function calls. +Function types come in two flavours - *internal* and *external* functions: Internal functions can only be used inside the current contract (more specifically, inside the current code unit, which also includes internal library functions @@ -289,7 +289,7 @@ contract internally. External functions consist of an address and a function signature and they can be passed via and returned from external function calls. -Function types are notated as follows: +Function types are notated as follows:: function () {internal|external} [constant] [payable] [returns ()] From 2c14a96820233809db4360b39f5f02039be5730a Mon Sep 17 00:00:00 2001 From: chriseth Date: Wed, 16 Nov 2016 15:09:01 +0100 Subject: [PATCH 115/125] Some more assertions and style changes. --- libevmasm/Assembly.cpp | 2 +- libsolidity/ast/Types.cpp | 41 ++++++++++++++++----------- libsolidity/codegen/CompilerUtils.cpp | 2 +- test/libsolidity/ASTJSON.cpp | 17 +++++++++-- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index a813a3a78..a30374564 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -81,7 +81,7 @@ string Assembly::out() const unsigned Assembly::bytesRequired(unsigned subTagSize) const { - for (unsigned tagSize = subTagSize;; ++tagSize) + for (unsigned tagSize = subTagSize; true; ++tagSize) { unsigned ret = 1; for (auto const& i: m_data) diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp index 3a8fc075e..4488398f8 100644 --- a/libsolidity/ast/Types.cpp +++ b/libsolidity/ast/Types.cpp @@ -1827,23 +1827,28 @@ FunctionType::FunctionType(FunctionTypeName const& _typeName): m_isPayable(_typeName.isPayable()) { if (_typeName.isPayable()) + { solAssert(m_location == Location::External, "Internal payable function type used."); + solAssert(!m_isConstant, "Payable constant function"); + } for (auto const& t: _typeName.parameterTypes()) { solAssert(t->annotation().type, "Type not set for parameter."); - solAssert( - m_location != Location::External || t->annotation().type->canBeUsedExternally(false), - "Internal type used as parameter for external function." - ); + if (m_location == Location::External) + solAssert( + t->annotation().type->canBeUsedExternally(false), + "Internal type used as parameter for external function." + ); m_parameterTypes.push_back(t->annotation().type); } for (auto const& t: _typeName.returnParameterTypes()) { solAssert(t->annotation().type, "Type not set for return parameter."); - solAssert( - m_location != Location::External || t->annotation().type->canBeUsedExternally(false), - "Internal type used as return parameter for external function." - ); + if (m_location == Location::External) + solAssert( + t->annotation().type->canBeUsedExternally(false), + "Internal type used as return parameter for external function." + ); m_returnParameterTypes.push_back(t->annotation().type); } } @@ -1941,17 +1946,21 @@ string FunctionType::toString(bool _short) const string name = "function ("; for (auto it = m_parameterTypes.begin(); it != m_parameterTypes.end(); ++it) name += (*it)->toString(_short) + (it + 1 == m_parameterTypes.end() ? "" : ","); - name += ") "; + name += ")"; if (m_isConstant) - name += "constant "; + name += " constant"; if (m_isPayable) - name += "payable "; + name += " payable"; if (m_location == Location::External) - name += "external "; - name += "returns ("; - for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) - name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); - return name + ")"; + name += " external"; + if (!m_returnParameterTypes.empty()) + { + name += " returns ("; + for (auto it = m_returnParameterTypes.begin(); it != m_returnParameterTypes.end(); ++it) + name += (*it)->toString(_short) + (it + 1 == m_returnParameterTypes.end() ? "" : ","); + name += ")"; + } + return name; } unsigned FunctionType::calldataEncodedSize(bool _padded) const diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp index 1e819ed45..bfe5386b3 100644 --- a/libsolidity/codegen/CompilerUtils.cpp +++ b/libsolidity/codegen/CompilerUtils.cpp @@ -138,7 +138,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound dynamic_cast(_type).location() == FunctionType::Location::External ) { - solAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); + solUnimplementedAssert(_padToWordBoundaries, "Non-padded store for function not implemented."); combineExternalFunctionType(true); m_context << Instruction::DUP2 << Instruction::MSTORE; m_context << u256(_padToWordBoundaries ? 32 : 24) << Instruction::ADD; diff --git a/test/libsolidity/ASTJSON.cpp b/test/libsolidity/ASTJSON.cpp index fd7c21b8d..b88218e78 100644 --- a/test/libsolidity/ASTJSON.cpp +++ b/test/libsolidity/ASTJSON.cpp @@ -200,7 +200,10 @@ BOOST_AUTO_TEST_CASE(non_utf8) BOOST_AUTO_TEST_CASE(function_type) { CompilerStack c; - c.addSource("a", "contract C { function f(function() external payable constant returns (uint) x) {} }"); + c.addSource("a", + "contract C { function f(function() external payable returns (uint) x) " + "returns (function() external constant returns (uint)) {} }" + ); c.parse(); map sourceIndices; sourceIndices["a"] = 1; @@ -210,11 +213,19 @@ BOOST_AUTO_TEST_CASE(function_type) Json::Value argument = fun["children"][0]["children"][0]; BOOST_CHECK_EQUAL(argument["name"], "VariableDeclaration"); BOOST_CHECK_EQUAL(argument["attributes"]["name"], "x"); - BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () constant payable external returns (uint256)"); + BOOST_CHECK_EQUAL(argument["attributes"]["type"], "function () payable external returns (uint256)"); Json::Value funType = argument["children"][0]; - BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], false); BOOST_CHECK_EQUAL(funType["attributes"]["payable"], true); BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); + Json::Value retval = fun["children"][1]["children"][0]; + BOOST_CHECK_EQUAL(retval["name"], "VariableDeclaration"); + BOOST_CHECK_EQUAL(retval["attributes"]["name"], ""); + BOOST_CHECK_EQUAL(retval["attributes"]["type"], "function () constant external returns (uint256)"); + funType = retval["children"][0]; + BOOST_CHECK_EQUAL(funType["attributes"]["constant"], true); + BOOST_CHECK_EQUAL(funType["attributes"]["payable"], false); + BOOST_CHECK_EQUAL(funType["attributes"]["visibility"], "external"); } BOOST_AUTO_TEST_SUITE_END() From ceeb8f4a2b20a54c38792c7451969b660b144246 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 10:24:53 +0000 Subject: [PATCH 116/125] Add payable check for constructor in codegen --- libsolidity/codegen/ContractCompiler.cpp | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 2e3f5d7fe..03296cb27 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -137,6 +137,12 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c appendConstructor(*constructor); else if (auto c = m_context.nextConstructor(_contract)) appendBaseConstructor(*c); + else + { + // Throw if function is not payable but call contained ether. + m_context << Instruction::CALLVALUE; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } } size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) @@ -184,6 +190,12 @@ void ContractCompiler::appendBaseConstructor(FunctionDefinition const& _construc void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) { CompilerContext::LocationSetter locationSetter(m_context, _constructor); + if (!_constructor.isPayable()) + { + // Throw if function is not payable but call contained ether. + m_context << Instruction::CALLVALUE; + m_context.appendConditionalJumpTo(m_context.errorTag()); + } // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program if (!_constructor.parameters().empty()) { From 60e9c901e9f4a314fca1acd374c13841d7be3963 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 10:25:39 +0000 Subject: [PATCH 117/125] Include payable for the constructor in the ABI --- libsolidity/interface/InterfaceHandler.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp index 0f7b6c359..9944bb22d 100644 --- a/libsolidity/interface/InterfaceHandler.cpp +++ b/libsolidity/interface/InterfaceHandler.cpp @@ -68,6 +68,7 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe method["type"] = "constructor"; auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType(); solAssert(!!externalFunction, ""); + method["payable"] = externalFunction->isPayable(); method["inputs"] = populateParameters( externalFunction->parameterNames(), externalFunction->parameterTypeNames(_contractDef.isLibrary()) From 819da2f0cd8d5ce9086f0fea05769788011f75fa Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 10:26:17 +0000 Subject: [PATCH 118/125] Add changelog entry for payable constructor --- Changelog.md | 1 + 1 file changed, 1 insertion(+) diff --git a/Changelog.md b/Changelog.md index ce3343d8c..6d080556a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -10,6 +10,7 @@ Features: Bugfixes: * Inline assembly: calculate stack height warning correctly even when local variables are used. + * Support the ``payable`` keyword on constructors. * Parser: disallow empty enum definitions. * Type checker: disallow conversion between different enum types. * Interface JSON: do not include trailing new line. From 7af360882e649a835619bf02ca5e63f6f6af6d9f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 18:40:37 +0000 Subject: [PATCH 119/125] Add missing payable constructors --- test/libsolidity/SolidityEndToEndTest.cpp | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index ed95d6877..09fba26ab 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -1337,6 +1337,7 @@ BOOST_AUTO_TEST_CASE(struct_accessor) BOOST_AUTO_TEST_CASE(balance) { char const* sourceCode = "contract test {\n" + " function test() payable {}\n" " function getBalance() returns (uint256 balance) {\n" " return address(this).balance;\n" " }\n" @@ -1348,6 +1349,7 @@ BOOST_AUTO_TEST_CASE(balance) BOOST_AUTO_TEST_CASE(blockchain) { char const* sourceCode = "contract test {\n" + " function test() payable {}\n" " function someInfo() payable returns (uint256 value, address coinbase, uint256 blockNumber) {\n" " value = msg.value;\n" " coinbase = block.coinbase;\n" @@ -1563,6 +1565,7 @@ BOOST_AUTO_TEST_CASE(convert_uint_to_fixed_bytes_greater_size) BOOST_AUTO_TEST_CASE(send_ether) { char const* sourceCode = "contract test {\n" + " function test() payable {}\n" " function a(address addr, uint amount) returns (uint ret) {\n" " addr.send(amount);\n" " return address(this).balance;\n" @@ -1675,6 +1678,7 @@ BOOST_AUTO_TEST_CASE(log_in_constructor) BOOST_AUTO_TEST_CASE(suicide) { char const* sourceCode = "contract test {\n" + " function test() payable {}\n" " function a(address receiver) returns (uint ret) {\n" " suicide(receiver);\n" " return 10;\n" @@ -1691,6 +1695,7 @@ BOOST_AUTO_TEST_CASE(suicide) BOOST_AUTO_TEST_CASE(selfdestruct) { char const* sourceCode = "contract test {\n" + " function test() payable {}\n" " function a(address receiver) returns (uint ret) {\n" " selfdestruct(receiver);\n" " return 10;\n" @@ -2992,12 +2997,14 @@ BOOST_AUTO_TEST_CASE(generic_delegatecall) uint public received; address public sender; uint public value; + function receiver() payable {} function receive(uint256 x) payable { received = x; sender = msg.sender; value = msg.value; } } contract sender { uint public received; address public sender; uint public value; + function sender() payable {} function doSend(address rec) payable { bytes4 signature = bytes4(bytes32(sha3("receive(uint256)"))); @@ -4818,6 +4825,7 @@ BOOST_AUTO_TEST_CASE(failing_send) } } contract Main { + function Main() payable {} function callHelper(address _a) returns (bool r, uint bal) { r = !_a.send(5); bal = this.balance; @@ -4840,6 +4848,7 @@ BOOST_AUTO_TEST_CASE(send_zero_ether) } } contract Main { + function Main() payable {} function s() returns (bool) { var r = new Receiver(); return r.send(0); @@ -6341,6 +6350,7 @@ BOOST_AUTO_TEST_CASE(reject_ether_sent_to_library) char const* sourceCode = R"( library lib {} contract c { + function c() payable {} function f(address x) returns (bool) { return x.send(1); } @@ -7279,6 +7289,7 @@ BOOST_AUTO_TEST_CASE(failed_create) contract D { function D() payable {} } contract C { uint public x; + function C() payable {} function f(uint amount) returns (address) { x++; return (new D).value(amount)(); @@ -7392,7 +7403,7 @@ BOOST_AUTO_TEST_CASE(mutex) } contract Fund is mutexed { uint shares; - function Fund() { shares = msg.value; } + function Fund() payable { shares = msg.value; } function withdraw(uint amount) protected returns (uint) { // NOTE: It is very bad practice to write this function this way. // Please refer to the documentation of how to do this properly. From 1d6fe5c4e4b01d9ef5e1527a405ae44e79b56726 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 18:41:54 +0000 Subject: [PATCH 120/125] Add payable to constructor ABI tests --- test/libsolidity/SolidityABIJSON.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/libsolidity/SolidityABIJSON.cpp b/test/libsolidity/SolidityABIJSON.cpp index 5cabb7fa8..a8a39b0b1 100644 --- a/test/libsolidity/SolidityABIJSON.cpp +++ b/test/libsolidity/SolidityABIJSON.cpp @@ -524,6 +524,7 @@ BOOST_AUTO_TEST_CASE(constructor_abi) "type": "bool" } ], + "payable": false, "type": "constructor" } ])"; @@ -567,6 +568,7 @@ BOOST_AUTO_TEST_CASE(return_param_in_abi) "type": "uint8" } ], + "payable": false, "type": "constructor" } ] From d97eb7cc75f6a789f41a43132c7ca3b5aefe967f Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 18:42:57 +0000 Subject: [PATCH 121/125] Add payable keyword to the multisig wallet --- test/contracts/Wallet.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/contracts/Wallet.cpp b/test/contracts/Wallet.cpp index ec9680583..4966b26c8 100644 --- a/test/contracts/Wallet.cpp +++ b/test/contracts/Wallet.cpp @@ -368,7 +368,7 @@ contract Wallet is multisig, multiowned, daylimit { // constructor - just pass on the owner array to the multiowned and // the limit to daylimit - function Wallet(address[] _owners, uint _required, uint _daylimit) + function Wallet(address[] _owners, uint _required, uint _daylimit) payable multiowned(_owners, _required) daylimit(_daylimit) { } From a35ca910c798e018c7cedc5a0436674a0c58ca1d Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Tue, 15 Nov 2016 18:59:07 +0000 Subject: [PATCH 122/125] Rename test contract names to capitalised --- test/libsolidity/SolidityEndToEndTest.cpp | 24 +++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index 09fba26ab..4abe0894e 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -2961,24 +2961,24 @@ BOOST_AUTO_TEST_CASE(generic_call) BOOST_AUTO_TEST_CASE(generic_callcode) { char const* sourceCode = R"**( - contract receiver { + contract Receiver { uint public received; function receive(uint256 x) payable { received = x; } } - contract sender { + contract Sender { uint public received; - function sender() payable { } + function Sender() payable { } function doSend(address rec) returns (uint d) { bytes4 signature = bytes4(bytes32(sha3("receive(uint256)"))); rec.callcode.value(2)(signature, 23); - return receiver(rec).received(); + return Receiver(rec).received(); } } )**"; - compileAndRun(sourceCode, 0, "receiver"); + compileAndRun(sourceCode, 0, "Receiver"); u160 const c_receiverAddress = m_contractAddress; - compileAndRun(sourceCode, 50, "sender"); + compileAndRun(sourceCode, 50, "Sender"); u160 const c_senderAddress = m_contractAddress; BOOST_CHECK(callContractFunction("doSend(address)", c_receiverAddress) == encodeArgs(0)); BOOST_CHECK(callContractFunction("received()") == encodeArgs(23)); @@ -2993,18 +2993,18 @@ BOOST_AUTO_TEST_CASE(generic_callcode) BOOST_AUTO_TEST_CASE(generic_delegatecall) { char const* sourceCode = R"**( - contract receiver { + contract Receiver { uint public received; address public sender; uint public value; - function receiver() payable {} + function Receiver() payable {} function receive(uint256 x) payable { received = x; sender = msg.sender; value = msg.value; } } - contract sender { + contract Sender { uint public received; address public sender; uint public value; - function sender() payable {} + function Sender() payable {} function doSend(address rec) payable { bytes4 signature = bytes4(bytes32(sha3("receive(uint256)"))); @@ -3012,9 +3012,9 @@ BOOST_AUTO_TEST_CASE(generic_delegatecall) } } )**"; - compileAndRun(sourceCode, 0, "receiver"); + compileAndRun(sourceCode, 0, "Receiver"); u160 const c_receiverAddress = m_contractAddress; - compileAndRun(sourceCode, 50, "sender"); + compileAndRun(sourceCode, 50, "Sender"); u160 const c_senderAddress = m_contractAddress; BOOST_CHECK(m_sender != c_senderAddress); // just for sanity BOOST_CHECK(callContractFunctionWithValue("doSend(address)", 11, c_receiverAddress) == encodeArgs()); From 910269a29f56265e42bce73694d45481cbd5fd54 Mon Sep 17 00:00:00 2001 From: Alex Beregszaszi Date: Thu, 17 Nov 2016 17:23:31 +0000 Subject: [PATCH 123/125] Add appendCallValueCheck --- libsolidity/codegen/ContractCompiler.cpp | 33 ++++++++++-------------- libsolidity/codegen/ContractCompiler.h | 1 + 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp index 03296cb27..8d60d6b3f 100644 --- a/libsolidity/codegen/ContractCompiler.cpp +++ b/libsolidity/codegen/ContractCompiler.cpp @@ -102,6 +102,13 @@ void ContractCompiler::initializeContext( m_context.resetVisitedNodes(&_contract); } +void ContractCompiler::appendCallValueCheck() +{ + // Throw if function is not payable but call contained ether. + m_context << Instruction::CALLVALUE; + m_context.appendConditionalJumpTo(m_context.errorTag()); +} + void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract) { // Determine the arguments that are used for the base constructors. @@ -138,11 +145,7 @@ void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _c else if (auto c = m_context.nextConstructor(_contract)) appendBaseConstructor(*c); else - { - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + appendCallValueCheck(); } size_t ContractCompiler::packIntoContractCreator(ContractDefinition const& _contract) @@ -191,11 +194,8 @@ void ContractCompiler::appendConstructor(FunctionDefinition const& _constructor) { CompilerContext::LocationSetter locationSetter(m_context, _constructor); if (!_constructor.isPayable()) - { - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + appendCallValueCheck(); + // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program if (!_constructor.parameters().empty()) { @@ -263,11 +263,8 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac if (fallback) { if (!fallback->isPayable()) - { - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + appendCallValueCheck(); + eth::AssemblyItem returnTag = m_context.pushNewTag(); fallback->accept(*this); m_context << returnTag; @@ -286,11 +283,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac // We have to allow this for libraries, because value of the previous // call is still visible in the delegatecall. if (!functionType->isPayable() && !_contract.isLibrary()) - { - // Throw if function is not payable but call contained ether. - m_context << Instruction::CALLVALUE; - m_context.appendConditionalJumpTo(m_context.errorTag()); - } + appendCallValueCheck(); eth::AssemblyItem returnTag = m_context.pushNewTag(); m_context << CompilerUtils::dataStartOffset; diff --git a/libsolidity/codegen/ContractCompiler.h b/libsolidity/codegen/ContractCompiler.h index 2244a3be5..10febcf72 100644 --- a/libsolidity/codegen/ContractCompiler.h +++ b/libsolidity/codegen/ContractCompiler.h @@ -80,6 +80,7 @@ private: void appendBaseConstructor(FunctionDefinition const& _constructor); void appendConstructor(FunctionDefinition const& _constructor); void appendFunctionSelector(ContractDefinition const& _contract); + void appendCallValueCheck(); /// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers. /// From memory if @a _fromMemory is true, otherwise from call data. /// Expects source offset on the stack, which is removed. From cc07a918e320395fced54eef19eb4222d4c452be Mon Sep 17 00:00:00 2001 From: RJ Date: Fri, 18 Nov 2016 12:34:43 -0600 Subject: [PATCH 124/125] Typo --- docs/control-structures.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/control-structures.rst b/docs/control-structures.rst index 7e1c690d2..f57e7936e 100644 --- a/docs/control-structures.rst +++ b/docs/control-structures.rst @@ -427,7 +427,7 @@ these curly braces, the following can be used (see the later sections for more d - literals, e.g. ``0x123``, ``42`` or ``"abc"`` (strings up to 32 characters) - opcodes (in "instruction style"), e.g. ``mload sload dup1 sstore``, for a list see below - - opcodes in functional style, e.g. ``add(1, mlod(0))`` + - opcodes in functional style, e.g. ``add(1, mload(0))`` - labels, e.g. ``name:`` - variable declarations, e.g. ``let x := 7`` or ``let x := add(y, 3)`` - identifiers (externals, labels or assembly-local variables), e.g. ``jump(name)``, ``3 x add`` From 5bb050a7390be03ec026554cc5307eac3010365d Mon Sep 17 00:00:00 2001 From: chriseth Date: Mon, 21 Nov 2016 11:18:04 +0100 Subject: [PATCH 125/125] Update changelog. --- Changelog.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Changelog.md b/Changelog.md index 6d080556a..abd8d593a 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,8 +1,8 @@ -### 0.4.5 (unreleased) +### 0.4.5 (2016-11-21) Features: * Function types - * Do-while loops: support for a C-style do{}while(); control structure + * Do-while loops: support for a ``do while ();`` control structure * Inline assembly: support ``invalidJumpLabel`` as a jump label. * Type checker: now more eagerly searches for a common type of an inline array with mixed types * Code generator: generates a runtime error when an out-of-range value is converted into an enum type. @@ -10,7 +10,7 @@ Features: Bugfixes: * Inline assembly: calculate stack height warning correctly even when local variables are used. - * Support the ``payable`` keyword on constructors. + * Code generator: check for value transfer in non-payable constructors. * Parser: disallow empty enum definitions. * Type checker: disallow conversion between different enum types. * Interface JSON: do not include trailing new line.