diff options
Diffstat (limited to 'Zend/zend_generators.c')
-rw-r--r-- | Zend/zend_generators.c | 268 |
1 files changed, 189 insertions, 79 deletions
diff --git a/Zend/zend_generators.c b/Zend/zend_generators.c index f1cc09e938..33a03ce7cc 100644 --- a/Zend/zend_generators.c +++ b/Zend/zend_generators.c @@ -31,6 +31,71 @@ static zend_object_handlers zend_generator_handlers; static zend_object *zend_generator_create(zend_class_entry *class_type); +static void zend_restore_call_stack(zend_generator *generator) /* {{{ */ +{ + zend_execute_data *call, *new_call, *prev_call = NULL; + + call = generator->frozen_call_stack; + do { + new_call = zend_vm_stack_push_call_frame( + (ZEND_CALL_INFO(call) & ~ZEND_CALL_ALLOCATED), + call->func, + ZEND_CALL_NUM_ARGS(call), + (Z_TYPE(call->This) == IS_UNDEF) ? + (zend_class_entry*)Z_OBJ(call->This) : NULL, + (Z_TYPE(call->This) != IS_UNDEF) ? + Z_OBJ(call->This) : NULL); + memcpy(((zval*)new_call) + ZEND_CALL_FRAME_SLOT, ((zval*)call) + ZEND_CALL_FRAME_SLOT, ZEND_CALL_NUM_ARGS(call) * sizeof(zval)); + new_call->prev_execute_data = prev_call; + prev_call = new_call; + + call = call->prev_execute_data; + } while (call); + generator->execute_data->call = prev_call; + efree(generator->frozen_call_stack); + generator->frozen_call_stack = NULL; +} +/* }}} */ + +static zend_execute_data* zend_freeze_call_stack(zend_execute_data *execute_data) /* {{{ */ +{ + size_t used_stack; + zend_execute_data *call, *new_call, *prev_call = NULL; + zval *stack; + + /* calculate required stack size */ + used_stack = 0; + call = EX(call); + do { + used_stack += ZEND_CALL_FRAME_SLOT + ZEND_CALL_NUM_ARGS(call); + call = call->prev_execute_data; + } while (call); + + stack = emalloc(used_stack * sizeof(zval)); + + /* save stack, linking frames in reverse order */ + call = EX(call); + do { + size_t frame_size = ZEND_CALL_FRAME_SLOT + ZEND_CALL_NUM_ARGS(call); + + new_call = (zend_execute_data*)(stack + used_stack - frame_size); + memcpy(new_call, call, frame_size * sizeof(zval)); + used_stack -= frame_size; + new_call->prev_execute_data = prev_call; + prev_call = new_call; + + new_call = call->prev_execute_data; + zend_vm_stack_free_call_frame(call); + call = new_call; + } while (call); + + execute_data->call = NULL; + ZEND_ASSERT(prev_call == (zend_execute_data*)stack); + + return prev_call; +} +/* }}} */ + static void zend_generator_cleanup_unfinished_execution( zend_generator *generator, uint32_t catch_op_num) /* {{{ */ { @@ -40,21 +105,10 @@ static void zend_generator_cleanup_unfinished_execution( /* -1 required because we want the last run opcode, not the next to-be-run one. */ uint32_t op_num = execute_data->opline - execute_data->func->op_array.opcodes - 1; - /* There may be calls to zend_vm_stack_free_call_frame(), which modifies the VM stack - * globals, so need to load/restore those. */ - zend_vm_stack original_stack = EG(vm_stack); - original_stack->top = EG(vm_stack_top); - EG(vm_stack_top) = generator->stack->top; - EG(vm_stack_end) = generator->stack->end; - EG(vm_stack) = generator->stack; - - zend_cleanup_unfinished_execution(execute_data, op_num, catch_op_num); - - generator->stack = EG(vm_stack); - generator->stack->top = EG(vm_stack_top); - EG(vm_stack_top) = original_stack->top; - EG(vm_stack_end) = original_stack->end; - EG(vm_stack) = original_stack; + if (UNEXPECTED(generator->frozen_call_stack)) { + zend_restore_call_stack(generator); + } + zend_cleanup_unfinished_execution(execute_data, op_num, 0); } } /* }}} */ @@ -64,13 +118,13 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished if (EXPECTED(generator->execute_data)) { zend_execute_data *execute_data = generator->execute_data; - if (execute_data->symbol_table) { + if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) { zend_clean_and_cache_symbol_table(execute_data->symbol_table); } /* always free the CV's, in the symtable are only not-free'd IS_INDIRECT's */ zend_free_compiled_variables(execute_data); - if (Z_OBJ(execute_data->This)) { + if (EX_CALL_INFO() & ZEND_CALL_RELEASE_THIS) { OBJ_RELEASE(Z_OBJ(execute_data->This)); } @@ -94,7 +148,13 @@ ZEND_API void zend_generator_close(zend_generator *generator, zend_bool finished OBJ_RELEASE((zend_object *) EX(func)->common.prototype); } - efree(generator->stack); + /* Free GC buffer. GC for closed generators doesn't need an allocated buffer */ + if (generator->gc_buffer) { + efree(generator->gc_buffer); + generator->gc_buffer = NULL; + } + + efree(generator->execute_data); generator->execute_data = NULL; } } @@ -194,12 +254,103 @@ static void zend_generator_free_storage(zend_object *object) /* {{{ */ } /* }}} */ +static uint32_t calc_gc_buffer_size(zend_generator *generator) /* {{{ */ +{ + uint32_t size = 4; /* value, key, retval, values */ + if (generator->execute_data) { + zend_execute_data *execute_data = generator->execute_data; + zend_op_array *op_array = &EX(func)->op_array; + + /* Compiled variables */ + if (!(EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE)) { + size += op_array->last_var; + } + /* Extra args */ + if (EX_CALL_INFO() & ZEND_CALL_FREE_EXTRA_ARGS) { + size += EX_NUM_ARGS() - op_array->num_args; + } + size += Z_TYPE(execute_data->This) == IS_OBJECT; /* $this */ + size += (EX_CALL_INFO() & ZEND_CALL_CLOSURE) != 0; /* Closure object */ + + /* Yield from root references */ + if (generator->node.children == 0) { + zend_generator *child = generator, *root = generator->node.ptr.root; + while (root != child) { + child = child->node.parent; + size++; + } + } + } + return size; +} +/* }}} */ + static HashTable *zend_generator_get_gc(zval *object, zval **table, int *n) /* {{{ */ { zend_generator *generator = (zend_generator*) Z_OBJ_P(object); - *table = &generator->value; - *n = 3; - return NULL; + zend_execute_data *execute_data = generator->execute_data; + zend_op_array *op_array; + zval *gc_buffer; + uint32_t gc_buffer_size; + + if (!execute_data) { + /* If the generator has been closed, it can only hold on to three values: The value, key + * and retval. These three zvals are stored sequentially starting at &generator->value. */ + *table = &generator->value; + *n = 3; + return NULL; + } + + op_array = &EX(func)->op_array; + gc_buffer_size = calc_gc_buffer_size(generator); + if (generator->gc_buffer_size < gc_buffer_size) { + generator->gc_buffer = safe_erealloc(generator->gc_buffer, sizeof(zval), gc_buffer_size, 0); + generator->gc_buffer_size = gc_buffer_size; + } + + *n = gc_buffer_size; + *table = gc_buffer = generator->gc_buffer; + + ZVAL_COPY_VALUE(gc_buffer++, &generator->value); + ZVAL_COPY_VALUE(gc_buffer++, &generator->key); + ZVAL_COPY_VALUE(gc_buffer++, &generator->retval); + ZVAL_COPY_VALUE(gc_buffer++, &generator->values); + + if (!(EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE)) { + uint32_t i, num_cvs = EX(func)->op_array.last_var; + for (i = 0; i < num_cvs; i++) { + ZVAL_COPY_VALUE(gc_buffer++, EX_VAR_NUM(i)); + } + } + + if (EX_CALL_INFO() & ZEND_CALL_FREE_EXTRA_ARGS) { + zval *zv = EX_VAR_NUM(op_array->last_var + op_array->T); + zval *end = zv + (EX_NUM_ARGS() - op_array->num_args); + while (zv != end) { + ZVAL_COPY_VALUE(gc_buffer++, zv++); + } + } + + if (Z_TYPE(execute_data->This) == IS_OBJECT) { + ZVAL_OBJ(gc_buffer++, Z_OBJ(execute_data->This)); + } + if (EX_CALL_INFO() & ZEND_CALL_CLOSURE) { + ZVAL_OBJ(gc_buffer++, (zend_object *) EX(func)->common.prototype); + } + + if (generator->node.children == 0) { + zend_generator *child = generator, *root = generator->node.ptr.root; + while (root != child) { + child = child->node.parent; + ZVAL_OBJ(gc_buffer++, &child->std); + } + } + + if (EX_CALL_INFO() & ZEND_CALL_HAS_SYMBOL_TABLE) { + return execute_data->symbol_table; + } else { + return NULL; + } } /* }}} */ @@ -228,44 +379,6 @@ static zend_object *zend_generator_create(zend_class_entry *class_type) /* {{{ * } /* }}} */ -/* Requires globals EG(scope), EG(This) and EG(current_execute_data). */ -ZEND_API void zend_generator_create_zval(zend_execute_data *call, zend_op_array *op_array, zval *return_value) /* {{{ */ -{ - zend_generator *generator; - zend_execute_data *current_execute_data; - zend_execute_data *execute_data; - zend_vm_stack current_stack = EG(vm_stack); - - current_stack->top = EG(vm_stack_top); - - /* Create new execution context. We have to back up and restore EG(current_execute_data) here. */ - current_execute_data = EG(current_execute_data); - execute_data = zend_create_generator_execute_data(call, op_array, return_value); - EG(current_execute_data) = current_execute_data; - - object_init_ex(return_value, zend_ce_generator); - - if (Z_OBJ(call->This)) { - Z_ADDREF(call->This); - } - - /* Save execution context in generator object. */ - generator = (zend_generator *) Z_OBJ_P(return_value); - generator->execute_data = execute_data; - generator->stack = EG(vm_stack); - generator->stack->top = EG(vm_stack_top); - EG(vm_stack_top) = current_stack->top; - EG(vm_stack_end) = current_stack->end; - EG(vm_stack) = current_stack; - - /* EX(return_value) keeps pointer to zend_object (not a real zval) */ - execute_data->return_value = (zval*)generator; - - memset(&generator->execute_fake, 0, sizeof(zend_execute_data)); - Z_OBJ(generator->execute_fake.This) = (zend_object *) generator; -} -/* }}} */ - static ZEND_COLD zend_function *zend_generator_get_constructor(zend_object *object) /* {{{ */ { zend_throw_error(NULL, "The \"Generator\" class is reserved for internal use and cannot be manually instantiated"); @@ -276,7 +389,7 @@ static ZEND_COLD zend_function *zend_generator_get_constructor(zend_object *obje ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_data *ptr) { - if (!ptr->func && Z_OBJ(ptr->This)) { + if (!ptr->func && Z_TYPE(ptr->This) == IS_OBJECT) { if (Z_OBJCE(ptr->This) == zend_ce_generator) { zend_generator *generator = (zend_generator *) Z_OBJ(ptr->This); zend_generator *root = (generator->node.children < 1 ? generator : generator->node.ptr.leaf)->node.ptr.root; @@ -297,6 +410,8 @@ ZEND_API zend_execute_data *zend_generator_check_placeholder_frame(zend_execute_ static void zend_generator_throw_exception(zend_generator *generator, zval *exception) { + zend_execute_data *original_execute_data = EG(current_execute_data); + /* if we don't stop an array/iterator yield from, the exception will only reach the generator after the values were all iterated over */ if (UNEXPECTED(Z_TYPE(generator->values) != IS_UNDEF)) { zval_ptr_dtor(&generator->values); @@ -305,7 +420,6 @@ static void zend_generator_throw_exception(zend_generator *generator, zval *exce /* Throw the exception in the context of the generator. Decrementing the opline * to pretend the exception happened during the YIELD opcode. */ - zend_execute_data *original_execute_data = EG(current_execute_data); EG(current_execute_data) = generator->execute_data; generator->execute_data->opline--; if (exception) { @@ -676,16 +790,9 @@ try_again: { /* Backup executor globals */ zend_execute_data *original_execute_data = EG(current_execute_data); - zend_class_entry *original_scope = EG(scope); - zend_vm_stack original_stack = EG(vm_stack); - original_stack->top = EG(vm_stack_top); /* Set executor globals */ EG(current_execute_data) = generator->execute_data; - EG(scope) = generator->execute_data->func->common.scope; - EG(vm_stack_top) = generator->stack->top; - EG(vm_stack_end) = generator->stack->end; - EG(vm_stack) = generator->stack; /* We want the backtrace to look as if the generator function was * called from whatever method we are current running (e.g. next()). @@ -699,23 +806,25 @@ try_again: orig_generator->execute_fake.prev_execute_data = original_execute_data; } + if (UNEXPECTED(generator->frozen_call_stack)) { + /* Restore frozen call-stack */ + zend_restore_call_stack(generator); + } + /* Resume execution */ generator->flags |= ZEND_GENERATOR_CURRENTLY_RUNNING; zend_execute_ex(generator->execute_data); generator->flags &= ~ZEND_GENERATOR_CURRENTLY_RUNNING; - /* Unlink generator call_frame from the caller and backup vm_stack_top */ - if (EXPECTED(generator->execute_data)) { - generator->stack = EG(vm_stack); - generator->stack->top = EG(vm_stack_top); + generator->frozen_call_stack = NULL; + if (EXPECTED(generator->execute_data) && + UNEXPECTED(generator->execute_data->call)) { + /* Frize call-stack */ + generator->frozen_call_stack = zend_freeze_call_stack(generator->execute_data); } /* Restore executor globals */ EG(current_execute_data) = original_execute_data; - EG(scope) = original_scope; - EG(vm_stack_top) = original_stack->top; - EG(vm_stack_end) = original_stack->end; - EG(vm_stack) = original_stack; /* If an exception was thrown in the generator we have to internally * rethrow it in the parent scope. @@ -741,7 +850,7 @@ try_again: } /* }}} */ -static void inline zend_generator_ensure_initialized(zend_generator *generator) /* {{{ */ +static inline void zend_generator_ensure_initialized(zend_generator *generator) /* {{{ */ { if (UNEXPECTED(Z_TYPE(generator->value) == IS_UNDEF) && EXPECTED(generator->execute_data) && EXPECTED(generator->node.parent == NULL)) { generator->flags |= ZEND_GENERATOR_DO_INIT; @@ -752,7 +861,7 @@ static void inline zend_generator_ensure_initialized(zend_generator *generator) } /* }}} */ -static void inline zend_generator_rewind(zend_generator *generator) /* {{{ */ +static inline void zend_generator_rewind(zend_generator *generator) /* {{{ */ { zend_generator_ensure_initialized(generator); @@ -1075,7 +1184,8 @@ static zend_object_iterator_funcs zend_generator_iterator_functions = { zend_generator_iterator_get_data, zend_generator_iterator_get_key, zend_generator_iterator_move_forward, - zend_generator_iterator_rewind + zend_generator_iterator_rewind, + NULL }; zend_object_iterator *zend_generator_get_iterator(zend_class_entry *ce, zval *object, int by_ref) /* {{{ */ |