mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #13141 from ethereum/danglingReferencesDocs
Add documentation section about dangling references.
This commit is contained in:
commit
71c838abb3
@ -468,6 +468,119 @@ Array Members
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.. index:: ! array;dangling storage references
|
||||||
|
|
||||||
|
Dangling References to Storage Array Elements
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
When working with storage arrays, you need to take care to avoid dangling references.
|
||||||
|
A dangling reference is a reference that points to something that no longer exists or has been
|
||||||
|
moved without updating the reference. A dangling reference can for example occur, if you store a
|
||||||
|
reference to an array element in a local variable and then ``.pop()`` from the containing array:
|
||||||
|
|
||||||
|
.. code-block:: solidity
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
uint[][] s;
|
||||||
|
|
||||||
|
function f() public {
|
||||||
|
// Stores a pointer to the last array element of s.
|
||||||
|
uint[] storage ptr = s[s.length - 1];
|
||||||
|
// Removes the last array element of s.
|
||||||
|
s.pop();
|
||||||
|
// Writes to the array element that is no longer within the array.
|
||||||
|
ptr.push(0x42);
|
||||||
|
// Adding a new element to ``s`` now will not add an empty array, but
|
||||||
|
// will result in an array of length 1 with ``0x42`` as element.
|
||||||
|
s.push();
|
||||||
|
assert(s[s.length - 1][0] == 0x42);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
The write in ``ptr.push(0x42)`` will **not** revert, despite the fact that ``ptr`` no
|
||||||
|
longer refers to a valid element of ``s``. Since the compiler assumes that unused storage
|
||||||
|
is always zeroed, a subsequent ``s.push()`` will not explicitly write zeroes to storage,
|
||||||
|
so the last element of ``s`` after that ``push()`` will have length ``1`` and contain
|
||||||
|
``0x42`` as its first element.
|
||||||
|
|
||||||
|
Note that Solidity does not allow to declare references to value types in storage. These kinds
|
||||||
|
of explicit dangling references are restricted to nested reference types. However, dangling references
|
||||||
|
can also occur temporarily when using complex expressions in tuple assignments:
|
||||||
|
|
||||||
|
.. code-block:: solidity
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
uint[] s;
|
||||||
|
uint[] t;
|
||||||
|
constructor() {
|
||||||
|
// Push some initial values to the storage arrays.
|
||||||
|
s.push(0x07);
|
||||||
|
t.push(0x03);
|
||||||
|
}
|
||||||
|
|
||||||
|
function g() internal returns (uint[] storage) {
|
||||||
|
s.pop();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
function f() public returns (uint[] memory) {
|
||||||
|
// The following will first evaluate ``s.push()`` to a reference to a new element
|
||||||
|
// at index 1. Afterwards, the call to ``g`` pops this new element, resulting in
|
||||||
|
// the left-most tuple element to become a dangling reference. The assignment still
|
||||||
|
// takes place and will write outside the data area of ``s``.
|
||||||
|
(s.push(), g()[0]) = (0x42, 0x17);
|
||||||
|
// A subsequent push to ``s`` will reveal the value written by the previous
|
||||||
|
// statement, i.e. the last element of ``s`` at the end of this function will have
|
||||||
|
// the value ``0x42``.
|
||||||
|
s.push();
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
It is always safer to only assign to storage once per statement and to avoid
|
||||||
|
complex expressions on the left-hand-side of an assignment.
|
||||||
|
|
||||||
|
You need to take particular care when dealing with references to elements of
|
||||||
|
``bytes`` arrays, since a ``.push()`` on a bytes array may switch :ref:`from short
|
||||||
|
to long layout in storage<bytes-and-string>`.
|
||||||
|
|
||||||
|
.. code-block:: solidity
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: GPL-3.0
|
||||||
|
pragma solidity >=0.8.0 <0.9.0;
|
||||||
|
|
||||||
|
contract C {
|
||||||
|
bytes x = "012345678901234567890123456789";
|
||||||
|
|
||||||
|
function test() external returns(uint) {
|
||||||
|
(x.push(), x.push()) = (0x01, 0x02);
|
||||||
|
return x.length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Here, when the first ``x.push()`` is evaluated, ``x`` is still stored in short
|
||||||
|
layout, thereby ``x.push()`` returns a reference to an element in the first storage slot of
|
||||||
|
``x``. However, the second ``x.push()`` switches the bytes array to large layout.
|
||||||
|
Now the element that ``x.push()`` referred to is in the data area of the array while
|
||||||
|
the reference still points at its original location, which is now a part of the length field
|
||||||
|
and the assignment will effectively garble the length of ``x``.
|
||||||
|
To be safe, only enlarge bytes arrays by at most one element during a single
|
||||||
|
assignment and do not simultaneously index-access the array in the same statement.
|
||||||
|
|
||||||
|
While the above describes the behaviour of dangling storage references in the
|
||||||
|
current version of the compiler, any code with dangling references should be
|
||||||
|
considered to have *undefined behaviour*. In particular, this means that
|
||||||
|
any future version of the compiler may change the behaviour of code that
|
||||||
|
involves dangling references.
|
||||||
|
|
||||||
|
Be sure to avoid dangling references in your code!
|
||||||
|
|
||||||
.. index:: ! array;slice
|
.. index:: ! array;slice
|
||||||
|
|
||||||
.. _array-slices:
|
.. _array-slices:
|
||||||
|
Loading…
Reference in New Issue
Block a user