From bd24df50ec9cd43cadfcf2af2b00692df55569cd Mon Sep 17 00:00:00 2001 From: manhatsu Date: Mon, 8 Dec 2025 13:14:51 +0900 Subject: [PATCH 1/5] doc: prohibit comparing raw pointers with different allocation origins --- src/coding-guidelines/types-and-traits.rst | 80 ++++++++++++++++++++++ 1 file changed, 80 insertions(+) diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits.rst index dd1109dc..29d41117 100644 --- a/src/coding-guidelines/types-and-traits.rst +++ b/src/coding-guidelines/types-and-traits.rst @@ -63,3 +63,83 @@ Types and Traits current.checked_add(velocity).expect("Position calculation overflowed") } +.. guideline:: Do not compare raw pointers to allocations with different provenance + :id: gui_5iE7d65xGPpJ + :category: required + :status: draft + :release: unclear-latest + :fls: fls_bw8zutjcteki + :decidability: decidable + :scope: system + :tags: surprising-behavior + + Do not compare raw pointers to allocations with different provenances for equality, inequality, or ordering. + + Pointer comparisons are permitted only when both pointers are guaranteed to reference the same allocation or subobject. + + Code shall not rely on: + + - Layout of variables in memory + - Assumed field layout of structs without ``repr(C)`` or ``repr(packed)`` + - Outcomes of pointer arithmetic across allocation boundaries + + .. rationale:: + :id: rat_AFpgNhAMQ4eC + :status: draft + + Although raw pointer comparison is not itself undefined behavior; comparing pointers with different provenance can give surprising results which might cause logic errors, portability issues, and inconsistent behavior across different optimization levels, builds, or platforms. Specifically, the result of comparing pointers with different providence is guaranteed to be the comparison of the pointer addresses. However, the addresses that are selected for allocations is unspecified. + + Pointer equality or ordering is only meaningful when both pointers are derived from the same allocated object or block of memory. Comparisons across unrelated allocations are semantically meaningless and must be avoided. + + .. non_compliant_example:: + :id: non_compl_ex_c5NpFUId5lMo + :status: draft + + This noncompliant example allocates two local ``u32`` variables on the stack. The order of these two variables in memory is unspecified behavior. The code then creates a raw pointer to ``v2`` and a raw pointer to ``v1``. Adds the address stored in ``v1`` to 1 × ``size_of::()`` = 4 bytes using `wrapping_offset `__ which: + + - ignores provenance + - may produce an arbitrary, invalid, or meaningless pointer + - is always allowed but does not guarantee the pointer points to anything valid + + Comparing two `values `__ of `raw pointer types `__ compares the addresses of the `values `__. + + This code then compares ptr (a pointer to ``v2``) with ``ptr2`` (a pointer to ``v1`` + 4 bytes). Because the stack layout is unspecified behavior, the result of this comparison depends on how the compiler the memory layout for ``v1`` and ``v2`` on the stack. The result may change across: + + - compiler versions + - optimization levels + - targets + - small code changes + - builds with or without link-time optimization + + This noncompliant example does not contain undefined behavior (because no pointer is dereferenced) but it does depend on unspecified behavior, meaning that the program is valid, but the results are undefined. + + .. code-block:: rust + + pub fn raw_ptr_comparison(){ + let v1: u32 = 1; + let v2: u32 = 2; + let ptr = &v2 as *const u32; + let ptr2 = (&v1 as *const u32).wrapping_offset(1); + if ptr == ptr2 { + println!("Same"); + } + else{ + println!("Not the same"); + } + } + + .. compliant_example:: + :id: compl_ex_pBPeA9tBOnxj + :status: draft + + This compliant example creates a mutable array of 16 bytes on the stack where all bytes are zero-initialized. The entire array is one contiguous allocation. The code creates a raw pointer ``p`` of type ``*const u8`` to the first element of the array (that is, ``buf[0]``). The ptr ``p`` points at the start of the allocation. The code then uses pointer arithmetic to compute a pointer ``q`` which points 4 elements past ``p``. Because the element type is ``u8``, this means “4 bytes past ``p``\ ”. The pointer arithmetic is safe as long as the resulting pointer stays within the same allocation (it does). This is permitted because pointer arithmetic is allowed within the same allocated object. + + Finally, the code compares the numerical address values of ``p`` and ``q``. Pointer comparison is always allowed. Comparing pointers from the same allocation is meaningful and defined. Because ``p`` points to the beginning and ``q`` to a later part of the same array, ``same_block`` becomes ``true``. + + .. code-block:: rust + + let mut buf = [0u8; 16]; + let p = buf.as_ptr(); + let q = unsafe { p.add(4) }; + + let same_block = p < q; // ok: comparison within same allocation \ No newline at end of file From 510903ff2939fc2e5a39c2d24a3df3c612893103 Mon Sep 17 00:00:00 2001 From: "Robert C. Seacord" Date: Mon, 8 Dec 2025 09:03:28 -0500 Subject: [PATCH 2/5] Revise pointer comparison guidelines for clarity Updated guidelines on pointer comparisons and memory access to clarify the importance of provenance and the implications of comparing pointers from different allocations. --- src/coding-guidelines/types-and-traits.rst | 57 +++++++++++++++++----- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits.rst index 29d41117..47d46a08 100644 --- a/src/coding-guidelines/types-and-traits.rst +++ b/src/coding-guidelines/types-and-traits.rst @@ -63,7 +63,7 @@ Types and Traits current.checked_add(velocity).expect("Position calculation overflowed") } -.. guideline:: Do not compare raw pointers to allocations with different provenance +.. guideline:: Do not access memory using a pointer with an incorrect provenance :id: gui_5iE7d65xGPpJ :category: required :status: draft @@ -73,9 +73,15 @@ Types and Traits :scope: system :tags: surprising-behavior - Do not compare raw pointers to allocations with different provenances for equality, inequality, or ordering. + Do not access memory using a pointer with an incorrect provenance. + Pointers, including values of reference type, have two components. + The pointer’s address identifies the memory location where the pointer is currently pointing. + The pointer’s provenance determines where and when the pointer is allowed to access memory. - Pointer comparisons are permitted only when both pointers are guaranteed to reference the same allocation or subobject. + Whether a memory access with a given pointer causes undefined behavior (UB) depends on both the address and the provenance: + the same address can access memory with one provenance but have undefined behavior with another provenance. + + Pointer comparisons are permitted only when both pointers are guaranteed to reference the same allocation. Code shall not rely on: @@ -83,27 +89,43 @@ Types and Traits - Assumed field layout of structs without ``repr(C)`` or ``repr(packed)`` - Outcomes of pointer arithmetic across allocation boundaries + This rule ignores any metadata that may come with wide pointers; + it only pertains to thin pointers and the data part of a wide pointer. + .. rationale:: :id: rat_AFpgNhAMQ4eC :status: draft - Although raw pointer comparison is not itself undefined behavior; comparing pointers with different provenance can give surprising results which might cause logic errors, portability issues, and inconsistent behavior across different optimization levels, builds, or platforms. Specifically, the result of comparing pointers with different providence is guaranteed to be the comparison of the pointer addresses. However, the addresses that are selected for allocations is unspecified. + Although raw pointer comparison is not itself undefined behavior; + comparing pointers with different provenance can give surprising results which might cause logic errors, + portability issues, and inconsistent behavior across different optimization levels, builds, or platforms. + Specifically, the result of comparing pointers with different providence is guaranteed to be the comparison of the pointer addresses. + However, the addresses that are selected for allocations is unspecified. - Pointer equality or ordering is only meaningful when both pointers are derived from the same allocated object or block of memory. Comparisons across unrelated allocations are semantically meaningless and must be avoided. + Pointer equality or ordering is only meaningful when both pointers are derived from the same allocated object or block of memory. + Comparisons across unrelated allocations are semantically meaningless and must be avoided. .. non_compliant_example:: :id: non_compl_ex_c5NpFUId5lMo :status: draft - This noncompliant example allocates two local ``u32`` variables on the stack. The order of these two variables in memory is unspecified behavior. The code then creates a raw pointer to ``v2`` and a raw pointer to ``v1``. Adds the address stored in ``v1`` to 1 × ``size_of::()`` = 4 bytes using `wrapping_offset `__ which: + This noncompliant example allocates two local ``u32`` variables on the stack. The order of these two variables in memory is unspecified behavior. + The code then creates a raw pointer to ``v2`` and a raw pointer to ``v1``. + Adds the address stored in ``v1`` to 1 × ``size_of::()`` = 4 bytes using + `wrapping_offset `__ which: - ignores provenance - may produce an arbitrary, invalid, or meaningless pointer - is always allowed but does not guarantee the pointer points to anything valid - Comparing two `values `__ of `raw pointer types `__ compares the addresses of the `values `__. + Comparing two `values `__ of `raw pointer types + `__ compares the addresses of the + `values `__. - This code then compares ptr (a pointer to ``v2``) with ``ptr2`` (a pointer to ``v1`` + 4 bytes). Because the stack layout is unspecified behavior, the result of this comparison depends on how the compiler the memory layout for ``v1`` and ``v2`` on the stack. The result may change across: + This code then compares ptr (a pointer to ``v2``) with ``ptr2`` (a pointer to ``v1`` + 4 bytes). + Because the stack layout is unspecified behavior, + the result of this comparison depends on how the compiler the memory layout for ``v1`` and ``v2`` on the stack. + The result may change across: - compiler versions - optimization levels @@ -111,7 +133,8 @@ Types and Traits - small code changes - builds with or without link-time optimization - This noncompliant example does not contain undefined behavior (because no pointer is dereferenced) but it does depend on unspecified behavior, meaning that the program is valid, but the results are undefined. + This noncompliant example does not contain undefined behavior (because no pointer is dereferenced) but it does depend on unspecified behavior, + meaning that the program is valid, but the results are undefined. .. code-block:: rust @@ -132,9 +155,19 @@ Types and Traits :id: compl_ex_pBPeA9tBOnxj :status: draft - This compliant example creates a mutable array of 16 bytes on the stack where all bytes are zero-initialized. The entire array is one contiguous allocation. The code creates a raw pointer ``p`` of type ``*const u8`` to the first element of the array (that is, ``buf[0]``). The ptr ``p`` points at the start of the allocation. The code then uses pointer arithmetic to compute a pointer ``q`` which points 4 elements past ``p``. Because the element type is ``u8``, this means “4 bytes past ``p``\ ”. The pointer arithmetic is safe as long as the resulting pointer stays within the same allocation (it does). This is permitted because pointer arithmetic is allowed within the same allocated object. + This compliant example creates a mutable array of 16 bytes on the stack where all bytes are zero-initialized. + The entire array is one contiguous allocation. + The code creates a raw pointer ``p`` of type ``*const u8`` to the first element of the array (that is, ``buf[0]``). + The ptr ``p`` points at the start of the allocation. + The code then uses pointer arithmetic to compute a pointer ``q`` which points 4 elements past ``p``. + Because the element type is ``u8``, this means “4 bytes past ``p``\ ”. + The pointer arithmetic is safe as long as the resulting pointer stays within the same allocation (it does). + This is permitted because pointer arithmetic is allowed within the same allocated object. - Finally, the code compares the numerical address values of ``p`` and ``q``. Pointer comparison is always allowed. Comparing pointers from the same allocation is meaningful and defined. Because ``p`` points to the beginning and ``q`` to a later part of the same array, ``same_block`` becomes ``true``. + Finally, the code compares the numerical address values of ``p`` and ``q``. + Pointer comparison is always allowed. + Comparing pointers from the same allocation is meaningful and defined. + Because ``p`` points to the beginning and ``q`` to a later part of the same array, ``same_block`` becomes ``true``. .. code-block:: rust @@ -142,4 +175,4 @@ Types and Traits let p = buf.as_ptr(); let q = unsafe { p.add(4) }; - let same_block = p < q; // ok: comparison within same allocation \ No newline at end of file + let same_block = p < q; // ok: comparison within same allocation From 2276188cfffde007543d0336d5506b92d66f6a2b Mon Sep 17 00:00:00 2001 From: "Robert C. Seacord" Date: Mon, 8 Dec 2025 10:57:42 -0500 Subject: [PATCH 3/5] Update types-and-traits.rst added new noncompliant / compliant solution --- src/coding-guidelines/types-and-traits.rst | 60 +++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits.rst index 47d46a08..fd9f6b89 100644 --- a/src/coding-guidelines/types-and-traits.rst +++ b/src/coding-guidelines/types-and-traits.rst @@ -105,11 +105,69 @@ Types and Traits Pointer equality or ordering is only meaningful when both pointers are derived from the same allocated object or block of memory. Comparisons across unrelated allocations are semantically meaningless and must be avoided. + .. non_compliant_example:: + :id: non_compl_ex_c5NpFUId5lMp + :status: draft + + This noncompliant example creates a mutable raw pointer and a shared reference to ``x``, + and derives a raw pointer from that shared reference. + The shared reference ``shrref`` is converted to a raw constant pointer, and then to a raw mutable pointer. + + This produces another raw pointer ``shrptr`` to the same memory location ``x``, but its provenance is different: + - ``ptr`` is derived from from ``&mut x`` + - ``shrptr`` is derived from a shared reference ``&x`` + + As a result, this noncompliant example has undefined behavior when writing ``x`` through the shared reference ``shrptr`` + attempting a write access using using a tag that only grants ``SharedReadOnly`` permission for this location. + + .. code-block:: rust + + fn main() { + unsafe { + let mut x = 5; + // Setup a mutable raw pointer and a shared reference to `x`, + // and derive a raw pointer from that shared reference. + let ptr = &mut x as *mut i32; + let shrref = &*ptr; + let shrptr = shrref as *const i32 as *mut i32; + // `ptr` and `shrptr` point to the same address. + assert_eq!(ptr, shrptr); + // Writing `x` through `shrptr` is undefined behavior + shrptr.write(0); + println!("x = {}", x); + } + } + + .. compliant_example:: + :id: compl_ex_pBPeA9tBOnxk + :status: draft + + This compliant example eliminates the undefined behavior by writing to ``x`` through ``ptr`` which is derived directly from ``&mut x``. + + .. code-block:: rust + + fn main() { + unsafe { + let mut x = 5; + // Setup a mutable raw pointer and a shared reference to `x`, + // and derive a raw pointer from that shared reference. + let ptr = &mut x as *mut i32; + let shrref = &*ptr; + let shrptr = shrref as *const i32 as *mut i32; + // `ptr` and `shrptr` point to the same address. + assert_eq!(ptr, shrptr); + // Eliminate UB by writing through `ptr` + ptr.write(0); + println!("x = {}", x); + } + } + .. non_compliant_example:: :id: non_compl_ex_c5NpFUId5lMo :status: draft - This noncompliant example allocates two local ``u32`` variables on the stack. The order of these two variables in memory is unspecified behavior. + This noncompliant example allocates two local ``u32`` variables on the stack. + The order of these two variables in memory is unspecified behavior. The code then creates a raw pointer to ``v2`` and a raw pointer to ``v1``. Adds the address stored in ``v1`` to 1 × ``size_of::()`` = 4 bytes using `wrapping_offset `__ which: From 67d8c046895453cbcd106ea5bb96af0c7ae58ade Mon Sep 17 00:00:00 2001 From: "Robert C. Seacord" Date: Thu, 11 Dec 2025 14:05:10 -0500 Subject: [PATCH 4/5] Update types-and-traits.rst --- src/coding-guidelines/types-and-traits.rst | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits.rst index fd9f6b89..9f942adb 100644 --- a/src/coding-guidelines/types-and-traits.rst +++ b/src/coding-guidelines/types-and-traits.rst @@ -73,12 +73,12 @@ Types and Traits :scope: system :tags: surprising-behavior - Do not access memory using a pointer with an incorrect provenance. + Do not access memory using a pointer with an incorrect `provenance `__. Pointers, including values of reference type, have two components. The pointer’s address identifies the memory location where the pointer is currently pointing. - The pointer’s provenance determines where and when the pointer is allowed to access memory. + The pointer’s provenance determines where and when the pointer is allowed to access memory and if it is allowed to mutate the memory. - Whether a memory access with a given pointer causes undefined behavior (UB) depends on both the address and the provenance: + Whether a memory access with a given pointer causes undefined behavior (UB) depends on both the address and the provenance; the same address can access memory with one provenance but have undefined behavior with another provenance. Pointer comparisons are permitted only when both pointers are guaranteed to reference the same allocation. @@ -89,8 +89,8 @@ Types and Traits - Assumed field layout of structs without ``repr(C)`` or ``repr(packed)`` - Outcomes of pointer arithmetic across allocation boundaries - This rule ignores any metadata that may come with wide pointers; - it only pertains to thin pointers and the data part of a wide pointer. + This rule ignores any `metadata `__ that may come with wide pointers; + it only pertains to thin pointers and the address part of a wide pointer. .. rationale:: :id: rat_AFpgNhAMQ4eC From a016d71758468b836d1bacb3e3ca594d8dc40ef5 Mon Sep 17 00:00:00 2001 From: "Robert C. Seacord" Date: Thu, 11 Dec 2025 14:32:08 -0500 Subject: [PATCH 5/5] Update types-and-traits.rst link to unstable docs as here the current kinds of metadata are explained. --- src/coding-guidelines/types-and-traits.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coding-guidelines/types-and-traits.rst b/src/coding-guidelines/types-and-traits.rst index ea207ed7..a3e2b694 100644 --- a/src/coding-guidelines/types-and-traits.rst +++ b/src/coding-guidelines/types-and-traits.rst @@ -161,7 +161,7 @@ Types and Traits - Assumed field layout of structs without ``repr(C)`` or ``repr(packed)`` - Outcomes of pointer arithmetic across allocation boundaries - This rule ignores any `metadata `__ that may come with wide pointers; + This rule ignores any `metadata `__ that may come with wide pointers; it only pertains to thin pointers and the address part of a wide pointer. .. rationale::