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 <ext/spl/spl_iterators.h>
32 #include <Zend/zend_API.h>
33 #include <Zend/zend_interfaces.h>
34 
35 #include "protobuf.h"
36 
37 ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1)
38   ZEND_ARG_INFO(0, index)
39 ZEND_END_ARG_INFO()
40 
41 ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetSet, 0, 0, 2)
42   ZEND_ARG_INFO(0, index)
43   ZEND_ARG_INFO(0, newval)
44 ZEND_END_ARG_INFO()
45 
46 ZEND_BEGIN_ARG_INFO(arginfo_void, 0)
47 ZEND_END_ARG_INFO()
48 
49 static zend_function_entry repeated_field_methods[] = {
50   PHP_ME(RepeatedField, __construct,  NULL,              ZEND_ACC_PUBLIC)
51   PHP_ME(RepeatedField, append,       NULL,              ZEND_ACC_PUBLIC)
52   PHP_ME(RepeatedField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC)
53   PHP_ME(RepeatedField, offsetGet,    arginfo_offsetGet, ZEND_ACC_PUBLIC)
54   PHP_ME(RepeatedField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
55   PHP_ME(RepeatedField, offsetUnset,  arginfo_offsetGet, ZEND_ACC_PUBLIC)
56   PHP_ME(RepeatedField, count,        arginfo_void,      ZEND_ACC_PUBLIC)
57   PHP_ME(RepeatedField, getIterator,  arginfo_void,      ZEND_ACC_PUBLIC)
58   ZEND_FE_END
59 };
60 
61 static zend_function_entry repeated_field_iter_methods[] = {
62   PHP_ME(RepeatedFieldIter, rewind,      arginfo_void, ZEND_ACC_PUBLIC)
63   PHP_ME(RepeatedFieldIter, current,     arginfo_void, ZEND_ACC_PUBLIC)
64   PHP_ME(RepeatedFieldIter, key,         arginfo_void, ZEND_ACC_PUBLIC)
65   PHP_ME(RepeatedFieldIter, next,        arginfo_void, ZEND_ACC_PUBLIC)
66   PHP_ME(RepeatedFieldIter, valid,       arginfo_void, ZEND_ACC_PUBLIC)
67   ZEND_FE_END
68 };
69 
70 // Forward declare static functions.
71 
72 static int repeated_field_array_init(zval *array, upb_fieldtype_t type,
73                                      uint size ZEND_FILE_LINE_DC);
74 static void repeated_field_write_dimension(zval *object, zval *offset,
75                                            zval *value TSRMLS_DC);
76 static int repeated_field_has_dimension(zval *object, zval *offset TSRMLS_DC);
77 static HashTable *repeated_field_get_gc(zval *object, CACHED_VALUE **table,
78                                         int *n TSRMLS_DC);
79 #if PHP_MAJOR_VERSION < 7
80 static zend_object_value repeated_field_create(zend_class_entry *ce TSRMLS_DC);
81 static zend_object_value repeated_field_iter_create(zend_class_entry *ce TSRMLS_DC);
82 #else
83 static zend_object *repeated_field_create(zend_class_entry *ce TSRMLS_DC);
84 static zend_object *repeated_field_iter_create(zend_class_entry *ce TSRMLS_DC);
85 #endif
86 
87 // -----------------------------------------------------------------------------
88 // RepeatedField creation/desctruction
89 // -----------------------------------------------------------------------------
90 
91 zend_class_entry* repeated_field_type;
92 zend_class_entry* repeated_field_iter_type;
93 zend_object_handlers* repeated_field_handlers;
94 zend_object_handlers* repeated_field_iter_handlers;
95 
96 // Define object free method.
97 PHP_PROTO_OBJECT_FREE_START(RepeatedField, repeated_field)
98 #if PHP_MAJOR_VERSION < 7
99 php_proto_zval_ptr_dtor(intern->array);
100 #else
101 php_proto_zval_ptr_dtor(&intern->array);
102 #endif
103 PHP_PROTO_OBJECT_FREE_END
104 
105 PHP_PROTO_OBJECT_DTOR_START(RepeatedField, repeated_field)
106 PHP_PROTO_OBJECT_DTOR_END
107 
108 // Define object create method.
109 PHP_PROTO_OBJECT_CREATE_START(RepeatedField, repeated_field)
110 #if PHP_MAJOR_VERSION < 7
111 intern->array = NULL;
112 #endif
113 intern->type = 0;
114 intern->msg_ce = NULL;
115 PHP_PROTO_OBJECT_CREATE_END(RepeatedField, repeated_field)
116 
117 // Init class entry.
118 PHP_PROTO_INIT_CLASS_START("Google\\Protobuf\\Internal\\RepeatedField",
119                            RepeatedField, repeated_field)
120 zend_class_implements(repeated_field_type TSRMLS_CC, 3, spl_ce_ArrayAccess,
121                       zend_ce_aggregate, spl_ce_Countable);
122 repeated_field_handlers->write_dimension = repeated_field_write_dimension;
123 repeated_field_handlers->get_gc = repeated_field_get_gc;
124 PHP_PROTO_INIT_CLASS_END
125 
126 // Define array element free function.
127 #if PHP_MAJOR_VERSION < 7
php_proto_array_string_release(void * value)128 static inline void php_proto_array_string_release(void *value) {
129   zval_ptr_dtor(value);
130 }
131 
php_proto_array_object_release(void * value)132 static inline void php_proto_array_object_release(void *value) {
133   zval_ptr_dtor(value);
134 }
php_proto_array_default_release(void * value)135 static inline void php_proto_array_default_release(void *value) {
136 }
137 #else
138 static inline void php_proto_array_string_release(zval *value) {
139   void* ptr = Z_PTR_P(value);
140   zend_string* object = *(zend_string**)ptr;
141   zend_string_release(object);
142   efree(ptr);
143 }
144 static inline void php_proto_array_object_release(zval *value) {
145   zval_ptr_dtor(value);
146 }
147 static void php_proto_array_default_release(zval* value) {
148   void* ptr = Z_PTR_P(value);
149   efree(ptr);
150 }
151 #endif
152 
repeated_field_array_init(zval * array,upb_fieldtype_t type,uint size ZEND_FILE_LINE_DC)153 static int repeated_field_array_init(zval *array, upb_fieldtype_t type,
154                                      uint size ZEND_FILE_LINE_DC) {
155   PHP_PROTO_ALLOC_ARRAY(array);
156 
157   switch (type) {
158     case UPB_TYPE_STRING:
159     case UPB_TYPE_BYTES:
160       zend_hash_init(Z_ARRVAL_P(array), size, NULL,
161                      php_proto_array_string_release, 0);
162       break;
163     case UPB_TYPE_MESSAGE:
164       zend_hash_init(Z_ARRVAL_P(array), size, NULL,
165                      php_proto_array_object_release, 0);
166       break;
167     default:
168       zend_hash_init(Z_ARRVAL_P(array), size, NULL,
169                      php_proto_array_default_release, 0);
170   }
171   return SUCCESS;
172 }
173 
174 // -----------------------------------------------------------------------------
175 // RepeatedField Handlers
176 // -----------------------------------------------------------------------------
177 
repeated_field_write_dimension(zval * object,zval * offset,zval * value TSRMLS_DC)178 static void repeated_field_write_dimension(zval *object, zval *offset,
179                                            zval *value TSRMLS_DC) {
180   uint64_t index;
181 
182   RepeatedField *intern = UNBOX(RepeatedField, object);
183   HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
184   int size = native_slot_size(intern->type);
185 
186   unsigned char memory[NATIVE_SLOT_MAX_SIZE];
187   memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
188 
189   if (!native_slot_set_by_array(intern->type, intern->msg_ce, memory,
190                                 value TSRMLS_CC)) {
191     return;
192   }
193 
194   if (!offset || Z_TYPE_P(offset) == IS_NULL) {
195     index = zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array));
196   } else {
197     if (protobuf_convert_to_uint64(offset, &index)) {
198       if (!zend_hash_index_exists(ht, index)) {
199         zend_error(E_USER_ERROR, "Element at %llu doesn't exist.\n",
200                    (long long unsigned int)index);
201         return;
202       }
203     } else {
204       return;
205     }
206   }
207 
208   if (intern->type == UPB_TYPE_MESSAGE) {
209     php_proto_zend_hash_index_update_zval(ht, index, *(zval**)memory);
210   } else {
211     php_proto_zend_hash_index_update_mem(ht, index, memory, size, NULL);
212   }
213 }
214 
215 #if PHP_MAJOR_VERSION < 7
repeated_field_get_gc(zval * object,zval *** table,int * n TSRMLS_DC)216 static HashTable *repeated_field_get_gc(zval *object, zval ***table,
217                                         int *n TSRMLS_DC) {
218 #else
219 static HashTable *repeated_field_get_gc(zval *object, zval **table, int *n) {
220 #endif
221   *table = NULL;
222   *n = 0;
223   RepeatedField *intern = UNBOX(RepeatedField, object);
224   return PHP_PROTO_HASH_OF(intern->array);
225 }
226 
227 // -----------------------------------------------------------------------------
228 // C RepeatedField Utilities
229 // -----------------------------------------------------------------------------
230 
231 void *repeated_field_index_native(RepeatedField *intern, int index TSRMLS_DC) {
232   HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
233   void *value;
234 
235   if (intern->type == UPB_TYPE_MESSAGE) {
236     if (php_proto_zend_hash_index_find_zval(ht, index, (void **)&value) ==
237         FAILURE) {
238       zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
239       return NULL;
240     }
241   } else {
242     if (php_proto_zend_hash_index_find_mem(ht, index, (void **)&value) ==
243         FAILURE) {
244       zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
245       return NULL;
246     }
247   }
248 
249   return value;
250 }
251 
252 void repeated_field_push_native(RepeatedField *intern, void *value) {
253   HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
254   int size = native_slot_size(intern->type);
255   if (intern->type == UPB_TYPE_MESSAGE) {
256     php_proto_zend_hash_next_index_insert_zval(ht, value);
257   } else {
258     php_proto_zend_hash_next_index_insert_mem(ht, (void **)value, size, NULL);
259   }
260 }
261 
262 void repeated_field_create_with_field(
263     zend_class_entry *ce, const upb_fielddef *field,
264     CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC) {
265   upb_fieldtype_t type = upb_fielddef_type(field);
266   const zend_class_entry *msg_ce = field_type_class(field PHP_PROTO_TSRMLS_CC);
267   repeated_field_create_with_type(ce, type, msg_ce,
268                                   repeated_field PHP_PROTO_TSRMLS_CC);
269 }
270 
271 void repeated_field_create_with_type(
272     zend_class_entry *ce, upb_fieldtype_t type, const zend_class_entry *msg_ce,
273     CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC) {
274   CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(CACHED_PTR_TO_ZVAL_PTR(repeated_field),
275                                    repeated_field_type);
276 
277   RepeatedField *intern =
278       UNBOX(RepeatedField, CACHED_TO_ZVAL_PTR(*repeated_field));
279   intern->type = type;
280   intern->msg_ce = msg_ce;
281 #if PHP_MAJOR_VERSION < 7
282   MAKE_STD_ZVAL(intern->array);
283   repeated_field_array_init(intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
284 #else
285   repeated_field_array_init(&intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
286 #endif
287 
288   // TODO(teboring): Link class entry for message and enum
289 }
290 
291 
292 // -----------------------------------------------------------------------------
293 // PHP RepeatedField Methods
294 // -----------------------------------------------------------------------------
295 
296 /**
297  * Constructs an instance of RepeatedField.
298  * @param long Type of the stored element.
299  * @param string Message/Enum class name (message/enum fields only).
300  */
301 PHP_METHOD(RepeatedField, __construct) {
302   long type;
303   zend_class_entry* klass = NULL;
304 
305   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l|C", &type, &klass) ==
306       FAILURE) {
307     return;
308   }
309 
310   RepeatedField *intern = UNBOX(RepeatedField, getThis());
311   intern->type = to_fieldtype(type);
312   intern->msg_ce = klass;
313 
314 #if PHP_MAJOR_VERSION < 7
315   MAKE_STD_ZVAL(intern->array);
316   repeated_field_array_init(intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
317 #else
318   repeated_field_array_init(&intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
319 #endif
320 
321   if (intern->type == UPB_TYPE_MESSAGE && klass == NULL) {
322     zend_error(E_USER_ERROR, "Message type must have concrete class.");
323     return;
324   }
325 
326   // TODO(teboring): Consider enum.
327 }
328 
329 /**
330  * Append element to the end of the repeated field.
331  * @param object The element to be added.
332  */
333 PHP_METHOD(RepeatedField, append) {
334   zval *value;
335 
336   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &value) ==
337       FAILURE) {
338     return;
339   }
340   repeated_field_write_dimension(getThis(), NULL, value TSRMLS_CC);
341 }
342 
343 /**
344  * Check whether the element at given index exists.
345  * @param long The index to be checked.
346  * @return bool True if the element at the given index exists.
347  */
348 PHP_METHOD(RepeatedField, offsetExists) {
349   long index;
350 
351   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
352       FAILURE) {
353     return;
354   }
355 
356   RepeatedField *intern = UNBOX(RepeatedField, getThis());
357 
358   RETURN_BOOL(index >= 0 &&
359               index < zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array)));
360 }
361 
362 /**
363  * Return the element at the given index.
364  * This will also be called for: $ele = $arr[0]
365  * @param long The index of the element to be fetched.
366  * @return object The stored element at given index.
367  * @exception Invalid type for index.
368  * @exception Non-existing index.
369  */
370 PHP_METHOD(RepeatedField, offsetGet) {
371   long index;
372   void *memory;
373 
374   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
375       FAILURE) {
376     return;
377   }
378 
379   RepeatedField *intern = UNBOX(RepeatedField, getThis());
380   HashTable *table = PHP_PROTO_HASH_OF(intern->array);
381 
382   if (intern->type == UPB_TYPE_MESSAGE) {
383     if (php_proto_zend_hash_index_find_zval(table, index, (void **)&memory) ==
384         FAILURE) {
385       zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
386       return;
387     }
388   } else {
389     if (php_proto_zend_hash_index_find_mem(table, index, (void **)&memory) ==
390         FAILURE) {
391       zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
392       return;
393     }
394   }
395   native_slot_get_by_array(intern->type, memory,
396                            ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
397 }
398 
399 /**
400  * Assign the element at the given index.
401  * This will also be called for: $arr []= $ele and $arr[0] = ele
402  * @param long The index of the element to be assigned.
403  * @param object The element to be assigned.
404  * @exception Invalid type for index.
405  * @exception Non-existing index.
406  * @exception Incorrect type of the element.
407  */
408 PHP_METHOD(RepeatedField, offsetSet) {
409   zval *index, *value;
410   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zz", &index, &value) ==
411       FAILURE) {
412     return;
413   }
414   repeated_field_write_dimension(getThis(), index, value TSRMLS_CC);
415 }
416 
417 /**
418  * Remove the element at the given index.
419  * This will also be called for: unset($arr)
420  * @param long The index of the element to be removed.
421  * @exception Invalid type for index.
422  * @exception The element to be removed is not at the end of the RepeatedField.
423  */
424 PHP_METHOD(RepeatedField, offsetUnset) {
425   long index;
426   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
427       FAILURE) {
428     return;
429   }
430 
431   RepeatedField *intern = UNBOX(RepeatedField, getThis());
432 
433   // Only the element at the end of the array can be removed.
434   if (index == -1 ||
435       index != (zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array)) - 1)) {
436     zend_error(E_USER_ERROR, "Cannot remove element at %ld.\n", index);
437     return;
438   }
439 
440   zend_hash_index_del(PHP_PROTO_HASH_OF(intern->array), index);
441 }
442 
443 /**
444  * Return the number of stored elements.
445  * This will also be called for: count($arr)
446  * @return long The number of stored elements.
447  */
448 PHP_METHOD(RepeatedField, count) {
449   RepeatedField *intern = UNBOX(RepeatedField, getThis());
450 
451   if (zend_parse_parameters_none() == FAILURE) {
452     return;
453   }
454 
455   RETURN_LONG(zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array)));
456 }
457 
458 /**
459  * Return the beginning iterator.
460  * This will also be called for: foreach($arr)
461  * @return object Beginning iterator.
462  */
463 PHP_METHOD(RepeatedField, getIterator) {
464   CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(return_value,
465                                    repeated_field_iter_type);
466 
467   RepeatedField *intern = UNBOX(RepeatedField, getThis());
468   RepeatedFieldIter *iter = UNBOX(RepeatedFieldIter, return_value);
469   iter->repeated_field = intern;
470   iter->position = 0;
471 }
472 
473 // -----------------------------------------------------------------------------
474 // RepeatedFieldIter creation/desctruction
475 // -----------------------------------------------------------------------------
476 
477 // Define object free method.
478 PHP_PROTO_OBJECT_FREE_START(RepeatedFieldIter, repeated_field_iter)
479 PHP_PROTO_OBJECT_FREE_END
480 
481 PHP_PROTO_OBJECT_DTOR_START(RepeatedFieldIter, repeated_field_iter)
482 PHP_PROTO_OBJECT_DTOR_END
483 
484 // Define object create method.
485 PHP_PROTO_OBJECT_CREATE_START(RepeatedFieldIter, repeated_field_iter)
486 intern->repeated_field = NULL;
487 intern->position = 0;
488 PHP_PROTO_OBJECT_CREATE_END(RepeatedFieldIter, repeated_field_iter)
489 
490 // Init class entry.
491 PHP_PROTO_INIT_CLASS_START("Google\\Protobuf\\Internal\\RepeatedFieldIter",
492                            RepeatedFieldIter, repeated_field_iter)
493 zend_class_implements(repeated_field_iter_type TSRMLS_CC, 1, zend_ce_iterator);
494 PHP_PROTO_INIT_CLASS_END
495 
496 // -----------------------------------------------------------------------------
497 // PHP RepeatedFieldIter Methods
498 // -----------------------------------------------------------------------------
499 
500 PHP_METHOD(RepeatedFieldIter, rewind) {
501   RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
502   intern->position = 0;
503 }
504 
505 PHP_METHOD(RepeatedFieldIter, current) {
506   RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
507   RepeatedField *repeated_field = intern->repeated_field;
508 
509   long index;
510   void *memory;
511 
512   HashTable *table = PHP_PROTO_HASH_OF(repeated_field->array);
513 
514   if (repeated_field->type == UPB_TYPE_MESSAGE) {
515     if (php_proto_zend_hash_index_find_zval(table, intern->position,
516                                             (void **)&memory) == FAILURE) {
517       zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
518       return;
519     }
520   } else {
521     if (php_proto_zend_hash_index_find_mem(table, intern->position,
522                                            (void **)&memory) == FAILURE) {
523       zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
524       return;
525     }
526   }
527   native_slot_get_by_array(repeated_field->type, memory,
528                            ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
529 }
530 
531 PHP_METHOD(RepeatedFieldIter, key) {
532   RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
533   RETURN_LONG(intern->position);
534 }
535 
536 PHP_METHOD(RepeatedFieldIter, next) {
537   RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
538   ++intern->position;
539 }
540 
541 PHP_METHOD(RepeatedFieldIter, valid) {
542   RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
543   RETURN_BOOL(zend_hash_num_elements(PHP_PROTO_HASH_OF(
544                   intern->repeated_field->array)) > intern->position);
545 }
546