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 "array.h"
32
33#include <Zend/zend_API.h>
34#include <Zend/zend_interfaces.h>
35
36#include <ext/spl/spl_iterators.h>
37
38// This is not self-contained: it must be after other Zend includes.
39#include <Zend/zend_exceptions.h>
40
41#include "arena.h"
42#include "convert.h"
43#include "def.h"
44#include "php-upb.h"
45#include "protobuf.h"
46
47static void RepeatedFieldIter_make(zval *val, zval *repeated_field);
48
49// -----------------------------------------------------------------------------
50// RepeatedField
51// -----------------------------------------------------------------------------
52
53typedef struct {
54  zend_object std;
55  zval arena;
56  upb_array *array;
57  upb_fieldtype_t type;
58  const Descriptor* desc;  // When values are messages.
59} RepeatedField;
60
61zend_class_entry *RepeatedField_class_entry;
62static zend_object_handlers RepeatedField_object_handlers;
63
64// PHP Object Handlers /////////////////////////////////////////////////////////
65
66/**
67 * RepeatedField_create()
68 *
69 * PHP class entry function to allocate and initialize a new RepeatedField
70 * object.
71 */
72static zend_object* RepeatedField_create(zend_class_entry *class_type) {
73  RepeatedField *intern = emalloc(sizeof(RepeatedField));
74  zend_object_std_init(&intern->std, class_type);
75  intern->std.handlers = &RepeatedField_object_handlers;
76  Arena_Init(&intern->arena);
77  intern->array = NULL;
78  intern->desc = NULL;
79  // Skip object_properties_init(), we don't allow derived classes.
80  return &intern->std;
81}
82
83/**
84 * RepeatedField_dtor()
85 *
86 * Object handler to destroy a RepeatedField. This releases all resources
87 * associated with the message. Note that it is possible to access a destroyed
88 * object from PHP in rare cases.
89 */
90static void RepeatedField_destructor(zend_object* obj) {
91  RepeatedField* intern = (RepeatedField*)obj;
92  ObjCache_Delete(intern->array);
93  zval_ptr_dtor(&intern->arena);
94  zend_object_std_dtor(&intern->std);
95}
96
97static HashTable *RepeatedField_GetProperties(PROTO_VAL *object) {
98  return NULL;  // We do not have a properties table.
99}
100
101static zval *RepeatedField_GetPropertyPtrPtr(PROTO_VAL *object,
102                                             PROTO_STR *member,
103                                             int type, void **cache_slot) {
104  return NULL;  // We don't offer direct references to our properties.
105}
106
107// C Functions from array.h ////////////////////////////////////////////////////
108
109// These are documented in the header file.
110
111void RepeatedField_GetPhpWrapper(zval *val, upb_array *arr,
112                                 const upb_fielddef *f, zval *arena) {
113  if (!arr) {
114    ZVAL_NULL(val);
115    return;
116  }
117
118  if (!ObjCache_Get(arr, val)) {
119    RepeatedField *intern = emalloc(sizeof(RepeatedField));
120    zend_object_std_init(&intern->std, RepeatedField_class_entry);
121    intern->std.handlers = &RepeatedField_object_handlers;
122    ZVAL_COPY(&intern->arena, arena);
123    intern->array = arr;
124    intern->type = upb_fielddef_type(f);
125    intern->desc = Descriptor_GetFromFieldDef(f);
126    // Skip object_properties_init(), we don't allow derived classes.
127    ObjCache_Add(intern->array, &intern->std);
128    ZVAL_OBJ(val, &intern->std);
129  }
130}
131
132upb_array *RepeatedField_GetUpbArray(zval *val, const upb_fielddef *f,
133                                     upb_arena *arena) {
134  if (Z_ISREF_P(val)) {
135    ZVAL_DEREF(val);
136  }
137
138  if (Z_TYPE_P(val) == IS_ARRAY) {
139    // Auto-construct, eg. [1, 2, 3] -> upb_array([1, 2, 3]).
140    upb_array *arr = upb_array_new(arena, upb_fielddef_type(f));
141    HashTable *table = HASH_OF(val);
142    HashPosition pos;
143    upb_fieldtype_t type = upb_fielddef_type(f);
144    const Descriptor *desc = Descriptor_GetFromFieldDef(f);
145
146    zend_hash_internal_pointer_reset_ex(table, &pos);
147
148    while (true) {
149      zval *zv = zend_hash_get_current_data_ex(table, &pos);
150      upb_msgval val;
151
152      if (!zv) return arr;
153
154      if (!Convert_PhpToUpbAutoWrap(zv, &val, type, desc, arena)) {
155        return NULL;
156      }
157
158      upb_array_append(arr, val, arena);
159      zend_hash_move_forward_ex(table, &pos);
160    }
161  } else if (Z_TYPE_P(val) == IS_OBJECT &&
162             Z_OBJCE_P(val) == RepeatedField_class_entry) {
163    // Unwrap existing RepeatedField object to get the upb_array* inside.
164    RepeatedField *intern = (RepeatedField*)Z_OBJ_P(val);
165    const Descriptor *desc = Descriptor_GetFromFieldDef(f);
166
167    if (intern->type != upb_fielddef_type(f) || intern->desc != desc) {
168      php_error_docref(NULL, E_USER_ERROR,
169                       "Wrong type for this repeated field.");
170    }
171
172    upb_arena_fuse(arena, Arena_Get(&intern->arena));
173    return intern->array;
174  } else {
175    php_error_docref(NULL, E_USER_ERROR, "Must be a repeated field");
176    return NULL;
177  }
178}
179
180// RepeatedField PHP methods ///////////////////////////////////////////////////
181
182/**
183 * RepeatedField::__construct()
184 *
185 * Constructs an instance of RepeatedField.
186 * @param long Type of the stored element.
187 * @param string Message/Enum class.
188 */
189PHP_METHOD(RepeatedField, __construct) {
190  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
191  upb_arena *arena = Arena_Get(&intern->arena);
192  zend_long type;
193  zend_class_entry* klass = NULL;
194
195  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l|C", &type, &klass) != SUCCESS) {
196    return;
197  }
198
199  intern->type = pbphp_dtype_to_type(type);
200  intern->desc = Descriptor_GetFromClassEntry(klass);
201
202  if (intern->type == UPB_TYPE_MESSAGE && klass == NULL) {
203    php_error_docref(NULL, E_USER_ERROR,
204                     "Message/enum type must have concrete class.");
205    return;
206  }
207
208  intern->array = upb_array_new(arena, intern->type);
209  ObjCache_Add(intern->array, &intern->std);
210}
211
212/**
213 * RepeatedField::append()
214 *
215 * Append element to the end of the repeated field.
216 * @param object The element to be added.
217 */
218PHP_METHOD(RepeatedField, append) {
219  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
220  upb_arena *arena = Arena_Get(&intern->arena);
221  zval *php_val;
222  upb_msgval msgval;
223
224  if (zend_parse_parameters(ZEND_NUM_ARGS(), "z", &php_val) != SUCCESS ||
225      !Convert_PhpToUpb(php_val, &msgval, intern->type, intern->desc, arena)) {
226    return;
227  }
228
229  upb_array_append(intern->array, msgval, arena);
230}
231
232/**
233 * RepeatedField::offsetExists()
234 *
235 * Implements the ArrayAccess interface. Invoked when PHP code calls:
236 *
237 *   isset($arr[$idx]);
238 *   empty($arr[$idx]);
239 *
240 * @param long The index to be checked.
241 * @return bool True if the element at the given index exists.
242 */
243PHP_METHOD(RepeatedField, offsetExists) {
244  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
245  zend_long index;
246
247  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
248    return;
249  }
250
251  RETURN_BOOL(index >= 0 && index < upb_array_size(intern->array));
252}
253
254/**
255 * RepeatedField::offsetGet()
256 *
257 * Implements the ArrayAccess interface. Invoked when PHP code calls:
258 *
259 *   $x = $arr[$idx];
260 *
261 * @param long The index of the element to be fetched.
262 * @return object The stored element at given index.
263 * @exception Invalid type for index.
264 * @exception Non-existing index.
265 */
266PHP_METHOD(RepeatedField, offsetGet) {
267  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
268  zend_long index;
269  upb_msgval msgval;
270  zval ret;
271
272  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) {
273    return;
274  }
275
276  if (index < 0 || index >= upb_array_size(intern->array)) {
277    zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
278    return;
279  }
280
281  msgval = upb_array_get(intern->array, index);
282  Convert_UpbToPhp(msgval, &ret, intern->type, intern->desc, &intern->arena);
283  RETURN_ZVAL(&ret, 0, 1);
284}
285
286/**
287 * RepeatedField::offsetSet()
288 *
289 * Implements the ArrayAccess interface. Invoked when PHP code calls:
290 *
291 *   $arr[$idx] = $x;
292 *   $arr []= $x;  // Append
293 *
294 * @param long The index of the element to be assigned.
295 * @param object The element to be assigned.
296 * @exception Invalid type for index.
297 * @exception Non-existing index.
298 * @exception Incorrect type of the element.
299 */
300PHP_METHOD(RepeatedField, offsetSet) {
301  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
302  upb_arena *arena = Arena_Get(&intern->arena);
303  size_t size = upb_array_size(intern->array);
304  zval *offset, *val;
305  int64_t index;
306  upb_msgval msgval;
307
308  if (zend_parse_parameters(ZEND_NUM_ARGS(), "zz", &offset, &val) != SUCCESS) {
309    return;
310  }
311
312  if (Z_TYPE_P(offset) == IS_NULL) {
313    index = size;
314  } else if (!Convert_PhpToInt64(offset, &index)) {
315    return;
316  }
317
318  if (!Convert_PhpToUpb(val, &msgval, intern->type, intern->desc, arena)) {
319    return;
320  }
321
322  if (index > size) {
323    zend_error(E_USER_ERROR, "Element at index %ld doesn't exist.\n", index);
324  } else if (index == size) {
325    upb_array_append(intern->array, msgval, Arena_Get(&intern->arena));
326  } else {
327    upb_array_set(intern->array, index, msgval);
328  }
329}
330
331/**
332 * RepeatedField::offsetUnset()
333 *
334 * Implements the ArrayAccess interface. Invoked when PHP code calls:
335 *
336 *   unset($arr[$idx]);
337 *
338 * @param long The index of the element to be removed.
339 * @exception Invalid type for index.
340 * @exception The element to be removed is not at the end of the RepeatedField.
341 */
342PHP_METHOD(RepeatedField, offsetUnset) {
343  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
344  zend_long index;
345  zend_long size = upb_array_size(intern->array);
346
347  // Only the element at the end of the array can be removed.
348  if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) != SUCCESS) {
349    return;
350  }
351
352  if (size == 0 || index != size - 1) {
353    php_error_docref(NULL, E_USER_ERROR, "Cannot remove element at %ld.\n",
354                     index);
355    return;
356  }
357
358  upb_array_resize(intern->array, size - 1, Arena_Get(&intern->arena));
359}
360
361/**
362 * RepeatedField::count()
363 *
364 * Implements the Countable interface. Invoked when PHP code calls:
365 *
366 *   $len = count($arr);
367 * Return the number of stored elements.
368 * This will also be called for: count($arr)
369 * @return long The number of stored elements.
370 */
371PHP_METHOD(RepeatedField, count) {
372  RepeatedField *intern = (RepeatedField*)Z_OBJ_P(getThis());
373
374  if (zend_parse_parameters_none() == FAILURE) {
375    return;
376  }
377
378  RETURN_LONG(upb_array_size(intern->array));
379}
380
381/**
382 * RepeatedField::getIterator()
383 *
384 * Implements the IteratorAggregate interface. Invoked when PHP code calls:
385 *
386 *   foreach ($arr) {}
387 *
388 * @return object Beginning iterator.
389 */
390PHP_METHOD(RepeatedField, getIterator) {
391  zval ret;
392  RepeatedFieldIter_make(&ret, getThis());
393  RETURN_ZVAL(&ret, 0, 1);
394}
395
396ZEND_BEGIN_ARG_INFO_EX(arginfo_construct, 0, 0, 1)
397  ZEND_ARG_INFO(0, type)
398  ZEND_ARG_INFO(0, class)
399ZEND_END_ARG_INFO()
400
401ZEND_BEGIN_ARG_INFO_EX(arginfo_append, 0, 0, 1)
402  ZEND_ARG_INFO(0, newval)
403ZEND_END_ARG_INFO()
404
405ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetGet, 0, 0, 1)
406  ZEND_ARG_INFO(0, index)
407ZEND_END_ARG_INFO()
408
409ZEND_BEGIN_ARG_INFO_EX(arginfo_offsetSet, 0, 0, 2)
410  ZEND_ARG_INFO(0, index)
411  ZEND_ARG_INFO(0, newval)
412ZEND_END_ARG_INFO()
413
414ZEND_BEGIN_ARG_INFO(arginfo_void, 0)
415ZEND_END_ARG_INFO()
416
417static zend_function_entry repeated_field_methods[] = {
418  PHP_ME(RepeatedField, __construct,  arginfo_construct, ZEND_ACC_PUBLIC)
419  PHP_ME(RepeatedField, append,       arginfo_append,    ZEND_ACC_PUBLIC)
420  PHP_ME(RepeatedField, offsetExists, arginfo_offsetGet, ZEND_ACC_PUBLIC)
421  PHP_ME(RepeatedField, offsetGet,    arginfo_offsetGet, ZEND_ACC_PUBLIC)
422  PHP_ME(RepeatedField, offsetSet,    arginfo_offsetSet, ZEND_ACC_PUBLIC)
423  PHP_ME(RepeatedField, offsetUnset,  arginfo_offsetGet, ZEND_ACC_PUBLIC)
424  PHP_ME(RepeatedField, count,        arginfo_void,      ZEND_ACC_PUBLIC)
425  PHP_ME(RepeatedField, getIterator,  arginfo_void,      ZEND_ACC_PUBLIC)
426  ZEND_FE_END
427};
428
429// -----------------------------------------------------------------------------
430// PHP RepeatedFieldIter
431// -----------------------------------------------------------------------------
432
433typedef struct {
434  zend_object std;
435  zval repeated_field;
436  zend_long position;
437} RepeatedFieldIter;
438
439zend_class_entry *RepeatedFieldIter_class_entry;
440static zend_object_handlers repeated_field_iter_object_handlers;
441
442/**
443 * RepeatedFieldIter_create()
444 *
445 * PHP class entry function to allocate and initialize a new RepeatedFieldIter
446 * object.
447 */
448zend_object* RepeatedFieldIter_create(zend_class_entry *class_type) {
449  RepeatedFieldIter *intern = emalloc(sizeof(RepeatedFieldIter));
450  zend_object_std_init(&intern->std, class_type);
451  intern->std.handlers = &repeated_field_iter_object_handlers;
452  ZVAL_NULL(&intern->repeated_field);
453  intern->position = 0;
454  // Skip object_properties_init(), we don't allow derived classes.
455  return &intern->std;
456}
457
458/**
459 * RepeatedFieldIter_dtor()
460 *
461 * Object handler to destroy a RepeatedFieldIter. This releases all resources
462 * associated with the message. Note that it is possible to access a destroyed
463 * object from PHP in rare cases.
464 */
465static void RepeatedFieldIter_dtor(zend_object* obj) {
466  RepeatedFieldIter* intern = (RepeatedFieldIter*)obj;
467  zval_ptr_dtor(&intern->repeated_field);
468  zend_object_std_dtor(&intern->std);
469}
470
471/**
472 * RepeatedFieldIter_make()
473 *
474 * C function to create a RepeatedFieldIter.
475 */
476static void RepeatedFieldIter_make(zval *val, zval *repeated_field) {
477  RepeatedFieldIter *iter;
478  ZVAL_OBJ(val, RepeatedFieldIter_class_entry->create_object(
479                    RepeatedFieldIter_class_entry));
480  iter = (RepeatedFieldIter*)Z_OBJ_P(val);
481  ZVAL_COPY(&iter->repeated_field, repeated_field);
482}
483
484/*
485 * When a user writes:
486 *
487 *   foreach($arr as $key => $val) {}
488 *
489 * PHP's iterator protocol is:
490 *
491 *   $iter = $arr->getIterator();
492 *   for ($iter->rewind(); $iter->valid(); $iter->next()) {
493 *     $key = $iter->key();
494 *     $val = $iter->current();
495 *   }
496 */
497
498/**
499 * RepeatedFieldIter::rewind()
500 *
501 * Implements the Iterator interface. Sets the iterator to the first element.
502 */
503PHP_METHOD(RepeatedFieldIter, rewind) {
504  RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis());
505  intern->position = 0;
506}
507
508/**
509 * RepeatedFieldIter::current()
510 *
511 * Implements the Iterator interface. Returns the current value.
512 */
513PHP_METHOD(RepeatedFieldIter, current) {
514  RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis());
515  RepeatedField *field = (RepeatedField*)Z_OBJ_P(&intern->repeated_field);
516  upb_array *array = field->array;
517  zend_long index = intern->position;
518  upb_msgval msgval;
519  zval ret;
520
521  if (index < 0 || index >= upb_array_size(array)) {
522    zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
523  }
524
525  msgval = upb_array_get(array, index);
526
527  Convert_UpbToPhp(msgval, &ret, field->type, field->desc, &field->arena);
528  RETURN_ZVAL(&ret, 0, 1);
529}
530
531/**
532 * RepeatedFieldIter::key()
533 *
534 * Implements the Iterator interface. Returns the current key.
535 */
536PHP_METHOD(RepeatedFieldIter, key) {
537  RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis());
538  RETURN_LONG(intern->position);
539}
540
541/**
542 * RepeatedFieldIter::next()
543 *
544 * Implements the Iterator interface. Advances to the next element.
545 */
546PHP_METHOD(RepeatedFieldIter, next) {
547  RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis());
548  ++intern->position;
549}
550
551/**
552 * RepeatedFieldIter::valid()
553 *
554 * Implements the Iterator interface. Returns true if this is a valid element.
555 */
556PHP_METHOD(RepeatedFieldIter, valid) {
557  RepeatedFieldIter *intern = (RepeatedFieldIter*)Z_OBJ_P(getThis());
558  RepeatedField *field = (RepeatedField*)Z_OBJ_P(&intern->repeated_field);
559  RETURN_BOOL(intern->position < upb_array_size(field->array));
560}
561
562static zend_function_entry repeated_field_iter_methods[] = {
563  PHP_ME(RepeatedFieldIter, rewind,      arginfo_void, ZEND_ACC_PUBLIC)
564  PHP_ME(RepeatedFieldIter, current,     arginfo_void, ZEND_ACC_PUBLIC)
565  PHP_ME(RepeatedFieldIter, key,         arginfo_void, ZEND_ACC_PUBLIC)
566  PHP_ME(RepeatedFieldIter, next,        arginfo_void, ZEND_ACC_PUBLIC)
567  PHP_ME(RepeatedFieldIter, valid,       arginfo_void, ZEND_ACC_PUBLIC)
568  ZEND_FE_END
569};
570
571// -----------------------------------------------------------------------------
572// Module init.
573// -----------------------------------------------------------------------------
574
575/**
576 * Array_ModuleInit()
577 *
578 * Called when the C extension is loaded to register all types.
579 */
580void Array_ModuleInit() {
581  zend_class_entry tmp_ce;
582  zend_object_handlers *h;
583
584  // RepeatedField.
585  INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Internal\\RepeatedField",
586                   repeated_field_methods);
587
588  RepeatedField_class_entry = zend_register_internal_class(&tmp_ce);
589  zend_class_implements(RepeatedField_class_entry, 3, spl_ce_ArrayAccess,
590                        zend_ce_aggregate, spl_ce_Countable);
591  RepeatedField_class_entry->ce_flags |= ZEND_ACC_FINAL;
592  RepeatedField_class_entry->create_object = RepeatedField_create;
593
594  h = &RepeatedField_object_handlers;
595  memcpy(h, &std_object_handlers, sizeof(zend_object_handlers));
596  h->dtor_obj = RepeatedField_destructor;
597  h->get_properties = RepeatedField_GetProperties;
598  h->get_property_ptr_ptr = RepeatedField_GetPropertyPtrPtr;
599
600  // RepeatedFieldIter
601  INIT_CLASS_ENTRY(tmp_ce, "Google\\Protobuf\\Internal\\RepeatedFieldIter",
602                   repeated_field_iter_methods);
603
604  RepeatedFieldIter_class_entry = zend_register_internal_class(&tmp_ce);
605  zend_class_implements(RepeatedFieldIter_class_entry, 1, zend_ce_iterator);
606  RepeatedFieldIter_class_entry->ce_flags |= ZEND_ACC_FINAL;
607  RepeatedFieldIter_class_entry->create_object = RepeatedFieldIter_create;
608
609  h = &repeated_field_iter_object_handlers;
610  memcpy(h, &std_object_handlers, sizeof(zend_object_handlers));
611  h->dtor_obj = RepeatedFieldIter_dtor;
612}
613