Skip to content

zend_portability: Add ZEND_CONTAINER_OF()#21903

Open
TimWolla wants to merge 2 commits intophp:masterfrom
TimWolla:container-of
Open

zend_portability: Add ZEND_CONTAINER_OF()#21903
TimWolla wants to merge 2 commits intophp:masterfrom
TimWolla:container-of

Conversation

@TimWolla
Copy link
Copy Markdown
Member

Changes made with Coccinelle:

@@
type T_container;
identifier member;
expression e;
@@

- (T_container *)(((char *)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)

@@
type T_container;
identifier member;
expression e;
typedef uintptr_t;
@@

- (T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)

@@
type T_container;
identifier member;
expression e;
@@

- (const T_container *)(((char *)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)

@@
type T_container;
identifier member;
expression e;
typedef uintptr_t;
@@

- (const T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
+ ZEND_CONTAINER_OF(e, T_container, member)

Changes made with Coccinelle:

    @@
    type T_container;
    identifier member;
    expression e;
    @@

    - (T_container *)(((char *)(e)) - offsetof(T_container, member))
    + ZEND_CONTAINER_OF(e, T_container, member)

    @@
    type T_container;
    identifier member;
    expression e;
    typedef uintptr_t;
    @@

    - (T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
    + ZEND_CONTAINER_OF(e, T_container, member)

    @@
    type T_container;
    identifier member;
    expression e;
    @@

    - (const T_container *)(((char *)(e)) - offsetof(T_container, member))
    + ZEND_CONTAINER_OF(e, T_container, member)

    @@
    type T_container;
    identifier member;
    expression e;
    typedef uintptr_t;
    @@

    - (const T_container *)(((uintptr_t)(e)) - offsetof(T_container, member))
    + ZEND_CONTAINER_OF(e, T_container, member)
Comment thread Zend/zend_portability.h
#define ZEND_ELEMENT_COUNT(m)
#endif

#define ZEND_CONTAINER_OF(ptr, Type, member) ((Type*)((char*)(1 ? (ptr) : &((Type*)0)->member) - offsetof(Type, member)))
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.

Nit: This will discard const from ptr. With _Generic it would be possible to retain it. get_bcmath_number_from_obj() looks like the only relevant case, but also looks somewhat questionable. obj should not be const if we return a mutable pointer derived from it.

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.

get_bcmath_number_from_obj() simply seems to be my mistake.

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.

get_bcmath_number_from_obj() simply seems to be my mistake.

Yes, that's a pre-existing bug.

With _Generic it would be possible to retain it.

Good idea, will look into that.

@ndossche
Copy link
Copy Markdown
Member

ndossche commented May 2, 2026

I think this relies on UB (pointer arithmetic on NULL)

@TimWolla
Copy link
Copy Markdown
Member Author

TimWolla commented May 2, 2026

pointer arithmetic on NULL

That branch is never evaluated; it's also the “accepted” solution to define container_of() in a type-safe way without statement expressions. So of all possible UB, this seems to be reasonably safe.

@ndossche
Copy link
Copy Markdown
Member

ndossche commented May 2, 2026

pointer arithmetic on NULL

That branch is never evaluated

This doesn't matter for UB.

it's also the “accepted” solution to define container_of() in a type-safe way without statement expressions.

The Linux kernel is compiled with various flags that makes various UB defined, so I'm not so sure this is a valid comparison.

@iluuu1994
Copy link
Copy Markdown
Member

I believe it's not UB unless executed, but we can probably achieve something similar with static assets?

@Girgias
Copy link
Copy Markdown
Member

Girgias commented May 2, 2026

I believe it's not UB unless executed, but we can probably achieve something similar with static assets?

The issue is that compilers can assume UB never happens, and then start removing stuff or doing whatever they want if that code is present (see famous blog article about UB: https://devblogs.microsoft.com/oldnewthing/20140627-00/?p=633)

@iluuu1994
Copy link
Copy Markdown
Member

iluuu1994 commented May 2, 2026

So, I have a different understanding.

and then start removing stuff

Yes, but this branch is already provably dead. UB allows the compiler to make some assumptions. For example:

  • If some branch contains UB, it's not reachable and can thus be removed. You may wonder why you're even allowed to write such code, but it's pretty common for dead branches to be generated from macros. And probably even more so with C++ templates.
  • If an operation on some variable would produce UB with value x, the variable does not hold that value. For example, this allows the compiler to optimize away repeated NULL checks, even on systems that technically have a zero page, or if you were to catch the segfault signal.

In other words, my understanding is: UB in dead branches has an effect on the result of the program, i.e. the compiler can assume the program is correct, and if the branch were reachable the program would not be correct, so the branch is unreachable. But that doesn't mean UB in dead branches makes the behavior of your program undefined if the branches are actually never reached.

That's my understanding, though to be honest, the C spec is not nearly explicit enough about this...

@ndossche
Copy link
Copy Markdown
Member

ndossche commented May 3, 2026

Unsure, but now I wonder if we even need this.
Doesn't this work: (zend_object *)(obj + 1) - 1, at least for the objects. If std is at the end this should work for all types.

@TimWolla
Copy link
Copy Markdown
Member Author

TimWolla commented May 3, 2026

Doesn't this work:

It likely would, but my understanding is that it would be equally UB. You can't just cast between pointers of different types. (and this already affected the original implementation, you may go to char *, but may not go from char * to something else).

My suggestion would be to just go with the established way of writing a container_of macro that reasonable compilers will compile to the expected machine code. php-src is already quite reliant on all kinds of technically-UB code, such as the generic zend_function struct.

@ndossche
Copy link
Copy Markdown
Member

ndossche commented May 3, 2026

Okay then

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.

5 participants