summaryrefslogtreecommitdiff
path: root/ext/zend_test
diff options
context:
space:
mode:
Diffstat (limited to 'ext/zend_test')
-rw-r--r--ext/zend_test/config.w321
-rw-r--r--ext/zend_test/php_test.h2
-rw-r--r--ext/zend_test/test.c521
-rw-r--r--ext/zend_test/test.stub.php84
-rw-r--r--ext/zend_test/test_arginfo.h152
-rw-r--r--ext/zend_test/tests/observer.inc8
-rw-r--r--ext/zend_test/tests/observer_backtrace_01.phpt106
-rw-r--r--ext/zend_test/tests/observer_basic_01.phpt55
-rw-r--r--ext/zend_test/tests/observer_basic_02.phpt59
-rw-r--r--ext/zend_test/tests/observer_basic_03.phpt36
-rw-r--r--ext/zend_test/tests/observer_basic_04.phpt30
-rw-r--r--ext/zend_test/tests/observer_basic_05.phpt32
-rw-r--r--ext/zend_test/tests/observer_call_user_func_01.phpt40
-rw-r--r--ext/zend_test/tests/observer_call_user_func_02.phpt40
-rw-r--r--ext/zend_test/tests/observer_call_user_func_03.phpt39
-rw-r--r--ext/zend_test/tests/observer_call_user_func_04.phpt39
-rw-r--r--ext/zend_test/tests/observer_closure_01.phpt49
-rw-r--r--ext/zend_test/tests/observer_closure_02.phpt32
-rw-r--r--ext/zend_test/tests/observer_error_01.phpt29
-rw-r--r--ext/zend_test/tests/observer_error_02.phpt28
-rw-r--r--ext/zend_test/tests/observer_error_03.phpt39
-rw-r--r--ext/zend_test/tests/observer_error_04.phpt46
-rw-r--r--ext/zend_test/tests/observer_error_05.phpt37
-rw-r--r--ext/zend_test/tests/observer_eval_01.phpt21
-rw-r--r--ext/zend_test/tests/observer_exception_01.phpt46
-rw-r--r--ext/zend_test/tests/observer_generator_01.phpt47
-rw-r--r--ext/zend_test/tests/observer_generator_02.phpt50
-rw-r--r--ext/zend_test/tests/observer_generator_03.phpt68
-rw-r--r--ext/zend_test/tests/observer_generator_04.phpt64
-rw-r--r--ext/zend_test/tests/observer_generator_05.phpt53
-rw-r--r--ext/zend_test/tests/observer_magic_01.phpt45
-rw-r--r--ext/zend_test/tests/observer_opline_01.phpt53
-rw-r--r--ext/zend_test/tests/observer_retval_01.phpt30
-rw-r--r--ext/zend_test/tests/observer_retval_02.phpt32
-rw-r--r--ext/zend_test/tests/observer_retval_03.phpt32
-rw-r--r--ext/zend_test/tests/observer_retval_04.phpt52
-rw-r--r--ext/zend_test/tests/observer_retval_05.phpt33
-rw-r--r--ext/zend_test/tests/observer_retval_06.phpt30
-rw-r--r--ext/zend_test/tests/observer_retval_07.phpt39
-rw-r--r--ext/zend_test/tests/observer_retval_by_ref_01.phpt30
-rw-r--r--ext/zend_test/tests/observer_retval_by_ref_02.phpt34
-rw-r--r--ext/zend_test/tests/observer_retval_by_ref_03.phpt43
-rw-r--r--ext/zend_test/tests/observer_shutdown_01.phpt44
-rw-r--r--ext/zend_test/tests/observer_shutdown_02.phpt50
-rw-r--r--ext/zend_test/tests/observer_types_01.phpt28
-rw-r--r--ext/zend_test/tests/observer_zend_call_function_01.phpt36
46 files changed, 2370 insertions, 94 deletions
diff --git a/ext/zend_test/config.w32 b/ext/zend_test/config.w32
index 76a0f1ae5b..d66fd0b1ee 100644
--- a/ext/zend_test/config.w32
+++ b/ext/zend_test/config.w32
@@ -4,4 +4,5 @@ ARG_ENABLE("zend-test", "enable zend-test extension", "no");
if (PHP_ZEND_TEST != "no") {
EXTENSION("zend_test", "test.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
+ ADD_FLAG("CFLAGS_ZEND_TEST", "/D PHP_ZEND_TEST_EXPORTS ");
}
diff --git a/ext/zend_test/php_test.h b/ext/zend_test/php_test.h
index 03e6d836e2..2a1d7763da 100644
--- a/ext/zend_test/php_test.h
+++ b/ext/zend_test/php_test.h
@@ -1,7 +1,5 @@
/*
+----------------------------------------------------------------------+
- | PHP Version 7 |
- +----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c
index 4f81adc6ac..4e7f55d1b1 100644
--- a/ext/zend_test/test.c
+++ b/ext/zend_test/test.c
@@ -1,7 +1,5 @@
/*
+----------------------------------------------------------------------+
- | PHP Version 7 |
- +----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
@@ -23,64 +21,81 @@
#include "php.h"
#include "php_ini.h"
#include "ext/standard/info.h"
+#include "ext/standard/php_var.h"
#include "php_test.h"
+#include "test_arginfo.h"
+#include "zend_attributes.h"
+#include "zend_observer.h"
+#include "zend_smart_str.h"
+
+ZEND_BEGIN_MODULE_GLOBALS(zend_test)
+ int observer_enabled;
+ int observer_show_output;
+ int observer_observe_all;
+ int observer_observe_includes;
+ int observer_observe_functions;
+ int observer_show_return_type;
+ int observer_show_return_value;
+ int observer_show_init_backtrace;
+ int observer_show_opcode;
+ int observer_nesting_depth;
+ int replace_zend_execute_ex;
+ZEND_END_MODULE_GLOBALS(zend_test)
+
+ZEND_DECLARE_MODULE_GLOBALS(zend_test)
+
+#define ZT_G(v) ZEND_MODULE_GLOBALS_ACCESSOR(zend_test, v)
static zend_class_entry *zend_test_interface;
static zend_class_entry *zend_test_class;
static zend_class_entry *zend_test_child_class;
static zend_class_entry *zend_test_trait;
+static zend_class_entry *zend_test_attribute;
static zend_object_handlers zend_test_class_handlers;
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_array_return, IS_ARRAY, 0)
-ZEND_END_ARG_INFO()
-
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_nullable_array_return, IS_ARRAY, 1)
-ZEND_END_ARG_INFO()
-
-ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO(arginfo_zend_test_void_return, IS_VOID, 0)
-ZEND_END_ARG_INFO()
-
-ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_terminate_string, 0, 0, 1)
- ZEND_ARG_INFO(1, str)
-ZEND_END_ARG_INFO()
+static ZEND_FUNCTION(zend_test_func)
+{
+ RETVAL_STR_COPY(EX(func)->common.function_name);
-ZEND_BEGIN_ARG_INFO_EX(arginfo_zend_leak_variable, 0, 0, 1)
- ZEND_ARG_INFO(0, variable)
-ZEND_END_ARG_INFO()
+ /* Cleanup trampoline */
+ ZEND_ASSERT(EX(func)->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE);
+ zend_string_release(EX(func)->common.function_name);
+ zend_free_trampoline(EX(func));
+ EX(func) = NULL;
+}
-ZEND_FUNCTION(zend_test_func)
+static ZEND_FUNCTION(zend_test_array_return)
{
- /* dummy */
+ ZEND_PARSE_PARAMETERS_NONE();
}
-ZEND_FUNCTION(zend_test_array_return)
+static ZEND_FUNCTION(zend_test_nullable_array_return)
{
- zval *arg1, *arg2;
-
- zend_parse_parameters(ZEND_NUM_ARGS(), "|zz", &arg1, &arg2);
+ ZEND_PARSE_PARAMETERS_NONE();
}
-ZEND_FUNCTION(zend_test_nullable_array_return)
+static ZEND_FUNCTION(zend_test_void_return)
{
- zval *arg1, *arg2;
-
- zend_parse_parameters(ZEND_NUM_ARGS(), "|zz", &arg1, &arg2);
+ /* dummy */
+ ZEND_PARSE_PARAMETERS_NONE();
}
-ZEND_FUNCTION(zend_test_void_return)
+static ZEND_FUNCTION(zend_test_deprecated)
{
- /* dummy */
+ zval *arg1;
+
+ zend_parse_parameters(ZEND_NUM_ARGS(), "|z", &arg1);
}
-/* Create a string without terminating null byte. Must be termined with
+/* Create a string without terminating null byte. Must be terminated with
* zend_terminate_string() before destruction, otherwise a warning is issued
* in debug builds. */
-ZEND_FUNCTION(zend_create_unterminated_string)
+static ZEND_FUNCTION(zend_create_unterminated_string)
{
zend_string *str, *res;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) {
- return;
+ RETURN_THROWS();
}
res = zend_string_alloc(ZSTR_LEN(str), 0);
@@ -91,39 +106,37 @@ ZEND_FUNCTION(zend_create_unterminated_string)
}
/* Enforce terminate null byte on string. This avoids a warning in debug builds. */
-ZEND_FUNCTION(zend_terminate_string)
+static ZEND_FUNCTION(zend_terminate_string)
{
zend_string *str;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "S", &str) == FAILURE) {
- return;
+ RETURN_THROWS();
}
ZSTR_VAL(str)[ZSTR_LEN(str)] = '\0';
}
-/* {{{ proto void zend_leak_bytes([int num_bytes])
- Cause an intentional memory leak, for testing/debugging purposes */
-ZEND_FUNCTION(zend_leak_bytes)
+/* {{{ Cause an intentional memory leak, for testing/debugging purposes */
+static ZEND_FUNCTION(zend_leak_bytes)
{
zend_long leakbytes = 3;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "|l", &leakbytes) == FAILURE) {
- return;
+ RETURN_THROWS();
}
emalloc(leakbytes);
}
/* }}} */
-/* {{{ proto void zend_leak_variable(mixed variable)
- Leak a refcounted variable */
-ZEND_FUNCTION(zend_leak_variable)
+/* {{{ Leak a refcounted variable */
+static ZEND_FUNCTION(zend_leak_variable)
{
zval *zv;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &zv) == FAILURE) {
- return;
+ RETURN_THROWS();
}
if (!Z_REFCOUNTED_P(zv)) {
@@ -135,6 +148,100 @@ ZEND_FUNCTION(zend_leak_variable)
}
/* }}} */
+/* Tests Z_PARAM_OBJ_OR_STR */
+static ZEND_FUNCTION(zend_string_or_object)
+{
+ zend_string *str;
+ zend_object *object;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_OBJ_OR_STR(object, str)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (str) {
+ RETURN_STR_COPY(str);
+ } else {
+ RETURN_OBJ_COPY(object);
+ }
+}
+/* }}} */
+
+/* Tests Z_PARAM_OBJ_OR_STR_OR_NULL */
+static ZEND_FUNCTION(zend_string_or_object_or_null)
+{
+ zend_string *str;
+ zend_object *object;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_OBJ_OR_STR_OR_NULL(object, str)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (str) {
+ RETURN_STR_COPY(str);
+ } else if (object) {
+ RETURN_OBJ_COPY(object);
+ } else {
+ RETURN_NULL();
+ }
+}
+/* }}} */
+
+/* Tests Z_PARAM_OBJ_OF_CLASS_OR_STR */
+static ZEND_FUNCTION(zend_string_or_stdclass)
+{
+ zend_string *str;
+ zend_object *object;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_OBJ_OF_CLASS_OR_STR(object, zend_standard_class_def, str)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (str) {
+ RETURN_STR_COPY(str);
+ } else {
+ RETURN_OBJ_COPY(object);
+ }
+}
+/* }}} */
+
+/* Tests Z_PARAM_OBJ_OF_CLASS_OR_STR_OR_NULL */
+static ZEND_FUNCTION(zend_string_or_stdclass_or_null)
+{
+ zend_string *str;
+ zend_object *object;
+
+ ZEND_PARSE_PARAMETERS_START(1, 1)
+ Z_PARAM_OBJ_OF_CLASS_OR_STR_OR_NULL(object, zend_standard_class_def, str)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (str) {
+ RETURN_STR_COPY(str);
+ } else if (object) {
+ RETURN_OBJ_COPY(object);
+ } else {
+ RETURN_NULL();
+ }
+}
+/* }}} */
+
+/* TESTS Z_PARAM_ITERABLE and Z_PARAM_ITERABLE_OR_NULL */
+static ZEND_FUNCTION(zend_iterable)
+{
+ zval *arg1, *arg2;
+
+ ZEND_PARSE_PARAMETERS_START(1, 2)
+ Z_PARAM_ITERABLE(arg1)
+ Z_PARAM_OPTIONAL
+ Z_PARAM_ITERABLE_OR_NULL(arg2)
+ ZEND_PARSE_PARAMETERS_END();
+}
+
+static ZEND_FUNCTION(namespaced_func)
+{
+ ZEND_PARSE_PARAMETERS_NONE();
+ RETURN_TRUE;
+}
+
static zend_object *zend_test_class_new(zend_class_entry *class_type) /* {{{ */ {
zend_object *obj = zend_objects_new(class_type);
object_properties_init(obj, class_type);
@@ -144,15 +251,20 @@ static zend_object *zend_test_class_new(zend_class_entry *class_type) /* {{{ */
/* }}} */
static zend_function *zend_test_class_method_get(zend_object **object, zend_string *name, const zval *key) /* {{{ */ {
- zend_internal_function *fptr = emalloc(sizeof(zend_internal_function));
- fptr->type = ZEND_OVERLOADED_FUNCTION_TEMPORARY;
+ zend_internal_function *fptr;
+
+ if (EXPECTED(EG(trampoline).common.function_name == NULL)) {
+ fptr = (zend_internal_function *) &EG(trampoline);
+ } else {
+ fptr = emalloc(sizeof(zend_internal_function));
+ }
+ memset(fptr, 0, sizeof(zend_internal_function));
+ fptr->type = ZEND_INTERNAL_FUNCTION;
fptr->num_args = 1;
- fptr->arg_info = NULL;
fptr->scope = (*object)->ce;
fptr->fn_flags = ZEND_ACC_CALL_VIA_HANDLER;
fptr->function_name = zend_string_copy(name);
fptr->handler = ZEND_FN(zend_test_func);
- zend_set_function_arg_flags((zend_function*)fptr);
return (zend_function*)fptr;
}
@@ -160,15 +272,20 @@ static zend_function *zend_test_class_method_get(zend_object **object, zend_stri
static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, zend_string *name) /* {{{ */ {
if (zend_string_equals_literal_ci(name, "test")) {
- zend_internal_function *fptr = emalloc(sizeof(zend_internal_function));
- fptr->type = ZEND_OVERLOADED_FUNCTION;
+ zend_internal_function *fptr;
+
+ if (EXPECTED(EG(trampoline).common.function_name == NULL)) {
+ fptr = (zend_internal_function *) &EG(trampoline);
+ } else {
+ fptr = emalloc(sizeof(zend_internal_function));
+ }
+ memset(fptr, 0, sizeof(zend_internal_function));
+ fptr->type = ZEND_INTERNAL_FUNCTION;
fptr->num_args = 1;
- fptr->arg_info = NULL;
fptr->scope = ce;
fptr->fn_flags = ZEND_ACC_CALL_VIA_HANDLER|ZEND_ACC_STATIC;
- fptr->function_name = name;
+ fptr->function_name = zend_string_copy(name);
fptr->handler = ZEND_FN(zend_test_func);
- zend_set_function_arg_flags((zend_function*)fptr);
return (zend_function*)fptr;
}
@@ -176,32 +293,62 @@ static zend_function *zend_test_class_static_method_get(zend_class_entry *ce, ze
}
/* }}} */
-static int zend_test_class_call_method(zend_string *method, zend_object *object, INTERNAL_FUNCTION_PARAMETERS) /* {{{ */ {
- RETVAL_STR(zend_string_copy(method));
- return 0;
+void zend_attribute_validate_zendtestattribute(zend_attribute *attr, uint32_t target, zend_class_entry *scope)
+{
+ if (target != ZEND_ATTRIBUTE_TARGET_CLASS) {
+ zend_error(E_COMPILE_ERROR, "Only classes can be marked with #[ZendTestAttribute]");
+ }
+}
+
+static ZEND_METHOD(_ZendTestClass, __toString) {
+ ZEND_PARSE_PARAMETERS_NONE();
+ RETURN_EMPTY_STRING();
}
-/* }}} */
/* Internal function returns bool, we return int. */
-static ZEND_METHOD(_ZendTestClass, is_object) /* {{{ */ {
+static ZEND_METHOD(_ZendTestClass, is_object) {
+ ZEND_PARSE_PARAMETERS_NONE();
RETURN_LONG(42);
}
-/* }}} */
-static ZEND_METHOD(_ZendTestTrait, testMethod) /* {{{ */ {
+static ZEND_METHOD(_ZendTestClass, returnsStatic) {
+ ZEND_PARSE_PARAMETERS_NONE();
+ object_init_ex(return_value, zend_get_called_scope(execute_data));
+}
+
+static ZEND_METHOD(_ZendTestTrait, testMethod) {
+ ZEND_PARSE_PARAMETERS_NONE();
RETURN_TRUE;
}
-/* }}} */
-static const zend_function_entry zend_test_class_methods[] = {
- ZEND_ME(_ZendTestClass, is_object, NULL, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
- ZEND_FE_END
-};
+static ZEND_METHOD(ZendTestNS_Foo, method) {
+ ZEND_PARSE_PARAMETERS_NONE();
+}
-static const zend_function_entry zend_test_trait_methods[] = {
- ZEND_ME(_ZendTestTrait, testMethod, NULL, ZEND_ACC_PUBLIC)
- ZEND_FE_END
-};
+static ZEND_METHOD(ZendTestNS2_Foo, method) {
+ ZEND_PARSE_PARAMETERS_NONE();
+}
+
+PHP_INI_BEGIN()
+ STD_PHP_INI_BOOLEAN("zend_test.observer.enabled", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_enabled, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.show_output", "1", PHP_INI_SYSTEM, OnUpdateBool, observer_show_output, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.observe_all", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_all, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.observe_includes", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_includes, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.observe_functions", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_observe_functions, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_type", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_type, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.show_return_value", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_return_value, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.show_init_backtrace", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_init_backtrace, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.observer.show_opcode", "0", PHP_INI_SYSTEM, OnUpdateBool, observer_show_opcode, zend_zend_test_globals, zend_test_globals)
+ STD_PHP_INI_BOOLEAN("zend_test.replace_zend_execute_ex", "0", PHP_INI_SYSTEM, OnUpdateBool, replace_zend_execute_ex, zend_zend_test_globals, zend_test_globals)
+PHP_INI_END()
+
+static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data);
+
+void (*old_zend_execute_ex)(zend_execute_data *execute_data);
+static void custom_zend_execute_ex(zend_execute_data *execute_data)
+{
+ old_zend_execute_ex(execute_data);
+}
PHP_MINIT_FUNCTION(zend_test)
{
@@ -210,8 +357,8 @@ PHP_MINIT_FUNCTION(zend_test)
INIT_CLASS_ENTRY(class_entry, "_ZendTestInterface", NULL);
zend_test_interface = zend_register_internal_interface(&class_entry);
zend_declare_class_constant_long(zend_test_interface, ZEND_STRL("DUMMY"), 0);
- INIT_CLASS_ENTRY(class_entry, "_ZendTestClass", zend_test_class_methods);
- zend_test_class = zend_register_internal_class_ex(&class_entry, NULL);
+ INIT_CLASS_ENTRY(class_entry, "_ZendTestClass", class__ZendTestClass_methods);
+ zend_test_class = zend_register_internal_class(&class_entry);
zend_class_implements(zend_test_class, 1, zend_test_interface);
zend_test_class->create_object = zend_test_class_new;
zend_test_class->get_static_method = zend_test_class_static_method_get;
@@ -223,7 +370,8 @@ PHP_MINIT_FUNCTION(zend_test)
zval val;
ZVAL_LONG(&val, 123);
zend_declare_typed_property(
- zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, ZEND_TYPE_ENCODE(IS_LONG, 0));
+ zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL,
+ (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0));
zend_string_release(name);
}
@@ -234,7 +382,22 @@ PHP_MINIT_FUNCTION(zend_test)
ZVAL_NULL(&val);
zend_declare_typed_property(
zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL,
- ZEND_TYPE_ENCODE_CLASS(class_name, 1));
+ (zend_type) ZEND_TYPE_INIT_CLASS(class_name, 1, 0));
+ zend_string_release(name);
+ }
+
+ {
+ zend_string *name = zend_string_init("classUnionProp", sizeof("classUnionProp") - 1, 1);
+ zend_string *class_name1 = zend_string_init("stdClass", sizeof("stdClass") - 1, 1);
+ zend_string *class_name2 = zend_string_init("Iterator", sizeof("Iterator") - 1, 1);
+ zend_type_list *type_list = malloc(ZEND_TYPE_LIST_SIZE(2));
+ type_list->num_types = 2;
+ type_list->types[0] = (zend_type) ZEND_TYPE_INIT_CLASS(class_name1, 0, 0);
+ type_list->types[1] = (zend_type) ZEND_TYPE_INIT_CLASS(class_name2, 0, 0);
+ zend_type type = ZEND_TYPE_INIT_PTR(type_list, _ZEND_TYPE_LIST_BIT, 1, 0);
+ zval val;
+ ZVAL_NULL(&val);
+ zend_declare_typed_property(zend_test_class, name, &val, ZEND_ACC_PUBLIC, NULL, type);
zend_string_release(name);
}
@@ -244,7 +407,7 @@ PHP_MINIT_FUNCTION(zend_test)
ZVAL_LONG(&val, 123);
zend_declare_typed_property(
zend_test_class, name, &val, ZEND_ACC_PUBLIC | ZEND_ACC_STATIC, NULL,
- ZEND_TYPE_ENCODE(IS_LONG, 0));
+ (zend_type) ZEND_TYPE_INIT_CODE(IS_LONG, 0, 0));
zend_string_release(name);
}
@@ -253,27 +416,184 @@ PHP_MINIT_FUNCTION(zend_test)
memcpy(&zend_test_class_handlers, &std_object_handlers, sizeof(zend_object_handlers));
zend_test_class_handlers.get_method = zend_test_class_method_get;
- zend_test_class_handlers.call_method = zend_test_class_call_method;
- INIT_CLASS_ENTRY(class_entry, "_ZendTestTrait", zend_test_trait_methods);
+ INIT_CLASS_ENTRY(class_entry, "_ZendTestTrait", class__ZendTestTrait_methods);
zend_test_trait = zend_register_internal_class(&class_entry);
zend_test_trait->ce_flags |= ZEND_ACC_TRAIT;
zend_declare_property_null(zend_test_trait, "testProp", sizeof("testProp")-1, ZEND_ACC_PUBLIC);
zend_register_class_alias("_ZendTestClassAlias", zend_test_class);
+
+ REGISTER_LONG_CONSTANT("ZEND_TEST_DEPRECATED", 42, CONST_PERSISTENT | CONST_DEPRECATED);
+
+ INIT_CLASS_ENTRY(class_entry, "ZendTestAttribute", NULL);
+ zend_test_attribute = zend_register_internal_class(&class_entry);
+ zend_test_attribute->ce_flags |= ZEND_ACC_FINAL;
+
+ {
+ zend_internal_attribute *attr = zend_internal_attribute_register(zend_test_attribute, ZEND_ATTRIBUTE_TARGET_ALL);
+ attr->validator = zend_attribute_validate_zendtestattribute;
+ }
+
+ // Loading via dl() not supported with the observer API
+ if (type != MODULE_TEMPORARY) {
+ REGISTER_INI_ENTRIES();
+ if (ZT_G(observer_enabled)) {
+ zend_observer_fcall_register(observer_fcall_init);
+ }
+ } else {
+ (void)ini_entries;
+ }
+
+ if (ZT_G(replace_zend_execute_ex)) {
+ old_zend_execute_ex = zend_execute_ex;
+ zend_execute_ex = custom_zend_execute_ex;
+ }
+
return SUCCESS;
}
PHP_MSHUTDOWN_FUNCTION(zend_test)
{
+ if (type != MODULE_TEMPORARY) {
+ UNREGISTER_INI_ENTRIES();
+ }
+
return SUCCESS;
}
+static void observer_show_opcode(zend_execute_data *execute_data)
+{
+ if (!ZT_G(observer_show_opcode)) {
+ return;
+ }
+ php_printf("%*s<!-- opcode: '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", zend_get_opcode_name(EX(opline)->opcode));
+}
+
+static void observer_begin(zend_execute_data *execute_data)
+{
+ if (!ZT_G(observer_show_output)) {
+ return;
+ }
+
+ if (execute_data->func && execute_data->func->common.function_name) {
+ if (execute_data->func->common.scope) {
+ php_printf("%*s<%s::%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.scope->name), ZSTR_VAL(execute_data->func->common.function_name));
+ } else {
+ php_printf("%*s<%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name));
+ }
+ } else {
+ php_printf("%*s<file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
+ }
+ ZT_G(observer_nesting_depth)++;
+ observer_show_opcode(execute_data);
+}
+
+static void get_retval_info(zval *retval, smart_str *buf)
+{
+ if (!ZT_G(observer_show_return_type) && !ZT_G(observer_show_return_value)) {
+ return;
+ }
+
+ smart_str_appendc(buf, ':');
+ if (retval == NULL) {
+ smart_str_appendl(buf, "NULL", 4);
+ } else if (ZT_G(observer_show_return_value)) {
+ if (Z_TYPE_P(retval) == IS_OBJECT) {
+ smart_str_appendl(buf, "object(", 7);
+ smart_str_append(buf, Z_OBJCE_P(retval)->name);
+ smart_str_appendl(buf, ")#", 2);
+ smart_str_append_long(buf, Z_OBJ_HANDLE_P(retval));
+ } else {
+ php_var_export_ex(retval, 2 * ZT_G(observer_nesting_depth) + 3, buf);
+ }
+ } else if (ZT_G(observer_show_return_type)) {
+ smart_str_appends(buf, zend_zval_type_name(retval));
+ }
+ smart_str_0(buf);
+}
+
+static void observer_end(zend_execute_data *execute_data, zval *retval)
+{
+ if (!ZT_G(observer_show_output)) {
+ return;
+ }
+
+ if (EG(exception)) {
+ php_printf("%*s<!-- Exception: %s -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(EG(exception)->ce->name));
+ }
+ observer_show_opcode(execute_data);
+ ZT_G(observer_nesting_depth)--;
+ if (execute_data->func && execute_data->func->common.function_name) {
+ smart_str retval_info = {0};
+ get_retval_info(retval, &retval_info);
+ if (execute_data->func->common.scope) {
+ php_printf("%*s</%s::%s%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.scope->name), ZSTR_VAL(execute_data->func->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : "");
+ } else {
+ php_printf("%*s</%s%s>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->common.function_name), retval_info.s ? ZSTR_VAL(retval_info.s) : "");
+ }
+ smart_str_free(&retval_info);
+ } else {
+ php_printf("%*s</file '%s'>\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(execute_data->func->op_array.filename));
+ }
+}
+
+static void observer_show_init(zend_function *fbc)
+{
+ if (fbc->common.function_name) {
+ if (fbc->common.scope) {
+ php_printf("%*s<!-- init %s::%s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
+ } else {
+ php_printf("%*s<!-- init %s() -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->common.function_name));
+ }
+ } else {
+ php_printf("%*s<!-- init '%s' -->\n", 2 * ZT_G(observer_nesting_depth), "", ZSTR_VAL(fbc->op_array.filename));
+ }
+}
+
+static void observer_show_init_backtrace(zend_execute_data *execute_data)
+{
+ zend_execute_data *ex = execute_data;
+ php_printf("%*s<!--\n", 2 * ZT_G(observer_nesting_depth), "");
+ do {
+ zend_function *fbc = ex->func;
+ int indent = 2 * ZT_G(observer_nesting_depth) + 4;
+ if (fbc->common.function_name) {
+ if (fbc->common.scope) {
+ php_printf("%*s%s::%s()\n", indent, "", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name));
+ } else {
+ php_printf("%*s%s()\n", indent, "", ZSTR_VAL(fbc->common.function_name));
+ }
+ } else {
+ php_printf("%*s{main} %s\n", indent, "", ZSTR_VAL(fbc->op_array.filename));
+ }
+ } while ((ex = ex->prev_execute_data) != NULL);
+ php_printf("%*s-->\n", 2 * ZT_G(observer_nesting_depth), "");
+}
+
+static zend_observer_fcall_handlers observer_fcall_init(zend_execute_data *execute_data)
+{
+ zend_function *fbc = execute_data->func;
+ if (ZT_G(observer_show_output)) {
+ observer_show_init(fbc);
+ if (ZT_G(observer_show_init_backtrace)) {
+ observer_show_init_backtrace(execute_data);
+ }
+ observer_show_opcode(execute_data);
+ }
+
+ if (ZT_G(observer_observe_all)) {
+ return (zend_observer_fcall_handlers){observer_begin, observer_end};
+ } else if (ZT_G(observer_observe_includes) && !fbc->common.function_name) {
+ return (zend_observer_fcall_handlers){observer_begin, observer_end};
+ } else if (ZT_G(observer_observe_functions) && fbc->common.function_name) {
+ return (zend_observer_fcall_handlers){observer_begin, observer_end};
+ }
+ return (zend_observer_fcall_handlers){NULL, NULL};
+}
+
PHP_RINIT_FUNCTION(zend_test)
{
-#if defined(COMPILE_DL_ZEND_TEST) && defined(ZTS)
- ZEND_TSRMLS_CACHE_UPDATE();
-#endif
return SUCCESS;
}
@@ -282,35 +602,38 @@ PHP_RSHUTDOWN_FUNCTION(zend_test)
return SUCCESS;
}
+static PHP_GINIT_FUNCTION(zend_test)
+{
+#if defined(COMPILE_DL_ZEND_TEST) && defined(ZTS)
+ ZEND_TSRMLS_CACHE_UPDATE();
+#endif
+ memset(zend_test_globals, 0, sizeof(*zend_test_globals));
+}
+
PHP_MINFO_FUNCTION(zend_test)
{
php_info_print_table_start();
php_info_print_table_header(2, "zend-test extension", "enabled");
php_info_print_table_end();
-}
-static const zend_function_entry zend_test_functions[] = {
- ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return)
- ZEND_FE(zend_test_nullable_array_return, arginfo_zend_test_nullable_array_return)
- ZEND_FE(zend_test_void_return, arginfo_zend_test_void_return)
- ZEND_FE(zend_create_unterminated_string, NULL)
- ZEND_FE(zend_terminate_string, arginfo_zend_terminate_string)
- ZEND_FE(zend_leak_bytes, NULL)
- ZEND_FE(zend_leak_variable, arginfo_zend_leak_variable)
- ZEND_FE_END
-};
+ DISPLAY_INI_ENTRIES();
+}
zend_module_entry zend_test_module_entry = {
STANDARD_MODULE_HEADER,
"zend-test",
- zend_test_functions,
+ ext_functions,
PHP_MINIT(zend_test),
PHP_MSHUTDOWN(zend_test),
PHP_RINIT(zend_test),
PHP_RSHUTDOWN(zend_test),
PHP_MINFO(zend_test),
PHP_ZEND_TEST_VERSION,
- STANDARD_MODULE_PROPERTIES
+ PHP_MODULE_GLOBALS(zend_test),
+ PHP_GINIT(zend_test),
+ NULL,
+ NULL,
+ STANDARD_MODULE_PROPERTIES_EX
};
#ifdef COMPILE_DL_ZEND_TEST
@@ -320,7 +643,7 @@ ZEND_TSRMLS_CACHE_DEFINE()
ZEND_GET_MODULE(zend_test)
#endif
-struct bug79096 bug79096(void)
+PHP_ZEND_TEST_API struct bug79096 bug79096(void)
{
struct bug79096 b;
@@ -329,7 +652,7 @@ struct bug79096 bug79096(void)
return b;
}
-void bug79532(off_t *array, size_t elems)
+PHP_ZEND_TEST_API void bug79532(off_t *array, size_t elems)
{
int i;
for (i = 0; i < elems; i++) {
@@ -342,3 +665,17 @@ void bug79177(void)
{
bug79177_cb();
}
+
+typedef struct bug80847_01 {
+ uint64_t b;
+ double c;
+} bug80847_01;
+typedef struct bug80847_02 {
+ bug80847_01 a;
+} bug80847_02;
+
+PHP_ZEND_TEST_API bug80847_02 ffi_bug80847(bug80847_02 s) {
+ s.a.b += 10;
+ s.a.c -= 10.0;
+ return s;
+}
diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php
new file mode 100644
index 0000000000..d9db786cee
--- /dev/null
+++ b/ext/zend_test/test.stub.php
@@ -0,0 +1,84 @@
+<?php
+
+/** @generate-function-entries static */
+
+namespace {
+
+interface _ZendTestInterface
+{
+}
+
+/** @alias _ZendTestClassAlias */
+class _ZendTestClass {
+ public static function is_object(): int {}
+
+ /** @deprecated */
+ public function __toString(): string {}
+
+ public function returnsStatic(): static {}
+}
+
+class _ZendTestChildClass extends _ZendTestClass
+{
+}
+
+trait _ZendTestTrait {
+ public function testMethod(): bool {}
+}
+
+final class ZendTestAttribute {
+
+}
+
+function zend_test_array_return(): array {}
+
+function zend_test_nullable_array_return(): ?array {}
+
+function zend_test_void_return(): void {}
+
+/** @deprecated */
+function zend_test_deprecated(mixed $arg = null): void {}
+
+function zend_create_unterminated_string(string $str): string {}
+
+function zend_terminate_string(string &$str): void {}
+
+function zend_leak_variable(mixed $variable): void {}
+
+function zend_leak_bytes(int $bytes = 3): void {}
+
+function zend_string_or_object(object|string $param): object|string {}
+
+function zend_string_or_object_or_null(object|string|null $param): object|string|null {}
+
+/** @param stdClass|string $param */
+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_iterable(iterable $arg1, ?iterable $arg2 = null): void {}
+
+}
+
+namespace ZendTestNS {
+
+class Foo {
+ public function method(): void {}
+}
+
+}
+
+namespace ZendTestNS2 {
+
+class Foo {
+ public function method(): void {}
+}
+
+}
+
+namespace ZendTestNS2\ZendSubNS {
+
+function namespaced_func(): bool {}
+
+}
diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h
new file mode 100644
index 0000000000..c58d53b457
--- /dev/null
+++ b/ext/zend_test/test_arginfo.h
@@ -0,0 +1,152 @@
+/* This is a generated file, edit the .stub.php file instead.
+ * Stub hash: 3240b7fa3461b40a211371250c4975802f44185b */
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_nullable_array_return, 0, 0, IS_ARRAY, 1)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_void_return, 0, 0, IS_VOID, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_deprecated, 0, 0, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg, IS_MIXED, 0, "null")
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_create_unterminated_string, 0, 1, IS_STRING, 0)
+ ZEND_ARG_TYPE_INFO(0, str, IS_STRING, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_terminate_string, 0, 1, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO(1, str, IS_STRING, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_leak_variable, 0, 1, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO(0, variable, IS_MIXED, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_leak_bytes, 0, 0, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, bytes, IS_LONG, 0, "3")
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_zend_string_or_object, 0, 1, MAY_BE_OBJECT|MAY_BE_STRING)
+ ZEND_ARG_TYPE_MASK(0, param, MAY_BE_OBJECT|MAY_BE_STRING, NULL)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_zend_string_or_object_or_null, 0, 1, MAY_BE_OBJECT|MAY_BE_STRING|MAY_BE_NULL)
+ ZEND_ARG_TYPE_MASK(0, param, MAY_BE_OBJECT|MAY_BE_STRING|MAY_BE_NULL, NULL)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_zend_string_or_stdclass, 0, 1, stdClass, MAY_BE_STRING)
+ ZEND_ARG_INFO(0, param)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_zend_string_or_stdclass_or_null, 0, 1, stdClass, MAY_BE_STRING|MAY_BE_NULL)
+ ZEND_ARG_INFO(0, param)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_iterable, 0, 1, IS_VOID, 0)
+ ZEND_ARG_TYPE_INFO(0, arg1, IS_ITERABLE, 0)
+ ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, arg2, IS_ITERABLE, 1, "null")
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_ZendSubNS_namespaced_func, 0, 0, _IS_BOOL, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass_is_object, 0, 0, IS_LONG, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass___toString, 0, 0, IS_STRING, 0)
+ZEND_END_ARG_INFO()
+
+ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass_returnsStatic, 0, 0, IS_STATIC, 0)
+ZEND_END_ARG_INFO()
+
+#define arginfo_class__ZendTestTrait_testMethod arginfo_ZendTestNS2_ZendSubNS_namespaced_func
+
+#define arginfo_class_ZendTestNS_Foo_method arginfo_zend_test_void_return
+
+#define arginfo_class_ZendTestNS2_Foo_method arginfo_zend_test_void_return
+
+
+static ZEND_FUNCTION(zend_test_array_return);
+static ZEND_FUNCTION(zend_test_nullable_array_return);
+static ZEND_FUNCTION(zend_test_void_return);
+static ZEND_FUNCTION(zend_test_deprecated);
+static ZEND_FUNCTION(zend_create_unterminated_string);
+static ZEND_FUNCTION(zend_terminate_string);
+static ZEND_FUNCTION(zend_leak_variable);
+static ZEND_FUNCTION(zend_leak_bytes);
+static ZEND_FUNCTION(zend_string_or_object);
+static ZEND_FUNCTION(zend_string_or_object_or_null);
+static ZEND_FUNCTION(zend_string_or_stdclass);
+static ZEND_FUNCTION(zend_string_or_stdclass_or_null);
+static ZEND_FUNCTION(zend_iterable);
+static ZEND_FUNCTION(namespaced_func);
+static ZEND_METHOD(_ZendTestClass, is_object);
+static ZEND_METHOD(_ZendTestClass, __toString);
+static ZEND_METHOD(_ZendTestClass, returnsStatic);
+static ZEND_METHOD(_ZendTestTrait, testMethod);
+static ZEND_METHOD(ZendTestNS_Foo, method);
+static ZEND_METHOD(ZendTestNS2_Foo, method);
+
+
+static const zend_function_entry ext_functions[] = {
+ ZEND_FE(zend_test_array_return, arginfo_zend_test_array_return)
+ ZEND_FE(zend_test_nullable_array_return, arginfo_zend_test_nullable_array_return)
+ ZEND_FE(zend_test_void_return, arginfo_zend_test_void_return)
+ ZEND_DEP_FE(zend_test_deprecated, arginfo_zend_test_deprecated)
+ ZEND_FE(zend_create_unterminated_string, arginfo_zend_create_unterminated_string)
+ ZEND_FE(zend_terminate_string, arginfo_zend_terminate_string)
+ ZEND_FE(zend_leak_variable, arginfo_zend_leak_variable)
+ ZEND_FE(zend_leak_bytes, arginfo_zend_leak_bytes)
+ ZEND_FE(zend_string_or_object, arginfo_zend_string_or_object)
+ 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_iterable, arginfo_zend_iterable)
+ ZEND_NS_FE("ZendTestNS2\\ZendSubNS", namespaced_func, arginfo_ZendTestNS2_ZendSubNS_namespaced_func)
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class__ZendTestInterface_methods[] = {
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class__ZendTestClass_methods[] = {
+ ZEND_ME(_ZendTestClass, is_object, arginfo_class__ZendTestClass_is_object, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
+ ZEND_ME(_ZendTestClass, __toString, arginfo_class__ZendTestClass___toString, ZEND_ACC_PUBLIC|ZEND_ACC_DEPRECATED)
+ ZEND_ME(_ZendTestClass, returnsStatic, arginfo_class__ZendTestClass_returnsStatic, ZEND_ACC_PUBLIC)
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class__ZendTestChildClass_methods[] = {
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class__ZendTestTrait_methods[] = {
+ ZEND_ME(_ZendTestTrait, testMethod, arginfo_class__ZendTestTrait_testMethod, ZEND_ACC_PUBLIC)
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class_ZendTestAttribute_methods[] = {
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class_ZendTestNS_Foo_methods[] = {
+ ZEND_ME(ZendTestNS_Foo, method, arginfo_class_ZendTestNS_Foo_method, ZEND_ACC_PUBLIC)
+ ZEND_FE_END
+};
+
+
+static const zend_function_entry class_ZendTestNS2_Foo_methods[] = {
+ ZEND_ME(ZendTestNS2_Foo, method, arginfo_class_ZendTestNS2_Foo_method, ZEND_ACC_PUBLIC)
+ ZEND_FE_END
+};
diff --git a/ext/zend_test/tests/observer.inc b/ext/zend_test/tests/observer.inc
new file mode 100644
index 0000000000..611e9fa0e6
--- /dev/null
+++ b/ext/zend_test/tests/observer.inc
@@ -0,0 +1,8 @@
+<?php
+
+function foo_observer_test()
+{
+ echo __FUNCTION__ . PHP_EOL;
+}
+
+foo_observer_test();
diff --git a/ext/zend_test/tests/observer_backtrace_01.phpt b/ext/zend_test/tests/observer_backtrace_01.phpt
new file mode 100644
index 0000000000..ece481cbba
--- /dev/null
+++ b/ext/zend_test/tests/observer_backtrace_01.phpt
@@ -0,0 +1,106 @@
+--TEST--
+Observer: Show backtrace on init
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_init_backtrace=1
+--FILE--
+<?php
+class TestClass
+{
+ private function bar($number)
+ {
+ return $number + 2;
+ }
+
+ public function foo()
+ {
+ return array_map(function ($value) {
+ return $this->bar($value);
+ }, [40, 1335]);
+ }
+}
+
+function gen()
+{
+ $test = new TestClass();
+ yield $test->foo();
+}
+
+function foo()
+{
+ return gen()->current();
+}
+
+var_dump(foo());
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_backtrace_%d.php' -->
+<!--
+ {main} %s%eobserver_backtrace_%d.php
+-->
+<file '%s%eobserver_backtrace_%d.php'>
+ <!-- init foo() -->
+ <!--
+ foo()
+ {main} %s%eobserver_backtrace_%d.php
+ -->
+ <foo>
+ <!-- init gen() -->
+ <!--
+ gen()
+ Generator::current()
+ foo()
+ {main} %s%eobserver_backtrace_%d.php
+ -->
+ <gen>
+ <!-- init TestClass::foo() -->
+ <!--
+ TestClass::foo()
+ gen()
+ Generator::current()
+ foo()
+ {main} %s%eobserver_backtrace_%d.php
+ -->
+ <TestClass::foo>
+ <!-- init TestClass::{closure}() -->
+ <!--
+ TestClass::{closure}()
+ array_map()
+ TestClass::foo()
+ gen()
+ Generator::current()
+ foo()
+ {main} %s%eobserver_backtrace_%d.php
+ -->
+ <TestClass::{closure}>
+ <!-- init TestClass::bar() -->
+ <!--
+ TestClass::bar()
+ TestClass::{closure}()
+ array_map()
+ TestClass::foo()
+ gen()
+ Generator::current()
+ foo()
+ {main} %s%eobserver_backtrace_%d.php
+ -->
+ <TestClass::bar>
+ </TestClass::bar>
+ </TestClass::{closure}>
+ <TestClass::{closure}>
+ <TestClass::bar>
+ </TestClass::bar>
+ </TestClass::{closure}>
+ </TestClass::foo>
+ </gen>
+ </foo>
+array(2) {
+ [0]=>
+ int(42)
+ [1]=>
+ int(1337)
+}
+</file '%s%eobserver_backtrace_%d.php'>
diff --git a/ext/zend_test/tests/observer_basic_01.phpt b/ext/zend_test/tests/observer_basic_01.phpt
new file mode 100644
index 0000000000..064ed99a29
--- /dev/null
+++ b/ext/zend_test/tests/observer_basic_01.phpt
@@ -0,0 +1,55 @@
+--TEST--
+Observer: Basic observability of userland functions
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+function bar()
+{
+ echo 'Bar' . PHP_EOL;
+ var_dump(array_sum([1,2,3]));
+}
+
+function foo()
+{
+ echo 'Foo' . PHP_EOL;
+ bar();
+}
+
+foo();
+foo();
+foo();
+
+echo 'DONE' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_basic_01.php' -->
+<file '%s%eobserver_basic_01.php'>
+ <!-- init foo() -->
+ <foo>
+Foo
+ <!-- init bar() -->
+ <bar>
+Bar
+int(6)
+ </bar>
+ </foo>
+ <foo>
+Foo
+ <bar>
+Bar
+int(6)
+ </bar>
+ </foo>
+ <foo>
+Foo
+ <bar>
+Bar
+int(6)
+ </bar>
+ </foo>
+DONE
+</file '%s%eobserver_basic_01.php'>
diff --git a/ext/zend_test/tests/observer_basic_02.phpt b/ext/zend_test/tests/observer_basic_02.phpt
new file mode 100644
index 0000000000..2b4d632d69
--- /dev/null
+++ b/ext/zend_test/tests/observer_basic_02.phpt
@@ -0,0 +1,59 @@
+--TEST--
+Observer: Basic observability of userland methods
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+class TestClass
+{
+ private function bar()
+ {
+ echo 'Bar' . PHP_EOL;
+ var_dump(array_sum([1,2,3]));
+ }
+
+ public function foo()
+ {
+ echo 'Foo' . PHP_EOL;
+ $this->bar();
+ }
+}
+
+$test = new TestClass();
+$test->foo();
+$test->foo();
+$test->foo();
+
+echo 'DONE' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_basic_02.php' -->
+<file '%s%eobserver_basic_02.php'>
+ <!-- init TestClass::foo() -->
+ <TestClass::foo>
+Foo
+ <!-- init TestClass::bar() -->
+ <TestClass::bar>
+Bar
+int(6)
+ </TestClass::bar>
+ </TestClass::foo>
+ <TestClass::foo>
+Foo
+ <TestClass::bar>
+Bar
+int(6)
+ </TestClass::bar>
+ </TestClass::foo>
+ <TestClass::foo>
+Foo
+ <TestClass::bar>
+Bar
+int(6)
+ </TestClass::bar>
+ </TestClass::foo>
+DONE
+</file '%s%eobserver_basic_02.php'>
diff --git a/ext/zend_test/tests/observer_basic_03.phpt b/ext/zend_test/tests/observer_basic_03.phpt
new file mode 100644
index 0000000000..8675a0fdc0
--- /dev/null
+++ b/ext/zend_test/tests/observer_basic_03.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Observer: Basic observability of includes
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+function foo()
+{
+ echo 'Foo' . PHP_EOL;
+}
+
+foo();
+include __DIR__ . '/observer.inc';
+foo();
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_basic_03.php' -->
+<file '%s%eobserver_basic_03.php'>
+ <!-- init foo() -->
+ <foo>
+Foo
+ </foo>
+ <!-- init '%s%eobserver.inc' -->
+ <file '%s%eobserver.inc'>
+ <!-- init foo_observer_test() -->
+ <foo_observer_test>
+foo_observer_test
+ </foo_observer_test>
+ </file '%s%eobserver.inc'>
+ <foo>
+Foo
+ </foo>
+</file '%s%eobserver_basic_03.php'>
diff --git a/ext/zend_test/tests/observer_basic_04.phpt b/ext/zend_test/tests/observer_basic_04.phpt
new file mode 100644
index 0000000000..6fc1fae1c0
--- /dev/null
+++ b/ext/zend_test/tests/observer_basic_04.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Observer: Basic observability of includes only (no functions)
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_includes=1
+--FILE--
+<?php
+function foo()
+{
+ echo 'Foo' . PHP_EOL;
+}
+
+foo();
+include __DIR__ . '/observer.inc';
+foo();
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_basic_04.php' -->
+<file '%s%eobserver_basic_04.php'>
+ <!-- init foo() -->
+Foo
+ <!-- init '%s%eobserver.inc' -->
+ <file '%s%eobserver.inc'>
+ <!-- init foo_observer_test() -->
+foo_observer_test
+ </file '%s%eobserver.inc'>
+Foo
+</file '%s%eobserver_basic_04.php'>
diff --git a/ext/zend_test/tests/observer_basic_05.phpt b/ext/zend_test/tests/observer_basic_05.phpt
new file mode 100644
index 0000000000..de14ebc1fd
--- /dev/null
+++ b/ext/zend_test/tests/observer_basic_05.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Observer: Basic observability of functions only (no includes)
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_functions=1
+--FILE--
+<?php
+function foo()
+{
+ echo 'Foo' . PHP_EOL;
+}
+
+foo();
+include __DIR__ . '/observer.inc';
+foo();
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_basic_05.php' -->
+<!-- init foo() -->
+<foo>
+Foo
+</foo>
+<!-- init '%s%eobserver.inc' -->
+<!-- init foo_observer_test() -->
+<foo_observer_test>
+foo_observer_test
+</foo_observer_test>
+<foo>
+Foo
+</foo>
diff --git a/ext/zend_test/tests/observer_call_user_func_01.phpt b/ext/zend_test/tests/observer_call_user_func_01.phpt
new file mode 100644
index 0000000000..802db43e33
--- /dev/null
+++ b/ext/zend_test/tests/observer_call_user_func_01.phpt
@@ -0,0 +1,40 @@
+--TEST--
+Observer: call_user_func() from root namespace
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+namespace Test {
+ final class MyClass
+ {
+ public static function myMethod()
+ {
+ echo 'MyClass::myMethod called' . PHP_EOL;
+ }
+ }
+
+ function my_function()
+ {
+ echo 'my_function called' . PHP_EOL;
+ }
+}
+namespace {
+ call_user_func('Test\\MyClass::myMethod');
+ call_user_func('Test\\my_function');
+}
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_call_user_func_%d.php' -->
+<file '%s%eobserver_call_user_func_%d.php'>
+ <!-- init Test\MyClass::myMethod() -->
+ <Test\MyClass::myMethod>
+MyClass::myMethod called
+ </Test\MyClass::myMethod>
+ <!-- init Test\my_function() -->
+ <Test\my_function>
+my_function called
+ </Test\my_function>
+</file '%s%eobserver_call_user_func_%d.php'>
diff --git a/ext/zend_test/tests/observer_call_user_func_02.phpt b/ext/zend_test/tests/observer_call_user_func_02.phpt
new file mode 100644
index 0000000000..25de267c06
--- /dev/null
+++ b/ext/zend_test/tests/observer_call_user_func_02.phpt
@@ -0,0 +1,40 @@
+--TEST--
+Observer: call_user_func_array() from root namespace
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+namespace Test {
+ final class MyClass
+ {
+ public static function myMethod(string $msg)
+ {
+ echo 'MyClass::myMethod ' . $msg . PHP_EOL;
+ }
+ }
+
+ function my_function(string $msg)
+ {
+ echo 'my_function ' . $msg . PHP_EOL;
+ }
+}
+namespace {
+ call_user_func_array('Test\\MyClass::myMethod', ['called']);
+ call_user_func_array('Test\\my_function', ['called']);
+}
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_call_user_func_%d.php' -->
+<file '%s%eobserver_call_user_func_%d.php'>
+ <!-- init Test\MyClass::myMethod() -->
+ <Test\MyClass::myMethod>
+MyClass::myMethod called
+ </Test\MyClass::myMethod>
+ <!-- init Test\my_function() -->
+ <Test\my_function>
+my_function called
+ </Test\my_function>
+</file '%s%eobserver_call_user_func_%d.php'>
diff --git a/ext/zend_test/tests/observer_call_user_func_03.phpt b/ext/zend_test/tests/observer_call_user_func_03.phpt
new file mode 100644
index 0000000000..7c041cbc70
--- /dev/null
+++ b/ext/zend_test/tests/observer_call_user_func_03.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Observer: call_user_func() from namespace
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+namespace Test {
+ final class MyClass
+ {
+ public static function myMethod()
+ {
+ echo 'MyClass::myMethod called' . PHP_EOL;
+ }
+ }
+
+ function my_function()
+ {
+ echo 'my_function called' . PHP_EOL;
+ }
+
+ call_user_func('Test\\MyClass::myMethod');
+ call_user_func('Test\\my_function');
+}
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_call_user_func_%d.php' -->
+<file '%s%eobserver_call_user_func_%d.php'>
+ <!-- init Test\MyClass::myMethod() -->
+ <Test\MyClass::myMethod>
+MyClass::myMethod called
+ </Test\MyClass::myMethod>
+ <!-- init Test\my_function() -->
+ <Test\my_function>
+my_function called
+ </Test\my_function>
+</file '%s%eobserver_call_user_func_%d.php'>
diff --git a/ext/zend_test/tests/observer_call_user_func_04.phpt b/ext/zend_test/tests/observer_call_user_func_04.phpt
new file mode 100644
index 0000000000..ebcf06082b
--- /dev/null
+++ b/ext/zend_test/tests/observer_call_user_func_04.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Observer: call_user_func_array() from namespace
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+namespace Test {
+ final class MyClass
+ {
+ public static function myMethod(string $msg)
+ {
+ echo 'MyClass::myMethod ' . $msg . PHP_EOL;
+ }
+ }
+
+ function my_function(string $msg)
+ {
+ echo 'my_function ' . $msg . PHP_EOL;
+ }
+
+ call_user_func_array('Test\\MyClass::myMethod', ['called']);
+ call_user_func_array('Test\\my_function', ['called']);
+}
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_call_user_func_%d.php' -->
+<file '%s%eobserver_call_user_func_%d.php'>
+ <!-- init Test\MyClass::myMethod() -->
+ <Test\MyClass::myMethod>
+MyClass::myMethod called
+ </Test\MyClass::myMethod>
+ <!-- init Test\my_function() -->
+ <Test\my_function>
+my_function called
+ </Test\my_function>
+</file '%s%eobserver_call_user_func_%d.php'>
diff --git a/ext/zend_test/tests/observer_closure_01.phpt b/ext/zend_test/tests/observer_closure_01.phpt
new file mode 100644
index 0000000000..85312b32ef
--- /dev/null
+++ b/ext/zend_test/tests/observer_closure_01.phpt
@@ -0,0 +1,49 @@
+--TEST--
+Observer: Basic observability of closures
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+$bar = function() {
+ var_dump(array_sum([40,2]));
+};
+
+$foo = function($bar) {
+ echo 'Answer' . PHP_EOL;
+ $bar();
+};
+
+$foo($bar);
+$foo($bar);
+$foo($bar);
+
+echo 'DONE' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_closure_%d.php' -->
+<file '%s%eobserver_closure_%d.php'>
+ <!-- init {closure}() -->
+ <{closure}>
+Answer
+ <!-- init {closure}() -->
+ <{closure}>
+int(42)
+ </{closure}>
+ </{closure}>
+ <{closure}>
+Answer
+ <{closure}>
+int(42)
+ </{closure}>
+ </{closure}>
+ <{closure}>
+Answer
+ <{closure}>
+int(42)
+ </{closure}>
+ </{closure}>
+DONE
+</file '%s%eobserver_closure_%d.php'>
diff --git a/ext/zend_test/tests/observer_closure_02.phpt b/ext/zend_test/tests/observer_closure_02.phpt
new file mode 100644
index 0000000000..9d6cc900e3
--- /dev/null
+++ b/ext/zend_test/tests/observer_closure_02.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Observer: Observability of fake closures
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend_test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+class Foo
+{
+ public function bar()
+ {
+ echo 'Called as fake closure.' . PHP_EOL;
+ }
+}
+
+$callable = [new Foo(), 'bar'];
+$closure = \Closure::fromCallable($callable);
+$closure();
+
+echo 'DONE' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_closure_%d.php' -->
+<file '%s%eobserver_closure_%d.php'>
+ <!-- init Foo::bar() -->
+ <Foo::bar>
+Called as fake closure.
+ </Foo::bar>
+DONE
+</file '%s%eobserver_closure_%d.php'>
diff --git a/ext/zend_test/tests/observer_error_01.phpt b/ext/zend_test/tests/observer_error_01.phpt
new file mode 100644
index 0000000000..e80a9ec3c7
--- /dev/null
+++ b/ext/zend_test/tests/observer_error_01.phpt
@@ -0,0 +1,29 @@
+--TEST--
+Observer: End handlers fire after a fatal error
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+memory_limit=1M
+--FILE--
+<?php
+function foo()
+{
+ str_repeat('.', 1024 * 1024 * 2); // 2MB
+}
+
+foo();
+
+echo 'You should not see this.';
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_error_%d.php' -->
+<file '%s%eobserver_error_%d.php'>
+ <!-- init foo() -->
+ <foo>
+
+Fatal error: Allowed memory size of 2097152 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d
+ </foo:NULL>
+</file '%s%eobserver_error_%d.php'>
diff --git a/ext/zend_test/tests/observer_error_02.phpt b/ext/zend_test/tests/observer_error_02.phpt
new file mode 100644
index 0000000000..9dfa71d182
--- /dev/null
+++ b/ext/zend_test/tests/observer_error_02.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Observer: End handlers fire after a userland fatal error
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo()
+{
+ trigger_error('Foo error', E_USER_ERROR);
+}
+
+foo();
+
+echo 'You should not see this.';
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_error_%d.php' -->
+<file '%s%eobserver_error_%d.php'>
+ <!-- init foo() -->
+ <foo>
+
+Fatal error: Foo error in %s on line %d
+ </foo:NULL>
+</file '%s%eobserver_error_%d.php'>
diff --git a/ext/zend_test/tests/observer_error_03.phpt b/ext/zend_test/tests/observer_error_03.phpt
new file mode 100644
index 0000000000..606b827bb2
--- /dev/null
+++ b/ext/zend_test/tests/observer_error_03.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Observer: non-fatal errors do not fire end handlers prematurely
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo()
+{
+ return $this_does_not_exit; // E_WARNING
+}
+
+function main()
+{
+ foo();
+ echo 'After error.' . PHP_EOL;
+}
+
+main();
+
+echo 'Done.' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_error_%d.php' -->
+<file '%s%eobserver_error_%d.php'>
+ <!-- init main() -->
+ <main>
+ <!-- init foo() -->
+ <foo>
+
+Warning: Undefined variable $this_does_not_exit in %s on line %d
+ </foo:NULL>
+After error.
+ </main:NULL>
+Done.
+</file '%s%eobserver_error_%d.php'>
diff --git a/ext/zend_test/tests/observer_error_04.phpt b/ext/zend_test/tests/observer_error_04.phpt
new file mode 100644
index 0000000000..49c56c17ab
--- /dev/null
+++ b/ext/zend_test/tests/observer_error_04.phpt
@@ -0,0 +1,46 @@
+--TEST--
+Observer: fatal errors caught with zend_try will not fire end handlers prematurely
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+<?php if (!extension_loaded('soap')) die('skip: soap extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo()
+{
+ // ext/soap catches a zend_bailout and then throws an exception
+ $client = new SoapClient('foo');
+}
+
+function main()
+{
+ foo();
+}
+
+// try/catch is on main() to ensure ZEND_HANDLE_EXCEPTION does not fire the end handlers again
+try {
+ main();
+} catch (SoapFault $e) {
+ echo $e->getMessage() . PHP_EOL;
+}
+
+echo 'Done.' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_error_%d.php' -->
+<file '%s%eobserver_error_%d.php'>
+ <!-- init main() -->
+ <main>
+ <!-- init foo() -->
+ <foo>
+ <!-- Exception: SoapFault -->
+ </foo:NULL>
+ <!-- Exception: SoapFault -->
+ </main:NULL>
+SOAP-ERROR: Parsing WSDL: Couldn't load from 'foo' : failed to load external entity "foo"
+
+Done.
+</file '%s%eobserver_error_%d.php'>
diff --git a/ext/zend_test/tests/observer_error_05.phpt b/ext/zend_test/tests/observer_error_05.phpt
new file mode 100644
index 0000000000..df05cdd8ed
--- /dev/null
+++ b/ext/zend_test/tests/observer_error_05.phpt
@@ -0,0 +1,37 @@
+--TEST--
+Observer: End handlers fire after a userland fatal error
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--XFAIL--
+This is unsafe and fails on macos
+--FILE--
+<?php
+set_error_handler(function ($errno, $errstr, $errfile, $errline) {
+ trigger_error('Foo error', E_USER_ERROR);
+});
+
+function foo()
+{
+ return $x; // warning
+}
+
+foo();
+
+echo 'You should not see this.';
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_error_%d.php' -->
+<file '%s%eobserver_error_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ <!-- init {closure}() -->
+ <{closure}>
+
+Fatal error: Foo error in %s on line %d
+ </{closure}:NULL>
+ </foo:NULL>
+</file '%s%eobserver_error_%d.php'>
diff --git a/ext/zend_test/tests/observer_eval_01.phpt b/ext/zend_test/tests/observer_eval_01.phpt
new file mode 100644
index 0000000000..addce2dba5
--- /dev/null
+++ b/ext/zend_test/tests/observer_eval_01.phpt
@@ -0,0 +1,21 @@
+--TEST--
+Observer: Basic eval observability
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+echo eval("return 'Foo eval' . PHP_EOL;");
+echo 'DONE' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_eval_%d.php' -->
+<file '%s%eobserver_eval_%d.php'>
+ <!-- init '%s%eobserver_eval_%d.php(%d) : eval()'d code' -->
+ <file '%s%eobserver_eval_%d.php(%d) : eval()'d code'>
+ </file '%s%eobserver_eval_%d.php(%d) : eval()'d code'>
+Foo eval
+DONE
+</file '%s%eobserver_eval_%d.php'>
diff --git a/ext/zend_test/tests/observer_exception_01.phpt b/ext/zend_test/tests/observer_exception_01.phpt
new file mode 100644
index 0000000000..6ded60c970
--- /dev/null
+++ b/ext/zend_test/tests/observer_exception_01.phpt
@@ -0,0 +1,46 @@
+--TEST--
+Observer: Basic observability of userland functions with uncaught exceptions
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+function foo()
+{
+ static $callCount = 0;
+ echo 'Call #' . $callCount . PHP_EOL;
+ if (++$callCount == 3) {
+ throw new RuntimeException('Third time is a charm');
+ }
+}
+
+foo();
+foo();
+foo();
+
+echo 'You should not see this' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_exception_%d.php' -->
+<file '%s%eobserver_exception_%d.php'>
+ <!-- init foo() -->
+ <foo>
+Call #0
+ </foo>
+ <foo>
+Call #1
+ </foo>
+ <foo>
+Call #2
+ <!-- Exception: RuntimeException -->
+ </foo>
+ <!-- Exception: RuntimeException -->
+</file '%s%eobserver_exception_%d.php'>
+
+Fatal error: Uncaught RuntimeException: Third time is a charm in %s%eobserver_exception_%d.php:%d
+Stack trace:
+#0 %s%eobserver_exception_%d.php(%d): foo()
+#1 {main}
+ thrown in %s%eobserver_exception_%d.php on line %d
diff --git a/ext/zend_test/tests/observer_generator_01.phpt b/ext/zend_test/tests/observer_generator_01.phpt
new file mode 100644
index 0000000000..34314cbf36
--- /dev/null
+++ b/ext/zend_test/tests/observer_generator_01.phpt
@@ -0,0 +1,47 @@
+--TEST--
+Observer: Basic generator observability
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function getResults() {
+ for ($i = 10; $i < 13; $i++) {
+ yield $i;
+ }
+}
+
+function doSomething() {
+ $generator = getResults();
+ foreach ($generator as $value) {
+ echo $value . PHP_EOL;
+ }
+
+ return 'Done';
+}
+
+echo doSomething() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_generator_%d.php' -->
+<file '%s%eobserver_generator_%d.php'>
+ <!-- init doSomething() -->
+ <doSomething>
+ <!-- init getResults() -->
+ <getResults>
+ </getResults:10>
+10
+ <getResults>
+ </getResults:11>
+11
+ <getResults>
+ </getResults:12>
+12
+ <getResults>
+ </getResults:NULL>
+ </doSomething:'Done'>
+Done
+</file '%s%eobserver_generator_%d.php'>
diff --git a/ext/zend_test/tests/observer_generator_02.phpt b/ext/zend_test/tests/observer_generator_02.phpt
new file mode 100644
index 0000000000..9d29b9e8e5
--- /dev/null
+++ b/ext/zend_test/tests/observer_generator_02.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Observer: Generator with explicit return
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function getResults() {
+ for ($i = 10; $i < 13; $i++) {
+ yield $i;
+ }
+ return 1337;
+}
+
+function doSomething() {
+ $generator = getResults();
+ foreach ($generator as $value) {
+ echo $value . PHP_EOL;
+ }
+ echo $generator->getReturn() . PHP_EOL;
+
+ return 'Done';
+}
+
+echo doSomething() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_generator_%d.php' -->
+<file '%s%eobserver_generator_%d.php'>
+ <!-- init doSomething() -->
+ <doSomething>
+ <!-- init getResults() -->
+ <getResults>
+ </getResults:10>
+10
+ <getResults>
+ </getResults:11>
+11
+ <getResults>
+ </getResults:12>
+12
+ <getResults>
+ </getResults:1337>
+1337
+ </doSomething:'Done'>
+Done
+</file '%s%eobserver_generator_%d.php'>
diff --git a/ext/zend_test/tests/observer_generator_03.phpt b/ext/zend_test/tests/observer_generator_03.phpt
new file mode 100644
index 0000000000..cad6e3bb96
--- /dev/null
+++ b/ext/zend_test/tests/observer_generator_03.phpt
@@ -0,0 +1,68 @@
+--TEST--
+Observer: Generator with 'yield from'
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function barResults() {
+ for ($i = 10; $i < 13; $i++) {
+ yield $i;
+ }
+}
+
+function fooResults() {
+ yield 0;
+ yield 1;
+ yield from barResults();
+ yield 42;
+}
+
+function doSomething() {
+ $generator = fooResults();
+ foreach ($generator as $value) {
+ echo $value . PHP_EOL;
+ }
+
+ return 'Done';
+}
+
+echo doSomething() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_generator_%d.php' -->
+<file '%s%eobserver_generator_%d.php'>
+ <!-- init doSomething() -->
+ <doSomething>
+ <!-- init fooResults() -->
+ <fooResults>
+ </fooResults:0>
+0
+ <fooResults>
+ </fooResults:1>
+1
+ <fooResults>
+ </fooResults:1>
+ <!-- init barResults() -->
+ <barResults>
+ </barResults:10>
+10
+ <barResults>
+ </barResults:11>
+11
+ <barResults>
+ </barResults:12>
+12
+ <barResults>
+ </barResults:NULL>
+ <fooResults>
+ </fooResults:42>
+42
+ <fooResults>
+ </fooResults:NULL>
+ </doSomething:'Done'>
+Done
+</file '%s%eobserver_generator_%d.php'>
diff --git a/ext/zend_test/tests/observer_generator_04.phpt b/ext/zend_test/tests/observer_generator_04.phpt
new file mode 100644
index 0000000000..dd941dd78f
--- /dev/null
+++ b/ext/zend_test/tests/observer_generator_04.phpt
@@ -0,0 +1,64 @@
+--TEST--
+Observer: Generator with manual traversal
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function fooResults() {
+ echo 'Starting generator' . PHP_EOL;
+ $i = 0;
+ while (true) {
+ if (yield $i++) break;
+ }
+}
+
+function doSomething() {
+ $generator = fooResults();
+
+ while($generator->current() !== NULL) {
+ echo $generator->current() . PHP_EOL;
+ if ($generator->current() === 5) {
+ $generator->send('Boop');
+ }
+ $generator->next();
+ }
+
+ return 'Done';
+}
+
+echo doSomething() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_generator_%d.php' -->
+<file '%s%eobserver_generator_%d.php'>
+ <!-- init doSomething() -->
+ <doSomething>
+ <!-- init fooResults() -->
+ <fooResults>
+Starting generator
+ </fooResults:0>
+0
+ <fooResults>
+ </fooResults:1>
+1
+ <fooResults>
+ </fooResults:2>
+2
+ <fooResults>
+ </fooResults:3>
+3
+ <fooResults>
+ </fooResults:4>
+4
+ <fooResults>
+ </fooResults:5>
+5
+ <fooResults>
+ </fooResults:NULL>
+ </doSomething:'Done'>
+Done
+</file '%s%eobserver_generator_%d.php'>
diff --git a/ext/zend_test/tests/observer_generator_05.phpt b/ext/zend_test/tests/observer_generator_05.phpt
new file mode 100644
index 0000000000..8809511361
--- /dev/null
+++ b/ext/zend_test/tests/observer_generator_05.phpt
@@ -0,0 +1,53 @@
+--TEST--
+Observer: Generator with uncaught exception
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function fooResults() {
+ yield 0;
+ yield 1;
+ throw new RuntimeException('Oops!');
+}
+
+function doSomething() {
+ $generator = fooResults();
+ foreach ($generator as $value) {
+ echo $value . PHP_EOL;
+ }
+
+ return 'You should not see this';
+}
+
+echo doSomething() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_generator_%d.php' -->
+<file '%s%eobserver_generator_%d.php'>
+ <!-- init doSomething() -->
+ <doSomething>
+ <!-- init fooResults() -->
+ <fooResults>
+ </fooResults:0>
+0
+ <fooResults>
+ </fooResults:1>
+1
+ <fooResults>
+ <!-- Exception: RuntimeException -->
+ </fooResults:NULL>
+ <!-- Exception: RuntimeException -->
+ </doSomething:NULL>
+ <!-- Exception: RuntimeException -->
+</file '%s%eobserver_generator_%d.php'>
+
+Fatal error: Uncaught RuntimeException: Oops! in %s%eobserver_generator_%d.php:%d
+Stack trace:
+#0 %s%eobserver_generator_%d.php(%d): fooResults()
+#1 %s%eobserver_generator_%d.php(%d): doSomething()
+#2 {main}
+ thrown in %s%eobserver_generator_%d.php on line %d
diff --git a/ext/zend_test/tests/observer_magic_01.phpt b/ext/zend_test/tests/observer_magic_01.phpt
new file mode 100644
index 0000000000..bae01e3b17
--- /dev/null
+++ b/ext/zend_test/tests/observer_magic_01.phpt
@@ -0,0 +1,45 @@
+--TEST--
+Observer: Basic magic method observability
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+class MagicTest
+{
+ public function __call($name, $args)
+ {
+ echo '__call()' . PHP_EOL;
+ $this->foo($name);
+ }
+
+ public function foo($name)
+ {
+ echo $name . PHP_EOL;
+ }
+}
+
+$test = new MagicTest();
+$test->foo('test');
+$test->bar();
+
+echo 'DONE' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_magic_01.php' -->
+<file '%s%eobserver_magic_01.php'>
+ <!-- init MagicTest::foo() -->
+ <MagicTest::foo>
+test
+ </MagicTest::foo>
+ <!-- init MagicTest::__call() -->
+ <MagicTest::__call>
+__call()
+ <MagicTest::foo>
+bar
+ </MagicTest::foo>
+ </MagicTest::__call>
+DONE
+</file '%s%eobserver_magic_01.php'>
diff --git a/ext/zend_test/tests/observer_opline_01.phpt b/ext/zend_test/tests/observer_opline_01.phpt
new file mode 100644
index 0000000000..924c22b3a0
--- /dev/null
+++ b/ext/zend_test/tests/observer_opline_01.phpt
@@ -0,0 +1,53 @@
+--TEST--
+Observer: Ensure opline exists on the execute_data
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_opcode=1
+--FILE--
+<?php
+function foo()
+{
+ echo 'Foo' . PHP_EOL;
+}
+
+foo();
+include __DIR__ . '/observer.inc';
+echo array_sum([1,2,3]) . PHP_EOL;
+foo();
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_opline_%d.php' -->
+<!-- opcode: 'ZEND_INIT_FCALL' -->
+<file '%s%eobserver_opline_%d.php'>
+ <!-- opcode: 'ZEND_INIT_FCALL' -->
+ <!-- init foo() -->
+ <!-- opcode: 'ZEND_ECHO' -->
+ <foo>
+ <!-- opcode: 'ZEND_ECHO' -->
+Foo
+ <!-- opcode: 'ZEND_RETURN' -->
+ </foo>
+ <!-- init '%s%eobserver.inc' -->
+ <!-- opcode: 'ZEND_INIT_FCALL' -->
+ <file '%s%eobserver.inc'>
+ <!-- opcode: 'ZEND_INIT_FCALL' -->
+ <!-- init foo_observer_test() -->
+ <!-- opcode: 'ZEND_ECHO' -->
+ <foo_observer_test>
+ <!-- opcode: 'ZEND_ECHO' -->
+foo_observer_test
+ <!-- opcode: 'ZEND_RETURN' -->
+ </foo_observer_test>
+ <!-- opcode: 'ZEND_RETURN' -->
+ </file '%s%eobserver.inc'>
+6
+ <foo>
+ <!-- opcode: 'ZEND_ECHO' -->
+Foo
+ <!-- opcode: 'ZEND_RETURN' -->
+ </foo>
+ <!-- opcode: 'ZEND_RETURN' -->
+</file '%s%eobserver_opline_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_01.phpt b/ext/zend_test/tests/observer_retval_01.phpt
new file mode 100644
index 0000000000..1359a00d40
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_01.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Observer: Retvals are observable that are: IS_CONST
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+opcache.optimization_level=0
+--FILE--
+<?php
+function foo() {
+ return 'I should be observable'; // IS_CONST
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'I should be observable'>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_02.phpt b/ext/zend_test/tests/observer_retval_02.phpt
new file mode 100644
index 0000000000..b48d8d6b7e
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_02.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Observer: Unused retvals from generators are still observable
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo() {
+ yield 'I should be observable';
+ yield 'Me too!';
+}
+
+$gen = foo();
+$gen->current();
+$gen->next();
+$gen->current();
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'Me too!'>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_03.phpt b/ext/zend_test/tests/observer_retval_03.phpt
new file mode 100644
index 0000000000..227fe5670a
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_03.phpt
@@ -0,0 +1,32 @@
+--TEST--
+Observer: Retvals are observable that are: refcounted, IS_CV
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+class MyRetval {}
+
+function foo() {
+ $retval = new MyRetval(); // Refcounted
+ return $retval; // IS_CV
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:object(MyRetval)#%d>
+ <foo>
+ </foo:object(MyRetval)#%d>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_04.phpt b/ext/zend_test/tests/observer_retval_04.phpt
new file mode 100644
index 0000000000..f6aea0d1e2
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_04.phpt
@@ -0,0 +1,52 @@
+--TEST--
+Observer: Retvals are observable that are: refcounted, IS_VAR
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+class MyRetval {}
+
+function getObj() {
+ return new MyRetval(); // Refcounted
+}
+
+function foo() {
+ return getObj(); // IS_VAR
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+function bar($what) {
+ return 'This gets ' . $what . ' in the return handler when unused'; // Refcounted + IS_VAR
+}
+
+$res = bar('freed'); // Retval used
+bar('freed'); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ <!-- init getObj() -->
+ <getObj>
+ </getObj:object(MyRetval)#%d>
+ </foo:object(MyRetval)#%d>
+ <foo>
+ <getObj>
+ </getObj:object(MyRetval)#%d>
+ </foo:object(MyRetval)#%d>
+ <!-- init bar() -->
+ <bar>
+ </bar:'This gets freed in the return handler when unused'>
+ <bar>
+ </bar:'This gets freed in the return handler when unused'>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_05.phpt b/ext/zend_test/tests/observer_retval_05.phpt
new file mode 100644
index 0000000000..e987a06d7a
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_05.phpt
@@ -0,0 +1,33 @@
+--TEST--
+Observer: Retvals are observable that are: IS_CV, IS_UNDEF
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo() {
+ return $i_do_not_exist; // IS_CV && IS_UNDEF
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+
+Warning: Undefined variable $i_do_not_exist in %s on line %d
+ </foo:NULL>
+ <foo>
+
+Warning: Undefined variable $i_do_not_exist in %s on line %d
+ </foo:NULL>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_06.phpt b/ext/zend_test/tests/observer_retval_06.phpt
new file mode 100644
index 0000000000..b3a64b3ee8
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_06.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Observer: Retvals are observable that are: IS_CV
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo() {
+ $retval = 'I should be observable';
+ return $retval; // IS_CV
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'I should be observable'>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_07.phpt b/ext/zend_test/tests/observer_retval_07.phpt
new file mode 100644
index 0000000000..678027a01a
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_07.phpt
@@ -0,0 +1,39 @@
+--TEST--
+Observer: Retvals are observable that are: IS_REFERENCE, IS_VAR
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function &getMessage() {
+ $retval = 'I should be observable';
+ return $retval;
+}
+
+function foo() {
+ return getMessage(); // IS_REFERENCE + IS_VAR
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_%d.php' -->
+<file '%s%eobserver_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ <!-- init getMessage() -->
+ <getMessage>
+ </getMessage:'I should be observable'>
+ </foo:'I should be observable'>
+ <foo>
+ <getMessage>
+ </getMessage:'I should be observable'>
+ </foo:'I should be observable'>
+Done
+</file '%s%eobserver_retval_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_by_ref_01.phpt b/ext/zend_test/tests/observer_retval_by_ref_01.phpt
new file mode 100644
index 0000000000..2e7d910aac
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_by_ref_01.phpt
@@ -0,0 +1,30 @@
+--TEST--
+Observer: Retvals by reference are observable that are: IS_CV
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function &foo() {
+ $retval = 'I should be observable';
+ return $retval; // IS_CV
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_by_ref_%d.php' -->
+<file '%s%eobserver_retval_by_ref_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'I should be observable'>
+Done
+</file '%s%eobserver_retval_by_ref_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_by_ref_02.phpt b/ext/zend_test/tests/observer_retval_by_ref_02.phpt
new file mode 100644
index 0000000000..7283a8d967
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_by_ref_02.phpt
@@ -0,0 +1,34 @@
+--TEST--
+Observer: Retvals by reference are observable that are: IS_TMP_VAR
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function &foo() {
+ $retval = 'I should be ';
+ return $retval . 'observable'; // IS_TMP_VAR
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_by_ref_%d.php' -->
+<file '%s%eobserver_retval_by_ref_%d.php'>
+ <!-- init foo() -->
+ <foo>
+
+Notice: Only variable references should be returned by reference in %s on line %d
+ </foo:'I should be observable'>
+ <foo>
+
+Notice: Only variable references should be returned by reference in %s on line %d
+ </foo:'I should be observable'>
+Done
+</file '%s%eobserver_retval_by_ref_%d.php'>
diff --git a/ext/zend_test/tests/observer_retval_by_ref_03.phpt b/ext/zend_test/tests/observer_retval_by_ref_03.phpt
new file mode 100644
index 0000000000..b75e589095
--- /dev/null
+++ b/ext/zend_test/tests/observer_retval_by_ref_03.phpt
@@ -0,0 +1,43 @@
+--TEST--
+Observer: Retvals by reference are observable that are: IS_VAR, ZEND_RETURNS_FUNCTION
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+opcache.optimization_level=0
+--FILE--
+<?php
+function getMessage() {
+ return 'I should be observable';
+}
+
+function &foo() {
+ return getMessage(); // IS_VAR + ZEND_RETURNS_FUNCTION
+}
+
+$res = foo(); // Retval used
+foo(); // Retval unused
+
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_retval_by_ref_%d.php' -->
+<file '%s%eobserver_retval_by_ref_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ <!-- init getMessage() -->
+ <getMessage>
+ </getMessage:'I should be observable'>
+
+Notice: Only variable references should be returned by reference in %s on line %d
+ </foo:'I should be observable'>
+ <foo>
+ <getMessage>
+ </getMessage:'I should be observable'>
+
+Notice: Only variable references should be returned by reference in %s on line %d
+ </foo:'I should be observable'>
+Done
+</file '%s%eobserver_retval_by_ref_%d.php'>
diff --git a/ext/zend_test/tests/observer_shutdown_01.phpt b/ext/zend_test/tests/observer_shutdown_01.phpt
new file mode 100644
index 0000000000..5fcc7aeb81
--- /dev/null
+++ b/ext/zend_test/tests/observer_shutdown_01.phpt
@@ -0,0 +1,44 @@
+--TEST--
+Observer: Function calls from a shutdown handler are observable
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+register_shutdown_function(function () {
+ echo 'Shutdown: ' . foo() . PHP_EOL;
+});
+
+function bar($arg) {
+ return $arg;
+}
+
+function foo() {
+ bar(41);
+ return bar(42);
+}
+
+echo 'Done: ' . bar(40) . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_shutdown_%d.php' -->
+<file '%s%eobserver_shutdown_%d.php'>
+ <!-- init bar() -->
+ <bar>
+ </bar:40>
+Done: 40
+</file '%s%eobserver_shutdown_%d.php'>
+<!-- init {closure}() -->
+<{closure}>
+ <!-- init foo() -->
+ <foo>
+ <bar>
+ </bar:41>
+ <bar>
+ </bar:42>
+ </foo:42>
+Shutdown: 42
+</{closure}:NULL>
diff --git a/ext/zend_test/tests/observer_shutdown_02.phpt b/ext/zend_test/tests/observer_shutdown_02.phpt
new file mode 100644
index 0000000000..410403df1a
--- /dev/null
+++ b/ext/zend_test/tests/observer_shutdown_02.phpt
@@ -0,0 +1,50 @@
+--TEST--
+Observer: Function calls from a __destruct during shutdown are observable
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+class MyClass
+{
+ public function __destruct()
+ {
+ echo 'Shutdown: ' . foo() . PHP_EOL;
+ }
+}
+
+function bar($arg) {
+ return $arg;
+}
+
+function foo() {
+ bar(41);
+ return bar(42);
+}
+
+$mc = new MyClass();
+
+echo 'Done: ' . bar(40) . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_shutdown_%d.php' -->
+<file '%s%eobserver_shutdown_%d.php'>
+ <!-- init bar() -->
+ <bar>
+ </bar:40>
+Done: 40
+</file '%s%eobserver_shutdown_%d.php'>
+<!-- init MyClass::__destruct() -->
+<MyClass::__destruct>
+ <!-- init foo() -->
+ <foo>
+ <bar>
+ </bar:41>
+ <bar>
+ </bar:42>
+ </foo:42>
+Shutdown: 42
+</MyClass::__destruct:NULL>
diff --git a/ext/zend_test/tests/observer_types_01.phpt b/ext/zend_test/tests/observer_types_01.phpt
new file mode 100644
index 0000000000..7b5408a456
--- /dev/null
+++ b/ext/zend_test/tests/observer_types_01.phpt
@@ -0,0 +1,28 @@
+--TEST--
+Observer: Observe basic TypeError
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+zend_test.observer.show_return_value=1
+--FILE--
+<?php
+function foo(array $a) { return 1; }
+foo(42);
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_types_%d.php' -->
+<file '%s%eobserver_types_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ <!-- Exception: TypeError -->
+ </foo:NULL>
+ <!-- Exception: TypeError -->
+</file '%s%eobserver_types_%d.php'>
+
+Fatal error: Uncaught TypeError: foo(): Argument #1 ($a) must be of type array, int given, called in %s:%d
+Stack trace:
+#0 %s%eobserver_types_%d.php(%d): foo(42)
+#1 {main}
+ thrown in %s%eobserver_types_%d.php on line %d
diff --git a/ext/zend_test/tests/observer_zend_call_function_01.phpt b/ext/zend_test/tests/observer_zend_call_function_01.phpt
new file mode 100644
index 0000000000..73b0f92cce
--- /dev/null
+++ b/ext/zend_test/tests/observer_zend_call_function_01.phpt
@@ -0,0 +1,36 @@
+--TEST--
+Observer: Calls that go through zend_call_function are observed
+--SKIPIF--
+<?php if (!extension_loaded('zend-test')) die('skip: zend-test extension required'); ?>
+--INI--
+zend_test.observer.enabled=1
+zend_test.observer.observe_all=1
+--FILE--
+<?php
+function sum($carry, $item) {
+ $carry += $item;
+ return $carry;
+}
+
+$a = [1, 2, 3, 4, 5];
+// array_reduce() calls zend_call_function() under the hood
+var_dump(array_reduce($a, 'sum'));
+echo 'Done' . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s%eobserver_zend_call_function_%d.php' -->
+<file '%s%eobserver_zend_call_function_%d.php'>
+ <!-- init sum() -->
+ <sum>
+ </sum>
+ <sum>
+ </sum>
+ <sum>
+ </sum>
+ <sum>
+ </sum>
+ <sum>
+ </sum>
+int(15)
+Done
+</file '%s%eobserver_zend_call_function_%d.php'>