Passed
Push — renovate/php-8.x ( bfb893...222c0a )
by
unknown
02:20
created

ZendArray::findByKey()   A

Complexity

Conditions 5
Paths 8

Size

Total Lines 31
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
cc 5
eloc 19
c 1
b 0
f 1
nc 8
nop 3
dl 0
loc 31
rs 9.3222
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\PhpInternals\Types\Zend;
15
16
use FFI\PhpInternals\zend_array;
17
use FFI\PhpInternals\zend_hash_func_ffi;
18
use PhpProfiler\Lib\PhpInternals\CastedCData;
19
use PhpProfiler\Lib\PhpInternals\Types\C\RawInt32;
20
use PhpProfiler\Lib\Process\Pointer\Dereferencable;
21
use PhpProfiler\Lib\Process\Pointer\Dereferencer;
22
use PhpProfiler\Lib\Process\Pointer\Pointer;
23
24
/**
25
 * struct _zend_array {
26
 * zend_refcounted_h gc;
27
* union {
28
* struct {
29
* zend_uchar    flags;
30
* zend_uchar    _unused;
31
* zend_uchar    nIteratorsCount;
32
* zend_uchar    _unused2;
33
* } v;
34
* uint32_t flags;
35
* } u;
36
* uint32_t          nTableMask;
37
* Bucket           *arData;
38
* uint32_t          nNumUsed;
39
* uint32_t          nNumOfElements;
40
* uint32_t          nTableSize;
41
* uint32_t          nInternalPointer;
42
* zend_long         nNextFreeElement;
43
* dtor_func_t       pDestructor;
44
* }; */
45
final class ZendArray implements Dereferencable
46
{
47
    /** @psalm-suppress PropertyNotSetInConstructor */
48
    public int $flags;
49
    /** @psalm-suppress PropertyNotSetInConstructor */
50
    public int $nTableMask;
51
    /**
52
     * @psalm-suppress PropertyNotSetInConstructor
53
     * @var Pointer<Bucket>
54
     */
55
    public Pointer $arData;
56
    /** @psalm-suppress PropertyNotSetInConstructor */
57
    public int $nNumUsed;
58
    /** @psalm-suppress PropertyNotSetInConstructor */
59
    public int $nNumOfElements;
60
    /** @psalm-suppress PropertyNotSetInConstructor */
61
    public int $nTableSize;
62
    /** @psalm-suppress PropertyNotSetInConstructor */
63
    public int $nInternalPointer;
64
    /** @psalm-suppress PropertyNotSetInConstructor */
65
    public int $nNextFreeElement;
66
67
    /** @param CastedCData<zend_array> $casted_cdata */
68
    public function __construct(
69
        private CastedCData $casted_cdata
70
    ) {
71
        unset($this->flags);
72
        unset($this->nTableMask);
73
        unset($this->arData);
74
        unset($this->nNumUsed);
75
        unset($this->nNumOfElements);
76
        unset($this->nTableSize);
77
        unset($this->nInternalPointer);
78
        unset($this->nNextFreeElement);
79
    }
80
81
    public function __get(string $field_name): mixed
82
    {
83
        return match ($field_name) {
84
            'flags' => $this->flags = $this->casted_cdata->casted->u->flags,
85
            'nTableMask' => $this->nTableMask = $this->casted_cdata->casted->nTableMask,
86
            'arData' => $this->arData = Pointer::fromCData(
87
                Bucket::class,
88
                $this->casted_cdata->casted->arData,
89
            ),
90
            'nNumUsed' => $this->nNumUsed = $this->casted_cdata->casted->nNumUsed,
91
            'nNumOfElements' => $this->nNumOfElements = $this->casted_cdata->casted->nNumOfElements,
92
            'nTableSize' => $this->nTableSize = $this->casted_cdata->casted->nTableSize,
93
            'nInternalPointer' => $this->nInternalPointer = $this->casted_cdata->casted->nInternalPointer,
94
            'nNextFreeElement' => $this->nNextFreeElement = $this->casted_cdata->casted->nNextFreeElement,
95
        };
96
    }
97
98
    public function findByKey(
99
        Dereferencer $dereferencer,
100
        string $key,
101
        int $hash = null
102
    ): ?Bucket {
103
        $hash ??= $this->calculateHash($key);
104
        $hash_index = $hash | $this->nTableMask;
105
        $hash_index = $hash_index & 0xFFFF_FFFF;
0 ignored issues
show
Bug introduced by
The constant PhpProfiler\Lib\PhpInter...\Types\Zend\0xFFFF_FFFF was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
106
        if ($hash_index & 0x8000_0000) {
0 ignored issues
show
Bug introduced by
The constant PhpProfiler\Lib\PhpInter...\Types\Zend\0x8000_0000 was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
107
            $hash_index = $hash_index & ~0x8000_0000;
108
            $hash_index = -2147483648 + $hash_index;
109
        }
110
        $idx = $dereferencer->deref(
111
            $this->calculateIndex($hash_index, $this->arData)
112
        )->value;
113
114
        while ($idx !== -1) {
115
            /** @var Bucket $bucket */
116
            $bucket = $dereferencer->deref(
117
                $this->arData->indexedAt($idx)
118
            );
119
            if ($bucket->h === $hash) {
120
                $bucket_key_zstring = $dereferencer->deref($bucket->key)->getValuePointer($bucket->key);
121
                $bucket_key = (string)$dereferencer->deref($bucket_key_zstring);
122
                if ($bucket_key === $key) {
123
                    return $bucket;
124
                }
125
            }
126
            $idx = $bucket->val->u2->next;
127
        }
128
        return null;
129
    }
130
131
    /**
132
     * @param Pointer<Bucket> $pointer
133
     * @return Pointer<RawInt32>
134
     */
135
    private function calculateIndex(int $index, Pointer $pointer): Pointer
136
    {
137
        return new Pointer(
138
            RawInt32::class,
139
            $pointer->address + $index * 4,
140
            4,
141
        );
142
    }
143
144
    private function calculateHash(string $key): int
145
    {
146
        static $ffi = null;
147
        /** @var ?zend_hash_func_ffi $ffi */
148
        $ffi ??= \FFI::cdef('int zend_hash_func(const char *str, int len);');
149
        assert(!is_null($ffi));
150
151
        return $ffi->zend_hash_func($key, strlen($key));
0 ignored issues
show
Bug introduced by
The method zend_hash_func() does not exist on FFI. It seems like you code against a sub-type of FFI such as FFI\PhpInternals\zend_hash_func_ffi. ( Ignorable by Annotation )

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

151
        return $ffi->/** @scrutinizer ignore-call */ zend_hash_func($key, strlen($key));
Loading history...
152
    }
153
154
    public static function getCTypeName(): string
155
    {
156
        return 'zend_array';
157
    }
158
159
    public static function fromCastedCData(CastedCData $casted_cdata, Pointer $pointer): static
160
    {
161
        /** @var CastedCData<zend_array> $casted_cdata */
162
        return new self($casted_cdata);
163
    }
164
}
165