Skip to content

Commit

Permalink
PHP: Added == operators for Map and Array. (#7900)
Browse files Browse the repository at this point in the history
  • Loading branch information
haberman committed Sep 22, 2020
1 parent 3abbbd8 commit cae85a9
Show file tree
Hide file tree
Showing 9 changed files with 162 additions and 49 deletions.
43 changes: 43 additions & 0 deletions php/ext/google/protobuf/array.c
Expand Up @@ -41,6 +41,7 @@
#include "arena.h"
#include "convert.h"
#include "def.h"
#include "message.h"
#include "php-upb.h"
#include "protobuf.h"

Expand Down Expand Up @@ -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.
}
Expand Down Expand Up @@ -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 ///////////////////////////////////////////////////

/**
Expand Down Expand Up @@ -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;

Expand Down
5 changes: 5 additions & 0 deletions php/ext/google/protobuf/array.h
Expand Up @@ -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_
45 changes: 45 additions & 0 deletions php/ext/google/protobuf/map.c
Expand Up @@ -37,6 +37,7 @@

#include "arena.h"
#include "convert.h"
#include "message.h"
#include "php-upb.h"
#include "protobuf.h"

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 ////////////////////////////////////////////////////////

/**
Expand Down Expand Up @@ -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;

Expand Down
3 changes: 3 additions & 0 deletions php/ext/google/protobuf/map.h
Expand Up @@ -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_
51 changes: 2 additions & 49 deletions php/ext/google/protobuf/message.c
Expand Up @@ -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;
Expand All @@ -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()
*/
Expand Down
3 changes: 3 additions & 0 deletions php/ext/google/protobuf/message.h
Expand Up @@ -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_
1 change: 1 addition & 0 deletions php/ext/google/protobuf/php-upb.c
Expand Up @@ -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;
Expand Down
31 changes: 31 additions & 0 deletions php/tests/ArrayTest.php
Expand Up @@ -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));
}
}
29 changes: 29 additions & 0 deletions php/tests/MapFieldTest.php
Expand Up @@ -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
#########################################################
Expand Down

0 comments on commit cae85a9

Please sign in to comment.