Skip to content

Nested ? matchers can cause the compiler to infinite loop/crash #57597

Description

@alercah

The following code will cause the compiler to fail:

macro_rules! ex {
    ($($(i:ident)?)+) => { };
}

fn main() {
    ex!();
}

Playground link: https://play.rust-lang.org/?version=beta&mode=debug&edition=2018&gist=acce7614d74028b67c92a79b50b93522

It should error that the inner matcher can match an empty string and reject it, just as it does if * is used in place of ?.

Activity

  1. jonas-schievink commented on Jan 14, 2019

    @jonas-schievink
    Contributor

    The fix should be very easy, since this was already fixed for * repetitions in #36721 (original bug: #5067):

    match *seq_tt {
    TokenTree::MetaVarDecl(_, _, id) => id.name == "vis",
    TokenTree::Sequence(_, ref sub_seq) =>
    sub_seq.op == quoted::KleeneOp::ZeroOrMore,
    _ => false,
    }

    Looks like sub_seq.op just needs to be checked against ?, too.

  2. added
    A-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)
    C-bugCategory: This is a bug.
    on Jan 14, 2019
  3. Centril commented on Jan 14, 2019

    @Centril
    Contributor
  4. alercah commented on Jan 14, 2019

    @alercah
    Author

    Note that with ? on the outside, this errors properly, although with inconsistent error messages: if the inner repetition uses * only, the error is "repetition matches empty token tree", but it seems that if any of the repetitions are ?, then the error is "multiple successful parses".

  5. added
    I-crashIssue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics.
    on Jan 14, 2019
  6. alercah commented on Jan 14, 2019

    @alercah
    Author

    The behaviour in my previous comment is because the check for "repetition matches empty token tree" is done at definition time, but does not catch $($($i:ident)*)?; this is caught at expansion time however. Ideally all of this will be moved to definition time and the expansion-time check should probably be a) corrected to address the first comment and b) possibly become an ICE, since it should never be encountered since the RFC 550 rules should eliminate all ambiguity at definition time?

  7. mark-i-m commented on Jan 14, 2019

    @mark-i-m
    Contributor

    Hmm... I don't really remember how most of this code works...

    Just grepping, but it looks like there are a couple of other places we might want to look at:

    // Reverse scan: Sequence comes before `first`.
    if subfirst.maybe_empty || seq_rep.op == quoted::KleeneOp::ZeroOrMore {
    // If sequence is potentially empty, then
    // union them (preserving first emptiness).
    first.add_all(&TokenSet { maybe_empty: true, ..subfirst });
    } else {
    // Otherwise, sequence guaranteed
    // non-empty; replace first.
    first = subfirst;
    }

    if subfirst.maybe_empty ||
    seq_rep.op == quoted::KleeneOp::ZeroOrMore {
    // continue scanning for more first
    // tokens, but also make sure we
    // restore empty-tracking state
    first.maybe_empty = true;
    continue;
    } else {
    return first;
    }
    }

  8. mark-i-m commented on Jan 14, 2019

    @mark-i-m
    Contributor

    I've opened #57610

  9. added 6 commits that reference this issue on Jan 17, 2019
    237df57
    4ea266b
    02a1295
    53f7e66
    94509dd
    fd779d3
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-macrosArea: All kinds of macros (custom derive, macro_rules!, proc macros, ..)C-bugCategory: This is a bug.I-crashIssue: The compiler crashes (SIGSEGV, SIGABRT, etc). Use I-ICE instead when the compiler panics.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions