Browse Source

Merge remote-tracking branch 'upstream/develop' into ide_m25

cl-refactor
yann300 10 years ago
parent
commit
6563b2050b
  1. 5
      .gitignore
  2. 30
      CMakeLists.txt
  3. 1
      alethzero/CMakeLists.txt
  4. BIN
      alethzero/alethzero.ico
  5. 1
      alethzero/alethzero.rc
  6. 2
      cmake/EthDependencies.cmake
  7. 46
      cmake/EthExecutableHelper.cmake
  8. 49
      cmake/FindCURL.cmake
  9. 4
      cmake/FindGmp.cmake
  10. 47
      cmake/FindJsonRpcCpp.cmake
  11. 2
      libdevcore/Common.cpp
  12. 2
      libdevcrypto/Common.h
  13. 2
      libethcore/CommonEth.cpp
  14. 7
      libethereum/Transaction.cpp
  15. 9
      libevm/VM.h
  16. 42
      libevmcore/Assembly.cpp
  17. 5
      libevmcore/Assembly.h
  18. 3
      libserpent/bignum.h
  19. 279
      libserpent/compiler.cpp
  20. 203
      libserpent/functions.cpp
  21. 39
      libserpent/functions.h
  22. 154
      libserpent/opcodes.cpp
  23. 123
      libserpent/opcodes.h
  24. 98
      libserpent/optimize.cpp
  25. 19
      libserpent/optimize.h
  26. 86
      libserpent/parser.cpp
  27. 327
      libserpent/preprocess.cpp
  28. 50
      libserpent/preprocess.h
  29. 1206
      libserpent/rewriter.cpp
  30. 212
      libserpent/rewriteutils.cpp
  31. 76
      libserpent/rewriteutils.h
  32. 4
      libserpent/tokenize.cpp
  33. 58
      libserpent/util.cpp
  34. 36
      libserpent/util.h
  35. 68
      libsolidity/AST.cpp
  36. 82
      libsolidity/AST.h
  37. 2
      libsolidity/ASTForward.h
  38. 25
      libsolidity/ASTPrinter.cpp
  39. 4
      libsolidity/ASTPrinter.h
  40. 8
      libsolidity/ASTVisitor.h
  41. 50
      libsolidity/AST_accept.h
  42. 67
      libsolidity/CallGraph.cpp
  43. 55
      libsolidity/CallGraph.h
  44. 124
      libsolidity/Compiler.cpp
  45. 15
      libsolidity/Compiler.h
  46. 24
      libsolidity/CompilerContext.cpp
  47. 14
      libsolidity/CompilerContext.h
  48. 35
      libsolidity/CompilerStack.cpp
  49. 11
      libsolidity/CompilerStack.h
  50. 6
      libsolidity/CompilerUtils.cpp
  51. 63
      libsolidity/ExpressionCompiler.cpp
  52. 1
      libsolidity/ExpressionCompiler.h
  53. 57
      libsolidity/InterfaceHandler.cpp
  54. 11
      libsolidity/InterfaceHandler.h
  55. 32
      libsolidity/NameAndTypeResolver.cpp
  56. 2
      libsolidity/NameAndTypeResolver.h
  57. 79
      libsolidity/Parser.cpp
  58. 5
      libsolidity/Parser.h
  59. 143
      libsolidity/Scanner.cpp
  60. 7
      libsolidity/Scanner.h
  61. 13
      libsolidity/Token.h
  62. 26
      libsolidity/Types.cpp
  63. 11
      libsolidity/Types.h
  64. 49
      libsolidity/Utils.h
  65. 10
      libsolidity/grammar.txt
  66. 13
      sc/cmdline.cpp
  67. 99
      solc/CommandLineInterface.cpp
  68. 1
      test/CMakeLists.txt
  69. 0
      test/SolidityABIJSON.cpp
  70. 13
      test/SolidityCompiler.cpp
  71. 119
      test/SolidityEndToEndTest.cpp
  72. 9
      test/SolidityExpressionCompiler.cpp
  73. 27
      test/SolidityNameAndTypeResolution.cpp
  74. 29
      test/SolidityNatspecJSON.cpp
  75. 2
      test/SolidityOptimizer.cpp
  76. 78
      test/SolidityParser.cpp
  77. 54
      test/SolidityScanner.cpp
  78. 145
      test/stRefundTestFiller.json
  79. 10
      test/state.cpp
  80. 40
      test/vm.cpp
  81. 2
      test/vm.h

5
.gitignore

@ -18,11 +18,14 @@ ipch
*.sdf
*.opensdf
*.suo
*.vcxproj
*.vcxproj.filters
*.sln
# VIM stuff
*.swp
#Xcode stuff
# Xcode stuff
build_xc
*.user

30
CMakeLists.txt

@ -156,4 +156,32 @@ add_test(NAME alltests WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testet
#unset(TARGET_PLATFORM CACHE)
if (WIN32)
# packaging stuff
include(InstallRequiredSystemLibraries)
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "ethereum")
set(CPACK_PACKAGE_VENDOR "ethereum.org")
set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README.md")
set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION "0.7")
set(CPACK_GENERATOR "NSIS")
# seems to be not working
# set(CPACK_PACKAGE_ICON "${CMAKE_CURRENT_SOURCE_DIR}/alethzero/alethzero.bmp")
# our stuff
set(CPACK_COMPONENT_ALETHZERO_GROUP "Applications")
set(CPACK_COMPONENT_THIRD_GROUP "Applications")
set(CPACK_COMPONENT_MIX_GROUP "Applications")
set(CPACK_COMPONENTS_ALL alethzero third mix)
# nsis specific stuff
set(CPACK_NSIS_DISPLAY_NAME "${CPACK_PACKAGE_INSTALL_DIRECTORY} ethereum")
set(CPACK_NSIS_HELP_LINK "https://github.com/ethereum/cpp-ethereum")
set(CPACK_NSIS_URL_INFO_ABOUT "https://github.com/ethereum/cpp-ethereum")
set(CPACK_NSIS_CONTACT "ethereum.org")
set(CPACK_NSIS_MODIFY_PATH ON)
set(CPACK_NSIS_MUI_ICON "${CMAKE_CURRENT_SOURCE_DIR}/alethzero/alethzero.ico")
set(CPACK_NSIS_MUI_UNIICON "${CMAKE_CURRENT_SOURCE_DIR}/alethzero/alethzero.ico")
include(CPack)
endif (WIN32)

1
alethzero/CMakeLists.txt

@ -27,6 +27,7 @@ endif ()
eth_add_executable(${EXECUTABLE}
ICON alethzero
UI_RESOURCES alethzero.icns Main.ui
WIN_RESOURCES alethzero.rc
)
add_dependencies(${EXECUTABLE} BuildInfo.h)

BIN
alethzero/alethzero.ico

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

1
alethzero/alethzero.rc

@ -0,0 +1 @@
APP_ICON ICON DISCARDABLE "alethzero.ico"

2
cmake/EthDependencies.cmake

@ -82,7 +82,7 @@ if (GMP_FOUND)
endif()
# curl is only requried for tests
# TODO specify min curl version, on windows we are currenly using 7.29
# TODO specify min curl version, on windows we are currently using 7.29
find_package (CURL)
message(" - curl header: ${CURL_INCLUDE_DIRS}")
message(" - curl lib : ${CURL_LIBRARIES}")

46
cmake/EthExecutableHelper.cmake

@ -18,7 +18,7 @@ macro(eth_add_executable EXECUTABLE)
set (extra_macro_args ${ARGN})
set (options)
set (one_value_args ICON)
set (multi_value_args UI_RESOURCES)
set (multi_value_args UI_RESOURCES WIN_RESOURCES)
cmake_parse_arguments (ETH_ADD_EXECUTABLE "${options}" "${one_value_args}" "${multi_value_args}" "${extra_macro_args}")
if (APPLE)
@ -38,7 +38,7 @@ macro(eth_add_executable EXECUTABLE)
set_source_files_properties(${MACOSX_BUNDLE_ICON_FILE}.icns PROPERTIES MACOSX_PACKAGE_LOCATION Resources)
else ()
add_executable(${EXECUTABLE} ${ETH_ADD_EXECUTABLE_UI_RESOURCES} ${SRC_LIST} ${HEADERS})
add_executable(${EXECUTABLE} ${ETH_ADD_EXECUTABLE_UI_RESOURCES} ${ETH_ADD_EXECUTABLE_WIN_RESOURCES} ${SRC_LIST} ${HEADERS})
endif()
endmacro()
@ -60,14 +60,20 @@ macro(eth_install_executable EXECUTABLE)
cmake_parse_arguments (ETH_INSTALL_EXECUTABLE "${options}" "${one_value_args}" "${multi_value_args}" "${extra_macro_args}")
if (ETH_INSTALL_EXECUTABLE_QMLDIR)
set(eth_qml_dir "-qmldir=${ETH_INSTALL_EXECUTABLE_QMLDIR}")
if (APPLE)
set(eth_qml_dir "-qmldir=${ETH_INSTALL_EXECUTABLE_QMLDIR}")
elseif (WIN32)
set(eth_qml_dir --qmldir ${ETH_INSTALL_EXECUTABLE_QMLDIR})
endif()
message(STATUS "${EXECUTABLE} qmldir: ${eth_qml_dir}")
endif()
if (APPLE)
# First have qt5 install plugins and frameworks
add_custom_command(TARGET ${EXECUTABLE} POST_BUILD
COMMAND ${MACDEPLOYQT_APP} ${eth_qml_dir} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${EXECUTABLE}.app
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY})
COMMAND ${MACDEPLOYQT_APP} ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${EXECUTABLE}.app ${eth_qml_dir}
WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}
)
# This tool and next will inspect linked libraries in order to determine which dependencies are required
if (${CMAKE_CFG_INTDIR} STREQUAL ".")
@ -107,7 +113,35 @@ macro(eth_install_executable EXECUTABLE)
$<TARGET_FILE_DIR:${EXECUTABLE}>/platforms
)
install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin)
# ugly way, improve that
add_custom_command(TARGET ${EXECUTABLE} POST_BUILD
COMMAND cmake -E copy_directory
"${ETH_DEPENDENCY_INSTALL_DIR}/qml"
$<TARGET_FILE_DIR:${EXECUTABLE}>
)
install( FILES ${DLLS}
DESTINATION bin
COMPONENT ${EXECUTABLE}
)
install( DIRECTORY ${ETH_DEPENDENCY_INSTALL_DIR}/plugins/platforms
DESTINATION bin
COMPONENT ${EXECUTABLE}
)
file (GLOB QMLS ${ETH_DEPENDENCY_INSTALL_DIR}/qml/*)
foreach(QML ${QMLS})
install( DIRECTORY ${QML}
DESTINATION bin
COMPONENT ${EXECUTABLE}
)
endforeach()
install( TARGETS ${EXECUTABLE} RUNTIME
DESTINATION bin
COMPONENT ${EXECUTABLE}
)
else()
install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin)

49
cmake/FindCURL.cmake

@ -0,0 +1,49 @@
# Find CURL
#
# Find the curl includes and library
#
# if you nee to add a custom library search path, do it via via CMAKE_PREFIX_PATH
#
# This module defines
# CURL_INCLUDE_DIRS, where to find header, etc.
# CURL_LIBRARIES, the libraries needed to use curl.
# CURL_FOUND, If false, do not try to use curl.
# only look in default directories
find_path(
CURL_INCLUDE_DIR
NAMES curl/curl.h
DOC "curl include dir"
)
find_library(
CURL_LIBRARY
# names from cmake's FindCURL
NAMES curl curllib libcurl_imp curllib_static libcurl
DOC "curl library"
)
set(CURL_INCLUDE_DIRS ${CURL_INCLUDE_DIR})
set(CURL_LIBRARIES ${CURL_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(
CURL_LIBRARY_DEBUG
NAMES curld libcurld
DOC "curl debug library"
)
set(CURL_LIBRARIES optimized ${CURL_LIBRARIES} debug ${CURL_LIBRARY_DEBUG})
endif()
# handle the QUIETLY and REQUIRED arguments and set CURL_FOUND to TRUE
# if all listed variables are TRUE, hide their existence from configuration view
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(CURL DEFAULT_MSG
CURL_INCLUDE_DIR CURL_LIBRARY)
mark_as_advanced (CURL_INCLUDE_DIR CURL_LIBRARY)

4
cmake/FindGmp.cmake

@ -14,13 +14,13 @@ find_path(
GMP_INCLUDE_DIR
NAMES gmp.h
DOC "gmp include dir"
)
)
find_library(
GMP_LIBRARY
NAMES gmp
DOC "gmp library"
)
)
set(GMP_INCLUDE_DIRS ${GMP_INCLUDE_DIR})
set(GMP_LIBRARIES ${GMP_LIBRARY})

47
cmake/FindJsonRpcCpp.cmake

@ -43,6 +43,53 @@ set (JSON_RPC_CPP_LIBRARIES ${JSON_RPC_CPP_COMMON_LIBRARY} ${JSON_RPC_CPP_SERVER
set (JSON_RPC_CPP_SERVER_LIBRARIES ${JSON_RPC_CPP_COMMON_LIBRARY} ${JSON_RPC_CPP_SERVER_LIBRARY})
set (JSON_RPC_CPP_CLIENT_LIBRARIES ${JSON_RPC_CPP_COMMON_LIBRARY} ${JSON_RPC_CPP_CLIENT_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(
JSON_RPC_CPP_COMMON_LIBRARY_DEBUG
NAMES jsonrpccpp-commond
DOC "json-rpc-cpp common debug library"
)
find_library(
JSON_RPC_CPP_SERVER_LIBRARY_DEBUG
NAMES jsonrpccpp-serverd
DOC "json-rpc-cpp server debug library"
)
find_library(
JSON_RPC_CPP_CLIENT_LIBRARY_DEBUG
NAMES jsonrpccpp-clientd
DOC "json-rpc-cpp client debug library"
)
set (JSON_RPC_CPP_LIBRARIES
optimized ${JSON_RPC_CPP_COMMON_LIBRARY}
optimized ${JSON_RPC_CPP_SERVER_LIBRARY}
optimized ${JSON_RPC_CPP_CLIENT_LIBRARY}
debug ${JSON_RPC_CPP_COMMON_LIBRARY_DEBUG}
debug ${JSON_RPC_CPP_SERVER_LIBRARY_DEBUG}
debug ${JSON_RPC_CPP_CLIENT_LIBRARY_DEBUG}
)
set (JSON_RPC_CPP_SERVER_LIBRARIES
optimized ${JSON_RPC_CPP_COMMON_LIBRARY}
optimized ${JSON_RPC_CPP_SERVER_LIBRARY}
debug ${JSON_RPC_CPP_COMMON_LIBRARY_DEBUG}
debug ${JSON_RPC_CPP_SERVER_LIBRARY_DEBUG}
)
set (JSON_RPC_CPP_CLIENT_LIBRARIES
optimized ${JSON_RPC_CPP_COMMON_LIBRARY}
optimized ${JSON_RPC_CPP_CLIENT_LIBRARY}
debug ${JSON_RPC_CPP_COMMON_LIBRARY_DEBUG}
debug ${JSON_RPC_CPP_CLIENT_LIBRARY_DEBUG}
)
endif()
# handle the QUIETLY and REQUIRED arguments and set JSON_RPC_CPP_FOUND to TRUE
# if all listed variables are TRUE, hide their existence from configuration view
include(FindPackageHandleStandardArgs)

2
libdevcore/Common.cpp

@ -27,7 +27,7 @@ using namespace dev;
namespace dev
{
char const* Version = "0.7.12";
char const* Version = "0.7.13";
}

2
libdevcrypto/Common.h

@ -47,7 +47,7 @@ struct SignatureStruct
{
SignatureStruct() {}
SignatureStruct(Signature const& _s) { *(h520*)this = _s; }
SignatureStruct(h256 _r, h256 _s, byte _v): r(_r), s(_s), v(_v) {}
SignatureStruct(h256 const& _r, h256 const& _s, byte _v): r(_r), s(_s), v(_v) {}
operator Signature() const { return *(h520 const*)this; }
/// @returns true if r,s,v values are valid, otherwise false

2
libethcore/CommonEth.cpp

@ -32,7 +32,7 @@ namespace dev
namespace eth
{
const unsigned c_protocolVersion = 49;
const unsigned c_protocolVersion = 50;
const unsigned c_databaseVersion = 5;
static const vector<pair<u256, string>> g_units =

7
libethereum/Transaction.cpp

@ -43,7 +43,10 @@ Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender)
m_receiveAddress = rlp[field = 3].toHash<Address>();
m_value = rlp[field = 4].toInt<u256>();
m_data = rlp[field = 5].toBytes();
m_vrs = SignatureStruct{ rlp[field = 7].toInt<u256>(), rlp[field = 8].toInt<u256>(), byte(rlp[field = 6].toInt<byte>() - 27) };
byte v = rlp[field = 6].toInt<byte>() - 27;
h256 r = rlp[field = 7].toInt<u256>();
h256 s = rlp[field = 8].toInt<u256>();
m_vrs = SignatureStruct{ r, s, v };
if (_checkSender)
m_sender = sender();
}
@ -71,7 +74,7 @@ Address Transaction::sender() const
{
if (!m_sender)
{
auto p = recover(*(Signature const*)&m_vrs, sha3(WithoutSignature));
auto p = recover(m_vrs, sha3(WithoutSignature));
if (!p)
BOOST_THROW_EXCEPTION(InvalidSignature());
m_sender = right160(dev::sha3(bytesConstRef(p.data(), sizeof(p))));

9
libevm/VM.h

@ -217,6 +217,8 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st
require(7);
runGas = (bigint)c_callGas + m_stack[m_stack.size() - 1];
newTempSize = std::max(memNeed(m_stack[m_stack.size() - 6], m_stack[m_stack.size() - 7]), memNeed(m_stack[m_stack.size() - 4], m_stack[m_stack.size() - 5]));
if (_ext.depth == 1024)
BOOST_THROW_EXCEPTION(OutOfGas());
break;
case Instruction::CREATE:
@ -226,6 +228,8 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st
u256 inSize = m_stack[m_stack.size() - 3];
newTempSize = (bigint)inOff + inSize;
runGas = c_createGas;
if (_ext.depth == 1024)
BOOST_THROW_EXCEPTION(OutOfGas());
break;
}
case Instruction::EXP:
@ -566,6 +570,7 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st
break;
default:
// this is unreachable, but if someone introduces a bug in the future, he may get here.
assert(false);
BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("CALLDATACOPY, CODECOPY or EXTCODECOPY instruction requested."));
break;
}
@ -795,8 +800,6 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st
if (_ext.balance(_ext.myAddress) >= endowment)
{
if (_ext.depth == 1024)
BOOST_THROW_EXCEPTION(OutOfGas());
_ext.subBalance(endowment);
m_stack.push_back((u160)_ext.create(endowment, m_gas, bytesConstRef(m_temp.data() + initOff, initSize), _onOp));
}
@ -825,8 +828,6 @@ inline bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _st
if (_ext.balance(_ext.myAddress) >= value)
{
if (_ext.depth == 1024)
BOOST_THROW_EXCEPTION(OutOfGas());
_ext.subBalance(value);
m_stack.push_back(_ext.call(inst == Instruction::CALL ? receiveAddress : _ext.myAddress, value, bytesConstRef(m_temp.data() + inOff, inSize), gas, bytesRef(m_temp.data() + outOff, outSize), _onOp, {}, receiveAddress));
}

42
libevmcore/Assembly.cpp

@ -39,6 +39,7 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
case Push:
return 1 + max<unsigned>(1, dev::bytesRequired(m_data));
case PushSubSize:
case PushProgramSize:
return 4; // worst case: a 16MB program
case PushTag:
case PushData:
@ -59,7 +60,13 @@ int AssemblyItem::deposit() const
{
case Operation:
return instructionInfo((Instruction)(byte)m_data).ret - instructionInfo((Instruction)(byte)m_data).args;
case Push: case PushString: case PushTag: case PushData: case PushSub: case PushSubSize:
case Push:
case PushString:
case PushTag:
case PushData:
case PushSub:
case PushSubSize:
case PushProgramSize:
return 1;
case Tag:
return 0;
@ -146,6 +153,9 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i)
case PushSubSize:
_out << " PUSHss[" << hex << h256(i.data()).abridged() << "]";
break;
case PushProgramSize:
_out << " PUSHSIZE";
break;
case NoOptimizeBegin:
_out << " DoNotOptimze{{";
break;
@ -185,6 +195,9 @@ ostream& Assembly::streamRLP(ostream& _out, string const& _prefix) const
case PushSubSize:
_out << _prefix << " PUSH #[$" << h256(i.m_data).abridged() << "]" << endl;
break;
case PushProgramSize:
_out << _prefix << " PUSHSIZE" << endl;
break;
case Tag:
_out << _prefix << "tag" << i.m_data << ": " << endl << _prefix << " JUMPDEST" << endl;
break;
@ -303,6 +316,7 @@ Assembly& Assembly::optimise(bool _enable)
{ { PushString, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushSub, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushSubSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { PushProgramSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
{ { Push, PushTag, Instruction::JUMPI }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data()) return { m[1], Instruction::JUMP }; else return {}; } },
{ { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
};
@ -464,16 +478,22 @@ bytes Assembly::assemble() const
bytes ret;
unsigned totalBytes = bytesRequired();
ret.reserve(totalBytes);
vector<unsigned> tagPos(m_usedTags);
map<unsigned, unsigned> tagRef;
multimap<h256, unsigned> dataRef;
vector<unsigned> sizeRef; ///< Pointers to code locations where the size of the program is inserted
unsigned bytesPerTag = dev::bytesRequired(totalBytes);
byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag;
for (auto const& i: m_subs)
m_data[i.first] = i.second.assemble();
unsigned bytesRequiredIncludingData = bytesRequired();
unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData);
byte dataRefPush = (byte)Instruction::PUSH1 - 1 + bytesPerDataRef;
ret.reserve(bytesRequiredIncludingData);
// m_data must not change from here on
for (AssemblyItem const& i: m_items)
switch (i.m_type)
{
@ -511,9 +531,9 @@ bytes Assembly::assemble() const
}
case PushData: case PushSub:
{
ret.push_back(tagPush);
ret.push_back(dataRefPush);
dataRef.insert(make_pair((h256)i.m_data, ret.size()));
ret.resize(ret.size() + bytesPerTag);
ret.resize(ret.size() + bytesPerDataRef);
break;
}
case PushSubSize:
@ -526,6 +546,13 @@ bytes Assembly::assemble() const
toBigEndian(s, byr);
break;
}
case PushProgramSize:
{
ret.push_back(dataRefPush);
sizeRef.push_back(ret.size());
ret.resize(ret.size() + bytesPerDataRef);
break;
}
case Tag:
tagPos[(unsigned)i.m_data] = ret.size();
ret.push_back((byte)Instruction::JUMPDEST);
@ -553,7 +580,7 @@ bytes Assembly::assemble() const
{
for (auto it = its.first; it != its.second; ++it)
{
bytesRef r(ret.data() + it->second, bytesPerTag);
bytesRef r(ret.data() + it->second, bytesPerDataRef);
toBigEndian(ret.size(), r);
}
for (auto b: i.second)
@ -561,5 +588,10 @@ bytes Assembly::assemble() const
}
}
}
for (unsigned pos: sizeRef)
{
bytesRef r(ret.data() + pos, bytesPerDataRef);
toBigEndian(ret.size(), r);
}
return ret;
}

5
libevmcore/Assembly.h

@ -32,7 +32,7 @@ namespace dev
namespace eth
{
enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, Tag, PushData, NoOptimizeBegin, NoOptimizeEnd };
enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData, NoOptimizeBegin, NoOptimizeEnd };
class Assembly;
@ -86,6 +86,9 @@ public:
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)); }
AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; }
AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; }

3
libserpent/bignum.h

@ -35,4 +35,7 @@ bool decimalGt(std::string a, std::string b, bool eqAllowed=false);
unsigned decimalToUnsigned(std::string a);
#define utd unsignedToDecimal
#define dtu decimalToUnsigned
#endif

279
libserpent/compiler.cpp

@ -6,6 +6,7 @@
#include "bignum.h"
#include "opcodes.h"
// Auxiliary data that is gathered while compiling
struct programAux {
std::map<std::string, std::string> vars;
int nextVarMem;
@ -13,15 +14,19 @@ struct programAux {
bool calldataUsed;
int step;
int labelLength;
int functionCount;
};
// Auxiliary data that gets passed down vertically
// but not back up
struct programVerticalAux {
int height;
std::string innerScopeName;
std::map<std::string, int> dupvars;
std::map<std::string, int> funvars;
std::vector<mss> scopes;
};
// Compilation result
struct programData {
programAux aux;
Node code;
@ -34,7 +39,6 @@ programAux Aux() {
o.calldataUsed = false;
o.step = 0;
o.nextVarMem = 32;
o.functionCount = 0;
return o;
}
@ -43,6 +47,7 @@ programVerticalAux verticalAux() {
o.height = 0;
o.dupvars = std::map<std::string, int>();
o.funvars = std::map<std::string, int>();
o.scopes = std::vector<mss>();
return o;
}
@ -72,29 +77,58 @@ Node popwrap(Node node) {
return multiToken(nodelist, 2, node.metadata);
}
// Grabs variables
mss getVariables(Node node, mss cur=mss()) {
Metadata m = node.metadata;
// Tokens don't contain any variables
if (node.type == TOKEN)
return cur;
// Don't descend into call fragments
else if (node.val == "lll")
return getVariables(node.args[1], cur);
// At global scope get/set/ref also declare
else if (node.val == "get" || node.val == "set" || node.val == "ref") {
if (node.args[0].type != TOKEN)
err("Variable name must be simple token,"
" not complex expression! " + printSimple(node.args[0]), m);
if (!cur.count(node.args[0].val)) {
cur[node.args[0].val] = utd(cur.size() * 32 + 32);
//std::cerr << node.args[0].val << " " << cur[node.args[0].val] << "\n";
}
}
// Recursively process children
for (unsigned i = 0; i < node.args.size(); i++) {
cur = getVariables(node.args[i], cur);
}
return cur;
}
// Turns LLL tree into tree of code fragments
programData opcodeify(Node node,
programAux aux=Aux(),
programVerticalAux vaux=verticalAux()) {
std::string symb = "_"+mkUniqueToken();
Metadata m = node.metadata;
// Get variables
if (!aux.vars.size()) {
aux.vars = getVariables(node);
aux.nextVarMem = aux.vars.size() * 32 + 32;
}
// Numbers
if (node.type == TOKEN) {
return pd(aux, nodeToNumeric(node), 1);
}
else if (node.val == "ref" || node.val == "get" ||
node.val == "set" || node.val == "declare") {
else if (node.val == "ref" || node.val == "get" || node.val == "set") {
std::string varname = node.args[0].val;
if (!aux.vars.count(varname)) {
aux.vars[varname] = unsignedToDecimal(aux.nextVarMem);
aux.nextVarMem += 32;
}
if (varname == "'msg.data") aux.calldataUsed = true;
// Determine reference to variable
Node varNode = tkn(aux.vars[varname], m);
//std::cerr << varname << " " << printSimple(varNode) << "\n";
// Set variable
if (node.val == "set") {
programData sub = opcodeify(node.args[1], aux, vaux);
if (!sub.outs)
err("Value to set variable must have nonzero arity!", m);
// What if we are setting a stack variable?
if (vaux.dupvars.count(node.args[0].val)) {
int h = vaux.height - vaux.dupvars[node.args[0].val];
if (h > 16) err("Too deep for stack variable (max 16)", m);
@ -105,149 +139,65 @@ programData opcodeify(Node node,
};
return pd(sub.aux, multiToken(nodelist, 3, m), 0);
}
Node nodelist[] = {
sub.code,
token(sub.aux.vars[varname], m),
token("MSTORE", m),
};
return pd(sub.aux, multiToken(nodelist, 3, m), 0);
// Setting a memory variable
else {
Node nodelist[] = {
sub.code,
varNode,
token("MSTORE", m),
};
return pd(sub.aux, multiToken(nodelist, 3, m), 0);
}
}
// Get variable
else if (node.val == "get") {
if (vaux.dupvars.count(node.args[0].val)) {
// Getting a stack variable
if (vaux.dupvars.count(node.args[0].val)) {
int h = vaux.height - vaux.dupvars[node.args[0].val];
if (h > 16) err("Too deep for stack variable (max 16)", m);
return pd(aux, token("DUP"+unsignedToDecimal(h)), 1);
}
Node nodelist[] =
{ token(aux.vars[varname], m), token("MLOAD", m) };
return pd(aux, multiToken(nodelist, 2, m), 1);
// Getting a memory variable
else {
Node nodelist[] =
{ varNode, token("MLOAD", m) };
return pd(aux, multiToken(nodelist, 2, m), 1);
}
}
// Refer variable
else if (node.val == "ref") {
if (vaux.dupvars.count(node.args[0].val))
err("Cannot ref stack variable!", m);
return pd(aux, token(aux.vars[varname], m), 1);
}
// Declare variable
else {
return pd(aux, multiToken(nullptr, 0, m), 0);
return pd(aux, varNode, 1);
}
}
// Define functions (TODO: eventually move to rewriter.cpp, keep
// compiler pure LLL)
if (node.val == "def") {
std::vector<std::string> varNames;
std::vector<int> varSizes;
bool useLt32 = false;
int totalSz = 0;
if (node.args.size() != 2)
err("Malformed def!", m);
// Collect the list of variable names and variable byte counts
for (unsigned i = 0; i < node.args[0].args.size(); i++) {
if (node.args[0].args[i].val == "kv") {
if (node.args[0].args[i].args.size() != 2)
err("Malformed def!", m);
varNames.push_back(node.args[0].args[i].args[0].val);
varSizes.push_back(
decimalToUnsigned(node.args[0].args[i].args[1].val));
if (varSizes.back() > 32)
err("Max argument width: 32 bytes", m);
useLt32 = true;
// Comments do nothing
else if (node.val == "comment") {
Node* nodelist = nullptr;
return pd(aux, multiToken(nodelist, 0, m), 0);
}
// Custom operation sequence
// eg. (ops bytez id msize swap1 msize add 0 swap1 mstore) == alloc
if (node.val == "ops") {
std::vector<Node> subs2;
int depth = 0;
for (unsigned i = 0; i < node.args.size(); i++) {
std::string op = upperCase(node.args[i].val);
if (node.args[i].type == ASTNODE || opinputs(op) == -1) {
programVerticalAux vaux2 = vaux;
vaux2.height = vaux.height - i - 1 + node.args.size();
programData sub = opcodeify(node.args[i], aux, vaux2);
aux = sub.aux;
depth += sub.outs;
subs2.push_back(sub.code);
}
else {
varNames.push_back(node.args[0].args[i].val);
varSizes.push_back(32);
subs2.push_back(token(op, m));
depth += opoutputs(op) - opinputs(op);
}
aux.vars[varNames.back()] = unsignedToDecimal(aux.nextVarMem + 32 * i);
totalSz += varSizes.back();
}
int functionCount = aux.functionCount;
int nextVarMem = aux.nextVarMem;
aux.nextVarMem += 32 * varNames.size();
aux.functionCount += 1;
programData inner;
// If we're only using 32-byte variables, then great, just copy
// over the calldata!
if (!useLt32) {
programData sub = opcodeify(node.args[1], aux, vaux);
Node nodelist[] = {
token(unsignedToDecimal(totalSz), m),
token("1", m),
token(unsignedToDecimal(nextVarMem), m),
token("CALLDATACOPY", m),
sub.code
};
inner = pd(sub.aux, multiToken(nodelist, 5, m), 0);
}
else {
std::vector<Node> innerList;
int cum = 1;
for (unsigned i = 0; i < varNames.size();) {
// If we get a series of 32-byte values, we calldatacopy them
if (varSizes[i] == 32) {
unsigned until = i+1;
while (until < varNames.size() && varSizes[until] == 32)
until += 1;
innerList.push_back(token(unsignedToDecimal((until - i) * 32), m));
innerList.push_back(token(unsignedToDecimal(cum), m));
innerList.push_back(token(unsignedToDecimal(nextVarMem + i * 32), m));
innerList.push_back(token("CALLDATACOPY", m));
cum += (until - i) * 32;
i = until;
}
// Otherwise, we do a clever trick to extract the value
else {
innerList.push_back(token(unsignedToDecimal(32 - varSizes[i]), m));
innerList.push_back(token("256", m));
innerList.push_back(token("EXP", m));
innerList.push_back(token(unsignedToDecimal(cum), m));
innerList.push_back(token("CALLDATALOAD", m));
innerList.push_back(token("DIV", m));
innerList.push_back(token(unsignedToDecimal(nextVarMem + i * 32), m));
innerList.push_back(token("MSTORE", m));
cum += varSizes[i];
i += 1;
}
}
// If caller == origin, then it's from a tx, so unpack, otherwise
// plain copy
programData sub = opcodeify(node.args[1], aux, vaux);
Node ilnode = astnode("", innerList, m);
Node nodelist[] = {
token(unsignedToDecimal(32 * varNames.size()), m),
token("1", m),
token(unsignedToDecimal(nextVarMem), m),
token("CALLDATACOPY", m),
token("CALLER", m),
token("ORIGIN", m),
token("EQ", m),
token("ISZERO", m),
token("$maincode"+symb, m),
token("JUMPI", m),
ilnode,
token("~maincode"+symb, m),
token("JUMPDEST", m),
sub.code
};
inner = pd(sub.aux, multiToken(nodelist, 14, m), 0);
}
// Check if the function call byte is the same
Node nodelist2[] = {
token("0", m),
token("CALLDATALOAD", m),
token("0", m),
token("BYTE", m),
token(unsignedToDecimal(functionCount), m),
token("EQ", m),
token("ISZERO", m),
token("$endcode"+symb, m),
token("JUMPI", m),
inner.code,
token("~endcode"+symb, m),
token("JUMPDEST", m),
};
return pd(inner.aux, multiToken(nodelist2, 12, m), 0);
if (depth < 0 || depth > 1) err("Stack depth mismatch", m);
return pd(aux, astnode("_", subs2, m), 0);
}
// Code blocks
if (node.val == "lll" && node.args.size() == 2) {
@ -372,49 +322,14 @@ programData opcodeify(Node node,
};
return pd(aux, multiToken(nodelist, 8, m), 1);
}
// Array literals
else if (node.val == "array_lit") {
aux.allocUsed = true;
std::vector<Node> nodes;
if (!node.args.size()) {
nodes.push_back(token("MSIZE", m));
return pd(aux, astnode("_", nodes, m));
}
nodes.push_back(token("MSIZE", m));
nodes.push_back(token("0", m));
nodes.push_back(token("MSIZE", m));
nodes.push_back(token(unsignedToDecimal(node.args.size() * 32 - 1), m));
nodes.push_back(token("ADD", m));
nodes.push_back(token("MSTORE8", m));
for (unsigned i = 0; i < node.args.size(); i++) {
Metadata m2 = node.args[i].metadata;
nodes.push_back(token("DUP1", m2));
programVerticalAux vaux2 = vaux;
vaux2.height += 2;
programData sub = opcodeify(node.args[i], aux, vaux2);
if (!sub.outs)
err("Array_lit item " + unsignedToDecimal(i) + " has zero arity", m2);
aux = sub.aux;
nodes.push_back(sub.code);
nodes.push_back(token("SWAP1", m2));
if (i > 0) {
nodes.push_back(token(unsignedToDecimal(i * 32), m2));
nodes.push_back(token("ADD", m2));
}
nodes.push_back(token("MSTORE", m2));
}
return pd(aux, astnode("_", nodes, m), 1);
}
// All other functions/operators
else {
std::vector<Node> subs2;
int depth = opinputs(upperCase(node.val));
if (node.val != "debug") {
if (depth == -1)
err("Not a function or opcode: "+node.val, m);
if ((int)node.args.size() != depth)
err("Invalid arity for "+node.val, m);
}
if (depth == -1)
err("Not a function or opcode: "+node.val, m);
if ((int)node.args.size() != depth)
err("Invalid arity for "+node.val, m);
for (int i = node.args.size() - 1; i >= 0; i--) {
programVerticalAux vaux2 = vaux;
vaux2.height = vaux.height - i - 1 + node.args.size();
@ -424,13 +339,8 @@ programData opcodeify(Node node,
err("Input "+unsignedToDecimal(i)+" has arity 0", sub.code.metadata);
subs2.push_back(sub.code);
}
if (node.val == "debug") {
subs2.push_back(token("DUP"+unsignedToDecimal(node.args.size()), m));
for (int i = 0; i <= (int)node.args.size(); i++)
subs2.push_back(token("POP", m));
}
else subs2.push_back(token(upperCase(node.val), m));
int outdepth = node.val == "debug" ? 0 : opoutputs(upperCase(node.val));
subs2.push_back(token(upperCase(node.val), m));
int outdepth = opoutputs(upperCase(node.val));
return pd(aux, astnode("_", subs2, m), outdepth);
}
}
@ -449,15 +359,6 @@ Node finalize(programData c) {
};
bottom.push_back(multiToken(nodelist, 3, m));
}
// If msg.data is being used as an array, then we need to copy it
if (c.aux.calldataUsed) {
Node nodelist[] = {
token("MSIZE", m), token("CALLDATASIZE", m), token("0", m),
token("MSIZE", m), token("CALLDATACOPY", m),
token(c.aux.vars["'msg.data"], m), token("MSTORE", m)
};
bottom.push_back(multiToken(nodelist, 7, m));
}
// The actual code
bottom.push_back(c.code);
return astnode("_", bottom, m);

203
libserpent/functions.cpp

@ -0,0 +1,203 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "optimize.h"
#include "rewriteutils.h"
#include "preprocess.h"
#include "functions.h"
std::string getSignature(std::vector<Node> args) {
std::string o;
for (unsigned i = 0; i < args.size(); i++) {
if (args[i].val == ":" && args[i].args[1].val == "s")
o += "s";
else if (args[i].val == ":" && args[i].args[1].val == "a")
o += "a";
else
o += "i";
}
return o;
}
// Convert a list of arguments into a node containing a
// < datastart, datasz > pair
Node packArguments(std::vector<Node> args, std::string sig,
int funId, Metadata m) {
// Plain old 32 byte arguments
std::vector<Node> nargs;
// Variable-sized arguments
std::vector<Node> vargs;
// Variable sizes
std::vector<Node> sizes;
// Is a variable an array?
std::vector<bool> isArray;
// Fill up above three argument lists
int argCount = 0;
for (unsigned i = 0; i < args.size(); i++) {
Metadata m = args[i].metadata;
if (args[i].val == "=") {
// do nothing
}
else {
// Determine the correct argument type
char argType;
if (sig.size() > 0) {
if (argCount >= (signed)sig.size())
err("Too many args", m);
argType = sig[argCount];
}
else argType = 'i';
// Integer (also usable for short strings)
if (argType == 'i') {
if (args[i].val == ":")
err("Function asks for int, provided string or array", m);
nargs.push_back(args[i]);
}
// Long string
else if (argType == 's') {
if (args[i].val != ":")
err("Must specify string length", m);
vargs.push_back(args[i].args[0]);
sizes.push_back(args[i].args[1]);
isArray.push_back(false);
}
// Array
else if (argType == 'a') {
if (args[i].val != ":")
err("Must specify array length", m);
vargs.push_back(args[i].args[0]);
sizes.push_back(args[i].args[1]);
isArray.push_back(true);
}
else err("Invalid arg type in signature", m);
argCount++;
}
}
int static_arg_size = 1 + (vargs.size() + nargs.size()) * 32;
// Start off by saving the size variables and calculating the total
msn kwargs;
kwargs["funid"] = tkn(utd(funId), m);
std::string pattern =
"(with _sztot "+utd(static_arg_size)+" "
" (with _sizes (alloc "+utd(sizes.size() * 32)+") "
" (seq ";
for (unsigned i = 0; i < sizes.size(); i++) {
std::string sizeIncrement =
isArray[i] ? "(mul 32 _x)" : "_x";
pattern +=
"(with _x $sz"+utd(i)+"(seq "
" (mstore (add _sizes "+utd(i * 32)+") _x) "
" (set _sztot (add _sztot "+sizeIncrement+" )))) ";
kwargs["sz"+utd(i)] = sizes[i];
}
// Allocate memory, and set first data byte
pattern +=
"(with _datastart (alloc (add _sztot 32)) (seq "
" (mstore8 _datastart $funid) ";
// Copy over size variables
for (unsigned i = 0; i < sizes.size(); i++) {
int v = 1 + i * 32;
pattern +=
" (mstore "
" (add _datastart "+utd(v)+") "
" (mload (add _sizes "+utd(v-1)+"))) ";
}
// Store normal arguments
for (unsigned i = 0; i < nargs.size(); i++) {
int v = 1 + (i + sizes.size()) * 32;
pattern +=
" (mstore (add _datastart "+utd(v)+") $"+utd(i)+") ";
kwargs[utd(i)] = nargs[i];
}
// Loop through variable-sized arguments, store them
pattern +=
" (with _pos (add _datastart "+utd(static_arg_size)+") (seq";
for (unsigned i = 0; i < vargs.size(); i++) {
std::string copySize =
isArray[i] ? "(mul 32 (mload (add _sizes "+utd(i * 32)+")))"
: "(mload (add _sizes "+utd(i * 32)+"))";
pattern +=
" (unsafe_mcopy _pos $vl"+utd(i)+" "+copySize+") "
" (set _pos (add _pos "+copySize+")) ";
kwargs["vl"+utd(i)] = vargs[i];
}
// Return a 2-item array containing the start and size
pattern += " (array_lit _datastart _sztot))))))))";
std::string prefix = "_temp_"+mkUniqueToken();
// Fill in pattern, return triple
return subst(parseLLL(pattern), kwargs, prefix, m);
}
// Create a node for argument unpacking
Node unpackArguments(std::vector<Node> vars, Metadata m) {
std::vector<std::string> varNames;
std::vector<std::string> longVarNames;
std::vector<bool> longVarIsArray;
// Fill in variable and long variable names, as well as which
// long variables are arrays and which are strings
for (unsigned i = 0; i < vars.size(); i++) {
if (vars[i].val == ":") {
if (vars[i].args.size() != 2)
err("Malformed def!", m);
longVarNames.push_back(vars[i].args[0].val);
std::string tag = vars[i].args[1].val;
if (tag == "s")
longVarIsArray.push_back(false);
else if (tag == "a")
longVarIsArray.push_back(true);
else
err("Function value can only be string or array", m);
}
else {
varNames.push_back(vars[i].val);
}
}
std::vector<Node> sub;
if (!varNames.size() && !longVarNames.size()) {
// do nothing if we have no arguments
}
else {
std::vector<Node> varNodes;
for (unsigned i = 0; i < longVarNames.size(); i++)
varNodes.push_back(token(longVarNames[i], m));
for (unsigned i = 0; i < varNames.size(); i++)
varNodes.push_back(token(varNames[i], m));
// Copy over variable lengths and short variables
for (unsigned i = 0; i < varNodes.size(); i++) {
int pos = 1 + i * 32;
std::string prefix = (i < longVarNames.size()) ? "_len_" : "";
sub.push_back(asn("untyped", asn("set",
token(prefix+varNodes[i].val, m),
asn("calldataload", tkn(utd(pos), m), m),
m)));
}
// Copy over long variables
if (longVarNames.size() > 0) {
std::vector<Node> sub2;
int pos = varNodes.size() * 32 + 1;
Node tot = tkn("_tot", m);
for (unsigned i = 0; i < longVarNames.size(); i++) {
Node var = tkn(longVarNames[i], m);
Node varlen = longVarIsArray[i]
? asn("mul", tkn("32", m), tkn("_len_"+longVarNames[i], m))
: tkn("_len_"+longVarNames[i], m);
sub2.push_back(asn("untyped",
asn("set", var, asn("alloc", varlen))));
sub2.push_back(asn("calldatacopy", var, tot, varlen));
sub2.push_back(asn("set", tot, asn("add", tot, varlen)));
}
std::string prefix = "_temp_"+mkUniqueToken();
sub.push_back(subst(
astnode("with", tot, tkn(utd(pos), m), asn("seq", sub2)),
msn(),
prefix,
m));
}
}
return asn("seq", sub, m);
}

39
libserpent/functions.h

@ -0,0 +1,39 @@
#ifndef ETHSERP_FUNCTIONS
#define ETHSERP_FUNCTIONS
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "optimize.h"
#include "rewriteutils.h"
#include "preprocess.h"
class argPack {
public:
argPack(Node a, Node b, Node c) {
pre = a;
datastart = b;
datasz = c;
}
Node pre;
Node datastart;
Node datasz;
};
// Get a signature from a function
std::string getSignature(std::vector<Node> args);
// Convert a list of arguments into a <pre, mstart, msize> node
// triple, given the signature of a function
Node packArguments(std::vector<Node> args, std::string sig,
int funId, Metadata m);
// Create a node for argument unpacking
Node unpackArguments(std::vector<Node> vars, Metadata m);
#endif

154
libserpent/opcodes.cpp

@ -0,0 +1,154 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "opcodes.h"
#include "util.h"
#include "bignum.h"
Mapping mapping[] = {
Mapping("STOP", 0x00, 0, 0),
Mapping("ADD", 0x01, 2, 1),
Mapping("MUL", 0x02, 2, 1),
Mapping("SUB", 0x03, 2, 1),
Mapping("DIV", 0x04, 2, 1),
Mapping("SDIV", 0x05, 2, 1),
Mapping("MOD", 0x06, 2, 1),
Mapping("SMOD", 0x07, 2, 1),
Mapping("ADDMOD", 0x08, 3, 1),
Mapping("MULMOD", 0x09, 3, 1),
Mapping("EXP", 0x0a, 2, 1),
Mapping("SIGNEXTEND", 0x0b, 2, 1),
Mapping("LT", 0x10, 2, 1),
Mapping("GT", 0x11, 2, 1),
Mapping("SLT", 0x12, 2, 1),
Mapping("SGT", 0x13, 2, 1),
Mapping("EQ", 0x14, 2, 1),
Mapping("ISZERO", 0x15, 1, 1),
Mapping("AND", 0x16, 2, 1),
Mapping("OR", 0x17, 2, 1),
Mapping("XOR", 0x18, 2, 1),
Mapping("NOT", 0x19, 1, 1),
Mapping("BYTE", 0x1a, 2, 1),
Mapping("SHA3", 0x20, 2, 1),
Mapping("ADDRESS", 0x30, 0, 1),
Mapping("BALANCE", 0x31, 1, 1),
Mapping("ORIGIN", 0x32, 0, 1),
Mapping("CALLER", 0x33, 0, 1),
Mapping("CALLVALUE", 0x34, 0, 1),
Mapping("CALLDATALOAD", 0x35, 1, 1),
Mapping("CALLDATASIZE", 0x36, 0, 1),
Mapping("CALLDATACOPY", 0x37, 3, 0),
Mapping("CODESIZE", 0x38, 0, 1),
Mapping("CODECOPY", 0x39, 3, 0),
Mapping("GASPRICE", 0x3a, 0, 1),
Mapping("EXTCODESIZE", 0x3b, 1, 1),
Mapping("EXTCODECOPY", 0x3c, 4, 0),
Mapping("PREVHASH", 0x40, 0, 1),
Mapping("COINBASE", 0x41, 0, 1),
Mapping("TIMESTAMP", 0x42, 0, 1),
Mapping("NUMBER", 0x43, 0, 1),
Mapping("DIFFICULTY", 0x44, 0, 1),
Mapping("GASLIMIT", 0x45, 0, 1),
Mapping("POP", 0x50, 1, 0),
Mapping("MLOAD", 0x51, 1, 1),
Mapping("MSTORE", 0x52, 2, 0),
Mapping("MSTORE8", 0x53, 2, 0),
Mapping("SLOAD", 0x54, 1, 1),
Mapping("SSTORE", 0x55, 2, 0),
Mapping("JUMP", 0x56, 1, 0),
Mapping("JUMPI", 0x57, 2, 0),
Mapping("PC", 0x58, 0, 1),
Mapping("MSIZE", 0x59, 0, 1),
Mapping("GAS", 0x5a, 0, 1),
Mapping("JUMPDEST", 0x5b, 0, 0),
Mapping("LOG0", 0xa0, 2, 0),
Mapping("LOG1", 0xa1, 3, 0),
Mapping("LOG2", 0xa2, 4, 0),
Mapping("LOG3", 0xa3, 5, 0),
Mapping("LOG4", 0xa4, 6, 0),
Mapping("CREATE", 0xf0, 3, 1),
Mapping("CALL", 0xf1, 7, 1),
Mapping("CALLCODE", 0xf2, 7, 1),
Mapping("RETURN", 0xf3, 2, 0),
Mapping("SUICIDE", 0xff, 1, 0),
Mapping("---END---", 0x00, 0, 0),
};
std::map<std::string, std::vector<int> > opcodes;
std::map<int, std::string> reverseOpcodes;
// Fetches everything EXCEPT PUSH1..32
std::pair<std::string, std::vector<int> > _opdata(std::string ops, int opi) {
if (!opcodes.size()) {
int i = 0;
while (mapping[i].op != "---END---") {
Mapping mi = mapping[i];
opcodes[mi.op] = triple(mi.opcode, mi.in, mi.out);
i++;
}
for (i = 1; i <= 16; i++) {
opcodes["DUP"+unsignedToDecimal(i)] = triple(0x7f + i, i, i+1);
opcodes["SWAP"+unsignedToDecimal(i)] = triple(0x8f + i, i+1, i+1);
}
for (std::map<std::string, std::vector<int> >::iterator it=opcodes.begin();
it != opcodes.end();
it++) {
reverseOpcodes[(*it).second[0]] = (*it).first;
}
}
ops = upperCase(ops);
std::string op;
std::vector<int> opdata;
op = reverseOpcodes.count(opi) ? reverseOpcodes[opi] : "";
opdata = opcodes.count(ops) ? opcodes[ops] : triple(-1, -1, -1);
return std::pair<std::string, std::vector<int> >(op, opdata);
}
int opcode(std::string op) {
return _opdata(op, -1).second[0];
}
int opinputs(std::string op) {
return _opdata(op, -1).second[1];
}
int opoutputs(std::string op) {
return _opdata(op, -1).second[2];
}
std::string op(int opcode) {
return _opdata("", opcode).first;
}
std::string lllSpecials[][3] = {
{ "ref", "1", "1" },
{ "get", "1", "1" },
{ "set", "2", "2" },
{ "with", "3", "3" },
{ "comment", "0", "2147483647" },
{ "ops", "0", "2147483647" },
{ "lll", "2", "2" },
{ "seq", "0", "2147483647" },
{ "if", "3", "3" },
{ "unless", "2", "2" },
{ "until", "2", "2" },
{ "alloc", "1", "1" },
{ "---END---", "0", "0" },
};
std::map<std::string, std::pair<int, int> > lllMap;
// Is a function name one of the valid functions above?
bool isValidLLLFunc(std::string f, int argc) {
if (lllMap.size() == 0) {
for (int i = 0; ; i++) {
if (lllSpecials[i][0] == "---END---") break;
lllMap[lllSpecials[i][0]] = std::pair<int, int>(
dtu(lllSpecials[i][1]), dtu(lllSpecials[i][2]));
}
}
return lllMap.count(f)
&& argc >= lllMap[f].first
&& argc <= lllMap[f].second;
}

123
libserpent/opcodes.h

@ -5,6 +5,7 @@
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
class Mapping {
public:
@ -20,119 +21,25 @@ class Mapping {
int out;
};
Mapping mapping[] = {
Mapping("STOP", 0x00, 0, 0),
Mapping("ADD", 0x01, 2, 1),
Mapping("MUL", 0x02, 2, 1),
Mapping("SUB", 0x03, 2, 1),
Mapping("DIV", 0x04, 2, 1),
Mapping("SDIV", 0x05, 2, 1),
Mapping("MOD", 0x06, 2, 1),
Mapping("SMOD", 0x07, 2, 1),
Mapping("ADDMOD", 0x08, 3, 1),
Mapping("MULMOD", 0x09, 3, 1),
Mapping("EXP", 0x0a, 2, 1),
Mapping("SIGNEXTEND", 0x0b, 2, 1),
Mapping("LT", 0x10, 2, 1),
Mapping("GT", 0x11, 2, 1),
Mapping("SLT", 0x12, 2, 1),
Mapping("SGT", 0x13, 2, 1),
Mapping("EQ", 0x14, 2, 1),
Mapping("ISZERO", 0x15, 1, 1),
Mapping("AND", 0x16, 2, 1),
Mapping("OR", 0x17, 2, 1),
Mapping("XOR", 0x18, 2, 1),
Mapping("NOT", 0x19, 1, 1),
Mapping("BYTE", 0x1a, 2, 1),
Mapping("ADDMOD", 0x14, 3, 1),
Mapping("MULMOD", 0x15, 3, 1),
Mapping("SIGNEXTEND", 0x16, 2, 1),
Mapping("SHA3", 0x20, 2, 1),
Mapping("ADDRESS", 0x30, 0, 1),
Mapping("BALANCE", 0x31, 1, 1),
Mapping("ORIGIN", 0x32, 0, 1),
Mapping("CALLER", 0x33, 0, 1),
Mapping("CALLVALUE", 0x34, 0, 1),
Mapping("CALLDATALOAD", 0x35, 1, 1),
Mapping("CALLDATASIZE", 0x36, 0, 1),
Mapping("CALLDATACOPY", 0x37, 3, 1),
Mapping("CODESIZE", 0x38, 0, 1),
Mapping("CODECOPY", 0x39, 3, 1),
Mapping("GASPRICE", 0x3a, 0, 1),
Mapping("PREVHASH", 0x40, 0, 1),
Mapping("COINBASE", 0x41, 0, 1),
Mapping("TIMESTAMP", 0x42, 0, 1),
Mapping("NUMBER", 0x43, 0, 1),
Mapping("DIFFICULTY", 0x44, 0, 1),
Mapping("GASLIMIT", 0x45, 0, 1),
Mapping("POP", 0x50, 1, 0),
Mapping("MLOAD", 0x51, 1, 1),
Mapping("MSTORE", 0x52, 2, 0),
Mapping("MSTORE8", 0x53, 2, 0),
Mapping("SLOAD", 0x54, 1, 1),
Mapping("SSTORE", 0x55, 2, 0),
Mapping("JUMP", 0x56, 1, 0),
Mapping("JUMPI", 0x57, 2, 0),
Mapping("PC", 0x58, 0, 1),
Mapping("MSIZE", 0x59, 0, 1),
Mapping("GAS", 0x5a, 0, 1),
Mapping("JUMPDEST", 0x5b, 0, 0),
Mapping("LOG0", 0xa0, 2, 0),
Mapping("LOG1", 0xa1, 3, 0),
Mapping("LOG2", 0xa2, 4, 0),
Mapping("LOG3", 0xa3, 5, 0),
Mapping("LOG4", 0xa4, 6, 0),
Mapping("CREATE", 0xf0, 3, 1),
Mapping("CALL", 0xf1, 7, 1),
Mapping("RETURN", 0xf2, 2, 0),
Mapping("CALL_CODE", 0xf3, 7, 1),
Mapping("SUICIDE", 0xff, 1, 0),
Mapping("---END---", 0x00, 0, 0),
};
extern Mapping mapping[];
std::map<std::string, std::vector<int> > opcodes;
std::map<int, std::string> reverseOpcodes;
extern std::map<std::string, std::vector<int> > opcodes;
extern std::map<int, std::string> reverseOpcodes;
// Fetches everything EXCEPT PUSH1..32
std::pair<std::string, std::vector<int> > _opdata(std::string ops, int opi) {
if (!opcodes.size()) {
int i = 0;
while (mapping[i].op != "---END---") {
Mapping mi = mapping[i];
opcodes[mi.op] = triple(mi.opcode, mi.in, mi.out);
i++;
}
for (i = 1; i <= 16; i++) {
opcodes["DUP"+unsignedToDecimal(i)] = triple(0x7f + i, i, i+1);
opcodes["SWAP"+unsignedToDecimal(i)] = triple(0x8f + i, i+1, i+1);
}
for (std::map<std::string, std::vector<int> >::iterator it=opcodes.begin();
it != opcodes.end();
it++) {
reverseOpcodes[(*it).second[0]] = (*it).first;
}
}
std::string op;
std::vector<int> opdata;
op = reverseOpcodes.count(opi) ? reverseOpcodes[opi] : "";
opdata = opcodes.count(ops) ? opcodes[ops] : triple(-1, -1, -1);
return std::pair<std::string, std::vector<int> >(op, opdata);
}
std::pair<std::string, std::vector<int> > _opdata(std::string ops, int opi);
int opcode(std::string op);
int opinputs(std::string op);
int opoutputs(std::string op);
int opcode(std::string op) {
return _opdata(op, -1).second[0];
}
std::string op(int opcode);
int opinputs(std::string op) {
return _opdata(op, -1).second[1];
}
extern std::string lllSpecials[][3];
int opoutputs(std::string op) {
return _opdata(op, -1).second[2];
}
extern std::map<std::string, std::pair<int, int> > lllMap;
std::string op(int opcode) {
return _opdata("", opcode).first;
}
bool isValidLLLFunc(std::string f, int argc);
#endif

98
libserpent/optimize.cpp

@ -0,0 +1,98 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
// Compile-time arithmetic calculations
Node optimize(Node inp) {
if (inp.type == TOKEN) {
Node o = tryNumberize(inp);
if (decimalGt(o.val, tt256, true))
err("Value too large (exceeds 32 bytes or 2^256)", inp.metadata);
return o;
}
for (unsigned i = 0; i < inp.args.size(); i++) {
inp.args[i] = optimize(inp.args[i]);
}
// Arithmetic-specific transform
if (inp.val == "+") inp.val = "add";
if (inp.val == "*") inp.val = "mul";
if (inp.val == "-") inp.val = "sub";
if (inp.val == "/") inp.val = "sdiv";
if (inp.val == "^") inp.val = "exp";
if (inp.val == "**") inp.val = "exp";
if (inp.val == "%") inp.val = "smod";
// Degenerate cases for add and mul
if (inp.args.size() == 2) {
if (inp.val == "add" && inp.args[0].type == TOKEN &&
inp.args[0].val == "0") {
Node x = inp.args[1];
inp = x;
}
if (inp.val == "add" && inp.args[1].type == TOKEN &&
inp.args[1].val == "0") {
Node x = inp.args[0];
inp = x;
}
if (inp.val == "mul" && inp.args[0].type == TOKEN &&
inp.args[0].val == "1") {
Node x = inp.args[1];
inp = x;
}
if (inp.val == "mul" && inp.args[1].type == TOKEN &&
inp.args[1].val == "1") {
Node x = inp.args[0];
inp = x;
}
}
// Arithmetic computation
if (inp.args.size() == 2
&& inp.args[0].type == TOKEN
&& inp.args[1].type == TOKEN) {
std::string o;
if (inp.val == "add") {
o = decimalMod(decimalAdd(inp.args[0].val, inp.args[1].val), tt256);
}
else if (inp.val == "sub") {
if (decimalGt(inp.args[0].val, inp.args[1].val, true))
o = decimalSub(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "mul") {
o = decimalMod(decimalMul(inp.args[0].val, inp.args[1].val), tt256);
}
else if (inp.val == "div" && inp.args[1].val != "0") {
o = decimalDiv(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "sdiv" && inp.args[1].val != "0"
&& decimalGt(tt255, inp.args[0].val)
&& decimalGt(tt255, inp.args[1].val)) {
o = decimalDiv(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "mod" && inp.args[1].val != "0") {
o = decimalMod(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "smod" && inp.args[1].val != "0"
&& decimalGt(tt255, inp.args[0].val)
&& decimalGt(tt255, inp.args[1].val)) {
o = decimalMod(inp.args[0].val, inp.args[1].val);
}
else if (inp.val == "exp") {
o = decimalModExp(inp.args[0].val, inp.args[1].val, tt256);
}
if (o.length()) return token(o, inp.metadata);
}
return inp;
}
// Is a node degenerate (ie. trivial to calculate) ?
bool isDegenerate(Node n) {
return optimize(n).type == TOKEN;
}
// Is a node purely arithmetic?
bool isPureArithmetic(Node n) {
return isNumberLike(optimize(n));
}

19
libserpent/optimize.h

@ -0,0 +1,19 @@
#ifndef ETHSERP_OPTIMIZER
#define ETHSERP_OPTIMIZER
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Compile-time arithmetic calculations
Node optimize(Node inp);
// Is a node degenerate (ie. trivial to calculate) ?
bool isDegenerate(Node n);
// Is a node purely arithmetic?
bool isPureArithmetic(Node n);
#endif

86
libserpent/parser.cpp

@ -12,17 +12,15 @@ int precedence(Node tok) {
if (v == ".") return -1;
else if (v == "!" || v == "not") return 1;
else if (v=="^" || v == "**") return 2;
else if (v=="*" || v=="/" || v=="@/" || v=="%" || v=="@%") return 3;
else if (v=="*" || v=="/" || v=="%") return 3;
else if (v=="+" || v=="-") return 4;
else if (v=="<" || v==">" || v=="<=" || v==">=") return 5;
else if (v=="@<" || v=="@>" || v=="@<=" || v=="@>=") return 5;
else if (v=="&" || v=="|" || v=="xor" || v=="==" || v == "!=") return 6;
else if (v=="&&" || v=="and") return 7;
else if (v=="||" || v=="or") return 8;
else if (v==":") return 9;
else if (v=="=") return 10;
else if (v=="+=" || v=="-=" || v=="*=" || v=="/=" || v=="%=") return 10;
else if (v=="@/=" || v=="@%=") return 10;
else if (v==":" || v == "::") return 11;
else return 0;
}
@ -223,8 +221,15 @@ Node treefy(std::vector<Node> stream) {
filename = filename.substr(1, filename.length() - 2);
if (!exists(root + filename))
err("File does not exist: "+root + filename, tok.metadata);
oq.back().args.pop_back();
oq.back().args.push_back(parseSerpent(root + filename));
if (v == "inset") {
oq.pop_back();
oq.push_back(parseSerpent(root + filename));
}
else {
oq.back().args.pop_back();
oq.back().args.push_back(
asn("outer", parseSerpent(root + filename), tok.metadata));
}
}
//Useful for debugging
//for (int i = 0; i < oq.size(); i++) {
@ -237,7 +242,7 @@ Node treefy(std::vector<Node> stream) {
err("Output blank", Metadata());
}
else if (oq.size() > 1) {
err("Multiple expressions or unclosed bracket", oq[1].metadata);
return asn("multi", oq, oq[0].metadata);
}
return oq[0];
@ -262,15 +267,9 @@ int spaceCount(std::string s) {
bool bodied(std::string tok) {
return tok == "if" || tok == "elif" || tok == "while"
|| tok == "with" || tok == "def" || tok == "extern"
|| tok == "data";
}
// Is this a command that takes an argument as a child block?
bool childBlocked(std::string tok) {
return tok == "if" || tok == "elif" || tok == "else"
|| tok == "code" || tok == "shared" || tok == "init"
|| tok == "while" || tok == "repeat" || tok == "for"
|| tok == "with" || tok == "def";
|| tok == "data" || tok == "assert" || tok == "return"
|| tok == "fun" || tok == "scope" || tok == "macro"
|| tok == "type";
}
// Are the two commands meant to continue each other?
@ -278,10 +277,7 @@ bool bodiedContinued(std::string prev, std::string tok) {
return (prev == "if" && tok == "elif")
|| (prev == "elif" && tok == "else")
|| (prev == "elif" && tok == "elif")
|| (prev == "if" && tok == "else")
|| (prev == "init" && tok == "code")
|| (prev == "shared" && tok == "code")
|| (prev == "shared" && tok == "init");
|| (prev == "if" && tok == "else");
}
// Is a line of code empty?
@ -310,16 +306,17 @@ Node parseLines(std::vector<std::string> lines, Metadata metadata, int sp) {
}
// Tokenize current line
std::vector<Node> tokens = tokenize(main.substr(sp), metadata);
// Remove extraneous tokens, including if / elif
// Remove comments
std::vector<Node> tokens2;
for (unsigned j = 0; j < tokens.size(); j++) {
if (tokens[j].val == "#" || tokens[j].val == "//") break;
if (j >= 1 || !bodied(tokens[j].val)) {
tokens2.push_back(tokens[j]);
}
tokens2.push_back(tokens[j]);
}
if (tokens2.size() > 0 && tokens2.back().val == ":")
bool expectingChildBlock = false;
if (tokens2.size() > 0 && tokens2.back().val == ":") {
tokens2.pop_back();
expectingChildBlock = true;
}
// Parse current line
Node out = parseSerpentTokenStream(tokens2);
// Parse child block
@ -343,14 +340,8 @@ Node parseLines(std::vector<std::string> lines, Metadata metadata, int sp) {
for (unsigned i = 0; i < childBlock.size(); i++) {
if (childBlock[i].length() > 0) { cbe = false; break; }
}
// Bring back if / elif into AST
if (bodied(tokens[0].val)) {
std::vector<Node> args;
args.push_back(out);
out = astnode(tokens[0].val, args, out.metadata);
}
// Add child block to AST
if (childBlocked(tokens[0].val)) {
if (expectingChildBlock) {
if (cbe)
err("Expected indented child block!", out.metadata);
out.type = ASTNODE;
@ -360,6 +351,37 @@ Node parseLines(std::vector<std::string> lines, Metadata metadata, int sp) {
}
else if (!cbe)
err("Did not expect indented child block!", out.metadata);
else if (out.args.size() && out.args[out.args.size() - 1].val == ":") {
Node n = out.args[out.args.size() - 1];
out.args.pop_back();
out.args.push_back(n.args[0]);
out.args.push_back(n.args[1]);
}
// Bring back if / elif into AST
if (bodied(tokens[0].val)) {
if (out.val != "multi") {
// token not being used in bodied form
}
else if (out.args[0].val == "id")
out = astnode(tokens[0].val, out.args[1].args, out.metadata);
else if (out.args[0].type == TOKEN) {
std::vector<Node> out2;
for (unsigned i = 1; i < out.args.size(); i++)
out2.push_back(out.args[i]);
out = astnode(tokens[0].val, out2, out.metadata);
}
else
out = astnode("fun", out.args, out.metadata);
}
// Multi not supported
if (out.val == "multi")
err("Multiple expressions or unclosed bracket", out.metadata);
// Convert top-level colon expressions into non-colon expressions;
// makes if statements and the like equivalent indented or not
//if (out.val == ":" && out.args[0].type == TOKEN)
// out = asn(out.args[0].val, out.args[1], out.metadata);
//if (bodied(tokens[0].val) && out.args[0].val == ":")
// out = asn(tokens[0].val, out.args[0].args);
if (o.size() == 0 || o.back().type == TOKEN) {
o.push_back(out);
continue;

327
libserpent/preprocess.cpp

@ -0,0 +1,327 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "rewriteutils.h"
#include "optimize.h"
#include "preprocess.h"
#include "functions.h"
#include "opcodes.h"
// Convert a function of the form (def (f x y z) (do stuff)) into
// (if (first byte of ABI is correct) (seq (setup x y z) (do stuff)))
Node convFunction(Node node, int functionCount) {
std::string prefix = "_temp"+mkUniqueToken()+"_";
Metadata m = node.metadata;
if (node.args.size() != 2)
err("Malformed def!", m);
// Collect the list of variable names and variable byte counts
Node unpack = unpackArguments(node.args[0].args, m);
// And the actual code
Node body = node.args[1];
// Main LLL-based function body
return astnode("if",
astnode("eq",
astnode("get", token("__funid", m), m),
token(unsignedToDecimal(functionCount), m),
m),
astnode("seq", unpack, body, m));
}
// Populate an svObj with the arguments needed to determine
// the storage position of a node
svObj getStorageVars(svObj pre, Node node, std::string prefix,
int index) {
Metadata m = node.metadata;
if (!pre.globalOffset.size()) pre.globalOffset = "0";
std::vector<Node> h;
std::vector<std::string> coefficients;
// Array accesses or atoms
if (node.val == "access" || node.type == TOKEN) {
std::string tot = "1";
h = listfyStorageAccess(node);
coefficients.push_back("1");
for (unsigned i = h.size() - 1; i >= 1; i--) {
// Array sizes must be constant or at least arithmetically
// evaluable at compile time
if (!isPureArithmetic(h[i]))
err("Array size must be fixed value", m);
// Create a list of the coefficient associated with each
// array index
coefficients.push_back(decimalMul(coefficients.back(), h[i].val));
}
}
// Tuples
else {
int startc;
// Handle the (fun <fun_astnode> args...) case
if (node.val == "fun") {
startc = 1;
h = listfyStorageAccess(node.args[0]);
}
// Handle the (<fun_name> args...) case, which
// the serpent parser produces when the function
// is a simple name and not a complex astnode
else {
startc = 0;
h = listfyStorageAccess(token(node.val, m));
}
svObj sub = pre;
sub.globalOffset = "0";
// Evaluate tuple elements recursively
for (unsigned i = startc; i < node.args.size(); i++) {
sub = getStorageVars(sub,
node.args[i],
prefix+h[0].val.substr(2)+".",
i-startc);
}
coefficients.push_back(sub.globalOffset);
for (unsigned i = h.size() - 1; i >= 1; i--) {
// Array sizes must be constant or at least arithmetically
// evaluable at compile time
if (!isPureArithmetic(h[i]))
err("Array size must be fixed value", m);
// Create a list of the coefficient associated with each
// array index
coefficients.push_back(decimalMul(coefficients.back(), h[i].val));
}
pre.offsets = sub.offsets;
pre.coefficients = sub.coefficients;
pre.nonfinal = sub.nonfinal;
pre.nonfinal[prefix+h[0].val.substr(2)] = true;
}
pre.coefficients[prefix+h[0].val.substr(2)] = coefficients;
pre.offsets[prefix+h[0].val.substr(2)] = pre.globalOffset;
pre.indices[prefix+h[0].val.substr(2)] = index;
if (decimalGt(tt176, coefficients.back()))
pre.globalOffset = decimalAdd(pre.globalOffset, coefficients.back());
return pre;
}
// Preprocess input containing functions
//
// localExterns is a map of the form, eg,
//
// { x: { foo: 0, bar: 1, baz: 2 }, y: { qux: 0, foo: 1 } ... }
//
// localExternSigs is a map of the form, eg,
//
// { x : { foo: iii, bar: iis, baz: ia }, y: { qux: i, foo: as } ... }
//
// Signifying that x.foo = 0, x.baz = 2, y.foo = 1, etc
// and that x.foo has three integers as arguments, x.bar has two
// integers and a variable-length string, and baz has an integer
// and an array
//
// globalExterns is a one-level map, eg from above
//
// { foo: 1, bar: 1, baz: 2, qux: 0 }
//
// globalExternSigs is a one-level map, eg from above
//
// { foo: as, bar: iis, baz: ia, qux: i}
//
// Note that globalExterns and globalExternSigs may be ambiguous
// Also, a null signature implies an infinite tail of integers
preprocessResult preprocessInit(Node inp) {
Metadata m = inp.metadata;
if (inp.val != "seq")
inp = astnode("seq", inp, m);
std::vector<Node> empty = std::vector<Node>();
Node init = astnode("seq", empty, m);
Node shared = astnode("seq", empty, m);
std::vector<Node> any;
std::vector<Node> functions;
preprocessAux out = preprocessAux();
out.localExterns["self"] = std::map<std::string, int>();
int functionCount = 0;
int storageDataCount = 0;
for (unsigned i = 0; i < inp.args.size(); i++) {
Node obj = inp.args[i];
// Functions
if (obj.val == "def") {
if (obj.args.size() == 0)
err("Empty def", m);
std::string funName = obj.args[0].val;
// Init, shared and any are special functions
if (funName == "init" || funName == "shared" || funName == "any") {
if (obj.args[0].args.size())
err(funName+" cannot have arguments", m);
}
if (funName == "init") init = obj.args[1];
else if (funName == "shared") shared = obj.args[1];
else if (funName == "any") any.push_back(obj.args[1]);
else {
// Other functions
functions.push_back(convFunction(obj, functionCount));
out.localExterns["self"][obj.args[0].val] = functionCount;
out.localExternSigs["self"][obj.args[0].val]
= getSignature(obj.args[0].args);
functionCount++;
}
}
// Extern declarations
else if (obj.val == "extern") {
std::string externName = obj.args[0].val;
Node al = obj.args[1];
if (!out.localExterns.count(externName))
out.localExterns[externName] = std::map<std::string, int>();
for (unsigned i = 0; i < al.args.size(); i++) {
if (al.args[i].val == ":") {
std::string v = al.args[i].args[0].val;
std::string sig = al.args[i].args[1].val;
out.globalExterns[v] = i;
out.globalExternSigs[v] = sig;
out.localExterns[externName][v] = i;
out.localExternSigs[externName][v] = sig;
}
else {
std::string v = al.args[i].val;
out.globalExterns[v] = i;
out.globalExternSigs[v] = "";
out.localExterns[externName][v] = i;
out.localExternSigs[externName][v] = "";
}
}
}
// Custom macros
else if (obj.val == "macro" || (obj.val == "fun" && obj.args[0].val == "macro")) {
// Rules for valid macros:
//
// There are only four categories of valid macros:
//
// 1. a macro where the outer function is something
// which is NOT an existing valid function/extern/datum
// 2. a macro of the form set(c(x), d) where c must NOT
// be an existing valid function/extern/datum
// 3. something of the form access(c(x)), where c must NOT
// be an existing valid function/extern/datum
// 4. something of the form set(access(c(x)), d) where c must
// NOT be an existing valid function/extern/datum
// 5. something of the form with(c(x), d, e) where c must
// NOT be an existing valid function/extern/datum
bool valid = false;
Node pattern;
Node substitution;
int priority;
// Priority not set: default zero
if (obj.val == "macro") {
pattern = obj.args[0];
substitution = obj.args[1];
priority = 0;
}
// Specified priority
else {
pattern = obj.args[1];
substitution = obj.args[2];
if (obj.args[0].args.size())
priority = dtu(obj.args[0].args[0].val);
else
priority = 0;
}
if (opcode(pattern.val) < 0 && !isValidFunctionName(pattern.val))
valid = true;
if (pattern.val == "set" &&
opcode(pattern.args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].val))
valid = true;
if (pattern.val == "access" &&
opcode(pattern.args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].val))
if (pattern.val == "set" &&
pattern.args[0].val == "access" &&
opcode(pattern.args[0].args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].args[0].val))
valid = true;
if (pattern.val == "with" &&
opcode(pattern.args[0].val) < 0 &&
!isValidFunctionName(pattern.args[0].val))
valid = true;
if (valid) {
if (!out.customMacros.count(priority))
out.customMacros[priority] = rewriteRuleSet();
out.customMacros[priority].addRule
(rewriteRule(pattern, substitution));
}
else warn("Macro does not fit valid template: "+printSimple(pattern), m);
}
// Variable types
else if (obj.val == "type") {
std::string typeName = obj.args[0].val;
std::vector<Node> vars = obj.args[1].args;
for (unsigned i = 0; i < vars.size(); i++)
out.types[vars[i].val] = typeName;
}
// Storage variables/structures
else if (obj.val == "data") {
out.storageVars = getStorageVars(out.storageVars,
obj.args[0],
"",
storageDataCount);
storageDataCount += 1;
}
else any.push_back(obj);
}
// Set up top-level AST structure
std::vector<Node> main;
if (shared.args.size()) main.push_back(shared);
if (init.args.size()) main.push_back(init);
std::vector<Node> code;
if (shared.args.size()) code.push_back(shared);
for (unsigned i = 0; i < any.size(); i++)
code.push_back(any[i]);
for (unsigned i = 0; i < functions.size(); i++)
code.push_back(functions[i]);
Node codeNode;
if (functions.size() > 0) {
codeNode = astnode("with",
token("__funid", m),
astnode("byte",
token("0", m),
astnode("calldataload", token("0", m), m),
m),
astnode("seq", code, m),
m);
}
else codeNode = astnode("seq", code, m);
main.push_back(astnode("~return",
token("0", m),
astnode("lll",
codeNode,
token("0", m),
m),
m));
Node result;
if (main.size() == 1) result = main[0];
else result = astnode("seq", main, inp.metadata);
return preprocessResult(result, out);
}
preprocessResult processTypes (preprocessResult pr) {
preprocessAux aux = pr.second;
Node node = pr.first;
if (node.type == TOKEN && aux.types.count(node.val))
node = asn(aux.types[node.val], node, node.metadata);
else if (node.val == "untyped")
return preprocessResult(node.args[0], aux);
else if (node.val == "outer")
return preprocessResult(node, aux);
else {
for (unsigned i = 0; i < node.args.size(); i++) {
node.args[i] =
processTypes(preprocessResult(node.args[i], aux)).first;
}
}
return preprocessResult(node, aux);
}
preprocessResult preprocess(Node n) {
return processTypes(preprocessInit(n));
}

50
libserpent/preprocess.h

@ -0,0 +1,50 @@
#ifndef ETHSERP_PREPROCESSOR
#define ETHSERP_PREPROCESSOR
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
#include "rewriteutils.h"
// Storage variable index storing object
struct svObj {
std::map<std::string, std::string> offsets;
std::map<std::string, int> indices;
std::map<std::string, std::vector<std::string> > coefficients;
std::map<std::string, bool> nonfinal;
std::string globalOffset;
};
// Preprocessing result storing object
class preprocessAux {
public:
preprocessAux() {
globalExterns = std::map<std::string, int>();
localExterns = std::map<std::string, std::map<std::string, int> >();
localExterns["self"] = std::map<std::string, int>();
}
std::map<std::string, int> globalExterns;
std::map<std::string, std::string> globalExternSigs;
std::map<std::string, std::map<std::string, int> > localExterns;
std::map<std::string, std::map<std::string, std::string> > localExternSigs;
std::map<int, rewriteRuleSet > customMacros;
std::map<std::string, std::string> types;
svObj storageVars;
};
#define preprocessResult std::pair<Node, preprocessAux>
// Populate an svObj with the arguments needed to determine
// the storage position of a node
svObj getStorageVars(svObj pre, Node node, std::string prefix="",
int index=0);
// Preprocess a function (see cpp for details)
preprocessResult preprocess(Node inp);
#endif

1206
libserpent/rewriter.cpp

File diff suppressed because it is too large

212
libserpent/rewriteutils.cpp

@ -0,0 +1,212 @@
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include "util.h"
#include "lllparser.h"
#include "bignum.h"
#include "rewriteutils.h"
#include "optimize.h"
// Valid functions and their min and max argument counts
std::string validFunctions[][3] = {
{ "if", "2", "3" },
{ "unless", "2", "2" },
{ "while", "2", "2" },
{ "until", "2", "2" },
{ "alloc", "1", "1" },
{ "array", "1", "1" },
{ "call", "2", tt256 },
{ "callcode", "2", tt256 },
{ "create", "1", "4" },
{ "getch", "2", "2" },
{ "setch", "3", "3" },
{ "sha3", "1", "2" },
{ "return", "1", "2" },
{ "inset", "1", "1" },
{ "min", "2", "2" },
{ "max", "2", "2" },
{ "array_lit", "0", tt256 },
{ "seq", "0", tt256 },
{ "log", "1", "6" },
{ "outer", "1", "1" },
{ "set", "2", "2" },
{ "get", "1", "1" },
{ "ref", "1", "1" },
{ "declare", "1", tt256 },
{ "with", "3", "3" },
{ "outer", "1", "1" },
{ "mcopy", "3", "3" },
{ "unsafe_mcopy", "3", "3" },
{ "save", "3", "3" },
{ "load", "2", "2" },
{ "---END---", "", "" } //Keep this line at the end of the list
};
std::map<std::string, bool> vfMap;
// Is a function name one of the valid functions above?
bool isValidFunctionName(std::string f) {
if (vfMap.size() == 0) {
for (int i = 0; ; i++) {
if (validFunctions[i][0] == "---END---") break;
vfMap[validFunctions[i][0]] = true;
}
}
return vfMap.count(f);
}
// Cool function for debug purposes (named cerrStringList to make
// all prints searchable via 'cerr')
void cerrStringList(std::vector<std::string> s, std::string suffix) {
for (unsigned i = 0; i < s.size(); i++) std::cerr << s[i] << " ";
std::cerr << suffix << "\n";
}
// Convert:
// self.cow -> ["cow"]
// self.horse[0] -> ["horse", "0"]
// self.a[6][7][self.storage[3]].chicken[9] ->
// ["6", "7", (sload 3), "chicken", "9"]
std::vector<Node> listfyStorageAccess(Node node) {
std::vector<Node> out;
std::vector<Node> nodez;
nodez.push_back(node);
while (1) {
if (nodez.back().type == TOKEN) {
out.push_back(token("--" + nodez.back().val, node.metadata));
std::vector<Node> outrev;
for (int i = (signed)out.size() - 1; i >= 0; i--) {
outrev.push_back(out[i]);
}
return outrev;
}
if (nodez.back().val == ".")
nodez.back().args[1].val = "--" + nodez.back().args[1].val;
if (nodez.back().args.size() == 0)
err("Error parsing storage variable statement", node.metadata);
if (nodez.back().args.size() == 1)
out.push_back(token(tt256m1, node.metadata));
else
out.push_back(nodez.back().args[1]);
nodez.push_back(nodez.back().args[0]);
}
}
// Is the given node something of the form
// self.cow
// self.horse[0]
// self.a[6][7][self.storage[3]].chicken[9]
bool isNodeStorageVariable(Node node) {
std::vector<Node> nodez;
nodez.push_back(node);
while (1) {
if (nodez.back().type == TOKEN) return false;
if (nodez.back().args.size() == 0) return false;
if (nodez.back().val != "." && nodez.back().val != "access")
return false;
if (nodez.back().args[0].val == "self") return true;
nodez.push_back(nodez.back().args[0]);
}
}
// Main pattern matching routine, for those patterns that can be expressed
// using our standard mini-language above
//
// Returns two values. First, a boolean to determine whether the node matches
// the pattern, second, if the node does match then a map mapping variables
// in the pattern to nodes
matchResult match(Node p, Node n) {
matchResult o;
o.success = false;
if (p.type == TOKEN) {
if (p.val == n.val && n.type == TOKEN) o.success = true;
else if (p.val[0] == '$' || p.val[0] == '@') {
o.success = true;
o.map[p.val.substr(1)] = n;
}
}
else if (n.type==TOKEN || p.val!=n.val || p.args.size()!=n.args.size()) {
// do nothing
}
else {
for (unsigned i = 0; i < p.args.size(); i++) {
matchResult oPrime = match(p.args[i], n.args[i]);
if (!oPrime.success) {
o.success = false;
return o;
}
for (std::map<std::string, Node>::iterator it = oPrime.map.begin();
it != oPrime.map.end();
it++) {
o.map[(*it).first] = (*it).second;
}
}
o.success = true;
}
return o;
}
// Fills in the pattern with a dictionary mapping variable names to
// nodes (these dicts are generated by match). Match and subst together
// create a full pattern-matching engine.
Node subst(Node pattern,
std::map<std::string, Node> dict,
std::string varflag,
Metadata m) {
// Swap out patterns at the token level
if (pattern.metadata.ln == -1)
pattern.metadata = m;
if (pattern.type == TOKEN &&
pattern.val[0] == '$') {
if (dict.count(pattern.val.substr(1))) {
return dict[pattern.val.substr(1)];
}
else {
return token(varflag + pattern.val.substr(1), m);
}
}
// Other tokens are untouched
else if (pattern.type == TOKEN) {
return pattern;
}
// Substitute recursively for ASTs
else {
std::vector<Node> args;
for (unsigned i = 0; i < pattern.args.size(); i++) {
args.push_back(subst(pattern.args[i], dict, varflag, m));
}
return asn(pattern.val, args, m);
}
}
// Transforms a sequence containing two-argument with statements
// into a statement containing those statements in nested form
Node withTransform (Node source) {
Node o = token("--");
Metadata m = source.metadata;
std::vector<Node> args;
for (int i = source.args.size() - 1; i >= 0; i--) {
Node a = source.args[i];
if (a.val == "with" && a.args.size() == 2) {
std::vector<Node> flipargs;
for (int j = args.size() - 1; j >= 0; j--)
flipargs.push_back(args[i]);
if (o.val != "--")
flipargs.push_back(o);
o = asn("with", a.args[0], a.args[1], asn("seq", flipargs, m), m);
args = std::vector<Node>();
}
else {
args.push_back(a);
}
}
std::vector<Node> flipargs;
for (int j = args.size() - 1; j >= 0; j--)
flipargs.push_back(args[j]);
if (o.val != "--")
flipargs.push_back(o);
return asn("seq", flipargs, m);
}

76
libserpent/rewriteutils.h

@ -0,0 +1,76 @@
#ifndef ETHSERP_REWRITEUTILS
#define ETHSERP_REWRITEUTILS
#include <stdio.h>
#include <iostream>
#include <vector>
#include <map>
#include "util.h"
// Valid functions and their min and max argument counts
extern std::string validFunctions[][3];
extern std::map<std::string, bool> vfMap;
bool isValidFunctionName(std::string f);
// Converts deep array access into ordered list of the arguments
// along the descent
std::vector<Node> listfyStorageAccess(Node node);
// Cool function for debug purposes (named cerrStringList to make
// all prints searchable via 'cerr')
void cerrStringList(std::vector<std::string> s, std::string suffix="");
// Is the given node something of the form
// self.cow
// self.horse[0]
// self.a[6][7][self.storage[3]].chicken[9]
bool isNodeStorageVariable(Node node);
// Applies rewrite rules adding without wrapper
Node rewriteChunk(Node inp);
// Match result storing object
struct matchResult {
bool success;
std::map<std::string, Node> map;
};
// Match node to pattern
matchResult match(Node p, Node n);
// Substitute node using pattern
Node subst(Node pattern,
std::map<std::string, Node> dict,
std::string varflag,
Metadata m);
Node withTransform(Node source);
class rewriteRule {
public:
rewriteRule(Node p, Node s) {
pattern = p;
substitution = s;
}
Node pattern;
Node substitution;
};
class rewriteRuleSet {
public:
rewriteRuleSet() {
ruleLists = std::map<std::string, std::vector<rewriteRule> >();
}
void addRule(rewriteRule r) {
if (!ruleLists.count(r.pattern.val))
ruleLists[r.pattern.val] = std::vector<rewriteRule>();
ruleLists[r.pattern.val].push_back(r);
}
std::map<std::string, std::vector<rewriteRule> > ruleLists;
};
#endif

4
libserpent/tokenize.cpp

@ -13,8 +13,8 @@ int chartype(char c) {
if (c >= '0' && c <= '9') return ALPHANUM;
else if (c >= 'a' && c <= 'z') return ALPHANUM;
else if (c >= 'A' && c <= 'Z') return ALPHANUM;
else if (std::string("~_$").find(c) != std::string::npos) return ALPHANUM;
else if (c == '\t' || c == ' ' || c == '\n') return SPACE;
else if (std::string("~_$@").find(c) != std::string::npos) return ALPHANUM;
else if (c == '\t' || c == ' ' || c == '\n' || c == '\r') return SPACE;
else if (std::string("()[]{}").find(c) != std::string::npos) return BRACK;
else if (c == '"') return DQUOTE;
else if (c == '\'') return SQUOTE;

58
libserpent/util.cpp

@ -2,10 +2,10 @@
#include <iostream>
#include <vector>
#include <map>
#include <string>
#include "util.h"
#include "bignum.h"
#include <fstream>
#include <string>
#include <cerrno>
//Token or value node constructor
@ -28,6 +28,11 @@ Node astnode(std::string val, std::vector<Node> args, Metadata met) {
}
//AST node constructors for a specific number of children
Node astnode(std::string val, Metadata met) {
std::vector<Node> args;
return astnode(val, args, met);
}
Node astnode(std::string val, Node a, Metadata met) {
std::vector<Node> args;
args.push_back(a);
@ -49,6 +54,16 @@ Node astnode(std::string val, Node a, Node b, Node c, Metadata met) {
return astnode(val, args, met);
}
Node astnode(std::string val, Node a, Node b, Node c, Node d, Metadata met) {
std::vector<Node> args;
args.push_back(a);
args.push_back(b);
args.push_back(c);
args.push_back(d);
return astnode(val, args, met);
}
// Print token list
std::string printTokens(std::vector<Node> tokens) {
std::string s = "";
@ -146,6 +161,15 @@ std::string indentLines(std::string inp) {
return joinLines(lines);
}
// Binary to hexadecimal
std::string binToNumeric(std::string inp) {
std::string o = "0";
for (unsigned i = 0; i < inp.length(); i++) {
o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i]));
}
return o;
}
// Converts string to simple numeric format
std::string strToNumeric(std::string inp) {
std::string o = "0";
@ -154,7 +178,7 @@ std::string strToNumeric(std::string inp) {
}
else if ((inp[0] == '"' && inp[inp.length()-1] == '"')
|| (inp[0] == '\'' && inp[inp.length()-1] == '\'')) {
for (unsigned i = 1; i < inp.length() - 1; i++) {
for (unsigned i = 1; i < inp.length() - 1; i++) {
o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i]));
}
}
@ -181,6 +205,14 @@ bool isNumberLike(Node node) {
return strToNumeric(node.val) != "";
}
// Is the number decimal?
bool isDecimal(std::string inp) {
for (unsigned i = 0; i < inp.length(); i++) {
if (inp[i] < '0' || inp[i] > '9') return false;
}
return true;
}
//Normalizes number representations
Node nodeToNumeric(Node node) {
std::string o = strToNumeric(node.val);
@ -246,6 +278,14 @@ void err(std::string errtext, Metadata met) {
throw(err);
}
//Report warning
void warn(std::string errtext, Metadata met) {
std::string err = "Warning (file \"" + met.file + "\", line " +
unsignedToDecimal(met.ln + 1) + ", char " + unsignedToDecimal(met.ch) +
"): " + errtext;
std::cerr << err << "\n";
}
//Bin to hex
std::string binToHex(std::string inp) {
std::string o = "";
@ -280,7 +320,15 @@ std::string upperCase(std::string inp) {
//Three-int vector
std::vector<int> triple(int a, int b, int c) {
std::vector<int> o;
o.push_back(a); o.push_back(b); o.push_back(c);
return o;
std::vector<int> v;
v.push_back(a);
v.push_back(b);
v.push_back(c);
return v;
}
//Extend node vector
std::vector<Node> extend(std::vector<Node> a, std::vector<Node> b) {
for (unsigned i = 0; i < b.size(); i++) a.push_back(b[i]);
return a;
}

36
libserpent/util.h

@ -28,30 +28,36 @@ const int TOKEN = 0,
// Stores metadata about each token
class Metadata {
public:
Metadata(std::string File="main", int Ln=0, int Ch=0) {
Metadata(std::string File="main", int Ln=-1, int Ch=-1) {
file = File;
ln = Ln;
ch = Ch;
fixed = false;
}
std::string file;
int ln;
int ch;
bool fixed;
};
std::string mkUniqueToken();
// type can be TOKEN or ASTNODE
struct Node {
int type;
std::string val;
std::vector<Node> args;
Metadata metadata;
class Node {
public:
int type;
std::string val;
std::vector<Node> args;
Metadata metadata;
};
Node token(std::string val, Metadata met=Metadata());
Node astnode(std::string val, std::vector<Node> args, Metadata met=Metadata());
Node astnode(std::string val, Metadata met=Metadata());
Node astnode(std::string val, Node a, Metadata met=Metadata());
Node astnode(std::string val, Node a, Node b, Metadata met=Metadata());
Node astnode(std::string val, Node a, Node b, Node c, Metadata met=Metadata());
Node astnode(std::string val, Node a, Node b,
Node c, Node d, Metadata met=Metadata());
// Number of tokens in a tree
int treeSize(Node prog);
@ -74,6 +80,9 @@ std::string joinLines(std::vector<std::string> lines);
// Indent all lines by 4 spaces
std::string indentLines(std::string inp);
// Converts binary to simple numeric format
std::string binToNumeric(std::string inp);
// Converts string to simple numeric format
std::string strToNumeric(std::string inp);
@ -98,6 +107,9 @@ bool exists(std::string fileName);
//Report error
void err(std::string errtext, Metadata met);
//Report warning
void warn(std::string errtext, Metadata met);
//Bin to hex
std::string binToHex(std::string inp);
@ -110,4 +122,16 @@ std::string upperCase(std::string inp);
//Three-int vector
std::vector<int> triple(int a, int b, int c);
//Extend node vector
std::vector<Node> extend(std::vector<Node> a, std::vector<Node> b);
// Is the number decimal?
bool isDecimal(std::string inp);
#define asn astnode
#define tkn token
#define msi std::map<std::string, int>
#define msn std::map<std::string, Node>
#define mss std::map<std::string, std::string>
#endif

68
libsolidity/AST.cpp

@ -21,7 +21,7 @@
*/
#include <algorithm>
#include <libsolidity/Utils.h>
#include <libsolidity/AST.h>
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/Exceptions.h>
@ -39,6 +39,17 @@ TypeError ASTNode::createTypeError(string const& _description) const
return TypeError() << errinfo_sourceLocation(getLocation()) << errinfo_comment(_description);
}
void ContractDefinition::checkTypeRequirements()
{
FunctionDefinition const* constructor = getConstructor();
if (constructor && !constructor->getReturnParameters().empty())
BOOST_THROW_EXCEPTION(constructor->getReturnParameterList()->createTypeError(
"Non-empty \"returns\" directive for constructor."));
for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
function->checkTypeRequirements();
}
vector<FunctionDefinition const*> ContractDefinition::getInterfaceFunctions() const
{
vector<FunctionDefinition const*> exportedFunctions;
@ -54,6 +65,14 @@ vector<FunctionDefinition const*> ContractDefinition::getInterfaceFunctions() co
return exportedFunctions;
}
FunctionDefinition const* ContractDefinition::getConstructor() const
{
for (ASTPointer<FunctionDefinition> const& f: m_definedFunctions)
if (f->getName() == getName())
return f.get();
return nullptr;
}
void StructDefinition::checkMemberTypes() const
{
for (ASTPointer<VariableDeclaration> const& member: getMembers())
@ -111,12 +130,22 @@ void WhileStatement::checkTypeRequirements()
m_body->checkTypeRequirements();
}
void ForStatement::checkTypeRequirements()
{
if (m_initExpression)
m_initExpression->checkTypeRequirements();
if (m_condExpression)
m_condExpression->expectType(BoolType());
if (m_loopExpression)
m_loopExpression->checkTypeRequirements();
m_body->checkTypeRequirements();
}
void Return::checkTypeRequirements()
{
if (!m_expression)
return;
if (asserts(m_returnParameters))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not assigned."));
solAssert(m_returnParameters, "Return parameters not assigned.");
if (m_returnParameters->getParameters().size() != 1)
BOOST_THROW_EXCEPTION(createTypeError("Different number of arguments in return statement "
"than in returns declaration."));
@ -235,13 +264,12 @@ void FunctionCall::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Explicit type conversion not allowed."));
m_type = type.getActualType();
}
else
else if (FunctionType const* functionType = dynamic_cast<FunctionType const*>(expressionType))
{
//@todo would be nice to create a struct type from the arguments
// and then ask if that is implicitly convertible to the struct represented by the
// function parameters
FunctionType const& functionType = dynamic_cast<FunctionType const&>(*expressionType);
TypePointers const& parameterTypes = functionType.getParameterTypes();
TypePointers const& parameterTypes = functionType->getParameterTypes();
if (parameterTypes.size() != m_arguments.size())
BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for function call."));
for (size_t i = 0; i < m_arguments.size(); ++i)
@ -249,11 +277,13 @@ void FunctionCall::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in function call."));
// @todo actually the return type should be an anonymous struct,
// but we change it to the type of the first return value until we have structs
if (functionType.getReturnParameterTypes().empty())
if (functionType->getReturnParameterTypes().empty())
m_type = make_shared<VoidType>();
else
m_type = functionType.getReturnParameterTypes().front();
m_type = functionType->getReturnParameterTypes().front();
}
else
BOOST_THROW_EXCEPTION(createTypeError("Type is not callable."));
}
bool FunctionCall::isTypeConversion() const
@ -261,6 +291,25 @@ bool FunctionCall::isTypeConversion() const
return m_expression->getType()->getCategory() == Type::Category::TYPE;
}
void NewExpression::checkTypeRequirements()
{
m_contractName->checkTypeRequirements();
for (ASTPointer<Expression> const& argument: m_arguments)
argument->checkTypeRequirements();
m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration());
if (!m_contract)
BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract."));
shared_ptr<ContractType const> type = make_shared<ContractType const>(*m_contract);
m_type = type;
TypePointers const& parameterTypes = type->getConstructorType()->getParameterTypes();
if (parameterTypes.size() != m_arguments.size())
BOOST_THROW_EXCEPTION(createTypeError("Wrong argument count for constructor call."));
for (size_t i = 0; i < m_arguments.size(); ++i)
if (!m_arguments[i]->getType()->isImplicitlyConvertibleTo(*parameterTypes[i]))
BOOST_THROW_EXCEPTION(createTypeError("Invalid type for argument in constructor call."));
}
void MemberAccess::checkTypeRequirements()
{
m_expression->checkTypeRequirements();
@ -286,8 +335,7 @@ void IndexAccess::checkTypeRequirements()
void Identifier::checkTypeRequirements()
{
if (asserts(m_referencedDeclaration))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Identifier not resolved."));
solAssert(m_referencedDeclaration, "Identifier not resolved.");
VariableDeclaration const* variable = dynamic_cast<VariableDeclaration const*>(m_referencedDeclaration);
if (variable)

82
libsolidity/AST.h

@ -27,6 +27,7 @@
#include <vector>
#include <memory>
#include <boost/noncopyable.hpp>
#include <libsolidity/Utils.h>
#include <libsolidity/ASTForward.h>
#include <libsolidity/BaseTypes.h>
#include <libsolidity/Token.h>
@ -174,6 +175,10 @@ public:
std::vector<ASTPointer<VariableDeclaration>> const& getStateVariables() const { return m_stateVariables; }
std::vector<ASTPointer<FunctionDefinition>> const& getDefinedFunctions() const { return m_definedFunctions; }
/// Checks that the constructor does not have a "returns" statement and calls
/// checkTypeRequirements on all its functions.
void checkTypeRequirements();
/// @return A shared pointer of an ASTString.
/// Can contain a nullptr in which case indicates absence of documentation
ASTPointer<ASTString> const& getDocumentation() const { return m_documentation; }
@ -181,6 +186,9 @@ public:
/// Returns the functions that make up the calling interface in the intended order.
std::vector<FunctionDefinition const*> getInterfaceFunctions() const;
/// Returns the constructor or nullptr if no constructor was specified
FunctionDefinition const* getConstructor() const;
private:
std::vector<ASTPointer<StructDefinition>> m_definedStructs;
std::vector<ASTPointer<VariableDeclaration>> m_stateVariables;
@ -357,7 +365,7 @@ public:
explicit ElementaryTypeName(Location const& _location, Token::Value _type):
TypeName(_location), m_type(_type)
{
if (asserts(Token::isElementaryTypeName(_type))) BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(Token::isElementaryTypeName(_type), "");
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@ -502,6 +510,42 @@ private:
ASTPointer<Statement> m_body;
};
/**
* For loop statement
*/
class ForStatement: public BreakableStatement
{
public:
ForStatement(Location const& _location,
ASTPointer<Statement> const& _initExpression,
ASTPointer<Expression> const& _conditionExpression,
ASTPointer<ExpressionStatement> const& _loopExpression,
ASTPointer<Statement> const& _body):
BreakableStatement(_location),
m_initExpression(_initExpression),
m_condExpression(_conditionExpression),
m_loopExpression(_loopExpression),
m_body(_body) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override;
Statement const* getInitializationExpression() const { return m_initExpression.get(); }
Expression const* getCondition() const { return m_condExpression.get(); }
ExpressionStatement const* getLoopExpression() const { return m_loopExpression.get(); }
Statement const& getBody() const { return *m_body; }
private:
/// For statement's initialization expresion. for(XXX; ; ). Can be empty
ASTPointer<Statement> m_initExpression;
/// For statement's condition expresion. for(; XXX ; ). Can be empty
ASTPointer<Expression> m_condExpression;
/// For statement's loop expresion. for(;;XXX). Can be empty
ASTPointer<ExpressionStatement> m_loopExpression;
/// The body of the loop
ASTPointer<Statement> m_body;
};
class Continue: public Statement
{
public:
@ -532,8 +576,7 @@ public:
void setFunctionReturnParameters(ParameterList& _parameters) { m_returnParameters = &_parameters; }
ParameterList const& getFunctionReturnParameters() const
{
if (asserts(m_returnParameters))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(m_returnParameters, "");
return *m_returnParameters;
}
Expression const* getExpression() const { return m_expression.get(); }
@ -639,7 +682,7 @@ public:
Expression(_location), m_leftHandSide(_leftHandSide),
m_assigmentOperator(_assignmentOperator), m_rightHandSide(_rightHandSide)
{
if (asserts(Token::isAssignmentOp(_assignmentOperator))) BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(Token::isAssignmentOp(_assignmentOperator), "");
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@ -667,7 +710,7 @@ public:
Expression(_location), m_operator(_operator),
m_subExpression(_subExpression), m_isPrefix(_isPrefix)
{
if (asserts(Token::isUnaryOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(Token::isUnaryOp(_operator), "");
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@ -694,7 +737,7 @@ public:
Token::Value _operator, ASTPointer<Expression> const& _right):
Expression(_location), m_left(_left), m_operator(_operator), m_right(_right)
{
if (asserts(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator))) BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(Token::isBinaryOp(_operator) || Token::isCompareOp(_operator), "");
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@ -740,6 +783,31 @@ private:
std::vector<ASTPointer<Expression>> m_arguments;
};
/**
* Expression that creates a new contract, e.g. "new SomeContract(1, 2)".
*/
class NewExpression: public Expression
{
public:
NewExpression(Location const& _location, ASTPointer<Identifier> const& _contractName,
std::vector<ASTPointer<Expression>> const& _arguments):
Expression(_location), m_contractName(_contractName), m_arguments(_arguments) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
virtual void checkTypeRequirements() override;
std::vector<ASTPointer<Expression const>> getArguments() const { return {m_arguments.begin(), m_arguments.end()}; }
/// Returns the referenced contract. Can only be called after type checking.
ContractDefinition const* getContract() const { solAssert(m_contract, ""); return m_contract; }
private:
ASTPointer<Identifier> m_contractName;
std::vector<ASTPointer<Expression>> m_arguments;
ContractDefinition const* m_contract = nullptr;
};
/**
* Access to a member of an object. Example: x.name
*/
@ -826,7 +894,7 @@ public:
ElementaryTypeNameExpression(Location const& _location, Token::Value _typeToken):
PrimaryExpression(_location), m_typeToken(_typeToken)
{
if (asserts(Token::isElementaryTypeName(_typeToken))) BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(Token::isElementaryTypeName(_typeToken), "");
}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;

2
libsolidity/ASTForward.h

@ -52,6 +52,7 @@ class Block;
class IfStatement;
class BreakableStatement;
class WhileStatement;
class ForStatement;
class Continue;
class Break;
class Return;
@ -62,6 +63,7 @@ class Assignment;
class UnaryOperation;
class BinaryOperation;
class FunctionCall;
class NewExpression;
class MemberAccess;
class IndexAccess;
class PrimaryExpression;

25
libsolidity/ASTPrinter.cpp

@ -150,6 +150,13 @@ bool ASTPrinter::visit(WhileStatement const& _node)
return goDeeper();
}
bool ASTPrinter::visit(ForStatement const& _node)
{
writeLine("ForStatement");
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(Continue const& _node)
{
writeLine("Continue");
@ -226,6 +233,14 @@ bool ASTPrinter::visit(FunctionCall const& _node)
return goDeeper();
}
bool ASTPrinter::visit(NewExpression const& _node)
{
writeLine("NewExpression");
printType(_node);
printSourcePart(_node);
return goDeeper();
}
bool ASTPrinter::visit(MemberAccess const& _node)
{
writeLine("MemberAccess to member " + _node.getMemberName());
@ -352,6 +367,11 @@ void ASTPrinter::endVisit(WhileStatement const&)
m_indentation--;
}
void ASTPrinter::endVisit(ForStatement const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(Continue const&)
{
m_indentation--;
@ -402,6 +422,11 @@ void ASTPrinter::endVisit(FunctionCall const&)
m_indentation--;
}
void ASTPrinter::endVisit(NewExpression const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(MemberAccess const&)
{
m_indentation--;

4
libsolidity/ASTPrinter.h

@ -57,6 +57,7 @@ public:
bool visit(IfStatement const& _node) override;
bool visit(BreakableStatement const& _node) override;
bool visit(WhileStatement const& _node) override;
bool visit(ForStatement const& _node) override;
bool visit(Continue const& _node) override;
bool visit(Break const& _node) override;
bool visit(Return const& _node) override;
@ -67,6 +68,7 @@ public:
bool visit(UnaryOperation const& _node) override;
bool visit(BinaryOperation const& _node) override;
bool visit(FunctionCall const& _node) override;
bool visit(NewExpression const& _node) override;
bool visit(MemberAccess const& _node) override;
bool visit(IndexAccess const& _node) override;
bool visit(PrimaryExpression const& _node) override;
@ -89,6 +91,7 @@ public:
void endVisit(IfStatement const&) override;
void endVisit(BreakableStatement const&) override;
void endVisit(WhileStatement const&) override;
void endVisit(ForStatement const&) override;
void endVisit(Continue const&) override;
void endVisit(Break const&) override;
void endVisit(Return const&) override;
@ -99,6 +102,7 @@ public:
void endVisit(UnaryOperation const&) override;
void endVisit(BinaryOperation const&) override;
void endVisit(FunctionCall const&) override;
void endVisit(NewExpression const&) override;
void endVisit(MemberAccess const&) override;
void endVisit(IndexAccess const&) override;
void endVisit(PrimaryExpression const&) override;

8
libsolidity/ASTVisitor.h

@ -58,6 +58,7 @@ public:
virtual bool visit(IfStatement&) { return true; }
virtual bool visit(BreakableStatement&) { return true; }
virtual bool visit(WhileStatement&) { return true; }
virtual bool visit(ForStatement&) { return true; }
virtual bool visit(Continue&) { return true; }
virtual bool visit(Break&) { return true; }
virtual bool visit(Return&) { return true; }
@ -68,6 +69,7 @@ public:
virtual bool visit(UnaryOperation&) { return true; }
virtual bool visit(BinaryOperation&) { return true; }
virtual bool visit(FunctionCall&) { return true; }
virtual bool visit(NewExpression&) { return true; }
virtual bool visit(MemberAccess&) { return true; }
virtual bool visit(IndexAccess&) { return true; }
virtual bool visit(PrimaryExpression&) { return true; }
@ -92,6 +94,7 @@ public:
virtual void endVisit(IfStatement&) { }
virtual void endVisit(BreakableStatement&) { }
virtual void endVisit(WhileStatement&) { }
virtual void endVisit(ForStatement&) { }
virtual void endVisit(Continue&) { }
virtual void endVisit(Break&) { }
virtual void endVisit(Return&) { }
@ -102,6 +105,7 @@ public:
virtual void endVisit(UnaryOperation&) { }
virtual void endVisit(BinaryOperation&) { }
virtual void endVisit(FunctionCall&) { }
virtual void endVisit(NewExpression&) { }
virtual void endVisit(MemberAccess&) { }
virtual void endVisit(IndexAccess&) { }
virtual void endVisit(PrimaryExpression&) { }
@ -130,6 +134,7 @@ public:
virtual bool visit(IfStatement const&) { return true; }
virtual bool visit(BreakableStatement const&) { return true; }
virtual bool visit(WhileStatement const&) { return true; }
virtual bool visit(ForStatement const&) { return true; }
virtual bool visit(Continue const&) { return true; }
virtual bool visit(Break const&) { return true; }
virtual bool visit(Return const&) { return true; }
@ -140,6 +145,7 @@ public:
virtual bool visit(UnaryOperation const&) { return true; }
virtual bool visit(BinaryOperation const&) { return true; }
virtual bool visit(FunctionCall const&) { return true; }
virtual bool visit(NewExpression const&) { return true; }
virtual bool visit(MemberAccess const&) { return true; }
virtual bool visit(IndexAccess const&) { return true; }
virtual bool visit(PrimaryExpression const&) { return true; }
@ -164,6 +170,7 @@ public:
virtual void endVisit(IfStatement const&) { }
virtual void endVisit(BreakableStatement const&) { }
virtual void endVisit(WhileStatement const&) { }
virtual void endVisit(ForStatement const&) { }
virtual void endVisit(Continue const&) { }
virtual void endVisit(Break const&) { }
virtual void endVisit(Return const&) { }
@ -174,6 +181,7 @@ public:
virtual void endVisit(UnaryOperation const&) { }
virtual void endVisit(BinaryOperation const&) { }
virtual void endVisit(FunctionCall const&) { }
virtual void endVisit(NewExpression const&) { }
virtual void endVisit(MemberAccess const&) { }
virtual void endVisit(IndexAccess const&) { }
virtual void endVisit(PrimaryExpression const&) { }

50
libsolidity/AST_accept.h

@ -267,6 +267,36 @@ void WhileStatement::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void ForStatement::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
if (m_initExpression)
m_initExpression->accept(_visitor);
if (m_condExpression)
m_condExpression->accept(_visitor);
if (m_loopExpression)
m_loopExpression->accept(_visitor);
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}
void ForStatement::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
if (m_initExpression)
m_initExpression->accept(_visitor);
if (m_condExpression)
m_condExpression->accept(_visitor);
if (m_loopExpression)
m_loopExpression->accept(_visitor);
m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}
void Continue::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);
@ -419,6 +449,26 @@ void FunctionCall::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void NewExpression::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
{
m_contractName->accept(_visitor);
listAccept(m_arguments, _visitor);
}
_visitor.endVisit(*this);
}
void NewExpression::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
{
m_contractName->accept(_visitor);
listAccept(m_arguments, _visitor);
}
_visitor.endVisit(*this);
}
void MemberAccess::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

67
libsolidity/CallGraph.cpp

@ -0,0 +1,67 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Callgraph of functions inside a contract.
*/
#include <libsolidity/AST.h>
#include <libsolidity/CallGraph.h>
using namespace std;
namespace dev
{
namespace solidity
{
void CallGraph::addFunction(FunctionDefinition const& _function)
{
if (!m_functionsSeen.count(&_function))
{
m_functionsSeen.insert(&_function);
m_workQueue.push(&_function);
}
}
set<FunctionDefinition const*> const& CallGraph::getCalls()
{
return m_functionsSeen;
}
void CallGraph::computeCallGraph()
{
while (!m_workQueue.empty())
{
FunctionDefinition const* fun = m_workQueue.front();
fun->accept(*this);
m_workQueue.pop();
}
}
bool CallGraph::visit(Identifier const& _identifier)
{
FunctionDefinition const* fun = dynamic_cast<FunctionDefinition const*>(_identifier.getReferencedDeclaration());
if (fun)
addFunction(*fun);
return true;
}
}
}

55
libsolidity/CallGraph.h

@ -0,0 +1,55 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Callgraph of functions inside a contract.
*/
#include <set>
#include <queue>
#include <boost/range/iterator_range.hpp>
#include <libsolidity/ASTVisitor.h>
namespace dev
{
namespace solidity
{
/**
* Can be used to compute the graph of calls (or rather references) between functions of the same
* contract. Current functionality is limited to computing all functions that are directly
* or indirectly called by some functions.
*/
class CallGraph: private ASTConstVisitor
{
public:
void addFunction(FunctionDefinition const& _function);
void computeCallGraph();
std::set<FunctionDefinition const*> const& getCalls();
private:
void addFunctionToQueue(FunctionDefinition const& _function);
virtual bool visit(Identifier const& _identifier) override;
std::set<FunctionDefinition const*> m_functionsSeen;
std::queue<FunctionDefinition const*> m_workQueue;
};
}
}

124
libsolidity/Compiler.cpp

@ -27,64 +27,93 @@
#include <libsolidity/Compiler.h>
#include <libsolidity/ExpressionCompiler.h>
#include <libsolidity/CompilerUtils.h>
#include <libsolidity/CallGraph.h>
using namespace std;
namespace dev {
namespace solidity {
void Compiler::compileContract(ContractDefinition const& _contract, vector<MagicVariableDeclaration const*> const& _magicGlobals)
void Compiler::compileContract(ContractDefinition const& _contract, vector<MagicVariableDeclaration const*> const& _magicGlobals,
map<ContractDefinition const*, bytes const*> const& _contracts)
{
m_context = CompilerContext(); // clear it just in case
for (MagicVariableDeclaration const* variable: _magicGlobals)
m_context.addMagicGlobal(*variable);
initializeContext(_contract, _magicGlobals, _contracts);
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
if (function->getName() != _contract.getName()) // don't add the constructor here
m_context.addFunction(*function);
registerStateVariables(_contract);
appendFunctionSelector(_contract);
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
if (function->getName() != _contract.getName()) // don't add the constructor here
function->accept(*this);
packIntoContractCreator(_contract);
}
void Compiler::packIntoContractCreator(ContractDefinition const& _contract)
{
// Swap the runtime context with the creation-time context
CompilerContext runtimeContext;
swap(m_context, runtimeContext);
initializeContext(_contract, _magicGlobals, _contracts);
packIntoContractCreator(_contract, runtimeContext);
}
void Compiler::initializeContext(ContractDefinition const& _contract, vector<MagicVariableDeclaration const*> const& _magicGlobals,
map<ContractDefinition const*, bytes const*> const& _contracts)
{
m_context.setCompiledContracts(_contracts);
for (MagicVariableDeclaration const* variable: _magicGlobals)
m_context.addMagicGlobal(*variable);
registerStateVariables(_contract);
}
FunctionDefinition* constructor = nullptr;
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
if (function->getName() == _contract.getName())
{
constructor = function.get();
break;
}
void Compiler::packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext)
{
set<FunctionDefinition const*> neededFunctions;
FunctionDefinition const* constructor = _contract.getConstructor();
if (constructor)
{
eth::AssemblyItem returnTag = m_context.pushNewTag();
m_context.addFunction(*constructor); // note that it cannot be called due to syntactic reasons
//@todo copy constructor arguments from calldata to memory prior to this
//@todo calling other functions inside the constructor should either trigger a parse error
//or we should copy them here (register them above and call "accept") - detecting which
// functions are referenced / called needs to be done in a recursive way.
appendCalldataUnpacker(*constructor, true);
m_context.appendJumpTo(m_context.getFunctionEntryLabel(*constructor));
constructor->accept(*this);
m_context << returnTag;
}
neededFunctions = getFunctionsNeededByConstructor(*constructor);
for (FunctionDefinition const* fun: neededFunctions)
m_context.addFunction(*fun);
eth::AssemblyItem sub = m_context.addSubroutine(runtimeContext.getAssembly());
if (constructor)
appendConstructorCall(*constructor);
eth::AssemblyItem sub = m_context.addSubroutine(_runtimeContext.getAssembly());
// stack contains sub size
m_context << eth::Instruction::DUP1 << sub << u256(0) << eth::Instruction::CODECOPY;
m_context << u256(0) << eth::Instruction::RETURN;
// note that we have to explicitly include all used functions because of absolute jump
// labels
for (FunctionDefinition const* fun: neededFunctions)
fun->accept(*this);
}
void Compiler::appendConstructorCall(FunctionDefinition const& _constructor)
{
eth::AssemblyItem returnTag = m_context.pushNewTag();
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
unsigned argumentSize = 0;
for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters())
argumentSize += var->getType()->getCalldataEncodedSize();
if (argumentSize > 0)
{
m_context << u256(argumentSize);
m_context.appendProgramSize();
m_context << u256(1); // copy it to byte one as expected for ABI calls
m_context << eth::Instruction::CODECOPY;
appendCalldataUnpacker(_constructor, true);
}
m_context.appendJumpTo(m_context.getFunctionEntryLabel(_constructor));
m_context << returnTag;
}
set<FunctionDefinition const*> Compiler::getFunctionsNeededByConstructor(FunctionDefinition const& _constructor)
{
CallGraph callgraph;
callgraph.addFunction(_constructor);
callgraph.computeCallGraph();
return callgraph.getCalls();
}
void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
@ -130,7 +159,6 @@ unsigned Compiler::appendCalldataUnpacker(FunctionDefinition const& _function, b
{
// We do not check the calldata size, everything is zero-padded.
unsigned dataOffset = 1;
//@todo this can be done more efficiently, saving some CALLDATALOAD calls
for (ASTPointer<VariableDeclaration> const& var: _function.getParameters())
{
@ -274,6 +302,40 @@ bool Compiler::visit(WhileStatement const& _whileStatement)
return false;
}
bool Compiler::visit(ForStatement const& _forStatement)
{
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart);
m_breakTags.push_back(loopEnd);
if (_forStatement.getInitializationExpression())
_forStatement.getInitializationExpression()->accept(*this);
m_context << loopStart;
// if there is no terminating condition in for, default is to always be true
if (_forStatement.getCondition())
{
compileExpression(*_forStatement.getCondition());
m_context << eth::Instruction::ISZERO;
m_context.appendConditionalJumpTo(loopEnd);
}
_forStatement.getBody().accept(*this);
// for's loop expression if existing
if (_forStatement.getLoopExpression())
_forStatement.getLoopExpression()->accept(*this);
m_context.appendJumpTo(loopStart);
m_context << loopEnd;
m_continueTags.pop_back();
m_breakTags.pop_back();
return false;
}
bool Compiler::visit(Continue const&)
{
if (!m_continueTags.empty())

15
libsolidity/Compiler.h

@ -32,14 +32,22 @@ class Compiler: private ASTConstVisitor
public:
explicit Compiler(bool _optimize = false): m_optimize(_optimize), m_returnTag(m_context.newTag()) {}
void compileContract(ContractDefinition const& _contract, std::vector<MagicVariableDeclaration const*> const& _magicGlobals);
void compileContract(ContractDefinition const& _contract, std::vector<MagicVariableDeclaration const*> const& _magicGlobals,
std::map<ContractDefinition const*, bytes const*> const& _contracts);
bytes getAssembledBytecode() { return m_context.getAssembledBytecode(m_optimize); }
void streamAssembly(std::ostream& _stream) const { m_context.streamAssembly(_stream); }
private:
/// Creates a new compiler context / assembly, packs the current code into the data part and
/// Registers the global objects and the non-function objects inside the contract with the context.
void initializeContext(ContractDefinition const& _contract, std::vector<MagicVariableDeclaration const*> const& _magicGlobals,
std::map<ContractDefinition const*, bytes const*> const& _contracts);
/// 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.
void packIntoContractCreator(ContractDefinition const& _contract);
void packIntoContractCreator(ContractDefinition const& _contract, CompilerContext const& _runtimeContext);
void appendConstructorCall(FunctionDefinition const& _constructor);
/// Recursively searches the call graph and returns all functions needed by the constructor (including itself).
std::set<FunctionDefinition const*> getFunctionsNeededByConstructor(FunctionDefinition const& _constructor);
void appendFunctionSelector(ContractDefinition const& _contract);
/// Creates code that unpacks the arguments for the given function, from memory if
/// @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes.
@ -51,6 +59,7 @@ private:
virtual bool visit(FunctionDefinition const& _function) override;
virtual bool visit(IfStatement const& _ifStatement) override;
virtual bool visit(WhileStatement const& _whileStatement) override;
virtual bool visit(ForStatement const& _forStatement) override;
virtual bool visit(Continue const& _continue) override;
virtual bool visit(Break const& _break) override;
virtual bool visit(Return const& _return) override;

24
libsolidity/CompilerContext.cpp

@ -27,8 +27,10 @@
using namespace std;
namespace dev {
namespace solidity {
namespace dev
{
namespace solidity
{
void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaration)
{
@ -62,6 +64,13 @@ void CompilerContext::addFunction(FunctionDefinition const& _function)
m_functionEntryLabels.insert(std::make_pair(&_function, m_asm.newTag()));
}
bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const
{
auto ret = m_compiledContracts.find(&_contract);
solAssert(ret != m_compiledContracts.end(), "Compiled contract not found.");
return *ret->second;
}
bool CompilerContext::isLocalVariable(Declaration const* _declaration) const
{
return m_localVariables.count(_declaration) > 0;
@ -70,16 +79,14 @@ bool CompilerContext::isLocalVariable(Declaration const* _declaration) const
eth::AssemblyItem CompilerContext::getFunctionEntryLabel(FunctionDefinition const& _function) const
{
auto res = m_functionEntryLabels.find(&_function);
if (asserts(res != m_functionEntryLabels.end()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Function entry label not found."));
solAssert(res != m_functionEntryLabels.end(), "Function entry label not found.");
return res->second.tag();
}
unsigned CompilerContext::getBaseStackOffsetOfVariable(Declaration const& _declaration) const
{
auto res = m_localVariables.find(&_declaration);
if (asserts(res != m_localVariables.end()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable not found on stack."));
solAssert(res != m_localVariables.end(), "Variable not found on stack.");
return m_localVariablesSize - res->second - 1;
}
@ -91,12 +98,9 @@ unsigned CompilerContext::baseToCurrentStackOffset(unsigned _baseOffset) const
u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const
{
auto it = m_stateVariables.find(&_declaration);
if (it == m_stateVariables.end())
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable not found in storage."));
solAssert(it != m_stateVariables.end(), "Variable not found in storage.");
return it->second;
}
}
}

14
libsolidity/CompilerContext.h

@ -39,8 +39,6 @@ namespace solidity {
class CompilerContext
{
public:
CompilerContext(): m_stateVariablesSize(0) {}
void addMagicGlobal(MagicVariableDeclaration const& _declaration);
void addStateVariable(VariableDeclaration const& _declaration);
void startNewFunction() { m_localVariables.clear(); m_asm.setDeposit(0); }
@ -48,6 +46,9 @@ public:
void addAndInitializeVariable(VariableDeclaration const& _declaration);
void addFunction(FunctionDefinition const& _function);
void setCompiledContracts(std::map<ContractDefinition const*, bytes const*> const& _contracts) { m_compiledContracts = _contracts; }
bytes const& getCompiledContract(ContractDefinition const& _contract) const;
void adjustStackOffset(int _adjustment) { m_asm.adjustDeposit(_adjustment); }
bool isMagicGlobal(Declaration const* _declaration) const { return m_magicGlobals.count(_declaration); }
@ -80,6 +81,10 @@ public:
/// 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); }
/// Pushes the size of the final program
void appendProgramSize() { return 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); }
/// Append elements to the current instruction list and adjust @a m_stackOffset.
CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; }
@ -90,13 +95,16 @@ public:
eth::Assembly const& getAssembly() const { return m_asm; }
void streamAssembly(std::ostream& _stream) const { _stream << m_asm; }
bytes getAssembledBytecode(bool _optimize = false) { return m_asm.optimise(_optimize).assemble(); }
private:
eth::Assembly m_asm;
/// Magic global variables like msg, tx or this, distinguished by type.
std::set<Declaration const*> m_magicGlobals;
/// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, bytes const*> m_compiledContracts;
/// Size of the state variables, offset of next variable to be added.
u256 m_stateVariablesSize;
u256 m_stateVariablesSize = 0;
/// Storage offsets of state variables
std::map<Declaration const*, u256> m_stateVariables;
/// Offsets of local variables on the stack (relative to stack base).

35
libsolidity/CompilerStack.cpp

@ -36,13 +36,12 @@ namespace dev
namespace solidity
{
void CompilerStack::addSource(string const& _name, string const& _content)
bool CompilerStack::addSource(string const& _name, string const& _content)
{
if (m_sources.count(_name))
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Source by given name already exists."));
bool existed = m_sources.count(_name);
reset(true);
m_sources[_name].scanner = make_shared<Scanner>(CharStream(_content), _name);
return existed;
}
void CompilerStack::setSource(string const& _sourceCode)
@ -73,6 +72,15 @@ void CompilerStack::parse()
resolver.resolveNamesAndTypes(*contract);
m_contracts[contract->getName()].contract = contract;
}
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
m_globalContext->setCurrentContract(*contract);
resolver.updateDeclaration(*m_globalContext->getCurrentThis());
resolver.checkTypeRequirements(*contract);
m_contracts[contract->getName()].contract = contract;
}
m_parseSuccessful = true;
}
@ -96,16 +104,20 @@ void CompilerStack::compile(bool _optimize)
{
if (!m_parseSuccessful)
parse();
map<ContractDefinition const*, bytes const*> contractBytecode;
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
m_globalContext->setCurrentContract(*contract);
shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize);
compiler->compileContract(*contract, m_globalContext->getMagicVariables());
compiler->compileContract(*contract, m_globalContext->getMagicVariables(),
contractBytecode);
Contract& compiledContract = m_contracts[contract->getName()];
compiledContract.bytecode = compiler->getAssembledBytecode();
compiledContract.compiler = move(compiler);
contractBytecode[compiledContract.contract] = &compiledContract.bytecode;
}
}
@ -168,6 +180,11 @@ SourceUnit const& CompilerStack::getAST(string const& _sourceName) const
return *getSource(_sourceName).ast;
}
ContractDefinition const& CompilerStack::getContractDefinition(string const& _contractName) const
{
return *getContract(_contractName).contract;
}
bytes CompilerStack::staticCompile(std::string const& _sourceCode, bool _optimize)
{
CompilerStack stack;
@ -221,9 +238,13 @@ CompilerStack::Contract const& CompilerStack::getContract(string const& _contrac
{
if (m_contracts.empty())
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("No compiled contracts found."));
string contractName = _contractName;
if (_contractName.empty())
return m_contracts.begin()->second;
auto it = m_contracts.find(_contractName);
// try to find the "last contract"
for (ASTPointer<ASTNode> const& node: m_sourceOrder.back()->ast->getNodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
contractName = contract->getName();
auto it = m_contracts.find(contractName);
if (it == m_contracts.end())
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found."));
return it->second;

11
libsolidity/CompilerStack.h

@ -57,7 +57,8 @@ public:
CompilerStack(): m_parseSuccessful(false) {}
/// Adds a source object (e.g. file) to the parser. After this, parse has to be called again.
void addSource(std::string const& _name, std::string const& _content);
/// @returns true if a source object by the name already existed and was replaced.
bool addSource(std::string const& _name, std::string const& _content);
void setSource(std::string const& _sourceCode);
/// Parses all source units that were added
void parse();
@ -86,9 +87,13 @@ public:
/// Can be one of 3 types defined at @c DocumentationType
std::string const& getJsonDocumentation(std::string const& _contractName, DocumentationType _type) const;
/// Returns the previously used scanner, useful for counting lines during error reporting.
/// @returns the previously used scanner, useful for counting lines during error reporting.
Scanner const& getScanner(std::string const& _sourceName = "") const;
/// @returns the parsed source unit with the supplied name.
SourceUnit const& getAST(std::string const& _sourceName = "") const;
/// @returns the parsed contract with the supplied name. Throws an exception if the contract
/// does not exist.
ContractDefinition const& getContractDefinition(std::string const& _contractName) const;
/// Compile the given @a _sourceCode to bytecode. If a scanner is provided, it is used for
/// scanning the source code - this is useful for printing exception information.
@ -108,7 +113,7 @@ private:
struct Contract
{
ContractDefinition* contract;
ContractDefinition const* contract;
std::shared_ptr<Compiler> compiler;
bytes bytecode;
std::shared_ptr<InterfaceHandler> interfaceHandler;

6
libsolidity/CompilerUtils.cpp

@ -39,8 +39,7 @@ void CompilerUtils::loadFromMemory(unsigned _offset, unsigned _bytes, bool _left
return;
}
eth::Instruction load = _fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD;
if (asserts(_bytes <= 32))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Memory load of more than 32 bytes requested."));
solAssert(_bytes <= 32, "Memory load of more than 32 bytes requested.");
if (_bytes == 32)
m_context << u256(_offset) << load;
else
@ -63,8 +62,7 @@ void CompilerUtils::storeInMemory(unsigned _offset, unsigned _bytes, bool _leftA
m_context << eth::Instruction::POP;
return;
}
if (asserts(_bytes <= 32))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Memory store of more than 32 bytes requested."));
solAssert(_bytes <= 32, "Memory store of more than 32 bytes requested.");
if (_bytes != 32 && !_leftAligned)
// shift the value accordingly before storing
m_context << (u256(1) << ((32 - _bytes) * 8)) << eth::Instruction::MUL;

63
libsolidity/ExpressionCompiler.cpp

@ -30,8 +30,10 @@
using namespace std;
namespace dev {
namespace solidity {
namespace dev
{
namespace solidity
{
void ExpressionCompiler::compileExpression(CompilerContext& _context, Expression const& _expression, bool _optimize)
{
@ -51,8 +53,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
_assignment.getRightHandSide().accept(*this);
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
_assignment.getLeftHandSide().accept(*this);
if (asserts(m_currentLValue.isValid()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved."));
solAssert(m_currentLValue.isValid(), "LValue not retrieved.");
Token::Value op = _assignment.getAssignmentOperator();
if (op != Token::ASSIGN) // compound assignment
@ -84,8 +85,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation)
break;
case Token::DELETE: // delete
// @todo semantics change for complex types
if (asserts(m_currentLValue.isValid()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved."));
solAssert(m_currentLValue.isValid(), "LValue not retrieved.");
m_context << u256(0);
if (m_currentLValue.storesReferenceOnStack())
@ -95,8 +95,7 @@ void ExpressionCompiler::endVisit(UnaryOperation const& _unaryOperation)
break;
case Token::INC: // ++ (pre- or postfix)
case Token::DEC: // -- (pre- or postfix)
if (asserts(m_currentLValue.isValid()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("LValue not retrieved."));
solAssert(m_currentLValue.isValid(), "LValue not retrieved.");
m_currentLValue.retrieveValue(_unaryOperation);
if (!_unaryOperation.isPrefixOperation())
{
@ -179,8 +178,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
if (_functionCall.isTypeConversion())
{
//@todo struct construction
if (asserts(_functionCall.getArguments().size() == 1))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(_functionCall.getArguments().size() == 1, "");
Expression const& firstArgument = *_functionCall.getArguments().front();
firstArgument.accept(*this);
if (firstArgument.getType()->getCategory() == Type::Category::CONTRACT &&
@ -195,8 +193,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
FunctionType const& function = dynamic_cast<FunctionType const&>(*_functionCall.getExpression().getType());
vector<ASTPointer<Expression const>> arguments = _functionCall.getArguments();
if (asserts(arguments.size() == function.getParameterTypes().size()))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(arguments.size() == function.getParameterTypes().size(), "");
switch (function.getLocation())
{
@ -279,6 +276,42 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
return false;
}
bool ExpressionCompiler::visit(NewExpression const& _newExpression)
{
ContractType const* type = dynamic_cast<ContractType const*>(_newExpression.getType().get());
solAssert(type, "");
TypePointers const& types = type->getConstructorType()->getParameterTypes();
vector<ASTPointer<Expression const>> arguments = _newExpression.getArguments();
solAssert(arguments.size() == types.size(), "");
// copy the contracts code into memory
bytes const& bytecode = m_context.getCompiledContract(*_newExpression.getContract());
m_context << u256(bytecode.size());
//@todo could be done by actually appending the Assembly, but then we probably need to compile
// multiple times. Will revisit once external fuctions are inlined.
m_context.appendData(bytecode);
//@todo copy to memory position 0, shift as soon as we use memory
m_context << u256(0) << eth::Instruction::CODECOPY;
unsigned dataOffset = bytecode.size();
for (unsigned i = 0; i < arguments.size(); ++i)
{
arguments[i]->accept(*this);
appendTypeConversion(*arguments[i]->getType(), *types[i]);
unsigned const numBytes = types[i]->getCalldataEncodedSize();
if (numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(arguments[i]->getLocation())
<< errinfo_comment("Type " + types[i]->toString() + " not yet supported."));
bool const leftAligned = types[i]->getCategory() == Type::Category::STRING;
CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, leftAligned);
dataOffset += numBytes;
}
// size, offset, endowment
m_context << u256(dataOffset) << u256(0) << u256(0) << eth::Instruction::CREATE;
return false;
}
void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
{
ASTString const& member = _memberAccess.getMemberName();
@ -401,8 +434,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
void ExpressionCompiler::appendAndOrOperatorCode(BinaryOperation const& _binaryOperation)
{
Token::Value const op = _binaryOperation.getOperator();
if (asserts(op == Token::OR || op == Token::AND))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(op == Token::OR || op == Token::AND, "");
_binaryOperation.getLeftExpression().accept(*this);
m_context << eth::Instruction::DUP1;
@ -554,8 +586,7 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio
vector<ASTPointer<Expression const>> const& _arguments,
FunctionCallOptions const& _options)
{
if (asserts(_arguments.size() == _functionType.getParameterTypes().size()))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(_arguments.size() == _functionType.getParameterTypes().size(), "");
unsigned dataOffset = _options.bare ? 0 : 1; // reserve one byte for the function index
for (unsigned i = 0; i < _arguments.size(); ++i)

1
libsolidity/ExpressionCompiler.h

@ -60,6 +60,7 @@ private:
virtual void endVisit(UnaryOperation const& _unaryOperation) override;
virtual bool visit(BinaryOperation const& _binaryOperation) override;
virtual bool visit(FunctionCall const& _functionCall) override;
virtual bool visit(NewExpression const& _newExpression) override;
virtual void endVisit(MemberAccess const& _memberAccess) override;
virtual bool visit(IndexAccess const& _indexAccess) override;
virtual void endVisit(Identifier const& _identifier) override;

57
libsolidity/InterfaceHandler.cpp

@ -15,7 +15,7 @@ InterfaceHandler::InterfaceHandler()
m_lastTag = DocTagType::NONE;
}
std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefinition& _contractDef,
std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefinition const& _contractDef,
DocumentationType _type)
{
switch(_type)
@ -32,7 +32,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDocumentation(ContractDefiniti
return nullptr;
}
std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinition& _contractDef)
std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinition const& _contractDef)
{
Json::Value methods(Json::arrayValue);
@ -63,7 +63,7 @@ std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinitio
return std::unique_ptr<std::string>(new std::string(m_writer.write(methods)));
}
std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefinition& _contractDef)
std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefinition const& _contractDef)
{
Json::Value doc;
Json::Value methods(Json::objectValue);
@ -88,7 +88,7 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi
return std::unique_ptr<std::string>(new std::string(m_writer.write(doc)));
}
std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefinition& _contractDef)
std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefinition const& _contractDef)
{
// LTODO: Somewhere in this function warnings for mismatch of param names
// should be thrown
@ -166,9 +166,12 @@ static inline std::string::const_iterator skipLineOrEOS(std::string::const_itera
std::string::const_iterator InterfaceHandler::parseDocTagLine(std::string::const_iterator _pos,
std::string::const_iterator _end,
std::string& _tagString,
DocTagType _tagType)
DocTagType _tagType,
bool _appending)
{
auto nlPos = std::find(_pos, _end, '\n');
if (_appending && _pos < _end && *_pos != ' ')
_tagString += " ";
std::copy(_pos, nlPos, back_inserter(_tagString));
m_lastTag = _tagType;
return skipLineOrEOS(nlPos, _end);
@ -198,11 +201,11 @@ std::string::const_iterator InterfaceHandler::appendDocTagParam(std::string::con
std::string::const_iterator _end)
{
// Should never be called with an empty vector
if (asserts(!m_params.empty()))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Internal: Tried to append to empty parameter"));
solAssert(!m_params.empty(), "Internal: Tried to append to empty parameter");
auto pair = m_params.back();
pair.second += " ";
if (_pos < _end && *_pos != ' ')
pair.second += " ";
auto nlPos = std::find(_pos, _end, '\n');
std::copy(_pos, nlPos, back_inserter(pair.second));
@ -222,17 +225,17 @@ std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_ite
if (m_lastTag == DocTagType::NONE || _tag != "")
{
if (_tag == "dev")
return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV);
return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV, false);
else if (_tag == "notice")
return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE);
return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE, false);
else if (_tag == "return")
return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN);
return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN, false);
else if (_tag == "author")
{
if (_owner == CommentOwner::CONTRACT)
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR);
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR, false);
else if (_owner == CommentOwner::FUNCTION)
return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR);
return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR, false);
else
// LTODO: for now this else makes no sense but later comments will go to more language constructs
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag is legal only for contracts"));
@ -240,7 +243,7 @@ std::string::const_iterator InterfaceHandler::parseDocTag(std::string::const_ite
else if (_tag == "title")
{
if (_owner == CommentOwner::CONTRACT)
return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE);
return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE, false);
else
// LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag is legal only for contracts"));
@ -262,34 +265,22 @@ std::string::const_iterator InterfaceHandler::appendDocTag(std::string::const_it
switch (m_lastTag)
{
case DocTagType::DEV:
m_dev += " ";
return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV);
return parseDocTagLine(_pos, _end, m_dev, DocTagType::DEV, true);
case DocTagType::NOTICE:
m_notice += " ";
return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE);
return parseDocTagLine(_pos, _end, m_notice, DocTagType::NOTICE, true);
case DocTagType::RETURN:
m_return += " ";
return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN);
return parseDocTagLine(_pos, _end, m_return, DocTagType::RETURN, true);
case DocTagType::AUTHOR:
if (_owner == CommentOwner::CONTRACT)
{
m_contractAuthor += " ";
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR);
}
return parseDocTagLine(_pos, _end, m_contractAuthor, DocTagType::AUTHOR, true);
else if (_owner == CommentOwner::FUNCTION)
{
m_author += " ";
return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR);
}
return parseDocTagLine(_pos, _end, m_author, DocTagType::AUTHOR, true);
else
// LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@author tag in illegal comment"));
case DocTagType::TITLE:
if (_owner == CommentOwner::CONTRACT)
{
m_title += " ";
return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE);
}
return parseDocTagLine(_pos, _end, m_title, DocTagType::TITLE, true);
else
// LTODO: Unknown tag, throw some form of warning and not just an exception
BOOST_THROW_EXCEPTION(DocstringParsingError() << errinfo_comment("@title tag in illegal comment"));
@ -330,7 +321,7 @@ void InterfaceHandler::parseDocString(std::string const& _string, CommentOwner _
currPos = parseDocTag(tagNameEndPos + 1, end, std::string(tagPos + 1, tagNameEndPos), _owner);
}
else if (m_lastTag != DocTagType::NONE) // continuation of the previous tag
currPos = appendDocTag(currPos + 1, end, _owner);
currPos = appendDocTag(currPos, end, _owner);
else if (currPos != end) // skip the line if a newline was found
currPos = nlPos + 1;
}

11
libsolidity/InterfaceHandler.h

@ -67,23 +67,23 @@ public:
/// types provided by @c DocumentationType
/// @return A unique pointer contained string with the json
/// representation of provided type
std::unique_ptr<std::string> getDocumentation(ContractDefinition& _contractDef,
std::unique_ptr<std::string> getDocumentation(ContractDefinition const& _contractDef,
DocumentationType _type);
/// Get the ABI Interface of the contract
/// @param _contractDef The contract definition
/// @return A unique pointer contained string with the json
/// representation of the contract's ABI Interface
std::unique_ptr<std::string> getABIInterface(ContractDefinition& _contractDef);
std::unique_ptr<std::string> getABIInterface(ContractDefinition const& _contractDef);
/// Get the User documentation of the contract
/// @param _contractDef The contract definition
/// @return A unique pointer contained string with the json
/// representation of the contract's user documentation
std::unique_ptr<std::string> getUserDocumentation(ContractDefinition& _contractDef);
std::unique_ptr<std::string> getUserDocumentation(ContractDefinition const& _contractDef);
/// Get the Developer's documentation of the contract
/// @param _contractDef The contract definition
/// @return A unique pointer contained string with the json
/// representation of the contract's developer documentation
std::unique_ptr<std::string> getDevDocumentation(ContractDefinition& _contractDef);
std::unique_ptr<std::string> getDevDocumentation(ContractDefinition const& _contractDef);
private:
void resetUser();
@ -92,7 +92,8 @@ private:
std::string::const_iterator parseDocTagLine(std::string::const_iterator _pos,
std::string::const_iterator _end,
std::string& _tagString,
DocTagType _tagType);
DocTagType _tagType,
bool _appending);
std::string::const_iterator parseDocTagParam(std::string::const_iterator _pos,
std::string::const_iterator _end);
std::string::const_iterator appendDocTagParam(std::string::const_iterator _pos,

32
libsolidity/NameAndTypeResolver.cpp

@ -49,8 +49,6 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
m_currentScope = &m_scopes[&_contract];
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs())
ReferencesResolver resolver(*structDef, *this, nullptr);
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs())
structDef->checkValidityOfMembers();
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables())
ReferencesResolver resolver(*variable, *this, nullptr);
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
@ -59,22 +57,20 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
ReferencesResolver referencesResolver(*function, *this,
function->getReturnParameterList().get());
}
// First, the parameter types of all functions need to be resolved before we can check
// the types, since it is possible to call functions that are only defined later
// in the source.
for (ASTPointer<FunctionDefinition> const& function: _contract.getDefinedFunctions())
{
m_currentScope = &m_scopes[function.get()];
function->checkTypeRequirements();
}
m_currentScope = &m_scopes[nullptr];
}
void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract)
{
for (ASTPointer<StructDefinition> const& structDef: _contract.getDefinedStructs())
structDef->checkValidityOfMembers();
_contract.checkTypeRequirements();
}
void NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
{
m_scopes[nullptr].registerDeclaration(_declaration, true);
if (asserts(_declaration.getScope() == nullptr))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Updated declaration outside global scope."));
solAssert(_declaration.getScope() == nullptr, "Updated declaration outside global scope.");
}
Declaration const* NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const
@ -136,8 +132,7 @@ void DeclarationRegistrationHelper::endVisit(VariableDefinition& _variableDefini
{
// Register the local variables with the function
// This does not fit here perfectly, but it saves us another AST visit.
if (asserts(m_currentFunction))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Variable definition without function."));
solAssert(m_currentFunction, "Variable definition without function.");
m_currentFunction->addLocalVariable(_variableDefinition.getDeclaration());
}
@ -152,15 +147,13 @@ void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declara
map<ASTNode const*, DeclarationContainer>::iterator iter;
bool newlyAdded;
tie(iter, newlyAdded) = m_scopes.emplace(&_declaration, DeclarationContainer(m_currentScope, &m_scopes[m_currentScope]));
if (asserts(newlyAdded))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to add new scope."));
solAssert(newlyAdded, "Unable to add new scope.");
m_currentScope = &_declaration;
}
void DeclarationRegistrationHelper::closeCurrentScope()
{
if (asserts(m_currentScope))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Closed non-existing scope."));
solAssert(m_currentScope, "Closed non-existing scope.");
m_currentScope = m_scopes[m_currentScope].getEnclosingDeclaration();
}
@ -199,8 +192,7 @@ void ReferencesResolver::endVisit(VariableDeclaration& _variable)
bool ReferencesResolver::visit(Return& _return)
{
if (asserts(m_returnParameters))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Return parameters not set."));
solAssert(m_returnParameters, "Return parameters not set.");
_return.setFunctionReturnParameters(*m_returnParameters);
return true;
}

2
libsolidity/NameAndTypeResolver.h

@ -46,6 +46,8 @@ public:
void registerDeclarations(SourceUnit& _sourceUnit);
/// Resolves all names and types referenced from the given contract.
void resolveNamesAndTypes(ContractDefinition& _contract);
/// Check all type requirements in the given contract.
void checkTypeRequirements(ContractDefinition& _contract);
/// Updates the given global declaration (used for "this"). Not to be used with declarations
/// that create their own scope.
void updateDeclaration(Declaration const& _declaration);

79
libsolidity/Parser.cpp

@ -304,6 +304,8 @@ ASTPointer<Statement> Parser::parseStatement()
return parseIfStatement();
case Token::WHILE:
return parseWhileStatement();
case Token::FOR:
return parseForStatement();
case Token::LBRACE:
return parseBlock();
// starting from here, all statements must be terminated by a semicolon
@ -328,18 +330,7 @@ ASTPointer<Statement> Parser::parseStatement()
}
break;
default:
// distinguish between variable definition (and potentially assignment) and expression statement
// (which include assignments to other expressions and pre-declared variables)
// We have a variable definition if we get a keyword that specifies a type name, or
// in the case of a user-defined type, we have two identifiers following each other.
if (m_scanner->getCurrentToken() == Token::MAPPING ||
m_scanner->getCurrentToken() == Token::VAR ||
((Token::isElementaryTypeName(m_scanner->getCurrentToken()) ||
m_scanner->getCurrentToken() == Token::IDENTIFIER) &&
m_scanner->peekNextToken() == Token::IDENTIFIER))
statement = parseVariableDefinition();
else // "ordinary" expression statement
statement = parseExpressionStatement();
statement = parseVarDefOrExprStmt();
}
expectToken(Token::SEMICOLON);
return statement;
@ -377,6 +368,44 @@ ASTPointer<WhileStatement> Parser::parseWhileStatement()
return nodeFactory.createNode<WhileStatement>(condition, body);
}
ASTPointer<ForStatement> Parser::parseForStatement()
{
ASTNodeFactory nodeFactory(*this);
ASTPointer<Statement> initExpression;
ASTPointer<Expression> conditionExpression;
ASTPointer<ExpressionStatement> loopExpression;
expectToken(Token::FOR);
expectToken(Token::LPAREN);
// LTODO: Maybe here have some predicate like peekExpression() instead of checking for semicolon and RPAREN?
if (m_scanner->getCurrentToken() != Token::SEMICOLON)
initExpression = parseVarDefOrExprStmt();
expectToken(Token::SEMICOLON);
if (m_scanner->getCurrentToken() != Token::SEMICOLON)
conditionExpression = parseExpression();
expectToken(Token::SEMICOLON);
if (m_scanner->getCurrentToken() != Token::RPAREN)
loopExpression = parseExpressionStatement();
expectToken(Token::RPAREN);
ASTPointer<Statement> body = parseStatement();
nodeFactory.setEndPositionFromNode(body);
return nodeFactory.createNode<ForStatement>(initExpression,
conditionExpression,
loopExpression,
body);
}
ASTPointer<Statement> Parser::parseVarDefOrExprStmt()
{
if (peekVariableDefinition())
return parseVariableDefinition();
else
return parseExpressionStatement();
}
ASTPointer<VariableDefinition> Parser::parseVariableDefinition()
{
ASTNodeFactory nodeFactory(*this);
@ -437,7 +466,17 @@ ASTPointer<Expression> Parser::parseUnaryExpression()
{
ASTNodeFactory nodeFactory(*this);
Token::Value token = m_scanner->getCurrentToken();
if (Token::isUnaryOp(token) || Token::isCountOp(token))
if (token == Token::NEW)
{
expectToken(Token::NEW);
ASTPointer<Identifier> contractName = ASTNodeFactory(*this).createNode<Identifier>(expectIdentifierToken());
expectToken(Token::LPAREN);
vector<ASTPointer<Expression>> arguments(parseFunctionCallArguments());
expectToken(Token::RPAREN);
nodeFactory.markEndPosition();
return nodeFactory.createNode<NewExpression>(contractName, arguments);
}
else if (Token::isUnaryOp(token) || Token::isCountOp(token))
{
// prefix expression
m_scanner->next();
@ -556,6 +595,20 @@ vector<ASTPointer<Expression>> Parser::parseFunctionCallArguments()
return arguments;
}
bool Parser::peekVariableDefinition()
{
// distinguish between variable definition (and potentially assignment) and expression statement
// (which include assignments to other expressions and pre-declared variables)
// We have a variable definition if we get a keyword that specifies a type name, or
// in the case of a user-defined type, we have two identifiers following each other.
return (m_scanner->getCurrentToken() == Token::MAPPING ||
m_scanner->getCurrentToken() == Token::VAR ||
((Token::isElementaryTypeName(m_scanner->getCurrentToken()) ||
m_scanner->getCurrentToken() == Token::IDENTIFIER) &&
m_scanner->peekNextToken() == Token::IDENTIFIER));
}
void Parser::expectToken(Token::Value _value)
{
if (m_scanner->getCurrentToken() != _value)

5
libsolidity/Parser.h

@ -59,6 +59,8 @@ private:
ASTPointer<Statement> parseStatement();
ASTPointer<IfStatement> parseIfStatement();
ASTPointer<WhileStatement> parseWhileStatement();
ASTPointer<ForStatement> parseForStatement();
ASTPointer<Statement> parseVarDefOrExprStmt();
ASTPointer<VariableDefinition> parseVariableDefinition();
ASTPointer<ExpressionStatement> parseExpressionStatement();
ASTPointer<Expression> parseExpression();
@ -72,6 +74,9 @@ private:
///@{
///@name Helper functions
/// Peeks ahead in the scanner to determine if a variable definition is going to follow
bool peekVariableDefinition();
/// If current token value is not _value, throw exception otherwise advance token.
void expectToken(Token::Value _value);
Token::Value expectAssignmentOperator();

143
libsolidity/Scanner.cpp

@ -52,6 +52,7 @@
#include <algorithm>
#include <tuple>
#include <libsolidity/Utils.h>
#include <libsolidity/Scanner.h>
using namespace std;
@ -79,7 +80,7 @@ bool isLineTerminator(char c)
}
bool isWhiteSpace(char c)
{
return c == ' ' || c == '\n' || c == '\t';
return c == ' ' || c == '\n' || c == '\t' || c == '\r';
}
bool isIdentifierStart(char c)
{
@ -208,6 +209,15 @@ bool Scanner::skipWhitespace()
return getSourcePos() != startPosition;
}
bool Scanner::skipWhitespaceExceptLF()
{
int const startPosition = getSourcePos();
while (isWhiteSpace(m_char) && !isLineTerminator(m_char))
advance();
// Return whether or not we skipped any characters.
return getSourcePos() != startPosition;
}
Token::Value Scanner::skipSingleLineComment()
{
// The line terminator at the end of the line is not considered
@ -218,10 +228,11 @@ Token::Value Scanner::skipSingleLineComment()
return Token::WHITESPACE;
}
Token::Value Scanner::scanDocumentationComment()
Token::Value Scanner::scanSingleLineDocComment()
{
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
advance(); //consume the last '/'
advance(); //consume the last '/' at ///
skipWhitespaceExceptLF();
while (!isSourcePastEndOfInput())
{
if (isLineTerminator(m_char))
@ -249,8 +260,6 @@ Token::Value Scanner::scanDocumentationComment()
Token::Value Scanner::skipMultiLineComment()
{
if (asserts(m_char == '*'))
BOOST_THROW_EXCEPTION(InternalCompilerError());
advance();
while (!isSourcePastEndOfInput())
{
@ -270,6 +279,97 @@ Token::Value Scanner::skipMultiLineComment()
return Token::ILLEGAL;
}
Token::Value Scanner::scanMultiLineDocComment()
{
LiteralScope literal(this, LITERAL_TYPE_COMMENT);
bool endFound = false;
bool charsAdded = false;
advance(); //consume the last '*' at /**
skipWhitespaceExceptLF();
while (!isSourcePastEndOfInput())
{
//handle newlines in multline comments
if (isLineTerminator(m_char))
{
skipWhitespace();
if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) != '/')
{ // skip first '*' in subsequent lines
if (charsAdded)
addCommentLiteralChar('\n');
m_char = m_source.advanceAndGet(2);
}
else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/')
{ // if after newline the comment ends, don't insert the newline
m_char = m_source.advanceAndGet(2);
endFound = true;
break;
}
else if (charsAdded)
addCommentLiteralChar('\n');
}
if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/')
{
m_char = m_source.advanceAndGet(2);
endFound = true;
break;
}
addCommentLiteralChar(m_char);
charsAdded = true;
advance();
}
literal.complete();
if (!endFound)
return Token::ILLEGAL;
else
return Token::COMMENT_LITERAL;
}
Token::Value Scanner::scanSlash()
{
int firstSlashPosition = getSourcePos();
advance();
if (m_char == '/')
{
if (!advance()) /* double slash comment directly before EOS */
return Token::WHITESPACE;
else if (m_char == '/')
{
// doxygen style /// comment
Token::Value comment;
m_nextSkippedComment.location.start = firstSlashPosition;
comment = scanSingleLineDocComment();
m_nextSkippedComment.location.end = getSourcePos();
m_nextSkippedComment.token = comment;
return Token::WHITESPACE;
}
else
return skipSingleLineComment();
}
else if (m_char == '*')
{
// doxygen style /** natspec comment
if (!advance()) /* slash star comment before EOS */
return Token::WHITESPACE;
else if (m_char == '*')
{
Token::Value comment;
m_nextSkippedComment.location.start = firstSlashPosition;
comment = scanMultiLineDocComment();
m_nextSkippedComment.location.end = getSourcePos();
m_nextSkippedComment.token = comment;
return Token::WHITESPACE;
}
else
return skipMultiLineComment();
}
else if (m_char == '=')
return selectToken(Token::ASSIGN_DIV);
else
return Token::DIV;
}
void Scanner::scanToken()
{
m_nextToken.literal.clear();
@ -372,29 +472,7 @@ void Scanner::scanToken()
break;
case '/':
// / // /* /=
advance();
if (m_char == '/')
{
if (!advance()) /* double slash comment directly before EOS */
token = Token::WHITESPACE;
else if (m_char == '/')
{
Token::Value comment;
m_nextSkippedComment.location.start = getSourcePos();
comment = scanDocumentationComment();
m_nextSkippedComment.location.end = getSourcePos();
m_nextSkippedComment.token = comment;
token = Token::WHITESPACE;
}
else
token = skipSingleLineComment();
}
else if (m_char == '*')
token = skipMultiLineComment();
else if (m_char == '=')
token = selectToken(Token::ASSIGN_DIV);
else
token = Token::DIV;
token = scanSlash();
break;
case '&':
// & && &=
@ -597,8 +675,7 @@ Token::Value Scanner::scanNumber(char _charSeen)
// scan exponent, if any
if (m_char == 'e' || m_char == 'E')
{
if (asserts(kind != HEX)) // 'e'/'E' must be scanned as part of the hex number
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number");
if (kind != DECIMAL)
return Token::ILLEGAL;
// scan exponent
@ -639,8 +716,7 @@ static Token::Value keywordOrIdentifierToken(string const& _input)
Token::Value Scanner::scanIdentifierOrKeyword()
{
if (asserts(isIdentifierStart(m_char)))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(isIdentifierStart(m_char), "");
LiteralScope literal(this, LITERAL_TYPE_STRING);
addLiteralCharAndAdvance();
// Scan the rest of the identifier characters.
@ -662,8 +738,7 @@ char CharStream::advanceAndGet(size_t _chars)
char CharStream::rollback(size_t _amount)
{
if (asserts(m_pos >= _amount))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(m_pos >= _amount, "");
m_pos -= _amount;
return get();
}

7
libsolidity/Scanner.h

@ -182,6 +182,8 @@ private:
/// Skips all whitespace and @returns true if something was skipped.
bool skipWhitespace();
/// Skips all whitespace except Line feeds and returns true if something was skipped
bool skipWhitespaceExceptLF();
Token::Value skipSingleLineComment();
Token::Value skipMultiLineComment();
@ -190,7 +192,10 @@ private:
Token::Value scanIdentifierOrKeyword();
Token::Value scanString();
Token::Value scanDocumentationComment();
Token::Value scanSingleLineDocComment();
Token::Value scanMultiLineDocComment();
/// Scans a slash '/' and depending on the characters returns the appropriate token
Token::Value scanSlash();
/// Scans an escape-sequence which is part of a string and adds the
/// decoded character to the current literal. Returns true if a pattern

13
libsolidity/Token.h

@ -44,6 +44,7 @@
#include <libdevcore/Common.h>
#include <libdevcore/Log.h>
#include <libsolidity/Utils.h>
#include <libsolidity/Exceptions.h>
namespace dev
@ -344,8 +345,7 @@ public:
// (e.g. "LT" for the token LT).
static char const* getName(Value tok)
{
if (asserts(tok < NUM_TOKENS))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(tok < NUM_TOKENS, "");
return m_name[tok];
}
@ -360,8 +360,7 @@ public:
static Value AssignmentToBinaryOp(Value op)
{
if (asserts(isAssignmentOp(op) && op != ASSIGN))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(isAssignmentOp(op) && op != ASSIGN, "");
return Token::Value(op + (BIT_OR - ASSIGN_BIT_OR));
}
@ -375,8 +374,7 @@ public:
// have a (unique) string (e.g. an IDENTIFIER).
static char const* toString(Value tok)
{
if (asserts(tok < NUM_TOKENS))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(tok < NUM_TOKENS, "");
return m_string[tok];
}
@ -384,8 +382,7 @@ public:
// operators; returns 0 otherwise.
static int precedence(Value tok)
{
if (asserts(tok < NUM_TOKENS))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(tok < NUM_TOKENS, "");
return m_precedence[tok];
}

26
libsolidity/Types.cpp

@ -22,6 +22,7 @@
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libsolidity/Utils.h>
#include <libsolidity/Types.h>
#include <libsolidity/AST.h>
@ -34,8 +35,7 @@ namespace solidity
shared_ptr<Type const> Type::fromElementaryTypeName(Token::Value _typeToken)
{
if (asserts(Token::isElementaryTypeName(_typeToken)))
BOOST_THROW_EXCEPTION(InternalCompilerError());
solAssert(Token::isElementaryTypeName(_typeToken), "");
if (Token::INT <= _typeToken && _typeToken <= Token::HASH256)
{
@ -120,8 +120,8 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
{
if (isAddress())
m_bits = 160;
if (asserts(m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid bit number for integer type: " + dev::toString(_bits)));
solAssert(m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
"Invalid bit number for integer type: " + dev::toString(_bits));
}
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@ -215,9 +215,8 @@ shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string con
StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes)
{
if (asserts(m_bytes >= 0 && m_bytes <= 32))
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid byte number for static string type: " +
dev::toString(m_bytes)));
solAssert(m_bytes >= 0 && m_bytes <= 32,
"Invalid byte number for static string type: " + dev::toString(m_bytes));
}
bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@ -310,6 +309,19 @@ MemberList const& ContractType::getMembers() const
return *m_members;
}
shared_ptr<FunctionType const> const& ContractType::getConstructorType() const
{
if (!m_constructorType)
{
FunctionDefinition const* constructor = m_contract.getConstructor();
if (constructor)
m_constructorType = make_shared<FunctionType const>(*constructor);
else
m_constructorType = make_shared<FunctionType const>(TypePointers(), TypePointers());
}
return m_constructorType;
}
unsigned ContractType::getFunctionIndex(string const& _functionName) const
{
unsigned index = 0;

11
libsolidity/Types.h

@ -39,6 +39,7 @@ namespace solidity
// @todo realMxN, dynamic strings, text, arrays
class Type; // forward
class FunctionType; // forward
using TypePointer = std::shared_ptr<Type const>;
using TypePointers = std::vector<TypePointer>;
@ -249,10 +250,16 @@ public:
virtual MemberList const& getMembers() const override;
/// Returns the function type of the constructor. Note that the location part of the function type
/// is not used, as this type cannot be the type of a variable or expression.
std::shared_ptr<FunctionType const> const& getConstructorType() const;
unsigned getFunctionIndex(std::string const& _functionName) const;
private:
ContractDefinition const& m_contract;
/// Type of the constructor, @see getConstructorType. Lazily initialized.
mutable std::shared_ptr<FunctionType const> m_constructorType;
/// List of member types, will be lazy-initialized because of recursive references.
mutable std::unique_ptr<MemberList> m_members;
};
@ -339,8 +346,8 @@ public:
virtual std::string toString() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
TypePointer getKeyType() const { return m_keyType; }
TypePointer getValueType() const { return m_valueType; }
TypePointer const& getKeyType() const { return m_keyType; }
TypePointer const& getValueType() const { return m_valueType; }
private:
TypePointer m_keyType;

49
libsolidity/Utils.h

@ -0,0 +1,49 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @author Christian <c@ethdev.com>
* @date 2014
* Solidity Utilities.
*/
#pragma once
#include <string>
#include <libsolidity/Exceptions.h>
namespace dev
{
namespace solidity
{
/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
#define solAssert(CONDITION, DESCRIPTION) \
::dev::solidity::solAssertAux(CONDITION, DESCRIPTION, __LINE__, __FILE__, ETH_FUNC)
inline void solAssertAux(bool _condition, std::string const& _errorDescription, unsigned _line,
char const* _file, char const* _function)
{
if (!_condition)
::boost::throw_exception( InternalCompilerError()
<< errinfo_comment(_errorDescription)
<< ::boost::throw_function(_function)
<< ::boost::throw_file(_file)
<< ::boost::throw_line(_line));
}
}
}

10
libsolidity/grammar.txt

@ -16,20 +16,24 @@ Mapping = 'mapping' '(' ElementaryTypeName '=>' TypeName ')'
Block = '{' Statement* '}'
Statement = IfStatement | WhileStatement | Block |
( Continue | Break | Return | VariableDefinition | Expression ) ';'
( Continue | Break | Return | VariableDefinition | ExpressionStatement ) ';'
ExpressionStatement = Expression
IfStatement = 'if' '(' Expression ')' Statement ( 'else' Statement )?
WhileStatement = 'while' '(' Expression ')' Statement
VardefOrExprStmt = Variabledefinition | ExpressionStatement
ForStatement = 'for' '(' (VardefOrExprStmt)? ';' (Expression)? ';' (ExpressionStatement)? ')' Statement
Continue = 'continue' ';'
Break = 'break' ';'
Return = 'return' Expression? ';'
VariableDefinition = VariableDeclaration ( = Expression )? ';'
Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | IndexAccess |
Expression = Assignment | UnaryOperation | BinaryOperation | FunctionCall | NewExpression | IndexAccess |
MemberAccess | PrimaryExpression
// The expression syntax is actually much more complicated
Assignment = Expression (AssignmentOp Expression)
FunctionCall = Expression '(' ( Expression ( ',' Expression )* ) ')'
FunctionCall = Expression '(' Expression ( ',' Expression )* ')'
NewExpression = 'new' Identifier '(' ( Expression ( ',' Expression )* ) ')'
MemberAccess = Expression '.' Identifier
IndexAccess = Expression '[' Expresison ']'
PrimaryExpression = Identifier | NumberLiteral | StringLiteral | ElementaryTypeName | '(' Expression ')'

13
sc/cmdline.cpp

@ -10,6 +10,19 @@ int main(int argv, char** argc) {
std::cerr << "Must provide a command and arguments! Try parse, rewrite, compile, assemble\n";
return 0;
}
if (argv == 2 && (std::string(argc[1]) == "--help" || std::string(argc[1]) == "-h" )) {
std::cout << argc[1] << "\n";
std::cout << "serpent command input\n";
std::cout << "where input -s for from stdin, a file, or interpreted as serpent code if does not exist as file.";
std::cout << "where command: \n";
std::cout << " parse: Just parses and returns s-expression code.\n";
std::cout << " rewrite: Parse, use rewrite rules print s-expressions of result.\n";
std::cout << " compile: Return resulting compiled EVM code in hex.\n";
std::cout << " assemble: Return result from step before compilation.\n";
return 0;
}
std::string flag = "";
std::string command = argc[1];
std::string input;

99
solc/CommandLineInterface.cpp

@ -48,6 +48,16 @@ namespace dev
namespace solidity
{
// LTODO: Maybe some argument class pairing names with
// extensions and other attributes would be a better choice here?
static string const g_argAbiStr = "abi";
static string const g_argAsmStr = "asm";
static string const g_argAstStr = "ast";
static string const g_argBinaryStr = "binary";
static string const g_argOpcodesStr = "opcodes";
static string const g_argNatspecDevStr = "natspec-dev";
static string const g_argNatspecUserStr = "natspec-user";
static void version()
{
cout << "solc, the solidity complier commandline interface " << dev::Version << endl
@ -56,15 +66,16 @@ static void version()
exit(0);
}
static inline bool argToStdout(po::variables_map const& _args, const char* _name)
static inline bool argToStdout(po::variables_map const& _args, string const& _name)
{
return _args.count(_name) && _args[_name].as<OutputType>() != OutputType::FILE;
}
static bool needStdout(po::variables_map const& _args)
{
return argToStdout(_args, "abi") || argToStdout(_args, "natspec-user") || argToStdout(_args, "natspec-dev") ||
argToStdout(_args, "asm") || argToStdout(_args, "opcodes") || argToStdout(_args, "binary");
return argToStdout(_args, g_argAbiStr) || argToStdout(_args, g_argNatspecUserStr) ||
argToStdout(_args, g_argNatspecDevStr) || argToStdout(_args, g_argAsmStr) ||
argToStdout(_args, g_argOpcodesStr) || argToStdout(_args, g_argBinaryStr);
}
static inline bool outputToFile(OutputType type)
@ -94,7 +105,7 @@ static std::istream& operator>>(std::istream& _in, OutputType& io_output)
void CommandLineInterface::handleBinary(string const& _contract)
{
auto choice = m_args["binary"].as<OutputType>();
auto choice = m_args[g_argBinaryStr].as<OutputType>();
if (outputToStdout(choice))
{
cout << "Binary: " << endl;
@ -111,28 +122,27 @@ void CommandLineInterface::handleBinary(string const& _contract)
void CommandLineInterface::handleOpcode(string const& _contract)
{
// TODO: Figure out why the wrong operator << (from boost) is used here
auto choice = m_args["opcode"].as<OutputType>();
auto choice = m_args[g_argOpcodesStr].as<OutputType>();
if (outputToStdout(choice))
{
cout << "Opcodes: " << endl;
dev::operator<<(cout, m_compiler.getBytecode(_contract));
cout << eth::disassemble(m_compiler.getBytecode(_contract));
cout << endl;
}
if (outputToFile(choice))
{
ofstream outFile(_contract + ".opcode");
dev::operator<<(outFile, m_compiler.getBytecode(_contract));
outFile << eth::disassemble(m_compiler.getBytecode(_contract));
outFile.close();
}
}
void CommandLineInterface::handleBytecode(string const& _contract)
{
if (m_args.count("opcodes"))
if (m_args.count(g_argOpcodesStr))
handleOpcode(_contract);
if (m_args.count("binary"))
if (m_args.count(g_argBinaryStr))
handleBinary(_contract);
}
@ -145,17 +155,17 @@ void CommandLineInterface::handleJson(DocumentationType _type,
switch(_type)
{
case DocumentationType::ABI_INTERFACE:
argName = "abi";
argName = g_argAbiStr;
suffix = ".abi";
title = "Contract ABI";
break;
case DocumentationType::NATSPEC_USER:
argName = "natspec-user";
argName = "g_argNatspecUserStr";
suffix = ".docuser";
title = "User Documentation";
break;
case DocumentationType::NATSPEC_DEV:
argName = "natspec-dev";
argName = g_argNatspecDevStr;
suffix = ".docdev";
title = "Developer Documentation";
break;
@ -195,20 +205,20 @@ bool CommandLineInterface::parseArguments(int argc, char** argv)
("version", "Show version and exit")
("optimize", po::value<bool>()->default_value(false), "Optimize bytecode for size")
("input-file", po::value<vector<string>>(), "input file")
("ast", po::value<OutputType>(),
(g_argAstStr.c_str(), po::value<OutputType>(),
"Request to output the AST of the contract. " OUTPUT_TYPE_STR)
("asm", po::value<OutputType>(),
"Request to output the EVM assembly of the contract. " OUTPUT_TYPE_STR)
("opcodes", po::value<OutputType>(),
"Request to output the Opcodes of the contract. " OUTPUT_TYPE_STR)
("binary", po::value<OutputType>(),
"Request to output the contract in binary (hexadecimal). " OUTPUT_TYPE_STR)
("abi", po::value<OutputType>(),
"Request to output the contract's ABI interface. " OUTPUT_TYPE_STR)
("natspec-user", po::value<OutputType>(),
"Request to output the contract's Natspec user documentation. " OUTPUT_TYPE_STR)
("natspec-dev", po::value<OutputType>(),
"Request to output the contract's Natspec developer documentation. " OUTPUT_TYPE_STR);
(g_argAsmStr.c_str(), po::value<OutputType>(),
"Request to output the EVM assembly of the contract. " OUTPUT_TYPE_STR)
(g_argOpcodesStr.c_str(), po::value<OutputType>(),
"Request to output the Opcodes of the contract. " OUTPUT_TYPE_STR)
(g_argBinaryStr.c_str(), po::value<OutputType>(),
"Request to output the contract in binary (hexadecimal). " OUTPUT_TYPE_STR)
(g_argAbiStr.c_str(), po::value<OutputType>(),
"Request to output the contract's ABI interface. " OUTPUT_TYPE_STR)
(g_argNatspecUserStr.c_str(), po::value<OutputType>(),
"Request to output the contract's Natspec user documentation. " OUTPUT_TYPE_STR)
(g_argNatspecDevStr.c_str(), po::value<OutputType>(),
"Request to output the contract's Natspec developer documentation. " OUTPUT_TYPE_STR);
#undef OUTPUT_TYPE_STR
// All positional options should be interpreted as input files
@ -220,9 +230,9 @@ bool CommandLineInterface::parseArguments(int argc, char** argv)
{
po::store(po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(), m_args);
}
catch (po::error const& exception)
catch (po::error const& _exception)
{
cout << exception.what() << endl;
cout << _exception.what() << endl;
return false;
}
po::notify(m_args);
@ -279,34 +289,35 @@ bool CommandLineInterface::processInput()
// TODO: Perhaps we should not compile unless requested
m_compiler.compile(m_args["optimize"].as<bool>());
}
catch (ParserError const& exception)
catch (ParserError const& _exception)
{
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Parser error", m_compiler);
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Parser error", m_compiler);
return false;
}
catch (DeclarationError const& exception)
catch (DeclarationError const& _exception)
{
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Declaration error", m_compiler);
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Declaration error", m_compiler);
return false;
}
catch (TypeError const& exception)
catch (TypeError const& _exception)
{
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Type error", m_compiler);
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Type error", m_compiler);
return false;
}
catch (CompilerError const& exception)
catch (CompilerError const& _exception)
{
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Compiler error", m_compiler);
SourceReferenceFormatter::printExceptionInformation(cerr, _exception, "Compiler error", m_compiler);
return false;
}
catch (InternalCompilerError const& exception)
catch (InternalCompilerError const& _exception)
{
SourceReferenceFormatter::printExceptionInformation(cerr, exception, "Internal compiler error", m_compiler);
cerr << "Internal compiler error during compilation:" << endl
<< boost::diagnostic_information(_exception);
return false;
}
catch (Exception const& exception)
catch (Exception const& _exception)
{
cerr << "Exception during compilation: " << boost::diagnostic_information(exception) << endl;
cerr << "Exception during compilation: " << boost::diagnostic_information(_exception) << endl;
return false;
}
catch (...)
@ -321,9 +332,9 @@ bool CommandLineInterface::processInput()
void CommandLineInterface::actOnInput()
{
// do we need AST output?
if (m_args.count("ast"))
if (m_args.count(g_argAstStr))
{
auto choice = m_args["ast"].as<OutputType>();
auto choice = m_args[g_argAstStr].as<OutputType>();
if (outputToStdout(choice))
{
cout << "Syntax trees:" << endl << endl;
@ -355,9 +366,9 @@ void CommandLineInterface::actOnInput()
cout << endl << "======= " << contract << " =======" << endl;
// do we need EVM assembly?
if (m_args.count("asm"))
if (m_args.count(g_argAsmStr))
{
auto choice = m_args["asm"].as<OutputType>();
auto choice = m_args[g_argAsmStr].as<OutputType>();
if (outputToStdout(choice))
{
cout << "EVM assembly:" << endl;

1
test/CMakeLists.txt

@ -3,6 +3,7 @@ cmake_policy(SET CMP0015 NEW)
aux_source_directory(. SRC_LIST)
list(REMOVE_ITEM SRC_LIST "./createRandomTest.cpp")
include_directories(${Boost_INCLUDE_DIRS})
include_directories(${CRYPTOPP_INCLUDE_DIRS})
include_directories(${JSONCPP_INCLUDE_DIRS})
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})

0
test/solidityJSONInterfaceTest.cpp → test/SolidityABIJSON.cpp

13
test/solidityCompiler.cpp → test/SolidityCompiler.cpp

@ -54,9 +54,18 @@ bytes compileContract(const string& _sourceCode)
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
}
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract));
}
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
Compiler compiler;
compiler.compileContract(*contract, {});
compiler.compileContract(*contract, {}, map<ContractDefinition const*, bytes const*>{});
// debug
//compiler.streamAssembly(cout);
return compiler.getAssembledBytecode();

119
test/solidityEndToEndTest.cpp → test/SolidityEndToEndTest.cpp

@ -36,7 +36,7 @@ namespace solidity
namespace test
{
BOOST_FIXTURE_TEST_SUITE(SolidityCompilerEndToEndTest, ExecutionFramework)
BOOST_FIXTURE_TEST_SUITE(SolidityEndToEndTest, ExecutionFramework)
BOOST_AUTO_TEST_CASE(smoke_test)
{
@ -176,6 +176,80 @@ BOOST_AUTO_TEST_CASE(nested_loops)
testSolidityAgainstCppOnRange(0, nested_loops_cpp, 0, 12);
}
BOOST_AUTO_TEST_CASE(for_loop)
{
char const* sourceCode = "contract test {\n"
" function f(uint n) returns(uint nfac) {\n"
" nfac = 1;\n"
" for (var i = 2; i <= n; i++)\n"
" nfac *= i;\n"
" }\n"
"}\n";
compileAndRun(sourceCode);
auto for_loop_cpp = [](u256 const& n) -> u256
{
u256 nfac = 1;
for (auto i = 2; i <= n; i++)
nfac *= i;
return nfac;
};
testSolidityAgainstCppOnRange(0, for_loop_cpp, 0, 5);
}
BOOST_AUTO_TEST_CASE(for_loop_empty)
{
char const* sourceCode = "contract test {\n"
" function f() returns(uint ret) {\n"
" ret = 1;\n"
" for (;;)\n"
" {\n"
" ret += 1;\n"
" if (ret >= 10) break;\n"
" }\n"
" }\n"
"}\n";
compileAndRun(sourceCode);
auto for_loop_empty_cpp = []() -> u256
{
u256 ret = 1;
for (;;)
{
ret += 1;
if (ret >= 10) break;
}
return ret;
};
testSolidityAgainstCpp(0, for_loop_empty_cpp);
}
BOOST_AUTO_TEST_CASE(for_loop_simple_init_expr)
{
char const* sourceCode = "contract test {\n"
" function f(uint n) returns(uint nfac) {\n"
" nfac = 1;\n"
" uint256 i;\n"
" for (i = 2; i <= n; i++)\n"
" nfac *= i;\n"
" }\n"
"}\n";
compileAndRun(sourceCode);
auto for_loop_simple_init_expr_cpp = [](u256 const& n) -> u256
{
u256 nfac = 1;
u256 i;
for (i = 2; i <= n; i++)
nfac *= i;
return nfac;
};
testSolidityAgainstCppOnRange(0, for_loop_simple_init_expr_cpp, 0, 5);
}
BOOST_AUTO_TEST_CASE(calling_other_functions)
{
// note that the index of a function is its index in the sorted sequence of functions
@ -1012,6 +1086,49 @@ BOOST_AUTO_TEST_CASE(strings_in_calls)
BOOST_CHECK(callContractFunction(0, bytes({0, 'a', 1})) == bytes({0, 'a', 0, 0, 0}));
}
BOOST_AUTO_TEST_CASE(constructor_arguments)
{
char const* sourceCode = R"(
contract Helper {
string3 name;
bool flag;
function Helper(string3 x, bool f) {
name = x;
flag = f;
}
function getName() returns (string3 ret) { return name; }
function getFlag() returns (bool ret) { return flag; }
}
contract Main {
Helper h;
function Main() {
h = new Helper("abc", true);
}
function getFlag() returns (bool ret) { return h.getFlag(); }
function getName() returns (string3 ret) { return h.getName(); }
})";
compileAndRun(sourceCode, 0, "Main");
BOOST_REQUIRE(callContractFunction(0) == bytes({byte(0x01)}));
BOOST_REQUIRE(callContractFunction(1) == bytes({'a', 'b', 'c'}));
}
BOOST_AUTO_TEST_CASE(functions_called_by_constructor)
{
char const* sourceCode = R"(
contract Test {
string3 name;
bool flag;
function Test() {
setName("abc");
}
function getName() returns (string3 ret) { return name; }
private:
function setName(string3 _name) { name = _name; }
})";
compileAndRun(sourceCode);
BOOST_REQUIRE(callContractFunction(0) == bytes({'a', 'b', 'c'}));
}
BOOST_AUTO_TEST_SUITE_END()
}

9
test/solidityExpressionCompiler.cpp → test/SolidityExpressionCompiler.cpp

@ -97,6 +97,15 @@ bytes compileFirstExpression(const string& _sourceCode, vector<vector<string>> _
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
BOOST_REQUIRE_NO_THROW(resolver.resolveNamesAndTypes(*contract));
}
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
BOOST_REQUIRE_NO_THROW(resolver.checkTypeRequirements(*contract));
}
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
FirstExpressionExtractor extractor(*contract);
BOOST_REQUIRE(extractor.getExpression() != nullptr);

27
test/solidityNameAndTypeResolution.cpp → test/SolidityNameAndTypeResolution.cpp

@ -47,6 +47,9 @@ void parseTextAndResolveNames(std::string const& _source)
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
resolver.resolveNamesAndTypes(*contract);
for (ASTPointer<ASTNode> const& node: sourceUnit->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
resolver.checkTypeRequirements(*contract);
}
}
@ -284,6 +287,30 @@ BOOST_AUTO_TEST_CASE(assignment_to_struct)
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(returns_in_constructor)
{
char const* text = "contract test {\n"
" function test() returns (uint a) {\n"
" }\n"
"}\n";
BOOST_CHECK_THROW(parseTextAndResolveNames(text), TypeError);
}
BOOST_AUTO_TEST_CASE(forward_function_reference)
{
char const* text = "contract First {\n"
" function fun() returns (bool ret) {\n"
" return Second(1).fun(1, true, 3) > 0;\n"
" }\n"
"}\n"
"contract Second {\n"
" function fun(uint a, bool b, uint c) returns (uint ret) {\n"
" if (First(2).fun() == true) return 1;\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text));
}
BOOST_AUTO_TEST_SUITE_END()
}

29
test/solidityNatspecJSON.cpp → test/SolidityNatspecJSON.cpp

@ -394,6 +394,35 @@ BOOST_AUTO_TEST_CASE(dev_multiline_return)
checkNatspec(sourceCode, natspec, false);
}
BOOST_AUTO_TEST_CASE(dev_multiline_comment)
{
char const* sourceCode = "contract test {\n"
" /**\n"
" * @dev Multiplies a number by 7 and adds second parameter\n"
" * @param a Documentation for the first parameter starts here.\n"
" * Since it's a really complicated parameter we need 2 lines\n"
" * @param second Documentation for the second parameter\n"
" * @return The result of the multiplication\n"
" * and cookies with nutella\n"
" */"
" function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
"}\n";
char const* natspec = "{"
"\"methods\":{"
" \"mul\":{ \n"
" \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
" \"params\": {\n"
" \"a\": \"Documentation for the first parameter starts here. Since it's a really complicated parameter we need 2 lines\",\n"
" \"second\": \"Documentation for the second parameter\"\n"
" },\n"
" \"return\": \"The result of the multiplication and cookies with nutella\"\n"
" }\n"
"}}";
checkNatspec(sourceCode, natspec, false);
}
BOOST_AUTO_TEST_CASE(dev_contract_no_doc)
{
char const* sourceCode = "contract test {\n"

2
test/solidityOptimizerTest.cpp → test/SolidityOptimizer.cpp

@ -71,7 +71,7 @@ protected:
Address m_nonOptimizedContract;
};
BOOST_FIXTURE_TEST_SUITE(SolidityOptimizerTest, OptimizerTestFramework)
BOOST_FIXTURE_TEST_SUITE(SolidityOptimizer, OptimizerTestFramework)
BOOST_AUTO_TEST_CASE(smoke_test)
{

78
test/solidityParser.cpp → test/SolidityParser.cpp

@ -49,6 +49,23 @@ ASTPointer<ContractDefinition> parseText(std::string const& _source)
BOOST_FAIL("No contract found in source.");
return ASTPointer<ContractDefinition>();
}
ASTPointer<ContractDefinition> parseTextExplainError(std::string const& _source)
{
try
{
return parseText(_source);
}
catch (Exception const& exception)
{
// LTODO: Print the error in a kind of a better way?
// In absence of CompilerStack we can't use SourceReferenceFormatter
cout << "Exception while parsing: " << diagnostic_information(exception);
// rethrow to signal test failure
throw exception;
}
}
}
@ -111,7 +128,7 @@ BOOST_AUTO_TEST_CASE(function_natspec_documentation)
BOOST_REQUIRE_NO_THROW(contract = parseText(text));
auto functions = contract->getDefinedFunctions();
BOOST_REQUIRE_NO_THROW(function = functions.at(0));
BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is a test function");
BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is a test function");
}
BOOST_AUTO_TEST_CASE(function_normal_comments)
@ -149,17 +166,17 @@ BOOST_AUTO_TEST_CASE(multiple_functions_natspec_documentation)
auto functions = contract->getDefinedFunctions();
BOOST_REQUIRE_NO_THROW(function = functions.at(0));
BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is test function 1");
BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 1");
BOOST_REQUIRE_NO_THROW(function = functions.at(1));
BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is test function 2");
BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 2");
BOOST_REQUIRE_NO_THROW(function = functions.at(2));
BOOST_CHECK_MESSAGE(function->getDocumentation() == nullptr,
"Should not have gotten natspec comment for functionName3()");
BOOST_REQUIRE_NO_THROW(function = functions.at(3));
BOOST_CHECK_EQUAL(*function->getDocumentation(), " This is test function 4");
BOOST_CHECK_EQUAL(*function->getDocumentation(), "This is test function 4");
}
BOOST_AUTO_TEST_CASE(multiline_function_documentation)
@ -177,7 +194,7 @@ BOOST_AUTO_TEST_CASE(multiline_function_documentation)
BOOST_REQUIRE_NO_THROW(function = functions.at(0));
BOOST_CHECK_EQUAL(*function->getDocumentation(),
" This is a test function\n"
"This is a test function\n"
" and it has 2 lines");
}
@ -203,11 +220,11 @@ BOOST_AUTO_TEST_CASE(natspec_comment_in_function_body)
auto functions = contract->getDefinedFunctions();
BOOST_REQUIRE_NO_THROW(function = functions.at(0));
BOOST_CHECK_EQUAL(*function->getDocumentation(), " fun1 description");
BOOST_CHECK_EQUAL(*function->getDocumentation(), "fun1 description");
BOOST_REQUIRE_NO_THROW(function = functions.at(1));
BOOST_CHECK_EQUAL(*function->getDocumentation(),
" This is a test function\n"
"This is a test function\n"
" and it has 2 lines");
}
@ -357,6 +374,53 @@ BOOST_AUTO_TEST_CASE(while_loop)
BOOST_CHECK_NO_THROW(parseText(text));
}
BOOST_AUTO_TEST_CASE(for_loop_vardef_initexpr)
{
char const* text = "contract test {\n"
" function fun(uint256 a) {\n"
" for (uint256 i = 0; i < 10; i++)\n"
" { uint256 x = i; break; continue; }\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextExplainError(text));
}
BOOST_AUTO_TEST_CASE(for_loop_simple_initexpr)
{
char const* text = "contract test {\n"
" function fun(uint256 a) {\n"
" uint256 i =0;\n"
" for (i = 0; i < 10; i++)\n"
" { uint256 x = i; break; continue; }\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextExplainError(text));
}
BOOST_AUTO_TEST_CASE(for_loop_simple_noexpr)
{
char const* text = "contract test {\n"
" function fun(uint256 a) {\n"
" uint256 i =0;\n"
" for (;;)\n"
" { uint256 x = i; break; continue; }\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextExplainError(text));
}
BOOST_AUTO_TEST_CASE(for_loop_single_stmt_body)
{
char const* text = "contract test {\n"
" function fun(uint256 a) {\n"
" uint256 i =0;\n"
" for (i = 0; i < 10; i++)\n"
" continue;\n"
" }\n"
"}\n";
BOOST_CHECK_NO_THROW(parseTextExplainError(text));
}
BOOST_AUTO_TEST_CASE(if_statement)
{
char const* text = "contract test {\n"

54
test/solidityScanner.cpp → test/SolidityScanner.cpp

@ -157,7 +157,14 @@ BOOST_AUTO_TEST_CASE(documentation_comments_parsed_begin)
{
Scanner scanner(CharStream("/// Send $(value / 1000) chocolates to the user"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user");
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed_begin)
{
Scanner scanner(CharStream("/** Send $(value / 1000) chocolates to the user*/"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(documentation_comments_parsed)
@ -167,7 +174,43 @@ BOOST_AUTO_TEST_CASE(documentation_comments_parsed)
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), " Send $(value / 1000) chocolates to the user");
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(multiline_documentation_comments_parsed)
{
Scanner scanner(CharStream("some other tokens /**\n"
"* Send $(value / 1000) chocolates to the user\n"
"*/"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(multiline_documentation_no_stars)
{
Scanner scanner(CharStream("some other tokens /**\n"
" Send $(value / 1000) chocolates to the user\n"
"*/"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(multiline_documentation_whitespace_hell)
{
Scanner scanner(CharStream("some other tokens /** \t \r \n"
"\t \r * Send $(value / 1000) chocolates to the user\n"
"*/"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::IDENTIFIER);
BOOST_CHECK_EQUAL(scanner.next(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "Send $(value / 1000) chocolates to the user");
}
BOOST_AUTO_TEST_CASE(comment_before_eos)
@ -184,6 +227,13 @@ BOOST_AUTO_TEST_CASE(documentation_comment_before_eos)
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
}
BOOST_AUTO_TEST_CASE(empty_multiline_documentation_comment_before_eos)
{
Scanner scanner(CharStream("/***/"));
BOOST_CHECK_EQUAL(scanner.getCurrentToken(), Token::EOS);
BOOST_CHECK_EQUAL(scanner.getCurrentCommentLiteral(), "");
}
BOOST_AUTO_TEST_CASE(comments_mixed_in_sequence)
{
Scanner scanner(CharStream("hello_world ///documentation comment \n"

145
test/stRefundTestFiller.json

@ -0,0 +1,145 @@
{
"refund_getEtherBack" : {
"env" : {
"previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6",
"currentNumber" : "0",
"currentGasLimit" : "1000000",
"currentDifficulty" : "256",
"currentTimestamp" : 1,
"currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"
},
"pre" : {
"095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
"balance" : "1000000000000000000",
"nonce" : 0,
"code" : "{ [[ 1 ]] 0 }",
"storage" : {
"0x01" : "0x01"
}
},
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "1000",
"nonce" : 0,
"code" : "",
"storage": {}
}
},
"transaction" : {
"nonce" : "0",
"gasPrice" : "1",
"gasLimit" : "850",
"to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87",
"value" : "10",
"secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"data" : ""
}
},
"refund_changeNonZeroStorage" : {
"env" : {
"previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6",
"currentNumber" : "0",
"currentGasLimit" : "1000000",
"currentDifficulty" : "256",
"currentTimestamp" : 1,
"currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"
},
"pre" : {
"095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
"balance" : "1000000000000000000",
"nonce" : 0,
"code" : "{ [[ 1 ]] 23 }",
"storage" : {
"0x01" : "0x01"
}
},
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "1000",
"nonce" : 0,
"code" : "",
"storage": {}
}
},
"transaction" : {
"nonce" : "0",
"gasPrice" : "1",
"gasLimit" : "850",
"to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87",
"value" : "10",
"secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"data" : ""
}
},
"refund_OOG" : {
"env" : {
"previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6",
"currentNumber" : "0",
"currentGasLimit" : "1000000",
"currentDifficulty" : "256",
"currentTimestamp" : 1,
"currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"
},
"pre" : {
"095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
"balance" : "1000000000000000000",
"nonce" : 0,
"code" : "{ [[ 1 ]] 0 }",
"storage" : {
"0x01" : "0x01"
}
},
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "500",
"nonce" : 0,
"code" : "",
"storage": {}
}
},
"transaction" : {
"nonce" : "0",
"gasPrice" : "1",
"gasLimit" : "500",
"to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87",
"value" : "0",
"secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"data" : ""
}
},
"refund_NoOOG_1" : {
"env" : {
"previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6",
"currentNumber" : "0",
"currentGasLimit" : "1000000",
"currentDifficulty" : "256",
"currentTimestamp" : 1,
"currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba"
},
"pre" : {
"095e7baea6a6c7c4c2dfeb977efac326af552d87" : {
"balance" : "1000000000000000000",
"nonce" : 0,
"code" : "{ [[ 1 ]] 0 }",
"storage" : {
"0x01" : "0x01"
}
},
"a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : {
"balance" : "502",
"nonce" : 0,
"code" : "",
"storage": {}
}
},
"transaction" : {
"nonce" : "0",
"gasPrice" : "1",
"gasLimit" : "502",
"to" : "095e7baea6a6c7c4c2dfeb977efac326af552d87",
"value" : "0",
"secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"data" : ""
}
}
}

10
test/state.cpp

@ -140,11 +140,21 @@ BOOST_AUTO_TEST_CASE(stInitCodeTest)
dev::test::executeTests("stInitCodeTest", "/StateTests", dev::test::doStateTests);
}
BOOST_AUTO_TEST_CASE(stTransactionTest)
{
dev::test::executeTests("stTransactionTest", "/StateTests", dev::test::doStateTests);
}
BOOST_AUTO_TEST_CASE(stSpecialTest)
{
dev::test::executeTests("stSpecialTest", "/StateTests", dev::test::doStateTests);
}
BOOST_AUTO_TEST_CASE(stRefundTest)
{
dev::test::executeTests("stRefundTest", "/StateTests", dev::test::doStateTests);
}
BOOST_AUTO_TEST_CASE(stCreateTest)
{
for (int i = 1; i < boost::unit_test::framework::master_test_suite().argc; ++i)

40
test/vm.cpp

@ -262,12 +262,44 @@ eth::OnOpFunc FakeExtVM::simpleTrace()
dev::LogOutputStream<eth::VMTraceChannel, false>(true) << o.str();
dev::LogOutputStream<eth::VMTraceChannel, false>(false) << " | " << std::dec << ext.depth << " | " << ext.myAddress << " | #" << steps << " | " << std::hex << std::setw(4) << std::setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << std::dec << vm.gas() << " | -" << std::dec << gasCost << " | " << newMemSize << "x32" << " ]";
/*creates json stack trace*/
if (eth::VMTraceChannel::verbosity <= g_logVerbosity)
{
std::ofstream f;
f.open("./vmtrace.log", std::ofstream::app);
f << o.str();
f << " | " << std::dec << ext.depth << " | " << ext.myAddress << " | #" << steps << " | " << std::hex << std::setw(4) << std::setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << std::dec << vm.gas() << " | -" << std::dec << gasCost << " | " << newMemSize << "x32";
Object o_step;
/*add the stack*/
Array a_stack;
for (auto i: vm.stack())
a_stack.push_back((string)i);
o_step.push_back(Pair( "stack", a_stack ));
/*add the memory*/
Array a_mem;
for(auto i: vm.memory())
a_mem.push_back(i);
o_step.push_back(Pair("memory", a_mem));
/*add the storage*/
Object storage;
for (auto const& i: std::get<2>(ext.addresses.find(ext.myAddress)->second))
storage.push_back(Pair( (string)i.first , (string)i.second));
/*add all the other details*/
o_step.push_back(Pair("storage", storage));
o_step.push_back(Pair("depth", to_string(ext.depth)));
o_step.push_back(Pair("gas", (string)vm.gas()));
o_step.push_back(Pair("address", "0x" + toString(ext.myAddress )));
o_step.push_back(Pair("step", steps ));
o_step.push_back(Pair("pc", (int)vm.curPC()));
o_step.push_back(Pair("opcode", instructionInfo(inst).name ));
/*append the JSON object to the log file*/
Value v(o_step);
ofstream os( "./stackTrace.json", ofstream::app);
os << write_string(v, true) << ",";
os.close();
}
};
}

2
test/vm.h

@ -26,7 +26,7 @@ along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
#include <fstream>
#include <cstdint>
#include <boost/test/unit_test.hpp>
#include "JsonSpiritHeaders.h"
#include <json_spirit/json_spirit.h>
#include <libdevcore/Log.h>
#include <libdevcore/CommonIO.h>
#include <libevmcore/Instruction.h>

Loading…
Cancel
Save