diff --git a/Zend/tests/throw_on_error/declare_off_must_not_promote_warning.phpt b/Zend/tests/throw_on_error/declare_off_must_not_promote_warning.phpt new file mode 100644 index 0000000000000..96ef96660d60b --- /dev/null +++ b/Zend/tests/throw_on_error/declare_off_must_not_promote_warning.phpt @@ -0,0 +1,10 @@ +--TEST-- +If throw_on_error declare statement is explicitly turned off do not promote warning +--FILE-- + + string(25) "Undefined variable $undef" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(2) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(5) + ["trace":"Exception":private]=> + array(0) { + } + ["previous":"Exception":private]=> + NULL +} diff --git a/Zend/tests/throw_on_error/declare_statement_isolated.phpt b/Zend/tests/throw_on_error/declare_statement_isolated.phpt new file mode 100644 index 0000000000000..cd6f548f145a7 --- /dev/null +++ b/Zend/tests/throw_on_error/declare_statement_isolated.phpt @@ -0,0 +1,20 @@ +--TEST-- +Test throw_on_error declare statement is isolated to file +--FILE-- + +DONE +--EXPECTF-- +Warning: Undefined variable $undef in %sno_declare.inc on line 2 +Warning caught + +Warning: Undefined variable $undef in %sdeclare_off.inc on line 4 +DONE diff --git a/Zend/tests/throw_on_error/exception_or_warning-docref_001.phpt b/Zend/tests/throw_on_error/exception_or_warning-docref_001.phpt new file mode 100644 index 0000000000000..af6cf317096fd --- /dev/null +++ b/Zend/tests/throw_on_error/exception_or_warning-docref_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +php_exception_or_warning_docref test when declare enabled +--SKIPIF-- + +--FILE-- +getMessage() . \PHP_EOL; +} + +echo "OK"; +--EXPECT-- +Context dependent +OK diff --git a/Zend/tests/throw_on_error/exception_or_warning-docref_002.phpt b/Zend/tests/throw_on_error/exception_or_warning-docref_002.phpt new file mode 100644 index 0000000000000..54efd51f24717 --- /dev/null +++ b/Zend/tests/throw_on_error/exception_or_warning-docref_002.phpt @@ -0,0 +1,16 @@ +--TEST-- +php_exception_or_warning_docref test when declare explicitly disable +--SKIPIF-- + +--FILE-- + +--FILE-- + + string(25) "Undefined variable $undef" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(2) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(5) + ["trace":"Exception":private]=> + array(0) { + } + ["previous":"Exception":private]=> + NULL +} diff --git a/Zend/tests/throw_on_error/ignore_suppress_operator.phpt b/Zend/tests/throw_on_error/ignore_suppress_operator.phpt new file mode 100644 index 0000000000000..59a164fccca64 --- /dev/null +++ b/Zend/tests/throw_on_error/ignore_suppress_operator.phpt @@ -0,0 +1,30 @@ +--TEST-- +Promoted warning by throw_on_error must ignore @ operator +--FILE-- + + string(25) "Undefined variable $undef" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(2) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(5) + ["trace":"Exception":private]=> + array(0) { + } + ["previous":"Exception":private]=> + NULL +} diff --git a/Zend/tests/throw_on_error/no_declare_must_not_promote_warning.phpt b/Zend/tests/throw_on_error/no_declare_must_not_promote_warning.phpt new file mode 100644 index 0000000000000..44dd899d09cbb --- /dev/null +++ b/Zend/tests/throw_on_error/no_declare_must_not_promote_warning.phpt @@ -0,0 +1,17 @@ +--TEST-- +If throw_on_error declare statement is missing do not promote warning +--FILE-- + + string(48) "Failed to open stream: No such file or directory" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(2) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(5) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(5) + ["function"]=> + string(17) "file_get_contents" + ["args"]=> + array(1) { + [0]=> + string(13) "not_found.txt" + } + } + } + ["previous":"Exception":private]=> + NULL +} diff --git a/Zend/tests/throw_on_error/promote_warning_in_user_function.phpt b/Zend/tests/throw_on_error/promote_warning_in_user_function.phpt new file mode 100644 index 0000000000000..9e521851f6a5c --- /dev/null +++ b/Zend/tests/throw_on_error/promote_warning_in_user_function.phpt @@ -0,0 +1,46 @@ +--TEST-- +throw_on_error declare statement must promote warning emit in user defined function +--FILE-- + + string(25) "Undefined variable $undef" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(2) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(5) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(9) + ["function"]=> + string(3) "foo" + ["args"]=> + array(0) { + } + } + } + ["previous":"Exception":private]=> + NULL +} diff --git a/Zend/tests/throw_on_error/statement_must_be_the_first_one.phpt b/Zend/tests/throw_on_error/statement_must_be_the_first_one.phpt new file mode 100644 index 0000000000000..065c8dd40cf50 --- /dev/null +++ b/Zend/tests/throw_on_error/statement_must_be_the_first_one.phpt @@ -0,0 +1,10 @@ +--TEST-- +throw_on_error declare statement must be before anything else +--FILE-- +func || !ZEND_USER_CODE(ex->func->type))) { + ex = ex->prev_execute_data; + } + if (ex->func == NULL) { + goto normal; + } + if ((ex->func->common.fn_flags & ZEND_ACC_THROW_WARNING) != 0) { + zend_throw_exception(NULL, ZSTR_VAL(message), E_WARNING); + zend_string_release(message); + return; + } + } + normal: + /* Report about uncaught exception in case of fatal errors */ if (EG(exception)) { zend_execute_data *ex; diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 790b2acc89612..b3df52491ce43 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -5824,6 +5824,28 @@ void zend_compile_declare(zend_ast *ast) /* {{{ */ CG(active_op_array)->fn_flags |= ZEND_ACC_STRICT_TYPES; } + } else if (zend_string_equals_literal_ci(name, "throw_on_error")) { + zval value_zv; + + if (FAILURE == zend_declare_is_first_statement(ast)) { + zend_error_noreturn(E_COMPILE_ERROR, "throw_on_error declaration must be " + "the very first statement in the script"); + } + + if (ast->child[1] != NULL) { + zend_error_noreturn(E_COMPILE_ERROR, "throw_on_error declaration must not " + "use block mode"); + } + + zend_const_expr_to_zval(&value_zv, value_ast); + + if (Z_TYPE(value_zv) != IS_LONG || (Z_LVAL(value_zv) != 0 && Z_LVAL(value_zv) != 1)) { + zend_error_noreturn(E_COMPILE_ERROR, "throw_on_error declaration must have 0 or 1 as its value"); + } + + if (Z_LVAL(value_zv) == 1) { + CG(active_op_array)->fn_flags |= ZEND_ACC_THROW_WARNING; + } } else { zend_error(E_COMPILE_WARNING, "Unsupported declare '%s'", ZSTR_VAL(name)); } @@ -6747,6 +6769,8 @@ void zend_compile_func_decl(znode *result, zend_ast *ast, zend_bool toplevel) /* ZEND_MAP_PTR_SET(op_array->run_time_cache, NULL); } + op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_THROW_WARNING); + op_array->fn_flags |= decl->flags; op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_STRICT_TYPES); op_array->fn_flags |= decl->flags; op_array->line_start = decl->start_lineno; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index d804b998dedd1..34055838d9f87 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -287,7 +287,7 @@ typedef struct _zend_oparray_context { /* Whether this class was used in its unlinked state. | | | */ #define ZEND_ACC_HAS_UNLINKED_USES (1 << 23) /* X | | | */ /* | | | */ -/* Function Flags (unused: 17, 23, 26, 29) | | | */ +/* Function Flags (unused: 23, 26, 29) | | | */ /* ============== | | | */ /* | | | */ /* deprecation flag | | | */ @@ -308,6 +308,8 @@ typedef struct _zend_oparray_context { /* "main" op_array with | | | */ /* ZEND_DECLARE_CLASS_DELAYED opcodes | | | */ #define ZEND_ACC_EARLY_BINDING (1 << 16) /* | X | | */ +/* op_array uses throw_on_error mode | | | */ +#define ZEND_ACC_THROW_WARNING (1U << 17) /* | X | | */ /* | | | */ /* call through user function trampoline. e.g. | | | */ /* __call, __callstatic | | | */ diff --git a/Zend/zend_exceptions.c b/Zend/zend_exceptions.c index 8c96bb60981a3..75b485c0309c5 100644 --- a/Zend/zend_exceptions.c +++ b/Zend/zend_exceptions.c @@ -41,6 +41,7 @@ ZEND_API zend_class_entry *zend_ce_value_error; ZEND_API zend_class_entry *zend_ce_arithmetic_error; ZEND_API zend_class_entry *zend_ce_division_by_zero_error; ZEND_API zend_class_entry *zend_ce_unhandled_match_error; +ZEND_API zend_class_entry *zend_ce_io; /* Internal pseudo-exception that is not exposed to userland. */ static zend_class_entry zend_ce_unwind_exit; @@ -807,6 +808,9 @@ void zend_register_default_exception(void) /* {{{ */ INIT_CLASS_ENTRY(ce, "UnhandledMatchError", NULL); zend_ce_unhandled_match_error = zend_register_internal_class_ex(&ce, zend_ce_error); zend_ce_unhandled_match_error->create_object = zend_default_exception_new; + + INIT_CLASS_ENTRY(ce, "IO", class_IO_methods); + zend_ce_io = zend_register_internal_interface(&ce); } /* }}} */ diff --git a/Zend/zend_exceptions.stub.php b/Zend/zend_exceptions.stub.php index 9cf55fd38404e..34fa19722a5c2 100644 --- a/Zend/zend_exceptions.stub.php +++ b/Zend/zend_exceptions.stub.php @@ -119,3 +119,5 @@ class ArithmeticError extends Error class DivisionByZeroError extends ArithmeticError { } + +interface IO extends Throwable {} diff --git a/Zend/zend_exceptions_arginfo.h b/Zend/zend_exceptions_arginfo.h index 6a226954a0c8c..9206091e4d041 100644 --- a/Zend/zend_exceptions_arginfo.h +++ b/Zend/zend_exceptions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7eb20393f4ca314324d9813983124f724189ce8a */ + * Stub hash: 7ced06621bcef27db8f911c656ac8b1c4b7b8cf3 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_Throwable_getMessage, 0, 0, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -179,3 +179,8 @@ static const zend_function_entry class_ArithmeticError_methods[] = { static const zend_function_entry class_DivisionByZeroError_methods[] = { ZEND_FE_END }; + + +static const zend_function_entry class_IO_methods[] = { + ZEND_FE_END +}; diff --git a/ext/json/json.c b/ext/json/json.c index cbf4f84014cf8..fe8b92e30a47c 100644 --- a/ext/json/json.c +++ b/ext/json/json.c @@ -200,7 +200,8 @@ PHP_JSON_API int php_json_decode_ex(zval *return_value, const char *str, size_t if (php_json_yyparse(&parser)) { php_json_error_code error_code = php_json_parser_error_code(&parser); - if (!(options & PHP_JSON_THROW_ON_ERROR)) { + if (!(options & PHP_JSON_THROW_ON_ERROR) && + (EG(current_execute_data)->prev_execute_data->func->common.fn_flags & ZEND_ACC_THROW_WARNING) == 0) { JSON_G(error_code) = error_code; } else { zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(error_code), error_code); @@ -233,7 +234,9 @@ PHP_FUNCTION(json_encode) encoder.max_depth = (int)depth; php_json_encode_zval(&buf, parameter, (int)options, &encoder); - if (!(options & PHP_JSON_THROW_ON_ERROR) || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { + if ((!(options & PHP_JSON_THROW_ON_ERROR) && + (EX(prev_execute_data)->func->common.fn_flags & ZEND_ACC_THROW_WARNING) == 0) + || (options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { JSON_G(error_code) = encoder.error_code; if (encoder.error_code != PHP_JSON_ERROR_NONE && !(options & PHP_JSON_PARTIAL_OUTPUT_ON_ERROR)) { smart_str_free(&buf); @@ -273,17 +276,19 @@ PHP_FUNCTION(json_decode) Z_PARAM_LONG(options) ZEND_PARSE_PARAMETERS_END(); - if (!(options & PHP_JSON_THROW_ON_ERROR)) { + if (!(options & PHP_JSON_THROW_ON_ERROR) && + (EX(prev_execute_data)->func->common.fn_flags & ZEND_ACC_THROW_WARNING) == 0) { JSON_G(error_code) = PHP_JSON_ERROR_NONE; } if (!str_len) { - if (!(options & PHP_JSON_THROW_ON_ERROR)) { + if (!(options & PHP_JSON_THROW_ON_ERROR) && + (EX(prev_execute_data)->func->common.fn_flags & ZEND_ACC_THROW_WARNING) == 0) { JSON_G(error_code) = PHP_JSON_ERROR_SYNTAX; - } else { - zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX); + RETURN_NULL(); } - RETURN_NULL(); + zend_throw_exception(php_json_exception_ce, php_json_get_error_msg(PHP_JSON_ERROR_SYNTAX), PHP_JSON_ERROR_SYNTAX); + RETURN_THROWS(); } if (depth <= 0) { diff --git a/ext/json/tests/json_decode_with_throw_on_error_declare.phpt b/ext/json/tests/json_decode_with_throw_on_error_declare.phpt new file mode 100644 index 0000000000000..8c1fbffdab5da --- /dev/null +++ b/ext/json/tests/json_decode_with_throw_on_error_declare.phpt @@ -0,0 +1,49 @@ +--TEST-- +Test json_decode() function when throw_on_error declare is present +--FILE-- + +--EXPECTF-- +object(JsonException)#1 (7) { + ["message":protected]=> + string(12) "Syntax error" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(4) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(%d) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["function"]=> + string(11) "json_decode" + ["args"]=> + array(3) { + [0]=> + string(1) "{" + [1]=> + bool(false) + [2]=> + int(512) + } + } + } + ["previous":"Exception":private]=> + NULL +} diff --git a/ext/json/tests/json_encode_with_throw_on_error_declare.phpt b/ext/json/tests/json_encode_with_throw_on_error_declare.phpt new file mode 100644 index 0000000000000..f2d42d077e820 --- /dev/null +++ b/ext/json/tests/json_encode_with_throw_on_error_declare.phpt @@ -0,0 +1,53 @@ +--TEST-- +Test json_encode() function when throw_on_error declare is present +--FILE-- + +--EXPECTF-- +object(JsonException)#1 (7) { + ["message":protected]=> + string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" + ["string":"Exception":private]=> + string(0) "" + ["code":protected]=> + int(5) + ["file":protected]=> + string(%d) "%s" + ["line":protected]=> + int(%d) + ["trace":"Exception":private]=> + array(1) { + [0]=> + array(4) { + ["file"]=> + string(%d) "%s" + ["line"]=> + int(%d) + ["function"]=> + string(11) "json_encode" + ["args"]=> + array(1) { + [0]=> + string(1) "%s" + } + } + } + ["previous":"Exception":private]=> + NULL +} +string(4) "null" +int(5) +string(56) "Malformed UTF-8 characters, possibly incorrectly encoded" diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 6f2ce46c88092..50dfa2636ddbf 100755 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -30,6 +30,7 @@ #include "ext/session/php_session.h" #include "zend_exceptions.h" #include "zend_operators.h" +#include "io_exceptions.h" #include "ext/standard/php_dns.h" #include "ext/standard/php_uuencode.h" #include "ext/standard/php_mt_rand.h" @@ -345,6 +346,8 @@ PHP_MINIT_FUNCTION(basic) /* {{{ */ register_html_constants(INIT_FUNC_ARGS_PASSTHRU); register_string_constants(INIT_FUNC_ARGS_PASSTHRU); + BASIC_MINIT_SUBMODULE(io_exceptions) + BASIC_MINIT_SUBMODULE(var) BASIC_MINIT_SUBMODULE(file) BASIC_MINIT_SUBMODULE(pack) diff --git a/ext/standard/config.m4 b/ext/standard/config.m4 index 04b9d0aea1d6a..6ca70c8c4692b 100644 --- a/ext/standard/config.m4 +++ b/ext/standard/config.m4 @@ -449,7 +449,7 @@ PHP_NEW_EXTENSION(standard, array.c base64.c basic_functions.c browscap.c crc32. http_fopen_wrapper.c php_fopen_wrapper.c credits.c css.c \ var_unserializer.c ftok.c sha1.c user_filters.c uuencode.c \ filters.c proc_open.c streamsfuncs.c http.c password.c \ - random.c net.c hrtime.c,,, + random.c net.c hrtime.c io_exceptions.c,,, -DZEND_ENABLE_STATIC_TSRMLS_CACHE=1) PHP_ADD_MAKEFILE_FRAGMENT diff --git a/ext/standard/dir.c b/ext/standard/dir.c index e480c245fee91..2d88a9ba33f75 100644 --- a/ext/standard/dir.c +++ b/ext/standard/dir.c @@ -23,6 +23,7 @@ #include "php_string.h" #include "php_scandir.h" #include "basic_functions.h" +#include "io_exceptions.h" #include "dir_arginfo.h" #if HAVE_UNISTD_H @@ -278,7 +279,7 @@ PHP_FUNCTION(chroot) ret = chroot(str); if (ret != 0) { - php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno); + handle_io_error(errno, str); RETURN_FALSE; } @@ -287,7 +288,7 @@ PHP_FUNCTION(chroot) ret = chdir("/"); if (ret != 0) { - php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno); + php_exception_or_warning_docref(NULL, zend_ce_filesystem_error, "%s (errno %d)", strerror(errno), errno); RETURN_FALSE; } @@ -313,7 +314,7 @@ PHP_FUNCTION(chdir) ret = VCWD_CHDIR(str); if (ret != 0) { - php_error_docref(NULL, E_WARNING, "%s (errno %d)", strerror(errno), errno); + php_exception_or_warning_docref(NULL, zend_ce_filesystem_error, "%s (errno %d)", strerror(errno), errno); RETURN_FALSE; } @@ -415,12 +416,12 @@ PHP_FUNCTION(glob) ZEND_PARSE_PARAMETERS_END(); if (pattern_len >= MAXPATHLEN) { - php_error_docref(NULL, E_WARNING, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN); + php_exception_or_warning_docref(NULL, zend_ce_filesystem_error, "Pattern exceeds the maximum allowed length of %d characters", MAXPATHLEN); RETURN_FALSE; } if ((GLOB_AVAILABLE_FLAGS & flags) != flags) { - php_error_docref(NULL, E_WARNING, "At least one of the passed flags is invalid or not supported on this platform"); + php_exception_or_warning_docref(NULL, zend_ce_filesystem_error, "At least one of the passed flags is invalid or not supported on this platform"); RETURN_FALSE; } @@ -558,7 +559,7 @@ PHP_FUNCTION(scandir) n = php_stream_scandir(dirn, &namelist, context, (void *) php_stream_dirent_alphasortr); } if (n < 0) { - php_error_docref(NULL, E_WARNING, "(errno %d): %s", errno, strerror(errno)); + php_exception_or_warning_docref(NULL, zend_ce_filesystem_error, "(errno %d): %s", errno, strerror(errno)); RETURN_FALSE; } diff --git a/ext/standard/io_exceptions.c b/ext/standard/io_exceptions.c new file mode 100644 index 0000000000000..1a4e5f70e0c53 --- /dev/null +++ b/ext/standard/io_exceptions.c @@ -0,0 +1,81 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: George Peter Banyard | + +----------------------------------------------------------------------+ + */ + +/* {{{ includes */ +#include "php.h" +#include "zend_exceptions.h" +#include "io_exceptions.h" +#include "io_exceptions_arginfo.h" + +/* Class entry pointers */ +PHPAPI zend_class_entry *zend_ce_filesystem; +PHPAPI zend_class_entry *zend_ce_network; +PHPAPI zend_class_entry *zend_ce_filesystem_error; +PHPAPI zend_class_entry *zend_ce_file_not_found; +PHPAPI zend_class_entry *zend_ce_not_directory; +PHPAPI zend_class_entry *zend_ce_insufficient_permissions; +PHPAPI zend_class_entry *zend_ce_temporary_failure; + +PHP_MINIT_FUNCTION(io_exceptions) { + zend_class_entry ce; + + /* Register interfaces */ + INIT_CLASS_ENTRY(ce, "FileSystem", class_FileSystem_methods); + zend_ce_filesystem = zend_register_internal_interface(&ce); + + INIT_CLASS_ENTRY(ce, "Network", class_Network_methods); + zend_ce_network = zend_register_internal_interface(&ce); + + /* Register exceptions */ + INIT_CLASS_ENTRY(ce, "FileSystemError", class_FileSystemError_methods); + zend_ce_filesystem_error = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_class_implements(zend_ce_filesystem_error, 1, zend_ce_filesystem); + + INIT_CLASS_ENTRY(ce, "FileNotFound", class_FileNotFound_methods); + zend_ce_file_not_found = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_class_implements(zend_ce_file_not_found, 1, zend_ce_filesystem); + + INIT_CLASS_ENTRY(ce, "NotDirectory", class_FileNotFound_methods); + zend_ce_not_directory = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_class_implements(zend_ce_not_directory, 1, zend_ce_filesystem); + + INIT_CLASS_ENTRY(ce, "InsufficientPermissions", class_InsufficientPermissions_methods); + zend_ce_insufficient_permissions = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_class_implements(zend_ce_insufficient_permissions, 1, zend_ce_filesystem); + + INIT_CLASS_ENTRY(ce, "TemporaryFailure", class_TemporaryFailure_methods); + zend_ce_temporary_failure = zend_register_internal_class_ex(&ce, zend_ce_exception); + zend_class_implements(zend_ce_temporary_failure, 1, zend_ce_network); + + return SUCCESS; +} + +PHPAPI void handle_io_error(int error, const char *path) { + if (path == NULL) { + path = "[unknown]"; + } + switch (error) { + case ENOENT: + php_exception_or_warning_docref(NULL, zend_ce_file_not_found, "File not found: \"%s\"", path); + break; + case ENOTDIR: + php_exception_or_warning_docref(NULL, zend_ce_not_directory, "\"%s\" is not a directory", path); + break; + default: + php_exception_or_warning_docref(NULL, zend_ce_filesystem_error, "%s (path: \"%s\", errno %d)", strerror(errno), path, errno); + break; + } +} diff --git a/ext/standard/io_exceptions.h b/ext/standard/io_exceptions.h new file mode 100644 index 0000000000000..1f4c36b8400b7 --- /dev/null +++ b/ext/standard/io_exceptions.h @@ -0,0 +1,36 @@ +/* + +----------------------------------------------------------------------+ + | Copyright (c) The PHP Group | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.01 of the PHP 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.php.net/license/3_01.txt | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Authors: George Peter Banyard | + +----------------------------------------------------------------------+ + */ + +#ifndef PHP_IO_EXCEPTION +#define PHP_IO_EXCEPTION + +PHP_MINIT_FUNCTION(io_exceptions); + +BEGIN_EXTERN_C() + +extern PHPAPI zend_class_entry *zend_ce_filesystem; +extern PHPAPI zend_class_entry *zend_ce_network; +extern PHPAPI zend_class_entry *zend_ce_filesystem_error; +extern PHPAPI zend_class_entry *zend_ce_file_not_found; +extern PHPAPI zend_class_entry *zend_ce_not_directory; +extern PHPAPI zend_class_entry *zend_ce_insufficient_permissions; +extern PHPAPI zend_class_entry *zend_ce_temporary_failure; + +END_EXTERN_C() + +PHPAPI void handle_io_error(int error, const char *path); + +#endif /* PHP_IO_EXCEPTION */ diff --git a/ext/standard/io_exceptions.stub.php b/ext/standard/io_exceptions.stub.php new file mode 100644 index 0000000000000..06dae6d317e14 --- /dev/null +++ b/ext/standard/io_exceptions.stub.php @@ -0,0 +1,18 @@ + +--FILE-- +getMessage() . \PHP_EOL; +} + +?> +--EXPECTF-- +*** Testing chroot() : error conditions *** + +-- Pass chroot() an absolute path that does not exist -- +File not found: "%sidonotexist" diff --git a/ext/standard/tests/dir/chroot_throw_on_error_file_in_path.phpt b/ext/standard/tests/dir/chroot_throw_on_error_file_in_path.phpt new file mode 100644 index 0000000000000..4dc2f72ec5704 --- /dev/null +++ b/ext/standard/tests/dir/chroot_throw_on_error_file_in_path.phpt @@ -0,0 +1,33 @@ +--TEST-- +Test chroot() function error conditions with throw on error declare enabled - Non-existent directory +--SKIPIF-- + +--FILE-- +getMessage() . \PHP_EOL; +} + +?> +--EXPECTF-- +*** Testing chroot() : error conditions *** + +-- Pass chroot() a non directory -- +"%schroot_throw_on_error_file_in_path.php" is not a directory diff --git a/ext/standard/tests/dir/scandir_error2_throw_on_error_declare.phpt b/ext/standard/tests/dir/scandir_error2_throw_on_error_declare.phpt new file mode 100644 index 0000000000000..0e24aa54ce6ac --- /dev/null +++ b/ext/standard/tests/dir/scandir_error2_throw_on_error_declare.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test scandir() function error conditions with throw on error declare enabled - Non-existent directory +--SKIPIF-- + +--FILE-- +getMessage() . \PHP_EOL; +} + +echo "\n-- Pass scandir() a relative path that does not exist --\n"; +try { + var_dump(scandir('/idonotexist')); +} catch (\FileSystem $e) { + echo $e->getMessage() . \PHP_EOL; +} +?> +--EXPECT-- +*** Testing scandir() : error conditions *** + +-- Pass scandir() an absolute path that does not exist -- +(errno 2): No such file or directory + +-- Pass scandir() a relative path that does not exist -- +(errno 2): No such file or directory diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 900c70f59d5af..61a179731473e 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -183,6 +183,14 @@ ZEND_FUNCTION(zend_string_or_stdclass) } /* }}} */ +/* Tests php_exception_or_warning_docref */ +ZEND_FUNCTION(zend_throw_on_error_declare_exception_or_warning) +{ + zend_parse_parameters_none(); + php_exception_or_warning_docref(NULL, NULL, "Context dependent"); +} +/* }}} */ + /* Tests Z_PARAM_STR_OR_OBJ_OF_CLASS_OR_NULL */ ZEND_FUNCTION(zend_string_or_stdclass_or_null) { diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index be8402aab72ca..9b7ad78a55102 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -39,3 +39,5 @@ function zend_string_or_stdclass($param): stdClass|string {} /** @param stdClass|string|null $param */ function zend_string_or_stdclass_or_null($param): stdClass|string|null {} + +function zend_throw_on_error_declare_exception_or_warning(): void {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index b406edf24f83d..cbb57836ea9f4 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d2f58424106d78e0bb3b363c34fa94472b5e758d */ + * Stub hash: c9cae80ba69b8c9f8a49d0e6c20116d4a66cc3bb */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -46,6 +46,8 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_zend_string_or_stdclass_or_n ZEND_ARG_INFO(0, param) ZEND_END_ARG_INFO() +#define arginfo_zend_throw_on_error_declare_exception_or_warning arginfo_zend_test_void_return + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass_is_object, 0, 0, IS_LONG, 0) ZEND_END_ARG_INFO() @@ -68,6 +70,7 @@ ZEND_FUNCTION(zend_string_or_object); ZEND_FUNCTION(zend_string_or_object_or_null); ZEND_FUNCTION(zend_string_or_stdclass); ZEND_FUNCTION(zend_string_or_stdclass_or_null); +ZEND_FUNCTION(zend_throw_on_error_declare_exception_or_warning); ZEND_METHOD(_ZendTestClass, is_object); ZEND_METHOD(_ZendTestClass, __toString); ZEND_METHOD(_ZendTestTrait, testMethod); @@ -86,6 +89,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_string_or_object_or_null, arginfo_zend_string_or_object_or_null) ZEND_FE(zend_string_or_stdclass, arginfo_zend_string_or_stdclass) ZEND_FE(zend_string_or_stdclass_or_null, arginfo_zend_string_or_stdclass_or_null) + ZEND_FE(zend_throw_on_error_declare_exception_or_warning, arginfo_zend_throw_on_error_declare_exception_or_warning) ZEND_FE_END }; diff --git a/main/main.c b/main/main.c index 4e7a4f44414c1..a83670e35512e 100644 --- a/main/main.c +++ b/main/main.c @@ -1156,6 +1156,35 @@ PHPAPI ZEND_COLD void php_win32_docref2_from_error(DWORD error, const char *para } #endif + +/* {{{ php_exception_or_warning_docref */ +/* Throw an Exception corresponding to exception_ce class if throw_on_error declare statement is present + * otherwise fallback to a traditional docref warning. */ +PHPAPI ZEND_COLD void php_exception_or_warning_docref(const char *docref, + zend_class_entry *exception_ce, const char *format, ...) +{ + va_list args; + zend_execute_data *ex = EG(current_execute_data); + + va_start(args, format); + + /* Find first non internal execute_data */ + while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) { + ex = ex->prev_execute_data; + } + if ((ex->func->common.fn_flags & ZEND_ACC_THROW_WARNING) != 0) { + char *message = NULL; + + zend_vspprintf(&message, 0, format, args); + zend_throw_exception(exception_ce, message, E_WARNING); + efree(message); + } else { + php_verror(docref, "", E_WARNING, format, args); + } + va_end(args); +} +/* }}} */ + /* {{{ php_html_puts */ PHPAPI void php_html_puts(const char *str, size_t size) { diff --git a/main/php.h b/main/php.h index 76ced5907ec0b..15e97732a5d1c 100644 --- a/main/php.h +++ b/main/php.h @@ -345,6 +345,8 @@ PHPAPI ZEND_COLD void php_error_docref2(const char *docref, const char *param1, #ifdef PHP_WIN32 PHPAPI ZEND_COLD void php_win32_docref2_from_error(DWORD error, const char *param1, const char *param2); #endif +PHPAPI ZEND_COLD void php_exception_or_warning_docref(const char *docref, zend_class_entry *exception_ce, const char *format, ...) + PHP_ATTRIBUTE_FORMAT(printf, 3, 4); END_EXTERN_C() #define zenderror phperror diff --git a/main/streams/streams.c b/main/streams/streams.c index ea0738c82a53f..b79168de5e549 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -29,6 +29,7 @@ #include #include #include "php_streams_int.h" +#include "Zend/zend_exceptions.h" /* {{{ resource and registration code */ /* Global wrapper hash, copied to FG(stream_wrappers) on registration of volatile wrapper */ @@ -210,8 +211,18 @@ void php_stream_display_wrapper_errors(php_stream_wrapper *wrapper, const char * msg = "no suitable wrapper could be found"; } - php_strip_url_passwd(tmp); - php_error_docref1(NULL, tmp, E_WARNING, "%s: %s", caption, msg); + /* To handle throw_on_error declare statement */ + zend_execute_data *ex = EG(current_execute_data); + /* Find first non internal execute_data */ + while (ex && (!ex->func || !ZEND_USER_CODE(ex->func->type))) { + ex = ex->prev_execute_data; + } + if ((ex->func->common.fn_flags & ZEND_ACC_THROW_WARNING) != 0) { + zend_throw_exception_ex(zend_ce_exception, E_WARNING, "%s: %s", caption, msg); + } else { + php_strip_url_passwd(tmp); + php_error_docref1(NULL, tmp, E_WARNING, "%s: %s", caption, msg); + } efree(tmp); if (free_msg) { efree(msg);