Skip to content

Storing a function pointer in a const usize works on nightly #51559

Description

@gnzlbg

Some people were of the opinion that this shouldn't work, but it currently does. playground:

#![feature(atomic_integers, const_fn)]
use std::sync::atomic::AtomicUsize;
use std::sync::atomic::Ordering;

const unsafe fn transmute(x: *mut u8) -> usize {
    union T {
        a: *mut u8,
        b: usize
    }
    T { a: x }.b
}

const BAR: *mut u8 = ((|| 3) as fn() -> i32) as *mut u8;
const FOO: AtomicUsize = AtomicUsize::new(unsafe { transmute(BAR) });
// static FOO: AtomicUsize = AtomicUsize::new(unsafe { transmute(BAR) }); // ALSO OK
// const BAZ: AtomicUsize = AtomicUsize::new(BAR as usize); ERRORs

fn main() {
    let l = FOO.load(Ordering::Relaxed);
    let l: fn() -> i32 = unsafe { std::mem::transmute(l) };
    assert_eq!(l(), 3);
}

cc @rkruppe @eddyb @oli-obk

Activity

  1. changed the title [-]Storing a function pointer in a static usize works on nightly[/-] [+]Storing a function pointer in a const usize works on nightly[/+] on Jun 14, 2018
  2. gnzlbg commented on Jun 14, 2018

    @gnzlbg
    ContributorAuthor
  3. oli-obk commented on Jun 14, 2018

    @oli-obk
    Contributor

    Some people were of the opinion that this shouldn't work

    It should work, it's just super scary.

    EDIT: it should not and does not work. If we allowed this, various surprising things can happen, especially around implicit promotion. If you want to store pointers and integers, you can use a raw pointer.

  4. scottmcm commented on Jun 15, 2018

    @scottmcm
    Member

    It should work

    For my own education, can you elaborate on why function pointers are ok? I'd thought that the runtime addresses of things -- including functions -- shouldn't be accessible at const-time since we don't know where they'll live at runtime. Could I make a const array that usize long?

  5. oli-obk commented on Jun 15, 2018

    @oli-obk
    Contributor

    When compiling to LLVM, the real function handle or pointer address is obtained. If you try doing anything weird with pointers at compile time, you will quickly notice that you are not at runtime. For example, you cannot divide such a usize by anything. You can add or subtract integers, because that's just pointer offsetting, but you can't inspect the address in any way.

    This also means that no, you cannot use this usize for array lengths or enum discriminants. This is due to the fact that miri pointers are not just addresses, but abstract pointers that are in a separate layer from the bytes of normal memory like the one of integers.

  6. scottmcm commented on Jun 15, 2018

    @scottmcm
    Member

    Thanks for the explanation.

    I guess that means that such a usize couldn't be passed to a const generic either?

  7. gnzlbg commented on Jun 15, 2018

    @gnzlbg
    ContributorAuthor

    I guess that means that such a usize couldn't be passed to a const generic either?

    The thing is const generics don't know where the usize comes from, so they could try to do something with it that is not allowed if it is just a pointer address. Until monomorphization we can't know if there is an issue.

    Note that converting a pointer to an usize and back just to be able to put it in const, statics, or use it with const generics, is a real pain. We would be better off in this case with an AtomicPtr<T> type, such that I can write AtomicPtr<fn()->i32> and avoid the conversion to usize.

    For const generics, C++ accepts function pointers at the type level and that works just fine with clang, so I expect fn bar<const PTR: fn()->i32>() {} to work fine as well.

  8. hanna-kruppe commented on Jun 15, 2018

    @hanna-kruppe
    Contributor

    How would AtomicPtr<T> work?

    • is AtomicPtr<i32> disallowed but AtomicPtr<*const i32> and AtomicPtr<fn(&str) -> &str> work? that seems very weird and special cased (especially for fn types that involve higher rank lifetimes, as in my example)
    • or is AtomicPtr<T> ~= *mut T? then AtomicPtr<fn()> is a double indirection and to use it you need to worry about managing the memory it points to (which contains the fn())
  9. gnzlbg commented on Jun 15, 2018

    @gnzlbg
    ContributorAuthor

    How would AtomicPtr work?

    I haven't thought this through beyond having used the atomic pointers in the C++ standard library: http://en.cppreference.com/w/cpp/atomic/atomic

    One way to implement this could be AtomicPtr<T> where T: AtomicPtrConstraint where we provide blanket impls for &'a, &'a mut, *const, *mut, fn() -> T, fn(T) -> U, fn(T, U) -> V, ... or use some compiler magic to achieve a similar effect...

  10. eddyb commented on Jun 15, 2018

    @eddyb
    Contributor

    This is a bit offtopic, but regarding AtomicPtr, @nikomatsakis mentioned at least once the possibility of introducing some new type, to make fn(A) -> B sugar for &ThatType<(A,), B>.
    I guess it can't entirely be "sugar", because of existing impls, but at least both could work the same.

  11. RalfJung commented on Jun 19, 2018

    @RalfJung
    Member

    Hehe, this is cute. :) And I agree it is working as intended. (Nice that the translation to LLVM actually gets this right, should there be a testcase to make sure it stays that way? :D )

  12. added
    E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.
    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)
    on Jun 19, 2018
  13. oli-obk commented on Jun 20, 2018

    @oli-obk
    Contributor

    So... other than testing this, all we need is a PR that adds *const T -> usize casts in constants under a feature gate (and makes those casts unsafe in constants and const fn)

  14. gnzlbg commented on Jun 20, 2018

    @gnzlbg
    ContributorAuthor

    I've opened rust-lang/rfcs#2481 to brainstorm how to either add an AtomicFnPtr type or retrofit AtomicPtr to support fn items.

  15. oli-obk commented on Jan 28, 2019

    @oli-obk
    Contributor

    These kind of operations are now unsafe in const contexts (impl PR: #55009)

  16. RalfJung commented on Jan 28, 2019

    @RalfJung
    Member

    So do we have a test that you cannot use such usize as array length? :D

  17. oli-obk commented on Jan 28, 2019

    @oli-obk
    Contributor

    hmm... apparently not...

    Test-instructions:

    1. use transmute (via the const_transmute feature gate) to turn various kinds of references and function pointers into usize
    2. use these expressions in
      • array length,
      • repeat lengths
      • and enum discriminant initializers (e.g. enum Foo { Bar = unsafe { transmute(main) } })

    Note: Do not use const items, everything needs to be inline in the use site to be properly tested

  18. added
    E-easyCall for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.
    on Jan 28, 2019
  19. kazcw commented on May 10, 2019

    @kazcw
    Contributor

    It seems this is no longer allowed by any means:

    #![feature(const_raw_ptr_to_usize_cast)]
    
    const BAR: *mut () = ((|| 3) as fn() -> i32) as *mut ();
    pub const FOO: usize = unsafe { BAR as usize };
    
    fn main() {}
    
       Compiling playground v0.0.1 (/playground)
    error[E0080]: it is undefined behavior to use this value
     --> src/main.rs:4:1
      |
    4 | pub const FOO: usize = unsafe { BAR as usize };
      | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ type validation failed: encountered a pointer, but expected initialized plain (non-pointer) bytes
      |
      = note: The rules on what exactly is undefined behavior aren't clear, so this check might be overzealous. Please open an issue on the rust compiler repository if you believe it should not be considered undefined behavior
    
    error: aborting due to previous error
    
    For more information about this error, try `rustc --explain E0080`.
    error: Could not compile `playground`.
    
    To learn more, run the command again with --verbose.
    
  20. oli-obk commented on May 10, 2019

    @oli-obk
    Contributor

    Yes that is intended. This issue is for adding a regression test testing what happens when you use a pointer casted to a usize in an array length.

  21. added a commit that references this issue on Jul 24, 2019
  22. added a commit that references this issue on Jul 25, 2019
  23. added a commit that references this issue on Jul 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-const-evalArea: Constant evaluation, covers all const contexts (static, const fn, ...)E-easyCall for participation: Easy difficulty. Experience needed to fix: Not much. Good first issue.E-needs-testCall for participation: An issue has been fixed and does not reproduce, but no test has been added.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions