Skip to content

Commit

Permalink
Lazily Create Singular Wrapper Message (#6833)
Browse files Browse the repository at this point in the history
* Register additional handlers from wrappers

* Return zval instead of parse frame

* Use parse frame

* Update upb

* Lazily create wrapper messages

* Fix a segment fault

Need check type of field before getting submsg def

* Avoid expanding during serialization and direct access

* Fix a bug that getXXXUnwrapped returns null for string

* Implement writeWrapperUnwrapped

* Add more tests

* Fix oneof wrapper parsing

* Fix get oneof field

* Avoid expansion for oneof wrappers

* Fix bug

* Fix a bug that in php7 variable is defined out of scope

* Fix broken tests
 * Update upb to fix Timestamp conformance tests
 * Fix segmentation fault for oneof wrapper fields

* Fix encoding/decoding top level wrapper values

* Add type checking for write wrapper value in php7

* Fix zts build

* Fix the bug that readWrapperValue uses parent message's layout to access wrapper value

* Fix wrapper in map
  • Loading branch information
TeBoring committed Nov 14, 2019
1 parent f068005 commit 601f696
Show file tree
Hide file tree
Showing 12 changed files with 991 additions and 177 deletions.
393 changes: 381 additions & 12 deletions php/ext/google/protobuf/encode_decode.c

Large diffs are not rendered by default.

135 changes: 129 additions & 6 deletions php/ext/google/protobuf/message.c
Expand Up @@ -55,6 +55,8 @@ static zend_function_entry message_methods[] = {
PHP_ME(Message, serializeToJsonString, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Message, mergeFromJsonString, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Message, mergeFrom, NULL, ZEND_ACC_PUBLIC)
PHP_ME(Message, readWrapperValue, NULL, ZEND_ACC_PROTECTED)
PHP_ME(Message, writeWrapperValue, NULL, ZEND_ACC_PROTECTED)
PHP_ME(Message, readOneof, NULL, ZEND_ACC_PROTECTED)
PHP_ME(Message, writeOneof, NULL, ZEND_ACC_PROTECTED)
PHP_ME(Message, whichOneof, NULL, ZEND_ACC_PROTECTED)
Expand Down Expand Up @@ -295,12 +297,6 @@ void build_class_from_descriptor(
// PHP Methods
// -----------------------------------------------------------------------------

static bool is_wrapper_msg(const upb_msgdef* m) {
upb_wellknowntype_t type = upb_msgdef_wellknowntype(m);
return type >= UPB_WELLKNOWN_DOUBLEVALUE &&
type <= UPB_WELLKNOWN_BOOLVALUE;
}

static void append_wrapper_message(
zend_class_entry* subklass, RepeatedField* intern, zval* value TSRMLS_DC) {
MessageHeader* submsg;
Expand Down Expand Up @@ -561,6 +557,133 @@ PHP_METHOD(Message, mergeFrom) {
layout_merge(from->descriptor->layout, from, to TSRMLS_CC);
}

PHP_METHOD(Message, readWrapperValue) {
char* member;
PHP_PROTO_SIZE length;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &member,
&length) == FAILURE) {
return;
}

MessageHeader* msg = UNBOX(MessageHeader, getThis());
const upb_fielddef* field =
upb_msgdef_ntofz(msg->descriptor->msgdef, member);

if (upb_fielddef_containingoneof(field)) {
uint32_t* oneof_case =
slot_oneof_case(msg->descriptor->layout, message_data(msg), field);
if (*oneof_case != upb_fielddef_number(field)) {
RETURN_NULL();
}
}

zval* cached_zval =
CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, field));

if (Z_TYPE_P(cached_zval) == IS_NULL) {
RETURN_NULL();
}

if (Z_TYPE_P(cached_zval) == IS_OBJECT) {
const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
MessageHeader* submsg = UNBOX(MessageHeader, cached_zval);
CACHED_VALUE* cached_value = find_zval_property(submsg, value_field);
layout_get(submsg->descriptor->layout, submsg, value_field,
cached_value TSRMLS_CC);
RETURN_ZVAL(CACHED_PTR_TO_ZVAL_PTR(cached_value), 1, 0);
} else {
RETURN_ZVAL(cached_zval, 1, 0);
}
}

PHP_METHOD(Message, writeWrapperValue) {
char* member;
PHP_PROTO_SIZE length;
zval* value;
if (zend_parse_parameters(
ZEND_NUM_ARGS() TSRMLS_CC, "sz", &member, &length, &value) ==
FAILURE) {
return;
}

MessageHeader* msg = UNBOX(MessageHeader, getThis());
const upb_fielddef* field = upb_msgdef_ntofz(msg->descriptor->msgdef, member);

zval* cached_zval =
CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, field));

if (Z_TYPE_P(value) == IS_NULL) {
MessageHeader* msg = UNBOX(MessageHeader, getThis());
layout_set(msg->descriptor->layout, msg,
field, value TSRMLS_CC);
return;
}

{
// Type Checking
const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
upb_fieldtype_t type = upb_fielddef_type(value_field);
switch(type) {
case UPB_TYPE_STRING:
case UPB_TYPE_BYTES: {
if (!protobuf_convert_to_string(value)) {
return;
}
if (type == UPB_TYPE_STRING &&
!is_structurally_valid_utf8(Z_STRVAL_P(value), Z_STRLEN_P(value))) {
zend_error(E_USER_ERROR, "Given string is not UTF8 encoded.");
return;
}
}
break;
#define CASE_TYPE(upb_type, type, c_type) \
case UPB_TYPE_##upb_type: { \
c_type type##_value; \
if (!protobuf_convert_to_##type(value, &type##_value)) { \
return; \
} \
break; \
}
CASE_TYPE(INT32, int32, int32_t)
CASE_TYPE(UINT32, uint32, uint32_t)
CASE_TYPE(ENUM, int32, int32_t)
CASE_TYPE(INT64, int64, int64_t)
CASE_TYPE(UINT64, uint64, uint64_t)
CASE_TYPE(FLOAT, float, float)
CASE_TYPE(DOUBLE, double, double)
CASE_TYPE(BOOL, bool, int8_t)

#undef CASE_TYPE
}
}

if (upb_fielddef_containingoneof(field)) {
uint32_t* oneof_case =
slot_oneof_case(msg->descriptor->layout, message_data(msg), field);
if (*oneof_case != upb_fielddef_number(field)) {
zval null_value;
ZVAL_NULL(&null_value);
layout_set(msg->descriptor->layout, msg, field, &null_value TSRMLS_CC);
cached_zval = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, field));
ZVAL_ZVAL(cached_zval, value, 1, 0);
return;
}
}

if (Z_TYPE_P(cached_zval) == IS_OBJECT) {
const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
MessageHeader* submsg = UNBOX(MessageHeader, cached_zval);
CACHED_VALUE* cached_value = find_zval_property(submsg, value_field);
layout_set(submsg->descriptor->layout, submsg,
value_field, value TSRMLS_CC);
} else {
ZVAL_ZVAL(cached_zval, value, 1, 0);
}
}

PHP_METHOD(Message, readOneof) {
PHP_PROTO_LONG index;

Expand Down
5 changes: 5 additions & 0 deletions php/ext/google/protobuf/protobuf.h
Expand Up @@ -965,6 +965,8 @@ void* slot_memory(MessageLayout* layout, const void* storage,

PHP_METHOD(Message, clear);
PHP_METHOD(Message, mergeFrom);
PHP_METHOD(Message, readWrapperValue);
PHP_METHOD(Message, writeWrapperValue);
PHP_METHOD(Message, readOneof);
PHP_METHOD(Message, writeOneof);
PHP_METHOD(Message, whichOneof);
Expand Down Expand Up @@ -1525,4 +1527,7 @@ static inline zval* php_proto_message_read_property(
bool is_reserved_name(const char* name);
bool is_valid_constant_name(const char* name);

// For lazy wrapper
bool is_wrapper_msg(const upb_msgdef* m);

#endif // __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__
113 changes: 62 additions & 51 deletions php/ext/google/protobuf/storage.c
Expand Up @@ -116,17 +116,17 @@ bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
return false;
}

#if PHP_MAJOR_VERSION < 7
REPLACE_ZVAL_VALUE((CACHED_VALUE*)memory, value, 1);
#else
zval* property_ptr = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
if (EXPECTED(property_ptr != value)) {
php_proto_zval_ptr_dtor(property_ptr);
}

#if PHP_MAJOR_VERSION < 7
DEREF(memory, zval*) = value;
Z_ADDREF_P(value);
#else
ZVAL_ZVAL(property_ptr, value, 1, 0);
#endif

break;
}

Expand Down Expand Up @@ -797,25 +797,62 @@ zval* layout_get(MessageLayout* layout, MessageHeader* header,
if (upb_fielddef_containingoneof(field)) {
if (*oneof_case != upb_fielddef_number(field)) {
native_slot_get_default(upb_fielddef_type(field), cache TSRMLS_CC);
} else {
upb_fieldtype_t type = upb_fielddef_type(field);
CACHED_VALUE* stored_cache = find_zval_property(header, field);
native_slot_get(
type, value_memory(type, memory, stored_cache), cache TSRMLS_CC);
return CACHED_PTR_TO_ZVAL_PTR(cache);
}
return CACHED_PTR_TO_ZVAL_PTR(cache);
// Intentional fall through to be handled as a signuarl field.
} else if (is_map_field(field)) {
map_field_ensure_created(field, cache PHP_PROTO_TSRMLS_CC);
return CACHED_PTR_TO_ZVAL_PTR(cache);
} else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
repeated_field_ensure_created(field, cache PHP_PROTO_TSRMLS_CC);
return CACHED_PTR_TO_ZVAL_PTR(cache);
}

CACHED_VALUE* stored_cache = find_zval_property(header, field);

if (upb_fielddef_type(field) == UPB_TYPE_MESSAGE &&
is_wrapper_msg(upb_fielddef_msgsubdef(field))) {
zval * cached_zval = CACHED_PTR_TO_ZVAL_PTR(stored_cache);
#if PHP_MAJOR_VERSION >= 7
zend_object* obj;
#endif
if (Z_TYPE_P(cached_zval) != IS_OBJECT &&
Z_TYPE_P(cached_zval) != IS_NULL) {
// Needs to expand value to wrapper.
const upb_msgdef* submsgdef = upb_fielddef_msgsubdef(field);
const upb_fielddef* value_field = upb_msgdef_itof(submsgdef, 1);
MessageHeader* submsg;
Descriptor* subdesc =
UNBOX_HASHTABLE_VALUE(
Descriptor, get_def_obj((void*)submsgdef));
zend_class_entry* subklass = subdesc->klass;
#if PHP_MAJOR_VERSION < 7
zval* val = NULL;
MAKE_STD_ZVAL(val);
ZVAL_OBJ(val, subklass->create_object(subklass TSRMLS_CC));
submsg = UNBOX(MessageHeader, val);
#else
obj = subklass->create_object(subklass TSRMLS_CC);
submsg = (MessageHeader*)((char*)obj - XtOffsetOf(MessageHeader, std));
#endif
custom_data_init(subklass, submsg PHP_PROTO_TSRMLS_CC);

layout_set(subdesc->layout, submsg, value_field, cached_zval TSRMLS_CC);
#if PHP_MAJOR_VERSION < 7
ZVAL_ZVAL(cached_zval, val, 1, 1);
#else
ZVAL_OBJ(cached_zval, obj);
#endif
}
if (stored_cache != cache) {
ZVAL_ZVAL(CACHED_PTR_TO_ZVAL_PTR(cache), cached_zval, 1, 0);
}
} else {
upb_fieldtype_t type = upb_fielddef_type(field);
native_slot_get(type, value_memory(type, memory, cache),
native_slot_get(type, value_memory(type, memory, stored_cache),
cache TSRMLS_CC);
return CACHED_PTR_TO_ZVAL_PTR(cache);
}
return CACHED_PTR_TO_ZVAL_PTR(cache);
}

void layout_set(MessageLayout* layout, MessageHeader* header,
Expand All @@ -825,33 +862,6 @@ void layout_set(MessageLayout* layout, MessageHeader* header,
uint32_t* oneof_case = slot_oneof_case(layout, storage, field);

if (upb_fielddef_containingoneof(field)) {
upb_fieldtype_t type = upb_fielddef_type(field);
zend_class_entry *ce = NULL;

// For non-singular fields, the related memory needs to point to the actual
// zval in properties table first.
switch (type) {
case UPB_TYPE_MESSAGE: {
const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
ce = desc->klass;
// Intentionally fall through.
}
case UPB_TYPE_STRING:
case UPB_TYPE_BYTES: {
int property_cache_index =
header->descriptor->layout->fields[upb_fielddef_index(field)]
.cache_index;
DEREF(memory, CACHED_VALUE*) =
OBJ_PROP(&header->std, property_cache_index);
memory = DEREF(memory, CACHED_VALUE*);
break;
}
default:
break;
}

native_slot_set(type, ce, memory, val TSRMLS_CC);
*oneof_case = upb_fielddef_number(field);
} else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
// Works for both repeated and map fields
Expand Down Expand Up @@ -895,19 +905,20 @@ void layout_set(MessageLayout* layout, MessageHeader* header,
#endif
zval_dtor(&converted_value);
}
} else {
upb_fieldtype_t type = upb_fielddef_type(field);
zend_class_entry *ce = NULL;
if (type == UPB_TYPE_MESSAGE) {
const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
ce = desc->klass;
}
CACHED_VALUE* cache = find_zval_property(header, field);
native_slot_set(
type, ce, value_memory(upb_fielddef_type(field), memory, cache),
val TSRMLS_CC);
return;
}

upb_fieldtype_t type = upb_fielddef_type(field);
zend_class_entry *ce = NULL;
if (type == UPB_TYPE_MESSAGE) {
const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
ce = desc->klass;
}
CACHED_VALUE* cache = find_zval_property(header, field);
native_slot_set(
type, ce, value_memory(upb_fielddef_type(field), memory, cache),
val TSRMLS_CC);
}

static void native_slot_merge(
Expand Down

0 comments on commit 601f696

Please sign in to comment.