Merge pull request #3464 from ethereum/iulia_interp

Yul interpreter
This commit is contained in:
chriseth 2019-02-25 15:38:41 +01:00 committed by GitHub
commit 70bbdd8fac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 1477 additions and 3 deletions

View File

@ -161,7 +161,7 @@ private:
/// Current positions of equivalence classes, equal to the empty set if already deleted.
std::map<Id, std::set<int>> m_classPositions;
/// The actual eqivalence class items and how to compute them.
/// The actual equivalence class items and how to compute them.
ExpressionClasses& m_expressionClasses;
/// Keeps information about which storage or memory slots were written to by which operations.
/// The operations are sorted ascendingly by sequence number.

View File

@ -30,7 +30,7 @@ add_executable(soltest ${sources} ${headers}
${libsolidity_sources} ${libsolidity_headers}
${libsolidity_util_sources} ${libsolidity_util_headers}
)
target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
if (LLL)
target_link_libraries(soltest PRIVATE lll)

View File

@ -23,6 +23,7 @@
#include <test/libsolidity/SemanticTest.h>
#include <test/libsolidity/SMTCheckerJSONTest.h>
#include <test/libyul/YulOptimizerTest.h>
#include <test/libyul/YulInterpreterTest.h>
#include <test/libyul/ObjectCompilerTest.h>
#include <boost/filesystem.hpp>
@ -51,6 +52,7 @@ Testsuite const g_interactiveTestsuites[] = {
/*
Title Path Subpath SMT IPC Creator function */
{"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},
{"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create},
{"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create},

View File

@ -0,0 +1,160 @@
/*
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/>.
*/
#include <test/libyul/YulInterpreterTest.h>
#include <test/tools/yulInterpreter/Interpreter.h>
#include <test/Options.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AsmParser.h>
#include <libyul/AssemblyStack.h>
#include <libyul/AsmAnalysisInfo.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libdevcore/AnsiColorized.h>
#include <boost/test/unit_test.hpp>
#include <boost/algorithm/string.hpp>
#include <fstream>
using namespace dev;
using namespace langutil;
using namespace yul;
using namespace yul::test;
using namespace dev::solidity;
using namespace dev::solidity::test;
using namespace std;
YulInterpreterTest::YulInterpreterTest(string const& _filename)
{
boost::filesystem::path path(_filename);
ifstream file(_filename);
if (!file)
BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\"."));
file.exceptions(ios::badbit);
string line;
while (getline(file, line))
{
if (boost::algorithm::starts_with(line, "// ----"))
break;
m_source += std::move(line) + "\n";
}
while (getline(file, line))
if (boost::algorithm::starts_with(line, "// "))
m_expectation += line.substr(3) + "\n";
else
m_expectation += line + "\n";
}
bool YulInterpreterTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted)
{
if (!parse(_stream, _linePrefix, _formatted))
return false;
m_obtainedResult = interpret();
if (m_expectation != m_obtainedResult)
{
string nextIndentLevel = _linePrefix + " ";
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl;
// TODO could compute a simple diff with highlighted lines
printIndented(_stream, m_expectation, nextIndentLevel);
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl;
printIndented(_stream, m_obtainedResult, nextIndentLevel);
return false;
}
return true;
}
void YulInterpreterTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const
{
printIndented(_stream, m_source, _linePrefix);
}
void YulInterpreterTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const
{
printIndented(_stream, m_obtainedResult, _linePrefix);
}
void YulInterpreterTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const
{
stringstream output(_output);
string line;
while (getline(output, line))
_stream << _linePrefix << line << endl;
}
bool YulInterpreterTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted)
{
AssemblyStack stack(dev::test::Options::get().evmVersion(), AssemblyStack::Language::StrictAssembly);
if (stack.parseAndAnalyze("", m_source))
{
m_ast = stack.parserResult()->code;
m_analysisInfo = stack.parserResult()->analysisInfo;
return true;
}
else
{
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl;
printErrors(_stream, stack.errors());
return false;
}
}
string YulInterpreterTest::interpret()
{
InterpreterState state;
state.maxTraceSize = 10000;
Interpreter interpreter(state);
try
{
interpreter(*m_ast);
}
catch (InterpreterTerminated const&)
{
}
stringstream result;
result << "Trace:" << endl;;
for (auto const& line: interpreter.trace())
result << " " << line << endl;
result << "Memory dump:\n";
for (size_t i = 0; i < state.memory.size(); i += 0x20)
result << " " << std::hex << std::setw(4) << i << ": " << toHex(bytesConstRef(state.memory.data() + i, 0x20).toBytes()) << endl;
result << "Storage dump:" << endl;
for (auto const& slot: state.storage)
result << " " << slot.first.hex() << ": " << slot.second.hex() << endl;
return result.str();
}
void YulInterpreterTest::printErrors(ostream& _stream, ErrorList const& _errors)
{
SourceReferenceFormatter formatter(_stream);
for (auto const& error: _errors)
formatter.printExceptionInformation(
*error,
(error->type() == Error::Type::Warning) ? "Warning" : "Error"
);
}

View File

@ -0,0 +1,72 @@
/*
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/>.
*/
#pragma once
#include <test/TestCase.h>
namespace langutil
{
class Scanner;
class Error;
using ErrorList = std::vector<std::shared_ptr<Error const>>;
}
namespace yul
{
struct AsmAnalysisInfo;
struct Block;
struct Dialect;
}
namespace yul
{
namespace test
{
class YulInterpreterTest: public dev::solidity::test::TestCase
{
public:
static std::unique_ptr<TestCase> create(Config const& _config)
{
return std::unique_ptr<TestCase>(new YulInterpreterTest(_config.filename));
}
explicit YulInterpreterTest(std::string const& _filename);
bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override;
void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override;
void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override;
private:
void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const;
bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted);
std::string interpret();
static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors);
std::string m_source;
std::string m_expectation;
std::shared_ptr<Block> m_ast;
std::shared_ptr<AsmAnalysisInfo> m_analysisInfo;
std::string m_obtainedResult;
};
}
}

View File

@ -0,0 +1,16 @@
{
{
let a := 0x20
mstore(a, 2)
}
let a
mstore(a, 3)
}
// ----
// Trace:
// MSTORE_AT_SIZE(32, 32) [0000000000000000000000000000000000000000000000000000000000000002]
// MSTORE_AT_SIZE(0, 32) [0000000000000000000000000000000000000000000000000000000000000003]
// Memory dump:
// 0: 0000000000000000000000000000000000000000000000000000000000000003
// 20: 0000000000000000000000000000000000000000000000000000000000000002
// Storage dump:

View File

@ -0,0 +1,17 @@
{
let x := call(gas(), 0x45, 0x5, 0, 0x20, 0x30, 0x20)
sstore(100, x)
}
// ----
// Trace:
// GAS()
// MLOAD_FROM_SIZE(0, 32)
// MSTORE_AT_SIZE(48, 32)
// CALL(153, 69, 5, 0, 32, 48, 32)
// SSTORE(100, 1)
// Memory dump:
// 0: 0000000000000000000000000000000000000000000000000000000000000000
// 20: 0000000000000000000000000000000000000000000000000000000000000000
// 40: 0000000000000000000000000000000000000000000000000000000000000000
// Storage dump:
// 0000000000000000000000000000000000000000000000000000000000000064: 0000000000000000000000000000000000000000000000000000000000000001

View File

@ -0,0 +1,14 @@
{
function f(a, b) -> x, y {
x := add(a, b)
y := mul(a, b)
}
let r, t := f(6, 7)
sstore(r, t)
}
// ----
// Trace:
// SSTORE(13, 42)
// Memory dump:
// Storage dump:
// 000000000000000000000000000000000000000000000000000000000000000d: 000000000000000000000000000000000000000000000000000000000000002a

View File

@ -0,0 +1,20 @@
{
for { let x := 2 } lt(x, 10) { x := add(x, 1) } {
mstore(mul(x, 5), mul(x, 0x1000))
}
}
// ----
// Trace:
// MSTORE_AT_SIZE(10, 32) [0000000000000000000000000000000000000000000000000000000000002000]
// MSTORE_AT_SIZE(15, 32) [0000000000000000000000000000000000000000000000000000000000003000]
// MSTORE_AT_SIZE(20, 32) [0000000000000000000000000000000000000000000000000000000000004000]
// MSTORE_AT_SIZE(25, 32) [0000000000000000000000000000000000000000000000000000000000005000]
// MSTORE_AT_SIZE(30, 32) [0000000000000000000000000000000000000000000000000000000000006000]
// MSTORE_AT_SIZE(35, 32) [0000000000000000000000000000000000000000000000000000000000007000]
// MSTORE_AT_SIZE(40, 32) [0000000000000000000000000000000000000000000000000000000000008000]
// MSTORE_AT_SIZE(45, 32) [0000000000000000000000000000000000000000000000000000000000009000]
// Memory dump:
// 0: 0000000000000000000000000000000000000000000000000000000000000000
// 20: 0000000000000000000000000000000000000000000000000000000000000000
// 40: 0000000000000000000000900000000000000000000000000000000000000000
// Storage dump:

View File

@ -0,0 +1,10 @@
{
mstore(10, 11)
}
// ----
// Trace:
// MSTORE_AT_SIZE(10, 32) [000000000000000000000000000000000000000000000000000000000000000b]
// Memory dump:
// 0: 0000000000000000000000000000000000000000000000000000000000000000
// 20: 0000000000000000000b00000000000000000000000000000000000000000000
// Storage dump:

View File

@ -0,0 +1,5 @@
{}
// ----
// Trace:
// Memory dump:
// Storage dump:

View File

@ -0,0 +1,13 @@
{
switch 7
case 7 { mstore(1, 2) }
case 3 { mstore(6, 7) }
default { mstore(8, 9) }
}
// ----
// Trace:
// MSTORE_AT_SIZE(1, 32) [0000000000000000000000000000000000000000000000000000000000000002]
// Memory dump:
// 0: 0000000000000000000000000000000000000000000000000000000000000000
// 20: 0200000000000000000000000000000000000000000000000000000000000000
// Storage dump:

View File

@ -2,6 +2,10 @@ if (OSSFUZZ)
add_subdirectory(ossfuzz)
endif()
add_subdirectory(yulInterpreter)
add_executable(yulrun yulrun.cpp)
target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES})
add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp)
target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES})
@ -26,5 +30,6 @@ add_executable(isoltest
../libsolidity/SMTCheckerJSONTest.cpp
../libyul/ObjectCompilerTest.cpp
../libyul/YulOptimizerTest.cpp
../libyul/YulInterpreterTest.cpp
)
target_link_libraries(isoltest PRIVATE libsolc solidity evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})

View File

@ -0,0 +1,9 @@
set(sources
EVMInstructionInterpreter.h
EVMInstructionInterpreter.cpp
Interpreter.h
Interpreter.cpp
)
add_library(yulInterpreter ${sources})
target_link_libraries(yulInterpreter PUBLIC yul solidity devcore)

View File

@ -0,0 +1,500 @@
/*
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/>.
*/
/**
* Yul interpreter module that evaluates EVM instructions.
*/
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
#include <test/tools/yulInterpreter/Interpreter.h>
#include <libyul/AsmData.h>
#include <libevmasm/Instruction.h>
#include <libdevcore/Keccak256.h>
using namespace std;
using namespace dev;
using namespace yul;
using namespace yul::test;
namespace
{
u256 exp256(u256 _base, u256 _exponent)
{
using boost::multiprecision::limb_type;
u256 result = 1;
while (_exponent)
{
if (static_cast<limb_type>(_exponent) & 1) // If exponent is odd.
result *= _base;
_base *= _base;
_exponent >>= 1;
}
return result;
}
/// Reads 32 bytes from @a _data at position @a _offset bytes while
/// interpreting @a _data to be padded with an infinite number of zero
/// bytes beyond its end.
u256 readZeroExtended(bytes const& _data, u256 const& _offset)
{
if (_offset >= _data.size())
return 0;
else if (_offset + 32 <= _data.size())
return *reinterpret_cast<h256 const*>(_data.data() + size_t(_offset));
else
{
size_t off = size_t(_offset);
u256 val;
for (size_t i = 0; i < 32; ++i)
{
val <<= 8;
if (off + i < _data.size())
val += _data[off + i];
}
return val;
}
}
/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to
/// @a _target at offset @a _targetOffset. Behaves as if @a _source would
/// continue with an infinite sequence of zero bytes beyond its end.
/// Asserts the target is large enough to hold the copied segment.
void copyZeroExtended(
bytes& _target, bytes const& _source,
size_t _targetOffset, size_t _sourceOffset, size_t _size
)
{
yulAssert(_targetOffset + _size < _target.size(), "");
for (size_t i = 0; i < _size; ++i)
_target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0;
}
}
using u512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
u256 EVMInstructionInterpreter::eval(
solidity::Instruction _instruction,
vector<u256> const& _arguments
)
{
using dev::solidity::Instruction;
auto info = solidity::instructionInfo(_instruction);
yulAssert(size_t(info.args) == _arguments.size(), "");
auto const& arg = _arguments;
switch (_instruction)
{
case Instruction::STOP:
throw InterpreterTerminated();
// --------------- arithmetic ---------------
case Instruction::ADD:
return arg[0] + arg[1];
case Instruction::MUL:
return arg[0] * arg[1];
case Instruction::SUB:
return arg[0] - arg[1];
case Instruction::DIV:
return arg[1] == 0 ? 0 : arg[0] / arg[1];
case Instruction::SDIV:
return arg[1] == 0 ? 0 : s2u(u2s(arg[0]) / u2s(arg[1]));
case Instruction::MOD:
return arg[1] == 0 ? 0 : arg[0] % arg[1];
case Instruction::SMOD:
return arg[1] == 0 ? 0 : s2u(u2s(arg[0]) % u2s(arg[1]));
case Instruction::EXP:
return exp256(arg[0], arg[1]);
case Instruction::NOT:
return ~arg[0];
case Instruction::LT:
return arg[0] < arg[1] ? 1 : 0;
case Instruction::GT:
return arg[0] > arg[1] ? 1 : 0;
case Instruction::SLT:
return u2s(arg[0]) < u2s(arg[1]) ? 1 : 0;
case Instruction::SGT:
return u2s(arg[0]) > u2s(arg[1]) ? 1 : 0;
case Instruction::EQ:
return arg[0] == arg[1] ? 1 : 0;
case Instruction::ISZERO:
return arg[0] == 0 ? 1 : 0;
case Instruction::AND:
return arg[0] & arg[1];
case Instruction::OR:
return arg[0] | arg[1];
case Instruction::XOR:
return arg[0] ^ arg[1];
case Instruction::BYTE:
return arg[0] >= 32 ? 0 : (arg[1] >> unsigned(8 * (31 - arg[0]))) & 0xff;
case Instruction::SHL:
return arg[0] > 255 ? 0 : (arg[1] << unsigned(arg[0]));
case Instruction::SHR:
return arg[0] > 255 ? 0 : (arg[1] >> unsigned(arg[0]));
case Instruction::SAR:
{
static u256 const hibit = u256(1) << 255;
if (arg[0] >= 256)
return arg[1] & hibit ? u256(-1) : 0;
else
{
unsigned amount = unsigned(arg[0]);
u256 v = arg[1] >> amount;
if (arg[1] & hibit)
v |= u256(-1) << (256 - amount);
return v;
}
}
case Instruction::ADDMOD:
return arg[2] == 0 ? 0 : u256((u512(arg[0]) + u512(arg[1])) % arg[2]);
case Instruction::MULMOD:
return arg[2] == 0 ? 0 : u256((u512(arg[0]) * u512(arg[1])) % arg[2]);
case Instruction::SIGNEXTEND:
if (arg[0] >= 31)
return arg[0];
else
{
unsigned testBit = unsigned(arg[0]) * 8 + 7;
u256 ret = arg[1];
u256 mask = ((u256(1) << testBit) - 1);
if (boost::multiprecision::bit_test(ret, testBit))
ret |= ~mask;
else
ret &= mask;
return ret;
}
// --------------- blockchain stuff ---------------
case Instruction::KECCAK256:
{
if (!logMemoryRead(arg[0], arg[1]))
return u256("0x1234cafe1234cafe1234cafe") + arg[0];
uint64_t offset = uint64_t(arg[0] & uint64_t(-1));
uint64_t size = uint64_t(arg[1] & uint64_t(-1));
return u256(keccak256(bytesConstRef(m_state.memory.data() + offset, size)));
}
case Instruction::ADDRESS:
return m_state.address;
case Instruction::BALANCE:
return m_state.balance;
case Instruction::ORIGIN:
return m_state.origin;
case Instruction::CALLER:
return m_state.caller;
case Instruction::CALLVALUE:
return m_state.callvalue;
case Instruction::CALLDATALOAD:
return readZeroExtended(m_state.calldata, arg[0]);
case Instruction::CALLDATASIZE:
return m_state.calldata.size();
case Instruction::CALLDATACOPY:
if (logMemoryWrite(arg[0], arg[2]))
copyZeroExtended(
m_state.memory, m_state.calldata,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
return 0;
case Instruction::CODESIZE:
return m_state.code.size();
case Instruction::CODECOPY:
if (logMemoryWrite(arg[0], arg[2]))
copyZeroExtended(
m_state.memory, m_state.code,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
return 0;
case Instruction::GASPRICE:
return m_state.gasprice;
case Instruction::EXTCODESIZE:
logTrace(_instruction, arg);
return u256(keccak256(h256(arg[0]))) & 0xffffff;
case Instruction::EXTCODEHASH:
logTrace(_instruction, arg);
return u256(keccak256(h256(arg[0] + 1)));
case Instruction::EXTCODECOPY:
logTrace(_instruction, arg);
if (logMemoryWrite(arg[1], arg[3]))
// TODO this way extcodecopy and codecopy do the same thing.
copyZeroExtended(
m_state.memory, m_state.code,
size_t(arg[1]), size_t(arg[2]), size_t(arg[3])
);
return 0;
case Instruction::RETURNDATASIZE:
logTrace(_instruction, arg);
return m_state.returndata.size();
case Instruction::RETURNDATACOPY:
logTrace(_instruction, arg);
if (logMemoryWrite(arg[0], arg[2]))
copyZeroExtended(
m_state.memory, m_state.returndata,
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
);
return 0;
case Instruction::BLOCKHASH:
if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber)
return 0;
else
return 0xaaaaaaaa + (arg[0] - m_state.blockNumber - 256);
case Instruction::COINBASE:
return m_state.coinbase;
case Instruction::TIMESTAMP:
return m_state.timestamp;
case Instruction::NUMBER:
return m_state.blockNumber;
case Instruction::DIFFICULTY:
return m_state.difficulty;
case Instruction::GASLIMIT:
return m_state.gaslimit;
// --------------- memory / storage / logs ---------------
case Instruction::MLOAD:
if (logMemoryRead(arg[0], 0x20))
return u256(*reinterpret_cast<h256 const*>(m_state.memory.data() + size_t(arg[0])));
else
return 0x1234 + arg[0];
case Instruction::MSTORE:
if (logMemoryWrite(arg[0], 0x20, h256(arg[1]).asBytes()))
*reinterpret_cast<h256*>(m_state.memory.data() + size_t(arg[0])) = h256(arg[1]);
return 0;
case Instruction::MSTORE8:
if (logMemoryWrite(arg[0], 1, bytes{1, uint8_t(arg[1] & 0xff)}))
m_state.memory[size_t(arg[0])] = uint8_t(arg[1] & 0xff);
return 0;
case Instruction::SLOAD:
logTrace(_instruction, arg);
return m_state.storage[h256(arg[0])];
case Instruction::SSTORE:
logTrace(Instruction::SSTORE, arg);
m_state.storage[h256(arg[0])] = h256(arg[1]);
return 0;
case Instruction::PC:
logTrace(_instruction);
return 0x77;
case Instruction::MSIZE:
logTrace(_instruction);
return m_state.msize;
case Instruction::GAS:
logTrace(_instruction);
return 0x99;
case Instruction::LOG0:
logMemoryRead(arg[0], arg[1]);
logTrace(_instruction, arg);
return 0;
case Instruction::LOG1:
logMemoryRead(arg[0], arg[1]);
logTrace(_instruction, arg);
return 0;
case Instruction::LOG2:
logMemoryRead(arg[0], arg[1]);
logTrace(_instruction, arg);
return 0;
case Instruction::LOG3:
logMemoryRead(arg[0], arg[1]);
logTrace(_instruction, arg);
return 0;
case Instruction::LOG4:
logMemoryRead(arg[0], arg[1]);
logTrace(_instruction, arg);
return 0;
// --------------- calls ---------------
case Instruction::CREATE:
logMemoryRead(arg[1], arg[2]);
logTrace(_instruction, arg);
return 0xcccccc + arg[1];
case Instruction::CREATE2:
logMemoryRead(arg[2], arg[3]);
logTrace(_instruction, arg);
return 0xdddddd + arg[1];
case Instruction::CALL:
case Instruction::CALLCODE:
// TODO assign returndata
logMemoryRead(arg[3], arg[4]);
logMemoryWrite(arg[5], arg[6]);
logTrace(_instruction, arg);
return arg[0] & 1;
case Instruction::DELEGATECALL:
case Instruction::STATICCALL:
logMemoryRead(arg[2], arg[3]);
logMemoryWrite(arg[4], arg[5]);
logTrace(_instruction, arg);
return 0;
case Instruction::RETURN:
{
bytes data;
if (logMemoryRead(arg[0], arg[1]))
data = bytesConstRef(m_state.memory.data() + size_t(arg[0]), size_t(arg[1])).toBytes();
logTrace(_instruction, arg, data);
throw InterpreterTerminated();
}
case Instruction::REVERT:
logMemoryRead(arg[0], arg[1]);
logTrace(_instruction, arg);
throw InterpreterTerminated();
case Instruction::INVALID:
logTrace(_instruction);
throw InterpreterTerminated();
case Instruction::SELFDESTRUCT:
logTrace(_instruction, arg);
throw InterpreterTerminated();
case Instruction::POP:
break;
// --------------- invalid in strict assembly ---------------
case Instruction::JUMP:
case Instruction::JUMPI:
case Instruction::JUMPDEST:
case Instruction::PUSH1:
case Instruction::PUSH2:
case Instruction::PUSH3:
case Instruction::PUSH4:
case Instruction::PUSH5:
case Instruction::PUSH6:
case Instruction::PUSH7:
case Instruction::PUSH8:
case Instruction::PUSH9:
case Instruction::PUSH10:
case Instruction::PUSH11:
case Instruction::PUSH12:
case Instruction::PUSH13:
case Instruction::PUSH14:
case Instruction::PUSH15:
case Instruction::PUSH16:
case Instruction::PUSH17:
case Instruction::PUSH18:
case Instruction::PUSH19:
case Instruction::PUSH20:
case Instruction::PUSH21:
case Instruction::PUSH22:
case Instruction::PUSH23:
case Instruction::PUSH24:
case Instruction::PUSH25:
case Instruction::PUSH26:
case Instruction::PUSH27:
case Instruction::PUSH28:
case Instruction::PUSH29:
case Instruction::PUSH30:
case Instruction::PUSH31:
case Instruction::PUSH32:
case Instruction::DUP1:
case Instruction::DUP2:
case Instruction::DUP3:
case Instruction::DUP4:
case Instruction::DUP5:
case Instruction::DUP6:
case Instruction::DUP7:
case Instruction::DUP8:
case Instruction::DUP9:
case Instruction::DUP10:
case Instruction::DUP11:
case Instruction::DUP12:
case Instruction::DUP13:
case Instruction::DUP14:
case Instruction::DUP15:
case Instruction::DUP16:
case Instruction::SWAP1:
case Instruction::SWAP2:
case Instruction::SWAP3:
case Instruction::SWAP4:
case Instruction::SWAP5:
case Instruction::SWAP6:
case Instruction::SWAP7:
case Instruction::SWAP8:
case Instruction::SWAP9:
case Instruction::SWAP10:
case Instruction::SWAP11:
case Instruction::SWAP12:
case Instruction::SWAP13:
case Instruction::SWAP14:
case Instruction::SWAP15:
case Instruction::SWAP16:
// --------------- EVM 2.0 ---------------
case Instruction::JUMPTO:
case Instruction::JUMPIF:
case Instruction::JUMPV:
case Instruction::JUMPSUB:
case Instruction::JUMPSUBV:
case Instruction::BEGINSUB:
case Instruction::BEGINDATA:
case Instruction::RETURNSUB:
case Instruction::PUTLOCAL:
case Instruction::GETLOCAL:
{
yulAssert(false, "");
return 0;
}
}
return 0;
}
bool EVMInstructionInterpreter::logMemoryRead(u256 const& _offset, u256 const& _size)
{
return logMemory(false, _offset, _size);
}
bool EVMInstructionInterpreter::logMemoryWrite(u256 const& _offset, u256 const& _size, bytes const& _data)
{
return logMemory(true, _offset, _size, _data);
}
bool EVMInstructionInterpreter::logMemory(bool _write, u256 const& _offset, u256 const& _size, bytes const& _data)
{
/// Memory size limit. Anything beyond this will still work, but it has
/// deterministic yet not necessarily consistent behaviour.
size_t constexpr maxMemSize = 0x20000000;
logTrace(_write ? "MSTORE_AT_SIZE" : "MLOAD_FROM_SIZE", {_offset, _size}, _data);
if (_offset + _size >= _offset)
{
u256 newSize = _offset + _size;
newSize = (newSize + 0x1f) & ~u256(0x1f);
m_state.msize = max(m_state.msize, newSize);
if (newSize < maxMemSize)
{
if (m_state.memory.size() < newSize)
m_state.memory.resize(size_t(newSize));
return true;
}
}
else
m_state.msize = u256(-1);
return false;
}
void EVMInstructionInterpreter::logTrace(solidity::Instruction _instruction, std::vector<u256> const& _arguments, bytes const& _data)
{
logTrace(solidity::instructionInfo(_instruction).name, _arguments, _data);
}
void EVMInstructionInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector<u256> const& _arguments, bytes const& _data)
{
string message = _pseudoInstruction + "(";
for (size_t i = 0; i < _arguments.size(); ++i)
message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]);
message += ")";
if (!_data.empty())
message += " [" + toHex(_data) + "]";
m_state.trace.emplace_back(std::move(message));
if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize)
{
m_state.trace.emplace_back("Trace size limit reached.");
throw InterpreterTerminated();
}
}

View File

@ -0,0 +1,90 @@
/*
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/>.
*/
/**
* Yul interpreter module that evaluates EVM instructions.
*/
#pragma once
#include <libyul/AsmDataForward.h>
#include <libdevcore/CommonData.h>
#include <vector>
namespace dev
{
namespace solidity
{
enum class Instruction: uint8_t;
}
}
namespace yul
{
namespace test
{
struct InterpreterState;
/**
* Interprets EVM instructions based on the current state and logs instructions with
* side-effects.
*
* Since this is mainly meant to be used for differential fuzz testing, it is focused
* on a single contract only, does not do any gas counting and differs from the correct
* implementation in many ways:
*
* - If memory access to a "large" memory position is performed, a deterministic
* value is returned. Data that is stored in a "large" memory position is not
* retained.
* - The blockhash instruction returns a fixed value if the argument is in range.
* - Extcodesize returns a deterministic value depending on the address.
* - Extcodecopy copies a deterministic value depending on the address.
* - And many other things
*
* The main focus is that the generated execution trace is the same for equivalent executions
* and likely to be different for non-equivalent executions.
*/
class EVMInstructionInterpreter
{
public:
explicit EVMInstructionInterpreter(InterpreterState& _state):
m_state(_state)
{}
dev::u256 eval(dev::solidity::Instruction _instruction, std::vector<dev::u256> const& _arguments);
private:
/// Record a memory read in the trace. Also updates m_state.msize
/// @returns true if m_state.memory can be used at that offset.
bool logMemoryRead(dev::u256 const& _offset, dev::u256 const& _size = 32);
/// Record a memory write in the trace. Also updates m_state.msize
/// @returns true if m_state.memory can be used at that offset.
bool logMemoryWrite(dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {});
bool logMemory(bool _write, dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {});
void logTrace(dev::solidity::Instruction _instruction, std::vector<dev::u256> const& _arguments = {}, dev::bytes const& _data = {});
/// Appends a log to the trace representing an instruction or similar operation by string,
/// with arguments and auxiliary data (if nonempty).
void logTrace(std::string const& _pseudoInstruction, std::vector<dev::u256> const& _arguments = {}, dev::bytes const& _data = {});
InterpreterState& m_state;
};
}
}

View File

@ -0,0 +1,235 @@
/*
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/>.
*/
/**
* Yul interpreter.
*/
#include <test/tools/yulInterpreter/Interpreter.h>
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
#include <libyul/AsmData.h>
#include <libyul/Utilities.h>
#include <liblangutil/Exceptions.h>
#include <libdevcore/FixedHash.h>
#include <boost/range/adaptor/reversed.hpp>
using namespace std;
using namespace dev;
using namespace yul;
using namespace yul::test;
void Interpreter::operator()(ExpressionStatement const& _expressionStatement)
{
evaluateMulti(_expressionStatement.expression);
}
void Interpreter::operator()(Assignment const& _assignment)
{
solAssert(_assignment.value, "");
vector<u256> values = evaluateMulti(*_assignment.value);
solAssert(values.size() == _assignment.variableNames.size(), "");
for (size_t i = 0; i < values.size(); ++i)
{
YulString varName = _assignment.variableNames.at(i).name;
solAssert(m_variables.count(varName), "");
m_variables[varName] = values.at(i);
}
}
void Interpreter::operator()(VariableDeclaration const& _declaration)
{
vector<u256> values(_declaration.variables.size(), 0);
if (_declaration.value)
values = evaluateMulti(*_declaration.value);
solAssert(values.size() == _declaration.variables.size(), "");
for (size_t i = 0; i < values.size(); ++i)
{
YulString varName = _declaration.variables.at(i).name;
solAssert(!m_variables.count(varName), "");
m_variables[varName] = values.at(i);
m_scopes.back().insert(varName);
}
}
void Interpreter::operator()(If const& _if)
{
solAssert(_if.condition, "");
if (evaluate(*_if.condition) != 0)
(*this)(_if.body);
}
void Interpreter::operator()(Switch const& _switch)
{
solAssert(_switch.expression, "");
u256 val = evaluate(*_switch.expression);
solAssert(!_switch.cases.empty(), "");
for (auto const& c: _switch.cases)
// Default case has to be last.
if (!c.value || evaluate(*c.value) == val)
{
(*this)(c.body);
break;
}
}
void Interpreter::operator()(FunctionDefinition const&)
{
}
void Interpreter::operator()(ForLoop const& _forLoop)
{
solAssert(_forLoop.condition, "");
openScope();
for (auto const& statement: _forLoop.pre.statements)
visit(statement);
while (evaluate(*_forLoop.condition) != 0)
{
(*this)(_forLoop.body);
(*this)(_forLoop.post);
}
closeScope();
}
void Interpreter::operator()(Block const& _block)
{
openScope();
// Register functions.
for (auto const& statement: _block.statements)
if (statement.type() == typeid(FunctionDefinition))
{
FunctionDefinition const& funDef = boost::get<FunctionDefinition>(statement);
m_functions[funDef.name] = &funDef;
m_scopes.back().insert(funDef.name);
}
ASTWalker::operator()(_block);
closeScope();
}
u256 Interpreter::evaluate(Expression const& _expression)
{
ExpressionEvaluator ev(m_state, m_variables, m_functions);
ev.visit(_expression);
return ev.value();
}
vector<u256> Interpreter::evaluateMulti(Expression const& _expression)
{
ExpressionEvaluator ev(m_state, m_variables, m_functions);
ev.visit(_expression);
return ev.values();
}
void Interpreter::closeScope()
{
for (auto const& var: m_scopes.back())
{
size_t erased = m_variables.erase(var) + m_functions.erase(var);
solAssert(erased == 1, "");
}
m_scopes.pop_back();
}
void ExpressionEvaluator::operator()(Literal const& _literal)
{
static YulString const trueString("true");
static YulString const falseString("false");
switch (_literal.kind)
{
case LiteralKind::Boolean:
solAssert(_literal.value == trueString || _literal.value == falseString, "");
setValue(_literal.value == trueString ? 1 : 0);
break;
case LiteralKind::Number:
setValue(valueOfNumberLiteral(_literal));
break;
case LiteralKind::String:
solAssert(_literal.value.str().size() <= 32, "");
setValue(u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft)));
break;
}
}
void ExpressionEvaluator::operator()(Identifier const& _identifier)
{
solAssert(m_variables.count(_identifier.name), "");
setValue(m_variables.at(_identifier.name));
}
void ExpressionEvaluator::operator()(FunctionalInstruction const& _instr)
{
evaluateArgs(_instr.arguments);
EVMInstructionInterpreter interpreter(m_state);
// The instruction might also return nothing, but it does not
// hurt to set the value in that case.
setValue(interpreter.eval(_instr.instruction, values()));
}
void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
{
solAssert(m_functions.count(_funCall.functionName.name), "");
evaluateArgs(_funCall.arguments);
FunctionDefinition const& fun = *m_functions.at(_funCall.functionName.name);
solAssert(m_values.size() == fun.parameters.size(), "");
map<YulString, u256> variables;
for (size_t i = 0; i < fun.parameters.size(); ++i)
variables[fun.parameters.at(i).name] = m_values.at(i);
for (size_t i = 0; i < fun.returnVariables.size(); ++i)
variables[fun.returnVariables.at(i).name] = 0;
// TODO function name lookup could be a little more efficient,
// we have to copy the list here.
Interpreter interpreter(m_state, variables, m_functions);
interpreter(fun.body);
m_values.clear();
for (auto const& retVar: fun.returnVariables)
m_values.emplace_back(interpreter.valueOfVariable(retVar.name));
}
u256 ExpressionEvaluator::value() const
{
solAssert(m_values.size() == 1, "");
return m_values.front();
}
void ExpressionEvaluator::setValue(u256 _value)
{
m_values.clear();
m_values.emplace_back(std::move(_value));
}
void ExpressionEvaluator::evaluateArgs(vector<Expression> const& _expr)
{
vector<u256> values;
/// Function arguments are evaluated in reverse.
for (auto const& expr: _expr | boost::adaptors::reversed)
{
visit(expr);
values.push_back(value());
}
m_values = std::move(values);
std::reverse(m_values.begin(), m_values.end());
}

View File

@ -0,0 +1,162 @@
/*
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/>.
*/
/**
* Yul interpreter.
*/
#pragma once
#include <libyul/AsmDataForward.h>
#include <libyul/optimiser/ASTWalker.h>
#include <libdevcore/FixedHash.h>
#include <libdevcore/CommonData.h>
#include <libdevcore/Exceptions.h>
#include <map>
namespace yul
{
namespace test
{
class InterpreterTerminated: dev::Exception
{
};
struct InterpreterState
{
dev::bytes calldata;
dev::bytes returndata;
/// TODO turn this into "vector with holes" for the randomized testing
dev::bytes memory;
/// This is different than memory.size() because we ignore gas.
dev::u256 msize;
std::map<dev::h256, dev::h256> storage;
dev::u160 address = 0x11111111;
dev::u256 balance = 0x22222222;
dev::u160 origin = 0x33333333;
dev::u160 caller = 0x44444444;
dev::u256 callvalue = 0x55555555;
/// Deployed code
dev::bytes code = dev::asBytes("codecodecodecodecode");
dev::u256 gasprice = 0x66666666;
dev::u160 coinbase = 0x77777777;
dev::u256 timestamp = 0x88888888;
dev::u256 blockNumber = 1024;
dev::u256 difficulty = 0x9999999;
dev::u256 gaslimit = 4000000;
/// Log of changes / effects. Sholud be structured data in the future.
std::vector<std::string> trace;
/// This is actually an input parameter that more or less limits the runtime.
size_t maxTraceSize = 0;
};
/**
* Yul interpreter.
*/
class Interpreter: public ASTWalker
{
public:
Interpreter(
InterpreterState& _state,
std::map<YulString, dev::u256> _variables = {},
std::map<YulString, FunctionDefinition const*> _functions = {}
):
m_state(_state),
m_variables(std::move(_variables)),
m_functions(std::move(_functions))
{}
void operator()(ExpressionStatement const& _statement) override;
void operator()(Assignment const& _assignment) override;
void operator()(VariableDeclaration const& _varDecl) override;
void operator()(If const& _if) override;
void operator()(Switch const& _switch) override;
void operator()(FunctionDefinition const&) override;
void operator()(ForLoop const&) override;
void operator()(Block const& _block) override;
std::vector<std::string> const& trace() const { return m_state.trace; }
dev::u256 valueOfVariable(YulString _name) const { return m_variables.at(_name); }
private:
/// Asserts that the expression evaluates to exactly one value and returns it.
dev::u256 evaluate(Expression const& _expression);
/// Evaluates the expression and returns its value.
std::vector<dev::u256> evaluateMulti(Expression const& _expression);
void openScope() { m_scopes.push_back({}); }
/// Unregisters variables.
void closeScope();
InterpreterState& m_state;
/// Values of variables.
std::map<YulString, dev::u256> m_variables;
/// Meanings of functions.
std::map<YulString, FunctionDefinition const*> m_functions;
/// Scopes of variables and functions, used to clear them at end of blocks.
std::vector<std::set<YulString>> m_scopes;
};
/**
* Yul expression evaluator.
*/
class ExpressionEvaluator: public ASTWalker
{
public:
ExpressionEvaluator(
InterpreterState& _state,
std::map<YulString, dev::u256> const& _variables,
std::map<YulString, FunctionDefinition const*> const& _functions
):
m_state(_state),
m_variables(_variables),
m_functions(_functions)
{}
void operator()(Literal const&) override;
void operator()(Identifier const&) override;
void operator()(FunctionalInstruction const& _instr) override;
void operator()(FunctionCall const& _funCall) override;
/// Asserts that the expression has exactly one value and returns it.
dev::u256 value() const;
/// Returns the list of values of the expression.
std::vector<dev::u256> values() const { return m_values; }
private:
void setValue(dev::u256 _value);
/// Evaluates the given expression from right to left and
/// stores it in m_value.
void evaluateArgs(std::vector<Expression> const& _expr);
InterpreterState& m_state;
/// Values of variables.
std::map<YulString, dev::u256> const& m_variables;
/// Meanings of functions.
std::map<YulString, FunctionDefinition const*> const& m_functions;
/// Current value of the expression
std::vector<dev::u256> m_values;
};
}
}

144
test/tools/yulrun.cpp Normal file
View File

@ -0,0 +1,144 @@
/*
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/>.
*/
/**
* Yul interpreter.
*/
#include <test/tools/yulInterpreter/Interpreter.h>
#include <libyul/AsmAnalysisInfo.h>
#include <libyul/AsmParser.h>
#include <libyul/AsmAnalysis.h>
#include <libyul/Dialect.h>
#include <libyul/backends/evm/EVMDialect.h>
#include <libyul/AssemblyStack.h>
#include <liblangutil/Exceptions.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceReferenceFormatter.h>
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <boost/program_options.hpp>
#include <string>
#include <memory>
#include <iostream>
using namespace std;
using namespace langutil;
using namespace yul;
using namespace dev;
using namespace yul::test;
namespace po = boost::program_options;
namespace
{
void printErrors(ErrorList const& _errors)
{
for (auto const& error: _errors)
SourceReferenceFormatter(cout).printExceptionInformation(
*error,
(error->type() == Error::Type::Warning) ? "Warning" : "Error"
);
}
pair<shared_ptr<Block>, shared_ptr<AsmAnalysisInfo>> parse(string const& _source)
{
AssemblyStack stack(dev::solidity::EVMVersion(), AssemblyStack::Language::StrictAssembly);
if (stack.parseAndAnalyze("--INPUT--", _source))
{
yulAssert(stack.errors().empty(), "Parsed successfully but had errors.");
return make_pair(stack.parserResult()->code, stack.parserResult()->analysisInfo);
}
else
{
printErrors(stack.errors());
return {};
}
}
void interpret()
{
string source = readStandardInput();
shared_ptr<Block> ast;
shared_ptr<AsmAnalysisInfo> analysisInfo;
tie(ast, analysisInfo) = parse(source);
if (!ast || !analysisInfo)
return;
InterpreterState state;
state.maxTraceSize = 10000;
Interpreter interpreter(state);
try
{
interpreter(*ast);
}
catch (InterpreterTerminated const&)
{
}
cout << "Trace:" << endl;
for (auto const& line: interpreter.trace())
cout << " " << line << endl;
cout << "Memory dump:" << endl;
for (size_t i = 0; i < state.memory.size(); i += 0x20)
cout << " " << std::hex << std::setw(4) << i << ": " << toHex(bytesConstRef(state.memory.data() + i, 0x20).toBytes()) << endl;
cout << "Storage dump:" << endl;
for (auto const& slot: state.storage)
cout << " " << slot.first.hex() << ": " << slot.second.hex() << endl;
}
}
int main(int argc, char** argv)
{
po::options_description options(
R"(yulrun, the Yul interpreter.
Usage: yulrun [Options] < input
Reads a single source from stdin, runs it and prints a trace of all side-effects.
Allowed options)",
po::options_description::m_default_line_length,
po::options_description::m_default_line_length - 23);
options.add_options()
("help", "Show this help screen.");
po::variables_map arguments;
try
{
po::command_line_parser cmdLineParser(argc, argv);
cmdLineParser.options(options);
po::store(cmdLineParser.run(), arguments);
}
catch (po::error const& _exception)
{
cerr << _exception.what() << endl;
return 1;
}
if (arguments.count("help"))
cout << options;
else
interpret();
return 0;
}