Tham Chiếu Lơ Lửng đến Phần Tử Mảng Lưu Trữ trong Solidity: Tác hại và ví dụ

Tham Chiếu Lơ Lửng (Dangling References) đến Phần Tử Mảng Lưu Trữ (Storage Array Elements) trong Solidity

Chào mọi người, trong quá trình phát triển các ứng dụng blockchain bằng Solidity, việc làm việc với các mảng lưu trữ là không thể tránh khỏi. Tuy nhiên, một khía cạnh quan trọng mà bạn cần lưu ý là nguy cơ tham chiếu lơ lửng đến các phần tử trong mảng lưu trữ. Trong bài viết này, chúng ta sẽ tìm hiểu về khái niệm tham chiếu lơ lửng đến phần tử mảng lưu trữ là gì, cách nó có thể gây ra lỗi trong Solidity, và cách để tránh khỏi tình trạng này.

Tham Chiếu Lơ Lửng (Dangling References) đến Phần Tử Mảng Lưu Trữ (Storage Array Elements) trong Solidity là Gì?

Tham chiếu lơ lửng (dangling reference) xuất hiện khi bạn sử dụng một tham chiếu (biến) để truy cập đến một phần tử trong mảng lưu trữ (storage array), nhưng sau đó phần tử đó bị xóa hoặc thay đổi mà tham chiếu không được cập nhật tương ứng. Khi điều này xảy ra, tham chiếu vẫn trỏ đến vị trí bộ nhớ cũ, dẫn đến nguy cơ gây ra lỗi không xác định hoặc hoạt động sai lệch trong chương trình.

Gây Ra Lỗi Gì?

Khi tham chiếu lơ lửng đến phần tử trong mảng lưu trữ, bạn có thể gặp phải các vấn đề sau:

  1. Truy Cập Dữ Liệu Không Hợp Lệ: Khi tham chiếu lơ lửng vẫn trỏ đến vùng nhớ đã giải phóng hoặc đã thay đổi, việc truy cập dữ liệu từ tham chiếu này có thể gây ra lỗi không xác định hoặc thậm chí là crash chương trình.
  2. Kết Quả Không Chính Xác: Nếu tham chiếu lơ lửng được sử dụng để truy cập dữ liệu đã bị thay đổi hoặc bị ghi đè bởi dữ liệu khác, kết quả có thể trở nên không chính xác và không đáng tin cậy.

Làm Sao Để Tránh?

  1. Hạn Chế Sử Dụng Tham Chiếu Lơ Lửng: Tránh sử dụng tham chiếu để truy cập đến các phần tử trong mảng lưu trữ, đặc biệt là sau khi mảng đã được thay đổi hoặc giải phóng.
  2. Cập Nhật Tham Chiếu Khi Mảng Thay Đổi: Nếu bạn phải sử dụng tham chiếu đến phần tử trong mảng lưu trữ, đảm bảo rằng bạn cập nhật tham chiếu mỗi khi mảng được thay đổi hoặc có sự thay đổi trong dữ liệu.

Ví dụ về Tham Chiếu Lơ Lửng và Giải Thích

Ví dụ 1:

pragma solidity ^0.8.0;

contract DanglingReferenceExample {
    uint[] public numbers;

    function addNumber(uint _number) public {
        numbers.push(_number);
    }

    function popAndAccessNumber() public {
        require(numbers.length > 0, "No numbers available");
        
        uint removedNumber = numbers[numbers.length - 1];
        numbers.pop(); // Xóa phần tử cuối cùng trong mảng
        
        // Tham chiếu lơ lửng đến phần tử đã bị xóa
        uint result = removedNumber; // Tham chiếu lơ lửng
        // Kết quả của biến result có thể không chính xác
    }
}

Trong ví dụ trên, hợp đồng DanglingReferenceExample có mảng numbers và hàm popAndAccessNumber để xóa phần tử cuối cùng trong mảng và sau đó sử dụng tham chiếu để truy cập đến giá trị đã bị xóa. Điều này dẫn đến tham chiếu lơ lửng và có thể gây ra lỗi trong chương trình.

Ví dụ 2:

Một tình huống tham chiếu lơ lửng có thể xảy ra khi bạn lưu một tham chiếu đến một phần tử của mảng trong một biến cục bộ, sau đó sử dụng hàm .pop() để xóa phần tử đó khỏi mảng chứa nó.

Ví dụ Mã Solidity:

// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.8.0 <0.9.0;

contract C {
    uint[][] s;

    function f() public {
        // Lưu trữ một tham chiếu đến phần tử cuối cùng của mảng s.
        uint[] storage ptr = s[s.length - 1];
        // Xóa phần tử cuối cùng khỏi mảng s.
        s.pop();
        // Gán giá trị vào phần tử của mảng đã không còn trong mảng.
        ptr.push(0x42);
        // Thêm một phần tử mới vào ``s`` sẽ không tạo ra một mảng rỗng,
        // mà sẽ là một mảng có độ dài 1 với phần tử ``0x42``.
        s.push();
        assert(s[s.length - 1][0] == 0x42);
    }
}

Trong ví dụ này, việc ghi vào ptr.push(0x42) sẽ không làm hàm bị quay trở lại, mặc dù ptr không còn trỏ đến phần tử hợp lệ của s. Vì trình biên dịch cho rằng không gian lưu trữ chưa được sử dụng luôn được làm trống, việc sử dụng hàm s.push() sau đó không viết rõ ràng các giá trị không phải là giá trị 0 vào bộ nhớ, vì vậy phần tử cuối cùng của s sau khi thực hiện hàm push() sẽ có độ dài là 1 và chứa giá trị 0x42 ở vị trí đầu tiên.

Lưu Ý: Solidity không cho phép khai báo tham chiếu đến kiểu dữ liệu giá trị trong lưu trữ. Những tham chiếu lơ lửng rõ ràng như vậy bị hạn chế đối với các kiểu dữ liệu tham chiếu lồng nhau. Tuy nhiên, tham chiếu lơ lửng có thể xảy ra tạm thời khi sử dụng biểu thức phức tạp trong gán giá trị tuple.

Để tránh lỗi tham chiếu lơ lửng, luôn luôn an toàn khi gán vào lưu trữ chỉ một lần mỗi câu lệnh và tránh sử dụng các biểu thức phức tạp ở phía trái của gán giá trị.

// 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;
    }
}

Kết: Tham chiếu lơ lửng đến phần tử trong mảng lưu trữ là một vấn đề quan trọng trong Solidity có thể gây ra các vấn đề nghiêm trọng trong chương trình. Để tránh tham chiếu lơ lửng, hãy luôn cập nhật tham chiếu khi mảng thay đổi và tránh sử dụng tham chiếu đến dữ liệu đã bị xóa hoặc thay đổi.

  • If you’d like to invest in blockchain advertising companies, just BUY token Saigon (SGN) at Pancakeswap: https://t.co/KJbk71cFe8 (do not worry about low liquidity)
  • Backed by Click Digital Company
  • Enhancing blockchain knowledge
  • BSC address: 0xa29c5da6673fd66e96065f44da94e351a3e2af65
  • Twitter: https://twitter.com/SaigonSGN135
  • Staking SGN: http://135web.net

Leave a Comment

Your email address will not be published. Required fields are marked *