Gav Wood
10 years ago
20 changed files with 956 additions and 411 deletions
@ -0,0 +1,326 @@ |
|||||
|
/*
|
||||
|
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/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @file KnownState.cpp |
||||
|
* @author Christian <c@ethdev.com> |
||||
|
* @date 2015 |
||||
|
* Contains knowledge about the state of the virtual machine at a specific instruction. |
||||
|
*/ |
||||
|
|
||||
|
#include "KnownState.h" |
||||
|
#include <functional> |
||||
|
#include <libdevcrypto/SHA3.h> |
||||
|
#include <libevmasm/AssemblyItem.h> |
||||
|
|
||||
|
using namespace std; |
||||
|
using namespace dev; |
||||
|
using namespace dev::eth; |
||||
|
|
||||
|
ostream& KnownState::stream(ostream& _out) const |
||||
|
{ |
||||
|
auto streamExpressionClass = [this](ostream& _out, Id _id) |
||||
|
{ |
||||
|
auto const& expr = m_expressionClasses->representative(_id); |
||||
|
_out << " " << dec << _id << ": "; |
||||
|
if (!expr.item) |
||||
|
_out << " no item"; |
||||
|
else if (expr.item->type() == UndefinedItem) |
||||
|
_out << " unknown " << int(expr.item->data()); |
||||
|
else |
||||
|
_out << *expr.item; |
||||
|
if (expr.sequenceNumber) |
||||
|
_out << "@" << dec << expr.sequenceNumber; |
||||
|
_out << "("; |
||||
|
for (Id arg: expr.arguments) |
||||
|
_out << dec << arg << ","; |
||||
|
_out << ")" << endl; |
||||
|
}; |
||||
|
|
||||
|
_out << "=== State ===" << endl; |
||||
|
_out << "Stack height: " << dec << m_stackHeight << endl; |
||||
|
_out << "Equivalence classes: " << endl; |
||||
|
for (Id eqClass = 0; eqClass < m_expressionClasses->size(); ++eqClass) |
||||
|
streamExpressionClass(_out, eqClass); |
||||
|
|
||||
|
_out << "Stack: " << endl; |
||||
|
for (auto const& it: m_stackElements) |
||||
|
{ |
||||
|
_out << " " << dec << it.first << ": "; |
||||
|
streamExpressionClass(_out, it.second); |
||||
|
} |
||||
|
_out << "Storage: " << endl; |
||||
|
for (auto const& it: m_storageContent) |
||||
|
{ |
||||
|
_out << " "; |
||||
|
streamExpressionClass(_out, it.first); |
||||
|
_out << ": "; |
||||
|
streamExpressionClass(_out, it.second); |
||||
|
} |
||||
|
_out << "Memory: " << endl; |
||||
|
for (auto const& it: m_memoryContent) |
||||
|
{ |
||||
|
_out << " "; |
||||
|
streamExpressionClass(_out, it.first); |
||||
|
_out << ": "; |
||||
|
streamExpressionClass(_out, it.second); |
||||
|
} |
||||
|
|
||||
|
return _out; |
||||
|
} |
||||
|
|
||||
|
KnownState::StoreOperation KnownState::feedItem(AssemblyItem const& _item, bool _copyItem) |
||||
|
{ |
||||
|
StoreOperation op; |
||||
|
if (_item.type() == Tag) |
||||
|
{ |
||||
|
// can be ignored
|
||||
|
} |
||||
|
else if (_item.type() != Operation) |
||||
|
{ |
||||
|
assertThrow(_item.deposit() == 1, InvalidDeposit, ""); |
||||
|
setStackElement(++m_stackHeight, m_expressionClasses->find(_item, {}, _copyItem)); |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
Instruction instruction = _item.instruction(); |
||||
|
InstructionInfo info = instructionInfo(instruction); |
||||
|
if (SemanticInformation::isDupInstruction(_item)) |
||||
|
setStackElement( |
||||
|
m_stackHeight + 1, |
||||
|
stackElement( |
||||
|
m_stackHeight - int(instruction) + int(Instruction::DUP1), |
||||
|
_item.getLocation() |
||||
|
) |
||||
|
); |
||||
|
else if (SemanticInformation::isSwapInstruction(_item)) |
||||
|
swapStackElements( |
||||
|
m_stackHeight, |
||||
|
m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1), |
||||
|
_item.getLocation() |
||||
|
); |
||||
|
else if (instruction != Instruction::POP) |
||||
|
{ |
||||
|
vector<Id> arguments(info.args); |
||||
|
for (int i = 0; i < info.args; ++i) |
||||
|
arguments[i] = stackElement(m_stackHeight - i, _item.getLocation()); |
||||
|
|
||||
|
if (_item.instruction() == Instruction::SSTORE) |
||||
|
op = storeInStorage(arguments[0], arguments[1], _item.getLocation()); |
||||
|
else if (_item.instruction() == Instruction::SLOAD) |
||||
|
setStackElement( |
||||
|
m_stackHeight + _item.deposit(), |
||||
|
loadFromStorage(arguments[0], _item.getLocation()) |
||||
|
); |
||||
|
else if (_item.instruction() == Instruction::MSTORE) |
||||
|
op = storeInMemory(arguments[0], arguments[1], _item.getLocation()); |
||||
|
else if (_item.instruction() == Instruction::MLOAD) |
||||
|
setStackElement( |
||||
|
m_stackHeight + _item.deposit(), |
||||
|
loadFromMemory(arguments[0], _item.getLocation()) |
||||
|
); |
||||
|
else if (_item.instruction() == Instruction::SHA3) |
||||
|
setStackElement( |
||||
|
m_stackHeight + _item.deposit(), |
||||
|
applySha3(arguments.at(0), arguments.at(1), _item.getLocation()) |
||||
|
); |
||||
|
else |
||||
|
{ |
||||
|
if (SemanticInformation::invalidatesMemory(_item.instruction())) |
||||
|
resetMemory(); |
||||
|
if (SemanticInformation::invalidatesStorage(_item.instruction())) |
||||
|
resetStorage(); |
||||
|
assertThrow(info.ret <= 1, InvalidDeposit, ""); |
||||
|
if (info.ret == 1) |
||||
|
setStackElement( |
||||
|
m_stackHeight + _item.deposit(), |
||||
|
m_expressionClasses->find(_item, arguments, _copyItem) |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
m_stackElements.erase( |
||||
|
m_stackElements.upper_bound(m_stackHeight + _item.deposit()), |
||||
|
m_stackElements.end() |
||||
|
); |
||||
|
m_stackHeight += _item.deposit(); |
||||
|
} |
||||
|
return op; |
||||
|
} |
||||
|
|
||||
|
void KnownState::reduceToCommonKnowledge(KnownState const& /*_other*/) |
||||
|
{ |
||||
|
//@todo
|
||||
|
*this = KnownState(m_expressionClasses); |
||||
|
} |
||||
|
|
||||
|
bool KnownState::operator==(const KnownState& _other) const |
||||
|
{ |
||||
|
//@todo
|
||||
|
return ( |
||||
|
m_stackElements.empty() && |
||||
|
_other.m_stackElements.empty() && |
||||
|
m_storageContent.empty() && |
||||
|
_other.m_storageContent.empty() && |
||||
|
m_memoryContent.empty() && |
||||
|
_other.m_memoryContent.empty() |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
ExpressionClasses::Id KnownState::stackElement(int _stackHeight, SourceLocation const& _location) |
||||
|
{ |
||||
|
if (m_stackElements.count(_stackHeight)) |
||||
|
return m_stackElements.at(_stackHeight); |
||||
|
// Stack element not found (not assigned yet), create new unknown equivalence class.
|
||||
|
//@todo check that we do not infer incorrect equivalences when the stack is cleared partially
|
||||
|
//in between.
|
||||
|
return m_stackElements[_stackHeight] = initialStackElement(_stackHeight, _location); |
||||
|
} |
||||
|
|
||||
|
ExpressionClasses::Id KnownState::initialStackElement( |
||||
|
int _stackHeight, |
||||
|
SourceLocation const& _location |
||||
|
) |
||||
|
{ |
||||
|
// This is a special assembly item that refers to elements pre-existing on the initial stack.
|
||||
|
return m_expressionClasses->find(AssemblyItem(UndefinedItem, u256(_stackHeight), _location)); |
||||
|
} |
||||
|
|
||||
|
void KnownState::setStackElement(int _stackHeight, Id _class) |
||||
|
{ |
||||
|
m_stackElements[_stackHeight] = _class; |
||||
|
} |
||||
|
|
||||
|
void KnownState::swapStackElements( |
||||
|
int _stackHeightA, |
||||
|
int _stackHeightB, |
||||
|
SourceLocation const& _location |
||||
|
) |
||||
|
{ |
||||
|
assertThrow(_stackHeightA != _stackHeightB, OptimizerException, "Swap on same stack elements."); |
||||
|
// ensure they are created
|
||||
|
stackElement(_stackHeightA, _location); |
||||
|
stackElement(_stackHeightB, _location); |
||||
|
|
||||
|
swap(m_stackElements[_stackHeightA], m_stackElements[_stackHeightB]); |
||||
|
} |
||||
|
|
||||
|
KnownState::StoreOperation KnownState::storeInStorage( |
||||
|
Id _slot, |
||||
|
Id _value, |
||||
|
SourceLocation const& _location) |
||||
|
{ |
||||
|
if (m_storageContent.count(_slot) && m_storageContent[_slot] == _value) |
||||
|
// do not execute the storage if we know that the value is already there
|
||||
|
return StoreOperation(); |
||||
|
m_sequenceNumber++; |
||||
|
decltype(m_storageContent) storageContents; |
||||
|
// Copy over all values (i.e. retain knowledge about them) where we know that this store
|
||||
|
// operation will not destroy the knowledge. Specifically, we copy storage locations we know
|
||||
|
// are different from _slot or locations where we know that the stored value is equal to _value.
|
||||
|
for (auto const& storageItem: m_storageContent) |
||||
|
if (m_expressionClasses->knownToBeDifferent(storageItem.first, _slot) || storageItem.second == _value) |
||||
|
storageContents.insert(storageItem); |
||||
|
m_storageContent = move(storageContents); |
||||
|
|
||||
|
AssemblyItem item(Instruction::SSTORE, _location); |
||||
|
Id id = m_expressionClasses->find(item, {_slot, _value}, true, m_sequenceNumber); |
||||
|
StoreOperation operation(StoreOperation::Storage, _slot, m_sequenceNumber, id); |
||||
|
m_storageContent[_slot] = _value; |
||||
|
// increment a second time so that we get unique sequence numbers for writes
|
||||
|
m_sequenceNumber++; |
||||
|
|
||||
|
return operation; |
||||
|
} |
||||
|
|
||||
|
ExpressionClasses::Id KnownState::loadFromStorage(Id _slot, SourceLocation const& _location) |
||||
|
{ |
||||
|
if (m_storageContent.count(_slot)) |
||||
|
return m_storageContent.at(_slot); |
||||
|
|
||||
|
AssemblyItem item(Instruction::SLOAD, _location); |
||||
|
return m_storageContent[_slot] = m_expressionClasses->find(item, {_slot}, true, m_sequenceNumber); |
||||
|
} |
||||
|
|
||||
|
KnownState::StoreOperation KnownState::storeInMemory(Id _slot, Id _value, SourceLocation const& _location) |
||||
|
{ |
||||
|
if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value) |
||||
|
// do not execute the store if we know that the value is already there
|
||||
|
return StoreOperation(); |
||||
|
m_sequenceNumber++; |
||||
|
decltype(m_memoryContent) memoryContents; |
||||
|
// copy over values at points where we know that they are different from _slot by at least 32
|
||||
|
for (auto const& memoryItem: m_memoryContent) |
||||
|
if (m_expressionClasses->knownToBeDifferentBy32(memoryItem.first, _slot)) |
||||
|
memoryContents.insert(memoryItem); |
||||
|
m_memoryContent = move(memoryContents); |
||||
|
|
||||
|
AssemblyItem item(Instruction::MSTORE, _location); |
||||
|
Id id = m_expressionClasses->find(item, {_slot, _value}, true, m_sequenceNumber); |
||||
|
StoreOperation operation(StoreOperation(StoreOperation::Memory, _slot, m_sequenceNumber, id)); |
||||
|
m_memoryContent[_slot] = _value; |
||||
|
// increment a second time so that we get unique sequence numbers for writes
|
||||
|
m_sequenceNumber++; |
||||
|
return operation; |
||||
|
} |
||||
|
|
||||
|
ExpressionClasses::Id KnownState::loadFromMemory(Id _slot, SourceLocation const& _location) |
||||
|
{ |
||||
|
if (m_memoryContent.count(_slot)) |
||||
|
return m_memoryContent.at(_slot); |
||||
|
|
||||
|
AssemblyItem item(Instruction::MLOAD, _location); |
||||
|
return m_memoryContent[_slot] = m_expressionClasses->find(item, {_slot}, true, m_sequenceNumber); |
||||
|
} |
||||
|
|
||||
|
KnownState::Id KnownState::applySha3( |
||||
|
Id _start, |
||||
|
Id _length, |
||||
|
SourceLocation const& _location |
||||
|
) |
||||
|
{ |
||||
|
AssemblyItem sha3Item(Instruction::SHA3, _location); |
||||
|
// Special logic if length is a short constant, otherwise we cannot tell.
|
||||
|
u256 const* l = m_expressionClasses->knownConstant(_length); |
||||
|
// unknown or too large length
|
||||
|
if (!l || *l > 128) |
||||
|
return m_expressionClasses->find(sha3Item, {_start, _length}, true, m_sequenceNumber); |
||||
|
|
||||
|
vector<Id> arguments; |
||||
|
for (u256 i = 0; i < *l; i += 32) |
||||
|
{ |
||||
|
Id slot = m_expressionClasses->find( |
||||
|
AssemblyItem(Instruction::ADD, _location), |
||||
|
{_start, m_expressionClasses->find(i)} |
||||
|
); |
||||
|
arguments.push_back(loadFromMemory(slot, _location)); |
||||
|
} |
||||
|
if (m_knownSha3Hashes.count(arguments)) |
||||
|
return m_knownSha3Hashes.at(arguments); |
||||
|
Id v; |
||||
|
// If all arguments are known constants, compute the sha3 here
|
||||
|
if (all_of(arguments.begin(), arguments.end(), [this](Id _a) { return !!m_expressionClasses->knownConstant(_a); })) |
||||
|
{ |
||||
|
bytes data; |
||||
|
for (Id a: arguments) |
||||
|
data += toBigEndian(*m_expressionClasses->knownConstant(a)); |
||||
|
data.resize(size_t(*l)); |
||||
|
v = m_expressionClasses->find(AssemblyItem(u256(sha3(data)), _location)); |
||||
|
} |
||||
|
else |
||||
|
v = m_expressionClasses->find(sha3Item, {_start, _length}, true, m_sequenceNumber); |
||||
|
return m_knownSha3Hashes[arguments] = v; |
||||
|
} |
||||
|
|
@ -0,0 +1,163 @@ |
|||||
|
/*
|
||||
|
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/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @file KnownState.h |
||||
|
* @author Christian <c@ethdev.com> |
||||
|
* @date 2015 |
||||
|
* Contains knowledge about the state of the virtual machine at a specific instruction. |
||||
|
*/ |
||||
|
|
||||
|
#pragma once |
||||
|
|
||||
|
#include <vector> |
||||
|
#include <map> |
||||
|
#include <set> |
||||
|
#include <tuple> |
||||
|
#include <memory> |
||||
|
#include <ostream> |
||||
|
#include <libdevcore/CommonIO.h> |
||||
|
#include <libdevcore/Exceptions.h> |
||||
|
#include <libevmasm/ExpressionClasses.h> |
||||
|
#include <libevmasm/SemanticInformation.h> |
||||
|
|
||||
|
namespace dev |
||||
|
{ |
||||
|
namespace eth |
||||
|
{ |
||||
|
|
||||
|
class AssemblyItem; |
||||
|
using AssemblyItems = std::vector<AssemblyItem>; |
||||
|
|
||||
|
/**
|
||||
|
* Class to infer and store knowledge about the state of the virtual machine at a specific |
||||
|
* instruction. |
||||
|
* |
||||
|
* The general workings are that for each assembly item that is fed, an equivalence class is |
||||
|
* derived from the operation and the equivalence class of its arguments. DUPi, SWAPi and some |
||||
|
* arithmetic instructions are used to infer equivalences while these classes are determined. |
||||
|
*/ |
||||
|
class KnownState |
||||
|
{ |
||||
|
public: |
||||
|
using Id = ExpressionClasses::Id; |
||||
|
struct StoreOperation |
||||
|
{ |
||||
|
enum Target { Invalid, Memory, Storage }; |
||||
|
StoreOperation(): target(Invalid), sequenceNumber(-1) {} |
||||
|
StoreOperation( |
||||
|
Target _target, |
||||
|
Id _slot, |
||||
|
unsigned _sequenceNumber, |
||||
|
Id _expression |
||||
|
): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {} |
||||
|
bool isValid() const { return target != Invalid; } |
||||
|
Target target; |
||||
|
Id slot; |
||||
|
unsigned sequenceNumber; |
||||
|
Id expression; |
||||
|
}; |
||||
|
|
||||
|
explicit KnownState( |
||||
|
std::shared_ptr<ExpressionClasses> _expressionClasses = std::make_shared<ExpressionClasses>() |
||||
|
): m_expressionClasses(_expressionClasses) |
||||
|
{ |
||||
|
} |
||||
|
|
||||
|
/// Streams debugging information to @a _out.
|
||||
|
std::ostream& stream(std::ostream& _out) const; |
||||
|
|
||||
|
/// Feeds the item into the system for analysis.
|
||||
|
/// @returns a possible store operation
|
||||
|
StoreOperation feedItem(AssemblyItem const& _item, bool _copyItem = false); |
||||
|
|
||||
|
/// Resets any knowledge about storage.
|
||||
|
void resetStorage() { m_storageContent.clear(); } |
||||
|
/// Resets any knowledge about storage.
|
||||
|
void resetMemory() { m_memoryContent.clear(); } |
||||
|
/// Resets any knowledge about the current stack.
|
||||
|
void resetStack() { m_stackElements.clear(); m_stackHeight = 0; } |
||||
|
/// Resets any knowledge.
|
||||
|
void reset() { resetStorage(); resetMemory(); resetStack(); } |
||||
|
|
||||
|
/// Manually increments the storage and memory sequence number.
|
||||
|
void incrementSequenceNumber() { m_sequenceNumber += 2; } |
||||
|
|
||||
|
/// Replaces the state by the intersection with _other, i.e. only equal knowledge is retained.
|
||||
|
/// If the stack heighht is different, the smaller one is used and the stack is compared
|
||||
|
/// relatively.
|
||||
|
void reduceToCommonKnowledge(KnownState const& _other); |
||||
|
|
||||
|
/// @returns a shared pointer to a copy of this state.
|
||||
|
std::shared_ptr<KnownState> copy() const { return std::make_shared<KnownState>(*this); } |
||||
|
|
||||
|
/// @returns true if the knowledge about the state of both objects is (known to be) equal.
|
||||
|
bool operator==(KnownState const& _other) const; |
||||
|
|
||||
|
///@todo the sequence numbers in two copies of this class should never be the same.
|
||||
|
/// might be doable using two-dimensional sequence numbers, where the first value is incremented
|
||||
|
/// for each copy
|
||||
|
|
||||
|
/// Retrieves the current equivalence class fo the given stack element (or generates a new
|
||||
|
/// one if it does not exist yet).
|
||||
|
Id stackElement(int _stackHeight, SourceLocation const& _location); |
||||
|
/// @returns the equivalence class id of the special initial stack element at the given height.
|
||||
|
Id initialStackElement(int _stackHeight, SourceLocation const& _location); |
||||
|
|
||||
|
int stackHeight() const { return m_stackHeight; } |
||||
|
std::map<int, Id> const& stackElements() const { return m_stackElements; } |
||||
|
ExpressionClasses& expressionClasses() const { return *m_expressionClasses; } |
||||
|
|
||||
|
private: |
||||
|
/// Assigns a new equivalence class to the next sequence number of the given stack element.
|
||||
|
void setStackElement(int _stackHeight, Id _class); |
||||
|
/// Swaps the given stack elements in their next sequence number.
|
||||
|
void swapStackElements(int _stackHeightA, int _stackHeightB, SourceLocation const& _location); |
||||
|
|
||||
|
/// Increments the sequence number, deletes all storage information that might be overwritten
|
||||
|
/// and stores the new value at the given slot.
|
||||
|
/// @returns the store operation, which might be invalid if storage was not modified
|
||||
|
StoreOperation storeInStorage(Id _slot, Id _value, SourceLocation const& _location); |
||||
|
/// Retrieves the current value at the given slot in storage or creates a new special sload class.
|
||||
|
Id loadFromStorage(Id _slot, SourceLocation const& _location); |
||||
|
/// Increments the sequence number, deletes all memory information that might be overwritten
|
||||
|
/// and stores the new value at the given slot.
|
||||
|
/// @returns the store operation, which might be invalid if memory was not modified
|
||||
|
StoreOperation storeInMemory(Id _slot, Id _value, SourceLocation const& _location); |
||||
|
/// Retrieves the current value at the given slot in memory or creates a new special mload class.
|
||||
|
Id loadFromMemory(Id _slot, SourceLocation const& _location); |
||||
|
/// Finds or creates a new expression that applies the sha3 hash function to the contents in memory.
|
||||
|
Id applySha3(Id _start, Id _length, SourceLocation const& _location); |
||||
|
|
||||
|
/// Current stack height, can be negative.
|
||||
|
int m_stackHeight = 0; |
||||
|
/// Current stack layout, mapping stack height -> equivalence class
|
||||
|
std::map<int, Id> m_stackElements; |
||||
|
/// Current sequence number, this is incremented with each modification to storage or memory.
|
||||
|
unsigned m_sequenceNumber = 1; |
||||
|
/// Knowledge about storage content.
|
||||
|
std::map<Id, Id> m_storageContent; |
||||
|
/// Knowledge about memory content. Keys are memory addresses, note that the values overlap
|
||||
|
/// and are not contained here if they are not completely known.
|
||||
|
std::map<Id, Id> m_memoryContent; |
||||
|
/// Keeps record of all sha3 hashes that are computed.
|
||||
|
std::map<std::vector<Id>, Id> m_knownSha3Hashes; |
||||
|
/// Structure containing the classes of equivalent expressions.
|
||||
|
std::shared_ptr<ExpressionClasses> m_expressionClasses; |
||||
|
}; |
||||
|
|
||||
|
} |
||||
|
} |
Loading…
Reference in new issue