Coverage Report

Created: 2025-11-16 06:23

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/php-src/Zend/Optimizer/zend_func_info.c
Line
Count
Source
1
/*
2
   +----------------------------------------------------------------------+
3
   | Zend Engine, Func Info                                               |
4
   +----------------------------------------------------------------------+
5
   | Copyright (c) The PHP Group                                          |
6
   +----------------------------------------------------------------------+
7
   | This source file is subject to version 3.01 of the PHP license,      |
8
   | that is bundled with this package in the file LICENSE, and is        |
9
   | available through the world-wide-web at the following url:           |
10
   | https://www.php.net/license/3_01.txt                                 |
11
   | If you did not receive a copy of the PHP license and are unable to   |
12
   | obtain it through the world-wide-web, please send a note to          |
13
   | license@php.net so we can mail you a copy immediately.               |
14
   +----------------------------------------------------------------------+
15
   | Authors: Dmitry Stogov <dmitry@php.net>                              |
16
   |          Xinchen Hui <laruence@php.net>                              |
17
   +----------------------------------------------------------------------+
18
*/
19
20
#include "zend_compile.h"
21
#include "zend_extensions.h"
22
#include "zend_ssa.h"
23
#include "zend_optimizer_internal.h"
24
#include "zend_inference.h"
25
#include "zend_call_graph.h"
26
#include "zend_func_info.h"
27
#include "zend_inference.h"
28
#ifdef _WIN32
29
#include "win32/ioutil.h"
30
#endif
31
32
typedef uint32_t (*info_func_t)(const zend_call_info *call_info, const zend_ssa *ssa);
33
34
typedef struct _func_info_t {
35
  const char *name;
36
  unsigned    name_len;
37
  uint32_t    info;
38
  info_func_t info_func;
39
} func_info_t;
40
41
#define F0(name, info) \
42
  {name, sizeof(name)-1, (info), NULL}
43
#define F1(name, info) \
44
  {name, sizeof(name)-1, (MAY_BE_RC1 | (info)), NULL}
45
#define FN(name, info) \
46
  {name, sizeof(name)-1, (MAY_BE_RC1 | MAY_BE_RCN | (info)), NULL}
47
#define FC(name, callback) \
48
  {name, sizeof(name)-1, 0, callback}
49
50
#include "zend_func_infos.h"
51
52
static uint32_t zend_range_info(const zend_call_info *call_info, const zend_ssa *ssa)
53
227
{
54
227
  ZEND_ASSERT(!call_info->is_frameless);
55
56
227
  if (!call_info->send_unpack
57
227
   && (call_info->num_args == 2 || call_info->num_args == 3)
58
225
   && ssa
59
225
   && !(ssa->cfg.flags & ZEND_SSA_TSSA)) {
60
225
    const zend_op_array *op_array = call_info->caller_op_array;
61
225
    uint32_t t1 = _ssa_op1_info(op_array, ssa, call_info->arg_info[0].opline,
62
225
      ssa->ops ? &ssa->ops[call_info->arg_info[0].opline - op_array->opcodes] : NULL);
63
225
    uint32_t t2 = _ssa_op1_info(op_array, ssa, call_info->arg_info[1].opline,
64
225
      ssa->ops ? &ssa->ops[call_info->arg_info[1].opline - op_array->opcodes] : NULL);
65
225
    uint32_t t3 = 0;
66
225
    uint32_t tmp = MAY_BE_RC1 | MAY_BE_ARRAY;
67
68
225
    if (call_info->num_args == 3) {
69
16
      t3 = _ssa_op1_info(op_array, ssa, call_info->arg_info[2].opline,
70
16
        ssa->ops ? &ssa->ops[call_info->arg_info[2].opline - op_array->opcodes] : NULL);
71
16
    }
72
225
    if ((t1 & MAY_BE_STRING) && (t2 & MAY_BE_STRING)) {
73
16
      tmp |= MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING;
74
16
    }
75
225
    if ((t1 & (MAY_BE_DOUBLE|MAY_BE_STRING))
76
209
        || (t2 & (MAY_BE_DOUBLE|MAY_BE_STRING))
77
209
        || (t3 & (MAY_BE_DOUBLE|MAY_BE_STRING))) {
78
16
      tmp |= MAY_BE_ARRAY_OF_DOUBLE;
79
16
    }
80
225
    if ((t1 & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))
81
225
        && (t2 & ((MAY_BE_ANY|MAY_BE_UNDEF)-MAY_BE_DOUBLE))) {
82
221
      tmp |= MAY_BE_ARRAY_OF_LONG;
83
221
    }
84
225
    if (tmp & MAY_BE_ARRAY_OF_ANY) {
85
221
      tmp |= MAY_BE_ARRAY_PACKED;
86
221
    }
87
225
    return tmp;
88
225
  } else {
89
    /* May throw */
90
2
    return MAY_BE_RC1 | MAY_BE_ARRAY | MAY_BE_ARRAY_EMPTY | MAY_BE_ARRAY_PACKED | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE | MAY_BE_ARRAY_OF_STRING;
91
2
  }
92
227
}
93
94
static const func_info_t old_func_infos[] = {
95
  FC("range",                        zend_range_info),
96
};
97
98
static HashTable func_info;
99
ZEND_API int zend_func_info_rid = -1;
100
101
uint32_t zend_get_internal_func_info(
102
13.8k
    const zend_function *callee_func, const zend_call_info *call_info, const zend_ssa *ssa) {
103
13.8k
  if (callee_func->common.scope) {
104
    /* This is a method, not a function. */
105
1.04k
    return 0;
106
1.04k
  }
107
108
12.7k
  zend_string *name = callee_func->common.function_name;
109
12.7k
  if (!name) {
110
    /* zend_pass_function has no name. */
111
0
    return 0;
112
0
  }
113
114
12.7k
  zval *zv = zend_hash_find_known_hash(&func_info, name);
115
12.7k
  if (!zv) {
116
6.88k
    return 0;
117
6.88k
  }
118
119
5.91k
  const func_info_t *info = Z_PTR_P(zv);
120
5.91k
  if (info->info_func) {
121
227
    return call_info ? info->info_func(call_info, ssa) : 0;
122
5.68k
  } else {
123
5.68k
    uint32_t ret = info->info;
124
125
5.68k
    if (ret & MAY_BE_ARRAY) {
126
2.51k
      ret |= MAY_BE_ARRAY_EMPTY;
127
2.51k
    }
128
5.68k
    return ret;
129
5.68k
  }
130
5.91k
}
131
132
ZEND_API uint32_t zend_get_func_info(
133
    const zend_call_info *call_info, const zend_ssa *ssa,
134
    zend_class_entry **ce, bool *ce_is_instanceof)
135
20.2k
{
136
20.2k
  uint32_t ret = 0;
137
20.2k
  const zend_function *callee_func = call_info->callee_func;
138
20.2k
  *ce = NULL;
139
20.2k
  *ce_is_instanceof = false;
140
141
20.2k
  if (callee_func->type == ZEND_INTERNAL_FUNCTION) {
142
13.8k
    uint32_t internal_ret = zend_get_internal_func_info(callee_func, call_info, ssa);
143
#if !ZEND_DEBUG
144
    if (internal_ret) {
145
      return internal_ret;
146
    }
147
#endif
148
149
13.8k
    ret = zend_get_return_info_from_signature_only(
150
13.8k
      callee_func, /* script */ NULL, ce, ce_is_instanceof, /* use_tentative_return_info */ !call_info->is_prototype);
151
152
13.8k
#if ZEND_DEBUG
153
13.8k
    if (internal_ret) {
154
5.91k
      zend_string *name = callee_func->common.function_name;
155
      /* Check whether the func_info information is a subset of the information we can
156
       * compute from the specified return type, otherwise it contains redundant types. */
157
5.91k
      if (internal_ret & ~ret) {
158
0
        fprintf(stderr, "Inaccurate func info for %s()\n", ZSTR_VAL(name));
159
0
      }
160
      /* Check whether the func info is completely redundant with arginfo. */
161
5.91k
      if (internal_ret == ret) {
162
0
        fprintf(stderr, "Useless func info for %s()\n", ZSTR_VAL(name));
163
0
      }
164
      /* If the return type is not mixed, check that the types match exactly if we exclude
165
       * RC and array information. */
166
5.91k
      uint32_t ret_any = ret & MAY_BE_ANY, internal_ret_any = internal_ret & MAY_BE_ANY;
167
5.91k
      if (ret_any != MAY_BE_ANY) {
168
5.88k
        uint32_t diff = internal_ret_any ^ ret_any;
169
        /* Func info may contain "true" types as well as isolated "null" and "false". */
170
5.88k
        if (diff && !(diff == MAY_BE_FALSE && (ret & MAY_BE_FALSE))
171
0
            && (internal_ret_any & ~(MAY_BE_NULL|MAY_BE_FALSE))) {
172
0
          fprintf(stderr, "Incorrect func info for %s()\n", ZSTR_VAL(name));
173
0
        }
174
5.88k
      }
175
5.91k
      return internal_ret;
176
5.91k
    }
177
13.8k
#endif
178
13.8k
  } else {
179
6.39k
    if (!call_info->is_prototype) {
180
      // FIXME: the order of functions matters!!!
181
6.34k
      const zend_func_info *info = ZEND_FUNC_INFO((zend_op_array*)callee_func);
182
6.34k
      if (info) {
183
6.32k
        ret = info->return_info.type;
184
6.32k
        *ce = info->return_info.ce;
185
6.32k
        *ce_is_instanceof = info->return_info.is_instanceof;
186
6.32k
      }
187
6.34k
    }
188
6.39k
    if (!ret) {
189
5.39k
      ret = zend_get_return_info_from_signature_only(
190
5.39k
        callee_func, /* TODO: script */ NULL, ce, ce_is_instanceof, /* use_tentative_return_info */ !call_info->is_prototype);
191
      /* It's allowed to override a method that return non-reference with a method that returns a reference */
192
5.39k
      if (call_info->is_prototype && (ret & ~MAY_BE_REF)) {
193
56
        ret |= MAY_BE_REF;
194
56
        *ce = NULL;
195
56
      }
196
5.39k
    }
197
6.39k
  }
198
14.3k
  return ret;
199
20.2k
}
200
201
static void zend_func_info_add(const func_info_t *new_func_infos, size_t n)
202
32
{
203
8.54k
  for (size_t i = 0; i < n; i++) {
204
8.51k
    zend_string *key = zend_string_init_interned(new_func_infos[i].name, new_func_infos[i].name_len, 1);
205
206
8.51k
    if (zend_hash_add_ptr(&func_info, key, (void**)&new_func_infos[i]) == NULL) {
207
0
      fprintf(stderr, "ERROR: Duplicate function info for \"%s\"\n", new_func_infos[i].name);
208
0
    }
209
210
8.51k
    zend_string_release_ex(key, 1);
211
8.51k
  }
212
32
}
213
214
zend_result zend_func_info_startup(void)
215
16
{
216
16
  if (zend_func_info_rid == -1) {
217
16
    zend_func_info_rid = zend_get_resource_handle("Zend Optimizer");
218
16
    if (zend_func_info_rid < 0) {
219
0
      return FAILURE;
220
0
    }
221
222
16
    zend_hash_init(&func_info, sizeof(old_func_infos)/sizeof(func_info_t) + sizeof(func_infos)/sizeof(func_info_t), NULL, NULL, 1);
223
224
16
    zend_func_info_add(old_func_infos, sizeof(old_func_infos)/sizeof(func_info_t));
225
16
    zend_func_info_add(func_infos, sizeof(func_infos)/sizeof(func_info_t));
226
16
  }
227
228
16
  return SUCCESS;
229
16
}
230
231
zend_result zend_func_info_shutdown(void)
232
0
{
233
0
  if (zend_func_info_rid != -1) {
234
0
    zend_hash_destroy(&func_info);
235
0
    zend_func_info_rid = -1;
236
0
  }
237
0
  return SUCCESS;
238
0
}