Merge pull request #10896 from ethereum/issue-10870

Fix issue with pop on storage array.
This commit is contained in:
chriseth 2021-02-23 17:49:17 +01:00 committed by GitHub
commit e33c058a60
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 73 additions and 56 deletions

View File

@ -20,6 +20,7 @@ Bugfixes:
AST Changes: AST Changes:
* Support field `documentation` to hold NatSpec comments above each statement. * Support field `documentation` to hold NatSpec comments above each statement.
* Adds `nameLocation` to declarations to represent the exact location of the symbolic name. * Adds `nameLocation` to declarations to represent the exact location of the symbolic name.
* Removed the redundant function type "bytearraypush" - replaced by "arraypush".
### 0.8.1 (2021-01-27) ### 0.8.1 (2021-01-27)

View File

@ -2345,10 +2345,7 @@ bool TypeChecker::visit(FunctionCall const& _functionCall)
*_functionCall.expression().annotation().isPure && *_functionCall.expression().annotation().isPure &&
functionType->isPure(); functionType->isPure();
if ( if (functionType->kind() == FunctionType::Kind::ArrayPush)
functionType->kind() == FunctionType::Kind::ArrayPush ||
functionType->kind() == FunctionType::Kind::ByteArrayPush
)
isLValue = functionType->parameterTypes().empty(); isLValue = functionType->parameterTypes().empty();
break; break;

View File

@ -47,6 +47,7 @@ namespace solidity::frontend
class Type; class Type;
using TypePointer = Type const*; using TypePointer = Type const*;
class ArrayType;
using namespace util; using namespace util;
struct CallGraph; struct CallGraph;

View File

@ -1806,27 +1806,28 @@ MemberList::MemberMap ArrayType::nativeMembers(ASTNode const*) const
members.emplace_back("length", TypeProvider::uint256()); members.emplace_back("length", TypeProvider::uint256());
if (isDynamicallySized() && location() == DataLocation::Storage) if (isDynamicallySized() && location() == DataLocation::Storage)
{ {
Type const* thisAsPointer = TypeProvider::withLocation(this, location(), true);
members.emplace_back("push", TypeProvider::function( members.emplace_back("push", TypeProvider::function(
TypePointers{}, TypePointers{thisAsPointer},
TypePointers{baseType()}, TypePointers{baseType()},
strings{},
strings{string()}, strings{string()},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush strings{string()},
)); FunctionType::Kind::ArrayPush
)->asBoundFunction());
members.emplace_back("push", TypeProvider::function( members.emplace_back("push", TypeProvider::function(
TypePointers{baseType()}, TypePointers{thisAsPointer, baseType()},
TypePointers{}, TypePointers{},
strings{string()}, strings{string(),string()},
strings{}, strings{},
isByteArray() ? FunctionType::Kind::ByteArrayPush : FunctionType::Kind::ArrayPush FunctionType::Kind::ArrayPush
)); )->asBoundFunction());
members.emplace_back("pop", TypeProvider::function( members.emplace_back("pop", TypeProvider::function(
TypePointers{thisAsPointer},
TypePointers{}, TypePointers{},
TypePointers{}, strings{string()},
strings{},
strings{}, strings{},
FunctionType::Kind::ArrayPop FunctionType::Kind::ArrayPop
)); )->asBoundFunction());
} }
} }
return members; return members;
@ -2876,7 +2877,6 @@ string FunctionType::richIdentifier() const
case Kind::MulMod: id += "mulmod"; break; case Kind::MulMod: id += "mulmod"; break;
case Kind::ArrayPush: id += "arraypush"; break; case Kind::ArrayPush: id += "arraypush"; break;
case Kind::ArrayPop: id += "arraypop"; break; case Kind::ArrayPop: id += "arraypop"; break;
case Kind::ByteArrayPush: id += "bytearraypush"; break;
case Kind::ObjectCreation: id += "objectcreation"; break; case Kind::ObjectCreation: id += "objectcreation"; break;
case Kind::Assert: id += "assert"; break; case Kind::Assert: id += "assert"; break;
case Kind::Require: id += "require"; break; case Kind::Require: id += "require"; break;
@ -3083,8 +3083,8 @@ vector<tuple<string, TypePointer>> FunctionType::makeStackItems() const
break; break;
case Kind::ArrayPush: case Kind::ArrayPush:
case Kind::ArrayPop: case Kind::ArrayPop:
case Kind::ByteArrayPush: solAssert(bound(), "");
slots = {make_tuple("slot", TypeProvider::uint256())}; slots = {};
break; break;
default: default:
break; break;
@ -3483,8 +3483,6 @@ TypePointer FunctionType::copyAndSetCallOptions(bool _setGas, bool _setValue, bo
FunctionTypePointer FunctionType::asBoundFunction() const FunctionTypePointer FunctionType::asBoundFunction() const
{ {
solAssert(!m_parameterTypes.empty(), ""); solAssert(!m_parameterTypes.empty(), "");
FunctionDefinition const* fun = dynamic_cast<FunctionDefinition const*>(m_declaration);
solAssert(fun && fun->libraryFunction(), "");
solAssert(!m_gasSet, ""); solAssert(!m_gasSet, "");
solAssert(!m_valueSet, ""); solAssert(!m_valueSet, "");
solAssert(!m_saltSet, ""); solAssert(!m_saltSet, "");

View File

@ -1157,7 +1157,6 @@ public:
MulMod, ///< MULMOD MulMod, ///< MULMOD
ArrayPush, ///< .push() to a dynamically sized array in storage ArrayPush, ///< .push() to a dynamically sized array in storage
ArrayPop, ///< .pop() from a dynamically sized array in storage ArrayPop, ///< .pop() from a dynamically sized array in storage
ByteArrayPush, ///< .push() to a dynamically sized byte array in storage
ObjectCreation, ///< array creation using new ObjectCreation, ///< array creation using new
Assert, ///< assert() Assert, ///< assert()
Require, ///< require() Require, ///< require()

View File

@ -584,8 +584,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
{ {
FunctionType const& function = *functionType; FunctionType const& function = *functionType;
if (function.bound()) if (function.bound())
// Only delegatecall and internal functions can be bound, this might be lifted later. solAssert(
solAssert(function.kind() == FunctionType::Kind::DelegateCall || function.kind() == FunctionType::Kind::Internal, ""); function.kind() == FunctionType::Kind::DelegateCall ||
function.kind() == FunctionType::Kind::Internal ||
function.kind() == FunctionType::Kind::ArrayPush ||
function.kind() == FunctionType::Kind::ArrayPop,
"");
switch (function.kind()) switch (function.kind())
{ {
case FunctionType::Kind::Declaration: case FunctionType::Kind::Declaration:
@ -945,9 +949,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
appendExternalFunctionCall(function, arguments, false); appendExternalFunctionCall(function, arguments, false);
break; break;
} }
case FunctionType::Kind::ByteArrayPush:
case FunctionType::Kind::ArrayPush: case FunctionType::Kind::ArrayPush:
{ {
solAssert(function.bound(), "");
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
if (function.parameterTypes().size() == 0) if (function.parameterTypes().size() == 0)
@ -955,10 +959,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
auto paramType = function.returnParameterTypes().at(0); auto paramType = function.returnParameterTypes().at(0);
solAssert(paramType, ""); solAssert(paramType, "");
ArrayType const* arrayType = ArrayType const* arrayType = dynamic_cast<ArrayType const*>(function.selfType());
function.kind() == FunctionType::Kind::ArrayPush ? solAssert(arrayType, "");
TypeProvider::array(DataLocation::Storage, paramType) :
TypeProvider::bytesStorage();
// stack: ArrayReference // stack: ArrayReference
m_context << u256(1) << Instruction::DUP2; m_context << u256(1) << Instruction::DUP2;
@ -978,10 +980,8 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
solAssert(function.parameterTypes().size() == 1, ""); solAssert(function.parameterTypes().size() == 1, "");
solAssert(!!function.parameterTypes()[0], ""); solAssert(!!function.parameterTypes()[0], "");
TypePointer paramType = function.parameterTypes()[0]; TypePointer paramType = function.parameterTypes()[0];
ArrayType const* arrayType = ArrayType const* arrayType = dynamic_cast<ArrayType const*>(function.selfType());
function.kind() == FunctionType::Kind::ArrayPush ? solAssert(arrayType, "");
TypeProvider::array(DataLocation::Storage, paramType) :
TypeProvider::bytesStorage();
// stack: ArrayReference // stack: ArrayReference
arguments[0]->accept(*this); arguments[0]->accept(*this);
@ -1004,7 +1004,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack());
utils().moveToStackTop(1 + type->sizeOnStack()); utils().moveToStackTop(1 + type->sizeOnStack());
// stack: argValue storageSlot slotOffset // stack: argValue storageSlot slotOffset
if (function.kind() == FunctionType::Kind::ArrayPush) if (!arrayType->isByteArray())
StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true); StorageItem(m_context, *paramType).storeValue(*type, _functionCall.location(), true);
else else
StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true); StorageByteArrayElement(m_context).storeValue(*type, _functionCall.location(), true);
@ -1014,14 +1014,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::ArrayPop: case FunctionType::Kind::ArrayPop:
{ {
_functionCall.expression().accept(*this); _functionCall.expression().accept(*this);
solAssert(function.bound(), "");
solAssert(function.parameterTypes().empty(), ""); solAssert(function.parameterTypes().empty(), "");
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(function.selfType());
ArrayType const& arrayType = dynamic_cast<ArrayType const&>( solAssert(arrayType && arrayType->dataStoredIn(DataLocation::Storage), "");
*dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression().annotation().type ArrayUtils(m_context).popStorageArrayElement(*arrayType);
);
solAssert(arrayType.dataStoredIn(DataLocation::Storage), "");
ArrayUtils(m_context).popStorageArrayElement(arrayType);
break; break;
} }
case FunctionType::Kind::ObjectCreation: case FunctionType::Kind::ObjectCreation:
@ -1325,6 +1322,12 @@ bool ExpressionCompiler::visit(MemberAccess const& _memberAccess)
utils().pushCombinedFunctionEntryLabel(funDef); utils().pushCombinedFunctionEntryLabel(funDef);
utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1); utils().moveIntoStack(funType->selfType()->sizeOnStack(), 1);
} }
else if (
funType->kind() == FunctionType::Kind::ArrayPop ||
funType->kind() == FunctionType::Kind::ArrayPush
)
{
}
else else
{ {
solAssert(funType->kind() == FunctionType::Kind::DelegateCall, ""); solAssert(funType->kind() == FunctionType::Kind::DelegateCall, "");

View File

@ -1276,30 +1276,31 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
} }
case FunctionType::Kind::ArrayPop: case FunctionType::Kind::ArrayPop:
{ {
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression(); solAssert(functionType->bound(), "");
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*memberAccessExpression.annotation().type); solAssert(functionType->parameterTypes().empty(), "");
ArrayType const* arrayType = dynamic_cast<ArrayType const*>(functionType->selfType());
solAssert(arrayType, "");
define(_functionCall) << define(_functionCall) <<
m_utils.storageArrayPopFunction(arrayType) << m_utils.storageArrayPopFunction(*arrayType) <<
"(" << "(" <<
IRVariable(_functionCall.expression()).commaSeparatedList() << IRVariable(_functionCall.expression()).commaSeparatedList() <<
")\n"; ")\n";
break; break;
} }
case FunctionType::Kind::ByteArrayPush:
case FunctionType::Kind::ArrayPush: case FunctionType::Kind::ArrayPush:
{ {
auto const& memberAccessExpression = dynamic_cast<MemberAccess const&>(_functionCall.expression()).expression(); ArrayType const* arrayType = dynamic_cast<ArrayType const*>(functionType->selfType());
ArrayType const& arrayType = dynamic_cast<ArrayType const&>(*memberAccessExpression.annotation().type); solAssert(arrayType, "");
if (arguments.empty()) if (arguments.empty())
{ {
auto slotName = m_context.newYulVariable(); auto slotName = m_context.newYulVariable();
auto offsetName = m_context.newYulVariable(); auto offsetName = m_context.newYulVariable();
m_code << "let " << slotName << ", " << offsetName << " := " << m_code << "let " << slotName << ", " << offsetName << " := " <<
m_utils.storageArrayPushZeroFunction(arrayType) << m_utils.storageArrayPushZeroFunction(*arrayType) <<
"(" << IRVariable(_functionCall.expression()).commaSeparatedList() << ")\n"; "(" << IRVariable(_functionCall.expression()).commaSeparatedList() << ")\n";
setLValue(_functionCall, IRLValue{ setLValue(_functionCall, IRLValue{
*arrayType.baseType(), *arrayType->baseType(),
IRLValue::Storage{ IRLValue::Storage{
slotName, slotName,
offsetName, offsetName,
@ -1309,12 +1310,12 @@ void IRGeneratorForStatements::endVisit(FunctionCall const& _functionCall)
else else
{ {
IRVariable argument = IRVariable argument =
arrayType.baseType()->isValueType() ? arrayType->baseType()->isValueType() ?
convert(*arguments.front(), *arrayType.baseType()) : convert(*arguments.front(), *arrayType->baseType()) :
*arguments.front(); *arguments.front();
m_code << m_code <<
m_utils.storageArrayPushFunction(arrayType, &argument.type()) << m_utils.storageArrayPushFunction(*arrayType, &argument.type()) <<
"(" << "(" <<
IRVariable(_functionCall.expression()).commaSeparatedList() << IRVariable(_functionCall.expression()).commaSeparatedList() <<
(argument.stackSlots().empty() ? "" : (", " + argument.commaSeparatedList())) << (argument.stackSlots().empty() ? "" : (", " + argument.commaSeparatedList())) <<
@ -1590,16 +1591,24 @@ void IRGeneratorForStatements::endVisit(MemberAccess const& _memberAccess)
}).count(objectCategory) > 0, ""); }).count(objectCategory) > 0, "");
define(IRVariable(_memberAccess).part("self"), _memberAccess.expression()); define(IRVariable(_memberAccess).part("self"), _memberAccess.expression());
auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(memberFunctionType->declaration());
solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, ""); solAssert(*_memberAccess.annotation().requiredLookup == VirtualLookup::Static, "");
if (memberFunctionType->kind() == FunctionType::Kind::Internal) if (memberFunctionType->kind() == FunctionType::Kind::Internal)
{ {
auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(memberFunctionType->declaration());
define(IRVariable(_memberAccess).part("functionIdentifier")) << to_string(functionDefinition.id()) << "\n"; define(IRVariable(_memberAccess).part("functionIdentifier")) << to_string(functionDefinition.id()) << "\n";
if (!_memberAccess.annotation().calledDirectly) if (!_memberAccess.annotation().calledDirectly)
m_context.addToInternalDispatch(functionDefinition); m_context.addToInternalDispatch(functionDefinition);
} }
else if (
memberFunctionType->kind() == FunctionType::Kind::ArrayPush ||
memberFunctionType->kind() == FunctionType::Kind::ArrayPop
)
{
// Nothing to do.
}
else else
{ {
auto const& functionDefinition = dynamic_cast<FunctionDefinition const&>(memberFunctionType->declaration());
solAssert(memberFunctionType->kind() == FunctionType::Kind::DelegateCall, ""); solAssert(memberFunctionType->kind() == FunctionType::Kind::DelegateCall, "");
auto contract = dynamic_cast<ContractDefinition const*>(functionDefinition.scope()); auto contract = dynamic_cast<ContractDefinition const*>(functionDefinition.scope());
solAssert(contract && contract->isLibrary(), ""); solAssert(contract && contract->isLibrary(), "");

View File

@ -718,7 +718,6 @@ void SMTEncoder::endVisit(FunctionCall const& _funCall)
break; break;
} }
case FunctionType::Kind::ArrayPush: case FunctionType::Kind::ArrayPush:
case FunctionType::Kind::ByteArrayPush:
arrayPush(_funCall); arrayPush(_funCall);
break; break;
case FunctionType::Kind::ArrayPop: case FunctionType::Kind::ArrayPop:
@ -1656,8 +1655,6 @@ void SMTEncoder::arrayPushPopAssign(Expression const& _expr, smtutil::Expression
else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr)) else if (auto const* funCall = dynamic_cast<FunctionCall const*>(expr))
{ {
FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type); FunctionType const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
// Push cannot occur on an expression that is itself a ByteArrayPush, i.e., bytes.push().push() is not possible.
solAssert(funType.kind() != FunctionType::Kind::ByteArrayPush, "");
if (funType.kind() == FunctionType::Kind::ArrayPush) if (funType.kind() == FunctionType::Kind::ArrayPush)
{ {
auto memberAccess = dynamic_cast<MemberAccess const*>(&funCall->expression()); auto memberAccess = dynamic_cast<MemberAccess const*>(&funCall->expression());
@ -2649,7 +2646,7 @@ MemberAccess const* SMTEncoder::isEmptyPush(Expression const& _expr) const
) )
{ {
auto const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type); auto const& funType = dynamic_cast<FunctionType const&>(*funCall->expression().annotation().type);
if (funType.kind() == FunctionType::Kind::ArrayPush || funType.kind() == FunctionType::Kind::ByteArrayPush) if (funType.kind() == FunctionType::Kind::ArrayPush)
return &dynamic_cast<MemberAccess const&>(funCall->expression()); return &dynamic_cast<MemberAccess const&>(funCall->expression());
} }
return nullptr; return nullptr;

View File

@ -0,0 +1,12 @@
contract C {
int[] data;
function f() public returns (uint) {
data.push(1);
(data.pop)();
return data.length;
}
}
// ====
// compileViaYul: also
// ----
// f() -> 0