|
| 1 | +--TEST-- |
| 2 | +GH-24622: Tracing JIT must spill inherited register values before property helpers |
| 3 | +--EXTENSIONS-- |
| 4 | +opcache |
| 5 | +spl |
| 6 | +--INI-- |
| 7 | +opcache.enable=1 |
| 8 | +opcache.enable_cli=1 |
| 9 | +opcache.file_update_protection=0 |
| 10 | +opcache.jit_buffer_size=64M |
| 11 | +opcache.jit=tracing |
| 12 | +zend.assertions=1 |
| 13 | +assert.exception=1 |
| 14 | +--CREDITS-- |
| 15 | +frodeborli |
| 16 | +--FILE-- |
| 17 | +<?php |
| 18 | + |
| 19 | +final class StringBuffer |
| 20 | +{ |
| 21 | + private \SplDoublyLinkedList $queue; |
| 22 | + private string $buffer = ''; |
| 23 | + private int $length = 0; |
| 24 | + private int $offset = 0; |
| 25 | + private int $totalRead = 0; |
| 26 | + private int $totalWritten = 0; |
| 27 | + private bool $ended = false; |
| 28 | + |
| 29 | + public function __construct() |
| 30 | + { |
| 31 | + $this->queue = new \SplDoublyLinkedList(); |
| 32 | + } |
| 33 | + |
| 34 | + public function isEmpty(): bool |
| 35 | + { |
| 36 | + return $this->offset === $this->length && $this->queue->isEmpty(); |
| 37 | + } |
| 38 | + |
| 39 | + public function write(string $chunk): void |
| 40 | + { |
| 41 | + if ($this->ended) { |
| 42 | + throw new \RuntimeException('Buffer has been ended'); |
| 43 | + } |
| 44 | + $this->totalWritten += \strlen($chunk); |
| 45 | + $this->queue->push($chunk); |
| 46 | + } |
| 47 | + |
| 48 | + public function read(int $maxLength): string |
| 49 | + { |
| 50 | + if ($maxLength < 0) { |
| 51 | + throw new \OutOfBoundsException("Can't read negative lengths"); |
| 52 | + } |
| 53 | + $this->fill($maxLength); |
| 54 | + $chunk = \substr($this->buffer, $this->offset, $maxLength); |
| 55 | + $length = \strlen($chunk); |
| 56 | + $this->offset += $length; |
| 57 | + $this->totalRead += $length; |
| 58 | + |
| 59 | + return $chunk; |
| 60 | + } |
| 61 | + |
| 62 | + public function end(): void |
| 63 | + { |
| 64 | + if ($this->ended) { |
| 65 | + throw new \LogicException('StringBuffer already ended'); |
| 66 | + } |
| 67 | + $this->ended = true; |
| 68 | + } |
| 69 | + |
| 70 | + public function eof(): bool |
| 71 | + { |
| 72 | + return $this->ended && $this->offset === $this->length && $this->queue->isEmpty(); |
| 73 | + } |
| 74 | + |
| 75 | + public function unread(string $chunk): void |
| 76 | + { |
| 77 | + if ($this->ended && $this->offset === $this->length && $this->queue->isEmpty()) { |
| 78 | + throw new \LogicException("Can't unread to an ended and empty StringBuffer"); |
| 79 | + } |
| 80 | + $chunkLength = \strlen($chunk); |
| 81 | + $this->totalRead -= $chunkLength; |
| 82 | + if ($this->length === $this->offset) { |
| 83 | + $this->queue->unshift($chunk); |
| 84 | + } else { |
| 85 | + $this->buffer = $chunk . \substr($this->buffer, $this->offset); |
| 86 | + $this->length += $chunkLength - $this->offset; |
| 87 | + $this->offset = 0; |
| 88 | + } |
| 89 | + } |
| 90 | + |
| 91 | + protected function fill(int $requiredLength): bool |
| 92 | + { |
| 93 | + if ($this->offset > 4096) { |
| 94 | + $this->buffer = \substr($this->buffer, $this->offset); |
| 95 | + $this->length -= $this->offset; |
| 96 | + $this->offset = 0; |
| 97 | + } |
| 98 | + while ($this->length < $this->offset + $requiredLength && !$this->queue->isEmpty()) { |
| 99 | + $chunk = $this->queue->shift(); |
| 100 | + $chunkLength = \strlen($chunk); |
| 101 | + |
| 102 | + if ($this->length === $this->offset) { |
| 103 | + $this->buffer = $chunk; |
| 104 | + $this->length = $chunkLength; |
| 105 | + $this->offset = 0; |
| 106 | + } else { |
| 107 | + $this->buffer .= $chunk; |
| 108 | + $this->length += $chunkLength; |
| 109 | + } |
| 110 | + } |
| 111 | + |
| 112 | + return $this->length >= $this->offset + $requiredLength; |
| 113 | + } |
| 114 | +} |
| 115 | + |
| 116 | +$sb = new StringBuffer(); |
| 117 | +$count = 0; |
| 118 | +for ($i = 0; $i < 1000; ++$i) { $sb->write('x'); } |
| 119 | +$sb->end(); |
| 120 | +while (!$sb->eof()) { if ('' !== $sb->read(1)) { ++$count; } } |
| 121 | +assert($count === 1000); |
| 122 | + |
| 123 | +$sb = new StringBuffer(); |
| 124 | +$totalReceived = 0; |
| 125 | +$chunk = str_repeat('x', 1024); |
| 126 | +for ($i = 0; $i < 100; ++$i) { $sb->write($chunk); } |
| 127 | +$sb->end(); |
| 128 | +while (!$sb->eof()) { $totalReceived += strlen($sb->read(8192)); } |
| 129 | +assert($totalReceived === 102400); |
| 130 | + |
| 131 | +$sb = new StringBuffer(); |
| 132 | +assert($sb->eof() === false); |
| 133 | +$sb->write('Hello'); |
| 134 | +assert($sb->read(10000) === 'Hello'); |
| 135 | +assert($sb->read(100) === ''); |
| 136 | +assert($sb->write('ab') === null); |
| 137 | +assert($sb->read(1) === 'a'); |
| 138 | +assert($sb->read(2) === 'b'); |
| 139 | +$sb->write('abc'); |
| 140 | +assert($sb->eof() === false); |
| 141 | +$sb->write('def'); |
| 142 | +$sb->end(); |
| 143 | +$sb->unread('b'); |
| 144 | +assert($sb->read(5) === 'babcd'); |
| 145 | +var_dump($sb->eof()); |
| 146 | +assert($sb->read(10000) === 'ef'); |
| 147 | +var_dump($sb->eof()); |
| 148 | + |
| 149 | +--EXPECT-- |
| 150 | +bool(false) |
| 151 | +bool(true) |
0 commit comments