Skip to content

Commit 3001ac8

Browse files
committed
Fixed issue #476: Exception chaining does not work properly
1 parent 6134f67 commit 3001ac8

File tree

9 files changed

+545
-304
lines changed

9 files changed

+545
-304
lines changed

src/develop/stack.c

Lines changed: 220 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737

3838
ZEND_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
"%s: %s in %s on line %d\n",
5769
"\nCall Stack:\n",
@@ -62,10 +74,22 @@ static const char* ansi_formats[11] = {
6274
"\n",
6375
" $%s = %s\n",
6476
" $%s = *uninitialized*\n",
65-
"SCREAM: Error suppression ignored for\n"
77+
"SCREAM: Error suppression ignored for\n",
78+
NULL,
79+
NULL,
80+
81+
// 13+ (for xdebug_append_printable_stack_from_zval)
82+
"\n\tCall Stack:\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\tNested Exceptions:\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&nbsp;=</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

84118
static 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+
246306
static 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+
524700
void 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));

tests/develop/bug00476-001-php81.phpt

Lines changed: 0 additions & 50 deletions
This file was deleted.

0 commit comments

Comments
 (0)