-
Notifications
You must be signed in to change notification settings - Fork 7.8k
Delay freeing of overwritten values in assignments #10606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
||
class Test { | ||
function __destruct() { | ||
$GLOBALS['a'] = null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in each __destruct
I would suggest to add echo "D\n";
at the start to make sure the destruction is really called at the time when expected/needed to test
I guess this also fixes GH-10582 given the assign_dim_ref test. I'll have a closer look tonight and also run some static analysis tools. Thanks for working on this. |
6694d28
to
6936692
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks good to me. Since this targets master anyway, my preference goes to the non-EG solution for the write_property case because it is more explicit and feels less dirty.
The only minor nit I have is that some code repetition could be avoided by creating 2 new zend_always_inline functions to cleanup the garbage; because the following two patterns are copied a lot:
if (garbage) {
if (GC_DELREF(garbage) == 0) {
rc_dtor_func(garbage);
} else {
gc_check_possible_root_no_ref(garbage);
}
}
and the same for the other case:
if (garbage) {
if (GC_DELREF(garbage) == 0) {
rc_dtor_func(garbage);
} else {
gc_check_possible_root(garbage);
}
}
With those helper functions you can always just unconditionally call them and they will perform the checks and cleanups.
Since they would be always inline it shouldn't matter for the codegen.
I definitely don't like 6694d28 9c7f0aa seems like a better compromise, but it introduces a "state" and in general some Similar may be achieved without a "state", but this may be less efficient. diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c
index 20d773e1d3..cdca0cdf69 100644
--- a/Zend/zend_object_handlers.c
+++ b/Zend/zend_object_handlers.c
@@ -837,8 +837,29 @@ ZEND_API zval *zend_std_write_property(zend_object *zobj, zend_string *name, zva
}
found:
- variable_ptr = zend_assign_to_variable(
- variable_ptr, value, IS_TMP_VAR, property_uses_strict_types());
+ zend_refcounted *garbage = NULL;
+
+ variable_ptr = zend_assign_to_variable_ex(
+ variable_ptr, value, IS_TMP_VAR, property_uses_strict_types(), &garbage);
+
+ if (garbage) {
+ zend_execute_data *execute_data = EG(current_execute_data);
+
+ if (execute_data
+ && EX(func)
+ && ZEND_USER_CODE(EX(func)->common.type)
+ && EX(opline)
+ && EX(opline)->opcode == ZEND_ASSIGN_OBJ
+ && EX(opline)->result_type) {
+ ZVAL_COPY_DEREF(EX_VAR(EX(opline)->result.var), variable_ptr);
+ variable_ptr = NULL;
+ }
+ if (GC_DELREF(garbage) == 0) {
+ rc_dtor_func(garbage);
+ } else {
+ gc_check_possible_root_no_ref(garbage);
+ }
+ }
goto exit;
}
if (Z_PROP_FLAG_P(variable_ptr) == IS_PROP_UNINIT) {
diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h
index 7323b5d9a1..facdd09611 100644
--- a/Zend/zend_vm_def.h
+++ b/Zend/zend_vm_def.h
@@ -2494,7 +2494,7 @@ ZEND_VM_C_LABEL(fast_assign_obj):
}
ZEND_VM_C_LABEL(free_and_exit_assign_obj):
- if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
+ if (UNEXPECTED(RETURN_VALUE_USED(opline)) && value) {
ZVAL_COPY_DEREF(EX_VAR(opline->result.var), value);
}
FREE_OP_DATA(); All three solutions are not ideal. May be you have any new ideas? |
@dstogov Thank you for your suggestion!
Is it safe to assume
Do we need this check? I think this is only necessary if Depending on how often the garbage is owned by the property (i.e. being the only reference) a Another idea I tried was to pass the |
Yes. We have similar check in few other places (e.g. ext/opcache/ZendAccelerator.c)
No. write_property() may emit error or throw exception so EX(opline) must be up to date.
I'm not sure what do you mean. Tty to construct a test case.
Unfortunately yes. For internal function we don't set EX(opline).
Remove these lines, run
Good idea.
I also thought about storing EX_VAR(opline->result.var) in EG(assign_result). I don't like this, but this may simplify some other places. |
As an extension maintainer, I'll certainly grumble a little about breaking the signature of write_property, but I rather have a clean API where I assign a As such I would oppose your proposal @dstogov. |
@bwoebi see my comment...
and the following... We are trying to find a different solution to keep the signature of |
At this point I don't have a different idea.
Of course!
My comment doesn't make sense 🙂
Ah, so it's uninitialized memory. Ok. I would also prefer to adjust
|
This |
Ok, I will create a patch for this so we can compare the solutions. |
d77d08b
to
1510191
Compare
I think this may be accepted. |
I don't think a review is coming for this one 🙂 How shall we move forward? |
I see 4 test failures with tracing JIT
Let me try to fix them and take the final decision. |
Nope, that's all the ones I'm seeing too. |
1510191
to
94392ec
Compare
This was merged into master. @nielsdos Thank you also for your help! |
Fixes GH-10168
@dstogov Your patch looks good. The path for references was missing (4c580af). This PR also contains the adjustment for
write_property
. If you think these are too many changes I drop that commit (6694d28).One alternative solution for
write_property
would be to utilizeEG
.delay_garbage
would signal that the garbage should be stored inEG(garbage)
instead of being released directly. That would avoid the additional parameter. I think that should work too, assuming we can avoid running userland code between storing and readingEG(garbage)
that could potentially overwrite it. Edit: Implemented here for comparison: 9c7f0aa/cc @nielsdos