1 /*
2    +----------------------------------------------------------------------+
3    | Xdebug                                                               |
4    +----------------------------------------------------------------------+
5    | Copyright (c) 2002-2021 Derick Rethans                               |
6    +----------------------------------------------------------------------+
7    | This source file is subject to version 1.01 of the Xdebug license,   |
8    | that is bundled with this package in the file LICENSE, and is        |
9    | available at through the world-wide-web at                           |
10    | https://xdebug.org/license.php                                       |
11    | If you did not receive a copy of the Xdebug license and are unable   |
12    | to obtain it through the world-wide-web, please send a note to       |
13    | derick@xdebug.org so we can mail you a copy immediately.             |
14    +----------------------------------------------------------------------+
15  */
16 #include "php_xdebug.h"
17 
18 #include "main/php_ini.h"
19 
20 #include "ext/standard/html.h"
21 #include "ext/standard/php_smart_string.h"
22 #include "zend_exceptions.h"
23 #include "zend_generators.h"
24 
25 #include "monitor.h"
26 #include "stack.h"
27 #include "superglobals.h"
28 
29 #include "base/filter.h"
30 #include "coverage/code_coverage.h"
31 #include "lib/compat.h"
32 #include "lib/lib_private.h"
33 #include "lib/str.h"
34 #include "lib/var_export_html.h"
35 #include "lib/var_export_line.h"
36 #include "profiler/profiler.h"
37 
38 ZEND_EXTERN_MODULE_GLOBALS(xdebug)
39 
40 static const char* text_formats[11] = {
41 	"\n",
42 	"%s: %s in %s on line %d\n",
43 	"\nCall Stack:\n",
44 	"%10.4F %10ld %3d. %s(",
45 	"'%s'",
46 	") %s:%d\n",
47 	"\n\nVariables in local scope (#%d):\n",
48 	"\n",
49 	"  $%s = %s\n",
50 	"  $%s = *uninitialized*\n",
51 	"SCREAM:  Error suppression ignored for\n"
52 };
53 
54 static const char* ansi_formats[11] = {
55 	"\n",
56 	"%s: %s in %s on line %d\n",
57 	"\nCall Stack:\n",
58 	"%10.4F %10ld %3d. %s(",
59 	"'%s'",
60 	") %s:%d\n",
61 	"\n\nVariables in local scope (#%d):\n",
62 	"\n",
63 	"  $%s = %s\n",
64 	"  $%s = *uninitialized*\n",
65 	"SCREAM:  Error suppression ignored for\n"
66 };
67 
DBCS_IBM_EBCDIC_Encoder(Charset cs)68 static const char* html_formats[13] = {
69 	"<br />\n<font size='1'><table class='xdebug-error xe-%s%s' dir='ltr' border='1' cellspacing='0' cellpadding='1'>\n",
70 	"<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",
71 	"<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",
72 	"<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( ",
73 	"<font color='#00bb00'>'%s'</font>",
74 	" )</td><td title='%s' bgcolor='#eeeeec'>%s<b>:</b>%d</td></tr>\n",
75 	"<tr><th align='left' colspan='5' bgcolor='#e9b96e'>Variables in local scope (#%d)</th></tr>\n",
76 	"</table></font>\n",
77 	"<tr><td colspan='2' align='right' bgcolor='#eeeeec' valign='top'><pre>$%s&nbsp;=</pre></td><td colspan='3' bgcolor='#eeeeec'>%s</td></tr>\n",
78 	"<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",
79 	" )</td><td title='%s' bgcolor='#eeeeec'><a style='color: black' href='%s'>%s<b>:</b>%d</a></td></tr>\n",
80 	"<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"
82 };
83 
84 static const char** select_formats(int html)
85 {
86 	if (html) {
87 		return html_formats;
88 	} else if ((XINI_DEV(cli_color) == 1 && xdebug_is_output_tty()) || (XINI_DEV(cli_color) == 2)) {
89 		return ansi_formats;
canEncode(char ch)90 	} else {
91 		return text_formats;
92 	}
93 }
94 
95 void xdebug_log_stack(const char *error_type_str, char *buffer, const char *error_filename, const int error_lineno)
96 {
97 	char *tmp_log_message;
98 	int   i;
99 	function_stack_entry *fse;
100 
101 	tmp_log_message = xdebug_sprintf( "PHP %s:  %s in %s on line %d", error_type_str, buffer, error_filename, error_lineno);
102 	php_log_err(tmp_log_message);
103 	xdfree(tmp_log_message);
104 
105 	if (!XG_BASE(stack) || XDEBUG_VECTOR_COUNT(XG_BASE(stack)) < 1) {
106 		return;
107 	}
108 
encodeArrayLoop(CharBuffer src, ByteBuffer dst)109 	fse = XDEBUG_VECTOR_HEAD(XG_BASE(stack));
110 
111 	php_log_err((char*) "PHP Stack trace:");
112 
113 	for (i = 0; i < XDEBUG_VECTOR_COUNT(XG_BASE(stack)); i++, fse++)
114 	{
115 		int c = 0; /* Comma flag */
116 		unsigned int j = 0; /* Counter */
117 		char *tmp_name;
118 		xdebug_str log_buffer = XDEBUG_STR_INITIALIZER;
119 		int variadic_opened = 0;
120 		int sent_variables = fse->varc;
121 
122 		if (sent_variables > 0 && fse->var[sent_variables-1].is_variadic && Z_ISUNDEF(fse->var[sent_variables-1].data)) {
123 			sent_variables--;
124 		}
125 
126 		tmp_name = xdebug_show_fname(fse->function, XDEBUG_SHOW_FNAME_DEFAULT);
127 		xdebug_str_add_fmt(&log_buffer, "PHP %3d. %s(", fse->level, tmp_name);
128 		xdfree(tmp_name);
129 
130 		/* Printing vars */
131 		for (j = 0; j < sent_variables; j++) {
132 			xdebug_str *tmp_value;
133 
134 			if (c) {
135 				xdebug_str_add_literal(&log_buffer, ", ");
136 			} else {
137 				c = 1;
138 			}
139 
140 			if (fse->var[j].is_variadic) {
141 				xdebug_str_add_literal(&log_buffer, "...");
142 				variadic_opened = 1;
143 			}
144 
145 			if (fse->var[j].name) {
146 				xdebug_str_add_fmt(&log_buffer, "$%s = ", ZSTR_VAL(fse->var[j].name));
147 			}
148 
149 			if (fse->var[j].is_variadic) {
150 				xdebug_str_add_literal(&log_buffer, "variadic(");
151 				c = 0;
152 				continue;
153 			}
154 
155 			if (!Z_ISUNDEF(fse->var[j].data)) {
156 				tmp_value = xdebug_get_zval_value_line(&fse->var[j].data, 0, NULL);
157 				xdebug_str_add_str(&log_buffer, tmp_value);
158 				xdebug_str_free(tmp_value);
159 			} else {
160 				xdebug_str_add_literal(&log_buffer, "*uninitialized*");
161 			}
162 		}
163 
164 		if (variadic_opened) {
165 			xdebug_str_add_literal(&log_buffer, ")");
166 		}
167 
168 		xdebug_str_add_fmt(&log_buffer, ") %s:%d", ZSTR_VAL(fse->filename), fse->lineno);
169 		php_log_err(log_buffer.d);
170 		xdebug_str_destroy(&log_buffer);
171 	}
172 }
173 
174 void xdebug_append_error_head(xdebug_str *str, int html, const char *error_type_str)
175 {
176 	const char **formats = select_formats(html);
177 
178 	if (html) {
179 		xdebug_str_add_fmt(str, formats[0], error_type_str, XG_DEV(in_at) ? " xe-scream" : "");
encodeBufferLoop(CharBuffer src, ByteBuffer dst)180 		if (XG_DEV(in_at)) {
181 			xdebug_str_add_const(str, formats[12]);
182 		}
183 	} else {
184 		xdebug_str_add_const(str, formats[0]);
185 		if (XG_DEV(in_at)) {
186 			xdebug_str_add_const(str, formats[10]);
187 		}
188 	}
189 }
190 
191 void xdebug_append_error_description(xdebug_str *str, int html, const char *error_type_str, const char *buffer, const char *error_filename, const int error_lineno)
192 {
193 	const char **formats = select_formats(html);
194 	char *escaped;
195 
196 	if (!html) {
197 		escaped = estrdup(buffer);
198 	} else {
199 		zend_string *tmp;
200 		char *first_closing = strchr(buffer, ']');
201 
202 		/* We do need to escape HTML entities here, as HTML chars could be in
203 		 * the error message. However, PHP in some circumstances also adds an
204 		 * HTML link to a manual page. That bit, we don't need to escape. So
205 		 * this bit of code finds the portion that doesn't need escaping, adds
206 		 * it to a tmp string, and then adds an HTML escaped string for the
207 		 * rest of the original buffer. */
208 		if (first_closing && strstr(buffer, "() [<a href=") != NULL) {
209 			smart_string special_escaped = { 0, 0, 0 };
210 
211 			*first_closing = '\0';
212 			first_closing++;
213 
214 			smart_string_appends(&special_escaped, buffer);
215 			tmp = php_escape_html_entities((unsigned char *) first_closing, strlen(first_closing), 0, 0, NULL);
216 			smart_string_appends(&special_escaped, tmp->val);
217 			zend_string_free(tmp);
218 
219 			smart_string_0(&special_escaped);
220 			escaped = estrdup(special_escaped.c);
221 			smart_string_free(&special_escaped);
222 		} else if (strncmp(buffer, "assert()", 8) == 0) {
223 			/* Also don't escape if we're in an assert, as things are already
224 			 * escaped. It's all nice and consistent ey? */
225 			escaped = estrdup(buffer);
226 		} else {
227 			tmp = php_escape_html_entities((unsigned char *) buffer, strlen(buffer), 0, 0, NULL);
228 			escaped = estrdup(tmp->val);
229 			zend_string_free(tmp);
230 		}
231 	}
232 
233 	if (strlen(XINI_LIB(file_link_format)) > 0 && html && strcmp(error_filename, "Unknown") != 0) {
234 		char *file_link;
235 
236 		xdebug_format_file_link(&file_link, error_filename, error_lineno);
237 		xdebug_str_add_fmt(str, formats[11], error_type_str, escaped, file_link, error_filename, error_lineno);
238 		xdfree(file_link);
239 	} else {
240 		xdebug_str_add_fmt(str, formats[1], error_type_str, escaped, error_filename, error_lineno);
241 	}
242 
243 	efree(escaped);
244 }
245 
encodeLoop(CharBuffer src, ByteBuffer dst)246 static void add_single_value(xdebug_str *str, zval *zv, int html)
247 {
248 	xdebug_str *tmp_value = NULL;
249 	char       *tmp_html_value = NULL;
250 	size_t      newlen;
251 
252 	if (html) {
253 		tmp_value = xdebug_get_zval_value_line(zv, 0, NULL);
254 		tmp_html_value = xdebug_xmlize(tmp_value->d, tmp_value->l, &newlen);
255 
256 		xdebug_str_add_literal(str, "<span>");
257 		xdebug_str_add(str, tmp_html_value, 0);
258 		xdebug_str_add_literal(str, "</span>");
259 
260 		xdebug_str_free(tmp_value);
261 		efree(tmp_html_value);
262 	} else {
263 		tmp_value = xdebug_get_zval_value_line(zv, 0, NULL);
264 
265 		if (tmp_value) {
266 			xdebug_str_add_str(str, tmp_value);
267 			xdebug_str_free(tmp_value);
268 		} else {
269 			xdebug_str_add_literal(str, "???");
270 		}
271 	}
272 }
273 
274 #define XDEBUG_VAR_FORMAT_INITIALISED   0
275 #define XDEBUG_VAR_FORMAT_UNINITIALISED 1
276 
277 static const char* text_var_formats[2] = {
278 	"  $%s = %s\n",
279 	"  $%s = *uninitialized*\n",
280 };
281 
282 static const char* ansi_var_formats[2] = {
283 	"  $%s = %s\n",
284 	"  $%s = *uninitialized*\n",
285 };
286 
287 static const char* html_var_formats[2] = {
288 	"<tr><td colspan='2' align='right' bgcolor='#eeeeec' valign='top'><pre>$%s&nbsp;=</pre></td><td colspan='3' bgcolor='#eeeeec'>%s</td></tr>\n",
289 	"<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",
290 };
291 
292 static const char** get_var_format_string(int html)
293 {
294 	if (html) {
295 		return html_var_formats;
296 	} else if ((XINI_DEV(cli_color) == 1 && xdebug_is_output_tty()) || (XINI_DEV(cli_color) == 2)) {
297 		return ansi_var_formats;
298 	} else {
299 		return text_var_formats;
300 	}
301 }
302 
303 
304 static void xdebug_dump_used_var_with_contents(void *htmlq, xdebug_hash_element* he, void *argument)
305 {
306 	int          html = *(int*) htmlq;
307 	zval         zvar;
308 	xdebug_str  *contents;
309 	xdebug_str  *name = (xdebug_str*) he->ptr;
310 	HashTable   *tmp_ht;
311 	const char **formats;
312 	xdebug_str   *str = (xdebug_str *) argument;
313 
314 	if (!he->ptr) {
315 		return;
316 	}
317 
318 	/* Bail out on $this and $GLOBALS */
319 	if (strcmp(name->d, "this") == 0 || strcmp(name->d, "GLOBALS") == 0) {
320 		return;
321 	}
322 
323 	if (EG(current_execute_data) && !(ZEND_CALL_INFO(EG(current_execute_data)) & ZEND_CALL_HAS_SYMBOL_TABLE)) {
324 		zend_rebuild_symbol_table();
325 	}
326 
327 	tmp_ht = xdebug_lib_get_active_symbol_table();
328 	{
329 		zend_execute_data *ex = EG(current_execute_data);
330 		while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) {
331 			ex = ex->prev_execute_data;
332 		}
333 		if (ex) {
334 			xdebug_lib_set_active_data(ex);
335 			xdebug_lib_set_active_symbol_table(ex->symbol_table);
336 		}
337 	}
338 
339 	xdebug_get_php_symbol(&zvar, name);
340 	xdebug_lib_set_active_symbol_table(tmp_ht);
341 
342 	formats = get_var_format_string(PG(html_errors));
343 
344 	if (Z_TYPE(zvar) == IS_UNDEF) {
345 		xdebug_str_add_fmt(str, formats[XDEBUG_VAR_FORMAT_UNINITIALISED], name->d);
346 		return;
347 	}
348 
349 	if (html) {
350 		contents = xdebug_get_zval_value_html(NULL, &zvar, 0, NULL);
351 	} else {
352 		contents = xdebug_get_zval_value_line(&zvar, 0, NULL);
353 	}
354 
355 	if (contents) {
356 		xdebug_str_add_fmt(str, formats[XDEBUG_VAR_FORMAT_INITIALISED], name->d, contents->d);
357 	} else {
358 		xdebug_str_add_fmt(str, formats[XDEBUG_VAR_FORMAT_UNINITIALISED], name->d);
359 	}
360 
361 	if (contents) {
362 		xdebug_str_free(contents);
363 	}
364 	zval_ptr_dtor_nogc(&zvar);
365 }
366 
367 void xdebug_append_printable_stack(xdebug_str *str, int html)
368 {
369 	int                   printed_frames = 0;
370 	const char          **formats = select_formats(html);
371 	int                   i;
372 	function_stack_entry *fse;
373 
374 	if (!XG_BASE(stack) || XDEBUG_VECTOR_COUNT(XG_BASE(stack)) < 1) {
375 		return;
376 	}
377 
378 	fse = XDEBUG_VECTOR_HEAD(XG_BASE(stack));
379 
380 	xdebug_str_add_const(str, formats[2]);
381 
382 	for (i = 0; i < XDEBUG_VECTOR_COUNT(XG_BASE(stack)); i++, fse++)
383 	{
384 		int c = 0; /* Comma flag */
385 		unsigned int j = 0; /* Counter */
386 		char *tmp_name;
387 		int variadic_opened = 0;
388 		int sent_variables = fse->varc;
389 
390 		if (sent_variables > 0 && fse->var[sent_variables-1].is_variadic && Z_ISUNDEF(fse->var[sent_variables-1].data)) {
391 			sent_variables--;
392 		}
393 
394 		if (xdebug_is_stack_frame_filtered(XDEBUG_FILTER_STACK, fse)) {
395 			continue;
396 		}
397 		tmp_name = xdebug_show_fname(fse->function, html ? XDEBUG_SHOW_FNAME_ALLOW_HTML : XDEBUG_SHOW_FNAME_DEFAULT);
398 		if (html) {
399 			xdebug_str_add_fmt(str, formats[3], fse->level, XDEBUG_SECONDS_SINCE_START(fse->nanotime), fse->memory, tmp_name);
400 		} else {
401 			xdebug_str_add_fmt(str, formats[3], XDEBUG_SECONDS_SINCE_START(fse->nanotime), fse->memory, fse->level, tmp_name);
402 		}
403 		xdfree(tmp_name);
404 
405 		/* Printing vars */
406 		for (j = 0; j < sent_variables; j++) {
407 			if (c) {
408 				xdebug_str_add_literal(str, ", ");
409 			} else {
410 				c = 1;
411 			}
412 
413 			if (
414 				(fse->var[j].is_variadic && Z_ISUNDEF(fse->var[j].data))
415 			) {
416 				xdebug_str_add_literal(str, "...");
417 			}
418 
419 			if (fse->var[j].name) {
420 				if (html) {
421 					xdebug_str_add_literal(str, "<span>$");
422 					xdebug_str_add_zstr(str, fse->var[j].name);
423 					xdebug_str_add_literal(str, " = </span>");
424 				} else {
425 					xdebug_str_add_literal(str, "$");
426 					xdebug_str_add_zstr(str, fse->var[j].name);
427 					xdebug_str_add_literal(str, " = ");
428 				}
429 			}
430 
431 			if (!variadic_opened && fse->var[j].is_variadic && Z_ISUNDEF(fse->var[j].data)) {
432 				if (html) {
433 					xdebug_str_add_literal(str, "<i>variadic</i>(");
434 				} else {
435 					xdebug_str_add_literal(str, "variadic(");
436 				}
437 				c = 0;
438 				variadic_opened = 1;
439 				continue;
440 			}
441 
442 			if (!Z_ISUNDEF(fse->var[j].data)) {
443 				add_single_value(str, &fse->var[j].data, html);
444 			} else {
445 				xdebug_str_add_literal(str, "???");
446 			}
447 		}
448 
449 		if (variadic_opened) {
450 			xdebug_str_add_literal(str, ")");
451 		}
452 
453 		if (fse->include_filename) {
454 			if (html) {
455 				xdebug_str_add_literal(str, "<font color='#00bb00'>'");
456 				xdebug_str_add_zstr(str, fse->include_filename);
457 				xdebug_str_add_literal(str, "</font>");
458 			} else {
459 				xdebug_str_addc(str, '\'');
460 				xdebug_str_add_zstr(str, fse->include_filename);
461 				xdebug_str_addc(str, '\'');
462 			}
463 		}
464 
465 		if (html) {
466 			char *formatted_filename;
467 			xdebug_format_filename(&formatted_filename, "...%s%n", fse->filename);
468 
469 			if (strlen(XINI_LIB(file_link_format)) > 0 && strcmp(ZSTR_VAL(fse->filename), "Unknown") != 0) {
470 				char *file_link;
471 
472 				xdebug_format_file_link(&file_link, ZSTR_VAL(fse->filename), fse->lineno);
473 				xdebug_str_add_fmt(str, formats[10], ZSTR_VAL(fse->filename), file_link, formatted_filename, fse->lineno);
474 				xdfree(file_link);
475 			} else {
476 				xdebug_str_add_fmt(str, formats[5], ZSTR_VAL(fse->filename), formatted_filename, fse->lineno);
477 			}
478 
479 			xdfree(formatted_filename);
480 		} else {
481 			xdebug_str_add_fmt(str, formats[5], ZSTR_VAL(fse->filename), fse->lineno);
482 		}
483 
484 		printed_frames++;
485 		if (XINI_DEV(max_stack_frames) > 0 && printed_frames >= XINI_DEV(max_stack_frames)) {
486 			break;
487 		}
488 	}
489 
490 	if (XINI_DEV(dump_globals) && !(XINI_DEV(dump_once) && XG_LIB(dumped))) {
491 		char *tmp = xdebug_get_printable_superglobals(html);
492 
493 		if (tmp) {
494 			xdebug_str_add(str, tmp, 1);
495 		}
496 		XG_LIB(dumped) = 1;
497 	}
498 
499 	if (XINI_DEV(show_local_vars) && XG_BASE(stack) && XDEBUG_VECTOR_TAIL(XG_BASE(stack))) {
500 		int scope_nr = XDEBUG_VECTOR_COUNT(XG_BASE(stack));
501 
502 		fse = XDEBUG_VECTOR_TAIL(XG_BASE(stack));
503 		if (fse->user_defined == XDEBUG_BUILT_IN && xdebug_vector_element_is_valid(XG_BASE(stack), fse -1)) {
504 			fse = fse - 1;
505 			scope_nr--;
506 		}
507 		if (fse->declared_vars && fse->declared_vars->size) {
508 			xdebug_hash *tmp_hash;
509 
510 			xdebug_str_add_fmt(str, formats[6], scope_nr);
511 			tmp_hash = xdebug_declared_var_hash_from_llist(fse->declared_vars);
512 			xdebug_hash_apply_with_argument(tmp_hash, (void*) &html, xdebug_dump_used_var_with_contents, (void *) str);
513 			xdebug_hash_destroy(tmp_hash);
514 		}
515 	}
516 }
517 
518 void xdebug_append_error_footer(xdebug_str *str, int html)
519 {
520 	const char **formats = select_formats(html);
521 
522 	xdebug_str_add_const(str, formats[7]);
523 }
524 
525 char *xdebug_get_printable_stack(int html, int error_type, const char *buffer, const char *error_filename, const int error_lineno, int include_decription)
526 {
527 	char *prepend_string;
528 	char *append_string;
529 	char *error_type_str = xdebug_error_type(error_type);
530 	char *error_type_str_simple = xdebug_error_type_simple(error_type);
531 	xdebug_str str = XDEBUG_STR_INITIALIZER;
532 
533 	prepend_string = INI_STR((char*) "error_prepend_string");
534 	append_string = INI_STR((char*) "error_append_string");
535 
536 	if (prepend_string) {
537 		xdebug_str_add(&str, prepend_string, 0);
538 	}
539 	xdebug_append_error_head(&str, html, error_type_str_simple);
540 	if (include_decription) {
541 		xdebug_append_error_description(&str, html, error_type_str, buffer, error_filename, error_lineno);
542 	}
543 	xdebug_append_printable_stack(&str, html);
544 	xdebug_append_error_footer(&str, html);
545 	if (append_string) {
546 		xdebug_str_add(&str, append_string, 0);
547 	}
548 
549 	xdfree(error_type_str);
550 	xdfree(error_type_str_simple);
551 
552 	return str.d;
553 }
554 
555 static void php_output_error(const char *error)
556 {
557 #ifdef PHP_DISPLAY_ERRORS_STDERR
558 	if (PG(display_errors) == PHP_DISPLAY_ERRORS_STDERR) {
559 		fputs(error, stderr);
560 		fflush(stderr);
561 		return;
562 	}
563 #endif
564 	php_printf("%s", error);
565 }
566 
567 char *xdebug_strip_php_stack_trace(char *buffer)
568 {
569 	char *tmp_buf, *p;
570 
571 	if (strncmp(buffer, "Uncaught ", 9) != 0) {
572 		return NULL;
573 	}
574 
575 	/* find first new line */
576 	p = strchr(buffer, '\n');
577 	if (!p) {
578 		p = buffer + strlen(buffer);
579 	} else {
580 		/* find the last " in ", which isn't great and might not work... but in most cases it will */
581 		p = xdebug_strrstr(buffer, " in ");
582 		if (!p) {
583 			p = buffer + strlen(buffer);
584 		}
585 	}
586 	/* Create new buffer */
587 	tmp_buf = calloc(p - buffer + 1, 1);
588 	strncpy(tmp_buf, buffer, p - buffer);
589 
590 	return tmp_buf;
591 }
592 
593 static char *xdebug_handle_stack_trace(int type, char *error_type_str, const char *error_filename, const unsigned int error_lineno, char *buffer)
594 {
595 	char *printable_stack;
596 	char *tmp_buf;
597 
598 	/* We need to see if we have an uncaught exception fatal error now */
599 	if (type == E_ERROR && ((tmp_buf = xdebug_strip_php_stack_trace(buffer)) != NULL)) {
600 		xdebug_str str = XDEBUG_STR_INITIALIZER;
601 
602 		/* Append error */
603 		xdebug_append_error_head(&str, PG(html_errors), "uncaught-exception");
604 		xdebug_append_error_description(&str, PG(html_errors), error_type_str, tmp_buf, error_filename, error_lineno);
605 		xdebug_append_printable_stack(&str, PG(html_errors));
606 		if (XG_BASE(last_exception_trace)) {
607 			xdebug_str_add(&str, XG_BASE(last_exception_trace), 0);
608 		}
609 		xdebug_append_error_footer(&str, PG(html_errors));
610 
611 		free(tmp_buf);
612 		printable_stack = str.d;
613 	} else {
614 		printable_stack = xdebug_get_printable_stack(PG(html_errors), type, buffer, error_filename, error_lineno, 1);
615 	}
616 
617 	return printable_stack;
618 }
619 
620 #if PHP_VERSION_ID >= 80000
621 static void clear_last_error()
622 {
623 	if (PG(last_error_message)) {
624 		zend_string_release(PG(last_error_message));
625 		PG(last_error_message) = NULL;
626 	}
627 	if (PG(last_error_file)) {
628 # if PHP_VERSION_ID >= 80100
629 		zend_string_release(PG(last_error_file));
630 # else
631 		free(PG(last_error_file));
632 # endif
633 		PG(last_error_file) = NULL;
634 	}
635 }
636 #else
637 static void clear_last_error()
638 {
639 	if (PG(last_error_message)) {
640 		char *s = PG(last_error_message);
641 		PG(last_error_message) = NULL;
642 		free(s);
643 	}
644 	if (PG(last_error_file)) {
645 		char *s = PG(last_error_file);
646 		PG(last_error_file) = NULL;
647 		free(s);
648 	}
649 }
650 #endif
651 
652 /* Error callback for formatting stack traces */
653 #if PHP_VERSION_ID >= 80100
654 void xdebug_develop_error_cb(int orig_type, zend_string *error_filename, const unsigned int error_lineno, zend_string *message)
655 {
656 #elif PHP_VERSION_ID >= 80000
657 void xdebug_develop_error_cb(int orig_type, const char *error_filename, const unsigned int error_lineno, zend_string *message)
658 {
659 #else
660 void xdebug_develop_error_cb(int orig_type, const char *error_filename, const unsigned int error_lineno, const char *format, va_list args)
661 {
662 	char *buffer;
663 	int buffer_len;
664 #endif
665 
666 	char *error_type_str;
667 	int display;
668 	int type = orig_type & E_ALL;
669 	error_handling_t  error_handling;
670 	zend_class_entry *exception_class;
671 
672 #if PHP_VERSION_ID < 80000
673 	buffer_len = vspprintf(&buffer, PG(log_errors_max_len), format, args);
674 #endif
675 
676 	error_type_str = xdebug_error_type(type);
677 
678 	/* check for repeated errors to be ignored */
679 	if (PG(ignore_repeated_errors) && PG(last_error_message)) {
680 			/* no check for PG(last_error_file) is needed since it cannot
681 			 * be NULL if PG(last_error_message) is not NULL */
682 
683 #if PHP_VERSION_ID >= 80000
684 			if (!zend_string_equals(PG(last_error_message), message)
685 #else
686 			if (strcmp(PG(last_error_message), buffer) != 0
687 #endif
688 				||
689 					(!PG(ignore_repeated_source) && (
690 						(PG(last_error_lineno) != (int)error_lineno) ||
691 #if PHP_VERSION_ID >= 80100
692 						!zend_string_equals(PG(last_error_file), error_filename)
693 #else
694 						strcmp(PG(last_error_file), error_filename) != 0
695 #endif
696 					))
697 			) {
698 					display = 1;
699 			} else {
700 					display = 0;
701 			}
702 	} else {
703 			display = 1;
704 	}
705 
706 #if PHP_VERSION_ID < 70300
707 	/* Store last error message for error_get_last() */
708 	if (display) {
709 		clear_last_error();
710 		if (!error_filename) {
711 			error_filename = "Unknown";
712 		}
713 		PG(last_error_type) = type;
714 		PG(last_error_message) = strdup(buffer);
715 		PG(last_error_file) = strdup(error_filename);
716 		PG(last_error_lineno) = error_lineno;
717 	}
718 #endif
719 	error_handling  = EG(error_handling);
720 	exception_class = EG(exception_class);
721 #if PHP_VERSION_ID >= 70300
722 	/* according to error handling mode, throw exception or show it */
723 	if (error_handling == EH_THROW) {
724 #else
725 	/* according to error handling mode, suppress error, throw exception or show it */
726 	if (error_handling != EH_NORMAL) {
727 #endif
728 		switch (type) {
729 			case E_ERROR:
730 			case E_CORE_ERROR:
731 			case E_COMPILE_ERROR:
732 			case E_USER_ERROR:
733 			case E_PARSE:
734 				/* fatal errors are real errors and cannot be made exceptions */
735 				break;
736 			case E_STRICT:
737 			case E_DEPRECATED:
738 			case E_USER_DEPRECATED:
739 				/* for the sake of BC to old damaged code */
740 				break;
741 			case E_NOTICE:
742 			case E_USER_NOTICE:
743 				/* notices are no errors and are not treated as such like E_WARNINGS */
744 				break;
745 			default:
746 				/* throw an exception if we are in EH_THROW mode
747 				 * but DO NOT overwrite a pending exception
748 				 */
749 #if PHP_VERSION_ID >= 70300
750 				if (!EG(exception)) {
751 #else
752 				if (error_handling == EH_THROW && !EG(exception)) {
753 #endif
754 #if PHP_VERSION_ID >= 80000
755 					zend_throw_error_exception(exception_class, message, 0, type);
756 #else
757 					zend_throw_error_exception(exception_class, buffer, 0, type);
758 #endif
759 				}
760 #if PHP_VERSION_ID < 80000
761 				efree(buffer);
762 #endif
763 				xdfree(error_type_str);
764 				return;
765 		}
766 	}
767 
768 #if PHP_VERSION_ID >= 70300
769 	/* Store last error message for error_get_last() */
770 	if (display) {
771 		clear_last_error();
772 		if (!error_filename) {
773 # if PHP_VERSION_ID >= 80100
774 			error_filename = zend_string_init(ZEND_STRL("Unknown"), 0);
775 # else
776 			error_filename = "Unknown";
777 # endif
778 		}
779 		PG(last_error_type) = type;
780 # if PHP_VERSION_ID >= 80000
781 		PG(last_error_message) = zend_string_copy(message);
782 # else
783 		PG(last_error_message) = strdup(buffer);
784 # endif
785 # if PHP_VERSION_ID >= 80100
786 		PG(last_error_file) = zend_string_copy(error_filename);
787 # else
788 		PG(last_error_file) = strdup(error_filename);
789 # endif
790 		PG(last_error_lineno) = error_lineno;
791 	}
792 #endif
793 
794 	if ((EG(error_reporting) | XINI_DEV(force_error_reporting)) & type) {
795 		/* Log to logger */
796 		if (PG(log_errors)) {
797 
798 #ifdef PHP_WIN32
799 			if (type==E_CORE_ERROR || type==E_CORE_WARNING) {
800 #if PHP_VERSION_ID >= 80000
801 				php_syslog(LOG_ALERT, "PHP %s: %s (%s)", error_type_str, ZSTR_VAL(message), GetCommandLine());
802 #else
803 				MessageBox(NULL, buffer, error_type_str, MB_OK);
804 #endif
805 			}
806 #endif
807 #if PHP_VERSION_ID >= 80100
808 			xdebug_log_stack(error_type_str, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno);
809 #elif PHP_VERSION_ID >= 80000
810 			xdebug_log_stack(error_type_str, ZSTR_VAL(message), error_filename, error_lineno);
811 #else
812 			xdebug_log_stack(error_type_str, buffer, error_filename, error_lineno);
813 #endif
814 			if (XINI_DEV(dump_globals) && !(XINI_DEV(dump_once) && XG_LIB(dumped))) {
815 				char *printable_stack = xdebug_get_printable_superglobals(0);
816 
817 				if (printable_stack) {
818 					int pc;
819 
820 					xdebug_arg *parts = xdebug_arg_ctor();
821 
822 					xdebug_explode("\n", printable_stack, parts, -1);
823 
824 					for (pc = 0; pc < parts->c; pc++) {
825 						char *tmp_line = xdebug_sprintf("PHP %s", parts->args[pc]);
826 						php_log_err(tmp_line);
827 						xdfree(tmp_line);
828 					}
829 
830 					xdebug_arg_dtor(parts);
831 					xdfree(printable_stack);
832 					php_log_err((char*) "PHP ");
833 				}
834 			}
835 		}
836 
837 		/* Display errors */
838 		if ((PG(display_errors) || XINI_DEV(force_display_errors)) && !PG(during_request_startup)) {
839 			char *printable_stack;
840 
841 #if PHP_VERSION_ID >= 80100
842 			printable_stack = xdebug_handle_stack_trace(type, error_type_str, ZSTR_VAL(error_filename), error_lineno, ZSTR_VAL(message));
843 #elif PHP_VERSION_ID >= 80000
844 			printable_stack = xdebug_handle_stack_trace(type, error_type_str, error_filename, error_lineno, ZSTR_VAL(message));
845 #else
846 			printable_stack = xdebug_handle_stack_trace(type, error_type_str, error_filename, error_lineno, buffer);
847 #endif
848 
849 			if (XG_LIB(do_collect_errors) && (type != E_ERROR) && (type != E_COMPILE_ERROR) && (type != E_USER_ERROR)) {
850 				xdebug_llist_insert_next(XG_DEV(collected_errors), XDEBUG_LLIST_TAIL(XG_DEV(collected_errors)), printable_stack);
851 			} else {
852 				php_output_error(printable_stack);
853 				xdfree(printable_stack);
854 			}
855 		} else if (XG_LIB(do_collect_errors)) {
856 			char *printable_stack;
857 #if PHP_VERSION_ID >= 80100
858 			printable_stack = xdebug_get_printable_stack(PG(html_errors), type, ZSTR_VAL(message), ZSTR_VAL(error_filename), error_lineno, 1);
859 #elif PHP_VERSION_ID >= 80000
860 			printable_stack = xdebug_get_printable_stack(PG(html_errors), type, ZSTR_VAL(message), error_filename, error_lineno, 1);
861 #else
862 			printable_stack = xdebug_get_printable_stack(PG(html_errors), type, buffer, error_filename, error_lineno, 1);
863 #endif
864 			xdebug_llist_insert_next(XG_DEV(collected_errors), XDEBUG_LLIST_TAIL(XG_DEV(collected_errors)), printable_stack);
865 		}
866 	}
867 
868 	{
869 #if PHP_VERSION_ID >= 80100
870 		zend_string *tmp_error_filename = zend_string_copy(error_filename);
871 #else
872 		zend_string *tmp_error_filename = zend_string_init(error_filename, strlen(error_filename), 0);
873 #endif
874 #if PHP_VERSION_ID >= 80000
875 		xdebug_debugger_error_cb(tmp_error_filename, error_lineno, type, error_type_str, ZSTR_VAL(message));
876 #else
877 		xdebug_debugger_error_cb(tmp_error_filename, error_lineno, type, error_type_str, buffer);
878 #endif
879 		zend_string_release(tmp_error_filename);
880 	}
881 
882 	xdfree(error_type_str);
883 
884 	if (type & XINI_DEV(halt_level) & XDEBUG_ALLOWED_HALT_LEVELS) {
885 		type = E_USER_ERROR;
886 	}
887 
888 	/* Bail out if we can't recover */
889 	switch (type) {
890 		case E_CORE_ERROR:
891 			if (!php_get_module_initialized()) {
892 				/* bad error in module startup - no way we can live with this */
893 				exit(-2);
894 			}
895 			XDEBUG_BREAK_INTENTIONALLY_MISSING
896 
897 		case E_ERROR:
898 		case E_RECOVERABLE_ERROR:
899 		case E_PARSE:
900 		case E_COMPILE_ERROR:
901 		case E_USER_ERROR:
902 			EG(exit_status) = 255;
903 			if (php_get_module_initialized()) {
904 				if (!PG(display_errors) &&
905 				    !SG(headers_sent) &&
906 					SG(sapi_headers).http_response_code == 200
907 				) {
908 					sapi_header_line ctr = { 0, 0, 0 };
909 
910 					ctr.line = (char*) "HTTP/1.0 500 Internal Server Error";
911 					ctr.line_len = sizeof("HTTP/1.0 500 Internal Server Error") - 1;
912 					sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
913 				}
914 				/* the parser would return 1 (failure), we can bail out nicely */
915 #if PHP_VERSION_ID >= 80000
916 				if (!(orig_type & E_DONT_BAIL)) {
917 #else
918 				if (type != E_PARSE) {
919 #endif
920 					/* restore memory limit */
921 					zend_set_memory_limit(PG(memory_limit));
922 #if PHP_VERSION_ID < 80000
923 					efree(buffer);
924 #endif
925 					zend_objects_store_mark_destructed(&EG(objects_store));
926 					_zend_bailout((char*) __FILE__, __LINE__);
927 					return;
928 				}
929 			}
930 			break;
931 	}
932 
933 #if PHP_VERSION_ID < 70400
934 	/* Log if necessary */
935 	if (!display) {
936 		efree(buffer);
937 		return;
938 	}
939 
940 	if (PG(track_errors) && EG(active)) {
941 		zval tmp;
942 		ZVAL_STRINGL(&tmp, buffer, buffer_len);
943 
944 		if (EG(current_execute_data)) {
945 			if (zend_set_local_var_str("php_errormsg", sizeof("php_errormsg")-1, &tmp, 0) == FAILURE) {
946 				zval_ptr_dtor(&tmp);
947 			}
948 		} else {
949 			zend_hash_str_update(&EG(symbol_table), "php_errormsg", sizeof("php_errormsg"), &tmp);
950 		}
951 	}
952 #endif
953 
954 #if PHP_VERSION_ID < 80000
955 	efree(buffer);
956 #endif
957 }
958 
959 #if PHP_VERSION_ID >= 80000
960 void xdebug_develop_throw_exception_hook(zend_object *exception, zval *file, zval *line, zval *code, char *code_str, zval *message)
961 {
962 	zend_class_entry *exception_ce = exception->ce;
963 #else
964 void xdebug_develop_throw_exception_hook(zval *exception, zval *file, zval *line, zval *code, char *code_str, zval *message)
965 {
966 	zend_class_entry *exception_ce = Z_OBJCE_P(exception);
967 #endif
968 	zval *xdebug_message_trace, *previous_exception;
969 	char *exception_trace;
970 	xdebug_str tmp_str = XDEBUG_STR_INITIALIZER;
971 	zval dummy;
972 
973 	previous_exception = zend_read_property(exception_ce, exception, "previous", sizeof("previous")-1, 1, &dummy);
974 	if (previous_exception && Z_TYPE_P(previous_exception) == IS_OBJECT) {
975 #if PHP_VERSION_ID >= 80000
976 		xdebug_message_trace = zend_read_property(exception_ce, Z_OBJ_P(previous_exception), "xdebug_message", sizeof("xdebug_message")-1, 1, &dummy);
977 #else
978 		xdebug_message_trace = zend_read_property(exception_ce, previous_exception, "xdebug_message", sizeof("xdebug_message")-1, 1, &dummy);
979 #endif
980 		if (xdebug_message_trace && Z_TYPE_P(xdebug_message_trace) != IS_NULL) {
981 			xdebug_str_add(&tmp_str, Z_STRVAL_P(xdebug_message_trace), 0);
982 		}
983 	}
984 
985 	if (!PG(html_errors)) {
986 		xdebug_str_addc(&tmp_str, '\n');
987 	}
988 	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));
989 	xdebug_append_printable_stack(&tmp_str, PG(html_errors));
990 	exception_trace = tmp_str.d;
991 	zend_update_property_string(exception_ce, exception, "xdebug_message", sizeof("xdebug_message")-1, exception_trace);
992 
993 	if (XG_BASE(last_exception_trace)) {
994 		xdfree(XG_BASE(last_exception_trace));
995 	}
996 	XG_BASE(last_exception_trace) = exception_trace;
997 
998 	if (XINI_DEV(show_ex_trace) || (instanceof_function(exception_ce, zend_ce_error) && XINI_DEV(show_error_trace))) {
999 		if (PG(log_errors)) {
1000 			xdebug_log_stack(STR_NAME_VAL(exception_ce->name), Z_STRVAL_P(message), Z_STRVAL_P(file), Z_LVAL_P(line));
1001 		}
1002 		if (PG(display_errors)) {
1003 			xdebug_str displ_tmp_str = XDEBUG_STR_INITIALIZER;
1004 			xdebug_append_error_head(&displ_tmp_str, PG(html_errors), "exception");
1005 			xdebug_str_add(&displ_tmp_str, exception_trace, 0);
1006 			xdebug_append_error_footer(&displ_tmp_str, PG(html_errors));
1007 
1008 			php_printf("%s", displ_tmp_str.d);
1009 			xdebug_str_dtor(displ_tmp_str);
1010 		}
1011 	}
1012 }
1013 
1014 /* {{{ proto int xdebug_get_stack_depth()
1015    Returns the stack depth */
1016 PHP_FUNCTION(xdebug_get_stack_depth)
1017 {
1018 	/* We substract one so that the function call to xdebug_get_stack_depth()
1019 	 * is not part of the returned depth. */
1020 	RETURN_LONG(XDEBUG_VECTOR_COUNT(XG_BASE(stack)) - 1);
1021 }
1022 
1023 /* {{{ proto array xdebug_get_function_stack()
1024    Returns an array representing the current stack */
1025 PHP_FUNCTION(xdebug_get_function_stack)
1026 {
1027 	function_stack_entry *fse;
1028 	unsigned int          i;
1029 	unsigned int          j;
1030 	zval                 *frame;
1031 	zval                 *params;
1032 	int                   variadic_opened = 0;
1033 
1034 	if (!XDEBUG_MODE_IS(XDEBUG_MODE_DEVELOP)) {
1035 		php_error(E_WARNING, "Function must be enabled in php.ini by setting 'xdebug.mode' to 'develop'");
1036 		array_init(return_value);
1037 		return;
1038 	}
1039 
1040 	array_init(return_value);
1041 
1042 	fse = XDEBUG_VECTOR_HEAD(XG_BASE(stack));
1043 
1044 	for (i = 0; i < XDEBUG_VECTOR_COUNT(XG_BASE(stack)) - 1; i++, fse++) {
1045 		int sent_variables = fse->varc;
1046 
1047 		if (fse->function.function) {
1048 			if (strcmp(fse->function.function, "xdebug_get_function_stack") == 0) {
1049 				return;
1050 			}
1051 		}
1052 
1053 		if (sent_variables > 0 && fse->var[sent_variables-1].is_variadic && Z_ISUNDEF(fse->var[sent_variables-1].data)) {
1054 			sent_variables--;
1055 		}
1056 
1057 		/* Initialize frame array */
1058 		XDEBUG_MAKE_STD_ZVAL(frame);
1059 		array_init(frame);
1060 
1061 		/* Add data */
1062 		if (fse->function.function) {
1063 			add_assoc_string_ex(frame, "function", HASH_KEY_SIZEOF("function"), fse->function.function);
1064 		}
1065 		if (fse->function.object_class) {
1066 			add_assoc_string_ex(frame, "type",     HASH_KEY_SIZEOF("type"),     (char*) (fse->function.type == XFUNC_STATIC_MEMBER ? "static" : "dynamic"));
1067 			add_assoc_str_ex(frame,    "class",    HASH_KEY_SIZEOF("class"),    zend_string_copy(fse->function.object_class));
1068 		}
1069 		add_assoc_str_ex(frame, "file", HASH_KEY_SIZEOF("file"), zend_string_copy(fse->filename));
1070 		add_assoc_long_ex(frame, "line", HASH_KEY_SIZEOF("line"), fse->lineno);
1071 
1072 		/* Add parameters */
1073 		XDEBUG_MAKE_STD_ZVAL(params);
1074 		array_init(params);
1075 		add_assoc_zval_ex(frame, "params", HASH_KEY_SIZEOF("params"), params);
1076 
1077 		for (j = 0; j < sent_variables; j++) {
1078 			xdebug_str *argument = NULL;
1079 
1080 			if (fse->var[j].is_variadic) {
1081 				zval *vparams;
1082 
1083 				XDEBUG_MAKE_STD_ZVAL(vparams);
1084 				array_init(vparams);
1085 
1086 				if (fse->var[j].name) {
1087 					add_assoc_zval_ex(params, ZSTR_VAL(fse->var[j].name), ZSTR_LEN(fse->var[j].name), vparams);
1088 				} else {
1089 					add_index_zval(params, j, vparams);
1090 				}
1091 				efree(params);
1092 				params = vparams;
1093 				variadic_opened = 1;
1094 				continue;
1095 			}
1096 			if (!Z_ISUNDEF(fse->var[j].data)) {
1097 				argument = xdebug_get_zval_value_line(&fse->var[j].data, 0, NULL);
1098 			} else {
1099 				argument = xdebug_str_create_from_char((char*) "???");
1100 			}
1101 			if (fse->var[j].name && !variadic_opened && argument) {
1102 				add_assoc_stringl_ex(params, ZSTR_VAL(fse->var[j].name), ZSTR_LEN(fse->var[j].name), argument->d, argument->l);
1103 			} else {
1104 				add_index_stringl(params, j - variadic_opened, argument->d, argument->l);
1105 			}
1106 			if (argument) {
1107 				xdebug_str_free(argument);
1108 				argument = NULL;
1109 			}
1110 		}
1111 
1112 		if (fse->include_filename) {
1113 			add_assoc_str_ex(frame, "include_filename", HASH_KEY_SIZEOF("include_filename"), zend_string_copy(fse->include_filename));
1114 		}
1115 
1116 		add_next_index_zval(return_value, frame);
1117 		efree(params);
1118 		efree(frame);
1119 	}
1120 }
1121 /* }}} */
1122