USE_OPLINE
zval *retval_ptr;
zval *return_value;
+ ZEND_OBSERVER_USE_RETVAL;
retval_ptr = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
return_value = EX(return_value);
+ ZEND_OBSERVER_SET_RETVAL();
if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
ZEND_OBSERVER_SAVE_OPLINE();
ZEND_OBSERVER_FCALL_END(execute_data, return_value);
+ ZEND_OBSERVER_FREE_RETVAL();
ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
}
{
USE_OPLINE
zval *retval_ptr;
+ zval *return_value;
+ ZEND_OBSERVER_USE_RETVAL;
SAVE_OPLINE();
+ return_value = EX(return_value);
+ ZEND_OBSERVER_SET_RETVAL();
do {
if ((OP1_TYPE & (IS_CONST|IS_TMP_VAR)) ||
(OP1_TYPE == IS_VAR && opline->extended_value == ZEND_RETURNS_VALUE)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
retval_ptr = GET_OP1_ZVAL_PTR(BP_VAR_R);
- if (!EX(return_value)) {
+ if (!return_value) {
FREE_OP1();
} else {
if (OP1_TYPE == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
- ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
+ ZVAL_COPY_VALUE(return_value, retval_ptr);
break;
}
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ ZVAL_NEW_REF(return_value, retval_ptr);
if (OP1_TYPE == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_ASSERT(retval_ptr != &EG(uninitialized_zval));
if (opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(retval_ptr)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
- if (EX(return_value)) {
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ if (return_value) {
+ ZVAL_NEW_REF(return_value, retval_ptr);
} else {
FREE_OP1_VAR_PTR();
}
}
}
- if (EX(return_value)) {
+ if (return_value) {
if (Z_ISREF_P(retval_ptr)) {
Z_ADDREF_P(retval_ptr);
} else {
ZVAL_MAKE_REF_EX(retval_ptr, 2);
}
- ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
+ ZVAL_REF(return_value, Z_REF_P(retval_ptr));
}
FREE_OP1_VAR_PTR();
} while (0);
- ZEND_OBSERVER_FCALL_END(execute_data, EX(return_value));
+ ZEND_OBSERVER_FCALL_END(execute_data, return_value);
+ ZEND_OBSERVER_FREE_RETVAL();
ZEND_VM_DISPATCH_TO_HELPER(zend_leave_helper);
}
/* Uncaught exception */
if (zend_observer_fcall_op_array_extension != -1) {
- zend_observer_fcall_end(execute_data, EX(return_value));
+ zend_observer_fcall_end(execute_data, NULL);
}
cleanup_live_vars(execute_data, op_num, 0);
if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
/* Uncaught exception */
if (zend_observer_fcall_op_array_extension != -1) {
- zend_observer_fcall_end(execute_data, EX(return_value));
+ zend_observer_fcall_end(execute_data, NULL);
}
cleanup_live_vars(execute_data, op_num, 0);
if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) {
retval_ptr = RT_CONSTANT(opline, opline->op1);
return_value = EX(return_value);
+
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
USE_OPLINE
zval *retval_ptr;
zval *return_value;
+ zval observer_retval;
retval_ptr = get_zval_ptr_undef(opline->op1_type, opline->op1, BP_VAR_R);
return_value = EX(return_value);
+ if (!return_value) { return_value = &observer_retval; };
if (opline->op1_type == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
SAVE_OPLINE();
zend_observer_fcall_end(execute_data, return_value);
+ if (return_value == &observer_retval) { zval_ptr_dtor_nogc(&observer_retval); };
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
{
USE_OPLINE
zval *retval_ptr;
+ zval *return_value;
SAVE_OPLINE();
+ return_value = EX(return_value);
+
do {
if ((IS_CONST & (IS_CONST|IS_TMP_VAR)) ||
(IS_CONST == IS_VAR && opline->extended_value == ZEND_RETURNS_VALUE)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
retval_ptr = RT_CONSTANT(opline, opline->op1);
- if (!EX(return_value)) {
+ if (!return_value) {
} else {
if (IS_CONST == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
- ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
+ ZVAL_COPY_VALUE(return_value, retval_ptr);
break;
}
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ ZVAL_NEW_REF(return_value, retval_ptr);
if (IS_CONST == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_ASSERT(retval_ptr != &EG(uninitialized_zval));
if (opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(retval_ptr)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
- if (EX(return_value)) {
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ if (return_value) {
+ ZVAL_NEW_REF(return_value, retval_ptr);
} else {
}
}
}
- if (EX(return_value)) {
+ if (return_value) {
if (Z_ISREF_P(retval_ptr)) {
Z_ADDREF_P(retval_ptr);
} else {
ZVAL_MAKE_REF_EX(retval_ptr, 2);
}
- ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
+ ZVAL_REF(return_value, Z_REF_P(retval_ptr));
}
} while (0);
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
{
USE_OPLINE
zval *retval_ptr;
+ zval *return_value;
+ zval observer_retval;
SAVE_OPLINE();
+ return_value = EX(return_value);
+ if (!return_value) { return_value = &observer_retval; };
do {
if ((opline->op1_type & (IS_CONST|IS_TMP_VAR)) ||
(opline->op1_type == IS_VAR && opline->extended_value == ZEND_RETURNS_VALUE)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
retval_ptr = get_zval_ptr(opline->op1_type, opline->op1, BP_VAR_R);
- if (!EX(return_value)) {
+ if (!return_value) {
FREE_OP(opline->op1_type, opline->op1.var);
} else {
if (opline->op1_type == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
- ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
+ ZVAL_COPY_VALUE(return_value, retval_ptr);
break;
}
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ ZVAL_NEW_REF(return_value, retval_ptr);
if (opline->op1_type == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_ASSERT(retval_ptr != &EG(uninitialized_zval));
if (opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(retval_ptr)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
- if (EX(return_value)) {
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ if (return_value) {
+ ZVAL_NEW_REF(return_value, retval_ptr);
} else {
if (opline->op1_type == IS_VAR) {zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));};
}
}
}
- if (EX(return_value)) {
+ if (return_value) {
if (Z_ISREF_P(retval_ptr)) {
Z_ADDREF_P(retval_ptr);
} else {
ZVAL_MAKE_REF_EX(retval_ptr, 2);
}
- ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
+ ZVAL_REF(return_value, Z_REF_P(retval_ptr));
}
if (opline->op1_type == IS_VAR) {zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));};
} while (0);
- zend_observer_fcall_end(execute_data, EX(return_value));
+ zend_observer_fcall_end(execute_data, return_value);
+ if (return_value == &observer_retval) { zval_ptr_dtor_nogc(&observer_retval); };
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
retval_ptr = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC);
return_value = EX(return_value);
+
if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
{
USE_OPLINE
zval *retval_ptr;
+ zval *return_value;
SAVE_OPLINE();
+ return_value = EX(return_value);
+
do {
if ((IS_TMP_VAR & (IS_CONST|IS_TMP_VAR)) ||
(IS_TMP_VAR == IS_VAR && opline->extended_value == ZEND_RETURNS_VALUE)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
retval_ptr = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC);
- if (!EX(return_value)) {
+ if (!return_value) {
zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
} else {
if (IS_TMP_VAR == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
- ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
+ ZVAL_COPY_VALUE(return_value, retval_ptr);
break;
}
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ ZVAL_NEW_REF(return_value, retval_ptr);
if (IS_TMP_VAR == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_ASSERT(retval_ptr != &EG(uninitialized_zval));
if (opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(retval_ptr)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
- if (EX(return_value)) {
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ if (return_value) {
+ ZVAL_NEW_REF(return_value, retval_ptr);
} else {
}
}
}
- if (EX(return_value)) {
+ if (return_value) {
if (Z_ISREF_P(retval_ptr)) {
Z_ADDREF_P(retval_ptr);
} else {
ZVAL_MAKE_REF_EX(retval_ptr, 2);
}
- ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
+ ZVAL_REF(return_value, Z_REF_P(retval_ptr));
}
} while (0);
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
retval_ptr = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC);
return_value = EX(return_value);
+
if (IS_VAR == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
{
USE_OPLINE
zval *retval_ptr;
+ zval *return_value;
SAVE_OPLINE();
+ return_value = EX(return_value);
+
do {
if ((IS_VAR & (IS_CONST|IS_TMP_VAR)) ||
(IS_VAR == IS_VAR && opline->extended_value == ZEND_RETURNS_VALUE)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
retval_ptr = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC);
- if (!EX(return_value)) {
+ if (!return_value) {
zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
} else {
if (IS_VAR == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
- ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
+ ZVAL_COPY_VALUE(return_value, retval_ptr);
break;
}
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ ZVAL_NEW_REF(return_value, retval_ptr);
if (IS_VAR == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_ASSERT(retval_ptr != &EG(uninitialized_zval));
if (opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(retval_ptr)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
- if (EX(return_value)) {
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ if (return_value) {
+ ZVAL_NEW_REF(return_value, retval_ptr);
} else {
zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
}
}
}
- if (EX(return_value)) {
+ if (return_value) {
if (Z_ISREF_P(retval_ptr)) {
Z_ADDREF_P(retval_ptr);
} else {
ZVAL_MAKE_REF_EX(retval_ptr, 2);
}
- ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
+ ZVAL_REF(return_value, Z_REF_P(retval_ptr));
}
zval_ptr_dtor_nogc(EX_VAR(opline->op1.var));
} while (0);
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
retval_ptr = EX_VAR(opline->op1.var);
return_value = EX(return_value);
+
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
{
USE_OPLINE
zval *retval_ptr;
+ zval *return_value;
SAVE_OPLINE();
+ return_value = EX(return_value);
+
do {
if ((IS_CV & (IS_CONST|IS_TMP_VAR)) ||
(IS_CV == IS_VAR && opline->extended_value == ZEND_RETURNS_VALUE)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
retval_ptr = _get_zval_ptr_cv_BP_VAR_R(opline->op1.var EXECUTE_DATA_CC);
- if (!EX(return_value)) {
+ if (!return_value) {
} else {
if (IS_CV == IS_VAR && UNEXPECTED(Z_ISREF_P(retval_ptr))) {
- ZVAL_COPY_VALUE(EX(return_value), retval_ptr);
+ ZVAL_COPY_VALUE(return_value, retval_ptr);
break;
}
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ ZVAL_NEW_REF(return_value, retval_ptr);
if (IS_CV == IS_CONST) {
Z_TRY_ADDREF_P(retval_ptr);
}
ZEND_ASSERT(retval_ptr != &EG(uninitialized_zval));
if (opline->extended_value == ZEND_RETURNS_FUNCTION && !Z_ISREF_P(retval_ptr)) {
zend_error(E_NOTICE, "Only variable references should be returned by reference");
- if (EX(return_value)) {
- ZVAL_NEW_REF(EX(return_value), retval_ptr);
+ if (return_value) {
+ ZVAL_NEW_REF(return_value, retval_ptr);
} else {
}
}
}
- if (EX(return_value)) {
+ if (return_value) {
if (Z_ISREF_P(retval_ptr)) {
Z_ADDREF_P(retval_ptr);
} else {
ZVAL_MAKE_REF_EX(retval_ptr, 2);
}
- ZVAL_REF(EX(return_value), Z_REF_P(retval_ptr));
+ ZVAL_REF(return_value, Z_REF_P(retval_ptr));
}
} while (0);
+
ZEND_VM_TAIL_CALL(zend_leave_helper_SPEC(ZEND_OPCODE_HANDLER_ARGS_PASSTHRU));
}
retval_ptr = RT_CONSTANT(opline, opline->op1);
return_value = EX(return_value);
+
if (IS_CONST == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
goto zend_leave_helper_SPEC_LABEL;
}
USE_OPLINE
zval *retval_ptr;
zval *return_value;
+ zval observer_retval;
retval_ptr = get_zval_ptr_undef(opline->op1_type, opline->op1, BP_VAR_R);
return_value = EX(return_value);
+ if (!return_value) { return_value = &observer_retval; };
if (opline->op1_type == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
SAVE_OPLINE();
zend_observer_fcall_end(execute_data, return_value);
+ if (return_value == &observer_retval) { zval_ptr_dtor_nogc(&observer_retval); };
goto zend_leave_helper_SPEC_LABEL;
}
retval_ptr = _get_zval_ptr_tmp(opline->op1.var EXECUTE_DATA_CC);
return_value = EX(return_value);
+
if (IS_TMP_VAR == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
goto zend_leave_helper_SPEC_LABEL;
}
retval_ptr = _get_zval_ptr_var(opline->op1.var EXECUTE_DATA_CC);
return_value = EX(return_value);
+
if (IS_VAR == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
goto zend_leave_helper_SPEC_LABEL;
}
retval_ptr = EX_VAR(opline->op1.var);
return_value = EX(return_value);
+
if (IS_CV == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(retval_ptr) == IS_UNDEF)) {
SAVE_OPLINE();
retval_ptr = ZVAL_UNDEFINED_OP1();
}
+
goto zend_leave_helper_SPEC_LABEL;
}
($extra_spec['ISSET'] == 0 ? "\\0" : "opline->extended_value")
: "\\0",
"/ZEND_OBSERVER_ENABLED/" => isset($extra_spec['OBSERVER']) && $extra_spec['OBSERVER'] == 1 ? "1" : "0",
+ "/ZEND_OBSERVER_USE_RETVAL/" => isset($extra_spec['OBSERVER']) && $extra_spec['OBSERVER'] == 1 ? "zval observer_retval" : "",
+ "/ZEND_OBSERVER_SET_RETVAL\(\)/" => isset($extra_spec['OBSERVER']) && $extra_spec['OBSERVER'] == 1 ? "if (!return_value) { return_value = &observer_retval; }" : "",
+ "/ZEND_OBSERVER_FREE_RETVAL\(\)/" => isset($extra_spec['OBSERVER']) && $extra_spec['OBSERVER'] == 1 ? "if (return_value == &observer_retval) { zval_ptr_dtor_nogc(&observer_retval); }" : "",
"/ZEND_OBSERVER_SAVE_OPLINE\(\)/" => isset($extra_spec['OBSERVER']) && $extra_spec['OBSERVER'] == 1 ? "SAVE_OPLINE()" : "",
"/ZEND_OBSERVER_FCALL_BEGIN\(\s*(.*)\s*\)/" => isset($extra_spec['OBSERVER']) ?
($extra_spec['OBSERVER'] == 0 ? "" : "zend_observer_fcall_begin(\\1)")
if (retval == NULL) {
smart_str_appendl(buf, "NULL", 4);
} else if (ZT_G(observer_show_return_value)) {
- php_var_export_ex(retval, 2 * ZT_G(observer_nesting_depth) + 3, buf);
+ 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));
}
--- /dev/null
+--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
+--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/observer_retval_%d.php' -->
+<file '%s/observer_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'I should be observable'>
+Done
+</file '%s/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_%d.php' -->
+<file '%s/observer_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'Me too!'>
+Done
+</file '%s/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_%d.php' -->
+<file '%s/observer_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:object(MyRetval)#%d>
+ <foo>
+ </foo:object(MyRetval)#%d>
+Done
+</file '%s/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_%d.php' -->
+<file '%s/observer_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/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_%d.php' -->
+<file '%s/observer_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/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_%d.php' -->
+<file '%s/observer_retval_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'I should be observable'>
+Done
+</file '%s/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_%d.php' -->
+<file '%s/observer_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/observer_retval_%d.php'>
--- /dev/null
+--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/observer_retval_by_ref_%d.php' -->
+<file '%s/observer_retval_by_ref_%d.php'>
+ <!-- init foo() -->
+ <foo>
+ </foo:'I should be observable'>
+ <foo>
+ </foo:'I should be observable'>
+Done
+</file '%s/observer_retval_by_ref_%d.php'>
--- /dev/null
+--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/observer_retval_by_ref_%d.php' -->
+<file '%s/observer_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/observer_retval_by_ref_%d.php'>
--- /dev/null
+--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
+--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/observer_retval_by_ref_%d.php' -->
+<file '%s/observer_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/observer_retval_by_ref_%d.php'>
--- /dev/null
+--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() {
+ return 42;
+}
+
+function foo() {
+ bar();
+ return bar();
+}
+
+echo 'Done: ' . bar() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s/observer_shutdown_%d.php' -->
+<file '%s/observer_shutdown_%d.php'>
+ <!-- init bar() -->
+ <bar>
+ </bar:42>
+Done: 42
+</file '%s/observer_shutdown_%d.php'>
+<!-- init {closure}() -->
+<{closure}>
+ <!-- init foo() -->
+ <foo>
+ <bar>
+ </bar:42>
+ <bar>
+ </bar:42>
+ </foo:42>
+Shutdown: 42
+</{closure}:NULL>
--- /dev/null
+--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() {
+ return 42;
+}
+
+function foo() {
+ bar();
+ return bar();
+}
+
+$mc = new MyClass();
+
+echo 'Done: ' . bar() . PHP_EOL;
+?>
+--EXPECTF--
+<!-- init '%s/observer_shutdown_%d.php' -->
+<file '%s/observer_shutdown_%d.php'>
+ <!-- init bar() -->
+ <bar>
+ </bar:42>
+Done: 42
+</file '%s/observer_shutdown_%d.php'>
+<!-- init MyClass::__destruct() -->
+<MyClass::__destruct>
+ <!-- init foo() -->
+ <foo>
+ <bar>
+ </bar:42>
+ <bar>
+ </bar:42>
+ </foo:42>
+Shutdown: 42
+</MyClass::__destruct:NULL>