1--TEST-- 2GH-10072 (PHP crashes when execute_ex is overridden and a trampoline is used from internal code) 3--EXTENSIONS-- 4zend_test 5--INI-- 6zend_test.replace_zend_execute_ex=1 7opcache.jit=disable 8--FILE-- 9<?php 10class DummyStreamWrapper 11{ 12 /** @var resource|null */ 13 public $context; 14 15 /** @var resource|null */ 16 public $handle; 17 18 19 public function stream_cast(int $castAs) 20 { 21 return $this->handle; 22 } 23 24 25 public function stream_close(): void 26 { 27 } 28 29 public function stream_open(string $path, string $mode, int $options = 0, ?string &$openedPath = null): bool 30 { 31 return true; 32 } 33 34 35 public function stream_read(int $count) 36 { 37 return 0; 38 } 39 40 41 public function stream_seek(int $offset, int $whence = SEEK_SET): bool 42 { 43 return true; 44 } 45 46 47 public function stream_set_option(int $option, int $arg1, ?int $arg2): bool 48 { 49 return false; 50 } 51 52 53 public function stream_stat() 54 { 55 return []; 56 } 57 58 59 public function stream_tell() 60 { 61 return []; 62 } 63 64 65 public function stream_truncate(int $newSize): bool 66 { 67 return true; 68 } 69 70 71 public function stream_write(string $data) 72 { 73 } 74 75 76 public function unlink(string $path): bool 77 { 78 return false; 79 } 80} 81 82class TrampolineTest { 83 /** @var resource|null */ 84 public $context; 85 86 /** @var object|null */ 87 private $wrapper; 88 89 public function __call(string $name, array $arguments) { 90 if (!$this->wrapper) { 91 $this->wrapper = new DummyStreamWrapper(); 92 } 93 echo 'Trampoline for ', $name, PHP_EOL; 94 return $this->wrapper->$name(...$arguments); 95 } 96 97} 98 99stream_wrapper_register('custom', TrampolineTest::class); 100 101 102$fp = fopen("custom://myvar", "r+"); 103?> 104--EXPECT-- 105Trampoline for stream_open 106Trampoline for stream_close 107