|
21 | 21 | use Php\Test\TestNamespace;
|
22 | 22 |
|
23 | 23 | # This is not allowed, but we at least shouldn't crash.
|
24 |
| -class C extends \Google\Protobuf\Internal\Message { |
25 |
| - public function __construct($data = null) { |
| 24 | +class C extends \Google\Protobuf\Internal\Message |
| 25 | +{ |
| 26 | + public function __construct($data = null) |
| 27 | + { |
26 | 28 | parent::__construct($data);
|
27 | 29 | }
|
28 | 30 | }
|
29 | 31 |
|
| 32 | +# This is not allowed, but we at least shouldn't crash. |
| 33 | +class TestMessageMockProxy extends TestMessage |
| 34 | +{ |
| 35 | + public $_proxy_data = null; |
| 36 | + |
| 37 | + public function __construct($data = null) |
| 38 | + { |
| 39 | + $this->_proxy_data = $data; |
| 40 | + // bypass parent constructor |
| 41 | + // This is common behavior by phpunit ond other mock/proxy libraries |
| 42 | + } |
| 43 | +} |
| 44 | + |
30 | 45 | class GeneratedClassTest extends TestBase
|
31 | 46 | {
|
32 | 47 |
|
@@ -1902,6 +1917,38 @@ public function testNoSegfaultWithError()
|
1902 | 1917 | new TestMessage(['optional_int32' => $this->throwIntendedException()]);
|
1903 | 1918 | }
|
1904 | 1919 |
|
| 1920 | + public function testNoSegfaultWithContructorBypass() |
| 1921 | + { |
| 1922 | + if (!extension_loaded('protobuf')) { |
| 1923 | + $this->markTestSkipped('PHP Protobuf extension is not loaded'); |
| 1924 | + } |
| 1925 | + |
| 1926 | + $this->expectException(Exception::class); |
| 1927 | + $this->expectExceptionMessage( |
| 1928 | + "Couldn't find descriptor. " . |
| 1929 | + "The message constructor was likely bypassed, resulting in an uninitialized descriptor." |
| 1930 | + ); |
| 1931 | + |
| 1932 | + $m = new TestMessageMockProxy(['optional_int32' => 123]); |
| 1933 | + |
| 1934 | + /** |
| 1935 | + * At this point the message constructor was bypassed and the descriptor is not initialized. |
| 1936 | + * This is a common PHP pattern where a proxy/mock class extends a concrete class, |
| 1937 | + * frequently used in frameworks like PHPUnit, phpspec, and Mockery. |
| 1938 | + * |
| 1939 | + * When this happens, the message's internal descriptor is never initialized. |
| 1940 | + * |
| 1941 | + * Without proper handling, accessing properties via getters (like $this->getOptionalInt32()) |
| 1942 | + * would cause the C extension to segfault when trying to access the uninitialized descriptor. |
| 1943 | + * |
| 1944 | + * Instead of segfaulting, we now detect this uninitialized state and throw an exception. |
| 1945 | + * |
| 1946 | + * See: https://github.com/protocolbuffers/protobuf/issues/19978 |
| 1947 | + */ |
| 1948 | + |
| 1949 | + $m->getOptionalInt32(); |
| 1950 | + } |
| 1951 | + |
1905 | 1952 | public function testNoExceptionWithVarDump()
|
1906 | 1953 | {
|
1907 | 1954 | $m = new Sub(['a' => 1]);
|
|
0 commit comments