1 // Protocol Buffers - Google's data interchange format
2 // Copyright 2008 Google Inc.  All rights reserved.
3 // https://developers.google.com/protocol-buffers/
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions are
7 // met:
8 //
9 //     * Redistributions of source code must retain the above copyright
10 // notice, this list of conditions and the following disclaimer.
11 //     * Redistributions in binary form must reproduce the above
12 // copyright notice, this list of conditions and the following disclaimer
13 // in the documentation and/or other materials provided with the
14 // distribution.
15 //     * Neither the name of Google Inc. nor the names of its
16 // contributors may be used to endorse or promote products derived from
17 // this software without specific prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 
31 #include <php.h>
32 #include <Zend/zend_exceptions.h>
33 
34 #include "protobuf.h"
35 #include "builtin_descriptors.inc"
36 
37 // Forward declare.
38 static void descriptor_init_c_instance(Descriptor* intern TSRMLS_DC);
39 static void descriptor_free_c(Descriptor* object TSRMLS_DC);
40 
41 static void field_descriptor_init_c_instance(FieldDescriptor* intern TSRMLS_DC);
42 static void field_descriptor_free_c(FieldDescriptor* object TSRMLS_DC);
43 
44 static void enum_descriptor_init_c_instance(EnumDescriptor* intern TSRMLS_DC);
45 static void enum_descriptor_free_c(EnumDescriptor* object TSRMLS_DC);
46 
47 static void enum_value_descriptor_init_c_instance(
48     EnumValueDescriptor *intern TSRMLS_DC);
49 static void enum_value_descriptor_free_c(EnumValueDescriptor *object TSRMLS_DC);
50 
51 static void descriptor_pool_free_c(DescriptorPool* object TSRMLS_DC);
52 static void descriptor_pool_init_c_instance(DescriptorPool* pool TSRMLS_DC);
53 
54 static void internal_descriptor_pool_free_c(
55     InternalDescriptorPool *object TSRMLS_DC);
56 static void internal_descriptor_pool_init_c_instance(
57     InternalDescriptorPool *pool TSRMLS_DC);
58 
59 static void oneof_descriptor_free_c(Oneof* object TSRMLS_DC);
60 static void oneof_descriptor_init_c_instance(Oneof* pool TSRMLS_DC);
61 
62 // -----------------------------------------------------------------------------
63 // Common Utilities
64 // -----------------------------------------------------------------------------
65 
check_upb_status(const upb_status * status,const char * msg)66 static void check_upb_status(const upb_status* status, const char* msg) {
67   if (!upb_ok(status)) {
68     zend_error(E_ERROR, "%s: %s\n", msg, upb_status_errmsg(status));
69   }
70 }
71 
72 // Camel-case the field name and append "Entry" for generated map entry name.
73 // e.g. map<KeyType, ValueType> foo_map => FooMapEntry
append_map_entry_name(char * result,const char * field_name,int pos)74 static void append_map_entry_name(char *result, const char *field_name,
75                                   int pos) {
76   bool cap_next = true;
77   int i;
78 
79   for (i = 0; i < strlen(field_name); ++i) {
80     if (field_name[i] == '_') {
81       cap_next = true;
82     } else if (cap_next) {
83       // Note: Do not use ctype.h due to locales.
84       if ('a' <= field_name[i] && field_name[i] <= 'z') {
85         result[pos++] = field_name[i] - 'a' + 'A';
86       } else {
87         result[pos++] = field_name[i];
88       }
89       cap_next = false;
90     } else {
91       result[pos++] = field_name[i];
92     }
93   }
94   strcat(result, "Entry");
95 }
96 
97 // -----------------------------------------------------------------------------
98 // GPBType
99 // -----------------------------------------------------------------------------
100 
101 zend_class_entry* gpb_type_type;
102 
103 static zend_function_entry gpb_type_methods[] = {
104   ZEND_FE_END
105 };
106 
gpb_type_init(TSRMLS_D)107 void gpb_type_init(TSRMLS_D) {
108   zend_class_entry class_type;
109   INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\GPBType",
110                    gpb_type_methods);
111   gpb_type_type = zend_register_internal_class(&class_type TSRMLS_CC);
112   zend_declare_class_constant_long(gpb_type_type, STR("DOUBLE"),  1 TSRMLS_CC);
113   zend_declare_class_constant_long(gpb_type_type, STR("FLOAT"),   2 TSRMLS_CC);
114   zend_declare_class_constant_long(gpb_type_type, STR("INT64"),   3 TSRMLS_CC);
115   zend_declare_class_constant_long(gpb_type_type, STR("UINT64"),  4 TSRMLS_CC);
116   zend_declare_class_constant_long(gpb_type_type, STR("INT32"),   5 TSRMLS_CC);
117   zend_declare_class_constant_long(gpb_type_type, STR("FIXED64"), 6 TSRMLS_CC);
118   zend_declare_class_constant_long(gpb_type_type, STR("FIXED32"), 7 TSRMLS_CC);
119   zend_declare_class_constant_long(gpb_type_type, STR("BOOL"),    8 TSRMLS_CC);
120   zend_declare_class_constant_long(gpb_type_type, STR("STRING"),  9 TSRMLS_CC);
121   zend_declare_class_constant_long(gpb_type_type, STR("GROUP"),   10 TSRMLS_CC);
122   zend_declare_class_constant_long(gpb_type_type, STR("MESSAGE"), 11 TSRMLS_CC);
123   zend_declare_class_constant_long(gpb_type_type, STR("BYTES"),   12 TSRMLS_CC);
124   zend_declare_class_constant_long(gpb_type_type, STR("UINT32"),  13 TSRMLS_CC);
125   zend_declare_class_constant_long(gpb_type_type, STR("ENUM"),    14 TSRMLS_CC);
126   zend_declare_class_constant_long(gpb_type_type, STR("SFIXED32"),
127                                    15 TSRMLS_CC);
128   zend_declare_class_constant_long(gpb_type_type, STR("SFIXED64"),
129                                    16 TSRMLS_CC);
130   zend_declare_class_constant_long(gpb_type_type, STR("SINT32"), 17 TSRMLS_CC);
131   zend_declare_class_constant_long(gpb_type_type, STR("SINT64"), 18 TSRMLS_CC);
132 }
133 
134 // -----------------------------------------------------------------------------
135 // Descriptor
136 // -----------------------------------------------------------------------------
137 
138 static zend_function_entry descriptor_methods[] = {
139   PHP_ME(Descriptor, getClass, NULL, ZEND_ACC_PUBLIC)
140   PHP_ME(Descriptor, getFullName, NULL, ZEND_ACC_PUBLIC)
141   PHP_ME(Descriptor, getField, NULL, ZEND_ACC_PUBLIC)
142   PHP_ME(Descriptor, getFieldCount, NULL, ZEND_ACC_PUBLIC)
143   PHP_ME(Descriptor, getOneofDecl, NULL, ZEND_ACC_PUBLIC)
144   PHP_ME(Descriptor, getOneofDeclCount, NULL, ZEND_ACC_PUBLIC)
145   ZEND_FE_END
146 };
147 
148 DEFINE_CLASS(Descriptor, descriptor, "Google\\Protobuf\\Descriptor");
149 
descriptor_free_c(Descriptor * self TSRMLS_DC)150 static void descriptor_free_c(Descriptor *self TSRMLS_DC) {
151   if (self->layout) {
152     free_layout(self->layout);
153   }
154 }
155 
descriptor_init_c_instance(Descriptor * desc TSRMLS_DC)156 static void descriptor_init_c_instance(Descriptor *desc TSRMLS_DC) {
157   desc->msgdef = NULL;
158   desc->layout = NULL;
159   desc->klass = NULL;
160 }
161 
PHP_METHOD(Descriptor,getClass)162 PHP_METHOD(Descriptor, getClass) {
163   Descriptor *intern = UNBOX(Descriptor, getThis());
164 #if PHP_MAJOR_VERSION < 7
165   const char* classname = intern->klass->name;
166 #else
167   const char* classname = ZSTR_VAL(intern->klass->name);
168 #endif
169   PHP_PROTO_RETVAL_STRINGL(classname, strlen(classname), 1);
170 }
171 
PHP_METHOD(Descriptor,getFullName)172 PHP_METHOD(Descriptor, getFullName) {
173   Descriptor *intern = UNBOX(Descriptor, getThis());
174   const char* fullname = upb_msgdef_fullname(intern->msgdef);
175   PHP_PROTO_RETVAL_STRINGL(fullname, strlen(fullname), 1);
176 }
177 
PHP_METHOD(Descriptor,getField)178 PHP_METHOD(Descriptor, getField) {
179   long index;
180   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
181       FAILURE) {
182     zend_error(E_USER_ERROR, "Expect integer for index.\n");
183     return;
184   }
185 
186   Descriptor *intern = UNBOX(Descriptor, getThis());
187   int field_num = upb_msgdef_numfields(intern->msgdef);
188   if (index < 0 || index >= field_num) {
189     zend_error(E_USER_ERROR, "Cannot get element at %ld.\n", index);
190     return;
191   }
192 
193   upb_msg_field_iter iter;
194   int i;
195   for(upb_msg_field_begin(&iter, intern->msgdef), i = 0;
196       !upb_msg_field_done(&iter) && i < index;
197       upb_msg_field_next(&iter), i++);
198   const upb_fielddef *field = upb_msg_iter_field(&iter);
199 
200   PHP_PROTO_HASHTABLE_VALUE field_hashtable_value = get_def_obj(field);
201   if (field_hashtable_value == NULL) {
202 #if PHP_MAJOR_VERSION < 7
203     MAKE_STD_ZVAL(field_hashtable_value);
204     ZVAL_OBJ(field_hashtable_value, field_descriptor_type->create_object(
205                                         field_descriptor_type TSRMLS_CC));
206     Z_DELREF_P(field_hashtable_value);
207 #else
208     field_hashtable_value =
209         field_descriptor_type->create_object(field_descriptor_type TSRMLS_CC);
210     GC_DELREF(field_hashtable_value);
211 #endif
212     FieldDescriptor *field_php =
213         UNBOX_HASHTABLE_VALUE(FieldDescriptor, field_hashtable_value);
214     field_php->fielddef = field;
215     add_def_obj(field, field_hashtable_value);
216   }
217 
218 #if PHP_MAJOR_VERSION < 7
219   RETURN_ZVAL(field_hashtable_value, 1, 0);
220 #else
221   GC_ADDREF(field_hashtable_value);
222   RETURN_OBJ(field_hashtable_value);
223 #endif
224 }
225 
PHP_METHOD(Descriptor,getFieldCount)226 PHP_METHOD(Descriptor, getFieldCount) {
227   Descriptor *intern = UNBOX(Descriptor, getThis());
228   RETURN_LONG(upb_msgdef_numfields(intern->msgdef));
229 }
230 
PHP_METHOD(Descriptor,getOneofDecl)231 PHP_METHOD(Descriptor, getOneofDecl) {
232   long index;
233   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
234       FAILURE) {
235     zend_error(E_USER_ERROR, "Expect integer for index.\n");
236     return;
237   }
238 
239   Descriptor *intern = UNBOX(Descriptor, getThis());
240   int field_num = upb_msgdef_numoneofs(intern->msgdef);
241   if (index < 0 || index >= field_num) {
242     zend_error(E_USER_ERROR, "Cannot get element at %ld.\n", index);
243     return;
244   }
245 
246   upb_msg_oneof_iter iter;
247   int i;
248   for(upb_msg_oneof_begin(&iter, intern->msgdef), i = 0;
249       !upb_msg_oneof_done(&iter) && i < index;
250       upb_msg_oneof_next(&iter), i++);
251   const upb_oneofdef *oneof = upb_msg_iter_oneof(&iter);
252 
253   ZVAL_OBJ(return_value, oneof_descriptor_type->create_object(
254                              oneof_descriptor_type TSRMLS_CC));
255   Oneof *oneof_php = UNBOX(Oneof, return_value);
256   oneof_php->oneofdef = oneof;
257 }
258 
PHP_METHOD(Descriptor,getOneofDeclCount)259 PHP_METHOD(Descriptor, getOneofDeclCount) {
260   Descriptor *intern = UNBOX(Descriptor, getThis());
261   RETURN_LONG(upb_msgdef_numoneofs(intern->msgdef));
262 }
263 
264 // -----------------------------------------------------------------------------
265 // EnumDescriptor
266 // -----------------------------------------------------------------------------
267 
268 static zend_function_entry enum_descriptor_methods[] = {
269   PHP_ME(EnumDescriptor, getValue, NULL, ZEND_ACC_PUBLIC)
270   PHP_ME(EnumDescriptor, getValueCount, NULL, ZEND_ACC_PUBLIC)
271   ZEND_FE_END
272 };
273 
274 DEFINE_CLASS(EnumDescriptor, enum_descriptor,
275              "Google\\Protobuf\\EnumDescriptor");
276 
enum_descriptor_free_c(EnumDescriptor * self TSRMLS_DC)277 static void enum_descriptor_free_c(EnumDescriptor *self TSRMLS_DC) {
278 }
279 
enum_descriptor_init_c_instance(EnumDescriptor * self TSRMLS_DC)280 static void enum_descriptor_init_c_instance(EnumDescriptor *self TSRMLS_DC) {
281   self->enumdef = NULL;
282   self->klass = NULL;
283 }
284 
PHP_METHOD(EnumDescriptor,getValue)285 PHP_METHOD(EnumDescriptor, getValue) {
286   long index;
287   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
288       FAILURE) {
289     zend_error(E_USER_ERROR, "Expect integer for index.\n");
290     return;
291   }
292 
293   EnumDescriptor *intern = UNBOX(EnumDescriptor, getThis());
294   int field_num = upb_enumdef_numvals(intern->enumdef);
295   if (index < 0 || index >= field_num) {
296     zend_error(E_USER_ERROR, "Cannot get element at %ld.\n", index);
297     return;
298   }
299 
300   upb_enum_iter iter;
301   int i;
302   for(upb_enum_begin(&iter, intern->enumdef), i = 0;
303       !upb_enum_done(&iter) && i < index;
304       upb_enum_next(&iter), i++);
305 
306   ZVAL_OBJ(return_value, enum_value_descriptor_type->create_object(
307                              enum_value_descriptor_type TSRMLS_CC));
308   EnumValueDescriptor *enum_value_php =
309       UNBOX(EnumValueDescriptor, return_value);
310   enum_value_php->name = upb_enum_iter_name(&iter);
311   enum_value_php->number = upb_enum_iter_number(&iter);
312 }
313 
PHP_METHOD(EnumDescriptor,getValueCount)314 PHP_METHOD(EnumDescriptor, getValueCount) {
315   EnumDescriptor *intern = UNBOX(EnumDescriptor, getThis());
316   RETURN_LONG(upb_enumdef_numvals(intern->enumdef));
317 }
318 
319 // -----------------------------------------------------------------------------
320 // EnumValueDescriptor
321 // -----------------------------------------------------------------------------
322 
323 static zend_function_entry enum_value_descriptor_methods[] = {
324   PHP_ME(EnumValueDescriptor, getName, NULL, ZEND_ACC_PUBLIC)
325   PHP_ME(EnumValueDescriptor, getNumber, NULL, ZEND_ACC_PUBLIC)
326   ZEND_FE_END
327 };
328 
329 DEFINE_CLASS(EnumValueDescriptor, enum_value_descriptor,
330              "Google\\Protobuf\\EnumValueDescriptor");
331 
enum_value_descriptor_free_c(EnumValueDescriptor * self TSRMLS_DC)332 static void enum_value_descriptor_free_c(EnumValueDescriptor *self TSRMLS_DC) {
333 }
334 
enum_value_descriptor_init_c_instance(EnumValueDescriptor * self TSRMLS_DC)335 static void enum_value_descriptor_init_c_instance(EnumValueDescriptor *self TSRMLS_DC) {
336   self->name = NULL;
337   self->number = 0;
338 }
339 
PHP_METHOD(EnumValueDescriptor,getName)340 PHP_METHOD(EnumValueDescriptor, getName) {
341   EnumValueDescriptor *intern = UNBOX(EnumValueDescriptor, getThis());
342   PHP_PROTO_RETVAL_STRINGL(intern->name, strlen(intern->name), 1);
343 }
344 
PHP_METHOD(EnumValueDescriptor,getNumber)345 PHP_METHOD(EnumValueDescriptor, getNumber) {
346   EnumValueDescriptor *intern = UNBOX(EnumValueDescriptor, getThis());
347   RETURN_LONG(intern->number);
348 }
349 
350 // -----------------------------------------------------------------------------
351 // FieldDescriptor
352 // -----------------------------------------------------------------------------
353 
354 static zend_function_entry field_descriptor_methods[] = {
355   PHP_ME(FieldDescriptor, getName,   NULL, ZEND_ACC_PUBLIC)
356   PHP_ME(FieldDescriptor, getNumber, NULL, ZEND_ACC_PUBLIC)
357   PHP_ME(FieldDescriptor, getLabel,  NULL, ZEND_ACC_PUBLIC)
358   PHP_ME(FieldDescriptor, getType,   NULL, ZEND_ACC_PUBLIC)
359   PHP_ME(FieldDescriptor, isMap,     NULL, ZEND_ACC_PUBLIC)
360   PHP_ME(FieldDescriptor, getEnumType, NULL, ZEND_ACC_PUBLIC)
361   PHP_ME(FieldDescriptor, getMessageType, NULL, ZEND_ACC_PUBLIC)
362   ZEND_FE_END
363 };
364 
365 DEFINE_CLASS(FieldDescriptor, field_descriptor,
366              "Google\\Protobuf\\FieldDescriptor");
367 
field_descriptor_free_c(FieldDescriptor * self TSRMLS_DC)368 static void field_descriptor_free_c(FieldDescriptor *self TSRMLS_DC) {
369 }
370 
field_descriptor_init_c_instance(FieldDescriptor * self TSRMLS_DC)371 static void field_descriptor_init_c_instance(FieldDescriptor *self TSRMLS_DC) {
372   self->fielddef = NULL;
373 }
374 
to_fieldtype(upb_descriptortype_t type)375 upb_fieldtype_t to_fieldtype(upb_descriptortype_t type) {
376   switch (type) {
377 #define CASE(descriptor_type, type)           \
378   case UPB_DESCRIPTOR_TYPE_##descriptor_type: \
379     return UPB_TYPE_##type;
380 
381   CASE(FLOAT,    FLOAT);
382   CASE(DOUBLE,   DOUBLE);
383   CASE(BOOL,     BOOL);
384   CASE(STRING,   STRING);
385   CASE(BYTES,    BYTES);
386   CASE(MESSAGE,  MESSAGE);
387   CASE(GROUP,    MESSAGE);
388   CASE(ENUM,     ENUM);
389   CASE(INT32,    INT32);
390   CASE(INT64,    INT64);
391   CASE(UINT32,   UINT32);
392   CASE(UINT64,   UINT64);
393   CASE(SINT32,   INT32);
394   CASE(SINT64,   INT64);
395   CASE(FIXED32,  UINT32);
396   CASE(FIXED64,  UINT64);
397   CASE(SFIXED32, INT32);
398   CASE(SFIXED64, INT64);
399 
400 #undef CONVERT
401 
402   }
403 
404   zend_error(E_ERROR, "Unknown field type.");
405   return 0;
406 }
407 
PHP_METHOD(FieldDescriptor,getName)408 PHP_METHOD(FieldDescriptor, getName) {
409   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
410   const char* name = upb_fielddef_name(intern->fielddef);
411   PHP_PROTO_RETVAL_STRINGL(name, strlen(name), 1);
412 }
413 
PHP_METHOD(FieldDescriptor,getNumber)414 PHP_METHOD(FieldDescriptor, getNumber) {
415   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
416   RETURN_LONG(upb_fielddef_number(intern->fielddef));
417 }
418 
PHP_METHOD(FieldDescriptor,getLabel)419 PHP_METHOD(FieldDescriptor, getLabel) {
420   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
421   RETURN_LONG(upb_fielddef_label(intern->fielddef));
422 }
423 
PHP_METHOD(FieldDescriptor,getType)424 PHP_METHOD(FieldDescriptor, getType) {
425   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
426   RETURN_LONG(upb_fielddef_descriptortype(intern->fielddef));
427 }
428 
PHP_METHOD(FieldDescriptor,isMap)429 PHP_METHOD(FieldDescriptor, isMap) {
430   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
431   RETURN_BOOL(upb_fielddef_ismap(intern->fielddef));
432 }
433 
PHP_METHOD(FieldDescriptor,getEnumType)434 PHP_METHOD(FieldDescriptor, getEnumType) {
435   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
436   if (upb_fielddef_type(intern->fielddef) != UPB_TYPE_ENUM) {
437     zend_throw_exception_ex(NULL, 0 TSRMLS_CC,
438                             "Cannot get enum type for non-enum field '%s'",
439                             upb_fielddef_name(intern->fielddef));
440     return;
441   }
442   const upb_enumdef *enumdef = upb_fielddef_enumsubdef(intern->fielddef);
443   PHP_PROTO_HASHTABLE_VALUE desc = get_def_obj(enumdef);
444 
445 #if PHP_MAJOR_VERSION < 7
446   RETURN_ZVAL(desc, 1, 0);
447 #else
448   GC_ADDREF(desc);
449   RETURN_OBJ(desc);
450 #endif
451 }
452 
PHP_METHOD(FieldDescriptor,getMessageType)453 PHP_METHOD(FieldDescriptor, getMessageType) {
454   FieldDescriptor *intern = UNBOX(FieldDescriptor, getThis());
455   if (upb_fielddef_type(intern->fielddef) != UPB_TYPE_MESSAGE) {
456     zend_throw_exception_ex(
457         NULL, 0 TSRMLS_CC, "Cannot get message type for non-message field '%s'",
458         upb_fielddef_name(intern->fielddef));
459     return;
460   }
461   const upb_msgdef *msgdef = upb_fielddef_msgsubdef(intern->fielddef);
462   PHP_PROTO_HASHTABLE_VALUE desc = get_def_obj(msgdef);
463 
464 #if PHP_MAJOR_VERSION < 7
465   RETURN_ZVAL(desc, 1, 0);
466 #else
467   GC_ADDREF(desc);
468   RETURN_OBJ(desc);
469 #endif
470 }
471 
472 // -----------------------------------------------------------------------------
473 // Oneof
474 // -----------------------------------------------------------------------------
475 
476 static zend_function_entry oneof_descriptor_methods[] = {
477   PHP_ME(Oneof, getName,  NULL, ZEND_ACC_PUBLIC)
478   PHP_ME(Oneof, getField, NULL, ZEND_ACC_PUBLIC)
479   PHP_ME(Oneof, getFieldCount, NULL, ZEND_ACC_PUBLIC)
480   ZEND_FE_END
481 };
482 
483 DEFINE_CLASS(Oneof, oneof_descriptor,
484              "Google\\Protobuf\\OneofDescriptor");
485 
oneof_descriptor_free_c(Oneof * self TSRMLS_DC)486 static void oneof_descriptor_free_c(Oneof *self TSRMLS_DC) {
487 }
488 
oneof_descriptor_init_c_instance(Oneof * self TSRMLS_DC)489 static void oneof_descriptor_init_c_instance(Oneof *self TSRMLS_DC) {
490   self->oneofdef = NULL;
491 }
492 
PHP_METHOD(Oneof,getName)493 PHP_METHOD(Oneof, getName) {
494   Oneof *intern = UNBOX(Oneof, getThis());
495   const char *name = upb_oneofdef_name(intern->oneofdef);
496   PHP_PROTO_RETVAL_STRINGL(name, strlen(name), 1);
497 }
498 
PHP_METHOD(Oneof,getField)499 PHP_METHOD(Oneof, getField) {
500   long index;
501   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
502       FAILURE) {
503     zend_error(E_USER_ERROR, "Expect integer for index.\n");
504     return;
505   }
506 
507   Oneof *intern = UNBOX(Oneof, getThis());
508   int field_num = upb_oneofdef_numfields(intern->oneofdef);
509   if (index < 0 || index >= field_num) {
510     zend_error(E_USER_ERROR, "Cannot get element at %ld.\n", index);
511     return;
512   }
513 
514   upb_oneof_iter iter;
515   int i;
516   for(upb_oneof_begin(&iter, intern->oneofdef), i = 0;
517       !upb_oneof_done(&iter) && i < index;
518       upb_oneof_next(&iter), i++);
519   const upb_fielddef *field = upb_oneof_iter_field(&iter);
520 
521   PHP_PROTO_HASHTABLE_VALUE field_hashtable_value = get_def_obj(field);
522   if (field_hashtable_value == NULL) {
523 #if PHP_MAJOR_VERSION < 7
524     MAKE_STD_ZVAL(field_hashtable_value);
525     ZVAL_OBJ(field_hashtable_value, field_descriptor_type->create_object(
526                                         field_descriptor_type TSRMLS_CC));
527 #else
528     field_hashtable_value =
529         field_descriptor_type->create_object(field_descriptor_type TSRMLS_CC);
530 #endif
531     FieldDescriptor *field_php =
532         UNBOX_HASHTABLE_VALUE(FieldDescriptor, field_hashtable_value);
533     field_php->fielddef = field;
534     add_def_obj(field, field_hashtable_value);
535   }
536 
537 #if PHP_MAJOR_VERSION < 7
538   RETURN_ZVAL(field_hashtable_value, 1, 0);
539 #else
540   GC_ADDREF(field_hashtable_value);
541   RETURN_OBJ(field_hashtable_value);
542 #endif
543 }
544 
PHP_METHOD(Oneof,getFieldCount)545 PHP_METHOD(Oneof, getFieldCount) {
546   Oneof *intern = UNBOX(Oneof, getThis());
547   RETURN_LONG(upb_oneofdef_numfields(intern->oneofdef));
548 }
549 
550 // -----------------------------------------------------------------------------
551 // DescriptorPool
552 // -----------------------------------------------------------------------------
553 
554 static zend_function_entry descriptor_pool_methods[] = {
555   PHP_ME(DescriptorPool, getGeneratedPool, NULL,
556          ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
557   PHP_ME(DescriptorPool, getDescriptorByClassName, NULL, ZEND_ACC_PUBLIC)
558   PHP_ME(DescriptorPool, getEnumDescriptorByClassName, NULL, ZEND_ACC_PUBLIC)
559   ZEND_FE_END
560 };
561 
562 static zend_function_entry internal_descriptor_pool_methods[] = {
563   PHP_ME(InternalDescriptorPool, getGeneratedPool, NULL,
564          ZEND_ACC_PUBLIC|ZEND_ACC_STATIC)
565   PHP_ME(InternalDescriptorPool, internalAddGeneratedFile, NULL, ZEND_ACC_PUBLIC)
566   ZEND_FE_END
567 };
568 
569 DEFINE_CLASS(DescriptorPool, descriptor_pool,
570              "Google\\Protobuf\\DescriptorPool");
571 DEFINE_CLASS(InternalDescriptorPool, internal_descriptor_pool,
572              "Google\\Protobuf\\Internal\\DescriptorPool");
573 
574 // wrapper of generated pool
575 #if PHP_MAJOR_VERSION < 7
576 zval* generated_pool_php;
577 zval* internal_generated_pool_php;
578 #else
579 zend_object *generated_pool_php;
580 zend_object *internal_generated_pool_php;
581 #endif
582 InternalDescriptorPool *generated_pool;  // The actual generated pool
583 
init_generated_pool_once(TSRMLS_D)584 void init_generated_pool_once(TSRMLS_D) {
585   if (generated_pool == NULL) {
586 #if PHP_MAJOR_VERSION < 7
587     MAKE_STD_ZVAL(generated_pool_php);
588     MAKE_STD_ZVAL(internal_generated_pool_php);
589     ZVAL_OBJ(internal_generated_pool_php,
590              internal_descriptor_pool_type->create_object(
591                  internal_descriptor_pool_type TSRMLS_CC));
592     generated_pool = UNBOX(InternalDescriptorPool, internal_generated_pool_php);
593     ZVAL_OBJ(generated_pool_php, descriptor_pool_type->create_object(
594                                      descriptor_pool_type TSRMLS_CC));
595 #else
596     internal_generated_pool_php = internal_descriptor_pool_type->create_object(
597         internal_descriptor_pool_type TSRMLS_CC);
598     generated_pool = (InternalDescriptorPool *)((char *)internal_generated_pool_php -
599                                         XtOffsetOf(InternalDescriptorPool, std));
600     generated_pool_php =
601         descriptor_pool_type->create_object(descriptor_pool_type TSRMLS_CC);
602 #endif
603   }
604 }
605 
internal_descriptor_pool_init_c_instance(InternalDescriptorPool * pool TSRMLS_DC)606 static void internal_descriptor_pool_init_c_instance(
607     InternalDescriptorPool *pool TSRMLS_DC) {
608   pool->symtab = upb_symtab_new();
609   pool->fill_handler_cache =
610       upb_handlercache_new(add_handlers_for_message, NULL);
611   pool->pb_serialize_handler_cache = upb_pb_encoder_newcache();
612   pool->json_serialize_handler_cache = upb_json_printer_newcache(false);
613   pool->json_serialize_handler_preserve_cache = upb_json_printer_newcache(true);
614   pool->fill_method_cache = upb_pbcodecache_new(pool->fill_handler_cache);
615   pool->json_fill_method_cache = upb_json_codecache_new();
616 }
617 
internal_descriptor_pool_free_c(InternalDescriptorPool * pool TSRMLS_DC)618 static void internal_descriptor_pool_free_c(
619     InternalDescriptorPool *pool TSRMLS_DC) {
620   upb_symtab_free(pool->symtab);
621   upb_handlercache_free(pool->fill_handler_cache);
622   upb_handlercache_free(pool->pb_serialize_handler_cache);
623   upb_handlercache_free(pool->json_serialize_handler_cache);
624   upb_handlercache_free(pool->json_serialize_handler_preserve_cache);
625   upb_pbcodecache_free(pool->fill_method_cache);
626   upb_json_codecache_free(pool->json_fill_method_cache);
627 }
628 
descriptor_pool_init_c_instance(DescriptorPool * pool TSRMLS_DC)629 static void descriptor_pool_init_c_instance(DescriptorPool *pool TSRMLS_DC) {
630   assert(generated_pool != NULL);
631   pool->intern = generated_pool;
632 }
633 
descriptor_pool_free_c(DescriptorPool * pool TSRMLS_DC)634 static void descriptor_pool_free_c(DescriptorPool *pool TSRMLS_DC) {
635 }
636 
validate_enumdef(const upb_enumdef * enumdef)637 static void validate_enumdef(const upb_enumdef *enumdef) {
638   // Verify that an entry exists with integer value 0. (This is the default
639   // value.)
640   const char *lookup = upb_enumdef_iton(enumdef, 0);
641   if (lookup == NULL) {
642     zend_error(E_USER_ERROR,
643                "Enum definition does not contain a value for '0'.");
644   }
645 }
646 
validate_msgdef(const upb_msgdef * msgdef)647 static void validate_msgdef(const upb_msgdef* msgdef) {
648   // Verify that no required fields exist. proto3 does not support these.
649   upb_msg_field_iter it;
650   for (upb_msg_field_begin(&it, msgdef);
651        !upb_msg_field_done(&it);
652        upb_msg_field_next(&it)) {
653     const upb_fielddef* field = upb_msg_iter_field(&it);
654     if (upb_fielddef_label(field) == UPB_LABEL_REQUIRED) {
655       zend_error(E_ERROR, "Required fields are unsupported in proto3.");
656     }
657   }
658 }
659 
PHP_METHOD(DescriptorPool,getGeneratedPool)660 PHP_METHOD(DescriptorPool, getGeneratedPool) {
661   init_generated_pool_once(TSRMLS_C);
662 #if PHP_MAJOR_VERSION < 7
663   RETURN_ZVAL(generated_pool_php, 1, 0);
664 #else
665   GC_ADDREF(generated_pool_php);
666   RETURN_OBJ(generated_pool_php);
667 #endif
668 }
669 
PHP_METHOD(InternalDescriptorPool,getGeneratedPool)670 PHP_METHOD(InternalDescriptorPool, getGeneratedPool) {
671   init_generated_pool_once(TSRMLS_C);
672 #if PHP_MAJOR_VERSION < 7
673   RETURN_ZVAL(internal_generated_pool_php, 1, 0);
674 #else
675   GC_ADDREF(internal_generated_pool_php);
676   RETURN_OBJ(internal_generated_pool_php);
677 #endif
678 }
679 
classname_len_max(const char * fullname,const char * package,const char * php_namespace,const char * prefix)680 static size_t classname_len_max(const char *fullname,
681                                 const char *package,
682                                 const char *php_namespace,
683                                 const char *prefix) {
684   size_t fullname_len = strlen(fullname);
685   size_t package_len = 0;
686   size_t prefix_len = 0;
687   size_t namespace_len = 0;
688   size_t length = fullname_len;
689   int i, segment, classname_start = 0;
690 
691   if (package != NULL) {
692     package_len = strlen(package);
693   }
694   if (prefix != NULL) {
695     prefix_len = strlen(prefix);
696   }
697   if (php_namespace != NULL) {
698     namespace_len = strlen(php_namespace);
699   }
700 
701   // Process package
702   if (package_len > 0) {
703     segment = 1;
704     for (i = 0; i < package_len; i++) {
705       if (package[i] == '.') {
706         segment++;
707       }
708     }
709     // In case of reserved name in package.
710     length += 3 * segment;
711 
712     classname_start = package_len + 1;
713   }
714 
715   // Process class name
716   segment = 1;
717   for (i = classname_start; i < fullname_len; i++) {
718     if (fullname[i] == '.') {
719       segment++;
720     }
721   }
722   if (prefix_len == 0) {
723     length += 3 * segment;
724   } else {
725     length += prefix_len * segment;
726   }
727 
728   // The additional 2, one is for preceding '.' and the other is for trailing 0.
729   return length + namespace_len + 2;
730 }
731 
is_reserved(const char * segment,int length)732 static bool is_reserved(const char *segment, int length) {
733   bool result;
734   char* lower = ALLOC_N(char, length + 1);
735   memset(lower, 0, length + 1);
736   memcpy(lower, segment, length);
737   int i = 0;
738   while(lower[i]) {
739     lower[i] = (char)tolower(lower[i]);
740     i++;
741   }
742   lower[length] = 0;
743   result = is_reserved_name(lower);
744   FREE(lower);
745   return result;
746 }
747 
fill_prefix(const char * segment,int length,const char * prefix_given,const char * package_name,stringsink * classname)748 static void fill_prefix(const char *segment, int length,
749                         const char *prefix_given,
750                         const char *package_name,
751                         stringsink *classname) {
752   size_t i;
753 
754   if (prefix_given != NULL && strcmp(prefix_given, "") != 0) {
755     stringsink_string(classname, NULL, prefix_given,
756                       strlen(prefix_given), NULL);
757   } else {
758     if (is_reserved(segment, length)) {
759       if (package_name != NULL &&
760           strcmp("google.protobuf", package_name) == 0) {
761         stringsink_string(classname, NULL, "GPB", 3, NULL);
762       } else {
763         stringsink_string(classname, NULL, "PB", 2, NULL);
764       }
765     }
766   }
767 }
768 
fill_segment(const char * segment,int length,stringsink * classname,bool use_camel)769 static void fill_segment(const char *segment, int length,
770                          stringsink *classname, bool use_camel) {
771   if (use_camel && (segment[0] < 'A' || segment[0] > 'Z')) {
772     char first = segment[0] + ('A' - 'a');
773     stringsink_string(classname, NULL, &first, 1, NULL);
774     stringsink_string(classname, NULL, segment + 1, length - 1, NULL);
775   } else {
776     stringsink_string(classname, NULL, segment, length, NULL);
777   }
778 }
779 
fill_namespace(const char * package,const char * php_namespace,stringsink * classname)780 static void fill_namespace(const char *package, const char *php_namespace,
781                            stringsink *classname) {
782   if (php_namespace != NULL) {
783     stringsink_string(classname, NULL, php_namespace, strlen(php_namespace),
784                       NULL);
785     stringsink_string(classname, NULL, "\\", 1, NULL);
786   } else if (package != NULL) {
787     int i = 0, j, offset = 0;
788     size_t package_len = strlen(package);
789     while (i < package_len) {
790       j = i;
791       while (j < package_len && package[j] != '.') {
792         j++;
793       }
794       fill_prefix(package + i, j - i, "", package, classname);
795       fill_segment(package + i, j - i, classname, true);
796       stringsink_string(classname, NULL, "\\", 1, NULL);
797       i = j + 1;
798     }
799   }
800 }
801 
fill_classname(const char * fullname,const char * package,const char * prefix,stringsink * classname,bool use_nested_submsg)802 static void fill_classname(const char *fullname,
803                            const char *package,
804                            const char *prefix,
805                            stringsink *classname,
806                            bool use_nested_submsg) {
807   int classname_start = 0;
808   if (package != NULL) {
809     size_t package_len = strlen(package);
810     classname_start = package_len == 0 ? 0 : package_len + 1;
811   }
812   size_t fullname_len = strlen(fullname);
813   bool is_first_segment = true;
814 
815   int i = classname_start, j;
816   while (i < fullname_len) {
817     j = i;
818     while (j < fullname_len && fullname[j] != '.') {
819       j++;
820     }
821     if (use_nested_submsg || is_first_segment && j == fullname_len) {
822       fill_prefix(fullname + i, j - i, prefix, package, classname);
823     }
824     is_first_segment = false;
825     fill_segment(fullname + i, j - i, classname, false);
826     if (j != fullname_len) {
827       if (use_nested_submsg) {
828         stringsink_string(classname, NULL, "\\", 1, NULL);
829       } else {
830         stringsink_string(classname, NULL, "_", 1, NULL);
831       }
832     }
833     i = j + 1;
834   }
835 }
836 
register_class(const upb_filedef * file,const char * fullname,PHP_PROTO_HASHTABLE_VALUE desc_php,bool use_nested_submsg TSRMLS_DC)837 static zend_class_entry *register_class(const upb_filedef *file,
838                                         const char *fullname,
839                                         PHP_PROTO_HASHTABLE_VALUE desc_php,
840                                         bool use_nested_submsg TSRMLS_DC) {
841   // Prepend '.' to package name to make it absolute. In the 5 additional
842   // bytes allocated, one for '.', one for trailing 0, and 3 for 'GPB' if
843   // given message is google.protobuf.Empty.
844   const char *package = upb_filedef_package(file);
845   const char *php_namespace = upb_filedef_phpnamespace(file);
846   const char *prefix = upb_filedef_phpprefix(file);
847   size_t classname_len =
848       classname_len_max(fullname, package, php_namespace, prefix);
849   char* after_package;
850   zend_class_entry* ret;
851   stringsink namesink;
852   stringsink_init(&namesink);
853 
854   fill_namespace(package, php_namespace, &namesink);
855   fill_classname(fullname, package, prefix, &namesink, use_nested_submsg);
856   stringsink_string(&namesink, NULL, "\0", 1, NULL);
857 
858   PHP_PROTO_CE_DECLARE pce;
859   if (php_proto_zend_lookup_class(namesink.ptr, namesink.len - 1, &pce) ==
860       FAILURE) {
861     zend_error(
862         E_ERROR,
863         "Generated message class %s hasn't been defined (%s, %s, %s, %s)",
864         namesink.ptr, fullname, package, php_namespace, prefix);
865     return NULL;
866   }
867   ret = PHP_PROTO_CE_UNREF(pce);
868   add_ce_obj(ret, desc_php);
869   add_proto_obj(fullname, desc_php);
870   stringsink_uninit(&namesink);
871   return ret;
872 }
873 
depends_on_descriptor(const google_protobuf_FileDescriptorProto * file)874 bool depends_on_descriptor(const google_protobuf_FileDescriptorProto* file) {
875   const upb_strview *deps;
876   upb_strview name = upb_strview_makez("google/protobuf/descriptor.proto");
877   size_t i, n;
878 
879   deps = google_protobuf_FileDescriptorProto_dependency(file, &n);
880   for (i = 0; i < n; i++) {
881     if (upb_strview_eql(deps[i], name)) {
882       return true;
883     }
884   }
885 
886   return false;
887 }
888 
parse_and_add_descriptor(const char * data,PHP_PROTO_SIZE data_len,InternalDescriptorPool * pool,upb_arena * arena)889 const upb_filedef *parse_and_add_descriptor(const char *data,
890                                             PHP_PROTO_SIZE data_len,
891                                             InternalDescriptorPool *pool,
892                                             upb_arena *arena) {
893   size_t n;
894   google_protobuf_FileDescriptorSet *set;
895   const google_protobuf_FileDescriptorProto* const* files;
896   const upb_filedef* file;
897   upb_status status;
898 
899   set = google_protobuf_FileDescriptorSet_parse(
900       data, data_len, arena);
901 
902   if (!set) {
903     zend_error(E_ERROR, "Failed to parse binary descriptor\n");
904     return false;
905   }
906 
907   files = google_protobuf_FileDescriptorSet_file(set, &n);
908 
909   if (n != 1) {
910     zend_error(E_ERROR, "Serialized descriptors should have exactly one file");
911     return false;
912   }
913 
914   // The PHP code generator currently special-cases descriptor.proto.  It
915   // doesn't add it as a dependency even if the proto file actually does
916   // depend on it.
917   if (depends_on_descriptor(files[0]) &&
918       upb_symtab_lookupfile(pool->symtab, "google/protobuf/descriptor.proto") ==
919           NULL) {
920     if (!parse_and_add_descriptor((char *)descriptor_proto,
921                                   descriptor_proto_len, pool, arena)) {
922       return false;
923     }
924   }
925 
926   upb_status_clear(&status);
927   file = upb_symtab_addfile(pool->symtab, files[0], &status);
928   check_upb_status(&status, "Unable to load descriptor");
929   return file;
930 }
931 
internal_add_generated_file(const char * data,PHP_PROTO_SIZE data_len,InternalDescriptorPool * pool,bool use_nested_submsg TSRMLS_DC)932 void internal_add_generated_file(const char *data, PHP_PROTO_SIZE data_len,
933                                  InternalDescriptorPool *pool,
934                                  bool use_nested_submsg TSRMLS_DC) {
935   int i;
936   upb_arena *arena;
937   const upb_filedef* file;
938 
939   arena = upb_arena_new();
940   file = parse_and_add_descriptor(data, data_len, pool, arena);
941   upb_arena_free(arena);
942   if (!file) return;
943 
944   // For each enum/message, we need its PHP class, upb descriptor and its PHP
945   // wrapper. These information are needed later for encoding, decoding and type
946   // checking. However, sometimes we just have one of them. In order to find
947   // them quickly, here, we store the mapping for them.
948 
949   for (i = 0; i < upb_filedef_msgcount(file); i++) {
950     const upb_msgdef *msgdef = upb_filedef_msg(file, i);
951     CREATE_HASHTABLE_VALUE(desc, desc_php, Descriptor, descriptor_type);
952     desc->msgdef = msgdef;
953     desc->pool = pool;
954     add_def_obj(desc->msgdef, desc_php);
955 
956     // Unlike other messages, MapEntry is shared by all map fields and doesn't
957     // have generated PHP class.
958     if (upb_msgdef_mapentry(msgdef)) {
959       continue;
960     }
961 
962     desc->klass = register_class(file, upb_msgdef_fullname(msgdef), desc_php,
963                                  use_nested_submsg TSRMLS_CC);
964 
965     if (desc->klass == NULL) {
966       return;
967     }
968 
969     build_class_from_descriptor(desc_php TSRMLS_CC);
970   }
971 
972   for (i = 0; i < upb_filedef_enumcount(file); i++) {
973     const upb_enumdef *enumdef = upb_filedef_enum(file, i);
974     CREATE_HASHTABLE_VALUE(desc, desc_php, EnumDescriptor, enum_descriptor_type);
975     desc->enumdef = enumdef;
976     add_def_obj(desc->enumdef, desc_php);
977     desc->klass = register_class(file, upb_enumdef_fullname(enumdef), desc_php,
978                                  use_nested_submsg TSRMLS_CC);
979 
980     if (desc->klass == NULL) {
981       return;
982     }
983   }
984 }
985 
PHP_METHOD(InternalDescriptorPool,internalAddGeneratedFile)986 PHP_METHOD(InternalDescriptorPool, internalAddGeneratedFile) {
987   char *data = NULL;
988   PHP_PROTO_SIZE data_len;
989   upb_filedef **files;
990   zend_bool use_nested_submsg = false;
991   size_t i;
992 
993   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s|b",
994                             &data, &data_len, &use_nested_submsg) ==
995       FAILURE) {
996     return;
997   }
998 
999   InternalDescriptorPool *pool = UNBOX(InternalDescriptorPool, getThis());
1000   internal_add_generated_file(data, data_len, pool,
1001                               use_nested_submsg TSRMLS_CC);
1002 }
1003 
PHP_METHOD(DescriptorPool,getDescriptorByClassName)1004 PHP_METHOD(DescriptorPool, getDescriptorByClassName) {
1005   DescriptorPool *public_pool = UNBOX(DescriptorPool, getThis());
1006   InternalDescriptorPool *pool = public_pool->intern;
1007 
1008   char *classname = NULL;
1009   PHP_PROTO_SIZE classname_len;
1010 
1011   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &classname,
1012                             &classname_len) == FAILURE) {
1013     return;
1014   }
1015 
1016   PHP_PROTO_CE_DECLARE pce;
1017   if (php_proto_zend_lookup_class(classname, classname_len, &pce) ==
1018       FAILURE) {
1019     RETURN_NULL();
1020   }
1021 
1022   PHP_PROTO_HASHTABLE_VALUE desc = get_ce_obj(PHP_PROTO_CE_UNREF(pce));
1023   if (desc == NULL) {
1024     RETURN_NULL();
1025   }
1026 
1027   zend_class_entry* instance_ce = HASHTABLE_VALUE_CE(desc);
1028 
1029   if (!instanceof_function(instance_ce, descriptor_type TSRMLS_CC)) {
1030     RETURN_NULL();
1031   }
1032 
1033 #if PHP_MAJOR_VERSION < 7
1034   RETURN_ZVAL(desc, 1, 0);
1035 #else
1036   GC_ADDREF(desc);
1037   RETURN_OBJ(desc);
1038 #endif
1039 }
1040 
PHP_METHOD(DescriptorPool,getEnumDescriptorByClassName)1041 PHP_METHOD(DescriptorPool, getEnumDescriptorByClassName) {
1042   DescriptorPool *public_pool = UNBOX(DescriptorPool, getThis());
1043   InternalDescriptorPool *pool = public_pool->intern;
1044 
1045   char *classname = NULL;
1046   PHP_PROTO_SIZE classname_len;
1047 
1048   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &classname,
1049                             &classname_len) == FAILURE) {
1050     return;
1051   }
1052 
1053   PHP_PROTO_CE_DECLARE pce;
1054   if (php_proto_zend_lookup_class(classname, classname_len, &pce) ==
1055       FAILURE) {
1056     RETURN_NULL();
1057   }
1058 
1059   PHP_PROTO_HASHTABLE_VALUE desc = get_ce_obj(PHP_PROTO_CE_UNREF(pce));
1060   if (desc == NULL) {
1061     RETURN_NULL();
1062   }
1063 
1064   zend_class_entry* instance_ce = HASHTABLE_VALUE_CE(desc);
1065 
1066   if (!instanceof_function(instance_ce, enum_descriptor_type TSRMLS_CC)) {
1067     RETURN_NULL();
1068   }
1069 
1070 #if PHP_MAJOR_VERSION < 7
1071   RETURN_ZVAL(desc, 1, 0);
1072 #else
1073   GC_ADDREF(desc);
1074   RETURN_OBJ(desc);
1075 #endif
1076 }
1077