Coverage Report

Created: 2026-06-02 06:40

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