Skip to content

Shim standard property handlers, in an attempt to move ThreadSafe behaviour closer to regular objects #149

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

Open
wants to merge 10 commits into
base: php/8.4
Choose a base branch
from
2 changes: 1 addition & 1 deletion classes/thread_safe.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ ThreadSafe_method(synchronized)

if (pmmpthread_monitor_lock(&threaded->monitor)) {
/* synchronize property tables */
pmmpthread_store_sync_local_properties(Z_OBJ_P(getThis()));
pmmpthread_store_clean_stale_cache(Z_OBJ_P(getThis()));

zend_try {
/* call the closure */
Expand Down
6 changes: 5 additions & 1 deletion src/globals.c
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,11 @@ zend_bool pmmpthread_globals_init(){
(dtor_func_t)pmmpthread_globals_string_dtor_func,
1
);
ZVAL_UNDEF(&PMMPTHREAD_G(undef_zval));

ZVAL_UNDEF(&PMMPTHREAD_G(uninitialized_property));
Z_PROP_FLAG_P(&PMMPTHREAD_G(uninitialized_property)) |= IS_PROP_UNINIT;
ZVAL_UNDEF(&PMMPTHREAD_G(unset_property));

PMMPTHREAD_G(thread_count) = 0; //only counting threads explicitly created by pmmpthread
}

Expand Down
3 changes: 2 additions & 1 deletion src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ struct _pmmpthread_globals {
*/
HashTable interned_strings;

zval undef_zval;
zval uninitialized_property;
zval unset_property;

/*
* High Frequency Strings
Expand Down
146 changes: 83 additions & 63 deletions src/handlers.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ zval *pmmpthread_get_property_ptr_ptr_stub(zend_object *object, zend_string *mem

/* {{{ */
zval* pmmpthread_read_dimension(PMMPTHREAD_READ_DIMENSION_PASSTHRU_D) {
if (pmmpthread_store_read(object, member, type, rv) == FAILURE) {
if (pmmpthread_store_read(object, member, NULL, type, rv) == FAILURE) {
//TODO: this ought to generate warnings, but this is a pain right now due to key type juggling
//for now this maintains the v4 behaviour of silently generating NULL, which is better than segfaulting
if (!EG(exception)) {
Expand All @@ -81,39 +81,48 @@ zval* pmmpthread_read_dimension(PMMPTHREAD_READ_DIMENSION_PASSTHRU_D) {

zval* pmmpthread_read_property(PMMPTHREAD_READ_PROPERTY_PASSTHRU_D) {
zval zmember;
zval *result;
zend_guard* guard;

ZVAL_STR(&zmember, member);

if (object->ce->__get && (guard = zend_get_property_guard(object, member)) && !((*guard) & IN_GET)) {
ZVAL_STR(&zmember, member);
(*guard) |= IN_GET;
zend_call_known_instance_method_with_1_params(object->ce->__get, object, rv, &zmember);
(*guard) &= ~IN_GET;
} else {
zend_property_info* info = zend_get_property_info(object->ce, member, 0);
if (info == ZEND_WRONG_PROPERTY_INFO) {
rv = &EG(uninitialized_zval);
} else if (info == NULL || !PMMPTHREAD_OBJECT_PROPERTY(info)) { //dynamic property
if (pmmpthread_store_read(object, &zmember, type, rv) == FAILURE) {
if (type != BP_VAR_IS) {
zend_error(E_WARNING, "Undefined property: %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
}
rv = &EG(uninitialized_zval);
zend_property_info* info = zend_get_property_info(object->ce, member, 1);
ZVAL_STR(&zmember, member);
if (info != NULL) {
if (info->flags & ZEND_ACC_VIRTUAL) {
//virtual properties are similar to magic methods - don't touch store or cache
return zend_std_read_property(object, member, type, NULL, rv);
}
} else {
//defined property, use mangled name
if (!PMMPTHREAD_OBJECT_PROPERTY(info)) {
info = NULL; //don't send invalid infos into store
}
}

if (info != NULL) {
ZVAL_STR(&zmember, info->name);
} else {
ZVAL_STR(&zmember, member);
}

if (pmmpthread_store_read(object, &zmember, type, rv) == FAILURE) {
if (type != BP_VAR_IS && !EG(exception)) {
zend_throw_error(NULL, "Typed property %s::$%s must not be accessed before initialization",
ZSTR_VAL(info->ce->name),
ZSTR_VAL(member));
}
rv = &EG(uninitialized_zval);
//this moves the value to cache for zend_std_read_property() to work on
pmmpthread_store_read_ex(object, &zmember, info, type, rv, 1);
if (EG(exception)) {
rv = &EG(uninitialized_zval);
} else {
//no cache for now - we don't want the VM bypassing this handler
result = zend_std_read_property(object, member, type, NULL, rv);
ZVAL_COPY_VALUE(rv, result);
//tidy property cache so we don't read wrong values later
if (!pmmpthread_store_retain_in_local_cache(rv)) {
pmmpthread_store_clean_local_property(object, &zmember, info);
}
}
}

return rv;
}
/* }}} */
Expand All @@ -129,7 +138,7 @@ zval* pmmpthread_read_property_deny(PMMPTHREAD_READ_PROPERTY_PASSTHRU_D) {

/* {{{ */
void pmmpthread_write_dimension(PMMPTHREAD_WRITE_DIMENSION_PASSTHRU_D) {
if (pmmpthread_store_write(object, member, value, PMMPTHREAD_STORE_NO_COERCE_ARRAY) == FAILURE && !EG(exception)){
if (pmmpthread_store_write(object, member, NULL, value, PMMPTHREAD_STORE_NO_COERCE_ARRAY) == FAILURE && !EG(exception)){
zend_throw_error(
pmmpthread_ce_nts_value_error,
"Cannot assign non-thread-safe value of type %s to %s",
Expand All @@ -141,15 +150,12 @@ void pmmpthread_write_dimension(PMMPTHREAD_WRITE_DIMENSION_PASSTHRU_D) {

zval* pmmpthread_write_property(PMMPTHREAD_WRITE_PROPERTY_PASSTHRU_D) {
zval zmember;
zval tmp;
zend_guard* guard;

ZVAL_STR(&zmember, member);
ZVAL_UNDEF(&tmp);

if (object->ce->__set && (guard = zend_get_property_guard(object, member)) && !((*guard) & IN_SET)) {
zval rv;
ZVAL_UNDEF(&rv);
ZVAL_STR(&zmember, member);

(*guard) |= IN_SET;
zend_call_known_instance_method_with_2_params(object->ce->__set, object, &rv, &zmember, value);
Expand All @@ -158,42 +164,51 @@ zval* pmmpthread_write_property(PMMPTHREAD_WRITE_PROPERTY_PASSTHRU_D) {
if (Z_TYPE(rv) != IS_UNDEF)
zval_dtor(&rv);
} else {
bool ok = true;
zend_property_info* info = zend_get_property_info(object->ce, member, 0);
if (info != ZEND_WRONG_PROPERTY_INFO) {
if (info != NULL && PMMPTHREAD_OBJECT_PROPERTY(info)) {
ZVAL_STR(&zmember, info->name); //use mangled name to avoid private member shadowing issues

zend_execute_data* execute_data = EG(current_execute_data);
bool strict = execute_data
&& execute_data->func
&& ZEND_CALL_USES_STRICT_TYPES(EG(current_execute_data));

//zend_verify_property_type() might modify the value
//value is not copied before we receive it, so it might be
//from opcache protected memory which we can't modify
ZVAL_COPY(&tmp, value);
value = &tmp;

if (ZEND_TYPE_IS_SET(info->type) && !zend_verify_property_type(info, value, strict)) {
ok = false;
//no cache for now - cache would allow the VM to bypass this handler
//std_write may coerce the var to a different type, so we need to use the result
value = zend_std_write_property(object, member, value, NULL);

if (value != &EG(error_zval)) {
zval* real_value = NULL;
zval zmember;

ZVAL_STR(&zmember, member);
zend_property_info* info = zend_get_property_info(object->ce, member, 1);
if (info != NULL) {
if (info->flags & ZEND_ACC_VIRTUAL) {
return value;
}
if (!PMMPTHREAD_OBJECT_PROPERTY(info)) {
info = NULL; //don't send invalid property infos into store
}
}

if (ok && pmmpthread_store_write(object, &zmember, value, PMMPTHREAD_STORE_NO_COERCE_ARRAY) == FAILURE && !EG(exception)) {
zend_throw_error(
pmmpthread_ce_nts_value_error,
"Cannot assign non-thread-safe value of type %s to thread-safe class property %s::$%s",
zend_zval_type_name(value),
ZSTR_VAL(object->ce->name),
ZSTR_VAL(member)
);
if (info != NULL) {
ZVAL_STR(&zmember, info->name);
real_value = OBJ_PROP(object, info->offset);
} else if (object->properties != NULL) {
ZVAL_STR(&zmember, member);
real_value = zend_hash_find(object->properties, member);
}
if (real_value != NULL && real_value == value) { //if real_value doesn't match, a hook or magic method was probably involved
zend_bool cached = 0;
if (pmmpthread_store_write_ex(object, &zmember, info, real_value, PMMPTHREAD_STORE_NO_COERCE_ARRAY, &cached) == FAILURE && !EG(exception)) {
zend_throw_error(
pmmpthread_ce_nts_value_error,
"Cannot assign non-thread-safe value of type %s to thread-safe class property %s::$%s",
zend_zval_type_name(real_value),
ZSTR_VAL(object->ce->name),
ZSTR_VAL(member)
);
value = &EG(error_zval);
}
if (!cached) {
pmmpthread_store_clean_local_property(object, &zmember, info);
}
}
}
}

zval_ptr_dtor(&tmp);

return EG(exception) ? &EG(error_zval) : value;
}
/* }}} */
Expand Down Expand Up @@ -249,18 +264,17 @@ int pmmpthread_has_property_deny(PMMPTHREAD_HAS_PROPERTY_PASSTHRU_D) {

/* {{{ */
void pmmpthread_unset_dimension(PMMPTHREAD_UNSET_DIMENSION_PASSTHRU_D) {
pmmpthread_store_delete(object, member);
pmmpthread_store_delete(object, member, NULL);
}

void pmmpthread_unset_property(PMMPTHREAD_UNSET_PROPERTY_PASSTHRU_D) {
zval zmember;
zend_guard* guard;

ZVAL_STR(&zmember, member);

if (object->ce->__unset && (guard = zend_get_property_guard(object, member)) && !((*guard) & IN_UNSET)) {
zval rv;
ZVAL_UNDEF(&rv);
ZVAL_STR(&zmember, member);

(*guard) |= IN_UNSET;
zend_call_known_instance_method_with_1_params(object->ce->__unset, object, &rv, &zmember);
Expand All @@ -270,12 +284,18 @@ void pmmpthread_unset_property(PMMPTHREAD_UNSET_PROPERTY_PASSTHRU_D) {
zval_dtor(&rv);
}
} else {
zend_property_info* info = zend_get_property_info(object->ce, member, 0);
if (info != ZEND_WRONG_PROPERTY_INFO) {
if (info != NULL && PMMPTHREAD_OBJECT_PROPERTY(info)) {
ZVAL_STR(&zmember, info->name); //defined property, use mangled name
zend_std_unset_property(object, member, NULL);
if (!EG(exception)) {
zend_property_info* info = zend_get_property_info(object->ce, member, 1);
if (info != NULL && !PMMPTHREAD_OBJECT_PROPERTY(info)) {
info = NULL; //don't send invalid infos into store
}
if (info != NULL) {
ZVAL_STR(&zmember, info->name);
} else {
ZVAL_STR(&zmember, member);
}
pmmpthread_store_delete(object, &zmember);
pmmpthread_store_delete(object, &zmember, info);
}
}
}
Expand Down
89 changes: 54 additions & 35 deletions src/object.c
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,31 @@ void pmmpthread_current_thread(zval *return_value) {
} /* }}} */

/* {{{ */
static inline int _pmmpthread_connect_nolock(pmmpthread_zend_object_t* source, pmmpthread_zend_object_t* destination) {
if (source && destination) {
static inline int _pmmpthread_connect_no_global_lock(pmmpthread_zend_object_t* source, pmmpthread_zend_object_t* destination) {
if (source && destination && pmmpthread_monitor_lock(&source->ts_obj->monitor)) {
destination->ts_obj = source->ts_obj;
++destination->ts_obj->refcount;

if (destination->std.properties)
zend_hash_clean(destination->std.properties);

for (int i = 0; i < destination->std.ce->default_properties_count; i++) {
zend_property_info* prop_info = destination->std.ce->properties_info_table[i];
if (!prop_info || !PMMPTHREAD_OBJECT_PROPERTY(prop_info)) {
continue;
}

zval* local = OBJ_PROP(destination, prop_info->offset);
zval* shared = zend_hash_find(&source->ts_obj->props.hash, prop_info->name);

//ensure connected objects reflect unset/uninit property state correctly
//indirections will point to PMMPTHREAD_G if they exist
Z_PROP_FLAG_P(local) = Z_TYPE_P(shared) == IS_INDIRECT ?
Z_PROP_FLAG_P(Z_INDIRECT_P(shared)) :
0;
}

pmmpthread_monitor_unlock(&source->ts_obj->monitor);
return SUCCESS;
} else return FAILURE;
} /* }}} */
Expand All @@ -195,7 +212,7 @@ static inline int _pmmpthread_connect_nolock(pmmpthread_zend_object_t* source, p
static int pmmpthread_connect(pmmpthread_zend_object_t* source, pmmpthread_zend_object_t* destination) {
int result = FAILURE;
if(pmmpthread_globals_lock()){
result = _pmmpthread_connect_nolock(source, destination);
result = _pmmpthread_connect_no_global_lock(source, destination);
pmmpthread_globals_unlock();
}
return result;
Expand Down Expand Up @@ -243,41 +260,43 @@ static inline void pmmpthread_base_write_property_defaults(pmmpthread_zend_objec

zend_class_entry* ce = base->std.ce;

while (ce != NULL) {
ZEND_HASH_FOREACH_PTR(&ce->properties_info, info) {
zval* value;
int result;
for (int i = 0; i < ce->default_properties_count; i++) {
info = ce->properties_info_table[i];
if (info == NULL || !PMMPTHREAD_OBJECT_PROPERTY(info)) {
continue;
}

if (!PMMPTHREAD_OBJECT_PROPERTY(info)) {
continue;
}
zval* value;
int result;

zend_string* interned_name = pmmpthread_globals_add_interned_string(info->name);
ZVAL_INTERNED_STR(&key, interned_name);

value = OBJ_PROP(&base->std, info->offset);
if (!Z_ISUNDEF_P(value)) {
result = pmmpthread_store_write(
&base->std, &key,
value,
PMMPTHREAD_STORE_NO_COERCE_ARRAY
);
if (result == FAILURE) {
zend_throw_error(
NULL,
"Cannot use non-thread-safe default of type %s for thread-safe class property %s::$%s",
zend_zval_type_name(value),
ZSTR_VAL(ce->name),
ZSTR_VAL(Z_STR(key))
);
break;
}
zval_ptr_dtor(value);
ZVAL_UNDEF(value);
}
} ZEND_HASH_FOREACH_END();
zend_string* interned_name = pmmpthread_globals_add_interned_string(info->name);
ZVAL_INTERNED_STR(&key, interned_name);

value = OBJ_PROP(&base->std, info->offset);
if (Z_ISUNDEF_P(value)) {
ZEND_ASSERT(Z_PROP_FLAG_P(value) & IS_PROP_UNINIT);
value = &PMMPTHREAD_G(uninitialized_property);
}
result = pmmpthread_store_write(
&base->std,
&key,
info,
value,
PMMPTHREAD_STORE_NO_COERCE_ARRAY
);
if (result == FAILURE) {
zend_throw_error(
NULL,
"Cannot use non-thread-safe default of type %s for thread-safe class property %s::$%s",
zend_zval_type_name(value),
ZSTR_VAL(ce->name),
ZSTR_VAL(Z_STR(key))
);
break;
}
zval_ptr_dtor(value);
ZVAL_UNDEF(value);

ce = ce->parent;
}
} /* }}} */

Expand Down
7 changes: 3 additions & 4 deletions src/pmmpthread.h
Original file line number Diff line number Diff line change
Expand Up @@ -163,11 +163,10 @@ typedef struct _pmmpthread_call_t {

#define PMMPTHREAD_CALL_EMPTY {empty_fcall_info, empty_fcall_info_cache}

#if PHP_VERSION_ID >= 80400
#define PMMPTHREAD_OBJECT_PROPERTY(prop_info) ((prop_info->flags & (ZEND_ACC_STATIC | ZEND_ACC_VIRTUAL)) == 0)
#else
#define PMMPTHREAD_OBJECT_PROPERTY(prop_info) ((prop_info->flags & ZEND_ACC_STATIC) == 0)
#if PHP_VERSION_ID < 80400
#define ZEND_ACC_VIRTUAL 0
#endif
#define PMMPTHREAD_OBJECT_PROPERTY(prop_info) ((prop_info->flags & (ZEND_ACC_STATIC | ZEND_ACC_VIRTUAL)) == 0)

/* this is a copy of the same struct in zend_closures.c, which unfortunately isn't exported */
typedef struct _zend_closure {
Expand Down
Loading