Press "Enter" to skip to content

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

13 Comments

  1. cs10 extension projector
    cs10 extension projector March 12, 2015

    Hello, all is going sound here and ofcourse every one is sharing data, that’s in fact fine, keep up writing.

  2. qq8554650
    qq8554650 February 4, 2015

    PHP 5.6.5 (cli) (built: Jan 22 2015 23:51:39)
    仍存在此问题

  3. […] PHP5多层继承顺序的bug   HTTP1.0下HTTP_HOST为空  一个巧妙的分页方法 31 Dec 08 一个低概率的PHP Core dump 21 Feb 09 PHP字符串比较 26 May 09 PHP+Gtk实例(求24点)  PHP受locale影响的函数  PHP CLI模式下的多进程应用 […]

  4. 好消息
    好消息 September 29, 2010

    好消息1.纵观社会的发展方向,火花机行业拥有良好的发展趋势,并且已经成为一个传统而又崭新的创业领域。上海加工中心目前,随着我国经济的快速发展,对苏州火花机机械行业的加大了重视度,尤其是矿山机械。随着近几年,砂石冶金等行业生产规模的扩大,加工中心维修其对人工机制砂的需求越来越大:2.秋季美容护肤:水嫩不干燥 美容秋季护理大,简要内容:爱美女性往往会过多关心面部的青春娇美,却易忽视自己颈部的保养。400尤其是在干燥的秋季,颈部皮肤容易老化,粗糙无光、松弛多皱。浙江环保设备采购每天晚上洗浴时,用含碱少的温和型香皂洗涤颈部,江苏墙体广告用松软毛巾擦干,再进行按摩。

  5. x
    x September 8, 2008

    [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

  6. chinawolfs
    chinawolfs August 26, 2008

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

  7. 雪候鸟
    雪候鸟 August 25, 2008

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

  8. surfchen
    surfchen August 25, 2008

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

  9. guoxiaod
    guoxiaod August 25, 2008

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

  10. 雪候鸟
    雪候鸟 August 24, 2008

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

  11. guoxiaod
    guoxiaod August 24, 2008

    给 rasmus 发个信 ?

  12. guoxiaod
    guoxiaod August 24, 2008

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

Comments are closed.