ZendExecuteData::getFunctionClassName()   A
last analyzed

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
c 0
b 0
f 0
nc 2
nop 1
dl 0
loc 8
rs 10
1
<?php
2
3
/**
4
 * This file is part of the reliforp/reli-prof package.
5
 *
6
 * (c) sji <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
declare(strict_types=1);
13
14
namespace Reli\Lib\PhpInternals\Types\Zend;
15
16
use FFI\PhpInternals\zend_execute_data;
17
use Reli\Lib\PhpInternals\CastedCData;
18
use Reli\Lib\PhpInternals\ZendTypeReader;
19
use Reli\Lib\Process\Pointer\Dereferencable;
20
use Reli\Lib\Process\Pointer\Dereferencer;
21
use Reli\Lib\Process\Pointer\Pointer;
22
23
final class ZendExecuteData implements Dereferencable
24
{
25
    /** @var Pointer<ZendFunction>|null */
26
    public ?Pointer $func;
27
28
    /** @var Pointer<ZendExecuteData>|null */
29
    public ?Pointer $prev_execute_data;
30
31
    /** @var Pointer<ZendOp>|null */
32
    public ?Pointer $opline;
33
34
    /** @psalm-suppress PropertyNotSetInConstructor */
35
    public Zval $This;
36
37
    /** @var Pointer<ZendArray>|null  */
38
    public ?Pointer $symbol_table;
39
40
    /** @var Pointer<ZendArray>|null  */
41
    public ?Pointer $extra_named_params;
42
43
    /**
44
     * @param CastedCData<zend_execute_data> $casted_cdata
45
     * @param Pointer<ZendExecuteData> $pointer
46
     */
47
    public function __construct(
48
        private CastedCData $casted_cdata,
49
        private Pointer $pointer,
50
    ) {
51
        unset($this->func);
52
        unset($this->prev_execute_data);
53
        unset($this->opline);
54
        unset($this->This);
55
        unset($this->symbol_table);
56
        unset($this->extra_named_params);
57
    }
58
59
    public function __get(string $field_name): mixed
60
    {
61
        return match ($field_name) {
62
            'func' => $this->func =
63
                $this->casted_cdata->casted->func !== null
64
                ? Pointer::fromCData(
65
                    ZendFunction::class,
66
                    $this->casted_cdata->casted->func,
67
                )
68
                : null
69
            ,
70
            'prev_execute_data' => $this->prev_execute_data =
71
                $this->casted_cdata->casted->prev_execute_data !== null
72
                ? Pointer::fromCData(
73
                    ZendExecuteData::class,
74
                    $this->casted_cdata->casted->prev_execute_data,
75
                )
76
                : null
77
            ,
78
            'opline' => $this->opline =
79
                $this->casted_cdata->casted->opline !== null
80
                ? Pointer::fromCData(
81
                    ZendOp::class,
82
                    $this->casted_cdata->casted->opline
83
                )
84
                : null
85
            ,
86
            'This' => $this->This = new Zval(
87
                new CastedCData(
88
                    $this->casted_cdata->casted->This,
89
                    $this->casted_cdata->casted->This,
90
                ),
91
                new Pointer(
92
                    Zval::class,
93
                    $this->pointer->address
94
                    +
95
                    \FFI::typeof($this->casted_cdata->casted)->getStructFieldOffset('This'),
0 ignored issues
show
Bug introduced by
The method getStructFieldOffset() does not exist on FFI\CType. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

95
                    \FFI::typeof($this->casted_cdata->casted)->/** @scrutinizer ignore-call */ getStructFieldOffset('This'),

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
96
                    \FFI::sizeof($this->casted_cdata->casted->This),
97
                ),
98
            ),
99
            'symbol_table' => $this->symbol_table =
100
                $this->casted_cdata->casted->symbol_table !== null
101
                ? Pointer::fromCData(
102
                    ZendArray::class,
0 ignored issues
show
Bug introduced by
The type Reli\Lib\PhpInternals\Types\Zend\ZendArray was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
103
                    $this->casted_cdata->casted->symbol_table,
104
                )
105
                : null
106
            ,
107
            'extra_named_params' => $this->extra_named_params =
108
                $this->casted_cdata->casted->extra_named_params !== null
109
                ? Pointer::fromCData(
110
                    ZendArray::class,
111
                    $this->casted_cdata->casted->extra_named_params,
112
                )
113
                : null
114
            ,
115
        };
116
    }
117
118
    public static function getCTypeName(): string
119
    {
120
        return 'zend_execute_data';
121
    }
122
123
    public static function fromCastedCData(
124
        CastedCData $casted_cdata,
125
        Pointer $pointer
126
    ): static {
127
        /**
128
         * @var CastedCData<zend_execute_data> $casted_cdata
129
         * @var Pointer<ZendExecuteData> $pointer
130
         */
131
        return new self($casted_cdata, $pointer);
132
    }
133
134
    /** @return Pointer<ZendExecuteData> */
135
    public function getPointer(): Pointer
136
    {
137
        return $this->pointer;
138
    }
139
140
    public function hasThis(): bool
141
    {
142
        return $this->This->value->obj !== null
143
            and ($this->This->u1->type_info & (8 | ((1 << 0) << 8) | ((1 << 1) << 8)))
144
        ;
145
    }
146
147
    public function isFunctionlessCall(ZendTypeReader $zend_type_reader): bool
148
    {
149
        return
150
            (bool)($this->This->u1->type_info & (int)$zend_type_reader->constants::ZEND_CALL_CODE)
151
            or
152
            (bool)($this->This->u1->type_info & (int)$zend_type_reader->constants::ZEND_CALL_TOP)
153
        ;
154
    }
155
156
    public function hasSymbolTable(): bool
157
    {
158
        return (bool)($this->This->u1->type_info & (1 << 20));
159
    }
160
161
    public function hasExtraNamedParams(): bool
162
    {
163
        return (bool)($this->This->u1->type_info & (1 << 27));
164
    }
165
166
    public function isInternalCall(Dereferencer $dereferencer): bool
167
    {
168
        if (is_null($this->func)) {
169
            return false;
170
        }
171
        $func = $dereferencer->deref($this->func);
172
        return $func->isInternalFunction();
173
    }
174
175
    public function getFunctionName(
176
        Dereferencer $dereferencer,
177
        ZendTypeReader $zend_type_reader,
178
    ): string {
179
        $function_name = null;
180
        if (is_null($this->func)) {
181
            if ($this->This->isObject() and !is_null($this->This->value->obj)) {
182
                $object = $dereferencer->deref($this->This->value->obj);
183
                if (!is_null($object->ce)) {
184
                    $class_entry = $dereferencer->deref($object->ce);
185
                    if ($class_entry->getClassName($dereferencer) === 'Generator') {
186
                        $function_name = '<generator>';
187
                    }
188
                }
189
            }
190
        } else {
191
            $func = $dereferencer->deref($this->func);
192
            $function_name = $func->getFunctionName($dereferencer, $zend_type_reader);
193
            $func = $dereferencer->deref($this->func);
194
            if (is_null($function_name)) {
195
                if ($this->isFunctionlessCall($zend_type_reader)) {
196
                    $function_name = '<main>';
197
                } elseif (!$func->isUserFunction()) {
198
                    $function_name = '<internal>';
199
                }
200
            }
201
        }
202
        if ($function_name === '' or is_null($function_name)) {
203
            $function_name = '<unknown>';
204
        }
205
        return $function_name;
206
    }
207
208
    public function getFunctionClassName(
209
        Dereferencer $dereferencer,
210
    ): string {
211
        if (is_null($this->func)) {
212
            return '';
213
        }
214
        $func = $dereferencer->deref($this->func);
215
        return $func->getClassName($dereferencer) ?? '';
216
    }
217
218
    public function getFullyQualifiedFunctionName(
219
        Dereferencer $dereferencer,
220
        ZendTypeReader $zend_type_reader,
221
    ): string {
222
        $function_name = $this->getFunctionName($dereferencer, $zend_type_reader);
223
        if (
224
            $function_name === '<internal>'
225
            or $function_name === '<main>'
226
            or $function_name === '<generator>'
227
        ) {
228
            return $function_name;
229
        }
230
        $class_name = $this->getFunctionClassName($dereferencer);
231
        if ($class_name === '') {
232
            return $function_name;
233
        }
234
        return $class_name . '::' . $function_name;
235
    }
236
237
    /** @return iterable<int, ZendExecuteData> */
238
    public function iterateStackChain(Dereferencer $dereferencer): iterable
239
    {
240
        yield $this;
241
        $stack = $this;
242
        while (!is_null($stack->prev_execute_data)) {
243
            yield $stack = $dereferencer->deref($stack->prev_execute_data);
244
        }
245
    }
246
247
    public function getRootFrame(
248
        Dereferencer $dereferencer,
249
        int $max_depth,
250
    ): ZendExecuteData {
251
        $depth = 0;
252
        $stack = $this;
253
        while (!is_null($stack->prev_execute_data) and ($depth < $max_depth or $max_depth === -1)) {
254
            $stack = $dereferencer->deref($stack->prev_execute_data);
255
            $depth++;
256
        }
257
        return $stack;
258
    }
259
260
    public function getVariableTableAddress(): int
261
    {
262
        return $this->pointer->indexedAt(1)->address;
263
    }
264
265
    public function getTotalVariablesNum(Dereferencer $dereferencer): int
266
    {
267
        if (is_null($this->func)) {
268
            return 0;
269
        }
270
        $func = $dereferencer->deref($this->func);
271
        if (!$func->isUserFunction()) {
272
            return $this->This->u2->num_args;
273
        }
274
        $compiled_variables_num = $func->op_array->last_var;
275
        $tmp_num = $func->op_array->T;
276
        $arg_num = $func->op_array->num_args;
277
        $real_arg_num = $this->This->u2->num_args;
278
        $extra_arg_num = $real_arg_num - $arg_num;
279
        return $compiled_variables_num + $tmp_num + $extra_arg_num;
280
    }
281
282
    /** @return Pointer<ZvalArray> */
283
    public function getVariableTablePointer(Dereferencer $dereferencer): Pointer
284
    {
285
        return new Pointer(
286
            ZvalArray::class,
287
            $this->getVariableTableAddress(),
288
            16 * $this->getTotalVariablesNum($dereferencer),
289
        );
290
    }
291
292
    /** @return Pointer<ZvalArray> */
293
    public function getInternalVariableTablePointer(Dereferencer $dereferencer): Pointer
294
    {
295
        return new Pointer(
296
            ZvalArray::class,
297
            $this->pointer->address + ($this->getCallFrameSlot()) * 16,
298
            16 * $this->getTotalVariablesNum($dereferencer),
299
        );
300
    }
301
302
    /** @return iterable<string, Zval> */
303
    public function getVariablesInternal(
304
        Dereferencer $dereferencer,
305
    ): iterable {
306
        $variable_table_pointer = $this->getInternalVariableTablePointer($dereferencer);
307
        $variable_table = $dereferencer->deref($variable_table_pointer);
308
        $passed_count = $this->getTotalVariablesNum($dereferencer);
309
310
        for ($i = 0; $i < $passed_count; $i++) {
311
            if (!isset($variable_table[$i])) {
312
                continue;
313
            }
314
            $zval = $variable_table[$i];
315
            if ($zval->isUndef()) {
316
                continue;
317
            }
318
            yield '$args_to_internal_function[' . $i . ']' => $zval;
319
        }
320
    }
321
322
    /** @return iterable<string, Zval> */
323
    public function getVariables(Dereferencer $dereferencer, ZendTypeReader $zend_type_reader): iterable
324
    {
325
        if (is_null($this->func)) {
326
            return [];
327
        }
328
        $func = $dereferencer->deref($this->func);
329
330
        $total_variables_num = $this->getTotalVariablesNum($dereferencer);
331
        if ($total_variables_num === 0) {
332
            return [];
333
        }
334
        if (!$func->isUserFunction()) {
335
            yield from $this->getVariablesInternal($dereferencer);
336
            return [];
337
        }
338
339
        $variable_table_pointer = $this->getVariableTablePointer($dereferencer);
340
        $variable_table = $dereferencer->deref($variable_table_pointer);
341
        foreach ($func->op_array->getVariableNames($dereferencer, $zend_type_reader) as $key => $name) {
342
            $zval = $variable_table->offsetGet($key);
343
            if ($zval->isUndef()) {
344
                continue;
345
            }
346
            yield $name => $zval;
347
        }
348
349
        $func = $dereferencer->deref($this->func);
350
        $compiled_variables_num = $func->op_array->last_var;
351
        $tmp_num = $func->op_array->T;
352
        assert(!is_null($this->opline));
353
        $current_op_num = $func->op_array->getOpNumFromOpline($this->opline);
354
        $live_tmp_vars = $func->op_array->findLiveTmpVars($current_op_num, $dereferencer);
355
        $live_tmp_vars_map = array_flip(array_map($this->liveTmpVarToNum(...), $live_tmp_vars));
356
        for ($i = $compiled_variables_num; $i < $compiled_variables_num + $tmp_num; $i++) {
357
            if (!isset($live_tmp_vars_map[$i])) {
358
                continue;
359
            }
360
            $name = '$_T[' . ($i - $compiled_variables_num) . ']';
361
            $zval = $variable_table->offsetGet($i);
362
            if ($zval->isUndef()) {
363
                continue;
364
            }
365
            yield $name => $zval;
366
        }
367
        for ($i = $compiled_variables_num + $tmp_num; $i < $total_variables_num; $i++) {
368
            $name = '$_ExtraArgs[' . ($i - $compiled_variables_num - $tmp_num) . ']';
369
            $zval = $variable_table->offsetGet($i);
370
            if ($zval->isUndef()) {
371
                continue;
372
            }
373
            yield $name => $zval;
374
        }
375
    }
376
377
    public function liveTmpVarToNum(int $live_tmp_var): int
378
    {
379
        return (int)($live_tmp_var / 16) - $this->getCallFrameSlot();
380
    }
381
382
    public function getCallFrameSlot(): int
383
    {
384
        return (int)(($this->pointer->size + 16 - 1) / 16);
385
    }
386
}
387