Last active
November 8, 2023 20:24
-
-
Save dstogov/87ed324e258d403b3ec66ed6d1f688dc to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git a/Zend/Optimizer/zend_optimizer.c b/Zend/Optimizer/zend_optimizer.c | |
index 463bbbfa84b..523c829fdab 100644 | |
--- a/Zend/Optimizer/zend_optimizer.c | |
+++ b/Zend/Optimizer/zend_optimizer.c | |
@@ -1330,6 +1330,44 @@ static void zend_redo_pass_two_ex(zend_op_array *op_array, zend_ssa *ssa) | |
} | |
break; | |
} | |
+#ifdef ZEND_VERIFY_TYPE_INFERENCE | |
+ if (ssa_op->op1_use >= 0) { | |
+ opline->op1_use_type = ssa->var_info[ssa_op->op1_use].type; | |
+ if (opline->op1_use_type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { | |
+ opline->op1_use_type |= MAY_BE_RC1 | MAY_BE_RCN; | |
+ } | |
+ } | |
+ if (ssa_op->op2_use >= 0) { | |
+ opline->op2_use_type = ssa->var_info[ssa_op->op2_use].type; | |
+ if (opline->op2_use_type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { | |
+ opline->op2_use_type |= MAY_BE_RC1 | MAY_BE_RCN; | |
+ } | |
+ } | |
+ if (ssa_op->result_use >= 0) { | |
+ opline->result_use_type = ssa->var_info[ssa_op->result_use].type; | |
+ if (opline->result_use_type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { | |
+ opline->result_use_type |= MAY_BE_RC1 | MAY_BE_RCN; | |
+ } | |
+ } | |
+ if (ssa_op->op1_def >= 0) { | |
+ opline->op1_def_type = ssa->var_info[ssa_op->op1_def].type; | |
+ if (opline->op1_def_type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { | |
+ opline->op1_def_type |= MAY_BE_RC1 | MAY_BE_RCN; | |
+ } | |
+ } | |
+ if (ssa_op->op2_def >= 0) { | |
+ opline->op2_def_type = ssa->var_info[ssa_op->op2_def].type; | |
+ if (opline->op2_def_type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { | |
+ opline->op2_def_type |= MAY_BE_RC1 | MAY_BE_RCN; | |
+ } | |
+ } | |
+ if (ssa_op->result_def >= 0) { | |
+ opline->result_def_type = ssa->var_info[ssa_op->result_def].type; | |
+ if (opline->result_def_type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) { | |
+ opline->result_def_type |= MAY_BE_RC1 | MAY_BE_RCN; | |
+ } | |
+ } | |
+#endif | |
zend_vm_set_opcode_handler_ex(opline, op1_info, op2_info, res_info); | |
opline++; | |
} | |
diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c | |
index 87bfd0b1e94..d667557d977 100644 | |
--- a/Zend/zend_compile.c | |
+++ b/Zend/zend_compile.c | |
@@ -124,6 +124,14 @@ static void init_op(zend_op *op) | |
MAKE_NOP(op); | |
op->extended_value = 0; | |
op->lineno = CG(zend_lineno); | |
+#ifdef ZEND_VERIFY_TYPE_INFERENCE | |
+ op->op1_use_type = 0; | |
+ op->op2_use_type = 0; | |
+ op->result_use_type = 0; | |
+ op->op1_def_type = 0; | |
+ op->op2_def_type = 0; | |
+ op->result_def_type = 0; | |
+#endif | |
} | |
static zend_always_inline uint32_t get_next_op_number(void) | |
diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h | |
index 765e54fb56e..ca8e6541329 100644 | |
--- a/Zend/zend_compile.h | |
+++ b/Zend/zend_compile.h | |
@@ -143,6 +143,14 @@ struct _zend_op { | |
uint8_t op1_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */ | |
uint8_t op2_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */ | |
uint8_t result_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */ | |
+#ifdef ZEND_VERIFY_TYPE_INFERENCE | |
+ uint32_t op1_use_type; | |
+ uint32_t op2_use_type; | |
+ uint32_t result_use_type; | |
+ uint32_t op1_def_type; | |
+ uint32_t op2_def_type; | |
+ uint32_t result_def_type; | |
+#endif | |
}; | |
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c | |
index 1819d0b671d..40e496b5d4a 100644 | |
--- a/Zend/zend_execute.c | |
+++ b/Zend/zend_execute.c | |
@@ -4639,6 +4639,16 @@ static void zend_swap_operands(zend_op *op) /* {{{ */ | |
op->op1_type = op->op2_type; | |
op->op2 = tmp; | |
op->op2_type = tmp_type; | |
+ | |
+#ifdef ZEND_VERIFY_TYPE_INFERENCE | |
+ uint32_t tmp_info; | |
+ tmp_info = op->op1_use_type; | |
+ op->op1_use_type = op->op2_use_type; | |
+ op->op2_use_type = tmp_info; | |
+ tmp_info = op->op1_def_type; | |
+ op->op1_def_type = op->op2_def_type; | |
+ op->op2_def_type = tmp_info; | |
+#endif | |
} | |
/* }}} */ | |
#endif | |
@@ -5303,7 +5313,147 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint | |
# include "zend_vm_trace_map.h" | |
#endif | |
+#ifdef ZEND_VERIFY_TYPE_INFERENCE | |
+ | |
+static void zend_verify_type_inference(zval *value, uint32_t type_mask, uint8_t op_type, zend_execute_data *execute_data, const zend_op *opline, const char *msg) | |
+{ | |
+ if (type_mask == MAY_BE_CLASS) { | |
+ return; | |
+ } | |
+ | |
+ if (Z_TYPE_P(value) == IS_INDIRECT) { | |
+ if (!(type_mask & MAY_BE_INDIRECT)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x mising MAY_BE_INDIRECT)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ value = Z_INDIRECT_P(value); | |
+ } | |
+ | |
+ if (Z_REFCOUNTED_P(value)) { | |
+ if (Z_REFCOUNT_P(value) == 1 && !(type_mask & MAY_BE_RC1)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_RC1)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ if (Z_REFCOUNT_P(value) > 1 && !(type_mask & MAY_BE_RCN)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_RCN)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ } | |
+ | |
+ if (Z_TYPE_P(value) == IS_REFERENCE) { | |
+ if (!(type_mask & MAY_BE_REF)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_REF)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ value = Z_REFVAL_P(value); | |
+ } | |
+ | |
+ if (!(type_mask & (1u << Z_TYPE_P(value)))) { | |
+ if (Z_TYPE_P(value) == IS_UNUSED && op_type == IS_VAR && (type_mask & MAY_BE_NULL)) { | |
+ /* FETCH_OBJ_* for typed property may return IS_UNDEF. This is an exception. */ | |
+ } else { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing type %d)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask, Z_TYPE_P(value)); | |
+ } | |
+ } | |
+ | |
+ if (Z_TYPE_P(value) == IS_ARRAY) { | |
+ HashTable *ht = Z_ARRVAL_P(value); | |
+ uint32_t num_checked = 0; | |
+ zend_string *str; | |
+ zval *val; | |
+ if (HT_IS_INITIALIZED(ht)) { | |
+ if (HT_IS_PACKED(ht) && !MAY_BE_PACKED(type_mask)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_ARRAY_PACKED)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ if (!HT_IS_PACKED(ht) && !MAY_BE_HASH(type_mask)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_ARRAY_HASH)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ } else { | |
+ if (!(type_mask & MAY_BE_ARRAY_EMPTY)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_ARRAY_EMPTY)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ } | |
+ } | |
+ ZEND_HASH_FOREACH_STR_KEY_VAL(ht, str, val) { | |
+ if (str) { | |
+ if (!(type_mask & MAY_BE_ARRAY_KEY_STRING)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_ARRAY_KEY_STRING)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ break; | |
+ } | |
+ } else { | |
+ if (!(type_mask & MAY_BE_ARRAY_KEY_LONG)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing MAY_BE_ARRAY_KEY_LONG)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask); | |
+ break; | |
+ } | |
+ } | |
+ | |
+ uint32_t array_type = 1u << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT); | |
+ if (!(type_mask & array_type)) { | |
+ fprintf(stderr, "Inference verification failed at %04d %s (mask 0x%x missing array type %d)\n", (int)(opline - EX(func)->op_array.opcodes), msg, type_mask, Z_TYPE_P(val)); | |
+ break; | |
+ } | |
+ | |
+ /* Don't check all elements of large arrays. */ | |
+ if (++num_checked > 16) { | |
+ break; | |
+ } | |
+ } ZEND_HASH_FOREACH_END(); | |
+ } | |
+} | |
+ | |
+static void zend_verify_inference_use(zend_execute_data *execute_data, const zend_op *opline) | |
+{ | |
+ if (opline->op1_use_type | |
+ && (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) | |
+ && opline->opcode != ZEND_ROPE_ADD | |
+ && opline->opcode != ZEND_ROPE_END) { | |
+ zend_verify_type_inference(EX_VAR(opline->op1.var), opline->op1_use_type, opline->op1_type, execute_data, opline, "op1_use"); | |
+ } | |
+ if (opline->op2_use_type | |
+ && (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV))) { | |
+ zend_verify_type_inference(EX_VAR(opline->op2.var), opline->op2_use_type, opline->op2_type, execute_data, opline, "op2_use"); | |
+ } | |
+ if (opline->result_use_type | |
+ && (opline->result_type & (IS_TMP_VAR|IS_VAR|IS_CV))) { | |
+ zend_verify_type_inference(EX_VAR(opline->result.var), opline->result_use_type, opline->result_type, execute_data, opline, "result_use"); | |
+ } | |
+} | |
+ | |
+static void zend_verify_inference_def(zend_execute_data *execute_data, const zend_op *opline) | |
+{ | |
+ if (EG(exception)) { | |
+ return; | |
+ } | |
+ if (opline->op1_def_type | |
+ && (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) | |
+ // array is actually changed by the the following insruction(s) | |
+ && opline->opcode != ZEND_FETCH_DIM_W | |
+ && opline->opcode != ZEND_FETCH_DIM_RW | |
+ && opline->opcode != ZEND_FETCH_DIM_FUNC_ARG | |
+ && opline->opcode != ZEND_FETCH_LIST_W) { | |
+ zend_verify_type_inference(EX_VAR(opline->op1.var), opline->op1_def_type, opline->op1_type, execute_data, opline, "op1_def"); | |
+ } | |
+ if (opline->op2_def_type | |
+ && (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV))) { | |
+ zend_verify_type_inference(EX_VAR(opline->op2.var), opline->op2_def_type, opline->op2_type, execute_data, opline, "op2_def"); | |
+ } | |
+ if (opline->result_def_type | |
+ && (opline->result_type & (IS_TMP_VAR|IS_VAR|IS_CV)) | |
+ && opline->opcode != ZEND_ROPE_INIT | |
+ && opline->opcode != ZEND_ROPE_ADD | |
+ // Some jump opcode handlers don't set result when it's never read | |
+ && opline->opcode != ZEND_JMP_SET | |
+ && opline->opcode != ZEND_JMP_NULL | |
+ && opline->opcode != ZEND_COALESCE | |
+ && opline->opcode != ZEND_ASSERT_CHECK) { | |
+ zend_verify_type_inference(EX_VAR(opline->result.var), opline->result_def_type, opline->result_type, execute_data, opline, "result_def"); | |
+ } | |
+} | |
+ | |
+# define ZEND_VERIFY_INFERENCE_USE() zend_verify_inference_use(execute_data, OPLINE); | |
+# define ZEND_VERIFY_INFERENCE_DEF() zend_verify_inference_def(execute_data, OPLINE); | |
+#else | |
+# define ZEND_VERIFY_INFERENCE_USE() | |
+# define ZEND_VERIFY_INFERENCE_DEF() | |
+#endif | |
+ | |
#define ZEND_VM_NEXT_OPCODE_EX(check_exception, skip) \ | |
+ ZEND_VERIFY_INFERENCE_DEF() \ | |
CHECK_SYMBOL_TABLES() \ | |
if (check_exception) { \ | |
OPLINE = EX(opline) + (skip); \ | |
@@ -5311,6 +5461,7 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint | |
ZEND_ASSERT(!EG(exception)); \ | |
OPLINE = opline + (skip); \ | |
} \ | |
+ ZEND_VERIFY_INFERENCE_USE() \ | |
ZEND_VM_CONTINUE() | |
#define ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION() \ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment