Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PHP: Added == operators for Map and Array. #7900

Merged
merged 1 commit into from Sep 22, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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