3737
3838ZEND_EXTERN_MODULE_GLOBALS (xdebug )
3939
40- static const char * text_formats [11 ] = {
40+ static const char * text_formats [21 ] = {
4141 "\n" ,
4242 "%s: %s in %s on line %d\n" ,
4343 "\nCall Stack:\n" ,
@@ -48,10 +48,22 @@ static const char* text_formats[11] = {
4848 "\n" ,
4949 " $%s = %s\n" ,
5050 " $%s = *uninitialized*\n" ,
51- "SCREAM: Error suppression ignored for\n"
51+ "SCREAM: Error suppression ignored for\n" ,
52+ NULL ,
53+ NULL ,
54+
55+ // 13+ (for xdebug_append_printable_stack_from_zval)
56+ "\n\tCall Stack:\n" ,
57+ "" ,
58+ "\tThe stack is empty or not available\n" ,
59+ "\t%10.4F %10ld %3d. %s() %s:%d\n" ,
60+ "\n\t" ,
61+ "\n\tNested Exceptions:\n" ,
62+ "" , // nested exceptions footer
63+ NULL // alternative to 16 for html only
5264};
5365
54- static const char * ansi_formats [11 ] = {
66+ static const char * ansi_formats [21 ] = {
5567 "\n" ,
5668 "[1m[31m%s[0m: %s[22m in [31m%s[0m on line [32m%d[0m[22m\n" ,
5769 "\n[1mCall Stack:[22m\n" ,
@@ -62,10 +74,22 @@ static const char* ansi_formats[11] = {
6274 "\n" ,
6375 " $%s = %s\n" ,
6476 " $%s = *uninitialized*\n" ,
65- "[1m[31mSCREAM[0m: Error suppression ignored for\n"
77+ "[1m[31mSCREAM[0m: Error suppression ignored for\n" ,
78+ NULL ,
79+ NULL ,
80+
81+ // 13+ (for xdebug_append_printable_stack_from_zval)
82+ "\n\t[1mCall Stack:[22m\n" ,
83+ "" ,
84+ "\tThe stack is empty or not available\n" ,
85+ "\t%10.4F %10ld %3d. %s() %s:%d\n" ,
86+ "\n\t" ,
87+ "\n\t[1mNested Exceptions:[22m\n" ,
88+ "" , // nested exceptions footer
89+ NULL // alternative to 16 for html only
6690};
6791
68- static const char * html_formats [13 ] = {
92+ static const char * html_formats [21 ] = {
6993 "<br />\n<font size='1'><table class='xdebug-error xe-%s%s' dir='ltr' border='1' cellspacing='0' cellpadding='1'>\n" ,
7094 "<tr><th align='left' bgcolor='#f57900' colspan=\"5\"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> %s: %s in %s on line <i>%d</i></th></tr>\n" ,
7195 "<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>\n<tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>\n" ,
@@ -78,7 +102,17 @@ static const char* html_formats[13] = {
78102 "<tr><td colspan='2' align='right' bgcolor='#eeeeec' valign='top'><pre>$%s =</pre></td><td colspan='3' bgcolor='#eeeeec' valign='top'><i>Undefined</i></td></tr>\n" ,
79103 " )</td><td title='%s' bgcolor='#eeeeec'><a style='color: black' href='%s'>%s<b>:</b>%d</a></td></tr>\n" ,
80104 "<tr><th align='left' bgcolor='#f57900' colspan=\"5\"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> %s: %s in <a style='color: black' href='%s'>%s</a> on line <i>%d</i></th></tr>\n" ,
81- "<tr><th align='left' bgcolor='#f57900' colspan=\"5\"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> SCREAM: Error suppression ignored for</th></tr>\n"
105+ "<tr><th align='left' bgcolor='#f57900' colspan=\"5\"><span style='background-color: #cc0000; color: #fce94f; font-size: x-large;'>( ! )</span> SCREAM: Error suppression ignored for</th></tr>\n" ,
106+
107+ // 13+ (for xdebug_append_printable_stack_from_zval)
108+ "<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Call Stack</th></tr>\n<tr><th align='center' bgcolor='#eeeeec'>#</th><th align='left' bgcolor='#eeeeec'>Time</th><th align='left' bgcolor='#eeeeec'>Memory</th><th align='left' bgcolor='#eeeeec'>Function</th><th align='left' bgcolor='#eeeeec'>Location</th></tr>\n" ,
109+ "" ,
110+ "<tr><td colspan='5' bgcolor='#eeeeec'>The stack is empty or not available</td></tr>\n" ,
111+ "<tr><td bgcolor='#eeeeec' align='center'>%d</td><td bgcolor='#eeeeec' align='center'>%.4F</td><td bgcolor='#eeeeec' align='right'>%ld</td><td bgcolor='#eeeeec'>%s()</td><td title='%s' bgcolor='#eeeeec'><a style='color: black' href='%s'>%s<b>:</b>%d</a></td></tr>\n" ,
112+ "<table class='xdebug-error xe-nested' style='width: 80%; margin: 1em' dir='ltr' border='1' cellspacing='0' cellpadding='1'>\n" ,
113+ "<tr><th align='left' bgcolor='#e9b96e' colspan='5'>Nested Exceptions</th></tr><tr><td colspan='5'>\n" ,
114+ "</table></tr>\n" , // nested exceptions footer
115+ "<tr><td bgcolor='#eeeeec' align='center'>%d</td><td bgcolor='#eeeeec' align='center'>%.4F</td><td bgcolor='#eeeeec' align='right'>%ld</td><td bgcolor='#eeeeec'>%s()</td><td title='%s' bgcolor='#eeeeec'>%s<b>:</b>%d</td></tr>\n"
82116};
83117
84118static const char * * select_formats (int html )
@@ -243,6 +277,32 @@ void xdebug_append_error_description(xdebug_str *str, int html, const char *erro
243277 efree (escaped );
244278}
245279
280+ static void xdebug_append_error_description_from_object (xdebug_str * str , int html , zval * exception_obj )
281+ {
282+ zval * message , * file , * line ;
283+ zval dummy ;
284+
285+ if (Z_TYPE_P (exception_obj ) != IS_OBJECT || !instanceof_function (Z_OBJCE_P (exception_obj ), zend_ce_throwable )) {
286+ return ;
287+ }
288+
289+ message = zend_read_property (Z_OBJCE_P (exception_obj ), Z_OBJ_P (exception_obj ), "message" , sizeof ("message" )- 1 , 1 , & dummy );
290+ file = zend_read_property (Z_OBJCE_P (exception_obj ), Z_OBJ_P (exception_obj ), "file" , sizeof ("file" )- 1 , 1 , & dummy );
291+ line = zend_read_property (Z_OBJCE_P (exception_obj ), Z_OBJ_P (exception_obj ), "line" , sizeof ("line" )- 1 , 1 , & dummy );
292+
293+ if (!message || !file || !line || Z_TYPE_P (message ) != IS_STRING || Z_TYPE_P (file ) != IS_STRING || Z_TYPE_P (line ) != IS_LONG ) {
294+ return ;
295+ }
296+
297+ xdebug_append_error_description (str , html , STR_NAME_VAL (Z_OBJCE_P (exception_obj )-> name ), Z_STRVAL_P (message ), Z_STRVAL_P (file ), Z_LVAL_P (line ));
298+ }
299+
300+ static void xdebug_append_sub_header (xdebug_str * str , int html )
301+ {
302+ const char * * formats = select_formats (html );
303+ xdebug_str_add_const (str , formats [17 ]);
304+ }
305+
246306static void add_single_value (xdebug_str * str , zval * zv , int html )
247307{
248308 xdebug_str * tmp_value = NULL ;
@@ -427,6 +487,37 @@ static void zval_from_stack(zval *output, bool add_local_vars, bool params_as_va
427487 }
428488}
429489
490+ /* Helpers for last_exception_trace slots */
491+
492+ static zval * last_exception_find_trace (zend_object * obj )
493+ {
494+ int i ;
495+
496+ for (i = 0 ; i < XDEBUG_LAST_EXCEPTION_TRACE_SLOTS ; i ++ ) {
497+ if (obj == XG_DEV (last_exception_trace ).obj_ptr [i ]) {
498+ return & XG_DEV (last_exception_trace ).stack_trace [i ];
499+ }
500+ }
501+ return NULL ;
502+ }
503+
504+ static zval * last_exception_get_slot (zend_object * obj )
505+ {
506+ int slot = XG_DEV (last_exception_trace ).next_slot ;
507+
508+ if (XG_DEV (last_exception_trace ).obj_ptr [slot ] != NULL ) {
509+ zval_ptr_dtor (& XG_DEV (last_exception_trace ).stack_trace [slot ]);
510+ XG_DEV (last_exception_trace ).obj_ptr [slot ] = NULL ;
511+ }
512+
513+ XG_DEV (last_exception_trace ).obj_ptr [slot ] = obj ;
514+
515+ XG_DEV (last_exception_trace ).next_slot = (slot + 1 == XDEBUG_LAST_EXCEPTION_TRACE_SLOTS ? 0 : slot + 1 );
516+
517+ return & XG_DEV (last_exception_trace ).stack_trace [slot ];
518+ }
519+
520+ /* Formatting variables */
430521
431522#define XDEBUG_VAR_FORMAT_INITIALISED 0
432523#define XDEBUG_VAR_FORMAT_UNINITIALISED 1
@@ -521,6 +612,91 @@ static void xdebug_dump_used_var_with_contents(void *htmlq, xdebug_hash_element*
521612 zval_ptr_dtor_nogc (& zvar );
522613}
523614
615+ void xdebug_append_printable_stack_from_zval (xdebug_str * str , zval * trace , int html )
616+ {
617+ const char * * formats = select_formats (html );
618+ zval * frame ;
619+ int counter = 0 ;
620+
621+ xdebug_str_add_const (str , formats [13 ]); // header
622+
623+ if (!trace || Z_TYPE_P (trace ) != IS_ARRAY ) {
624+ xdebug_str_add_const (str , formats [15 ]); // message
625+ xdebug_str_add_const (str , formats [14 ]); // footer
626+ return ;
627+ }
628+
629+ ZEND_HASH_FOREACH_VAL_IND (HASH_OF (trace ), frame ) {
630+ zval * time , * memory , * class , * type , * function , * file , * line ;
631+ char * combined_function ;
632+
633+ counter ++ ;
634+
635+ if (Z_TYPE_P (frame ) != IS_ARRAY ) {
636+ continue ;
637+ }
638+
639+ time = zend_hash_str_find (HASH_OF (frame ), "time" , 4 );
640+ memory = zend_hash_str_find (HASH_OF (frame ), "memory" , 6 );
641+ class = zend_hash_str_find (HASH_OF (frame ), "class" , 5 );
642+ type = zend_hash_str_find (HASH_OF (frame ), "type" , 4 );
643+ function = zend_hash_str_find (HASH_OF (frame ), "function" , 8 );
644+ file = zend_hash_str_find (HASH_OF (frame ), "file" , 4 );
645+ line = zend_hash_str_find (HASH_OF (frame ), "line" , 4 );
646+
647+ if (!time || !memory || !function || !file || !line ) {
648+ continue ;
649+ }
650+
651+ if (Z_TYPE_P (time ) != IS_DOUBLE || Z_TYPE_P (memory ) != IS_LONG || Z_TYPE_P (function ) != IS_STRING || Z_TYPE_P (file ) != IS_STRING || Z_TYPE_P (line ) != IS_LONG ) {
652+ continue ;
653+ }
654+
655+ if (class && type && Z_TYPE_P (class ) == IS_STRING && Z_TYPE_P (type ) == IS_STRING ) {
656+ combined_function = xdebug_sprintf ("%s%s%s" , Z_STRVAL_P (class ), strcmp (Z_STRVAL_P (type ), "static" ) == 0 ? "::" : "->" , Z_STRVAL_P (function ));
657+ } else {
658+ combined_function = xdstrdup (Z_STRVAL_P (function ));
659+ }
660+
661+ if (html ) {
662+ char * formatted_filename ;
663+ xdebug_format_filename (& formatted_filename , "...%s%n" , Z_STR_P (file ));
664+
665+ if (strlen (XINI_LIB (file_link_format )) > 0 && strcmp (Z_STRVAL_P (file ), "Unknown" ) != 0 ) {
666+ char * file_link ;
667+
668+ xdebug_format_file_link (& file_link , Z_STRVAL_P (file ), Z_LVAL_P (line ));
669+ xdebug_str_add_fmt (str , formats [16 ], counter , Z_DVAL_P (time ), Z_LVAL_P (memory ), combined_function , Z_STRVAL_P (file ), file_link , formatted_filename , Z_LVAL_P (line ));
670+ xdfree (file_link );
671+ } else {
672+ xdebug_str_add_fmt (str , formats [20 ], counter , Z_DVAL_P (time ), Z_LVAL_P (memory ), combined_function , Z_STRVAL_P (file ), formatted_filename , Z_LVAL_P (line ));
673+ }
674+
675+ xdfree (formatted_filename );
676+ } else {
677+ xdebug_str_add_fmt (str , formats [16 ], Z_DVAL_P (time ), Z_LVAL_P (memory ), counter , combined_function , Z_STRVAL_P (file ), Z_LVAL_P (line ));
678+ }
679+ xdfree (combined_function );
680+
681+ } ZEND_HASH_FOREACH_END ();
682+
683+ xdebug_str_add_const (str , formats [14 ]); // footer
684+ }
685+
686+ static void xdebug_append_nested_section_header (xdebug_str * str , int html )
687+ {
688+ const char * * formats = select_formats (html );
689+
690+ xdebug_str_add_const (str , formats [18 ]);
691+ }
692+
693+ static void xdebug_append_nested_section_footer (xdebug_str * str , int html )
694+ {
695+ const char * * formats = select_formats (html );
696+
697+ xdebug_str_add_const (str , formats [19 ]);
698+ }
699+
524700void xdebug_append_printable_stack (xdebug_str * str , int html )
525701{
526702 int printed_frames = 0 ;
@@ -1009,48 +1185,62 @@ void xdebug_develop_throw_exception_hook(zend_object *exception, zval *file, zva
10091185 char * exception_trace ;
10101186 xdebug_str tmp_str = XDEBUG_STR_INITIALIZER ;
10111187
1012- zval * z_previous_exception ;
1188+ zval * z_previous_exception , * z_last_exception_slot , * z_previous_trace ;
10131189 zend_object * previous_exception_obj = exception ;
10141190 zval dummy ;
10151191
1192+ if (!PG (html_errors )) {
1193+ xdebug_str_addc (& tmp_str , '\n' );
1194+ }
1195+ xdebug_append_error_description (& tmp_str , PG (html_errors ), STR_NAME_VAL (exception_ce -> name ), message ? Z_STRVAL_P (message ) : "" , Z_STRVAL_P (file ), Z_LVAL_P (line ));
1196+ xdebug_append_printable_stack (& tmp_str , PG (html_errors ));
1197+
10161198 /* Loop over previous exceptions until there are none left */
1017- do {
1018- z_previous_exception = zend_read_property (exception_ce , previous_exception_obj , "previous" , sizeof ("previous" )- 1 , 1 , & dummy );
1019- if (!z_previous_exception || Z_TYPE_P (z_previous_exception ) != IS_OBJECT ) {
1020- break ;
1021- }
1199+ {
1200+ bool first = true;
1201+ bool found = false;
10221202
1023- if (XG_DEV (last_exception_trace ).obj_ptr [0 ] == Z_OBJ_P (z_previous_exception )) {
1024- xdebug_str_add_fmt (& tmp_str , "\n\tPrevious trace for %x found\n" , z_previous_exception );
1025- } else {
1026- xdebug_str_add_fmt (& tmp_str , "\n\tPrevious trace for %x NOT found\n" , z_previous_exception );
1027- }
1028- previous_exception_obj = Z_OBJ_P (z_previous_exception );
1029- } while (true);
1203+ do {
1204+ z_previous_exception = zend_read_property (exception_ce , previous_exception_obj , "previous" , sizeof ("previous" )- 1 , 1 , & dummy );
1205+ if (!z_previous_exception || Z_TYPE_P (z_previous_exception ) != IS_OBJECT ) {
1206+ break ;
1207+ }
1208+
1209+ if (first ) {
1210+ first = false;
1211+ found = true;
1212+ xdebug_append_nested_section_header (& tmp_str , PG (html_errors ));
1213+ }
1214+
1215+ xdebug_append_sub_header (& tmp_str , PG (html_errors ));
1216+ xdebug_append_error_description_from_object (& tmp_str , PG (html_errors ), z_previous_exception );
1217+
1218+ z_previous_trace = last_exception_find_trace (Z_OBJ_P (z_previous_exception ));
1219+ xdebug_append_printable_stack_from_zval (& tmp_str , z_previous_trace , PG (html_errors ));
1220+
1221+ previous_exception_obj = Z_OBJ_P (z_previous_exception );
1222+ } while (true);
10301223
1031- if (XG_DEV ( last_exception_trace ). obj_ptr [ 0 ] != NULL ) {
1032- zval_ptr_dtor ( & XG_DEV ( last_exception_trace ). stack_trace [ 0 ] );
1033- XG_DEV ( last_exception_trace ). obj_ptr [ 0 ] = NULL ;
1224+ if (found ) {
1225+ xdebug_append_nested_section_footer ( & tmp_str , PG ( html_errors ) );
1226+ }
10341227 }
10351228
10361229 /* Remember last stack trace so it can be retrieved in an exception handler through
10371230 * xdebug_get_function_stack(['from_exception' => $e]) */
1038- XG_DEV ( last_exception_trace ). obj_ptr [ 0 ] = exception ;
1039- zval_from_stack (& XG_DEV ( last_exception_trace ). stack_trace [ 0 ] , true, true);
1040- zval_from_stack_add_frame (& XG_DEV ( last_exception_trace ). stack_trace [ 0 ] , XDEBUG_VECTOR_TAIL (XG_BASE (stack )), EG (current_execute_data ), true, true);
1231+ z_last_exception_slot = last_exception_get_slot ( exception ) ;
1232+ zval_from_stack (z_last_exception_slot , true, true);
1233+ zval_from_stack_add_frame (z_last_exception_slot , XDEBUG_VECTOR_TAIL (XG_BASE (stack )), EG (current_execute_data ), true, true);
10411234
1042- if (!PG (html_errors )) {
1043- xdebug_str_addc (& tmp_str , '\n' );
1044- }
1045- xdebug_append_error_description (& tmp_str , PG (html_errors ), STR_NAME_VAL (exception_ce -> name ), message ? Z_STRVAL_P (message ) : "" , Z_STRVAL_P (file ), Z_LVAL_P (line ));
1046- xdebug_append_printable_stack (& tmp_str , PG (html_errors ));
10471235 exception_trace = tmp_str .d ;
10481236
1237+ /* Save */
10491238 if (XG_BASE (last_exception_trace )) {
10501239 xdfree (XG_BASE (last_exception_trace ));
10511240 }
10521241 XG_BASE (last_exception_trace ) = exception_trace ;
10531242
1243+ /* Display if expected */
10541244 if (XINI_DEV (show_ex_trace ) || (instanceof_function (exception_ce , zend_ce_error ) && XINI_DEV (show_error_trace ))) {
10551245 if (PG (log_errors )) {
10561246 xdebug_log_stack (STR_NAME_VAL (exception_ce -> name ), Z_STRVAL_P (message ), Z_STRVAL_P (file ), Z_LVAL_P (line ));
0 commit comments