Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changed: Implementing improved error callback mechanism (php_error_cb) #1247

Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion Zend/Makefile.am
Expand Up @@ -8,7 +8,7 @@ noinst_LTLIBRARIES=libZend.la
libZend_la_SOURCES=\
zend_language_parser.y zend_language_scanner.l \
zend_ini_parser.y zend_ini_scanner.l \
zend_alloc.c zend_compile.c zend_constants.c \
zend_alloc.c zend_compile.c zend_constants.c zend_errors.c \
zend_execute.c zend_execute_API.c zend_highlight.c zend_llist.c \
zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c zend_stack.c \
zend_variables.c zend.c zend_API.c zend_extensions.c zend_hash.c \
Expand Down
10 changes: 5 additions & 5 deletions Zend/zend.c
Expand Up @@ -53,7 +53,7 @@ ZEND_API int (*zend_stream_open_function)(const char *filename, zend_file_handle
ZEND_API void (*zend_block_interruptions)(void);
ZEND_API void (*zend_unblock_interruptions)(void);
ZEND_API void (*zend_ticks_function)(int ticks);
ZEND_API void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args);
ZEND_API void (*zend_error_cb)(ZEND_ERROR_CB_FUNC_ARGS);
size_t (*zend_vspprintf)(char **pbuf, size_t max_len, const char *format, va_list ap);
zend_string *(*zend_vstrpprintf)(size_t max_len, const char *format, va_list ap);
ZEND_API char *(*zend_getenv)(char *name, size_t name_len);
Expand Down Expand Up @@ -1158,7 +1158,7 @@ static void zend_error_va_list(int type, const char *format, va_list args)
if (Z_TYPE(EG(user_error_handler)) == IS_UNDEF
|| !(EG(user_error_handler_error_reporting) & type)
|| EG(error_handling) != EH_NORMAL) {
zend_error_cb(type, error_filename, error_lineno, format, args);
zend_error_cb(ZEND_ERROR_CB_FUNC_ARGS_PASSTHRU);
} else switch (type) {
case E_ERROR:
case E_PARSE:
Expand All @@ -1167,7 +1167,7 @@ static void zend_error_va_list(int type, const char *format, va_list args)
case E_COMPILE_ERROR:
case E_COMPILE_WARNING:
/* The error may not be safe to handle in user-space */
zend_error_cb(type, error_filename, error_lineno, format, args);
zend_error_cb(ZEND_ERROR_CB_FUNC_ARGS_PASSTHRU);
break;
default:
/* Handle the error in user space */
Expand Down Expand Up @@ -1231,13 +1231,13 @@ static void zend_error_va_list(int type, const char *format, va_list args)
if (call_user_function_ex(CG(function_table), NULL, &orig_user_error_handler, &retval, 5, params, 1, NULL) == SUCCESS) {
if (Z_TYPE(retval) != IS_UNDEF) {
if (Z_TYPE(retval) == IS_FALSE) {
zend_error_cb(type, error_filename, error_lineno, format, args);
zend_error_cb(ZEND_ERROR_CB_FUNC_ARGS_PASSTHRU);
}
zval_ptr_dtor(&retval);
}
} else if (!EG(exception)) {
/* The user error handler failed, use built-in error handler */
zend_error_cb(type, error_filename, error_lineno, format, args);
zend_error_cb(ZEND_ERROR_CB_FUNC_ARGS_PASSTHRU);
}

if (in_compilation) {
Expand Down
4 changes: 2 additions & 2 deletions Zend/zend.h
Expand Up @@ -190,7 +190,7 @@ struct _zend_class_entry {
};

typedef struct _zend_utility_functions {
void (*error_function)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 4, 0);
void (*error_function)(ZEND_ERROR_CB_FUNC_ARGS) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 4, 0);
size_t (*printf_function)(const char *format, ...) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 1, 2);
size_t (*write_function)(const char *str, size_t str_length);
FILE *(*fopen_function)(const char *filename, zend_string **opened_path);
Expand Down Expand Up @@ -276,7 +276,7 @@ extern ZEND_API FILE *(*zend_fopen)(const char *filename, zend_string **opened_p
extern ZEND_API void (*zend_block_interruptions)(void);
extern ZEND_API void (*zend_unblock_interruptions)(void);
extern ZEND_API void (*zend_ticks_function)(int ticks);
extern ZEND_API void (*zend_error_cb)(int type, const char *error_filename, const uint error_lineno, const char *format, va_list args) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 4, 0);
extern ZEND_API void (*zend_error_cb)(ZEND_ERROR_CB_FUNC_ARGS) ZEND_ATTRIBUTE_PTR_FORMAT(printf, 4, 0);
extern ZEND_API void (*zend_on_timeout)(int seconds);
extern ZEND_API int (*zend_stream_open_function)(const char *filename, zend_file_handle *handle);
extern size_t (*zend_vspprintf)(char **pbuf, size_t max_len, const char *format, va_list ap);
Expand Down
203 changes: 203 additions & 0 deletions Zend/zend_errors.c
@@ -0,0 +1,203 @@
/*
+----------------------------------------------------------------------+
| Zend Engine |
+----------------------------------------------------------------------+
| Copyright (c) 1998-2015 Zend Technologies Ltd. (http://www.zend.com) |
+----------------------------------------------------------------------+
| This source file is subject to version 2.00 of the Zend license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| http://www.zend.com/license/2_00.txt. |
| If you did not receive a copy of the Zend license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@zend.com so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Patrick Allaert <patrick@catchy.io> |
+----------------------------------------------------------------------+
*/

/* $Id: $ */

#include "zend.h"
#include "zend_errors.h"
#include "php.h"
#include "php_globals.h"
#include "php_syslog.h"
#include "SAPI.h"
#include "ext/standard/html.h"

/* {{{ zend_error_display_cb */
ZEND_ERROR_CB_API zend_error_display_cb(ZEND_ERROR_CB_HOOK_ARGS)
{
char *buffer = PG(last_error_message);

if (PG(display_errors) && ((php_get_module_initialized() && !PG(during_request_startup)) || (PG(display_startup_errors)))) {
if (PG(xmlrpc_errors)) {
php_printf("<?xml version=\"1.0\"?><methodResponse><fault><value><struct><member><name>faultCode</name><value><int>%pd</int></value></member><member><name>faultString</name><value><string>%s:%s in %s on line %d</string></value></member></struct></value></fault></methodResponse>", PG(xmlrpc_error_number), error_type_str, buffer, error_filename, error_lineno);
} else {
char *prepend_string = INI_STR("error_prepend_string");
char *append_string = INI_STR("error_append_string");

if (PG(html_errors)) {
if (type == E_ERROR || type == E_PARSE) {
zend_string *buf = php_escape_html_entities((unsigned char*)buffer, strlen(buffer), 0, ENT_COMPAT, NULL);
php_printf("%s<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br />\n%s", STR_PRINT(prepend_string), error_type_str, buf->val, error_filename, error_lineno, STR_PRINT(append_string));
zend_string_free(buf);
} else {
php_printf("%s<br />\n<b>%s</b>: %s in <b>%s</b> on line <b>%d</b><br />\n%s", STR_PRINT(prepend_string), error_type_str, buffer, error_filename, error_lineno, STR_PRINT(append_string));
}
} else {
/* Write CLI/CGI errors to stderr if display_errors = "stderr" */
if ((!strcmp(sapi_module.name, "cli") || !strcmp(sapi_module.name, "cgi")) &&
PG(display_errors) == PHP_DISPLAY_ERRORS_STDERR
) {
#ifdef PHP_WIN32
fprintf(stderr, "%s: %s in %s on line %u\n", error_type_str, buffer, error_filename, error_lineno);
fflush(stderr);
#else
fprintf(stderr, "%s: %s in %s on line %u\n", error_type_str, buffer, error_filename, error_lineno);
#endif
} else {
php_printf("%s\n%s: %s in %s on line %d\n%s", STR_PRINT(prepend_string), error_type_str, buffer, error_filename, error_lineno, STR_PRINT(append_string));
}
}
}
}
#if ZEND_DEBUG
if (PG(report_zend_debug)) {
zend_bool trigger_break;

switch (type) {
case E_ERROR:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
case E_USER_ERROR:
trigger_break=1;
break;
default:
trigger_break=0;
break;
}
zend_output_debug_string(trigger_break, "%s(%d) : %s - %s", error_filename, error_lineno, error_type_str, buffer);
}
#endif
return SUCCESS;
}
/* }}} */

/* {{{ zend_error_log_cb */
ZEND_ERROR_CB_API zend_error_log_cb(ZEND_ERROR_CB_HOOK_ARGS)
{
char *buffer = PG(last_error_message);

if (!php_get_module_initialized() || PG(log_errors)) {
char *log_buffer;
#ifdef PHP_WIN32
if (type == E_CORE_ERROR || type == E_CORE_WARNING) {
syslog(LOG_ALERT, "PHP %s: %s (%s)", error_type_str, buffer, GetCommandLine());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs win32/syslog.h to be included. Or maybe it'd be better to check what header inclusion did provide it in the old file. For the rest it looks fine.

}
#endif
spprintf(&log_buffer, 0, "PHP %s: %s in %s on line %d", error_type_str, buffer, error_filename, error_lineno);
php_log_err(log_buffer);
efree(log_buffer);
}
return SUCCESS;
}
/* }}} */

/* {{{ zend_error_bailout_cb */
ZEND_ERROR_CB_API zend_error_bailout_cb(ZEND_ERROR_CB_HOOK_ARGS)
{
/* Bail out if we can't recover */
switch (type) {
case E_CORE_ERROR:
if(!php_get_module_initialized()) {
/* bad error in module startup - no way we can live with this */
exit(-2);
}
/* no break - intentionally */
case E_ERROR:
case E_RECOVERABLE_ERROR:
case E_PARSE:
case E_COMPILE_ERROR:
case E_USER_ERROR:
EG(exit_status) = 255;
if (php_get_module_initialized()) {
if (!PG(display_errors) &&
!SG(headers_sent) &&
SG(sapi_headers).http_response_code == 200
) {
sapi_header_line ctr = {0};

ctr.line = "HTTP/1.0 500 Internal Server Error";
ctr.line_len = sizeof("HTTP/1.0 500 Internal Server Error") - 1;
sapi_header_op(SAPI_HEADER_REPLACE, &ctr);
}
/* the parser would return 1 (failure), we can bail out nicely */
if (type == E_PARSE) {
CG(parse_error) = 0;
} else {
/* restore memory limit */
zend_set_memory_limit(PG(memory_limit));
zend_objects_store_mark_destructed(&EG(objects_store));
zend_bailout();
return FAILURE;
}
}
}
return SUCCESS;
}
/* }}} */

/* {{{ zend_append_error_hook */
ZEND_API void zend_append_error_hook(zend_error_cb_hook_t hook_part, void (*hook)(ZEND_ERROR_CB_HOOK_ARGS))
{
zend_llist_add_element(&PG(error_hooks)[hook_part], (void *)&hook);
}
/* }}} */

/* {{{ zend_prepend_error_hook */
ZEND_API void zend_prepend_error_hook(zend_error_cb_hook_t hook_part, void (*hook)(ZEND_ERROR_CB_HOOK_ARGS))
{
zend_llist_prepend_element(&PG(error_hooks)[hook_part], (void *)&hook);
}
/* }}} */

/* {{{ zend_clear_error_hook */
ZEND_API void zend_clear_error_hook(zend_error_cb_hook_t hook_part)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why any extension should be allowed to do this? This seems too dangerous.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cfr. mailing list discussion.

This is required when an extension is supposed to overtake the whole responsibility of a hook category by clearing all existing hooks in it and registering it's own one.
It is really more dangerous than the current situation that implies replacing the whole implementation of php_error_cb?

{
zend_llist_clean(&PG(error_hooks)[hook_part]);
}
/* }}} */

/* {{{ zend_init_error_hooks */
ZEND_API void zend_init_error_hooks(void)
{
int i;

for (i = 0; i < E_HOOK_LAST; ++i) {
zend_llist_init(&PG(error_hooks)[i], sizeof(void (*)(ZEND_ERROR_CB_HOOK_ARGS)), NULL, 1);
}
}
/* }}} */

/* {{{ zend_register_error_hooks */
ZEND_API void zend_register_error_hooks(void)
{
zend_append_error_hook(E_HOOK_DISPLAY, &zend_error_display_cb);
zend_append_error_hook(E_HOOK_LOG, &zend_error_log_cb);
// bail out functions have a slightly different signature, returning a zend_bool instead of void
zend_append_error_hook(E_HOOK_BAILOUT, (void (*)(ZEND_ERROR_CB_HOOK_ARGS))&zend_error_bailout_cb);
}
/* }}} */

/* {{{ zend_unregister_error_hooks */
ZEND_API void zend_unregister_error_hooks(void)
{
int i;

for (i = 0; i < E_HOOK_LAST; ++i) {
zend_llist_destroy(&PG(error_hooks)[i]);
}
}
/* }}} */
27 changes: 27 additions & 0 deletions Zend/zend_errors.h
Expand Up @@ -14,6 +14,7 @@
+----------------------------------------------------------------------+
| Authors: Andi Gutmans <andi@zend.com> |
| Zeev Suraski <zeev@zend.com> |
| Patrick Allaert <patrick@catchy.io> |
+----------------------------------------------------------------------+
*/

Expand Down Expand Up @@ -43,6 +44,32 @@
#define E_ALL (E_ERROR | E_WARNING | E_PARSE | E_NOTICE | E_CORE_ERROR | E_CORE_WARNING | E_COMPILE_ERROR | E_COMPILE_WARNING | E_USER_ERROR | E_USER_WARNING | E_USER_NOTICE | E_RECOVERABLE_ERROR | E_DEPRECATED | E_USER_DEPRECATED | E_STRICT)
#define E_CORE (E_CORE_ERROR | E_CORE_WARNING)

typedef enum {
E_HOOK_DISPLAY,
E_HOOK_LOG,
E_HOOK_PROCESS,
E_HOOK_BAILOUT,
E_HOOK_LAST
} zend_error_cb_hook_t;

#define ZEND_ERROR_CB_FUNC_ARGS int type, const char *error_filename, const uint error_lineno, const char *format, va_list args
#define ZEND_ERROR_CB_FUNC_ARGS_PASSTHRU type, error_filename, error_lineno, format, args
#define ZEND_ERROR_CB_HOOK_ARGS ZEND_ERROR_CB_FUNC_ARGS, const char *error_type_str
#define ZEND_ERROR_CB_HOOK_ARGS_PASSTHRU ZEND_ERROR_CB_FUNC_ARGS_PASSTHRU, error_type_str
#define ZEND_ERROR_CB_API ZEND_API int

BEGIN_EXTERN_C()
ZEND_ERROR_CB_API zend_error_display_cb(ZEND_ERROR_CB_HOOK_ARGS);
ZEND_ERROR_CB_API zend_error_log_cb(ZEND_ERROR_CB_HOOK_ARGS);
ZEND_ERROR_CB_API zend_error_bailout_cb(ZEND_ERROR_CB_HOOK_ARGS);
ZEND_API void zend_append_error_hook(zend_error_cb_hook_t hook_part, void (*hook)(ZEND_ERROR_CB_HOOK_ARGS));
ZEND_API void zend_prepend_error_hook(zend_error_cb_hook_t hook_part, void (*hook)(ZEND_ERROR_CB_HOOK_ARGS));
ZEND_API void zend_clear_error_hook(zend_error_cb_hook_t hook_part);
ZEND_API void zend_init_error_hooks(void);
ZEND_API void zend_register_error_hooks(void);
ZEND_API void zend_unregister_error_hooks(void);
END_EXTERN_C()

#endif /* ZEND_ERRORS_H */

/*
Expand Down
2 changes: 1 addition & 1 deletion configure.in
Expand Up @@ -1485,7 +1485,7 @@ esac
PHP_ADD_SOURCES(Zend, \
zend_language_parser.c zend_language_scanner.c \
zend_ini_parser.c zend_ini_scanner.c \
zend_alloc.c zend_compile.c zend_constants.c zend_dtrace.c \
zend_alloc.c zend_compile.c zend_constants.c zend_errors.c zend_dtrace.c \
zend_execute_API.c zend_highlight.c zend_llist.c \
zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c zend_stack.c \
zend_variables.c zend.c zend_API.c zend_extensions.c zend_hash.c \
Expand Down