PHP8 RETURN_THROWS宏用法

本文基于php的commit为:b18b2c8fe587321384c9423470cf97d8040b32e2

PHP8之前,扩展函数解析参数的时候,如果解析失败了,那么会return,如下所示:

1
2
3
if (zend_parse_parameters_none() == FAILURE) {
return;
}

PHP8后,扩展函数解析参数失败的时候,会使用RETURN_THROWS这个宏,例如:

1
2
3
if (c() == FAILURE) {
RETURN_THROWS();
}

我们来看看RETURN_THROWS这个宏:

1
2
3
4
5
6
7
#define RETURN_THROWS()             do { ZEND_ASSERT(EG(exception)); (void) return_value; return; } while (0)

#if ZEND_DEBUG
# define ZEND_ASSERT(c) assert(c)
#else
# define ZEND_ASSERT(c) ZEND_ASSUME(c)
#endif

可以看出,这个宏只会去断言此时EG(exception)不为NULL。因为在PHP8中,大部分不被期待的错误都应该抛异常。

但是,真正会去打印异常消息的地方是在函数zend_execute_scripts里面:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
if (op_array) {
zend_execute(op_array, retval);
zend_exception_restore();
if (UNEXPECTED(EG(exception))) {
if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
zend_user_exception_handler();
}
if (EG(exception)) {
ret = zend_exception_error(EG(exception), E_ERROR);
}
}
destroy_op_array(op_array);
efree_size(op_array, sizeof(zend_op_array));
} else if (type==ZEND_REQUIRE) {
ret = FAILURE;
}

可以看到,在执行完了我们的opcode之后,会去判断EG(exception)是否为NULL,如果不为NULL,就会调用zend_exception_error函数,这个函数的核心是:

1
2
3
zend_string *message = zval_get_string(GET_PROPERTY(&exception, ZEND_STR_MESSAGE));
zend_string *file = zval_get_string(GET_PROPERTY_SILENT(&exception, ZEND_STR_FILE));
zend_long line = zval_get_long(GET_PROPERTY_SILENT(&exception, ZEND_STR_LINE));

(其中,exception指向的是EG(exception)

可以看到,这三行会去获取EG(exception)异常对象zend_object的三个属性,分别是messagefileline。然后组成我们的报错信息,最后通过zend_error_va函数(实际上调用的是zend_error_cb函数)打印出来。