diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c index 43c3ca295de2..6f4c69e6e8cc 100644 --- a/php/ext/google/protobuf/message.c +++ b/php/ext/google/protobuf/message.c @@ -551,14 +551,32 @@ bool Message_InitFromPhp(upb_msg *msg, const upb_msgdef *m, zval *init, */ PHP_METHOD(Message, __construct) { Message* intern = (Message*)Z_OBJ_P(getThis()); - const Descriptor* desc = Descriptor_GetFromClassEntry(Z_OBJCE_P(getThis())); - const upb_msgdef *msgdef = desc->msgdef; + const Descriptor* desc; + zend_class_entry *ce = Z_OBJCE_P(getThis()); upb_arena *arena = Arena_Get(&intern->arena); zval *init_arr = NULL; - intern->desc = desc; - intern->msg = upb_msg_new(msgdef, arena); - ObjCache_Add(intern->msg, &intern->std); + // This descriptor should always be available, as the generated __construct + // method calls initOnce() to load the descriptor prior to calling us. + // + // However, if the user created their own class derived from Message, this + // will trigger an infinite construction loop and blow the stack. We + // temporarily clear create_object to break this loop (see check in + // NameMap_GetMessage()). + PBPHP_ASSERT(ce->create_object == Message_create); + ce->create_object = NULL; + desc = Descriptor_GetFromClassEntry(ce); + ce->create_object = Message_create; + + if (!desc) { + zend_throw_exception_ex( + NULL, 0, + "Couldn't find descriptor. Note only generated code may derive from " + "\\Google\\Protobuf\\Internal\\Message"); + return; + } + + Message_Initialize(intern, desc); if (zend_parse_parameters(ZEND_NUM_ARGS(), "|a!", &init_arr) == FAILURE) { return; diff --git a/php/src/Google/Protobuf/Internal/Message.php b/php/src/Google/Protobuf/Internal/Message.php index c02d2b451729..64aadf9d260c 100644 --- a/php/src/Google/Protobuf/Internal/Message.php +++ b/php/src/Google/Protobuf/Internal/Message.php @@ -93,8 +93,9 @@ private function initWithGeneratedPool() $pool = DescriptorPool::getGeneratedPool(); $this->desc = $pool->getDescriptorByClassName(get_class($this)); if (is_null($this->desc)) { - user_error(get_class($this) . " is not found in descriptor pool."); - return; + throw new \InvalidArgumentException( + get_class($this) ." is not found in descriptor pool. " . + 'Only generated classes may derive from Message.'); } foreach ($this->desc->getField() as $field) { $setter = $field->getSetter(); diff --git a/php/tests/GeneratedClassTest.php b/php/tests/GeneratedClassTest.php index 90c1069ed3c6..bf57ead73bc3 100644 --- a/php/tests/GeneratedClassTest.php +++ b/php/tests/GeneratedClassTest.php @@ -24,6 +24,13 @@ use PBEmpty\PBEcho\TestEmptyPackage; use Php\Test\TestNamespace; +# This is not allowed, but we at least shouldn't crash. +class C extends \Google\Protobuf\Internal\Message { + public function __construct($data = null) { + parent::__construct($data); + } +} + class GeneratedClassTest extends TestBase { @@ -1671,6 +1678,33 @@ public function testDeepEquality() # TODO: what about unknown fields? } + ######################################################### + # Test hasOneof methods exists and working + ######################################################### + + public function testHasOneof() { + $m = new TestMessage(); + $this->assertFalse($m->hasOneofInt32()); + $m->setOneofInt32(42); + $this->assertTrue($m->hasOneofInt32()); + $m->setOneofString("bar"); + $this->assertFalse($m->hasOneofInt32()); + $this->assertTrue($m->hasOneofString()); + $m->clear(); + $this->assertFalse($m->hasOneofInt32()); + $this->assertFalse($m->hasOneofString()); + } + + ######################################################### + # Test that we don't crash if users create their own messages. + ######################################################### + + public function testUserDefinedClass() { + # This is not allowed, but at least we shouldn't crash. + $this->expectException(Exception::class); + $p = new C(); + } + ######################################################### # Test no segfault when error happens #########################################################