Skip to content
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

Fix hygiene issues in declare_program! and declare_loader! #10905

Merged
merged 6 commits into from
Jul 14, 2020

Conversation

Aaron1011
Copy link
Contributor

@Aaron1011 Aaron1011 commented Jul 3, 2020

The declare_program! and declare_loader! macros both expand to
new macro definitions (based on the $name argument). These 'inner'
macros make use of the special $crate metavariable to access items in
the crate where the 'inner' macros is defined.

However, this only works due to a bug in rustc. When a macro is
expanded, all $crate tokens in its output are 'marked' as being
resolved in the defining crate of that macro. An inner macro (including
the body of its arms) is 'just' another set of tokens that appears in
the body of the outer macro, so any $crate identifiers used there are
resolved relative to the 'outer' macro.

For example, consider the following code:

macro_rules! outer {
    () => {
        macro_rules! inner {
            () => {
                $crate::Foo
            }
        }
    }
}

The path $crate::Foo will be resolved relative to the crate that defines outer,
not the crate which defines inner.

However, rustc currently loses this extra resolution information
(referred to as 'hygiene' information) when a crate is serialized.
In the above example, this means that the macro inner (which gets
defined in whatever crate invokes outer!) will behave differently
depending on which crate it is invoked from:

When inner is invoked from the same crate in which it is defined,
the hygiene information will still be available,
which will cause $crate::Foo to be resolved in the crate which defines 'outer'.

When inner is invoked from a different crate, it will be loaded from
the metadata of the crate which defines 'inner'. Since the hygiene
information is currently lost, rust will 'forget' that $crate::Foo is
supposed to be resolved in the context of 'outer'. Instead, it will be
resolved relative to the crate which defines 'inner', which can cause
incorrect code to compile.

This bug will soon be fixed in rust (see rust-lang/rust#72121),
which will break declare_program! and declare_loader!. Fortunately,
it's possible to obtain the desired behavior ($crate resolving in the
context of the 'inner' macro) by use of a procedural macro.

This commit adds a respan! proc-macro to the sdk/macro crate.
Using the newly-stabilized (on Nightly) Span::resolved_at method,
the $crate identifier can be made to be resolved in the context of the
proper crate.

Since Span::resolved_at is only stable on the latest nightly,
referencing it on an earlier version of Rust will cause a compilation error.
This requires the rustversion crate to be used, which allows conditionally
compiling code epending on the Rust compiler version in use. Since this method is already
stabilized in the latest nightly, there will never be a situation where
the hygiene bug is fixed (e.g. rust-lang/rust#72121)
is merged but we are unable to call Span::resolved_at.

@Aaron1011
Copy link
Contributor Author

Aaron1011 commented Jul 3, 2020

You can test this code with the upcoming hygiene changes as follows:

  1. Install https://github.com/kennytm/rustup-toolchain-install-master
  2. Run rustup-toolchain-install-master 70fc6a85bc30b2537a0d3b43e27bfa981104d3c3
  3. Run cargo +70fc6a85bc30b2537a0d3b43e27bfa981104d3c3 test

@Aaron1011
Copy link
Contributor Author

cc @jackcmay: It looks like you originally wrote the declare_program! macro.

@jackcmay
Copy link
Contributor

jackcmay commented Jul 6, 2020

Thanks @Aaron1011 I'll take a look!

@jackcmay jackcmay added the CI Pull Request is ready to enter CI label Jul 6, 2020
@solana-grimes solana-grimes removed the CI Pull Request is ready to enter CI label Jul 6, 2020
@Aaron1011
Copy link
Contributor Author

It looks like CI doesn't like the FIXME comment I added. Should I open an issue to track switching to $crate::?

@jackcmay
Copy link
Contributor

jackcmay commented Jul 6, 2020

That would be great, thanks @Aaron1011

Btw, what brought our issue into your radar?

@Aaron1011
Copy link
Contributor Author

Aaron1011 commented Jul 6, 2020

@jackcmay: I've opened #10933 to track switching to $crate.

Btw, what brought our issue into your radar?

This crate regressed in a crater run when I was working on rust-lang/rust#72121. Fixing this crate should allow the rustc change to land with a minimum of ecosystem breakage.

@jackcmay jackcmay added the CI Pull Request is ready to enter CI label Jul 7, 2020
@solana-grimes solana-grimes removed the CI Pull Request is ready to enter CI label Jul 7, 2020
@jackcmay
Copy link
Contributor

jackcmay commented Jul 7, 2020

@Aaron1011 FYI, this PR should fix the CI failures: Aaron1011#1

@Aaron1011
Copy link
Contributor Author

@jackcmay: I've merged your PR

@jackcmay jackcmay added the CI Pull Request is ready to enter CI label Jul 8, 2020
@solana-grimes solana-grimes removed the CI Pull Request is ready to enter CI label Jul 8, 2020
@Aaron1011
Copy link
Contributor Author

Aaron1011 commented Jul 8, 2020

The 2020-06-10nightly is too old - newer nightlies contain bufixes that allow this change to compile. Do you need to support that nightly specifically? If so, I can add in extra logic to the version detection code.

@jackcmay
Copy link
Contributor

jackcmay commented Jul 8, 2020 via email

@Aaron1011
Copy link
Contributor Author

The most recent one as of today should be fine. If you'd like, I can find the earliest one that will work.

@mvines
Copy link
Member

mvines commented Jul 8, 2020

We aren't picky about which nightly to depend on generally. If there's a known one that works we can just update https://github.com/solana-labs/solana/tree/master/ci/docker-rust-nightly to use it

@jackcmay
Copy link
Contributor

jackcmay commented Jul 8, 2020

And https://github.com/solana-labs/solana/blob/master/ci/rust-version.sh

@Aaron1011 you want to try one out in your PR?

@Aaron1011
Copy link
Contributor Author

I've bumped CI to the latest nightly - that should allow this PR to compile on both the latest stable and nightly.

@jackcmay jackcmay added CI Pull Request is ready to enter CI and removed CI Pull Request is ready to enter CI labels Jul 9, 2020
@solana-grimes solana-grimes removed the CI Pull Request is ready to enter CI label Jul 9, 2020
@jackcmay
Copy link
Contributor

jackcmay commented Jul 9, 2020

@Aaron1011 FYI: Bumping our rust nightly involes releasing a new docker image. I'm in the process of that now, will ping you when ready.

@jackcmay
Copy link
Contributor

@Aaron1011 When bumping to 2020-07-08 nightly I see the following error:

error: variable does not need to be mutable
    --> runtime/src/message_processor.rs:1357:13
     |
1357 |         let mut accounts = vec![
     |             ----^^^^^^^^
     |             |
     |             help: remove this `mut`
     |
     = note: `-D unused-mut` implied by `-D warnings`

error: aborting due to previous error

error: could not compile `solana-runtime`.

Source code is here:

let mut accounts = vec![

Broken nightly?

@Aaron1011
Copy link
Contributor Author

Aaron1011 commented Jul 10, 2020

The nightly looks correct - it still compiles without mut. However, it does not compile on stable, as it looks like the compiler is unecessarily calling DerefMut. I suspect that rust-lang/rust#72280 is related. EDIT: This is definitely caused by that PR, see rust-lang/rust#73592 for another example of this kind of warning being generated.

We should be able to add #[allow(unused_mut)] to the statements in question.

@Aaron1011
Copy link
Contributor Author

I've fixed all local issues I've found (by running the CI script manually)

@mvines mvines added the CI Pull Request is ready to enter CI label Jul 14, 2020
@solana-grimes solana-grimes removed the CI Pull Request is ready to enter CI label Jul 14, 2020
@mvines mvines added the automerge Merge this Pull Request automatically once CI passes label Jul 14, 2020
@mergify mergify bot removed the automerge Merge this Pull Request automatically once CI passes label Jul 14, 2020
@mergify
Copy link
Contributor

mergify bot commented Jul 14, 2020

automerge label removed due to a CI failure

@mvines
Copy link
Member

mvines commented Jul 14, 2020

@jackcmay - check out the stable-perf build failure, do we need an update for xargo or some other bpf-sdk component?


error[E0658]: procedural macros cannot expand to macro definitions
 --> /var/lib/buildkite-agent/builds/bernal-1/solana-labs/solana/sdk/src/entrypoint_native.rs:77:1
   \|
   77 \| #[rustversion::not(since(1.46.0))]
   \| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   \|
   = note: for more information, see https://github.com/rust-lang/rust/issues/54727
   = help: add `#![feature(proc_macro_hygiene)]` to the crate attributes to enable

   error: aborting due to previous error
    
   For more information about this error, try `rustc --explain E0658`.
   error: could not compile `solana-sdk`.

@jackcmay
Copy link
Contributor

Probably, upgrade of rust-bpf or we can modify our bpf sysroot. Or maybe do as the message suggests and add the crate attribute?

This allows the rust-bpf-builder toolchain to build the sdk
@Aaron1011
Copy link
Contributor Author

I've conditionally applied #![feature(proc_macro_hygiene)] when a dev build of Rust is detected (which might be the rust-bpf-builder toolchain).

@mvines mvines added the CI Pull Request is ready to enter CI label Jul 14, 2020
@solana-grimes solana-grimes removed the CI Pull Request is ready to enter CI label Jul 14, 2020
@mvines mvines added the automerge Merge this Pull Request automatically once CI passes label Jul 14, 2020
@Aaron1011
Copy link
Contributor Author

It looks like the automerge label didn't work?

@ryoqun
Copy link
Member

ryoqun commented Jul 14, 2020

It looks like the automerge label didn't work?

Yeah, it seems that....

@mvines Ok to merge this? Or should I wait to investigate something?

@mvines
Copy link
Member

mvines commented Jul 14, 2020

Ah crap, Travis CI is being silly again. They continually flag PRs from new contributors as "abuse" because we're a blockchain and therefore must be trying to mine BTC/ETH on their CI machines 📉

Thanks so much for this PR @Aaron1011

@mvines mvines merged commit 95490ff into solana-labs:master Jul 14, 2020
@Aaron1011
Copy link
Contributor Author

This will need to be backported to the 1.2 and 1.1 branches if they are to continue compiling once the rustc change is merged. Should I open additional PRs, or is there a standard backporting process?

@mvines
Copy link
Member

mvines commented Jul 15, 2020

@Aaron1011 - I think it's ok if 1.1 don't support the latest nightly, it'll be EOLed at the end of the month. v1.2 will live on for another couple months so that backport to that branch be useful, I initiated a backport

mergify bot added a commit that referenced this pull request Jul 15, 2020
…0905) (#11073)

* Fix hygiene issues in `declare_program!` and `declare_loader!`

The `declare_program!` and `declare_loader!` macros both expand to
new macro definitions (based on the `$name` argument). These 'inner'
macros make use of the special `$crate` metavariable to access items in
the crate where the 'inner' macros is defined.

However, this only works due to a bug in rustc. When a macro is
expanded, all `$crate` tokens in its output are 'marked' as being
resolved in the defining crate of that macro. An inner macro (including
the body of its arms) is 'just' another set of tokens that appears in
the body of the outer macro, so any `$crate` identifiers used there are
resolved relative to the 'outer' macro.

For example, consider the following code:

```rust
macro_rules! outer {
    () => {
        macro_rules! inner {
            () => {
                $crate::Foo
            }
        }
    }
}
```

The path `$crate::Foo` will be resolved relative to the crate that defines `outer`,
**not** the crate which defines `inner`.

However, rustc currently loses this extra resolution information
(referred to as 'hygiene' information) when a crate is serialized.
In the above example, this means that the macro `inner` (which gets
defined in whatever crate invokes `outer!`) will behave differently
depending on which crate it is invoked from:

When `inner` is invoked from the same crate in which it is defined,
the hygiene information will still be available,
which will cause `$crate::Foo` to be resolved in the crate which defines 'outer'.

When `inner` is invoked from a different crate, it will be loaded from
the metadata of the crate which defines 'inner'. Since the hygiene
information is currently lost, rust will 'forget' that `$crate::Foo` is
supposed to be resolved in the context of 'outer'. Instead, it will be
resolved relative to the crate which defines 'inner', which can cause
incorrect code to compile.

This bug will soon be fixed in rust (see rust-lang/rust#72121),
which will break `declare_program!` and `declare_loader!`. Fortunately,
it's possible to obtain the desired behavior (`$crate` resolving in the
context of the 'inner' macro) by use of a procedural macro.

This commit adds a `respan!` proc-macro to the `sdk/macro` crate.
Using the newly-stabilized (on Nightly) `Span::resolved_at` method,
the `$crate` identifier can be made to be resolved in the context of the
proper crate.

Since `Span::resolved_at` is only stable on the latest nightly,
referencing it on an earlier version of Rust will cause a compilation error.
This requires the `rustversion` crate to be used, which allows conditionally
compiling code epending on the Rust compiler version in use. Since this method is already
stabilized in the latest nightly, there will never be a situation where
the hygiene bug is fixed (e.g. rust-lang/rust#72121)
is merged but we are unable to call `Span::resolved_at`.

(cherry picked from commit 05445c7)

# Conflicts:
#	Cargo.lock
#	sdk/Cargo.toml

* Replace FIXME with an issue link

(cherry picked from commit b0cb2b0)

* Update lock files

(cherry picked from commit 42f8848)

# Conflicts:
#	programs/bpf/Cargo.lock
#	programs/librapay/Cargo.lock
#	programs/move_loader/Cargo.lock

* Split comment over multiple lines

Due to rust-lang/rustfmt#4325, leaving this as
one line causes rustfmt to add extra indentation to the surrounding
code.

(cherry picked from commit fed69e9)

* Fix clippy lints

(cherry picked from commit e7387f6)

* Apply #![feature(proc_macro_hygiene)] when needed

This allows the rust-bpf-builder toolchain to build the sdk

(cherry picked from commit 95490ff)

# Conflicts:
#	sdk/build.rs
#	sdk/src/lib.rs

* Update Cargo.toml

* Update lib.rs

* Add rustc_version

* lock file updates

Co-authored-by: Aaron Hill <aa1ronham@gmail.com>
Co-authored-by: Jack May <jack@solana.com>
Co-authored-by: Michael Vines <mvines@gmail.com>
Comment on lines +20 to +24
// See https://github.com/solana-labs/solana/issues/11055
// We may be running the custom `rust-bpf-builder` toolchain,
// which currently needs `#![feature(proc_macro_hygiene)]` to
// be applied.
println!("cargo:rustc-cfg=RUSTC_NEEDS_PROC_MACRO_HYGIENE");
Copy link
Member

@ryoqun ryoqun Mar 6, 2024

Choose a reason for hiding this comment

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

here (context: anza-xyz#109 (comment))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge Merge this Pull Request automatically once CI passes community Community contribution need:merge-assist
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants