aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchriseth <chris@ethereum.org>2017-02-01 01:29:51 +0800
committerGitHub <noreply@github.com>2017-02-01 01:29:51 +0800
commit364da425d3116a4b85863df39a1864340861d71e (patch)
tree521a52c3f8182cd6a054f148bd649c8b6b0e2af9
parent60cc1668517f56ce6ca8225555472e7a27eab8b0 (diff)
parent7b18c9df1dfa0076566bfa1e4a3bc5e5ba9c8594 (diff)
downloaddexon-solidity-364da425d3116a4b85863df39a1864340861d71e.tar.gz
dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.tar.zst
dexon-solidity-364da425d3116a4b85863df39a1864340861d71e.zip
Merge pull request #1622 from ethereum/develop
Solidity version 0.4.9
-rw-r--r--CMakeLists.txt2
-rw-r--r--Changelog.md21
-rw-r--r--cmake/scripts/buildinfo.cmake2
-rw-r--r--cmake/templates/BuildInfo.h.in2
-rw-r--r--docs/conf.py6
-rw-r--r--docs/control-structures.rst33
-rw-r--r--docs/grammar.txt2
-rw-r--r--docs/installing-solidity.rst23
-rw-r--r--docs/types.rst13
-rw-r--r--docs/utils/SolidityLexer.py3
-rw-r--r--libdevcore/CommonData.cpp40
-rw-r--r--libdevcore/CommonData.h5
-rw-r--r--libevmasm/Assembly.cpp96
-rw-r--r--libevmasm/Assembly.h1
-rw-r--r--libevmasm/AssemblyItem.cpp12
-rw-r--r--libevmasm/AssemblyItem.h4
-rw-r--r--libevmasm/GasMeter.cpp16
-rw-r--r--libevmasm/Instruction.cpp264
-rw-r--r--libevmasm/Instruction.h24
-rw-r--r--libevmasm/LinkerObject.cpp29
-rw-r--r--libevmasm/LinkerObject.h6
-rw-r--r--libevmasm/PeepholeOptimiser.cpp1
-rw-r--r--libevmasm/SemanticInformation.cpp1
-rw-r--r--libsolidity/analysis/DeclarationContainer.cpp22
-rw-r--r--libsolidity/analysis/NameAndTypeResolver.cpp4
-rw-r--r--libsolidity/analysis/ReferencesResolver.cpp1
-rw-r--r--libsolidity/analysis/TypeChecker.cpp17
-rw-r--r--libsolidity/ast/AST.cpp44
-rw-r--r--libsolidity/ast/AST.h19
-rw-r--r--libsolidity/ast/ASTAnnotations.h2
-rw-r--r--libsolidity/ast/ASTJsonConverter.cpp4
-rw-r--r--libsolidity/ast/Types.cpp264
-rw-r--r--libsolidity/ast/Types.h50
-rw-r--r--libsolidity/codegen/ArrayUtils.cpp880
-rw-r--r--libsolidity/codegen/ArrayUtils.h5
-rw-r--r--libsolidity/codegen/CompilerContext.cpp79
-rw-r--r--libsolidity/codegen/CompilerContext.h47
-rw-r--r--libsolidity/codegen/CompilerUtils.cpp75
-rw-r--r--libsolidity/codegen/ContractCompiler.cpp20
-rw-r--r--libsolidity/codegen/ExpressionCompiler.cpp56
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp16
-rw-r--r--libsolidity/inlineasm/AsmParser.h1
-rw-r--r--libsolidity/interface/CompilerStack.cpp118
-rw-r--r--libsolidity/interface/CompilerStack.h9
-rw-r--r--libsolidity/interface/InterfaceHandler.cpp48
-rw-r--r--libsolidity/interface/InterfaceHandler.h10
-rw-r--r--libsolidity/interface/Version.cpp4
-rw-r--r--libsolidity/interface/Version.h1
-rw-r--r--libsolidity/parsing/DocStringParser.cpp70
-rwxr-xr-xscripts/install_deps.sh2
-rwxr-xr-xscripts/tests.sh18
-rw-r--r--solc/CommandLineInterface.cpp69
-rw-r--r--solc/CommandLineInterface.h8
-rw-r--r--std/StandardToken.sol44
-rwxr-xr-xtest/cmdlineTests.sh51
-rw-r--r--test/libdevcore/Checksum.cpp83
-rw-r--r--test/liblll/EndToEndTest.cpp7
-rw-r--r--test/libsolidity/Assembly.cpp4
-rw-r--r--test/libsolidity/Imports.cpp1
-rw-r--r--test/libsolidity/InlineAssembly.cpp23
-rw-r--r--test/libsolidity/SolidityEndToEndTest.cpp175
-rw-r--r--test/libsolidity/SolidityExpressionCompiler.cpp10
-rw-r--r--test/libsolidity/SolidityNameAndTypeResolution.cpp155
-rw-r--r--test/libsolidity/SolidityNatspecJSON.cpp71
-rw-r--r--test/libsolidity/SolidityParser.cpp1
-rw-r--r--test/libsolidity/SolidityTypes.cpp67
66 files changed, 2362 insertions, 899 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ecd857ad..68bb71f4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -8,7 +8,7 @@ include(EthPolicy)
eth_policy()
# project name and version should be set after cmake_policy CMP0048
-set(PROJECT_VERSION "0.4.8")
+set(PROJECT_VERSION "0.4.9")
project(solidity VERSION ${PROJECT_VERSION})
# Let's find our dependencies
diff --git a/Changelog.md b/Changelog.md
index b8effc19..03ee2193 100644
--- a/Changelog.md
+++ b/Changelog.md
@@ -1,3 +1,24 @@
+### 0.4.9 (2017-01-31)
+
+Features:
+ * Compiler interface: Contracts and libraries can be referenced with a ``file:`` prefix to make them unique.
+ * Compiler interface: Report source location for "stack too deep" errors.
+ * AST: Use deterministic node identifiers.
+ * Inline assembly: introduce ``invalid`` (EIP141) as an opcode.
+ * Type system: Introduce type identifier strings.
+ * Type checker: Warn about invalid checksum for addresses and deduce type from valid ones.
+ * Metadata: Do not include platform in the version number.
+ * Metadata: Add option to store sources as literal content.
+ * Code generator: Extract array utils into low-level functions.
+ * Code generator: Internal errors (array out of bounds, etc.) now cause a reversion by using an invalid
+ instruction (0xfe - EIP141) instead of an invalid jump. Invalid jump is still kept for explicit throws.
+
+Bugfixes:
+ * Code generator: Allow recursive structs.
+ * Inline assembly: Disallow variables named like opcodes.
+ * Type checker: Allow multiple events of the same name (but with different arities or argument types)
+ * Natspec parser: Fix error with ``@param`` parsing and whitespace.
+
### 0.4.8 (2017-01-13)
Features:
diff --git a/cmake/scripts/buildinfo.cmake b/cmake/scripts/buildinfo.cmake
index 8e1615f6..efbfb8fb 100644
--- a/cmake/scripts/buildinfo.cmake
+++ b/cmake/scripts/buildinfo.cmake
@@ -60,6 +60,8 @@ if (SOL_COMMIT_HASH AND SOL_LOCAL_CHANGES)
set(SOL_COMMIT_HASH "${SOL_COMMIT_HASH}.mod")
endif()
+set(SOL_VERSION_COMMIT "commit.${SOL_COMMIT_HASH}")
+set(SOl_VERSION_PLATFORM ETH_BUILD_PLATFORM)
set(SOL_VERSION_BUILDINFO "commit.${SOL_COMMIT_HASH}.${ETH_BUILD_PLATFORM}")
set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp")
diff --git a/cmake/templates/BuildInfo.h.in b/cmake/templates/BuildInfo.h.in
index 6c16e4ac..4b35df98 100644
--- a/cmake/templates/BuildInfo.h.in
+++ b/cmake/templates/BuildInfo.h.in
@@ -8,3 +8,5 @@
#define ETH_BUILD_PLATFORM "@ETH_BUILD_PLATFORM@"
#define SOL_VERSION_PRERELEASE "@SOL_VERSION_PRERELEASE@"
#define SOL_VERSION_BUILDINFO "@SOL_VERSION_BUILDINFO@"
+#define SOL_VERSION_COMMIT "@SOL_VERSION_COMMIT@"
+#define SOL_VERSION_PLATFORM "@SOL_VERSION_PLATFORM@"
diff --git a/docs/conf.py b/docs/conf.py
index ecabbb86..e97eff3a 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -49,16 +49,16 @@ master_doc = 'index'
# General information about the project.
project = 'Solidity'
-copyright = '2016, Ethereum'
+copyright = '2016-2017, Ethereum'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
-version = '0.4.8'
+version = '0.4.9'
# The full version, including alpha/beta/rc tags.
-release = '0.4.8-develop'
+release = '0.4.9-develop'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
diff --git a/docs/control-structures.rst b/docs/control-structures.rst
index 6c0b0f27..c83d654e 100644
--- a/docs/control-structures.rst
+++ b/docs/control-structures.rst
@@ -104,7 +104,7 @@ contract can be called internally.
External Function Calls
-----------------------
-The expressions ``this.g(8);`` and ``c.g(2);`` (where ``g`` is a contract
+The expressions ``this.g(8);`` and ``c.g(2);`` (where ``c`` is a contract
instance) are also valid function calls, but this time, the function
will be called "externally", via a message call and not directly via jumps.
Please note that function calls on ``this`` cannot be used in the constructor, as the
@@ -384,18 +384,23 @@ In the following example, we show how ``throw`` can be used to easily revert an
Currently, Solidity automatically generates a runtime exception in the following situations:
-1. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
-1. If you access a fixed-length ``bytesN`` at a too large or negative index.
-1. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
-1. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
-1. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
-1. If you shift by a negative amount.
-1. If you convert a value too big or negative into an enum type.
-1. If you perform an external function call targeting a contract that contains no code.
-1. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
-1. If your contract receives Ether via a public accessor function.
-
-Internally, Solidity performs an "invalid jump" when an exception is thrown and thus causes the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction (or at least call) without effect.
+#. If you access an array at a too large or negative index (i.e. ``x[i]`` where ``i >= x.length`` or ``i < 0``).
+#. If you access a fixed-length ``bytesN`` at a too large or negative index.
+#. If you call a function via a message call but it does not finish properly (i.e. it runs out of gas, has no matching function, or throws an exception itself), except when a low level operation ``call``, ``send``, ``delegatecall`` or ``callcode`` is used. The low level operations never throw exceptions but indicate failures by returning ``false``.
+#. If you create a contract using the ``new`` keyword but the contract creation does not finish properly (see above for the definition of "not finish properly").
+#. If you divide or modulo by zero (e.g. ``5 / 0`` or ``23 % 0``).
+#. If you shift by a negative amount.
+#. If you convert a value too big or negative into an enum type.
+#. If you perform an external function call targeting a contract that contains no code.
+#. If your contract receives Ether via a public function without ``payable`` modifier (including the constructor and the fallback function).
+#. If your contract receives Ether via a public accessor function.
+#. If you call a zero-initialized variable of internal function type.
+
+Internally, Solidity performs an "invalid jump" when a user-provided exception is thrown. In contrast, it performs an invalid operation
+(instruction ``0xfe``) if a runtime exception is encountered. In both cases, this causes
+the EVM to revert all changes made to the state. The reason for this is that there is no safe way to continue execution, because an expected effect
+did not occur. Because we want to retain the atomicity of transactions, the safest thing to do is to revert all changes and make the whole transaction
+(or at least call) without effect.
.. index:: ! assembly, ! asm, ! evmasm
@@ -627,6 +632,8 @@ The opcodes ``pushi`` and ``jumpdest`` cannot be used directly.
+-------------------------+------+-----------------------------------------------------------------+
| selfdestruct(a) | `-` | end execution, destroy current contract and send funds to a |
+-------------------------+------+-----------------------------------------------------------------+
+| invalid | `-` | end execution with invalid instruction |
++-------------------------+------+-----------------------------------------------------------------+
| log0(p, s) | `-` | log without topics and data mem[p..(p+s)) |
+-------------------------+------+-----------------------------------------------------------------+
| log1(p, s, t1) | `-` | log with topic t1 and data mem[p..(p+s)) |
diff --git a/docs/grammar.txt b/docs/grammar.txt
index 62b4a021..a9f328c0 100644
--- a/docs/grammar.txt
+++ b/docs/grammar.txt
@@ -114,7 +114,7 @@ NumberUnit = 'wei' | 'szabo' | 'finney' | 'ether'
| 'seconds' | 'minutes' | 'hours' | 'days' | 'weeks' | 'years'
HexLiteral = 'hex' ('"' ([0-9a-fA-F]{2})* '"' | '\'' ([0-9a-fA-F]{2})* '\'')
StringLiteral = '"' ([^"\r\n\\] | '\\' .)* '"'
-Identifier = [a-zA-Z_] [a-zA-Z_0-9]*
+Identifier = [a-zA-Z_$] [a-zA-Z_$0-9]*
HexNumber = '0x' [0-9a-fA-F]+
DecimalNumber = [0-9]+
diff --git a/docs/installing-solidity.rst b/docs/installing-solidity.rst
index ef38705c..345780d7 100644
--- a/docs/installing-solidity.rst
+++ b/docs/installing-solidity.rst
@@ -119,6 +119,11 @@ you should fork Solidity and add your personal fork as a second remote:
cd solidity
git remote add personal git@github.com:[username]/solidity.git
+Solidity has git submodules. Ensure they are properly loaded:
+
+.. code:: bash
+
+ git submodule update --init --recursive
Prerequisites - macOS
---------------------
@@ -211,6 +216,24 @@ Alternatively, you can build for Windows on the command-line, like so:
cmake --build . --config RelWithDebInfo
+The version string in detail
+============================
+
+The Solidity version string contains four parts:
+- the version number
+- pre-release tag, usually set to ``develop.YYYY.MM.DD`` or ``nightly.YYYY.MM.DD``
+- commit in the format of ``commit.GITHASH``
+- platform has arbitrary number of items, containing details about the platform and compiler
+
+If there are local modifications, the commit will be postfixed with ``.mod``.
+
+These parts are combined as required by Semver, where the Solidity pre-release tag equals to the Semver pre-release
+and the Solidity commit and platform combined make up the Semver build metadata.
+
+A relase example: ``0.4.8+commit.60cc1668.Emscripten.clang``.
+
+A pre-release example: ``0.4.9-nightly.2017.1.17+commit.6ecb4aa3.Emscripten.clang``
+
Important information about versioning
======================================
diff --git a/docs/types.rst b/docs/types.rst
index 6b67e684..3fb1db95 100644
--- a/docs/types.rst
+++ b/docs/types.rst
@@ -171,6 +171,19 @@ Fixed Point Numbers
**COMING SOON...**
+.. index:: address, literal;address
+
+.. _address_literals:
+
+Address Literals
+----------------
+
+Hexadecimal literals that pass the address checksum test, for example
+``0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF`` are of ``address`` type.
+Hexadecimal literals that are between 39 and 41 digits
+long and do not pass the checksum test produce
+a warning and are treated as regular rational number literals.
+
.. index:: literal, literal;rational
.. _rational_literals:
diff --git a/docs/utils/SolidityLexer.py b/docs/utils/SolidityLexer.py
index 779147f4..a7da59aa 100644
--- a/docs/utils/SolidityLexer.py
+++ b/docs/utils/SolidityLexer.py
@@ -57,7 +57,7 @@ class SolidityLexer(RegexLexer):
(r'(for|in|while|do|break|return|continue|switch|case|default|if|else|'
r'throw|try|catch|finally|new|delete|typeof|instanceof|void|'
r'this|import|mapping|returns|private|public|external|internal|'
- r'constant|memory|storage)\b', Keyword, 'slashstartsregex'),
+ r'constant|memory|storage|payable)\b', Keyword, 'slashstartsregex'),
(r'(var|let|with|function|event|modifier|struct|enum|contract|library)\b', Keyword.Declaration, 'slashstartsregex'),
(r'(bytes|string|address|uint|int|bool|byte|' +
'|'.join(
@@ -67,6 +67,7 @@ class SolidityLexer(RegexLexer):
['ufixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)] +
['fixed%dx%d' % ((i), (j + 8)) for i in range(0, 256, 8) for j in range(0, 256 - i, 8)]
) + r')\b', Keyword.Type, 'slashstartsregex'),
+ (r'(wei|szabo|finney|ether|seconds|minutes|hours|days|weeks|years)\b', Keyword.Type, 'slashstartsregex'),
(r'(abstract|boolean|byte|char|class|const|debugger|double|enum|export|'
r'extends|final|float|goto|implements|int|interface|long|native|'
r'package|private|protected|public|short|static|super|synchronized|throws|'
diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp
index 062d1b29..a53d9628 100644
--- a/libdevcore/CommonData.cpp
+++ b/libdevcore/CommonData.cpp
@@ -19,8 +19,12 @@
* @date 2014
*/
-#include "CommonData.h"
-#include "Exceptions.h"
+#include <libdevcore/CommonData.h>
+#include <libdevcore/Exceptions.h>
+#include <libdevcore/SHA3.h>
+
+#include <boost/algorithm/string.hpp>
+
using namespace std;
using namespace dev;
@@ -95,3 +99,35 @@ bytes dev::fromHex(std::string const& _s, WhenError _throw)
}
return ret;
}
+
+
+bool dev::passesAddressChecksum(string const& _str, bool _strict)
+{
+ string s = _str.substr(0, 2) == "0x" ? _str.substr(2) : _str;
+
+ if (s.length() != 40)
+ return false;
+
+ if (!_strict && (
+ _str.find_first_of("abcdef") == string::npos ||
+ _str.find_first_of("ABCDEF") == string::npos
+ ))
+ return true;
+
+ h256 hash = keccak256(boost::algorithm::to_lower_copy(s, std::locale::classic()));
+ for (size_t i = 0; i < 40; ++i)
+ {
+ char addressCharacter = s[i];
+ bool lowerCase;
+ if ('a' <= addressCharacter && addressCharacter <= 'f')
+ lowerCase = true;
+ else if ('A' <= addressCharacter && addressCharacter <= 'F')
+ lowerCase = false;
+ else
+ continue;
+ unsigned nibble = (unsigned(hash[i / 2]) >> (4 * (1 - (i % 2)))) & 0xf;
+ if ((nibble >= 8) == lowerCase)
+ return false;
+ }
+ return true;
+}
diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h
index 5ffcdcca..e0a6d221 100644
--- a/libdevcore/CommonData.h
+++ b/libdevcore/CommonData.h
@@ -179,4 +179,9 @@ bool contains(T const& _t, V const& _v)
return std::end(_t) != std::find(std::begin(_t), std::end(_t), _v);
}
+/// @returns true iff @a _str passess the hex address checksum test.
+/// @param _strict if false, hex strings with only uppercase or only lowercase letters
+/// are considered valid.
+bool passesAddressChecksum(std::string const& _str, bool _strict);
+
}
diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp
index 845abfd4..b7859c1f 100644
--- a/libevmasm/Assembly.cpp
+++ b/libevmasm/Assembly.cpp
@@ -40,7 +40,7 @@ void Assembly::append(Assembly const& _a)
auto newDeposit = m_deposit + _a.deposit();
for (AssemblyItem i: _a.m_items)
{
- if (i.type() == Tag || i.type() == PushTag)
+ if (i.type() == Tag || (i.type() == PushTag && i != errorTag()))
i.setData(i.data() + m_usedTags);
else if (i.type() == PushSub || i.type() == PushSubSize)
i.setData(i.data() + m_subs.size());
@@ -94,7 +94,10 @@ unsigned Assembly::bytesRequired(unsigned subTagSize) const
}
}
-string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const
+namespace
+{
+
+string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location)
{
if (_location.isEmpty() || _sourceCodes.empty() || _location.start >= _location.end || _location.start < 0)
return "";
@@ -115,27 +118,92 @@ string Assembly::locationFromSources(StringMap const& _sourceCodes, SourceLocati
return cut;
}
-ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
+class Functionalizer
{
- for (size_t i = 0; i < m_items.size(); ++i)
+public:
+ Functionalizer (ostream& _out, string const& _prefix, StringMap const& _sourceCodes):
+ m_out(_out), m_prefix(_prefix), m_sourceCodes(_sourceCodes)
+ {}
+
+ void feed(AssemblyItem const& _item)
{
- AssemblyItem const& item = m_items[i];
- if (!item.location().isEmpty() && (i == 0 || m_items[i - 1].location() != item.location()))
+ if (!_item.location().isEmpty() && _item.location() != m_location)
{
- _out << _prefix << " /*";
- if (item.location().sourceName)
- _out << " \"" + *item.location().sourceName + "\"";
- if (!item.location().isEmpty())
- _out << ":" << to_string(item.location().start) + ":" + to_string(item.location().end);
- _out << " */" << endl;
+ flush();
+ printLocation();
+ m_location = _item.location();
}
- _out << _prefix << (item.type() == Tag ? "" : " ") << item.toAssemblyText() << endl;
+ if (!(
+ _item.canBeFunctional() &&
+ _item.returnValues() <= 1 &&
+ _item.arguments() <= int(m_pending.size())
+ ))
+ {
+ flush();
+ m_out << m_prefix << (_item.type() == Tag ? "" : " ") << _item.toAssemblyText() << endl;
+ return;
+ }
+ string expression = _item.toAssemblyText();
+ if (_item.arguments() > 0)
+ {
+ expression += "(";
+ for (int i = 0; i < _item.arguments(); ++i)
+ {
+ expression += m_pending.back();
+ m_pending.pop_back();
+ if (i + 1 < _item.arguments())
+ expression += ", ";
+ }
+ expression += ")";
+ }
+
+ m_pending.push_back(expression);
+ if (_item.returnValues() != 1)
+ flush();
+ }
+
+ void flush()
+ {
+ for (string const& expression: m_pending)
+ m_out << m_prefix << " " << expression << endl;
+ m_pending.clear();
+ }
+
+ void printLocation()
+ {
+ if (!m_location.sourceName && m_location.isEmpty())
+ return;
+ m_out << m_prefix << " /*";
+ if (m_location.sourceName)
+ m_out << " \"" + *m_location.sourceName + "\"";
+ if (!m_location.isEmpty())
+ m_out << ":" << to_string(m_location.start) + ":" + to_string(m_location.end);
+ m_out << " " << locationFromSources(m_sourceCodes, m_location);
+ m_out << " */" << endl;
}
+private:
+ strings m_pending;
+ SourceLocation m_location;
+
+ ostream& m_out;
+ string const& m_prefix;
+ StringMap const& m_sourceCodes;
+};
+
+}
+
+ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const
+{
+ Functionalizer f(_out, _prefix, _sourceCodes);
+
+ for (auto const& i: m_items)
+ f.feed(i);
+ f.flush();
+
if (!m_data.empty() || !m_subs.empty())
{
_out << _prefix << "stop" << endl;
- Json::Value data;
for (auto const& i: m_data)
assertThrow(u256(i.first) < m_subs.size(), AssemblyException, "Data not yet implemented.");
diff --git a/libevmasm/Assembly.h b/libevmasm/Assembly.h
index 9e7f9f7b..528c9e74 100644
--- a/libevmasm/Assembly.h
+++ b/libevmasm/Assembly.h
@@ -118,7 +118,6 @@ protected:
/// returns the replaced tags.
std::map<u256, u256> optimiseInternal(bool _enable, bool _isCreation, size_t _runs);
- std::string locationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const;
void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) BOOST_THROW_EXCEPTION(InvalidDeposit()); }
unsigned bytesRequired(unsigned subTagSize) const;
diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp
index 6c7d5425..26d9fded 100644
--- a/libevmasm/AssemblyItem.cpp
+++ b/libevmasm/AssemblyItem.cpp
@@ -76,12 +76,20 @@ unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const
BOOST_THROW_EXCEPTION(InvalidOpcode());
}
-int AssemblyItem::deposit() const
+int AssemblyItem::arguments() const
+{
+ if (type() == Operation)
+ return instructionInfo(instruction()).args;
+ else
+ return 0;
+}
+
+int AssemblyItem::returnValues() const
{
switch (m_type)
{
case Operation:
- return instructionInfo(instruction()).ret - instructionInfo(instruction()).args;
+ return instructionInfo(instruction()).ret;
case Push:
case PushString:
case PushTag:
diff --git a/libevmasm/AssemblyItem.h b/libevmasm/AssemblyItem.h
index 002b3c87..464368fb 100644
--- a/libevmasm/AssemblyItem.h
+++ b/libevmasm/AssemblyItem.h
@@ -116,7 +116,9 @@ public:
/// @returns an upper bound for the number of bytes required by this item, assuming that
/// the value of a jump tag takes @a _addressLength bytes.
unsigned bytesRequired(unsigned _addressLength) const;
- int deposit() const;
+ int arguments() const;
+ int returnValues() const;
+ int deposit() const { return returnValues() - arguments(); }
/// @returns true if the assembly item can be used in a functional context.
bool canBeFunctional() const;
diff --git a/libevmasm/GasMeter.cpp b/libevmasm/GasMeter.cpp
index 21db3565..462c09dd 100644
--- a/libevmasm/GasMeter.cpp
+++ b/libevmasm/GasMeter.cpp
@@ -223,14 +223,14 @@ unsigned GasMeter::runGas(Instruction _instruction)
switch (instructionInfo(_instruction).gasPriceTier)
{
- case 0: return GasCosts::tier0Gas;
- case 1: return GasCosts::tier1Gas;
- case 2: return GasCosts::tier2Gas;
- case 3: return GasCosts::tier3Gas;
- case 4: return GasCosts::tier4Gas;
- case 5: return GasCosts::tier5Gas;
- case 6: return GasCosts::tier6Gas;
- case 7: return GasCosts::tier7Gas;
+ case Tier::Zero: return GasCosts::tier0Gas;
+ case Tier::Base: return GasCosts::tier1Gas;
+ case Tier::VeryLow: return GasCosts::tier2Gas;
+ case Tier::Low: return GasCosts::tier3Gas;
+ case Tier::Mid: return GasCosts::tier4Gas;
+ case Tier::High: return GasCosts::tier5Gas;
+ case Tier::Ext: return GasCosts::tier6Gas;
+ case Tier::Special: return GasCosts::tier7Gas;
default: break;
}
assertThrow(false, OptimizerException, "Invalid gas tier.");
diff --git a/libevmasm/Instruction.cpp b/libevmasm/Instruction.cpp
index 5244a91f..b0f063da 100644
--- a/libevmasm/Instruction.cpp
+++ b/libevmasm/Instruction.cpp
@@ -159,141 +159,143 @@ const std::map<std::string, Instruction> dev::solidity::c_instructions =
{ "CALLCODE", Instruction::CALLCODE },
{ "RETURN", Instruction::RETURN },
{ "DELEGATECALL", Instruction::DELEGATECALL },
+ { "INVALID", Instruction::INVALID },
{ "SUICIDE", Instruction::SUICIDE }
};
static const std::map<Instruction, InstructionInfo> c_instructionInfo =
{ // Add, Args, Ret, SideEffects, GasPriceTier
- { Instruction::STOP, { "STOP", 0, 0, 0, true, ZeroTier } },
- { Instruction::ADD, { "ADD", 0, 2, 1, false, VeryLowTier } },
- { Instruction::SUB, { "SUB", 0, 2, 1, false, VeryLowTier } },
- { Instruction::MUL, { "MUL", 0, 2, 1, false, LowTier } },
- { Instruction::DIV, { "DIV", 0, 2, 1, false, LowTier } },
- { Instruction::SDIV, { "SDIV", 0, 2, 1, false, LowTier } },
- { Instruction::MOD, { "MOD", 0, 2, 1, false, LowTier } },
- { Instruction::SMOD, { "SMOD", 0, 2, 1, false, LowTier } },
- { Instruction::EXP, { "EXP", 0, 2, 1, false, SpecialTier } },
- { Instruction::NOT, { "NOT", 0, 1, 1, false, VeryLowTier } },
- { Instruction::LT, { "LT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::GT, { "GT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::SLT, { "SLT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::SGT, { "SGT", 0, 2, 1, false, VeryLowTier } },
- { Instruction::EQ, { "EQ", 0, 2, 1, false, VeryLowTier } },
- { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, VeryLowTier } },
- { Instruction::AND, { "AND", 0, 2, 1, false, VeryLowTier } },
- { Instruction::OR, { "OR", 0, 2, 1, false, VeryLowTier } },
- { Instruction::XOR, { "XOR", 0, 2, 1, false, VeryLowTier } },
- { Instruction::BYTE, { "BYTE", 0, 2, 1, false, VeryLowTier } },
- { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, MidTier } },
- { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, MidTier } },
- { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, LowTier } },
- { Instruction::SHA3, { "SHA3", 0, 2, 1, false, SpecialTier } },
- { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, BaseTier } },
- { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, ExtTier } },
- { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLER, { "CALLER", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, VeryLowTier } },
- { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, BaseTier } },
- { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, VeryLowTier } },
- { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, BaseTier } },
- { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, VeryLowTier } },
- { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, BaseTier } },
- { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, ExtTier } },
- { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, ExtTier } },
- { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, ExtTier } },
- { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, BaseTier } },
- { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, BaseTier } },
- { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, BaseTier } },
- { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, BaseTier } },
- { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, BaseTier } },
- { Instruction::POP, { "POP", 0, 1, 0, false, BaseTier } },
- { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, VeryLowTier } },
- { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, VeryLowTier } },
- { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, VeryLowTier } },
- { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, SpecialTier } },
- { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, SpecialTier } },
- { Instruction::JUMP, { "JUMP", 0, 1, 0, true, MidTier } },
- { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, HighTier } },
- { Instruction::PC, { "PC", 0, 0, 1, false, BaseTier } },
- { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, BaseTier } },
- { Instruction::GAS, { "GAS", 0, 0, 1, false, BaseTier } },
- { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, SpecialTier } },
- { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, VeryLowTier } },
- { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, VeryLowTier } },
- { Instruction::DUP1, { "DUP1", 0, 1, 2, false, VeryLowTier } },
- { Instruction::DUP2, { "DUP2", 0, 2, 3, false, VeryLowTier } },
- { Instruction::DUP3, { "DUP3", 0, 3, 4, false, VeryLowTier } },
- { Instruction::DUP4, { "DUP4", 0, 4, 5, false, VeryLowTier } },
- { Instruction::DUP5, { "DUP5", 0, 5, 6, false, VeryLowTier } },
- { Instruction::DUP6, { "DUP6", 0, 6, 7, false, VeryLowTier } },
- { Instruction::DUP7, { "DUP7", 0, 7, 8, false, VeryLowTier } },
- { Instruction::DUP8, { "DUP8", 0, 8, 9, false, VeryLowTier } },
- { Instruction::DUP9, { "DUP9", 0, 9, 10, false, VeryLowTier } },
- { Instruction::DUP10, { "DUP10", 0, 10, 11, false, VeryLowTier } },
- { Instruction::DUP11, { "DUP11", 0, 11, 12, false, VeryLowTier } },
- { Instruction::DUP12, { "DUP12", 0, 12, 13, false, VeryLowTier } },
- { Instruction::DUP13, { "DUP13", 0, 13, 14, false, VeryLowTier } },
- { Instruction::DUP14, { "DUP14", 0, 14, 15, false, VeryLowTier } },
- { Instruction::DUP15, { "DUP15", 0, 15, 16, false, VeryLowTier } },
- { Instruction::DUP16, { "DUP16", 0, 16, 17, false, VeryLowTier } },
- { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, VeryLowTier } },
- { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, VeryLowTier } },
- { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, VeryLowTier } },
- { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, VeryLowTier } },
- { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, VeryLowTier } },
- { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, VeryLowTier } },
- { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, VeryLowTier } },
- { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, VeryLowTier } },
- { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, VeryLowTier } },
- { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, VeryLowTier } },
- { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, VeryLowTier } },
- { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, VeryLowTier } },
- { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, VeryLowTier } },
- { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, VeryLowTier } },
- { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, VeryLowTier } },
- { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, VeryLowTier } },
- { Instruction::LOG0, { "LOG0", 0, 2, 0, true, SpecialTier } },
- { Instruction::LOG1, { "LOG1", 0, 3, 0, true, SpecialTier } },
- { Instruction::LOG2, { "LOG2", 0, 4, 0, true, SpecialTier } },
- { Instruction::LOG3, { "LOG3", 0, 5, 0, true, SpecialTier } },
- { Instruction::LOG4, { "LOG4", 0, 6, 0, true, SpecialTier } },
- { Instruction::CREATE, { "CREATE", 0, 3, 1, true, SpecialTier } },
- { Instruction::CALL, { "CALL", 0, 7, 1, true, SpecialTier } },
- { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, SpecialTier } },
- { Instruction::RETURN, { "RETURN", 0, 2, 0, true, ZeroTier } },
- { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, SpecialTier } },
- { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, ZeroTier } }
+ { Instruction::STOP, { "STOP", 0, 0, 0, true, Tier::Zero } },
+ { Instruction::ADD, { "ADD", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::SUB, { "SUB", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::MUL, { "MUL", 0, 2, 1, false, Tier::Low } },
+ { Instruction::DIV, { "DIV", 0, 2, 1, false, Tier::Low } },
+ { Instruction::SDIV, { "SDIV", 0, 2, 1, false, Tier::Low } },
+ { Instruction::MOD, { "MOD", 0, 2, 1, false, Tier::Low } },
+ { Instruction::SMOD, { "SMOD", 0, 2, 1, false, Tier::Low } },
+ { Instruction::EXP, { "EXP", 0, 2, 1, false, Tier::Special } },
+ { Instruction::NOT, { "NOT", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::LT, { "LT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::GT, { "GT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::SLT, { "SLT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::SGT, { "SGT", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::EQ, { "EQ", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::ISZERO, { "ISZERO", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::AND, { "AND", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::OR, { "OR", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::XOR, { "XOR", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::BYTE, { "BYTE", 0, 2, 1, false, Tier::VeryLow } },
+ { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1, false, Tier::Mid } },
+ { Instruction::MULMOD, { "MULMOD", 0, 3, 1, false, Tier::Mid } },
+ { Instruction::SIGNEXTEND, { "SIGNEXTEND", 0, 2, 1, false, Tier::Low } },
+ { Instruction::SHA3, { "SHA3", 0, 2, 1, false, Tier::Special } },
+ { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1, false, Tier::Base } },
+ { Instruction::BALANCE, { "BALANCE", 0, 1, 1, false, Tier::Ext } },
+ { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLER, { "CALLER", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0, true, Tier::VeryLow } },
+ { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0, true, Tier::VeryLow } },
+ { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1, false, Tier::Ext } },
+ { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0, true, Tier::Ext } },
+ { Instruction::BLOCKHASH, { "BLOCKHASH", 0, 1, 1, false, Tier::Ext } },
+ { Instruction::COINBASE, { "COINBASE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1, false, Tier::Base } },
+ { Instruction::NUMBER, { "NUMBER", 0, 0, 1, false, Tier::Base } },
+ { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1, false, Tier::Base } },
+ { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1, false, Tier::Base } },
+ { Instruction::POP, { "POP", 0, 1, 0, false, Tier::Base } },
+ { Instruction::MLOAD, { "MLOAD", 0, 1, 1, false, Tier::VeryLow } },
+ { Instruction::MSTORE, { "MSTORE", 0, 2, 0, true, Tier::VeryLow } },
+ { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0, true, Tier::VeryLow } },
+ { Instruction::SLOAD, { "SLOAD", 0, 1, 1, false, Tier::Special } },
+ { Instruction::SSTORE, { "SSTORE", 0, 2, 0, true, Tier::Special } },
+ { Instruction::JUMP, { "JUMP", 0, 1, 0, true, Tier::Mid } },
+ { Instruction::JUMPI, { "JUMPI", 0, 2, 0, true, Tier::High } },
+ { Instruction::PC, { "PC", 0, 0, 1, false, Tier::Base } },
+ { Instruction::MSIZE, { "MSIZE", 0, 0, 1, false, Tier::Base } },
+ { Instruction::GAS, { "GAS", 0, 0, 1, false, Tier::Base } },
+ { Instruction::JUMPDEST, { "JUMPDEST", 0, 0, 0, true, Tier::Special } },
+ { Instruction::PUSH1, { "PUSH1", 1, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH2, { "PUSH2", 2, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH3, { "PUSH3", 3, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH4, { "PUSH4", 4, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH5, { "PUSH5", 5, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH6, { "PUSH6", 6, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH7, { "PUSH7", 7, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH8, { "PUSH8", 8, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH9, { "PUSH9", 9, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH10, { "PUSH10", 10, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH11, { "PUSH11", 11, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH12, { "PUSH12", 12, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH13, { "PUSH13", 13, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH14, { "PUSH14", 14, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH15, { "PUSH15", 15, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH16, { "PUSH16", 16, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH17, { "PUSH17", 17, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH18, { "PUSH18", 18, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH19, { "PUSH19", 19, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH20, { "PUSH20", 20, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH21, { "PUSH21", 21, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH22, { "PUSH22", 22, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH23, { "PUSH23", 23, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH24, { "PUSH24", 24, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH25, { "PUSH25", 25, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH26, { "PUSH26", 26, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH27, { "PUSH27", 27, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH28, { "PUSH28", 28, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH29, { "PUSH29", 29, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH30, { "PUSH30", 30, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH31, { "PUSH31", 31, 0, 1, false, Tier::VeryLow } },
+ { Instruction::PUSH32, { "PUSH32", 32, 0, 1, false, Tier::VeryLow } },
+ { Instruction::DUP1, { "DUP1", 0, 1, 2, false, Tier::VeryLow } },
+ { Instruction::DUP2, { "DUP2", 0, 2, 3, false, Tier::VeryLow } },
+ { Instruction::DUP3, { "DUP3", 0, 3, 4, false, Tier::VeryLow } },
+ { Instruction::DUP4, { "DUP4", 0, 4, 5, false, Tier::VeryLow } },
+ { Instruction::DUP5, { "DUP5", 0, 5, 6, false, Tier::VeryLow } },
+ { Instruction::DUP6, { "DUP6", 0, 6, 7, false, Tier::VeryLow } },
+ { Instruction::DUP7, { "DUP7", 0, 7, 8, false, Tier::VeryLow } },
+ { Instruction::DUP8, { "DUP8", 0, 8, 9, false, Tier::VeryLow } },
+ { Instruction::DUP9, { "DUP9", 0, 9, 10, false, Tier::VeryLow } },
+ { Instruction::DUP10, { "DUP10", 0, 10, 11, false, Tier::VeryLow } },
+ { Instruction::DUP11, { "DUP11", 0, 11, 12, false, Tier::VeryLow } },
+ { Instruction::DUP12, { "DUP12", 0, 12, 13, false, Tier::VeryLow } },
+ { Instruction::DUP13, { "DUP13", 0, 13, 14, false, Tier::VeryLow } },
+ { Instruction::DUP14, { "DUP14", 0, 14, 15, false, Tier::VeryLow } },
+ { Instruction::DUP15, { "DUP15", 0, 15, 16, false, Tier::VeryLow } },
+ { Instruction::DUP16, { "DUP16", 0, 16, 17, false, Tier::VeryLow } },
+ { Instruction::SWAP1, { "SWAP1", 0, 2, 2, false, Tier::VeryLow } },
+ { Instruction::SWAP2, { "SWAP2", 0, 3, 3, false, Tier::VeryLow } },
+ { Instruction::SWAP3, { "SWAP3", 0, 4, 4, false, Tier::VeryLow } },
+ { Instruction::SWAP4, { "SWAP4", 0, 5, 5, false, Tier::VeryLow } },
+ { Instruction::SWAP5, { "SWAP5", 0, 6, 6, false, Tier::VeryLow } },
+ { Instruction::SWAP6, { "SWAP6", 0, 7, 7, false, Tier::VeryLow } },
+ { Instruction::SWAP7, { "SWAP7", 0, 8, 8, false, Tier::VeryLow } },
+ { Instruction::SWAP8, { "SWAP8", 0, 9, 9, false, Tier::VeryLow } },
+ { Instruction::SWAP9, { "SWAP9", 0, 10, 10, false, Tier::VeryLow } },
+ { Instruction::SWAP10, { "SWAP10", 0, 11, 11, false, Tier::VeryLow } },
+ { Instruction::SWAP11, { "SWAP11", 0, 12, 12, false, Tier::VeryLow } },
+ { Instruction::SWAP12, { "SWAP12", 0, 13, 13, false, Tier::VeryLow } },
+ { Instruction::SWAP13, { "SWAP13", 0, 14, 14, false, Tier::VeryLow } },
+ { Instruction::SWAP14, { "SWAP14", 0, 15, 15, false, Tier::VeryLow } },
+ { Instruction::SWAP15, { "SWAP15", 0, 16, 16, false, Tier::VeryLow } },
+ { Instruction::SWAP16, { "SWAP16", 0, 17, 17, false, Tier::VeryLow } },
+ { Instruction::LOG0, { "LOG0", 0, 2, 0, true, Tier::Special } },
+ { Instruction::LOG1, { "LOG1", 0, 3, 0, true, Tier::Special } },
+ { Instruction::LOG2, { "LOG2", 0, 4, 0, true, Tier::Special } },
+ { Instruction::LOG3, { "LOG3", 0, 5, 0, true, Tier::Special } },
+ { Instruction::LOG4, { "LOG4", 0, 6, 0, true, Tier::Special } },
+ { Instruction::CREATE, { "CREATE", 0, 3, 1, true, Tier::Special } },
+ { Instruction::CALL, { "CALL", 0, 7, 1, true, Tier::Special } },
+ { Instruction::CALLCODE, { "CALLCODE", 0, 7, 1, true, Tier::Special } },
+ { Instruction::RETURN, { "RETURN", 0, 2, 0, true, Tier::Zero } },
+ { Instruction::DELEGATECALL,{ "DELEGATECALL", 0, 6, 1, true, Tier::Special } },
+ { Instruction::INVALID, { "INVALID", 0, 0, 0, true, Tier::Zero } },
+ { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0, true, Tier::Zero } }
};
void dev::solidity::eachInstruction(
@@ -343,7 +345,7 @@ InstructionInfo dev::solidity::instructionInfo(Instruction _inst)
}
catch (...)
{
- return InstructionInfo({"<INVALID_INSTRUCTION: " + toString((unsigned)_inst) + ">", 0, 0, 0, false, InvalidTier});
+ return InstructionInfo({"<INVALID_INSTRUCTION: " + toString((unsigned)_inst) + ">", 0, 0, 0, false, Tier::Invalid});
}
}
diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h
index c7fad1c5..a8a72234 100644
--- a/libevmasm/Instruction.h
+++ b/libevmasm/Instruction.h
@@ -176,6 +176,8 @@ enum class Instruction: uint8_t
CALLCODE, ///< message-call with another account's code only
RETURN, ///< halt execution returning output data
DELEGATECALL, ///< like CALLCODE but keeps caller's value and sender
+
+ INVALID = 0xfe, ///< invalid instruction for expressing runtime errors (e.g., division-by-zero)
SUICIDE = 0xff ///< halt execution and register account for later deletion
};
@@ -225,17 +227,17 @@ inline Instruction logInstruction(unsigned _number)
return Instruction(unsigned(Instruction::LOG0) + _number);
}
-enum Tier
+enum class Tier : unsigned
{
- ZeroTier = 0, // 0, Zero
- BaseTier, // 2, Quick
- VeryLowTier, // 3, Fastest
- LowTier, // 5, Fast
- MidTier, // 8, Mid
- HighTier, // 10, Slow
- ExtTier, // 20, Ext
- SpecialTier, // multiparam or otherwise special
- InvalidTier // Invalid.
+ Zero = 0, // 0, Zero
+ Base, // 2, Quick
+ VeryLow, // 3, Fastest
+ Low, // 5, Fast
+ Mid, // 8, Mid
+ High, // 10, Slow
+ Ext, // 20, Ext
+ Special, // multiparam or otherwise special
+ Invalid // Invalid.
};
/// Information structure for a particular instruction.
@@ -246,7 +248,7 @@ struct InstructionInfo
int args; ///< Number of items required on the stack for this instruction (and, for the purposes of ret, the number taken from the stack).
int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed.
bool sideEffects; ///< false if the only effect on the execution environment (apart from gas usage) is a change to a topmost segment of the stack
- int gasPriceTier; ///< Tier for gas pricing.
+ Tier gasPriceTier; ///< Tier for gas pricing.
};
/// Information on all the instructions.
diff --git a/libevmasm/LinkerObject.cpp b/libevmasm/LinkerObject.cpp
index 93e4067c..06607089 100644
--- a/libevmasm/LinkerObject.cpp
+++ b/libevmasm/LinkerObject.cpp
@@ -37,13 +37,10 @@ void LinkerObject::link(map<string, h160> const& _libraryAddresses)
{
std::map<size_t, std::string> remainingRefs;
for (auto const& linkRef: linkReferences)
- {
- auto it = _libraryAddresses.find(linkRef.second);
- if (it == _libraryAddresses.end())
- remainingRefs.insert(linkRef);
+ if (h160 const* address = matchLibrary(linkRef.second, _libraryAddresses))
+ address->ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
else
- it->second.ref().copyTo(ref(bytecode).cropped(linkRef.first, 20));
- }
+ remainingRefs.insert(linkRef);
linkReferences.swap(remainingRefs);
}
@@ -60,3 +57,23 @@ string LinkerObject::toHex() const
}
return hex;
}
+
+h160 const*
+LinkerObject::matchLibrary(
+ string const& _linkRefName,
+ map<string, h160> const& _libraryAddresses
+)
+{
+ auto it = _libraryAddresses.find(_linkRefName);
+ if (it != _libraryAddresses.end())
+ return &it->second;
+ // If the user did not supply a fully qualified library name,
+ // try to match only the simple libary name
+ size_t colon = _linkRefName.find(':');
+ if (colon == string::npos)
+ return nullptr;
+ it = _libraryAddresses.find(_linkRefName.substr(colon + 1));
+ if (it != _libraryAddresses.end())
+ return &it->second;
+ return nullptr;
+}
diff --git a/libevmasm/LinkerObject.h b/libevmasm/LinkerObject.h
index d3ec3e97..152487b4 100644
--- a/libevmasm/LinkerObject.h
+++ b/libevmasm/LinkerObject.h
@@ -49,6 +49,12 @@ struct LinkerObject
/// @returns a hex representation of the bytecode of the given object, replacing unlinked
/// addresses by placeholders.
std::string toHex() const;
+
+private:
+ static h160 const* matchLibrary(
+ std::string const& _linkRefName,
+ std::map<std::string, h160> const& _libraryAddresses
+ );
};
}
diff --git a/libevmasm/PeepholeOptimiser.cpp b/libevmasm/PeepholeOptimiser.cpp
index 923ffa67..528ce1c4 100644
--- a/libevmasm/PeepholeOptimiser.cpp
+++ b/libevmasm/PeepholeOptimiser.cpp
@@ -199,6 +199,7 @@ struct UnreachableCode
it[0] != Instruction::JUMP &&
it[0] != Instruction::RETURN &&
it[0] != Instruction::STOP &&
+ it[0] != Instruction::INVALID &&
it[0] != Instruction::SUICIDE
)
return false;
diff --git a/libevmasm/SemanticInformation.cpp b/libevmasm/SemanticInformation.cpp
index 23a00d95..d3ce4735 100644
--- a/libevmasm/SemanticInformation.cpp
+++ b/libevmasm/SemanticInformation.cpp
@@ -118,6 +118,7 @@ bool SemanticInformation::altersControlFlow(AssemblyItem const& _item)
case Instruction::RETURN:
case Instruction::SUICIDE:
case Instruction::STOP:
+ case Instruction::INVALID:
return true;
default:
return false;
diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp
index f8c12c5b..b33c8568 100644
--- a/libsolidity/analysis/DeclarationContainer.cpp
+++ b/libsolidity/analysis/DeclarationContainer.cpp
@@ -42,20 +42,32 @@ Declaration const* DeclarationContainer::conflictingDeclaration(
if (m_invisibleDeclarations.count(*_name))
declarations += m_invisibleDeclarations.at(*_name);
- if (dynamic_cast<FunctionDefinition const*>(&_declaration))
+ if (
+ dynamic_cast<FunctionDefinition const*>(&_declaration) ||
+ dynamic_cast<EventDefinition const*>(&_declaration)
+ )
{
- // check that all other declarations with the same name are functions or a public state variable
+ // check that all other declarations with the same name are functions or a public state variable or events.
+ // And then check that the signatures are different.
for (Declaration const* declaration: declarations)
{
- if (dynamic_cast<FunctionDefinition const*>(declaration))
- continue;
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(declaration))
{
if (variableDeclaration->isStateVariable() && !variableDeclaration->isConstant() && variableDeclaration->isPublic())
continue;
return declaration;
}
- return declaration;
+ if (
+ dynamic_cast<FunctionDefinition const*>(&_declaration) &&
+ !dynamic_cast<FunctionDefinition const*>(declaration)
+ )
+ return declaration;
+ if (
+ dynamic_cast<EventDefinition const*>(&_declaration) &&
+ !dynamic_cast<EventDefinition const*>(declaration)
+ )
+ return declaration;
+ // Or, continue.
}
}
else if (declarations.size() == 1 && declarations.front() == &_declaration)
diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp
index 08323243..b0a82715 100644
--- a/libsolidity/analysis/NameAndTypeResolver.cpp
+++ b/libsolidity/analysis/NameAndTypeResolver.cpp
@@ -260,8 +260,8 @@ vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations(
for (auto it = _declarations.begin(); it != _declarations.end(); ++it)
{
solAssert(*it, "");
- // the declaration is functionDefinition or a VariableDeclaration while declarations > 1
- solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it),
+ // the declaration is functionDefinition, eventDefinition or a VariableDeclaration while declarations > 1
+ solAssert(dynamic_cast<FunctionDefinition const*>(*it) || dynamic_cast<EventDefinition const*>(*it) || dynamic_cast<VariableDeclaration const*>(*it),
"Found overloading involving something not a function or a variable");
shared_ptr<FunctionType const> functionType { (*it)->functionType(false) };
diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp
index 66bf1d0e..df579c3d 100644
--- a/libsolidity/analysis/ReferencesResolver.cpp
+++ b/libsolidity/analysis/ReferencesResolver.cpp
@@ -87,7 +87,6 @@ void ReferencesResolver::endVisit(FunctionTypeName const& _typeName)
{
switch (_typeName.visibility())
{
- case VariableDeclaration::Visibility::Default:
case VariableDeclaration::Visibility::Internal:
case VariableDeclaration::Visibility::External:
break;
diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp
index 67c8ac17..533c787b 100644
--- a/libsolidity/analysis/TypeChecker.cpp
+++ b/libsolidity/analysis/TypeChecker.cpp
@@ -75,7 +75,10 @@ bool TypeChecker::visit(ContractDefinition const& _contract)
checkContractAbstractConstructors(_contract);
FunctionDefinition const* function = _contract.constructor();
- if (function) {
+ if (function)
+ {
+ if (!function->isPublic())
+ _contract.annotation().hasPublicConstructor = false;
if (!function->returnParameters().empty())
typeError(function->returnParameterList()->location(), "Non-empty \"returns\" directive for constructor.");
if (function->isDeclaredConst())
@@ -1280,6 +1283,8 @@ void TypeChecker::endVisit(NewExpression const& _newExpression)
fatalTypeError(_newExpression.location(), "Identifier is not a contract.");
if (!contract->annotation().isFullyImplemented)
typeError(_newExpression.location(), "Trying to create an instance of an abstract contract.");
+ if (!contract->annotation().hasPublicConstructor)
+ typeError(_newExpression.location(), "Contract with internal constructor cannot be created directly.");
solAssert(!!m_scope, "");
m_scope->annotation().contractDependencies.insert(contract);
@@ -1560,6 +1565,16 @@ void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr)
void TypeChecker::endVisit(Literal const& _literal)
{
+ if (_literal.looksLikeAddress())
+ {
+ if (_literal.passesAddressChecksum())
+ {
+ _literal.annotation().type = make_shared<IntegerType>(0, IntegerType::Modifier::Address);
+ return;
+ }
+ else
+ warning(_literal.location(), "This looks like an address but has an invalid checksum.");
+ }
_literal.annotation().type = Type::forLiteral(_literal);
if (!_literal.annotation().type)
fatalTypeError(_literal.location(), "Invalid literal value.");
diff --git a/libsolidity/ast/AST.cpp b/libsolidity/ast/AST.cpp
index 6f7a64dc..616de54e 100644
--- a/libsolidity/ast/AST.cpp
+++ b/libsolidity/ast/AST.cpp
@@ -20,8 +20,6 @@
* Solidity abstract syntax tree.
*/
-#include <algorithm>
-#include <functional>
#include <libsolidity/interface/Utils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
@@ -30,11 +28,31 @@
#include <libdevcore/SHA3.h>
+#include <boost/algorithm/string.hpp>
+
+#include <algorithm>
+#include <functional>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
+class IDDispenser
+{
+public:
+ static size_t next() { return ++instance(); }
+ static void reset() { instance() = 0; }
+private:
+ static size_t& instance()
+ {
+ static IDDispenser dispenser;
+ return dispenser.id;
+ }
+ size_t id = 0;
+};
+
ASTNode::ASTNode(SourceLocation const& _location):
+ m_id(IDDispenser::next()),
m_location(_location)
{
}
@@ -44,6 +62,11 @@ ASTNode::~ASTNode()
delete m_annotation;
}
+void ASTNode::resetID()
+{
+ IDDispenser::reset();
+}
+
ASTAnnotation& ASTNode::annotation() const
{
if (!m_annotation)
@@ -189,7 +212,6 @@ void ContractDefinition::setUserDocumentation(Json::Value const& _userDocumentat
m_userDocumentation = _userDocumentation;
}
-
vector<Declaration const*> const& ContractDefinition::inheritableMembers() const
{
if (!m_inheritableMembers)
@@ -503,3 +525,19 @@ IdentifierAnnotation& Identifier::annotation() const
m_annotation = new IdentifierAnnotation();
return static_cast<IdentifierAnnotation&>(*m_annotation);
}
+
+bool Literal::looksLikeAddress() const
+{
+ if (subDenomination() != SubDenomination::None)
+ return false;
+
+ string lit = value();
+ return lit.substr(0, 2) == "0x" && abs(int(lit.length()) - 42) <= 1;
+}
+
+bool Literal::passesAddressChecksum() const
+{
+ string lit = value();
+ solAssert(lit.substr(0, 2) == "0x", "Expected hex prefix");
+ return dev::passesAddressChecksum(lit, true);
+}
diff --git a/libsolidity/ast/AST.h b/libsolidity/ast/AST.h
index 2d092408..743fdaa1 100644
--- a/libsolidity/ast/AST.h
+++ b/libsolidity/ast/AST.h
@@ -57,6 +57,11 @@ public:
explicit ASTNode(SourceLocation const& _location);
virtual ~ASTNode();
+ /// @returns an identifier of this AST node that is unique for a single compilation run.
+ size_t id() const { return m_id; }
+ /// Resets the global ID counter. This invalidates all previous IDs.
+ static void resetID();
+
virtual void accept(ASTVisitor& _visitor) = 0;
virtual void accept(ASTConstVisitor& _visitor) const = 0;
template <class T>
@@ -94,6 +99,7 @@ public:
///@}
protected:
+ size_t const m_id = 0;
/// Annotation - is specialised in derived classes, is created upon request (because of polymorphism).
mutable ASTAnnotation* m_annotation = nullptr;
@@ -161,6 +167,7 @@ public:
/// @returns the source name this declaration is present in.
/// Can be combined with annotation().canonicalName to form a globally unique name.
std::string sourceUnitName() const;
+ std::string fullyQualifiedName() const { return sourceUnitName() + ":" + name(); }
virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; }
@@ -601,7 +608,7 @@ private:
/**
* Declaration of a variable. This can be used in various places, e.g. in function parameter
- * lists, struct definitions and even function bodys.
+ * lists, struct definitions and even function bodies.
*/
class VariableDeclaration: public Declaration
{
@@ -862,7 +869,10 @@ public:
std::vector<ASTPointer<VariableDeclaration>> const& parameterTypes() const { return m_parameterTypes->parameters(); }
std::vector<ASTPointer<VariableDeclaration>> const& returnParameterTypes() const { return m_returnTypes->parameters(); }
- Declaration::Visibility visibility() const { return m_visibility; }
+ Declaration::Visibility visibility() const
+ {
+ return m_visibility == Declaration::Visibility::Default ? Declaration::Visibility::Internal : m_visibility;
+ }
bool isDeclaredConst() const { return m_isDeclaredConst; }
bool isPayable() const { return m_isPayable; }
@@ -1574,6 +1584,11 @@ public:
SubDenomination subDenomination() const { return m_subDenomination; }
+ /// @returns true if this looks like a checksummed address.
+ bool looksLikeAddress() const;
+ /// @returns true if it passes the address checksum test.
+ bool passesAddressChecksum() const;
+
private:
Token::Value m_token;
ASTPointer<ASTString> m_value;
diff --git a/libsolidity/ast/ASTAnnotations.h b/libsolidity/ast/ASTAnnotations.h
index 9c4c3ae8..61e97a55 100644
--- a/libsolidity/ast/ASTAnnotations.h
+++ b/libsolidity/ast/ASTAnnotations.h
@@ -80,6 +80,8 @@ struct ContractDefinitionAnnotation: TypeDeclarationAnnotation, DocumentedAnnota
{
/// Whether all functions are implemented.
bool isFullyImplemented = true;
+ /// Whether a public constructor (even the default one) is available.
+ bool hasPublicConstructor = true;
/// List of all (direct and indirect) base contracts in order from derived to
/// base, including the contract itself.
std::vector<ContractDefinition const*> linearizedBaseContracts;
diff --git a/libsolidity/ast/ASTJsonConverter.cpp b/libsolidity/ast/ASTJsonConverter.cpp
index abaad0fd..69c10c8d 100644
--- a/libsolidity/ast/ASTJsonConverter.cpp
+++ b/libsolidity/ast/ASTJsonConverter.cpp
@@ -42,7 +42,7 @@ void ASTJsonConverter::addJsonNode(
{
Json::Value node;
- node["id"] = reinterpret_cast<Json::UInt64>(&_node);
+ node["id"] = Json::UInt64(_node.id());
node["src"] = sourceLocationToString(_node.location());
node["name"] = _nodeName;
if (_attributes.size() != 0)
@@ -124,7 +124,7 @@ bool ASTJsonConverter::visit(ContractDefinition const& _node)
{
Json::Value linearizedBaseContracts(Json::arrayValue);
for (auto const& baseContract: _node.annotation().linearizedBaseContracts)
- linearizedBaseContracts.append(reinterpret_cast<Json::UInt64>(baseContract));
+ linearizedBaseContracts.append(Json::UInt64(baseContract->id()));
addJsonNode(_node, "ContractDefinition", {
make_pair("name", _node.name()),
make_pair("isLibrary", _node.isLibrary()),
diff --git a/libsolidity/ast/Types.cpp b/libsolidity/ast/Types.cpp
index 03ff8471..dbabc8db 100644
--- a/libsolidity/ast/Types.cpp
+++ b/libsolidity/ast/Types.cpp
@@ -21,15 +21,22 @@
*/
#include <libsolidity/ast/Types.h>
-#include <limits>
-#include <boost/range/adaptor/reversed.hpp>
-#include <boost/range/adaptor/sliced.hpp>
+
+#include <libsolidity/interface/Utils.h>
+#include <libsolidity/ast/AST.h>
+
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/SHA3.h>
#include <libdevcore/UTF8.h>
-#include <libsolidity/interface/Utils.h>
-#include <libsolidity/ast/AST.h>
+
+#include <boost/algorithm/string/join.hpp>
+#include <boost/algorithm/string/replace.hpp>
+#include <boost/range/adaptor/reversed.hpp>
+#include <boost/range/adaptor/sliced.hpp>
+#include <boost/range/adaptor/transformed.hpp>
+
+#include <limits>
using namespace std;
using namespace dev;
@@ -117,6 +124,51 @@ u256 const& MemberList::storageSize() const
return m_storageOffsets->storageSize();
}
+/// Helper functions for type identifier
+namespace
+{
+
+string parenthesizeIdentifier(string const& _internal)
+{
+ return "$_" + _internal + "_$";
+}
+
+template <class Range>
+string identifierList(Range const&& _list)
+{
+ return parenthesizeIdentifier(boost::algorithm::join(_list, "_$_"));
+}
+
+string identifier(TypePointer const& _type)
+{
+ return _type ? _type->identifier() : "";
+}
+
+string identifierList(vector<TypePointer> const& _list)
+{
+ return identifierList(_list | boost::adaptors::transformed(identifier));
+}
+
+string identifierList(TypePointer const& _type)
+{
+ return parenthesizeIdentifier(identifier(_type));
+}
+
+string identifierList(TypePointer const& _type1, TypePointer const& _type2)
+{
+ TypePointers list;
+ list.push_back(_type1);
+ list.push_back(_type2);
+ return identifierList(list);
+}
+
+string parenthesizeUserIdentifier(string const& _internal)
+{
+ return parenthesizeIdentifier(boost::algorithm::replace_all_copy(_internal, "$", "$$$"));
+}
+
+}
+
TypePointer Type::fromElementaryTypeName(ElementaryTypeNameToken const& _type)
{
solAssert(Token::isElementaryTypeName(_type.token()),
@@ -272,7 +324,15 @@ IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
solAssert(
m_bits > 0 && m_bits <= 256 && m_bits % 8 == 0,
"Invalid bit number for integer type: " + dev::toString(_bits)
- );
+ );
+}
+
+string IntegerType::identifier() const
+{
+ if (isAddress())
+ return "t_address";
+ else
+ return "t_" + string(isSigned() ? "" : "u") + "int" + std::to_string(numBits());
}
bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -345,6 +405,14 @@ string IntegerType::toString(bool) const
return prefix + dev::toString(m_bits);
}
+u256 IntegerType::literalValue(Literal const* _literal) const
+{
+ solAssert(m_modifier == Modifier::Address, "");
+ solAssert(_literal, "");
+ solAssert(_literal->value().substr(0, 2) == "0x", "");
+ return u256(_literal->value());
+}
+
TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
{
if (
@@ -412,7 +480,12 @@ FixedPointType::FixedPointType(int _integerBits, int _fractionalBits, FixedPoint
m_fractionalBits % 8 == 0,
"Invalid bit number(s) for fixed type: " +
dev::toString(_integerBits) + "x" + dev::toString(_fractionalBits)
- );
+ );
+}
+
+string FixedPointType::identifier() const
+{
+ return "t_" + string(isSigned() ? "" : "u") + "fixed" + std::to_string(integerBits()) + "x" + std::to_string(fractionalBits());
}
bool FixedPointType::isImplicitlyConvertibleTo(Type const& _convertTo) const
@@ -770,6 +843,11 @@ TypePointer RationalNumberType::binaryOperatorResult(Token::Value _operator, Typ
}
}
+string RationalNumberType::identifier() const
+{
+ return "t_rational_" + m_value.numerator().str() + "_by_" + m_value.denominator().str();
+}
+
bool RationalNumberType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -909,6 +987,13 @@ bool StringLiteralType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false;
}
+string StringLiteralType::identifier() const
+{
+ // Since we have to return a valid identifier and the string itself may contain
+ // anything, we hash it.
+ return "t_stringliteral_" + toHex(keccak256(m_value).asBytes());
+}
+
bool StringLiteralType::operator==(const Type& _other) const
{
if (_other.category() != category())
@@ -1002,6 +1087,11 @@ MemberList::MemberMap FixedBytesType::nativeMembers(const ContractDefinition*) c
return MemberList::MemberMap{MemberList::Member{"length", make_shared<IntegerType>(8)}};
}
+string FixedBytesType::identifier() const
+{
+ return "t_bytes" + std::to_string(m_bytes);
+}
+
bool FixedBytesType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1115,6 +1205,20 @@ string ReferenceType::stringForReferencePart() const
return "";
}
+string ReferenceType::identifierLocationSuffix() const
+{
+ string id;
+ if (location() == DataLocation::Storage)
+ id += "_storage";
+ else if (location() == DataLocation::Memory)
+ id += "_memory";
+ else
+ id += "_calldata";
+ if (isPointer())
+ id += "_ptr";
+ return id;
+}
+
bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
{
if (_convertTo.category() != category())
@@ -1170,6 +1274,27 @@ bool ArrayType::isExplicitlyConvertibleTo(const Type& _convertTo) const
return true;
}
+string ArrayType::identifier() const
+{
+ string id;
+ if (isString())
+ id = "t_string";
+ else if (isByteArray())
+ id = "t_bytes";
+ else
+ {
+ id = "t_array";
+ id += identifierList(baseType());
+ if (isDynamicallySized())
+ id += "dyn";
+ else
+ id += length().str();
+ }
+ id += identifierLocationSuffix();
+
+ return id;
+}
+
bool ArrayType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1184,7 +1309,7 @@ bool ArrayType::operator==(Type const& _other) const
return false;
if (*other.baseType() != *baseType())
return false;
- return isDynamicallySized() || length() == other.length();
+ return isDynamicallySized() || length() == other.length();
}
unsigned ArrayType::calldataEncodedSize(bool _padded) const
@@ -1356,6 +1481,11 @@ TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer)
return copy;
}
+string ContractType::identifier() const
+{
+ return (m_super ? "t_super" : "t_contract") + parenthesizeUserIdentifier(m_contract.name()) + std::to_string(m_contract.id());
+}
+
bool ContractType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1465,6 +1595,11 @@ bool StructType::isImplicitlyConvertibleTo(const Type& _convertTo) const
return this->m_struct == convertTo.m_struct;
}
+string StructType::identifier() const
+{
+ return "t_struct" + parenthesizeUserIdentifier(m_struct.name()) + std::to_string(m_struct.id()) + identifierLocationSuffix();
+}
+
bool StructType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1605,6 +1740,11 @@ TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
return _operator == Token::Delete ? make_shared<TupleType>() : TypePointer();
}
+string EnumType::identifier() const
+{
+ return "t_enum" + parenthesizeUserIdentifier(m_enum.name()) + std::to_string(m_enum.id());
+}
+
bool EnumType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -1686,6 +1826,11 @@ bool TupleType::isImplicitlyConvertibleTo(Type const& _other) const
return false;
}
+string TupleType::identifier() const
+{
+ return "t_tuple" + identifierList(components());
+}
+
bool TupleType::operator==(Type const& _other) const
{
if (auto tupleType = dynamic_cast<TupleType const*>(&_other))
@@ -1934,6 +2079,53 @@ TypePointers FunctionType::parameterTypes() const
return TypePointers(m_parameterTypes.cbegin() + 1, m_parameterTypes.cend());
}
+string FunctionType::identifier() const
+{
+ string id = "t_function_";
+ switch (location())
+ {
+ case Location::Internal: id += "internal"; break;
+ case Location::External: id += "external"; break;
+ case Location::CallCode: id += "callcode"; break;
+ case Location::DelegateCall: id += "delegatecall"; break;
+ case Location::Bare: id += "bare"; break;
+ case Location::BareCallCode: id += "barecallcode"; break;
+ case Location::BareDelegateCall: id += "baredelegatecall"; break;
+ case Location::Creation: id += "creation"; break;
+ case Location::Send: id += "send"; break;
+ case Location::SHA3: id += "sha3"; break;
+ case Location::Selfdestruct: id += "selfdestruct"; break;
+ case Location::ECRecover: id += "ecrecover"; break;
+ case Location::SHA256: id += "sha256"; break;
+ case Location::RIPEMD160: id += "ripemd160"; break;
+ case Location::Log0: id += "log0"; break;
+ case Location::Log1: id += "log1"; break;
+ case Location::Log2: id += "log2"; break;
+ case Location::Log3: id += "log3"; break;
+ case Location::Log4: id += "log4"; break;
+ case Location::Event: id += "event"; break;
+ case Location::SetGas: id += "setgas"; break;
+ case Location::SetValue: id += "setvalue"; break;
+ case Location::BlockHash: id += "blockhash"; break;
+ case Location::AddMod: id += "addmod"; break;
+ case Location::MulMod: id += "mulmod"; break;
+ case Location::ArrayPush: id += "arraypush"; break;
+ case Location::ByteArrayPush: id += "bytearraypush"; break;
+ case Location::ObjectCreation: id += "objectcreation"; break;
+ default: solAssert(false, "Unknown function location."); break;
+ }
+ if (isConstant())
+ id += "_constant";
+ id += identifierList(m_parameterTypes) + "returns" + identifierList(m_returnParameterTypes);
+ if (m_gasSet)
+ id += "gas";
+ if (m_valueSet)
+ id += "value";
+ if (bound())
+ id += "bound_to" + identifierList(selfType());
+ return id;
+}
+
bool FunctionType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2320,25 +2512,7 @@ FunctionTypePointer FunctionType::asMemberFunction(bool _inLibrary, bool _bound)
);
}
-vector<string> const FunctionType::parameterTypeNames(bool _addDataLocation) const
-{
- vector<string> names;
- for (TypePointer const& t: parameterTypes())
- names.push_back(t->canonicalName(_addDataLocation));
-
- return names;
-}
-
-vector<string> const FunctionType::returnParameterTypeNames(bool _addDataLocation) const
-{
- vector<string> names;
- for (TypePointer const& t: m_returnParameterTypes)
- names.push_back(t->canonicalName(_addDataLocation));
-
- return names;
-}
-
-TypePointer FunctionType::selfType() const
+TypePointer const& FunctionType::selfType() const
{
solAssert(bound(), "Function is not bound.");
solAssert(m_parameterTypes.size() > 0, "Function has no self type.");
@@ -2354,6 +2528,11 @@ ASTPointer<ASTString> FunctionType::documentation() const
return ASTPointer<ASTString>();
}
+string MappingType::identifier() const
+{
+ return "t_mapping" + identifierList(m_keyType, m_valueType);
+}
+
bool MappingType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2372,6 +2551,11 @@ string MappingType::canonicalName(bool) const
return "mapping(" + keyType()->canonicalName(false) + " => " + valueType()->canonicalName(false) + ")";
}
+string TypeType::identifier() const
+{
+ return "t_type" + identifierList(actualType());
+}
+
bool TypeType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2456,6 +2640,11 @@ u256 ModifierType::storageSize() const
<< errinfo_comment("Storage size of non-storable type type requested."));
}
+string ModifierType::identifier() const
+{
+ return "t_modifier" + identifierList(m_parameterTypes);
+}
+
bool ModifierType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2480,6 +2669,11 @@ string ModifierType::toString(bool _short) const
return name + ")";
}
+string ModuleType::identifier() const
+{
+ return "t_module_" + std::to_string(m_sourceUnit.id());
+}
+
bool ModuleType::operator==(Type const& _other) const
{
if (_other.category() != category())
@@ -2501,6 +2695,22 @@ string ModuleType::toString(bool) const
return string("module \"") + m_sourceUnit.annotation().path + string("\"");
}
+string MagicType::identifier() const
+{
+ switch (m_kind)
+ {
+ case Kind::Block:
+ return "t_magic_block";
+ case Kind::Message:
+ return "t_magic_message";
+ case Kind::Transaction:
+ return "t_magic_transaction";
+ default:
+ solAssert(false, "Unknown kind of magic");
+ }
+ return "";
+}
+
bool MagicType::operator==(Type const& _other) const
{
if (_other.category() != category())
diff --git a/libsolidity/ast/Types.h b/libsolidity/ast/Types.h
index 26e2b8f2..a5147f17 100644
--- a/libsolidity/ast/Types.h
+++ b/libsolidity/ast/Types.h
@@ -22,18 +22,21 @@
#pragma once
-#include <memory>
-#include <string>
-#include <map>
-#include <boost/noncopyable.hpp>
-#include <boost/rational.hpp>
-#include <libdevcore/Common.h>
-#include <libdevcore/CommonIO.h>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/parsing/Token.h>
+
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonIO.h>
#include <libdevcore/UndefMacros.h>
+#include <boost/noncopyable.hpp>
+#include <boost/rational.hpp>
+
+#include <memory>
+#include <string>
+#include <map>
+
namespace dev
{
namespace solidity
@@ -155,6 +158,13 @@ public:
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
virtual Category category() const = 0;
+ /// @returns a valid solidity identifier such that two types should compare equal if and
+ /// only if they have the same identifier.
+ /// The identifier should start with "t_".
+ /// More complex identifier strings use "parentheses", where $_ is interpreted as as
+ /// "opening parenthesis", _$ as "closing parenthesis", _$_ as "comma" and any $ that
+ /// appears as part of a user-supplied identifier is escaped as _$$$_.
+ virtual std::string identifier() const = 0;
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
{
@@ -288,6 +298,7 @@ public:
explicit IntegerType(int _bits, Modifier _modifier = Modifier::Unsigned);
+ virtual std::string identifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -303,6 +314,8 @@ public:
virtual std::string toString(bool _short) const override;
+ virtual u256 literalValue(Literal const* _literal) const override;
+
virtual TypePointer encodingType() const override { return shared_from_this(); }
virtual TypePointer interfaceType(bool) const override { return shared_from_this(); }
@@ -329,6 +342,7 @@ public:
explicit FixedPointType(int _integerBits, int _fractionalBits, Modifier _modifier = Modifier::Unsigned);
+ virtual std::string identifier() const override;
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -378,6 +392,7 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
@@ -416,6 +431,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
@@ -449,6 +465,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@@ -476,6 +493,7 @@ class BoolType: public Type
public:
BoolType() {}
virtual Category category() const override { return Category::Bool; }
+ virtual std::string identifier() const override { return "t_bool"; }
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@@ -533,6 +551,8 @@ protected:
TypePointer copyForLocationIfReference(TypePointer const& _type) const;
/// @returns a human-readable description of the reference part of the type.
std::string stringForReferencePart() const;
+ /// @returns the suffix computed from the reference part to be used by identifier();
+ std::string identifierLocationSuffix() const;
DataLocation m_location = DataLocation::Storage;
bool m_isPointer = true;
@@ -573,6 +593,7 @@ public:
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(const Type& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
virtual bool isDynamicallySized() const override { return m_hasDynamicLength; }
@@ -622,6 +643,7 @@ public:
/// Contracts can be converted to themselves and to integers.
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded ) const override
{
@@ -677,6 +699,7 @@ public:
explicit StructType(StructDefinition const& _struct, DataLocation _location = DataLocation::Storage):
ReferenceType(_location), m_struct(_struct) {}
virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override;
u256 memorySize() const;
@@ -720,6 +743,7 @@ public:
virtual Category category() const override { return Category::Enum; }
explicit EnumType(EnumDefinition const& _enum): m_enum(_enum) {}
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned calldataEncodedSize(bool _padded) const override
{
@@ -760,6 +784,7 @@ public:
virtual Category category() const override { return Category::Tuple; }
explicit TupleType(std::vector<TypePointer> const& _types = std::vector<TypePointer>()): m_components(_types) {}
virtual bool isImplicitlyConvertibleTo(Type const& _other) const override;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual std::string toString(bool) const override;
@@ -890,13 +915,12 @@ public:
TypePointers parameterTypes() const;
std::vector<std::string> parameterNames() const;
- std::vector<std::string> const parameterTypeNames(bool _addDataLocation) const;
TypePointers const& returnParameterTypes() const { return m_returnParameterTypes; }
std::vector<std::string> const& returnParameterNames() const { return m_returnParameterNames; }
- std::vector<std::string> const returnParameterTypeNames(bool _addDataLocation) const;
/// @returns the "self" parameter type for a bound function
- TypePointer selfType() const;
+ TypePointer const& selfType() const;
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual std::string canonicalName(bool /*_addDataLocation*/) const override;
@@ -995,6 +1019,7 @@ public:
MappingType(TypePointer const& _keyType, TypePointer const& _valueType):
m_keyType(_keyType), m_valueType(_valueType) {}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
virtual std::string canonicalName(bool _addDataLocation) const override;
@@ -1029,6 +1054,7 @@ public:
TypePointer const& actualType() const { return m_actualType; }
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual u256 storageSize() const override;
@@ -1056,6 +1082,7 @@ public:
virtual u256 storageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned sizeOnStack() const override { return 0; }
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual std::string toString(bool _short) const override;
@@ -1080,6 +1107,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -1109,6 +1137,7 @@ public:
return TypePointer();
}
+ virtual std::string identifier() const override;
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
virtual bool canLiveOutsideStorage() const override { return true; }
@@ -1132,6 +1161,7 @@ class InaccessibleDynamicType: public Type
public:
virtual Category category() const override { return Category::InaccessibleDynamic; }
+ virtual std::string identifier() const override { return "t_inaccessible"; }
virtual bool isImplicitlyConvertibleTo(Type const&) const override { return false; }
virtual bool isExplicitlyConvertibleTo(Type const&) const override { return false; }
virtual unsigned calldataEncodedSize(bool _padded) const override { (void)_padded; return 32; }
diff --git a/libsolidity/codegen/ArrayUtils.cpp b/libsolidity/codegen/ArrayUtils.cpp
index 352c7177..bdd29abd 100644
--- a/libsolidity/codegen/ArrayUtils.cpp
+++ b/libsolidity/codegen/ArrayUtils.cpp
@@ -40,9 +40,9 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack layout: [source_ref] [source length] target_ref (top)
solAssert(_targetType.location() == DataLocation::Storage, "");
- IntegerType uint256(256);
- Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.baseType());
- Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.baseType());
+ TypePointer uint256 = make_shared<IntegerType>(256);
+ TypePointer targetBaseType = _targetType.isByteArray() ? uint256 : _targetType.baseType();
+ TypePointer sourceBaseType = _sourceType.isByteArray() ? uint256 : _sourceType.baseType();
// TODO unroll loop for small sizes
@@ -70,202 +70,216 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
}
// stack: target_ref source_ref source_length
- m_context << Instruction::DUP3;
- // stack: target_ref source_ref source_length target_ref
- retrieveLength(_targetType);
- // stack: target_ref source_ref source_length target_ref target_length
- if (_targetType.isDynamicallySized())
- // store new target length
- if (!_targetType.isByteArray())
- // Otherwise, length will be stored below.
- m_context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
- if (sourceBaseType->category() == Type::Category::Mapping)
- {
- solAssert(targetBaseType->category() == Type::Category::Mapping, "");
- solAssert(_sourceType.location() == DataLocation::Storage, "");
- // nothing to copy
- m_context
- << Instruction::POP << Instruction::POP
- << Instruction::POP << Instruction::POP;
- return;
- }
- // stack: target_ref source_ref source_length target_ref target_length
- // compute hashes (data positions)
- m_context << Instruction::SWAP1;
- if (_targetType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref source_ref source_length target_length target_data_pos
- m_context << Instruction::SWAP1;
- convertLengthToSize(_targetType);
- m_context << Instruction::DUP2 << Instruction::ADD;
- // stack: target_ref source_ref source_length target_data_pos target_data_end
- m_context << Instruction::SWAP3;
- // stack: target_ref target_data_end source_length target_data_pos source_ref
-
- eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
-
- // special case for short byte arrays: Store them together with their length.
- if (_targetType.isByteArray())
- {
- // stack: target_ref target_data_end source_length target_data_pos source_ref
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- eth::AssemblyItem longByteArray = m_context.appendConditionalJump();
- // store the short byte array
- solAssert(_sourceType.isByteArray(), "");
- if (_sourceType.location() == DataLocation::Storage)
- {
- // just copy the slot, it contains length and data
- m_context << Instruction::DUP1 << Instruction::SLOAD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
- else
+ TypePointer targetType = _targetType.shared_from_this();
+ TypePointer sourceType = _sourceType.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(),
+ 3,
+ 1,
+ [=](CompilerContext& _context)
{
- m_context << Instruction::DUP1;
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- // stack: target_ref target_data_end source_length target_data_pos source_ref value
- // clear the lower-order byte - which will hold the length
- m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
- // fetch the length and shift it left by one
- m_context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
- // combine value and length and store them
- m_context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
- }
- // end of special case, jump right into cleaning target data area
- m_context.appendJumpTo(copyLoopEndWithoutByteOffset);
- m_context << longByteArray;
- // Store length (2*length+1)
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- m_context << Instruction::DUP6 << Instruction::SSTORE;
- }
+ ArrayUtils utils(_context);
+ ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType);
+ ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType);
+ // stack: target_ref source_ref source_length
+ _context << Instruction::DUP3;
+ // stack: target_ref source_ref source_length target_ref
+ utils.retrieveLength(_targetType);
+ // stack: target_ref source_ref source_length target_ref target_length
+ if (_targetType.isDynamicallySized())
+ // store new target length
+ if (!_targetType.isByteArray())
+ // Otherwise, length will be stored below.
+ _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE;
+ if (sourceBaseType->category() == Type::Category::Mapping)
+ {
+ solAssert(targetBaseType->category() == Type::Category::Mapping, "");
+ solAssert(_sourceType.location() == DataLocation::Storage, "");
+ // nothing to copy
+ _context
+ << Instruction::POP << Instruction::POP
+ << Instruction::POP << Instruction::POP;
+ return;
+ }
+ // stack: target_ref source_ref source_length target_ref target_length
+ // compute hashes (data positions)
+ _context << Instruction::SWAP1;
+ if (_targetType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref source_ref source_length target_length target_data_pos
+ _context << Instruction::SWAP1;
+ utils.convertLengthToSize(_targetType);
+ _context << Instruction::DUP2 << Instruction::ADD;
+ // stack: target_ref source_ref source_length target_data_pos target_data_end
+ _context << Instruction::SWAP3;
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+
+ eth::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag();
+
+ // special case for short byte arrays: Store them together with their length.
+ if (_targetType.isByteArray())
+ {
+ // stack: target_ref target_data_end source_length target_data_pos source_ref
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem longByteArray = _context.appendConditionalJump();
+ // store the short byte array
+ solAssert(_sourceType.isByteArray(), "");
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ // just copy the slot, it contains length and data
+ _context << Instruction::DUP1 << Instruction::SLOAD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ else
+ {
+ _context << Instruction::DUP1;
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ // stack: target_ref target_data_end source_length target_data_pos source_ref value
+ // clear the lower-order byte - which will hold the length
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // fetch the length and shift it left by one
+ _context << Instruction::DUP4 << Instruction::DUP1 << Instruction::ADD;
+ // combine value and length and store them
+ _context << Instruction::OR << Instruction::DUP6 << Instruction::SSTORE;
+ }
+ // end of special case, jump right into cleaning target data area
+ _context.appendJumpTo(copyLoopEndWithoutByteOffset);
+ _context << longByteArray;
+ // Store length (2*length+1)
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ _context << Instruction::DUP6 << Instruction::SSTORE;
+ }
- // skip copying if source length is zero
- m_context << Instruction::DUP3 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
-
- if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
- CompilerUtils(m_context).computeHashStatic();
- // stack: target_ref target_data_end source_length target_data_pos source_data_pos
- m_context << Instruction::SWAP2;
- convertLengthToSize(_sourceType);
- m_context << Instruction::DUP3 << Instruction::ADD;
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
- if (haveByteOffsetTarget)
- m_context << u256(0);
- if (haveByteOffsetSource)
- m_context << u256(0);
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- eth::AssemblyItem copyLoopStart = m_context.newTag();
- m_context << copyLoopStart;
- // check for loop condition
- m_context
- << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
- << Instruction::GT << Instruction::ISZERO;
- eth::AssemblyItem copyLoopEnd = m_context.appendConditionalJump();
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- // copy
- if (sourceBaseType->category() == Type::Category::Array)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
- auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
- m_context << Instruction::DUP3;
- if (sourceBaseArrayType.location() == DataLocation::Memory)
- m_context << Instruction::MLOAD;
- m_context << Instruction::DUP3;
- copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
- m_context << Instruction::POP;
- }
- else if (directCopy)
- {
- solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
- m_context
- << Instruction::DUP3 << Instruction::SLOAD
- << Instruction::DUP3 << Instruction::SSTORE;
- }
- else
- {
- // Note that we have to copy each element on its own in case conversion is involved.
- // We might copy too much if there is padding at the last element, but this way end
- // checking is easier.
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- m_context << dupInstruction(3 + byteOffsetSize);
- if (_sourceType.location() == DataLocation::Storage)
- {
+ // skip copying if source length is zero
+ _context << Instruction::DUP3 << Instruction::ISZERO;
+ _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
+
+ if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized())
+ CompilerUtils(_context).computeHashStatic();
+ // stack: target_ref target_data_end source_length target_data_pos source_data_pos
+ _context << Instruction::SWAP2;
+ utils.convertLengthToSize(_sourceType);
+ _context << Instruction::DUP3 << Instruction::ADD;
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
+ if (haveByteOffsetTarget)
+ _context << u256(0);
if (haveByteOffsetSource)
- m_context << Instruction::DUP2;
+ _context << u256(0);
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ eth::AssemblyItem copyLoopStart = _context.newTag();
+ _context << copyLoopStart;
+ // check for loop condition
+ _context
+ << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize)
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem copyLoopEnd = _context.appendConditionalJump();
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ // copy
+ if (sourceBaseType->category() == Type::Category::Array)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
+ auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType);
+ _context << Instruction::DUP3;
+ if (sourceBaseArrayType.location() == DataLocation::Memory)
+ _context << Instruction::MLOAD;
+ _context << Instruction::DUP3;
+ utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType);
+ _context << Instruction::POP;
+ }
+ else if (directCopy)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
+ _context
+ << Instruction::DUP3 << Instruction::SLOAD
+ << Instruction::DUP3 << Instruction::SSTORE;
+ }
+ else
+ {
+ // Note that we have to copy each element on its own in case conversion is involved.
+ // We might copy too much if there is padding at the last element, but this way end
+ // checking is easier.
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ _context << dupInstruction(3 + byteOffsetSize);
+ if (_sourceType.location() == DataLocation::Storage)
+ {
+ if (haveByteOffsetSource)
+ _context << Instruction::DUP2;
+ else
+ _context << u256(0);
+ StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ }
+ else if (sourceBaseType->isValueType())
+ CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
+ else
+ solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
+ solAssert(
+ 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
+ "Stack too deep, try removing local variables."
+ );
+ // fetch target storage reference
+ _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ if (haveByteOffsetTarget)
+ _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
+ else
+ _context << u256(0);
+ StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
+ }
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
+ // increment source
+ if (haveByteOffsetSource)
+ utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
else
- m_context << u256(0);
- StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ {
+ _context << swapInstruction(2 + byteOffsetSize);
+ if (sourceIsStorage)
+ _context << sourceBaseType->storageSize();
+ else if (_sourceType.location() == DataLocation::Memory)
+ _context << sourceBaseType->memoryHeadSize();
+ else
+ _context << sourceBaseType->calldataEncodedSize(true);
+ _context
+ << Instruction::ADD
+ << swapInstruction(2 + byteOffsetSize);
+ }
+ // increment target
+ if (haveByteOffsetTarget)
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ else
+ _context
+ << swapInstruction(1 + byteOffsetSize)
+ << targetBaseType->storageSize()
+ << Instruction::ADD
+ << swapInstruction(1 + byteOffsetSize);
+ _context.appendJumpTo(copyLoopStart);
+ _context << copyLoopEnd;
+ if (haveByteOffsetTarget)
+ {
+ // clear elements that might be left over in the current slot in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
+ _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
+ eth::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump();
+ _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
+ StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true);
+ utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ _context.appendJumpTo(copyLoopEnd);
+
+ _context << copyCleanupLoopEnd;
+ _context << Instruction::POP; // might pop the source, but then target is popped next
+ }
+ if (haveByteOffsetSource)
+ _context << Instruction::POP;
+ _context << copyLoopEndWithoutByteOffset;
+
+ // zero-out leftovers in target
+ // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
+ _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
+ // stack: target_ref target_data_end target_data_pos_updated
+ utils.clearStorageLoop(targetBaseType);
+ _context << Instruction::POP;
}
- else if (sourceBaseType->isValueType())
- CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false);
- else
- solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported.");
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
- solAssert(
- 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16,
- "Stack too deep, try removing local variables."
- );
- // fetch target storage reference
- m_context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack());
- if (haveByteOffsetTarget)
- m_context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack());
- else
- m_context << u256(0);
- StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true);
- }
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
- // increment source
- if (haveByteOffsetSource)
- incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
- else
- {
- m_context << swapInstruction(2 + byteOffsetSize);
- if (sourceIsStorage)
- m_context << sourceBaseType->storageSize();
- else if (_sourceType.location() == DataLocation::Memory)
- m_context << sourceBaseType->memoryHeadSize();
- else
- m_context << sourceBaseType->calldataEncodedSize(true);
- m_context
- << Instruction::ADD
- << swapInstruction(2 + byteOffsetSize);
- }
- // increment target
- if (haveByteOffsetTarget)
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- else
- m_context
- << swapInstruction(1 + byteOffsetSize)
- << targetBaseType->storageSize()
- << Instruction::ADD
- << swapInstruction(1 + byteOffsetSize);
- m_context.appendJumpTo(copyLoopStart);
- m_context << copyLoopEnd;
- if (haveByteOffsetTarget)
- {
- // clear elements that might be left over in the current slot in target
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset]
- m_context << dupInstruction(byteOffsetSize) << Instruction::ISZERO;
- eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
- m_context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize);
- StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true);
- incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2);
- m_context.appendJumpTo(copyLoopEnd);
-
- m_context << copyCleanupLoopEnd;
- m_context << Instruction::POP; // might pop the source, but then target is popped next
- }
- if (haveByteOffsetSource)
- m_context << Instruction::POP;
- m_context << copyLoopEndWithoutByteOffset;
-
- // zero-out leftovers in target
- // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
- m_context << Instruction::POP << Instruction::SWAP1 << Instruction::POP;
- // stack: target_ref target_data_end target_data_pos_updated
- clearStorageLoop(*targetBaseType);
- m_context << Instruction::POP;
+ );
}
void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const
@@ -502,60 +516,70 @@ void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWord
}
}
-void ArrayUtils::clearArray(ArrayType const& _type) const
+void ArrayUtils::clearArray(ArrayType const& _typeIn) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- solAssert(_type.location() == DataLocation::Storage, "");
- if (_type.baseType()->storageBytes() < 32)
- {
- solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
- solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
- }
- if (_type.baseType()->isValueType())
- solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
-
- m_context << Instruction::POP; // remove byte offset
- if (_type.isDynamicallySized())
- clearDynamicArray(_type);
- else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
- m_context << Instruction::POP;
- else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
- {
- // unroll loop for small arrays @todo choose a good value
- // Note that we loop over storage slots here, not elements.
- for (unsigned i = 1; i < _type.storageSize(); ++i)
- m_context
- << u256(0) << Instruction::DUP2 << Instruction::SSTORE
- << u256(1) << Instruction::ADD;
- m_context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
- }
- else if (!_type.baseType()->isValueType() && _type.length() <= 4)
- {
- // unroll loop for small arrays @todo choose a good value
- solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
- for (unsigned i = 1; i < _type.length(); ++i)
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$clearArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
{
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), false);
- m_context
- << Instruction::POP
- << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ unsigned stackHeightStart = _context.stackHeight();
+ solAssert(_type.location() == DataLocation::Storage, "");
+ if (_type.baseType()->storageBytes() < 32)
+ {
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+ solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type.");
+ }
+ if (_type.baseType()->isValueType())
+ solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type.");
+
+ _context << Instruction::POP; // remove byte offset
+ if (_type.isDynamicallySized())
+ ArrayUtils(_context).clearDynamicArray(_type);
+ else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping)
+ _context << Instruction::POP;
+ else if (_type.baseType()->isValueType() && _type.storageSize() <= 5)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ // Note that we loop over storage slots here, not elements.
+ for (unsigned i = 1; i < _type.storageSize(); ++i)
+ _context
+ << u256(0) << Instruction::DUP2 << Instruction::SSTORE
+ << u256(1) << Instruction::ADD;
+ _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE;
+ }
+ else if (!_type.baseType()->isValueType() && _type.length() <= 4)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size.");
+ for (unsigned i = 1; i < _type.length(); ++i)
+ {
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false);
+ _context
+ << Instruction::POP
+ << u256(_type.baseType()->storageSize()) << Instruction::ADD;
+ }
+ _context << u256(0);
+ StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true);
+ }
+ else
+ {
+ _context << Instruction::DUP1 << _type.length();
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::ADD << Instruction::SWAP1;
+ if (_type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
+ _context << Instruction::POP;
+ }
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
}
- m_context << u256(0);
- StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true);
- }
- else
- {
- m_context << Instruction::DUP1 << _type.length();
- convertLengthToSize(_type);
- m_context << Instruction::ADD << Instruction::SWAP1;
- if (_type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
- m_context << Instruction::POP;
- }
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ );
}
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@@ -589,191 +613,209 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
<< Instruction::SWAP1;
// stack: data_pos_end data_pos
if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
+ clearStorageLoop(make_shared<IntegerType>(256));
else
- clearStorageLoop(*_type.baseType());
+ clearStorageLoop(_type.baseType());
// cleanup
m_context << endTag;
m_context << Instruction::POP;
}
-void ArrayUtils::resizeDynamicArray(ArrayType const& _type) const
+void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const
{
- solAssert(_type.location() == DataLocation::Storage, "");
- solAssert(_type.isDynamicallySized(), "");
- if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
- solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
-
- unsigned stackHeightStart = m_context.stackHeight();
- eth::AssemblyItem resizeEnd = m_context.newTag();
-
- // stack: ref new_length
- // fetch old length
- retrieveLength(_type, 1);
- // stack: ref new_length old_length
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "2");
-
- // Special case for short byte arrays, they are stored together with their length
- if (_type.isByteArray())
- {
- eth::AssemblyItem regularPath = m_context.newTag();
- // We start by a large case-distinction about the old and new length of the byte array.
-
- m_context << Instruction::DUP3 << Instruction::SLOAD;
- // stack: ref new_length current_length ref_value
-
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::DUP2 << u256(31) << Instruction::LT;
- eth::AssemblyItem currentIsLong = m_context.appendConditionalJump();
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- eth::AssemblyItem newIsLong = m_context.appendConditionalJump();
-
- // Here: short -> short
-
- // Compute 1 << (256 - 8 * new_size)
- eth::AssemblyItem shortToShort = m_context.newTag();
- m_context << shortToShort;
- m_context << Instruction::DUP3 << u256(8) << Instruction::MUL;
- m_context << u256(0x100) << Instruction::SUB;
- m_context << u256(2) << Instruction::EXP;
- // Divide and multiply by that value, clearing bits.
- m_context << Instruction::DUP1 << Instruction::SWAP2;
- m_context << Instruction::DIV << Instruction::MUL;
- // Insert 2*length.
- m_context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
- m_context << Instruction::OR;
- // Store.
- m_context << Instruction::DUP4 << Instruction::SSTORE;
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
- m_context.appendJumpTo(resizeEnd);
-
- m_context.adjustStackOffset(1); // we have to do that because of the jumps
- // Here: short -> long
-
- m_context << newIsLong;
- // stack: ref new_length current_length ref_value
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- // Zero out lower-order byte.
- m_context << u256(0xff) << Instruction::NOT << Instruction::AND;
- // Store at data location.
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::SSTORE;
- // stack: ref new_length current_length
- // Store new length: Compule 2*length + 1 and store it.
- m_context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
- m_context << u256(1) << Instruction::ADD;
- // stack: ref new_length current_length 2*new_length+1
- m_context << Instruction::DUP4 << Instruction::SSTORE;
- solAssert(m_context.stackHeight() - stackHeightStart == 3 - 2, "3");
- m_context.appendJumpTo(resizeEnd);
-
- m_context.adjustStackOffset(1); // we have to do that because of the jumps
-
- m_context << currentIsLong;
- m_context << Instruction::DUP3 << u256(31) << Instruction::LT;
- m_context.appendConditionalJumpTo(regularPath);
-
- // Here: long -> short
- // Read the first word of the data and store it on the stack. Clear the data location and
- // then jump to the short -> short case.
-
- // stack: ref new_length current_length ref_value
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context << Instruction::POP << Instruction::DUP3;
- CompilerUtils(m_context).computeHashStatic();
- m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location
- m_context << Instruction::DUP3;
- convertLengthToSize(_type);
- m_context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
- // stack: ref new_length current_length first_word data_location_end data_location
- clearStorageLoop(IntegerType(256));
- m_context << Instruction::POP;
- // stack: ref new_length current_length first_word
- solAssert(m_context.stackHeight() - stackHeightStart == 4 - 2, "3");
- m_context.appendJumpTo(shortToShort);
-
- m_context << regularPath;
- // stack: ref new_length current_length ref_value
- m_context << Instruction::POP;
- }
+ TypePointer type = _typeIn.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$resizeDynamicArray_" + _typeIn.identifier(),
+ 2,
+ 0,
+ [type](CompilerContext& _context)
+ {
+ ArrayType const& _type = dynamic_cast<ArrayType const&>(*type);
+ solAssert(_type.location() == DataLocation::Storage, "");
+ solAssert(_type.isDynamicallySized(), "");
+ if (!_type.isByteArray() && _type.baseType()->storageBytes() < 32)
+ solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type.");
+
+ unsigned stackHeightStart = _context.stackHeight();
+ eth::AssemblyItem resizeEnd = _context.newTag();
+
+ // stack: ref new_length
+ // fetch old length
+ ArrayUtils(_context).retrieveLength(_type, 1);
+ // stack: ref new_length old_length
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2");
+
+ // Special case for short byte arrays, they are stored together with their length
+ if (_type.isByteArray())
+ {
+ eth::AssemblyItem regularPath = _context.newTag();
+ // We start by a large case-distinction about the old and new length of the byte array.
+
+ _context << Instruction::DUP3 << Instruction::SLOAD;
+ // stack: ref new_length current_length ref_value
+
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::DUP2 << u256(31) << Instruction::LT;
+ eth::AssemblyItem currentIsLong = _context.appendConditionalJump();
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ eth::AssemblyItem newIsLong = _context.appendConditionalJump();
+
+ // Here: short -> short
+
+ // Compute 1 << (256 - 8 * new_size)
+ eth::AssemblyItem shortToShort = _context.newTag();
+ _context << shortToShort;
+ _context << Instruction::DUP3 << u256(8) << Instruction::MUL;
+ _context << u256(0x100) << Instruction::SUB;
+ _context << u256(2) << Instruction::EXP;
+ // Divide and multiply by that value, clearing bits.
+ _context << Instruction::DUP1 << Instruction::SWAP2;
+ _context << Instruction::DIV << Instruction::MUL;
+ // Insert 2*length.
+ _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD;
+ _context << Instruction::OR;
+ // Store.
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+ // Here: short -> long
+
+ _context << newIsLong;
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ // Zero out lower-order byte.
+ _context << u256(0xff) << Instruction::NOT << Instruction::AND;
+ // Store at data location.
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::SSTORE;
+ // stack: ref new_length current_length
+ // Store new length: Compule 2*length + 1 and store it.
+ _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD;
+ _context << u256(1) << Instruction::ADD;
+ // stack: ref new_length current_length 2*new_length+1
+ _context << Instruction::DUP4 << Instruction::SSTORE;
+ solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3");
+ _context.appendJumpTo(resizeEnd);
+
+ _context.adjustStackOffset(1); // we have to do that because of the jumps
+
+ _context << currentIsLong;
+ _context << Instruction::DUP3 << u256(31) << Instruction::LT;
+ _context.appendConditionalJumpTo(regularPath);
+
+ // Here: long -> short
+ // Read the first word of the data and store it on the stack. Clear the data location and
+ // then jump to the short -> short case.
+
+ // stack: ref new_length current_length ref_value
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context << Instruction::POP << Instruction::DUP3;
+ CompilerUtils(_context).computeHashStatic();
+ _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location
+ _context << Instruction::DUP3;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1;
+ // stack: ref new_length current_length first_word data_location_end data_location
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ _context << Instruction::POP;
+ // stack: ref new_length current_length first_word
+ solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3");
+ _context.appendJumpTo(shortToShort);
+
+ _context << regularPath;
+ // stack: ref new_length current_length ref_value
+ _context << Instruction::POP;
+ }
- // Change of length for a regular array (i.e. length at location, data at sha3(location)).
- // stack: ref new_length old_length
- // store new length
- m_context << Instruction::DUP2;
- if (_type.isByteArray())
- // For a "long" byte array, store length as 2*length+1
- m_context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
- m_context<< Instruction::DUP4 << Instruction::SSTORE;
- // skip if size is not reduced
- m_context << Instruction::DUP2 << Instruction::DUP2
- << Instruction::ISZERO << Instruction::GT;
- m_context.appendConditionalJumpTo(resizeEnd);
-
- // size reduced, clear the end of the array
- // stack: ref new_length old_length
- convertLengthToSize(_type);
- m_context << Instruction::DUP2;
- convertLengthToSize(_type);
- // stack: ref new_length old_size new_size
- // compute data positions
- m_context << Instruction::DUP4;
- CompilerUtils(m_context).computeHashStatic();
- // stack: ref new_length old_size new_size data_pos
- m_context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
- // stack: ref new_length data_pos new_size delete_end
- m_context << Instruction::SWAP2 << Instruction::ADD;
- // stack: ref new_length delete_end delete_start
- if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
- clearStorageLoop(IntegerType(256));
- else
- clearStorageLoop(*_type.baseType());
+ // Change of length for a regular array (i.e. length at location, data at sha3(location)).
+ // stack: ref new_length old_length
+ // store new length
+ _context << Instruction::DUP2;
+ if (_type.isByteArray())
+ // For a "long" byte array, store length as 2*length+1
+ _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD;
+ _context<< Instruction::DUP4 << Instruction::SSTORE;
+ // skip if size is not reduced
+ _context << Instruction::DUP2 << Instruction::DUP2
+ << Instruction::ISZERO << Instruction::GT;
+ _context.appendConditionalJumpTo(resizeEnd);
+
+ // size reduced, clear the end of the array
+ // stack: ref new_length old_length
+ ArrayUtils(_context).convertLengthToSize(_type);
+ _context << Instruction::DUP2;
+ ArrayUtils(_context).convertLengthToSize(_type);
+ // stack: ref new_length old_size new_size
+ // compute data positions
+ _context << Instruction::DUP4;
+ CompilerUtils(_context).computeHashStatic();
+ // stack: ref new_length old_size new_size data_pos
+ _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD;
+ // stack: ref new_length data_pos new_size delete_end
+ _context << Instruction::SWAP2 << Instruction::ADD;
+ // stack: ref new_length delete_end delete_start
+ if (_type.isByteArray() || _type.baseType()->storageBytes() < 32)
+ ArrayUtils(_context).clearStorageLoop(make_shared<IntegerType>(256));
+ else
+ ArrayUtils(_context).clearStorageLoop(_type.baseType());
- m_context << resizeEnd;
- // cleanup
- m_context << Instruction::POP << Instruction::POP << Instruction::POP;
- solAssert(m_context.stackHeight() == stackHeightStart - 2, "");
+ _context << resizeEnd;
+ // cleanup
+ _context << Instruction::POP << Instruction::POP << Instruction::POP;
+ solAssert(_context.stackHeight() == stackHeightStart - 2, "");
+ }
+ );
}
-void ArrayUtils::clearStorageLoop(Type const& _type) const
+void ArrayUtils::clearStorageLoop(TypePointer const& _type) const
{
- unsigned stackHeightStart = m_context.stackHeight();
- if (_type.category() == Type::Category::Mapping)
- {
- m_context << Instruction::POP;
- return;
- }
- // stack: end_pos pos
-
- // jump to and return from the loop to allow for duplicate code removal
- eth::AssemblyItem returnTag = m_context.pushNewTag();
- m_context << Instruction::SWAP2 << Instruction::SWAP1;
-
- // stack: <return tag> end_pos pos
- eth::AssemblyItem loopStart = m_context.appendJumpToNew();
- m_context << loopStart;
- // check for loop condition
- m_context << Instruction::DUP1 << Instruction::DUP3
- << Instruction::GT << Instruction::ISZERO;
- eth::AssemblyItem zeroLoopEnd = m_context.newTag();
- m_context.appendConditionalJumpTo(zeroLoopEnd);
- // delete
- m_context << u256(0);
- StorageItem(m_context, _type).setToZero(SourceLocation(), false);
- m_context << Instruction::POP;
- // increment
- m_context << _type.storageSize() << Instruction::ADD;
- m_context.appendJumpTo(loopStart);
- // cleanup
- m_context << zeroLoopEnd;
- m_context << Instruction::POP << Instruction::SWAP1;
- // "return"
- m_context << Instruction::JUMP;
-
- m_context << returnTag;
- solAssert(m_context.stackHeight() == stackHeightStart - 1, "");
+ m_context.callLowLevelFunction(
+ "$clearStorageLoop_" + _type->identifier(),
+ 2,
+ 1,
+ [_type](CompilerContext& _context)
+ {
+ unsigned stackHeightStart = _context.stackHeight();
+ if (_type->category() == Type::Category::Mapping)
+ {
+ _context << Instruction::POP;
+ return;
+ }
+ // stack: end_pos pos
+
+ // jump to and return from the loop to allow for duplicate code removal
+ eth::AssemblyItem returnTag = _context.pushNewTag();
+ _context << Instruction::SWAP2 << Instruction::SWAP1;
+
+ // stack: <return tag> end_pos pos
+ eth::AssemblyItem loopStart = _context.appendJumpToNew();
+ _context << loopStart;
+ // check for loop condition
+ _context << Instruction::DUP1 << Instruction::DUP3
+ << Instruction::GT << Instruction::ISZERO;
+ eth::AssemblyItem zeroLoopEnd = _context.newTag();
+ _context.appendConditionalJumpTo(zeroLoopEnd);
+ // delete
+ _context << u256(0);
+ StorageItem(_context, *_type).setToZero(SourceLocation(), false);
+ _context << Instruction::POP;
+ // increment
+ _context << _type->storageSize() << Instruction::ADD;
+ _context.appendJumpTo(loopStart);
+ // cleanup
+ _context << zeroLoopEnd;
+ _context << Instruction::POP << Instruction::SWAP1;
+ // "return"
+ _context << Instruction::JUMP;
+
+ _context << returnTag;
+ solAssert(_context.stackHeight() == stackHeightStart - 1, "");
+ }
+ );
}
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
@@ -859,7 +901,7 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck) c
// check out-of-bounds access
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
if (location == DataLocation::CallData && _arrayType.isDynamicallySized())
// remove length if present
diff --git a/libsolidity/codegen/ArrayUtils.h b/libsolidity/codegen/ArrayUtils.h
index d0ba2892..806fbea7 100644
--- a/libsolidity/codegen/ArrayUtils.h
+++ b/libsolidity/codegen/ArrayUtils.h
@@ -22,6 +22,8 @@
#pragma once
+#include <memory>
+
namespace dev
{
namespace solidity
@@ -30,6 +32,7 @@ namespace solidity
class CompilerContext;
class Type;
class ArrayType;
+using TypePointer = std::shared_ptr<Type const>;
/**
* Class that provides code generation for handling arrays.
@@ -67,7 +70,7 @@ public:
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
- void clearStorageLoop(Type const& _type) const;
+ void clearStorageLoop(TypePointer const& _type) const;
/// Converts length to size (number of storage slots or calldata/memory bytes).
/// if @a _pad then add padding to multiples of 32 bytes for calldata/memory.
/// Stack pre: length
diff --git a/libsolidity/codegen/CompilerContext.cpp b/libsolidity/codegen/CompilerContext.cpp
index c14ab845..a8316109 100644
--- a/libsolidity/codegen/CompilerContext.cpp
+++ b/libsolidity/codegen/CompilerContext.cpp
@@ -21,15 +21,18 @@
*/
#include <libsolidity/codegen/CompilerContext.h>
-#include <utility>
-#include <numeric>
-#include <boost/algorithm/string/replace.hpp>
+#include <libsolidity/codegen/CompilerUtils.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/codegen/Compiler.h>
#include <libsolidity/interface/Version.h>
#include <libsolidity/inlineasm/AsmData.h>
#include <libsolidity/inlineasm/AsmStack.h>
+#include <boost/algorithm/string/replace.hpp>
+
+#include <utility>
+#include <numeric>
+
using namespace std;
namespace dev
@@ -57,6 +60,62 @@ void CompilerContext::startFunction(Declaration const& _function)
*this << functionEntryLabel(_function);
}
+void CompilerContext::callLowLevelFunction(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ eth::AssemblyItem retTag = pushNewTag();
+ CompilerUtils(*this).moveIntoStack(_inArgs);
+
+ *this << lowLevelFunctionTag(_name, _inArgs, _outArgs, _generator);
+
+ appendJump(eth::AssemblyItem::JumpType::IntoFunction);
+ adjustStackOffset(int(_outArgs) - 1 - _inArgs);
+ *this << retTag.tag();
+}
+
+eth::AssemblyItem CompilerContext::lowLevelFunctionTag(
+ string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ function<void(CompilerContext&)> const& _generator
+)
+{
+ auto it = m_lowLevelFunctions.find(_name);
+ if (it == m_lowLevelFunctions.end())
+ {
+ eth::AssemblyItem tag = newTag().pushTag();
+ m_lowLevelFunctions.insert(make_pair(_name, tag));
+ m_lowLevelFunctionGenerationQueue.push(make_tuple(_name, _inArgs, _outArgs, _generator));
+ return tag;
+ }
+ else
+ return it->second;
+}
+
+void CompilerContext::appendMissingLowLevelFunctions()
+{
+ while (!m_lowLevelFunctionGenerationQueue.empty())
+ {
+ string name;
+ unsigned inArgs;
+ unsigned outArgs;
+ function<void(CompilerContext&)> generator;
+ tie(name, inArgs, outArgs, generator) = m_lowLevelFunctionGenerationQueue.front();
+ m_lowLevelFunctionGenerationQueue.pop();
+
+ setStackOffset(inArgs + 1);
+ *this << m_lowLevelFunctions.at(name).tag();
+ generator(*this);
+ CompilerUtils(*this).moveToStackTop(outArgs);
+ appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
+ solAssert(stackHeight() == outArgs, "Invalid stack height in low-level function " + name + ".");
+ }
+}
+
void CompilerContext::addVariable(VariableDeclaration const& _declaration,
unsigned _offsetToCurrent)
{
@@ -167,6 +226,20 @@ CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpTy
return *this << item;
}
+CompilerContext& CompilerContext::appendInvalid()
+{
+ return *this << Instruction::INVALID;
+}
+
+CompilerContext& CompilerContext::appendConditionalInvalid()
+{
+ *this << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = appendConditionalJump();
+ *this << Instruction::INVALID;
+ *this << afterTag;
+ return *this;
+}
+
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{
stack<ASTNode const*> newStack;
diff --git a/libsolidity/codegen/CompilerContext.h b/libsolidity/codegen/CompilerContext.h
index 80671528..c37142c9 100644
--- a/libsolidity/codegen/CompilerContext.h
+++ b/libsolidity/codegen/CompilerContext.h
@@ -22,17 +22,21 @@
#pragma once
-#include <ostream>
-#include <stack>
-#include <queue>
-#include <utility>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/Assembly.h>
#include <libsolidity/ast/ASTForward.h>
#include <libsolidity/ast/Types.h>
#include <libsolidity/ast/ASTAnnotations.h>
+
+#include <libevmasm/Instruction.h>
+#include <libevmasm/Assembly.h>
+
#include <libdevcore/Common.h>
+#include <ostream>
+#include <stack>
+#include <queue>
+#include <utility>
+#include <functional>
+
namespace dev {
namespace solidity {
@@ -90,6 +94,29 @@ public:
/// as "having code".
void startFunction(Declaration const& _function);
+ /// Appends a call to the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ void callLowLevelFunction(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Returns the tag of the named low-level function and inserts the generator into the
+ /// list of low-level-functions to be generated, unless it already exists.
+ /// Note that the generator should not assume that objects are still alive when it is called,
+ /// unless they are guaranteed to be alive for the whole run of the compiler (AST nodes, for example).
+ eth::AssemblyItem lowLevelFunctionTag(
+ std::string const& _name,
+ unsigned _inArgs,
+ unsigned _outArgs,
+ std::function<void(CompilerContext&)> const& _generator
+ );
+ /// Generates the code for missing low-level functions, i.e. calls the generators passed above.
+ void appendMissingLowLevelFunctions();
+
ModifierDefinition const& functionModifier(std::string const& _name) const;
/// Returns the distance of the given local variable from the bottom of the stack (of the current function).
unsigned baseStackOffsetOfVariable(Declaration const& _declaration) const;
@@ -110,6 +137,10 @@ public:
eth::AssemblyItem appendJumpToNew() { return m_asm->appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack
CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
+ /// Appends an INVALID instruction
+ CompilerContext& appendInvalid();
+ /// Appends a conditional INVALID instruction
+ CompilerContext& appendConditionalInvalid();
/// Returns an "ErrorTag"
eth::AssemblyItem errorTag() { return m_asm->errorTag(); }
/// Appends a JUMP to a specific tag
@@ -248,6 +279,10 @@ private:
CompilerContext *m_runtimeContext;
/// The index of the runtime subroutine.
size_t m_runtimeSub = -1;
+ /// An index of low-level function labels by name.
+ std::map<std::string, eth::AssemblyItem> m_lowLevelFunctions;
+ /// The queue of low-level functions to generate.
+ std::queue<std::tuple<std::string, unsigned, unsigned, std::function<void(CompilerContext&)>>> m_lowLevelFunctionGenerationQueue;
};
}
diff --git a/libsolidity/codegen/CompilerUtils.cpp b/libsolidity/codegen/CompilerUtils.cpp
index 7d382aba..477f021a 100644
--- a/libsolidity/codegen/CompilerUtils.cpp
+++ b/libsolidity/codegen/CompilerUtils.cpp
@@ -468,7 +468,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_typeOnStack);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
break;
@@ -497,7 +497,7 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp
EnumType const& enumType = dynamic_cast<decltype(enumType)>(_targetType);
solAssert(enumType.numberOfMembers() > 0, "empty enum should have caused a parser error.");
m_context << u256(enumType.numberOfMembers() - 1) << Instruction::DUP2 << Instruction::GT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
enumOverflowCheckPending = false;
}
else if (targetTypeCategory == Type::Category::FixedPoint)
@@ -807,7 +807,9 @@ void CompilerUtils::pushZeroValue(Type const& _type)
{
if (funType->location() == FunctionType::Location::Internal)
{
- m_context << m_context.errorTag();
+ m_context << m_context.lowLevelFunctionTag("$invalidFunction", 0, 0, [](CompilerContext& _context) {
+ _context.appendInvalid();
+ });
return;
}
}
@@ -820,37 +822,46 @@ void CompilerUtils::pushZeroValue(Type const& _type)
}
solAssert(referenceType->location() == DataLocation::Memory, "");
- m_context << u256(max(32u, _type.calldataEncodedSize()));
- allocateMemory();
- m_context << Instruction::DUP1;
+ TypePointer type = _type.shared_from_this();
+ m_context.callLowLevelFunction(
+ "$pushZeroValue_" + referenceType->identifier(),
+ 0,
+ 1,
+ [type](CompilerContext& _context) {
+ CompilerUtils utils(_context);
+ _context << u256(max(32u, type->calldataEncodedSize()));
+ utils.allocateMemory();
+ _context << Instruction::DUP1;
- if (auto structType = dynamic_cast<StructType const*>(&_type))
- for (auto const& member: structType->members(nullptr))
- {
- pushZeroValue(*member.type);
- storeInMemoryDynamic(*member.type);
- }
- else if (auto arrayType = dynamic_cast<ArrayType const*>(&_type))
- {
- if (arrayType->isDynamicallySized())
- {
- // zero length
- m_context << u256(0);
- storeInMemoryDynamic(IntegerType(256));
- }
- else if (arrayType->length() > 0)
- {
- m_context << arrayType->length() << Instruction::SWAP1;
- // stack: items_to_do memory_pos
- zeroInitialiseMemoryArray(*arrayType);
- // stack: updated_memory_pos
- }
- }
- else
- solAssert(false, "Requested initialisation for unknown type: " + _type.toString());
+ if (auto structType = dynamic_cast<StructType const*>(type.get()))
+ for (auto const& member: structType->members(nullptr))
+ {
+ utils.pushZeroValue(*member.type);
+ utils.storeInMemoryDynamic(*member.type);
+ }
+ else if (auto arrayType = dynamic_cast<ArrayType const*>(type.get()))
+ {
+ if (arrayType->isDynamicallySized())
+ {
+ // zero length
+ _context << u256(0);
+ utils.storeInMemoryDynamic(IntegerType(256));
+ }
+ else if (arrayType->length() > 0)
+ {
+ _context << arrayType->length() << Instruction::SWAP1;
+ // stack: items_to_do memory_pos
+ utils.zeroInitialiseMemoryArray(*arrayType);
+ // stack: updated_memory_pos
+ }
+ }
+ else
+ solAssert(false, "Requested initialisation for unknown type: " + type->toString());
- // remove the updated memory pointer
- m_context << Instruction::POP;
+ // remove the updated memory pointer
+ _context << Instruction::POP;
+ }
+ );
}
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
diff --git a/libsolidity/codegen/ContractCompiler.cpp b/libsolidity/codegen/ContractCompiler.cpp
index a0f196bc..4d33927d 100644
--- a/libsolidity/codegen/ContractCompiler.cpp
+++ b/libsolidity/codegen/ContractCompiler.cpp
@@ -106,7 +106,7 @@ void ContractCompiler::appendCallValueCheck()
{
// Throw if function is not payable but call contained ether.
m_context << Instruction::CALLVALUE;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
void ContractCompiler::appendInitAndConstructorCode(ContractDefinition const& _contract)
@@ -271,7 +271,7 @@ void ContractCompiler::appendFunctionSelector(ContractDefinition const& _contrac
appendReturnValuePacker(FunctionType(*fallback).returnParameterTypes(), _contract.isLibrary());
}
else
- m_context.appendJumpTo(m_context.errorTag());
+ m_context.appendInvalid();
for (auto const& it: interfaceFunctions)
{
@@ -486,7 +486,12 @@ bool ContractCompiler::visit(FunctionDefinition const& _function)
stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1);
- solAssert(stackLayout.size() <= 17, "Stack too deep, try removing local variables.");
+ if (stackLayout.size() > 17)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_function.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
@@ -551,6 +556,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (stackDiff < 1 || stackDiff > 16)
BOOST_THROW_EXCEPTION(
CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
for (unsigned i = 0; i < variable->type()->sizeOnStack(); ++i)
@@ -575,7 +581,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
else if (auto contract = dynamic_cast<ContractDefinition const*>(decl))
{
solAssert(contract->isLibrary(), "");
- _assembly.appendLibraryAddress(contract->name());
+ _assembly.appendLibraryAddress(contract->fullyQualifiedName());
}
else
solAssert(false, "Invalid declaration type.");
@@ -591,6 +597,7 @@ bool ContractCompiler::visit(InlineAssembly const& _inlineAssembly)
if (stackDiff > 16 || stackDiff < 1)
BOOST_THROW_EXCEPTION(
CompilerError() <<
+ errinfo_sourceLocation(_inlineAssembly.location()) <<
errinfo_comment("Stack too deep, try removing local variables.")
);
for (unsigned i = 0; i < size; ++i) {
@@ -820,6 +827,7 @@ void ContractCompiler::appendMissingFunctions()
function->accept(*this);
solAssert(m_context.nextFunctionToCompile() != function, "Compiled the wrong function?");
}
+ m_context.appendMissingLowLevelFunctions();
}
void ContractCompiler::appendModifierOrFunctionCode()
@@ -910,7 +918,9 @@ eth::AssemblyPointer ContractCompiler::cloneRuntime()
a << Instruction::DELEGATECALL;
//Propagate error condition (if DELEGATECALL pushes 0 on stack).
a << Instruction::ISZERO;
- a.appendJumpI(a.errorTag());
+ a << Instruction::ISZERO;
+ eth::AssemblyItem afterTag = a.appendJumpI().tag();
+ a << Instruction::INVALID << afterTag;
//@todo adjust for larger return values, make this dynamic.
a << u256(0x20) << u256(0) << Instruction::RETURN;
return make_shared<eth::Assembly>(a);
diff --git a/libsolidity/codegen/ExpressionCompiler.cpp b/libsolidity/codegen/ExpressionCompiler.cpp
index 3922da88..b66a3e12 100644
--- a/libsolidity/codegen/ExpressionCompiler.cpp
+++ b/libsolidity/codegen/ExpressionCompiler.cpp
@@ -250,7 +250,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
}
if (lvalueSize > 0)
{
- solAssert(itemSize + lvalueSize <= 16, "Stack too deep, try removing local variables.");
+ if (itemSize + lvalueSize > 16)
+ BOOST_THROW_EXCEPTION(
+ CompilerError() <<
+ errinfo_sourceLocation(_assignment.location()) <<
+ errinfo_comment("Stack too deep, try removing local variables.")
+ );
// value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i)
m_context << swapInstruction(itemSize + lvalueSize) << Instruction::POP;
@@ -551,20 +556,24 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arg->accept(*this);
argumentTypes.push_back(arg->annotation().type);
}
- ContractDefinition const& contract =
- dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
- // copy the contract's code into memory
- eth::Assembly const& assembly = m_context.compiledContract(contract);
- utils().fetchFreeMemoryPointer();
- // TODO we create a copy here, which is actually what we want.
- // This should be revisited at the point where we fix
- // https://github.com/ethereum/solidity/issues/1092
- // pushes size
- auto subroutine = m_context.addSubroutine(make_shared<eth::Assembly>(assembly));
- m_context << Instruction::DUP1 << subroutine;
- m_context << Instruction::DUP4 << Instruction::CODECOPY;
-
- m_context << Instruction::ADD;
+ ContractDefinition const* contract =
+ &dynamic_cast<ContractType const&>(*function.returnParameterTypes().front()).contractDefinition();
+ m_context.callLowLevelFunction(
+ "$copyContractCreationCodeToMemory_" + contract->type()->identifier(),
+ 0,
+ 1,
+ [contract](CompilerContext& _context)
+ {
+ // copy the contract's code into memory
+ eth::Assembly const& assembly = _context.compiledContract(*contract);
+ CompilerUtils(_context).fetchFreeMemoryPointer();
+ // pushes size
+ auto subroutine = _context.addSubroutine(make_shared<eth::Assembly>(assembly));
+ _context << Instruction::DUP1 << subroutine;
+ _context << Instruction::DUP4 << Instruction::CODECOPY;
+ _context << Instruction::ADD;
+ }
+ );
utils().encodeToMemory(argumentTypes, function.parameterTypes());
// now on stack: memory_end_ptr
// need: size, offset, endowment
@@ -576,7 +585,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << Instruction::CREATE;
// Check if zero (out of stack or not enough balance).
m_context << Instruction::DUP1 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
if (function.valueSet())
m_context << swapInstruction(1) << Instruction::POP;
break;
@@ -892,7 +901,7 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
solAssert(funType->location() == FunctionType::Location::DelegateCall, "");
auto contract = dynamic_cast<ContractDefinition const*>(funType->declaration().scope());
solAssert(contract && contract->isLibrary(), "");
- m_context.appendLibraryAddress(contract->name());
+ m_context.appendLibraryAddress(contract->fullyQualifiedName());
m_context << funType->externalIdentifier();
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 2);
}
@@ -1225,7 +1234,7 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
m_context << u256(fixedBytesType.numBytes());
m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO;
// out-of-bounds access throws exception
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
m_context << Instruction::BYTE;
m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
@@ -1270,7 +1279,7 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
else if (auto contract = dynamic_cast<ContractDefinition const*>(declaration))
{
if (contract->isLibrary())
- m_context.appendLibraryAddress(contract->name());
+ m_context.appendLibraryAddress(contract->fullyQualifiedName());
}
else if (dynamic_cast<EventDefinition const*>(declaration))
{
@@ -1299,6 +1308,7 @@ void ExpressionCompiler::endVisit(Literal const& _literal)
{
case Type::Category::RationalNumber:
case Type::Category::Bool:
+ case Type::Category::Integer:
m_context << type->literalValue(&_literal);
break;
case Type::Category::StringLiteral:
@@ -1406,7 +1416,7 @@ void ExpressionCompiler::appendArithmeticOperatorCode(Token::Value _operator, Ty
{
// Test for division by zero
m_context << Instruction::DUP2 << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
if (_operator == Token::Div)
m_context << (c_isSigned ? Instruction::SDIV : Instruction::DIV);
@@ -1467,7 +1477,7 @@ void ExpressionCompiler::appendShiftOperatorCode(Token::Value _operator, Type co
if (c_amountSigned)
{
m_context << u256(0) << Instruction::DUP3 << Instruction::SLT;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
switch (_operator)
@@ -1653,7 +1663,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
if (funKind == FunctionKind::External || funKind == FunctionKind::CallCode || funKind == FunctionKind::DelegateCall)
{
m_context << Instruction::DUP1 << Instruction::EXTCODESIZE << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
existenceChecked = true;
}
@@ -1689,7 +1699,7 @@ void ExpressionCompiler::appendExternalFunctionCall(
{
//Propagate error condition (if CALL pushes 0 on stack).
m_context << Instruction::ISZERO;
- m_context.appendConditionalJumpTo(m_context.errorTag());
+ m_context.appendConditionalInvalid();
}
utils().popStackSlots(remainsSize);
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index ef3da255..fcc92dbb 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -71,6 +71,8 @@ assembly::Statement Parser::parseStatement()
expectToken(Token::Colon);
assignment.variableName.location = location();
assignment.variableName.name = m_scanner->currentLiteral();
+ if (instructions().count(assignment.variableName.name))
+ fatalParserError("Identifier expected, got instruction name.");
assignment.location.end = endPosition();
expectToken(Token::Identifier);
return assignment;
@@ -101,6 +103,8 @@ assembly::Statement Parser::parseStatement()
{
// functional assignment
FunctionalAssignment funAss = createWithLocation<FunctionalAssignment>(identifier.location);
+ if (instructions().count(identifier.name))
+ fatalParserError("Cannot use instruction names for identifier names.");
m_scanner->next();
funAss.variableName = identifier;
funAss.value.reset(new Statement(parseExpression()));
@@ -130,7 +134,7 @@ assembly::Statement Parser::parseExpression()
return operation;
}
-assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+std::map<string, dev::solidity::Instruction> const& Parser::instructions()
{
// Allowed instructions, lowercase names.
static map<string, dev::solidity::Instruction> s_instructions;
@@ -151,7 +155,11 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
// add alias for selfdestruct
s_instructions["selfdestruct"] = solidity::Instruction::SUICIDE;
}
+ return s_instructions;
+}
+assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
+{
Statement ret;
switch (m_scanner->currentToken())
{
@@ -170,9 +178,9 @@ assembly::Statement Parser::parseElementaryOperation(bool _onlySinglePusher)
else
literal = m_scanner->currentLiteral();
// first search the set of instructions.
- if (s_instructions.count(literal))
+ if (instructions().count(literal))
{
- dev::solidity::Instruction const& instr = s_instructions[literal];
+ dev::solidity::Instruction const& instr = instructions().at(literal);
if (_onlySinglePusher)
{
InstructionInfo info = dev::solidity::instructionInfo(instr);
@@ -207,6 +215,8 @@ assembly::VariableDeclaration Parser::parseVariableDeclaration()
VariableDeclaration varDecl = createWithLocation<VariableDeclaration>();
expectToken(Token::Let);
varDecl.name = m_scanner->currentLiteral();
+ if (instructions().count(varDecl.name))
+ fatalParserError("Cannot use instruction names for identifier names.");
expectToken(Token::Identifier);
expectToken(Token::Colon);
expectToken(Token::Assign);
diff --git a/libsolidity/inlineasm/AsmParser.h b/libsolidity/inlineasm/AsmParser.h
index 8b56ab90..643548dd 100644
--- a/libsolidity/inlineasm/AsmParser.h
+++ b/libsolidity/inlineasm/AsmParser.h
@@ -64,6 +64,7 @@ protected:
Statement parseStatement();
/// Parses a functional expression that has to push exactly one stack element
Statement parseExpression();
+ std::map<std::string, dev::solidity::Instruction> const& instructions();
Statement parseElementaryOperation(bool _onlySinglePusher = false);
VariableDeclaration parseVariableDeclaration();
FunctionalInstruction parseFunctionalInstruction(Statement&& _instruction);
diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp
index a31df584..3335c40e 100644
--- a/libsolidity/interface/CompilerStack.cpp
+++ b/libsolidity/interface/CompilerStack.cpp
@@ -21,6 +21,7 @@
* Full-stack compiler that converts a source code string to bytecode.
*/
+
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/Version.h>
@@ -111,6 +112,7 @@ bool CompilerStack::parse()
{
//reset
m_errors.clear();
+ ASTNode::resetID();
m_parseSuccessful = false;
if (SemVerVersion{string(VersionString)}.isPrerelease())
@@ -180,11 +182,15 @@ bool CompilerStack::parse()
if (!resolver.updateDeclaration(*m_globalContext->currentThis())) return false;
if (!resolver.updateDeclaration(*m_globalContext->currentSuper())) return false;
if (!resolver.resolveNamesAndTypes(*contract)) return false;
- m_contracts[contract->name()].contract = contract;
- }
- if (!checkLibraryNameClashes())
- noErrors = false;
+ // Note that we now reference contracts by their fully qualified names, and
+ // thus contracts can only conflict if declared in the same source file. This
+ // already causes a double-declaration error elsewhere, so we do not report
+ // an error here and instead silently drop any additional contracts we find.
+
+ if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
+ m_contracts[contract->fullyQualifiedName()].contract = contract;
+ }
for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes())
@@ -201,7 +207,13 @@ bool CompilerStack::parse()
else
noErrors = false;
- m_contracts[contract->name()].contract = contract;
+ // Note that we now reference contracts by their fully qualified names, and
+ // thus contracts can only conflict if declared in the same source file. This
+ // already causes a double-declaration error elsewhere, so we do not report
+ // an error here and instead silently drop any additional contracts we find.
+
+ if (m_contracts.find(contract->fullyQualifiedName()) == m_contracts.end())
+ m_contracts[contract->fullyQualifiedName()].contract = contract;
}
if (noErrors)
@@ -315,6 +327,27 @@ string const* CompilerStack::runtimeSourceMapping(string const& _contractName) c
return c.runtimeSourceMapping.get();
}
+std::string const CompilerStack::filesystemFriendlyName(string const& _contractName) const
+{
+ // Look up the contract (by its fully-qualified name)
+ Contract const& matchContract = m_contracts.at(_contractName);
+ // Check to see if it could collide on name
+ for (auto const& contract: m_contracts)
+ {
+ if (contract.second.contract->name() == matchContract.contract->name() &&
+ contract.second.contract != matchContract.contract)
+ {
+ // If it does, then return its fully-qualified name, made fs-friendly
+ std::string friendlyName = boost::algorithm::replace_all_copy(_contractName, "/", "_");
+ boost::algorithm::replace_all(friendlyName, ":", "_");
+ boost::algorithm::replace_all(friendlyName, ".", "_");
+ return friendlyName;
+ }
+ }
+ // If no collision, return the contract's name
+ return matchContract.contract->name();
+}
+
eth::LinkerObject const& CompilerStack::object(string const& _contractName) const
{
return contract(_contractName).object;
@@ -569,37 +602,6 @@ void CompilerStack::resolveImports()
swap(m_sourceOrder, sourceOrder);
}
-bool CompilerStack::checkLibraryNameClashes()
-{
- bool clashFound = false;
- map<string, SourceLocation> libraries;
- for (Source const* source: m_sourceOrder)
- for (ASTPointer<ASTNode> const& node: source->ast->nodes())
- if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
- if (contract->isLibrary())
- {
- if (libraries.count(contract->name()))
- {
- auto err = make_shared<Error>(Error::Type::DeclarationError);
- *err <<
- errinfo_sourceLocation(contract->location()) <<
- errinfo_comment(
- "Library \"" + contract->name() + "\" declared twice "
- "(will create ambiguities during linking)."
- ) <<
- errinfo_secondarySourceLocation(SecondarySourceLocation().append(
- "The other declaration is here:", libraries[contract->name()]
- ));
-
- m_errors.push_back(err);
- clashFound = true;
- }
- else
- libraries[contract->name()] = contract->location();
- }
- return !clashFound;
-}
-
string CompilerStack::absolutePath(string const& _path, string const& _reference) const
{
using path = boost::filesystem::path;
@@ -622,13 +624,17 @@ void CompilerStack::compileContract(
map<ContractDefinition const*, eth::Assembly const*>& _compiledContracts
)
{
- if (_compiledContracts.count(&_contract) || !_contract.annotation().isFullyImplemented)
+ if (
+ _compiledContracts.count(&_contract) ||
+ !_contract.annotation().isFullyImplemented ||
+ !_contract.annotation().hasPublicConstructor
+ )
return;
for (auto const* dependency: _contract.annotation().contractDependencies)
compileContract(*dependency, _compiledContracts);
shared_ptr<Compiler> compiler = make_shared<Compiler>(m_optimize, m_optimizeRuns);
- Contract& compiledContract = m_contracts.at(_contract.name());
+ Contract& compiledContract = m_contracts.at(_contract.fullyQualifiedName());
string onChainMetadata = createOnChainMetadata(compiledContract);
bytes cborEncodedMetadata =
// CBOR-encoding of {"bzzr0": dev::swarmHash(onChainMetadata)}
@@ -674,10 +680,27 @@ CompilerStack::Contract const& CompilerStack::contract(string const& _contractNa
for (auto const& it: m_sources)
for (ASTPointer<ASTNode> const& node: it.second.ast->nodes())
if (auto contract = dynamic_cast<ContractDefinition const*>(node.get()))
- contractName = contract->name();
+ contractName = contract->fullyQualifiedName();
auto it = m_contracts.find(contractName);
- if (it == m_contracts.end())
+ // To provide a measure of backward-compatibility, if a contract is not located by its
+ // fully-qualified name, a lookup will be attempted purely on the contract's name to see
+ // if anything will satisfy.
+ if (it == m_contracts.end() && contractName.find(":") == string::npos)
+ {
+ for (auto const& contractEntry: m_contracts)
+ {
+ stringstream ss;
+ ss.str(contractEntry.first);
+ // All entries are <source>:<contract>
+ string source;
+ string foundName;
+ getline(ss, source, ':');
+ getline(ss, foundName, ':');
+ if (foundName == contractName) return contractEntry.second;
+ }
+ // If we get here, both lookup methods failed.
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Contract " + _contractName + " not found."));
+ }
return it->second;
}
@@ -695,7 +718,7 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
Json::Value meta;
meta["version"] = 1;
meta["language"] = "Solidity";
- meta["compiler"]["version"] = VersionString;
+ meta["compiler"]["version"] = VersionStringStrict;
meta["sources"] = Json::objectValue;
for (auto const& s: m_sources)
@@ -703,10 +726,15 @@ string CompilerStack::createOnChainMetadata(Contract const& _contract) const
solAssert(s.second.scanner, "Scanner not available");
meta["sources"][s.first]["keccak256"] =
"0x" + toHex(dev::keccak256(s.second.scanner->source()).asBytes());
- meta["sources"][s.first]["urls"] = Json::arrayValue;
- meta["sources"][s.first]["urls"].append(
- "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes())
- );
+ if (m_metadataLiteralSources)
+ meta["sources"][s.first]["content"] = s.second.scanner->source();
+ else
+ {
+ meta["sources"][s.first]["urls"] = Json::arrayValue;
+ meta["sources"][s.first]["urls"].append(
+ "bzzr://" + toHex(dev::swarmHash(s.second.scanner->source()).asBytes())
+ );
+ }
}
meta["settings"]["optimizer"]["enabled"] = m_optimize;
meta["settings"]["optimizer"]["runs"] = m_optimizeRuns;
diff --git a/libsolidity/interface/CompilerStack.h b/libsolidity/interface/CompilerStack.h
index d49a8df1..9ee70215 100644
--- a/libsolidity/interface/CompilerStack.h
+++ b/libsolidity/interface/CompilerStack.h
@@ -148,6 +148,10 @@ public:
/// @returns the string that provides a mapping between runtime bytecode and sourcecode.
/// if the contract does not (yet) have bytecode.
std::string const* runtimeSourceMapping(std::string const& _contractName = "") const;
+
+ /// @returns either the contract's name or a mixture of its name and source file, sanitized for filesystem use
+ std::string const filesystemFriendlyName(std::string const& _contractName) const;
+
/// @returns hash of the runtime bytecode for the contract, i.e. the code that is
/// returned by the constructor or the zero-h256 if the contract still needs to be linked or
/// does not have runtime code.
@@ -173,6 +177,7 @@ public:
/// Can be one of 4 types defined at @c DocumentationType
Json::Value const& metadata(std::string const& _contractName, DocumentationType _type) const;
std::string const& onChainMetadata(std::string const& _contractName) const;
+ void useMetadataLiteralSources(bool _metadataLiteralSources) { m_metadataLiteralSources = _metadataLiteralSources; }
/// @returns the previously used scanner, useful for counting lines during error reporting.
Scanner const& scanner(std::string const& _sourceName = "") const;
@@ -230,9 +235,6 @@ private:
StringMap loadMissingSources(SourceUnit const& _ast, std::string const& _path);
std::string applyRemapping(std::string const& _path, std::string const& _context);
void resolveImports();
- /// Checks whether there are libraries with the same name, reports that as an error and
- /// @returns false in this case.
- bool checkLibraryNameClashes();
/// @returns the absolute path corresponding to @a _path relative to @a _reference.
std::string absolutePath(std::string const& _path, std::string const& _reference) const;
/// Helper function to return path converted strings.
@@ -273,6 +275,7 @@ private:
std::map<std::string const, Contract> m_contracts;
std::string m_formalTranslation;
ErrorList m_errors;
+ bool m_metadataLiteralSources = false;
};
}
diff --git a/libsolidity/interface/InterfaceHandler.cpp b/libsolidity/interface/InterfaceHandler.cpp
index 9944bb22..6c1bb0c4 100644
--- a/libsolidity/interface/InterfaceHandler.cpp
+++ b/libsolidity/interface/InterfaceHandler.cpp
@@ -30,20 +30,6 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe
{
Json::Value abi(Json::arrayValue);
- auto populateParameters = [](vector<string> const& _paramNames, vector<string> const& _paramTypes)
- {
- Json::Value params(Json::arrayValue);
- solAssert(_paramNames.size() == _paramTypes.size(), "Names and types vector size does not match");
- for (unsigned i = 0; i < _paramNames.size(); ++i)
- {
- Json::Value param;
- param["name"] = _paramNames[i];
- param["type"] = _paramTypes[i];
- params.append(param);
- }
- return params;
- };
-
for (auto it: _contractDef.interfaceFunctions())
{
auto externalFunctionType = it.second->interfaceFunctionType();
@@ -52,13 +38,15 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe
method["name"] = it.second->declaration().name();
method["constant"] = it.second->isConstant();
method["payable"] = it.second->isPayable();
- method["inputs"] = populateParameters(
+ method["inputs"] = formatTypeList(
externalFunctionType->parameterNames(),
- externalFunctionType->parameterTypeNames(_contractDef.isLibrary())
+ externalFunctionType->parameterTypes(),
+ _contractDef.isLibrary()
);
- method["outputs"] = populateParameters(
+ method["outputs"] = formatTypeList(
externalFunctionType->returnParameterNames(),
- externalFunctionType->returnParameterTypeNames(_contractDef.isLibrary())
+ externalFunctionType->returnParameterTypes(),
+ _contractDef.isLibrary()
);
abi.append(method);
}
@@ -69,9 +57,10 @@ Json::Value InterfaceHandler::abiInterface(ContractDefinition const& _contractDe
auto externalFunction = FunctionType(*_contractDef.constructor(), false).interfaceFunctionType();
solAssert(!!externalFunction, "");
method["payable"] = externalFunction->isPayable();
- method["inputs"] = populateParameters(
+ method["inputs"] = formatTypeList(
externalFunction->parameterNames(),
- externalFunction->parameterTypeNames(_contractDef.isLibrary())
+ externalFunction->parameterTypes(),
+ _contractDef.isLibrary()
);
abi.append(method);
}
@@ -179,6 +168,25 @@ Json::Value InterfaceHandler::devDocumentation(ContractDefinition const& _contra
return doc;
}
+Json::Value InterfaceHandler::formatTypeList(
+ vector<string> const& _names,
+ vector<TypePointer> const& _types,
+ bool _forLibrary
+)
+{
+ Json::Value params(Json::arrayValue);
+ solAssert(_names.size() == _types.size(), "Names and types vector size does not match");
+ for (unsigned i = 0; i < _names.size(); ++i)
+ {
+ solAssert(_types[i], "");
+ Json::Value param;
+ param["name"] = _names[i];
+ param["type"] = _types[i]->canonicalName(_forLibrary);
+ params.append(param);
+ }
+ return params;
+}
+
string InterfaceHandler::extractDoc(multimap<string, DocTag> const& _tags, string const& _name)
{
string value;
diff --git a/libsolidity/interface/InterfaceHandler.h b/libsolidity/interface/InterfaceHandler.h
index b7e1bb00..56927d44 100644
--- a/libsolidity/interface/InterfaceHandler.h
+++ b/libsolidity/interface/InterfaceHandler.h
@@ -37,6 +37,8 @@ namespace solidity
// Forward declarations
class ContractDefinition;
+class Type;
+using TypePointer = std::shared_ptr<Type const>;
struct DocTag;
enum class DocumentationType: uint8_t;
@@ -84,6 +86,14 @@ public:
static Json::Value devDocumentation(ContractDefinition const& _contractDef);
private:
+ /// @returns a json value suitable for a list of types in function input or output
+ /// parameters or other places. If @a _forLibrary is true, complex types are referenced
+ /// by name, otherwise they are anonymously expanded.
+ static Json::Value formatTypeList(
+ std::vector<std::string> const& _names,
+ std::vector<TypePointer> const& _types,
+ bool _forLibrary
+ );
/// @returns concatenation of all content under the given tag name.
static std::string extractDoc(std::multimap<std::string, DocTag> const& _tags, std::string const& _name);
};
diff --git a/libsolidity/interface/Version.cpp b/libsolidity/interface/Version.cpp
index ff66f039..0d23f9c3 100644
--- a/libsolidity/interface/Version.cpp
+++ b/libsolidity/interface/Version.cpp
@@ -38,6 +38,10 @@ string const dev::solidity::VersionString =
(string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) +
(string(SOL_VERSION_BUILDINFO).empty() ? "" : "+" + string(SOL_VERSION_BUILDINFO));
+string const dev::solidity::VersionStringStrict =
+ string(dev::solidity::VersionNumber) +
+ (string(SOL_VERSION_PRERELEASE).empty() ? "" : "-" + string(SOL_VERSION_PRERELEASE)) +
+ (string(SOL_VERSION_COMMIT).empty() ? "" : "+" + string(SOL_VERSION_COMMIT));
bytes dev::solidity::binaryVersion()
{
diff --git a/libsolidity/interface/Version.h b/libsolidity/interface/Version.h
index 5b07b3f4..24c3555d 100644
--- a/libsolidity/interface/Version.h
+++ b/libsolidity/interface/Version.h
@@ -32,6 +32,7 @@ namespace solidity
extern char const* VersionNumber;
extern std::string const VersionString;
+extern std::string const VersionStringStrict;
/// @returns a binary form of the version string, where A.B.C-HASH is encoded such that
/// the first byte is zero, the following three bytes encode A B and C (interpreted as decimals)
diff --git a/libsolidity/parsing/DocStringParser.cpp b/libsolidity/parsing/DocStringParser.cpp
index bbee35f5..8e912126 100644
--- a/libsolidity/parsing/DocStringParser.cpp
+++ b/libsolidity/parsing/DocStringParser.cpp
@@ -1,14 +1,19 @@
#include <libsolidity/parsing/DocStringParser.h>
-#include <boost/range/irange.hpp>
#include <libsolidity/interface/Utils.h>
+#include <boost/range/irange.hpp>
+#include <boost/range/algorithm.hpp>
+
using namespace std;
using namespace dev;
using namespace dev::solidity;
-static inline string::const_iterator skipLineOrEOS(
+namespace
+{
+
+string::const_iterator skipLineOrEOS(
string::const_iterator _nlPos,
string::const_iterator _end
)
@@ -16,14 +21,34 @@ static inline string::const_iterator skipLineOrEOS(
return (_nlPos == _end) ? _end : ++_nlPos;
}
-static inline string::const_iterator firstSpaceOrNl(
+string::const_iterator firstSpaceOrTab(
string::const_iterator _pos,
string::const_iterator _end
)
{
- auto spacePos = find(_pos, _end, ' ');
- auto nlPos = find(_pos, _end, '\n');
- return (spacePos < nlPos) ? spacePos : nlPos;
+ return boost::range::find_first_of(make_pair(_pos, _end), " \t");
+}
+
+string::const_iterator firstWhitespaceOrNewline(
+ string::const_iterator _pos,
+ string::const_iterator _end
+)
+{
+ return boost::range::find_first_of(make_pair(_pos, _end), " \t\n");
+}
+
+
+string::const_iterator skipWhitespace(
+ string::const_iterator _pos,
+ string::const_iterator _end
+)
+{
+ auto currPos = _pos;
+ while (currPos != _end && (*currPos == ' ' || *currPos == '\t'))
+ currPos += 1;
+ return currPos;
+}
+
}
bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
@@ -43,7 +68,7 @@ bool DocStringParser::parse(string const& _docString, ErrorList& _errors)
if (tagPos != end && tagPos < nlPos)
{
// we found a tag
- auto tagNameEndPos = firstSpaceOrNl(tagPos, end);
+ auto tagNameEndPos = firstWhitespaceOrNewline(tagPos, end);
if (tagNameEndPos == end)
{
appendError("End of tag " + string(tagPos, tagNameEndPos) + "not found");
@@ -75,27 +100,40 @@ DocStringParser::iter DocStringParser::parseDocTagLine(iter _pos, iter _end, boo
{
solAssert(!!m_lastTag, "");
auto nlPos = find(_pos, _end, '\n');
- if (_appending && _pos < _end && *_pos != ' ')
+ if (_appending && _pos < _end && *_pos != ' ' && *_pos != '\t')
m_lastTag->content += " ";
+ else if (!_appending)
+ _pos = skipWhitespace(_pos, _end);
copy(_pos, nlPos, back_inserter(m_lastTag->content));
return skipLineOrEOS(nlPos, _end);
}
DocStringParser::iter DocStringParser::parseDocTagParam(iter _pos, iter _end)
{
- // find param name
- auto currPos = find(_pos, _end, ' ');
- if (currPos == _end)
+ // find param name start
+ auto nameStartPos = skipWhitespace(_pos, _end);
+ if (nameStartPos == _end)
{
- appendError("End of param name not found" + string(_pos, _end));
+ appendError("No param name given");
return _end;
}
+ auto nameEndPos = firstSpaceOrTab(nameStartPos, _end);
+ if (nameEndPos == _end)
+ {
+ appendError("End of param name not found: " + string(nameStartPos, _end));
+ return _end;
+ }
+ auto paramName = string(nameStartPos, nameEndPos);
- auto paramName = string(_pos, currPos);
+ auto descStartPos = skipWhitespace(nameEndPos, _end);
+ if (descStartPos == _end)
+ {
+ appendError("No description given for param " + paramName);
+ return _end;
+ }
- currPos += 1;
- auto nlPos = find(currPos, _end, '\n');
- auto paramDesc = string(currPos, nlPos);
+ auto nlPos = find(descStartPos, _end, '\n');
+ auto paramDesc = string(descStartPos, nlPos);
newTag("param");
m_lastTag->paramName = paramName;
m_lastTag->content = paramDesc;
diff --git a/scripts/install_deps.sh b/scripts/install_deps.sh
index 255176ab..f21c48d0 100755
--- a/scripts/install_deps.sh
+++ b/scripts/install_deps.sh
@@ -138,11 +138,13 @@ case $(uname -s) in
# All our dependencies can be found in the Arch Linux official repositories.
# See https://wiki.archlinux.org/index.php/Official_repositories
+ # Also adding ethereum-git to allow for testing with the `eth` client
sudo pacman -Sy \
base-devel \
boost \
cmake \
git \
+ ethereum-git \
;;
#------------------------------------------------------------------------------
diff --git a/scripts/tests.sh b/scripts/tests.sh
index dfbda734..f2142946 100755
--- a/scripts/tests.sh
+++ b/scripts/tests.sh
@@ -30,20 +30,12 @@ set -e
REPO_ROOT="$(dirname "$0")"/..
- # Compile all files in std and examples.
+echo "Running commandline tests..."
+"$REPO_ROOT/test/cmdlineTests.sh"
-for f in "$REPO_ROOT"/std/*.sol
-do
- echo "Compiling $f..."
- set +e
- output=$("$REPO_ROOT"/build/solc/solc "$f" 2>&1)
- failed=$?
- # Remove the pre-release warning from the compiler output
- output=$(echo "$output" | grep -v 'pre-release')
- echo "$output"
- set -e
- test -z "$output" -a "$failed" -eq 0
-done
+echo "Checking that StandardToken.sol, owned.sol and mortal.sol produce bytecode..."
+output=$("$REPO_ROOT"/build/solc/solc --bin "$REPO_ROOT"/std/*.sol 2>/dev/null | grep "ffff" | wc -l)
+test "$output" = "3"
# This conditional is only needed because we don't have a working Homebrew
# install for `eth` at the time of writing, so we unzip the ZIP file locally
diff --git a/solc/CommandLineInterface.cpp b/solc/CommandLineInterface.cpp
index e322455b..dd80e189 100644
--- a/solc/CommandLineInterface.cpp
+++ b/solc/CommandLineInterface.cpp
@@ -22,28 +22,8 @@
*/
#include "CommandLineInterface.h"
-#ifdef _WIN32 // windows
- #include <io.h>
- #define isatty _isatty
- #define fileno _fileno
-#else // unix
- #include <unistd.h>
-#endif
-#include <string>
-#include <iostream>
-#include <fstream>
-
-#include <boost/filesystem.hpp>
-#include <boost/filesystem/operations.hpp>
-#include <boost/algorithm/string.hpp>
-
#include "solidity/BuildInfo.h"
-#include <libdevcore/Common.h>
-#include <libdevcore/CommonData.h>
-#include <libdevcore/CommonIO.h>
-#include <libdevcore/JSON.h>
-#include <libevmasm/Instruction.h>
-#include <libevmasm/GasMeter.h>
+
#include <libsolidity/interface/Version.h>
#include <libsolidity/parsing/Scanner.h>
#include <libsolidity/parsing/Parser.h>
@@ -54,9 +34,31 @@
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/interface/SourceReferenceFormatter.h>
#include <libsolidity/interface/GasEstimator.h>
-#include <libsolidity/inlineasm/AsmParser.h>
#include <libsolidity/formal/Why3Translator.h>
+#include <libevmasm/Instruction.h>
+#include <libevmasm/GasMeter.h>
+
+#include <libdevcore/Common.h>
+#include <libdevcore/CommonData.h>
+#include <libdevcore/CommonIO.h>
+#include <libdevcore/JSON.h>
+
+#include <boost/filesystem.hpp>
+#include <boost/filesystem/operations.hpp>
+#include <boost/algorithm/string.hpp>
+
+#ifdef _WIN32 // windows
+ #include <io.h>
+ #define isatty _isatty
+ #define fileno _fileno
+#else // unix
+ #include <unistd.h>
+#endif
+#include <string>
+#include <iostream>
+#include <fstream>
+
using namespace std;
namespace po = boost::program_options;
@@ -98,6 +100,7 @@ static string const g_strSrcMap = "srcmap";
static string const g_strSrcMapRuntime = "srcmap-runtime";
static string const g_strVersion = "version";
static string const g_stdinFileNameStr = "<stdin>";
+static string const g_strMetadataLiteral = "metadata-literal";
static string const g_argAbi = g_strAbi;
static string const g_argAddStandard = g_strAddStandard;
@@ -126,6 +129,7 @@ static string const g_argOutputDir = g_strOutputDir;
static string const g_argSignatureHashes = g_strSignatureHashes;
static string const g_argVersion = g_strVersion;
static string const g_stdinFileName = g_stdinFileNameStr;
+static string const g_argMetadataLiteral = g_strMetadataLiteral;
/// Possible arguments to for --combined-json
static set<string> const g_combinedJsonArgs{
@@ -186,7 +190,7 @@ void CommandLineInterface::handleBinary(string const& _contract)
if (m_args.count(g_argBinary))
{
if (m_args.count(g_argOutputDir))
- createFile(_contract + ".bin", m_compiler->object(_contract).toHex());
+ createFile(m_compiler->filesystemFriendlyName(_contract) + ".bin", m_compiler->object(_contract).toHex());
else
{
cout << "Binary: " << endl;
@@ -422,7 +426,9 @@ bool CommandLineInterface::parseLibraryOption(string const& _input)
for (string const& lib: libraries)
if (!lib.empty())
{
- auto colon = lib.find(':');
+ //search for last colon in string as our binaries output placeholders in the form of file:Name
+ //so we need to search for the second `:` in the string
+ auto colon = lib.rfind(':');
if (colon == string::npos)
{
cerr << "Colon separator missing in library address specifier \"" << lib << "\"" << endl;
@@ -432,6 +438,11 @@ bool CommandLineInterface::parseLibraryOption(string const& _input)
string addrString(lib.begin() + colon + 1, lib.end());
boost::trim(libName);
boost::trim(addrString);
+ if (!passesAddressChecksum(addrString, false))
+ {
+ cerr << "Invalid checksum on library address \"" << libName << "\": " << addrString << endl;
+ return false;
+ }
bytes binAddr = fromHex(addrString);
h160 address(binAddr, h160::AlignRight);
if (binAddr.size() > 20 || address == h160())
@@ -511,7 +522,8 @@ Allowed options)",
g_argLink.c_str(),
"Switch to linker mode, ignoring all options apart from --libraries "
"and modify binaries in place."
- );
+ )
+ (g_argMetadataLiteral.c_str(), "Store referenced sources are literal data in the metadata output.");
po::options_description outputComponents("Output Components");
outputComponents.add_options()
(g_argAst.c_str(), "AST of all source files.")
@@ -634,6 +646,8 @@ bool CommandLineInterface::processInput()
auto scannerFromSourceName = [&](string const& _sourceName) -> solidity::Scanner const& { return m_compiler->scanner(_sourceName); };
try
{
+ if (m_args.count(g_argMetadataLiteral) > 0)
+ m_compiler->useMetadataLiteralSources(true);
if (m_args.count(g_argInputFile))
m_compiler->setRemappings(m_args[g_argInputFile].as<vector<string>>());
for (auto const& sourceCode: m_sourceCodes)
@@ -907,7 +921,6 @@ void CommandLineInterface::writeLinkedFiles()
bool CommandLineInterface::assemble()
{
- //@TODO later, we will use the convenience interface and should also remove the include above
bool successful = true;
map<string, shared_ptr<Scanner>> scanners;
for (auto const& src: m_sourceCodes)
@@ -921,6 +934,7 @@ bool CommandLineInterface::assemble()
m_assemblyStacks[src.first].assemble();
}
for (auto const& stack: m_assemblyStacks)
+ {
for (auto const& error: stack.second.errors())
SourceReferenceFormatter::printExceptionInformation(
cerr,
@@ -928,6 +942,9 @@ bool CommandLineInterface::assemble()
(error->type() == Error::Type::Warning) ? "Warning" : "Error",
[&](string const& _source) -> Scanner const& { return *scanners.at(_source); }
);
+ if (!Error::containsOnlyWarnings(stack.second.errors()))
+ successful = false;
+ }
return successful;
}
diff --git a/solc/CommandLineInterface.h b/solc/CommandLineInterface.h
index b8fc1823..bcfc43d7 100644
--- a/solc/CommandLineInterface.h
+++ b/solc/CommandLineInterface.h
@@ -21,12 +21,14 @@
*/
#pragma once
-#include <memory>
-#include <boost/program_options.hpp>
-#include <boost/filesystem/path.hpp>
#include <libsolidity/interface/CompilerStack.h>
#include <libsolidity/inlineasm/AsmStack.h>
+#include <boost/program_options.hpp>
+#include <boost/filesystem/path.hpp>
+
+#include <memory>
+
namespace dev
{
namespace solidity
diff --git a/std/StandardToken.sol b/std/StandardToken.sol
index 4ff1b8f9..4dad8541 100644
--- a/std/StandardToken.sol
+++ b/std/StandardToken.sol
@@ -3,31 +3,43 @@ pragma solidity ^0.4.0;
import "./Token.sol";
contract StandardToken is Token {
- uint256 public totalSupply;
- mapping (address => uint256) public balanceOf;
+ uint256 supply;
+ mapping (address => uint256) balance;
mapping (address =>
- mapping (address => uint256)) public allowance;
+ mapping (address => uint256)) m_allowance;
function StandardToken(address _initialOwner, uint256 _supply) {
- totalSupply = _supply;
- balanceOf[_initialOwner] = _supply;
+ supply = _supply;
+ balance[_initialOwner] = _supply;
+ }
+
+ function balanceOf(address _account) constant returns (uint) {
+ return balance[_account];
+ }
+
+ function totalSupply() constant returns (uint) {
+ return supply;
}
function transfer(address _to, uint256 _value) returns (bool success) {
- if (balanceOf[msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]) {
- balanceOf[msg.sender] -= _value;
- balanceOf[_to] += _value;
- Transfer(msg.sender, _to, _value);
+ return doTransfer(msg.sender, _to, _value);
+ }
+
+ function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
+ if (m_allowance[_from][msg.sender] >= _value) {
+ if (doTransfer(_from, _to, _value)) {
+ m_allowance[_from][msg.sender] -= _value;
+ }
return true;
} else {
return false;
}
}
- function transferFrom(address _from, address _to, uint256 _value) returns (bool success) {
- if (allowance[_from][msg.sender] >= _value && balanceOf[_to] + _value >= balanceOf[_to]) {
- allowance[_from][msg.sender] -= _value;
- balanceOf[_to] += _value;
+ function doTransfer(address _from, address _to, uint _value) internal returns (bool success) {
+ if (balance[_from] >= _value && balance[_to] + _value >= balance[_to]) {
+ balance[_from] -= _value;
+ balance[_to] += _value;
Transfer(_from, _to, _value);
return true;
} else {
@@ -36,8 +48,12 @@ contract StandardToken is Token {
}
function approve(address _spender, uint256 _value) returns (bool success) {
- allowance[msg.sender][_spender] = _value;
+ m_allowance[msg.sender][_spender] = _value;
Approval(msg.sender, _spender, _value);
return true;
}
+
+ function allowance(address _owner, address _spender) constant returns (uint256 remaining) {
+ return m_allowance[_owner][_spender];
+ }
}
diff --git a/test/cmdlineTests.sh b/test/cmdlineTests.sh
new file mode 100755
index 00000000..fc48654a
--- /dev/null
+++ b/test/cmdlineTests.sh
@@ -0,0 +1,51 @@
+#!/usr/bin/env bash
+
+#------------------------------------------------------------------------------
+# Bash script to run commandline Solidity tests.
+#
+# The documentation for solidity is hosted at:
+#
+# https://solidity.readthedocs.org
+#
+# ------------------------------------------------------------------------------
+# This file is part of solidity.
+#
+# solidity is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# solidity is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with solidity. If not, see <http://www.gnu.org/licenses/>
+#
+# (c) 2016 solidity contributors.
+#------------------------------------------------------------------------------
+
+set -e
+
+REPO_ROOT="$(dirname "$0")"/..
+SOLC="$REPO_ROOT/build/solc/solc"
+
+ # Compile all files in std and examples.
+
+for f in "$REPO_ROOT"/std/*.sol
+do
+ echo "Compiling $f..."
+ set +e
+ output=$("$SOLC" "$f" 2>&1)
+ failed=$?
+ # Remove the pre-release warning from the compiler output
+ output=$(echo "$output" | grep -v 'pre-release')
+ echo "$output"
+ set -e
+ test -z "$output" -a "$failed" -eq 0
+done
+
+# Test library checksum
+echo 'contact C {}' | "$SOLC" --link --libraries a:0x90f20564390eAe531E810af625A22f51385Cd222
+! echo 'contract C {}' | "$SOLC" --link --libraries a:0x80f20564390eAe531E810af625A22f51385Cd222 2>/dev/null
diff --git a/test/libdevcore/Checksum.cpp b/test/libdevcore/Checksum.cpp
new file mode 100644
index 00000000..32260888
--- /dev/null
+++ b/test/libdevcore/Checksum.cpp
@@ -0,0 +1,83 @@
+/*
+ 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/>.
+*/
+/**
+ * Unit tests for the address checksum.
+ */
+
+#include <libdevcore/CommonData.h>
+
+#include "../TestHelper.h"
+
+using namespace std;
+
+namespace dev
+{
+namespace test
+{
+
+BOOST_AUTO_TEST_SUITE(Checksum)
+
+BOOST_AUTO_TEST_CASE(regular)
+{
+ BOOST_CHECK(passesAddressChecksum("0x5aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
+ BOOST_CHECK(passesAddressChecksum("0xfB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true));
+ BOOST_CHECK(passesAddressChecksum("0xdbF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true));
+ BOOST_CHECK(passesAddressChecksum("0xD1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true));
+}
+
+BOOST_AUTO_TEST_CASE(regular_negative)
+{
+ BOOST_CHECK(!passesAddressChecksum("0x6aAeb6053F3E94C9b9A09f33669435E7Ef1BeAed", true));
+ BOOST_CHECK(!passesAddressChecksum("0xeB6916095ca1df60bB79Ce92cE3Ea74c37c5d359", true));
+ BOOST_CHECK(!passesAddressChecksum("0xebF03B407c01E7cD3CBea99509d93f8DDDC8C6FB", true));
+ BOOST_CHECK(!passesAddressChecksum("0xE1220A0cf47c7B9Be7A2E6BA89F429762e7b9aDb", true));
+}
+
+BOOST_AUTO_TEST_CASE(regular_invalid_length)
+{
+ BOOST_CHECK(passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad60", true));
+ BOOST_CHECK(!passesAddressChecksum("0x9426cbfc57389778d313268E7F85F1CDc2fdad6", true));
+ BOOST_CHECK(passesAddressChecksum("0x08A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true));
+ BOOST_CHECK(!passesAddressChecksum("0x8A61851FFa4637dE289D630Ae8c5dFb0ff9171F", true));
+ BOOST_CHECK(passesAddressChecksum("0x00c40cC30cb4675673c9ee382de805c19734986A", true));
+ BOOST_CHECK(!passesAddressChecksum("0xc40cC30cb4675673c9ee382de805c19734986A", true));
+ BOOST_CHECK(passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a00", true));
+ BOOST_CHECK(!passesAddressChecksum("0xC40CC30cb4675673C9ee382dE805c19734986a", true));
+}
+
+BOOST_AUTO_TEST_CASE(homocaps_valid)
+{
+ BOOST_CHECK(passesAddressChecksum("0x52908400098527886E0F7030069857D2E4169EE7", true));
+ BOOST_CHECK(passesAddressChecksum("0x8617E340B3D01FA5F11F306F4090FD50E238070D", true));
+ BOOST_CHECK(passesAddressChecksum("0xde709f2102306220921060314715629080e2fb77", true));
+ BOOST_CHECK(passesAddressChecksum("0x27b1fdb04752bbc536007a920d24acb045561c26", true));
+}
+
+BOOST_AUTO_TEST_CASE(homocaps_invalid)
+{
+ string upper = "0x00AA0000000012400000000DDEEFF000000000BB";
+ BOOST_CHECK(passesAddressChecksum(upper, false));
+ BOOST_CHECK(!passesAddressChecksum(upper, true));
+ string lower = "0x11aa000000000000000d00cc00000000000000bb";
+ BOOST_CHECK(passesAddressChecksum(lower, false));
+ BOOST_CHECK(!passesAddressChecksum(lower, true));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp
index 77c1f740..c7c1fd3b 100644
--- a/test/liblll/EndToEndTest.cpp
+++ b/test/liblll/EndToEndTest.cpp
@@ -50,6 +50,13 @@ BOOST_AUTO_TEST_CASE(bare_panic)
BOOST_REQUIRE(m_output.empty());
}
+BOOST_AUTO_TEST_CASE(panic)
+{
+ char const* sourceCode = "{ (panic) }";
+ compileAndRunWithoutCheck(sourceCode);
+ BOOST_REQUIRE(m_output.empty());
+}
+
BOOST_AUTO_TEST_CASE(exp_operator_const)
{
char const* sourceCode = R"(
diff --git a/test/libsolidity/Assembly.cpp b/test/libsolidity/Assembly.cpp
index 155dd5c9..497bfc77 100644
--- a/test/libsolidity/Assembly.cpp
+++ b/test/libsolidity/Assembly.cpp
@@ -116,8 +116,8 @@ BOOST_AUTO_TEST_CASE(location_test)
shared_ptr<string const> n = make_shared<string>("");
AssemblyItems items = compileContract(sourceCode);
vector<SourceLocation> locations =
- vector<SourceLocation>(18, SourceLocation(2, 75, n)) +
- vector<SourceLocation>(27, SourceLocation(20, 72, n)) +
+ vector<SourceLocation>(17, SourceLocation(2, 75, n)) +
+ vector<SourceLocation>(30, SourceLocation(20, 72, n)) +
vector<SourceLocation>{SourceLocation(42, 51, n), SourceLocation(65, 67, n)} +
vector<SourceLocation>(2, SourceLocation(58, 67, n)) +
vector<SourceLocation>(3, SourceLocation(20, 72, n));
diff --git a/test/libsolidity/Imports.cpp b/test/libsolidity/Imports.cpp
index 56895fdc..6aa96fb8 100644
--- a/test/libsolidity/Imports.cpp
+++ b/test/libsolidity/Imports.cpp
@@ -106,6 +106,7 @@ BOOST_AUTO_TEST_CASE(library_name_clash)
CompilerStack c;
c.addSource("a", "library A {} pragma solidity >=0.0;");
c.addSource("b", "library A {} pragma solidity >=0.0;");
+ c.addSource("c", "import {A} from \"./a\"; import {A} from \"./b\";");
BOOST_CHECK(!c.compile());
}
diff --git a/test/libsolidity/InlineAssembly.cpp b/test/libsolidity/InlineAssembly.cpp
index 64073edc..cf0343a9 100644
--- a/test/libsolidity/InlineAssembly.cpp
+++ b/test/libsolidity/InlineAssembly.cpp
@@ -182,6 +182,29 @@ BOOST_AUTO_TEST_CASE(error_tag)
BOOST_CHECK(successAssemble("{ invalidJumpLabel }"));
}
+BOOST_AUTO_TEST_CASE(designated_invalid_instruction)
+{
+ BOOST_CHECK(successAssemble("{ invalid }"));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_declaration)
+{
+ // Error message: "Cannot use instruction names for identifier names."
+ BOOST_CHECK(!successAssemble("{ let gas := 1 }"));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_assignment)
+{
+ // Error message: "Identifier expected, got instruction name."
+ BOOST_CHECK(!successAssemble("{ 2 =: gas }"));
+}
+
+BOOST_AUTO_TEST_CASE(inline_assembly_shadowed_instruction_functional_assignment)
+{
+ // Error message: "Cannot use instruction names for identifier names."
+ BOOST_CHECK(!successAssemble("{ gas := 2 }"));
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp
index 19161831..4075a016 100644
--- a/test/libsolidity/SolidityEndToEndTest.cpp
+++ b/test/libsolidity/SolidityEndToEndTest.cpp
@@ -2505,6 +2505,16 @@ BOOST_AUTO_TEST_CASE(constructor_argument_overriding)
BOOST_CHECK(callContractFunction("getA()") == encodeArgs(3));
}
+BOOST_AUTO_TEST_CASE(internal_constructor)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function C() internal {}
+ }
+ )";
+ BOOST_CHECK(compileAndRunWithoutCheck(sourceCode, 0, "C").empty());
+}
+
BOOST_AUTO_TEST_CASE(function_modifier)
{
char const* sourceCode = R"(
@@ -2761,6 +2771,7 @@ BOOST_AUTO_TEST_CASE(event_no_arguments)
}
}
)";
+
compileAndRun(sourceCode);
callContractFunction("deposit()");
BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
@@ -2792,6 +2803,104 @@ BOOST_AUTO_TEST_CASE(event_access_through_base_name)
BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("x()")));
}
+BOOST_AUTO_TEST_CASE(events_with_same_name)
+{
+ char const* sourceCode = R"(
+ contract ClientReceipt {
+ event Deposit;
+ event Deposit(address _addr);
+ event Deposit(address _addr, uint _amount);
+ function deposit() returns (uint) {
+ Deposit();
+ return 1;
+ }
+ function deposit(address _addr) returns (uint) {
+ Deposit(_addr);
+ return 1;
+ }
+ function deposit(address _addr, uint _amount) returns (uint) {
+ Deposit(_addr, _amount);
+ return 1;
+ }
+ }
+ )";
+ u160 const c_loggedAddress = m_contractAddress;
+
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("deposit()") == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data.empty());
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()")));
+
+ BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)")));
+
+ BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)")));
+}
+
+BOOST_AUTO_TEST_CASE(events_with_same_name_inherited)
+{
+ char const* sourceCode = R"(
+ contract A {
+ event Deposit;
+ }
+
+ contract B {
+ event Deposit(address _addr);
+ }
+
+ contract ClientReceipt is A, B {
+ event Deposit(address _addr, uint _amount);
+ function deposit() returns (uint) {
+ Deposit();
+ return 1;
+ }
+ function deposit(address _addr) returns (uint) {
+ Deposit(_addr);
+ return 1;
+ }
+ function deposit(address _addr, uint _amount) returns (uint) {
+ Deposit(_addr, _amount);
+ return 1;
+ }
+ }
+ )";
+ u160 const c_loggedAddress = m_contractAddress;
+
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("deposit()") == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data.empty());
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit()")));
+
+ BOOST_CHECK(callContractFunction("deposit(address)", c_loggedAddress) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address)")));
+
+ BOOST_CHECK(callContractFunction("deposit(address,uint256)", c_loggedAddress, u256(100)) == encodeArgs(u256(1)));
+ BOOST_REQUIRE_EQUAL(m_logs.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].address, m_contractAddress);
+ BOOST_CHECK(m_logs[0].data == encodeArgs(c_loggedAddress, 100));
+ BOOST_REQUIRE_EQUAL(m_logs[0].topics.size(), 1);
+ BOOST_CHECK_EQUAL(m_logs[0].topics[0], dev::keccak256(string("Deposit(address,uint256)")));
+}
+
BOOST_AUTO_TEST_CASE(event_anonymous)
{
char const* sourceCode = R"(
@@ -8883,6 +8992,72 @@ BOOST_AUTO_TEST_CASE(contracts_separated_with_comment)
compileAndRun(sourceCode, 0, "C2");
}
+BOOST_AUTO_TEST_CASE(include_creation_bytecode_only_once)
+{
+ char const* sourceCode = R"(
+ contract D {
+ bytes a = hex"1237651237125387136581271652831736512837126583171583712358126123765123712538713658127165283173651283712658317158371235812612376512371253871365812716528317365128371265831715837123581261237651237125387136581271652831736512837126583171583712358126";
+ bytes b = hex"1237651237125327136581271252831736512837126583171383712358126123765125712538713658127165253173651283712658357158371235812612376512371a5387136581271652a317365128371265a317158371235812612a765123712538a13658127165a83173651283712a58317158371235a126";
+ function D(uint) {}
+ }
+ contract Double {
+ function f() {
+ new D(2);
+ }
+ function g() {
+ new D(3);
+ }
+ }
+ contract Single {
+ function f() {
+ new D(2);
+ }
+ }
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK_LE(
+ double(m_compiler.object("Double").bytecode.size()),
+ 1.1 * double(m_compiler.object("Single").bytecode.size())
+ );
+}
+
+BOOST_AUTO_TEST_CASE(recursive_structs)
+{
+ char const* sourceCode = R"(
+ contract C {
+ struct S {
+ S[] x;
+ }
+ S sstorage;
+ function f() returns (uint) {
+ S memory s;
+ s.x = new S[](10);
+ delete s;
+ sstorage.x.length++;
+ delete sstorage;
+ return 1;
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs(u256(1)));
+}
+
+BOOST_AUTO_TEST_CASE(invalid_instruction)
+{
+ char const* sourceCode = R"(
+ contract C {
+ function f() {
+ assembly {
+ invalid
+ }
+ }
+ }
+ )";
+ compileAndRun(sourceCode, 0, "C");
+ BOOST_CHECK(callContractFunction("f()") == encodeArgs());
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityExpressionCompiler.cpp b/test/libsolidity/SolidityExpressionCompiler.cpp
index 0c5a09c3..a769776e 100644
--- a/test/libsolidity/SolidityExpressionCompiler.cpp
+++ b/test/libsolidity/SolidityExpressionCompiler.cpp
@@ -337,13 +337,19 @@ BOOST_AUTO_TEST_CASE(arithmetics)
byte(Instruction::ADD),
byte(Instruction::DUP2),
byte(Instruction::ISZERO),
- byte(Instruction::PUSH1), 0x0,
+ byte(Instruction::ISZERO),
+ byte(Instruction::PUSH1), 0x1d,
byte(Instruction::JUMPI),
+ byte(Instruction::INVALID),
+ byte(Instruction::JUMPDEST),
byte(Instruction::MOD),
byte(Instruction::DUP2),
byte(Instruction::ISZERO),
- byte(Instruction::PUSH1), 0x0,
+ byte(Instruction::ISZERO),
+ byte(Instruction::PUSH1), 0x26,
byte(Instruction::JUMPI),
+ byte(Instruction::INVALID),
+ byte(Instruction::JUMPDEST),
byte(Instruction::DIV),
byte(Instruction::MUL)});
BOOST_CHECK_EQUAL_COLLECTIONS(code.begin(), code.end(), expectation.begin(), expectation.end());
diff --git a/test/libsolidity/SolidityNameAndTypeResolution.cpp b/test/libsolidity/SolidityNameAndTypeResolution.cpp
index 9f6ea2b3..472067ec 100644
--- a/test/libsolidity/SolidityNameAndTypeResolution.cpp
+++ b/test/libsolidity/SolidityNameAndTypeResolution.cpp
@@ -1102,25 +1102,25 @@ BOOST_AUTO_TEST_CASE(state_variable_accessors)
BOOST_REQUIRE((contract = retrieveContract(source, 0)) != nullptr);
FunctionTypePointer function = retrieveFunctionBySignature(*contract, "foo()");
BOOST_REQUIRE(function && function->hasDeclaration());
- auto returnParams = function->returnParameterTypeNames(false);
- BOOST_CHECK_EQUAL(returnParams.at(0), "uint256");
+ auto returnParams = function->returnParameterTypes();
+ BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "uint256");
BOOST_CHECK(function->isConstant());
function = retrieveFunctionBySignature(*contract, "map(uint256)");
BOOST_REQUIRE(function && function->hasDeclaration());
- auto params = function->parameterTypeNames(false);
- BOOST_CHECK_EQUAL(params.at(0), "uint256");
- returnParams = function->returnParameterTypeNames(false);
- BOOST_CHECK_EQUAL(returnParams.at(0), "bytes4");
+ auto params = function->parameterTypes();
+ BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256");
+ returnParams = function->returnParameterTypes();
+ BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4");
BOOST_CHECK(function->isConstant());
function = retrieveFunctionBySignature(*contract, "multiple_map(uint256,uint256)");
BOOST_REQUIRE(function && function->hasDeclaration());
- params = function->parameterTypeNames(false);
- BOOST_CHECK_EQUAL(params.at(0), "uint256");
- BOOST_CHECK_EQUAL(params.at(1), "uint256");
- returnParams = function->returnParameterTypeNames(false);
- BOOST_CHECK_EQUAL(returnParams.at(0), "bytes4");
+ params = function->parameterTypes();
+ BOOST_CHECK_EQUAL(params.at(0)->canonicalName(false), "uint256");
+ BOOST_CHECK_EQUAL(params.at(1)->canonicalName(false), "uint256");
+ returnParams = function->returnParameterTypes();
+ BOOST_CHECK_EQUAL(returnParams.at(0)->canonicalName(false), "bytes4");
BOOST_CHECK(function->isConstant());
}
@@ -1365,6 +1365,17 @@ BOOST_AUTO_TEST_CASE(anonymous_event_too_many_indexed)
CHECK_ERROR(text, TypeError, "");
}
+BOOST_AUTO_TEST_CASE(events_with_same_name)
+{
+ char const* text = R"(
+ contract TestIt {
+ event A();
+ event A(uint i);
+ }
+ )";
+ BOOST_CHECK(success(text));
+}
+
BOOST_AUTO_TEST_CASE(event_call)
{
char const* text = R"(
@@ -1376,6 +1387,53 @@ BOOST_AUTO_TEST_CASE(event_call)
CHECK_SUCCESS(text);
}
+BOOST_AUTO_TEST_CASE(event_function_inheritance_clash)
+{
+ char const* text = R"(
+ contract A {
+ function dup() returns (uint) {
+ return 1;
+ }
+ }
+ contract B {
+ event dup();
+ }
+ contract C is A, B {
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
+}
+
+BOOST_AUTO_TEST_CASE(function_event_inheritance_clash)
+{
+ char const* text = R"(
+ contract B {
+ event dup();
+ }
+ contract A {
+ function dup() returns (uint) {
+ return 1;
+ }
+ }
+ contract C is B, A {
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
+}
+
+BOOST_AUTO_TEST_CASE(function_event_in_contract_clash)
+{
+ char const* text = R"(
+ contract A {
+ event dup();
+ function dup() returns (uint) {
+ return 1;
+ }
+ }
+ )";
+ CHECK_ERROR(text, DeclarationError, "Identifier already declared.");
+}
+
BOOST_AUTO_TEST_CASE(event_inheritance)
{
char const* text = R"(
@@ -4899,6 +4957,81 @@ BOOST_AUTO_TEST_CASE(assignment_to_constant)
CHECK_ERROR(text, TypeError, "Cannot assign to a constant variable.");
}
+BOOST_AUTO_TEST_CASE(inconstructible_internal_constructor)
+{
+ char const* text = R"(
+ contract C {
+ function C() internal {}
+ }
+ contract D {
+ function f() { var x = new C(); }
+ }
+ )";
+ CHECK_ERROR(text, TypeError, "Contract with internal constructor cannot be created directly.");
+}
+
+BOOST_AUTO_TEST_CASE(constructible_internal_constructor)
+{
+ char const* text = R"(
+ contract C {
+ function C() internal {}
+ }
+ contract D is C {
+ function D() { }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(address_checksum_type_deduction)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ var x = 0xfA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ x.send(2);
+ }
+ }
+ )";
+ success(text);
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_checksum)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ var x = 0xFA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "checksum");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_no_checksum)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ var x = 0xfa0bfc97e48458494ccd857e1a85dc91f7f0046e;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "checksum");
+}
+
+BOOST_AUTO_TEST_CASE(invalid_address_length)
+{
+ char const* text = R"(
+ contract C {
+ function f() {
+ var x = 0xA0bFc97E48458494Ccd857e1A85DC91F7F0046E;
+ }
+ }
+ )";
+ CHECK_WARNING(text, "checksum");
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityNatspecJSON.cpp b/test/libsolidity/SolidityNatspecJSON.cpp
index e32264c4..ac55382b 100644
--- a/test/libsolidity/SolidityNatspecJSON.cpp
+++ b/test/libsolidity/SolidityNatspecJSON.cpp
@@ -56,7 +56,7 @@ public:
m_reader.parse(_expectedDocumentationString, expectedDocumentation);
BOOST_CHECK_MESSAGE(
expectedDocumentation == generatedDocumentation,
- "Expected " << expectedDocumentation.toStyledString() <<
+ "Expected:\n" << expectedDocumentation.toStyledString() <<
"\n but got:\n" << generatedDocumentation.toStyledString()
);
}
@@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(dev_desc_after_nl)
char const* natspec = "{"
"\"methods\":{"
" \"mul(uint256,uint256)\":{ \n"
- " \"details\": \" Multiplies a number by 7 and adds second parameter\",\n"
+ " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
" \"params\": {\n"
" \"a\": \"Documentation for the first parameter\",\n"
" \"second\": \"Documentation for the second parameter\"\n"
@@ -251,6 +251,29 @@ BOOST_AUTO_TEST_CASE(dev_multiple_params)
checkNatspec(sourceCode, natspec, false);
}
+BOOST_AUTO_TEST_CASE(dev_multiple_params_mixed_whitespace)
+{
+ char const* sourceCode = "contract test {\n"
+ " /// @dev Multiplies a number by 7 and adds second parameter\n"
+ " /// @param a Documentation for the first parameter\n"
+ " /// @param second Documentation for the second parameter\n"
+ " function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }\n"
+ "}\n";
+
+ char const* natspec = "{"
+ "\"methods\":{"
+ " \"mul(uint256,uint256)\":{ \n"
+ " \"details\": \"Multiplies a number by 7 and adds second parameter\",\n"
+ " \"params\": {\n"
+ " \"a\": \"Documentation for the first parameter\",\n"
+ " \"second\": \"Documentation for the second parameter\"\n"
+ " }\n"
+ " }\n"
+ "}}";
+
+ checkNatspec(sourceCode, natspec, false);
+}
+
BOOST_AUTO_TEST_CASE(dev_mutiline_param_description)
{
char const* sourceCode = R"(
@@ -379,7 +402,7 @@ BOOST_AUTO_TEST_CASE(dev_return_desc_after_nl)
" \"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\"\n"
+ " \"return\": \"The result of the multiplication\"\n"
" }\n"
"}}";
@@ -612,6 +635,48 @@ BOOST_AUTO_TEST_CASE(dev_documenting_nonexistent_param)
expectNatspecError(sourceCode);
}
+BOOST_AUTO_TEST_CASE(dev_documenting_no_paramname)
+{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ expectNatspecError(sourceCode);
+}
+
+BOOST_AUTO_TEST_CASE(dev_documenting_no_paramname_end)
+{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param se
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ expectNatspecError(sourceCode);
+}
+
+BOOST_AUTO_TEST_CASE(dev_documenting_no_param_description)
+{
+ char const* sourceCode = R"(
+ contract test {
+ /// @dev Multiplies a number by 7 and adds second parameter
+ /// @param a Documentation for the first parameter
+ /// @param second
+ function mul(uint a, uint second) returns(uint d) { return a * 7 + second; }
+ }
+ )";
+
+ expectNatspecError(sourceCode);
+}
+
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityParser.cpp b/test/libsolidity/SolidityParser.cpp
index a3bfab75..e5362e78 100644
--- a/test/libsolidity/SolidityParser.cpp
+++ b/test/libsolidity/SolidityParser.cpp
@@ -1479,7 +1479,6 @@ BOOST_AUTO_TEST_CASE(function_type_state_variable)
BOOST_CHECK(successParse(text));
}
-
BOOST_AUTO_TEST_SUITE_END()
}
diff --git a/test/libsolidity/SolidityTypes.cpp b/test/libsolidity/SolidityTypes.cpp
index dc3143c8..2dcb9226 100644
--- a/test/libsolidity/SolidityTypes.cpp
+++ b/test/libsolidity/SolidityTypes.cpp
@@ -21,6 +21,8 @@
*/
#include <libsolidity/ast/Types.h>
+#include <libsolidity/ast/AST.h>
+#include <libdevcore/SHA3.h>
#include <boost/test/unit_test.hpp>
using namespace std;
@@ -86,6 +88,71 @@ BOOST_AUTO_TEST_CASE(storage_layout_arrays)
BOOST_CHECK(ArrayType(DataLocation::Storage, make_shared<FixedBytesType>(32), 9).storageSize() == 9);
}
+BOOST_AUTO_TEST_CASE(type_identifiers)
+{
+ ASTNode::resetID();
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint128")->identifier(), "t_uint128");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("int128")->identifier(), "t_int128");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("address")->identifier(), "t_address");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("uint8")->identifier(), "t_uint8");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("ufixed8x64")->identifier(), "t_ufixed8x64");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("fixed128x8")->identifier(), "t_fixed128x8");
+ BOOST_CHECK_EQUAL(RationalNumberType(rational(7, 1)).identifier(), "t_rational_7_by_1");
+ BOOST_CHECK_EQUAL(RationalNumberType(rational(200, 77)).identifier(), "t_rational_200_by_77");
+ BOOST_CHECK_EQUAL(RationalNumberType(rational(2 * 200, 2 * 77)).identifier(), "t_rational_200_by_77");
+ BOOST_CHECK_EQUAL(
+ StringLiteralType(Literal(SourceLocation{}, Token::StringLiteral, make_shared<string>("abc - def"))).identifier(),
+ "t_stringliteral_196a9142ee0d40e274a6482393c762b16dd8315713207365e1e13d8d85b74fc4"
+ );
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes8")->identifier(), "t_bytes8");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes32")->identifier(), "t_bytes32");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bool")->identifier(), "t_bool");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("bytes")->identifier(), "t_bytes_storage_ptr");
+ BOOST_CHECK_EQUAL(Type::fromElementaryTypeName("string")->identifier(), "t_string_storage_ptr");
+ ArrayType largeintArray(DataLocation::Memory, Type::fromElementaryTypeName("int128"), u256("2535301200456458802993406410752"));
+ BOOST_CHECK_EQUAL(largeintArray.identifier(), "t_array$_t_int128_$2535301200456458802993406410752_memory_ptr");
+ TypePointer stringArray = make_shared<ArrayType>(DataLocation::Storage, Type::fromElementaryTypeName("string"), u256("20"));
+ TypePointer multiArray = make_shared<ArrayType>(DataLocation::Storage, stringArray);
+ BOOST_CHECK_EQUAL(multiArray->identifier(), "t_array$_t_array$_t_string_storage_$20_storage_$dyn_storage_ptr");
+
+ ContractDefinition c(SourceLocation{}, make_shared<string>("MyContract$"), {}, {}, {}, false);
+ BOOST_CHECK_EQUAL(c.type()->identifier(), "t_type$_t_contract$_MyContract$$$_$2_$");
+ BOOST_CHECK_EQUAL(ContractType(c, true).identifier(), "t_super$_MyContract$$$_$2");
+
+ StructDefinition s({}, make_shared<string>("Struct"), {});
+ BOOST_CHECK_EQUAL(s.type()->identifier(), "t_type$_t_struct$_Struct_$3_storage_ptr_$");
+
+ EnumDefinition e({}, make_shared<string>("Enum"), {});
+ BOOST_CHECK_EQUAL(e.type()->identifier(), "t_type$_t_enum$_Enum_$4_$");
+
+ TupleType t({e.type(), s.type(), stringArray, nullptr});
+ BOOST_CHECK_EQUAL(t.identifier(), "t_tuple$_t_type$_t_enum$_Enum_$4_$_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_t_array$_t_string_storage_$20_storage_ptr_$__$");
+
+ TypePointer sha3fun = make_shared<FunctionType>(strings{}, strings{}, FunctionType::Location::SHA3);
+ BOOST_CHECK_EQUAL(sha3fun->identifier(), "t_function_sha3$__$returns$__$");
+
+ FunctionType metaFun(TypePointers{sha3fun}, TypePointers{s.type()});
+ BOOST_CHECK_EQUAL(metaFun.identifier(), "t_function_internal$_t_function_sha3$__$returns$__$_$returns$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$");
+
+ TypePointer m = make_shared<MappingType>(Type::fromElementaryTypeName("bytes32"), s.type());
+ MappingType m2(Type::fromElementaryTypeName("uint64"), m);
+ BOOST_CHECK_EQUAL(m2.identifier(), "t_mapping$_t_uint64_$_t_mapping$_t_bytes32_$_t_type$_t_struct$_Struct_$3_storage_ptr_$_$_$");
+
+ // TypeType is tested with contract
+
+ auto emptyParams = make_shared<ParameterList>(SourceLocation(), std::vector<ASTPointer<VariableDeclaration>>());
+ ModifierDefinition mod(SourceLocation{}, make_shared<string>("modif"), {}, emptyParams, {});
+ BOOST_CHECK_EQUAL(ModifierType(mod).identifier(), "t_modifier$__$");
+
+ SourceUnit su({}, {});
+ BOOST_CHECK_EQUAL(ModuleType(su).identifier(), "t_module_7");
+ BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Block).identifier(), "t_magic_block");
+ BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Message).identifier(), "t_magic_message");
+ BOOST_CHECK_EQUAL(MagicType(MagicType::Kind::Transaction).identifier(), "t_magic_transaction");
+
+ BOOST_CHECK_EQUAL(InaccessibleDynamicType().identifier(), "t_inaccessible");
+}
+
BOOST_AUTO_TEST_SUITE_END()
}