◐ Shell
reader mode source ↗
Skip to content

#[vm::pyclass] / #[vm::pymethod] causes infinite loop in macro expansion #8097

New issue
New issue
@mapf0000

Description

@mapf0000

Summary

cargo check hangs indefinitely — no error, just a rustc process pegged at 100% CPU —
when any #[pyclass] or #[pymethod] attribute is written with a path prefix such as
#[vm::pymethod].

Affected: rustpython-derive-impl 0.5.0 on rustc 1.96.0 / aarch64-apple-darwin

Root cause

attrs_to_content_items (crates/derive-impl/src/pyclass.rs ~line 1986) scans the
attribute list of each impl item with a Peekable iterator. peek() reads the current
element without advancing; only next() moves forward. The loop places iter.next()
after an early continue, so it is skipped whenever get_ident() returns None:

while let Some((_, attr)) = iter.peek() {
    let attr_name = if let Some(ident) = attr.get_ident() {
        ident.to_string()
    } else {
        continue;   // ← iter.next() skipped; same attribute peeked forever
    };
    // ...
    iter.next();    // ← never reached when get_ident() returns None
}

syn::Attribute::get_ident() only returns Some(name) for single-word attribute names
like pymethod. For a prefixed name like vm::pymethod it returns None, because
vm::pymethod is two path segments, not one. That None triggers continue, the
iterator never advances, and the loop spins forever.

Writing #[pymethod] (no prefix) works fine because get_ident() returns
Some("pymethod"), which hits the break in ALL_ALLOWED_NAMES.

Minimal reproduction

Cargo.toml:

[package]
name = "reproduce-pyclass-hang"
version = "0.1.0"
edition = "2021"

[dependencies]
rustpython-vm = { version = "0.5", default-features = false, features = ["compiler", "gc"] }

src/lib.rs:

use rustpython_vm as vm;

#[derive(Debug, vm::PyPayload)]
struct Item { value: i64 }

#[vm::pyclass]
impl Item {
    #[vm::pymethod]         // ← qualified path triggers the hang
    fn value(&self) -> i64 { self.value }
}

Run cargo checkrustc pegs a CPU core indefinitely with no output.

Fix

Move iter.next() outside the if let so it always fires, even when get_ident()
returns None. Unrecognised attribute paths are then simply skipped over.

// crates/derive-impl/src/pyclass.rs  (~line 1986)
while let Some((_, attr)) = iter.peek() {
    // Wrap in if-let so multi-segment paths (e.g. vm::pymethod) that return
    // None from get_ident() are skipped instead of looping forever.
    if let Some(ident) = attr.get_ident() {
        let attr_name = ident.to_string();
        if attr_name == "cfg" {
            cfgs.push(attr.clone());
        } else if ALL_ALLOWED_NAMES.contains(&attr_name.as_str()) {
            break;
        }
    }
    iter.next();
}

Metadata

Metadata

Labels

C-bugSomething isn't working

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions