-
Notifications
You must be signed in to change notification settings - Fork 9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
To what extent (if any) are allocator safety requirements relaxed by installing a *specific* global allocator? #99
Comments
I think it should be UB because the compiler handles However what you can do is call the global allocator directly, which is not UB since it doesn't go through the #[no_mangle]
unsafe extern "C" fn malloc(size: usize) -> *mut u8 {
GlobalDlmalloc.alloc(Layout::from_size_align_unchecked(size, 16))
}
#[no_mangle]
unsafe extern "C" fn realloc(ptr: *mut u8, size: usize) -> *mut u8 {
if ptr.is_null() {
let layout = Layout::from_size_align_unchecked(size, 16);
GlobalDlmalloc.alloc(layout)
} else {
// dlmalloc doesn't actually look at the passed in layout
let dummy_layout = Layout::from_size_align_unchecked(0, 16);
GlobalDlmalloc.realloc(ptr, dummy_layout, size)
}
}
#[no_mangle]
unsafe extern "C" fn free(ptr: *mut u8) {
if !ptr.is_null() {
// dlmalloc doesn't actually look at the passed in layout
let dummy_layout = Layout::from_size_align_unchecked(0, 16);
GlobalDlmalloc.dealloc(ptr, dummy_layout)
}
} |
[Apologies if parts of this are incoherent. I'm still adjusting to a new medication. I think I wrote something worthwhile in here...] I think my personally desired answer to “what is guaranteed about
Allowing is probably just a matter of specifying a small set of operational possibilities:
Two wants to poorly segue into the below:
“If you check it was, it was; if you don't check, it might not've been.” Which now sounds kinda bad, but I already wrote the below and it has some good bits... [The beginning of this post was originally quite different, but writing the below refined my understanding significantly enough that it was unacceptably inaccurate. Everything below is still interesting and at least somewhat relevant, so forgive the non sequitur.] At an absolute minimum, exposing allocation addresses (in the strict-pointer sense) is sufficient to allow proving whether an allocation was done by a concrete allocator, as one exposed address range overlaps the other exposed address range. Caveats about provenance still apply, but pointer comparisons are still stable, so it's still at that address. IMHO the main question here is basically: does observing the address of a Derivative: does there exist a way to observe overalignment of a pointer without observing the address for the purpose of eliding heap allocation? Derivative: do safe operations between pointers to independently allocated objects such as pointer comparison observe the address sufficiently to prevent eliding heap allocation? (Perhaps this is a reason why such comparisons are UB in C/C++?) Disclaimer: following is significantly more just my personal interpretation and opinion, and I am not making a formalized argument. Additionally, I do see the benefit in still being able to replace (not just omit) global allocations which have been * Two very important caveats:
The notable thing which can be used to determine if There effectively exists an “allocation plane” for each “rust allocated object” (e.g. miri's AllocId). When comparing pointers between separate “planes,” you get the order between planes, which is arbitrary but consistent. When you observe8 the address of an allocation, this forces the allocation to exist on a shared “concrete” allocation plane, and separate allocations live on the same plane must not overlap (have distinct addresses). Each allocation exists on a single plane only; when I say an operation "concretizes" an address/allocation, this applies retroactively. The purpose of this scheme is that separate “rust allocated objects” can have overlapping addresses if the address is not observed. This is what allows us to remove any (even stack) allocations in the first place in the face of finite memory (since memory exhaustion is an observable event via allocation failure) without making code authors deal with the possibility of seeing overlapping allocations. (You can still indirectly observe this, of course.) My main observation here is that this scheme also allows us to describe how the global allocation boundary functions when it creates a new “rust allocated object” (w.r.t. properties of the address). This could potentially additionally be used to describe the behavior of Footnotes
|
If a user installs a
#[global_allocator]
that has less strict safety requirements than those described in the documentation for the allocator, does this in turn relax what (e.g)alloc::alloc::dealloc
considers UB?For example, our allocators require accurate size/align to be provided when deallocating, but many allocators do not need this information. If a user has explicitly installed a specific
global_allocator
that does not care about size/align when deallocating, is it still UB for them to pass an incorrect layout toalloc::alloc::dealloc
?Naïvely, my suspicion is that the answer is "yes, it's still UB" (because the requirements of that function say so), but this seems... rather unfortunate, for various reasons1.
If it is UB, does that mean it's always UB to rely that a specific
global_allocator
is in use?That is, it seems reasonable to me for a Rust library embedded in a larger C/C++ program to configure its
#[global_allocator]
to be the same one as used by the larger program, and then to rely on this by having one language free pointers allocated from another.I've actually seen that done in several cases, as can both simplify code and improve performance2 (although perhaps it's somewhat unhygenic). So it would be nice to support (obviously only in the case where the user knows for certain that a specific
global_allocator
is in use), and I don't think there's any benefit we get from forbidding it.Footnotes
For example, maintaining this information can often cause require additional bookkeeping, and can prevent the use from reusing an allocation which would otherwise be allowed. ↩
For example, avoiding the need to allocate a fresh buffer and copy data into it. ↩
The text was updated successfully, but these errors were encountered: