PHP

PHP7源码系列-异常处理实现

2019年4月11日

内部的异常处理

zend_try{} zend_end_try();

#define zend_try                                                \
    {                                                           \
        JMP_BUF *__orig_bailout = EG(bailout);                  \
        JMP_BUF __bailout;                                      \
                                                                \
        EG(bailout) = &__bailout;                               \
        if (SETJMP(__bailout)==0) {
#define zend_catch                                                \
        } else {                                                \
            EG(bailout) = __orig_bailout;
#define zend_end_try()                                            \
        }                                                       \
        EG(bailout) = __orig_bailout;                           \
    }

原理setjmp() and longjump()

#include <stdio.h>
#include <setjmp.h>
jmp_buf env;
void foo() {
    printf("before jmp\n");
      // 保存上下文
    int ret = setjmp(env);
    if(ret == 0) {
        return;
    } else {
        printf("return %d\n", ret);
    }
    printf("after jmp\n");
}
int main(int argc, char* argv[]) {
    foo();
      // 跳转到执行上下文再次执行
    longjmp(env, 999);
    return 0;
}

// 输出:
/*
before jmp
return 999
after jmp

使用zend_try

## 释放shutdown_handler
PHPAPI void php_free_shutdown_functions(void) /* {{{ */
{
    if (BG(user_shutdown_function_names))
        zend_try {
            zend_hash_destroy(BG(user_shutdown_function_names));
            FREE_HASHTABLE(BG(user_shutdown_function_names));
            BG(user_shutdown_function_names) = NULL;
        } zend_catch {
            /* maybe shutdown method call exit, we just ignore it */
            FREE_HASHTABLE(BG(user_shutdown_function_names));
            BG(user_shutdown_function_names) = NULL;
        } zend_end_try();
}

set_error_handler

Zend/zend_builtin_functions.c

set_error_handler ( callable $error_handler [, int $error_types = E_ALL | E_STRICT ] ) : mixed

error_handler
以下格式的回调(callback): 可以传入 NULL 重置处理程序到默认状态。 除了可以传入函数名,还可以传入引用对象和对象方法名的数组。

ZEND_FUNCTION(set_error_handler)
{
    zval *error_handler;
    zend_string *error_handler_name = NULL;
    zend_long error_type = E_ALL;

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "z|l", &error_handler, &error_type) == FAILURE) {
        return;
    }

    // 非NULL
    if (Z_TYPE_P(error_handler) != IS_NULL) { /* NULL == unset */
    // callable
        if (!zend_is_callable(error_handler, 0, &error_handler_name)) {
            zend_error(E_WARNING, "%s() expects the argument (%s) to be a valid callback",
                       get_active_function_name(), error_handler_name?ZSTR_VAL(error_handler_name):"unknown");
            zend_string_release(error_handler_name);
            return;
        }
        zend_string_release(error_handler_name);
    }
    // 如果 用户已经定义过错误处理函数
    if (Z_TYPE(EG(user_error_handler)) != IS_UNDEF) {
    // 设置return_value
        ZVAL_COPY(return_value, &EG(user_error_handler));
        // 有两个stack保存设置过的handler,不知干啥用  
        zend_stack_push(&EG(user_error_handlers_error_reporting), &EG(user_error_handler_error_reporting));
        zend_stack_push(&EG(user_error_handlers), &EG(user_error_handler));
    }

    // 传入NULL
    if (Z_TYPE_P(error_handler) == IS_NULL) { /* unset user-defined handler */
    // 重置 EG(user_error_handler)
        ZVAL_UNDEF(&EG(user_error_handler));
        return;
    }
    // 设置 EG(user_error_handler)
    ZVAL_COPY(&EG(user_error_handler), error_handler);
  // 设置 要处理的错误类型
    EG(user_error_handler_error_reporting) = (int)error_type;
}

set_exception_handler

执行exception_handler

Zend/zend.c
ZEND_API int zend_execute_scripts(int type, zval *retval, int file_count, ...) /* {{{ */
{
    va_list files;
    int i;
    zend_file_handle *file_handle;
    zend_op_array *op_array;

    va_start(files, file_count);
    for (i = 0; i < file_count; i++) {
        file_handle = va_arg(files, zend_file_handle *);
        if (!file_handle) {
            continue;
        }

        op_array = zend_compile_file(file_handle, type);
        if (file_handle->opened_path) {
            zend_hash_add_empty_element(&EG(included_files), file_handle->opened_path);
        }
        zend_destroy_file_handle(file_handle);
        if (op_array) {
            // 执行opcode
            zend_execute(op_array, retval);
      // 这个地方有个一个prev_exception的东西
      // 对应到php层面的应该是 Exception::getPrevious,但是我没看清楚,可以以后再看看
            zend_exception_restore();
            // 处理异常
            zend_try_exception_handler();
            // 因为用户未定义exception_handler
            // 或者其他原因导致exception未处理
            if (EG(exception)) {
                zend_exception_error(EG(exception), E_ERROR);
            }
            // 释放opcode
            destroy_op_array(op_array);
            efree_size(op_array, sizeof(zend_op_array));
        } else if (type==ZEND_REQUIRE) {
            va_end(files);
            return FAILURE;
        }
    }
    va_end(files);

    return SUCCESS;
}
/* }}} */
ZEND_API void zend_try_exception_handler() /* {{{ */
{
    if (EG(exception)) {
        // 如果用户定义了exception_handler
        if (Z_TYPE(EG(user_exception_handler)) != IS_UNDEF) {
            zval orig_user_exception_handler;
            zval params[1], retval2;
            zend_object *old_exception;
            old_exception = EG(exception);
            EG(exception) = NULL;
            ZVAL_OBJ(&params[0], old_exception);
            ZVAL_COPY_VALUE(&orig_user_exception_handler, &EG(user_exception_handler));

            // 执行exception_handler
            if (call_user_function_ex(CG(function_table), NULL, &orig_user_exception_handler, &retval2, 1, params, 1, NULL) == SUCCESS) {
                zval_ptr_dtor(&retval2);
                // 释放exception
                if (EG(exception)) {
                    OBJ_RELEASE(EG(exception));
                    EG(exception) = NULL;
                }
                OBJ_RELEASE(old_exception);
            } else {
                EG(exception) = old_exception;
            }
        }
    }
} /* }}} */

发表评论

电子邮件地址不会被公开。 必填项已用*标注