From 9e59cee9d0dfa9b812dbfea93727027f2f99edc8 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Fri, 24 Apr 2015 22:29:51 -0400 Subject: [PATCH 1/3] PHPC-274: Fix zval_to_bson() encoding of BSON\Serializable objects Previously, only BSON\Persistable objects were handled. --- src/bson.c | 31 +++--- tests/bson/bson-encode-002.phpt | 138 +++++++++++++++++++------- tests/bson/bson-encode_error-001.phpt | 42 ++++++++ 3 files changed, 163 insertions(+), 48 deletions(-) create mode 100644 tests/bson/bson-encode_error-001.phpt diff --git a/src/bson.c b/src/bson.c index 434709a73..93d6ea3d6 100644 --- a/src/bson.c +++ b/src/bson.c @@ -763,23 +763,30 @@ PHONGO_API void zval_to_bson(zval *data, php_phongo_bson_flags_t flags, bson_t * switch(Z_TYPE_P(data)) { case IS_OBJECT: - if (instanceof_function(Z_OBJCE_P(data), php_phongo_persistable_ce TSRMLS_CC)) { + if (instanceof_function(Z_OBJCE_P(data), php_phongo_serializable_ce TSRMLS_CC)) { + zend_call_method_with_0_params(&data, NULL, NULL, BSON_SERIALIZE_FUNC_NAME, &obj_data); - if (flags & PHONGO_BSON_ADD_ODS) { - bson_append_binary(bson, PHONGO_ODM_FIELD_NAME, -1, 0x80, (const uint8_t *)Z_OBJCE_P(data)->name, strlen(Z_OBJCE_P(data)->name)); + if (!obj_data) { + /* zend_call_method() failed */ + return; } - zend_call_method_with_0_params(&data, NULL, NULL, BSON_SERIALIZE_FUNC_NAME, &obj_data); - if(obj_data) { - if (Z_TYPE_P(obj_data) == IS_ARRAY) { - ht_data = HASH_OF(obj_data); - } else { - phongo_throw_exception(PHONGO_ERROR_RUNTIME TSRMLS_CC, "%s", "Return value expected to be array"); - zval_ptr_dtor(&obj_data); - } + if (Z_TYPE_P(obj_data) != IS_ARRAY) { + phongo_throw_exception(PHONGO_ERROR_RUNTIME TSRMLS_CC, "Expected %s() to return an array, %s given", BSON_SERIALIZE_FUNC_NAME, zend_get_type_by_const(Z_TYPE_P(obj_data))); + zval_ptr_dtor(&obj_data); - break; + return; + } + + ht_data = HASH_OF(obj_data); + + if (instanceof_function(Z_OBJCE_P(data), php_phongo_persistable_ce TSRMLS_CC)) { + if (flags & PHONGO_BSON_ADD_ODS) { + bson_append_binary(bson, PHONGO_ODM_FIELD_NAME, -1, 0x80, (const uint8_t *)Z_OBJCE_P(data)->name, strlen(Z_OBJCE_P(data)->name)); + } } + + break; } /* break intentionally omitted */ diff --git a/tests/bson/bson-encode-002.phpt b/tests/bson/bson-encode-002.phpt index 14d2ae981..4fe1b4878 100644 --- a/tests/bson/bson-encode-002.phpt +++ b/tests/bson/bson-encode-002.phpt @@ -6,65 +6,130 @@ BSON encoding: Encoding objects into BSON representation "class", - "data" - ); +class AssociativeArray implements BSON\Serializable, BSON\Unserializable +{ + public function bsonSerialize() + { + return array("random" => "class", "data"); } - function bsonUnserialize(array $data) { - var_dump(__METHOD__, $data); + + public function bsonUnserialize(array $data) + { + echo __METHOD__, "() was called with data:\n"; + var_dump($data); } } -class MyClass2 implements BSON\Serializable, BSON\Unserializable { - function bsonSerialize() { - return array( - 1, 2, 3, - ); + +class NumericArray implements BSON\Serializable, BSON\Unserializable +{ + public function bsonSerialize() + { + return array(1, 2, 3); } - function bsonUnserialize(array $data) { - var_dump(__METHOD__, $data); + + public function bsonUnserialize(array $data) + { + echo __METHOD__, "() was called with data:\n"; + var_dump($data); } } -$tests = array( - array("stuff" => new MyClass), - array("stuff" => new MyClass2), -); +echo "Testing top-level AssociativeArray:\n"; +$bson = BSON\fromArray(new AssociativeArray); +echo BSON\toJSON($bson), "\n"; +echo "Encoded BSON:\n"; +hex_dump($bson); +$value = BSON\toArray($bson, array("document" => 'AssociativeArray')); +echo "Decoded BSON:\n"; +var_dump($value); + +echo "\nTesting embedded AssociativeArray:\n"; +$bson = BSON\fromArray(array('embed' => new AssociativeArray)); +echo BSON\toJSON($bson), "\n"; +echo "Encoded BSON:\n"; +hex_dump($bson); +$value = BSON\toArray($bson, array("document" => 'AssociativeArray')); +echo "Decoded BSON:\n"; +var_dump($value); + +echo "\nTesting top-level NumericArray:\n"; +$bson = BSON\fromArray(new NumericArray); +echo BSON\toJSON($bson), "\n"; +echo "Encoded BSON:\n"; +hex_dump($bson); +$value = BSON\toArray($bson, array("document" => 'NumericArray')); +echo "Decoded BSON:\n"; +var_dump($value); + +echo "\nTesting embedded NumericArray:\n"; +$bson = BSON\fromArray(array('embed' => new NumericArray)); +echo BSON\toJSON($bson), "\n"; +echo "Encoded BSON:\n"; +hex_dump($bson); +$value = BSON\toArray($bson, array("document" => 'NumericArray')); +echo "Decoded BSON:\n"; +var_dump($value); -foreach($tests as $n => $test) { - $s = BSON\fromArray($test); - echo "Test#{$n} ", BSON\toJSON($s), "\n"; - hex_dump($s); - $obj = BSON\toArray($s, array("document" => get_class($test["stuff"]))); - var_dump($obj); -} ?> ===DONE=== --EXPECTF-- -Test#0 { "stuff" : { "random" : "class", "0" : "data" } } - 0 : 2f 00 00 00 03 73 74 75 66 66 00 23 00 00 00 02 [/....stuff.#....] +Testing top-level AssociativeArray: +{ "random" : "class", "0" : "data" } +Encoded BSON: + 0 : 23 00 00 00 02 72 61 6e 64 6f 6d 00 06 00 00 00 [#....random.....] + 10 : 63 6c 61 73 73 00 02 30 00 05 00 00 00 64 61 74 [class..0.....dat] + 20 : 61 00 00 [a..] +Decoded BSON: +array(2) { + ["random"]=> + string(5) "class" + [0]=> + string(4) "data" +} + +Testing embedded AssociativeArray: +{ "embed" : { "random" : "class", "0" : "data" } } +Encoded BSON: + 0 : 2f 00 00 00 03 65 6d 62 65 64 00 23 00 00 00 02 [/....embed.#....] 10 : 72 61 6e 64 6f 6d 00 06 00 00 00 63 6c 61 73 73 [random.....class] 20 : 00 02 30 00 05 00 00 00 64 61 74 61 00 00 00 [..0.....data...] -string(24) "MyClass::bsonUnserialize" +AssociativeArray::bsonUnserialize() was called with data: array(2) { ["random"]=> string(5) "class" [0]=> string(4) "data" } +Decoded BSON: array(1) { - ["stuff"]=> - object(MyClass)#%d (0) { + ["embed"]=> + object(AssociativeArray)#%d (0) { } } -Test#1 { "stuff" : { "0" : 1, "1" : 2, "2" : 3 } } - 0 : 26 00 00 00 03 73 74 75 66 66 00 1a 00 00 00 10 [&....stuff......] + +Testing top-level NumericArray: +{ "0" : 1, "1" : 2, "2" : 3 } +Encoded BSON: + 0 : 1a 00 00 00 10 30 00 01 00 00 00 10 31 00 02 00 [.....0......1...] + 10 : 00 00 10 32 00 03 00 00 00 00 [...2......] +Decoded BSON: +array(3) { + [0]=> + int(1) + [1]=> + int(2) + [2]=> + int(3) +} + +Testing embedded NumericArray: +{ "embed" : { "0" : 1, "1" : 2, "2" : 3 } } +Encoded BSON: + 0 : 26 00 00 00 03 65 6d 62 65 64 00 1a 00 00 00 10 [&....embed......] 10 : 30 00 01 00 00 00 10 31 00 02 00 00 00 10 32 00 [0......1......2.] 20 : 03 00 00 00 00 00 [......] -string(25) "MyClass2::bsonUnserialize" +NumericArray::bsonUnserialize() was called with data: array(3) { [0]=> int(1) @@ -73,9 +138,10 @@ array(3) { [2]=> int(3) } +Decoded BSON: array(1) { - ["stuff"]=> - object(MyClass2)#%d (0) { + ["embed"]=> + object(NumericArray)#%d (0) { } } ===DONE=== diff --git a/tests/bson/bson-encode_error-001.phpt b/tests/bson/bson-encode_error-001.phpt new file mode 100644 index 000000000..81daddd71 --- /dev/null +++ b/tests/bson/bson-encode_error-001.phpt @@ -0,0 +1,42 @@ +--TEST-- +BSON encoding error when bsonSerialize() for root document does not return an array +--SKIPIF-- + +--FILE-- +value = $value; + } + + public function bsonSerialize() + { + return $this->value; + } +} + +$invalidValues = array(new stdClass, 'foo', 1, true); + +foreach ($invalidValues as $invalidValue) { + try { + BSON\fromArray(new MyClass($invalidValue)); + } catch (MongoDB\Driver\Exception\RuntimeException $e) { + echo $e->getMessage(), "\n"; + } +} + +?> +===DONE=== + +--EXPECT-- +Expected bsonSerialize() to return an array, object given +Expected bsonSerialize() to return an array, string given +Expected bsonSerialize() to return an array, integer given +Expected bsonSerialize() to return an array, boolean given +===DONE=== From 047bea57054b4a35b90b7a7cf5134128d9df5443 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 26 Apr 2015 14:23:37 -0400 Subject: [PATCH 2/3] PHPC-275: object_to_bson() handling for invalid bsonSerialize() retval This also refactors the handling to be consistent with zval_to_bson(). --- src/bson.c | 51 ++++++++++++++++----------- tests/bson/bson-encode_error-002.phpt | 42 ++++++++++++++++++++++ 2 files changed, 72 insertions(+), 21 deletions(-) create mode 100644 tests/bson/bson-encode_error-002.phpt diff --git a/src/bson.c b/src/bson.c index 93d6ea3d6..627b1f65e 100644 --- a/src/bson.c +++ b/src/bson.c @@ -607,34 +607,43 @@ void object_to_bson(zval *object, php_phongo_bson_flags_t flags, const char *key if (instanceof_function(Z_OBJCE_P(object), php_phongo_type_ce TSRMLS_CC)) { if (instanceof_function(Z_OBJCE_P(object), php_phongo_serializable_ce TSRMLS_CC)) { - zval *retval = NULL; + zval *obj_data = NULL; + bson_t child; + HashTable *tmp_ht; - zend_call_method_with_0_params(&object, NULL, NULL, BSON_SERIALIZE_FUNC_NAME, &retval); - if (retval) { - bson_t child; - HashTable *tmp_ht; + zend_call_method_with_0_params(&object, NULL, NULL, BSON_SERIALIZE_FUNC_NAME, &obj_data); - convert_to_array_ex(&retval); - tmp_ht = HASH_OF(retval); + if (!obj_data) { + /* zend_call_method() failed */ + return; + } - if (tmp_ht) { - tmp_ht->nApplyCount++; - } + if (Z_TYPE_P(obj_data) != IS_ARRAY) { + phongo_throw_exception(PHONGO_ERROR_RUNTIME TSRMLS_CC, "Expected %s() to return an array, %s given", BSON_SERIALIZE_FUNC_NAME, zend_get_type_by_const(Z_TYPE_P(obj_data))); + zval_ptr_dtor(&obj_data); - bson_append_document_begin(bson, key, key_len, &child); - if (instanceof_function(Z_OBJCE_P(object), php_phongo_persistable_ce TSRMLS_CC)) { - if (flags & PHONGO_BSON_ADD_CHILD_ODS) { - bson_append_binary(&child, PHONGO_ODM_FIELD_NAME, -1, 0x80, (const uint8_t *)Z_OBJCE_P(object)->name, strlen(Z_OBJCE_P(object)->name)); - } - } - zval_to_bson(retval, flags, &child, NULL TSRMLS_CC); - bson_append_document_end(bson, &child); + return; + } - if (tmp_ht) { - tmp_ht->nApplyCount--; + tmp_ht = HASH_OF(obj_data); + + if (tmp_ht) { + tmp_ht->nApplyCount++; + } + + bson_append_document_begin(bson, key, key_len, &child); + if (instanceof_function(Z_OBJCE_P(object), php_phongo_persistable_ce TSRMLS_CC)) { + if (flags & PHONGO_BSON_ADD_CHILD_ODS) { + bson_append_binary(&child, PHONGO_ODM_FIELD_NAME, -1, 0x80, (const uint8_t *)Z_OBJCE_P(object)->name, strlen(Z_OBJCE_P(object)->name)); } - zval_ptr_dtor(&retval); } + zval_to_bson(obj_data, flags, &child, NULL TSRMLS_CC); + bson_append_document_end(bson, &child); + + if (tmp_ht) { + tmp_ht->nApplyCount--; + } + zval_ptr_dtor(&obj_data); return; } diff --git a/tests/bson/bson-encode_error-002.phpt b/tests/bson/bson-encode_error-002.phpt new file mode 100644 index 000000000..560f86620 --- /dev/null +++ b/tests/bson/bson-encode_error-002.phpt @@ -0,0 +1,42 @@ +--TEST-- +BSON encoding error when bsonSerialize() for embedded document does not return an array +--SKIPIF-- + +--FILE-- +value = $value; + } + + public function bsonSerialize() + { + return $this->value; + } +} + +$invalidValues = array(new stdClass, 'foo', 1, true); + +foreach ($invalidValues as $invalidValue) { + try { + $bson = BSON\fromArray(array('embed' => new MyClass($invalidValue))); + } catch (MongoDB\Driver\Exception\RuntimeException $e) { + echo $e->getMessage(), "\n"; + } +} + +?> +===DONE=== + +--EXPECT-- +Expected bsonSerialize() to return an array, object given +Expected bsonSerialize() to return an array, string given +Expected bsonSerialize() to return an array, integer given +Expected bsonSerialize() to return an array, boolean given +===DONE=== From 56e55670b4c36bb1533400d9e333ab01aa7fc958 Mon Sep 17 00:00:00 2001 From: Jeremy Mikola Date: Sun, 26 Apr 2015 14:38:34 -0400 Subject: [PATCH 3/3] PHPC-274: Regression test for root BSON\Serializable encoding --- tests/bson/bug0274.phpt | 53 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 tests/bson/bug0274.phpt diff --git a/tests/bson/bug0274.phpt b/tests/bson/bug0274.phpt new file mode 100644 index 000000000..93d6f823a --- /dev/null +++ b/tests/bson/bug0274.phpt @@ -0,0 +1,53 @@ +--TEST-- +Test for PHPC-274: zval_to_bson() should process BSON\Serializable instances +--SKIPIF-- + +--FILE-- + "class", "data"); + } +} + +class NumericArray implements BSON\Serializable +{ + public function bsonSerialize() + { + return array(1, 2, 3); + } +} + +echo "Testing top-level AssociativeArray:\n"; +$bson = BSON\fromArray(new AssociativeArray); +echo BSON\toJSON($bson), "\n"; +echo "Encoded BSON:\n"; +hex_dump($bson); + +echo "\nTesting top-level NumericArray:\n"; +$bson = BSON\fromArray(new NumericArray); +echo BSON\toJSON($bson), "\n"; +echo "Encoded BSON:\n"; +hex_dump($bson); + +?> +===DONE=== + +--EXPECT-- +Testing top-level AssociativeArray: +{ "random" : "class", "0" : "data" } +Encoded BSON: + 0 : 23 00 00 00 02 72 61 6e 64 6f 6d 00 06 00 00 00 [#....random.....] + 10 : 63 6c 61 73 73 00 02 30 00 05 00 00 00 64 61 74 [class..0.....dat] + 20 : 61 00 00 [a..] + +Testing top-level NumericArray: +{ "0" : 1, "1" : 2, "2" : 3 } +Encoded BSON: + 0 : 1a 00 00 00 10 30 00 01 00 00 00 10 31 00 02 00 [.....0......1...] + 10 : 00 00 10 32 00 03 00 00 00 00 [...2......] +===DONE===