@@ -468,6 +468,119 @@ Array Members
468
468
}
469
469
}
470
470
471
+ .. index :: ! array;dangling storage references
472
+
473
+ Dangling References to Storage Array Elements
474
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
475
+
476
+ When working with storage arrays, you need to take care to avoid dangling references.
477
+ A dangling reference is a reference that points to something that no longer exists or has been
478
+ moved without updating the reference. A dangling reference can for example occur, if you store a
479
+ reference to an array element in a local variable and then ``.pop() `` from the containing array:
480
+
481
+ .. code-block :: solidity
482
+
483
+ // SPDX-License-Identifier: GPL-3.0
484
+ pragma solidity >=0.8.0 <0.9.0;
485
+
486
+ contract C {
487
+ uint[][] s;
488
+
489
+ function f() public {
490
+ // Stores a pointer to the last array element of s.
491
+ uint[] storage ptr = s[s.length - 1];
492
+ // Removes the last array element of s.
493
+ s.pop();
494
+ // Writes to the array element that is no longer within the array.
495
+ ptr.push(0x42);
496
+ // Adding a new element to ``s`` now will not add an empty array, but
497
+ // will result in an array of length 1 with ``0x42`` as element.
498
+ s.push();
499
+ assert(s[s.length - 1][0] == 0x42);
500
+ }
501
+ }
502
+
503
+ The write in ``ptr.push(0x42) `` will **not ** revert, despite the fact that ``ptr `` no
504
+ longer refers to a valid element of ``s ``. Since the compiler assumes that unused storage
505
+ is always zeroed, a subsequent ``s.push() `` will not explicitly write zeroes to storage,
506
+ so the last element of ``s `` after that ``push() `` will have length ``1 `` and contain
507
+ ``0x42 `` as its first element.
508
+
509
+ Note that Solidity does not allow to declare references to value types in storage. These kinds
510
+ of explicit dangling references are restricted to nested reference types. However, dangling references
511
+ can also occur temporarily when using complex expressions in tuple assignments:
512
+
513
+ .. code-block :: solidity
514
+
515
+ // SPDX-License-Identifier: GPL-3.0
516
+ pragma solidity >=0.8.0 <0.9.0;
517
+
518
+ contract C {
519
+ uint[] s;
520
+ uint[] t;
521
+ constructor() {
522
+ // Push some initial values to the storage arrays.
523
+ s.push(0x07);
524
+ t.push(0x03);
525
+ }
526
+
527
+ function g() internal returns (uint[] storage) {
528
+ s.pop();
529
+ return t;
530
+ }
531
+
532
+ function f() public returns (uint[] memory) {
533
+ // The following will first evaluate ``s.push()`` to a reference to a new element
534
+ // at index 1. Afterwards, the call to ``g`` pops this new element, resulting in
535
+ // the left-most tuple element to become a dangling reference. The assignment still
536
+ // takes place and will write outside the data area of ``s``.
537
+ (s.push(), g()[0]) = (0x42, 0x17);
538
+ // A subsequent push to ``s`` will reveal the value written by the previous
539
+ // statement, i.e. the last element of ``s`` at the end of this function will have
540
+ // the value ``0x42``.
541
+ s.push();
542
+ return s;
543
+ }
544
+ }
545
+
546
+ It is always safer to only assign to storage once per statement and to avoid
547
+ complex expressions on the left-hand-side of an assignment.
548
+
549
+ You need to take particular care when dealing with references to elements of
550
+ ``bytes `` arrays, since a ``.push() `` on a bytes array may switch :ref: `from short
551
+ to long layout in storage<bytes-and-string>`.
552
+
553
+ .. code-block :: solidity
554
+
555
+ // SPDX-License-Identifier: GPL-3.0
556
+ pragma solidity >=0.8.0 <0.9.0;
557
+
558
+ contract C {
559
+ bytes x = "012345678901234567890123456789";
560
+
561
+ function test() external returns(uint) {
562
+ (x.push(), x.push()) = (0x01, 0x02);
563
+ return x.length;
564
+ }
565
+ }
566
+
567
+ Here, when the first ``x.push() `` is evaluated, ``x `` is still stored in short
568
+ layout, thereby ``x.push() `` returns a reference to an element in the first storage slot of
569
+ ``x ``. However, the second ``x.push() `` switches the bytes array to large layout.
570
+ Now the element that ``x.push() `` referred to is in the data area of the array while
571
+ the reference still points at its original location, which is now a part of the length field
572
+ and the assignment will effectively garble the length of ``x ``.
573
+ To be safe, only enlarge bytes arrays by at most one element during a single
574
+ assignment and do not simultaneously index-access the array in the same statement.
575
+
576
+ While the above describes the behaviour of dangling storage references in the
577
+ current version of the compiler, any code with dangling references should be
578
+ considered to have *undefined behaviour *. In particular, this means that
579
+ any future version of the compiler may change the behaviour of code that
580
+ involves dangling references.
581
+
582
+ Be sure to avoid dangling references in your code!
583
+
471
584
.. index :: ! array;slice
472
585
473
586
.. _array-slices :
0 commit comments