Skip to content

docs(D-W6.6): precise Class::MOP failure stack — Try::Tiny + _post_add_attribute#610

Open
fglock wants to merge 1 commit intomasterfrom
fix/d-w6-bisect-cmop
Open

docs(D-W6.6): precise Class::MOP failure stack — Try::Tiny + _post_add_attribute#610
fglock wants to merge 1 commit intomasterfrom
fix/d-w6-bisect-cmop

Conversation

@fglock
Copy link
Copy Markdown
Owner

@fglock fglock commented Apr 29, 2026

Summary

D-W6.6 narrows the Class::MOP drift failure to the exact line in
_post_add_attribute — and shows that the visible error is a CASCADE
caught by Try::Tiny.

What the failure stack actually shows

A $SIG{__DIE__} probe before use Class::MOP (gate disabled)
captures:

FAILURE at:
  main::__ANON__         at .../Try/Tiny.pm:139
  (eval)                 at .../Try/Tiny.pm:140
  Try::Tiny::try         at .../Class/MOP/Class.pm:897
  Class::MOP::Class::_post_add_attribute
                         at .../Class/MOP/Mixin/HasAttributes.pm:41
  Class::MOP::Mixin::HasAttributes::add_attribute
                         at .../Class/MOP.pm:188

So the bootstrap chain is:

# CMOP.pm:188
HasMethods->meta->add_attribute(
    Attribute->new('wrapped_method_metaclass' =>
        reader => { 'wrapped_method_metaclass' => \&CV },
        ...
    )
);

# HasAttributes::add_attribute (line 41)
$self->_attach_attribute($attribute);   # weaken back-ref
$self->{_attribute_map}{...} = $attr;
$self->_post_add_attribute($attribute);

# Class.pm:897 — _post_add_attribute body
try {
    local $SIG{__DIE__};
    $attribute->install_accessors;     # ← dies (associated_class undef)
} catch {
    $self->remove_attribute(...);       # ← then THIS dies for the same reason
    die $_;
};

The visible error ("Can't call method 'get_method' on an undefined
value at Attribute.pm line 475") is the SECOND failure — the catch
block's call to remove_attribute → remove_accessors. The first die
is hidden by Try::Tiny's local $SIG{__DIE__}.

What landed

  • src/test/resources/unit/refcount/drift/cmop_add_attr_loop.t
    the exact shape: 11 iterations of _attach_attribute
    _attribute_map insert → _post_add_attribute with HASH-form
    reader. Both Perl 5 and PerlOnJava (gate disabled) pass.
  • try_tiny_weak.t — Try::Tiny + weakened back-ref alone
    (4 patterns). Pass.

So the simple shape isn't enough. The bug requires the recursive
bootstrap
where HasMethods->meta is itself a Class::MOP::Class
with a multi-level @ISA chain (Module, HasAttributes, HasMethods,
HasOverloads) currently being built up.

Next concrete probe

A $SIG{__DIE__} Perl-side probe that prints
$self->associated_class // 'UNDEF' immediately before each die.
The first die where associated_class is 'UNDEF' reveals the
exact LAST PRIOR refCount-decrement step that took the metaclass to
0. That decrement's stack identifies the specific linearized_isa /
_method_map traversal inside install_accessors that loses the
metaclass strong hold during the partial-build state.

Test plan

  • make (build + unit tests) green.
  • cmop_add_attr_loop.t passes on master.
  • cmop_add_attr_loop.t passes with the gate disabled.
  • try_tiny_weak.t passes both ways.

Open D-W6 PR backlog (cumulative)

# Branch Lands
599 feature/moose-phase-d-v2 Universal walker (no class-name dispatch)
603 fix/d-w6-1-sub-install-drift sub_install.t
605 fix/d-w6-2-closure-capture-drift closure_capture.t + hash_slot.t
606 fix/d-w6-4-pending-double-add weak_metaclass.t
607 fix/d-w6-pending-instrumentation Diagnostic env-flags
609 fix/d-w6-function-hash-store function_hash_store.t + cmop_bootstrap.t
this PR fix/d-w6-bisect-cmop try_tiny_weak.t + cmop_add_attr_loop.t + precise stack

Generated with Devin

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

…dd_attribute

A `$SIG{__DIE__}` probe before `use Class::MOP` (gate disabled)
captured the exact failure stack:

    main::__ANON__         at .../Try/Tiny.pm:139
    (eval)                 at .../Try/Tiny.pm:140
    Try::Tiny::try         at .../Class/MOP/Class.pm:897
    Class::MOP::Class::_post_add_attribute
                           at .../Class/MOP/Mixin/HasAttributes.pm:41
    Class::MOP::Mixin::HasAttributes::add_attribute
                           at .../Class/MOP.pm:188

The chain: bootstrap calls `HasMethods->meta->add_attribute(...)`
with a HASH-form `reader => { name => CV }`. Inside `add_attribute`,
`_attach_attribute` weakens the back-ref, then `_post_add_attribute`
runs a `try { install_accessors } catch { remove_attribute; die }`.

`install_accessors` dies first because `$self->associated_class`
reads as undef. The catch tries `remove_attribute` which does
`$attr->associated_class()` — also undef — and produces the visible
"Can't call method 'get_method' on an undefined value" at line 475.
The first die is hidden by Try::Tiny's `local $SIG{__DIE__}`.

Adds reproducer src/test/resources/unit/refcount/drift/cmop_add_attr_loop.t
that exercises this exact shape. Both Perl 5 and PerlOnJava (gate
disabled) pass it — so the bug requires the *recursive* bootstrap
where `HasMethods->meta` is itself a Class::MOP::Class with a
multi-level @isa chain currently being built up.

Documents the next probe in moose_support.md: a Perl-side
`$SIG{__DIE__}` that prints `associated_class // UNDEF` immediately
before each die, to identify the specific scope-exit that took the
metaclass refCount to 0.

Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant