diff --git a/php/ext/google/protobuf/array.c b/php/ext/google/protobuf/array.c index 4615ed31aaaa..0fa9bf0e5bb3 100644 --- a/php/ext/google/protobuf/array.c +++ b/php/ext/google/protobuf/array.c @@ -41,6 +41,7 @@ #include "arena.h" #include "convert.h" #include "def.h" +#include "message.h" #include "php-upb.h" #include "protobuf.h" @@ -94,6 +95,26 @@ static void RepeatedField_destructor(zend_object* obj) { zend_object_std_dtor(&intern->std); } +/** + * RepeatedField_compare_objects() + * + * Object handler for comparing two repeated field objects. Called whenever PHP + * code does: + * + * $rf1 == $rf2 + */ +static int RepeatedField_compare_objects(zval *rf1, zval *rf2) { + RepeatedField* intern1 = (RepeatedField*)Z_OBJ_P(rf1); + RepeatedField* intern2 = (RepeatedField*)Z_OBJ_P(rf2); + upb_fieldtype_t type = intern1->type; + const upb_msgdef *m = intern1->desc ? intern1->desc->msgdef : NULL; + + if (type != intern2->type) return 1; + if (intern1->desc != intern2->desc) return 1; + + return ArrayEq(intern1->array, intern2->array, type, m) ? 0 : 1; +} + static HashTable *RepeatedField_GetProperties(PROTO_VAL *object) { return NULL; // We do not have a properties table. } @@ -177,6 +198,27 @@ upb_array *RepeatedField_GetUpbArray(zval *val, const upb_fielddef *f, } } +bool ArrayEq(const upb_array *a1, const upb_array *a2, upb_fieldtype_t type, + const upb_msgdef *m) { + size_t i; + size_t n; + + if ((a1 == NULL) != (a2 == NULL)) return false; + if (a1 == NULL) return true; + + n = upb_array_size(a1); + if (n != upb_array_size(a2)) return false; + + for (i = 0; i < n; i++) { + upb_msgval val1 = upb_array_get(a1, i); + upb_msgval val2 = upb_array_get(a2, i); + if (!ValueEq(val1, val2, type, m)) return false; + } + + return true; +} + + // RepeatedField PHP methods /////////////////////////////////////////////////// /** @@ -594,6 +636,7 @@ void Array_ModuleInit() { h = &RepeatedField_object_handlers; memcpy(h, &std_object_handlers, sizeof(zend_object_handlers)); h->dtor_obj = RepeatedField_destructor; + h->compare_objects = RepeatedField_compare_objects; h->get_properties = RepeatedField_GetProperties; h->get_property_ptr_ptr = RepeatedField_GetPropertyPtrPtr; diff --git a/php/ext/google/protobuf/array.h b/php/ext/google/protobuf/array.h index 5cf517c5662d..921e0bf564f4 100644 --- a/php/ext/google/protobuf/array.h +++ b/php/ext/google/protobuf/array.h @@ -58,4 +58,9 @@ upb_array *RepeatedField_GetUpbArray(zval *val, const upb_fielddef *f, upb_arena void RepeatedField_GetPhpWrapper(zval *val, upb_array *arr, const upb_fielddef *f, zval *arena); +// Returns true if the given arrays are equal. Both arrays must be of this +// |type| and, if the type is |UPB_TYPE_MESSAGE|, must have the same |m|. +bool ArrayEq(const upb_array *a1, const upb_array *a2, upb_fieldtype_t type, + const upb_msgdef *m); + #endif // PHP_PROTOBUF_ARRAY_H_ diff --git a/php/ext/google/protobuf/map.c b/php/ext/google/protobuf/map.c index f29c18c9bd9a..426d56a4f9b4 100644 --- a/php/ext/google/protobuf/map.c +++ b/php/ext/google/protobuf/map.c @@ -37,6 +37,7 @@ #include "arena.h" #include "convert.h" +#include "message.h" #include "php-upb.h" #include "protobuf.h" @@ -90,6 +91,28 @@ static void MapField_destructor(zend_object* obj) { zend_object_std_dtor(&intern->std); } +/** + * MapField_compare_objects() + * + * Object handler for comparing two repeated field objects. Called whenever PHP + * code does: + * + * $map1 == $map2 + */ +static int MapField_compare_objects(zval *map1, zval *map2) { + MapField* intern1 = (MapField*)Z_OBJ_P(map1); + MapField* intern2 = (MapField*)Z_OBJ_P(map2); + const upb_msgdef *m = intern1->desc ? intern1->desc->msgdef : NULL; + upb_fieldtype_t key_type = intern1->key_type; + upb_fieldtype_t val_type = intern1->val_type; + + if (key_type != intern2->key_type) return 1; + if (val_type != intern2->val_type) return 1; + if (intern1->desc != intern2->desc) return 1; + + return MapEq(intern1->map, intern2->map, key_type, val_type, m) ? 0 : 1; +} + static zval *Map_GetPropertyPtrPtr(PROTO_VAL *object, PROTO_STR *member, int type, void **cache_slot) { return NULL; // We don't offer direct references to our properties. @@ -185,6 +208,27 @@ upb_map *MapField_GetUpbMap(zval *val, const upb_fielddef *f, upb_arena *arena) } } +bool MapEq(const upb_map *m1, const upb_map *m2, upb_fieldtype_t key_type, + upb_fieldtype_t val_type, const upb_msgdef *m) { + size_t iter = UPB_MAP_BEGIN; + + if ((m1 == NULL) != (m2 == NULL)) return false; + if (m1 == NULL) return true; + if (upb_map_size(m1) != upb_map_size(m2)) return false; + + while (upb_mapiter_next(m1, &iter)) { + upb_msgval key = upb_mapiter_key(m1, iter); + upb_msgval val1 = upb_mapiter_value(m1, iter); + upb_msgval val2; + + if (!upb_map_get(m2, key, &val2)) return false; + if (!ValueEq(val1, val2, val_type, m)) return false; + } + + return true; +} + + // MapField PHP methods //////////////////////////////////////////////////////// /** @@ -578,6 +622,7 @@ void Map_ModuleInit() { h = &MapField_object_handlers; memcpy(h, &std_object_handlers, sizeof(zend_object_handlers)); h->dtor_obj = MapField_destructor; + h->compare_objects = MapField_compare_objects; h->get_properties = Map_GetProperties; h->get_property_ptr_ptr = Map_GetPropertyPtrPtr; diff --git a/php/ext/google/protobuf/map.h b/php/ext/google/protobuf/map.h index 9b3c9c144cd4..6eb0620c2e06 100644 --- a/php/ext/google/protobuf/map.h +++ b/php/ext/google/protobuf/map.h @@ -57,4 +57,7 @@ upb_map *MapField_GetUpbMap(zval *val, const upb_fielddef *f, upb_arena *arena); void MapField_GetPhpWrapper(zval *val, upb_map *arr, const upb_fielddef *f, zval *arena); +bool MapEq(const upb_map *m1, const upb_map *m2, upb_fieldtype_t key_type, + upb_fieldtype_t val_type, const upb_msgdef *m); + #endif // PHP_PROTOBUF_MAP_H_ diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c index e913379cf094..b2a03063f52d 100644 --- a/php/ext/google/protobuf/message.c +++ b/php/ext/google/protobuf/message.c @@ -113,8 +113,8 @@ static bool MessageEq(const upb_msg *m1, const upb_msg *m2, const upb_msgdef *m) /** * ValueEq()() */ -static bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type, - const upb_msgdef *m) { +bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type, + const upb_msgdef *m) { switch (type) { case UPB_TYPE_BOOL: return val1.bool_val == val2.bool_val; @@ -140,53 +140,6 @@ static bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type, } } -/** - * MapEq() - */ -static bool MapEq(const upb_map *m1, const upb_map *m2, - upb_fieldtype_t key_type, upb_fieldtype_t val_type, - const upb_msgdef *m) { - size_t iter = UPB_MAP_BEGIN; - - if ((m1 == NULL) != (m2 == NULL)) return false; - if (m1 == NULL) return true; - if (upb_map_size(m1) != upb_map_size(m2)) return false; - - while (upb_mapiter_next(m1, &iter)) { - upb_msgval key = upb_mapiter_key(m1, iter); - upb_msgval val1 = upb_mapiter_value(m1, iter); - upb_msgval val2; - - if (!upb_map_get(m2, key, &val2)) return false; - if (!ValueEq(val1, val2, val_type, m)) return false; - } - - return true; -} - -/** - * ArrayEq() - */ -static bool ArrayEq(const upb_array *a1, const upb_array *a2, - upb_fieldtype_t type, const upb_msgdef *m) { - size_t i; - size_t n; - - if ((a1 == NULL) != (a2 == NULL)) return false; - if (a1 == NULL) return true; - - n = upb_array_size(a1); - if (n != upb_array_size(a2)) return false; - - for (i = 0; i < n; i++) { - upb_msgval val1 = upb_array_get(a1, i); - upb_msgval val2 = upb_array_get(a2, i); - if (!ValueEq(val1, val2, type, m)) return false; - } - - return true; -} - /** * MessageEq() */ diff --git a/php/ext/google/protobuf/message.h b/php/ext/google/protobuf/message.h index 508397601643..0e6fb5a4fcbe 100644 --- a/php/ext/google/protobuf/message.h +++ b/php/ext/google/protobuf/message.h @@ -56,4 +56,7 @@ bool Message_GetUpbMessage(zval *val, const Descriptor *desc, upb_arena *arena, void Message_GetPhpWrapper(zval *val, const Descriptor *desc, upb_msg *msg, zval *arena); +bool ValueEq(upb_msgval val1, upb_msgval val2, upb_fieldtype_t type, + const upb_msgdef *m); + #endif // PHP_PROTOBUF_MESSAGE_H_ diff --git a/php/ext/google/protobuf/php-upb.c b/php/ext/google/protobuf/php-upb.c index 6ce85f1e6a9b..d1fc36663dfc 100644 --- a/php/ext/google/protobuf/php-upb.c +++ b/php/ext/google/protobuf/php-upb.c @@ -407,6 +407,7 @@ static const char *decode_varint64(upb_decstate *d, const char *ptr, } } +UPB_FORCEINLINE static const char *decode_varint32(upb_decstate *d, const char *ptr, const char *limit, uint32_t *val) { uint64_t u64; diff --git a/php/tests/ArrayTest.php b/php/tests/ArrayTest.php index 2cb4b3910de2..d167331364dd 100644 --- a/php/tests/ArrayTest.php +++ b/php/tests/ArrayTest.php @@ -590,4 +590,35 @@ public function testCycleLeak() $end = memory_get_usage(); $this->assertLessThan($start, $end); } + + ######################################################### + # Test equality + ######################################################### + + public function testEquality() + { + $arr = new RepeatedField(GPBType::INT32); + $arr2 = new RepeatedField(GPBType::INT32); + + $this->assertTrue($arr == $arr2); + + $arr[] = 0; + $arr[] = 1; + $arr[] = 2; + + $this->assertFalse($arr == $arr2); + + $arr2[] = 0; + $arr2[] = 1; + $arr2[] = 2; + + $this->assertTrue($arr == $arr2); + + // Arrays of different types always compare false. + $this->assertFalse(new RepeatedField(GPBType::INT32) == + new RepeatedField(GPBType::INT64)); + $this->assertFalse( + new RepeatedField(GPBType::MESSAGE, TestMessage::class) == + new RepeatedField(GPBType::MESSAGE, Sub::class)); + } } diff --git a/php/tests/MapFieldTest.php b/php/tests/MapFieldTest.php index 577be681bf69..4ed4b09cffe0 100644 --- a/php/tests/MapFieldTest.php +++ b/php/tests/MapFieldTest.php @@ -479,6 +479,35 @@ public function testMapElementIsReference() $m->setMapInt32Message($values); } + ######################################################### + # Test equality + ######################################################### + + public function testEquality() + { + $map = new MapField(GPBType::INT32, GPBType::INT32); + $map2 = new MapField(GPBType::INT32, GPBType::INT32); + + $this->assertTrue($map == $map2); + + $map[1] = 2; + + $this->assertFalse($map == $map2); + + $map2[1] = 2; + + $this->assertTrue($map == $map2); + + // Arrays of different types always compare false. + $this->assertFalse(new MapField(GPBType::INT32, GPBType::INT32) == + new MapField(GPBType::INT32, GPBType::INT64)); + $this->assertFalse(new MapField(GPBType::INT32, GPBType::INT32) == + new MapField(GPBType::INT64, GPBType::INT32)); + $this->assertFalse( + new MapField(GPBType::INT32, GPBType::MESSAGE, TestMessage::class) == + new MapField(GPBType::INT32, GPBType::MESSAGE, Sub::class)); + } + ######################################################### # Test memory leak #########################################################