zend_throw_error用法

本文基于PHP的commit为:b18b2c8fe587321384c9423470cf97d8040b32e2

在执行PHP扩展层面的代码的时候,如果遇到了错误,我们可以通过zend_throw_error这个函数来设置error异常对象,然后使用宏RETURN_THROWS来退出扩展函数。例如:

1
2
3
4
5
6
7
digest = algo->hash(password, options);
if (!digest) {
if (!EG(exception)) {
zend_throw_error(NULL, "Password hashing failed for unknown reason");
}
RETURN_THROWS();
}

我们来看看zend_throw_error会做些什么:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
ZEND_API ZEND_COLD void zend_throw_error(zend_class_entry *exception_ce, const char *format, ...) /* {{{ */
{
va_list va;
char *message = NULL;

if (!exception_ce) {
exception_ce = zend_ce_error;
}

/* Marker used to disable exception generation during preloading. */
if (EG(exception) == (void*)(uintptr_t)-1) {
return;
}

va_start(va, format);
zend_vspprintf(&message, 0, format, va);

//TODO: we can't convert compile-time errors to exceptions yet???
if (EG(current_execute_data) && !CG(in_compilation)) {
zend_throw_exception(exception_ce, message, 0);
} else {
zend_error(E_ERROR, "%s", message);
}

efree(message);
va_end(va);
}

首先是:

1
2
3
if (!exception_ce) {
exception_ce = zend_ce_error;
}

会先判断是否传递了异常类exception_ce,如果没有传递,那么使用PHP默认的zend_ce_error异常类。

然后,这里核心的地方是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
if (EG(current_execute_data) && !CG(in_compilation)) {
zend_throw_exception(exception_ce, message, 0);
}

ZEND_API ZEND_COLD zend_object *zend_throw_exception(zend_class_entry *exception_ce, const char *message, zend_long code) /* {{{ */
{
zend_string *msg_str = message ? zend_string_init(message, strlen(message), 0) : NULL;
zend_object *ex = zend_throw_exception_zstr(exception_ce, msg_str, code);
if (msg_str) {
zend_string_release(msg_str);
}
return ex;
}

static zend_object *zend_throw_exception_zstr(zend_class_entry *exception_ce, zend_string *message, zend_long code) /* {{{ */
{
// 省略其他代码
object_init_ex(&ex, exception_ce);

if (message) {
ZVAL_STR(&tmp, message);
zend_update_property_ex(exception_ce, &ex, ZSTR_KNOWN(ZEND_STR_MESSAGE), &tmp);
}
if (code) {
ZVAL_LONG(&tmp, code);
zend_update_property_ex(exception_ce, &ex, ZSTR_KNOWN(ZEND_STR_CODE), &tmp);
}

zend_throw_exception_internal(&ex);
return Z_OBJ(ex);
}

ZEND_API ZEND_COLD void zend_throw_exception_internal(zval *exception) /* {{{ */
{
// 省略其他代码
if (exception != NULL) {
zend_object *previous = EG(exception);
zend_exception_set_previous(Z_OBJ_P(exception), EG(exception));
EG(exception) = Z_OBJ_P(exception);
if (previous) {
return;
}
}
// 省略其他代码
}

这段代码做了一件事情,把zend_ce_error异常类实例化,然后设置它的message等属性,最后设置EG(exception)为这个实例化的对象。(所以,我们的RETURN_THROWS断言会成功)。