Consider the following small test case where $y is first made to be a
reference to $x. And next, we assign a literal to $y.
Example #1:
<?php
$x = 5;
$y = &$x;
$y = null; <---
When we come to the third assignment statement above we try to increase
(followed quickly by a decrease) of the "refcount" on the right hand
side ZVAL (the null zval) in the following piece of code in
zend_assign_to_variable():
} else if (PZVAL_IS_REF(variable_ptr)) {
if (variable_ptr!=value) {
zend_uint refcount = variable_ptr->refcount;
zval garbage;
if (type!=IS_TMP_VAR) {
value->refcount++; <----- incrementing the refcount
of RHS zval
}
garbage = *variable_ptr;
*variable_ptr = *value;
variable_ptr->refcount = refcount;
variable_ptr->is_ref = 1;
if (type!=IS_TMP_VAR) {
zendi_zval_copy_ctor(*variable_ptr);
value->refcount--; <------ and then decrementing
it back...
}
zendi_zval_dtor(garbage);
}
}
I am trying to understand the rationale for value->refcount++ and
value->refcount-- above. Why do we need that?
With PHP running in apache+APC environment, the "null" literal is an
IS_CONST operand in the op_array in APC (shared memory). And
incrementing/decrementing a ref count on the shared memory area without
a mutex can cause unexpected problems due to race conditions (and
eventually lead to memory corruptions etc.)
Recall that zend_opcode.c:pass_two() sets "is_ref" on all IS_CONST
operands to 1, as a way to allow op_arrays to be sharable across
processes. And indeed, in the simpler case of an assignment such as in
the following example (#2), zend_assign_to_variable() notices that the
RHS has "is_ref" set and goes on to make a copy (rather than
incrementing the RHS's refcount).
Example #2:
<?php
$a = 5;
But in the case the LHS is itself an "is_ref" (like in the example #1)
it doesn't seem to make a similar check on the RHS (to see if it is safe
to modify the refcount on the RHS). Why?
regards,
Kannan