<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                ## 4.6 異常處理 PHP的異常處理與其它語言的類似,在程序中可以拋出、捕獲一個異常,異常拋出必須只有定義在try{...}塊中才可以被捕獲,捕獲以后將跳到catch塊中進行處理,不再執行try中拋出異常之后的代碼。 異常可以在任意位置拋出,然后將由最近的一個try所捕獲,如果在當前執行空間沒有進行捕獲,那么將調用棧一直往上拋,比如在一個函數內部拋出一個異常,但是函數內沒有進行try,而在函數調用的位置try了,那么就由調用處的catch捕獲。 接下來我們從兩個方面介紹下PHP異常處理的實現。 ### 4.6.1 異常處理的編譯 異常捕獲及處理的語法: ```php try{ try statement; }catch(exception_class_1 $e){ catch statement 1; }catch(exception_class_2 $e){ catch statement 2; }finally{ finally statement; } ``` try表示要捕獲try statement中可能拋出的異常;catch是捕獲到異常后的處理,可以定義多個,當try中拋出異常時會依次檢查各個catch的異常類是否與拋出的匹配,如果匹配則有命中的那個catch塊處理;finally為最后執行的代碼,不管是否有異常拋出都會執行。 語法規則: ```c statement: ... | T_TRY '{' inner_statement_list '}' catch_list finally_statement { $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); } ... ; catch_list: /* empty */ { $$ = zend_ast_create_list(0, ZEND_AST_CATCH_LIST); } | catch_list T_CATCH '(' name T_VARIABLE ')' '{' inner_statement_list '}' { $$ = zend_ast_list_add($1, zend_ast_create(ZEND_AST_CATCH, $4, $5, $8)); } ; finally_statement: /* empty */ { $$ = NULL; } | T_FINALLY '{' inner_statement_list '}' { $$ = $3; } ; ``` 從語法規則可以看出,try-catch-finally最終編譯為一個`ZEND_AST_TRY`節點,包含三個子節點,分別是:try statement、catch list、finally statement,try statement、finally statement就是普通的`ZEND_AST_STMT_LIST`節點,catch list包含多個`ZEND_AST_CATCH`節點,每個節點有三個子節點:exception class、exception object及catch statement,最終生成的AST: ![](https://box.kancloud.cn/e6c9b0051fb721f0c917ca4c7489be05_543x256.png) 具體的編譯過程如下: * __(1)__ 向所屬zend_op_array注冊一個zend_try_catch_element結構,所有try都會注冊一個這樣的結構,與循環結構注冊的zend_brk_cont_element類似,當前zend_op_array所有定義的異常保存在zend_op_array->try_catch_array數組中,這個結構用來記錄try、catch以及finally開始的位置,具體結構: ```c typedef struct _zend_try_catch_element { uint32_t try_op; //try開始的opcode位置 uint32_t catch_op; //第1個catch塊的opcode位置 uint32_t finally_op; //finally開始的opcode位置 uint32_t finally_end;//finally結束的opcode位置 } zend_try_catch_element; ``` * __(2)__ 編譯try statement,編譯完以后如果定義了catch塊則編譯一條`ZEND_JMP`,此opcode的作用時當無異常拋出時跳過所有catch跳到finally或整個異常之外的,因為catch塊是在try statement之后編譯的,所以具體的跳轉值目前還無法確定; * __(3)__ 依次編譯各個catch塊,如果沒有定義則跳過此步驟,每個catch編譯時首先編譯一條`ZEND_CATCH`,此opcode保存著此catch的exception class、exception object以及下一個catch塊開始的位置,編譯第1個catch時將此opcode的位置記錄在zend_try_catch_element.catch_op上,接著編譯catch statement,最后編譯一條`ZEND_JMP`(最后一個catch不需要),此opcode的作用與步驟(2)的相同; * __(4)__ 將步驟(2)、步驟(3)中`ZEND_JMP`跳轉值設置為finally第1條opcode或異常定義之外的代碼,如果沒有定義finally則結束編譯,否則編譯finally塊,首先編譯一條`ZEND_FAST_CALL`及`ZEND_JMP`,接著編譯finally statement,最后編譯一條`ZEND_FAST_RET`。 編譯完以后的結構: ![](https://box.kancloud.cn/71288d3bfb560bac02e826ccfd6cae4c_626x434.png) 異常的拋出通過throw一個異常對象來實現,這個對象必須繼承>自Exception類,拋出異常的語法: ```php throw exception_object; ``` throw的編譯比較簡單,最終只編譯為一條opcode:`ZEND_THROW`。 ### 4.6.2 異常的拋出與捕獲 上一小節我們介紹了exception結構在編譯階段的處理,接下來我們再介紹下運行時exception的處理過程,這個過程相對比較復雜,整體的講其處理流程整體如下: * __(1)__ 檢查拋出的是否是object,否則將導致error錯誤; * __(2)__ 將EG(exception)設置為拋出的異常對象,同時將當前stack(即:zend_execute_data)接下來要執行的opcode設置為`ZEND_HANDLE_EXCEPTION`; * __(3)__ 執行`ZEND_HANDLE_EXCEPTION`,查找匹配的catch: * __(3.1)__ 首先遍歷當前zend_op_array下定義的所有異常捕獲,即`zend_op_array->try_catch_array`數組,然后根據throw的位置、try開始的位置、catch開始的位置、finally開始的位置判斷判斷異常是否在try范圍內,如果同時命中了多個try(即嵌套try的情況)則選擇最后那個(也就是最里層的),遍歷完以后如果命中了則進入步驟(3.2)處理,如果沒有命中當前stack下任何try則進入步驟(4); * __(3.2)__ 到這一步表示拋出的異常在當前zend_op_array下有try攔截(注意這里只是表示異常在try中拋出的,但是拋出的異常并一定能被catch),然后根據當前try塊的`zend_try_catch_element`結構取出第一個catch的位置,將opcode設置為zend_try_catch_element.catch_op,跳到第一個catch塊開始的位置執行,即:執行`ZEND_CATCH`; * __(3.3)__ 執行`ZEND_CATCH`,檢查拋出的異常對象是否與當前catch的類型匹配,檢查的過程為判斷兩個類是否存在父子關系,如果匹配則表示異常被成功捕獲,將EG(exception)清空,如果沒有則跳到下一個catch的位置重復步驟(3.3),如果到最后一個catch仍然沒有命中則在這個catch的位置拋出一個異常(實際還是原來按個異常,只是將拋出的位置轉移了當前catch的位置),然后回到步驟(3); * __(4)__ 當前zend_op_array沒能成功捕獲異常,需要繼續往上拋:回到調用位置,將接下來要執行的opcode設置為`ZEND_HANDLE_EXCEPTION`,比如函數中拋出了一個異常沒有在函數中捕獲,則跳到調用的位置繼續捕獲,回到步驟(3);如果到最終主腳本也沒有被捕獲則將結束執行并導致error錯誤。 ![](https://box.kancloud.cn/2374ccefe430525a560d0dadddeab2bb_462x229.png) 這個過程最復雜的地方在于異常匹配、傳遞的過程,主要為`ZEND_HANDLE_EXCEPTION`、`ZEND_CATCH`兩條opcode之間的調用,當拋出一個異常時會終止后面opcode的執行,轉向執行`ZEND_HANDLE_EXCEPTION`,根據異常拋出的位置定位到最近的一個try的catch位置,如果這個catch沒有匹配則跳到下一個catch塊,然后再次執行`ZEND_HANDLE_EXCEPTION`,如果到最后一個catch仍沒有匹配則將異常拋出前位置EG(opline_before_exception)更新為最后一個catch的位置,再次執行`ZEND_HANDLE_EXCEPTION`,由于異常拋出的位置已經更新了所以不會再匹配上次檢查過的那個catch,這個過程實際就是不斷遞歸執行`ZEND_HANDLE_EXCEPTION`、`ZEND_CATCH`;如果當前zend_op_array都無法捕獲則將異常拋向上一個調用棧繼續捕獲,下面根據一個例子具體說明下: ```php function my_func(){ //... throw new Exception("This is a exception from my_func()"); } try{ my_func(); }catch(ErrorException $e){ echo "ErrorException"; }catch(Exception $e){ echo "Exception"; } ``` my_func()中拋出了一個異常,首先在my_func()中拋出一個異常,然后在my_func()的zend_op_array中檢查是不是能夠捕獲,發現沒有,則回到調用的位置,再次檢查,第1次匹配到`catch(ErrorException $e)`,檢查后發現并不匹配,然后跳到下一個catch塊繼續匹配,第2次匹配到`catch(Exception $e)`,檢查后發現命中,捕獲成功。 ![](https://box.kancloud.cn/25e25bdf3e9a412fc526cbb8dcc48ff5_500x467.png) 上面的過程并沒有提到finally的執行時機,首先要明確finally在哪些情況下會執行,命中catch的情況比較簡單,即在catch statement執行完以后跳到finally執行,另外一種情況是如果一個異常在try中但沒有命中任何catch那么其finally也是會被執行的,這種情況的finally實際是在步驟(3)中執行的,最后一個catch檢查完以后會更新異常拋出位置:EG(opline_before_exception),然后會再次執行`ZEND_HANDLE_EXCEPTION`,再次檢查時就會發現沒有命中任何catch但命中finally了(因為異常位置更新了),這時候就會將異常對象保存在finally塊中,然后執行finally,執行完再將異常對象還原繼續捕獲,下面看下步驟(3)的具體處理過程: ```c static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_HANDLE_EXCEPTION_SPEC_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { //op_num為異常拋出的位置,根據異常拋出前最后一條opcode與第一條opcode計算得出 uint32_t op_num = EG(opline_before_exception) - EX(func)->op_array.opcodes; uint32_t catch_op_num = 0, finally_op_num = 0, finally_op_end = 0; //查找異常是不是被try了:找最近的一層try for (i = 0; i < EX(func)->op_array.last_try_catch; i++) { if (EX(func)->op_array.try_catch_array[i].try_op > op_num) { //try在拋出之后 break; } in_finally = 0; //異常拋出位置在try后且比第一個catch位置小,表明這個try有可能捕獲異常 if (op_num < EX(func)->op_array.try_catch_array[i].catch_op) { //第一個catch的位置 catch_op_num = EX(func)->op_array.try_catch_array[i].catch_op; } //當前try有finally if (op_num < EX(func)->op_array.try_catch_array[i].finally_op) { finally_op_num = EX(func)->op_array.try_catch_array[i].finally_op; finally_op_end = EX(func)->op_array.try_catch_array[i].finally_end; } if (op_num >= EX(func)->op_array.try_catch_array[i].finally_op && op_num < EX(func)->op_array.try_catch_array[i].finally_end) { finally_op_end = EX(func)->op_array.try_catch_array[i].finally_end; in_finally = 1; } } cleanup_unfinished_calls(execute_data, op_num); //異常命中了try但沒有命中任何catch且那個try定義了finally:需要執行finally //catch_op_num >= finally_op_num是嵌套try的情況,因為finally是檢查完所有catch、更新異常拋出位置之后再執行的 //所以檢查完內層try再檢查外層循環時會出現這種情況 if (finally_op_num && (!catch_op_num || catch_op_num >= finally_op_num)) { zval *fast_call = EX_VAR(EX(func)->op_array.opcodes[finally_op_end].op1.var); cleanup_live_vars(execute_data, op_num, finally_op_num); if (in_finally && Z_OBJ_P(fast_call)) { zend_exception_set_previous(EG(exception), Z_OBJ_P(fast_call)); } //臨時將EG(exception)轉移到finally下,執行完finally再拋出 Z_OBJ_P(fast_call) = EG(exception); EG(exception) = NULL; fast_call->u2.lineno = (uint32_t)-1; ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[finally_op_num]); ZEND_VM_CONTINUE(); }else{ //這個是善后處理,因為異常拋出后后面的opcode將不再執行,但有些情況下還需要把一些資源釋放掉 //比如前面我們介紹goto時提到的foreach中是不能直接跳出的,throw也是類似 cleanup_live_vars(execute_data, op_num, catch_op_num); ... if (catch_op_num) { //匹配到catch(但不一定命中),跳到catch處執行ZEND_CATCH進行判斷 ZEND_VM_SET_OPCODE(&EX(func)->op_array.opcodes[catch_op_num]); ZEND_VM_CONTINUE(); } else if (UNEXPECTED((EX(func)->op_array.fn_flags & ZEND_ACC_GENERATOR) != 0)) { ... } else { //當前zend_op_array下已經沒有匹配到的try了,如果異常仍沒有被捕獲則將在zend_leave_helper_SPEC()將異常拋給prev_execute_data繼續捕獲 ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU)); } } } ``` 具體的實現過程還有很多額外的處理,這里不再展開,感興趣的可以詳細研究下`ZEND_HANDLE_EXCEPTION`、`ZEND_CATCH`兩條opcode以及zend_exception.c中具體邏輯。 ### 4.6.3 內核的異常處理 前面介紹的異常處理是PHP語言層面的實現,在內核中也有一套供內核使用的異常處理模型,也就是C語言異常處理的實現,如: ```c static int php_start_sapi(void) { ... zend_try { ... } zend_catch { ... } zend_end_try(); ... } ``` C語言并沒有在語言層面提供try-catch機制,那么PHP中的是如何實現的呢?這個主要利用sigsetjmp()、siglongjmp()兩個函數實現堆棧的保存、還原,在try的位置通過sigsetjmp()將當前位置的堆棧保存在一個變量中,異常拋出通過siglongjmp()跳回原位置,具體看下這幾個宏的定義: ```c #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; \ } # define JMP_BUF sigjmp_buf # define SETJMP(a) sigsetjmp(a, 0) # define LONGJMP(a,b) siglongjmp(a, b) # define JMP_BUF sigjmp_buf ``` 展開后: ```c { //保存上一個zend_try記錄的JMP_BUF,目的是實現多層嵌套try JMP_BUF *__orig_bailout = EG(bailout); JMP_BUF __bailout; //將當前堆棧保存在__bailout EG(bailout) = &__bailout; if (SETJMP(__bailout)==0) { //try中的代碼 //拋出異常調用:LONGJMP() }else { //異常拋出后到這個分支 EG(bailout) = __orig_bailout; } EG(bailout) = __orig_bailout; } ```
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看