风雪之隅
msgbartop
PHP源码分析,PHP源码研究,Zend引擎分析,Web相关技术研究,Web技术分享–左手代码 右手诗
msgbarbottom

24 Aug 08 PHP5多层继承顺序的bug

今天guoxiaod提出了一个问题,如下:

<?php
class a extends b {
};
class b extends c{
};
class c{
};
?>

会导致fatal error:

PHP Fatal error:  Class 'b' not found in /home/xinchen/1.php on line 2
Fatal error: Class 'b' not found in /home/xinchen/1.php on line 2

分析这个问题,是运行阶段出错,经过分析PHP的编译,执行过程,得出如下的parsing顺序…

start:
    top_statement_list
;

top_statement_list:
        top_statement_list 
.... //有省略
;


top_statement:
.... //有省略
    |   class_declaration_statement   
 .... //有省略
;

class_declaration_statement:
        unticked_class_declaration_statement 
;

unticked_class_declaration_statement:
        class_entry_type T_STRING extends_from
.... //有省略
;

class_entry_type:
        T_CLASS
.... //有省略
;

extends_from:
        /* empty */   
    |   T_EXTENDS fully_qualified_class_name
.... //有省略
;
fully_qualified_class_name:
        T_STRING { zend_do_fetch_class(&$$, &$1 TSRMLS_CC); }
;
zend_do_fetch_class 会设置opcode = ZEND_FETCH_CLASS

从这个过程我们可以发现,这个应该是PHP5的bug, 对于fully_qualified_class_name,如果fully_qualified_class_name也是继承来自一个类,那么就会出错, 因为fully_qualified_class_name只是简单的去fetch_class, 而如果这个时候,这个类还没有被填入到class_table就会出错。也就是说,需要有个机制,来保证父class首先被处理。

以下是我分析源码后的结论:
对于a,因为是个派生类,在编译阶段,当遇到它的定义的时候,会:

zend_do_begin_class_declaration

在这个函数中,会调用:

build_runtime_defined_function_key(&opline->op1.u.constant, lcname, name_len TSRMLS_CC);

来产生一个:

sprintf(result->value.str.val, "%c%s%s%s", '\0', name, filename, char_pos_buf);

的字符串,来做为一个编译器的classname存入class_table:

zend_hash_update(CG(class_table), opline->op1.u.constant.value.str.val, opline->op1.u.constant.value.str.len, &new_class_entry, sizeof(zend_class_entry *), NULL);

最后在吸收top_statement的时候,会有一次类的生成(填入class_table);

top_statement:
        
statement
 ...
    |   
class_declaration_statement     { zend_do_early_binding(TSRMLS_C); }
...
...
;

在zend_do_early_binding的时候:

void zend_do_early_binding(TSRMLS_D){
...
...
 
switch (opline->opcode) {
      
case ZEND_DECLARE_FUNCTION:
          
if (do_bind_function(opline, CG(function_table), 1) == FAILURE) {
              
return;
          
}
          
table = CG(function_table);
          
break;
      
case ZEND_DECLARE_CLASS:
      
case ZEND_DECLARE_INHERITED_CLASS:
          
is_abstract_class = 1;
          
/* break missing intentionally */
      
case ZEND_VERIFY_ABSTRACT_CLASS: {
              
zend_op *verify_abstract_class_op = opline;
 
              
if (!is_abstract_class) {
                  
opline--;
              
}
              
if (opline->opcode == ZEND_DECLARE_CLASS) {
                  
if (do_bind_class(opline, CG(class_table), 1 TSRMLS_CC) == NULL) {
                      
return;
                  
}
              
} else if (opline->opcode == ZEND_DECLARE_INHERITED_CLASS) {
                  
zval *parent_name = &(opline-1)->op2.u.constant;
                  
zend_class_entry **pce;
                                                                                                    
                    
if (zend_lookup_class(Z_STRVAL_P(parent_name), Z_STRLEN_P(parent_name), &pce TSR
MLS_CC) == FAILURE) {
                        
return;
                    
}
                    
if (do_bind_inherited_class(opline, CG(class_table), *pce, 1 TSRMLS_CC) == NULL)
 
{
                        
return;
                    
}
                    
/* clear unnecessary ZEND_FETCH_CLASS opcode */
}

看到了吧,如果找不到父类,就直接返回了,也就是说,派生类在编译期如果找不到父类,就不会被真正初始化,而是推迟到执行期。会分配一个opcode为ZEND_DECLARE_INHERITED_CLASS的opline,用来在运行期真正生成定义的类:

ZEND_API zend_class_entry *do_bind_inherited_class(zend_op *opline, HashTable *class_table, zend_class_entry *parent_ce, zend_bool compile_time TSRMLS_DC)
{
.......
//hash_merg子类和父类的属性、方法
    
if (zend_hash_add(class_table, opline->op2.u.constant.value.str.val, opline->op2.u.constant.value.str.len+1, pce, sizeof(zend_class_entry *), NULL)==FAILURE)
.....
}

这个时候问题就来了:
因为我们的b也是一个派生类,所以在执行a的do_bind_inherited_class时候,对于b,他也需要做一个ZEND_DECLARE_INHERITED_CLASS,也就是说,此时的class_table中是没有b的。
这也就解释了,如果最基类c,定义在前的时候,就不会出错。

恩,这个应该是PHP5的一个Bug。

我已经报bug给PHP开发组并发信询问Rasmus Lerdof(the creator of PHP),看他们怎么说了:
http://bugs.php.net/bug.php?id=45904

Related Posts:

TrackBack URI:

Tags: , , ,



Reader's Comments

  1. |

    不过,按照这个逻辑的话,似乎就是这么设计的。

    回复

  2. |

    给 rasmus 发个信 ?

    回复

  3. |

    报了bug了,看看PHP开发组怎么回应吧。

    回复

  4. |

    这样似乎可以想的通
    bind 的时候只有 c 是存在的
    执行的时候 自然找不到 b
    而 如果c 在前的话 , b 就bind 了
    自然 b就可以找到了

    回复

  5. |

    这个bug有人报过。http://bugs.php.net/bug.php?id=6418

    回复

  6. |

    哦?看到了,他是针对PHP4, 另外也没关系,起码我们分析出原因了。
    也就明白了,为什么单层继承不会出问题。
    并且,对于上例,如果c定义在前,也不会出问题。

    回复

  7. |

    执行这样应该不会有什么问题的.顺序的情况

    回复

  8. |

    [25 Aug 9:13am UTC] tularis@php.net
    Please do not submit the same bug more than once. An existing
    bug report already describes this very problem. Even if you feel
    that your issue is somewhat different, the resolution is likely
    to be the same.

    Thank you for your interest in PHP.

    Duplicate of bug #45903

    回复



Leave a Comment

click to change Verify Code(required)click the picture to change