diff --git a/Zend/tests/arginfo_zpp_mismatch.inc b/Zend/tests/arginfo_zpp_mismatch.inc index 5b2711fdb6303..2eb8905f5d4a1 100644 --- a/Zend/tests/arginfo_zpp_mismatch.inc +++ b/Zend/tests/arginfo_zpp_mismatch.inc @@ -9,6 +9,7 @@ function skipFunction($function): bool { /* terminates script */ || $function === 'exit' || $function === 'die' + || $function === 'zend_trigger_bailout' /* intentionally violate invariants */ || $function === 'zend_create_unterminated_string' || $function === 'zend_test_array_return' diff --git a/Zend/tests/bug41421.phpt b/Zend/tests/bug41421.phpt index 762c32b96df80..fdf86da443867 100644 --- a/Zend/tests/bug41421.phpt +++ b/Zend/tests/bug41421.phpt @@ -9,22 +9,19 @@ class wrapper { return true; } function stream_eof() { - throw new exception(); + throw new Exception('cannot eof'); } } stream_wrapper_register("wrap", "wrapper"); $fp = fopen("wrap://...", "r"); -feof($fp); -echo "Done\n"; -?> ---EXPECTF-- -Warning: feof(): wrapper::stream_eof is not implemented! Assuming EOF in %s on line %d +try { + feof($fp); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), PHP_EOL; +} -Fatal error: Uncaught Exception in %s:%d -Stack trace: -#0 [internal function]: wrapper->stream_eof() -#1 %s(%d): feof(Resource id #%d) -#2 {main} - thrown in %s on line %d +?> +--EXPECT-- +Exception: cannot eof diff --git a/ext/standard/tests/file/userstreams_005.phpt b/ext/standard/tests/file/userstreams_005.phpt index 8d37040e1950b..11dc8d45b0cd6 100644 --- a/ext/standard/tests/file/userstreams_005.phpt +++ b/ext/standard/tests/file/userstreams_005.phpt @@ -65,5 +65,5 @@ ftruncate(): Argument #2 ($size) must be greater than or equal to 0 ------ stream_truncate bad return: ------- truncation with new_size=0 -Warning: ftruncate(): test_wrapper_bad::stream_truncate did not return a boolean! in %s on line %d +Warning: ftruncate(): test_wrapper_bad::stream_truncate value must be of type bool, string given in %s on line %d bool(false) diff --git a/ext/standard/tests/streams/user-stream-dir-open-bailout.phpt b/ext/standard/tests/streams/user-stream-dir-open-bailout.phpt new file mode 100644 index 0000000000000..d7421ee9dea82 --- /dev/null +++ b/ext/standard/tests/streams/user-stream-dir-open-bailout.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bailout (E_ERROR) during UserStream Dir Open +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECTF-- +Fatal error: Bailout in %s on line %d diff --git a/ext/standard/tests/streams/user-stream-error.phpt b/ext/standard/tests/streams/user-stream-error.phpt deleted file mode 100644 index 8ad4be644181f..0000000000000 --- a/ext/standard/tests/streams/user-stream-error.phpt +++ /dev/null @@ -1,22 +0,0 @@ ---TEST-- -E_ERROR during UserStream Open ---FILE-- - ---EXPECTF-- -Fatal error: Uncaught Error: Call to undefined function _some_undefined_function() in %s%euser-stream-error.php:%d -Stack trace: -#0 [internal function]: FailStream->stream_open('mystream://foo', 'r', 0, NULL) -#1 %s%euser-stream-error.php(%d): fopen('mystream://foo', 'r') -#2 {main} - thrown in %s%euser-stream-error.php on line %d diff --git a/ext/standard/tests/streams/user-stream-open-bailout.phpt b/ext/standard/tests/streams/user-stream-open-bailout.phpt new file mode 100644 index 0000000000000..552e32a04a237 --- /dev/null +++ b/ext/standard/tests/streams/user-stream-open-bailout.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bailout (E_ERROR) during UserStream Open +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECTF-- +Fatal error: Bailout in %s on line %d diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 255b0a7f99443..c2e5ae6130e7d 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -87,6 +87,13 @@ static ZEND_FUNCTION(zend_test_func) EX(func) = NULL; } +static ZEND_FUNCTION(zend_trigger_bailout) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + zend_error(E_ERROR, "Bailout"); +} + static ZEND_FUNCTION(zend_test_array_return) { ZEND_PARSE_PARAMETERS_NONE(); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 200ab22e79435..17316230e8d3e 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -208,6 +208,8 @@ enum ZendTestIntEnum: int { case Baz = -1; } + function zend_trigger_bailout(): never {} + function zend_test_array_return(): array {} /** @genstubs-expose-comment-block diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index bf3d626088f53..33c02310ba4fb 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,8 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 781677c7ada9095af9c964cf86ce6ba63a52a930 */ + * Stub hash: b767745e4e7be7cb4ba294e238a1b0f63da8479e */ + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_trigger_bailout, 0, 0, IS_NEVER, 0) +ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -272,6 +275,7 @@ ZEND_END_ARG_INFO() #define arginfo_class_ZendTestNS2_ZendSubNS_Foo_method arginfo_zend_test_void_return +static ZEND_FUNCTION(zend_trigger_bailout); static ZEND_FUNCTION(zend_test_array_return); static ZEND_FUNCTION(zend_test_nullable_array_return); static ZEND_FUNCTION(zend_test_void_return); @@ -358,6 +362,7 @@ static ZEND_METHOD(ZendTestNS2_Foo, method); static ZEND_METHOD(ZendTestNS2_ZendSubNS_Foo, method); static const zend_function_entry ext_functions[] = { + ZEND_FE(zend_trigger_bailout, arginfo_zend_trigger_bailout) ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return) #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_nullable_array_return", zif_zend_test_nullable_array_return, arginfo_zend_test_nullable_array_return, ZEND_ACC_COMPILE_TIME_EVAL, NULL, "/**\n * \"Lorem ipsum\"\n * @see https://www.php.net\n * @since 8.3\n */") diff --git a/ext/zip/tests/bug53603.phpt b/ext/zip/tests/bug53603.phpt index 5423206233977..914b0b8440c61 100644 --- a/ext/zip/tests/bug53603.phpt +++ b/ext/zip/tests/bug53603.phpt @@ -28,5 +28,5 @@ $a = $zip->extractTo('teststream://test'); var_dump($a); ?> --EXPECTF-- -Warning: ZipArchive::extractTo(teststream://test/foo): Failed to open stream: "TestStream::stream_open" call failed in %s on line %d +Warning: ZipArchive::extractTo(teststream://test/foo): Failed to open stream: "TestStream::stream_open" is not implemented in %s on line %d bool(false) diff --git a/main/streams/userspace.c b/main/streams/userspace.c index a25900eda3054..64aa58f776bf5 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -251,13 +251,6 @@ typedef struct _php_userstream_data php_userstream_data_t; }}} **/ -static zend_result call_method_if_exists( - zval *object, zval *method_name, zval *retval, uint32_t param_count, zval *params) -{ - return zend_call_method_if_exists( - Z_OBJ_P(object), Z_STR_P(method_name), retval, param_count, params); -} - static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object) { if (uwrap->ce->ce_flags & (ZEND_ACC_INTERFACE|ZEND_ACC_TRAIT|ZEND_ACC_IMPLICIT_ABSTRACT_CLASS|ZEND_ACC_EXPLICIT_ABSTRACT_CLASS)) { @@ -295,7 +288,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; php_userstream_data_t *us; - zval zretval, zfuncname; + zval zretval; zval args[4]; php_stream *stream = NULL; bool old_in_user_include; @@ -320,34 +313,40 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * us = emalloc(sizeof(*us)); us->wrapper = uwrap; - /* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */ + /* zend_call_method_if_exists() may unregister the stream wrapper. Hold on to it. */ GC_ADDREF(us->wrapper->resource); user_stream_create_object(uwrap, context, &us->object); - if (Z_TYPE(us->object) == IS_UNDEF) { - FG(user_stream_current_filename) = NULL; - PG(in_user_include) = old_in_user_include; - efree(us); - return NULL; + if (Z_ISUNDEF(us->object)) { + goto end; } - /* call it's stream_open method - set up params first */ + /* call its stream_open method - set up params first */ ZVAL_STRING(&args[0], filename); ZVAL_STRING(&args[1], mode); ZVAL_LONG(&args[2], options); ZVAL_NEW_REF(&args[3], &EG(uninitialized_zval)); - ZVAL_STRING(&zfuncname, USERSTREAM_OPEN); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_OPEN, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &zretval, 4, args); + zend_string_release_ex(func_name, false); - zend_result call_result; - zend_try { - call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 4, args); - } zend_catch { - FG(user_stream_current_filename) = NULL; - zend_bailout(); - } zend_end_try(); + /* Keep arg3 alive if it has assigned the reference */ + zval_ptr_dtor(&args[1]); + zval_ptr_dtor(&args[0]); - if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) { + if (UNEXPECTED(call_result == FAILURE)) { + php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_OPEN "\" is not implemented", + ZSTR_VAL(us->wrapper->ce->name)); + zval_ptr_dtor(&args[3]); + goto end; + } + /* Exception occurred */ + if (UNEXPECTED(Z_ISUNDEF(zretval))) { + zval_ptr_dtor(&args[3]); + goto end; + } + if (zval_is_true(&zretval)) { /* the stream is now open! */ stream = php_stream_alloc_rel(&php_stream_userspace_ops, us, 0, mode); @@ -355,6 +354,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * if (Z_ISREF(args[3]) && Z_TYPE_P(Z_REFVAL(args[3])) == IS_STRING && opened_path) { *opened_path = zend_string_copy(Z_STR_P(Z_REFVAL(args[3]))); } + // TODO Warn when assigning a non string value to the reference? /* set wrapper data to be a reference to our object */ ZVAL_COPY(&stream->wrapperdata, &us->object); @@ -363,23 +363,17 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * ZSTR_VAL(us->wrapper->ce->name)); } - /* destroy everything else */ - if (stream == NULL) { - zval_ptr_dtor(&us->object); - ZVAL_UNDEF(&us->object); - zend_list_delete(us->wrapper->resource); - efree(us); - } zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); zval_ptr_dtor(&args[3]); - zval_ptr_dtor(&args[2]); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); +end: FG(user_stream_current_filename) = NULL; - PG(in_user_include) = old_in_user_include; + if (stream == NULL) { + zval_ptr_dtor(&us->object); + zend_list_delete(us->wrapper->resource); + efree(us); + } return stream; } @@ -396,7 +390,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; php_userstream_data_t *us; - zval zretval, zfuncname; + zval zretval; zval args[2]; php_stream *stream = NULL; @@ -409,25 +403,34 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char us = emalloc(sizeof(*us)); us->wrapper = uwrap; - /* call_method_if_exists() may unregister the stream wrapper. Hold on to it. */ + /* zend_call_method_if_exists() may unregister the stream wrapper. Hold on to it. */ GC_ADDREF(us->wrapper->resource); user_stream_create_object(uwrap, context, &us->object); if (Z_TYPE(us->object) == IS_UNDEF) { - FG(user_stream_current_filename) = NULL; - efree(us); - return NULL; + goto end; } - /* call it's dir_open method - set up params first */ + /* call its dir_open method - set up params first */ ZVAL_STRING(&args[0], filename); ZVAL_LONG(&args[1], options); - ZVAL_STRING(&zfuncname, USERSTREAM_DIR_OPEN); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_OPEN, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &zretval, 2, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[0]); - zend_result call_result = call_method_if_exists(&us->object, &zfuncname, &zretval, 2, args); + if (UNEXPECTED(call_result == FAILURE)) { + php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" is not implemented", + ZSTR_VAL(us->wrapper->ce->name)); + goto end; + } + /* Exception occurred in call */ + if (UNEXPECTED(Z_ISUNDEF(zretval))) { + goto end; + } - if (call_result == SUCCESS && Z_TYPE(zretval) != IS_UNDEF && zval_is_true(&zretval)) { + if (zval_is_true(&zretval)) { /* the stream is now open! */ stream = php_stream_alloc_rel(&php_stream_userspace_dir_ops, us, 0, mode); @@ -437,22 +440,15 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char php_stream_wrapper_log_error(wrapper, options, "\"%s::" USERSTREAM_DIR_OPEN "\" call failed", ZSTR_VAL(us->wrapper->ce->name)); } + zval_ptr_dtor(&zretval); - /* destroy everything else */ +end: + FG(user_stream_current_filename) = NULL; if (stream == NULL) { zval_ptr_dtor(&us->object); - ZVAL_UNDEF(&us->object); zend_list_delete(us->wrapper->resource); efree(us); } - zval_ptr_dtor(&zretval); - - zval_ptr_dtor(&zfuncname); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); - - FG(user_stream_current_filename) = NULL; - return stream; } @@ -561,7 +557,6 @@ PHP_FUNCTION(stream_wrapper_restore) static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_t count) { - zval func_name; zval retval; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; zval args[1]; @@ -569,29 +564,27 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ assert(us != NULL); - ZVAL_STRINGL(&func_name, USERSTREAM_WRITE, sizeof(USERSTREAM_WRITE)-1); - ZVAL_STRINGL(&args[0], (char*)buf, count); - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_WRITE, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args); + zend_string_release_ex(func_name, false); zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&func_name); - if (EG(exception)) { + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); + } + /* Exception occurred */ + if (Z_ISUNDEF(retval)) { return -1; } - if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) { - if (Z_TYPE(retval) == IS_FALSE) { - didwrite = -1; - } else { - convert_to_long(&retval); - didwrite = Z_LVAL(retval); - } - } else { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_WRITE " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); + if (Z_TYPE(retval) == IS_FALSE) { didwrite = -1; + } else { + convert_to_long(&retval); + didwrite = Z_LVAL(retval); } /* don't allow strange buffer overruns due to bogus return */ @@ -609,7 +602,6 @@ static ssize_t php_userstreamop_write(php_stream *stream, const char *buf, size_ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count) { - zval func_name; zval retval; zval args[1]; size_t didread = 0; @@ -617,20 +609,16 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count assert(us != NULL); - ZVAL_STRINGL(&func_name, USERSTREAM_READ, sizeof(USERSTREAM_READ)-1); - ZVAL_LONG(&args[0], count); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_READ, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args); + zend_string_release_ex(func_name, false); - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); - - zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&func_name); - - if (EG(exception)) { + if (UNEXPECTED(Z_ISUNDEF(retval))) { return -1; } - if (call_result == FAILURE) { + if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_READ " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); return -1; @@ -660,25 +648,25 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count /* since the user stream has no way of setting the eof flag directly, we need to ask it if we hit eof */ - ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1); - call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); - zval_ptr_dtor(&func_name); + func_name = ZSTR_INIT_LITERAL(USERSTREAM_EOF, false); + call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); - if (EG(exception)) { + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", + ZSTR_VAL(us->wrapper->ce->name)); stream->eof = 1; return -1; } - - if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) { + if (UNEXPECTED(Z_ISUNDEF(retval))) { stream->eof = 1; - } else if (call_result == FAILURE) { - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", - ZSTR_VAL(us->wrapper->ce->name)); + return -1; + } + if (zval_is_true(&retval)) { stream->eof = 1; } - zval_ptr_dtor(&retval); return didread; @@ -686,18 +674,16 @@ static ssize_t php_userstreamop_read(php_stream *stream, char *buf, size_t count static int php_userstreamop_close(php_stream *stream, int close_handle) { - zval func_name; zval retval; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; assert(us != NULL); - ZVAL_STRINGL(&func_name, USERSTREAM_CLOSE, sizeof(USERSTREAM_CLOSE)-1); - - call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CLOSE, false); + zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); zval_ptr_dtor(&us->object); ZVAL_UNDEF(&us->object); @@ -709,30 +695,24 @@ static int php_userstreamop_close(php_stream *stream, int close_handle) static int php_userstreamop_flush(php_stream *stream) { - zval func_name; zval retval; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; assert(us != NULL); - ZVAL_STRINGL(&func_name, USERSTREAM_FLUSH, sizeof(USERSTREAM_FLUSH)-1); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_FLUSH, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); - - if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval)) - call_result = 0; - else - call_result = -1; + int ret = call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF && zval_is_true(&retval) ? 0 : -1; zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); - return call_result; + return ret; } static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs) { - zval func_name; zval retval; int ret; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; @@ -740,16 +720,15 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when assert(us != NULL); - ZVAL_STRINGL(&func_name, USERSTREAM_SEEK, sizeof(USERSTREAM_SEEK)-1); - ZVAL_LONG(&args[0], offset); ZVAL_LONG(&args[1], whence); - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 2, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_SEEK, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 2, args); + zend_string_release_ex(func_name, false); zval_ptr_dtor(&args[0]); zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&func_name); if (call_result == FAILURE) { /* stream_seek is not implemented, so disable seeks for this stream */ @@ -773,14 +752,14 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when } /* now determine where we are */ - ZVAL_STRINGL(&func_name, USERSTREAM_TELL, sizeof(USERSTREAM_TELL)-1); - - call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); + func_name = ZSTR_INIT_LITERAL(USERSTREAM_TELL, false); + call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); if (call_result == SUCCESS && Z_TYPE(retval) == IS_LONG) { *newoffs = Z_LVAL(retval); ret = 0; - } else if (call_result == FAILURE) { + } else if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_TELL " is not implemented!", ZSTR_VAL(us->wrapper->ce->name)); ret = -1; } else { @@ -788,7 +767,6 @@ static int php_userstreamop_seek(php_stream *stream, zend_off_t offset, int when } zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); return ret; } @@ -832,202 +810,239 @@ static void statbuf_from_array(const HashTable *array, php_stream_statbuf *ssb) static int php_userstreamop_stat(php_stream *stream, php_stream_statbuf *ssb) { - zval func_name; zval retval; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; int ret = -1; - ZVAL_STRINGL(&func_name, USERSTREAM_STAT, sizeof(USERSTREAM_STAT)-1); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_STAT, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); + return -1; + } + if (UNEXPECTED(Z_ISUNDEF(retval))) { + return -1; + } - if (call_result == SUCCESS && Z_TYPE(retval) == IS_ARRAY) { + if (EXPECTED(Z_TYPE(retval) == IS_ARRAY)) { statbuf_from_array(Z_ARR(retval), ssb); ret = 0; - } else { - if (call_result == FAILURE) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STAT " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); - } } + // TODO: Warning on incorrect return type? zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); return ret; } - -static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) { - zval func_name; +static int user_stream_set_check_liveliness(const php_userstream_data_t *us) +{ zval retval; - zend_result call_result; - php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; - int ret = PHP_STREAM_OPTION_RETURN_NOTIMPL; - zval args[3]; - switch (option) { - case PHP_STREAM_OPTION_CHECK_LIVENESS: - ZVAL_STRINGL(&func_name, USERSTREAM_EOF, sizeof(USERSTREAM_EOF)-1); - call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); - if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { - ret = zval_is_true(&retval) ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; - } else { - ret = PHP_STREAM_OPTION_RETURN_ERR; - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", - ZSTR_VAL(us->wrapper->ce->name)); - } + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_EOF, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); + + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_EOF " is not implemented! Assuming EOF", + ZSTR_VAL(us->wrapper->ce->name)); + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (UNEXPECTED(Z_ISUNDEF(retval))) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { + return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_ERR : PHP_STREAM_OPTION_RETURN_OK; + } else { + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_EOF " value must be of type bool, %s given", + ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); - break; + return PHP_STREAM_OPTION_RETURN_ERR; + } +} - case PHP_STREAM_OPTION_LOCKING: - ZVAL_LONG(&args[0], 0); +static int user_stream_set_locking(const php_userstream_data_t *us, int value) +{ + zval retval; + zval zlock; + zend_long lock = 0; - if (value & LOCK_NB) { - Z_LVAL_P(&args[0]) |= PHP_LOCK_NB; - } - switch(value & ~LOCK_NB) { + if (value & LOCK_NB) { + lock |= PHP_LOCK_NB; + } + switch (value & ~LOCK_NB) { case LOCK_SH: - Z_LVAL_P(&args[0]) |= PHP_LOCK_SH; + lock |= PHP_LOCK_SH; break; case LOCK_EX: - Z_LVAL_P(&args[0]) |= PHP_LOCK_EX; + lock |= PHP_LOCK_EX; break; case LOCK_UN: - Z_LVAL_P(&args[0]) |= PHP_LOCK_UN; + lock |= PHP_LOCK_UN; break; - } - - /* TODO wouldblock */ - ZVAL_STRINGL(&func_name, USERSTREAM_LOCK, sizeof(USERSTREAM_LOCK)-1); - - call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); - - if (call_result == SUCCESS && (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { - ret = (Z_TYPE(retval) == IS_FALSE); - } else if (call_result == FAILURE) { - if (value == 0) { - /* lock support test (TODO: more check) */ - ret = PHP_STREAM_OPTION_RETURN_OK; - } else { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_LOCK " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); - ret = PHP_STREAM_OPTION_RETURN_ERR; - } - } - - zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); - zval_ptr_dtor(&args[0]); - break; - - case PHP_STREAM_OPTION_TRUNCATE_API: - ZVAL_STRINGL(&func_name, USERSTREAM_TRUNCATE, sizeof(USERSTREAM_TRUNCATE)-1); + default: + // TODO: Warn on invalid option value? + ; + } + ZVAL_LONG(&zlock, lock); - switch (value) { - case PHP_STREAM_TRUNCATE_SUPPORTED: - if (zend_is_callable_ex(&func_name, Z_OBJ(us->object), IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, NULL, NULL)) - ret = PHP_STREAM_OPTION_RETURN_OK; - else - ret = PHP_STREAM_OPTION_RETURN_ERR; - break; + /* TODO wouldblock */ + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_LOCK, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, &zlock); + zend_string_release_ex(func_name, false); - case PHP_STREAM_TRUNCATE_SET_SIZE: { - ptrdiff_t new_size = *(ptrdiff_t*) ptrparam; - if (new_size >= 0 && new_size <= (ptrdiff_t)LONG_MAX) { - ZVAL_LONG(&args[0], (zend_long)new_size); - call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); - if (call_result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) { - if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) { - ret = (Z_TYPE(retval) == IS_TRUE) ? PHP_STREAM_OPTION_RETURN_OK : - PHP_STREAM_OPTION_RETURN_ERR; - } else { - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_TRUNCATE " did not return a boolean!", - ZSTR_VAL(us->wrapper->ce->name)); - } - } else { - php_error_docref(NULL, E_WARNING, - "%s::" USERSTREAM_TRUNCATE " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); - } - zval_ptr_dtor(&retval); - zval_ptr_dtor(&args[0]); - } else { /* bad new size */ - ret = PHP_STREAM_OPTION_RETURN_ERR; - } - break; - } + if (UNEXPECTED(call_result == FAILURE)) { + if (value == 0) { + /* lock support test (TODO: more check) */ + return PHP_STREAM_OPTION_RETURN_OK; } - zval_ptr_dtor(&func_name); - break; + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_LOCK " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (UNEXPECTED(Z_ISUNDEF(retval))) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { + // This is somewhat confusing and relies on magic numbers. + return Z_TYPE(retval) == IS_FALSE; + } + // TODO: ext/standard/tests/file/userstreams_004.phpt returns null implicitly for function + // Should this warn or not? And should this be considered an error? + //php_error_docref(NULL, E_WARNING, + // "%s::" USERSTREAM_LOCK " value must be of type bool, %s given", + // ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); + zval_ptr_dtor(&retval); + return PHP_STREAM_OPTION_RETURN_NOTIMPL; +} - case PHP_STREAM_OPTION_READ_BUFFER: - case PHP_STREAM_OPTION_WRITE_BUFFER: - case PHP_STREAM_OPTION_READ_TIMEOUT: - case PHP_STREAM_OPTION_BLOCKING: { +static int user_stream_set_truncation(const php_userstream_data_t *us, int value, void *ptrparam) { + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_TRUNCATE, false); - ZVAL_STRINGL(&func_name, USERSTREAM_SET_OPTION, sizeof(USERSTREAM_SET_OPTION)-1); + if (value == PHP_STREAM_TRUNCATE_SUPPORTED) { + zval zstr; + ZVAL_STR(&zstr, func_name); + bool is_callable = zend_is_callable_ex(&zstr, Z_OBJ(us->object), IS_CALLABLE_SUPPRESS_DEPRECATIONS, NULL, NULL, NULL); + // Frees func_name + zval_ptr_dtor(&zstr); + return is_callable ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } + ZEND_ASSERT(value == PHP_STREAM_TRUNCATE_SET_SIZE); + ptrdiff_t new_size = *(ptrdiff_t*) ptrparam; - ZVAL_LONG(&args[0], option); - ZVAL_NULL(&args[1]); - ZVAL_NULL(&args[2]); + if (UNEXPECTED(new_size < 0 || new_size > (ptrdiff_t)LONG_MAX)) { + /* bad new size */ + zend_string_release_ex(func_name, false); + return PHP_STREAM_OPTION_RETURN_ERR; + } - switch(option) { - case PHP_STREAM_OPTION_READ_BUFFER: - case PHP_STREAM_OPTION_WRITE_BUFFER: - ZVAL_LONG(&args[1], value); - if (ptrparam) { - ZVAL_LONG(&args[2], *(long *)ptrparam); - } else { - ZVAL_LONG(&args[2], BUFSIZ); - } - break; - case PHP_STREAM_OPTION_READ_TIMEOUT: { - struct timeval tv = *(struct timeval*)ptrparam; - ZVAL_LONG(&args[1], tv.tv_sec); - ZVAL_LONG(&args[2], tv.tv_usec); - break; - } - case PHP_STREAM_OPTION_BLOCKING: - ZVAL_LONG(&args[1], value); - break; - default: - break; - } + zval retval; + zval size; - call_result = call_method_if_exists(&us->object, &func_name, &retval, 3, args); + ZVAL_LONG(&size, (zend_long)new_size); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, &size); + zend_string_release_ex(func_name, false); - if (call_result == FAILURE) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_SET_OPTION " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); - ret = PHP_STREAM_OPTION_RETURN_ERR; - } else if (zend_is_true(&retval)) { - ret = PHP_STREAM_OPTION_RETURN_OK; + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_TRUNCATE " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (UNEXPECTED(Z_ISUNDEF(retval))) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (EXPECTED(Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE)) { + return Z_TYPE(retval) == IS_TRUE ? PHP_STREAM_OPTION_RETURN_OK : PHP_STREAM_OPTION_RETURN_ERR; + } else { + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_TRUNCATE " value must be of type bool, %s given", + ZSTR_VAL(us->wrapper->ce->name), zend_zval_value_name(&retval)); + zval_ptr_dtor(&retval); + return PHP_STREAM_OPTION_RETURN_ERR; + } +} + +static int user_stream_set_option(const php_userstream_data_t *us, int option, int value, void *ptrparam) +{ + zval args[3]; + ZVAL_LONG(&args[0], option); + ZVAL_LONG(&args[1], value); + ZVAL_NULL(&args[2]); + + if (option == PHP_STREAM_OPTION_READ_TIMEOUT) { + struct timeval tv = *(struct timeval*)ptrparam; + ZVAL_LONG(&args[1], tv.tv_sec); + ZVAL_LONG(&args[2], tv.tv_usec); + } else if (option == PHP_STREAM_OPTION_READ_BUFFER || option == PHP_STREAM_OPTION_WRITE_BUFFER) { + if (ptrparam) { + ZVAL_LONG(&args[2], *(long *)ptrparam); } else { - ret = PHP_STREAM_OPTION_RETURN_ERR; + ZVAL_LONG(&args[2], BUFSIZ); } + } - zval_ptr_dtor(&retval); - zval_ptr_dtor(&args[2]); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&func_name); + zval retval; + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_SET_OPTION, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 3, args); + zend_string_release_ex(func_name, false); - break; - } + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, + "%s::" USERSTREAM_SET_OPTION " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); + return PHP_STREAM_OPTION_RETURN_ERR; + } + if (UNEXPECTED(Z_ISUNDEF(retval))) { + return PHP_STREAM_OPTION_RETURN_ERR; + } + + int ret; + if (zend_is_true(&retval)) { + ret = PHP_STREAM_OPTION_RETURN_OK; + } else { + ret = PHP_STREAM_OPTION_RETURN_ERR; } + zval_ptr_dtor(&retval); return ret; } +static int php_userstreamop_set_option(php_stream *stream, int option, int value, void *ptrparam) { + php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; + + switch (option) { + case PHP_STREAM_OPTION_CHECK_LIVENESS: + return user_stream_set_check_liveliness(us); + + case PHP_STREAM_OPTION_LOCKING: + return user_stream_set_locking(us, value); + + case PHP_STREAM_OPTION_TRUNCATE_API: + return user_stream_set_truncation(us, value, ptrparam); + + case PHP_STREAM_OPTION_READ_BUFFER: + case PHP_STREAM_OPTION_WRITE_BUFFER: + case PHP_STREAM_OPTION_READ_TIMEOUT: + case PHP_STREAM_OPTION_BLOCKING: + return user_stream_set_option(us, option, value, ptrparam); + + default: + return PHP_STREAM_OPTION_RETURN_NOTIMPL; + } +} + static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; - zval zfuncname, zretval; + zval zretval; zval args[1]; zval object; int ret = 0; @@ -1041,22 +1056,21 @@ static int user_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int /* call the unlink method */ ZVAL_STRING(&args[0], url); - ZVAL_STRING(&zfuncname, USERSTREAM_UNLINK); - zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 1, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_UNLINK, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 1, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&object); - if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) { - ret = (Z_TYPE(zretval) == IS_TRUE); - } else if (call_result == FAILURE) { + if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_UNLINK " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { + ret = Z_TYPE(zretval) == IS_TRUE; } + // TODO: Warn on invalid return type, or use zval_is_true()? - /* clean up */ - zval_ptr_dtor(&object); zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); - - zval_ptr_dtor(&args[0]); return ret; } @@ -1065,7 +1079,7 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from int options, php_stream_context *context) { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; - zval zfuncname, zretval; + zval zretval; zval args[2]; zval object; int ret = 0; @@ -1080,24 +1094,22 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from ZVAL_STRING(&args[0], url_from); ZVAL_STRING(&args[1], url_to); - ZVAL_STRING(&zfuncname, USERSTREAM_RENAME); - - zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_RENAME, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 2, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[1]); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&object); - if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) { - ret = (Z_TYPE(zretval) == IS_TRUE); - } else if (call_result == FAILURE) { + if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RENAME " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { + ret = Z_TYPE(zretval) == IS_TRUE; } + // TODO: Warn on invalid return type, or use zval_is_true()? - /* clean up */ - zval_ptr_dtor(&object); zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); - return ret; } @@ -1105,7 +1117,7 @@ static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int int options, php_stream_context *context) { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; - zval zfuncname, zretval; + zval zretval; zval args[3]; zval object; int ret = 0; @@ -1121,25 +1133,21 @@ static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int ZVAL_LONG(&args[1], mode); ZVAL_LONG(&args[2], options); - ZVAL_STRING(&zfuncname, USERSTREAM_MKDIR); - - zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_MKDIR, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 3, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&object); - if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) { - ret = (Z_TYPE(zretval) == IS_TRUE); - } else if (call_result == FAILURE) { + if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_MKDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { + ret = Z_TYPE(zretval) == IS_TRUE; } + // TODO: Warn on invalid return type, or use zval_is_true()? - /* clean up */ - zval_ptr_dtor(&object); zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); - zval_ptr_dtor(&args[2]); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); - return ret; } @@ -1147,7 +1155,7 @@ static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context) { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; - zval zfuncname, zretval; + zval zretval; zval args[2]; zval object; int ret = 0; @@ -1162,24 +1170,21 @@ static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, ZVAL_STRING(&args[0], url); ZVAL_LONG(&args[1], options); - ZVAL_STRING(&zfuncname, USERSTREAM_RMDIR); - - zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_RMDIR, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 2, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&object); - if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) { - ret = (Z_TYPE(zretval) == IS_TRUE); - } else if (call_result == FAILURE) { + if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_RMDIR " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { + ret = Z_TYPE(zretval) == IS_TRUE; } + // TODO: Warn on invalid return type, or use zval_is_true()? - /* clean up */ - zval_ptr_dtor(&object); zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); - return ret; } @@ -1187,7 +1192,7 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i void *value, php_stream_context *context) { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; - zval zfuncname, zretval; + zval zretval; zval args[3]; zval object; int ret = 0; @@ -1227,25 +1232,22 @@ static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, i ZVAL_STRING(&args[0], url); ZVAL_LONG(&args[1], option); - ZVAL_STRING(&zfuncname, USERSTREAM_METADATA); - - zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 3, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_METADATA, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 3, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[2]); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&object); - if (call_result == SUCCESS && (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE)) { - ret = Z_TYPE(zretval) == IS_TRUE; - } else if (call_result == FAILURE) { + if (UNEXPECTED(call_result == FAILURE)) { php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_METADATA " is not implemented!", ZSTR_VAL(uwrap->ce->name)); + } else if (Z_TYPE(zretval) == IS_FALSE || Z_TYPE(zretval) == IS_TRUE) { + ret = Z_TYPE(zretval) == IS_TRUE; } + // TODO: Warn on invalid return type, or use zval_is_true()? - /* clean up */ - zval_ptr_dtor(&object); zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); - zval_ptr_dtor(&args[0]); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[2]); - return ret; } @@ -1254,7 +1256,7 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i php_stream_statbuf *ssb, php_stream_context *context) { struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; - zval zfuncname, zretval; + zval zretval; zval args[2]; zval object; int ret = -1; @@ -1262,90 +1264,97 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i /* create an instance of our class */ user_stream_create_object(uwrap, context, &object); if (Z_TYPE(object) == IS_UNDEF) { - return ret; + return -1; } /* call it's stat_url method - set up params first */ ZVAL_STRING(&args[0], url); ZVAL_LONG(&args[1], flags); - ZVAL_STRING(&zfuncname, USERSTREAM_STATURL); - - zend_result call_result = call_method_if_exists(&object, &zfuncname, &zretval, 2, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_STATURL, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(object), func_name, &zretval, 2, args); + zend_string_release_ex(func_name, false); + zval_ptr_dtor(&args[0]); + zval_ptr_dtor(&object); - if (call_result == SUCCESS && Z_TYPE(zretval) == IS_ARRAY) { - /* We got the info we needed */ + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!", + ZSTR_VAL(uwrap->ce->name)); + return -1; + } + if (UNEXPECTED(Z_ISUNDEF(zretval))) { + return -1; + } + if (EXPECTED(Z_TYPE(zretval) == IS_ARRAY)) { statbuf_from_array(Z_ARR(zretval), ssb); ret = 0; - } else { - if (call_result == FAILURE) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_STATURL " is not implemented!", - ZSTR_VAL(uwrap->ce->name)); - } } + // TODO: Warning on incorrect return type? - /* clean up */ - zval_ptr_dtor(&object); zval_ptr_dtor(&zretval); - zval_ptr_dtor(&zfuncname); - zval_ptr_dtor(&args[1]); - zval_ptr_dtor(&args[0]); - return ret; } static ssize_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count) { - zval func_name; zval retval; size_t didread = 0; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; php_stream_dirent *ent = (php_stream_dirent*)buf; /* avoid problems if someone mis-uses the stream */ - if (count != sizeof(php_stream_dirent)) + if (count != sizeof(php_stream_dirent)) { return -1; + } - ZVAL_STRINGL(&func_name, USERSTREAM_DIR_READ, sizeof(USERSTREAM_DIR_READ)-1); - - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_READ, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); - if (call_result == SUCCESS && Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) { + if (UNEXPECTED(call_result == FAILURE)) { + php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); + return -1; + } + if (UNEXPECTED(Z_ISUNDEF(retval))) { + return -1; + } + // TODO: Warn/TypeError for invalid returns? + if (Z_TYPE(retval) != IS_FALSE && Z_TYPE(retval) != IS_TRUE) { + zend_string *str = zval_try_get_string(&retval); + if (UNEXPECTED(str == NULL)) { + zval_ptr_dtor(&retval); + return -1; + } convert_to_string(&retval); - PHP_STRLCPY(ent->d_name, Z_STRVAL(retval), sizeof(ent->d_name), Z_STRLEN(retval)); + PHP_STRLCPY(ent->d_name, ZSTR_VAL(str), sizeof(ent->d_name), ZSTR_LEN(str)); + zend_string_release(str); ent->d_type = DT_UNKNOWN; didread = sizeof(php_stream_dirent); - } else if (call_result == FAILURE) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_DIR_READ " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); } zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); return didread; } static int php_userstreamop_closedir(php_stream *stream, int close_handle) { - zval func_name; zval retval; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; assert(us != NULL); - ZVAL_STRINGL(&func_name, USERSTREAM_DIR_CLOSE, sizeof(USERSTREAM_DIR_CLOSE)-1); - - call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_CLOSE, false); + zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); zval_ptr_dtor(&us->object); ZVAL_UNDEF(&us->object); - efree(us); return 0; @@ -1353,16 +1362,14 @@ static int php_userstreamop_closedir(php_stream *stream, int close_handle) static int php_userstreamop_rewinddir(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs) { - zval func_name; zval retval; php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; - ZVAL_STRINGL(&func_name, USERSTREAM_DIR_REWIND, sizeof(USERSTREAM_DIR_REWIND)-1); - - call_method_if_exists(&us->object, &func_name, &retval, 0, NULL); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_DIR_REWIND, false); + zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 0, NULL); + zend_string_release_ex(func_name, false); zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); return 0; @@ -1371,7 +1378,6 @@ static int php_userstreamop_rewinddir(php_stream *stream, zend_off_t offset, int static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) { php_userstream_data_t *us = (php_userstream_data_t *)stream->abstract; - zval func_name; zval retval; zval args[1]; php_stream * intstream = NULL; @@ -1379,8 +1385,6 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) /* If we are checking if the stream can cast, no return pointer is provided, so do not emit errors */ bool report_errors = retptr; - ZVAL_STRINGL(&func_name, USERSTREAM_CAST, sizeof(USERSTREAM_CAST)-1); - switch(castas) { case PHP_STREAM_AS_FD_FOR_SELECT: ZVAL_LONG(&args[0], PHP_STREAM_AS_FD_FOR_SELECT); @@ -1390,19 +1394,23 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) break; } - zend_result call_result = call_method_if_exists(&us->object, &func_name, &retval, 1, args); + zend_string *func_name = ZSTR_INIT_LITERAL(USERSTREAM_CAST, false); + zend_result call_result = zend_call_method_if_exists(Z_OBJ(us->object), func_name, &retval, 1, args); + zend_string_release_ex(func_name, false); - do { - if (call_result == FAILURE) { - if (report_errors) { - php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!", - ZSTR_VAL(us->wrapper->ce->name)); - } - break; + if (UNEXPECTED(call_result == FAILURE)) { + if (report_errors) { + php_error_docref(NULL, E_WARNING, "%s::" USERSTREAM_CAST " is not implemented!", + ZSTR_VAL(us->wrapper->ce->name)); } + return FAILURE; + } + + do { if (!zend_is_true(&retval)) { break; } + // TODO: Can this emit an exception even with no error reporting? php_stream_from_zval_no_verify(intstream, &retval); if (!intstream) { if (report_errors) { @@ -1423,8 +1431,6 @@ static int php_userstreamop_cast(php_stream *stream, int castas, void **retptr) } while (0); zval_ptr_dtor(&retval); - zval_ptr_dtor(&func_name); - zval_ptr_dtor(&args[0]); return ret; }