aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorchriseth <c@ethdev.com>2015-04-07 23:08:49 +0800
committerchriseth <c@ethdev.com>2015-04-07 23:08:49 +0800
commit158795e48f4285d713b11f78cdd04de8c6d1f667 (patch)
treea4194b4dfbac5bcbed9dbd6290e156c945fff56b
parentff4d2cc7dc035a248de4698c2c59a8df14db2e82 (diff)
parent8e19eea7d5d6cd4bffa03f8617023a74268de608 (diff)
downloaddexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar
dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.gz
dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.bz2
dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.lz
dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.xz
dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.tar.zst
dexon-solidity-158795e48f4285d713b11f78cdd04de8c6d1f667.zip
Merge remote-tracking branch 'ethereum/develop' into sol_overloadingFunctions
-rw-r--r--AST.cpp81
-rw-r--r--AST.h135
-rw-r--r--AST_accept.h6
-rw-r--r--ArrayUtils.cpp340
-rw-r--r--ArrayUtils.h24
-rw-r--r--Compiler.cpp46
-rw-r--r--Compiler.h4
-rw-r--r--CompilerContext.cpp59
-rw-r--r--CompilerContext.h31
-rw-r--r--CompilerStack.cpp10
-rw-r--r--CompilerUtils.cpp10
-rw-r--r--ExpressionCompiler.cpp259
-rw-r--r--ExpressionCompiler.h2
-rw-r--r--GlobalContext.cpp19
-rw-r--r--InterfaceHandler.cpp14
-rw-r--r--LValue.cpp186
-rw-r--r--LValue.h10
-rw-r--r--Parser.cpp42
-rw-r--r--Parser.h5
-rw-r--r--Token.h117
-rw-r--r--Types.cpp406
-rw-r--r--Types.h131
-rw-r--r--Utils.h29
23 files changed, 1312 insertions, 654 deletions
diff --git a/AST.cpp b/AST.cpp
index 09c03ef6..ada4a01d 100644
--- a/AST.cpp
+++ b/AST.cpp
@@ -21,6 +21,7 @@
*/
#include <algorithm>
+#include <boost/range/adaptor/reversed.hpp>
#include <libsolidity/Utils.h>
#include <libsolidity/AST.h>
#include <libsolidity/ASTVisitor.h>
@@ -52,6 +53,7 @@ void ContractDefinition::checkTypeRequirements()
baseSpecifier->checkTypeRequirements();
checkIllegalOverrides();
+ checkAbstractFunctions();
FunctionDefinition const* constructor = getConstructor();
if (constructor && !constructor->getReturnParameters().empty())
@@ -60,6 +62,7 @@ void ContractDefinition::checkTypeRequirements()
FunctionDefinition const* fallbackFunction = nullptr;
for (ASTPointer<FunctionDefinition> const& function: getDefinedFunctions())
+ {
if (function->getName().empty())
{
if (fallbackFunction)
@@ -71,6 +74,9 @@ void ContractDefinition::checkTypeRequirements()
BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters."));
}
}
+ if (!function->isFullyImplemented())
+ setFullyImplemented(false);
+ }
for (ASTPointer<ModifierDefinition> const& modifier: getFunctionModifiers())
modifier->checkTypeRequirements();
@@ -98,7 +104,7 @@ void ContractDefinition::checkTypeRequirements()
if (hashes.count(hash))
BOOST_THROW_EXCEPTION(createTypeError(
std::string("Function signature hash collision for ") +
- it.second->getCanonicalSignature()));
+ it.second->externalSignature()));
hashes.insert(hash);
}
}
@@ -134,6 +140,28 @@ FunctionDefinition const* ContractDefinition::getFallbackFunction() const
return nullptr;
}
+void ContractDefinition::checkAbstractFunctions()
+{
+ map<string, bool> functions;
+
+ // Search from base to derived
+ for (ContractDefinition const* contract: boost::adaptors::reverse(getLinearizedBaseContracts()))
+ for (ASTPointer<FunctionDefinition> const& function: contract->getDefinedFunctions())
+ {
+ string const& name = function->getName();
+ if (!function->isFullyImplemented() && functions.count(name) && functions[name])
+ BOOST_THROW_EXCEPTION(function->createTypeError("Redeclaring an already implemented function as abstract"));
+ functions[name] = function->isFullyImplemented();
+ }
+
+ for (auto const& it: functions)
+ if (!it.second)
+ {
+ setFullyImplemented(false);
+ break;
+ }
+}
+
void ContractDefinition::checkIllegalOverrides() const
{
// TODO unify this at a later point. for this we need to put the constness and the access specifier
@@ -203,8 +231,8 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
{
for (ASTPointer<FunctionDefinition> const& f: contract->getDefinedFunctions())
{
- string functionSignature = f->getCanonicalSignature();
- if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && signaturesSeen.count(functionSignature) == 0)
+ string functionSignature = f->externalSignature();
+ if (f->isPartOfExternalInterface() && signaturesSeen.count(functionSignature) == 0)
{
functionsSeen.insert(f->getName());
signaturesSeen.insert(functionSignature);
@@ -214,11 +242,12 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
}
for (ASTPointer<VariableDeclaration> const& v: contract->getStateVariables())
- if (v->isPublic() && functionsSeen.count(v->getName()) == 0)
+ if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface())
{
FunctionType ftype(*v);
+ solAssert(v->getType().get(), "");
functionsSeen.insert(v->getName());
- FixedHash<4> hash(dev::sha3(ftype.getCanonicalSignature(v->getName())));
+ FixedHash<4> hash(dev::sha3(ftype.externalSignature(v->getName())));
m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v)));
}
}
@@ -322,25 +351,35 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const
void FunctionDefinition::checkTypeRequirements()
{
for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters())
+ {
if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
+ if (getVisibility() >= Visibility::Public && !(var->getType()->externalType()))
+ {
+ // todo delete when will be implemented arrays as parameter type in internal functions
+ if (getVisibility() == Visibility::Public && var->getType()->getCategory() == Type::Category::Array)
+ BOOST_THROW_EXCEPTION(var->createTypeError("Arrays only implemented for external functions."));
+ else
+ BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions."));
+ }
+ }
for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers)
modifier->checkTypeRequirements(isConstructor() ?
dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() :
vector<ASTPointer<InheritanceSpecifier>>());
-
- m_body->checkTypeRequirements();
+ if (m_body)
+ m_body->checkTypeRequirements();
}
-string FunctionDefinition::getCanonicalSignature() const
+string FunctionDefinition::externalSignature() const
{
- return FunctionType(*this).getCanonicalSignature(getName());
+ return FunctionType(*this).externalSignature(getName());
}
bool VariableDeclaration::isLValue() const
{
- // External function parameters are Read-Only
- return !isExternalFunctionParameter();
+ // External function parameters and constant declared variables are Read-Only
+ return !isExternalFunctionParameter() && !m_isConstant;
}
void VariableDeclaration::checkTypeRequirements()
@@ -349,10 +388,24 @@ void VariableDeclaration::checkTypeRequirements()
// sets the type.
// Note that assignments before the first declaration are legal because of the special scoping
// rules inherited from JavaScript.
+ if (m_isConstant)
+ {
+ if (!dynamic_cast<ContractDefinition const*>(getScope()))
+ BOOST_THROW_EXCEPTION(createTypeError("Illegal use of \"constant\" specifier."));
+ if ((m_type && !m_type->isValueType()) || !m_value)
+ BOOST_THROW_EXCEPTION(createTypeError("Unitialized \"constant\" variable."));
+ }
if (!m_value)
return;
if (m_type)
+ {
m_value->expectType(*m_type);
+ if (m_isStateVariable && !m_type->externalType() && getVisibility() >= Visibility::Public)
+ BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for state variables."));
+
+ if (!FunctionType(*this).externalType())
+ BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables."));
+ }
else
{
// no type declared and no previous assignment, infer the type
@@ -437,6 +490,8 @@ void EventDefinition::checkTypeRequirements()
numIndexed++;
if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
+ if (!var->getType()->externalType())
+ BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed as event parameter type."));
}
if (numIndexed > 3)
BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event."));
@@ -681,6 +736,8 @@ void NewExpression::checkTypeRequirements()
m_contract = dynamic_cast<ContractDefinition const*>(m_contractName->getReferencedDeclaration());
if (!m_contract)
BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract."));
+ if (!m_contract->isFullyImplemented())
+ BOOST_THROW_EXCEPTION(m_contract->createTypeError("Trying to create an instance of an abstract contract."));
shared_ptr<ContractType const> contractType = make_shared<ContractType>(*m_contract);
TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes();
m_type = make_shared<FunctionType>(parameterTypes, TypePointers{contractType},
@@ -720,7 +777,7 @@ void IndexAccess::checkTypeRequirements()
BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted."));
m_index->expectType(IntegerType(256));
if (type.isByteArray())
- m_type = make_shared<IntegerType>(8, IntegerType::Modifier::Hash);
+ m_type = make_shared<FixedBytesType>(1);
else
m_type = type.getBaseType();
m_isLValue = type.getLocation() != ArrayType::Location::CallData;
diff --git a/AST.h b/AST.h
index 6cc7f742..fd50eb95 100644
--- a/AST.h
+++ b/AST.h
@@ -156,6 +156,7 @@ public:
/// contract types.
virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0;
virtual bool isLValue() const { return false; }
+ virtual bool isPartOfExternalInterface() const { return false; };
protected:
virtual Visibility getDefaultVisibility() const { return Visibility::Public; }
@@ -195,6 +196,22 @@ protected:
ASTPointer<ASTString> m_documentation;
};
+/**
+ * Abstract class that is added to AST nodes that can be marked as not being fully implemented
+ */
+class ImplementationOptional
+{
+public:
+ explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {}
+
+ /// @return whether this node is fully implemented or not
+ bool isFullyImplemented() const { return m_implemented; }
+ void setFullyImplemented(bool _implemented) { m_implemented = _implemented; }
+
+protected:
+ bool m_implemented;
+};
+
/// @}
/**
@@ -202,20 +219,24 @@ protected:
* document order. It first visits all struct declarations, then all variable declarations and
* finally all function declarations.
*/
-class ContractDefinition: public Declaration, public Documented
+class ContractDefinition: public Declaration, public Documented, public ImplementationOptional
{
public:
- ContractDefinition(SourceLocation const& _location,
- ASTPointer<ASTString> const& _name,
- ASTPointer<ASTString> const& _documentation,
- std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
- std::vector<ASTPointer<StructDefinition>> const& _definedStructs,
- std::vector<ASTPointer<EnumDefinition>> const& _definedEnums,
- std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables,
- std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions,
- std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers,
- std::vector<ASTPointer<EventDefinition>> const& _events):
- Declaration(_location, _name), Documented(_documentation),
+ ContractDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<ASTString> const& _documentation,
+ std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
+ std::vector<ASTPointer<StructDefinition>> const& _definedStructs,
+ std::vector<ASTPointer<EnumDefinition>> const& _definedEnums,
+ std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables,
+ std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions,
+ std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers,
+ std::vector<ASTPointer<EventDefinition>> const& _events
+ ):
+ Declaration(_location, _name),
+ Documented(_documentation),
+ ImplementationOptional(true),
m_baseContracts(_baseContracts),
m_definedStructs(_definedStructs),
m_definedEnums(_definedEnums),
@@ -262,6 +283,7 @@ public:
private:
void checkIllegalOverrides() const;
+ void checkAbstractFunctions();
std::vector<std::pair<FixedHash<4>, FunctionTypePointer>> const& getInterfaceFunctionList() const;
@@ -377,24 +399,29 @@ private:
std::vector<ASTPointer<VariableDeclaration>> m_parameters;
};
-class FunctionDefinition: public Declaration, public VariableScope, public Documented
+class FunctionDefinition: public Declaration, public VariableScope, public Documented, public ImplementationOptional
{
public:
- FunctionDefinition(SourceLocation const& _location, ASTPointer<ASTString> const& _name,
- Declaration::Visibility _visibility, bool _isConstructor,
- ASTPointer<ASTString> const& _documentation,
- ASTPointer<ParameterList> const& _parameters,
- bool _isDeclaredConst,
- std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
- ASTPointer<ParameterList> const& _returnParameters,
- ASTPointer<Block> const& _body):
- Declaration(_location, _name, _visibility), Documented(_documentation),
- m_isConstructor(_isConstructor),
- m_parameters(_parameters),
- m_isDeclaredConst(_isDeclaredConst),
- m_functionModifiers(_modifiers),
- m_returnParameters(_returnParameters),
- m_body(_body)
+ FunctionDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ Declaration::Visibility _visibility, bool _isConstructor,
+ ASTPointer<ASTString> const& _documentation,
+ ASTPointer<ParameterList> const& _parameters,
+ bool _isDeclaredConst,
+ std::vector<ASTPointer<ModifierInvocation>> const& _modifiers,
+ ASTPointer<ParameterList> const& _returnParameters,
+ ASTPointer<Block> const& _body
+ ):
+ Declaration(_location, _name, _visibility),
+ Documented(_documentation),
+ ImplementationOptional(_body != nullptr),
+ m_isConstructor(_isConstructor),
+ m_parameters(_parameters),
+ m_isDeclaredConst(_isDeclaredConst),
+ m_functionModifiers(_modifiers),
+ m_returnParameters(_returnParameters),
+ m_body(_body)
{}
virtual void accept(ASTVisitor& _visitor) override;
@@ -415,14 +442,15 @@ public:
getVisibility() >= Visibility::Internal;
}
virtual TypePointer getType(ContractDefinition const*) const override;
+ virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstructor && !getName().empty(); }
/// Checks that all parameters have allowed types and calls checkTypeRequirements on the body.
void checkTypeRequirements();
- /// @returns the canonical signature of the function
+ /// @returns the external signature of the function
/// That consists of the name of the function followed by the types of the
/// arguments separated by commas all enclosed in parentheses without any spaces.
- std::string getCanonicalSignature() const;
+ std::string externalSignature() const;
private:
bool m_isConstructor;
@@ -440,13 +468,23 @@ private:
class VariableDeclaration: public Declaration
{
public:
- VariableDeclaration(SourceLocation const& _location, ASTPointer<TypeName> const& _type,
- ASTPointer<ASTString> const& _name, ASTPointer<Expression> _value,
- Visibility _visibility,
- bool _isStateVar = false, bool _isIndexed = false):
- Declaration(_location, _name, _visibility),
- m_typeName(_type), m_value(_value),
- m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed) {}
+ VariableDeclaration(
+ SourceLocation const& _location,
+ ASTPointer<TypeName> const& _type,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<Expression> _value,
+ Visibility _visibility,
+ bool _isStateVar = false,
+ bool _isIndexed = false,
+ bool _isConstant = false
+ ):
+ Declaration(_location, _name, _visibility),
+ m_typeName(_type),
+ m_value(_value),
+ m_isStateVariable(_isStateVar),
+ m_isIndexed(_isIndexed),
+ m_isConstant(_isConstant){}
+
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
@@ -459,21 +497,24 @@ public:
void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; }
virtual bool isLValue() const override;
+ virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstant; }
void checkTypeRequirements();
bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); }
bool isExternalFunctionParameter() const;
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
+ bool isConstant() const { return m_isConstant; }
protected:
Visibility getDefaultVisibility() const override { return Visibility::Internal; }
private:
ASTPointer<TypeName> m_typeName; ///< can be empty ("var")
- ASTPointer<Expression> m_value; ///< the assigned value, can be missing
+ ASTPointer<Expression> m_value; ///< the assigned value, can be missing
bool m_isStateVariable; ///< Whether or not this is a contract state variable
bool m_isIndexed; ///< Whether this is an indexed variable (used by events).
+ bool m_isConstant; ///< Whether the variable is a compile-time constant.
std::shared_ptr<Type const> m_type; ///< derived type, initially empty
};
@@ -538,17 +579,24 @@ private:
class EventDefinition: public Declaration, public VariableScope, public Documented
{
public:
- EventDefinition(SourceLocation const& _location,
- ASTPointer<ASTString> const& _name,
- ASTPointer<ASTString> const& _documentation,
- ASTPointer<ParameterList> const& _parameters):
- Declaration(_location, _name), Documented(_documentation), m_parameters(_parameters) {}
+ EventDefinition(
+ SourceLocation const& _location,
+ ASTPointer<ASTString> const& _name,
+ ASTPointer<ASTString> const& _documentation,
+ ASTPointer<ParameterList> const& _parameters,
+ bool _anonymous = false
+ ):
+ Declaration(_location, _name),
+ Documented(_documentation),
+ m_parameters(_parameters),
+ m_anonymous(_anonymous){}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
std::vector<ASTPointer<VariableDeclaration>> const& getParameters() const { return m_parameters->getParameters(); }
ParameterList const& getParameterList() const { return *m_parameters; }
+ bool isAnonymous() const { return m_anonymous; }
virtual TypePointer getType(ContractDefinition const* = nullptr) const override
{
@@ -559,6 +607,7 @@ public:
private:
ASTPointer<ParameterList> m_parameters;
+ bool m_anonymous = false;
};
/**
diff --git a/AST_accept.h b/AST_accept.h
index 81ede8fc..3557f877 100644
--- a/AST_accept.h
+++ b/AST_accept.h
@@ -175,7 +175,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor)
if (m_returnParameters)
m_returnParameters->accept(_visitor);
listAccept(m_functionModifiers, _visitor);
- m_body->accept(_visitor);
+ if (m_body)
+ m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}
@@ -188,7 +189,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const
if (m_returnParameters)
m_returnParameters->accept(_visitor);
listAccept(m_functionModifiers, _visitor);
- m_body->accept(_visitor);
+ if (m_body)
+ m_body->accept(_visitor);
}
_visitor.endVisit(*this);
}
diff --git a/ArrayUtils.cpp b/ArrayUtils.cpp
index f0d7d6a8..58031390 100644
--- a/ArrayUtils.cpp
+++ b/ArrayUtils.cpp
@@ -34,8 +34,10 @@ using namespace solidity;
void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const
{
- // stack layout: [source_ref] target_ref (top)
- // need to leave target_ref on the stack at the end
+ // this copies source to target and also clears target if it was larger
+ // need to leave "target_ref target_byte_off" on the stack at the end
+
+ // stack layout: [source_ref] [source_byte_off] [source length] target_ref target_byte_off (top)
solAssert(_targetType.getLocation() == ArrayType::Location::Storage, "");
solAssert(
_sourceType.getLocation() == ArrayType::Location::CallData ||
@@ -47,14 +49,26 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType());
Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType());
- // this copies source to target and also clears target if it was larger
-
// TODO unroll loop for small sizes
- // stack: source_ref [source_length] target_ref
+ bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage;
+ bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType;
+ bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16;
+ bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16;
+ unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0);
+
+ // stack: source_ref [source_byte_off] [source_length] target_ref target_byte_off
// store target_ref
+ // arrays always start at zero byte offset, pop offset
+ m_context << eth::Instruction::POP;
for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i)
m_context << eth::swapInstruction(i);
+ // stack: target_ref source_ref [source_byte_off] [source_length]
+ if (sourceIsStorage)
+ // arrays always start at zero byte offset, pop offset
+ m_context << eth::Instruction::POP;
+ // stack: target_ref source_ref [source_length]
+ // retrieve source length
if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized())
retrieveLength(_sourceType); // otherwise, length is already there
// stack: target_ref source_ref source_length
@@ -73,6 +87,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
m_context
<< eth::Instruction::POP << eth::Instruction::POP
<< eth::Instruction::POP << eth::Instruction::POP;
+ m_context << u256(0);
return;
}
// compute hashes (data positions)
@@ -88,8 +103,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack: target_ref target_data_end source_length target_data_pos source_ref
// skip copying if source length is zero
m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO;
- eth::AssemblyItem copyLoopEnd = m_context.newTag();
- m_context.appendConditionalJumpTo(copyLoopEnd);
+ eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
+ m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized())
CompilerUtils(m_context).computeHashStatic();
@@ -98,88 +113,172 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
convertLengthToSize(_sourceType);
m_context << eth::Instruction::DUP3 << eth::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
- << eth::Instruction::DUP3 << eth::Instruction::DUP2
+ << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize)
<< eth::Instruction::GT << eth::Instruction::ISZERO;
+ eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
+ // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset]
// copy
if (sourceBaseType->getCategory() == Type::Category::Array)
{
- m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3;
+ solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
+ m_context << eth::Instruction::DUP3;
+ if (sourceIsStorage)
+ m_context << u256(0);
+ m_context << eth::dupInstruction(sourceIsStorage ? 4 : 3) << u256(0);
copyArrayToStorage(
dynamic_cast<ArrayType const&>(*targetBaseType),
dynamic_cast<ArrayType const&>(*sourceBaseType)
);
- m_context << eth::Instruction::POP;
+ m_context << eth::Instruction::POP << eth::Instruction::POP;
+ }
+ else if (directCopy)
+ {
+ solAssert(byteOffsetSize == 0, "Byte offset for direct copy.");
+ m_context
+ << eth::Instruction::DUP3 << eth::Instruction::SLOAD
+ << eth::Instruction::DUP3 << eth::Instruction::SSTORE;
}
else
{
- m_context << eth::Instruction::DUP3;
+ // 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 << eth::dupInstruction(3 + byteOffsetSize);
if (_sourceType.getLocation() == ArrayType::Location::Storage)
+ {
+ if (haveByteOffsetSource)
+ m_context << eth::Instruction::DUP2;
+ else
+ m_context << u256(0);
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
+ }
else if (sourceBaseType->isValueType())
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false);
else
solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString());
- m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack());
+ // 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->getSizeOnStack() <= 16, "Stack too deep.");
+ // fetch target storage reference
+ m_context << eth::dupInstruction(2 + byteOffsetSize + sourceBaseType->getSizeOnStack());
+ if (haveByteOffsetTarget)
+ m_context << eth::dupInstruction(1 + byteOffsetSize + sourceBaseType->getSizeOnStack());
+ 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
- m_context
- << eth::Instruction::SWAP2
- << (_sourceType.getLocation() == ArrayType::Location::Storage ?
- sourceBaseType->getStorageSize() :
- sourceBaseType->getCalldataEncodedSize())
- << eth::Instruction::ADD
- << eth::Instruction::SWAP2;
+ if (haveByteOffsetSource)
+ incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
+ else
+ m_context
+ << eth::swapInstruction(2 + byteOffsetSize)
+ << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize())
+ << eth::Instruction::ADD
+ << eth::swapInstruction(2 + byteOffsetSize);
// increment target
- m_context
- << eth::Instruction::SWAP1
- << targetBaseType->getStorageSize()
- << eth::Instruction::ADD
- << eth::Instruction::SWAP1;
+ if (haveByteOffsetTarget)
+ incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ else
+ m_context
+ << eth::swapInstruction(1 + byteOffsetSize)
+ << targetBaseType->getStorageSize()
+ << eth::Instruction::ADD
+ << eth::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 << eth::dupInstruction(byteOffsetSize) << eth::Instruction::ISZERO;
+ eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump();
+ m_context << eth::dupInstruction(2 + byteOffsetSize) << eth::dupInstruction(1 + byteOffsetSize);
+ StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true);
+ incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2);
+ m_context.appendJumpTo(copyLoopEnd);
+
+ m_context << copyCleanupLoopEnd;
+ m_context << eth::Instruction::POP; // might pop the source, but then target is popped next
+ }
+ if (haveByteOffsetSource)
+ m_context << eth::Instruction::POP;
+ m_context << copyLoopEndWithoutByteOffset;
// zero-out leftovers in target
- // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end
+ // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP;
// stack: target_ref target_data_end target_data_pos_updated
clearStorageLoop(*targetBaseType);
m_context << eth::Instruction::POP;
+ m_context << u256(0);
}
void ArrayUtils::clearArray(ArrayType const& _type) const
{
+ unsigned stackHeightStart = m_context.getStackHeight();
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
+ if (_type.getBaseType()->getStorageBytes() < 32)
+ {
+ solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
+ solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid storage size for type.");
+ }
+ if (_type.getBaseType()->isValueType())
+ solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid size for value type.");
+
+ m_context << eth::Instruction::POP; // remove byte offset
if (_type.isDynamicallySized())
clearDynamicArray(_type);
else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping)
m_context << eth::Instruction::POP;
- else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value
+ else if (_type.getBaseType()->isValueType() && _type.getStorageSize() <= 5)
{
- solAssert(!_type.isByteArray(), "");
+ // 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.getStorageSize(); ++i)
+ m_context
+ << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE
+ << u256(1) << eth::Instruction::ADD;
+ m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
+ }
+ else if (!_type.getBaseType()->isValueType() && _type.getLength() <= 4)
+ {
+ // unroll loop for small arrays @todo choose a good value
+ solAssert(_type.getBaseType()->getStorageBytes() >= 32, "Invalid storage size.");
for (unsigned i = 1; i < _type.getLength(); ++i)
{
+ m_context << u256(0);
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false);
- m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD;
+ m_context
+ << eth::Instruction::POP
+ << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD;
}
+ m_context << u256(0);
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true);
}
else
{
- solAssert(!_type.isByteArray(), "");
- m_context
- << eth::Instruction::DUP1 << u256(_type.getLength())
- << u256(_type.getBaseType()->getStorageSize())
- << eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1;
- clearStorageLoop(*_type.getBaseType());
+ m_context << eth::Instruction::DUP1 << _type.getLength();
+ convertLengthToSize(_type);
+ m_context << eth::Instruction::ADD << eth::Instruction::SWAP1;
+ if (_type.getBaseType()->getStorageBytes() < 32)
+ clearStorageLoop(IntegerType(256));
+ else
+ clearStorageLoop(*_type.getBaseType());
m_context << eth::Instruction::POP;
}
+ solAssert(m_context.getStackHeight() == stackHeightStart - 2, "");
}
void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
@@ -187,6 +286,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), "");
+ unsigned stackHeightStart = m_context.getStackHeight();
// fetch length
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// set length to zero
@@ -196,23 +296,27 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
// compute data positions
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
- // stack: len data_pos (len is in slots for byte array and in items for other arrays)
+ // stack: len data_pos
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
// stack: data_pos_end data_pos
- if (_type.isByteArray())
+ if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32)
clearStorageLoop(IntegerType(256));
else
clearStorageLoop(*_type.getBaseType());
// cleanup
m_context << eth::Instruction::POP;
+ solAssert(m_context.getStackHeight() == stackHeightStart - 1, "");
}
void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
{
solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), "");
+ if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32)
+ solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
+ unsigned stackHeightStart = m_context.getStackHeight();
eth::AssemblyItem resizeEnd = m_context.newTag();
// stack: ref new_length
@@ -240,7 +344,7 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
// stack: ref new_length data_pos new_size delete_end
m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD;
// stack: ref new_length delete_end delete_start
- if (_type.isByteArray())
+ if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32)
clearStorageLoop(IntegerType(256));
else
clearStorageLoop(*_type.getBaseType());
@@ -248,10 +352,12 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
m_context << resizeEnd;
// cleanup
m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP;
+ solAssert(m_context.getStackHeight() == stackHeightStart - 2, "");
}
void ArrayUtils::clearStorageLoop(Type const& _type) const
{
+ unsigned stackHeightStart = m_context.getStackHeight();
if (_type.getCategory() == Type::Category::Mapping)
{
m_context << eth::Instruction::POP;
@@ -266,13 +372,16 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const
eth::AssemblyItem zeroLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd);
// delete
+ m_context << u256(0);
StorageItem(m_context, _type).setToZero(SourceLocation(), false);
+ m_context << eth::Instruction::POP;
// increment
m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(loopStart);
// cleanup
m_context << zeroLoopEnd;
m_context << eth::Instruction::POP;
+ solAssert(m_context.getStackHeight() == stackHeightStart - 1, "");
}
void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const
@@ -282,7 +391,20 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con
if (_arrayType.isByteArray())
m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
- else if (_arrayType.getBaseType()->getStorageSize() > 1)
+ else if (_arrayType.getBaseType()->getStorageSize() <= 1)
+ {
+ unsigned baseBytes = _arrayType.getBaseType()->getStorageBytes();
+ if (baseBytes == 0)
+ m_context << eth::Instruction::POP << u256(1);
+ else if (baseBytes <= 16)
+ {
+ unsigned itemsPerSlot = 32 / baseBytes;
+ m_context
+ << u256(itemsPerSlot - 1) << eth::Instruction::ADD
+ << u256(itemsPerSlot) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
+ }
+ }
+ else
m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL;
}
else
@@ -318,3 +440,143 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const
}
}
+void ArrayUtils::accessIndex(ArrayType const& _arrayType) const
+{
+ ArrayType::Location location = _arrayType.getLocation();
+ eth::Instruction load =
+ location == ArrayType::Location::Storage ? eth::Instruction::SLOAD :
+ location == ArrayType::Location::Memory ? eth::Instruction::MLOAD :
+ eth::Instruction::CALLDATALOAD;
+
+ // retrieve length
+ if (!_arrayType.isDynamicallySized())
+ m_context << _arrayType.getLength();
+ else if (location == ArrayType::Location::CallData)
+ // length is stored on the stack
+ m_context << eth::Instruction::SWAP1;
+ else
+ m_context << eth::Instruction::DUP2 << load;
+ // stack: <base_ref> <index> <length>
+ // check out-of-bounds access
+ m_context << eth::Instruction::DUP2 << eth::Instruction::LT;
+ eth::AssemblyItem legalAccess = m_context.appendConditionalJump();
+ // out-of-bounds access throws exception (just STOP for now)
+ m_context << eth::Instruction::STOP;
+
+ m_context << legalAccess;
+ // stack: <base_ref> <index>
+ if (_arrayType.isByteArray())
+ switch (location)
+ {
+ case ArrayType::Location::Storage:
+ // byte array index storage lvalue on stack (goal):
+ // <ref> <byte_number> = <base_ref + index / 32> <index % 32>
+ m_context << u256(32) << eth::Instruction::SWAP2;
+ CompilerUtils(m_context).computeHashStatic();
+ // stack: 32 index data_ref
+ m_context
+ << eth::Instruction::DUP3 << eth::Instruction::DUP3
+ << eth::Instruction::DIV << eth::Instruction::ADD
+ // stack: 32 index (data_ref + index / 32)
+ << eth::Instruction::SWAP2 << eth::Instruction::SWAP1
+ << eth::Instruction::MOD;
+ break;
+ case ArrayType::Location::CallData:
+ // no lvalue, just retrieve the value
+ m_context
+ << eth::Instruction::ADD << eth::Instruction::CALLDATALOAD
+ << ((u256(0xff) << (256 - 8))) << eth::Instruction::AND;
+ break;
+ case ArrayType::Location::Memory:
+ solAssert(false, "Memory lvalues not yet implemented.");
+ }
+ else
+ {
+ // stack: <base_ref> <index>
+ m_context << eth::Instruction::SWAP1;
+ if (_arrayType.isDynamicallySized())
+ {
+ if (location == ArrayType::Location::Storage)
+ CompilerUtils(m_context).computeHashStatic();
+ else if (location == ArrayType::Location::Memory)
+ m_context << u256(32) << eth::Instruction::ADD;
+ }
+ // stack: <index> <data_ref>
+ switch (location)
+ {
+ case ArrayType::Location::CallData:
+ m_context
+ << eth::Instruction::SWAP1 << _arrayType.getBaseType()->getCalldataEncodedSize()
+ << eth::Instruction::MUL << eth::Instruction::ADD;
+ if (_arrayType.getBaseType()->isValueType())
+ CompilerUtils(m_context).loadFromMemoryDynamic(*_arrayType.getBaseType(), true, true, false);
+ break;
+ case ArrayType::Location::Storage:
+ m_context << eth::Instruction::SWAP1;
+ if (_arrayType.getBaseType()->getStorageBytes() <= 16)
+ {
+ // stack: <data_ref> <index>
+ // goal:
+ // <ref> <byte_number> = <base_ref + index / itemsPerSlot> <(index % itemsPerSlot) * byteSize>
+ unsigned byteSize = _arrayType.getBaseType()->getStorageBytes();
+ solAssert(byteSize != 0, "");
+ unsigned itemsPerSlot = 32 / byteSize;
+ m_context << u256(itemsPerSlot) << eth::Instruction::SWAP2;
+ // stack: itemsPerSlot index data_ref
+ m_context
+ << eth::Instruction::DUP3 << eth::Instruction::DUP3
+ << eth::Instruction::DIV << eth::Instruction::ADD
+ // stack: itemsPerSlot index (data_ref + index / itemsPerSlot)
+ << eth::Instruction::SWAP2 << eth::Instruction::SWAP1
+ << eth::Instruction::MOD
+ << u256(byteSize) << eth::Instruction::MUL;
+ }
+ else
+ {
+ if (_arrayType.getBaseType()->getStorageSize() != 1)
+ m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL;
+ m_context << eth::Instruction::ADD << u256(0);
+ }
+ break;
+ case ArrayType::Location::Memory:
+ solAssert(false, "Memory lvalues not yet implemented.");
+ }
+ }
+}
+
+void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const
+{
+ solAssert(_byteSize < 32, "");
+ solAssert(_byteSize != 0, "");
+ // We do the following, but avoiding jumps:
+ // byteOffset += byteSize
+ // if (byteOffset + byteSize > 32)
+ // {
+ // storageOffset++;
+ // byteOffset = 0;
+ // }
+ if (_byteOffsetPosition > 1)
+ m_context << eth::swapInstruction(_byteOffsetPosition - 1);
+ m_context << u256(_byteSize) << eth::Instruction::ADD;
+ if (_byteOffsetPosition > 1)
+ m_context << eth::swapInstruction(_byteOffsetPosition - 1);
+ // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32
+ m_context
+ << u256(32) << eth::dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1)
+ << eth::Instruction::ADD << eth::Instruction::DIV;
+ // increment storage offset if X == 1 (just add X to it)
+ // stack: X
+ m_context
+ << eth::swapInstruction(_storageOffsetPosition) << eth::dupInstruction(_storageOffsetPosition + 1)
+ << eth::Instruction::ADD << eth::swapInstruction(_storageOffsetPosition);
+ // stack: X
+ // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X)
+ m_context << u256(1) << eth::Instruction::SUB;
+ // stack: 1 - X
+ if (_byteOffsetPosition == 1)
+ m_context << eth::Instruction::MUL;
+ else
+ m_context
+ << eth::dupInstruction(_byteOffsetPosition + 1) << eth::Instruction::MUL
+ << eth::swapInstruction(_byteOffsetPosition) << eth::Instruction::POP;
+}
diff --git a/ArrayUtils.h b/ArrayUtils.h
index 31cca817..dab40e2d 100644
--- a/ArrayUtils.h
+++ b/ArrayUtils.h
@@ -41,19 +41,19 @@ public:
/// Copies an array to an array in storage. The arrays can be of different types only if
/// their storage representation is the same.
- /// Stack pre: [source_reference] target_reference
- /// Stack post: target_reference
+ /// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset
+ /// Stack post: target_reference target_byte_offset
void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const;
/// Clears the given dynamic or static array.
- /// Stack pre: reference
+ /// Stack pre: storage_ref storage_byte_offset
/// Stack post:
void clearArray(ArrayType const& _type) const;
/// Clears the length and data elements of the array referenced on the stack.
- /// Stack pre: reference
+ /// Stack pre: reference (excludes byte offset)
/// Stack post:
void clearDynamicArray(ArrayType const& _type) const;
/// Changes the size of a dynamic array and clears the tail if it is shortened.
- /// Stack pre: reference new_length
+ /// Stack pre: reference (excludes byte offset) new_length
/// Stack post:
void resizeDynamicArray(ArrayType const& _type) const;
/// Appends a loop that clears a sequence of storage slots of the given type (excluding end).
@@ -67,11 +67,23 @@ public:
void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const;
/// Retrieves the length (number of elements) of the array ref on the stack. This also
/// works for statically-sized arrays.
- /// Stack pre: reference
+ /// Stack pre: reference (excludes byte offset for dynamic storage arrays)
/// Stack post: reference length
void retrieveLength(ArrayType const& _arrayType) const;
+ /// Retrieves the value at a specific index. If the location is storage, only retrieves the
+ /// position.
+ /// Stack pre: reference [length] index
+ /// Stack post for storage: slot byte_offset
+ /// Stack post for calldata: value
+ void accessIndex(ArrayType const& _arrayType) const;
private:
+ /// Adds the given number of bytes to a storage byte offset counter and also increments
+ /// the storage offset if adding this number again would increase the counter over 32.
+ /// @param byteOffsetPosition the stack offset of the storage byte offset
+ /// @param storageOffsetPosition the stack offset of the storage slot offset
+ void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const;
+
CompilerContext& m_context;
};
diff --git a/Compiler.cpp b/Compiler.cpp
index 5eeb0c3e..8e263449 100644
--- a/Compiler.cpp
+++ b/Compiler.cpp
@@ -20,12 +20,12 @@
* Solidity compiler.
*/
+#include <libsolidity/Compiler.h>
#include <algorithm>
#include <boost/range/adaptor/reversed.hpp>
#include <libevmcore/Instruction.h>
#include <libevmcore/Assembly.h>
#include <libsolidity/AST.h>
-#include <libsolidity/Compiler.h>
#include <libsolidity/ExpressionCompiler.h>
#include <libsolidity/CompilerUtils.h>
@@ -132,7 +132,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp
void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_constructor);
+ CompilerContext::LocationSetter locationSetter(m_context, _constructor);
FunctionType constructorType(_constructor);
if (!constructorType.getParameterTypes().empty())
{
@@ -146,7 +146,7 @@ void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor)
void Compiler::appendConstructor(FunctionDefinition const& _constructor)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_constructor);
+ CompilerContext::LocationSetter locationSetter(m_context, _constructor);
// copy constructor arguments from code to memory and then to stack, they are supplied after the actual program
unsigned argumentSize = 0;
for (ASTPointer<VariableDeclaration> const& var: _constructor.getParameters())
@@ -192,10 +192,12 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
for (auto const& it: interfaceFunctions)
{
FunctionTypePointer const& functionType = it.second;
+ solAssert(functionType->hasDeclaration(), "");
+ CompilerContext::LocationSetter locationSetter(m_context, functionType->getDeclaration());
m_context << callDataUnpackerEntryPoints.at(it.first);
eth::AssemblyItem returnTag = m_context.pushNewTag();
appendCalldataUnpacker(functionType->getParameterTypes());
- m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration()));
+ m_context.appendJumpTo(m_context.getFunctionEntryLabel(functionType->getDeclaration()));
m_context << returnTag;
appendReturnValuePacker(functionType->getReturnParameterTypes());
}
@@ -226,6 +228,7 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool
{
// Retrieve data start offset by adding length to start offset of previous dynamic type
unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument;
+ solAssert(stackDepth <= 16, "Stack too deep.");
m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth);
ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true);
m_context << eth::Instruction::ADD;
@@ -271,22 +274,21 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
void Compiler::registerStateVariables(ContractDefinition const& _contract)
{
- for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts()))
- for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables())
- m_context.addStateVariable(*variable);
+ for (auto const& var: ContractType(_contract).getStateVariables())
+ m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var));
}
void Compiler::initializeStateVariables(ContractDefinition const& _contract)
{
for (ASTPointer<VariableDeclaration> const& variable: _contract.getStateVariables())
- if (variable->getValue())
+ if (variable->getValue() && !variable->isConstant())
ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable);
}
bool Compiler::visit(VariableDeclaration const& _variableDeclaration)
{
solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration.");
- CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclaration);
+ CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration);
m_context.startFunction(_variableDeclaration);
m_breakTags.clear();
@@ -300,7 +302,7 @@ bool Compiler::visit(VariableDeclaration const& _variableDeclaration)
bool Compiler::visit(FunctionDefinition const& _function)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_function);
+ CompilerContext::LocationSetter locationSetter(m_context, _function);
//@todo to simplify this, the calling convention could by changed such that
// caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn]
// although note that this reduces the size of the visible stack
@@ -357,6 +359,7 @@ bool Compiler::visit(FunctionDefinition const& _function)
stackLayout.push_back(i);
stackLayout += vector<int>(c_localVariablesSize, -1);
+ solAssert(stackLayout.size() <= 17, "Stack too deep.");
while (stackLayout.back() != int(stackLayout.size() - 1))
if (stackLayout.back() < 0)
{
@@ -376,15 +379,16 @@ bool Compiler::visit(FunctionDefinition const& _function)
m_context.removeVariable(*localVariable);
m_context.adjustStackOffset(-(int)c_returnValuesSize);
+
if (!_function.isConstructor())
- m_context << eth::Instruction::JUMP;
+ m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
return false;
}
bool Compiler::visit(IfStatement const& _ifStatement)
{
StackHeightChecker checker(m_context);
- CompilerContext::LocationSetter locationSetter(m_context, &_ifStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _ifStatement);
compileExpression(_ifStatement.getCondition());
eth::AssemblyItem trueTag = m_context.appendConditionalJump();
if (_ifStatement.getFalseStatement())
@@ -401,7 +405,7 @@ bool Compiler::visit(IfStatement const& _ifStatement)
bool Compiler::visit(WhileStatement const& _whileStatement)
{
StackHeightChecker checker(m_context);
- CompilerContext::LocationSetter locationSetter(m_context, &_whileStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _whileStatement);
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart);
@@ -427,7 +431,7 @@ bool Compiler::visit(WhileStatement const& _whileStatement)
bool Compiler::visit(ForStatement const& _forStatement)
{
StackHeightChecker checker(m_context);
- CompilerContext::LocationSetter locationSetter(m_context, &_forStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _forStatement);
eth::AssemblyItem loopStart = m_context.newTag();
eth::AssemblyItem loopEnd = m_context.newTag();
m_continueTags.push_back(loopStart);
@@ -464,7 +468,7 @@ bool Compiler::visit(ForStatement const& _forStatement)
bool Compiler::visit(Continue const& _continueStatement)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_continueStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _continueStatement);
if (!m_continueTags.empty())
m_context.appendJumpTo(m_continueTags.back());
return false;
@@ -472,7 +476,7 @@ bool Compiler::visit(Continue const& _continueStatement)
bool Compiler::visit(Break const& _breakStatement)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_breakStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _breakStatement);
if (!m_breakTags.empty())
m_context.appendJumpTo(m_breakTags.back());
return false;
@@ -480,7 +484,7 @@ bool Compiler::visit(Break const& _breakStatement)
bool Compiler::visit(Return const& _return)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_return);
+ CompilerContext::LocationSetter locationSetter(m_context, _return);
//@todo modifications are needed to make this work with functions returning multiple values
if (Expression const* expression = _return.getExpression())
{
@@ -499,7 +503,7 @@ bool Compiler::visit(Return const& _return)
bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{
StackHeightChecker checker(m_context);
- CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement);
if (Expression const* expression = _variableDeclarationStatement.getExpression())
{
compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType());
@@ -512,7 +516,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta
bool Compiler::visit(ExpressionStatement const& _expressionStatement)
{
StackHeightChecker checker(m_context);
- CompilerContext::LocationSetter locationSetter(m_context, &_expressionStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement);
Expression const& expression = _expressionStatement.getExpression();
compileExpression(expression);
CompilerUtils(m_context).popStackElement(*expression.getType());
@@ -523,7 +527,7 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement)
bool Compiler::visit(PlaceholderStatement const& _placeholderStatement)
{
StackHeightChecker checker(m_context);
- CompilerContext::LocationSetter locationSetter(m_context, &_placeholderStatement);
+ CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement);
++m_modifierDepth;
appendModifierOrFunctionCode();
--m_modifierDepth;
@@ -550,7 +554,7 @@ void Compiler::appendModifierOrFunctionCode()
}
ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName());
- CompilerContext::LocationSetter locationSetter(m_context, &modifier);
+ CompilerContext::LocationSetter locationSetter(m_context, modifier);
solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), "");
for (unsigned i = 0; i < modifier.getParameters().size(); ++i)
{
diff --git a/Compiler.h b/Compiler.h
index 76f16f3a..4b1e1b4d 100644
--- a/Compiler.h
+++ b/Compiler.h
@@ -94,8 +94,8 @@ private:
std::vector<eth::AssemblyItem> m_continueTags; ///< tag to jump to for a "continue" statement
eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement
unsigned m_modifierDepth = 0;
- FunctionDefinition const* m_currentFunction;
- unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag
+ FunctionDefinition const* m_currentFunction = nullptr;
+ unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag
// arguments for base constructors, filled in derived-to-base order
std::map<FunctionDefinition const*, std::vector<ASTPointer<Expression>> const*> m_baseArguments;
};
diff --git a/CompilerContext.cpp b/CompilerContext.cpp
index b12e0192..0afda136 100644
--- a/CompilerContext.cpp
+++ b/CompilerContext.cpp
@@ -37,15 +37,13 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio
m_magicGlobals.insert(&_declaration);
}
-void CompilerContext::addStateVariable(VariableDeclaration const& _declaration)
+void CompilerContext::addStateVariable(
+ VariableDeclaration const& _declaration,
+ u256 const& _storageOffset,
+ unsigned _byteOffset
+)
{
- m_stateVariables[&_declaration] = m_stateVariablesSize;
- bigint newSize = bigint(m_stateVariablesSize) + _declaration.getType()->getStorageSize();
- if (newSize >= bigint(1) << 256)
- BOOST_THROW_EXCEPTION(TypeError()
- << errinfo_comment("State variable does not fit in storage.")
- << errinfo_sourceLocation(_declaration.getLocation()));
- m_stateVariablesSize = u256(newSize);
+ m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset);
}
void CompilerContext::startFunction(Declaration const& _function)
@@ -69,7 +67,7 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration)
void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration)
{
- LocationSetter locationSetter(*this, &_declaration);
+ LocationSetter locationSetter(*this, _declaration);
addVariable(_declaration);
int const size = _declaration.getType()->getSizeOnStack();
for (int i = 0; i < size; ++i)
@@ -174,46 +172,26 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const
return m_asm.deposit() - _offset - 1;
}
-u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const
+pair<u256, unsigned> CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const
{
auto it = m_stateVariables.find(&_declaration);
solAssert(it != m_stateVariables.end(), "Variable not found in storage.");
return it->second;
}
+CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpType)
+{
+ eth::AssemblyItem item(eth::Instruction::JUMP);
+ item.setJumpType(_jumpType);
+ return *this << item;
+}
+
void CompilerContext::resetVisitedNodes(ASTNode const* _node)
{
stack<ASTNode const*> newStack;
newStack.push(_node);
std::swap(m_visitedNodes, newStack);
-}
-
-CompilerContext& CompilerContext::operator<<(eth::AssemblyItem const& _item)
-{
- solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
- m_asm.append(_item, m_visitedNodes.top()->getLocation());
- return *this;
-}
-
-CompilerContext& CompilerContext::operator<<(eth::Instruction _instruction)
-{
- solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
- m_asm.append(_instruction, m_visitedNodes.top()->getLocation());
- return *this;
-}
-
-CompilerContext& CompilerContext::operator<<(u256 const& _value)
-{
- solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
- m_asm.append(_value, m_visitedNodes.top()->getLocation());
- return *this;
-}
-
-CompilerContext& CompilerContext::operator<<(bytes const& _data)
-{
- solAssert(!m_visitedNodes.empty(), "No node on the visited stack");
- m_asm.append(_data, m_visitedNodes.top()->getLocation());
- return *this;
+ updateSourceLocation();
}
vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const
@@ -224,5 +202,10 @@ vector<ContractDefinition const*>::const_iterator CompilerContext::getSuperContr
return ++it;
}
+void CompilerContext::updateSourceLocation()
+{
+ m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->getLocation());
+}
+
}
}
diff --git a/CompilerContext.h b/CompilerContext.h
index e42e7c76..87f90d4c 100644
--- a/CompilerContext.h
+++ b/CompilerContext.h
@@ -24,6 +24,7 @@
#include <ostream>
#include <stack>
+#include <utility>
#include <libevmcore/Instruction.h>
#include <libevmcore/Assembly.h>
#include <libsolidity/ASTForward.h>
@@ -42,7 +43,7 @@ class CompilerContext
{
public:
void addMagicGlobal(MagicVariableDeclaration const& _declaration);
- void addStateVariable(VariableDeclaration const& _declaration);
+ void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset);
void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0);
void removeVariable(VariableDeclaration const& _declaration);
void addAndInitializeVariable(VariableDeclaration const& _declaration);
@@ -82,7 +83,7 @@ public:
/// Converts an offset relative to the current stack height to a value that can be used later
/// with baseToCurrentStackOffset to point to the same stack element.
unsigned currentToBaseStackOffset(unsigned _offset) const;
- u256 getStorageLocationOfVariable(Declaration const& _declaration) const;
+ std::pair<u256, unsigned> getStorageLocationOfVariable(Declaration const& _declaration) const;
/// Appends a JUMPI instruction to a new tag and @returns the tag
eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); }
@@ -91,7 +92,7 @@ public:
/// Appends a JUMP to a new tag and @returns the tag
eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); }
/// Appends a JUMP to a tag already on the stack
- CompilerContext& appendJump() { return *this << eth::Instruction::JUMP; }
+ CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary);
/// Appends a JUMP to a specific tag
CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; }
/// Appends pushing of a new tag and @returns the new tag.
@@ -108,19 +109,19 @@ public:
/// Resets the stack of visited nodes with a new stack having only @c _node
void resetVisitedNodes(ASTNode const* _node);
/// Pops the stack of visited nodes
- void popVisitedNodes() { m_visitedNodes.pop(); }
+ void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); }
/// Pushes an ASTNode to the stack of visited nodes
- void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); }
+ void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); }
/// Append elements to the current instruction list and adjust @a m_stackOffset.
- CompilerContext& operator<<(eth::AssemblyItem const& _item);
- CompilerContext& operator<<(eth::Instruction _instruction);
- CompilerContext& operator<<(u256 const& _value);
- CompilerContext& operator<<(bytes const& _data);
+ CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; }
+ CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; }
+ CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; }
+ CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; }
eth::Assembly const& getAssembly() const { return m_asm; }
/// @arg _sourceCodes is the map of input files to source code strings
- void streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap()) const { m_asm.streamRLP(_stream, "", _sourceCodes); }
+ void streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap()) const { m_asm.stream(_stream, "", _sourceCodes); }
bytes getAssembledBytecode(bool _optimize = false) { return m_asm.optimise(_optimize).assemble(); }
@@ -130,22 +131,22 @@ public:
class LocationSetter: public ScopeGuard
{
public:
- LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node):
- ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); }
+ LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node):
+ ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); }
};
private:
std::vector<ContractDefinition const*>::const_iterator getSuperContract(const ContractDefinition &_contract) const;
+ /// Updates source location set in the assembly.
+ void updateSourceLocation();
eth::Assembly m_asm;
/// Magic global variables like msg, tx or this, distinguished by type.
std::set<Declaration const*> m_magicGlobals;
/// Other already compiled contracts to be used in contract creation calls.
std::map<ContractDefinition const*, bytes const*> m_compiledContracts;
- /// Size of the state variables, offset of next variable to be added.
- u256 m_stateVariablesSize = 0;
/// Storage offsets of state variables
- std::map<Declaration const*, u256> m_stateVariables;
+ std::map<Declaration const*, std::pair<u256, unsigned>> m_stateVariables;
/// Offsets of local variables on the stack (relative to stack base).
std::map<Declaration const*, unsigned> m_localVariables;
/// Labels pointing to the entry points of functions.
diff --git a/CompilerStack.cpp b/CompilerStack.cpp
index a878bb61..1301bfa5 100644
--- a/CompilerStack.cpp
+++ b/CompilerStack.cpp
@@ -41,14 +41,14 @@ namespace solidity
{
const map<string, string> StandardSources = map<string, string>{
- {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(string3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"},
+ {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(bytes3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"},
{"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"},
- {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}})"},
+ {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,bytes3 name,uint256 denom){}function register(bytes3 name,uint256 denom){}function unregister(){}})"},
{"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"},
{"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"},
{"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"},
- {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"},
- {"NameReg", R"(contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}})"},
+ {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(bytes32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"},
+ {"NameReg", R"(contract NameReg{function register(bytes32 name){}function addressOf(bytes32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(bytes32 name){}})"},
{"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"},
{"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"},
{"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"}
@@ -138,6 +138,8 @@ void CompilerStack::compile(bool _optimize)
for (ASTPointer<ASTNode> const& node: source->ast->getNodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))
{
+ if (!contract->isFullyImplemented())
+ continue;
shared_ptr<Compiler> compiler = make_shared<Compiler>(_optimize);
compiler->compileContract(*contract, contractBytecode);
Contract& compiledContract = m_contracts[contract->getName()];
diff --git a/CompilerUtils.cpp b/CompilerUtils.cpp
index 8a26b5d1..511254fa 100644
--- a/CompilerUtils.cpp
+++ b/CompilerUtils.cpp
@@ -93,6 +93,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound
else
{
solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented.");
+ m_context << eth::Instruction::POP; //@todo
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// stack here: memory_offset storage_offset length_bytes
// jump to end if length is zero
@@ -138,6 +139,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
{
unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable));
unsigned const size = _variable.getType()->getSizeOnStack();
+ solAssert(stackPosition >= size, "Variable size and position mismatch.");
// move variable starting from its top end in the stack
if (stackPosition - size + 1 > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_variable.getLocation())
@@ -148,8 +150,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize)
{
- if (_stackDepth > 16)
- BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Stack too deep."));
+ solAssert(_stackDepth <= 16, "Stack too deep.");
for (unsigned i = 0; i < _itemSize; ++i)
m_context << eth::dupInstruction(_stackDepth);
}
@@ -178,7 +179,7 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
- bool leftAligned = _type.getCategory() == Type::Category::String;
+ bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
if (numBytes == 0)
m_context << eth::Instruction::POP << u256(0);
else
@@ -198,11 +199,10 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda
return numBytes;
}
-
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
{
unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries);
- bool leftAligned = _type.getCategory() == Type::Category::String;
+ bool leftAligned = _type.getCategory() == Type::Category::FixedBytes;
if (numBytes == 0)
m_context << eth::Instruction::POP;
else
diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp
index 4be461b2..3ca8de89 100644
--- a/ExpressionCompiler.cpp
+++ b/ExpressionCompiler.cpp
@@ -48,7 +48,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
if (!_varDecl.getValue())
return;
solAssert(!!_varDecl.getValue()->getType(), "Type information not available.");
- CompilerContext::LocationSetter locationSetter(m_context, &_varDecl);
+ CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
_varDecl.getValue()->accept(*this);
appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true);
@@ -57,7 +57,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c
void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_varDecl);
+ CompilerContext::LocationSetter locationSetter(m_context, _varDecl);
FunctionType accessorType(_varDecl);
unsigned length = 0;
@@ -67,9 +67,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
length += CompilerUtils(m_context).storeInMemory(length, *paramType, true);
// retrieve the position of the variable
- m_context << m_context.getStorageLocationOfVariable(_varDecl);
- TypePointer returnType = _varDecl.getType();
+ auto const& location = m_context.getStorageLocationOfVariable(_varDecl);
+ m_context << location.first;
+ TypePointer returnType = _varDecl.getType();
for (TypePointer const& paramType: paramTypes)
{
// move offset to memory
@@ -90,9 +91,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
// struct
for (size_t i = 0; i < names.size(); ++i)
{
- m_context << eth::Instruction::DUP1
- << structType->getStorageOffsetOfMember(names[i])
- << eth::Instruction::ADD;
+ if (types[i]->getCategory() == Type::Category::Mapping)
+ continue;
+ pair<u256, unsigned> const& offsets = structType->getStorageOffsetsOfMember(names[i]);
+ m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second);
StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true);
solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented.");
m_context << eth::Instruction::SWAP1;
@@ -104,11 +106,13 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
{
// simple value
solAssert(accessorType.getReturnParameterTypes().size() == 1, "");
+ m_context << u256(location.second);
StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true);
retSizeOnStack = returnType->getSizeOnStack();
}
solAssert(retSizeOnStack <= 15, "Stack too deep.");
- m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP;
+ m_context << eth::dupInstruction(retSizeOnStack + 1);
+ m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction);
}
void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded)
@@ -122,23 +126,25 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
Type::Category stackTypeCategory = _typeOnStack.getCategory();
Type::Category targetTypeCategory = _targetType.getCategory();
- if (stackTypeCategory == Type::Category::String)
+ switch (stackTypeCategory)
{
- StaticStringType const& typeOnStack = dynamic_cast<StaticStringType const&>(_typeOnStack);
+ case Type::Category::FixedBytes:
+ {
+ FixedBytesType const& typeOnStack = dynamic_cast<FixedBytesType const&>(_typeOnStack);
if (targetTypeCategory == Type::Category::Integer)
{
- // conversion from string to hash. no need to clean the high bit
+ // conversion from bytes to integer. no need to clean the high bit
// only to shift right because of opposite alignment
IntegerType const& targetIntegerType = dynamic_cast<IntegerType const&>(_targetType);
- solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed.");
- solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same.");
m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
+ if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8)
+ appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded);
}
else
{
- // clear lower-order bytes for conversion to shorter strings - we always clean
- solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested.");
- StaticStringType const& targetType = dynamic_cast<StaticStringType const&>(_targetType);
+ // clear lower-order bytes for conversion to shorter bytes - we always clean
+ solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested.");
+ FixedBytesType const& targetType = dynamic_cast<FixedBytesType const&>(_targetType);
if (targetType.getNumBytes() < typeOnStack.getNumBytes())
{
if (targetType.getNumBytes() == 0)
@@ -150,22 +156,24 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
}
}
}
- else if (stackTypeCategory == Type::Category::Enum)
- solAssert(targetTypeCategory == Type::Category::Integer ||
- targetTypeCategory == Type::Category::Enum, "");
- else if (stackTypeCategory == Type::Category::Integer ||
- stackTypeCategory == Type::Category::Contract ||
- stackTypeCategory == Type::Category::IntegerConstant)
- {
- if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer)
+ break;
+ case Type::Category::Enum:
+ solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, "");
+ break;
+ case Type::Category::Integer:
+ case Type::Category::Contract:
+ case Type::Category::IntegerConstant:
+ if (targetTypeCategory == Type::Category::FixedBytes)
{
- // conversion from hash to string. no need to clean the high bit
+ solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant,
+ "Invalid conversion to FixedBytesType requested.");
+ // conversion from bytes to string. no need to clean the high bit
// only to shift left because of opposite alignment
- StaticStringType const& targetStringType = dynamic_cast<StaticStringType const&>(_targetType);
- IntegerType const& typeOnStack = dynamic_cast<IntegerType const&>(_typeOnStack);
- solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed.");
- solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same.");
- m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL;
+ FixedBytesType const& targetBytesType = dynamic_cast<FixedBytesType const&>(_targetType);
+ if (auto typeOnStack = dynamic_cast<IntegerType const*>(&_typeOnStack))
+ if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits())
+ appendHighBitsCleanup(*typeOnStack);
+ m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL;
}
else if (targetTypeCategory == Type::Category::Enum)
// just clean
@@ -175,7 +183,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, "");
IntegerType addressType(0, IntegerType::Modifier::Address);
IntegerType const& targetType = targetTypeCategory == Type::Category::Integer
- ? dynamic_cast<IntegerType const&>(_targetType) : addressType;
+ ? dynamic_cast<IntegerType const&>(_targetType) : addressType;
if (stackTypeCategory == Type::Category::IntegerConstant)
{
IntegerConstantType const& constType = dynamic_cast<IntegerConstantType const&>(_typeOnStack);
@@ -187,7 +195,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
else
{
IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer
- ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType;
+ ? dynamic_cast<IntegerType const&>(_typeOnStack) : addressType;
// Widening: clean up according to source type width
// Non-widening and force: clean up according to target type bits
if (targetType.getNumBits() > typeOnStack.getNumBits())
@@ -196,15 +204,17 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con
appendHighBitsCleanup(targetType);
}
}
- }
- else if (_typeOnStack != _targetType)
+ break;
+ default:
// All other types should not be convertible to non-equal types.
- BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested."));
+ solAssert(_typeOnStack == _targetType, "Invalid type conversion requested.");
+ break;
+ }
}
bool ExpressionCompiler::visit(Assignment const& _assignment)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_assignment);
+ CompilerContext::LocationSetter locationSetter(m_context, _assignment);
_assignment.getRightHandSide().accept(*this);
if (_assignment.getType()->isValueType())
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
@@ -226,9 +236,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
m_currentLValue->retrieveValue(_assignment.getLocation(), true);
appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType());
if (lvalueSize > 0)
+ {
+ solAssert(itemSize + lvalueSize <= 16, "Stack too deep.");
// value [lvalue_ref] updated_value
for (unsigned i = 0; i < itemSize; ++i)
m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP;
+ }
}
m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation());
m_currentLValue.reset();
@@ -237,7 +250,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_unaryOperation);
+ CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation);
//@todo type checking and creating code for an operator should be in the same place:
// the operator should know how to convert itself and to which types it applies, so
// put this code together with "Type::acceptsBinary/UnaryOperator" into a class that
@@ -270,23 +283,24 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
case Token::Dec: // -- (pre- or postfix)
solAssert(!!m_currentLValue, "LValue not retrieved.");
m_currentLValue->retrieveValue(_unaryOperation.getLocation());
- solAssert(m_currentLValue->sizeOnStack() <= 1, "Not implemented.");
if (!_unaryOperation.isPrefixOperation())
{
- if (m_currentLValue->sizeOnStack() == 1)
- m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2;
- else
- m_context << eth::Instruction::DUP1;
+ // store value for later
+ solAssert(_unaryOperation.getType()->getSizeOnStack() == 1, "Stack size != 1 not implemented.");
+ m_context << eth::Instruction::DUP1;
+ if (m_currentLValue->sizeOnStack() > 0)
+ for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i)
+ m_context << eth::swapInstruction(i);
}
m_context << u256(1);
if (_unaryOperation.getOperator() == Token::Inc)
m_context << eth::Instruction::ADD;
else
- m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap
- // Stack for prefix: [ref] (*ref)+-1
- // Stack for postfix: *ref [ref] (*ref)+-1
- if (m_currentLValue->sizeOnStack() == 1)
- m_context << eth::Instruction::SWAP1;
+ m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB;
+ // Stack for prefix: [ref...] (*ref)+-1
+ // Stack for postfix: *ref [ref...] (*ref)+-1
+ for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i)
+ m_context << eth::swapInstruction(i);
m_currentLValue->storeValue(
*_unaryOperation.getType(), _unaryOperation.getLocation(),
!_unaryOperation.isPrefixOperation());
@@ -307,7 +321,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_binaryOperation);
+ CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation);
Expression const& leftExpression = _binaryOperation.getLeftExpression();
Expression const& rightExpression = _binaryOperation.getRightExpression();
Type const& commonType = _binaryOperation.getCommonType();
@@ -354,7 +368,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation)
bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_functionCall);
+ CompilerContext::LocationSetter locationSetter(m_context, _functionCall);
using Location = FunctionType::Location;
if (_functionCall.isTypeConversion())
{
@@ -405,7 +419,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
}
_functionCall.getExpression().accept(*this);
- m_context.appendJump();
+ m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction);
m_context << returnLabel;
unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes());
@@ -528,8 +542,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
appendTypeConversion(*arguments[arg - 1]->getType(),
*function.getParameterTypes()[arg - 1], true);
}
- m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName()))));
- ++numIndexed;
+ if (!event.isAnonymous())
+ {
+ m_context << u256(h256::Arith(dev::sha3(function.externalSignature(event.getName()))));
+ ++numIndexed;
+ }
solAssert(numIndexed <= 4, "Too many indexed arguments.");
// Copy all non-indexed arguments to memory (data)
m_context << u256(0);
@@ -550,10 +567,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case Location::SHA256:
case Location::RIPEMD160:
{
+ _functionCall.getExpression().accept(*this);
static const map<Location, u256> contractAddresses{{Location::ECRecover, 1},
{Location::SHA256, 2},
{Location::RIPEMD160, 3}};
m_context << contractAddresses.find(function.getLocation())->second;
+ for (unsigned i = function.getSizeOnStack(); i > 0; --i)
+ m_context << eth::swapInstruction(i);
appendExternalFunctionCall(function, arguments, true);
break;
}
@@ -572,7 +592,7 @@ bool ExpressionCompiler::visit(NewExpression const&)
void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_memberAccess);
+ CompilerContext::LocationSetter locationSetter(m_context, _memberAccess);
ASTString const& member = _memberAccess.getMemberName();
switch (_memberAccess.getExpression().getType()->getCategory())
{
@@ -639,13 +659,18 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
m_context << eth::Instruction::GASPRICE;
else if (member == "data")
m_context << u256(0) << eth::Instruction::CALLDATASIZE;
+ else if (member == "sig")
+ m_context << u256(0) << eth::Instruction::CALLDATALOAD
+ << (u256(0xffffffff) << (256 - 32)) << eth::Instruction::AND;
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member."));
break;
case Type::Category::Struct:
{
StructType const& type = dynamic_cast<StructType const&>(*_memberAccess.getExpression().getType());
- m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD;
+ m_context << eth::Instruction::POP; // structs always align to new slot
+ pair<u256, unsigned> const& offsets = type.getStorageOffsetsOfMember(member);
+ m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second);
setLValueToStorageItem(_memberAccess);
break;
}
@@ -707,110 +732,43 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
{
- CompilerContext::LocationSetter locationSetter(m_context, &_indexAccess);
+ CompilerContext::LocationSetter locationSetter(m_context, _indexAccess);
_indexAccess.getBaseExpression().accept(*this);
Type const& baseType = *_indexAccess.getBaseExpression().getType();
if (baseType.getCategory() == Type::Category::Mapping)
{
+ // storage byte offset is ignored for mappings, it should be zero.
+ m_context << eth::Instruction::POP;
+ // stack: storage_base_ref
Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType();
- m_context << u256(0);
+ m_context << u256(0); // memory position
solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression());
- solAssert(baseType.getSizeOnStack() == 1,
- "Unexpected: Not exactly one stack slot taken by subscriptable expression.");
m_context << eth::Instruction::SWAP1;
appendTypeMoveToMemory(IntegerType(256));
m_context << u256(0) << eth::Instruction::SHA3;
+ m_context << u256(0);
setLValueToStorageItem( _indexAccess);
}
else if (baseType.getCategory() == Type::Category::Array)
{
- // stack layout: <base_ref> [<length>] <index>
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(baseType);
solAssert(_indexAccess.getIndexExpression(), "Index expression expected.");
- ArrayType::Location location = arrayType.getLocation();
- eth::Instruction load =
- location == ArrayType::Location::Storage ? eth::Instruction::SLOAD :
- location == ArrayType::Location::Memory ? eth::Instruction::MLOAD :
- eth::Instruction::CALLDATALOAD;
+
+ // remove storage byte offset
+ if (arrayType.getLocation() == ArrayType::Location::Storage)
+ m_context << eth::Instruction::POP;
_indexAccess.getIndexExpression()->accept(*this);
- // retrieve length
- if (!arrayType.isDynamicallySized())
- m_context << arrayType.getLength();
- else if (location == ArrayType::Location::CallData)
- // length is stored on the stack
- m_context << eth::Instruction::SWAP1;
- else
- m_context << eth::Instruction::DUP2 << load;
- // stack: <base_ref> <index> <length>
- // check out-of-bounds access
- m_context << eth::Instruction::DUP2 << eth::Instruction::LT;
- eth::AssemblyItem legalAccess = m_context.appendConditionalJump();
- // out-of-bounds access throws exception (just STOP for now)
- m_context << eth::Instruction::STOP;
-
- m_context << legalAccess;
- // stack: <base_ref> <index>
- if (arrayType.isByteArray())
- // byte array is packed differently, especially in storage
- switch (location)
- {
- case ArrayType::Location::Storage:
- // byte array index storage lvalue on stack (goal):
- // <ref> <byte_number> = <base_ref + index / 32> <index % 32>
- m_context << u256(32) << eth::Instruction::SWAP2;
- CompilerUtils(m_context).computeHashStatic();
- // stack: 32 index data_ref
- m_context
- << eth::Instruction::DUP3 << eth::Instruction::DUP3
- << eth::Instruction::DIV << eth::Instruction::ADD
- // stack: 32 index (data_ref + index / 32)
- << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 << eth::Instruction::MOD;
- setLValue<StorageByteArrayElement>(_indexAccess);
- break;
- case ArrayType::Location::CallData:
- // no lvalue, just retrieve the value
- m_context
- << eth::Instruction::ADD << eth::Instruction::CALLDATALOAD
- << u256(0) << eth::Instruction::BYTE;
- break;
- case ArrayType::Location::Memory:
- solAssert(false, "Memory lvalues not yet implemented.");
- }
- else
+ // stack layout: <base_ref> [<length>] <index>
+ ArrayUtils(m_context).accessIndex(arrayType);
+ if (arrayType.getLocation() == ArrayType::Location::Storage)
{
- u256 elementSize =
- location == ArrayType::Location::Storage ?
- arrayType.getBaseType()->getStorageSize() :
- arrayType.getBaseType()->getCalldataEncodedSize();
- solAssert(elementSize != 0, "Invalid element size.");
- if (elementSize > 1)
- m_context << elementSize << eth::Instruction::MUL;
- if (arrayType.isDynamicallySized())
- {
- if (location == ArrayType::Location::Storage)
- {
- m_context << eth::Instruction::SWAP1;
- CompilerUtils(m_context).computeHashStatic();
- }
- else if (location == ArrayType::Location::Memory)
- m_context << u256(32) << eth::Instruction::ADD;
- }
- m_context << eth::Instruction::ADD;
- switch (location)
- {
- case ArrayType::Location::CallData:
- if (arrayType.getBaseType()->isValueType())
- CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false);
- break;
- case ArrayType::Location::Storage:
+ if (arrayType.isByteArray())
+ setLValue<StorageByteArrayElement>(_indexAccess);
+ else
setLValueToStorageItem(_indexAccess);
- break;
- case ArrayType::Location::Memory:
- solAssert(false, "Memory lvalues not yet implemented.");
- }
}
}
else
@@ -821,18 +779,34 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
void ExpressionCompiler::endVisit(Identifier const& _identifier)
{
+ CompilerContext::LocationSetter locationSetter(m_context, _identifier);
Declaration const* declaration = _identifier.getReferencedDeclaration();
if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
{
- if (magicVar->getType()->getCategory() == Type::Category::Contract)
+ switch (magicVar->getType()->getCategory())
+ {
+ case Type::Category::Contract:
// "this" or "super"
if (!dynamic_cast<ContractType const&>(*magicVar->getType()).isSuper())
m_context << eth::Instruction::ADDRESS;
+ break;
+ case Type::Category::Integer:
+ // "now"
+ m_context << eth::Instruction::TIMESTAMP;
+ break;
+ default:
+ break;
+ }
}
else if (FunctionDefinition const* functionDef = dynamic_cast<FunctionDefinition const*>(declaration))
m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag();
- else if (dynamic_cast<VariableDeclaration const*>(declaration))
- setLValueFromDeclaration(*declaration, _identifier);
+ else if (auto variable = dynamic_cast<VariableDeclaration const*>(declaration))
+ {
+ if (!variable->isConstant())
+ setLValueFromDeclaration(*declaration, _identifier);
+ else
+ variable->getValue()->accept(*this);
+ }
else if (dynamic_cast<ContractDefinition const*>(declaration))
{
// no-op
@@ -860,11 +834,12 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier)
void ExpressionCompiler::endVisit(Literal const& _literal)
{
+ CompilerContext::LocationSetter locationSetter(m_context, _literal);
switch (_literal.getType()->getCategory())
{
case Type::Category::IntegerConstant:
case Type::Category::Bool:
- case Type::Category::String:
+ case Type::Category::FixedBytes:
m_context << _literal.getType()->literalValue(&_literal);
break;
default:
@@ -1115,7 +1090,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio
if (m_context.isLocalVariable(&_declaration))
setLValue<StackVariable>(_expression, _declaration);
else if (m_context.isStateVariable(&_declaration))
- setLValue<StorageItem>(_expression, _declaration);
+ setLValue<StorageItem>(_expression, _declaration);
else
BOOST_THROW_EXCEPTION(InternalCompilerError()
<< errinfo_sourceLocation(_expression.getLocation())
diff --git a/ExpressionCompiler.h b/ExpressionCompiler.h
index 9cab757e..2577d21b 100644
--- a/ExpressionCompiler.h
+++ b/ExpressionCompiler.h
@@ -42,7 +42,6 @@ class CompilerContext;
class Type;
class IntegerType;
class ArrayType;
-class StaticStringType;
/**
* Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream
@@ -140,7 +139,6 @@ void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments con
m_currentLValue = move(lvalue);
else
lvalue->retrieveValue(_expression.getLocation(), true);
-
}
}
diff --git a/GlobalContext.cpp b/GlobalContext.cpp
index 60de5105..80cebd76 100644
--- a/GlobalContext.cpp
+++ b/GlobalContext.cpp
@@ -37,26 +37,27 @@ GlobalContext::GlobalContext():
m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)),
make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)),
make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)),
+ make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)),
make_shared<MagicVariableDeclaration>("suicide",
make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)),
make_shared<MagicVariableDeclaration>("sha3",
- make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA3, true)),
+ make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)),
make_shared<MagicVariableDeclaration>("log0",
- make_shared<FunctionType>(strings{"hash"},strings{}, FunctionType::Location::Log0)),
+ make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)),
make_shared<MagicVariableDeclaration>("log1",
- make_shared<FunctionType>(strings{"hash", "hash"},strings{}, FunctionType::Location::Log1)),
+ make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)),
make_shared<MagicVariableDeclaration>("log2",
- make_shared<FunctionType>(strings{"hash", "hash", "hash"},strings{}, FunctionType::Location::Log2)),
+ make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)),
make_shared<MagicVariableDeclaration>("log3",
- make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log3)),
+ make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)),
make_shared<MagicVariableDeclaration>("log4",
- make_shared<FunctionType>(strings{"hash", "hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log4)),
+ make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)),
make_shared<MagicVariableDeclaration>("sha256",
- make_shared<FunctionType>(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)),
+ make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)),
make_shared<MagicVariableDeclaration>("ecrecover",
- make_shared<FunctionType>(strings{"hash", "hash8", "hash", "hash"}, strings{"address"}, FunctionType::Location::ECRecover)),
+ make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)),
make_shared<MagicVariableDeclaration>("ripemd160",
- make_shared<FunctionType>(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))})
+ make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))})
{
}
diff --git a/InterfaceHandler.cpp b/InterfaceHandler.cpp
index 99a7db96..aacbbfd7 100644
--- a/InterfaceHandler.cpp
+++ b/InterfaceHandler.cpp
@@ -70,6 +70,7 @@ std::unique_ptr<std::string> InterfaceHandler::getABIInterface(ContractDefinitio
Json::Value event;
event["type"] = "event";
event["name"] = it->getName();
+ event["anonymous"] = it->isAnonymous();
Json::Value params(Json::arrayValue);
for (auto const& p: it->getParameters())
{
@@ -128,7 +129,7 @@ std::unique_ptr<std::string> InterfaceHandler::getUserDocumentation(ContractDefi
if (!m_notice.empty())
{// since @notice is the only user tag if missing function should not appear
user["notice"] = Json::Value(m_notice);
- methods[it.second->getCanonicalSignature()] = user;
+ methods[it.second->externalSignature()] = user;
}
}
}
@@ -174,8 +175,17 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
method["author"] = m_author;
Json::Value params(Json::objectValue);
+ std::vector<std::string> paramNames = it.second->getParameterNames();
for (auto const& pair: m_params)
+ {
+ if (find(paramNames.begin(), paramNames.end(), pair.first) == paramNames.end())
+ // LTODO: mismatching parameter name, throw some form of warning and not just an exception
+ BOOST_THROW_EXCEPTION(
+ DocstringParsingError() <<
+ errinfo_comment("documented parameter \"" + pair.first + "\" not found found in the function")
+ );
params[pair.first] = pair.second;
+ }
if (!m_params.empty())
method["params"] = params;
@@ -184,7 +194,7 @@ std::unique_ptr<std::string> InterfaceHandler::getDevDocumentation(ContractDefin
method["return"] = m_return;
if (!method.empty()) // add the function, only if we have any documentation to add
- methods[it.second->getCanonicalSignature()] = method;
+ methods[it.second->externalSignature()] = method;
}
}
doc["methods"] = methods;
diff --git a/LValue.cpp b/LValue.cpp
index a036be80..234072bc 100644
--- a/LValue.cpp
+++ b/LValue.cpp
@@ -77,7 +77,8 @@ void StackVariable::setToZero(SourceLocation const& _location, bool) const
StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration):
StorageItem(_compilerContext, *_declaration.getType())
{
- m_context << m_context.getStorageLocationOfVariable(_declaration);
+ auto const& location = m_context.getStorageLocationOfVariable(_declaration);
+ m_context << location.first << u256(location.second);
}
StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
@@ -86,62 +87,78 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type):
if (m_dataType.isValueType())
{
solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), "");
- solAssert(m_dataType.getStorageSize() <= numeric_limits<unsigned>::max(),
- "The storage size of " + m_dataType.toString() + " should fit in an unsigned");
- m_size = unsigned(m_dataType.getStorageSize());
+ solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size.");
}
- else
- m_size = 0; // unused
}
void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const
{
+ // stack: storage_key storage_offset
if (!m_dataType.isValueType())
return; // no distinction between value and reference for non-value types
if (!_remove)
- m_context << eth::Instruction::DUP1;
- if (m_size == 1)
- m_context << eth::Instruction::SLOAD;
+ CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
+ if (m_dataType.getStorageBytes() == 32)
+ m_context << eth::Instruction::POP << eth::Instruction::SLOAD;
else
- for (unsigned i = 0; i < m_size; ++i)
- {
- m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1;
- if (i + 1 < m_size)
- m_context << u256(1) << eth::Instruction::ADD;
- else
- m_context << eth::Instruction::POP;
- }
+ {
+ m_context
+ << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1
+ << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV;
+ if (m_dataType.getCategory() == Type::Category::FixedBytes)
+ m_context << (u256(0x1) << (256 - 8 * m_dataType.getStorageBytes())) << eth::Instruction::MUL;
+ else
+ m_context << ((u256(0x1) << (8 * m_dataType.getStorageBytes())) - 1) << eth::Instruction::AND;
+ }
}
void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const
{
- // stack layout: value value ... value target_ref
+ // stack: value storage_key storage_offset
if (m_dataType.isValueType())
{
- if (!_move) // copy values
+ solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size.");
+ solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size.");
+ if (m_dataType.getStorageBytes() == 32)
{
- if (m_size + 1 > 16)
- BOOST_THROW_EXCEPTION(CompilerError()
- << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep."));
- for (unsigned i = 0; i < m_size; ++i)
- m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1;
+ // offset should be zero
+ m_context << eth::Instruction::POP;
+ if (!_move)
+ m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1;
+ m_context << eth::Instruction::SSTORE;
}
- if (m_size > 1) // store high index value first
- m_context << u256(m_size - 1) << eth::Instruction::ADD;
- for (unsigned i = 0; i < m_size; ++i)
+ else
{
- if (i + 1 >= m_size)
- m_context << eth::Instruction::SSTORE;
- else
- // stack here: value value ... value value (target_ref+offset)
- m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2
- << eth::Instruction::SSTORE
- << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
+ // OR the value into the other values in the storage slot
+ m_context << u256(0x100) << eth::Instruction::EXP;
+ // stack: value storage_ref multiplier
+ // fetch old value
+ m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
+ // stack: value storege_ref multiplier old_full_value
+ // clear bytes in old value
+ m_context
+ << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1)
+ << eth::Instruction::MUL;
+ m_context << eth::Instruction::NOT << eth::Instruction::AND;
+ // stack: value storage_ref multiplier cleared_value
+ m_context
+ << eth::Instruction::SWAP1 << eth::Instruction::DUP4;
+ // stack: value storage_ref cleared_value multiplier value
+ if (m_dataType.getCategory() == Type::Category::FixedBytes)
+ m_context
+ << (u256(0x1) << (256 - 8 * dynamic_cast<FixedBytesType const&>(m_dataType).getNumBytes()))
+ << eth::Instruction::SWAP1 << eth::Instruction::DIV;
+ m_context << eth::Instruction::MUL << eth::Instruction::OR;
+ // stack: value storage_ref updated_value
+ m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
+ if (_move)
+ m_context << eth::Instruction::POP;
}
}
else
{
- solAssert(_sourceType.getCategory() == m_dataType.getCategory(),
+ solAssert(
+ _sourceType.getCategory() == m_dataType.getCategory(),
"Wrong type conversation for assignment.");
if (m_dataType.getCategory() == Type::Category::Array)
{
@@ -149,11 +166,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
dynamic_cast<ArrayType const&>(m_dataType),
dynamic_cast<ArrayType const&>(_sourceType));
if (_move)
- m_context << eth::Instruction::POP;
+ CompilerUtils(m_context).popStackElement(_sourceType);
}
else if (m_dataType.getCategory() == Type::Category::Struct)
{
- // stack layout: source_ref target_ref
+ // stack layout: source_ref source_offset target_ref target_offset
+ // note that we have structs, so offsets should be zero and are ignored
auto const& structType = dynamic_cast<StructType const&>(m_dataType);
solAssert(structType == _sourceType, "Struct assignment with conversion.");
for (auto const& member: structType.getMembers())
@@ -162,26 +180,37 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc
TypePointer const& memberType = member.second;
if (memberType->getCategory() == Type::Category::Mapping)
continue;
- m_context << structType.getStorageOffsetOfMember(member.first)
- << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD;
- // stack: source_ref target_ref member_offset source_member_ref
+ pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first);
+ m_context
+ << offsets.first << u256(offsets.second)
+ << eth::Instruction::DUP6 << eth::Instruction::DUP3
+ << eth::Instruction::ADD << eth::Instruction::DUP2;
+ // stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_member_ref source_member_off
StorageItem(m_context, *memberType).retrieveValue(_location, true);
- // stack: source_ref target_ref member_offset source_value...
- m_context << eth::dupInstruction(2 + memberType->getSizeOnStack())
- << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD;
- // stack: source_ref target_ref member_offset source_value... target_member_ref
+ // stack: source_ref source_off target_ref target_off member_offset source_value...
+ solAssert(4 + memberType->getSizeOnStack() <= 16, "Stack too deep.");
+ m_context
+ << eth::dupInstruction(4 + memberType->getSizeOnStack())
+ << eth::dupInstruction(3 + memberType->getSizeOnStack()) << eth::Instruction::ADD
+ << eth::dupInstruction(2 + memberType->getSizeOnStack());
+ // stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_value... target_member_ref target_member_byte_off
StorageItem(m_context, *memberType).storeValue(*memberType, _location, true);
- m_context << eth::Instruction::POP;
+ m_context << eth::Instruction::POP << eth::Instruction::POP;
}
if (_move)
- m_context << eth::Instruction::POP;
+ m_context
+ << eth::Instruction::POP << eth::Instruction::POP
+ << eth::Instruction::POP << eth::Instruction::POP;
else
- m_context << eth::Instruction::SWAP1;
- m_context << eth::Instruction::POP;
+ m_context
+ << eth::Instruction::SWAP2 << eth::Instruction::POP
+ << eth::Instruction::SWAP2 << eth::Instruction::POP;
}
else
- BOOST_THROW_EXCEPTION(InternalCompilerError()
- << errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment."));
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_sourceLocation(_location)
+ << errinfo_comment("Invalid non-value type for assignment."));
}
}
@@ -190,12 +219,13 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
if (m_dataType.getCategory() == Type::Category::Array)
{
if (!_removeReference)
- m_context << eth::Instruction::DUP1;
+ CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(m_dataType));
}
else if (m_dataType.getCategory() == Type::Category::Struct)
{
- // stack layout: ref
+ // stack layout: storage_key storage_offset
+ // @todo this can be improved for packed types
auto const& structType = dynamic_cast<StructType const&>(m_dataType);
for (auto const& member: structType.getMembers())
{
@@ -203,38 +233,48 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const
TypePointer const& memberType = member.second;
if (memberType->getCategory() == Type::Category::Mapping)
continue;
- m_context << structType.getStorageOffsetOfMember(member.first)
- << eth::Instruction::DUP2 << eth::Instruction::ADD;
+ pair<u256, unsigned> const& offsets = structType.getStorageOffsetsOfMember(member.first);
+ m_context
+ << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD
+ << u256(offsets.second);
StorageItem(m_context, *memberType).setToZero();
}
if (_removeReference)
- m_context << eth::Instruction::POP;
+ m_context << eth::Instruction::POP << eth::Instruction::POP;
}
else
{
solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString());
- if (m_size == 0 && _removeReference)
- m_context << eth::Instruction::POP;
- else if (m_size == 1)
+ // @todo actually use offset
+ if (!_removeReference)
+ CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
+ if (m_dataType.getStorageBytes() == 32)
+ {
+ // offset should be zero
m_context
- << u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2)
- << eth::Instruction::SSTORE;
+ << eth::Instruction::POP << u256(0)
+ << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
+ }
else
{
- if (!_removeReference)
- m_context << eth::Instruction::DUP1;
- for (unsigned i = 0; i < m_size; ++i)
- if (i + 1 >= m_size)
- m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
- else
- m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE
- << u256(1) << eth::Instruction::ADD;
+ m_context << u256(0x100) << eth::Instruction::EXP;
+ // stack: storage_ref multiplier
+ // fetch old value
+ m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD;
+ // stack: storege_ref multiplier old_full_value
+ // clear bytes in old value
+ m_context
+ << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1)
+ << eth::Instruction::MUL;
+ m_context << eth::Instruction::NOT << eth::Instruction::AND;
+ // stack: storage_ref cleared_value
+ m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
}
}
}
/// Used in StorageByteArrayElement
-static IntegerType byteType(8, IntegerType::Modifier::Hash);
+static FixedBytesType byteType(1);
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext):
LValue(_compilerContext, byteType)
@@ -250,6 +290,7 @@ void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove)
else
m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD
<< eth::Instruction::DUP2 << eth::Instruction::BYTE;
+ m_context << (u256(1) << (256 - 8)) << eth::Instruction::MUL;
}
void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const
@@ -265,8 +306,9 @@ void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, boo
m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL
<< eth::Instruction::NOT << eth::Instruction::AND;
// stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte
- m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4 << eth::Instruction::MUL
- << eth::Instruction::OR;
+ m_context << eth::Instruction::SWAP1;
+ m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV
+ << eth::Instruction::MUL << eth::Instruction::OR;
// stack: value ref new_full_value
m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
if (_move)
@@ -297,6 +339,8 @@ StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const
m_arrayType(_arrayType)
{
solAssert(m_arrayType.isDynamicallySized(), "");
+ // storage byte offset must be zero
+ m_context << eth::Instruction::POP;
}
void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const
diff --git a/LValue.h b/LValue.h
index c57c80e3..ad622516 100644
--- a/LValue.h
+++ b/LValue.h
@@ -98,7 +98,9 @@ private:
};
/**
- * Reference to some item in storage. The (starting) position of the item is stored on the stack.
+ * Reference to some item in storage. On the stack this is <storage key> <offset_inside_value>,
+ * where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied
+ * by 2**i before storing it.
*/
class StorageItem: public LValue
{
@@ -107,6 +109,7 @@ public:
StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration);
/// Constructs the LValue and assumes that the storage reference is already on the stack.
StorageItem(CompilerContext& _compilerContext, Type const& _type);
+ virtual unsigned sizeOnStack() const { return 2; }
virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
Type const& _sourceType,
@@ -117,11 +120,6 @@ public:
SourceLocation const& _location = SourceLocation(),
bool _removeReference = true
) const override;
-
-private:
- /// Number of stack elements occupied by the value (not the reference).
- /// Only used for value types.
- unsigned m_size;
};
/**
diff --git a/Parser.cpp b/Parser.cpp
index 44d11159..5c7676df 100644
--- a/Parser.cpp
+++ b/Parser.cpp
@@ -164,8 +164,17 @@ ASTPointer<ContractDefinition> Parser::parseContractDefinition()
}
nodeFactory.markEndPosition();
expectToken(Token::RBrace);
- return nodeFactory.createNode<ContractDefinition>(name, docString, baseContracts, structs, enums,
- stateVariables, functions, modifiers, events);
+ return nodeFactory.createNode<ContractDefinition>(
+ name,
+ docString,
+ baseContracts,
+ structs,
+ enums,
+ stateVariables,
+ functions,
+ modifiers,
+ events
+ );
}
ASTPointer<InheritanceSpecifier> Parser::parseInheritanceSpecifier()
@@ -247,8 +256,15 @@ ASTPointer<FunctionDefinition> Parser::parseFunctionDefinition(ASTString const*
}
else
returnParameters = createEmptyParameterList();
- ASTPointer<Block> block = parseBlock();
- nodeFactory.setEndPositionFromNode(block);
+ ASTPointer<Block> block = ASTPointer<Block>();
+ nodeFactory.markEndPosition();
+ if (m_scanner->getCurrentToken() != Token::Semicolon)
+ {
+ block = parseBlock();
+ nodeFactory.setEndPositionFromNode(block);
+ }
+ else
+ m_scanner->next(); // just consume the ';'
bool const c_isConstructor = (_contractName && *name == *_contractName);
return nodeFactory.createNode<FunctionDefinition>(name, visibility, c_isConstructor, docstring,
parameters, isDeclaredConst, modifiers,
@@ -317,6 +333,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
nodeFactory.setEndPositionFromNode(type);
}
bool isIndexed = false;
+ bool isDeclaredConst = false;
ASTPointer<ASTString> identifier;
Token::Value token = m_scanner->getCurrentToken();
Declaration::Visibility visibility(Declaration::Visibility::Default);
@@ -327,7 +344,13 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
isIndexed = true;
m_scanner->next();
}
+ if (token == Token::Const)
+ {
+ isDeclaredConst = true;
+ m_scanner->next();
+ }
nodeFactory.markEndPosition();
+
if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier)
{
identifier = make_shared<ASTString>("");
@@ -348,7 +371,7 @@ ASTPointer<VariableDeclaration> Parser::parseVariableDeclaration(
}
return nodeFactory.createNode<VariableDeclaration>(type, identifier, value,
visibility, _options.isStateVariable,
- isIndexed);
+ isIndexed, isDeclaredConst);
}
ASTPointer<ModifierDefinition> Parser::parseModifierDefinition()
@@ -387,9 +410,15 @@ ASTPointer<EventDefinition> Parser::parseEventDefinition()
parameters = parseParameterList(true, true);
else
parameters = createEmptyParameterList();
+ bool anonymous = false;
+ if (m_scanner->getCurrentToken() == Token::Anonymous)
+ {
+ anonymous = true;
+ m_scanner->next();
+ }
nodeFactory.markEndPosition();
expectToken(Token::Semicolon);
- return nodeFactory.createNode<EventDefinition>(name, docstring, parameters);
+ return nodeFactory.createNode<EventDefinition>(name, docstring, parameters, anonymous);
}
ASTPointer<ModifierInvocation> Parser::parseModifierInvocation()
@@ -913,6 +942,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const
// In all other cases, we have an expression statement.
Token::Value token(m_scanner->getCurrentToken());
bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier);
+
if (token == Token::Mapping || token == Token::Var ||
(mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier))
return LookAheadInfo::VariableDeclarationStatement;
diff --git a/Parser.h b/Parser.h
index 87eb2f8f..08c47c25 100644
--- a/Parser.h
+++ b/Parser.h
@@ -34,6 +34,8 @@ class Scanner;
class Parser
{
public:
+ Parser() {}
+
ASTPointer<SourceUnit> parse(std::shared_ptr<Scanner> const& _scanner);
std::shared_ptr<std::string const> const& getSourceName() const;
@@ -64,8 +66,7 @@ private:
ASTPointer<StructDefinition> parseStructDefinition();
ASTPointer<EnumDefinition> parseEnumDefinition();
ASTPointer<EnumValue> parseEnumValue();
- ASTPointer<VariableDeclaration> parseVariableDeclaration(
- VarDeclParserOptions const& _options = VarDeclParserOptions(),
+ ASTPointer<VariableDeclaration> parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions(),
ASTPointer<TypeName> const& _lookAheadArrayType = ASTPointer<TypeName>());
ASTPointer<ModifierDefinition> parseModifierDefinition();
ASTPointer<EventDefinition> parseEventDefinition();
diff --git a/Token.h b/Token.h
index 85979b56..1435dcc5 100644
--- a/Token.h
+++ b/Token.h
@@ -143,8 +143,8 @@ namespace solidity
\
/* Keywords */ \
K(Break, "break", 0) \
- K(Case, "case", 0) \
K(Const, "constant", 0) \
+ K(Anonymous, "anonymous", 0) \
K(Continue, "continue", 0) \
K(Contract, "contract", 0) \
K(Default, "default", 0) \
@@ -167,7 +167,6 @@ namespace solidity
K(Return, "return", 0) \
K(Returns, "returns", 0) \
K(Struct, "struct", 0) \
- K(Switch, "switch", 0) \
K(Var, "var", 0) \
K(While, "while", 0) \
K(Enum, "enum", 0) \
@@ -252,77 +251,43 @@ namespace solidity
K(UInt240, "uint240", 0) \
K(UInt248, "uint248", 0) \
K(UInt256, "uint256", 0) \
- K(Hash, "hash", 0) \
- K(Hash8, "hash8", 0) \
- K(Hash16, "hash16", 0) \
- K(Hash24, "hash24", 0) \
- K(Hash32, "hash32", 0) \
- K(Hash40, "hash40", 0) \
- K(Hash48, "hash48", 0) \
- K(Hash56, "hash56", 0) \
- K(Hash64, "hash64", 0) \
- K(Hash72, "hash72", 0) \
- K(Hash80, "hash80", 0) \
- K(Hash88, "hash88", 0) \
- K(Hash96, "hash96", 0) \
- K(Hash104, "hash104", 0) \
- K(Hash112, "hash112", 0) \
- K(Hash120, "hash120", 0) \
- K(Hash128, "hash128", 0) \
- K(Hash136, "hash136", 0) \
- K(Hash144, "hash144", 0) \
- K(Hash152, "hash152", 0) \
- K(Hash160, "hash160", 0) \
- K(Hash168, "hash168", 0) \
- K(Hash176, "hash178", 0) \
- K(Hash184, "hash184", 0) \
- K(Hash192, "hash192", 0) \
- K(Hash200, "hash200", 0) \
- K(Hash208, "hash208", 0) \
- K(Hash216, "hash216", 0) \
- K(Hash224, "hash224", 0) \
- K(Hash232, "hash232", 0) \
- K(Hash240, "hash240", 0) \
- K(Hash248, "hash248", 0) \
- K(Hash256, "hash256", 0) \
+ K(Bytes0, "bytes0", 0) \
+ K(Bytes1, "bytes1", 0) \
+ K(Bytes2, "bytes2", 0) \
+ K(Bytes3, "bytes3", 0) \
+ K(Bytes4, "bytes4", 0) \
+ K(Bytes5, "bytes5", 0) \
+ K(Bytes6, "bytes6", 0) \
+ K(Bytes7, "bytes7", 0) \
+ K(Bytes8, "bytes8", 0) \
+ K(Bytes9, "bytes9", 0) \
+ K(Bytes10, "bytes10", 0) \
+ K(Bytes11, "bytes11", 0) \
+ K(Bytes12, "bytes12", 0) \
+ K(Bytes13, "bytes13", 0) \
+ K(Bytes14, "bytes14", 0) \
+ K(Bytes15, "bytes15", 0) \
+ K(Bytes16, "bytes16", 0) \
+ K(Bytes17, "bytes17", 0) \
+ K(Bytes18, "bytes18", 0) \
+ K(Bytes19, "bytes19", 0) \
+ K(Bytes20, "bytes20", 0) \
+ K(Bytes21, "bytes21", 0) \
+ K(Bytes22, "bytes22", 0) \
+ K(Bytes23, "bytes23", 0) \
+ K(Bytes24, "bytes24", 0) \
+ K(Bytes25, "bytes25", 0) \
+ K(Bytes26, "bytes26", 0) \
+ K(Bytes27, "bytes27", 0) \
+ K(Bytes28, "bytes28", 0) \
+ K(Bytes29, "bytes29", 0) \
+ K(Bytes30, "bytes30", 0) \
+ K(Bytes31, "bytes31", 0) \
+ K(Bytes32, "bytes32", 0) \
+ K(Bytes, "bytes", 0) \
+ K(Byte, "byte", 0) \
K(Address, "address", 0) \
K(Bool, "bool", 0) \
- K(Bytes, "bytes", 0) \
- K(StringType, "string", 0) \
- K(String0, "string0", 0) \
- K(String1, "string1", 0) \
- K(String2, "string2", 0) \
- K(String3, "string3", 0) \
- K(String4, "string4", 0) \
- K(String5, "string5", 0) \
- K(String6, "string6", 0) \
- K(String7, "string7", 0) \
- K(String8, "string8", 0) \
- K(String9, "string9", 0) \
- K(String10, "string10", 0) \
- K(String11, "string11", 0) \
- K(String12, "string12", 0) \
- K(String13, "string13", 0) \
- K(String14, "string14", 0) \
- K(String15, "string15", 0) \
- K(String16, "string16", 0) \
- K(String17, "string17", 0) \
- K(String18, "string18", 0) \
- K(String19, "string19", 0) \
- K(String20, "string20", 0) \
- K(String21, "string21", 0) \
- K(String22, "string22", 0) \
- K(String23, "string23", 0) \
- K(String24, "string24", 0) \
- K(String25, "string25", 0) \
- K(String26, "string26", 0) \
- K(String27, "string27", 0) \
- K(String28, "string28", 0) \
- K(String29, "string29", 0) \
- K(String30, "string30", 0) \
- K(String31, "string31", 0) \
- K(String32, "string32", 0) \
- K(Text, "text", 0) \
K(Real, "real", 0) \
K(UReal, "ureal", 0) \
T(TypesEnd, NULL, 0) /* used as type enum end marker */ \
@@ -338,6 +303,16 @@ namespace solidity
/* Identifiers (not keywords or future reserved words). */ \
T(Identifier, NULL, 0) \
\
+ /* Keywords reserved for future. use*/ \
+ T(String, "string", 0) \
+ K(Case, "case", 0) \
+ K(Switch, "switch", 0) \
+ K(Throw, "throw", 0) \
+ K(Try, "try", 0) \
+ K(Catch, "catch", 0) \
+ K(Using, "using", 0) \
+ K(Type, "type", 0) \
+ K(TypeOf, "typeof", 0) \
/* Illegal token - not able to scan. */ \
T(Illegal, "ILLEGAL", 0) \
\
diff --git a/Types.cpp b/Types.cpp
index 454d79d9..78649cc9 100644
--- a/Types.cpp
+++ b/Types.cpp
@@ -20,14 +20,14 @@
* Solidity data types
*/
+#include <libsolidity/Types.h>
+#include <limits>
+#include <boost/range/adaptor/reversed.hpp>
#include <libdevcore/CommonIO.h>
#include <libdevcore/CommonData.h>
#include <libsolidity/Utils.h>
-#include <libsolidity/Types.h>
#include <libsolidity/AST.h>
-#include <limits>
-
using namespace std;
namespace dev
@@ -35,28 +35,113 @@ namespace dev
namespace solidity
{
+void StorageOffsets::computeOffsets(TypePointers const& _types)
+{
+ bigint slotOffset = 0;
+ unsigned byteOffset = 0;
+ map<size_t, pair<u256, unsigned>> offsets;
+ for (size_t i = 0; i < _types.size(); ++i)
+ {
+ TypePointer const& type = _types[i];
+ if (!type->canBeStored())
+ continue;
+ if (byteOffset + type->getStorageBytes() > 32)
+ {
+ // would overflow, go to next slot
+ ++slotOffset;
+ byteOffset = 0;
+ }
+ if (slotOffset >= bigint(1) << 256)
+ BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
+ offsets[i] = make_pair(u256(slotOffset), byteOffset);
+ solAssert(type->getStorageSize() >= 1, "Invalid storage size.");
+ if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32)
+ byteOffset += type->getStorageBytes();
+ else
+ {
+ slotOffset += type->getStorageSize();
+ byteOffset = 0;
+ }
+ }
+ if (byteOffset > 0)
+ ++slotOffset;
+ if (slotOffset >= bigint(1) << 256)
+ BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage."));
+ m_storageSize = u256(slotOffset);
+ swap(m_offsets, offsets);
+}
+
+pair<u256, unsigned> const* StorageOffsets::getOffset(size_t _index) const
+{
+ if (m_offsets.count(_index))
+ return &m_offsets.at(_index);
+ else
+ return nullptr;
+}
+
+MemberList& MemberList::operator=(MemberList&& _other)
+{
+ m_memberTypes = std::move(_other.m_memberTypes);
+ m_storageOffsets = std::move(_other.m_storageOffsets);
+ return *this;
+}
+
+std::pair<u256, unsigned> const* MemberList::getMemberStorageOffset(string const& _name) const
+{
+ if (!m_storageOffsets)
+ {
+ TypePointers memberTypes;
+ memberTypes.reserve(m_memberTypes.size());
+ for (auto const& nameAndType: m_memberTypes)
+ memberTypes.push_back(nameAndType.second);
+ m_storageOffsets.reset(new StorageOffsets());
+ m_storageOffsets->computeOffsets(memberTypes);
+ }
+ for (size_t index = 0; index < m_memberTypes.size(); ++index)
+ if (m_memberTypes[index].first == _name)
+ return m_storageOffsets->getOffset(index);
+ return nullptr;
+}
+
+u256 const& MemberList::getStorageSize() const
+{
+ // trigger lazy computation
+ getMemberStorageOffset("");
+ return m_storageOffsets->getStorageSize();
+}
+
TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
{
- solAssert(Token::isElementaryTypeName(_typeToken), "Elementary type name expected.");
+ char const* tokenCstr = Token::toString(_typeToken);
+ solAssert(Token::isElementaryTypeName(_typeToken),
+ "Expected an elementary type name but got " + ((tokenCstr) ? std::string(Token::toString(_typeToken)) : ""));
- if (Token::Int <= _typeToken && _typeToken <= Token::Hash256)
+ if (Token::Int <= _typeToken && _typeToken <= Token::Bytes32)
{
int offset = _typeToken - Token::Int;
int bytes = offset % 33;
- if (bytes == 0)
+ if (bytes == 0 && _typeToken != Token::Bytes0)
bytes = 32;
int modifier = offset / 33;
- return make_shared<IntegerType>(bytes * 8,
- modifier == 0 ? IntegerType::Modifier::Signed :
- modifier == 1 ? IntegerType::Modifier::Unsigned :
- IntegerType::Modifier::Hash);
+ switch(modifier)
+ {
+ case 0:
+ return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Signed);
+ case 1:
+ return make_shared<IntegerType>(bytes * 8, IntegerType::Modifier::Unsigned);
+ case 2:
+ return make_shared<FixedBytesType>(bytes);
+ default:
+ solAssert(false, "Unexpected modifier value. Should never happen");
+ return TypePointer();
+ }
}
+ else if (_typeToken == Token::Byte)
+ return make_shared<FixedBytesType>(1);
else if (_typeToken == Token::Address)
return make_shared<IntegerType>(0, IntegerType::Modifier::Address);
else if (_typeToken == Token::Bool)
return make_shared<BoolType>();
- else if (Token::String0 <= _typeToken && _typeToken <= Token::String32)
- return make_shared<StaticStringType>(int(_typeToken) - int(Token::String0));
else if (_typeToken == Token::Bytes)
return make_shared<ArrayType>(ArrayType::Location::Storage);
else
@@ -99,6 +184,8 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length
TypePointer baseType = _baseTypeName.toType();
if (!baseType)
BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name."));
+ if (baseType->getStorageBytes() == 0)
+ BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Illegal base type of storage size zero for array."));
if (_length)
{
if (!_length->getType())
@@ -123,7 +210,7 @@ TypePointer Type::forLiteral(Literal const& _literal)
return make_shared<IntegerConstantType>(_literal);
case Token::StringLiteral:
//@todo put larger strings into dynamic strings
- return StaticStringType::smallestTypeForLiteral(_literal.getValue());
+ return FixedBytesType::smallestTypeForLiteral(_literal.getValue());
default:
return shared_ptr<Type>();
}
@@ -139,7 +226,7 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b)
return TypePointer();
}
-const MemberList Type::EmptyMemberList = MemberList();
+const MemberList Type::EmptyMemberList;
IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier):
m_bits(_bits), m_modifier(_modifier)
@@ -159,8 +246,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
return false;
if (isAddress())
return convertTo.isAddress();
- else if (isHash())
- return convertTo.isHash();
else if (isSigned())
return convertTo.isSigned();
else
@@ -169,14 +254,10 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const
bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- if (_convertTo.getCategory() == Category::String)
- {
- StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo);
- return isHash() && (m_bits == convertTo.getNumBytes() * 8);
- }
return _convertTo.getCategory() == getCategory() ||
- _convertTo.getCategory() == Category::Contract ||
- _convertTo.getCategory() == Category::Enum;
+ _convertTo.getCategory() == Category::Contract ||
+ _convertTo.getCategory() == Category::Enum ||
+ _convertTo.getCategory() == Category::FixedBytes;
}
TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
@@ -187,16 +268,10 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const
// no further unary operators for addresses
else if (isAddress())
return TypePointer();
- // "~" is ok for all other types
- else if (_operator == Token::BitNot)
- return shared_from_this();
- // nothing else for hashes
- else if (isHash())
- return TypePointer();
- // for non-hash integers, we allow +, -, ++ and --
+ // for non-address integers, we allow +, -, ++ and --
else if (_operator == Token::Add || _operator == Token::Sub ||
_operator == Token::Inc || _operator == Token::Dec ||
- _operator == Token::After)
+ _operator == Token::After || _operator == Token::BitNot)
return shared_from_this();
else
return TypePointer();
@@ -214,7 +289,7 @@ string IntegerType::toString() const
{
if (isAddress())
return "address";
- string prefix = isHash() ? "hash" : (isSigned() ? "int" : "uint");
+ string prefix = isSigned() ? "int" : "uint";
return prefix + dev::toString(m_bits);
}
@@ -230,20 +305,18 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe
// All integer types can be compared
if (Token::isCompareOp(_operator))
return commonType;
-
- // Nothing else can be done with addresses, but hashes can receive bit operators
+ // Nothing else can be done with addresses
if (commonType->isAddress())
return TypePointer();
- else if (commonType->isHash() && !Token::isBitOp(_operator))
- return TypePointer();
- else
- return commonType;
+
+ return commonType;
}
-const MemberList IntegerType::AddressMemberList =
- MemberList({{"balance", make_shared<IntegerType >(256)},
- {"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)},
- {"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)}});
+const MemberList IntegerType::AddressMemberList({
+ {"balance", make_shared<IntegerType >(256)},
+ {"call", make_shared<FunctionType>(strings(), strings(), FunctionType::Location::Bare, true)},
+ {"send", make_shared<FunctionType>(strings{"uint"}, strings{}, FunctionType::Location::Send)}
+});
IntegerConstantType::IntegerConstantType(Literal const& _literal)
{
@@ -284,8 +357,17 @@ IntegerConstantType::IntegerConstantType(Literal const& _literal)
bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
- TypePointer integerType = getIntegerType();
- return integerType && integerType->isImplicitlyConvertibleTo(_convertTo);
+ shared_ptr<IntegerType const> integerType = getIntegerType();
+ if (!integerType)
+ return false;
+
+ if (_convertTo.getCategory() == Category::FixedBytes)
+ {
+ FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
+ return convertTo.getNumBytes() * 8 >= integerType->getNumBits();
+ }
+
+ return integerType->isImplicitlyConvertibleTo(_convertTo);
}
bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const
@@ -433,50 +515,67 @@ shared_ptr<IntegerType const> IntegerConstantType::getIntegerType() const
: IntegerType::Modifier::Unsigned);
}
-shared_ptr<StaticStringType> StaticStringType::smallestTypeForLiteral(string const& _literal)
+shared_ptr<FixedBytesType> FixedBytesType::smallestTypeForLiteral(string const& _literal)
{
if (_literal.length() <= 32)
- return make_shared<StaticStringType>(_literal.length());
- return shared_ptr<StaticStringType>();
+ return make_shared<FixedBytesType>(_literal.length());
+ return shared_ptr<FixedBytesType>();
}
-StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes)
+FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes)
{
solAssert(m_bytes >= 0 && m_bytes <= 32,
- "Invalid byte number for static string type: " + dev::toString(m_bytes));
+ "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes));
}
-bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const
+bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const
{
if (_convertTo.getCategory() != getCategory())
return false;
- StaticStringType const& convertTo = dynamic_cast<StaticStringType const&>(_convertTo);
+ FixedBytesType const& convertTo = dynamic_cast<FixedBytesType const&>(_convertTo);
return convertTo.m_bytes >= m_bytes;
}
-bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const
+bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const
{
- if (_convertTo.getCategory() == getCategory())
- return true;
- if (_convertTo.getCategory() == Category::Integer)
- {
- IntegerType const& convertTo = dynamic_cast<IntegerType const&>(_convertTo);
- if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits()))
- return true;
- }
+ return _convertTo.getCategory() == Category::Integer ||
+ _convertTo.getCategory() == Category::Contract ||
+ _convertTo.getCategory() == getCategory();
+}
- return false;
+TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const
+{
+ // "delete" and "~" is okay for FixedBytesType
+ if (_operator == Token::Delete)
+ return make_shared<VoidType>();
+ else if (_operator == Token::BitNot)
+ return shared_from_this();
+
+ return TypePointer();
+}
+
+TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const
+{
+ auto commonType = dynamic_pointer_cast<FixedBytesType const>(Type::commonType(shared_from_this(), _other));
+ if (!commonType)
+ return TypePointer();
+
+ // FixedBytes can be compared and have bitwise operators applied to them
+ if (Token::isCompareOp(_operator) || Token::isBitOp(_operator))
+ return commonType;
+
+ return TypePointer();
}
-bool StaticStringType::operator==(Type const& _other) const
+bool FixedBytesType::operator==(Type const& _other) const
{
if (_other.getCategory() != getCategory())
return false;
- StaticStringType const& other = dynamic_cast<StaticStringType const&>(_other);
+ FixedBytesType const& other = dynamic_cast<FixedBytesType const&>(_other);
return other.m_bytes == m_bytes;
}
-u256 StaticStringType::literalValue(const Literal* _literal) const
+u256 FixedBytesType::literalValue(const Literal* _literal) const
{
solAssert(_literal, "");
u256 value = 0;
@@ -603,13 +702,21 @@ u256 ArrayType::getStorageSize() const
{
if (isDynamicallySized())
return 1;
- else
+
+ bigint size;
+ unsigned baseBytes = getBaseType()->getStorageBytes();
+ if (baseBytes == 0)
+ size = 1;
+ else if (baseBytes < 32)
{
- bigint size = bigint(getLength()) * getBaseType()->getStorageSize();
- if (size >= bigint(1) << 256)
- BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage."));
- return max<u256>(1, u256(size));
+ unsigned itemsPerSlot = 32 / baseBytes;
+ size = (bigint(getLength()) + (itemsPerSlot - 1)) / itemsPerSlot;
}
+ else
+ size = bigint(getLength()) * getBaseType()->getStorageSize();
+ if (size >= bigint(1) << 256)
+ BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage."));
+ return max<u256>(1, u256(size));
}
unsigned ArrayType::getSizeOnStack() const
@@ -617,6 +724,9 @@ unsigned ArrayType::getSizeOnStack() const
if (m_location == Location::CallData)
// offset [length] (stack top)
return 1 + (isDynamicallySized() ? 1 : 0);
+ else if (m_location == Location::Storage)
+ // storage_key storage_offset
+ return 2;
else
// offset
return 1;
@@ -632,6 +742,23 @@ string ArrayType::toString() const
return ret + "]";
}
+TypePointer ArrayType::externalType() const
+{
+ if (m_location != Location::CallData)
+ return TypePointer();
+ if (m_isByteArray)
+ return shared_from_this();
+ if (!m_baseType->externalType())
+ return TypePointer();
+ if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized())
+ return TypePointer();
+
+ if (isDynamicallySized())
+ return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType());
+ else
+ return std::make_shared<ArrayType>(Location::CallData, m_baseType->externalType(), m_length);
+}
+
shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location) const
{
auto copy = make_shared<ArrayType>(_location);
@@ -645,7 +772,7 @@ shared_ptr<ArrayType> ArrayType::copyForLocation(ArrayType::Location _location)
return copy;
}
-const MemberList ArrayType::s_arrayTypeMemberList = MemberList({{"length", make_shared<IntegerType>(256)}});
+const MemberList ArrayType::s_arrayTypeMemberList({{"length", make_shared<IntegerType>(256)}});
bool ContractType::operator==(Type const& _other) const
{
@@ -706,6 +833,26 @@ u256 ContractType::getFunctionIdentifier(string const& _functionName) const
return Invalid256;
}
+vector<tuple<VariableDeclaration const*, u256, unsigned>> ContractType::getStateVariables() const
+{
+ vector<VariableDeclaration const*> variables;
+ for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.getLinearizedBaseContracts()))
+ for (ASTPointer<VariableDeclaration> const& variable: contract->getStateVariables())
+ if (!variable->isConstant())
+ variables.push_back(variable.get());
+ TypePointers types;
+ for (auto variable: variables)
+ types.push_back(variable->getType());
+ StorageOffsets offsets;
+ offsets.computeOffsets(types);
+
+ vector<tuple<VariableDeclaration const*, u256, unsigned>> variablesAndOffsets;
+ for (size_t index = 0; index < variables.size(); ++index)
+ if (auto const* offset = offsets.getOffset(index))
+ variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second));
+ return variablesAndOffsets;
+}
+
TypePointer StructType::unaryOperatorResult(Token::Value _operator) const
{
return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer();
@@ -721,12 +868,7 @@ bool StructType::operator==(Type const& _other) const
u256 StructType::getStorageSize() const
{
- bigint size = 0;
- for (pair<string, TypePointer> const& member: getMembers())
- size += member.second->getStorageSize();
- if (size >= bigint(1) << 256)
- BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Struct too large for storage."));
- return max<u256>(1, u256(size));
+ return max<u256>(1, getMembers().getStorageSize());
}
bool StructType::canLiveOutsideStorage() const
@@ -755,17 +897,11 @@ MemberList const& StructType::getMembers() const
return *m_members;
}
-u256 StructType::getStorageOffsetOfMember(string const& _name) const
+pair<u256, unsigned> const& StructType::getStorageOffsetsOfMember(string const& _name) const
{
- //@todo cache member offset?
- u256 offset;
- for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers())
- {
- if (variable->getName() == _name)
- return offset;
- offset += variable->getType()->getStorageSize();
- }
- BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested."));
+ auto const* offsets = getMembers().getMemberStorageOffset(_name);
+ solAssert(offsets, "Storage offset of non-existing member requested.");
+ return *offsets;
}
TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const
@@ -781,6 +917,15 @@ bool EnumType::operator==(Type const& _other) const
return other.m_enum == m_enum;
}
+unsigned EnumType::getStorageBytes() const
+{
+ size_t elements = m_enum.getMembers().size();
+ if (elements <= 1)
+ return 1;
+ else
+ return dev::bytesRequired(elements - 1);
+}
+
string EnumType::toString() const
{
return string("enum ") + m_enum.getName();
@@ -925,6 +1070,13 @@ string FunctionType::toString() const
return name + ")";
}
+u256 FunctionType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable function type requested."));
+}
+
unsigned FunctionType::getSizeOnStack() const
{
Location location = m_location;
@@ -946,6 +1098,26 @@ unsigned FunctionType::getSizeOnStack() const
return size;
}
+TypePointer FunctionType::externalType() const
+{
+ TypePointers paramTypes;
+ TypePointers retParamTypes;
+
+ for (auto type: m_parameterTypes)
+ {
+ if (!type->externalType())
+ return TypePointer();
+ paramTypes.push_back(type->externalType());
+ }
+ for (auto type: m_returnParameterTypes)
+ {
+ if (!type->externalType())
+ return TypePointer();
+ retParamTypes.push_back(type->externalType());
+ }
+ return make_shared<FunctionType>(paramTypes, retParamTypes, m_location, m_arbitraryParameters);
+}
+
MemberList const& FunctionType::getMembers() const
{
switch (m_location)
@@ -975,7 +1147,7 @@ MemberList const& FunctionType::getMembers() const
}
}
-string FunctionType::getCanonicalSignature(std::string const& _name) const
+string FunctionType::externalSignature(std::string const& _name) const
{
std::string funcName = _name;
if (_name == "")
@@ -985,8 +1157,12 @@ string FunctionType::getCanonicalSignature(std::string const& _name) const
}
string ret = funcName + "(";
- for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it)
- ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ",");
+ TypePointers externalParameterTypes = dynamic_cast<FunctionType const&>(*externalType()).getParameterTypes();
+ for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it)
+ {
+ solAssert(!!(*it), "Parameter should have external type");
+ ret += (*it)->toString() + (it + 1 == externalParameterTypes.cend() ? "" : ",");
+ }
return ret + ")";
}
@@ -1047,6 +1223,13 @@ string MappingType::toString() const
return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")";
}
+u256 VoidType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable void type requested."));
+}
+
bool TypeType::operator==(Type const& _other) const
{
if (_other.getCategory() != getCategory())
@@ -1055,6 +1238,13 @@ bool TypeType::operator==(Type const& _other) const
return *getActualType() == *other.getActualType();
}
+u256 TypeType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable type type requested."));
+}
+
MemberList const& TypeType::getMembers() const
{
// We need to lazy-initialize it because of recursive references.
@@ -1092,6 +1282,13 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier)
swap(params, m_parameterTypes);
}
+u256 ModifierType::getStorageSize() const
+{
+ BOOST_THROW_EXCEPTION(
+ InternalCompilerError()
+ << errinfo_comment("Storage size of non-storable type type requested."));
+}
+
bool ModifierType::operator==(Type const& _other) const
{
if (_other.getCategory() != getCategory())
@@ -1122,22 +1319,29 @@ MagicType::MagicType(MagicType::Kind _kind):
switch (m_kind)
{
case Kind::Block:
- m_members = MemberList({{"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
- {"timestamp", make_shared<IntegerType>(256)},
- {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"hash"}, FunctionType::Location::BlockHash)},
- {"difficulty", make_shared<IntegerType>(256)},
- {"number", make_shared<IntegerType>(256)},
- {"gaslimit", make_shared<IntegerType>(256)}});
+ m_members = move(MemberList({
+ {"coinbase", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
+ {"timestamp", make_shared<IntegerType>(256)},
+ {"blockhash", make_shared<FunctionType>(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)},
+ {"difficulty", make_shared<IntegerType>(256)},
+ {"number", make_shared<IntegerType>(256)},
+ {"gaslimit", make_shared<IntegerType>(256)}
+ }));
break;
case Kind::Message:
- m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
- {"gas", make_shared<IntegerType>(256)},
- {"value", make_shared<IntegerType>(256)},
- {"data", make_shared<ArrayType>(ArrayType::Location::CallData)}});
+ m_members = move(MemberList({
+ {"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
+ {"gas", make_shared<IntegerType>(256)},
+ {"value", make_shared<IntegerType>(256)},
+ {"data", make_shared<ArrayType>(ArrayType::Location::CallData)},
+ {"sig", make_shared<FixedBytesType>(4)}
+ }));
break;
case Kind::Transaction:
- m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
- {"gasprice", make_shared<IntegerType>(256)}});
+ m_members = move(MemberList({
+ {"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
+ {"gasprice", make_shared<IntegerType>(256)}
+ }));
break;
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic."));
diff --git a/Types.h b/Types.h
index b19f7540..5fa09f69 100644
--- a/Types.h
+++ b/Types.h
@@ -43,6 +43,26 @@ using TypePointer = std::shared_ptr<Type const>;
using FunctionTypePointer = std::shared_ptr<FunctionType const>;
using TypePointers = std::vector<TypePointer>;
+
+/**
+ * Helper class to compute storage offsets of members of structs and contracts.
+ */
+class StorageOffsets
+{
+public:
+ /// Resets the StorageOffsets objects and determines the position in storage for each
+ /// of the elements of @a _types.
+ void computeOffsets(TypePointers const& _types);
+ /// @returns the offset of the given member, might be null if the member is not part of storage.
+ std::pair<u256, unsigned> const* getOffset(size_t _index) const;
+ /// @returns the total number of slots occupied by all members.
+ u256 const& getStorageSize() const { return m_storageSize; }
+
+private:
+ u256 m_storageSize;
+ std::map<size_t, std::pair<u256, unsigned>> m_offsets;
+};
+
/**
* List of members of a type.
*/
@@ -53,6 +73,7 @@ public:
MemberList() {}
explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {}
+ MemberList& operator=(MemberList&& _other);
TypePointer getMemberType(std::string const& _name) const
{
for (auto const& it: m_memberTypes)
@@ -60,12 +81,18 @@ public:
return it.second;
return TypePointer();
}
+ /// @returns the offset of the given member in storage slots and bytes inside a slot or
+ /// a nullptr if the member is not part of storage.
+ std::pair<u256, unsigned> const* getMemberStorageOffset(std::string const& _name) const;
+ /// @returns the number of storage slots occupied by the members.
+ u256 const& getStorageSize() const;
MemberMap::const_iterator begin() const { return m_memberTypes.begin(); }
MemberMap::const_iterator end() const { return m_memberTypes.end(); }
private:
MemberMap m_memberTypes;
+ mutable std::unique_ptr<StorageOffsets> m_storageOffsets;
};
/**
@@ -77,12 +104,12 @@ public:
enum class Category
{
Integer, IntegerConstant, Bool, Real, Array,
- String, Contract, Struct, Function, OverloadedFunctions, Enum,
+ FixedBytes, Contract, Struct, Function, OverloadedFunctions, Enum,
Mapping, Void, TypeType, Modifier, Magic
};
- ///@{
- ///@name Factory functions
+ /// @{
+ /// @name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(Token::Value _typeToken);
static TypePointer fromElementaryTypeName(std::string const& _name);
@@ -97,6 +124,8 @@ public:
/// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise
static TypePointer commonType(TypePointer const& _a, TypePointer const& _b);
+ /// Calculates the
+
virtual Category getCategory() const = 0;
virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const
@@ -126,9 +155,15 @@ public:
unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); }
/// @returns true if the type is dynamically encoded in calldata
virtual bool isDynamicallySized() const { return false; }
- /// @returns number of bytes required to hold this value in storage.
+ /// @returns the number of storage slots required to hold this value in storage.
/// For dynamically "allocated" types, it returns the size of the statically allocated head,
virtual u256 getStorageSize() const { return 1; }
+ /// Multiple small types can be packed into a single storage slot. If such a packing is possible
+ /// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if
+ /// it does not fit.
+ /// In order to avoid computation at runtime of whether such moving is necessary, structs and
+ /// array data (not each element) always start a new slot.
+ virtual unsigned getStorageBytes() const { return 32; }
/// Returns true if the type can be stored in storage.
virtual bool canBeStored() const { return true; }
/// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping.
@@ -152,20 +187,24 @@ public:
"for type without literals."));
}
+ /// @returns a type suitable for outside of Solidity, i.e. for contract types it returns address.
+ /// If there is no such type, returns an empty shared pointer.
+ virtual TypePointer externalType() const { return TypePointer(); }
+
protected:
/// Convenience object used when returning an empty member list.
static const MemberList EmptyMemberList;
};
/**
- * Any kind of integer type including hash and address.
+ * Any kind of integer type (signed, unsigned, address).
*/
class IntegerType: public Type
{
public:
enum class Modifier
{
- Unsigned, Signed, Hash, Address
+ Unsigned, Signed, Address
};
virtual Category getCategory() const override { return Category::Integer; }
@@ -179,14 +218,16 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; }
+ virtual unsigned getStorageBytes() const override { return m_bits / 8; }
virtual bool isValueType() const override { return true; }
- virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; }
+ virtual MemberList const& getMembers() const override { return isAddress() ? AddressMemberList : EmptyMemberList; }
virtual std::string toString() const override;
+ virtual TypePointer externalType() const override { return shared_from_this(); }
+
int getNumBits() const { return m_bits; }
- bool isHash() const { return m_modifier == Modifier::Hash || m_modifier == Modifier::Address; }
bool isAddress() const { return m_modifier == Modifier::Address; }
bool isSigned() const { return m_modifier == Modifier::Signed; }
@@ -232,28 +273,32 @@ private:
};
/**
- * String type with fixed length, up to 32 bytes.
+ * Bytes type with fixed length of up to 32 bytes.
*/
-class StaticStringType: public Type
+class FixedBytesType: public Type
{
public:
- virtual Category getCategory() const override { return Category::String; }
+ virtual Category getCategory() const override { return Category::FixedBytes; }
- /// @returns the smallest string type for the given literal or an empty pointer
+ /// @returns the smallest bytes type for the given literal or an empty pointer
/// if no type fits.
- static std::shared_ptr<StaticStringType> smallestTypeForLiteral(std::string const& _literal);
+ static std::shared_ptr<FixedBytesType> smallestTypeForLiteral(std::string const& _literal);
- explicit StaticStringType(int _bytes);
+ explicit FixedBytesType(int _bytes);
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) 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;
virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; }
+ virtual unsigned getStorageBytes() const override { return m_bytes; }
virtual bool isValueType() const override { return true; }
- virtual std::string toString() const override { return "string" + dev::toString(m_bytes); }
+ virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); }
virtual u256 literalValue(Literal const* _literal) const override;
+ virtual TypePointer externalType() const override { return shared_from_this(); }
int getNumBytes() const { return m_bytes; }
@@ -273,16 +318,21 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
- virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; }
+ virtual unsigned getCalldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; }
+ virtual unsigned getStorageBytes() const override { return 1; }
virtual bool isValueType() const override { return true; }
virtual std::string toString() const override { return "bool"; }
virtual u256 literalValue(Literal const* _literal) const override;
+ virtual TypePointer externalType() const override { return shared_from_this(); }
};
/**
* The type of an array. The flavours are byte array (bytes), statically- (<type>[<length>])
* and dynamically-sized array (<type>[]).
+ * In storage, all arrays are packed tightly (as long as more than one elementary type fits in
+ * one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and
+ * thus start on their own slot.
*/
class ArrayType: public Type
{
@@ -293,13 +343,22 @@ public:
/// Constructor for a byte array ("bytes")
explicit ArrayType(Location _location):
- m_location(_location), m_isByteArray(true), m_baseType(std::make_shared<IntegerType>(8)) {}
+ m_location(_location),
+ m_isByteArray(true),
+ m_baseType(std::make_shared<FixedBytesType>(8))
+ {}
/// Constructor for a dynamically sized array type ("type[]")
ArrayType(Location _location, const TypePointer &_baseType):
- m_location(_location), m_baseType(_baseType) {}
+ m_location(_location),
+ m_baseType(_baseType)
+ {}
/// Constructor for a fixed-size array type ("type[20]")
ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length):
- m_location(_location), m_baseType(_baseType), m_hasDynamicLength(false), m_length(_length) {}
+ m_location(_location),
+ m_baseType(_baseType),
+ m_hasDynamicLength(false),
+ m_length(_length)
+ {}
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
@@ -310,6 +369,7 @@ public:
virtual unsigned getSizeOnStack() const override;
virtual std::string toString() const override;
virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; }
+ virtual TypePointer externalType() const override;
Location getLocation() const { return m_location; }
bool isByteArray() const { return m_isByteArray; }
@@ -344,10 +404,12 @@ public:
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(Type const& _other) const override;
+ virtual unsigned getStorageBytes() const override { return 20; }
virtual bool isValueType() const override { return true; }
virtual std::string toString() const override;
virtual MemberList const& getMembers() const override;
+ virtual TypePointer externalType() const override { return std::make_shared<IntegerType>(160, IntegerType::Modifier::Address); }
bool isSuper() const { return m_super; }
ContractDefinition const& getContractDefinition() const { return m_contract; }
@@ -360,6 +422,10 @@ public:
/// not exist.
u256 getFunctionIdentifier(std::string const& _functionName) const;
+ /// @returns a list of all state variables (including inherited) of the contract and their
+ /// offsets in storage.
+ std::vector<std::tuple<VariableDeclaration const*, u256, unsigned>> getStateVariables() const;
+
private:
ContractDefinition const& m_contract;
/// If true, it is the "super" type of the current contract, i.e. it contains only inherited
@@ -383,12 +449,12 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override;
- virtual unsigned getSizeOnStack() const override { return 1; /*@todo*/ }
+ virtual unsigned getSizeOnStack() const override { return 2; }
virtual std::string toString() const override;
virtual MemberList const& getMembers() const override;
- u256 getStorageOffsetOfMember(std::string const& _name) const;
+ std::pair<u256, unsigned> const& getStorageOffsetsOfMember(std::string const& _name) const;
private:
StructDefinition const& m_struct;
@@ -407,10 +473,12 @@ public:
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(Type const& _other) const override;
virtual unsigned getSizeOnStack() const override { return 1; }
+ virtual unsigned getStorageBytes() const override;
virtual std::string toString() const override;
virtual bool isValueType() const override { return true; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
+ virtual TypePointer externalType() const override { return std::make_shared<IntegerType>(8 * int(getStorageBytes())); }
EnumDefinition const& getEnumDefinition() const { return m_enum; }
/// @returns the value that the string has in the Enum
@@ -443,6 +511,11 @@ public:
Bare };
virtual Category getCategory() const override { return Category::Function; }
+
+ /// @returns TypePointer of a new FunctionType object. All input/return parameters are an appropriate external types of input/return parameters of current function.
+ /// Returns an empty shared pointer if one of the input/return parameters does not have an externaltype.
+ virtual TypePointer externalType() const override;
+
explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true);
explicit FunctionType(VariableDeclaration const& _varDecl);
explicit FunctionType(EventDefinition const& _event);
@@ -476,16 +549,16 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override;
virtual bool canBeStored() const override { return false; }
- virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); }
+ virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override;
virtual MemberList const& getMembers() const override;
Location const& getLocation() const { return m_location; }
- /// @returns the canonical signature of this function type given the function name
+ /// @returns the external signature of this function type given the function name
/// If @a _name is not provided (empty string) then the @c m_declaration member of the
/// function type is used
- std::string getCanonicalSignature(std::string const& _name = "") const;
+ std::string externalSignature(std::string const& _name = "") const;
Declaration const& getDeclaration() const
{
solAssert(m_declaration, "Requested declaration from a FunctionType that has none");
@@ -515,7 +588,7 @@ private:
std::vector<std::string> m_parameterNames;
std::vector<std::string> m_returnParameterNames;
Location const m_location;
- /// true iff the function takes an arbitrary number of arguments of arbitrary types
+ /// true if the function takes an arbitrary number of arguments of arbitrary types
bool const m_arbitraryParameters = false;
bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack
bool const m_valueSet = false; ///< true iff the value to be sent is on the stack
@@ -540,6 +613,7 @@ private:
/**
* The type of a mapping, there is one distinct type per key/value type pair.
+ * Mappings always occupy their own storage slot, but do not actually use it.
*/
class MappingType: public Type
{
@@ -550,6 +624,7 @@ public:
virtual bool operator==(Type const& _other) const override;
virtual std::string toString() const override;
+ virtual unsigned getSizeOnStack() const override { return 2; }
virtual bool canLiveOutsideStorage() const override { return false; }
TypePointer const& getKeyType() const { return m_keyType; }
@@ -573,7 +648,7 @@ public:
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual std::string toString() const override { return "void"; }
virtual bool canBeStored() const override { return false; }
- virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); }
+ virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; }
};
@@ -593,7 +668,7 @@ public:
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual bool operator==(Type const& _other) const override;
virtual bool canBeStored() const override { return false; }
- virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); }
+ virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; }
virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; }
@@ -619,7 +694,7 @@ public:
virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); }
virtual bool canBeStored() const override { return false; }
- virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); }
+ virtual u256 getStorageSize() const override;
virtual bool canLiveOutsideStorage() const override { return false; }
virtual unsigned getSizeOnStack() const override { return 0; }
virtual bool operator==(Type const& _other) const override;
diff --git a/Utils.h b/Utils.h
index 1411f66b..05c5fa6f 100644
--- a/Utils.h
+++ b/Utils.h
@@ -22,34 +22,9 @@
#pragma once
-#include <string>
-#include <libsolidity/Exceptions.h>
-
-namespace dev
-{
-namespace solidity
-{
+#include <libdevcore/Assertions.h>
/// Assertion that throws an InternalCompilerError containing the given description if it is not met.
#define solAssert(CONDITION, DESCRIPTION) \
- ::dev::solidity::solAssertAux(CONDITION, DESCRIPTION, __LINE__, __FILE__, ETH_FUNC)
-
-inline void solAssertAux(bool _condition, std::string const& _errorDescription, unsigned _line,
- char const* _file, char const* _function)
-{
- if (!_condition)
- ::boost::throw_exception( InternalCompilerError()
- << errinfo_comment(_errorDescription)
- << ::boost::throw_function(_function)
- << ::boost::throw_file(_file)
- << ::boost::throw_line(_line));
-}
-
-inline void solAssertAux(void const* _pointer, std::string const& _errorDescription, unsigned _line,
- char const* _file, char const* _function)
-{
- solAssertAux(_pointer != nullptr, _errorDescription, _line, _file, _function);
-}
+ assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION)
-}
-}