In depth understanding of PHP's exception mechanism

  • 2020-03-31 21:05:57
  • OfStack

How does PHP's exception mechanism work?
What is the last ZEND_HANDLE_EXCEPTION for each independently executable op array in PHP?
Let's start with a question that blue5tar asked last week: "for the following code, onError was executed, but onException was not. Why?" .
 
<?php 
function onError($errCode, $errMesg, $errFile, $errLine) { 
echo "Error Occurredn"; 
throw new Exception($errMesg); 
} 
function onException($e) { 
echo $e->getMessage(); 
} 
set_error_handler("onError"); 
set_exception_handler("onException"); 
 
require("laruence.php"); 

Operation results:
 
Error Occurred 
PHP Fatal error: main(): Failed opening required 'laruence.php 

First, we need to know that Require throws two errors back and forth when it contains a problem that cannot be found,
 
WARNING :  in PHP Thrown when trying to open this file . 
E_COMPILE_ERROR :  from PHP The open file function returns a failure thrown after failure  

And we know that set_error_handler cannot catch E_COMPILE_ERROR:
The following error types cannot be handled with a user defined function: E_ERROR, E_PARSE, E_CORE_ERROR, E_CORE_WARNING, E_COMPILE_ERROR, e_warning, and most of E_STRICT raised in the file where set_error_handler() is called.
So, in onError, only the first WARNING error is caught, while in onError, why is the exception thrown not caught by default exception_handler?
This brings us to PHP's exception mechanism.
Those of you who have a deep understanding of Opcodes in PHP will know that before PHP5.3, the last opcode of each independently runable op array(file, function, method) was ZEND_HANDLE_EXCEPTION. What is this opcode for?
In PHP, when an exception is thrown, it will jump to the last line of each op array to execute this ZEND_HANDLE_EXCEPTION. The pseudo-code is as follows:
 
void on_throw_exception(zval *exception TSRMLS_DC) { 
1.  Determines if an exception has been thrown  
2.  record exception 
3.  Record the next item to execute op line The serial number of  
4.  The next one to execute op line The serial number  =  The current op array The last one  
} 

Well, just like overwriting the IP register, overwriting the sequence number of the next op line to execute changes the flow of the program so that it enters the processing logic of ZEND_HANDLE_EXCEPTION.
In ZEND_HANDLE_EXCEPTION, it determines whether the exception is in a try catch,
 
 If it is   Take the next item to execute op line,  Let's put it in the first one catch the op line,  And continue to execute . 
 If it is not   Destroys unwanted variables ,  and opline,  The execution is then terminated directly  

Some of you may ask, "when does the default exception handler set by set_exception_handler (user_exception_handler) work?"
Well, it is after the execution completes and exits the execution LOOP that it determines whether there is a default exception handler. If so, it will call:
 
//perform
zend_execute(EG(active_op_array) TSRMLS_CC); 
if (EG(exception)) { 
if (EG(user_exception_handler)) { 
 Invokes the user-defined default exception handler  
} else { 
 Exceptions not caught  
} 
} else { 
 No abnormal  
} 
destroy_op_array(EG(active_op_array) TSRMLS_CC); 
efree(EG(active_op_array)); 

< img border = 0 SRC = "http://files.jb51.net/upload/201008/20100821213237631.png" border = 0 >
PHP exception flow

Note: there is a loose point in the figure, that is, when determining whether the last catch block is or not, it will judge (is_a) at the same time, if the last catch block is only executed.
However, when PHP encounters Fatal Error, it will directly zend_bailout, and zend_bailout will cause the program process to skip the above code segment directly, which can also be interpreted as a direct exit (longjmp), resulting in no chance for user_exception_handler to function.
With that in mind, why do I want to ask the question at the beginning of the article? That's pretty clear, right?
Finally, some of you may wonder about ZEND_HANDLE_EXCEPTION: if so, why does every independently executable op array end up with this ZEND_HANDLE_EXCEPTION? The simplest, if a function doesn't have throw in it, then this opcode is obviously not needed, right? Hey hey, you're smart, PHP 5.3 started, has been adjusted according to your idea.. ZEND_HANDLE_EXCEPTION opline is generated dynamically only at throw time.
PHP5 changelog:
Changed exception handling. Now each op_array doesn't contain ZEND_HANDLE_EXCEPTION opcode in the end. (Dmitry)

Related articles: