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

Invocation of feature-gated macros inside #[cfg]-guarded code is not checked. #32648

Closed
LeoTestard opened this issue Mar 31, 2016 · 6 comments · Fixed by #32791
Closed

Invocation of feature-gated macros inside #[cfg]-guarded code is not checked. #32648

LeoTestard opened this issue Mar 31, 2016 · 6 comments · Fixed by #32791
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..)

Comments

@LeoTestard
Copy link
Contributor

While reading libsyntax code, I came across this piece of code:

impl<'a, 'v> Visitor<'v> for MacroVisitor<'a> {
    fn visit_mac(&mut self, mac: &ast::Mac) {
        let path = &mac.node.path;
        let name = path.segments.last().unwrap().identifier.name.as_str();

        // Issue 22234: If you add a new case here, make sure to also
        // add code to catch the macro during or after expansion.
        //
        // We still keep this MacroVisitor (rather than *solely*
        // relying on catching cases during or after expansion) to
        // catch uses of these macros within conditionally-compiled
        // code, e.g. `#[cfg]`-guarded functions.

        // [...]
    }

   // [...]
}

The comment clearly states that this visitor (MacroVisitor, called by the driver using syntax::feature_gate::check_macros) is there only to check use of such macros inside #[cfg]-guarded code.

Except that this is not the case:

#[cfg(foo)]
fn pwet() { asm!() }

fn main() {}

This compiles on both stable and nightly. The reason for that is that the call to check_crate_macros occur after the call to strip_unconfigured_items. If we still want to catch those invocations, it should be the opposite, although this may have other implications (a comment above the call to strip_unconfigured_items states that this must come before anything else because of #[cfg_attr], I'm not sure if this is relevant for feature-gate checking).
Otherwise, if we want to allow them, then this whole piece of code is useless and should be removed.
I think a lot of crates use #[cfg] to differenciate between stable and nightly builds, especially crates using syntax extensions. For example, we might want to allow this:

#![cfg_attr(nightly, feature(asm))]

#[cfg(nightly)]      fn main() { unsafe { asm!("") } }
#[cfg(not(nightly))] fn main() {}

(I took asm!() as example here because it's one of the feature-gated macros but others might be more relevant I guess.)

(cc @pnkfelix: This is the weird thing I told you about earlier.)

@durka
Copy link
Contributor

durka commented Mar 31, 2016

I don't think your code examples are problems. The whole point of #[cfg] is to stop code from being considered after parsing, and as you say one of the main uses is to choose between unstable code and stable alternatives. When the cfg'ed code lives beyond cfg stripping, as in

#[cfg(not(foo))]
fn pwet() { asm!() }

fn main() {}

the stability error is generated.

@LeoTestard
Copy link
Contributor Author

Well, there is a comment in libsyntax that explicitely states that a whole piece of code is used only to find such invocations in #[cfg]-guarded code. If this is not wanted, then this code should probably be removed. :)

@pnkfelix
Copy link
Member

Just to clarify, as I remember it, my original motivation was to catch uses of unstable things on other targets, like if you're on a unix system and have code with #[cfg(windows)] ...

This may have mattered more when we were first introducing the macro stability checks than it does now... (I remain pretty sure that this did work at some point...)

@pnkfelix
Copy link
Member

(but as the others have noted above, the fact that this is used in practice to distinguish between nightly and stable builds is probably a sign that we cannot hope to actually put in this check pre-cfg stripping, at least not without making it some sort of hack that also inspects the particular cfg and skips some but not others...)

@steveklabnik steveklabnik added the A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..) label Apr 1, 2016
@LeoTestard
Copy link
Contributor Author

Okay so I tried to remove this piece of code to see what would break, and some test do, but I think it is legit.

Basically, MacroVisitor checks:

  • Use of feature-gated macros such as asm!, concat_idents!, but also featured-gated sugar such as box. Those are already checked during expansion and the only benefit of checking them first here is to catch them inside conditionalized code, which I think shouldn't be done
  • Use of #[plugin]. Again, I really think we should NOT catch uses of this inside #[cfg]-flagged code since crates use this to write syntax extensions that use plugins on nightly and syntex on stable. (I do this in RustLex, for example, and I'm pretty sure I'm not the only one to do so.) So I suggest we move the check to the actual ‶expansion″ of #[plugin] instead.
  • Use of reserved attributes, like #[rustc_*] or #[derive_*]. I see no specific reason why those should be checked before macro expansion, but we might want to check them even inside conditionalized code (note: this is not the case at the moment, since right now MacroVisitor comes after stripping of #[cfg], so fixing it for doing so might be a breaking change).
  • Use of #[allow_internal_unstable]. This one is also checked during macro expansion, and I'm not sure wether it's important to catch it in conditionalized code (as above, it's not the case now).

However, for the third item, examples in the test suite are not catched by the second checking pass, I'm not sure why, maybe they are stripped in the meantime. I'll investigate on this.

Once this is fixed, I suggest we move checking of #[plugin] to during expansion and allow all of this to appear inside conditionalized code, then remove this pass.

@LeoTestard
Copy link
Contributor Author

  • Use of reserved attributes, like #[rustc_] or #[derive_]. I see no specific reason why those should be checked before macro expansion, but we might want to check them even inside conditionalized code (note: this is not the case at the moment, since right now MacroVisitor comes after stripping of #[cfg], so fixing it for doing so might be a breaking change).

Well, simply to catch use of such attributes on macro definitions themselves... I guess this can be checked during compilation of macros, just as it's the case for #[allow_internal_unstable] now.

LeoTestard added a commit to LeoTestard/rust that referenced this issue Apr 21, 2016
This pass was supposed to check use of gated features before
`#[cfg]`-stripping but this was not the case since it in fact happens
after. Checks that are actually important and must be done before macro
expansion are now made where the features are actually used. Close rust-lang#32648.
Also ensure that attributes on macro-generated macro invocations are
checked as well. Close rust-lang#32782 and rust-lang#32655.
LeoTestard added a commit to LeoTestard/rust that referenced this issue Apr 21, 2016
This pass was supposed to check use of gated features before
`#[cfg]`-stripping but this was not the case since it in fact happens
after. Checks that are actually important and must be done before macro
expansion are now made where the features are actually used. Close rust-lang#32648.
Also ensure that attributes on macro-generated macro invocations are
checked as well. Close rust-lang#32782 and rust-lang#32655.
LeoTestard added a commit to LeoTestard/rust that referenced this issue Apr 21, 2016
This pass was supposed to check use of gated features before
`#[cfg]`-stripping but this was not the case since it in fact happens
after. Checks that are actually important and must be done before macro
expansion are now made where the features are actually used. Close rust-lang#32648.
Also ensure that attributes on macro-generated macro invocations are
checked as well. Close rust-lang#32782 and rust-lang#32655.
bors added a commit that referenced this issue Apr 28, 2016
Feature gate clean

This PR does a bit of cleaning in the feature-gate-handling code of libsyntax. It also fixes two bugs (#32782 and #32648). Changes include:

* Change the way the existing features are declared in `feature_gate.rs`. The array of features and the `Features` struct are now defined together by a single macro. `featureck.py` has been updated accordingly. Note: there are now three different arrays for active, removed and accepted features instead of a single one with a `Status` item to tell wether a feature is active, removed, or accepted. This is mainly due to the way I implemented my macro in the first time and I can switch back to a single array if needed. But an advantage of the way it is now is that when an active feature is used, the parser only searches through the list of active features. It goes through the other arrays only if the feature is not found. I like to think that error checking (in this case, checking that an used feature is active) does not slow down compilation of valid code. :) But this is not very important...
* Feature-gate checking pass now use the `Features` structure instead of looking through a string vector. This should speed them up a bit. The construction of the `Features` struct should be faster too since it is build directly when parsing features instead of calling `has_feature` dozens of times.
* The MacroVisitor pass has been removed, it was mostly useless since the `#[cfg]-stripping` phase happens before (fixes #32648). The features that must actually be checked before expansion are now checked at the time they are used. This also allows us to check attributes that are generated by macro expansion and not visible to MacroVisitor, but are also removed by macro expansion and thus not visible to PostExpansionVisitor either. This fixes #32782. Note that in order for `#[derive_*]` to be feature-gated but still accepted when generated by `#[derive(Trait)]`, I had to do a little bit of trickery with spans that I'm not totally confident into. Please review that part carefully. (It's in `libsyntax_ext/deriving/mod.rs`.)::

Note: this is a [breaking change], since programs with feature-gated attributes on macro-generated macro invocations were not rejected before. For example:

```rust
macro_rules! bar (
    () => ()
);

macro_rules! foo (
    () => (
        #[allow_internal_unstable] //~ ERROR allow_internal_unstable side-steps
        bar!();
    );
);
```
foo!();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-macros Area: All kinds of macros (custom derive, macro_rules!, proc macros, ..)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants