Skip to content

GH-22354: support interfaces added by attribute validators in zend_compile_implements()#22355

Open
DanielEScherzer wants to merge 2 commits into
php:PHP-8.4from
DanielEScherzer:attribute-validator-interfaces
Open

GH-22354: support interfaces added by attribute validators in zend_compile_implements()#22355
DanielEScherzer wants to merge 2 commits into
php:PHP-8.4from
DanielEScherzer:attribute-validator-interfaces

Conversation

@DanielEScherzer

Copy link
Copy Markdown
Member

No description provided.

Comment thread ext/zend_test/test.c Outdated
@DanielEScherzer DanielEScherzer force-pushed the attribute-validator-interfaces branch from a911024 to 094b72f Compare June 17, 2026 21:41

@TimWolla TimWolla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic-wise this seems correct. But I feel this is a “master-only” bugfix, since the situation for this to appear seems to be quite rare.

Comment thread NEWS Outdated
Comment thread ext/zend_test/test.c Outdated
Comment thread ext/zend_test/test.c Outdated
Comment on lines +922 to +923
* Technically this shouldn't be needed since attribute validators run before
* interfaces are added and classes are linked, but better safe than sorry

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is zend_test, taking a short-cut here is IMO okay.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except that another attribute might have added an interface (also I realized that I neglected to update the reference to "custom casting") so I think this is still useful

Comment thread Zend/zend_compile.c
// there are errors about trying to implement an interface that was already
// implemented.
if (EXPECTED(ce->num_interfaces == 0)) {
interface_names = emalloc(sizeof(zend_class_name) * list->children);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should also be safe_emalloc.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unchanged from earlier and I'd prefer not to mess with it - I just moved it into a conditional block

Comment thread Zend/zend_compile.c Outdated
Comment thread Zend/zend_compile.c
}
}
if (already_skipped) {
to_add_names[i] = name;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks wrong. It's in already_added and you are adding it to to_add_names again.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not wrong - it was already added by an attribute validator, but we already skipped the first time that the developer manually tried to add the interface, here the interface is being added a second or subsequent time manually in addition to what the attribute did, added a comment to document this

See the ext/zend_test/tests/attribute-adds-interface-04.phpt test

Comment thread Zend/zend_compile.c Outdated
Comment thread Zend/zend_compile.c
efree(skipped_names);

const uint32_t initial_count = ce->num_interfaces;
interface_names = safe_erealloc(ce->interface_names, (initial_count + to_add_count), sizeof(*interface_names), 0);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are repeating initial_count + to_add_count a lot. I suggest a local variable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is precisely the only place that that expression occurs? Below I use added_count which also changes each time through the loop so shouldn't be saved

@DanielEScherzer DanielEScherzer force-pushed the attribute-validator-interfaces branch from 094b72f to 910c09b Compare June 21, 2026 02:03

@DanielEScherzer DanielEScherzer left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Logic-wise this seems correct. But I feel this is a “master-only” bugfix, since the situation for this to appear seems to be quite rare.

I encountered this in an actual extension, https://github.com/DanielEScherzer/CustomCast - I'm not sure why I didn't notice or report it earlier, but it does indeed affect me

Comment thread ext/zend_test/test.c Outdated
Comment on lines +922 to +923
* Technically this shouldn't be needed since attribute validators run before
* interfaces are added and classes are linked, but better safe than sorry

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Except that another attribute might have added an interface (also I realized that I neglected to update the reference to "custom casting") so I think this is still useful

Comment thread ext/zend_test/test.c Outdated
Comment thread Zend/zend_compile.c
// there are errors about trying to implement an interface that was already
// implemented.
if (EXPECTED(ce->num_interfaces == 0)) {
interface_names = emalloc(sizeof(zend_class_name) * list->children);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is unchanged from earlier and I'd prefer not to mess with it - I just moved it into a conditional block

Comment thread Zend/zend_compile.c Outdated
Comment thread Zend/zend_compile.c
}
}
if (already_skipped) {
to_add_names[i] = name;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not wrong - it was already added by an attribute validator, but we already skipped the first time that the developer manually tried to add the interface, here the interface is being added a second or subsequent time manually in addition to what the attribute did, added a comment to document this

See the ext/zend_test/tests/attribute-adds-interface-04.phpt test

Comment thread Zend/zend_compile.c
efree(skipped_names);

const uint32_t initial_count = ce->num_interfaces;
interface_names = safe_erealloc(ce->interface_names, (initial_count + to_add_count), sizeof(*interface_names), 0);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is precisely the only place that that expression occurs? Below I use added_count which also changes each time through the loop so shouldn't be saved

Comment thread Zend/zend_compile.c Outdated
Comment thread NEWS Outdated
In `zend_compile_implements()`, when there were no interfaces already added,
keep the existing code of just adding the newly declared interfaces. But, when
some interfaces are already present, don't overwrite them. Instead, add to the
existing list.

In case the developer tries to add an interface that was already added by an
attribute, skip the manual interface addition to avoid errors about trying to
apply the same interface multiple times. But, only skip the *first* manual
interface addition of the same interface, if there are multiple such additions
then there really was an attempt to apply the same interface multiple times.

Fixes php#22354
@DanielEScherzer DanielEScherzer force-pushed the attribute-validator-interfaces branch from 910c09b to a13a680 Compare June 21, 2026 03:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

zend_compile_implements() assumes that the class entry has no interfaces already

2 participants