Start addressing some bugs in new Rust code #10

Merged
trurl merged 4 commits from trurl/wmaker:refactor/riir into refactor/riir 2025-12-15 12:17:23 -05:00
11 changed files with 132 additions and 51 deletions

View File

@@ -39,7 +39,7 @@ ACLOCAL_AMFLAGS = -I m4
AM_DISTCHECK_CONFIGURE_FLAGS = --enable-silent-rules LINGUAS='*'
SUBDIRS = wrlib wutil-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc
SUBDIRS = wrlib wutil-rs wings-rs WINGs wmaker-rs src util po WindowMaker wmlib WPrefs.app doc
DIST_SUBDIRS = $(SUBDIRS) test
EXTRA_DIST = TODO BUGS BUGFORM FAQ INSTALL \

View File

@@ -156,12 +156,7 @@ typedef struct {
/* DO NOT ACCESS THE CONTENTS OF THIS STRUCT */
typedef struct {
void *table;
void *nextItem;
int index;
} WMHashEnumerator;
typedef struct WMHashEnumerator WMHashEnumerator;
typedef struct {
@@ -334,7 +329,7 @@ void WMDeleteInputHandler(WMHandlerID handlerID);
void WHandleEvents(void);
/* ---[ WINGs/hashtable.c ]----------------------------------------------- */
/* ---[ wutil-rs/src/hash_table.rs ]----------------------------------------------- */
WMHashTable* WMCreateIdentityHashTable();
@@ -366,7 +361,7 @@ void* WMHashInsert(WMHashTable *table, const void *key, const void *data);
void WMHashRemove(WMHashTable *table, const void *key);
/* warning: do not manipulate the table while using the enumerator functions */
WMHashEnumerator WMEnumerateHashTable(WMHashTable *table);
WMHashEnumerator* WMEnumerateHashTable(WMHashTable *table);
void* WMNextHashEnumeratorItem(WMHashEnumerator *enumerator);
@@ -381,7 +376,7 @@ Bool WMNextHashEnumeratorItemAndKey(WMHashEnumerator *enumerator,
void **item, void **key);
void WMFreeHashEnumerator(WMHashEnumerator *enumerator);
/* some predefined callback sets */

View File

@@ -456,12 +456,12 @@ static void handleEvents(XEvent * event, void *data)
static void destroyBalloon(Balloon * bPtr)
{
WMHashEnumerator e;
WMHashEnumerator *e;
char *str;
e = WMEnumerateHashTable(bPtr->table);
while ((str = WMNextHashEnumeratorItem(&e))) {
while ((str = WMNextHashEnumeratorItem(e))) {
wfree(str);
}
WMFreeHashTable(bPtr->table);
@@ -472,5 +472,7 @@ static void destroyBalloon(Balloon * bPtr)
if (bPtr->font)
WMReleaseFont(bPtr->font);
WMFreeHashEnumerator(e);
wfree(bPtr);
}

View File

@@ -520,7 +520,7 @@ static void listFamilies(WMScreen * scr, WMFontPanel * panel)
FcFontSet *fs;
FcPattern *pat;
WMHashTable *families;
WMHashEnumerator enumer;
WMHashEnumerator *enumer;
WMArray *array;
int i;
@@ -551,7 +551,8 @@ static void listFamilies(WMScreen * scr, WMFontPanel * panel)
enumer = WMEnumerateHashTable(families);
while ((array = WMNextHashEnumeratorItem(&enumer))) {
while ((array = WMNextHashEnumeratorItem(enumer))) {
printf("listFamilies: got family with %d items\n", WMGetArrayItemCount(array));
WMArrayIterator i;
Family *fam;
char buffer[256];

View File

@@ -959,6 +959,7 @@ AC_CONFIG_FILES(
dnl Rust implementation of WINGs libraries
wutil-rs/Makefile
wings-rs/Makefile
dnl WINGs toolkit
WINGs/Makefile WINGs/WINGs/Makefile WINGs/po/Makefile

View File

@@ -89,6 +89,8 @@ Xephyr -screen 640x480 "$xephyr_display" &
xephyr_pid=$!
DISPLAY="$xephyr_display" gdb \
--directory "$project_base" \
--directory "$HOME/src/libX11-1.5.0/build/src" \
--quiet \
--fullname \
--args "$WindowMaker" -display "$xephyr_display" --for-real "$@"
kill $xephyr_pid

7
wings-rs/Cargo.toml Normal file
View File

@@ -0,0 +1,7 @@
[package]
name = "wings-rs"
version = "0.1.0"
edition = "2024"
[dependencies]
wutil-rs = { path = "../wutil-rs" }

19
wings-rs/Makefile.am Normal file
View File

@@ -0,0 +1,19 @@
AUTOMAKE_OPTIONS =
RUST_SOURCES = \
src/lib.rs
RUST_EXTRA = \
Cargo.lock \
Cargo.toml
target/debug/libwings_rs.a: $(RUST_SOURCES) $(RUST_EXTRA)
$(CARGO) build
check-local:
$(CARGO) test
clean-local:
$(CARGO) clean
all: target/debug/libwings_rs.a

14
wings-rs/src/lib.rs Normal file
View File

@@ -0,0 +1,14 @@
pub fn add(left: u64, right: u64) -> u64 {
left + right
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn it_works() {
let result = add(2, 2);
assert_eq!(result, 4);
}
}

View File

@@ -1,7 +1,7 @@
use std::{ffi::c_void, ptr::NonNull};
use std::ffi::c_void;
pub struct Array {
items: Vec<NonNull<c_void>>,
items: Vec<usize>,
trurl marked this conversation as resolved
Review

I had thought at one point that it might be nice to try to make this generic over some type T, and bifurcate usage between c_void and usize, but looking at the sheer volume of places this code is used, I think that would be tedious and premature. It's a bummer that we have to do a lot of pointer casting as a result, but ... c'est la vie.

I had thought at one point that it might be nice to try to make this generic over some type T, and bifurcate usage between `c_void` and `usize`, but looking at the sheer volume of places this code is used, I think that would be tedious and premature. It's a bummer that we have to do a lot of pointer casting as a result, but ... c'est la vie.
Review

On the bright side, ditching WMArray and WMHashTable will be possible soon-ish.

On the bright side, ditching `WMArray` and `WMHashTable` will be possible soon-ish.
destructor: Option<unsafe extern "C" fn(x: *mut c_void)>,
}
@@ -10,7 +10,7 @@ pub mod ffi {
use std::{
ffi::{c_int, c_void},
ptr::{self, NonNull},
ptr,
};
pub const NOT_FOUND: c_int = -1;
@@ -64,7 +64,7 @@ pub mod ffi {
let array = unsafe { &mut *array };
if let Some(f) = array.destructor {
for item in &mut array.items {
unsafe { (f)(item.as_ptr()) }
unsafe { (f)(*item as *mut c_void) }
}
}
array.items.clear();
@@ -94,10 +94,8 @@ pub mod ffi {
if array.is_null() {
return;
}
if let Some(item) = NonNull::new(item) {
unsafe {
(*array).items.push(item);
}
unsafe {
(*array).items.push(item.addr());
}
}
@@ -114,9 +112,7 @@ pub mod ffi {
if index >= array.len() {
return;
}
if let Some(item) = NonNull::new(item) {
array.insert(index, item);
}
array.insert(index, item.addr());
}
#[unsafe(no_mangle)]
@@ -141,15 +137,11 @@ pub mod ffi {
return ptr::null_mut();
}
let item = match NonNull::new(item) {
Some(x) => x,
None => return ptr::null_mut(),
};
let array = unsafe { &mut (*array).items };
let old = array[index];
array[index] = item;
old.as_ptr()
array[index] = item.addr();
old as *mut c_void
}
#[unsafe(no_mangle)]
@@ -168,7 +160,7 @@ pub mod ffi {
let old = array.items.remove(index);
if let Some(f) = array.destructor {
unsafe {
(f)(old.as_ptr());
(f)(old as *mut c_void);
}
}
1
@@ -192,8 +184,8 @@ pub mod ffi {
let array = unsafe { &mut *array };
let original_len = array.items.len();
match pred {
Some(f) => array.items.retain(|x| unsafe { f(x.as_ptr(), cdata) != 0 }),
None => array.items.retain(|x| ptr::eq(x.as_ptr(), cdata)),
Some(f) => array.items.retain(|x| unsafe { f(*x as *const c_void, cdata) != 0 }),
None => array.items.retain(|x| ptr::eq(*x as *mut c_void, cdata)),
}
(original_len - array.items.len()) as c_int
}
@@ -207,7 +199,7 @@ pub mod ffi {
(&(*array))
.items
.get(index as usize)
.map(|p| p.as_ptr())
.map(|p| *p as *mut c_void)
.unwrap_or(ptr::null_mut())
}
}
@@ -226,7 +218,7 @@ pub mod ffi {
(*array)
.items
.pop()
.map(|p| p.as_ptr())
trurl marked this conversation as resolved Outdated
Outdated
Review

Could you use .cast() here instead of as (and similarly elsewhere)?

Could you use `.cast()` here instead of `as` (and similarly elsewhere)?
Review

I'm not sure I can do this. Is there a .cast() method for converting between raw pointer and non-raw-pointer types? For example, most of the time this file uses as, it's casting a usize to some flavor of void*.

I'm not sure I can do this. Is there a `.cast()` method for converting between raw pointer and non-raw-pointer types? For example, most of the time this file uses `as`, it's casting a `usize` to some flavor of `void*`.
Review

oh, hmm. No, not really, then. In that case, I'd recommend using the "provenance" based functions, though: .with_addr and so forth, if that's reasonable: https://doc.rust-lang.org/std/ptr/index.html#strict-provenance

oh, hmm. No, not really, then. In that case, I'd recommend using the "provenance" based functions, though: `.with_addr` and so forth, if that's reasonable: https://doc.rust-lang.org/std/ptr/index.html#strict-provenance
Review

I don't see how I can reasonably use with_addr to retain provenance when converting values stored in the array from usize back to pointers. The problem lies in how this API is used by existing code: the caller may provide arbitrary integers to functions like WMInsertInArray. When those values are queried by C code, they may be cast back to integers or dereferenced. The knowledge of whether the value is a real address with proper provenance is only in the programmer's head. As you have suggested, I'd have to track something at runtime or split this into two different array types (one for usizes, one for * mut c_voids).

I do hope to be rid of WMArray sooner or later, so for now I think I'll just keep the casts as-is.

I don't see how I can reasonably use `with_addr` to retain provenance when converting values stored in the array from `usize` back to pointers. The problem lies in how this API is used by existing code: the caller may provide arbitrary integers to functions like `WMInsertInArray`. When those values are queried by C code, they may be cast back to integers or dereferenced. The knowledge of whether the value is a real address with proper provenance is only in the programmer's head. As you have suggested, I'd have to track something at runtime or split this into two different array types (one for `usize`s, one for `* mut c_void`s). I do hope to be rid of `WMArray` sooner or later, so for now I think I'll just keep the casts as-is.
.map(|p| p as *mut c_void)
.unwrap_or(ptr::null_mut())
}
}
@@ -246,7 +238,7 @@ pub mod ffi {
.items
.iter()
.enumerate()
.find(|(_, item)| unsafe { f(item.as_ptr(), cdata) != 0 })
.find(|(_, item)| unsafe { f(**item as *const c_void, cdata) != 0 })
.map(|(i, _)| i as c_int)
.unwrap_or(NOT_FOUND)
} else {
@@ -254,7 +246,7 @@ pub mod ffi {
.items
.iter()
.enumerate()
.find(|(_, item)| ptr::eq(item.as_ptr(), cdata))
.find(|(_, item)| ptr::eq(**item as *const c_void, cdata))
.map(|(i, _)| i as c_int)
.unwrap_or(NOT_FOUND)
}
@@ -269,7 +261,7 @@ pub mod ffi {
array
.items
.iter()
.filter(|x| ptr::eq(x.as_ptr(), item))
.filter(|x| ptr::eq(**x as *const c_void, item))
.count() as c_int
}
@@ -284,13 +276,17 @@ pub mod ffi {
unsafe {
(*array)
.items
.sort_by(|&a, &b| match comparator(a.as_ptr(), b.as_ptr()).signum() {
-1 => std::cmp::Ordering::Less,
0 => std::cmp::Ordering::Equal,
1 => std::cmp::Ordering::Greater,
_ => unreachable!(),
.sort_by(|a, b| {
let a = a as *const _ as *const c_void;
let b = b as *const _ as *const c_void;
match comparator(a, b).signum() {
-1 => std::cmp::Ordering::Less,
0 => std::cmp::Ordering::Equal,
1 => std::cmp::Ordering::Greater,
_ => unreachable!(),
}
})
}
}
}
#[unsafe(no_mangle)]
@@ -304,7 +300,7 @@ pub mod ffi {
}
unsafe {
for a in &mut (*array).items {
(f)(a.as_ptr(), data);
(f)(*a as *mut c_void, data);
}
}
}
@@ -326,7 +322,7 @@ pub mod ffi {
unsafe {
*iter = 0;
}
x.as_ptr()
*x as *mut c_void
}
}
}
@@ -348,7 +344,7 @@ pub mod ffi {
unsafe {
*iter = (array.items.len() - 1) as c_int;
}
x.as_ptr()
*x as *mut c_void
}
}
}
@@ -368,7 +364,7 @@ pub mod ffi {
unsafe {
*iter += 1;
}
i.as_ptr()
*i as *mut c_void
}
None => {
unsafe {
@@ -394,7 +390,7 @@ pub mod ffi {
unsafe {
*iter -= 1;
}
i.as_ptr()
*i as *mut c_void
}
None => {
unsafe {

View File

@@ -69,7 +69,6 @@ impl HashTable {
}
}
#[derive(Debug)]
#[repr(transparent)]
pub struct StringKey(*const u8);
@@ -85,6 +84,12 @@ impl PartialEq for StringKey {
impl Eq for StringKey {}
impl std::fmt::Debug for StringKey {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "StringKey({:?})", unsafe { std::ffi::CStr::from_ptr(self.0) })
}
}
impl Hash for StringKey {
fn hash<H: Hasher>(&self, h: &mut H) {
if self.0.is_null() {
@@ -291,4 +296,43 @@ pub mod ffi {
},
}
}
#[cfg(test)]
mod test {
use super::*;
use std::{ffi::CString, ptr};
#[test]
fn enumerate_nonempty() {
unsafe {
let table = WMCreateStringHashTable();
let k1 = CString::new("hello").unwrap().into_raw();
let v1 = CString::new("world").unwrap().into_raw();
let k2 = CString::new("foo").unwrap().into_raw();
let v2 = CString::new("bar").unwrap().into_raw();
WMHashInsert(table, k1.cast(), v1.cast());
WMHashInsert(table, k2.cast(), v2.cast());
let i = WMEnumerateHashTable(table);
let v = WMNextHashEnumeratorItem(i);
assert_ne!(v, ptr::null_mut());
assert!(v == v1.cast() || v == v2.cast());
let v = WMNextHashEnumeratorItem(i);
assert_ne!(v, ptr::null_mut());
assert!(v == v1.cast() || v == v2.cast());
let v = WMNextHashEnumeratorItem(i);
assert_eq!(v, ptr::null_mut());
WMFreeHashEnumerator(i);
let _ = CString::from_raw(k1);
let _ = CString::from_raw(v1);
let _ = CString::from_raw(k2);
let _ = CString::from_raw(v2);
}
}
}
}