Include internal functions in the observer API

There are two main motivations to this:
a) The logic for handling internal and userland observation can be unified.
b) Unwinding of observed functions on a bailout does notably not include observers. Even if users of observers were to ensure such handling themselves, it would be impossible to retain the relative ordering - either the user has to unwind all internal observed frames before the automatic unwinding (zend_observer_fcall_end_all) or afterwards, but not properly interleaved.

Signed-off-by: Bob Weinand <bobwei9@hotmail.com>
This commit is contained in:
Bob Weinand 2022-07-20 15:50:12 +02:00
parent 0c225a2f57
commit 625f164963
49 changed files with 1675 additions and 1257 deletions

View file

@ -51,6 +51,7 @@ PHP 8.2 INTERNALS UPGRADE NOTES
are deprecated (see main UPGRADING notes). To suppress the notice, e.g. to
avoid duplicates when processing the same value multiple times, pass or add
IS_CALLABLE_SUPPRESS_DEPRECATIONS to the check_flags parameter.
* Registered zend_observer_fcall_init handlers are now also called for internal functions.
========================
2. Build system changes

View file

@ -1226,6 +1226,7 @@ ZEND_API void zend_activate(void) /* {{{ */
if (CG(map_ptr_last)) {
memset(CG(map_ptr_real_base), 0, CG(map_ptr_last) * sizeof(void*));
}
zend_init_internal_run_time_cache();
zend_observer_activate();
}
/* }}} */

View file

@ -2694,6 +2694,11 @@ ZEND_API zend_result zend_register_functions(zend_class_entry *scope, const zend
internal_function->scope = scope;
internal_function->prototype = NULL;
internal_function->attributes = NULL;
if (EG(active)) { // at run-time: this ought to only happen if registered with dl() or somehow temporarily at runtime
ZEND_MAP_PTR_INIT(internal_function->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));
} else {
ZEND_MAP_PTR_NEW(internal_function->run_time_cache);
}
if (ptr->flags) {
if (!(ptr->flags & ZEND_ACC_PPP_MASK)) {
if (ptr->flags != ZEND_ACC_DEPRECATED && scope) {

View file

@ -33,6 +33,7 @@
#include "zend_inheritance.h"
#include "zend_vm.h"
#include "zend_enum.h"
#include "zend_observer.h"
#define SET_NODE(target, src) do { \
target ## _type = (src)->op_type; \

View file

@ -448,6 +448,7 @@ struct _zend_op_array {
uint32_t required_num_args;
zend_arg_info *arg_info;
HashTable *attributes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
/* END of common elements */
int cache_size; /* number of run_time_cache_slots * sizeof(void*) */
@ -456,7 +457,6 @@ struct _zend_op_array {
uint32_t last; /* number of opcodes */
zend_op *opcodes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
ZEND_MAP_PTR_DEF(HashTable *, static_variables_ptr);
HashTable *static_variables;
zend_string **vars; /* names of CV variables */
@ -503,6 +503,7 @@ typedef struct _zend_internal_function {
uint32_t required_num_args;
zend_internal_arg_info *arg_info;
HashTable *attributes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
/* END of common elements */
zif_handler handler;
@ -527,6 +528,7 @@ union _zend_function {
uint32_t required_num_args;
zend_arg_info *arg_info; /* index -1 represents the return value info, if any */
HashTable *attributes;
ZEND_MAP_PTR_DEF(void **, run_time_cache);
} common;
zend_op_array op_array;

View file

@ -22,6 +22,7 @@
#include "zend_enum_arginfo.h"
#include "zend_interfaces.h"
#include "zend_enum.h"
#include "zend_extensions.h"
#define ZEND_ENUM_DISALLOW_MAGIC_METHOD(propertyName, methodName) \
do { \
@ -401,59 +402,48 @@ static ZEND_NAMED_FUNCTION(zend_enum_try_from_func)
zend_enum_from_base(INTERNAL_FUNCTION_PARAM_PASSTHRU, 1);
}
static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id name_id, zend_internal_function *zif) {
zend_string *name = ZSTR_KNOWN(name_id);
zif->type = ZEND_INTERNAL_FUNCTION;
zif->module = EG(current_module);
zif->scope = ce;
ZEND_MAP_PTR_NEW(zif->run_time_cache);
ZEND_MAP_PTR_SET(zif->run_time_cache, zend_arena_alloc(&CG(arena), zend_internal_run_time_cache_reserved_size()));
if (!zend_hash_add_ptr(&ce->function_table, name, zif)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::%s()", ZSTR_VAL(ce->name), ZSTR_VAL(name));
}
}
void zend_enum_register_funcs(zend_class_entry *ce)
{
const uint32_t fn_flags =
ZEND_ACC_PUBLIC|ZEND_ACC_STATIC|ZEND_ACC_HAS_RETURN_TYPE|ZEND_ACC_ARENA_ALLOCATED;
zend_internal_function *cases_function =
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memset(cases_function, 0, sizeof(zend_internal_function));
cases_function->type = ZEND_INTERNAL_FUNCTION;
cases_function->module = EG(current_module);
zend_internal_function *cases_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
cases_function->handler = zend_enum_cases_func;
cases_function->function_name = ZSTR_KNOWN(ZEND_STR_CASES);
cases_function->scope = ce;
cases_function->fn_flags = fn_flags;
cases_function->arg_info = (zend_internal_arg_info *) (arginfo_class_UnitEnum_cases + 1);
if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_CASES), cases_function)) {
zend_error_noreturn(E_COMPILE_ERROR, "Cannot redeclare %s::cases()", ZSTR_VAL(ce->name));
}
zend_enum_register_func(ce, ZEND_STR_CASES, cases_function);
if (ce->enum_backing_type != IS_UNDEF) {
zend_internal_function *from_function =
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memset(from_function, 0, sizeof(zend_internal_function));
from_function->type = ZEND_INTERNAL_FUNCTION;
from_function->module = EG(current_module);
zend_internal_function *from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
from_function->handler = zend_enum_from_func;
from_function->function_name = ZSTR_KNOWN(ZEND_STR_FROM);
from_function->scope = ce;
from_function->fn_flags = fn_flags;
from_function->num_args = 1;
from_function->required_num_args = 1;
from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_from + 1);
if (!zend_hash_add_ptr(&ce->function_table, ZSTR_KNOWN(ZEND_STR_FROM), from_function)) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot redeclare %s::from()", ZSTR_VAL(ce->name));
}
zend_enum_register_func(ce, ZEND_STR_FROM, from_function);
zend_internal_function *try_from_function =
zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
memset(try_from_function, 0, sizeof(zend_internal_function));
try_from_function->type = ZEND_INTERNAL_FUNCTION;
try_from_function->module = EG(current_module);
zend_internal_function *try_from_function = zend_arena_calloc(&CG(arena), sizeof(zend_internal_function), 1);
try_from_function->handler = zend_enum_try_from_func;
try_from_function->function_name = ZSTR_KNOWN(ZEND_STR_TRYFROM);
try_from_function->scope = ce;
try_from_function->fn_flags = fn_flags;
try_from_function->num_args = 1;
try_from_function->required_num_args = 1;
try_from_function->arg_info = (zend_internal_arg_info *) (arginfo_class_BackedEnum_tryFrom + 1);
if (!zend_hash_add_ptr(
&ce->function_table, ZSTR_KNOWN(ZEND_STR_TRYFROM_LOWERCASE), try_from_function)) {
zend_error_noreturn(E_COMPILE_ERROR,
"Cannot redeclare %s::tryFrom()", ZSTR_VAL(ce->name));
}
zend_enum_register_func(ce, ZEND_STR_TRYFROM_LOWERCASE, try_from_function);
}
}

View file

@ -144,6 +144,7 @@ ZEND_API const zend_internal_function zend_pass_function = {
0, /* required_num_args */
(zend_internal_arg_info *) zend_pass_function_arg_info + 1, /* arg_info */
NULL, /* attributes */
NULL, /* run_time_cache */
ZEND_FN(pass), /* handler */
NULL, /* module */
{NULL,NULL,NULL,NULL} /* reserved */

View file

@ -935,6 +935,7 @@ cleanup_args:
#if ZEND_DEBUG
bool should_throw = zend_internal_call_should_throw(func, call);
#endif
ZEND_OBSERVER_FCALL_BEGIN(call);
if (EXPECTED(zend_execute_internal == NULL)) {
/* saves one function call if zend_execute_internal is not used */
func->internal_function.handler(call, fci->retval);
@ -953,6 +954,7 @@ cleanup_args:
? Z_ISREF_P(fci->retval) : !Z_ISREF_P(fci->retval));
}
#endif
ZEND_OBSERVER_FCALL_END(call, fci->retval);
EG(current_execute_data) = call->prev_execute_data;
zend_vm_stack_free_args(call);
if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_HAS_EXTRA_NAMED_PARAMS)) {

View file

@ -280,6 +280,40 @@ ZEND_API int zend_get_op_array_extension_handles(const char *module_name, int ha
return handle;
}
ZEND_API size_t zend_internal_run_time_cache_reserved_size() {
return zend_op_array_extension_handles * sizeof(void *);
}
ZEND_API void zend_init_internal_run_time_cache() {
size_t rt_size = zend_internal_run_time_cache_reserved_size();
if (rt_size) {
size_t functions = zend_hash_num_elements(CG(function_table));
zend_class_entry *ce;
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
functions += zend_hash_num_elements(&ce->function_table);
} ZEND_HASH_FOREACH_END();
char *ptr = zend_arena_calloc(&CG(arena), functions, rt_size);
zend_internal_function *zif;
ZEND_HASH_MAP_FOREACH_PTR(CG(function_table), zif) {
if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL)
{
ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr);
ptr += rt_size;
}
} ZEND_HASH_FOREACH_END();
ZEND_HASH_MAP_FOREACH_PTR(CG(class_table), ce) {
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, zif) {
if (!ZEND_USER_CODE(zif->type) && ZEND_MAP_PTR_GET(zif->run_time_cache) == NULL)
{
ZEND_MAP_PTR_SET(zif->run_time_cache, (void *)ptr);
ptr += rt_size;
}
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();
}
}
ZEND_API zend_extension *zend_get_extension(const char *extension_name)
{
zend_llist_element *element;

View file

@ -145,6 +145,9 @@ void zend_startup_extensions_mechanism(void);
void zend_startup_extensions(void);
void zend_shutdown_extensions(void);
ZEND_API size_t zend_internal_run_time_cache_reserved_size(void);
ZEND_API void zend_init_internal_run_time_cache(void);
BEGIN_EXTERN_C()
ZEND_API zend_result zend_load_extension(const char *path);
ZEND_API zend_result zend_load_extension_handle(DL_HANDLE handle, const char *path);

View file

@ -23,13 +23,13 @@
#include "zend_llist.h"
#include "zend_vm.h"
#define ZEND_OBSERVER_DATA(op_array) \
ZEND_OP_ARRAY_EXTENSION(op_array, zend_observer_fcall_op_array_extension)
#define ZEND_OBSERVER_DATA(function) \
ZEND_OP_ARRAY_EXTENSION((&(function)->common), zend_observer_fcall_op_array_extension)
#define ZEND_OBSERVER_NOT_OBSERVED ((void *) 2)
#define ZEND_OBSERVABLE_FN(fn_flags) \
(!(fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
#define ZEND_OBSERVABLE_FN(function) \
(ZEND_MAP_PTR(function->common.run_time_cache) && !(function->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE))
zend_llist zend_observers_fcall_list;
zend_llist zend_observer_error_callbacks;
@ -100,12 +100,9 @@ static void zend_observer_fcall_install(zend_execute_data *execute_data)
{
zend_llist *list = &zend_observers_fcall_list;
zend_function *function = execute_data->func;
zend_op_array *op_array = &function->op_array;
ZEND_ASSERT(function->type != ZEND_INTERNAL_FUNCTION);
ZEND_ASSERT(RUN_TIME_CACHE(op_array));
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
ZEND_ASSERT(RUN_TIME_CACHE(&function->common));
zend_observer_fcall_begin_handler *begin_handlers = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
zend_observer_fcall_end_handler *end_handlers = (zend_observer_fcall_end_handler *)begin_handlers + list->count, *end_handlers_start = end_handlers;
*begin_handlers = ZEND_OBSERVER_NOT_OBSERVED;
@ -152,9 +149,9 @@ static bool zend_observer_remove_handler(void **first_handler, void *old_handler
return false;
}
ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
size_t registered_observers = zend_observers_fcall_list.count;
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(op_array), *last_handler = first_handler + registered_observers - 1;
zend_observer_fcall_begin_handler *first_handler = (void *)&ZEND_OBSERVER_DATA(function), *last_handler = first_handler + registered_observers - 1;
if (*first_handler == ZEND_OBSERVER_NOT_OBSERVED) {
*first_handler = begin;
} else {
@ -169,13 +166,13 @@ ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_obse
}
}
ZEND_API bool zend_observer_remove_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin) {
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array), begin);
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin) {
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function), begin);
}
ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
size_t registered_observers = zend_observers_fcall_list.count;
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(op_array) + registered_observers;
zend_observer_fcall_end_handler *end_handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(function) + registered_observers;
// to allow to preserve the invariant that end handlers are in reverse order of begin handlers, push the new end handler in front
if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) {
// there's no space for new handlers, then it's forbidden to call this function
@ -185,9 +182,9 @@ ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observ
*end_handler = end;
}
ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end) {
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end) {
size_t registered_observers = zend_observers_fcall_list.count;
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(op_array) + registered_observers, end);
return zend_observer_remove_handler((void **)&ZEND_OBSERVER_DATA(function) + registered_observers, end);
}
static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_data)
@ -196,14 +193,13 @@ static void ZEND_FASTCALL _zend_observe_fcall_begin(zend_execute_data *execute_d
return;
}
zend_op_array *op_array = &execute_data->func->op_array;
uint32_t fn_flags = op_array->fn_flags;
zend_function *function = execute_data->func;
if (!ZEND_OBSERVABLE_FN(fn_flags)) {
if (!ZEND_OBSERVABLE_FN(function)) {
return;
}
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(op_array);
zend_observer_fcall_begin_handler *handler = (zend_observer_fcall_begin_handler *)&ZEND_OBSERVER_DATA(function);
if (!*handler) {
zend_observer_fcall_install(execute_data);
}
@ -243,11 +239,11 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_begin(zend_execute_data *execute
static inline bool zend_observer_is_skipped_frame(zend_execute_data *execute_data) {
zend_function *func = execute_data->func;
if (!func || func->type == ZEND_INTERNAL_FUNCTION || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
if (!func || !ZEND_OBSERVABLE_FN(func)) {
return true;
}
zend_observer_fcall_end_handler end_handler = (&ZEND_OBSERVER_DATA(&func->op_array))[zend_observers_fcall_list.count];
zend_observer_fcall_end_handler end_handler = (&ZEND_OBSERVER_DATA(func))[zend_observers_fcall_list.count];
if (end_handler == NULL || end_handler == ZEND_OBSERVER_NOT_OBSERVED) {
return true;
}
@ -259,11 +255,11 @@ ZEND_API void ZEND_FASTCALL zend_observer_fcall_end(zend_execute_data *execute_d
{
zend_function *func = execute_data->func;
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func->common.fn_flags)) {
if (!ZEND_OBSERVER_ENABLED || !ZEND_OBSERVABLE_FN(func)) {
return;
}
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(&func->op_array) + zend_observers_fcall_list.count;
zend_observer_fcall_end_handler *handler = (zend_observer_fcall_end_handler *)&ZEND_OBSERVER_DATA(func) + zend_observers_fcall_list.count;
// TODO: Fix exceptions from generators
// ZEND_ASSERT(fcall_data);
if (!*handler || *handler == ZEND_OBSERVER_NOT_OBSERVED) {
@ -291,7 +287,7 @@ ZEND_API void zend_observer_fcall_end_all(void)
{
zend_execute_data *ex = current_observed_frame;
while (ex != NULL) {
if (ex->func && ex->func->type != ZEND_INTERNAL_FUNCTION) {
if (ex->func) {
zend_observer_fcall_end(ex, NULL);
}
ex = ex->prev_execute_data;

View file

@ -58,10 +58,10 @@ ZEND_API void zend_observer_fcall_register(zend_observer_fcall_init);
// Call during runtime, but only if you have used zend_observer_fcall_register.
// You must not have more than one begin and one end handler active at the same time. Remove the old one first, if there is an existing one.
ZEND_API void zend_observer_add_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin);
ZEND_API bool zend_observer_remove_begin_handler(zend_op_array *op_array, zend_observer_fcall_begin_handler begin);
ZEND_API void zend_observer_add_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end);
ZEND_API bool zend_observer_remove_end_handler(zend_op_array *op_array, zend_observer_fcall_end_handler end);
ZEND_API void zend_observer_add_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
ZEND_API bool zend_observer_remove_begin_handler(zend_function *function, zend_observer_fcall_begin_handler begin);
ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
ZEND_API bool zend_observer_remove_end_handler(zend_function *function, zend_observer_fcall_end_handler end);
ZEND_API void zend_observer_startup(void); // Called by engine before MINITs
ZEND_API void zend_observer_post_startup(void); // Called by engine after MINITs

View file

@ -405,7 +405,7 @@ ZEND_VM_HANDLER(8, ZEND_CONCAT, CONST|TMPVAR|CV, CONST|TMPVAR|CV, SPEC(NO_CONST_
}
} else if (OP1_TYPE != IS_CONST && OP1_TYPE != IS_CV &&
!ZSTR_IS_INTERNED(op1_str) && GC_REFCOUNT(op1_str) == 1) {
size_t len = ZSTR_LEN(op1_str);
size_t len = ZSTR_LEN(op1_str);
if (UNEXPECTED(len > ZSTR_MAX_LEN - ZSTR_LEN(op2_str))) {
zend_error_noreturn(E_ERROR, "Integer overflow in memory allocation");
@ -1998,9 +1998,9 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(93, ZEND_FETCH_DIM_FUNC_ARG, CONST|TMP|VAR|CV, C
#endif
if (UNEXPECTED(ZEND_CALL_INFO(EX(call)) & ZEND_CALL_SEND_ARG_BY_REF)) {
if ((OP1_TYPE & (IS_CONST|IS_TMP_VAR))) {
if ((OP1_TYPE & (IS_CONST|IS_TMP_VAR))) {
ZEND_VM_DISPATCH_TO_HELPER(zend_use_tmp_in_write_context_helper);
}
}
ZEND_VM_DISPATCH_TO_HANDLER(ZEND_FETCH_DIM_W);
} else {
if (OP2_TYPE == IS_UNUSED) {
@ -2036,7 +2036,7 @@ ZEND_VM_HOT_OBJ_HANDLER(82, ZEND_FETCH_OBJ_R, CONST|TMPVAR|UNUSED|THIS|CV, CONST
if (OP1_TYPE == IS_CONST ||
(OP1_TYPE != IS_UNUSED && UNEXPECTED(Z_TYPE_P(container) != IS_OBJECT))) {
do {
do {
if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(container)) {
container = Z_REFVAL_P(container);
if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) {
@ -2084,9 +2084,9 @@ ZEND_VM_C_LABEL(fetch_obj_r_fast_copy):
Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx);
if (EXPECTED(p->key == name) ||
(EXPECTED(p->h == ZSTR_H(name)) &&
EXPECTED(p->key != NULL) &&
EXPECTED(zend_string_equal_content(p->key, name)))) {
(EXPECTED(p->h == ZSTR_H(name)) &&
EXPECTED(p->key != NULL) &&
EXPECTED(zend_string_equal_content(p->key, name)))) {
retval = &p->val;
if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) {
ZEND_VM_C_GOTO(fetch_obj_r_copy);
@ -2248,9 +2248,9 @@ ZEND_VM_C_LABEL(fetch_obj_is_fast_copy):
Bucket *p = (Bucket*)((char*)zobj->properties->arData + idx);
if (EXPECTED(p->key == name) ||
(EXPECTED(p->h == ZSTR_H(name)) &&
EXPECTED(p->key != NULL) &&
EXPECTED(zend_string_equal_content(p->key, name)))) {
(EXPECTED(p->h == ZSTR_H(name)) &&
EXPECTED(p->key != NULL) &&
EXPECTED(zend_string_equal_content(p->key, name)))) {
retval = &p->val;
if (!ZEND_VM_SPEC || (OP1_TYPE & (IS_TMP_VAR|IS_VAR)) != 0) {
ZEND_VM_C_GOTO(fetch_obj_is_copy);
@ -3161,7 +3161,7 @@ ZEND_VM_COLD_CONSTCONST_HANDLER(53, ZEND_FAST_CONCAT, CONST|TMPVAR|CV, CONST|TMP
}
} else if (OP1_TYPE != IS_CONST && OP1_TYPE != IS_CV &&
!ZSTR_IS_INTERNED(op1_str) && GC_REFCOUNT(op1_str) == 1) {
size_t len = ZSTR_LEN(op1_str);
size_t len = ZSTR_LEN(op1_str);
str = zend_string_extend(op1_str, len + ZSTR_LEN(op2_str), 0);
memcpy(ZSTR_VAL(str) + len, ZSTR_VAL(op2_str), ZSTR_LEN(op2_str)+1);
@ -3503,9 +3503,9 @@ ZEND_VM_HOT_OBJ_HANDLER(112, ZEND_INIT_METHOD_CALL, CONST|TMPVAR|UNUSED|THIS|CV,
if (OP2_TYPE == IS_CONST &&
EXPECTED(CACHED_PTR(opline->result.num) == called_scope)) {
fbc = CACHED_PTR(opline->result.num + sizeof(void*));
fbc = CACHED_PTR(opline->result.num + sizeof(void*));
} else {
zend_object *orig_obj = obj;
zend_object *orig_obj = obj;
if (OP2_TYPE == IS_CONST) {
function_name = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
@ -3908,7 +3908,7 @@ ZEND_VM_HOT_HANDLER(61, ZEND_INIT_FCALL, NUM, CONST, NUM|CACHE_SLOT)
ZEND_VM_NEXT_OPCODE();
}
ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
{
USE_OPLINE
zend_execute_data *call = EX(call);
@ -3929,6 +3929,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
ZVAL_NULL(ret);
ZEND_OBSERVER_FCALL_BEGIN(call);
fbc->internal_function.handler(call, ret);
#if ZEND_DEBUG
@ -3943,6 +3944,7 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL))
zend_verify_internal_func_info(call->func, ret);
}
#endif
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
EG(current_execute_data) = execute_data;
zend_vm_stack_free_args(call);
@ -4048,6 +4050,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
ZVAL_NULL(ret);
ZEND_OBSERVER_FCALL_BEGIN(call);
fbc->internal_function.handler(call, ret);
#if ZEND_DEBUG
@ -4062,6 +4065,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER))
zend_verify_internal_func_info(call->func, ret);
}
#endif
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
EG(current_execute_data) = execute_data;
@ -4153,6 +4157,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
ret = RETURN_VALUE_USED(opline) ? EX_VAR(opline->result.var) : &retval;
ZVAL_NULL(ret);
ZEND_OBSERVER_FCALL_BEGIN(call);
if (!zend_execute_internal) {
/* saves one function call if zend_execute_internal is not used */
fbc->internal_function.handler(call, ret);
@ -4172,6 +4177,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER))
zend_verify_internal_func_info(call->func, ret);
}
#endif
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
EG(current_execute_data) = execute_data;
@ -5782,7 +5788,7 @@ ZEND_VM_COLD_CONST_HANDLER(110, ZEND_CLONE, CONST|TMPVAR|UNUSED|THIS|CV, ANY)
do {
if (OP1_TYPE == IS_CONST ||
(OP1_TYPE != IS_UNUSED && UNEXPECTED(Z_TYPE_P(obj) != IS_OBJECT))) {
if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(obj)) {
if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(obj)) {
obj = Z_REFVAL_P(obj);
if (EXPECTED(Z_TYPE_P(obj) == IS_OBJECT)) {
break;
@ -6301,7 +6307,7 @@ ZEND_VM_HANDLER(73, ZEND_INCLUDE_OR_EVAL, CONST|TMPVAR|CV, ANY, EVAL, SPEC(OBSER
new_op_array->scope = EX(func)->op_array.scope;
call = zend_vm_stack_push_call_frame(
(Z_TYPE_INFO(EX(This)) & ZEND_CALL_HAS_THIS) | ZEND_CALL_NESTED_CODE | ZEND_CALL_HAS_SYMBOL_TABLE,
(Z_TYPE_INFO(EX(This)) & ZEND_CALL_HAS_THIS) | ZEND_CALL_NESTED_CODE | ZEND_CALL_HAS_SYMBOL_TABLE,
(zend_function*)new_op_array, 0,
Z_PTR(EX(This)));
@ -8129,7 +8135,7 @@ ZEND_VM_HANDLER(160, ZEND_YIELD, CONST|TMP|VAR|CV|UNUSED, CONST|TMPVAR|CV|UNUSED
}
} else if (OP1_TYPE == IS_TMP_VAR) {
ZVAL_COPY_VALUE(&generator->value, value);
} else if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(value)) {
} else if ((OP1_TYPE & (IS_VAR|IS_CV)) && Z_ISREF_P(value)) {
ZVAL_COPY(&generator->value, Z_REFVAL_P(value));
FREE_OP1_IF_VAR();
} else {
@ -8364,9 +8370,9 @@ ZEND_VM_HOT_HANDLER(168, ZEND_BIND_GLOBAL, CV, CONST, CACHE_SLOT)
Bucket *p = (Bucket*)((char*)EG(symbol_table).arData + idx);
if (EXPECTED(p->key == varname) ||
(EXPECTED(p->h == ZSTR_H(varname)) &&
EXPECTED(p->key != NULL) &&
EXPECTED(zend_string_equal_content(p->key, varname)))) {
(EXPECTED(p->h == ZSTR_H(varname)) &&
EXPECTED(p->key != NULL) &&
EXPECTED(zend_string_equal_content(p->key, varname)))) {
value = (zval*)p; /* value = &p->val; */
ZEND_VM_C_GOTO(check_indirect);
@ -8710,6 +8716,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER))
}
ZVAL_NULL(ret);
ZEND_OBSERVER_FCALL_BEGIN(call);
if (!zend_execute_internal) {
/* saves one function call if zend_execute_internal is not used */
fbc->internal_function.handler(call, ret);
@ -8729,6 +8736,7 @@ ZEND_VM_HANDLER(158, ZEND_CALL_TRAMPOLINE, ANY, ANY, SPEC(OBSERVER))
zend_verify_internal_func_info(call->func, ret);
}
#endif
ZEND_OBSERVER_FCALL_END(call, EG(exception) ? NULL : ret);
EG(current_execute_data) = call->prev_execute_data;

767
Zend/zend_vm_execute.h generated

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -9527,13 +9527,19 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
}
}
| // EG(current_execute_data) = execute_data;
| MEM_STORE_64_ZTS str, RX, executor_globals, current_execute_data, REG1
if (ZEND_OBSERVER_ENABLED) {
| mov FCARG1x, RX
| EXT_CALL zend_observer_fcall_begin, REG0
| ldr REG0, EX:RX->func // reload
}
| // ZVAL_NULL(EX_VAR(opline->result.var));
| LOAD_ZVAL_ADDR FCARG2x, res_addr
| SET_Z_TYPE_INFO FCARG2x, IS_NULL, TMP1w
| // EG(current_execute_data) = execute_data;
| MEM_STORE_64_ZTS str, RX, executor_globals, current_execute_data, REG1
zend_jit_reset_last_valid_opline();
| // fbc->internal_function.handler(call, ret);
@ -9545,6 +9551,12 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
| blr TMP1
}
if (ZEND_OBSERVER_ENABLED) {
| LOAD_ZVAL_ADDR FCARG2x, res_addr
| mov FCARG1x, RX
| EXT_CALL zend_observer_fcall_end, REG0
}
| // EG(current_execute_data) = execute_data;
| MEM_STORE_64_ZTS str, FP, executor_globals, current_execute_data, REG0

View file

@ -10231,13 +10231,19 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
}
}
| // EG(current_execute_data) = execute_data;
| MEM_STORE_ZTS aword, executor_globals, current_execute_data, RX, r1
if (ZEND_OBSERVER_ENABLED) {
| mov FCARG1a, RX
| EXT_CALL zend_observer_fcall_begin, r0
| mov r0, EX:RX->func // reload
}
| // ZVAL_NULL(EX_VAR(opline->result.var));
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| SET_Z_TYPE_INFO FCARG2a, IS_NULL
| // EG(current_execute_data) = execute_data;
| MEM_STORE_ZTS aword, executor_globals, current_execute_data, RX, r1
zend_jit_reset_last_valid_opline();
| // fbc->internal_function.handler(call, ret);
@ -10248,6 +10254,12 @@ static int zend_jit_do_fcall(dasm_State **Dst, const zend_op *opline, const zend
| call aword [r0 + offsetof(zend_internal_function, handler)]
}
if (ZEND_OBSERVER_ENABLED) {
| LOAD_ZVAL_ADDR FCARG2a, res_addr
| mov FCARG1a, RX
| EXT_CALL zend_observer_fcall_end, r0
}
| // EG(current_execute_data) = execute_data;
| MEM_STORE_ZTS aword, executor_globals, current_execute_data, FP, r0

View file

@ -1245,6 +1245,7 @@ bool pdo_hash_methods(pdo_dbh_object_t *dbh_obj, int kind)
func.function_name = zend_string_init(funcs->fname, strlen(funcs->fname), dbh->is_persistent);
func.scope = dbh_obj->std.ce;
func.prototype = NULL;
ZEND_MAP_PTR(func.run_time_cache) = NULL;
if (funcs->flags) {
func.fn_flags = funcs->flags | ZEND_ACC_NEVER_CACHE;
} else {

View file

@ -264,8 +264,8 @@ static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
zend_observer_remove_begin_handler(&func->op_array, observer_begin);
zend_observer_remove_end_handler(&func->op_array, observer_end);
zend_observer_remove_begin_handler(func, observer_begin);
zend_observer_remove_end_handler(func, observer_end);
}
} ZEND_HASH_FOREACH_END();
}
@ -280,8 +280,8 @@ static ZEND_INI_MH(zend_test_observer_OnUpdateCommaList)
if (stage != PHP_INI_STAGE_STARTUP && stage != PHP_INI_STAGE_ACTIVATE && stage != PHP_INI_STAGE_DEACTIVATE && stage != PHP_INI_STAGE_SHUTDOWN) {
ZEND_HASH_FOREACH_STR_KEY(*p, funcname) {
if ((func = zend_hash_find_ptr(EG(function_table), funcname))) {
zend_observer_add_begin_handler(&func->op_array, observer_begin);
zend_observer_add_end_handler(&func->op_array, observer_end);
zend_observer_add_begin_handler(func, observer_begin);
zend_observer_add_end_handler(func, observer_end);
}
} ZEND_HASH_FOREACH_END();
}

View file

@ -48,38 +48,32 @@ var_dump(foo());
{main} %s%eobserver_backtrace_%d.php
-->
<foo>
<!-- init gen() -->
<!-- init Generator::current() -->
<!--
gen()
Generator::current()
foo()
{main} %s%eobserver_backtrace_%d.php
-->
<gen>
<!-- init TestClass::foo() -->
<Generator::current>
<!-- init gen() -->
<!--
TestClass::foo()
gen()
Generator::current()
foo()
{main} %s%eobserver_backtrace_%d.php
-->
<TestClass::foo>
<!-- init TestClass::{closure}() -->
<gen>
<!-- init TestClass::foo() -->
<!--
TestClass::{closure}()
array_map()
TestClass::foo()
gen()
Generator::current()
foo()
{main} %s%eobserver_backtrace_%d.php
-->
<TestClass::{closure}>
<!-- init TestClass::bar() -->
<TestClass::foo>
<!-- init array_map() -->
<!--
TestClass::bar()
TestClass::{closure}()
array_map()
TestClass::foo()
gen()
@ -87,20 +81,52 @@ var_dump(foo());
foo()
{main} %s%eobserver_backtrace_%d.php
-->
<TestClass::bar>
</TestClass::bar>
</TestClass::{closure}>
<TestClass::{closure}>
<TestClass::bar>
</TestClass::bar>
</TestClass::{closure}>
</TestClass::foo>
</gen>
<array_map>
<!-- 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}>
</array_map>
</TestClass::foo>
</gen>
</Generator::current>
</foo>
<!-- init var_dump() -->
<!--
var_dump()
{main} %s%eobserver_backtrace_%d.php
-->
<var_dump>
array(2) {
[0]=>
int(42)
[1]=>
int(1337)
}
</var_dump>
</file '%s%eobserver_backtrace_%d.php'>

View file

@ -26,30 +26,44 @@ foo();
echo 'DONE' . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_basic_01.php' -->
<file '%s%eobserver_basic_01.php'>
<!-- init '%s%eobserver_basic_%d.php' -->
<file '%s%eobserver_basic_%d.php'>
<!-- init foo() -->
<foo>
Foo
<!-- init bar() -->
<bar>
Bar
<!-- init array_sum() -->
<array_sum>
</array_sum>
<!-- init var_dump() -->
<var_dump>
int(6)
</var_dump>
</bar>
</foo>
<foo>
Foo
<bar>
Bar
<array_sum>
</array_sum>
<var_dump>
int(6)
</var_dump>
</bar>
</foo>
<foo>
Foo
<bar>
Bar
<array_sum>
</array_sum>
<var_dump>
int(6)
</var_dump>
</bar>
</foo>
DONE
</file '%s%eobserver_basic_01.php'>
</file '%s%eobserver_basic_%d.php'>

View file

@ -30,30 +30,44 @@ $test->foo();
echo 'DONE' . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_basic_02.php' -->
<file '%s%eobserver_basic_02.php'>
<!-- init '%s%eobserver_basic_%d.php' -->
<file '%s%eobserver_basic_%d.php'>
<!-- init TestClass::foo() -->
<TestClass::foo>
Foo
<!-- init TestClass::bar() -->
<TestClass::bar>
Bar
<!-- init array_sum() -->
<array_sum>
</array_sum>
<!-- init var_dump() -->
<var_dump>
int(6)
</var_dump>
</TestClass::bar>
</TestClass::foo>
<TestClass::foo>
Foo
<TestClass::bar>
Bar
<array_sum>
</array_sum>
<var_dump>
int(6)
</var_dump>
</TestClass::bar>
</TestClass::foo>
<TestClass::foo>
Foo
<TestClass::bar>
Bar
<array_sum>
</array_sum>
<var_dump>
int(6)
</var_dump>
</TestClass::bar>
</TestClass::foo>
DONE
</file '%s%eobserver_basic_02.php'>
</file '%s%eobserver_basic_%d.php'>

View file

@ -34,6 +34,7 @@ Foo
</foo>
<!-- init bar() -->
Bar
<!-- init ini_set() -->
Foo
<bar>
Bar

View file

@ -25,7 +25,16 @@ call_user_func([$r->getAttributes(A::class)[0], 'newInstance']);
--EXPECTF--
<!-- init '%s' -->
<file '%s'>
<!-- init A::__construct() -->
<A::__construct>
</A::__construct>
<!-- init ReflectionFunction::__construct() -->
<ReflectionFunction::__construct>
</ReflectionFunction::__construct>
<!-- init ReflectionFunctionAbstract::getAttributes() -->
<ReflectionFunctionAbstract::getAttributes>
</ReflectionFunctionAbstract::getAttributes>
<!-- init ReflectionAttribute::newInstance() -->
<ReflectionAttribute::newInstance>
<!-- init A::__construct() -->
<A::__construct>
</A::__construct>
</ReflectionAttribute::newInstance>
</file '%s'>

View file

@ -25,9 +25,24 @@ call_user_func([$r->getAttributes(A::class)[0], 'newInstance']);
--EXPECTF--
<!-- init '%s' -->
<file '%s'>
<!-- init A::__construct() -->
<A::__construct>
<!-- init ReflectionFunction::__construct() -->
<ReflectionFunction::__construct>
</ReflectionFunction::__construct>
<!-- init ReflectionFunctionAbstract::getAttributes() -->
<ReflectionFunctionAbstract::getAttributes>
</ReflectionFunctionAbstract::getAttributes>
<!-- init ReflectionAttribute::newInstance() -->
<ReflectionAttribute::newInstance>
<!-- init A::__construct() -->
<A::__construct>
<!-- init array_map() -->
<array_map>
<!-- init str_repeat() -->
<str_repeat>
Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d
</A::__construct>
</str_repeat>
</array_map>
</A::__construct>
</ReflectionAttribute::newInstance>
</file '%s'>

View file

@ -42,6 +42,8 @@ a();
<d>
</d>
<!-- init bailout() -->
<!-- init array_map() -->
<!-- init str_repeat() -->
Fatal error: Allowed memory size of 20971520 bytes exhausted %s in %s on line %d
</a>

View file

@ -26,14 +26,19 @@ namespace Test {
}
?>
--EXPECTF--
<!-- init '%s%eobserver_call_user_func_%d.php' -->
<file '%s%eobserver_call_user_func_%d.php'>
<!-- init Test\MyClass::myMethod() -->
<Test\MyClass::myMethod>
<!-- init '%s' -->
<file '%s'>
<!-- init call_user_func() -->
<call_user_func>
<!-- init Test\MyClass::myMethod() -->
<Test\MyClass::myMethod>
MyClass::myMethod called
</Test\MyClass::myMethod>
<!-- init Test\my_function() -->
<Test\my_function>
</Test\MyClass::myMethod>
</call_user_func>
<call_user_func>
<!-- init Test\my_function() -->
<Test\my_function>
my_function called
</Test\my_function>
</file '%s%eobserver_call_user_func_%d.php'>
</Test\my_function>
</call_user_func>
</file '%s'>

View file

@ -26,14 +26,19 @@ namespace Test {
}
?>
--EXPECTF--
<!-- init '%s%eobserver_call_user_func_%d.php' -->
<file '%s%eobserver_call_user_func_%d.php'>
<!-- init Test\MyClass::myMethod() -->
<Test\MyClass::myMethod>
<!-- init '%s' -->
<file '%s'>
<!-- init call_user_func_array() -->
<call_user_func_array>
<!-- init Test\MyClass::myMethod() -->
<Test\MyClass::myMethod>
MyClass::myMethod called
</Test\MyClass::myMethod>
<!-- init Test\my_function() -->
<Test\my_function>
</Test\MyClass::myMethod>
</call_user_func_array>
<call_user_func_array>
<!-- init Test\my_function() -->
<Test\my_function>
my_function called
</Test\my_function>
</file '%s%eobserver_call_user_func_%d.php'>
</Test\my_function>
</call_user_func_array>
</file '%s'>

View file

@ -23,27 +23,41 @@ $foo($bar);
echo 'DONE' . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_closure_%d.php' -->
<file '%s%eobserver_closure_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init {closure}() -->
<{closure}>
Answer
<!-- init {closure}() -->
<{closure}>
<!-- init array_sum() -->
<array_sum>
</array_sum>
<!-- init var_dump() -->
<var_dump>
int(42)
</var_dump>
</{closure}>
</{closure}>
<{closure}>
Answer
<{closure}>
<array_sum>
</array_sum>
<var_dump>
int(42)
</var_dump>
</{closure}>
</{closure}>
<{closure}>
Answer
<{closure}>
<array_sum>
</array_sum>
<var_dump>
int(42)
</var_dump>
</{closure}>
</{closure}>
DONE
</file '%s%eobserver_closure_%d.php'>
</file '%s'>

View file

@ -22,11 +22,14 @@ $closure();
echo 'DONE' . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_closure_%d.php' -->
<file '%s%eobserver_closure_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init Closure::fromCallable() -->
<Closure::fromCallable>
</Closure::fromCallable>
<!-- init Foo::bar() -->
<Foo::bar>
Called as fake closure.
</Foo::bar>
DONE
</file '%s%eobserver_closure_%d.php'>
</file '%s'>

View file

@ -19,11 +19,14 @@ foo();
echo 'You should not see this.';
?>
--EXPECTF--
<!-- init '%s%eobserver_error_%d.php' -->
<file '%s%eobserver_error_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init foo() -->
<foo>
<!-- init str_repeat() -->
<str_repeat>
Fatal error: Allowed memory size of 2097152 bytes exhausted%s(tried to allocate %d bytes) in %s on line %d
</str_repeat:NULL>
</foo:NULL>
</file '%s%eobserver_error_%d.php'>

View file

@ -18,11 +18,14 @@ foo();
echo 'You should not see this.';
?>
--EXPECTF--
<!-- init '%s%eobserver_error_%d.php' -->
<file '%s%eobserver_error_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init foo() -->
<foo>
<!-- init trigger_error() -->
<trigger_error>
Fatal error: Foo error in %s on line %d
</trigger_error:NULL>
</foo:NULL>
</file '%s%eobserver_error_%d.php'>
</file '%s'>

View file

@ -36,10 +36,18 @@ echo 'Done.' . PHP_EOL;
<main>
<!-- init foo() -->
<foo>
<!-- init SoapClient::__construct() -->
<SoapClient::__construct>
<!-- Exception: SoapFault -->
</SoapClient::__construct:NULL>
<!-- Exception: SoapFault -->
</foo:NULL>
<!-- Exception: SoapFault -->
</main:NULL>
<!-- init Exception::getMessage() -->
<Exception::getMessage>
</Exception::getMessage:'SOAP-ERROR: Parsing WSDL: Couldn\'t load from \'foo\' : failed to load external entity "foo"
'>
SOAP-ERROR: Parsing WSDL: Couldn't load from 'foo' : failed to load external entity "foo"
Done.

View file

@ -22,14 +22,20 @@ foo();
echo 'You should not see this.';
?>
--EXPECTF--
<!-- init '%s%eobserver_error_%d.php' -->
<file '%s%eobserver_error_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init set_error_handler() -->
<set_error_handler>
</set_error_handler:NULL>
<!-- init foo() -->
<foo>
<!-- init {closure}() -->
<{closure}>
<!-- init trigger_error() -->
<trigger_error>
Fatal error: Foo error in %s on line %d
</trigger_error:NULL>
</{closure}:NULL>
</foo:NULL>
</file '%s%eobserver_error_%d.php'>
</file '%s'>

View file

@ -23,8 +23,8 @@ foo();
echo 'You should not see this' . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_exception_%d.php' -->
<file '%s%eobserver_exception_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init foo() -->
<foo>
Call #0
@ -34,10 +34,19 @@ Call #1
</foo>
<foo>
Call #2
<!-- init Exception::__construct() -->
<Exception::__construct>
</Exception::__construct>
<!-- Exception: RuntimeException -->
</foo>
<!-- Exception: RuntimeException -->
</file '%s%eobserver_exception_%d.php'>
</file '%s'>
<!-- init Exception::__toString() -->
<Exception::__toString>
<!-- init Exception::getTraceAsString() -->
<Exception::getTraceAsString>
</Exception::getTraceAsString>
</Exception::__toString>
Fatal error: Uncaught RuntimeException: Third time is a charm in %s%eobserver_exception_%d.php:%d
Stack trace:

View file

@ -19,13 +19,17 @@ $fiber->resume();
?>
--EXPECTF--
<!-- init '%sobserver_fiber_01.php' -->
<!-- init '%s' -->
<!-- init Fiber::__construct() -->
<!-- init Fiber::start() -->
<!-- alloc: %s -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<!-- init Fiber::suspend() -->
<!-- switching from fiber %s to %s -->
<suspend '%s'>
<!-- init Fiber::resume() -->
<!-- switching from fiber %s to %s -->
<resume '%s'>
<!-- switching from fiber %s to %s -->

View file

@ -16,10 +16,13 @@ $fiber->start();
?>
--EXPECTF--
<!-- init '%sobserver_fiber_02.php' -->
<!-- init '%s' -->
<!-- init Fiber::__construct() -->
<!-- init Fiber::start() -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<!-- init Fiber::suspend() -->
<!-- switching from fiber %s to %s -->
<suspend '%s'>
<!-- switching from fiber %s to %s -->

View file

@ -39,14 +39,19 @@ $fiber->resume();
?>
--EXPECTF--
<!-- init '%sobserver_fiber_03.php' -->
<!-- init '%s' -->
<!-- init Fiber::__construct() -->
<!-- init Fiber::start() -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<!-- init Fiber::suspend() -->
<!-- switching from fiber %s to %s -->
<suspend '%s'>
<!-- init Fiber::resume() -->
<!-- switching from fiber %s to %s -->
<resume '%s'>
<!-- init var_dump() -->
int(1)
<!-- switching from fiber %s to %s -->
<init '%s'>

View file

@ -26,12 +26,16 @@ $fiber->resume();
?>
--EXPECTF--
<!-- init '%sobserver_fiber_04.php' -->
<!-- init '%s' -->
<!-- init Fiber::__construct() -->
<!-- init Fiber::start() -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<!-- init Fiber::suspend() -->
<!-- switching from fiber %s to %s -->
<suspend '%s'>
<!-- init Fiber::resume() -->
<!-- switching from fiber %s to %s -->
<resume '%s'>
<!-- switching from fiber %s to %s -->

View file

@ -25,12 +25,16 @@ $fiber->resume();
?>
--EXPECTF--
<!-- init '%sobserver_fiber_05.php' -->
<!-- init '%s' -->
<!-- init Fiber::__construct() -->
<!-- init Fiber::start() -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<!-- init Fiber::suspend() -->
<!-- switching from fiber %s to %s -->
<suspend '%s'>
<!-- init Fiber::resume() -->
<!-- switching from fiber %s to %s -->
<resume '%s'>
<!-- switching from fiber %s to %s -->

View file

@ -22,12 +22,17 @@ try {
?>
--EXPECTF--
<!-- init '%sobserver_fiber_06.php' -->
<!-- init '%s' -->
<!-- init Fiber::__construct() -->
<!-- init Fiber::start() -->
<!-- switching from fiber %s to %s -->
<init '%s'>
<!-- init {closure}() -->
<!-- init Fiber::suspend() -->
<!-- switching from fiber %s to %s -->
<suspend '%s'>
<!-- init Exception::__construct() -->
<!-- init Fiber::throw() -->
<!-- switching from fiber %s to %s -->
<resume '%s'>
<!-- switching from fiber %s to %s -->

View file

@ -28,8 +28,8 @@ function doSomething() {
echo doSomething() . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_generator_%d.php' -->
<file '%s%eobserver_generator_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init doSomething() -->
<doSomething>
<!-- init getResults() -->
@ -44,7 +44,10 @@ echo doSomething() . PHP_EOL;
12
<getResults>
</getResults:1337>
<!-- init Generator::getReturn() -->
<Generator::getReturn>
</Generator::getReturn:1337>
1337
</doSomething:'Done'>
Done
</file '%s%eobserver_generator_%d.php'>
</file '%s'>

View file

@ -33,32 +33,87 @@ function doSomething() {
echo doSomething() . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_generator_%d.php' -->
<file '%s%eobserver_generator_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init doSomething() -->
<doSomething>
<!-- init fooResults() -->
<fooResults>
<!-- init Generator::current() -->
<Generator::current>
<!-- init fooResults() -->
<fooResults>
Starting generator
</fooResults:0>
</fooResults:0>
</Generator::current:0>
<Generator::current>
</Generator::current:0>
0
<fooResults>
</fooResults:1>
<Generator::current>
</Generator::current:0>
<!-- init Generator::next() -->
<Generator::next>
<fooResults>
</fooResults:1>
</Generator::next:NULL>
<Generator::current>
</Generator::current:1>
<Generator::current>
</Generator::current:1>
1
<fooResults>
</fooResults:2>
<Generator::current>
</Generator::current:1>
<Generator::next>
<fooResults>
</fooResults:2>
</Generator::next:NULL>
<Generator::current>
</Generator::current:2>
<Generator::current>
</Generator::current:2>
2
<fooResults>
</fooResults:3>
<Generator::current>
</Generator::current:2>
<Generator::next>
<fooResults>
</fooResults:3>
</Generator::next:NULL>
<Generator::current>
</Generator::current:3>
<Generator::current>
</Generator::current:3>
3
<fooResults>
</fooResults:4>
<Generator::current>
</Generator::current:3>
<Generator::next>
<fooResults>
</fooResults:4>
</Generator::next:NULL>
<Generator::current>
</Generator::current:4>
<Generator::current>
</Generator::current:4>
4
<fooResults>
</fooResults:5>
<Generator::current>
</Generator::current:4>
<Generator::next>
<fooResults>
</fooResults:5>
</Generator::next:NULL>
<Generator::current>
</Generator::current:5>
<Generator::current>
</Generator::current:5>
5
<fooResults>
</fooResults:NULL>
<Generator::current>
</Generator::current:5>
<!-- init Generator::send() -->
<Generator::send>
<fooResults>
</fooResults:NULL>
</Generator::send:NULL>
<Generator::next>
</Generator::next:NULL>
<Generator::current>
</Generator::current:NULL>
</doSomething:'Done'>
Done
</file '%s%eobserver_generator_%d.php'>
</file '%s'>

View file

@ -26,8 +26,8 @@ function doSomething() {
echo doSomething() . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_generator_%d.php' -->
<file '%s%eobserver_generator_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init doSomething() -->
<doSomething>
<!-- init fooResults() -->
@ -38,12 +38,27 @@ echo doSomething() . PHP_EOL;
</fooResults:1>
1
<fooResults>
<!-- init Exception::__construct() -->
<Exception::__construct>
</Exception::__construct:NULL>
<!-- Exception: RuntimeException -->
</fooResults:NULL>
<!-- Exception: RuntimeException -->
</doSomething:NULL>
<!-- Exception: RuntimeException -->
</file '%s%eobserver_generator_%d.php'>
</file '%s'>
<!-- init Exception::__toString() -->
<Exception::__toString>
<!-- init Exception::getTraceAsString() -->
<Exception::getTraceAsString>
</Exception::getTraceAsString:'#0 %s(%d): fooResults()
#1 %s(%d): doSomething()
#2 {main}'>
</Exception::__toString:'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}'>
Fatal error: Uncaught RuntimeException: Oops! in %s%eobserver_generator_%d.php:%d
Stack trace:

View file

@ -20,9 +20,9 @@ echo array_sum([1,2,3]) . PHP_EOL;
foo();
?>
--EXPECTF--
<!-- init '%s%eobserver_opline_%d.php' -->
<!-- init '%s' -->
<!-- opcode: 'ZEND_INIT_FCALL' -->
<file '%s%eobserver_opline_%d.php'>
<file '%s'>
<!-- opcode: 'ZEND_INIT_FCALL' -->
<!-- init foo() -->
<!-- opcode: 'ZEND_ECHO' -->
@ -31,9 +31,9 @@ foo();
Foo
<!-- opcode: 'ZEND_RETURN' -->
</foo>
<!-- init '%s%eobserver.inc' -->
<!-- init '%s' -->
<!-- opcode: 'ZEND_INIT_FCALL' -->
<file '%s%eobserver.inc'>
<file '%s'>
<!-- opcode: 'ZEND_INIT_FCALL' -->
<!-- init foo_observer_test() -->
<!-- opcode: 'ZEND_ECHO' -->
@ -43,7 +43,13 @@ foo_observer_test
<!-- opcode: 'ZEND_RETURN' -->
</foo_observer_test>
<!-- opcode: 'ZEND_RETURN' -->
</file '%s%eobserver.inc'>
</file '%s'>
<!-- init array_sum() -->
<!-- opcode: 'ZEND_RETURN' -->
<array_sum>
<!-- opcode: 'ZEND_RETURN' -->
<!-- opcode: 'ZEND_RETURN' -->
</array_sum>
6
<foo>
<!-- opcode: 'ZEND_ECHO' -->
@ -51,4 +57,4 @@ Foo
<!-- opcode: 'ZEND_RETURN' -->
</foo>
<!-- opcode: 'ZEND_RETURN' -->
</file '%s%eobserver_opline_%d.php'>
</file '%s'>

View file

@ -21,12 +21,20 @@ $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!'>
<!-- init '%s' -->
<file '%s'>
<!-- init Generator::current() -->
<Generator::current>
<!-- init foo() -->
<foo>
</foo:'I should be observable'>
</Generator::current:'I should be observable'>
<!-- init Generator::next() -->
<Generator::next>
<foo>
</foo:'Me too!'>
</Generator::next:NULL>
<Generator::current>
</Generator::current:'Me too!'>
Done
</file '%s%eobserver_retval_%d.php'>
</file '%s'>

View file

@ -24,13 +24,16 @@ function foo() {
echo 'Done: ' . bar(40) . PHP_EOL;
?>
--EXPECTF--
<!-- init '%s%eobserver_shutdown_%d.php' -->
<file '%s%eobserver_shutdown_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init register_shutdown_function() -->
<register_shutdown_function>
</register_shutdown_function:NULL>
<!-- init bar() -->
<bar>
</bar:40>
Done: 40
</file '%s%eobserver_shutdown_%d.php'>
</file '%s'>
<!-- init {closure}() -->
<{closure}>
<!-- init foo() -->

View file

@ -12,14 +12,24 @@ function foo(array $a) { return 1; }
foo(42);
?>
--EXPECTF--
<!-- init '%s%eobserver_types_%d.php' -->
<file '%s%eobserver_types_%d.php'>
<!-- init '%s' -->
<file '%s'>
<!-- init foo() -->
<foo>
<!-- Exception: TypeError -->
</foo:NULL>
<!-- Exception: TypeError -->
</file '%s%eobserver_types_%d.php'>
</file '%s'>
<!-- init Error::__toString() -->
<Error::__toString>
<!-- init Error::getTraceAsString() -->
<Error::getTraceAsString>
</Error::getTraceAsString:'#0 %s(%d): foo(42)
#1 {main}'>
</Error::__toString:'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}'>
Fatal error: Uncaught TypeError: foo(): Argument #1 ($a) must be of type array, int given, called in %s:%d
Stack trace:

View file

@ -18,19 +18,25 @@ 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>
<!-- init '%s' -->
<file '%s'>
<!-- init array_reduce() -->
<array_reduce>
<!-- init sum() -->
<sum>
</sum>
<sum>
</sum>
<sum>
</sum>
<sum>
</sum>
<sum>
</sum>
</array_reduce>
<!-- init var_dump() -->
<var_dump>
int(15)
</var_dump>
Done
</file '%s%eobserver_zend_call_function_%d.php'>
</file '%s'>