aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--libdevcore/Exceptions.cpp4
-rw-r--r--libevmasm/Instruction.h18
-rw-r--r--liblll/CodeFragment.cpp52
-rw-r--r--libsolidity/inlineasm/AsmParser.cpp6
-rw-r--r--test/liblll/Compiler.cpp128
-rw-r--r--test/liblll/EndToEndTest.cpp86
6 files changed, 283 insertions, 11 deletions
diff --git a/libdevcore/Exceptions.cpp b/libdevcore/Exceptions.cpp
index f422d926..25fd1478 100644
--- a/libdevcore/Exceptions.cpp
+++ b/libdevcore/Exceptions.cpp
@@ -27,7 +27,9 @@ char const* Exception::what() const noexcept
if (string const* cmt = comment())
return cmt->c_str();
else
- return nullptr;
+ /// Boost accepts nullptr, but the C++ standard doesn't
+ /// and crashes on some platforms.
+ return std::exception::what();
}
string Exception::lineInfo() const
diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h
index afbef71d..d9c53900 100644
--- a/libevmasm/Instruction.h
+++ b/libevmasm/Instruction.h
@@ -197,6 +197,24 @@ enum class Instruction: uint8_t
SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion
};
+/// @returns true if the instruction is a PUSH
+inline bool isPushInstruction(Instruction _inst)
+{
+ return Instruction::PUSH1 <= _inst && _inst <= Instruction::PUSH32;
+}
+
+/// @returns true if the instruction is a DUP
+inline bool isDupInstruction(Instruction _inst)
+{
+ return Instruction::DUP1 <= _inst && _inst <= Instruction::DUP16;
+}
+
+/// @returns true if the instruction is a SWAP
+inline bool isSwapInstruction(Instruction _inst)
+{
+ return Instruction::SWAP1 <= _inst && _inst <= Instruction::SWAP16;
+}
+
/// @returns the number of PUSH Instruction _inst
inline unsigned getPushNumber(Instruction _inst)
{
diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp
index 254f436f..4fa2c646 100644
--- a/liblll/CodeFragment.cpp
+++ b/liblll/CodeFragment.cpp
@@ -103,7 +103,7 @@ CodeFragment::CodeFragment(sp::utree const& _t, CompilerState& _s, bool _allowAS
{
bigint i = *_t.get<bigint*>();
if (i < 0 || i > bigint(u256(0) - 1))
- error<IntegerOutOfRange>();
+ error<IntegerOutOfRange>(toString(i));
m_asm.append((u256)i);
break;
}
@@ -157,7 +157,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
auto i = *++_t.begin();
if (i.tag())
- error<InvalidName>();
+ error<InvalidName>(toString(i));
if (i.which() == sp::utree_type::string_type)
{
auto sr = i.get<sp::basic_string<boost::iterator_range<char const*>, sp::utree_type::string_type>>();
@@ -244,7 +244,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
if (ii == 1)
{
if (i.tag())
- error<InvalidName>();
+ error<InvalidName>(toString(i));
if (i.which() == sp::utree_type::string_type)
{
auto sr = i.get<sp::basic_string<boost::iterator_range<char const*>, sp::utree_type::string_type>>();
@@ -303,11 +303,11 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
pos = CodeFragment(i, _s);
if (pos.m_asm.deposit() != 1)
- error<InvalidDeposit>(us);
+ error<InvalidDeposit>(toString(i));
}
else if (i.tag() != 0)
{
- error<InvalidLiteral>();
+ error<InvalidLiteral>(toString(i));
}
else if (i.which() == sp::utree_type::string_type)
{
@@ -318,7 +318,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
{
bigint bi = *i.get<bigint*>();
if (bi < 0)
- error<IntegerOutOfRange>();
+ error<IntegerOutOfRange>(toString(i));
else
{
bytes tmp = toCompactBigEndian(bi);
@@ -327,7 +327,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
}
else
{
- error<InvalidLiteral>();
+ error<InvalidLiteral>(toString(i));
}
ii++;
@@ -514,6 +514,44 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s)
m_asm.appendJump(begin);
m_asm << end.tag();
}
+ else if (us == "SWITCH")
+ {
+ requireMinSize(1);
+
+ bool hasDefault = (code.size() % 2 == 1);
+ int startDeposit = m_asm.deposit();
+ int targetDeposit = hasDefault ? code[code.size() - 1].m_asm.deposit() : 0;
+
+ // The conditions
+ AssemblyItems jumpTags;
+ for (unsigned i = 0; i < code.size() - 1; i += 2)
+ {
+ requireDeposit(i, 1);
+ m_asm.append(code[i].m_asm);
+ jumpTags.push_back(m_asm.appendJumpI());
+ }
+
+ // The default, if present
+ if (hasDefault)
+ m_asm.append(code[code.size() - 1].m_asm);
+
+ // The targets - appending in reverse makes the top case the most efficient.
+ if (code.size() > 1)
+ {
+ auto end = m_asm.appendJump();
+ for (int i = 2 * (code.size() / 2 - 1); i >= 0; i -= 2)
+ {
+ m_asm << jumpTags[i / 2].tag();
+ requireDeposit(i + 1, targetDeposit);
+ m_asm.append(code[i + 1].m_asm);
+ if (i != 0)
+ m_asm.appendJump(end);
+ }
+ m_asm << end.tag();
+ }
+
+ m_asm.setDeposit(startDeposit + targetDeposit);
+ }
else if (us == "ALLOC")
{
requireSize(1);
diff --git a/libsolidity/inlineasm/AsmParser.cpp b/libsolidity/inlineasm/AsmParser.cpp
index 3087ad86..1f4df75b 100644
--- a/libsolidity/inlineasm/AsmParser.cpp
+++ b/libsolidity/inlineasm/AsmParser.cpp
@@ -256,7 +256,7 @@ std::map<string, dev::solidity::Instruction> const& Parser::instructions()
{
if (
instruction.second == solidity::Instruction::JUMPDEST ||
- (solidity::Instruction::PUSH1 <= instruction.second && instruction.second <= solidity::Instruction::PUSH32)
+ solidity::isPushInstruction(instruction.second)
)
continue;
string name = instruction.first;
@@ -443,9 +443,9 @@ assembly::Statement Parser::parseCall(assembly::Statement&& _instruction)
ret.location = ret.instruction.location;
solidity::Instruction instr = ret.instruction.instruction;
InstructionInfo instrInfo = instructionInfo(instr);
- if (solidity::Instruction::DUP1 <= instr && instr <= solidity::Instruction::DUP16)
+ if (solidity::isDupInstruction(instr))
fatalParserError("DUPi instructions not allowed for functional notation");
- if (solidity::Instruction::SWAP1 <= instr && instr <= solidity::Instruction::SWAP16)
+ if (solidity::isSwapInstruction(instr))
fatalParserError("SWAPi instructions not allowed for functional notation");
expectToken(Token::LParen);
unsigned args = unsigned(instrInfo.args);
diff --git a/test/liblll/Compiler.cpp b/test/liblll/Compiler.cpp
new file mode 100644
index 00000000..2d66bce1
--- /dev/null
+++ b/test/liblll/Compiler.cpp
@@ -0,0 +1,128 @@
+/*
+ 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/>.
+*/
+/**
+ * @author Alex Beregszaszi
+ * @date 2017
+ * Unit tests for the LLL compiler.
+ */
+
+#include <string>
+#include <memory>
+#include <boost/test/unit_test.hpp>
+#include <liblll/Compiler.h>
+
+using namespace std;
+
+namespace dev
+{
+namespace lll
+{
+namespace test
+{
+
+namespace
+{
+
+bool successCompile(std::string const& _sourceCode)
+{
+ std::vector<std::string> errors;
+ bytes bytecode = eth::compileLLL(_sourceCode, false, &errors);
+ if (!errors.empty())
+ return false;
+ if (bytecode.empty())
+ return false;
+ return true;
+}
+
+}
+
+BOOST_AUTO_TEST_SUITE(LLLCompiler)
+
+BOOST_AUTO_TEST_CASE(smoke_test)
+{
+ char const* sourceCode = "1";
+ BOOST_CHECK(successCompile(sourceCode));
+}
+
+BOOST_AUTO_TEST_CASE(switch_valid)
+{
+ char const* sourceCode = R"(
+ (switch (origin))
+ )";
+ BOOST_CHECK(successCompile(sourceCode));
+ sourceCode = R"(
+ (switch
+ 1 (panic)
+ 2 (panic))
+ )";
+ BOOST_CHECK(successCompile(sourceCode));
+ sourceCode = R"(
+ (switch
+ 1 (panic)
+ 2 (panic)
+ (panic))
+ )";
+ BOOST_CHECK(successCompile(sourceCode));
+ sourceCode = R"(
+ (switch
+ 1 (origin)
+ 2 (origin)
+ (origin))
+ )";
+ BOOST_CHECK(successCompile(sourceCode));
+}
+
+BOOST_AUTO_TEST_CASE(switch_invalid_arg_count)
+{
+ char const* sourceCode = R"(
+ (switch)
+ )";
+ BOOST_CHECK(!successCompile(sourceCode));
+}
+
+BOOST_AUTO_TEST_CASE(switch_inconsistent_return_count)
+{
+ // cannot return stack items if the default case is not present
+ char const* sourceCode = R"(
+ (switch
+ 1 (origin)
+ 2 (origin)
+ )";
+ BOOST_CHECK(!successCompile(sourceCode));
+ // return count mismatch
+ sourceCode = R"(
+ (switch
+ 1 (origin)
+ 2 (origin)
+ (panic))
+ )";
+ BOOST_CHECK(!successCompile(sourceCode));
+ // return count mismatch
+ sourceCode = R"(
+ (switch
+ 1 (panic)
+ 2 (panic)
+ (origin))
+ )";
+ BOOST_CHECK(!successCompile(sourceCode));
+}
+
+BOOST_AUTO_TEST_SUITE_END()
+
+}
+}
+} // end namespaces
diff --git a/test/liblll/EndToEndTest.cpp b/test/liblll/EndToEndTest.cpp
index 9292d963..1a5bb490 100644
--- a/test/liblll/EndToEndTest.cpp
+++ b/test/liblll/EndToEndTest.cpp
@@ -215,6 +215,92 @@ BOOST_AUTO_TEST_CASE(conditional_nested_then)
BOOST_CHECK(callContractFunction("test()", 0xfc) == encodeArgs(u256(6)));
}
+BOOST_AUTO_TEST_CASE(conditional_switch)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (def 'input (calldataload 0x04))
+ ;; Calculates width in bytes of utf-8 characters.
+ (return
+ (switch
+ (< input 0x80) 1
+ (< input 0xE0) 2
+ (< input 0xF0) 3
+ (< input 0xF8) 4
+ (< input 0xFC) 5
+ 6))))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()", 0x00) == encodeArgs(u256(1)));
+ BOOST_CHECK(callContractFunction("test()", 0x80) == encodeArgs(u256(2)));
+ BOOST_CHECK(callContractFunction("test()", 0xe0) == encodeArgs(u256(3)));
+ BOOST_CHECK(callContractFunction("test()", 0xf0) == encodeArgs(u256(4)));
+ BOOST_CHECK(callContractFunction("test()", 0xf8) == encodeArgs(u256(5)));
+ BOOST_CHECK(callContractFunction("test()", 0xfc) == encodeArgs(u256(6)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_switch_one_arg_with_deposit)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return
+ (switch 42)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_switch_one_arg_no_deposit)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (switch [0]:42)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callFallback() == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_switch_two_args)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (seq
+ (switch (= (calldataload 0x04) 1) [0]:42)
+ (return 0x00 0x20)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(0)));
+ BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(42)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_switch_three_args_with_deposit)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (return
+ (switch (= (calldataload 0x04) 1) 41 42)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(42)));
+ BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(41)));
+}
+
+BOOST_AUTO_TEST_CASE(conditional_switch_three_args_no_deposit)
+{
+ char const* sourceCode = R"(
+ (returnlll
+ (switch
+ (= (calldataload 0x04) 1) (return 41)
+ (return 42)))
+ )";
+ compileAndRun(sourceCode);
+ BOOST_CHECK(callContractFunction("test()", 0) == encodeArgs(u256(42)));
+ BOOST_CHECK(callContractFunction("test()", 1) == encodeArgs(u256(41)));
+}
+
BOOST_AUTO_TEST_CASE(exp_operator_const)
{
char const* sourceCode = R"(