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