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