From 754c804d191ad8f05d886566e599a82efbd38d8e Mon Sep 17 00:00:00 2001 From: Christian Date: Sun, 22 Feb 2015 19:15:41 +0100 Subject: [PATCH 1/3] Implementation of index access. --- CompilerContext.cpp | 7 +++- ExpressionCompiler.cpp | 74 ++++++++++++++++++++++++++++-------------- Types.cpp | 19 +++++++++-- Types.h | 1 + 4 files changed, 74 insertions(+), 27 deletions(-) diff --git a/CompilerContext.cpp b/CompilerContext.cpp index 01a71d7c9..8d32a1a53 100644 --- a/CompilerContext.cpp +++ b/CompilerContext.cpp @@ -40,7 +40,12 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) { m_stateVariables[&_declaration] = m_stateVariablesSize; - m_stateVariablesSize += _declaration.getType()->getStorageSize(); + 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); } void CompilerContext::startFunction(Declaration const& _function) diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index 94f65b93f..dcd411c3e 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -534,19 +534,24 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { solAssert(member == "length", "Illegal array member."); auto const& type = dynamic_cast(*_memberAccess.getExpression().getType()); - solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); - switch (type.getLocation()) + if (!type.isDynamicallySized()) { - case ArrayType::Location::CallData: - m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; - break; - case ArrayType::Location::Storage: - m_context << eth::Instruction::SLOAD; - break; - default: - solAssert(false, "Unsupported array location."); - break; + CompilerUtils(m_context).popStackElement(type); + m_context << type.getLength(); } + else + switch (type.getLocation()) + { + case ArrayType::Location::CallData: + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + break; + case ArrayType::Location::Storage: + m_context << eth::Instruction::SLOAD; + break; + default: + solAssert(false, "Unsupported array location."); + break; + } break; } default: @@ -559,19 +564,40 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) _indexAccess.getBaseExpression().accept(*this); Type const& baseType = *_indexAccess.getBaseExpression().getType(); - solAssert(baseType.getCategory() == Type::Category::Mapping, ""); - Type const& keyType = *dynamic_cast(baseType).getKeyType(); - m_context << u256(0); - 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_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); - m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); + if (baseType.getCategory() == Type::Category::Mapping) + { + Type const& keyType = *dynamic_cast(baseType).getKeyType(); + m_context << u256(0); + 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_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); + m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); + } + else if (baseType.getCategory() == Type::Category::Array) + { + ArrayType const& arrayType = dynamic_cast(baseType); + solAssert(arrayType.getLocation() == ArrayType::Location::Storage, + "TODO: Index acces only implemented for storage arrays."); + solAssert(!arrayType.isDynamicallySized(), + "TODO: Index acces only implemented for fixed-size arrays."); + solAssert(!arrayType.isByteArray(), + "TODO: Index acces not implemented for byte arrays."); + solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); + // TODO: for dynamically-sized arrays, update the length for each write + // TODO: do we want to check the index? + _indexAccess.getIndexExpression()->accept(*this); + m_context << arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL + << eth::Instruction::ADD; + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); + m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); + } + else + solAssert(false, "Index access only allowed for mappings or arrays."); return false; } diff --git a/Types.cpp b/Types.cpp index c0be0d93e..adcd2e542 100644 --- a/Types.cpp +++ b/Types.cpp @@ -555,6 +555,19 @@ bool ArrayType::operator==(Type const& _other) const return other.m_location == m_location; } +u256 ArrayType::getStorageSize() const +{ + if (isDynamicallySized()) + return 1; + else + { + bigint size = bigint(getLength()) * getBaseType()->getStorageSize(); + if (size >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage.")); + return max(1, u256(size)); + } +} + unsigned ArrayType::getSizeOnStack() const { if (m_location == Location::CallData) @@ -665,10 +678,12 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { - u256 size = 0; + bigint size = 0; for (pair const& member: getMembers()) size += member.second->getStorageSize(); - return max(1, size); + if (size >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Struct too large for storage.")); + return max(1, u256(size)); } bool StructType::canLiveOutsideStorage() const diff --git a/Types.h b/Types.h index 0d24b7221..9961f03a3 100644 --- a/Types.h +++ b/Types.h @@ -303,6 +303,7 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(const Type& _other) const override; virtual bool isDynamicallySized() const { return m_hasDynamicLength; } + virtual u256 getStorageSize() const override; virtual unsigned getSizeOnStack() const override; virtual std::string toString() const override; virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; } From 5d2323c91486cfcab9322b01d52ba35525e06272 Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 23 Feb 2015 01:31:05 +0100 Subject: [PATCH 2/3] Index and length access for dynamic arrays. --- AST.cpp | 12 +++++++++++- ExpressionCompiler.cpp | 18 ++++++++---------- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/AST.cpp b/AST.cpp index 179461152..c37e8c374 100644 --- a/AST.cpp +++ b/AST.cpp @@ -603,7 +603,17 @@ void MemberAccess::checkTypeRequirements() if (!m_type) BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found or not " "visible in " + type.toString())); - m_isLValue = (type.getCategory() == Type::Category::Struct); + // This should probably move somewhere else. + if (type.getCategory() == Type::Category::Struct) + m_isLValue = true; + else if (type.getCategory() == Type::Category::Array) + { + auto const& arrayType(dynamic_cast(type)); + m_isLValue = (*m_memberName == "length" && + arrayType.getLocation() != ArrayType::Location::CallData && arrayType.isDynamicallySized()); + } + else + m_isLValue = false; } void IndexAccess::checkTypeRequirements() diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index dcd411c3e..a54915bc7 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -546,7 +546,8 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; break; case ArrayType::Location::Storage: - m_context << eth::Instruction::SLOAD; + m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _memberAccess.getType()); + m_currentLValue.retrieveValueIfLValueNotRequested(_memberAccess); break; default: solAssert(false, "Unsupported array location."); @@ -583,13 +584,10 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) ArrayType const& arrayType = dynamic_cast(baseType); solAssert(arrayType.getLocation() == ArrayType::Location::Storage, "TODO: Index acces only implemented for storage arrays."); - solAssert(!arrayType.isDynamicallySized(), - "TODO: Index acces only implemented for fixed-size arrays."); - solAssert(!arrayType.isByteArray(), - "TODO: Index acces not implemented for byte arrays."); + solAssert(!arrayType.isByteArray(), "TODO: Index acces not implemented for byte arrays."); solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); - // TODO: for dynamically-sized arrays, update the length for each write - // TODO: do we want to check the index? + if (arrayType.isDynamicallySized()) + CompilerUtils(m_context).computeHashStatic(); _indexAccess.getIndexExpression()->accept(*this); m_context << arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL << eth::Instruction::ADD; @@ -1075,7 +1073,7 @@ void ExpressionCompiler::LValue::retrieveValue(Location const& _location, bool _ { case LValueType::Stack: { - unsigned stackPos = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); + unsigned stackPos = m_context->baseToCurrentStackOffset(m_baseStackOffset); if (stackPos >= 15) //@todo correct this by fetching earlier or moving to memory BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); @@ -1124,7 +1122,7 @@ void ExpressionCompiler::LValue::storeValue(Type const& _sourceType, Location co { case LValueType::Stack: { - unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)) - m_size + 1; + unsigned stackDiff = m_context->baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1; if (stackDiff > 16) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); @@ -1227,7 +1225,7 @@ void ExpressionCompiler::LValue::setToZero(Location const& _location) const { case LValueType::Stack: { - unsigned stackDiff = m_context->baseToCurrentStackOffset(unsigned(m_baseStackOffset)); + unsigned stackDiff = m_context->baseToCurrentStackOffset(m_baseStackOffset); if (stackDiff > 16) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); From 820ed2dfe17d93a586ba1519333bbe79cc6b9a9c Mon Sep 17 00:00:00 2001 From: Christian Date: Mon, 23 Feb 2015 18:21:17 +0100 Subject: [PATCH 3/3] Out-of-bounds checking. --- ExpressionCompiler.cpp | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/ExpressionCompiler.cpp b/ExpressionCompiler.cpp index a54915bc7..183864ec7 100644 --- a/ExpressionCompiler.cpp +++ b/ExpressionCompiler.cpp @@ -586,11 +586,29 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) "TODO: Index acces only implemented for storage arrays."); solAssert(!arrayType.isByteArray(), "TODO: Index acces not implemented for byte arrays."); solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); - if (arrayType.isDynamicallySized()) - CompilerUtils(m_context).computeHashStatic(); + _indexAccess.getIndexExpression()->accept(*this); - m_context << arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL - << eth::Instruction::ADD; + // retrieve length + if (arrayType.isDynamicallySized()) + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + else + m_context << arrayType.getLength(); + // stack: + // 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: + m_context << arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; + if (arrayType.isDynamicallySized()) + { + m_context << eth::Instruction::SWAP1; + CompilerUtils(m_context).computeHashStatic(); + } + m_context << eth::Instruction::ADD; m_currentLValue = LValue(m_context, LValue::LValueType::Storage, _indexAccess.getType()); m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess); }