summaryrefslogtreecommitdiff
path: root/ext/opcache
diff options
context:
space:
mode:
Diffstat (limited to 'ext/opcache')
-rw-r--r--ext/opcache/Optimizer/compact_literals.c112
-rw-r--r--ext/opcache/Optimizer/dce.c3
-rw-r--r--ext/opcache/Optimizer/escape_analysis.c6
-rw-r--r--ext/opcache/Optimizer/sccp.c11
-rw-r--r--ext/opcache/Optimizer/zend_dfg.c9
-rw-r--r--ext/opcache/Optimizer/zend_dump.c14
-rw-r--r--ext/opcache/Optimizer/zend_inference.c266
-rw-r--r--ext/opcache/Optimizer/zend_optimizer.c68
-rw-r--r--ext/opcache/Optimizer/zend_ssa.c16
-rw-r--r--ext/opcache/tests/opt/prop_types.phpt115
-rw-r--r--ext/opcache/zend_accelerator_util_funcs.c18
-rw-r--r--ext/opcache/zend_file_cache.c44
-rw-r--r--ext/opcache/zend_persist.c34
-rw-r--r--ext/opcache/zend_persist_calc.c20
14 files changed, 631 insertions, 105 deletions
diff --git a/ext/opcache/Optimizer/compact_literals.c b/ext/opcache/Optimizer/compact_literals.c
index 9e3a0ab9a1..397458365f 100644
--- a/ext/opcache/Optimizer/compact_literals.c
+++ b/ext/opcache/Optimizer/compact_literals.c
@@ -108,7 +108,7 @@ static uint32_t add_static_slot(HashTable *hash,
ret = Z_LVAL_P(pos);
} else {
ret = *cache_size;
- *cache_size += 2 * sizeof(void *);
+ *cache_size += (kind == LITERAL_STATIC_PROPERTY ? 3 : 2) * sizeof(void *);
ZVAL_LONG(&tmp, ret);
zend_hash_add(hash, key, &tmp);
}
@@ -183,6 +183,8 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
}
LITERAL_INFO(opline->op2.constant, LITERAL_CLASS_CONST, 1);
break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
case ZEND_FETCH_STATIC_PROP_R:
case ZEND_FETCH_STATIC_PROP_W:
case ZEND_FETCH_STATIC_PROP_RW:
@@ -191,6 +193,11 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
case ZEND_UNSET_STATIC_PROP:
case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+literals_handle_static_prop:
if (opline->op2_type == IS_CONST) {
LITERAL_INFO(opline->op2.constant, LITERAL_CLASS, 2);
}
@@ -210,6 +217,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
}
break;
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
@@ -238,6 +246,9 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_ASSIGN_BW_OR:
case ZEND_ASSIGN_BW_AND:
case ZEND_ASSIGN_BW_XOR:
+ if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ goto literals_handle_static_prop;
+ }
if (opline->op2_type == IS_CONST) {
if (opline->extended_value == ZEND_ASSIGN_OBJ) {
LITERAL_INFO(opline->op2.constant, LITERAL_PROPERTY, 1);
@@ -547,24 +558,48 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_ASSIGN_BW_OR:
case ZEND_ASSIGN_BW_AND:
case ZEND_ASSIGN_BW_XOR:
- if (opline->extended_value != ZEND_ASSIGN_OBJ) {
- break;
+ if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ if (opline->op1_type == IS_CONST) {
+ // op1 static property
+ if (opline->op2_type == IS_CONST) {
+ (opline+1)->extended_value = add_static_slot(&hash, op_array,
+ opline->op2.constant,
+ opline->op1.constant,
+ LITERAL_STATIC_PROPERTY,
+ &cache_size);
+ } else {
+ (opline+1)->extended_value = cache_size;
+ cache_size += 3 * sizeof(void *);
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ // op2 class
+ if (class_slot[opline->op2.constant] >= 0) {
+ (opline+1)->extended_value = class_slot[opline->op2.constant];
+ } else {
+ (opline+1)->extended_value = cache_size;
+ class_slot[opline->op2.constant] = cache_size;
+ cache_size += sizeof(void *);
+ }
+ }
}
- if (opline->op2_type == IS_CONST) {
- // op2 property
- if (opline->op1_type == IS_UNUSED &&
- property_slot[opline->op2.constant] >= 0) {
- (opline+1)->extended_value = property_slot[opline->op2.constant];
- } else {
- (opline+1)->extended_value = cache_size;
- cache_size += 2 * sizeof(void *);
- if (opline->op1_type == IS_UNUSED) {
- property_slot[opline->op2.constant] = (opline+1)->extended_value;
+ if (opline->extended_value == ZEND_ASSIGN_OBJ) {
+ if (opline->op2_type == IS_CONST) {
+ // op2 property
+ if (opline->op1_type == IS_UNUSED &&
+ property_slot[opline->op2.constant] >= 0) {
+ (opline+1)->extended_value = property_slot[opline->op2.constant];
+ } else {
+ (opline+1)->extended_value = cache_size;
+ cache_size += 3 * sizeof(void *);
+ if (opline->op1_type == IS_UNUSED) {
+ property_slot[opline->op2.constant] = (opline+1)->extended_value;
+ }
}
}
}
break;
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
@@ -580,12 +615,12 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
// op2 property
if (opline->op1_type == IS_UNUSED &&
property_slot[opline->op2.constant] >= 0) {
- opline->extended_value = property_slot[opline->op2.constant];
+ opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
} else {
- opline->extended_value = cache_size;
- cache_size += 2 * sizeof(void *);
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ cache_size += 3 * sizeof(void *);
if (opline->op1_type == IS_UNUSED) {
- property_slot[opline->op2.constant] = opline->extended_value;
+ property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS;
}
}
}
@@ -598,7 +633,7 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
opline->extended_value = property_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY);
} else {
opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
- cache_size += 2 * sizeof(void *);
+ cache_size += 3 * sizeof(void *);
if (opline->op1_type == IS_UNUSED) {
property_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY;
}
@@ -689,6 +724,8 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
cache_size += 2 * sizeof(void *);
}
break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
case ZEND_FETCH_STATIC_PROP_R:
case ZEND_FETCH_STATIC_PROP_W:
case ZEND_FETCH_STATIC_PROP_RW:
@@ -696,30 +733,11 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
case ZEND_FETCH_STATIC_PROP_UNSET:
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
case ZEND_UNSET_STATIC_PROP:
- if (opline->op1_type == IS_CONST) {
- // op1 static property
- if (opline->op2_type == IS_CONST) {
- opline->extended_value = add_static_slot(&hash, op_array,
- opline->op2.constant,
- opline->op1.constant,
- LITERAL_STATIC_PROPERTY,
- &cache_size);
- } else {
- opline->extended_value = cache_size;
- cache_size += 2 * sizeof(void *);
- }
- } else if (opline->op2_type == IS_CONST) {
- // op2 class
- if (class_slot[opline->op2.constant] >= 0) {
- opline->extended_value = class_slot[opline->op2.constant];
- } else {
- opline->extended_value = cache_size;
- cache_size += sizeof(void *);
- class_slot[opline->op2.constant] = opline->extended_value;
- }
- }
- break;
case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
if (opline->op1_type == IS_CONST) {
// op1 static property
if (opline->op2_type == IS_CONST) {
@@ -727,19 +745,19 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
opline->op2.constant,
opline->op1.constant,
LITERAL_STATIC_PROPERTY,
- &cache_size) | (opline->extended_value & ZEND_ISEMPTY);
+ &cache_size) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
} else {
- opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
- cache_size += 2 * sizeof(void *);
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ cache_size += 3 * sizeof(void *);
}
} else if (opline->op2_type == IS_CONST) {
// op2 class
if (class_slot[opline->op2.constant] >= 0) {
- opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_ISEMPTY);
+ opline->extended_value = class_slot[opline->op2.constant] | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
} else {
- opline->extended_value = cache_size | (opline->extended_value & ZEND_ISEMPTY);
+ opline->extended_value = cache_size | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
+ class_slot[opline->op2.constant] = cache_size;
cache_size += sizeof(void *);
- class_slot[opline->op2.constant] = opline->extended_value & ~ZEND_ISEMPTY;
}
}
break;
diff --git a/ext/opcache/Optimizer/dce.c b/ext/opcache/Optimizer/dce.c
index 89898393a7..95755d559f 100644
--- a/ext/opcache/Optimizer/dce.c
+++ b/ext/opcache/Optimizer/dce.c
@@ -330,6 +330,9 @@ static zend_bool try_remove_var_def(context *ctx, int free_var, int use_chain, z
case ZEND_ASSIGN_REF:
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
case ZEND_ASSIGN_ADD:
case ZEND_ASSIGN_SUB:
case ZEND_ASSIGN_MUL:
diff --git a/ext/opcache/Optimizer/escape_analysis.c b/ext/opcache/Optimizer/escape_analysis.c
index f88de6202a..a92d773783 100644
--- a/ext/opcache/Optimizer/escape_analysis.c
+++ b/ext/opcache/Optimizer/escape_analysis.c
@@ -217,6 +217,7 @@ static int is_allocation_def(zend_op_array *op_array, zend_ssa *ssa, int def, in
break;
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
if (OP1_INFO() & (MAY_BE_UNDEF | MAY_BE_NULL | MAY_BE_FALSE)) {
/* implicit object/array allocation */
return 1;
@@ -259,6 +260,7 @@ static int is_local_def(zend_op_array *op_array, zend_ssa *ssa, int def, int var
return 1;
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
return 1;
case ZEND_ASSIGN_ADD:
case ZEND_ASSIGN_SUB:
@@ -328,6 +330,7 @@ static int is_escape_use(zend_op_array *op_array, zend_ssa *ssa, int use, int va
/* break missing intentionally */
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
break;
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
@@ -508,7 +511,8 @@ int zend_ssa_escape_analysis(const zend_script *script, zend_op_array *op_array,
if (opline->opcode == ZEND_OP_DATA &&
((opline-1)->opcode == ZEND_ASSIGN_DIM ||
- (opline-1)->opcode == ZEND_ASSIGN_OBJ) &&
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ_REF) &&
op->op1_use == i &&
(op-1)->op1_use >= 0) {
enclosing_root = ees[(op-1)->op1_use];
diff --git a/ext/opcache/Optimizer/sccp.c b/ext/opcache/Optimizer/sccp.c
index 08e1584923..69a3e81c6b 100644
--- a/ext/opcache/Optimizer/sccp.c
+++ b/ext/opcache/Optimizer/sccp.c
@@ -203,6 +203,7 @@ static zend_bool can_replace_op1(
case ZEND_ASSIGN_REF:
case ZEND_ASSIGN_DIM:
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
case ZEND_ASSIGN_ADD:
case ZEND_ASSIGN_SUB:
case ZEND_ASSIGN_MUL:
@@ -251,6 +252,9 @@ static zend_bool can_replace_op1(
case ZEND_VERIFY_RETURN_TYPE:
// TODO: This would require a non-local change ???
return 0;
+ case ZEND_OP_DATA:
+ return (opline - 1)->opcode != ZEND_ASSIGN_OBJ_REF &&
+ (opline - 1)->opcode != ZEND_ASSIGN_STATIC_PROP_REF;
default:
if (ssa_op->op1_def != -1) {
ZEND_ASSERT(0);
@@ -1400,7 +1404,7 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
if (op2) {
SKIP_IF_TOP(op2);
}
- if (!opline->extended_value) {
+ if (opline->extended_value == 0) {
if (zend_optimizer_eval_binary_op(&zv, zend_compound_assign_to_binary_op(opline->opcode), op1, op2) == SUCCESS) {
SET_RESULT(op1, &zv);
SET_RESULT(result, &zv);
@@ -1494,6 +1498,11 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
zval_ptr_dtor_nogc(&zv);
}
}
+ } else if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ SET_RESULT_BOT(result);
+ break;
+ } else {
+ ZEND_ASSERT(0 && "Invalid compound assignment kind");
}
SET_RESULT_BOT(result);
SET_RESULT_BOT(op1);
diff --git a/ext/opcache/Optimizer/zend_dfg.c b/ext/opcache/Optimizer/zend_dfg.c
index 4430f193a8..66dd15da00 100644
--- a/ext/opcache/Optimizer/zend_dfg.c
+++ b/ext/opcache/Optimizer/zend_dfg.c
@@ -52,8 +52,14 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg
if (next < end && next->opcode == ZEND_OP_DATA) {
if (next->op1_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
var_num = EX_VAR_TO_NUM(next->op1.var);
- if (!DFG_ISSET(def, set_size, j, var_num)) {
+ if (opline->opcode == ZEND_ASSIGN_OBJ_REF
+ || opline->opcode == ZEND_ASSIGN_STATIC_PROP_REF) {
DFG_SET(use, set_size, j, var_num);
+ DFG_SET(def, set_size, j, var_num);
+ } else {
+ if (!DFG_ISSET(def, set_size, j, var_num)) {
+ DFG_SET(use, set_size, j, var_num);
+ }
}
}
if (next->op2_type & (IS_CV|IS_VAR|IS_TMP_VAR)) {
@@ -92,6 +98,7 @@ int zend_build_dfg(const zend_op_array *op_array, const zend_cfg *cfg, zend_dfg
case ZEND_UNSET_CV:
case ZEND_ASSIGN:
case ZEND_ASSIGN_REF:
+ case ZEND_ASSIGN_OBJ_REF:
case ZEND_BIND_GLOBAL:
case ZEND_BIND_STATIC:
case ZEND_SEND_VAR_EX:
diff --git a/ext/opcache/Optimizer/zend_dump.c b/ext/opcache/Optimizer/zend_dump.c
index 90107785b7..d5f6c4564b 100644
--- a/ext/opcache/Optimizer/zend_dump.c
+++ b/ext/opcache/Optimizer/zend_dump.c
@@ -451,6 +451,8 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *
fprintf(stderr, " (dim)");
} else if (opline->extended_value == ZEND_ASSIGN_OBJ) {
fprintf(stderr, " (obj)");
+ } else if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ fprintf(stderr, " (static prop)");
}
} else if (ZEND_VM_EXT_TYPE == (flags & ZEND_VM_EXT_MASK)) {
switch (opline->extended_value) {
@@ -554,7 +556,7 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *
} else if (ZEND_VM_EXT_SRC == (flags & ZEND_VM_EXT_MASK)) {
if (opline->extended_value == ZEND_RETURNS_VALUE) {
fprintf(stderr, " (value)");
- } else if (opline->extended_value == ZEND_RETURNS_FUNCTION) {
+ } else if (opline->extended_value & ZEND_RETURNS_FUNCTION) {
fprintf(stderr, " (function)");
}
} else {
@@ -585,6 +587,16 @@ static void zend_dump_op(const zend_op_array *op_array, const zend_basic_block *
fprintf(stderr, " (ref)");
}
}
+ if ((ZEND_VM_EXT_DIM_OBJ_WRITE|ZEND_VM_EXT_FETCH_REF) & flags) {
+ uint32_t obj_flags = opline->extended_value & ZEND_FETCH_OBJ_FLAGS;
+ if (obj_flags == ZEND_FETCH_REF) {
+ fprintf(stderr, " (ref)");
+ } else if (obj_flags == ZEND_FETCH_DIM_WRITE) {
+ fprintf(stderr, " (dim write)");
+ } else if (obj_flags == ZEND_FETCH_OBJ_WRITE) {
+ fprintf(stderr, " (obj write)");
+ }
+ }
}
if (opline->op1_type == IS_CONST) {
diff --git a/ext/opcache/Optimizer/zend_inference.c b/ext/opcache/Optimizer/zend_inference.c
index 9f1dfdba20..980f3c5fea 100644
--- a/ext/opcache/Optimizer/zend_inference.c
+++ b/ext/opcache/Optimizer/zend_inference.c
@@ -2240,6 +2240,24 @@ static inline zend_class_entry *get_class_entry(const zend_script *script, zend_
return NULL;
}
+static uint32_t zend_convert_type_code_to_may_be(zend_uchar type_code) {
+ switch (type_code) {
+ case IS_VOID:
+ return MAY_BE_NULL;
+ case IS_CALLABLE:
+ return MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ case IS_ITERABLE:
+ return MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ case IS_ARRAY:
+ return MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ case _IS_BOOL:
+ return MAY_BE_TRUE|MAY_BE_FALSE;
+ default:
+ ZEND_ASSERT(type_code < IS_REFERENCE);
+ return 1 << type_code;
+ }
+}
+
static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce)
{
uint32_t tmp = 0;
@@ -2252,22 +2270,7 @@ static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *ar
*pce = get_class_entry(script, lcname);
zend_string_release_ex(lcname, 0);
} else if (ZEND_TYPE_IS_CODE(arg_info->type)) {
- zend_uchar type_hint = ZEND_TYPE_CODE(arg_info->type);
-
- if (type_hint == IS_VOID) {
- tmp |= MAY_BE_NULL;
- } else if (type_hint == IS_CALLABLE) {
- tmp |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
- } else if (type_hint == IS_ITERABLE) {
- tmp |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
- } else if (type_hint == IS_ARRAY) {
- tmp |= MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
- } else if (type_hint == _IS_BOOL) {
- tmp |= MAY_BE_TRUE|MAY_BE_FALSE;
- } else {
- ZEND_ASSERT(type_hint < IS_REFERENCE);
- tmp |= 1 << type_hint;
- }
+ tmp |= zend_convert_type_code_to_may_be(ZEND_TYPE_CODE(arg_info->type));
} else {
tmp |= MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
}
@@ -2277,6 +2280,123 @@ static uint32_t zend_fetch_arg_info(const zend_script *script, zend_arg_info *ar
return tmp;
}
+static zend_property_info *lookup_prop_info(zend_class_entry *ce, zend_string *name, zend_class_entry *scope) {
+ zend_property_info *prop_info;
+
+ /* If the class is linked, reuse the precise runtime logic. */
+ if (ce->ce_flags & ZEND_ACC_LINKED) {
+ zend_class_entry *prev_scope = EG(fake_scope);
+ EG(fake_scope) = scope;
+ prop_info = zend_get_property_info(ce, name, 1);
+ EG(fake_scope) = prev_scope;
+ if (prop_info && prop_info != ZEND_WRONG_PROPERTY_INFO) {
+ return prop_info;
+ }
+ return NULL;
+ }
+
+ /* Otherwise, handle only some safe cases */
+ prop_info = zend_hash_find_ptr(&ce->properties_info, name);
+ if (prop_info &&
+ ((prop_info->ce == scope) ||
+ (!scope && (prop_info->flags & ZEND_ACC_PUBLIC)))
+ ) {
+ return prop_info;
+ }
+ return NULL;
+}
+
+static zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, int i)
+{
+ zend_property_info *prop_info = NULL;
+ if (opline->op2_type == IS_CONST) {
+ zend_class_entry *ce = NULL;
+
+ if (opline->op1_type == IS_UNUSED) {
+ ce = op_array->scope;
+ } else if (ssa->ops[i].op1_use >= 0) {
+ ce = ssa->var_info[ssa->ops[i].op1_use].ce;
+ }
+ if (ce) {
+ prop_info = lookup_prop_info(ce,
+ Z_STR_P(CRT_CONSTANT_EX(op_array, opline, opline->op2, ssa->rt_constants)),
+ op_array->scope);
+ if (prop_info && (prop_info->flags & ZEND_ACC_STATIC)) {
+ prop_info = NULL;
+ }
+ }
+ }
+ return prop_info;
+}
+
+static zend_property_info *zend_fetch_static_prop_info(const zend_script *script, const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline)
+{
+ zend_property_info *prop_info = NULL;
+ if (opline->op1_type == IS_CONST) {
+ zend_class_entry *ce = NULL;
+ if (opline->op2_type == IS_UNUSED) {
+ int fetch_type = opline->op2.num & ZEND_FETCH_CLASS_MASK;
+ switch (fetch_type) {
+ case ZEND_FETCH_CLASS_SELF:
+ case ZEND_FETCH_CLASS_STATIC:
+ /* We enforce that static property types cannot change during inheritance, so
+ * handling static the same way as self here is legal. */
+ ce = op_array->scope;
+ break;
+ case ZEND_FETCH_CLASS_PARENT:
+ if (op_array->scope) {
+ ce = op_array->scope->parent;
+ }
+ break;
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT_EX(op_array, opline, opline->op2, ssa->rt_constants);
+ ce = get_class_entry(script, Z_STR_P(zv + 1));
+ }
+
+ if (ce) {
+ zval *zv = CRT_CONSTANT_EX(op_array, opline, opline->op1, ssa->rt_constants);
+ prop_info = lookup_prop_info(ce, Z_STR_P(zv), op_array->scope);
+ if (prop_info && !(prop_info->flags & ZEND_ACC_STATIC)) {
+ prop_info = NULL;
+ }
+ }
+ }
+ return prop_info;
+}
+
+static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce)
+{
+ if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
+ uint32_t type = ZEND_TYPE_IS_CLASS(prop_info->type)
+ ? MAY_BE_OBJECT
+ : zend_convert_type_code_to_may_be(ZEND_TYPE_CODE(prop_info->type));
+
+ if (ZEND_TYPE_ALLOW_NULL(prop_info->type)) {
+ type |= MAY_BE_NULL;
+ }
+ if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ type |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (pce) {
+ if (ZEND_TYPE_IS_CE(prop_info->type)) {
+ *pce = ZEND_TYPE_CE(prop_info->type);
+ } else if (ZEND_TYPE_IS_NAME(prop_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release(lcname);
+ } else {
+ *pce = NULL;
+ }
+ }
+ return type;
+ }
+ if (pce) {
+ *pce = NULL;
+ }
+ return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
+}
+
static int zend_update_type_info(const zend_op_array *op_array,
zend_ssa *ssa,
const zend_script *script,
@@ -2474,13 +2594,14 @@ static int zend_update_type_info(const zend_op_array *op_array,
case ZEND_ASSIGN_BW_OR:
case ZEND_ASSIGN_BW_AND:
case ZEND_ASSIGN_BW_XOR:
- case ZEND_ASSIGN_CONCAT:
+ case ZEND_ASSIGN_CONCAT: {
+ zend_property_info *prop_info = NULL;
orig = 0;
tmp = 0;
if (opline->extended_value == ZEND_ASSIGN_OBJ) {
- tmp |= MAY_BE_REF;
+ prop_info = zend_fetch_prop_info(op_array, ssa, opline, i);
orig = t1;
- t1 = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ t1 = zend_fetch_prop_type(script, prop_info, &ce);
t2 = OP1_DATA_INFO();
} else if (opline->extended_value == ZEND_ASSIGN_DIM) {
if (t1 & MAY_BE_ARRAY_OF_REF) {
@@ -2489,6 +2610,10 @@ static int zend_update_type_info(const zend_op_array *op_array,
orig = t1;
t1 = zend_array_element_type(t1, 1, 0);
t2 = OP1_DATA_INFO();
+ } else if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
+ t1 = zend_fetch_prop_type(script, prop_info, &ce);
+ t2 = OP1_DATA_INFO();
} else {
if (t1 & MAY_BE_REF) {
tmp |= MAY_BE_REF;
@@ -2524,10 +2649,13 @@ static int zend_update_type_info(const zend_op_array *op_array,
UPDATE_SSA_TYPE(orig, ssa_ops[i].op1_def);
COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
+ } else if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ /* Nothing to do */
} else {
UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
+ ce = NULL;
if (opline->extended_value == ZEND_ASSIGN_DIM) {
if (opline->op2_type == IS_UNUSED) {
/* When appending to an array and the LONG_MAX key is already used
@@ -2549,10 +2677,25 @@ static int zend_update_type_info(const zend_op_array *op_array,
* anything else results in a null return value. */
tmp |= MAY_BE_NULL;
}
+
+ /* The return value must also satisfy the property type */
+ if (prop_info) {
+ tmp &= zend_fetch_prop_type(script, prop_info, NULL);
+ }
+ } else if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ /* The return value must also satisfy the property type */
+ if (prop_info) {
+ tmp &= zend_fetch_prop_type(script, prop_info, NULL);
+ }
}
+ tmp &= ~MAY_BE_REF;
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+ }
}
break;
+ }
case ZEND_PRE_INC:
case ZEND_PRE_DEC:
tmp = 0;
@@ -2727,9 +2870,13 @@ static int zend_update_type_info(const zend_op_array *op_array,
COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
- // TODO: ???
- tmp = MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ // TODO: If there is no __set we might do better
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_prop_info(op_array, ssa, opline, i), &ce);
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+ }
}
if ((opline+1)->op1_type == IS_CV) {
opline++;
@@ -2827,6 +2974,42 @@ static int zend_update_type_info(const zend_op_array *op_array,
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
}
break;
+ case ZEND_ASSIGN_OBJ_REF:
+ if (opline->op1_type == IS_CV) {
+ tmp = t1;
+ if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
+ tmp |= MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (tmp & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
+ }
+
+ t2 = OP1_DATA_INFO();
+ if ((opline+1)->op1_type == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION)) {
+ tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
+ } else {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_ERROR|MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (ssa_ops[i].result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ }
+ if ((opline+1)->op1_type == IS_CV) {
+ opline++;
+ i++;
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].op1_def);
+ }
+ break;
case ZEND_BIND_GLOBAL:
tmp = MAY_BE_REF | MAY_BE_ANY
| MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
@@ -3245,6 +3428,7 @@ static int zend_update_type_info(const zend_op_array *op_array,
case ZEND_FETCH_OBJ_RW:
case ZEND_FETCH_OBJ_FUNC_ARG:
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_INC_OBJ:
@@ -3342,14 +3526,31 @@ static int zend_update_type_info(const zend_op_array *op_array,
COPY_SSA_OBJ_TYPE(ssa_ops[i].op1_use, ssa_ops[i].op1_def);
}
if (ssa_ops[i].result_def >= 0) {
- tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
- if (opline->result_type == IS_TMP_VAR) {
- /* can't be REF because of ZVAL_COPY_DEREF() usage */
- tmp |= MAY_BE_RC1 | MAY_BE_RCN;
- } else {
- tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ERROR;
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_prop_info(op_array, ssa, opline, i), &ce);
+ if (opline->result_type != IS_TMP_VAR) {
+ tmp |= MAY_BE_REF | MAY_BE_ERROR;
}
UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
+ }
+ }
+ break;
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
+ if (opline->result_type != IS_TMP_VAR) {
+ tmp |= MAY_BE_REF | MAY_BE_ERROR;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_ops[i].result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_ops[i].result_def);
}
break;
case ZEND_DO_FCALL:
@@ -4182,7 +4383,7 @@ int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa
return 1;
}
}
- } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY)) {
switch (opline->opcode) {
case ZEND_CASE:
@@ -4206,9 +4407,9 @@ int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa
return 1;
}
}
- }
+ }
- if (opline->op2_type == IS_CV) {
+ if (opline->op2_type == IS_CV) {
if (t2 & MAY_BE_UNDEF) {
switch (opline->opcode) {
case ZEND_ASSIGN_REF:
@@ -4228,7 +4429,7 @@ int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa
return 1;
}
}
- }
+ }
switch (opline->opcode) {
case ZEND_NOP:
@@ -4390,6 +4591,9 @@ int zend_may_throw(const zend_op *opline, zend_op_array *op_array, zend_ssa *ssa
return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
(t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT));
case ZEND_ASSIGN:
+ if (t1 & MAY_BE_REF) {
+ return 1;
+ }
case ZEND_UNSET_VAR:
return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
case ZEND_ASSIGN_DIM:
diff --git a/ext/opcache/Optimizer/zend_optimizer.c b/ext/opcache/Optimizer/zend_optimizer.c
index d13b8a000e..42cd5eef8f 100644
--- a/ext/opcache/Optimizer/zend_optimizer.c
+++ b/ext/opcache/Optimizer/zend_optimizer.c
@@ -256,6 +256,14 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array,
zval *val)
{
switch (opline->opcode) {
+ case ZEND_OP_DATA:
+ switch ((opline-1)->opcode) {
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ return 0;
+ }
+ opline->op1.constant = zend_optimizer_add_literal(op_array, val);
+ break;
case ZEND_FREE:
case ZEND_CHECK_VAR:
MAKE_NOP(opline);
@@ -312,6 +320,23 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array,
}
zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
break;
+ case ZEND_ASSIGN_ADD:
+ case ZEND_ASSIGN_SUB:
+ case ZEND_ASSIGN_MUL:
+ case ZEND_ASSIGN_DIV:
+ case ZEND_ASSIGN_MOD:
+ case ZEND_ASSIGN_SL:
+ case ZEND_ASSIGN_SR:
+ case ZEND_ASSIGN_CONCAT:
+ case ZEND_ASSIGN_BW_OR:
+ case ZEND_ASSIGN_BW_AND:
+ case ZEND_ASSIGN_BW_XOR:
+ case ZEND_ASSIGN_POW:
+ if (opline->extended_value != ZEND_ASSIGN_STATIC_PROP) {
+ break;
+ }
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
case ZEND_FETCH_STATIC_PROP_R:
case ZEND_FETCH_STATIC_PROP_W:
case ZEND_FETCH_STATIC_PROP_RW:
@@ -319,21 +344,17 @@ int zend_optimizer_update_op1_const(zend_op_array *op_array,
case ZEND_FETCH_STATIC_PROP_UNSET:
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
case ZEND_UNSET_STATIC_PROP:
- TO_STRING_NOWARN(val);
- opline->op1.constant = zend_optimizer_add_literal(op_array, val);
- if (opline->op2_type == IS_CONST && opline->extended_value + sizeof(void*) == op_array->cache_size) {
- op_array->cache_size += sizeof(void *);
- } else {
- opline->extended_value = alloc_cache_slots(op_array, 2);
- }
- break;
case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
TO_STRING_NOWARN(val);
opline->op1.constant = zend_optimizer_add_literal(op_array, val);
- if (opline->op2_type == IS_CONST && (opline->extended_value & ~ZEND_ISEMPTY) + sizeof(void*) == op_array->cache_size) {
+ if (opline->op2_type == IS_CONST && (opline->extended_value & ~ZEND_FETCH_OBJ_FLAGS) + sizeof(void*) == op_array->cache_size) {
op_array->cache_size += sizeof(void *);
} else {
- opline->extended_value = alloc_cache_slots(op_array, 2) | (opline->extended_value & ZEND_ISEMPTY);
+ opline->extended_value = alloc_cache_slots(op_array, 3) | (opline->extended_value & ZEND_FETCH_OBJ_FLAGS);
}
break;
case ZEND_SEND_VAR:
@@ -405,6 +426,8 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
opline->result.num = alloc_cache_slots(op_array, 1);
break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_STATIC_PROP_REF:
case ZEND_FETCH_STATIC_PROP_R:
case ZEND_FETCH_STATIC_PROP_W:
case ZEND_FETCH_STATIC_PROP_RW:
@@ -412,21 +435,17 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
case ZEND_FETCH_STATIC_PROP_UNSET:
case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
case ZEND_UNSET_STATIC_PROP:
+ case ZEND_PRE_INC_STATIC_PROP:
+ case ZEND_PRE_DEC_STATIC_PROP:
+ case ZEND_POST_INC_STATIC_PROP:
+ case ZEND_POST_DEC_STATIC_PROP:
+handle_static_prop:
REQUIRES_STRING(val);
drop_leading_backslash(val);
opline->op2.constant = zend_optimizer_add_literal(op_array, val);
zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
if (opline->op1_type != IS_CONST) {
- opline->extended_value = alloc_cache_slots(op_array, 1);
- }
- break;
- case ZEND_ISSET_ISEMPTY_STATIC_PROP:
- REQUIRES_STRING(val);
- drop_leading_backslash(val);
- opline->op2.constant = zend_optimizer_add_literal(op_array, val);
- zend_optimizer_add_literal_string(op_array, zend_string_tolower(Z_STR_P(val)));
- if (opline->op1_type != IS_CONST) {
- opline->extended_value = alloc_cache_slots(op_array, 1) | (opline->extended_value & ZEND_ISEMPTY);
+ opline->extended_value = alloc_cache_slots(op_array, 1) | (opline->extended_value & (ZEND_RETURNS_FUNCTION|ZEND_ISEMPTY|ZEND_FETCH_OBJ_FLAGS));
}
break;
case ZEND_INIT_FCALL:
@@ -477,6 +496,7 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
}
break;
case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_REF:
case ZEND_FETCH_OBJ_R:
case ZEND_FETCH_OBJ_W:
case ZEND_FETCH_OBJ_RW:
@@ -490,12 +510,12 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
case ZEND_POST_DEC_OBJ:
TO_STRING_NOWARN(val);
opline->op2.constant = zend_optimizer_add_literal(op_array, val);
- opline->extended_value = alloc_cache_slots(op_array, 2);
+ opline->extended_value = alloc_cache_slots(op_array, 3);
break;
case ZEND_ISSET_ISEMPTY_PROP_OBJ:
TO_STRING_NOWARN(val);
opline->op2.constant = zend_optimizer_add_literal(op_array, val);
- opline->extended_value = alloc_cache_slots(op_array, 2) | (opline->extended_value & ZEND_ISEMPTY);
+ opline->extended_value = alloc_cache_slots(op_array, 3) | (opline->extended_value & ZEND_ISEMPTY);
break;
case ZEND_ASSIGN_ADD:
case ZEND_ASSIGN_SUB:
@@ -512,7 +532,9 @@ int zend_optimizer_update_op2_const(zend_op_array *op_array,
if (opline->extended_value == ZEND_ASSIGN_OBJ) {
TO_STRING_NOWARN(val);
opline->op2.constant = zend_optimizer_add_literal(op_array, val);
- (opline+1)->extended_value = alloc_cache_slots(op_array, 2);
+ (opline+1)->extended_value = alloc_cache_slots(op_array, 3);
+ } else if (opline->extended_value == ZEND_ASSIGN_STATIC_PROP) {
+ goto handle_static_prop;
} else if (opline->extended_value == ZEND_ASSIGN_DIM) {
if (Z_TYPE_P(val) == IS_STRING) {
zend_ulong index;
diff --git a/ext/opcache/Optimizer/zend_ssa.c b/ext/opcache/Optimizer/zend_ssa.c
index ea387bb95f..ad34e7f643 100644
--- a/ext/opcache/Optimizer/zend_ssa.c
+++ b/ext/opcache/Optimizer/zend_ssa.c
@@ -642,6 +642,22 @@ static int zend_ssa_rename(const zend_op_array *op_array, uint32_t build_flags,
//NEW_SSA_VAR(next->op1.var)
}
break;
+ case ZEND_ASSIGN_OBJ_REF:
+ if (opline->op1_type == IS_CV) {
+ ssa_ops[k].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(opline->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(opline->op1.var)
+ }
+ /* break missing intentionally */
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ if (next->op1_type == IS_CV) {
+ ssa_ops[k + 1].op1_def = ssa_vars_count;
+ var[EX_VAR_TO_NUM(next->op1.var)] = ssa_vars_count;
+ ssa_vars_count++;
+ //NEW_SSA_VAR(next->op1.var)
+ }
+ break;
case ZEND_PRE_INC_OBJ:
case ZEND_PRE_DEC_OBJ:
case ZEND_POST_INC_OBJ:
diff --git a/ext/opcache/tests/opt/prop_types.phpt b/ext/opcache/tests/opt/prop_types.phpt
new file mode 100644
index 0000000000..7962b1b893
--- /dev/null
+++ b/ext/opcache/tests/opt/prop_types.phpt
@@ -0,0 +1,115 @@
+--TEST--
+Property types in inference
+--INI--
+opcache.enable=1
+opcache.enable_cli=1
+opcache.optimization_level=-1
+opcache.opt_debug_level=0x200000
+--SKIPIF--
+<?php require_once('skipif.inc'); ?>
+--FILE--
+<?php
+
+class Test {
+ public bool $public;
+ protected int $protected;
+ private float $private;
+
+ public function inTest() {
+ var_dump($this->public, $this->protected, $this->private);
+ }
+
+ public function inTestWithTest2(Test2 $test2) {
+ var_dump($test2->public, $test2->protected, $test2->private);
+ }
+}
+
+class Test2 extends Test {
+ private array $private;
+
+ public function inTest2() {
+ var_dump($this->public, $this->protected, $this->private);
+ }
+}
+
+function noScope(Test $test) {
+ var_dump($test->public, $test->protected, $test->private);
+}
+
+?>
+--EXPECTF--
+$_main: ; (lines=1, args=0, vars=0, tmps=0, ssa_vars=0, no_loops)
+ ; (before dfa pass)
+ ; %s
+ ; return [long] RANGE[1..1]
+BB0: start exit lines=[0-0]
+ ; level=0
+ RETURN int(1)
+
+noScope: ; (lines=10, args=1, vars=1, tmps=1, ssa_vars=5, no_loops)
+ ; (before dfa pass)
+ ; %s
+ ; return [null] RANGE[0..0]
+ ; #0.CV0($test) NOVAL [undef]
+BB0: start exit lines=[0-9]
+ ; level=0
+ #1.CV0($test) [object (instanceof Test)] = RECV 1
+ INIT_FCALL 3 128 string("var_dump")
+ #2.T1 [bool] = FETCH_OBJ_R #1.CV0($test) [object (instanceof Test)] string("public")
+ SEND_VAL #2.T1 [bool] 1
+ #3.T1 [any] = FETCH_OBJ_R #1.CV0($test) [object (instanceof Test)] string("protected")
+ SEND_VAL #3.T1 [any] 2
+ #4.T1 [any] = FETCH_OBJ_R #1.CV0($test) [object (instanceof Test)] string("private")
+ SEND_VAL #4.T1 [any] 3
+ DO_ICALL
+ RETURN null
+
+Test::inTest: ; (lines=9, args=0, vars=0, tmps=1, ssa_vars=3, no_loops)
+ ; (before dfa pass)
+ ; %s
+ ; return [null] RANGE[0..0]
+BB0: start exit lines=[0-8]
+ ; level=0
+ INIT_FCALL 3 128 string("var_dump")
+ #0.T0 [bool] = FETCH_OBJ_R THIS string("public")
+ SEND_VAL #0.T0 [bool] 1
+ #1.T0 [long] = FETCH_OBJ_R THIS string("protected")
+ SEND_VAL #1.T0 [long] 2
+ #2.T0 [double] = FETCH_OBJ_R THIS string("private")
+ SEND_VAL #2.T0 [double] 3
+ DO_ICALL
+ RETURN null
+
+Test::inTestWithTest2: ; (lines=10, args=1, vars=1, tmps=1, ssa_vars=5, no_loops)
+ ; (before dfa pass)
+ ; %s
+ ; return [null] RANGE[0..0]
+ ; #0.CV0($test2) NOVAL [undef]
+BB0: start exit lines=[0-9]
+ ; level=0
+ #1.CV0($test2) [object (instanceof Test2)] = RECV 1
+ INIT_FCALL 3 128 string("var_dump")
+ #2.T1 [bool] = FETCH_OBJ_R #1.CV0($test2) [object (instanceof Test2)] string("public")
+ SEND_VAL #2.T1 [bool] 1
+ #3.T1 [long] = FETCH_OBJ_R #1.CV0($test2) [object (instanceof Test2)] string("protected")
+ SEND_VAL #3.T1 [long] 2
+ #4.T1 [double] = FETCH_OBJ_R #1.CV0($test2) [object (instanceof Test2)] string("private")
+ SEND_VAL #4.T1 [double] 3
+ DO_ICALL
+ RETURN null
+
+Test2::inTest2: ; (lines=9, args=0, vars=0, tmps=1, ssa_vars=3, no_loops)
+ ; (before dfa pass)
+ ; %s
+ ; return [null] RANGE[0..0]
+BB0: start exit lines=[0-8]
+ ; level=0
+ INIT_FCALL 3 128 string("var_dump")
+ #0.T0 [bool] = FETCH_OBJ_R THIS string("public")
+ SEND_VAL #0.T0 [bool] 1
+ #1.T0 [long] = FETCH_OBJ_R THIS string("protected")
+ SEND_VAL #1.T0 [long] 2
+ #2.T0 [array of [any, ref]] = FETCH_OBJ_R THIS string("private")
+ SEND_VAL #2.T0 [array of [any, ref]] 3
+ DO_ICALL
+ RETURN null
diff --git a/ext/opcache/zend_accelerator_util_funcs.c b/ext/opcache/zend_accelerator_util_funcs.c
index d22cc7ffd7..7b04d0d2df 100644
--- a/ext/opcache/zend_accelerator_util_funcs.c
+++ b/ext/opcache/zend_accelerator_util_funcs.c
@@ -232,6 +232,14 @@ static void zend_hash_clone_prop_info(HashTable *ht)
if (IN_ARENA(prop_info->ce)) {
prop_info->ce = ARENA_REALLOC(prop_info->ce);
}
+
+ if (ZEND_TYPE_IS_CE(prop_info->type)) {
+ zend_class_entry *ce = ZEND_TYPE_CE(prop_info->type);
+ if (IN_ARENA(ce)) {
+ ce = ARENA_REALLOC(ce);
+ prop_info->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop_info->type));
+ }
+ }
}
}
}
@@ -304,6 +312,16 @@ static void zend_class_copy_ctor(zend_class_entry **pce)
/* constants table */
zend_hash_clone_constants(&ce->constants_table);
+ if (ce->properties_info_table) {
+ int i;
+ ce->properties_info_table = ARENA_REALLOC(ce->properties_info_table);
+ for (i = 0; i < ce->default_properties_count; i++) {
+ if (IN_ARENA(ce->properties_info_table[i])) {
+ ce->properties_info_table[i] = ARENA_REALLOC(ce->properties_info_table[i]);
+ }
+ }
+ }
+
if (ce->num_interfaces) {
zend_class_name *interface_names;
diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c
index 86cb8a2e25..92a77ab792 100644
--- a/ext/opcache/zend_file_cache.c
+++ b/ext/opcache/zend_file_cache.c
@@ -577,6 +577,17 @@ static void zend_file_cache_serialize_prop_info(zval *zv,
SERIALIZE_STR(prop->doc_comment);
}
}
+ if (prop->type) {
+ if (ZEND_TYPE_IS_NAME(prop->type)) {
+ zend_string *name = ZEND_TYPE_NAME(prop->type);
+ SERIALIZE_STR(name);
+ prop->type = ZEND_TYPE_ENCODE_CLASS(name, ZEND_TYPE_ALLOW_NULL(prop->type));
+ } else if (ZEND_TYPE_IS_CE(prop->type)) {
+ zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
+ SERIALIZE_PTR(ce);
+ prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type));
+ }
+ }
}
}
@@ -660,6 +671,19 @@ static void zend_file_cache_serialize_class(zval *zv,
SERIALIZE_STR(ce->info.user.doc_comment);
zend_file_cache_serialize_hash(&ce->properties_info, script, info, buf, zend_file_cache_serialize_prop_info);
+ if (ce->properties_info_table) {
+ uint32_t i;
+ zend_property_info **table;
+
+ SERIALIZE_PTR(ce->properties_info_table);
+ table = ce->properties_info_table;
+ UNSERIALIZE_PTR(table);
+
+ for (i = 0; i < ce->default_properties_count; i++) {
+ SERIALIZE_PTR(table[i]);
+ }
+ }
+
if (ce->num_interfaces) {
uint32_t i;
zend_class_name *interface_names;
@@ -1246,6 +1270,17 @@ static void zend_file_cache_unserialize_prop_info(zval *zv,
UNSERIALIZE_STR(prop->doc_comment);
}
}
+ if (prop->type) {
+ if (ZEND_TYPE_IS_NAME(prop->type)) {
+ zend_string *name = ZEND_TYPE_NAME(prop->type);
+ UNSERIALIZE_STR(name);
+ prop->type = ZEND_TYPE_ENCODE_CLASS(name, ZEND_TYPE_ALLOW_NULL(prop->type));
+ } else if (ZEND_TYPE_IS_CE(prop->type)) {
+ zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
+ UNSERIALIZE_PTR(ce);
+ prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type));
+ }
+ }
}
}
@@ -1325,6 +1360,15 @@ static void zend_file_cache_unserialize_class(zval *zv,
zend_file_cache_unserialize_hash(&ce->properties_info,
script, buf, zend_file_cache_unserialize_prop_info, NULL);
+ if (ce->properties_info_table) {
+ uint32_t i;
+ UNSERIALIZE_PTR(ce->properties_info_table);
+
+ for (i = 0; i < ce->default_properties_count; i++) {
+ UNSERIALIZE_PTR(ce->properties_info_table[i]);
+ }
+ }
+
if (ce->num_interfaces) {
uint32_t i;
diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c
index eba9dd23e4..af9244bbf5 100644
--- a/ext/opcache/zend_persist.c
+++ b/ext/opcache/zend_persist.c
@@ -746,6 +746,16 @@ static void zend_persist_property_info(zval *zv)
prop->doc_comment = NULL;
}
}
+
+ if (ZEND_TYPE_IS_NAME(prop->type)) {
+ zend_string *class_name = ZEND_TYPE_NAME(prop->type);
+ zend_accel_store_interned_string(class_name);
+ prop->type = ZEND_TYPE_ENCODE_CLASS(class_name, ZEND_TYPE_ALLOW_NULL(prop->type));
+ } else if (ZEND_TYPE_IS_CE(prop->type)) {
+ zend_class_entry *ce = ZEND_TYPE_CE(prop->type);
+ ce = zend_shared_alloc_get_xlat_entry(ce);
+ prop->type = ZEND_TYPE_ENCODE_CE(ce, ZEND_TYPE_ALLOW_NULL(prop->type));
+ }
}
static void zend_persist_class_constant(zval *zv)
@@ -786,6 +796,16 @@ static void zend_persist_class_constant(zval *zv)
}
}
+static zend_bool has_unresolved_property_types(zend_class_entry *ce) {
+ zend_property_info *prop;
+ ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
+ if (ZEND_TYPE_IS_NAME(prop->type)) {
+ return 1;
+ }
+ } ZEND_HASH_FOREACH_END();
+ return 0;
+}
+
static void zend_persist_class_entry(zval *zv)
{
zend_class_entry *ce = Z_PTR_P(zv);
@@ -793,6 +813,7 @@ static void zend_persist_class_entry(zval *zv)
if (ce->type == ZEND_USER_CLASS) {
if ((ce->ce_flags & ZEND_ACC_LINKED)
&& (ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED)
+ && !has_unresolved_property_types(ce)
&& !ZCG(current_persistent_script)->corrupted) {
ZCG(is_immutable_class) = 1;
ce = Z_PTR_P(zv) = zend_shared_memdup_put(ce, sizeof(zend_class_entry));
@@ -855,6 +876,19 @@ static void zend_persist_class_entry(zval *zv)
zend_hash_persist(&ce->properties_info, zend_persist_property_info);
HT_FLAGS(&ce->properties_info) &= (HASH_FLAG_UNINITIALIZED | HASH_FLAG_STATIC_KEYS);
+ if (ce->properties_info_table) {
+ int i;
+
+ size_t size = sizeof(zend_property_info *) * ce->default_properties_count;
+ memcpy(ZCG(arena_mem), ce->properties_info_table, size);
+ ce->properties_info_table = ZCG(arena_mem);
+ ZCG(arena_mem) = (void*)((char*)ZCG(arena_mem) + ZEND_ALIGNED_SIZE(size));
+
+ for (i = 0; i < ce->default_properties_count; i++) {
+ ce->properties_info_table[i] = zend_shared_alloc_get_xlat_entry(ce->properties_info_table[i]);
+ }
+ }
+
if (ce->num_interfaces && !(ce->ce_flags & ZEND_ACC_LINKED)) {
uint32_t i = 0;
diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c
index 5fd48a224e..a17d7c4627 100644
--- a/ext/opcache/zend_persist_calc.c
+++ b/ext/opcache/zend_persist_calc.c
@@ -306,6 +306,11 @@ static void zend_persist_property_info_calc(zval *zv)
zend_shared_alloc_register_xlat_entry(prop, prop);
ADD_SIZE_EX(sizeof(zend_property_info));
ADD_INTERNED_STRING(prop->name);
+ if (ZEND_TYPE_IS_NAME(prop->type)) {
+ zend_string *class_name = ZEND_TYPE_NAME(prop->type);
+ ADD_INTERNED_STRING(class_name);
+ prop->type = ZEND_TYPE_ENCODE_CLASS(class_name, ZEND_TYPE_ALLOW_NULL(prop->type));
+ }
if (ZCG(accel_directives).save_comments && prop->doc_comment) {
ADD_STRING(prop->doc_comment);
}
@@ -326,6 +331,16 @@ static void zend_persist_class_constant_calc(zval *zv)
}
}
+static zend_bool has_unresolved_property_types(zend_class_entry *ce) {
+ zend_property_info *prop;
+ ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop) {
+ if (ZEND_TYPE_IS_NAME(prop->type)) {
+ return 1;
+ }
+ } ZEND_HASH_FOREACH_END();
+ return 0;
+}
+
static void zend_persist_class_entry_calc(zval *zv)
{
zend_class_entry *ce = Z_PTR_P(zv);
@@ -334,6 +349,7 @@ static void zend_persist_class_entry_calc(zval *zv)
ZCG(is_immutable_class) =
(ce->ce_flags & ZEND_ACC_LINKED) &&
(ce->ce_flags & ZEND_ACC_CONSTANTS_UPDATED) &&
+ !has_unresolved_property_types(ce) &&
!ZCG(current_persistent_script)->corrupted;
ADD_SIZE_EX(sizeof(zend_class_entry));
@@ -371,6 +387,10 @@ static void zend_persist_class_entry_calc(zval *zv)
zend_hash_persist_calc(&ce->properties_info, zend_persist_property_info_calc);
+ if (ce->properties_info_table) {
+ ADD_ARENA_SIZE(sizeof(zend_property_info *) * ce->default_properties_count);
+ }
+
if (ce->num_interfaces) {
uint32_t i;