Makes array length access read-only.

This commit is contained in:
Erik Kundt 2019-09-18 14:58:20 +02:00
parent 94272d44aa
commit ecaed1030f
9 changed files with 13 additions and 143 deletions

View File

@ -14,6 +14,7 @@ Breaking changes:
* Syntax: Abstract contracts need to be marked explicitly as abstract by using the ``abstract`` keyword. * Syntax: Abstract contracts need to be marked explicitly as abstract by using the ``abstract`` keyword.
* Inline Assembly: Only strict inline assembly is allowed. * Inline Assembly: Only strict inline assembly is allowed.
* Inline Assembly: Variable declarations cannot shadow declarations outside the assembly block. * Inline Assembly: Variable declarations cannot shadow declarations outside the assembly block.
* Syntax: ``length`` member of arrays is now always read-only, even for storage arrays.
* Type checker: Resulting type of exponentiation is equal to the type of the base. Also allow signed types for the base. * Type checker: Resulting type of exponentiation is equal to the type of the base. Also allow signed types for the base.
* Natspec JSON Interface: Properly support multiple ``@return`` statements in ``@dev`` documentation and enforce named return parameters to be mentioned documentation. * Natspec JSON Interface: Properly support multiple ``@return`` statements in ``@dev`` documentation and enforce named return parameters to be mentioned documentation.
* Source mappings: Add "modifier depth" as a fifth field in the source mappings. * Source mappings: Add "modifier depth" as a fifth field in the source mappings.

View File

@ -2398,14 +2398,7 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
if (auto const* structType = dynamic_cast<StructType const*>(exprType)) if (auto const* structType = dynamic_cast<StructType const*>(exprType))
annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData); annotation.isLValue = !structType->dataStoredIn(DataLocation::CallData);
else if (exprType->category() == Type::Category::Array) else if (exprType->category() == Type::Category::Array)
{ annotation.isLValue = false;
auto const& arrayType(dynamic_cast<ArrayType const&>(*exprType));
annotation.isLValue = (
memberName == "length" &&
arrayType.location() == DataLocation::Storage &&
arrayType.isDynamicallySized()
);
}
else if (exprType->category() == Type::Category::FixedBytes) else if (exprType->category() == Type::Category::FixedBytes)
annotation.isLValue = false; annotation.isLValue = false;
else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType)) else if (TypeType const* typeType = dynamic_cast<decltype(typeType)>(exprType))
@ -2862,20 +2855,9 @@ void TypeChecker::requireLValue(Expression const& _expression)
if (structType->dataStoredIn(DataLocation::CallData)) if (structType->dataStoredIn(DataLocation::CallData))
return "Calldata structs are read-only."; return "Calldata structs are read-only.";
} }
else if (auto arrayType = dynamic_cast<ArrayType const*>(type(memberAccess->expression()))) else if (dynamic_cast<ArrayType const*>(type(memberAccess->expression())))
if ( if (memberAccess->memberName() == "length")
memberAccess->memberName() == "length" || return "Member \"length\" is read-only and cannot be used to resize arrays.";
memberAccess->memberName() == "push"
)
switch (arrayType->location())
{
case DataLocation::Memory:
return "Memory arrays cannot be resized.";
case DataLocation::CallData:
return "Calldata arrays cannot be resized.";
case DataLocation::Storage:
break;
}
} }
if (auto identifier = dynamic_cast<Identifier const*>(&_expression)) if (auto identifier = dynamic_cast<Identifier const*>(&_expression))

View File

@ -381,7 +381,7 @@ void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
{ {
auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type); auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage)) if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage))
mutability = writes ? StateMutability::NonPayable : StateMutability::View; mutability = StateMutability::View;
break; break;
} }
default: default:

View File

@ -1565,7 +1565,8 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
m_context << Instruction::SWAP1 << Instruction::POP; m_context << Instruction::SWAP1 << Instruction::POP;
break; break;
case DataLocation::Storage: case DataLocation::Storage:
setLValue<StorageArrayLength>(_memberAccess, type); ArrayUtils(m_context).retrieveLength(type);
m_context << Instruction::SWAP1 << Instruction::POP;
break; break;
case DataLocation::Memory: case DataLocation::Memory:
m_context << Instruction::MLOAD; m_context << Instruction::MLOAD;

View File

@ -478,36 +478,6 @@ void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeRefer
m_context << Instruction::SWAP1 << Instruction::SSTORE; m_context << Instruction::SWAP1 << Instruction::SSTORE;
} }
StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType):
LValue(_compilerContext, _arrayType.memberType("length")),
m_arrayType(_arrayType)
{
solAssert(m_arrayType.isDynamicallySized(), "");
}
void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const
{
ArrayUtils(m_context).retrieveLength(m_arrayType);
if (_remove)
m_context << Instruction::SWAP1 << Instruction::POP;
}
void StorageArrayLength::storeValue(Type const&, SourceLocation const&, bool _move) const
{
if (_move)
m_context << Instruction::SWAP1;
else
m_context << Instruction::DUP2;
ArrayUtils(m_context).resizeDynamicArray(m_arrayType);
}
void StorageArrayLength::setToZero(SourceLocation const&, bool _removeReference) const
{
solAssert(_removeReference, "");
ArrayUtils(m_context).clearDynamicArray(m_arrayType);
}
TupleObject::TupleObject( TupleObject::TupleObject(
CompilerContext& _compilerContext, CompilerContext& _compilerContext,
std::vector<std::unique_ptr<LValue>>&& _lvalues std::vector<std::unique_ptr<LValue>>&& _lvalues

View File

@ -171,31 +171,6 @@ public:
) const override; ) const override;
}; };
/**
* Reference to the "length" member of a dynamically-sized array. This is an LValue with special
* semantics since assignments to it might reduce its length and thus arrays members have to be
* deleted.
*/
class StorageArrayLength: public LValue
{
public:
/// Constructs the LValue, assumes that the reference to the array head is already on the stack.
StorageArrayLength(CompilerContext& _compilerContext, ArrayType const& _arrayType);
void retrieveValue(langutil::SourceLocation const& _location, bool _remove = false) const override;
virtual void storeValue(
Type const& _sourceType,
langutil::SourceLocation const& _location = {},
bool _move = false
) const override;
virtual void setToZero(
langutil::SourceLocation const& _location = {},
bool _removeReference = true
) const override;
private:
ArrayType const& m_arrayType;
};
/** /**
* Tuple object that can itself hold several LValues. * Tuple object that can itself hold several LValues.
*/ */

View File

@ -800,14 +800,12 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
//m_context << Instruction::SWAP1 << Instruction::POP; //m_context << Instruction::SWAP1 << Instruction::POP;
break; break;
case DataLocation::Storage: case DataLocation::Storage:
setLValue(_memberAccess, make_unique<IRStorageArrayLength>( {
m_context.utils(), string slot = m_context.variable(_memberAccess.expression());
m_context.variable(_memberAccess.expression()), defineExpression(_memberAccess) <<
*_memberAccess.annotation().type, m_utils.arrayLengthFunction(type) + "(" + slot + ")\n";
type
));
break; break;
}
case DataLocation::Memory: case DataLocation::Memory:
defineExpression(_memberAccess) << defineExpression(_memberAccess) <<
"mload(" << "mload(" <<

View File

@ -148,39 +148,6 @@ string IRStorageItem::setToZero() const
")\n"; ")\n";
} }
IRStorageArrayLength::IRStorageArrayLength(
YulUtilFunctions _utils,
string _slot,
Type const& _type,
ArrayType const& _arrayType
):
IRLValue(std::move(_utils), &_type), m_arrayType(_arrayType), m_slot(move(_slot))
{
solAssert(*m_type == *TypeProvider::uint256(), "Must be uint256!");
}
string IRStorageArrayLength::retrieveValue() const
{
return m_utils.arrayLengthFunction(m_arrayType) + "(" + m_slot + ")";
}
string IRStorageArrayLength::storeValue(std::string const& _value, Type const& _type) const
{
solAssert(_type == *m_type, "Different type, but might not be an error.");
return m_utils.resizeDynamicArrayFunction(m_arrayType) +
"(" +
m_slot +
", " +
_value +
")\n";
}
string IRStorageArrayLength::setToZero() const
{
return storeValue("0", *TypeProvider::uint256());
}
IRMemoryItem::IRMemoryItem( IRMemoryItem::IRMemoryItem(
YulUtilFunctions _utils, YulUtilFunctions _utils,
std::string _address, std::string _address,

View File

@ -110,30 +110,6 @@ private:
boost::variant<std::string, unsigned> const m_offset; boost::variant<std::string, unsigned> const m_offset;
}; };
/**
* Reference to the "length" member of a dynamically-sized storage array. This is an LValue with special
* semantics since assignments to it might reduce its length and thus the array's members have to be
* deleted.
*/
class IRStorageArrayLength: public IRLValue
{
public:
IRStorageArrayLength(
YulUtilFunctions _utils,
std::string _slot,
Type const& _type,
ArrayType const& _arrayType
);
std::string retrieveValue() const override;
std::string storeValue(std::string const& _value, Type const& _type) const override;
std::string setToZero() const override;
private:
ArrayType const& m_arrayType;
std::string const m_slot;
};
class IRMemoryItem: public IRLValue class IRMemoryItem: public IRLValue
{ {
public: public: