C string from FFI getting strange symbols

#1

I’m having problem with my wrapper of a C library
The following code:

let val = conf.value_get(sub_key).unwrap();
println!("value:{}", val);

Gives me this strange output value:�
If I add println!(); before this code i get the expected result value:10.
value_get() is a wrapper for a C function that returns a C string. It look like that.

pub fn value_get(&self, Id(key): Id) -> Result<&str, Error> {
    let mut size = 0;
    if !unsafe { clingo_configuration_value_get_size(&self.0, key, &mut size) } {
        Err(ClingoError::new(
            "Call to clingo_configuration_value_get_size() failed.",
        ))?
    }
    let mut value = Vec::<i8>::with_capacity(size);
    let value_ptr = value.as_mut_ptr();
    if !unsafe { clingo_configuration_value_get(&self.0, key, value_ptr, size) } {
        Err(ClingoError::new(
            "Call to clingo_configuration_value_get() failed.",
        ))?
    }
    let cstr = unsafe { CStr::from_ptr(value_ptr) };
    Ok(cstr.to_str()?)
}

Does anyone know what is going wrong?

#2

The value vec is dropped at the end of value_get, so you’re reading freed memory. You probably want to read data into a Vec<u8>, and then return a CString::new off that vec.

1 Like
#3

Thanks i rewrote the function like this.

pub fn value_get(&self, Id(key): Id) -> Result<String, Error> {
    let mut size = 0;
    if !unsafe { clingo_configuration_value_get_size(&self.0, key, &mut size) } {
        Err(ClingoError::new(
            "Call to clingo_configuration_value_get_size() failed.",
        ))?
    }
    let mut value = Vec::<i8>::with_capacity(size);
    let value_ptr = value.as_mut_ptr();
    if !unsafe { clingo_configuration_value_get(&self.0, key, value_ptr, size) } {
        Err(ClingoError::new(
            "Call to clingo_configuration_value_get() failed.",
        ))?
    }
    let value_ref =
        unsafe { std::slice::from_raw_parts(value_ptr as *const u8, size) };
    Ok(String::from_utf8(value_ref.to_owned())?)
}

I got a few questions.
Is it clear that value is not yet dropped before the end of the function?
from_raw_parts() creates a second reference to value … might this cause a double free problem?
I think to_owned() makes a copy and from_utf8() makes another copy … maybe this can be done better?

#4

Vec has (indirectly) an explicit Drop impl, which is guaranteed to only run at the end of the scope where the vec was defined (end of value_get() in this case).

The more straightforward code would be:

...
let mut value = Vec::<u8>::with_capacity(size);
if !unsafe { clingo_configuration_value_get(&self.0, key, value.as_mut_ptr() as *mut c_char, size) } {
     Err(ClingoError::new(
         "Call to clingo_configuration_value_get() failed.",
     ))?
}
Ok(String::from_utf8(value)?)
#5

With this i get an only an empty string. I guess because the Vec is not filled via Vec methods. I probably should use vec![0;size] instead of with_capacity().

#6

Oh sorry, I left out a value.set_len(size) call. But yeah, you can just zero-fill on construction instead (this is safer and likely won’t make any perf difference here).

3 Likes