CallTraceReader::__construct()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 0
c 0
b 0
f 0
nc 1
nop 3
dl 0
loc 5
rs 10
1
<?php
2
3
/**
4
 * This file is part of the sj-i/php-profiler 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 PhpProfiler\Lib\PhpProcessReader\PhpMemoryReader;
15
16
use PhpProfiler\Lib\PhpInternals\Opcodes\OpcodeFactory;
17
use PhpProfiler\Lib\PhpInternals\Types\Zend\Opline;
18
use PhpProfiler\Lib\PhpInternals\Types\Zend\ZendCastedTypeProvider;
19
use PhpProfiler\Lib\PhpInternals\Types\Zend\ZendExecuteData;
20
use PhpProfiler\Lib\PhpInternals\Types\Zend\ZendExecutorGlobals;
21
use PhpProfiler\Lib\PhpInternals\Types\Zend\ZendFunction;
22
use PhpProfiler\Lib\PhpInternals\Types\Zend\ZendOp;
23
use PhpProfiler\Lib\PhpInternals\ZendTypeReader;
24
use PhpProfiler\Lib\PhpInternals\ZendTypeReaderCreator;
25
use PhpProfiler\Lib\PhpProcessReader\CallFrame;
26
use PhpProfiler\Lib\PhpProcessReader\CallTrace;
27
use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderInterface;
28
use PhpProfiler\Lib\Process\MemoryReader\MemoryReaderException;
29
use PhpProfiler\Lib\Process\Pointer\Dereferencer;
30
use PhpProfiler\Lib\Process\Pointer\Pointer;
31
use PhpProfiler\Lib\Process\Pointer\RemoteProcessDereferencer;
32
use PhpProfiler\Lib\Process\ProcessSpecifier;
33
34
final class CallTraceReader
35
{
36
    private ?ZendTypeReader $zend_type_reader = null;
37
38
    public function __construct(
39
        private MemoryReaderInterface $memory_reader,
40
        private ZendTypeReaderCreator $zend_type_reader_creator,
41
        private OpcodeFactory $opcode_factory,
42
    ) {
43
    }
44
45
    /**
46
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
47
     */
48
    public function getTypeReader(string $php_version): ZendTypeReader
49
    {
50
        if (is_null($this->zend_type_reader)) {
51
            $this->zend_type_reader = $this->zend_type_reader_creator->create($php_version);
52
        }
53
        return $this->zend_type_reader;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->zend_type_reader could return the type null which is incompatible with the type-hinted return PhpProfiler\Lib\PhpInternals\ZendTypeReader. Consider adding an additional type-check to rule them out.
Loading history...
54
    }
55
56
    /**
57
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
58
     */
59
    private function getDereferencer(int $pid, string $php_version): Dereferencer
60
    {
61
        return new RemoteProcessDereferencer(
62
            $this->memory_reader,
63
            new ProcessSpecifier($pid),
64
            new ZendCastedTypeProvider(
65
                $this->getTypeReader($php_version),
66
            )
67
        );
68
    }
69
70
    /**
71
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
72
     */
73
    private function getExecutorGlobals(
74
        int $eg_address,
75
        string $php_version,
76
        Dereferencer $dereferencer
77
    ): ZendExecutorGlobals {
78
        $zend_type_reader = $this->getTypeReader($php_version);
79
        $eg_pointer = new Pointer(
80
            ZendExecutorGlobals::class,
81
            $eg_address,
82
            $zend_type_reader->sizeOf('zend_executor_globals')
83
        );
84
        return $dereferencer->deref($eg_pointer);
85
    }
86
87
    /**
88
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
89
     * @throws MemoryReaderException
90
     */
91
    public function readCurrentFunctionName(int $pid, string $php_version, int $executor_globals_address): string
92
    {
93
        $dereferencer = $this->getDereferencer($pid, $php_version);
94
        $eg = $this->getExecutorGlobals($executor_globals_address, $php_version, $dereferencer);
95
        if (is_null($eg->current_execute_data)) {
96
            throw new \Exception('cannot read current execute data');
97
        }
98
        /**
99
         * @var ZendExecuteData $current_execute_data
100
         * @psalm-ignore-var
101
         */
102
        $current_execute_data = $dereferencer->deref($eg->current_execute_data);
103
        return $current_execute_data->getFunctionName($dereferencer) ?? 'unknown';
104
    }
105
106
    /**
107
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
108
     * @throws MemoryReaderException
109
     */
110
    public function readCallTrace(int $pid, string $php_version, int $executor_globals_address, int $depth): ?CallTrace
111
    {
112
        $dereferencer = $this->getDereferencer($pid, $php_version);
113
        $eg = $this->getExecutorGlobals($executor_globals_address, $php_version, $dereferencer);
114
        if (is_null($eg->current_execute_data)) {
115
            return null;
116
        }
117
        /**
118
         * @var ZendExecuteData $current_execute_data
119
         * @psalm-ignore-var
120
         */
121
        $current_execute_data = $dereferencer->deref($eg->current_execute_data);
122
123
        $stack = [];
124
        $stack[] = $current_execute_data;
125
        for ($i = 0; $i < $depth; $i++) {
126
            if (is_null($current_execute_data->prev_execute_data)) {
127
                break;
128
            }
129
            $current_execute_data = $dereferencer->deref($current_execute_data->prev_execute_data);
130
            $stack[] = $current_execute_data;
131
        }
132
133
        $result = [];
134
        foreach ($stack as $current_execute_data) {
135
            if (is_null($current_execute_data->func)) {
136
                $result[] = new CallFrame(
137
                    '',
138
                    '<unknown>',
139
                    '<unknown>',
140
                    null
141
                );
142
                continue;
143
            }
144
            /**
145
             * @var ZendFunction $current_function
146
             * @psalm-ignore-var
147
             */
148
            $current_function = $dereferencer->deref($current_execute_data->func);
149
150
            $function_name = $current_function->getFunctionName($dereferencer) ?? '<main>';
151
            $class_name = $current_function->getClassName($dereferencer) ?? '';
152
            $file_name = $current_function->getFileName($dereferencer) ?? '<unknown>';
153
154
            $opline = null;
155
            if ($file_name !== '<internal>' and !is_null($current_execute_data->opline)) {
156
                $opline = $this->readOpline(
157
                    $php_version,
158
                    $dereferencer->deref($current_execute_data->opline)
159
                );
160
            }
161
162
            $result[] = new CallFrame(
163
                $class_name,
164
                $function_name,
165
                $file_name,
166
                $opline
167
            );
168
        }
169
170
        return new CallTrace(...$result);
171
    }
172
173
    /**
174
     * @param value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> $php_version
0 ignored issues
show
Documentation Bug introduced by
The doc comment value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS> at position 0 could not be parsed: Unknown type name 'value-of' at position 0 in value-of<ZendTypeReader::ALL_SUPPORTED_VERSIONS>.
Loading history...
175
     */
176
    private function readOpline(string $php_version, ZendOp $zend_op): Opline
177
    {
178
        return new Opline(
179
            $zend_op->op1,
180
            $zend_op->op2,
181
            $zend_op->result,
182
            $zend_op->extended_value,
183
            $zend_op->lineno,
184
            $this->opcode_factory->create($php_version, $zend_op->opcode),
185
            $zend_op->op1_type,
186
            $zend_op->op2_type,
187
            $zend_op->result_type
188
        );
189
    }
190
}
191