1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
//! ## why is this sound
//!
//! To withstand this future I'm raving about in the readme, we
//! return to the old ways.
//!
//! C defines the `printf` format specifier `p` something like
//!
//! > The argument shall be a pointer to void. The value of the pointer
//! > is converted to a sequence of printing characters, in an
//! > implementation-defined manner.
//!
//! and the `scanf` format specifier `p` like
//!
//! > Matches an implementation-defined set of sequences, which should
//! > be the same as the set of sequences that may be produced by the %p
//! > conversion of the fprintf function. The corresponding argument
//! > shall be a pointer to a pointer to void. The input item is
//! > converted to a pointer value in an implementation-defined manner.
//! > If the input item is a value converted earlier during the same
//! > program execution, the pointer that results shall compare equal to
//! > that value; otherwise the behavior of the %p conversion is
//! > undefined.
//!
//! Once you're at "shall compare equal", you pretty much can't get out
//! of the pointers being usable interchangeably, or the wording for
//! casting to `void*` and back doesn't work.
//!
//! We additionally note that glibc documents that non-null pointers are
//! printed as hex integers. I'm pretty sure musl does about the same
//! thing but I haven't tested with musl.
//!
//! So, we `%p`-print a pointer and parse the result as an integer and
//! there's our very defensible integer representation of a pointer. For
//! the inverse, we print the integer in that exact hexadecimal form,
//! and then `%p`-parse it back into a pointer, and we have the great,
//! very defensible argument that we can actually use this pointer
//! because that's what the `scanf` docs say. The rust memory model
//! can hardly say that C stops working as defined, right?
use std::{mem, ptr};
pub fn ptr2int<T>(p: *const T) -> u64 {
if p.is_null() { return 0; }
unsafe {
let mut buf = [0i8; 32];
let r = libc::snprintf(
buf.as_mut_ptr(),
32,
"%p\0".as_ptr() as *const _,
p as *const libc::c_void);
if r >= 32 {
panic!("too long");
}
let mut n = mem::MaybeUninit::new(0u64);
let r = libc::sscanf(
buf.as_ptr(),
"%lx\0".as_ptr() as *const i8,
ptr::addr_of_mut!(n) as *mut _);
if r != 1 {
panic!("yr platform isnt supported");
}
n.assume_init()
}
}
pub fn int2ptr<T>(n: u64) -> *const T {
int2ptr_mut(n)
}
pub fn int2ptr_mut<T>(n: u64) -> *mut T {
if n == 0 { return ptr::null_mut(); }
unsafe {
let mut buf = [0i8; 32];
let r = libc::snprintf(
buf.as_mut_ptr(),
32,
"0x%lx\0".as_ptr() as *const _,
n);
if r >= 32 {
panic!("too long");
}
let mut p = mem::MaybeUninit::new(ptr::null_mut());
let r = libc::sscanf(
buf.as_ptr(),
"%p\0".as_ptr() as *const i8,
ptr::addr_of_mut!(p) as *mut *mut libc::c_void);
if r != 1 {
panic!("yr platform isnt supported");
}
p.assume_init()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let stuff = "it definitely works".to_string();
let n = ptr2int(ptr::addr_of!(stuff));
let p: *const String = int2ptr(n);
let stuff = unsafe { &*p };
assert_eq!(stuff, "it definitely works");
}
#[test]
fn the_thing_from_the_readme() {
use crate as totally_sound_ptr_int_cast;
use std::thread;
let your_boxed_stuff = Box::new(42.0);
let n = totally_sound_ptr_int_cast::ptr2int(
Box::into_raw(your_boxed_stuff));
// lets do the "any later point" in a silly way
thread::spawn(move || {
let p = totally_sound_ptr_int_cast::int2ptr_mut(n);
let s1 = unsafe { format!("{:?}", *p) };
let my_boxed_stuff: Box<f64> = unsafe { Box::from_raw(p) };
let s2 = format!("{:?}", my_boxed_stuff);
assert_eq!(s1, s2);
}).join().unwrap();
}
}