summaryrefslogtreecommitdiff
path: root/Zend/zend_execute.c
diff options
context:
space:
mode:
authorNikita Popov <nikita.ppv@gmail.com>2019-01-07 12:28:51 +0100
committerNikita Popov <nikita.ppv@gmail.com>2019-01-11 15:49:06 +0100
commite219ec144ef6682b71e135fd18654ee1bb4676b4 (patch)
treee4a3ae2b619cdc9fe50ee8e1fa5adb99d804dddf /Zend/zend_execute.c
parentfe8fdfa3bd588d80ce60f6b3848058239e0a760f (diff)
downloadphp-git-e219ec144ef6682b71e135fd18654ee1bb4676b4.tar.gz
Implement typed properties
RFC: https://wiki.php.net/rfc/typed_properties_v2 This is a squash of PR #3734, which is a squash of PR #3313. Co-authored-by: Bob Weinand <bobwei9@hotmail.com> Co-authored-by: Joe Watkins <krakjoe@php.net> Co-authored-by: Dmitry Stogov <dmitry@zend.com>
Diffstat (limited to 'Zend/zend_execute.c')
-rw-r--r--Zend/zend_execute.c916
1 files changed, 873 insertions, 43 deletions
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index 4f334c6b86..363812e01f 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -626,7 +626,7 @@ static zend_never_inline ZEND_COLD int zend_wrong_assign_to_variable_reference(z
return 0;
}
- value_ptr = zend_assign_to_variable(variable_ptr, value_ptr, value_type);
+ value_ptr = zend_assign_to_variable(variable_ptr, value_ptr, value_type, EX_USES_STRICT_TYPES());
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_COPY(EX_VAR(opline->result.var), value_ptr);
@@ -634,16 +634,63 @@ static zend_never_inline ZEND_COLD int zend_wrong_assign_to_variable_reference(z
return 1;
}
+static void zend_format_type(zend_type type, const char **part1, const char **part2) {
+ *part1 = ZEND_TYPE_ALLOW_NULL(type) ? "?" : "";
+ if (ZEND_TYPE_IS_CLASS(type)) {
+ if (ZEND_TYPE_IS_CE(type)) {
+ *part2 = ZSTR_VAL(ZEND_TYPE_CE(type)->name);
+ } else {
+ *part2 = ZSTR_VAL(ZEND_TYPE_NAME(type));
+ }
+ } else {
+ *part2 = zend_get_type_by_const(ZEND_TYPE_CODE(type));
+ }
+}
+
+static zend_never_inline ZEND_COLD void zend_throw_auto_init_in_prop_error(zend_property_info *prop, const char *type) {
+ const char *prop_type1, *prop_type2;
+ zend_format_type(prop->type, &prop_type1, &prop_type2);
+ zend_type_error(
+ "Cannot auto-initialize an %s inside property %s::$%s of type %s%s",
+ type,
+ ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name),
+ prop_type1, prop_type2
+ );
+}
+
+static zend_never_inline ZEND_COLD void zend_throw_auto_init_in_ref_error(zend_property_info *prop, const char *type) {
+ const char *prop_type1, *prop_type2;
+ zend_format_type(prop->type, &prop_type1, &prop_type2);
+ zend_type_error(
+ "Cannot auto-initialize an %s inside a reference held by property %s::$%s of type %s%s",
+ type,
+ ZSTR_VAL(prop->ce->name), zend_get_unmangled_property_name(prop->name),
+ prop_type1, prop_type2
+ );
+}
+
+static zend_never_inline ZEND_COLD void zend_throw_access_uninit_prop_by_ref_error(
+ zend_property_info *prop) {
+ zend_throw_error(NULL,
+ "Cannot access uninitialized non-nullable property %s::$%s by reference",
+ ZSTR_VAL(prop->ce->name),
+ zend_get_unmangled_property_name(prop->name));
+}
+
+static zend_always_inline zend_property_info *i_zend_check_ref_stdClass_assignable(zend_reference *ref);
+
/* this should modify object only if it's empty */
-static zend_never_inline ZEND_COLD int ZEND_FASTCALL make_real_object(zval *object, zval *property OPLINE_DC EXECUTE_DATA_DC)
+static zend_never_inline ZEND_COLD zval* ZEND_FASTCALL make_real_object(zval *object, zval *property OPLINE_DC EXECUTE_DATA_DC)
{
zend_object *obj;
+ zval *ref = NULL;
+ if (Z_ISREF_P(object)) {
+ ref = object;
+ object = Z_REFVAL_P(object);
+ }
- if (EXPECTED(Z_TYPE_P(object) <= IS_FALSE)) {
- /* nothing to destroy */
- } else if (EXPECTED((Z_TYPE_P(object) == IS_STRING && Z_STRLEN_P(object) == 0))) {
- zval_ptr_dtor_nogc(object);
- } else {
+ if (UNEXPECTED(Z_TYPE_P(object) > IS_FALSE &&
+ (Z_TYPE_P(object) != IS_STRING || Z_STRLEN_P(object) != 0))) {
if (opline->op1_type != IS_VAR || EXPECTED(!Z_ISERROR_P(object))) {
zend_string *tmp_property_name;
zend_string *property_name = zval_get_tmp_string(property, &tmp_property_name);
@@ -661,8 +708,21 @@ static zend_never_inline ZEND_COLD int ZEND_FASTCALL make_real_object(zval *obje
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
- return 0;
+ return NULL;
}
+
+ if (ref) {
+ zend_property_info *error_prop = i_zend_check_ref_stdClass_assignable(Z_REF_P(ref));
+ if (error_prop) {
+ zend_throw_auto_init_in_ref_error(error_prop, "stdClass");
+ if (RETURN_VALUE_USED(opline)) {
+ ZVAL_UNDEF(EX_VAR(opline->result.var));
+ }
+ return NULL;
+ }
+ }
+
+ zval_ptr_dtor_nogc(object);
object_init(object);
Z_ADDREF_P(object);
obj = Z_OBJ_P(object);
@@ -673,29 +733,42 @@ static zend_never_inline ZEND_COLD int ZEND_FASTCALL make_real_object(zval *obje
if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
ZVAL_NULL(EX_VAR(opline->result.var));
}
- return 0;
+ return NULL;
}
Z_DELREF_P(object);
- return 1;
+ return object;
}
-static zend_never_inline ZEND_COLD int ZEND_FASTCALL make_real_object_rw(zval *object, zval *property OPLINE_DC)
+static zend_never_inline ZEND_COLD zval* ZEND_FASTCALL make_real_object_rw(zval *object, zval *property OPLINE_DC)
{
- if (EXPECTED(Z_TYPE_P(object) <= IS_FALSE)) {
- /* nothing to destroy */
- } else if (EXPECTED((Z_TYPE_P(object) == IS_STRING && Z_STRLEN_P(object) == 0))) {
- zval_ptr_dtor_nogc(object);
- } else {
+ zval *ref = NULL;
+ if (Z_ISREF_P(object)) {
+ ref = object;
+ object = Z_REFVAL_P(object);
+ }
+
+ if (UNEXPECTED(Z_TYPE_P(object) > IS_FALSE &&
+ (Z_TYPE_P(object) != IS_STRING || Z_STRLEN_P(object) != 0))) {
if (opline->op1_type != IS_VAR || EXPECTED(!Z_ISERROR_P(object))) {
zend_string *tmp_property_name;
zend_string *property_name = zval_get_tmp_string(property, &tmp_property_name);
zend_error(E_WARNING, "Attempt to modify property '%s' of non-object", ZSTR_VAL(property_name));
zend_tmp_string_release(tmp_property_name);
}
- return 0;
+ return NULL;
+ }
+
+ if (ref) {
+ zend_property_info *error_prop = i_zend_check_ref_stdClass_assignable(Z_REF_P(ref));
+ if (error_prop) {
+ zend_throw_auto_init_in_ref_error(error_prop, "stdClass");
+ return NULL;
+ }
}
+
+ zval_ptr_dtor_nogc(object);
object_init(object);
- return 1;
+ return object;
}
static ZEND_COLD void zend_verify_type_error_common(
@@ -875,17 +948,125 @@ static zend_bool zend_verify_scalar_type_hint(zend_uchar type_hint, zval *arg, z
return zend_verify_weak_scalar_type_hint(type_hint, arg);
}
+ZEND_COLD zend_never_inline void zend_verify_property_type_error(zend_property_info *info, zval *property)
+{
+ const char *prop_type1, *prop_type2;
+
+ /* we _may_ land here in case reading already errored and runtime cache thus has not been updated (i.e. it contains a valid but unrelated info) */
+ if (EG(exception)) {
+ return;
+ }
+
+ // TODO Switch to a more standard error message?
+ zend_format_type(info->type, &prop_type1, &prop_type2);
+ (void) prop_type1;
+ if (ZEND_TYPE_IS_CLASS(info->type)) {
+ zend_type_error("Typed property %s::$%s must be an instance of %s%s, %s used",
+ ZSTR_VAL(info->ce->name),
+ zend_get_unmangled_property_name(info->name),
+ prop_type2,
+ ZEND_TYPE_ALLOW_NULL(info->type) ? " or null" : "",
+ Z_TYPE_P(property) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(property)->name) : zend_get_type_by_const(Z_TYPE_P(property)));
+ } else {
+ zend_type_error("Typed property %s::$%s must be %s%s, %s used",
+ ZSTR_VAL(info->ce->name),
+ zend_get_unmangled_property_name(info->name),
+ prop_type2,
+ ZEND_TYPE_ALLOW_NULL(info->type) ? " or null" : "",
+ Z_TYPE_P(property) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(property)->name) : zend_get_type_by_const(Z_TYPE_P(property)));
+ }
+}
+
+static zend_bool zend_resolve_class_type(zend_type *type, zend_class_entry *self_ce) {
+ zend_class_entry *ce;
+ zend_string *name = ZEND_TYPE_NAME(*type);
+ if (zend_string_equals_literal_ci(name, "self")) {
+ /* We need to explicitly check for this here, to avoid updating the type in the trait and
+ * later using the wrong "self" when the trait is used in a class. */
+ if (UNEXPECTED((self_ce->ce_flags & ZEND_ACC_TRAIT) != 0)) {
+ zend_throw_error(NULL, "Cannot write a%s value to a 'self' typed static property of a trait", ZEND_TYPE_ALLOW_NULL(*type) ? " non-null" : "");
+ return 0;
+ }
+ ce = self_ce;
+ } else if (zend_string_equals_literal_ci(name, "parent")) {
+ if (UNEXPECTED(!self_ce->parent)) {
+ zend_throw_error(NULL, "Cannot access parent:: when current class scope has no parent");
+ return 0;
+ }
+ ce = self_ce->parent;
+ } else {
+ ce = zend_lookup_class(name);
+ if (UNEXPECTED(!ce)) {
+ return 0;
+ }
+ }
+
+ zend_string_release(name);
+ *type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(*type));
+ return 1;
+}
+
+
+static zend_always_inline zend_bool i_zend_check_property_type(zend_property_info *info, zval *property, zend_bool strict)
+{
+ ZEND_ASSERT(!Z_ISREF_P(property));
+ if (ZEND_TYPE_IS_CLASS(info->type)) {
+ if (UNEXPECTED(Z_TYPE_P(property) != IS_OBJECT)) {
+ return Z_TYPE_P(property) == IS_NULL && ZEND_TYPE_ALLOW_NULL(info->type);
+ }
+
+ if (UNEXPECTED(!ZEND_TYPE_IS_CE(info->type)) && UNEXPECTED(!zend_resolve_class_type(&info->type, info->ce))) {
+ return 0;
+ }
+
+ return instanceof_function(Z_OBJCE_P(property), ZEND_TYPE_CE(info->type));
+ }
+
+ ZEND_ASSERT(ZEND_TYPE_CODE(info->type) != IS_CALLABLE);
+ if (EXPECTED(ZEND_TYPE_CODE(info->type) == Z_TYPE_P(property))) {
+ return 1;
+ } else if (EXPECTED(Z_TYPE_P(property) == IS_NULL)) {
+ return ZEND_TYPE_ALLOW_NULL(info->type);
+ } else if (ZEND_TYPE_CODE(info->type) == _IS_BOOL && EXPECTED(Z_TYPE_P(property) == IS_FALSE || Z_TYPE_P(property) == IS_TRUE)) {
+ return 1;
+ } else if (ZEND_TYPE_CODE(info->type) == IS_ITERABLE) {
+ return zend_is_iterable(property);
+ } else {
+ return zend_verify_scalar_type_hint(ZEND_TYPE_CODE(info->type), property, strict);
+ }
+}
+
+static zend_bool zend_always_inline i_zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict)
+{
+ if (i_zend_check_property_type(info, property, strict)) {
+ return 1;
+ }
+
+ zend_verify_property_type_error(info, property);
+ return 0;
+}
+
+zend_bool zend_never_inline zend_verify_property_type(zend_property_info *info, zval *property, zend_bool strict) {
+ return i_zend_verify_property_type(info, property, strict);
+}
+
static zend_always_inline zend_bool zend_check_type(
zend_type type,
zval *arg, zend_class_entry **ce, void **cache_slot,
zval *default_value, zend_class_entry *scope,
zend_bool is_return_type)
{
+ zend_reference *ref = NULL;
+
if (!ZEND_TYPE_IS_SET(type)) {
return 1;
}
- ZVAL_DEREF(arg);
+ if (UNEXPECTED(Z_ISREF_P(arg))) {
+ ref = Z_REF_P(arg);
+ arg = Z_REFVAL_P(arg);
+ }
+
if (ZEND_TYPE_IS_CLASS(type)) {
if (EXPECTED(*cache_slot)) {
*ce = (zend_class_entry *) *cache_slot;
@@ -916,6 +1097,8 @@ static zend_always_inline zend_bool zend_check_type(
} else if (ZEND_TYPE_CODE(type) == _IS_BOOL &&
EXPECTED(Z_TYPE_P(arg) == IS_FALSE || Z_TYPE_P(arg) == IS_TRUE)) {
return 1;
+ } else if (ref && ZEND_REF_HAS_TYPE_SOURCES(ref)) {
+ return 0; /* we cannot have conversions for typed refs */
} else {
return zend_verify_scalar_type_hint(ZEND_TYPE_CODE(type), arg,
is_return_type ? ZEND_RET_USES_STRICT_TYPES() : ZEND_ARG_USES_STRICT_TYPES());
@@ -1393,6 +1576,159 @@ static zend_never_inline void zend_assign_to_string_offset(zval *str, zval *dim,
}
}
+static zend_property_info *zend_get_prop_not_accepting_double(zend_reference *ref)
+{
+ zend_property_info *prop;
+ ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) {
+ if (ZEND_TYPE_CODE(prop->type) != IS_DOUBLE) {
+ return prop;
+ }
+ } ZEND_REF_FOREACH_TYPE_SOURCES_END();
+ return NULL;
+}
+
+static ZEND_COLD void zend_throw_incdec_ref_error(zend_reference *ref, zend_bool inc)
+{
+ zend_property_info *error_prop = zend_get_prop_not_accepting_double(ref);
+ /* Currently there should be no way for a typed reference to accept both int and double.
+ * Generalize this and the related property code once this becomes possible. */
+ ZEND_ASSERT(error_prop);
+ zend_type_error(
+ "Cannot %s a reference held by property %s::$%s of type %sint past its %simal value",
+ inc ? "increment" : "decrement",
+ ZSTR_VAL(error_prop->ce->name),
+ zend_get_unmangled_property_name(error_prop->name),
+ ZEND_TYPE_ALLOW_NULL(error_prop->type) ? "?" : "",
+ inc ? "max" : "min");
+}
+
+static ZEND_COLD void zend_throw_incdec_prop_error(zend_property_info *prop, zend_bool inc) {
+ const char *prop_type1, *prop_type2;
+ zend_format_type(prop->type, &prop_type1, &prop_type2);
+ zend_type_error("Cannot %s property %s::$%s of type %s%s past its %simal value",
+ inc ? "increment" : "decrement",
+ ZSTR_VAL(prop->ce->name),
+ zend_get_unmangled_property_name(prop->name),
+ prop_type1, prop_type2,
+ inc ? "max" : "min");
+}
+
+static void zend_pre_incdec_property_zval(zval *prop, zend_property_info *prop_info, int inc OPLINE_DC EXECUTE_DATA_DC)
+{
+ if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) {
+ if (inc) {
+ fast_long_increment_function(prop);
+ } else {
+ fast_long_decrement_function(prop);
+ }
+ if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) {
+ zend_throw_incdec_prop_error(prop_info, inc);
+ if (inc) {
+ ZVAL_LONG(prop, ZEND_LONG_MAX);
+ } else {
+ ZVAL_LONG(prop, ZEND_LONG_MIN);
+ }
+ }
+ } else {
+ zend_reference *ref = NULL;
+ if (Z_ISREF_P(prop)) {
+ if (ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(prop))) {
+ ref = Z_REF_P(prop);
+ }
+ prop = Z_REFVAL_P(prop);
+ }
+
+ if (UNEXPECTED(ref || prop_info)) {
+ zval z_copy;
+ ZVAL_COPY(&z_copy, prop);
+ if (inc) {
+ increment_function(&z_copy);
+ } else {
+ decrement_function(&z_copy);
+ }
+ if (UNEXPECTED(Z_TYPE(z_copy) == IS_DOUBLE) && Z_TYPE_P(prop) == IS_LONG) {
+ if (prop_info) {
+ zend_throw_incdec_prop_error(prop_info, inc);
+ } else {
+ zend_throw_incdec_ref_error(ref, inc);
+ }
+ } else if (EXPECTED(ref ? zend_verify_ref_assignable_zval(ref, &z_copy, EX_USES_STRICT_TYPES()) : zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES()))) {
+ zval_ptr_dtor(prop);
+ ZVAL_COPY_VALUE(prop, &z_copy);
+ } else {
+ zval_ptr_dtor(&z_copy);
+ }
+ } else {
+ if (inc) {
+ increment_function(prop);
+ } else {
+ decrement_function(prop);
+ }
+ }
+ }
+ if (UNEXPECTED(RETURN_VALUE_USED(opline))) {
+ ZVAL_COPY(EX_VAR(opline->result.var), prop);
+ }
+}
+
+static void zend_post_incdec_property_zval(zval *prop, zend_property_info *prop_info, int inc OPLINE_DC EXECUTE_DATA_DC)
+{
+ if (EXPECTED(Z_TYPE_P(prop) == IS_LONG)) {
+ ZVAL_LONG(EX_VAR(opline->result.var), Z_LVAL_P(prop));
+ if (inc) {
+ fast_long_increment_function(prop);
+ } else {
+ fast_long_decrement_function(prop);
+ }
+ if (UNEXPECTED(Z_TYPE_P(prop) != IS_LONG) && UNEXPECTED(prop_info)) {
+ zend_throw_incdec_prop_error(prop_info, inc);
+ if (inc) {
+ ZVAL_LONG(prop, ZEND_LONG_MAX);
+ } else {
+ ZVAL_LONG(prop, ZEND_LONG_MIN);
+ }
+ }
+ } else {
+ zend_reference *ref = NULL;
+ if (Z_ISREF_P(prop)) {
+ if (ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(prop))) {
+ ref = Z_REF_P(prop);
+ }
+ prop = Z_REFVAL_P(prop);
+ }
+
+ ZVAL_COPY(EX_VAR(opline->result.var), prop);
+
+ if (UNEXPECTED(ref || prop_info)) {
+ zval z_copy;
+ ZVAL_COPY(&z_copy, prop);
+ if (inc) {
+ increment_function(&z_copy);
+ } else {
+ decrement_function(&z_copy);
+ }
+ if (UNEXPECTED(Z_TYPE(z_copy) == IS_DOUBLE) && Z_TYPE_P(prop) == IS_LONG) {
+ if (prop_info) {
+ zend_throw_incdec_prop_error(prop_info, inc);
+ } else {
+ zend_throw_incdec_ref_error(ref, inc);
+ }
+ } else if (EXPECTED(ref ? zend_verify_ref_assignable_zval(ref, &z_copy, EX_USES_STRICT_TYPES()) : zend_verify_property_type(prop_info, &z_copy, EX_USES_STRICT_TYPES()))) {
+ zval_ptr_dtor(prop);
+ ZVAL_COPY_VALUE(prop, &z_copy);
+ } else {
+ zval_ptr_dtor(&z_copy);
+ }
+ } else {
+ if (inc) {
+ increment_function(prop);
+ } else {
+ decrement_function(prop);
+ }
+ }
+ }
+}
+
static zend_never_inline void zend_post_incdec_overloaded_property(zval *object, zval *property, void **cache_slot, int inc OPLINE_DC EXECUTE_DATA_DC)
{
zval rv, obj;
@@ -1743,7 +2079,7 @@ static zend_never_inline zval* ZEND_FASTCALL zend_fetch_dimension_address_inner_
static zend_always_inline void zend_fetch_dimension_address(zval *result, zval *container, zval *dim, int dim_type, int type EXECUTE_DATA_DC)
{
- zval *retval;
+ zval *retval, *orig_container = container;
if (EXPECTED(Z_TYPE_P(container) == IS_ARRAY)) {
try_array:
@@ -1821,8 +2157,14 @@ fetch_from_array:
}
if (EXPECTED(Z_TYPE_P(container) <= IS_FALSE)) {
if (type != BP_VAR_UNSET) {
- array_init(container);
- goto fetch_from_array;
+ zend_property_info *error_prop;
+ if (UNEXPECTED(orig_container != container) && Z_ISREF_P(orig_container) && (error_prop = zend_check_ref_array_assignable(Z_REF_P(orig_container))) != NULL) {
+ zend_throw_auto_init_in_ref_error(error_prop, "array");
+ ZVAL_ERROR(result);
+ } else {
+ array_init(container);
+ goto fetch_from_array;
+ }
} else {
/* for read-mode only */
ZVAL_NULL(result);
@@ -2150,21 +2492,151 @@ static zend_never_inline uint32_t ZEND_FASTCALL zend_array_key_exists_slow(zval
}
}
-static zend_always_inline void zend_fetch_property_address(zval *result, zval *container, uint32_t container_op_type, zval *prop_ptr, uint32_t prop_op_type, void **cache_slot, int type OPLINE_DC)
+static zend_always_inline zend_bool promotes_to_array(zval *val) {
+ return Z_TYPE_P(val) <= IS_FALSE
+ || (Z_ISREF_P(val) && Z_TYPE_P(Z_REFVAL_P(val)) <= IS_FALSE);
+}
+
+static zend_always_inline zend_bool promotes_to_object(zval *val) {
+ ZVAL_DEREF(val);
+ return Z_TYPE_P(val) <= IS_FALSE
+ || (Z_TYPE_P(val) == IS_STRING && Z_STRLEN_P(val) == 0);
+}
+
+static zend_always_inline zend_bool check_type_array_assignable(zend_type type) {
+ if (!type) {
+ return 1;
+ }
+ return ZEND_TYPE_IS_CODE(type)
+ && (ZEND_TYPE_CODE(type) == IS_ARRAY || ZEND_TYPE_CODE(type) == IS_ITERABLE);
+}
+
+static zend_always_inline zend_bool check_type_stdClass_assignable(zend_type type) {
+ if (!type) {
+ return 1;
+ }
+ if (ZEND_TYPE_IS_CLASS(type)) {
+ if (ZEND_TYPE_IS_CE(type)) {
+ return ZEND_TYPE_CE(type) == zend_standard_class_def;
+ } else {
+ return zend_string_equals_literal_ci(ZEND_TYPE_NAME(type), "stdclass");
+ }
+ } else {
+ return ZEND_TYPE_CODE(type) == IS_OBJECT;
+ }
+}
+
+/* Checks whether an array can be assigned to the reference. Returns conflicting property if
+ * assignment is not possible, NULL otherwise. */
+static zend_always_inline zend_property_info *i_zend_check_ref_array_assignable(zend_reference *ref) {
+ zend_property_info *prop;
+ if (!ZEND_REF_HAS_TYPE_SOURCES(ref)) {
+ return NULL;
+ }
+ ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) {
+ if (!check_type_array_assignable(prop->type)) {
+ return prop;
+ }
+ } ZEND_REF_FOREACH_TYPE_SOURCES_END();
+ return NULL;
+}
+
+/* Checks whether an stdClass can be assigned to the reference. Returns conflicting property if
+ * assignment is not possible, NULL otherwise. */
+static zend_always_inline zend_property_info *i_zend_check_ref_stdClass_assignable(zend_reference *ref) {
+ zend_property_info *prop;
+ if (!ZEND_REF_HAS_TYPE_SOURCES(ref)) {
+ return NULL;
+ }
+ ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) {
+ if (!check_type_stdClass_assignable(prop->type)) {
+ return prop;
+ }
+ } ZEND_REF_FOREACH_TYPE_SOURCES_END();
+ return NULL;
+}
+
+ZEND_API zend_property_info *zend_check_ref_array_assignable(zend_reference *ref) {
+ return i_zend_check_ref_array_assignable(ref);
+}
+
+static zend_always_inline zend_bool zend_need_to_handle_fetch_obj_flags(uint32_t flags, zval *ptr) {
+ return (flags == ZEND_FETCH_REF && Z_TYPE_P(ptr) != IS_REFERENCE)
+ || (flags == ZEND_FETCH_DIM_WRITE && promotes_to_array(ptr))
+ || (flags == ZEND_FETCH_OBJ_WRITE && promotes_to_object(ptr));
+}
+
+static zend_never_inline zend_bool zend_handle_fetch_obj_flags(
+ zval *result, zval *ptr, zend_property_info *prop_info, uint32_t flags)
+{
+ ZEND_ASSERT(prop_info && prop_info->type);
+ switch (flags) {
+ case ZEND_FETCH_DIM_WRITE:
+ if (!check_type_array_assignable(prop_info->type)) {
+ zend_throw_auto_init_in_prop_error(prop_info, "array");
+ if (result) ZVAL_ERROR(result);
+ return 0;
+ }
+ break;
+ case ZEND_FETCH_OBJ_WRITE:
+ if (!check_type_stdClass_assignable(prop_info->type)) {
+ zend_throw_auto_init_in_prop_error(prop_info, "stdClass");
+ if (result) ZVAL_ERROR(result);
+ return 0;
+ }
+ break;
+ case ZEND_FETCH_REF:
+ if (Z_TYPE_P(ptr) == IS_UNDEF) {
+ if (!ZEND_TYPE_ALLOW_NULL(prop_info->type)) {
+ zend_throw_access_uninit_prop_by_ref_error(prop_info);
+ if (result) ZVAL_ERROR(result);
+ return 0;
+ }
+ ZVAL_NULL(ptr);
+ }
+
+ ZVAL_NEW_REF(ptr, ptr);
+ ZEND_REF_ADD_TYPE_SOURCE(Z_REF_P(ptr), prop_info);
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ return 1;
+}
+
+static zend_always_inline zend_property_info *zend_object_fetch_property_type_info(
+ zend_object *obj, zval *slot)
+{
+ if (EXPECTED(!ZEND_CLASS_HAS_TYPE_HINTS(obj->ce))) {
+ return NULL;
+ }
+
+ /* Not a declared property */
+ if (UNEXPECTED(slot < obj->properties_table ||
+ slot >= obj->properties_table + obj->ce->default_properties_count)) {
+ return NULL;
+ }
+
+ return zend_get_typed_property_info_for_slot(obj, slot);
+}
+
+static zend_always_inline void zend_fetch_property_address(zval *result, zval *container, uint32_t container_op_type, zval *prop_ptr, uint32_t prop_op_type, void **cache_slot, int type, uint32_t flags, zend_bool init_undef OPLINE_DC)
{
zval *ptr;
- if (container_op_type != IS_UNUSED && UNEXPECTED(Z_TYPE_P(container) != IS_OBJECT)) {
+
+ if (container_op_type != IS_UNUSED && UNEXPECTED(Z_TYPE_P(container) != IS_OBJECT)) {
do {
- if (Z_ISREF_P(container)) {
+ if (Z_ISREF_P(container) && Z_TYPE_P(Z_REFVAL_P(container)) == IS_OBJECT) {
container = Z_REFVAL_P(container);
- if (EXPECTED(Z_TYPE_P(container) == IS_OBJECT)) {
- break;
- }
+ break;
}
/* this should modify object only if it's empty */
- if (type == BP_VAR_UNSET ||
- UNEXPECTED(!make_real_object_rw(container, prop_ptr OPLINE_CC))) {
+ if (type == BP_VAR_UNSET) {
+ return;
+ }
+
+ container = make_real_object_rw(container, prop_ptr OPLINE_CC);
+ if (UNEXPECTED(!container)) {
ZVAL_ERROR(result);
return;
}
@@ -2174,12 +2646,16 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
EXPECTED(Z_OBJCE_P(container) == CACHED_PTR_EX(cache_slot))) {
uintptr_t prop_offset = (uintptr_t)CACHED_PTR_EX(cache_slot + 1);
zend_object *zobj = Z_OBJ_P(container);
- zval *retval;
if (EXPECTED(IS_VALID_PROPERTY_OFFSET(prop_offset))) {
- retval = OBJ_PROP(zobj, prop_offset);
- if (EXPECTED(Z_TYPE_P(retval) != IS_UNDEF)) {
- ZVAL_INDIRECT(result, retval);
+ ptr = OBJ_PROP(zobj, prop_offset);
+ if (EXPECTED(Z_TYPE_P(ptr) != IS_UNDEF)) {
+ zend_property_info *prop_info;
+ ZVAL_INDIRECT(result, ptr);
+ if (flags && (prop_info = CACHED_PTR_EX(cache_slot + 2)) &&
+ UNEXPECTED(zend_need_to_handle_fetch_obj_flags(flags, ptr))) {
+ zend_handle_fetch_obj_flags(result, ptr, prop_info, flags);
+ }
return;
}
} else if (EXPECTED(zobj->properties != NULL)) {
@@ -2189,24 +2665,378 @@ static zend_always_inline void zend_fetch_property_address(zval *result, zval *c
}
zobj->properties = zend_array_dup(zobj->properties);
}
- retval = zend_hash_find_ex(zobj->properties, Z_STR_P(prop_ptr), 1);
- if (EXPECTED(retval)) {
- ZVAL_INDIRECT(result, retval);
+ ptr = zend_hash_find_ex(zobj->properties, Z_STR_P(prop_ptr), 1);
+ if (EXPECTED(ptr)) {
+ ZVAL_INDIRECT(result, ptr);
return;
}
}
}
-
ptr = Z_OBJ_HT_P(container)->get_property_ptr_ptr(container, prop_ptr, type, cache_slot);
if (NULL == ptr) {
ptr = Z_OBJ_HT_P(container)->read_property(container, prop_ptr, type, cache_slot, result);
- if (ptr != result) {
- ZVAL_INDIRECT(result, ptr);
- } else if (UNEXPECTED(Z_ISREF_P(ptr) && Z_REFCOUNT_P(ptr) == 1)) {
- ZVAL_UNREF(ptr);
+ if (ptr == result) {
+ if (UNEXPECTED(Z_ISREF_P(ptr) && Z_REFCOUNT_P(ptr) == 1)) {
+ ZVAL_UNREF(ptr);
+ }
+ return;
+ }
+ }
+
+ ZVAL_INDIRECT(result, ptr);
+ if (flags && UNEXPECTED(zend_need_to_handle_fetch_obj_flags(flags, ptr))) {
+ zend_property_info *prop_info =
+ zend_object_fetch_property_type_info(Z_OBJ_P(container), ptr);
+ if (prop_info && !zend_handle_fetch_obj_flags(result, ptr, prop_info, flags)) {
+ return;
+ }
+ }
+ if (init_undef && UNEXPECTED(Z_TYPE_P(ptr) == IS_UNDEF)) {
+ ZVAL_NULL(ptr);
+ }
+}
+
+static zend_never_inline int zend_fetch_static_property_address_ex(zval **retval, zend_property_info **prop_info, uint32_t cache_slot, int fetch_type OPLINE_DC EXECUTE_DATA_DC) {
+ zend_free_op free_op1;
+ zend_string *name, *tmp_name;
+ zend_class_entry *ce;
+ zend_property_info *property_info;
+
+ zend_uchar op1_type = opline->op1_type, op2_type = opline->op2_type;
+
+ if (EXPECTED(op2_type == IS_CONST)) {
+ zval *class_name = RT_CONSTANT(opline, opline->op2);
+
+ ZEND_ASSERT(op1_type != IS_CONST || CACHED_PTR(cache_slot) == NULL);
+
+ if (EXPECTED((ce = CACHED_PTR(cache_slot)) == NULL)) {
+ ce = zend_fetch_class_by_name(Z_STR_P(class_name), Z_STR_P(class_name + 1), ZEND_FETCH_CLASS_DEFAULT | ZEND_FETCH_CLASS_EXCEPTION);
+ if (UNEXPECTED(ce == NULL)) {
+ FREE_UNFETCHED_OP(op1_type, opline->op1.var);
+ return FAILURE;
+ }
+ if (UNEXPECTED(op1_type != IS_CONST)) {
+ CACHE_PTR(cache_slot, ce);
+ }
}
} else {
- ZVAL_INDIRECT(result, ptr);
+ if (EXPECTED(op2_type == IS_UNUSED)) {
+ ce = zend_fetch_class(NULL, opline->op2.num);
+ if (UNEXPECTED(ce == NULL)) {
+ FREE_UNFETCHED_OP(op1_type, opline->op1.var);
+ return FAILURE;
+ }
+ } else {
+ ce = Z_CE_P(EX_VAR(opline->op2.var));
+ }
+ if (EXPECTED(op1_type == IS_CONST) && EXPECTED(CACHED_PTR(cache_slot) == ce)) {
+ *retval = CACHED_PTR(cache_slot + sizeof(void *));
+ *prop_info = CACHED_PTR(cache_slot + sizeof(void *) * 2);
+ return SUCCESS;
+ }
+ }
+
+ if (EXPECTED(op1_type == IS_CONST)) {
+ name = Z_STR_P(RT_CONSTANT(opline, opline->op1));
+ } else {
+ zval *varname = get_zval_ptr_undef(opline->op1_type, opline->op1, &free_op1, BP_VAR_R);
+ if (EXPECTED(Z_TYPE_P(varname) == IS_STRING)) {
+ name = Z_STR_P(varname);
+ tmp_name = NULL;
+ } else {
+ if (op1_type == IS_CV && UNEXPECTED(Z_TYPE_P(varname) == IS_UNDEF)) {
+ zval_undefined_cv(opline->op1.var EXECUTE_DATA_CC);
+ }
+ name = zval_get_tmp_string(varname, &tmp_name);
+ }
+ }
+
+ *retval = zend_std_get_static_property_with_info(ce, name, fetch_type, &property_info);
+
+ if (UNEXPECTED(op1_type != IS_CONST)) {
+ zend_tmp_string_release(tmp_name);
+
+ if (op1_type != IS_CV) {
+ zval_ptr_dtor_nogc(free_op1);
+ }
+ }
+
+ if (UNEXPECTED(*retval == NULL)) {
+ return FAILURE;
+ }
+
+ *prop_info = property_info;
+
+ if (EXPECTED(op1_type == IS_CONST)) {
+ CACHE_POLYMORPHIC_PTR(cache_slot, ce, *retval);
+ CACHE_PTR(cache_slot + sizeof(void *) * 2, property_info);
+ }
+
+ return SUCCESS;
+}
+
+
+static zend_always_inline int zend_fetch_static_property_address(zval **retval, zend_property_info **prop_info, uint32_t cache_slot, int fetch_type, int flags OPLINE_DC EXECUTE_DATA_DC) {
+ int success;
+ zend_property_info *property_info;
+
+ if (opline->op1_type == IS_CONST && (opline->op2_type == IS_CONST || (opline->op2_type == IS_UNUSED && (opline->op2.num == ZEND_FETCH_CLASS_SELF || opline->op2.num == ZEND_FETCH_CLASS_PARENT))) && EXPECTED(CACHED_PTR(cache_slot) != NULL)) {
+ *retval = CACHED_PTR(cache_slot + sizeof(void *));
+ property_info = CACHED_PTR(cache_slot + sizeof(void *) * 2);
+
+ if ((fetch_type == BP_VAR_R || fetch_type == BP_VAR_RW)
+ && UNEXPECTED(Z_TYPE_P(*retval) == IS_UNDEF) && UNEXPECTED(property_info->type != 0)) {
+ zend_throw_error(NULL, "Typed static property %s::$%s must not be accessed before initialization",
+ ZSTR_VAL(property_info->ce->name),
+ zend_get_unmangled_property_name(property_info->name));
+ return FAILURE;
+ }
+ } else {
+ success = zend_fetch_static_property_address_ex(retval, &property_info, cache_slot, fetch_type OPLINE_CC EXECUTE_DATA_CC);
+ if (UNEXPECTED(success != SUCCESS)) {
+ return FAILURE;
+ }
+ }
+
+ if (flags && property_info->type && zend_need_to_handle_fetch_obj_flags(flags, *retval)) {
+ zend_handle_fetch_obj_flags(NULL, *retval, property_info, flags);
+ }
+
+ if (prop_info) {
+ *prop_info = property_info;
+ }
+
+ return SUCCESS;
+}
+
+ZEND_API ZEND_COLD void zend_throw_ref_type_error_type(zend_property_info *prop1, zend_property_info *prop2, zval *zv) {
+ const char *prop1_type1, *prop1_type2, *prop2_type1, *prop2_type2;
+ zend_format_type(prop1->type, &prop1_type1, &prop1_type2);
+ zend_format_type(prop2->type, &prop2_type1, &prop2_type2);
+ zend_type_error("Reference with value of type %s held by property %s::$%s of type %s%s is not compatible with property %s::$%s of type %s%s",
+ Z_TYPE_P(zv) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(zv)->name) : zend_get_type_by_const(Z_TYPE_P(zv)),
+ ZSTR_VAL(prop1->ce->name),
+ zend_get_unmangled_property_name(prop1->name),
+ prop1_type1, prop1_type2,
+ ZSTR_VAL(prop2->ce->name),
+ zend_get_unmangled_property_name(prop2->name),
+ prop2_type1, prop2_type2
+ );
+}
+
+ZEND_API ZEND_COLD void zend_throw_ref_type_error_zval(zend_property_info *prop, zval *zv) {
+ const char *prop_type1, *prop_type2;
+ zend_format_type(prop->type, &prop_type1, &prop_type2);
+ zend_type_error("Cannot assign %s to reference held by property %s::$%s of type %s%s",
+ Z_TYPE_P(zv) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(zv)->name) : zend_get_type_by_const(Z_TYPE_P(zv)),
+ ZSTR_VAL(prop->ce->name),
+ zend_get_unmangled_property_name(prop->name),
+ prop_type1, prop_type2
+ );
+}
+
+ZEND_API ZEND_COLD void zend_throw_conflicting_coercion_error(zend_property_info *prop1, zend_property_info *prop2, zval *zv) {
+ const char *prop1_type1, *prop1_type2, *prop2_type1, *prop2_type2;
+ zend_format_type(prop1->type, &prop1_type1, &prop1_type2);
+ zend_format_type(prop2->type, &prop2_type1, &prop2_type2);
+ zend_type_error("Cannot assign %s to reference held by property %s::$%s of type %s%s and property %s::$%s of type %s%s, as this would result in an inconsistent type conversion",
+ Z_TYPE_P(zv) == IS_OBJECT ? ZSTR_VAL(Z_OBJCE_P(zv)->name) : zend_get_type_by_const(Z_TYPE_P(zv)),
+ ZSTR_VAL(prop1->ce->name),
+ zend_get_unmangled_property_name(prop1->name),
+ prop1_type1, prop1_type2,
+ ZSTR_VAL(prop2->ce->name),
+ zend_get_unmangled_property_name(prop2->name),
+ prop2_type1, prop2_type2
+ );
+}
+
+/* 1: valid, 0: invalid, -1: may be valid after type coercion */
+static zend_always_inline int i_zend_verify_type_assignable_zval(
+ zend_type *type_ptr, zend_class_entry *self_ce, zval *zv, zend_bool strict) {
+ zend_type type = *type_ptr;
+ zend_uchar type_code;
+ zend_uchar zv_type = Z_TYPE_P(zv);
+
+ if (ZEND_TYPE_ALLOW_NULL(type) && zv_type == IS_NULL) {
+ return 1;
+ }
+
+ if (ZEND_TYPE_IS_CLASS(type)) {
+ if (!ZEND_TYPE_IS_CE(type)) {
+ if (!zend_resolve_class_type(type_ptr, self_ce)) {
+ return 0;
+ }
+ type = *type_ptr;
+ }
+ return zv_type == IS_OBJECT && instanceof_function(Z_OBJCE_P(zv), ZEND_TYPE_CE(type));
+ }
+
+ type_code = ZEND_TYPE_CODE(type);
+ if (type_code == zv_type ||
+ (type_code == _IS_BOOL && (zv_type == IS_FALSE || zv_type == IS_TRUE))) {
+ return 1;
+ }
+
+ if (type_code == IS_ITERABLE) {
+ return zend_is_iterable(zv);
+ }
+
+ /* SSTH Exception: IS_LONG may be accepted as IS_DOUBLE (converted) */
+ if (strict) {
+ if (type_code == IS_DOUBLE && zv_type == IS_LONG) {
+ return -1;
+ }
+ return 0;
+ }
+
+ /* No weak conversions for arrays and objects */
+ if (type_code == IS_ARRAY || type_code == IS_OBJECT) {
+ return 0;
+ }
+
+ /* NULL may be accepted only by nullable hints (this is already checked) */
+ if (zv_type == IS_NULL) {
+ return 0;
+ }
+
+ /* Coercion may be necessary, check separately */
+ return -1;
+}
+
+static zend_always_inline zend_bool i_zend_verify_ref_assignable_zval(zend_reference *ref, zval *zv, zend_bool strict) {
+ zend_property_info *prop;
+
+ /* The value must satisfy each property type, and coerce to the same value for each property
+ * type. Right now, the latter rule means that *if* coercion is necessary, then all types
+ * must be the same (modulo nullability). To handle this, remember the first type we see and
+ * compare against it when coercion becomes necessary. */
+ zend_property_info *seen_prop = NULL;
+ zend_uchar seen_type;
+ zend_bool needs_coercion = 0;
+
+ ZEND_ASSERT(Z_TYPE_P(zv) != IS_REFERENCE);
+ ZEND_REF_FOREACH_TYPE_SOURCES(ref, prop) {
+ int result = i_zend_verify_type_assignable_zval(&prop->type, prop->ce, zv, strict);
+ if (result == 0) {
+ zend_throw_ref_type_error_zval(prop, zv);
+ return 0;
+ }
+
+ if (result < 0) {
+ needs_coercion = 1;
+ }
+
+ if (!seen_prop) {
+ seen_prop = prop;
+ seen_type = ZEND_TYPE_IS_CLASS(prop->type) ? IS_OBJECT : ZEND_TYPE_CODE(prop->type);
+ } else if (needs_coercion && seen_type != ZEND_TYPE_CODE(prop->type)) {
+ zend_throw_conflicting_coercion_error(seen_prop, prop, zv);
+ return 0;
+ }
+ } ZEND_REF_FOREACH_TYPE_SOURCES_END();
+
+ if (UNEXPECTED(needs_coercion && !zend_verify_weak_scalar_type_hint(seen_type, zv))) {
+ zend_throw_ref_type_error_zval(seen_prop, zv);
+ return 0;
+ }
+
+ return 1;
+}
+
+ZEND_API zend_bool zend_verify_ref_assignable_zval(zend_reference *ref, zval *zv, zend_bool strict)
+{
+ return i_zend_verify_ref_assignable_zval(ref, zv, strict);
+}
+
+ZEND_API zend_bool zend_verify_prop_assignable_by_ref(zend_property_info *prop_info, zval *orig_val, zend_bool strict) {
+ zval *val = orig_val;
+ if (Z_ISREF_P(val) && ZEND_REF_HAS_TYPE_SOURCES(Z_REF_P(val))) {
+ int result;
+
+ val = Z_REFVAL_P(val);
+ result = i_zend_verify_type_assignable_zval(&prop_info->type, prop_info->ce, val, strict);
+ if (result > 0) {
+ return 1;
+ }
+
+ if (result < 0) {
+ zend_property_info *ref_prop = ZEND_REF_FIRST_SOURCE(Z_REF_P(orig_val));
+ if (ZEND_TYPE_CODE(prop_info->type) != ZEND_TYPE_CODE(ref_prop->type)) {
+ /* Invalid due to conflicting coercion */
+ zend_throw_ref_type_error_type(ref_prop, prop_info, val);
+ return 0;
+ }
+ if (zend_verify_weak_scalar_type_hint(ZEND_TYPE_CODE(prop_info->type), val)) {
+ return 1;
+ }
+ }
+ } else {
+ ZVAL_DEREF(val);
+ if (i_zend_check_property_type(prop_info, val, strict)) {
+ return 1;
+ }
+ }
+
+ zend_verify_property_type_error(prop_info, val);
+ return 0;
+}
+
+ZEND_API void zend_ref_add_type_source(zend_property_info_source_list *source_list, zend_property_info *prop)
+{
+ zend_property_info_list *list;
+ if (source_list->ptr == NULL) {
+ source_list->ptr = prop;
+ return;
+ }
+
+ list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(source_list->list);
+ if (!ZEND_PROPERTY_INFO_SOURCE_IS_LIST(source_list->list)) {
+ list = emalloc(sizeof(zend_property_info_list) + (4 - 1) * sizeof(zend_property_info *));
+ list->ptr[0] = source_list->ptr;
+ list->num_allocated = 4;
+ list->num = 1;
+ } else if (list->num_allocated == list->num) {
+ list->num_allocated = list->num * 2;
+ list = erealloc(list, sizeof(zend_property_info_list) + (list->num_allocated - 1) * sizeof(zend_property_info *));
+ }
+
+ list->ptr[list->num++] = prop;
+ source_list->list = ZEND_PROPERTY_INFO_SOURCE_FROM_LIST(list);
+}
+
+ZEND_API void zend_ref_del_type_source(zend_property_info_source_list *source_list, zend_property_info *prop)
+{
+ zend_property_info_list *list = ZEND_PROPERTY_INFO_SOURCE_TO_LIST(source_list->list);
+ zend_property_info **ptr, **end;
+
+ if (!ZEND_PROPERTY_INFO_SOURCE_IS_LIST(source_list->list)) {
+ ZEND_ASSERT(source_list->ptr == prop);
+ source_list->ptr = NULL;
+ return;
+ }
+
+ if (list->num == 1) {
+ ZEND_ASSERT(*list->ptr == prop);
+ efree(list);
+ source_list->ptr = NULL;
+ return;
+ }
+
+ /* Checking against end here to get a more graceful failure mode if we missed adding a type
+ * source at some point. */
+ ptr = list->ptr;
+ end = ptr + list->num;
+ while (ptr < end && *ptr != prop) {
+ ptr++;
+ }
+ ZEND_ASSERT(*ptr == prop);
+
+ /* Copy the last list element into the deleted slot. */
+ *ptr = list->ptr[--list->num];
+
+ if (list->num >= 4 && list->num * 4 == list->num_allocated) {
+ list->num_allocated = list->num * 2;
+ source_list->list = ZEND_PROPERTY_INFO_SOURCE_FROM_LIST(erealloc(list, sizeof(zend_property_info_list) + (list->num_allocated - 1) * sizeof(zend_property_info *)));
}
}