Skip to content

Commit 749b60c

Browse files
iluuu1994dstogov
andcommitted
Add runtime type inference verification
Co-authored-by: Dmitry Stogov <dmitry@zend.com>
1 parent 0b7cd14 commit 749b60c

File tree

6 files changed

+174
-3
lines changed

6 files changed

+174
-3
lines changed

.github/nightly_matrix.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ function get_matrix_include(array $branches) {
6565
'branch' => $branch,
6666
'debug' => true,
6767
'zts' => true,
68-
'configuration_parameters' => "CFLAGS='-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1'",
68+
'configuration_parameters' => "CFLAGS='-DZEND_RC_DEBUG=1 -DPROFITABILITY_CHECKS=0 -DZEND_VERIFY_FUNC_INFO=1 -DZEND_VERIFY_TYPE_INFERENCE'",
6969
'timeout_minutes' => 360,
7070
'test_function_jit' => true,
7171
'asan' => false,

.github/workflows/nightly.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -368,7 +368,7 @@ jobs:
368368
configurationParameters: >-
369369
--enable-debug
370370
--enable-zts
371-
CFLAGS='-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC'
371+
CFLAGS='-fsanitize=undefined,address -fno-sanitize-recover -DZEND_TRACK_ARENA_ALLOC -DZEND_VERIFY_TYPE_INFERENCE'
372372
LDFLAGS='-fsanitize=undefined,address'
373373
- name: make
374374
run: make -j$(/usr/bin/nproc) >/dev/null

.github/workflows/push.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ jobs:
123123
configurationParameters: >-
124124
--${{ matrix.debug && 'enable' || 'disable' }}-debug
125125
--${{ matrix.zts && 'enable' || 'disable' }}-zts
126-
${{ matrix.asan && 'CFLAGS="-fsanitize=undefined,address -fno-sanitize=pointer-overflow -DZEND_TRACK_ARENA_ALLOC" LDFLAGS="-fsanitize=undefined,address -fno-sanitize=pointer-overflow" CC=clang-16 CXX=clang++-16' || '' }}
126+
${{ matrix.asan && 'CFLAGS="-fsanitize=undefined,address -fno-sanitize=pointer-overflow -DZEND_TRACK_ARENA_ALLOC -DZEND_VERIFY_TYPE_INFERENCE" LDFLAGS="-fsanitize=undefined,address -fno-sanitize=pointer-overflow" CC=clang-16 CXX=clang++-16' || '' }}
127127
skipSlow: ${{ matrix.asan }}
128128
- name: make
129129
run: make -j$(/usr/bin/nproc) >/dev/null

Zend/zend_compile.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,14 @@ static void init_op(zend_op *op)
124124
MAKE_NOP(op);
125125
op->extended_value = 0;
126126
op->lineno = CG(zend_lineno);
127+
#ifdef ZEND_VERIFY_TYPE_INFERENCE
128+
op->op1_use_type = 0;
129+
op->op2_use_type = 0;
130+
op->result_use_type = 0;
131+
op->op1_def_type = 0;
132+
op->op2_def_type = 0;
133+
op->result_def_type = 0;
134+
#endif
127135
}
128136

129137
static zend_always_inline uint32_t get_next_op_number(void)

Zend/zend_compile.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ struct _zend_op {
143143
uint8_t op1_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */
144144
uint8_t op2_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */
145145
uint8_t result_type; /* IS_UNUSED, IS_CONST, IS_TMP_VAR, IS_VAR, IS_CV */
146+
#ifdef ZEND_VERIFY_TYPE_INFERENCE
147+
uint32_t op1_use_type;
148+
uint32_t op2_use_type;
149+
uint32_t result_use_type;
150+
uint32_t op1_def_type;
151+
uint32_t op2_def_type;
152+
uint32_t result_def_type;
153+
#endif
146154
};
147155

148156

Zend/zend_execute.c

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4639,6 +4639,16 @@ static void zend_swap_operands(zend_op *op) /* {{{ */
46394639
op->op1_type = op->op2_type;
46404640
op->op2 = tmp;
46414641
op->op2_type = tmp_type;
4642+
4643+
#ifdef ZEND_VERIFY_TYPE_INFERENCE
4644+
uint32_t tmp_info;
4645+
tmp_info = op->op1_use_type;
4646+
op->op1_use_type = op->op2_use_type;
4647+
op->op2_use_type = tmp_info;
4648+
tmp_info = op->op1_def_type;
4649+
op->op1_def_type = op->op2_def_type;
4650+
op->op2_def_type = tmp_info;
4651+
#endif
46424652
}
46434653
/* }}} */
46444654
#endif
@@ -5303,14 +5313,159 @@ static zend_always_inline zend_execute_data *_zend_vm_stack_push_call_frame(uint
53035313
# include "zend_vm_trace_map.h"
53045314
#endif
53055315

5316+
#ifdef ZEND_VERIFY_TYPE_INFERENCE
5317+
5318+
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)
5319+
{
5320+
if (type_mask == MAY_BE_CLASS) {
5321+
return;
5322+
}
5323+
5324+
if (Z_TYPE_P(value) == IS_INDIRECT) {
5325+
if (!(type_mask & MAY_BE_INDIRECT)) {
5326+
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);
5327+
}
5328+
value = Z_INDIRECT_P(value);
5329+
}
5330+
5331+
/* Verifying RC inference is currently not possible because type information is based on the SSA
5332+
* built without ZEND_SSA_RC_INFERENCE, which is missing various definitions for RC-modifying
5333+
* operations. Support could be added by repeating SSA-construction and type inference with the
5334+
* given flag. */
5335+
// if (Z_REFCOUNTED_P(value)) {
5336+
// if (Z_REFCOUNT_P(value) == 1 && !(type_mask & MAY_BE_RC1)) {
5337+
// 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);
5338+
// }
5339+
// if (Z_REFCOUNT_P(value) > 1 && !(type_mask & MAY_BE_RCN)) {
5340+
// 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);
5341+
// }
5342+
// }
5343+
5344+
if (Z_TYPE_P(value) == IS_REFERENCE) {
5345+
if (!(type_mask & MAY_BE_REF)) {
5346+
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);
5347+
}
5348+
value = Z_REFVAL_P(value);
5349+
}
5350+
5351+
if (!(type_mask & (1u << Z_TYPE_P(value)))) {
5352+
if (Z_TYPE_P(value) == IS_UNUSED && op_type == IS_VAR && (type_mask & MAY_BE_NULL)) {
5353+
/* FETCH_OBJ_* for typed property may return IS_UNDEF. This is an exception. */
5354+
} else {
5355+
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));
5356+
}
5357+
}
5358+
5359+
if (Z_TYPE_P(value) == IS_ARRAY) {
5360+
HashTable *ht = Z_ARRVAL_P(value);
5361+
uint32_t num_checked = 0;
5362+
zend_string *str;
5363+
zval *val;
5364+
if (HT_IS_INITIALIZED(ht)) {
5365+
if (HT_IS_PACKED(ht) && !MAY_BE_PACKED(type_mask)) {
5366+
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);
5367+
}
5368+
if (!HT_IS_PACKED(ht) && !MAY_BE_HASH(type_mask)) {
5369+
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);
5370+
}
5371+
} else {
5372+
if (!(type_mask & MAY_BE_ARRAY_EMPTY)) {
5373+
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);
5374+
}
5375+
}
5376+
ZEND_HASH_FOREACH_STR_KEY_VAL(ht, str, val) {
5377+
if (str) {
5378+
if (!(type_mask & MAY_BE_ARRAY_KEY_STRING)) {
5379+
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);
5380+
break;
5381+
}
5382+
} else {
5383+
if (!(type_mask & MAY_BE_ARRAY_KEY_LONG)) {
5384+
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);
5385+
break;
5386+
}
5387+
}
5388+
5389+
uint32_t array_type = 1u << (Z_TYPE_P(val) + MAY_BE_ARRAY_SHIFT);
5390+
if (!(type_mask & array_type)) {
5391+
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));
5392+
break;
5393+
}
5394+
5395+
/* Don't check all elements of large arrays. */
5396+
if (++num_checked > 16) {
5397+
break;
5398+
}
5399+
} ZEND_HASH_FOREACH_END();
5400+
}
5401+
}
5402+
5403+
static void zend_verify_inference_use(zend_execute_data *execute_data, const zend_op *opline)
5404+
{
5405+
if (opline->op1_use_type
5406+
&& (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV))
5407+
&& opline->opcode != ZEND_ROPE_ADD
5408+
&& opline->opcode != ZEND_ROPE_END) {
5409+
zend_verify_type_inference(EX_VAR(opline->op1.var), opline->op1_use_type, opline->op1_type, execute_data, opline, "op1_use");
5410+
}
5411+
if (opline->op2_use_type
5412+
&& (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV))) {
5413+
zend_verify_type_inference(EX_VAR(opline->op2.var), opline->op2_use_type, opline->op2_type, execute_data, opline, "op2_use");
5414+
}
5415+
if (opline->result_use_type
5416+
&& (opline->result_type & (IS_TMP_VAR|IS_VAR|IS_CV))) {
5417+
zend_verify_type_inference(EX_VAR(opline->result.var), opline->result_use_type, opline->result_type, execute_data, opline, "result_use");
5418+
}
5419+
}
5420+
5421+
static void zend_verify_inference_def(zend_execute_data *execute_data, const zend_op *opline)
5422+
{
5423+
if (EG(exception)) {
5424+
return;
5425+
}
5426+
if (opline->op1_def_type
5427+
&& (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV))
5428+
// array is actually changed by the the following instruction(s)
5429+
&& opline->opcode != ZEND_FETCH_DIM_W
5430+
&& opline->opcode != ZEND_FETCH_DIM_RW
5431+
&& opline->opcode != ZEND_FETCH_DIM_FUNC_ARG
5432+
&& opline->opcode != ZEND_FETCH_LIST_W) {
5433+
zend_verify_type_inference(EX_VAR(opline->op1.var), opline->op1_def_type, opline->op1_type, execute_data, opline, "op1_def");
5434+
}
5435+
if (opline->op2_def_type
5436+
&& (opline->op2_type & (IS_TMP_VAR|IS_VAR|IS_CV))) {
5437+
zend_verify_type_inference(EX_VAR(opline->op2.var), opline->op2_def_type, opline->op2_type, execute_data, opline, "op2_def");
5438+
}
5439+
if (opline->result_def_type
5440+
&& (opline->result_type & (IS_TMP_VAR|IS_VAR|IS_CV))
5441+
&& opline->opcode != ZEND_ROPE_INIT
5442+
&& opline->opcode != ZEND_ROPE_ADD
5443+
// Some jump opcode handlers don't set result when it's never read
5444+
&& opline->opcode != ZEND_JMP_SET
5445+
&& opline->opcode != ZEND_JMP_NULL
5446+
&& opline->opcode != ZEND_COALESCE
5447+
&& opline->opcode != ZEND_ASSERT_CHECK) {
5448+
zend_verify_type_inference(EX_VAR(opline->result.var), opline->result_def_type, opline->result_type, execute_data, opline, "result_def");
5449+
}
5450+
}
5451+
5452+
# define ZEND_VERIFY_INFERENCE_USE() zend_verify_inference_use(execute_data, OPLINE);
5453+
# define ZEND_VERIFY_INFERENCE_DEF() zend_verify_inference_def(execute_data, OPLINE);
5454+
#else
5455+
# define ZEND_VERIFY_INFERENCE_USE()
5456+
# define ZEND_VERIFY_INFERENCE_DEF()
5457+
#endif
5458+
53065459
#define ZEND_VM_NEXT_OPCODE_EX(check_exception, skip) \
5460+
ZEND_VERIFY_INFERENCE_DEF() \
53075461
CHECK_SYMBOL_TABLES() \
53085462
if (check_exception) { \
53095463
OPLINE = EX(opline) + (skip); \
53105464
} else { \
53115465
ZEND_ASSERT(!EG(exception)); \
53125466
OPLINE = opline + (skip); \
53135467
} \
5468+
ZEND_VERIFY_INFERENCE_USE() \
53145469
ZEND_VM_CONTINUE()
53155470

53165471
#define ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION() \

0 commit comments

Comments
 (0)