Merge pull request #6824 from jskeet/fix-extensions

Fix reflection access when using old generated code
diff --git a/.gitignore b/.gitignore
index dbbec53..4e909ae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -139,6 +139,8 @@
 php/tests/generated/
 php/tests/old_protoc
 php/tests/protobuf/
+php/tests/core
+php/tests/vgcore*
 php/ext/google/protobuf/.libs/
 php/ext/google/protobuf/Makefile.fragments
 php/ext/google/protobuf/Makefile.global
diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt
index 8e5e680..0ae5435 100644
--- a/cmake/CMakeLists.txt
+++ b/cmake/CMakeLists.txt
@@ -40,8 +40,6 @@
 endif (BUILD_SHARED_LIBS)
 option(protobuf_BUILD_SHARED_LIBS "Build Shared Libraries" ${protobuf_BUILD_SHARED_LIBS_DEFAULT})
 include(CMakeDependentOption)
-cmake_dependent_option(protobuf_MSVC_STATIC_RUNTIME "Link static runtime libraries" ON
-  "NOT protobuf_BUILD_SHARED_LIBS" OFF)
 set(protobuf_WITH_ZLIB_DEFAULT ON)
 option(protobuf_WITH_ZLIB "Build with zlib support" ${protobuf_WITH_ZLIB_DEFAULT})
 set(protobuf_DEBUG_POSTFIX "d"
@@ -155,22 +153,22 @@
   set(protobuf_SHARED_OR_STATIC "SHARED")
 else (protobuf_BUILD_SHARED_LIBS)
   set(protobuf_SHARED_OR_STATIC "STATIC")
-  # In case we are building static libraries, link also the runtime library statically
-  # so that MSVCR*.DLL is not required at runtime.
-  # https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx
-  # This is achieved by replacing msvc option /MD with /MT and /MDd with /MTd
-  # http://www.cmake.org/Wiki/CMake_FAQ#How_can_I_build_my_MSVC_application_with_a_static_runtime.3F
-  if (MSVC AND protobuf_MSVC_STATIC_RUNTIME)
-    foreach(flag_var
-        CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
-        CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
-      if(${flag_var} MATCHES "/MD")
-        string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
-      endif(${flag_var} MATCHES "/MD")
-    endforeach(flag_var)
-  endif (MSVC AND protobuf_MSVC_STATIC_RUNTIME)
 endif (protobuf_BUILD_SHARED_LIBS)
 
+# In case we are linking the runtime library statically so that MSVCR*.DLL is not required at runtime.
+# https://msdn.microsoft.com/en-us/library/2kzt1wy3.aspx
+# This is achieved by replacing msvc option /MD with /MT and /MDd with /MTd
+# http://www.cmake.org/Wiki/CMake_FAQ#How_can_I_build_my_MSVC_application_with_a_static_runtime.3F
+if (MSVC AND protobuf_MSVC_STATIC_RUNTIME)
+  foreach(flag_var
+      CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE
+      CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO)
+    if(${flag_var} MATCHES "/MD")
+      string(REGEX REPLACE "/MD" "/MT" ${flag_var} "${${flag_var}}")
+    endif(${flag_var} MATCHES "/MD")
+  endforeach(flag_var)
+endif (MSVC AND protobuf_MSVC_STATIC_RUNTIME)
+
 if (MSVC)
   # Build with multiple processes
   add_definitions(/MP)
diff --git a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
index 02fe778..d65a6f2 100644
--- a/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
+++ b/csharp/src/Google.Protobuf.Test/Reflection/CustomOptionsTest.cs
@@ -41,6 +41,7 @@
 using static UnitTest.Issues.TestProtos.ComplexOptionType2.Types;
 using static UnitTest.Issues.TestProtos.UnittestCustomOptionsProto3Extensions;
 using static UnitTest.Issues.TestProtos.DummyMessageContainingEnum.Types;
+using Google.Protobuf.TestProtos;
 
 #pragma warning disable CS0618
 
@@ -177,6 +178,21 @@
             AssertOption(new Aggregate { S = "FieldAnnotation" }, fieldOptions.TryGetMessage, Fieldopt, AggregateMessage.Descriptor.Fields["fieldname"].GetOption);
         }
 
+        [Test]
+        public void NoOptions()
+        {
+            var fileDescriptor = UnittestProto3Reflection.Descriptor;
+            var messageDescriptor = TestAllTypes.Descriptor;
+            Assert.NotNull(fileDescriptor.CustomOptions);
+            Assert.NotNull(messageDescriptor.CustomOptions);
+            Assert.NotNull(messageDescriptor.Fields[1].CustomOptions);
+            Assert.NotNull(fileDescriptor.Services[0].CustomOptions);
+            Assert.NotNull(fileDescriptor.Services[0].Methods[0].CustomOptions);
+            Assert.NotNull(fileDescriptor.EnumTypes[0].CustomOptions);
+            Assert.NotNull(fileDescriptor.EnumTypes[0].Values[0].CustomOptions);
+            Assert.NotNull(TestAllTypes.Descriptor.Oneofs[0].CustomOptions);
+        }
+
         private void AssertOption<T, D>(T expected, OptionFetcher<T> fetcher, Extension<D, T> extension, Func<Extension<D, T>, T> descriptorOptionFetcher) where D : IExtendableMessage<D>
         {
             T customOptionsValue;
diff --git a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
index 33be961..264a88a 100644
--- a/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/EnumDescriptor.cs
@@ -129,7 +129,7 @@
         /// The (possibly empty) set of custom options for this enum.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value enum option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
index a476ef1..3933820 100644
--- a/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/EnumValueDescriptor.cs
@@ -74,7 +74,7 @@
         /// The (possibly empty) set of custom options for this enum value.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value enum value option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
index a81bc9c..ddd671a 100644
--- a/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/FieldDescriptor.cs
@@ -299,7 +299,7 @@
         /// The (possibly empty) set of custom options for this field.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value field option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
index 79ba13b..388a40b 100644
--- a/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/FileDescriptor.cs
@@ -547,7 +547,7 @@
         /// The (possibly empty) set of custom options for this file.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value file option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
index 510c079..eda1965 100644
--- a/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/MessageDescriptor.cs
@@ -264,7 +264,7 @@
         /// The (possibly empty) set of custom options for this message.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value message option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
index 13d2396..92250ba 100644
--- a/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/MethodDescriptor.cs
@@ -74,7 +74,7 @@
         /// The (possibly empty) set of custom options for this method.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value method option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs
index 655c07e..1e30b92 100644
--- a/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/OneofDescriptor.cs
@@ -106,7 +106,7 @@
         /// The (possibly empty) set of custom options for this oneof.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value oneof option for this descriptor
diff --git a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
index 120c6c6..ba310ad 100644
--- a/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
+++ b/csharp/src/Google.Protobuf/Reflection/ServiceDescriptor.cs
@@ -95,7 +95,7 @@
         /// The (possibly empty) set of custom options for this service.
         /// </summary>
         [Obsolete("CustomOptions are obsolete. Use GetOption")]
-        public CustomOptions CustomOptions => new CustomOptions(Proto.Options._extensions?.ValuesByNumber);
+        public CustomOptions CustomOptions => new CustomOptions(Proto.Options?._extensions?.ValuesByNumber);
 
         /// <summary>
         /// Gets a single value service option for this descriptor
diff --git a/docs/options.md b/docs/options.md
index 7149b21..22a8bc9 100644
--- a/docs/options.md
+++ b/docs/options.md
@@ -212,3 +212,7 @@
 1. Dart port of protocol buffers
    * Website https://github.com/dart-lang/protobuf
    * Extensions: 1073
+
+1. Ocaml-protoc-plugin
+   * Website: https://github.com/issuu/ocaml-protoc-plugin
+   * Extensions: 1074
diff --git a/java/README.md b/java/README.md
index ccd2c97..a109494 100644
--- a/java/README.md
+++ b/java/README.md
@@ -23,7 +23,7 @@
 <dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java</artifactId>
-  <version>3.9.2</version>
+  <version>3.10.0</version>
 </dependency>
 ```
 
@@ -37,7 +37,7 @@
 <dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java-util</artifactId>
-  <version>3.9.2</version>
+  <version>3.10.0</version>
 </dependency>
 ```
 
@@ -45,7 +45,7 @@
 
 If you are using Gradle, add the following to your `build.gradle` file's dependencies:
 ```
-    compile 'com.google.protobuf:protobuf-java:3.9.2'
+    compile 'com.google.protobuf:protobuf-java:3.10.0'
 ```
 Again, be sure to check that the version number maches (or is newer than) the version number of protoc that you are using.
 
diff --git a/java/util/pom.xml b/java/util/pom.xml
index f0777b9..54a3581 100644
--- a/java/util/pom.xml
+++ b/java/util/pom.xml
@@ -25,7 +25,7 @@
     <dependency>
       <groupId>com.google.errorprone</groupId>
       <artifactId>error_prone_annotations</artifactId>
-      <version>2.3.2</version>
+      <version>2.3.3</version>
     </dependency>
     <dependency>
       <groupId>com.google.guava</groupId>
@@ -35,7 +35,7 @@
     <dependency>
       <groupId>com.google.code.gson</groupId>
       <artifactId>gson</artifactId>
-      <version>2.8.5</version>
+      <version>2.8.6</version>
     </dependency>
     <dependency>
       <groupId>junit</groupId>
diff --git a/php/ext/google/protobuf/array.c b/php/ext/google/protobuf/array.c
index e69bef4..b52bdf6 100644
--- a/php/ext/google/protobuf/array.c
+++ b/php/ext/google/protobuf/array.c
@@ -259,6 +259,19 @@
   }
 }
 
+void repeated_field_ensure_created(
+    const upb_fielddef *field,
+    CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC) {
+  if (ZVAL_IS_NULL(CACHED_PTR_TO_ZVAL_PTR(repeated_field))) {
+    zval_ptr_dtor(repeated_field);
+#if PHP_MAJOR_VERSION < 7
+    MAKE_STD_ZVAL(CACHED_PTR_TO_ZVAL_PTR(repeated_field));
+#endif
+    repeated_field_create_with_field(repeated_field_type, field,
+                                     repeated_field PHP_PROTO_TSRMLS_CC);
+  }
+}
+
 void repeated_field_create_with_field(
     zend_class_entry *ce, const upb_fielddef *field,
     CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC) {
diff --git a/php/ext/google/protobuf/encode_decode.c b/php/ext/google/protobuf/encode_decode.c
index 166fb91..39304a5 100644
--- a/php/ext/google/protobuf/encode_decode.c
+++ b/php/ext/google/protobuf/encode_decode.c
@@ -139,6 +139,15 @@
   return hd_ofs;
 }
 
+static const void* newhandlerfielddata(
+    upb_handlers* h, const upb_fielddef* field) {
+  const void** hd_field = malloc(sizeof(void*));
+  PHP_PROTO_ASSERT(hd_field != NULL);
+  *hd_field = field;
+  upb_handlers_addcleanup(h, hd_field, free);
+  return hd_field;
+}
+
 typedef struct {
   void* closure;
   stringsink sink;
@@ -163,16 +172,18 @@
 }
 
 typedef struct {
+  const upb_fielddef *fd;
   size_t ofs;
   const upb_msgdef *md;
 } submsg_handlerdata_t;
 
-// Creates a handlerdata that contains offset and submessage type information.
+// Creates a handlerdata that contains field and submessage type information.
 static const void *newsubmsghandlerdata(upb_handlers* h, uint32_t ofs,
                                         const upb_fielddef* f) {
   submsg_handlerdata_t* hd =
       (submsg_handlerdata_t*)malloc(sizeof(submsg_handlerdata_t));
   PHP_PROTO_ASSERT(hd != NULL);
+  hd->fd = f;
   hd->ofs = ofs;
   hd->md = upb_fielddef_msgsubdef(f);
   upb_handlers_addcleanup(h, hd, free);
@@ -221,8 +232,11 @@
 // this field (such an instance always exists even in an empty message).
 static void *startseq_handler(void* closure, const void* hd) {
   MessageHeader* msg = closure;
-  const size_t *ofs = hd;
-  return CACHED_PTR_TO_ZVAL_PTR(DEREF(message_data(msg), *ofs, CACHED_VALUE*));
+  const upb_fielddef** field = (const upb_fielddef**) hd;
+  CACHED_VALUE* cache = find_zval_property(msg, *field);
+  TSRMLS_FETCH();
+  repeated_field_ensure_created(*field, cache PHP_PROTO_TSRMLS_CC);
+  return CACHED_PTR_TO_ZVAL_PTR(cache);
 }
 
 // Handlers that append primitive values to a repeated field.
@@ -322,15 +336,6 @@
 }
 #endif
 #if PHP_MAJOR_VERSION < 7
-static void *empty_php_string2(zval** value_ptr) {
-  SEPARATE_ZVAL_IF_NOT_REF(value_ptr);
-  if (Z_TYPE_PP(value_ptr) == IS_STRING &&
-      !IS_INTERNED(Z_STRVAL_PP(value_ptr))) {
-    FREE(Z_STRVAL_PP(value_ptr));
-  }
-  ZVAL_EMPTY_STRING(*value_ptr);
-  return (void*)(*value_ptr);
-}
 static void new_php_string(zval** value_ptr, const char* str, size_t len) {
   SEPARATE_ZVAL_IF_NOT_REF(value_ptr);
   if (Z_TYPE_PP(value_ptr) == IS_STRING &&
@@ -340,13 +345,6 @@
   ZVAL_STRINGL(*value_ptr, str, len, 1);
 }
 #else
-static void *empty_php_string2(zval* value_ptr) {
-  if (Z_TYPE_P(value_ptr) == IS_STRING) {
-    zend_string_release(Z_STR_P(value_ptr));
-  }
-  ZVAL_EMPTY_STRING(value_ptr);
-  return value_ptr;
-}
 static void new_php_string(zval* value_ptr, const char* str, size_t len) {
   if (Z_TYPE_P(value_ptr) == IS_STRING) {
     zend_string_release(Z_STR_P(value_ptr));
@@ -372,6 +370,21 @@
 
 static bool str_end_handler(void *closure, const void *hd) {
   stringfields_parseframe_t* frame = closure;
+  const upb_fielddef **field = (const upb_fielddef **) hd;
+  MessageHeader* msg = (MessageHeader*)frame->closure;
+
+  CACHED_VALUE* cached = find_zval_property(msg, *field);
+
+  new_php_string(cached, frame->sink.ptr, frame->sink.len);
+
+  stringsink_uninit(&frame->sink);
+  free(frame);
+
+  return true;
+}
+
+static bool map_str_end_handler(void *closure, const void *hd) {
+  stringfields_parseframe_t* frame = closure;
   const size_t *ofs = hd;
   MessageHeader* msg = (MessageHeader*)frame->closure;
 
@@ -430,26 +443,60 @@
   zval* submsg_php;
   MessageHeader* submsg;
 
-  if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(message_data(msg), submsgdata->ofs,
-                                            CACHED_VALUE*))) == IS_NULL) {
+  CACHED_VALUE* cached = find_zval_property(msg, submsgdata->fd);
+
+  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(DEREF(message_data(msg), submsgdata->ofs, zval**),
-                       &val, 1);
+    REPLACE_ZVAL_VALUE(cached, &val, 1);
     zval_dtor(&val);
 #else
     zend_object* obj = subklass->create_object(subklass TSRMLS_CC);
-    ZVAL_OBJ(DEREF(message_data(msg), submsgdata->ofs, zval*), obj);
+    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(
-      DEREF(message_data(msg), submsgdata->ofs, CACHED_VALUE*));
+  submsg_php = CACHED_PTR_TO_ZVAL_PTR(cached);
+
+  submsg = UNBOX(MessageHeader, submsg_php);
+  return submsg;
+}
+
+static void *map_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;
+
+  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);
   return submsg;
@@ -457,7 +504,7 @@
 
 // Handler data for startmap/endmap handlers.
 typedef struct {
-  size_t ofs;
+  const upb_fielddef* fd;
   const upb_msgdef* value_md;
   upb_fieldtype_t key_field_type;
   upb_fieldtype_t value_field_type;
@@ -612,9 +659,10 @@
 static void *startmapentry_handler(void *closure, const void *hd) {
   MessageHeader* msg = closure;
   const map_handlerdata_t* mapdata = hd;
+  CACHED_VALUE* cache = find_zval_property(msg, mapdata->fd);
   TSRMLS_FETCH();
-  zval* map = CACHED_PTR_TO_ZVAL_PTR(
-      DEREF(message_data(msg), mapdata->ofs, CACHED_VALUE*));
+  map_field_ensure_created(mapdata->fd, cache PHP_PROTO_TSRMLS_CC);
+  zval* map = CACHED_PTR_TO_ZVAL_PTR(cache);
 
   map_parse_frame_t* frame = ALLOC(map_parse_frame_t);
   frame->data = ALLOC(map_parse_frame_data_t);
@@ -662,7 +710,7 @@
 // key/value and endmsg handlers. The reason is that there is no easy way to
 // pass the handlerdata down to the sub-message handler setup.
 static map_handlerdata_t* new_map_handlerdata(
-    size_t ofs,
+    const upb_fielddef* field,
     const upb_msgdef* mapentry_def,
     Descriptor* desc) {
   const upb_fielddef* key_field;
@@ -671,7 +719,7 @@
   map_handlerdata_t* hd =
       (map_handlerdata_t*)malloc(sizeof(map_handlerdata_t));
   PHP_PROTO_ASSERT(hd != NULL);
-  hd->ofs = ofs;
+  hd->fd = field;
   key_field = upb_msgdef_itof(mapentry_def, MAP_KEY_FIELD);
   PHP_PROTO_ASSERT(key_field != NULL);
   hd->key_field_type = upb_fielddef_type(key_field);
@@ -844,7 +892,7 @@
                                             const upb_fielddef *f,
                                             size_t offset) {
   upb_handlerattr attr = UPB_HANDLERATTR_INIT;
-  attr.handler_data = newhandlerdata(h, offset);
+  attr.handler_data = newhandlerfielddata(h, f);
   upb_handlers_setstartseq(h, f, startseq_handler, &attr);
 
   switch (upb_fielddef_type(f)) {
@@ -884,7 +932,7 @@
 // Set up handlers for a singular field.
 static void add_handlers_for_singular_field(upb_handlers *h,
                                             const upb_fielddef *f,
-                                            size_t offset) {
+                                            size_t offset, bool is_map) {
   switch (upb_fielddef_type(f)) {
 #define SET_HANDLER(utype, ltype)                          \
   case utype: {                                            \
@@ -908,16 +956,29 @@
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
       upb_handlerattr attr = UPB_HANDLERATTR_INIT;
-      attr.handler_data = newhandlerdata(h, offset);
+      if (is_map) {
+        attr.handler_data = newhandlerdata(h, offset);
+      } else {
+        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, str_end_handler, &attr);
+      if (is_map) {
+        upb_handlers_setendstr(h, f, map_str_end_handler, &attr);
+      } else {
+        upb_handlers_setendstr(h, f, str_end_handler, &attr);
+      }
       break;
     }
     case UPB_TYPE_MESSAGE: {
       upb_handlerattr attr = UPB_HANDLERATTR_INIT;
-      attr.handler_data = newsubmsghandlerdata(h, offset, f);
-      upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr);
+      if (is_map) {
+        attr.handler_data = newsubmsghandlerdata(h, offset, f);
+        upb_handlers_setstartsubmsg(h, f, map_submsg_handler, &attr);
+      } else {
+        attr.handler_data = newsubmsghandlerdata(h, 0, f);
+        upb_handlers_setstartsubmsg(h, f, submsg_handler, &attr);
+      }
       break;
     }
   }
@@ -929,7 +990,7 @@
                                       size_t offset,
                                       Descriptor* desc) {
   const upb_msgdef* map_msgdef = upb_fielddef_msgsubdef(fielddef);
-  map_handlerdata_t* hd = new_map_handlerdata(offset, map_msgdef, desc);
+  map_handlerdata_t* hd = new_map_handlerdata(fielddef, map_msgdef, desc);
   upb_handlerattr attr = UPB_HANDLERATTR_INIT;
 
   upb_handlers_addcleanup(h, hd, free);
@@ -951,10 +1012,10 @@
 
   add_handlers_for_singular_field(h, key_field,
                                   offsetof(map_parse_frame_data_t,
-                                           key_storage));
+                                           key_storage), true);
   add_handlers_for_singular_field(h, value_field,
                                   offsetof(map_parse_frame_data_t,
-                                           value_storage));
+                                           value_storage), true);
 }
 
 // Set up handlers for a oneof field.
@@ -1063,7 +1124,7 @@
     } else if (upb_fielddef_isseq(f)) {
       add_handlers_for_repeated_field(h, f, offset);
     } else {
-      add_handlers_for_singular_field(h, f, offset);
+      add_handlers_for_singular_field(h, f, offset, false);
     }
   }
 }
@@ -1259,16 +1320,13 @@
   const upb_fielddef* type_field = upb_msgdef_itof(desc->msgdef, UPB_ANY_TYPE);
   const upb_fielddef* value_field = upb_msgdef_itof(desc->msgdef, UPB_ANY_VALUE);
 
-  uint32_t type_url_offset;
   zval* type_url_php_str;
   const upb_msgdef *payload_type = NULL;
 
   upb_sink_startmsg(sink);
 
   /* Handle type url */
-  type_url_offset = desc->layout->fields[upb_fielddef_index(type_field)].offset;
-  type_url_php_str = CACHED_PTR_TO_ZVAL_PTR(
-      DEREF(message_data(msg), type_url_offset, CACHED_VALUE*));
+  type_url_php_str = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, type_field));
   if (Z_STRLEN_P(type_url_php_str) > 0) {
     putstr(type_url_php_str, type_field, sink, false);
   }
@@ -1294,14 +1352,11 @@
   }
 
   {
-    uint32_t value_offset;
     zval* value_php_str;
     const char* value_str;
     size_t value_len;
 
-    value_offset = desc->layout->fields[upb_fielddef_index(value_field)].offset;
-    value_php_str = CACHED_PTR_TO_ZVAL_PTR(
-        DEREF(message_data(msg), value_offset, CACHED_VALUE*));
+    value_php_str = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, value_field));
     value_str = Z_STRVAL_P(value_php_str);
     value_len = Z_STRLEN_P(value_php_str);
 
@@ -1355,17 +1410,21 @@
 
   upb_sink_startmsg(sink);
 
-  array = CACHED_PTR_TO_ZVAL_PTR(
-      DEREF(message_data(msg), offset, CACHED_VALUE*));
-  intern = UNBOX(RepeatedField, array);
-  ht = PHP_PROTO_HASH_OF(intern->array);
-  size = zend_hash_num_elements(ht);
-
-  if (size == 0) {
+  array = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+  if (ZVAL_IS_NULL(array)) {
     upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
     upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
   } else {
-    putarray(array, f, sink, depth, true TSRMLS_CC);
+    intern = UNBOX(RepeatedField, array);
+    ht = PHP_PROTO_HASH_OF(intern->array);
+    size = zend_hash_num_elements(ht);
+
+    if (size == 0) {
+      upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
+      upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
+    } else {
+      putarray(array, f, sink, depth, true TSRMLS_CC);
+    }
   }
 
   upb_sink_endmsg(sink, &status);
@@ -1384,16 +1443,20 @@
 
   upb_sink_startmsg(sink);
 
-  map = CACHED_PTR_TO_ZVAL_PTR(
-      DEREF(message_data(msg), offset, CACHED_VALUE*));
-  intern = UNBOX(Map, map);
-  size = upb_strtable_count(&intern->table);
-
-  if (size == 0) {
+  map = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+  if (ZVAL_IS_NULL(map)) {
     upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
     upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
   } else {
-    putmap(map, f, sink, depth, true TSRMLS_CC);
+    intern = UNBOX(Map, map);
+    size = upb_strtable_count(&intern->table);
+
+    if (size == 0) {
+      upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
+      upb_sink_endseq(sink, getsel(f, UPB_HANDLER_ENDSEQ));
+    } else {
+      putmap(map, f, sink, depth, true TSRMLS_CC);
+    }
   }
 
   upb_sink_endmsg(sink, &status);
@@ -1455,28 +1518,24 @@
     }
 
     if (is_map_field(f)) {
-      zval* map = CACHED_PTR_TO_ZVAL_PTR(
-          DEREF(message_data(msg), offset, CACHED_VALUE*));
-      if (map != NULL) {
+      zval* map = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+      if (!ZVAL_IS_NULL(map)) {
         putmap(map, f, sink, depth, is_json TSRMLS_CC);
       }
     } else if (upb_fielddef_isseq(f)) {
-      zval* array = CACHED_PTR_TO_ZVAL_PTR(
-          DEREF(message_data(msg), offset, CACHED_VALUE*));
-      if (array != NULL) {
+      zval* array = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+      if (!ZVAL_IS_NULL(array)) {
         putarray(array, f, sink, depth, is_json TSRMLS_CC);
       }
     } else if (upb_fielddef_isstring(f)) {
-      zval* str = CACHED_PTR_TO_ZVAL_PTR(
-          DEREF(message_data(msg), offset, CACHED_VALUE*));
+      zval* str = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
       if (containing_oneof || (is_json && is_wrapper_msg(desc->msgdef)) ||
           Z_STRLEN_P(str) > 0) {
         putstr(str, f, sink, is_json && is_wrapper_msg(desc->msgdef));
       }
     } else if (upb_fielddef_issubmsg(f)) {
-      putsubmsg(CACHED_PTR_TO_ZVAL_PTR(
-                    DEREF(message_data(msg), offset, CACHED_VALUE*)),
-                f, sink, depth, is_json TSRMLS_CC);
+      zval* submsg = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+      putsubmsg(submsg, f, sink, depth, is_json TSRMLS_CC);
     } else {
       upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
 
@@ -1847,9 +1906,8 @@
       value_field = map_field_value(f);
       if (!upb_fielddef_issubmsg(value_field)) continue;
 
-      zval* map_php = CACHED_PTR_TO_ZVAL_PTR(
-          DEREF(message_data(msg), offset, CACHED_VALUE*));
-      if (map_php == NULL) continue;
+      zval* map_php = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+      if (ZVAL_IS_NULL(map_php)) continue;
 
       Map* intern = UNBOX(Map, map_php);
       for (map_begin(map_php, &map_it TSRMLS_CC);
@@ -1868,9 +1926,8 @@
     } else if (upb_fielddef_isseq(f)) {
       if (!upb_fielddef_issubmsg(f)) continue;
 
-      zval* array_php = CACHED_PTR_TO_ZVAL_PTR(
-          DEREF(message_data(msg), offset, CACHED_VALUE*));
-      if (array_php == NULL) continue;
+      zval* array_php = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
+      if (ZVAL_IS_NULL(array_php)) continue;
 
       int size, i;
       RepeatedField* intern = UNBOX(RepeatedField, array_php);
@@ -1890,8 +1947,7 @@
         discard_unknown_fields(submsg);
       }
     } else if (upb_fielddef_issubmsg(f)) {
-      zval* submsg_php = CACHED_PTR_TO_ZVAL_PTR(
-          DEREF(message_data(msg), offset, CACHED_VALUE*));
+      zval* submsg_php = CACHED_PTR_TO_ZVAL_PTR(find_zval_property(msg, f));
       if (Z_TYPE_P(submsg_php) == IS_NULL) continue;
       MessageHeader* submsg = UNBOX(MessageHeader, submsg_php);
       discard_unknown_fields(submsg);
diff --git a/php/ext/google/protobuf/map.c b/php/ext/google/protobuf/map.c
index 0ce1019..2764788 100644
--- a/php/ext/google/protobuf/map.c
+++ b/php/ext/google/protobuf/map.c
@@ -243,6 +243,18 @@
 map_field_handlers->get_gc = map_field_get_gc;
 PHP_PROTO_INIT_CLASS_END
 
+void map_field_ensure_created(const upb_fielddef *field,
+                              CACHED_VALUE *map_field PHP_PROTO_TSRMLS_DC) {
+  if (ZVAL_IS_NULL(CACHED_PTR_TO_ZVAL_PTR(map_field))) {
+    zval_ptr_dtor(map_field);
+#if PHP_MAJOR_VERSION < 7
+    MAKE_STD_ZVAL(CACHED_PTR_TO_ZVAL_PTR(map_field));
+#endif
+    map_field_create_with_field(map_field_type, field,
+                                map_field PHP_PROTO_TSRMLS_CC);
+  }
+}
+
 void map_field_create_with_field(const zend_class_entry *ce,
                                  const upb_fielddef *field,
                                  CACHED_VALUE *map_field PHP_PROTO_TSRMLS_DC) {
diff --git a/php/ext/google/protobuf/message.c b/php/ext/google/protobuf/message.c
index 303f5d4..03dec75 100644
--- a/php/ext/google/protobuf/message.c
+++ b/php/ext/google/protobuf/message.c
@@ -178,7 +178,7 @@
       zend_get_property_info(Z_OBJCE_P(object), Z_STR_P(member), true);
 #endif
   return layout_get(
-      self->descriptor->layout, message_data(self), field,
+      self->descriptor->layout, self, field,
       OBJ_PROP(Z_OBJ_P(object), property_info->offset) TSRMLS_CC);
 }
 
@@ -191,7 +191,7 @@
     return;
   }
 
-  layout_get(self->descriptor->layout, message_data(self), field,
+  layout_get(self->descriptor->layout, self, field,
              ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
 }
 
@@ -255,7 +255,6 @@
                       MessageHeader* intern PHP_PROTO_TSRMLS_DC) {
   Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(ce));
   intern->data = ALLOC_N(uint8_t, desc->layout->size);
-  memset(message_data(intern), 0, desc->layout->size);
   // We wrap first so that everything in the message object is GC-rooted in
   // case a collection happens during object creation in layout_init().
   intern->descriptor = desc;
@@ -575,9 +574,9 @@
   const upb_fielddef* field = upb_msgdef_itof(msg->descriptor->msgdef, index);
 
   // Unlike singular fields, oneof fields share cached property. So we cannot
-  // let lay_get modify the cached property. Instead, we pass in the return
+  // let layout_get modify the cached property. Instead, we pass in the return
   // value directly.
-  layout_get(msg->descriptor->layout, message_data(msg), field,
+  layout_get(msg->descriptor->layout, msg, field,
              ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
 }
 
diff --git a/php/ext/google/protobuf/protobuf.h b/php/ext/google/protobuf/protobuf.h
index 4abfecb..86bc5b3 100644
--- a/php/ext/google/protobuf/protobuf.h
+++ b/php/ext/google/protobuf/protobuf.h
@@ -200,7 +200,7 @@
 
 #define CACHED_VALUE zval*
 #define CACHED_TO_ZVAL_PTR(VALUE) (VALUE)
-#define CACHED_PTR_TO_ZVAL_PTR(VALUE) (*VALUE)
+#define CACHED_PTR_TO_ZVAL_PTR(VALUE) (*(CACHED_VALUE*)(VALUE))
 #define ZVAL_PTR_TO_CACHED_PTR(VALUE) (&VALUE)
 #define ZVAL_PTR_TO_CACHED_VALUE(VALUE) (VALUE)
 #define ZVAL_TO_CACHED_VALUE(VALUE) (&VALUE)
@@ -475,7 +475,7 @@
 
 #define CACHED_VALUE zval
 #define CACHED_TO_ZVAL_PTR(VALUE) (&VALUE)
-#define CACHED_PTR_TO_ZVAL_PTR(VALUE) (VALUE)
+#define CACHED_PTR_TO_ZVAL_PTR(VALUE) ((CACHED_VALUE*)(VALUE))
 #define ZVAL_PTR_TO_CACHED_PTR(VALUE) (VALUE)
 #define ZVAL_PTR_TO_CACHED_VALUE(VALUE) (*VALUE)
 #define ZVAL_TO_CACHED_VALUE(VALUE) (VALUE)
@@ -935,6 +935,7 @@
 
 struct MessageLayout {
   const upb_msgdef* msgdef;
+  void* empty_template;  // Can memcpy() onto a layout to clear it.
   MessageField* fields;
   size_t size;
 };
@@ -948,7 +949,7 @@
 MessageLayout* create_layout(const upb_msgdef* msgdef);
 void layout_init(MessageLayout* layout, void* storage,
                  zend_object* object PHP_PROTO_TSRMLS_DC);
-zval* layout_get(MessageLayout* layout, const void* storage,
+zval* layout_get(MessageLayout* layout, MessageHeader* header,
                  const upb_fielddef* field, CACHED_VALUE* cache TSRMLS_DC);
 void layout_set(MessageLayout* layout, MessageHeader* header,
                 const upb_fielddef* field, zval* val TSRMLS_DC);
@@ -1089,6 +1090,8 @@
 const upb_fielddef* map_entry_key(const upb_msgdef* msgdef);
 const upb_fielddef* map_entry_value(const upb_msgdef* msgdef);
 
+void map_field_ensure_created(const upb_fielddef *field,
+                              CACHED_VALUE *map_field PHP_PROTO_TSRMLS_DC);
 void map_field_create_with_field(const zend_class_entry* ce,
                                  const upb_fielddef* field,
                                  CACHED_VALUE* map_field PHP_PROTO_TSRMLS_DC);
@@ -1147,6 +1150,9 @@
   long position;
 PHP_PROTO_WRAP_OBJECT_END
 
+void repeated_field_ensure_created(
+    const upb_fielddef *field,
+    CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC);
 void repeated_field_create_with_field(
     zend_class_entry* ce, const upb_fielddef* field,
     CACHED_VALUE* repeated_field PHP_PROTO_TSRMLS_DC);
@@ -1489,6 +1495,9 @@
 #define FREE(object) efree(object)
 #define PEFREE(object) pefree(object, 1)
 
+// Find corresponding zval property for the field.
+CACHED_VALUE* find_zval_property(MessageHeader* msg, const upb_fielddef* field);
+
 // String argument.
 #define STR(str) (str), strlen(str)
 
diff --git a/php/ext/google/protobuf/storage.c b/php/ext/google/protobuf/storage.c
index 1c28b1c..e6050d0 100644
--- a/php/ext/google/protobuf/storage.c
+++ b/php/ext/google/protobuf/storage.c
@@ -75,11 +75,9 @@
 #undef CASE_TYPE
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
-      return Z_STRLEN_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(memory, CACHED_VALUE*))) ==
-             0;
+      return Z_STRLEN_P(CACHED_PTR_TO_ZVAL_PTR(memory)) == 0;
     case UPB_TYPE_MESSAGE:
-      return Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(memory, CACHED_VALUE*))) ==
-             IS_NULL;
+      return Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(memory)) == IS_NULL;
     default: return false;
   }
 }
@@ -599,6 +597,8 @@
   // Reserve space for unknown fields.
   off += sizeof(void*);
 
+  layout->empty_template = NULL;
+
   TSRMLS_FETCH();
   Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msgdef));
   layout->fields = ALLOC_N(MessageField, nfields);
@@ -744,64 +744,35 @@
   layout->size = off;
   layout->msgdef = msgdef;
 
+  // Create the empty message template.
+  layout->empty_template = ALLOC_N(char, layout->size);
+  memset(layout->empty_template, 0, layout->size);
+
   return layout;
 }
 
 void free_layout(MessageLayout* layout) {
+  FREE(layout->empty_template);
   FREE(layout->fields);
   FREE(layout);
 }
 
 void layout_init(MessageLayout* layout, void* storage,
                  zend_object* object PHP_PROTO_TSRMLS_DC) {
-  int i;
-  upb_msg_field_iter it;
-
-  // Init unknown fields
-  memset(storage, 0, sizeof(void*));
-
-  for (upb_msg_field_begin(&it, layout->msgdef), i = 0; !upb_msg_field_done(&it);
-       upb_msg_field_next(&it), i++) {
-    const upb_fielddef* field = upb_msg_iter_field(&it);
-    void* memory = slot_memory(layout, storage, field);
-    uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
-    int cache_index = slot_property_cache(layout, storage, field);
-    CACHED_VALUE* property_ptr = OBJ_PROP(object, cache_index);
-
-    if (upb_fielddef_containingoneof(field)) {
-      memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
-      *oneof_case = ONEOF_CASE_NONE;
-    } else if (is_map_field(field)) {
-      zval_ptr_dtor(property_ptr);
-#if PHP_MAJOR_VERSION < 7
-      MAKE_STD_ZVAL(*property_ptr);
-#endif
-      map_field_create_with_field(map_field_type, field,
-                                  property_ptr PHP_PROTO_TSRMLS_CC);
-      DEREF(memory, CACHED_VALUE*) = property_ptr;
-    } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-      zval_ptr_dtor(property_ptr);
-#if PHP_MAJOR_VERSION < 7
-      MAKE_STD_ZVAL(*property_ptr);
-#endif
-      repeated_field_create_with_field(repeated_field_type, field,
-                                       property_ptr PHP_PROTO_TSRMLS_CC);
-      DEREF(memory, CACHED_VALUE*) = property_ptr;
-    } else {
-      native_slot_init(upb_fielddef_type(field), memory, property_ptr);
-    }
-  }
+  memcpy(storage, layout->empty_template, layout->size);
 }
 
-// For non-singular fields, the related memory needs to point to the actual
-// zval in properties table first.
-static void* value_memory(const upb_fielddef* field, void* memory) {
-  switch (upb_fielddef_type(field)) {
+// Switch memory for processing for singular fields based on field type.
+//   * primitive fields: memory
+//   * others (string, bytes and message): cache (the correspond zval
+//   property)
+static void* value_memory(
+    upb_fieldtype_t type, void* memory, CACHED_VALUE* cache) {
+  switch (type) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
     case UPB_TYPE_MESSAGE:
-      memory = DEREF(memory, CACHED_VALUE*);
-      break;
+      return cache;
     default:
       // No operation
       break;
@@ -809,8 +780,17 @@
   return memory;
 }
 
-zval* layout_get(MessageLayout* layout, const void* storage,
+CACHED_VALUE* find_zval_property(
+    MessageHeader* header, const upb_fielddef* field) {
+  int property_cache_index =
+      header->descriptor->layout->fields[upb_fielddef_index(field)]
+          .cache_index;
+  return OBJ_PROP(&header->std, property_cache_index);
+}
+
+zval* layout_get(MessageLayout* layout, MessageHeader* header,
                  const upb_fielddef* field, CACHED_VALUE* cache TSRMLS_DC) {
+  const void* storage = message_data(header);
   void* memory = slot_memory(layout, storage, field);
   uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
 
@@ -818,14 +798,21 @@
     if (*oneof_case != upb_fielddef_number(field)) {
       native_slot_get_default(upb_fielddef_type(field), cache TSRMLS_CC);
     } else {
-      native_slot_get(upb_fielddef_type(field), value_memory(field, memory),
-                      cache TSRMLS_CC);
+      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);
+  } 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);
   } else {
-    native_slot_get(upb_fielddef_type(field), value_memory(field, memory),
+    upb_fieldtype_t type = upb_fielddef_type(field);
+    native_slot_get(type, value_memory(type, memory, cache),
                     cache TSRMLS_CC);
     return CACHED_PTR_TO_ZVAL_PTR(cache);
   }
@@ -868,8 +855,8 @@
     *oneof_case = upb_fielddef_number(field);
   } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
     // Works for both repeated and map fields
-    memory = DEREF(memory, void**);
-    zval* property_ptr = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+    CACHED_VALUE* cached = find_zval_property(header, field);
+    zval* property_ptr = CACHED_PTR_TO_ZVAL_PTR(cached);
 
     if (EXPECTED(property_ptr != val)) {
       zend_class_entry *subce = NULL;
@@ -901,7 +888,7 @@
                              &converted_value);
       }
 #if PHP_MAJOR_VERSION < 7
-      REPLACE_ZVAL_VALUE((zval**)memory, &converted_value, 1);
+      REPLACE_ZVAL_VALUE((zval**)cached, &converted_value, 1);
 #else
       php_proto_zval_ptr_dtor(property_ptr);
       ZVAL_ZVAL(property_ptr, &converted_value, 1, 0);
@@ -916,12 +903,16 @@
       Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
       ce = desc->klass;
     }
-    native_slot_set(type, ce, value_memory(field, memory), val TSRMLS_CC);
+    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(const upb_fielddef* field, const void* from_memory,
-                         void* to_memory PHP_PROTO_TSRMLS_DC) {
+static void native_slot_merge(
+    const upb_fielddef* field, const void* from_memory,
+    void* to_memory PHP_PROTO_TSRMLS_DC) {
   upb_fieldtype_t type = upb_fielddef_type(field);
   zend_class_entry* ce = NULL;
   if (!native_slot_is_default(type, from_memory)) {
@@ -943,9 +934,8 @@
 #undef CASE_TYPE
       case UPB_TYPE_STRING:
       case UPB_TYPE_BYTES:
-        native_slot_set(type, NULL, value_memory(field, to_memory),
-                        CACHED_PTR_TO_ZVAL_PTR(DEREF(
-                            from_memory, CACHED_VALUE*)) PHP_PROTO_TSRMLS_CC);
+        native_slot_set(type, NULL, to_memory,
+                        CACHED_PTR_TO_ZVAL_PTR(from_memory) PHP_PROTO_TSRMLS_CC);
         break;
       case UPB_TYPE_MESSAGE: {
         const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
@@ -953,22 +943,21 @@
         ce = desc->klass;
         if (native_slot_is_default(type, to_memory)) {
 #if PHP_MAJOR_VERSION < 7
-          SEPARATE_ZVAL_IF_NOT_REF((zval**)value_memory(field, to_memory));
+          SEPARATE_ZVAL_IF_NOT_REF((zval**)to_memory);
 #endif
           CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(
-              CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*)), ce);
+              CACHED_PTR_TO_ZVAL_PTR(to_memory), ce);
           MessageHeader* submsg =
-              UNBOX(MessageHeader,
-                    CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*)));
+              UNBOX(MessageHeader, CACHED_PTR_TO_ZVAL_PTR(to_memory));
           custom_data_init(ce, submsg PHP_PROTO_TSRMLS_CC);
         }
 
         MessageHeader* sub_from =
             UNBOX(MessageHeader,
-                  CACHED_PTR_TO_ZVAL_PTR(DEREF(from_memory, CACHED_VALUE*)));
+                  CACHED_PTR_TO_ZVAL_PTR(from_memory));
         MessageHeader* sub_to =
             UNBOX(MessageHeader,
-                  CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*)));
+                  CACHED_PTR_TO_ZVAL_PTR(to_memory));
 
         layout_merge(desc->layout, sub_from, sub_to PHP_PROTO_TSRMLS_CC);
         break;
@@ -1069,10 +1058,17 @@
       int size, key_length, value_length;
       MapIter map_it;
 
-      zval* to_map_php =
-          CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*));
-      zval* from_map_php =
-          CACHED_PTR_TO_ZVAL_PTR(DEREF(from_memory, CACHED_VALUE*));
+      CACHED_VALUE* from_cache = find_zval_property(from, field);
+      CACHED_VALUE* to_cache = find_zval_property(to, field);
+
+      if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(from_cache)) == IS_NULL) {
+        continue;
+      }
+      map_field_ensure_created(field, to_cache PHP_PROTO_TSRMLS_CC);
+
+      zval* to_map_php = CACHED_PTR_TO_ZVAL_PTR(to_cache);
+      zval* from_map_php = CACHED_PTR_TO_ZVAL_PTR(from_cache);
+
       Map* to_map = UNBOX(Map, to_map_php);
       Map* from_map = UNBOX(Map, from_map_php);
 
@@ -1098,8 +1094,16 @@
       }
 
     } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-      zval* to_array_php = CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*));
-      zval* from_array_php = CACHED_PTR_TO_ZVAL_PTR(DEREF(from_memory, CACHED_VALUE*));
+      CACHED_VALUE* from_cache = find_zval_property(from, field);
+      CACHED_VALUE* to_cache = find_zval_property(to, field);
+
+      if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(from_cache)) == IS_NULL) {
+        continue;
+      }
+      repeated_field_ensure_created(field, to_cache PHP_PROTO_TSRMLS_CC);
+
+      zval* to_array_php = CACHED_PTR_TO_ZVAL_PTR(to_cache);
+      zval* from_array_php = CACHED_PTR_TO_ZVAL_PTR(from_cache);
       RepeatedField* to_array = UNBOX(RepeatedField, to_array_php);
       RepeatedField* from_array = UNBOX(RepeatedField, from_array_php);
 
@@ -1129,7 +1133,19 @@
         }
       }
     } else {
-      native_slot_merge(field, from_memory, to_memory PHP_PROTO_TSRMLS_CC);
+      switch (upb_fielddef_type(field)) {
+        case UPB_TYPE_STRING:
+        case UPB_TYPE_BYTES:
+        case UPB_TYPE_MESSAGE: {
+          CACHED_VALUE* from_cache = find_zval_property(from, field);
+          CACHED_VALUE* to_cache = find_zval_property(to, field);
+          native_slot_merge(field, from_cache, to_cache PHP_PROTO_TSRMLS_CC);
+          break;
+        }
+        default:
+          native_slot_merge(field, from_memory, to_memory PHP_PROTO_TSRMLS_CC);
+          break;
+      }
     }
   }
 }
diff --git a/src/google/protobuf/stubs/structurally_valid.cc b/src/google/protobuf/stubs/structurally_valid.cc
index 0598427..788b224 100644
--- a/src/google/protobuf/stubs/structurally_valid.cc
+++ b/src/google/protobuf/stubs/structurally_valid.cc
@@ -395,7 +395,7 @@
   const uint8* isrc = reinterpret_cast<const uint8*>(str);
   const uint8* src = isrc;
   const uint8* srclimit = isrc + str_length;
-  const uint8* srclimit8 = srclimit - 7;
+  const uint8* srclimit8 = str_length < 7 ? isrc : srclimit - 7;
   const uint8* Tbl_0 = &st->state_table[st->state0];
 
  DoAgain:
@@ -504,7 +504,7 @@
   const uint8* isrc =  reinterpret_cast<const uint8*>(str);
   const uint8* src = isrc;
   const uint8* srclimit = isrc + str_length;
-  const uint8* srclimit8 = srclimit - 7;
+  const uint8* srclimit8 = str_length < 7 ? isrc : srclimit - 7;
   int n;
   int rest_consumed;
   int exit_reason;