@@ -8963,17 +8963,108 @@ static void zend_compile_implements(zend_ast *ast) /* {{{ */
89638963 zend_class_name * interface_names ;
89648964 uint32_t i ;
89658965
8966- interface_names = emalloc (sizeof (zend_class_name ) * list -> children );
8966+ // Attribute validators run before `implements` parts are compiled, an
8967+ // internal attribute from an extension might have added an interface
8968+ // already so we cannot assume that the list of interfaces is empty.
8969+ // See GH-22354. The attribute validator might have also added an interface
8970+ // that the user is trying to manually implement, skip those since otherwise
8971+ // there are errors about trying to implement an interface that was already
8972+ // implemented.
8973+ if (EXPECTED (ce -> num_interfaces == 0 )) {
8974+ interface_names = emalloc (sizeof (zend_class_name ) * list -> children );
8975+ for (i = 0 ; i < list -> children ; ++ i ) {
8976+ zend_ast * class_ast = list -> child [i ];
8977+ interface_names [i ].name =
8978+ zend_resolve_const_class_name_reference (class_ast , "interface name" );
8979+ interface_names [i ].lc_name = zend_string_tolower (interface_names [i ].name );
8980+ }
8981+
8982+ ce -> num_interfaces = list -> children ;
8983+ ce -> interface_names = interface_names ;
8984+ return ;
8985+ }
89678986
8987+ // Figure out which interfaces in the list should be skipped; first, resolve
8988+ // the names
8989+ // BUT, only skip the *first* usage of an interface in the manual `implements`
8990+ // part, if an interface is added by an attribute but also manually declared
8991+ // twice it should still be warned about
8992+ const size_t array_size = zend_safe_address_guarded (list -> children , sizeof (zend_string * ), 0 );
8993+ ALLOCA_FLAG (use_heap )
8994+ zend_string * * to_add_names = do_alloca (array_size , use_heap );
8995+ zend_string * * skipped_names = do_alloca (array_size , use_heap );
8996+ uint32_t to_add_count = 0 ;
8997+ uint32_t skipped_count = 0 ;
89688998 for (i = 0 ; i < list -> children ; ++ i ) {
89698999 zend_ast * class_ast = list -> child [i ];
8970- interface_names [i ].name =
8971- zend_resolve_const_class_name_reference (class_ast , "interface name" );
8972- interface_names [i ].lc_name = zend_string_tolower (interface_names [i ].name );
9000+ zend_string * name = zend_resolve_const_class_name_reference (class_ast , "interface name" );
9001+ bool already_added = false;
9002+ for (uint32_t idx = 0 ; idx < ce -> num_interfaces ; ++ idx ) {
9003+ if (zend_string_equals_ci (name , ce -> interface_names [idx ].name )) {
9004+ already_added = true;
9005+ break ;
9006+ }
9007+ }
9008+ if (already_added ) {
9009+ // Did we already skip this interface name once?
9010+ bool already_skipped = false;
9011+ for (uint32_t idx = 0 ; idx < skipped_count ; ++ idx ) {
9012+ if (zend_string_equals_ci (name , skipped_names [idx ])) {
9013+ already_skipped = true;
9014+ break ;
9015+ }
9016+ }
9017+ if (already_skipped ) {
9018+ /* Yes, we want to add the interface even though it was already
9019+ * added by an attribute. We get here if 1) an attribute added
9020+ * the interface (already_added), 2) the developer declared
9021+ * the interface manually (this function), and 3) this is
9022+ * *not* the first manual declaration of the interface (i.e.
9023+ * a previous manual declaration was already skipped, the
9024+ * already_skipped flag). In that case, we add the interface
9025+ * again so that the standard warning about implementing the
9026+ * same interface multiple times is shown. */
9027+ to_add_names [i ] = name ;
9028+ to_add_count ++ ;
9029+ } else {
9030+ to_add_names [i ] = NULL ;
9031+ skipped_names [i ] = name ;
9032+ skipped_count ++ ;
9033+ }
9034+ } else {
9035+ to_add_names [i ] = name ;
9036+ to_add_count ++ ;
9037+ }
9038+ }
9039+ ZEND_ASSERT (skipped_count + to_add_count == list -> children );
9040+
9041+ // Free the skipped names
9042+ for (uint32_t idx = 0 ; idx < skipped_count ; ++ idx ) {
9043+ zend_string_release (skipped_names [idx ]);
9044+ }
9045+ free_alloca (skipped_names , use_heap );
9046+
9047+ const uint32_t initial_count = ce -> num_interfaces ;
9048+ interface_names = safe_erealloc (ce -> interface_names , (initial_count + to_add_count ), sizeof (* interface_names ), 0 );
9049+
9050+ uint32_t added_count = 0 ;
9051+ for (i = 0 ; i < list -> children ; ++ i ) {
9052+ zend_string * name = to_add_names [i ];
9053+ if (name == NULL ) {
9054+ // This was one of the names that was already added by a validator
9055+ continue ;
9056+ }
9057+ interface_names [initial_count + added_count ].name = name ;
9058+ interface_names [initial_count + added_count ].lc_name = zend_string_tolower (name );
9059+ // To make it clear that the to_add_names no longer owns the reference
9060+ to_add_names [i ] = NULL ;
9061+ added_count ++ ;
89739062 }
9063+ ZEND_ASSERT (added_count == to_add_count );
89749064
8975- ce -> num_interfaces = list -> children ;
9065+ ce -> num_interfaces = initial_count + added_count ;
89769066 ce -> interface_names = interface_names ;
9067+ free_alloca (to_add_names , use_heap );
89779068}
89789069/* }}} */
89799070
0 commit comments