rust-lang / rust-lang/rust

`derive(PartialEq)` on enums is unsound with user-defined attribute macros.

Open
#148,423 29 comments 23 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

A-macros A-proc-macros C-bug I-lang-radar I-unsound P-high T-compiler T-lang T-libs
Dominant language
Rust
Stars
119k
Forks
16.1k
PR merge metrics
PR metrics pending

Description

View all comments

dep/src/lib.rs:

use proc_macro::TokenStream;

#[proc_macro_attribute]
pub fn discard(_: TokenStream, _: TokenStream) -> TokenStream {
    TokenStream::new()
}

src/main.rs

#![allow(unused)]

#[derive(PartialEq)]
#[dep::discard]
enum Thing {
    One(i32),
    Two(i32),
}

enum Thing {
    One(i32),
    Two(i32),
    Three(i32),
}

fn main() {
    Thing::Three(1) == Thing::Three(1);
}

The above code causes undefined behavior in safe code. Miri output below:

error: Undefined Behavior: entering unreachable code
  --> src/main.rs:3:10
   |
 3 | #[derive(PartialEq)]
   |          ^^^^^^^^^ Undefined Behavior occurred here
   |
   = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior
   = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information
   = note: BACKTRACE:
   = note: inside `<Thing as std::cmp::PartialEq>::eq` at src/main.rs:3:10: 3:19
note: inside `main`
  --> src/main.rs:17:5
   |
17 |     Thing::Three(1) == Thing::Three(1);
   |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace

error: aborting due to 1 previous error

This happens because the derive(PartialEq) macro expands to code that assumes that the enum has exactly two variants. However, the discard macro discarded the enum definition seen by PartialEq, so the expanded code instead refers to the second definition of Foo, which has three variants.

Output of cargo +nightly rustc -- -Zunpretty=expanded:

#![feature(prelude_import)]
#![allow(unused)]
#[macro_use]
extern crate std;
#[prelude_import]
use std::prelude::rust_2024::*;

#[automatically_derived]
impl ::core::marker::StructuralPartialEq for Thing { }
#[automatically_derived]
impl ::core::cmp::PartialEq for Thing {
    #[inline]
    fn eq(&self, other: &Thing) -> bool {
        let __self_discr = ::core::intrinsics::discriminant_value(self);
        let __arg1_discr = ::core::intrinsics::discriminant_value(other);
        __self_discr == __arg1_discr &&
            match (self, other) {
                (Thing::One(__self_0), Thing::One(__arg1_0)) =>
                    __self_0 == __arg1_0,
                (Thing::Two(__self_0), Thing::Two(__arg1_0)) =>
                    __self_0 == __arg1_0,
                _ => unsafe { ::core::intrinsics::unreachable() }
            }
    }
}

enum Thing { One(i32), Two(i32), Three(i32), }

fn main() { Thing::Three(1) == Thing::Three(1); }

This bug could plausibly be hit in real code if an attribute macro adds a new variant to an enum.

See also #148277

Meta

rustc --version --verbose:

rustc 1.93.0-nightly (b15a874aa 2025-11-02)
binary: rustc
commit-hash: b15a874aafe7eab9ea3ac2c1d59c7b03e1425027
commit-date: 2025-11-02
host: aarch64-apple-darwin
release: 1.93.0-nightly
LLVM version: 21.1.3

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the dep/src/lib.rs attribute macro and src/main.rs reproducer, then inspect the expanded PartialEq implementation using cargo +nightly rustc -- -Zunpretty=expanded and verify the failure with Miri. Done means the same safe-code example no longer reaches unreachable code or undefined behavior when an attribute macro changes the enum definition.

Written by the indexing model from the issue text.

Assessment

Tech stack
rust
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.