Skip to content

Conversation

@bryant
Copy link

@bryant bryant commented Jun 7, 2017

bug was introduced in 877104f.

the intent here is to allocate enough space for gmpf_get_str to plop a string. if all digits are requested (n_digits == 0), we defer to gmpf_get_str itself to figure out how much space is needed (because the limb nonsense is too tedious to deal with) by passing nullptr to the first argument, as documented in the manual:

If str is NULL, the result string is allocated using the current allocation function (see Custom Allocation). The block will be strlen(str)+1 bytes, that being exactly enough for the string and null-terminator.

if the number of digits requested is known (non-zero n_digits), then simply allocate a correctly sized cstring (unlike 877104f, which allocates a 0-byte string).

to avoid memory leaks from repeated get_str(n_digits=0) calls, a wrapper is used to de-allocate on drop the gmp-allocated string.

with this patch, #24 passes.

bryant added 3 commits June 6, 2017 20:43
from https://gmplib.org/manual/Converting-Floats.html : `Function: char
* mpf_get_str (char *str, mp_exp_t *expptr, int base, size_t n_digits,
const mpf_t op)`
@bryant bryant force-pushed the mpf-get-str-fixups branch from 1113816 to e2c9bde Compare June 7, 2017 01:00
@bryant bryant mentioned this pull request Jun 7, 2017
src/mpf.rs Outdated
}
} else {
// From mpf/get_str.c.
let c_str = CString::new(vec![37; n_digits as usize + 2]).unwrap();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This intermediate CString is just overhead—why not use Vec<c_char>::as_ptr directly?

src/mpf.rs Outdated
let c_str = CString::new(vec![37; n_digits as usize + 2]).unwrap();
unsafe {
let p = __gmpf_get_str(c_str.as_ptr(), exp, base, n_digits, &self.mpf);
CString::from_raw(p).to_str().unwrap().to_string()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn’t this double-free the string, once via c_str and once via the CString? I think you want CStr::from_ptr.


pub fn to_str(&self) -> Result<&str, str::Utf8Error> {
let bytes: &[u8] = unsafe { slice::from_raw_parts(self.0, self.1) };
str::from_utf8(&bytes[..bytes.len() - 1])
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CStr::from_ptr is more direct and avoids the separate strlen.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no matter what, the length needs to be known in order to call the free func, so it's either strlen or CStr::to_bytes().len(). i think strlen communicates more clearly how the length param to free_func is computed, whereas the alternate method relies on knowing that CStr implicitly calls strlen behind the scenes.

src/mpf.rs Outdated
} else {
// From mpf/get_str.c.
let bytes = unsafe {
let mut bytes: Vec<u8> = uninitialized();
Copy link

@andersk andersk Jun 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need a Vec with uninitialized data, not an uninitialized Vec! I think Vec::with_capacity is the right thing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ugh, thanks for catching this.

src/mpf.rs Outdated
// From mpf/get_str.c.
let bytes = unsafe {
let mut bytes: Vec<u8> = uninitialized();
let c_str: *mut c_char = transmute(bytes.as_mut_ptr());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for a worrying transmute—you can just bytes.as_mut_ptr() as *mut c_char.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pretty sure that as_mut_ptr will return a * mut u8.

src/mpf.rs Outdated
__gmpf_get_str(c_str, exp, base, n_digits, &self.mpf);
bytes
};
String::from_utf8(bytes).unwrap()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That includes the trailing NUL, right?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep. from mpf/get_str.c:

  if (dbuf == 0)
    {
      /* We didn't get a string from the user.  Allocate one (and return
	 a pointer to it) with space for `-' and terminating null.  */
      alloc_size = n_digits + 2;
      dbuf = (char *) (*__gmp_allocate_func) (n_digits + 2);

src/mpf.rs Outdated
} else {
let bytes = unsafe {
// n_digits + 2 from mpf/get_str.c.
let mut bytes: Vec<u8> = vec![uninitialized(); n_digits as usize + 2];
Copy link

@andersk andersk Jun 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This create a single uninitialized byte, then copies it n_digits + 2 times, so you haven’t really saved any work.

I think what you want is

let mut buf = Vec::with_capacity(n_digits as usize + 2);
let ptr = __gmpf_get_str(buf.as_mut_ptr(), exp, base, n_digits, &self.mpf);
CStr::from_ptr(ptr).to_str().unwrap().to_string()

(prior art)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think i'll revert to vec![0; n_digits + 2], which will call rust_allocate_zeroed which i'm guessing is more or less calloc. the with_capacity trick is neat but seems a little unsafe, and to be honest, i'm not sure that enough digits are requested for an appreciable amount of work to be saved.

src/mpf.rs Outdated
__gmpf_get_str(c_str, exp, base, n_digits, &self.mpf);
bytes
};
String::from_utf8(bytes).unwrap()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no, my point here was that String::from_utf8 includes the trailing NUL in its return value.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i realized what you meant a few minutes after hitting send. this branch should be tested anyway, and it shows up in the test.

this incorrectly created an uninitialized Vec, and using uninitialized
isn't worth it anyway.
@bryant bryant force-pushed the mpf-get-str-fixups branch from 851473b to 9483bf6 Compare June 9, 2017 21:45
src/ffi.rs Outdated
use std::ptr::null_mut;
unsafe {
let mut free_func: FreeFunc = mem::uninitialized();
__gmp_get_memory_functions(null_mut(), null_mut(), mem::transmute(&mut free_func));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This transmute is unnecessary because &mut FreeFunc converts implicitly to *mut FreeFunc.

@bryant bryant force-pushed the mpf-get-str-fixups branch from 9483bf6 to 58d47c2 Compare June 10, 2017 03:45
@fizyk20
Copy link
Owner

fizyk20 commented Sep 3, 2017

Looks like this and #27 attempt to fix the same problem. I merged #27 before I had a look at your PR and now I think I might have done this too hastily. Your solution looks quite a bit more complex - do you see any obvious problems with how #27 attempts to do this?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants