From e8a21fae260b55dc55fc4027768640e80e0201bd Mon Sep 17 00:00:00 2001 From: Paul Yang Date: Thu, 14 Nov 2019 12:17:04 -0800 Subject: [PATCH] Lazily Create Singular Wrapper Message (#6833) * 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 --- php/ext/google/protobuf/encode_decode.c | 393 +++++++++++++++++- php/ext/google/protobuf/message.c | 135 +++++- php/ext/google/protobuf/protobuf.h | 5 + php/ext/google/protobuf/storage.c | 113 ++--- php/ext/google/protobuf/upb.c | 170 ++++---- php/ext/google/protobuf/upb.h | 13 +- php/src/Google/Protobuf/Internal/Message.php | 36 ++ php/tests/encode_decode_test.php | 220 +++++++++- php/tests/memory_leak_test.php | 6 + php/tests/proto/test.proto | 60 +++ php/tests/wrapper_type_setters_test.php | 3 +- .../protobuf/compiler/php/php_generator.cc | 14 +- 12 files changed, 991 insertions(+), 177 deletions(-) diff --git a/php/ext/google/protobuf/encode_decode.c b/php/ext/google/protobuf/encode_decode.c index 39304a571133..2612d22d6696 100644 --- a/php/ext/google/protobuf/encode_decode.c +++ b/php/ext/google/protobuf/encode_decode.c @@ -122,12 +122,29 @@ static void stackenv_uninit(stackenv* se) { // Parsing. // ----------------------------------------------------------------------------- -// TODO(teboring): This shoud be a bit in upb_msgdef -static bool is_wrapper_msg(const upb_msgdef *msg) { - return !strcmp(upb_filedef_name(upb_msgdef_file(msg)), - "google/protobuf/wrappers.proto"); +bool is_wrapper_msg(const upb_msgdef* m) { + switch (upb_msgdef_wellknowntype(m)) { + case UPB_WELLKNOWN_DOUBLEVALUE: + case UPB_WELLKNOWN_FLOATVALUE: + case UPB_WELLKNOWN_INT64VALUE: + case UPB_WELLKNOWN_UINT64VALUE: + case UPB_WELLKNOWN_INT32VALUE: + case UPB_WELLKNOWN_UINT32VALUE: + case UPB_WELLKNOWN_STRINGVALUE: + case UPB_WELLKNOWN_BYTESVALUE: + case UPB_WELLKNOWN_BOOLVALUE: + return true; + default: + return false; + } } +typedef struct { + void* closure; + void* submsg; + bool is_msg; +} wrapperfields_parseframe_t; + #define DEREF(msg, ofs, type) *(type*)(((uint8_t *)msg) + ofs) // Creates a handlerdata that simply contains the offset for this field. @@ -432,6 +449,40 @@ static void *appendsubmsg_handler(void *closure, const void *hd) { return submsg; } +// Appends a wrapper submessage to a repeated field. +static void *appendwrappersubmsg_handler(void *closure, const void *hd) { + zval* array = (zval*)closure; + TSRMLS_FETCH(); + RepeatedField* intern = UNBOX(RepeatedField, array); + + const submsg_handlerdata_t *submsgdata = hd; + Descriptor* subdesc = + UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md)); + zend_class_entry* subklass = subdesc->klass; + MessageHeader* submsg; + wrapperfields_parseframe_t* frame = + (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t)); + +#if PHP_MAJOR_VERSION < 7 + zval* val = NULL; + MAKE_STD_ZVAL(val); + ZVAL_OBJ(val, subklass->create_object(subklass TSRMLS_CC)); + repeated_field_push_native(intern, &val); + submsg = UNBOX(MessageHeader, val); +#else + zend_object* obj = subklass->create_object(subklass TSRMLS_CC); + repeated_field_push_native(intern, &obj); + submsg = (MessageHeader*)((char*)obj - XtOffsetOf(MessageHeader, std)); +#endif + custom_data_init(subklass, submsg PHP_PROTO_TSRMLS_CC); + + frame->closure = closure; + frame->submsg = submsg; + frame->is_msg = true; + + return frame; +} + // Sets a non-repeated submessage field in a message. static void *submsg_handler(void *closure, const void *hd) { MessageHeader* msg = closure; @@ -502,6 +553,46 @@ static void *map_submsg_handler(void *closure, const void *hd) { return submsg; } +static void *map_wrapper_submsg_handler(void *closure, const void *hd) { + MessageHeader* msg = closure; + const submsg_handlerdata_t* submsgdata = hd; + TSRMLS_FETCH(); + Descriptor* subdesc = + UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md)); + zend_class_entry* subklass = subdesc->klass; + zval* submsg_php; + MessageHeader* submsg; + wrapperfields_parseframe_t* frame = + (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t)); + + CACHED_VALUE* cached = + DEREF(message_data(msg), submsgdata->ofs, CACHED_VALUE*); + + if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(cached)) == IS_NULL) { +#if PHP_MAJOR_VERSION < 7 + zval val; + ZVAL_OBJ(&val, subklass->create_object(subklass TSRMLS_CC)); + MessageHeader* intern = UNBOX(MessageHeader, &val); + custom_data_init(subklass, intern PHP_PROTO_TSRMLS_CC); + REPLACE_ZVAL_VALUE(cached, &val, 1); + zval_dtor(&val); +#else + zend_object* obj = subklass->create_object(subklass TSRMLS_CC); + ZVAL_OBJ(cached, obj); + MessageHeader* intern = UNBOX_HASHTABLE_VALUE(MessageHeader, obj); + custom_data_init(subklass, intern PHP_PROTO_TSRMLS_CC); +#endif + } + + submsg_php = CACHED_PTR_TO_ZVAL_PTR(cached); + + submsg = UNBOX(MessageHeader, submsg_php); + frame->closure = closure; + frame->submsg = submsg; + frame->is_msg = true; + return frame; +} + // Handler data for startmap/endmap handlers. typedef struct { const upb_fielddef* fd; @@ -887,6 +978,78 @@ static void* oneofsubmsg_handler(void* closure, const void* hd) { return submsg; } +// Sets a non-repeated wrapper submessage field in a message. +static void* wrapper_submsg_handler(void* closure, const void* hd) { + MessageHeader* msg = closure; + const submsg_handlerdata_t* submsgdata = hd; + TSRMLS_FETCH(); + Descriptor* subdesc = + UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md)); + zend_class_entry* subklass = subdesc->klass; + zval* submsg_php; + MessageHeader* submsg; + wrapperfields_parseframe_t* frame = + (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t)); + + CACHED_VALUE* cached = find_zval_property(msg, submsgdata->fd); + submsg_php = CACHED_PTR_TO_ZVAL_PTR(cached); + frame->closure = closure; + + if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(cached)) == IS_OBJECT) { + submsg = UNBOX(MessageHeader, submsg_php); + frame->submsg = submsg; + frame->is_msg = true; + } else { + // In this case, wrapper message hasn't been created and value will be + // stored in cache directly. + frame->submsg = cached; + frame->is_msg = false; + } + + return frame; +} + +// Handler for a wrapper submessage field in a oneof. +static void* wrapper_oneofsubmsg_handler(void* closure, const void* hd) { + MessageHeader* msg = closure; + const oneof_handlerdata_t *oneofdata = hd; + uint32_t oldcase = DEREF(message_data(msg), oneofdata->case_ofs, uint32_t); + TSRMLS_FETCH(); + Descriptor* subdesc = + UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)oneofdata->md)); + zend_class_entry* subklass = subdesc->klass; + wrapperfields_parseframe_t* frame = + (wrapperfields_parseframe_t*)malloc(sizeof(wrapperfields_parseframe_t)); + CACHED_VALUE* cached = OBJ_PROP(&msg->std, oneofdata->property_ofs); + MessageHeader* submsg; + + if (oldcase != oneofdata->oneof_case_num) { + oneof_cleanup(msg, oneofdata); + frame->submsg = cached; + frame->is_msg = false; + } else if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(cached)) == IS_OBJECT) { + submsg = UNBOX(MessageHeader, CACHED_PTR_TO_ZVAL_PTR(cached)); + frame->submsg = submsg; + frame->is_msg = true; + } else { + // In this case, wrapper message hasn't been created and value will be + // stored in cache directly. + frame->submsg = cached; + frame->is_msg = false; + } + + DEREF(message_data(msg), oneofdata->case_ofs, uint32_t) = + oneofdata->oneof_case_num; + + return frame; +} + +static bool wrapper_submsg_end_handler(void *closure, const void *hd) { + wrapperfields_parseframe_t* frame = closure; + free(frame); + return true; +} + // Set up handlers for a repeated field. static void add_handlers_for_repeated_field(upb_handlers *h, const upb_fielddef *f, @@ -923,7 +1086,12 @@ static void add_handlers_for_repeated_field(upb_handlers *h, case UPB_TYPE_MESSAGE: { upb_handlerattr attr = UPB_HANDLERATTR_INIT; attr.handler_data = newsubmsghandlerdata(h, 0, f); - upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr); + if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) { + upb_handlers_setstartsubmsg(h, f, appendwrappersubmsg_handler, &attr); + upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr); + } else { + upb_handlers_setstartsubmsg(h, f, appendsubmsg_handler, &attr); + } break; } } @@ -974,7 +1142,16 @@ static void add_handlers_for_singular_field(upb_handlers *h, upb_handlerattr attr = UPB_HANDLERATTR_INIT; if (is_map) { attr.handler_data = newsubmsghandlerdata(h, offset, f); - upb_handlers_setstartsubmsg(h, f, map_submsg_handler, &attr); + if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) { + upb_handlers_setstartsubmsg(h, f, map_wrapper_submsg_handler, &attr); + upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr); + } else { + upb_handlers_setstartsubmsg(h, f, map_submsg_handler, &attr); + } + } else if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) { + attr.handler_data = newsubmsghandlerdata(h, 0, f); + upb_handlers_setstartsubmsg(h, f, wrapper_submsg_handler, &attr); + upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr); } else { attr.handler_data = newsubmsghandlerdata(h, 0, f); upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr); @@ -1056,9 +1233,106 @@ static void add_handlers_for_oneof_field(upb_handlers *h, break; } case UPB_TYPE_MESSAGE: { - upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr); + if (is_wrapper_msg(upb_fielddef_msgsubdef(f))) { + upb_handlers_setstartsubmsg(h, f, wrapper_oneofsubmsg_handler, &attr); + upb_handlers_setendsubmsg(h, f, wrapper_submsg_end_handler, &attr); + } else { + upb_handlers_setstartsubmsg(h, f, oneofsubmsg_handler, &attr); + } + break; + } + } +} + +#define DEFINE_WRAPPER_HANDLER(utype, type, ctype) \ + static bool type##wrapper_handler( \ + void* closure, const void* hd, ctype val) { \ + wrapperfields_parseframe_t* frame = closure; \ + if (frame->is_msg) { \ + MessageHeader* msg = frame->submsg; \ + const size_t *ofs = hd; \ + DEREF(message_data(msg), *ofs, ctype) = val; \ + } else { \ + TSRMLS_FETCH(); \ + native_slot_get(utype, &val, frame->submsg TSRMLS_CC); \ + } \ + return true; \ + } + +DEFINE_WRAPPER_HANDLER(UPB_TYPE_BOOL, bool, bool) +DEFINE_WRAPPER_HANDLER(UPB_TYPE_INT32, int32, int32_t) +DEFINE_WRAPPER_HANDLER(UPB_TYPE_UINT32, uint32, uint32_t) +DEFINE_WRAPPER_HANDLER(UPB_TYPE_FLOAT, float, float) +DEFINE_WRAPPER_HANDLER(UPB_TYPE_INT64, int64, int64_t) +DEFINE_WRAPPER_HANDLER(UPB_TYPE_UINT64, uint64, uint64_t) +DEFINE_WRAPPER_HANDLER(UPB_TYPE_DOUBLE, double, double) + +#undef DEFINE_WRAPPER_HANDLER + +static bool strwrapper_end_handler(void *closure, const void *hd) { + stringfields_parseframe_t* frame = closure; + const upb_fielddef **field = (const upb_fielddef **) hd; + wrapperfields_parseframe_t* wrapper_frame = frame->closure; + MessageHeader* msg; + CACHED_VALUE* cached; + + if (wrapper_frame->is_msg) { + msg = wrapper_frame->submsg; + cached = find_zval_property(msg, *field); + } else { + cached = wrapper_frame->submsg; + } + + new_php_string(cached, frame->sink.ptr, frame->sink.len); + + stringsink_uninit(&frame->sink); + free(frame); + + return true; +} + +static void add_handlers_for_wrapper(const upb_msgdef* msgdef, + upb_handlers* h) { + const upb_fielddef* f = upb_msgdef_itof(msgdef, 1); + Descriptor* desc; + size_t offset; + + TSRMLS_FETCH(); + desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)msgdef)); + offset = desc->layout->fields[upb_fielddef_index(f)].offset; + + switch (upb_msgdef_wellknowntype(msgdef)) { +#define SET_HANDLER(utype, ltype) \ + case utype: { \ + upb_handlerattr attr = UPB_HANDLERATTR_INIT; \ + attr.handler_data = newhandlerdata(h, offset); \ + upb_handlers_set##ltype(h, f, ltype##wrapper_handler, &attr); \ + break; \ + } + + SET_HANDLER(UPB_WELLKNOWN_BOOLVALUE, bool); + SET_HANDLER(UPB_WELLKNOWN_INT32VALUE, int32); + SET_HANDLER(UPB_WELLKNOWN_UINT32VALUE, uint32); + SET_HANDLER(UPB_WELLKNOWN_FLOATVALUE, float); + SET_HANDLER(UPB_WELLKNOWN_INT64VALUE, int64); + SET_HANDLER(UPB_WELLKNOWN_UINT64VALUE, uint64); + SET_HANDLER(UPB_WELLKNOWN_DOUBLEVALUE, double); + +#undef SET_HANDLER + + case UPB_WELLKNOWN_STRINGVALUE: + case UPB_WELLKNOWN_BYTESVALUE: { + upb_handlerattr attr = UPB_HANDLERATTR_INIT; + attr.handler_data = newhandlerfielddata(h, f); + + upb_handlers_setstartstr(h, f, str_handler, &attr); + upb_handlers_setstring(h, f, stringdata_handler, &attr); + upb_handlers_setendstr(h, f, strwrapper_end_handler, &attr); break; } + default: + // Cannot reach here. + break; } } @@ -1102,6 +1376,14 @@ void add_handlers_for_message(const void* closure, upb_handlers* h) { desc->layout = create_layout(desc->msgdef); } + + // If this is a wrapper message type, set up a special set of handlers and + // bail out of the normal (user-defined) message type handling. + if (is_wrapper_msg(msgdef)) { + add_handlers_for_wrapper(msgdef, h); + return; + } + upb_handlerattr attr = UPB_HANDLERATTR_INIT; attr.handler_data = newunknownfieldshandlerdata(h); upb_handlers_setunknown(h, add_unknown_handler, &attr); @@ -1152,6 +1434,9 @@ static void putmsg(zval* msg, const Descriptor* desc, upb_sink sink, static void putrawmsg(MessageHeader* msg, const Descriptor* desc, upb_sink sink, int depth, bool is_json, bool open_msg TSRMLS_DC); +static void putwrappervalue( + zval* value, const upb_fielddef* f, + upb_sink sink, int depth, bool is_json TSRMLS_DC); static void putjsonany(MessageHeader* msg, const Descriptor* desc, upb_sink sink, int depth TSRMLS_DC); static void putjsonlistvalue( @@ -1299,7 +1584,7 @@ static void putmap(zval* map, const upb_fielddef* f, upb_sink sink, value_field, depth + 1, entry_sink, is_json TSRMLS_CC); upb_sink_endmsg(entry_sink, &status); - upb_sink_endsubmsg(subsink, getsel(f, UPB_HANDLER_ENDSUBMSG)); + upb_sink_endsubmsg(subsink, entry_sink, getsel(f, UPB_HANDLER_ENDSUBMSG)); } upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ)); @@ -1535,7 +1820,12 @@ static void putrawmsg(MessageHeader* msg, const Descriptor* desc, } } else if (upb_fielddef_issubmsg(f)) { zval* submsg = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f)); - putsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC); + if (is_wrapper_msg(upb_fielddef_msgsubdef(f)) && + Z_TYPE_P(submsg) != IS_NULL && Z_TYPE_P(submsg) != IS_OBJECT) { + putwrappervalue(submsg, f, sink, depth, is_json TSRMLS_CC); + } else { + putsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC); + } } else { upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f)); @@ -1634,6 +1924,54 @@ static void putsubmsg(zval* submsg_php, const upb_fielddef* f, upb_sink sink, putrawsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC); } +static void putwrappervalue( + zval* value, const upb_fielddef* f, + upb_sink sink, int depth, bool is_json TSRMLS_DC) { + upb_sink subsink; + const upb_msgdef* msgdef = upb_fielddef_msgsubdef(f); + const upb_fielddef* value_field = upb_msgdef_itof(msgdef, 1); + upb_selector_t sel = + getsel(value_field, upb_handlers_getprimitivehandlertype(value_field)); + + upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink); + +#define T(upbtypeconst, upbtype, ctype, default_value) \ + case upbtypeconst: { \ + ctype value_raw; \ + native_slot_set(upb_fielddef_type(value_field), NULL, \ + &value_raw, value PHP_PROTO_TSRMLS_CC); \ + if ((is_json && is_wrapper_msg(msgdef)) || \ + value_raw != default_value) { \ + upb_sink_put##upbtype(subsink, sel, value_raw); \ + } \ + } break; + + switch (upb_fielddef_type(value_field)) { + T(UPB_TYPE_FLOAT, float, float, 0.0) + T(UPB_TYPE_DOUBLE, double, double, 0.0) + T(UPB_TYPE_BOOL, bool, uint8_t, 0) + T(UPB_TYPE_INT32, int32, int32_t, 0) + T(UPB_TYPE_UINT32, uint32, uint32_t, 0) + T(UPB_TYPE_INT64, int64, int64_t, 0) + T(UPB_TYPE_UINT64, uint64, uint64_t, 0) + case UPB_TYPE_STRING: + case UPB_TYPE_BYTES: { + if ((is_json && is_wrapper_msg(msgdef)) || + Z_STRLEN_P(value) > 0) { + putstr(value, value_field, subsink, is_json && is_wrapper_msg(msgdef)); + } + break; + } + case UPB_TYPE_ENUM: + case UPB_TYPE_MESSAGE: + zend_error(E_ERROR, "Internal error."); + } + +#undef T + + upb_sink_endsubmsg(sink, subsink, getsel(f, UPB_HANDLER_ENDSUBMSG)); +} + static void putrawsubmsg(MessageHeader* submsg, const upb_fielddef* f, upb_sink sink, int depth, bool is_json TSRMLS_DC) { upb_sink subsink; @@ -1643,7 +1981,7 @@ static void putrawsubmsg(MessageHeader* submsg, const upb_fielddef* f, upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink); putrawmsg(submsg, subdesc, subsink, depth + 1, is_json, true TSRMLS_CC); - upb_sink_endsubmsg(sink, getsel(f, UPB_HANDLER_ENDSUBMSG)); + upb_sink_endsubmsg(sink, subsink, getsel(f, UPB_HANDLER_ENDSUBMSG)); } static void putarray(zval* array, const upb_fielddef* f, upb_sink sink, @@ -1769,12 +2107,28 @@ void merge_from_string(const char* data, int data_len, Descriptor* desc, stackenv se; upb_sink sink; upb_pbdecoder* decoder; + void* closure; stackenv_init(&se, "Error occurred during parsing: %s"); - upb_sink_reset(&sink, h, msg); + if (is_wrapper_msg(desc->msgdef)) { + wrapperfields_parseframe_t* frame = + (wrapperfields_parseframe_t*)malloc( + sizeof(wrapperfields_parseframe_t)); + frame->submsg = msg; + frame->is_msg = true; + closure = frame; + } else { + closure = msg; + } + + upb_sink_reset(&sink, h, closure); decoder = upb_pbdecoder_create(se.arena, method, sink, &se.status); upb_bufsrc_putbuf(data, data_len, upb_pbdecoder_input(decoder)); + if (is_wrapper_msg(desc->msgdef)) { + free((wrapperfields_parseframe_t*)closure); + } + stackenv_uninit(&se); } @@ -1852,13 +2206,28 @@ PHP_METHOD(Message, mergeFromJsonString) { stackenv se; upb_sink sink; upb_json_parser* parser; + void* closure; stackenv_init(&se, "Error occurred during parsing: %s"); - upb_sink_reset(&sink, get_fill_handlers(desc), msg); + if (is_wrapper_msg(desc->msgdef)) { + wrapperfields_parseframe_t* frame = + (wrapperfields_parseframe_t*)malloc( + sizeof(wrapperfields_parseframe_t)); + frame->submsg = msg; + frame->is_msg = true; + closure = frame; + } else { + closure = msg; + } + + upb_sink_reset(&sink, get_fill_handlers(desc), closure); parser = upb_json_parser_create(se.arena, method, generated_pool->symtab, sink, &se.status, ignore_json_unknown); upb_bufsrc_putbuf(data, data_len, upb_json_parser_input(parser)); + if (is_wrapper_msg(desc->msgdef)) { + free((wrapperfields_parseframe_t*)closure); + } stackenv_uninit(&se); } } diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c index 03dec75928ef..291c2e5dfef6 100644 --- a/php/ext/google/protobuf/message.c +++ b/php/ext/google/protobuf/message.c @@ -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) @@ -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; @@ -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; diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h index 86bc5b3317b3..6151ff4cd613 100644 --- a/php/ext/google/protobuf/protobuf.h +++ b/php/ext/google/protobuf/protobuf.h @@ -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); @@ -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__ diff --git a/php/ext/google/protobuf/storage.c b/php/ext/google/protobuf/storage.c index e6050d0eb3ef..4a8543f7b948 100644 --- a/php/ext/google/protobuf/storage.c +++ b/php/ext/google/protobuf/storage.c @@ -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; } @@ -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, @@ -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 @@ -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( diff --git a/php/ext/google/protobuf/upb.c b/php/ext/google/protobuf/upb.c index be9d730d2e27..9ec5bdf289d4 100644 --- a/php/ext/google/protobuf/upb.c +++ b/php/ext/google/protobuf/upb.c @@ -116,7 +116,7 @@ int msvc_vsnprintf(char* s, size_t n, const char* format, va_list arg); #ifdef __cplusplus #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) || \ (defined(_MSC_VER) && _MSC_VER >= 1900) -// C++11 is present +/* C++11 is present */ #else #error upb requires C++11 for C++ support #endif @@ -724,6 +724,7 @@ static bool upb_decode_field(upb_decstate *d, upb_decframe *frame) { CHK(upb_append_unknown(d, frame)); return true; } + UPB_UNREACHABLE(); } static bool upb_decode_message(upb_decstate *d, char *msg, const upb_msglayout *l) { @@ -7153,7 +7154,8 @@ size_t run_decoder_vm(upb_pbdecoder *d, const mgroup *group, CHECK_SUSPEND(upb_sink_startsubmsg(outer->sink, arg, &d->top->sink)); ) VMCASE(OP_ENDSUBMSG, - CHECK_SUSPEND(upb_sink_endsubmsg(d->top->sink, arg)); + upb_sink subsink = (d->top + 1)->sink; + CHECK_SUSPEND(upb_sink_endsubmsg(d->top->sink, subsink, arg)); ) VMCASE(OP_STARTSTR, uint32_t len = delim_remaining(d); @@ -8430,7 +8432,7 @@ upb_decoderet upb_vdecode_max8_branch64(upb_decoderet r) { return r; } -#line 1 "upb/json/parser.rl" +// #line 1 "upb/json/parser.rl" /* ** upb::json::Parser (upb_json_parser) ** @@ -10117,28 +10119,18 @@ static void start_timestamp_zone(upb_json_parser *p, const char *ptr) { capture_begin(p, ptr); } -#define EPOCH_YEAR 1970 -#define TM_YEAR_BASE 1900 - -static bool isleap(int year) { - return (year % 4) == 0 && (year % 100 != 0 || (year % 400) == 0); +static int div_round_up2(int n, int d) { + return (n + d - 1) / d; } -const unsigned short int __mon_yday[2][13] = { - /* Normal years. */ - { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, - /* Leap years. */ - { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } -}; - /* epoch_days(1970, 1, 1) == 1970-01-01 == 0. */ static int epoch_days(int year, int month, int day) { static const uint16_t month_yday[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}; int febs_since_0 = month > 2 ? year + 1 : year; - int leap_days_since_0 = div_round_up(febs_since_0, 4) - - div_round_up(febs_since_0, 100) + - div_round_up(febs_since_0, 400); + int leap_days_since_0 = div_round_up2(febs_since_0, 4) - + div_round_up2(febs_since_0, 100) + + div_round_up2(febs_since_0, 400); int days_since_0 = 365 * year + month_yday[month - 1] + (day - 1) + leap_days_since_0; @@ -10460,7 +10452,7 @@ static void end_member(upb_json_parser *p) { p->top--; ok = upb_handlers_getselector(mapfield, UPB_HANDLER_ENDSUBMSG, &sel); UPB_ASSERT(ok); - upb_sink_endsubmsg(p->top->sink, sel); + upb_sink_endsubmsg(p->top->sink, (p->top + 1)->sink, sel); } p->top->f = NULL; @@ -10574,7 +10566,7 @@ static void end_subobject(upb_json_parser *p) { p->top--; if (!is_unknown) { sel = getsel_for_handlertype(p, UPB_HANDLER_ENDSUBMSG); - upb_sink_endsubmsg(p->top->sink, sel); + upb_sink_endsubmsg(p->top->sink, (p->top + 1)->sink, sel); } } } @@ -11013,11 +11005,11 @@ static bool does_fieldmask_end(upb_json_parser *p) { * final state once, when the closing '"' is seen. */ -#line 2794 "upb/json/parser.rl" +// #line 2780 "upb/json/parser.rl" -#line 2597 "upb/json/parser.c" +// #line 2583 "upb/json/parser.c" static const char _json_actions[] = { 0, 1, 0, 1, 1, 1, 3, 1, 4, 1, 6, 1, 7, 1, 8, 1, @@ -11272,7 +11264,7 @@ static const int json_en_value_machine = 78; static const int json_en_main = 1; -#line 2797 "upb/json/parser.rl" +// #line 2783 "upb/json/parser.rl" size_t parse(void *closure, const void *hd, const char *buf, size_t size, const upb_bufhandle *handle) { @@ -11295,7 +11287,7 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, capture_resume(parser, buf); -#line 2875 "upb/json/parser.c" +// #line 2861 "upb/json/parser.c" { int _klen; unsigned int _trans; @@ -11370,147 +11362,147 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, switch ( *_acts++ ) { case 1: -#line 2602 "upb/json/parser.rl" +// #line 2588 "upb/json/parser.rl" { p--; {cs = stack[--top]; goto _again;} } break; case 2: -#line 2604 "upb/json/parser.rl" +// #line 2590 "upb/json/parser.rl" { p--; {stack[top++] = cs; cs = 23;goto _again;} } break; case 3: -#line 2608 "upb/json/parser.rl" +// #line 2594 "upb/json/parser.rl" { start_text(parser, p); } break; case 4: -#line 2609 "upb/json/parser.rl" +// #line 2595 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_text(parser, p)); } break; case 5: -#line 2615 "upb/json/parser.rl" +// #line 2601 "upb/json/parser.rl" { start_hex(parser); } break; case 6: -#line 2616 "upb/json/parser.rl" +// #line 2602 "upb/json/parser.rl" { hexdigit(parser, p); } break; case 7: -#line 2617 "upb/json/parser.rl" +// #line 2603 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_hex(parser)); } break; case 8: -#line 2623 "upb/json/parser.rl" +// #line 2609 "upb/json/parser.rl" { CHECK_RETURN_TOP(escape(parser, p)); } break; case 9: -#line 2629 "upb/json/parser.rl" +// #line 2615 "upb/json/parser.rl" { p--; {cs = stack[--top]; goto _again;} } break; case 10: -#line 2634 "upb/json/parser.rl" +// #line 2620 "upb/json/parser.rl" { start_year(parser, p); } break; case 11: -#line 2635 "upb/json/parser.rl" +// #line 2621 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_year(parser, p)); } break; case 12: -#line 2639 "upb/json/parser.rl" +// #line 2625 "upb/json/parser.rl" { start_month(parser, p); } break; case 13: -#line 2640 "upb/json/parser.rl" +// #line 2626 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_month(parser, p)); } break; case 14: -#line 2644 "upb/json/parser.rl" +// #line 2630 "upb/json/parser.rl" { start_day(parser, p); } break; case 15: -#line 2645 "upb/json/parser.rl" +// #line 2631 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_day(parser, p)); } break; case 16: -#line 2649 "upb/json/parser.rl" +// #line 2635 "upb/json/parser.rl" { start_hour(parser, p); } break; case 17: -#line 2650 "upb/json/parser.rl" +// #line 2636 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_hour(parser, p)); } break; case 18: -#line 2654 "upb/json/parser.rl" +// #line 2640 "upb/json/parser.rl" { start_minute(parser, p); } break; case 19: -#line 2655 "upb/json/parser.rl" +// #line 2641 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_minute(parser, p)); } break; case 20: -#line 2659 "upb/json/parser.rl" +// #line 2645 "upb/json/parser.rl" { start_second(parser, p); } break; case 21: -#line 2660 "upb/json/parser.rl" +// #line 2646 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_second(parser, p)); } break; case 22: -#line 2665 "upb/json/parser.rl" +// #line 2651 "upb/json/parser.rl" { start_duration_base(parser, p); } break; case 23: -#line 2666 "upb/json/parser.rl" +// #line 2652 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_duration_base(parser, p)); } break; case 24: -#line 2668 "upb/json/parser.rl" +// #line 2654 "upb/json/parser.rl" { p--; {cs = stack[--top]; goto _again;} } break; case 25: -#line 2673 "upb/json/parser.rl" +// #line 2659 "upb/json/parser.rl" { start_timestamp_base(parser); } break; case 26: -#line 2675 "upb/json/parser.rl" +// #line 2661 "upb/json/parser.rl" { start_timestamp_fraction(parser, p); } break; case 27: -#line 2676 "upb/json/parser.rl" +// #line 2662 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_timestamp_fraction(parser, p)); } break; case 28: -#line 2678 "upb/json/parser.rl" +// #line 2664 "upb/json/parser.rl" { start_timestamp_zone(parser, p); } break; case 29: -#line 2679 "upb/json/parser.rl" +// #line 2665 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_timestamp_zone(parser, p)); } break; case 30: -#line 2681 "upb/json/parser.rl" +// #line 2667 "upb/json/parser.rl" { p--; {cs = stack[--top]; goto _again;} } break; case 31: -#line 2686 "upb/json/parser.rl" +// #line 2672 "upb/json/parser.rl" { start_fieldmask_path_text(parser, p); } break; case 32: -#line 2687 "upb/json/parser.rl" +// #line 2673 "upb/json/parser.rl" { end_fieldmask_path_text(parser, p); } break; case 33: -#line 2692 "upb/json/parser.rl" +// #line 2678 "upb/json/parser.rl" { start_fieldmask_path(parser); } break; case 34: -#line 2693 "upb/json/parser.rl" +// #line 2679 "upb/json/parser.rl" { end_fieldmask_path(parser); } break; case 35: -#line 2699 "upb/json/parser.rl" +// #line 2685 "upb/json/parser.rl" { p--; {cs = stack[--top]; goto _again;} } break; case 36: -#line 2704 "upb/json/parser.rl" +// #line 2690 "upb/json/parser.rl" { if (is_wellknown_msg(parser, UPB_WELLKNOWN_TIMESTAMP)) { {stack[top++] = cs; cs = 47;goto _again;} @@ -11524,11 +11516,11 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, } break; case 37: -#line 2717 "upb/json/parser.rl" +// #line 2703 "upb/json/parser.rl" { p--; {stack[top++] = cs; cs = 78;goto _again;} } break; case 38: -#line 2722 "upb/json/parser.rl" +// #line 2708 "upb/json/parser.rl" { if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) { start_any_member(parser, p); @@ -11538,11 +11530,11 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, } break; case 39: -#line 2729 "upb/json/parser.rl" +// #line 2715 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_membername(parser)); } break; case 40: -#line 2732 "upb/json/parser.rl" +// #line 2718 "upb/json/parser.rl" { if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) { end_any_member(parser, p); @@ -11552,7 +11544,7 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, } break; case 41: -#line 2743 "upb/json/parser.rl" +// #line 2729 "upb/json/parser.rl" { if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) { start_any_object(parser, p); @@ -11562,7 +11554,7 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, } break; case 42: -#line 2752 "upb/json/parser.rl" +// #line 2738 "upb/json/parser.rl" { if (is_wellknown_msg(parser, UPB_WELLKNOWN_ANY)) { CHECK_RETURN_TOP(end_any_object(parser, p)); @@ -11572,54 +11564,54 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, } break; case 43: -#line 2764 "upb/json/parser.rl" +// #line 2750 "upb/json/parser.rl" { CHECK_RETURN_TOP(start_array(parser)); } break; case 44: -#line 2768 "upb/json/parser.rl" +// #line 2754 "upb/json/parser.rl" { end_array(parser); } break; case 45: -#line 2773 "upb/json/parser.rl" +// #line 2759 "upb/json/parser.rl" { CHECK_RETURN_TOP(start_number(parser, p)); } break; case 46: -#line 2774 "upb/json/parser.rl" +// #line 2760 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_number(parser, p)); } break; case 47: -#line 2776 "upb/json/parser.rl" +// #line 2762 "upb/json/parser.rl" { CHECK_RETURN_TOP(start_stringval(parser)); } break; case 48: -#line 2777 "upb/json/parser.rl" +// #line 2763 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_stringval(parser)); } break; case 49: -#line 2779 "upb/json/parser.rl" +// #line 2765 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_bool(parser, true)); } break; case 50: -#line 2781 "upb/json/parser.rl" +// #line 2767 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_bool(parser, false)); } break; case 51: -#line 2783 "upb/json/parser.rl" +// #line 2769 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_null(parser)); } break; case 52: -#line 2785 "upb/json/parser.rl" +// #line 2771 "upb/json/parser.rl" { CHECK_RETURN_TOP(start_subobject_full(parser)); } break; case 53: -#line 2786 "upb/json/parser.rl" +// #line 2772 "upb/json/parser.rl" { end_subobject_full(parser); } break; case 54: -#line 2791 "upb/json/parser.rl" +// #line 2777 "upb/json/parser.rl" { p--; {cs = stack[--top]; goto _again;} } break; -#line 3199 "upb/json/parser.c" +// #line 3185 "upb/json/parser.c" } } @@ -11636,32 +11628,32 @@ size_t parse(void *closure, const void *hd, const char *buf, size_t size, while ( __nacts-- > 0 ) { switch ( *__acts++ ) { case 0: -#line 2600 "upb/json/parser.rl" +// #line 2586 "upb/json/parser.rl" { p--; {cs = stack[--top]; if ( p == pe ) goto _test_eof; goto _again;} } break; case 46: -#line 2774 "upb/json/parser.rl" +// #line 2760 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_number(parser, p)); } break; case 49: -#line 2779 "upb/json/parser.rl" +// #line 2765 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_bool(parser, true)); } break; case 50: -#line 2781 "upb/json/parser.rl" +// #line 2767 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_bool(parser, false)); } break; case 51: -#line 2783 "upb/json/parser.rl" +// #line 2769 "upb/json/parser.rl" { CHECK_RETURN_TOP(end_null(parser)); } break; case 53: -#line 2786 "upb/json/parser.rl" +// #line 2772 "upb/json/parser.rl" { end_subobject_full(parser); } break; -#line 3241 "upb/json/parser.c" +// #line 3227 "upb/json/parser.c" } } } @@ -11669,7 +11661,7 @@ goto _again;} } _out: {} } -#line 2819 "upb/json/parser.rl" +// #line 2805 "upb/json/parser.rl" if (p != pe) { upb_status_seterrf(parser->status, "Parse error at '%.*s'\n", pe - p, p); @@ -11712,13 +11704,13 @@ static void json_parser_reset(upb_json_parser *p) { /* Emit Ragel initialization of the parser. */ -#line 3292 "upb/json/parser.c" +// #line 3278 "upb/json/parser.c" { cs = json_start; top = 0; } -#line 2861 "upb/json/parser.rl" +// #line 2847 "upb/json/parser.rl" p->current_state = cs; p->parser_top = top; accumulate_clear(p); diff --git a/php/ext/google/protobuf/upb.h b/php/ext/google/protobuf/upb.h index 0a55baf01061..058772741674 100644 --- a/php/ext/google/protobuf/upb.h +++ b/php/ext/google/protobuf/upb.h @@ -122,7 +122,7 @@ int msvc_vsnprintf(char* s, size_t n, const char* format, va_list arg); #ifdef __cplusplus #if __cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__) || \ (defined(_MSC_VER) && _MSC_VER >= 1900) -// C++11 is present +/* C++11 is present */ #else #error upb requires C++11 for C++ support #endif @@ -5700,15 +5700,16 @@ UPB_INLINE bool upb_sink_startsubmsg(upb_sink s, upb_selector_t sel, return sub->closure ? true : false; } -UPB_INLINE bool upb_sink_endsubmsg(upb_sink s, upb_selector_t sel) { +UPB_INLINE bool upb_sink_endsubmsg(upb_sink s, upb_sink sub, + upb_selector_t sel) { typedef upb_endfield_handlerfunc func; func *endsubmsg; const void *hd; if (!s.handlers) return true; endsubmsg = (func*)upb_handlers_gethandler(s.handlers, sel, &hd); - if (!endsubmsg) return s.closure; - return endsubmsg(s.closure, hd); + if (!endsubmsg) return true; + return endsubmsg(sub.closure, hd); } #ifdef __cplusplus @@ -5874,8 +5875,8 @@ class upb::Sink { return ret; } - bool EndSubMessage(HandlersPtr::Selector s) { - return upb_sink_endsubmsg(sink_, s); + bool EndSubMessage(HandlersPtr::Selector s, Sink sub) { + return upb_sink_endsubmsg(sink_, sub.sink_, s); } /* For repeated fields of any type, the sequence of values must be wrapped in diff --git a/php/src/Google/Protobuf/Internal/Message.php b/php/src/Google/Protobuf/Internal/Message.php index 1ff2dc9a67c4..1ffb24501c83 100644 --- a/php/src/Google/Protobuf/Internal/Message.php +++ b/php/src/Google/Protobuf/Internal/Message.php @@ -175,6 +175,42 @@ private function initWithDescriptor(Descriptor $desc) } } + protected function readWrapperValue($member) + { + $field = $this->desc->getFieldByName($member); + $oneof_index = $field->getOneofIndex(); + if ($oneof_index === -1) { + $wrapper = $this->$member; + } else { + $wrapper = $this->readOneof($field->getNumber()); + } + + if (is_null($wrapper)) { + return NULL; + } else { + return $wrapper->getValue(); + } + } + + protected function writeWrapperValue($member, $value) + { + $field = $this->desc->getFieldByName($member); + $wrapped_value = $value; + if (!is_null($value)) { + $desc = $field->getMessageType(); + $klass = $desc->getClass(); + $wrapped_value = new $klass; + $wrapped_value->setValue($value); + } + + $oneof_index = $field->getOneofIndex(); + if ($oneof_index === -1) { + $this->$member = $wrapped_value; + } else { + $this->writeOneof($field->getNumber(), $wrapped_value); + } + } + protected function readOneof($number) { $field = $this->desc->getFieldByNumber($number); diff --git a/php/tests/encode_decode_test.php b/php/tests/encode_decode_test.php index 3dad5f39efd6..53cd526e4f83 100644 --- a/php/tests/encode_decode_test.php +++ b/php/tests/encode_decode_test.php @@ -5,7 +5,13 @@ use Google\Protobuf\RepeatedField; use Google\Protobuf\GPBType; +use Foo\TestInt32Value; +use Foo\TestInt64Value; +use Foo\TestUInt32Value; +use Foo\TestUInt64Value; +use Foo\TestBoolValue; use Foo\TestStringValue; +use Foo\TestBytesValue; use Foo\TestAny; use Foo\TestEnum; use Foo\TestMessage; @@ -98,6 +104,13 @@ public function testEncodeTopLevelInt32Value() $this->assertSame("1", $m->serializeToJsonString()); } + public function testDecodeRepeatedInt32Value() + { + $m = new TestInt32Value(); + $m->mergeFromJsonString("{\"repeated_field\":[12345]}"); + $this->assertSame(12345, $m->getRepeatedField()[0]->getValue()); + } + public function testDecodeTopLevelUInt32Value() { $m = new UInt32Value(); @@ -168,12 +181,18 @@ public function testEncodeTopLevelStringValue() $this->assertSame("\"a\"", $m->serializeToJsonString()); } - public function testEncodeStringValue() + public function testDecodeRepeatedStringValue() + { + $m = new TestStringValue(); + $m->mergeFromJsonString("{\"repeated_field\":[\"a\"]}"); + $this->assertSame("a", $m->getRepeatedField()[0]->getValue()); + } + + public function testDecodeMapStringValue() { - $m = new TestStringValue(['field' => new StringValue(['value' => ''])]); - var_dump($m->getField()); - var_dump($m->serializeToJsonString()); - $this->assertSame("{\"field\":\"\"}", $m->serializeToJsonString()); + $m = new TestStringValue(); + $m->mergeFromJsonString("{\"map_field\":{\"1\": \"a\"}}"); + $this->assertSame("a", $m->getMapField()[1]->getValue()); } public function testDecodeTopLevelBytesValue() @@ -1230,4 +1249,195 @@ public function testStructMapNoValue() $this->assertTrue(true); } + /** + * @dataProvider wrappersDataProvider + */ + public function testWrapperJsonDecodeAndGet( + $class, + $nonDefaultValue, + $nonDefaultValueData, + $defaultValue, + $defaultValueData + ) + { + // Singular with non-default + $m = new $class(); + $m->mergeFromJsonString("{\"field\":" . $nonDefaultValueData . "}"); + $wrapper = $m->getField(); + $this->assertEquals($nonDefaultValue, $wrapper->getValue()); + + // Singular with default + $m = new $class(); + $m->mergeFromJsonString("{\"field\":" . $defaultValueData . "}"); + $wrapper = $m->getField(); + $this->assertEquals($defaultValue, $wrapper->getValue()); + + // Repeated with empty + $m = new $class(); + $m->mergeFromJsonString("{\"repeated_field\":[]}"); + $repeatedWrapper = $m->getRepeatedField(); + $this->assertSame(0, count($repeatedWrapper)); + + // Repeated with non-default + $m = new $class(); + $m->mergeFromJsonString("{\"repeated_field\":[" . $defaultValueData . "]}"); + $repeatedWrapper = $m->getRepeatedField(); + $this->assertSame(1, count($repeatedWrapper)); + $this->assertEquals($defaultValue, $repeatedWrapper[0]->getValue()); + + // Repeated with default + $m = new $class(); + $m->mergeFromJsonString("{\"repeated_field\":[" . $defaultValueData . "]}"); + $repeatedWrapper = $m->getRepeatedField(); + $this->assertSame(1, count($repeatedWrapper)); + $this->assertEquals($defaultValue, $repeatedWrapper[0]->getValue()); + + // Oneof with non-default + $m = new $class(); + $m->mergeFromJsonString("{\"oneof_field\":" . $nonDefaultValueData . "}"); + $wrapper = $m->getOneofField(); + $this->assertEquals($nonDefaultValue, $wrapper->getValue()); + $this->assertEquals("oneof_field", $m->getOneofFields()); + $this->assertEquals(0, $m->getInt32Field()); + + // Oneof with default + $m = new $class(); + $m->mergeFromJsonString("{\"oneof_field\":" . $defaultValueData . "}"); + $wrapper = $m->getOneofField(); + $this->assertEquals($defaultValue, $wrapper->getValue()); + $this->assertEquals("oneof_field", $m->getOneofFields()); + $this->assertEquals(0, $m->getInt32Field()); + } + + /** + * @dataProvider wrappersDataProvider + */ + public function testWrapperJsonDecodeAndGetUnwrapped( + $class, + $nonDefaultValue, + $nonDefaultValueData, + $defaultValue, + $defaultValueData + ) + { + // Singular with non-default + $m = new $class(); + $m->mergeFromJsonString("{\"field\":" . $nonDefaultValueData . "}"); + $this->assertEquals($nonDefaultValue, $m->getFieldUnwrapped()); + + // Singular with default + $m = new $class(); + $m->mergeFromJsonString("{\"field\":" . $defaultValueData . "}"); + $this->assertEquals($defaultValue, $m->getFieldUnwrapped()); + + // Oneof with non-default + $m = new $class(); + $m->mergeFromJsonString("{\"oneof_field\":" . $nonDefaultValueData . "}"); + $this->assertEquals($nonDefaultValue, $m->getOneofFieldUnwrapped()); + $this->assertEquals("oneof_field", $m->getOneofFields()); + $this->assertEquals(0, $m->getInt32Field()); + + // Oneof with default + $m = new $class(); + $m->mergeFromJsonString("{\"oneof_field\":" . $defaultValueData . "}"); + $this->assertEquals($defaultValue, $m->getOneofFieldUnwrapped()); + $this->assertEquals("oneof_field", $m->getOneofFields()); + $this->assertEquals(0, $m->getInt32Field()); + } + + /** + * @dataProvider wrappersDataProvider + */ + public function testWrapperJsonDecodeEncode( + $class, + $nonDefaultValue, + $nonDefaultValueData, + $defaultValue, + $defaultValueData + ) + { + // Singular with non-default + $from = new $class(); + $to = new $class(); + $from->mergeFromJsonString("{\"field\":" . $nonDefaultValueData . "}"); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($nonDefaultValue, $to->getFieldUnwrapped()); + + // Singular with default + $from = new $class(); + $to = new $class(); + $from->mergeFromJsonString("{\"field\":" . $defaultValueData . "}"); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($defaultValue, $to->getFieldUnwrapped()); + + // Oneof with non-default + $from = new $class(); + $to = new $class(); + $from->mergeFromJsonString("{\"oneof_field\":" . $nonDefaultValueData . "}"); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($nonDefaultValue, $to->getOneofFieldUnwrapped()); + + // Oneof with default + $from = new $class(); + $to = new $class(); + $from->mergeFromJsonString("{\"oneof_field\":" . $defaultValueData . "}"); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($defaultValue, $to->getOneofFieldUnwrapped()); + } + + /** + * @dataProvider wrappersDataProvider + */ + public function testWrapperSetUnwrappedJsonEncode( + $class, + $nonDefaultValue, + $nonDefaultValueData, + $defaultValue, + $defaultValueData + ) + { + // Singular with non-default + $from = new $class(); + $to = new $class(); + $from->setFieldUnwrapped($nonDefaultValue); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($nonDefaultValue, $to->getFieldUnwrapped()); + + // Singular with default + $from = new $class(); + $to = new $class(); + $from->setFieldUnwrapped($defaultValue); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($defaultValue, $to->getFieldUnwrapped()); + + // Oneof with non-default + $from = new $class(); + $to = new $class(); + $from->setOneofFieldUnwrapped($nonDefaultValue); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($nonDefaultValue, $to->getOneofFieldUnwrapped()); + + // Oneof with default + $from = new $class(); + $to = new $class(); + $from->setOneofFieldUnwrapped($defaultValue); + $data = $from->serializeToJsonString(); + $to->mergeFromJsonString($data); + $this->assertEquals($defaultValue, $to->getOneofFieldUnwrapped()); + } + + public function wrappersDataProvider() + { + return [ + [TestInt32Value::class, 1, "1", 0, "0"], + [TestStringValue::class, "a", "\"a\"", "", "\"\""], + ]; + } } diff --git a/php/tests/memory_leak_test.php b/php/tests/memory_leak_test.php index 2873f34432cf..a9c292d0b612 100644 --- a/php/tests/memory_leak_test.php +++ b/php/tests/memory_leak_test.php @@ -20,9 +20,13 @@ require_once('generated/Foo/PBARRAY.php'); require_once('generated/Foo/PBEmpty.php'); require_once('generated/Foo/TestAny.php'); +require_once('generated/Foo/TestBoolValue.php'); +require_once('generated/Foo/TestBytesValue.php'); require_once('generated/Foo/TestEnum.php'); require_once('generated/Foo/TestIncludeNamespaceMessage.php'); require_once('generated/Foo/TestIncludePrefixMessage.php'); +require_once('generated/Foo/TestInt32Value.php'); +require_once('generated/Foo/TestInt64Value.php'); require_once('generated/Foo/TestMessage.php'); require_once('generated/Foo/TestMessage/PBEmpty.php'); require_once('generated/Foo/TestMessage/NestedEnum.php'); @@ -32,6 +36,8 @@ require_once('generated/Foo/TestRandomFieldOrder.php'); require_once('generated/Foo/TestReverseFieldOrder.php'); require_once('generated/Foo/TestStringValue.php'); +require_once('generated/Foo/TestUInt32Value.php'); +require_once('generated/Foo/TestUInt64Value.php'); require_once('generated/Foo/TestUnpackedMessage.php'); require_once('generated/Foo/testLowerCaseMessage.php'); require_once('generated/Foo/testLowerCaseEnum.php'); diff --git a/php/tests/proto/test.proto b/php/tests/proto/test.proto index 6821858412e9..95057090f976 100644 --- a/php/tests/proto/test.proto +++ b/php/tests/proto/test.proto @@ -215,6 +215,66 @@ message TestAny { google.protobuf.Any any = 1; } +message TestInt32Value { + google.protobuf.Int32Value field = 1; + repeated google.protobuf.Int32Value repeated_field = 2; + oneof oneof_fields { + google.protobuf.Int32Value oneof_field = 3; + int32 int32_field = 4; + } +} + +message TestInt64Value { + google.protobuf.Int64Value field = 1; + repeated google.protobuf.Int64Value repeated_field = 2; + oneof oneof_fields { + google.protobuf.Int64Value oneof_field = 3; + int32 int32_field = 4; + } +} + +message TestUInt32Value { + google.protobuf.UInt32Value field = 1; + repeated google.protobuf.UInt32Value repeated_field = 2; + oneof oneof_fields { + google.protobuf.UInt32Value oneof_field = 3; + int32 int32_field = 4; + } +} + +message TestUInt64Value { + google.protobuf.UInt64Value field = 1; + repeated google.protobuf.UInt64Value repeated_field = 2; + oneof oneof_fields { + google.protobuf.UInt64Value oneof_field = 3; + int32 int32_field = 4; + } +} + +message TestBoolValue { + google.protobuf.BoolValue field = 1; + repeated google.protobuf.BoolValue repeated_field = 2; + oneof oneof_fields { + google.protobuf.BoolValue oneof_field = 3; + int32 int32_field = 4; + } +} + message TestStringValue { google.protobuf.StringValue field = 1; + repeated google.protobuf.StringValue repeated_field = 2; + oneof oneof_fields { + google.protobuf.StringValue oneof_field = 3; + int32 int32_field = 4; + } + map map_field = 5; +} + +message TestBytesValue { + google.protobuf.BytesValue field = 1; + repeated google.protobuf.BytesValue repeated_field = 2; + oneof oneof_fields { + google.protobuf.BytesValue oneof_field = 3; + int32 int32_field = 4; + } } diff --git a/php/tests/wrapper_type_setters_test.php b/php/tests/wrapper_type_setters_test.php index ad9f7181dd05..e4bdfcfad333 100644 --- a/php/tests/wrapper_type_setters_test.php +++ b/php/tests/wrapper_type_setters_test.php @@ -132,7 +132,8 @@ public function gettersAndSettersDataProvider() [2.2, new DoubleValue(["value" => 2.2])], [null, null], [0, new DoubleValue()], - ]],[TestWrapperSetters::class, StringValue::class, "setStringValueOneof", "setStringValueOneofUnwrapped", "getStringValueOneof", "getStringValueOneofUnwrapped", [ + ]], + [TestWrapperSetters::class, StringValue::class, "setStringValueOneof", "setStringValueOneofUnwrapped", "getStringValueOneof", "getStringValueOneofUnwrapped", [ ["asdf", new StringValue(["value" => "asdf"])], ["", new StringValue(["value" => ""])], [null, null], diff --git a/src/google/protobuf/compiler/php/php_generator.cc b/src/google/protobuf/compiler/php/php_generator.cc index 29a5ac26423d..55b3de2316f5 100644 --- a/src/google/protobuf/compiler/php/php_generator.cc +++ b/src/google/protobuf/compiler/php/php_generator.cc @@ -628,7 +628,7 @@ void GenerateField(const FieldDescriptor* field, io::Printer* printer, } else { GenerateFieldDocComment(printer, field, is_descriptor, kFieldProperty); printer->Print( - "private $^name^ = ^default^;\n", + "protected $^name^ = ^default^;\n", "name", field->name(), "default", DefaultForField(field)); } @@ -682,10 +682,10 @@ void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor, printer->Print( "public function get^camel_name^Unwrapped()\n" "{\n" - " $wrapper = $this->get^camel_name^();\n" - " return is_null($wrapper) ? null : $wrapper->getValue();\n" + " return $this->readWrapperValue(\"^field_name^\");\n" "}\n\n", - "camel_name", UnderscoresToCamelCase(field->name(), true)); + "camel_name", UnderscoresToCamelCase(field->name(), true), + "field_name", field->name()); } // Generate setter. @@ -795,11 +795,11 @@ void GenerateFieldAccessor(const FieldDescriptor* field, bool is_descriptor, printer->Print( "public function set^camel_name^Unwrapped($var)\n" "{\n" - " $wrappedVar = is_null($var) ? null : new \\^wrapper_type^(['value' => $var]);\n" - " return $this->set^camel_name^($wrappedVar);\n" + " $this->writeWrapperValue(\"^field_name^\", $var);\n" + " return $this;" "}\n\n", "camel_name", UnderscoresToCamelCase(field->name(), true), - "wrapper_type", LegacyFullClassName(field->message_type(), is_descriptor)); + "field_name", field->name()); } // Generate has method for proto2 only.