rust-lang / rust

Empowering everyone to build reliable and efficient software.
https://www.rust-lang.org
Other
99.17k stars 12.8k forks source link

`alloc` methods are available in `#![no_std]` when running tests, despite not being imported. #99637

Open ilyvion opened 2 years ago

ilyvion commented 2 years ago

I was messing around in a test in a #[no_std] crate and discovered that this code:

#![no_std]

#[test]
fn foo() {
    let b = b"hello";
    let _v = b.to_vec();
}

(playground link)

compiles, which really surprised me, because [T]::to_vec() is defined in the alloc crate, and as you can see, I don't have any extern alloc; directive. It's even more surprising in that it only works in functions marked either #[test] or #[cfg(test)]. If you put it on a regular function (just remove #[test] from above), you get the expected error:

error[E0599]: no method named `to_vec` found for reference `&[u8; 5]` in the current scope
 --> src\lib.rs:5:16
  |
5 |     let _v = b.to_vec();
  |                ^^^^^^ method not found in `&[u8; 5]`

I expected this error to also happen in #[cfg(test)], but it doesn't. This is unexpected and surprising.

What makes it even more surprising is that alloc isn't actually in scope, because if you change let _v to let _v: alloc::Vec<_> in the code above, you'll be told as much.

ehuss commented 2 years ago

I think this is similar to #90907. This isn't exactly related to #[test], other than the fact that libtest gets linked in and it depends on std/alloc.

For example:

// foo.rs
#![no_std]
pub fn foo() {
    bar::x();
    let b = b"hello";
    let _v = b.to_vec();
}
// bar.rs
#![no_std]
extern crate alloc;

pub fn x() {}
rustc bar.rs --crate-type=rlib
# This succeeds
rustc foo.rs --crate-type=rlib --extern bar=libbar.rlib
ilyvion commented 2 years ago

I don't know if anyone else agrees, but this feels like some kind of boundary violation to me. If nothing else it cost me a bunch of time because I knew I wasn't supposed to have .to_vec() available to me, and yet it just kept on working. 😅

ilyvion commented 2 years ago

I'm seeing more strange behavior related to this odd alloc "leak." I had this problem:

error: cannot find macro `format` in this scope
   --> src\range_type.rs:246:14
    |
246 |             &format!("{}", invalid_deserialized_error.unwrap_err()),

(which makes sense, we're in #![no_std] right?) So I changed it to alloc::format!, but still:

error[E0433]: failed to resolve: use of undeclared crate or module `alloc`
   --> src\range_type.rs:246:14
    |
246 |             &alloc::format!("{}", invalid_deserialized_error.unwrap_err()),
    |              ^^^^^ use of undeclared crate or module `alloc`

Right! I forgot to add extern crate alloc;! But uh...:

warning: unused extern crate
   --> src\range_type.rs:161:5
    |
160 | /     #[warn(unused)]
161 | |     extern crate alloc;
    | |     ^^^^^^^^^^^^^^^^^^-
    | |_______________________|
    |                         help: remove it

It finally compiles with extern crate alloc; there, though. (I have #[warn(unused)] there because my crate-wide setting is #![deny(unused)]; although I'll be making the warn into allow after making this report, because it clearly isn't truly unused.)

The compiler seems to have a hard time deciding whether or not alloc really is available here. It yells at me when I have it there and it yells at me when I don't have it there.