mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Disallow delete on types containing nested mappings.
This commit is contained in:
parent
359bc09ada
commit
d248d35905
@ -1,6 +1,8 @@
|
|||||||
### 0.9.0 (unreleased)
|
### 0.9.0 (unreleased)
|
||||||
|
|
||||||
Breaking changes:
|
Breaking changes:
|
||||||
|
* Disallow ``.pop()`` on arrays containing nested mappings.
|
||||||
|
* Disallow ``delete`` on types that contain nested mappings.
|
||||||
* Inline Assembly: Consider functions, function parameters and return variables for shadowing checks.
|
* Inline Assembly: Consider functions, function parameters and return variables for shadowing checks.
|
||||||
|
|
||||||
|
|
||||||
|
@ -317,48 +317,12 @@ key-value data structure that does not keep track of the keys that were
|
|||||||
assigned a non-zero value. Because of that, cleaning a mapping without extra
|
assigned a non-zero value. Because of that, cleaning a mapping without extra
|
||||||
information about the written keys is not possible.
|
information about the written keys is not possible.
|
||||||
If a ``mapping`` is used as the base type of a dynamic storage array, deleting
|
If a ``mapping`` is used as the base type of a dynamic storage array, deleting
|
||||||
or popping the array will have no effect over the ``mapping`` elements. The
|
or popping the array is not possible.
|
||||||
same happens, for example, if a ``mapping`` is used as the type of a member
|
The same is true, for example, if a ``mapping`` is used as the type of a member
|
||||||
field of a ``struct`` that is the base type of a dynamic storage array. The
|
field of a ``struct`` that is the base type of a dynamic storage array.
|
||||||
``mapping`` is also ignored in assignments of structs or arrays containing a
|
It is also not possible to assign new values to structs or arrays containing a
|
||||||
``mapping``.
|
``mapping``.
|
||||||
|
|
||||||
.. code-block:: solidity
|
|
||||||
|
|
||||||
// SPDX-License-Identifier: GPL-3.0
|
|
||||||
pragma solidity >=0.6.0 <0.9.0;
|
|
||||||
|
|
||||||
contract Map {
|
|
||||||
mapping (uint => uint)[] array;
|
|
||||||
|
|
||||||
function allocate(uint _newMaps) public {
|
|
||||||
for (uint i = 0; i < _newMaps; i++)
|
|
||||||
array.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
function writeMap(uint _map, uint _key, uint _value) public {
|
|
||||||
array[_map][_key] = _value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function readMap(uint _map, uint _key) public view returns (uint) {
|
|
||||||
return array[_map][_key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function eraseMaps() public {
|
|
||||||
delete array;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Consider the example above and the following sequence of calls: ``allocate(10)``,
|
|
||||||
``writeMap(4, 128, 256)``.
|
|
||||||
At this point, calling ``readMap(4, 128)`` returns 256.
|
|
||||||
If we call ``eraseMaps``, the length of state variable ``array`` is zeroed, but
|
|
||||||
since its ``mapping`` elements cannot be zeroed, their information stays alive
|
|
||||||
in the contract's storage.
|
|
||||||
After deleting ``array``, calling ``allocate(5)`` allows us to access
|
|
||||||
``array[4]`` again, and calling ``readMap(4, 128)`` returns 256 even without
|
|
||||||
another call to ``writeMap``.
|
|
||||||
|
|
||||||
If your ``mapping`` information must be deleted, consider using a library similar to
|
If your ``mapping`` information must be deleted, consider using a library similar to
|
||||||
`iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_,
|
`iterable mapping <https://github.com/ethereum/dapp-bin/blob/master/library/iterable_mapping.sol>`_,
|
||||||
allowing you to traverse the keys and delete their values in the appropriate ``mapping``.
|
allowing you to traverse the keys and delete their values in the appropriate ``mapping``.
|
||||||
|
@ -1594,11 +1594,18 @@ bool TypeChecker::visit(UnaryOperation const& _operation)
|
|||||||
requireLValue(_operation.subExpression(), false);
|
requireLValue(_operation.subExpression(), false);
|
||||||
else
|
else
|
||||||
_operation.subExpression().accept(*this);
|
_operation.subExpression().accept(*this);
|
||||||
|
|
||||||
Type const* subExprType = type(_operation.subExpression());
|
Type const* subExprType = type(_operation.subExpression());
|
||||||
Type const* t = type(_operation.subExpression())->unaryOperatorResult(op);
|
TypeResult result = subExprType->unaryOperatorResult(op);
|
||||||
if (!t)
|
Type const* t = result;
|
||||||
|
if (!result)
|
||||||
{
|
{
|
||||||
string description = "Unary operator " + string(TokenTraits::toString(op)) + " cannot be applied to type " + subExprType->toString();
|
string description = "Unary operator " +
|
||||||
|
string(TokenTraits::toString(op)) +
|
||||||
|
" cannot be applied to type " +
|
||||||
|
subExprType->toString() +
|
||||||
|
(result.message().empty() ? "" : (": " + result.message()));
|
||||||
|
|
||||||
if (modifying)
|
if (modifying)
|
||||||
// Cannot just report the error, ignore the unary operator, and continue,
|
// Cannot just report the error, ignore the unary operator, and continue,
|
||||||
// because the sub-expression was already processed with requireLValue()
|
// because the sub-expression was already processed with requireLValue()
|
||||||
@ -2838,6 +2845,15 @@ bool TypeChecker::visit(MemberAccess const& _memberAccess)
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
funType->kind() == FunctionType::Kind::ArrayPop &&
|
||||||
|
exprType->containsNestedMapping()
|
||||||
|
)
|
||||||
|
m_errorReporter.typeError(
|
||||||
|
6298_error,
|
||||||
|
_memberAccess.location(),
|
||||||
|
"Storage arrays with nested mappings do not support .pop()."
|
||||||
|
);
|
||||||
|
else if (
|
||||||
funType->kind() == FunctionType::Kind::ArrayPush &&
|
funType->kind() == FunctionType::Kind::ArrayPush &&
|
||||||
arguments.value().numArguments() != 0 &&
|
arguments.value().numArguments() != 0 &&
|
||||||
exprType->containsNestedMapping()
|
exprType->containsNestedMapping()
|
||||||
|
@ -1449,6 +1449,10 @@ TypeResult ReferenceType::unaryOperatorResult(Token _operator) const
|
|||||||
{
|
{
|
||||||
if (_operator != Token::Delete)
|
if (_operator != Token::Delete)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
|
if (containsNestedMapping())
|
||||||
|
return TypeResult::err("Contains a (possibly nested) mapping");
|
||||||
|
|
||||||
// delete can be used on everything except calldata references or storage pointers
|
// delete can be used on everything except calldata references or storage pointers
|
||||||
// (storage references are ok)
|
// (storage references are ok)
|
||||||
switch (location())
|
switch (location())
|
||||||
|
@ -1,41 +0,0 @@
|
|||||||
contract C {
|
|
||||||
mapping (uint => uint)[][] a;
|
|
||||||
|
|
||||||
function n1(uint key, uint value) public {
|
|
||||||
a.push();
|
|
||||||
mapping (uint => uint)[] storage b = a[a.length - 1];
|
|
||||||
b.push();
|
|
||||||
b[b.length - 1][key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function n2() public {
|
|
||||||
a.push();
|
|
||||||
mapping (uint => uint)[] storage b = a[a.length - 1];
|
|
||||||
b.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
function map(uint key) public view returns (uint) {
|
|
||||||
mapping (uint => uint)[] storage b = a[a.length - 1];
|
|
||||||
return b[b.length - 1][key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function p() public {
|
|
||||||
a.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function d() public returns (uint) {
|
|
||||||
delete a;
|
|
||||||
return a.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ====
|
|
||||||
// compileViaYul: also
|
|
||||||
// ----
|
|
||||||
// n1(uint256,uint256): 42, 64 ->
|
|
||||||
// map(uint256): 42 -> 64
|
|
||||||
// p() ->
|
|
||||||
// n2() ->
|
|
||||||
// map(uint256): 42 -> 64
|
|
||||||
// d() -> 0
|
|
||||||
// n2() ->
|
|
||||||
// map(uint256): 42 -> 64
|
|
@ -1,36 +0,0 @@
|
|||||||
contract C {
|
|
||||||
mapping (uint => uint)[] a;
|
|
||||||
|
|
||||||
function n1(uint key, uint value) public {
|
|
||||||
a.push();
|
|
||||||
a[a.length - 1][key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
function n2() public {
|
|
||||||
a.push();
|
|
||||||
}
|
|
||||||
|
|
||||||
function map(uint key) public view returns (uint) {
|
|
||||||
return a[a.length - 1][key];
|
|
||||||
}
|
|
||||||
|
|
||||||
function p() public {
|
|
||||||
a.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
function d() public returns (uint) {
|
|
||||||
delete a;
|
|
||||||
return a.length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// ====
|
|
||||||
// compileViaYul: also
|
|
||||||
// ----
|
|
||||||
// n1(uint256,uint256): 42, 64 ->
|
|
||||||
// map(uint256): 42 -> 64
|
|
||||||
// p() ->
|
|
||||||
// n2() ->
|
|
||||||
// map(uint256): 42 -> 64
|
|
||||||
// d() -> 0
|
|
||||||
// n2() ->
|
|
||||||
// map(uint256): 42 -> 64
|
|
@ -2,23 +2,17 @@ contract test {
|
|||||||
struct topStruct {
|
struct topStruct {
|
||||||
nestedStruct nstr;
|
nestedStruct nstr;
|
||||||
uint topValue;
|
uint topValue;
|
||||||
mapping (uint => uint) topMapping;
|
|
||||||
}
|
}
|
||||||
uint toDelete;
|
uint toDelete;
|
||||||
topStruct str;
|
topStruct str;
|
||||||
struct nestedStruct {
|
struct nestedStruct {
|
||||||
uint nestedValue;
|
uint nestedValue;
|
||||||
mapping (uint => bool) nestedMapping;
|
|
||||||
}
|
}
|
||||||
constructor() {
|
constructor() {
|
||||||
toDelete = 5;
|
toDelete = 5;
|
||||||
str.topValue = 1;
|
str.topValue = 1;
|
||||||
str.topMapping[0] = 1;
|
|
||||||
str.topMapping[1] = 2;
|
|
||||||
|
|
||||||
str.nstr.nestedValue = 2;
|
str.nstr.nestedValue = 2;
|
||||||
str.nstr.nestedMapping[0] = true;
|
|
||||||
str.nstr.nestedMapping[1] = false;
|
|
||||||
delete str;
|
delete str;
|
||||||
delete toDelete;
|
delete toDelete;
|
||||||
}
|
}
|
||||||
@ -31,12 +25,6 @@ contract test {
|
|||||||
function getNestedValue() public returns(uint nestedValue){
|
function getNestedValue() public returns(uint nestedValue){
|
||||||
nestedValue = str.nstr.nestedValue;
|
nestedValue = str.nstr.nestedValue;
|
||||||
}
|
}
|
||||||
function getTopMapping(uint index) public returns(uint ret) {
|
|
||||||
ret = str.topMapping[index];
|
|
||||||
}
|
|
||||||
function getNestedMapping(uint index) public returns(bool ret) {
|
|
||||||
return str.nstr.nestedMapping[index];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// ====
|
// ====
|
||||||
// compileViaYul: also
|
// compileViaYul: also
|
||||||
@ -44,7 +32,3 @@ contract test {
|
|||||||
// getToDelete() -> 0
|
// getToDelete() -> 0
|
||||||
// getTopValue() -> 0
|
// getTopValue() -> 0
|
||||||
// getNestedValue() -> 0 #mapping values should be the same#
|
// getNestedValue() -> 0 #mapping values should be the same#
|
||||||
// getTopMapping(uint256): 0 -> 1
|
|
||||||
// getTopMapping(uint256): 1 -> 2
|
|
||||||
// getNestedMapping(uint256): 0 -> true
|
|
||||||
// getNestedMapping(uint256): 1 -> false
|
|
||||||
|
@ -6,3 +6,4 @@ contract C {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
|
// TypeError 6298: (109-118): Storage arrays with nested mappings do not support .pop().
|
||||||
|
@ -6,4 +6,4 @@ contract test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// ----
|
// ----
|
||||||
// TypeError 4907: (97-99): Unary operator - cannot be applied to type uint256
|
// TypeError 4907: (97-99): Unary operator - cannot be applied to type uint256: Unary negation is only allowed for signed integers.
|
||||||
|
@ -0,0 +1,15 @@
|
|||||||
|
contract D {
|
||||||
|
struct Test {
|
||||||
|
mapping(address => uint) balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
Test test;
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
test = Test();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError 9214: (102-106): Types in storage containing (nested) mappings cannot be assigned to.
|
||||||
|
// TypeError 9515: (109-115): Struct containing a (nested) mapping cannot be constructed.
|
@ -0,0 +1,14 @@
|
|||||||
|
contract D {
|
||||||
|
struct Test {
|
||||||
|
mapping(address => uint) balances;
|
||||||
|
}
|
||||||
|
|
||||||
|
Test test;
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
{
|
||||||
|
delete test;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----
|
||||||
|
// TypeError 9767: (102-113): Unary operator delete cannot be applied to type struct D.Test storage ref: Contains a (possibly nested) mapping
|
Loading…
Reference in New Issue
Block a user