Преглед на файлове

Merge remote-tracking branch 'remotes/google/3.3.x' into merge-3.3-to-master

Adam Cozzette преди 8 години
родител
ревизия
9053033a50
променени са 100 файла, в които са добавени 3418 реда и са изтрити 1585 реда
  1. 3 0
      .gitignore
  2. 1 0
      .travis.yml
  3. 10 0
      BUILD
  4. 108 0
      CHANGES.txt
  5. 5 1
      Makefile.am
  6. 1 1
      Protobuf.podspec
  7. 2 0
      cmake/extract_includes.bat.in
  8. 1 0
      cmake/libprotoc.cmake
  9. 4 1
      cmake/tests.cmake
  10. 1 1
      configure.ac
  11. 17 0
      conformance/conformance_test.cc
  12. 1 0
      conformance/conformance_test.h
  13. 0 2
      conformance/failure_list_cpp.txt
  14. 4 0
      conformance/failure_list_csharp.txt
  15. 4 2
      conformance/failure_list_objc.txt
  16. 10 18
      conformance/failure_list_php_c.txt
  17. 4 0
      conformance/failure_list_python.txt
  18. 1 1
      csharp/Google.Protobuf.Tools.nuspec
  19. 47 46
      csharp/src/Google.Protobuf/Reflection/Descriptor.cs
  20. 14 1
      csharp/src/Google.Protobuf/WellKnownTypes/Duration.cs
  21. 25 0
      csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs
  22. 1 1
      csharp/src/Google.Protobuf/project.json
  23. 8 13
      generate_descriptor_proto.sh
  24. 6 1
      java/core/pom.xml
  25. 2 2
      java/core/src/main/java/com/google/protobuf/AbstractMessage.java
  26. 11 10
      java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java
  27. 25 1
      java/core/src/main/java/com/google/protobuf/AbstractParser.java
  28. 5 7
      java/core/src/main/java/com/google/protobuf/BooleanArrayList.java
  29. 2 2
      java/core/src/main/java/com/google/protobuf/CodedInputStream.java
  30. 2 2
      java/core/src/main/java/com/google/protobuf/CodedOutputStream.java
  31. 3 4
      java/core/src/main/java/com/google/protobuf/Descriptors.java
  32. 5 7
      java/core/src/main/java/com/google/protobuf/DoubleArrayList.java
  33. 3 4
      java/core/src/main/java/com/google/protobuf/DynamicMessage.java
  34. 3 4
      java/core/src/main/java/com/google/protobuf/FieldSet.java
  35. 5 7
      java/core/src/main/java/com/google/protobuf/FloatArrayList.java
  36. 20 2
      java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java
  37. 1 1
      java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java
  38. 5 7
      java/core/src/main/java/com/google/protobuf/IntArrayList.java
  39. 15 0
      java/core/src/main/java/com/google/protobuf/Internal.java
  40. 1 0
      java/core/src/main/java/com/google/protobuf/LazyFieldLite.java
  41. 5 7
      java/core/src/main/java/com/google/protobuf/LongArrayList.java
  42. 18 6
      java/core/src/main/java/com/google/protobuf/MapEntry.java
  43. 5 0
      java/core/src/main/java/com/google/protobuf/MapEntryLite.java
  44. 8 0
      java/core/src/main/java/com/google/protobuf/MapField.java
  45. 13 1
      java/core/src/main/java/com/google/protobuf/MapFieldLite.java
  46. 13 0
      java/core/src/main/java/com/google/protobuf/Parser.java
  47. 34 0
      java/core/src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java
  48. 6 12
      java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java
  49. 4 8
      java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java
  50. 3 1
      java/core/src/main/java/com/google/protobuf/TextFormat.java
  51. 2 2
      java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java
  52. 308 93
      java/core/src/main/java/com/google/protobuf/UnsafeUtil.java
  53. 1 1
      java/core/src/main/java/com/google/protobuf/Utf8.java
  54. 1 1
      java/core/src/test/java/com/google/protobuf/LazyFieldTest.java
  55. 108 0
      java/core/src/test/java/com/google/protobuf/LiteTest.java
  56. 1 0
      java/core/src/test/java/com/google/protobuf/MapForProto2Test.java
  57. 39 1
      java/core/src/test/java/com/google/protobuf/MapTest.java
  58. 23 4
      java/core/src/test/java/com/google/protobuf/ParserTest.java
  59. 2 0
      java/core/src/test/java/com/google/protobuf/TestUtil.java
  60. 92 0
      java/core/src/test/java/com/google/protobuf/TextFormatTest.java
  61. 8 0
      java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto
  62. 1 0
      java/lite/pom.xml
  63. 8 2
      java/pom.xml
  64. 1 1
      java/util/pom.xml
  65. 99 77
      java/util/src/main/java/com/google/protobuf/util/JsonFormat.java
  66. 1 1
      java/util/src/main/java/com/google/protobuf/util/Timestamps.java
  67. 126 11
      java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java
  68. 1 1
      jenkins/buildcmds/pull_request_32.sh
  69. 25 8
      jenkins/docker/Dockerfile
  70. 19 2
      jenkins/docker32/Dockerfile
  71. 11 6
      js/binary/decoder.js
  72. 19 0
      js/binary/decoder_test.js
  73. 2 2
      js/binary/encoder.js
  74. 1 1
      js/binary/reader.js
  75. 1 1
      js/binary/utils.js
  76. 2 2
      js/binary/writer.js
  77. 1 1
      js/binary/writer_test.js
  78. 5 5
      js/map.js
  79. 25 6
      js/message.js
  80. 1 0
      js/message_test.js
  81. 1 1
      js/package.json
  82. 14 1
      objectivec/google/protobuf/Duration.pbobjc.h
  83. 25 0
      objectivec/google/protobuf/Timestamp.pbobjc.h
  84. 157 156
      php/ext/google/protobuf/array.c
  85. 240 210
      php/ext/google/protobuf/def.c
  86. 341 182
      php/ext/google/protobuf/encode_decode.c
  87. 90 105
      php/ext/google/protobuf/map.c
  88. 108 116
      php/ext/google/protobuf/message.c
  89. 21 5
      php/ext/google/protobuf/package.xml
  90. 46 11
      php/ext/google/protobuf/protobuf.c
  91. 399 79
      php/ext/google/protobuf/protobuf.h
  92. 349 229
      php/ext/google/protobuf/storage.c
  93. 60 25
      php/ext/google/protobuf/type_check.c
  94. 3 0
      php/src/Google/Protobuf/Internal/DescriptorPool.php
  95. 47 0
      php/src/Google/Protobuf/Internal/GPBDecodeException.php
  96. 7 10
      php/src/Google/Protobuf/Internal/InputStream.php
  97. 3 1
      php/src/Google/Protobuf/Internal/MapField.php
  98. 42 31
      php/src/Google/Protobuf/Internal/Message.php
  99. 4 0
      php/src/Google/Protobuf/Internal/RepeatedField.php
  100. 32 10
      php/src/Google/Protobuf/descriptor.php

+ 3 - 0
.gitignore

@@ -65,6 +65,9 @@ src/js_embed
 src/protoc
 src/unittest_proto_middleman
 
+# vim generated
+*.swp
+
 # Generated test scaffolding
 src/no_warning_test.cc
 src/no-warning-test

+ 1 - 0
.travis.yml

@@ -32,6 +32,7 @@ env:
   - CONFIG=ruby22
   - CONFIG=jruby
   - CONFIG=php5.6_mac
+  - CONFIG=php7.0_mac
 matrix:
   exclude:
     # It's nontrivial to programmatically install a new JDK from the command

+ 10 - 0
BUILD

@@ -206,6 +206,7 @@ RELATIVE_WELL_KNOWN_PROTOS = [
     "google/protobuf/any.proto",
     "google/protobuf/api.proto",
     "google/protobuf/compiler/plugin.proto",
+    "google/protobuf/compiler/profile.proto",
     "google/protobuf/descriptor.proto",
     "google/protobuf/duration.proto",
     "google/protobuf/empty.proto",
@@ -350,6 +351,7 @@ cc_library(
         "src/google/protobuf/compiler/php/php_generator.cc",
         "src/google/protobuf/compiler/plugin.cc",
         "src/google/protobuf/compiler/plugin.pb.cc",
+        "src/google/protobuf/compiler/profile.pb.cc",
         "src/google/protobuf/compiler/python/python_generator.cc",
         "src/google/protobuf/compiler/ruby/ruby_generator.cc",
         "src/google/protobuf/compiler/subprocess.cc",
@@ -401,6 +403,9 @@ RELATIVE_TEST_PROTOS = [
     "google/protobuf/unittest_enormous_descriptor.proto",
     "google/protobuf/unittest_import.proto",
     "google/protobuf/unittest_import_public.proto",
+    "google/protobuf/unittest_lazy_dependencies.proto",
+    "google/protobuf/unittest_lazy_dependencies_custom_option.proto",
+    "google/protobuf/unittest_lazy_dependencies_enum.proto",
     "google/protobuf/unittest_lite_imports_nonlite.proto",
     "google/protobuf/unittest_mset.proto",
     "google/protobuf/unittest_mset_wire_format.proto",
@@ -477,6 +482,7 @@ cc_test(
         "src/google/protobuf/compiler/cpp/cpp_plugin_unittest.cc",
         "src/google/protobuf/compiler/cpp/cpp_unittest.cc",
         "src/google/protobuf/compiler/cpp/metadata_test.cc",
+        "src/google/protobuf/compiler/csharp/csharp_bootstrap_unittest.cc",
         "src/google/protobuf/compiler/csharp/csharp_generator_unittest.cc",
         "src/google/protobuf/compiler/importer_unittest.cc",
         "src/google/protobuf/compiler/java/java_doc_comment_unittest.cc",
@@ -543,6 +549,10 @@ cc_test(
         ":test_plugin",
     ] + glob([
         "src/google/protobuf/**/*",
+        # Files for csharp_bootstrap_unittest.cc.
+        "conformance/**/*",
+        "csharp/src/**/*",
+        "examples/**/*",
     ]),
     includes = [
         "src/",

+ 108 - 0
CHANGES.txt

@@ -1,3 +1,111 @@
+2017-04-05 version 3.3.0 (C++/Java/Python/PHP/Objective-C/C#/Ruby/JavaScript)
+  Planned Future Changes
+  * There are some changes that are not included in this release but are
+    planned for the near future:
+      - Preserve unknown fields in proto3: please read this doc:
+
+          https://docs.google.com/document/d/1KMRX-G91Aa-Y2FkEaHeeviLRRNblgIahbsk4wA14gRk/view
+
+        for the timeline and follow up this github issue:
+
+          https://github.com/google/protobuf/issues/272
+
+        for discussion.
+      - Make C++ implementation C++11 only: we plan to require C++11 to build
+        protobuf code starting from 3.4.0 or 3.5.0 release. Please join this
+        github issue:
+
+          https://github.com/google/protobuf/issues/2780
+
+        to provide your feedback.
+
+  C++
+  * Fixed map fields serialization of DynamicMessage to correctly serialize
+    both key and value regardless of their presence.
+  * Parser now rejects field number 0 correctly.
+  * New API Message::SpaceUsedLong() that’s equivalent to
+    Message::SpaceUsed() but returns the value in size_t.
+  * JSON support
+    - New flag always_print_enums_as_ints in JsonPrintOptions.
+    - New flag preserve_proto_field_names in JsonPrintOptions. It will instruct
+      the JSON printer to use the original field name declared in the .proto
+      file instead of converting them to lowerCamelCase when printing JSON.
+    - JsonPrintOptions.always_print_primtive_fields now works for oneof message
+      fields.
+    - Fixed a bug that doesn’t allow different fields to set the same json_name
+      value.
+    - Fixed a performance bug that causes excessive memory copy when printing
+      large messages.
+  * Various performance optimizations.
+
+  Java
+  * Map field setters eagerly validate inputs and throw NullPointerExceptions
+    as appropriate.
+  * Added ByteBuffer overloads to the generated parsing methods and the Parser
+    interface.
+  * proto3 enum's getNumber() method now throws on UNRECOGNIZED values.
+  * Output of JsonFormat is now locale independent.
+
+  Python
+  * Added FindServiceByName() in the pure-Python DescriptorPool. This works only
+    for descriptors added with DescriptorPool.Add(). Generated descriptor_pool
+    does not support this yet.
+  * Added a descriptor_pool parameter for parsing Any in text_format.Parse().
+  * descriptor_pool.FindFileContainingSymbol() now is able to find nested
+    extensions.
+  * Extending empty [] to repeated field now sets parent message presence.
+
+  PHP
+  * Added file option php_class_prefix. The prefix will be prepended to all
+    generated classes defined in the file.
+  * When encoding, negative int32 values are sign-extended to int64.
+  * Repeated/Map field setter accepts a regular PHP array. Type checking is
+    done on the array elements.
+  * encode/decode are renamed to serializeToString/mergeFromString.
+  * Added mergeFrom, clear method on Message.
+  * Fixed a bug that oneof accessor didn’t return the field name that is
+    actually set.
+  * C extension now works with php7.
+  * This is the first GA release of PHP. We guarantee that old generated code
+    can always work with new runtime and new generated code.
+
+  Objective-C
+  * Fixed help for GPBTimestamp for dates before the epoch that contain
+    fractional seconds.
+  * Added GPBMessageDropUnknownFieldsRecursively() to remove unknowns from a
+    message and any sub messages.
+  * Addressed a threading race in extension registration/lookup.
+  * Increased the max message parsing depth to 100 to match the other languages.
+  * Removed some use of dispatch_once in favor of atomic compare/set since it
+    needs to be heap based.
+  * Fixes for new Xcode 8.3 warnings.
+
+  C#
+  * Fixed MapField.Values.CopyTo, which would throw an exception unnecessarily
+    if provided exactly the right size of array to copy to.
+  * Fixed enum JSON formatting when multiple names mapped to the same numeric
+    value.
+  * Added JSON formatting option to format enums as integers.
+  * Modified RepeatedField<T> to implement IReadOnlyList<T>.
+  * Introduced the start of custom option handling; it's not as pleasant as it
+    might be, but the information is at least present. We expect to extend code
+    generation to improve this in the future.
+  * Introduced ByteString.FromStream and ByteString.FromStreamAsync to
+    efficiently create a ByteString from a stream.
+  * Added whole-message deprecation, which decorates the class with [Obsolete].
+
+  Ruby
+  * Fixed Message#to_h for messages with map fields.
+  * Fixed memcpy() in binary gems to work for old glibc, without breaking the
+    build for non-glibc libc’s like musl.
+
+  Javascript
+  * Added compatibility tests for version 3.0.0.
+  * Added conformance tests.
+  * Fixed serialization of extensions: we need to emit a value even if it is
+    falsy (like the number 0).
+  * Use closurebuilder.py in favor of calcdeps.py for compiling JavaScript.
+
 2017-01-23 version 3.2.0 (C++/Java/Python/PHP/Ruby/Objective-C/C#/JavaScript/Lite)
   General
   * Added protoc version number to protoc plugin protocol. It can be used by

+ 5 - 1
Makefile.am

@@ -247,6 +247,7 @@ java_EXTRA_DIST=
   java/core/src/main/java/com/google/protobuf/MutabilityOracle.java                \
   java/core/src/main/java/com/google/protobuf/NioByteString.java                   \
   java/core/src/main/java/com/google/protobuf/Parser.java                          \
+  java/core/src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java    \
   java/core/src/main/java/com/google/protobuf/ProtobufArrayList.java               \
   java/core/src/main/java/com/google/protobuf/ProtocolMessageEnum.java             \
   java/core/src/main/java/com/google/protobuf/ProtocolStringList.java              \
@@ -645,6 +646,7 @@ php_EXTRA_DIST=                                                       \
   php/src/Google/Protobuf/Internal/EnumBuilderContext.php             \
   php/src/Google/Protobuf/Internal/GPBUtil.php                        \
   php/src/Google/Protobuf/Internal/FieldOptions_CType.php             \
+  php/src/Google/Protobuf/Internal/GPBDecodeException.php             \
   php/src/Google/Protobuf/descriptor.php                              \
   php/src/GPBMetadata/Google/Protobuf/Internal/Descriptor.php         \
   php/tests/array_test.php                                            \
@@ -655,6 +657,7 @@ php_EXTRA_DIST=                                                       \
   php/tests/map_field_test.php                                        \
   php/tests/memory_leak_test.php                                      \
   php/tests/php_implementation_test.php                               \
+  php/tests/proto/test_import_descriptor_proto.proto                  \
   php/tests/proto/test_include.proto                                  \
   php/tests/proto/test.proto                                          \
   php/tests/proto/test_prefix.proto                                   \
@@ -710,6 +713,7 @@ python_EXTRA_DIST=                                                           \
   python/google/protobuf/internal/packed_field_test.proto                    \
   python/google/protobuf/internal/proto_builder_test.py                      \
   python/google/protobuf/internal/python_message.py                          \
+  python/google/protobuf/internal/python_protobuf.cc                         \
   python/google/protobuf/internal/reflection_test.py                         \
   python/google/protobuf/internal/service_reflection_test.py                 \
   python/google/protobuf/internal/symbol_database_test.py                    \
@@ -752,13 +756,13 @@ python_EXTRA_DIST=                                                           \
   python/google/protobuf/pyext/message_module.cc                             \
   python/google/protobuf/pyext/proto2_api_test.proto                         \
   python/google/protobuf/pyext/python.proto                                  \
-  python/google/protobuf/pyext/python_protobuf.h                             \
   python/google/protobuf/pyext/repeated_composite_container.cc               \
   python/google/protobuf/pyext/repeated_composite_container.h                \
   python/google/protobuf/pyext/repeated_scalar_container.cc                  \
   python/google/protobuf/pyext/repeated_scalar_container.h                   \
   python/google/protobuf/pyext/safe_numerics.h                               \
   python/google/protobuf/pyext/scoped_pyobject_ptr.h                         \
+  python/google/protobuf/python_protobuf.h                                   \
   python/google/protobuf/reflection.py                                       \
   python/google/protobuf/service.py                                          \
   python/google/protobuf/service_reflection.py                               \

+ 1 - 1
Protobuf.podspec

@@ -5,7 +5,7 @@
 # dependent projects use the :git notation to refer to the library.
 Pod::Spec.new do |s|
   s.name     = 'Protobuf'
-  s.version  = '3.2.0'
+  s.version  = '3.3.0'
   s.summary  = 'Protocol Buffers v.3 runtime library for Objective-C.'
   s.homepage = 'https://github.com/google/protobuf'
   s.license  = '3-Clause BSD License'

+ 2 - 0
cmake/extract_includes.bat.in

@@ -36,6 +36,7 @@ copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\parser.h" in
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\php\php_generator.h" include\google\protobuf\compiler\php\php_generator.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.h" include\google\protobuf\compiler\plugin.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\plugin.pb.h" include\google\protobuf\compiler\plugin.pb.h
+copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\profile.pb.h" include\google\protobuf\compiler\profile.pb.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\python\python_generator.h" include\google\protobuf\compiler\python\python_generator.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\compiler\ruby\ruby_generator.h" include\google\protobuf\compiler\ruby\ruby_generator.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\descriptor.h" include\google\protobuf\descriptor.h
@@ -114,6 +115,7 @@ copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\text_format.h" includ
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\timestamp.pb.h" include\google\protobuf\timestamp.pb.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\type.pb.h" include\google\protobuf\type.pb.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\unknown_field_set.h" include\google\protobuf\unknown_field_set.h
+copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\delimited_message_util.h" include\google\protobuf\util\delimited_message_util.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\field_comparator.h" include\google\protobuf\util\field_comparator.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\field_mask_util.h" include\google\protobuf\util\field_mask_util.h
 copy "${PROTOBUF_SOURCE_WIN32_PATH}\..\src\google\protobuf\util\json_util.h" include\google\protobuf\util\json_util.h

+ 1 - 0
cmake/libprotoc.cmake

@@ -88,6 +88,7 @@ set(libprotoc_files
   ${protobuf_source_dir}/src/google/protobuf/compiler/php/php_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/plugin.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/plugin.pb.cc
+  ${protobuf_source_dir}/src/google/protobuf/compiler/profile.pb.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/python/python_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/ruby/ruby_generator.cc
   ${protobuf_source_dir}/src/google/protobuf/compiler/subprocess.cc

+ 4 - 1
cmake/tests.cmake

@@ -43,6 +43,9 @@ set(tests_protos
   google/protobuf/unittest_empty.proto
   google/protobuf/unittest_import.proto
   google/protobuf/unittest_import_public.proto
+  google/protobuf/unittest_lazy_dependencies.proto
+  google/protobuf/unittest_lazy_dependencies_custom_option.proto
+  google/protobuf/unittest_lazy_dependencies_enum.proto
   google/protobuf/unittest_lite_imports_nonlite.proto
   google/protobuf/unittest_mset.proto
   google/protobuf/unittest_mset_wire_format.proto
@@ -207,7 +210,7 @@ set(lite_test_files
   ${protobuf_source_dir}/src/google/protobuf/lite_unittest.cc
 )
 add_executable(lite-test ${lite_test_files} ${common_lite_test_files} ${lite_test_proto_files})
-target_link_libraries(lite-test libprotobuf-lite)
+target_link_libraries(lite-test libprotobuf-lite gmock_main)
 
 set(lite_arena_test_files
   ${protobuf_source_dir}/src/google/protobuf/lite_arena_unittest.cc

+ 1 - 1
configure.ac

@@ -17,7 +17,7 @@ AC_PREREQ(2.59)
 # In the SVN trunk, the version should always be the next anticipated release
 # version with the "-pre" suffix.  (We used to use "-SNAPSHOT" but this pushed
 # the size of one file name in the dist tarfile over the 99-char limit.)
-AC_INIT([Protocol Buffers],[3.2.0],[protobuf@googlegroups.com],[protobuf])
+AC_INIT([Protocol Buffers],[3.3.0],[protobuf@googlegroups.com],[protobuf])
 
 AM_MAINTAINER_MODE([enable])
 

+ 17 - 0
conformance/conformance_test.cc

@@ -708,6 +708,21 @@ bool ConformanceTestSuite::CheckSetEmpty(const set<string>& set_to_check,
   }
 }
 
+void ConformanceTestSuite::TestIllegalTags() {
+  // field num 0 is illegal
+  string nullfield[] = {
+    "\1DEADBEEF",
+    "\2\1\1",
+    "\3\4",
+    "\5DEAD"
+  };
+  for (int i = 0; i < 4; i++) {
+    string name = "IllegalZeroFieldNum_Case_0";
+    name.back() += i;
+    ExpectParseFailureForProto(nullfield[i], name, REQUIRED);
+  }
+}
+
 bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
                                     std::string* output) {
   runner_ = runner;
@@ -728,6 +743,8 @@ bool ConformanceTestSuite::RunSuite(ConformanceTestRunner* runner,
     TestPrematureEOFForType(static_cast<FieldDescriptor::Type>(i));
   }
 
+  TestIllegalTags();
+
   int64 kInt64Min = -9223372036854775808ULL;
   int64 kInt64Max = 9223372036854775807ULL;
   uint64 kUint64Max = 18446744073709551615ULL;

+ 1 - 0
conformance/conformance_test.h

@@ -201,6 +201,7 @@ class ConformanceTestSuite {
                                       const std::string& test_name,
                                       ConformanceLevel level);
   void TestPrematureEOFForType(google::protobuf::FieldDescriptor::Type type);
+  void TestIllegalTags();
   void TestValidDataForType(
       google::protobuf::FieldDescriptor::Type,
       std::vector<std::pair<std::string, std::string>> values);

+ 0 - 2
conformance/failure_list_cpp.txt

@@ -32,7 +32,6 @@ Recommended.JsonInput.TrailingCommaInAnObject
 Recommended.JsonInput.TrailingCommaInAnObjectWithNewlines
 Recommended.JsonInput.TrailingCommaInAnObjectWithSpace
 Recommended.JsonInput.TrailingCommaInAnObjectWithSpaceCommaSpace
-Required.ProtobufInput.PrematureEofBeforeKnownRepeatedValue.MESSAGE
 Required.ProtobufInput.PrematureEofInDelimitedDataForKnownNonRepeatedValue.MESSAGE
 Required.ProtobufInput.PrematureEofInDelimitedDataForKnownRepeatedValue.MESSAGE
 Required.ProtobufInput.PrematureEofInPackedField.BOOL
@@ -43,4 +42,3 @@ Required.ProtobufInput.PrematureEofInPackedField.SINT32
 Required.ProtobufInput.PrematureEofInPackedField.SINT64
 Required.ProtobufInput.PrematureEofInPackedField.UINT32
 Required.ProtobufInput.PrematureEofInPackedField.UINT64
-Required.ProtobufInput.PrematureEofInsideKnownRepeatedValue.MESSAGE

+ 4 - 0
conformance/failure_list_csharp.txt

@@ -0,0 +1,4 @@
+Required.ProtobufInput.IllegalZeroFieldNum_Case_0
+Required.ProtobufInput.IllegalZeroFieldNum_Case_1
+Required.ProtobufInput.IllegalZeroFieldNum_Case_2
+Required.ProtobufInput.IllegalZeroFieldNum_Case_3

+ 4 - 2
conformance/failure_list_objc.txt

@@ -1,4 +1,6 @@
-# All tests currently passing.
-#
 # JSON input or output tests are skipped (in conformance_objc.m) as mobile
 # platforms don't support JSON wire format to avoid code bloat.
+Required.ProtobufInput.IllegalZeroFieldNum_Case_0
+Required.ProtobufInput.IllegalZeroFieldNum_Case_1
+Required.ProtobufInput.IllegalZeroFieldNum_Case_2
+Required.ProtobufInput.IllegalZeroFieldNum_Case_3

+ 10 - 18
conformance/failure_list_php_c.txt

@@ -8,6 +8,7 @@ Recommended.JsonInput.DurationHas6FractionalDigits.Validator
 Recommended.JsonInput.DurationHas9FractionalDigits.Validator
 Recommended.JsonInput.DurationHasZeroFractionalDigit.Validator
 Recommended.JsonInput.Int64FieldBeString.Validator
+Recommended.JsonInput.MapFieldValueIsNull
 Recommended.JsonInput.OneofZeroBool.JsonOutput
 Recommended.JsonInput.OneofZeroBool.ProtobufOutput
 Recommended.JsonInput.OneofZeroBytes.JsonOutput
@@ -24,6 +25,8 @@ Recommended.JsonInput.OneofZeroUint32.JsonOutput
 Recommended.JsonInput.OneofZeroUint32.ProtobufOutput
 Recommended.JsonInput.OneofZeroUint64.JsonOutput
 Recommended.JsonInput.OneofZeroUint64.ProtobufOutput
+Recommended.JsonInput.RepeatedFieldMessageElementIsNull
+Recommended.JsonInput.RepeatedFieldPrimitiveElementIsNull
 Recommended.JsonInput.StringEndsWithEscapeChar
 Recommended.JsonInput.StringFieldSurrogateInWrongOrder
 Recommended.JsonInput.StringFieldUnpairedHighSurrogate
@@ -127,24 +130,12 @@ Required.JsonInput.Int32FieldStringValue.JsonOutput
 Required.JsonInput.Int32FieldStringValue.ProtobufOutput
 Required.JsonInput.Int32FieldStringValueEscaped.JsonOutput
 Required.JsonInput.Int32FieldStringValueEscaped.ProtobufOutput
-Required.JsonInput.Int32MapEscapedKey.JsonOutput
-Required.JsonInput.Int32MapEscapedKey.ProtobufOutput
-Required.JsonInput.Int32MapField.JsonOutput
-Required.JsonInput.Int32MapField.ProtobufOutput
 Required.JsonInput.Int64FieldMaxValue.JsonOutput
 Required.JsonInput.Int64FieldMaxValue.ProtobufOutput
 Required.JsonInput.Int64FieldMinValue.JsonOutput
 Required.JsonInput.Int64FieldMinValue.ProtobufOutput
-Required.JsonInput.Int64MapEscapedKey.JsonOutput
-Required.JsonInput.Int64MapEscapedKey.ProtobufOutput
-Required.JsonInput.Int64MapField.JsonOutput
-Required.JsonInput.Int64MapField.ProtobufOutput
 Required.JsonInput.MessageField.JsonOutput
 Required.JsonInput.MessageField.ProtobufOutput
-Required.JsonInput.MessageMapField.JsonOutput
-Required.JsonInput.MessageMapField.ProtobufOutput
-Required.JsonInput.MessageRepeatedField.JsonOutput
-Required.JsonInput.MessageRepeatedField.ProtobufOutput
 Required.JsonInput.OptionalBoolWrapper.JsonOutput
 Required.JsonInput.OptionalBoolWrapper.ProtobufOutput
 Required.JsonInput.OptionalBytesWrapper.JsonOutput
@@ -165,14 +156,13 @@ Required.JsonInput.OptionalUint64Wrapper.JsonOutput
 Required.JsonInput.OptionalUint64Wrapper.ProtobufOutput
 Required.JsonInput.OptionalWrapperTypesWithNonDefaultValue.JsonOutput
 Required.JsonInput.OptionalWrapperTypesWithNonDefaultValue.ProtobufOutput
-Required.JsonInput.PrimitiveRepeatedField.JsonOutput
-Required.JsonInput.PrimitiveRepeatedField.ProtobufOutput
 Required.JsonInput.RepeatedBoolWrapper.JsonOutput
 Required.JsonInput.RepeatedBoolWrapper.ProtobufOutput
 Required.JsonInput.RepeatedBytesWrapper.JsonOutput
 Required.JsonInput.RepeatedBytesWrapper.ProtobufOutput
 Required.JsonInput.RepeatedDoubleWrapper.JsonOutput
 Required.JsonInput.RepeatedDoubleWrapper.ProtobufOutput
+Required.JsonInput.RepeatedFieldWrongElementTypeExpectingMessagesGotInt
 Required.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt
 Required.JsonInput.RepeatedFloatWrapper.JsonOutput
 Required.JsonInput.RepeatedFloatWrapper.ProtobufOutput
@@ -186,9 +176,15 @@ Required.JsonInput.RepeatedUint32Wrapper.JsonOutput
 Required.JsonInput.RepeatedUint32Wrapper.ProtobufOutput
 Required.JsonInput.RepeatedUint64Wrapper.JsonOutput
 Required.JsonInput.RepeatedUint64Wrapper.ProtobufOutput
+Required.JsonInput.StringFieldEscape.JsonOutput
+Required.JsonInput.StringFieldEscape.ProtobufOutput
 Required.JsonInput.StringFieldNotAString
 Required.JsonInput.StringFieldSurrogatePair.JsonOutput
 Required.JsonInput.StringFieldSurrogatePair.ProtobufOutput
+Required.JsonInput.StringFieldUnicodeEscape.JsonOutput
+Required.JsonInput.StringFieldUnicodeEscape.ProtobufOutput
+Required.JsonInput.StringFieldUnicodeEscapeWithLowercaseHexLetters.JsonOutput
+Required.JsonInput.StringFieldUnicodeEscapeWithLowercaseHexLetters.ProtobufOutput
 Required.JsonInput.Struct.JsonOutput
 Required.JsonInput.Struct.ProtobufOutput
 Required.JsonInput.TimestampMaxValue.JsonOutput
@@ -203,12 +199,8 @@ Required.JsonInput.TimestampWithPositiveOffset.JsonOutput
 Required.JsonInput.TimestampWithPositiveOffset.ProtobufOutput
 Required.JsonInput.Uint32FieldMaxFloatValue.JsonOutput
 Required.JsonInput.Uint32FieldMaxFloatValue.ProtobufOutput
-Required.JsonInput.Uint32MapField.JsonOutput
-Required.JsonInput.Uint32MapField.ProtobufOutput
 Required.JsonInput.Uint64FieldMaxValue.JsonOutput
 Required.JsonInput.Uint64FieldMaxValue.ProtobufOutput
-Required.JsonInput.Uint64MapField.JsonOutput
-Required.JsonInput.Uint64MapField.ProtobufOutput
 Required.JsonInput.ValueAcceptBool.JsonOutput
 Required.JsonInput.ValueAcceptBool.ProtobufOutput
 Required.JsonInput.ValueAcceptFloat.JsonOutput

+ 4 - 0
conformance/failure_list_python.txt

@@ -11,3 +11,7 @@ Required.JsonInput.FloatFieldTooLarge
 Required.JsonInput.FloatFieldTooSmall
 Required.JsonInput.RepeatedFieldWrongElementTypeExpectingIntegersGotBool
 Required.JsonInput.TimestampJsonInputLowercaseT
+Required.ProtobufInput.IllegalZeroFieldNum_Case_0
+Required.ProtobufInput.IllegalZeroFieldNum_Case_1
+Required.ProtobufInput.IllegalZeroFieldNum_Case_2
+Required.ProtobufInput.IllegalZeroFieldNum_Case_3

+ 1 - 1
csharp/Google.Protobuf.Tools.nuspec

@@ -5,7 +5,7 @@
     <title>Google Protocol Buffers tools</title>
     <summary>Tools for Protocol Buffers - Google's data interchange format.</summary>
     <description>See project site for more info.</description>
-    <version>3.2.0</version>
+    <version>3.3.0</version>
     <authors>Google Inc.</authors>
     <owners>protobuf-packages</owners>
     <licenseUrl>https://github.com/google/protobuf/blob/master/LICENSE</licenseUrl>

+ 47 - 46
csharp/src/Google.Protobuf/Reflection/Descriptor.cs

@@ -95,57 +95,58 @@ namespace Google.Protobuf.Reflection {
             "X3ByZWZpeBgoIAEoCRJDChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsy",
             "JC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvbiI6CgxPcHRp",
             "bWl6ZU1vZGUSCQoFU1BFRUQQARINCglDT0RFX1NJWkUQAhIQCgxMSVRFX1JV",
-            "TlRJTUUQAyoJCOgHEICAgIACSgQIJhAnIuwBCg5NZXNzYWdlT3B0aW9ucxIm",
+            "TlRJTUUQAyoJCOgHEICAgIACSgQIJhAnIvIBCg5NZXNzYWdlT3B0aW9ucxIm",
             "ChdtZXNzYWdlX3NldF93aXJlX2Zvcm1hdBgBIAEoCDoFZmFsc2USLgofbm9f",
             "c3RhbmRhcmRfZGVzY3JpcHRvcl9hY2Nlc3NvchgCIAEoCDoFZmFsc2USGQoK",
             "ZGVwcmVjYXRlZBgDIAEoCDoFZmFsc2USEQoJbWFwX2VudHJ5GAcgASgIEkMK",
             "FHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1",
-            "Zi5VbmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAJKBAgIEAkingMKDEZp",
-            "ZWxkT3B0aW9ucxI6CgVjdHlwZRgBIAEoDjIjLmdvb2dsZS5wcm90b2J1Zi5G",
-            "aWVsZE9wdGlvbnMuQ1R5cGU6BlNUUklORxIOCgZwYWNrZWQYAiABKAgSPwoG",
-            "anN0eXBlGAYgASgOMiQuZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0aW9ucy5K",
-            "U1R5cGU6CUpTX05PUk1BTBITCgRsYXp5GAUgASgIOgVmYWxzZRIZCgpkZXBy",
-            "ZWNhdGVkGAMgASgIOgVmYWxzZRITCgR3ZWFrGAogASgIOgVmYWxzZRJDChR1",
-            "bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYu",
-            "VW5pbnRlcnByZXRlZE9wdGlvbiIvCgVDVHlwZRIKCgZTVFJJTkcQABIICgRD",
-            "T1JEEAESEAoMU1RSSU5HX1BJRUNFEAIiNQoGSlNUeXBlEg0KCUpTX05PUk1B",
-            "TBAAEg0KCUpTX1NUUklORxABEg0KCUpTX05VTUJFUhACKgkI6AcQgICAgAJK",
-            "BAgEEAUiXgoMT25lb2ZPcHRpb25zEkMKFHVuaW50ZXJwcmV0ZWRfb3B0aW9u",
+            "Zi5VbmludGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAJKBAgIEAlKBAgJEAoi",
+            "ngMKDEZpZWxkT3B0aW9ucxI6CgVjdHlwZRgBIAEoDjIjLmdvb2dsZS5wcm90",
+            "b2J1Zi5GaWVsZE9wdGlvbnMuQ1R5cGU6BlNUUklORxIOCgZwYWNrZWQYAiAB",
+            "KAgSPwoGanN0eXBlGAYgASgOMiQuZ29vZ2xlLnByb3RvYnVmLkZpZWxkT3B0",
+            "aW9ucy5KU1R5cGU6CUpTX05PUk1BTBITCgRsYXp5GAUgASgIOgVmYWxzZRIZ",
+            "CgpkZXByZWNhdGVkGAMgASgIOgVmYWxzZRITCgR3ZWFrGAogASgIOgVmYWxz",
+            "ZRJDChR1bmludGVycHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJv",
+            "dG9idWYuVW5pbnRlcnByZXRlZE9wdGlvbiIvCgVDVHlwZRIKCgZTVFJJTkcQ",
+            "ABIICgRDT1JEEAESEAoMU1RSSU5HX1BJRUNFEAIiNQoGSlNUeXBlEg0KCUpT",
+            "X05PUk1BTBAAEg0KCUpTX1NUUklORxABEg0KCUpTX05VTUJFUhACKgkI6AcQ",
+            "gICAgAJKBAgEEAUiXgoMT25lb2ZPcHRpb25zEkMKFHVuaW50ZXJwcmV0ZWRf",
+            "b3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVk",
+            "T3B0aW9uKgkI6AcQgICAgAIikwEKC0VudW1PcHRpb25zEhMKC2FsbG93X2Fs",
+            "aWFzGAIgASgIEhkKCmRlcHJlY2F0ZWQYAyABKAg6BWZhbHNlEkMKFHVuaW50",
+            "ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5Vbmlu",
+            "dGVycHJldGVkT3B0aW9uKgkI6AcQgICAgAJKBAgFEAYifQoQRW51bVZhbHVl",
+            "T3B0aW9ucxIZCgpkZXByZWNhdGVkGAEgASgIOgVmYWxzZRJDChR1bmludGVy",
+            "cHJldGVkX29wdGlvbhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRl",
+            "cnByZXRlZE9wdGlvbioJCOgHEICAgIACInsKDlNlcnZpY2VPcHRpb25zEhkK",
+            "CmRlcHJlY2F0ZWQYISABKAg6BWZhbHNlEkMKFHVuaW50ZXJwcmV0ZWRfb3B0",
+            "aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0",
+            "aW9uKgkI6AcQgICAgAIirQIKDU1ldGhvZE9wdGlvbnMSGQoKZGVwcmVjYXRl",
+            "ZBghIAEoCDoFZmFsc2USXwoRaWRlbXBvdGVuY3lfbGV2ZWwYIiABKA4yLy5n",
+            "b29nbGUucHJvdG9idWYuTWV0aG9kT3B0aW9ucy5JZGVtcG90ZW5jeUxldmVs",
+            "OhNJREVNUE9URU5DWV9VTktOT1dOEkMKFHVuaW50ZXJwcmV0ZWRfb3B0aW9u",
             "GOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9u",
-            "KgkI6AcQgICAgAIijQEKC0VudW1PcHRpb25zEhMKC2FsbG93X2FsaWFzGAIg",
-            "ASgIEhkKCmRlcHJlY2F0ZWQYAyABKAg6BWZhbHNlEkMKFHVuaW50ZXJwcmV0",
-            "ZWRfb3B0aW9uGOcHIAMoCzIkLmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJl",
-            "dGVkT3B0aW9uKgkI6AcQgICAgAIifQoQRW51bVZhbHVlT3B0aW9ucxIZCgpk",
-            "ZXByZWNhdGVkGAEgASgIOgVmYWxzZRJDChR1bmludGVycHJldGVkX29wdGlv",
-            "bhjnByADKAsyJC5nb29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlv",
-            "bioJCOgHEICAgIACInsKDlNlcnZpY2VPcHRpb25zEhkKCmRlcHJlY2F0ZWQY",
-            "ISABKAg6BWZhbHNlEkMKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIk",
-            "Lmdvb2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uKgkI6AcQgICA",
-            "gAIirQIKDU1ldGhvZE9wdGlvbnMSGQoKZGVwcmVjYXRlZBghIAEoCDoFZmFs",
-            "c2USXwoRaWRlbXBvdGVuY3lfbGV2ZWwYIiABKA4yLy5nb29nbGUucHJvdG9i",
-            "dWYuTWV0aG9kT3B0aW9ucy5JZGVtcG90ZW5jeUxldmVsOhNJREVNUE9URU5D",
-            "WV9VTktOT1dOEkMKFHVuaW50ZXJwcmV0ZWRfb3B0aW9uGOcHIAMoCzIkLmdv",
-            "b2dsZS5wcm90b2J1Zi5VbmludGVycHJldGVkT3B0aW9uIlAKEElkZW1wb3Rl",
-            "bmN5TGV2ZWwSFwoTSURFTVBPVEVOQ1lfVU5LTk9XThAAEhMKD05PX1NJREVf",
-            "RUZGRUNUUxABEg4KCklERU1QT1RFTlQQAioJCOgHEICAgIACIp4CChNVbmlu",
-            "dGVycHJldGVkT3B0aW9uEjsKBG5hbWUYAiADKAsyLS5nb29nbGUucHJvdG9i",
-            "dWYuVW5pbnRlcnByZXRlZE9wdGlvbi5OYW1lUGFydBIYChBpZGVudGlmaWVy",
-            "X3ZhbHVlGAMgASgJEhoKEnBvc2l0aXZlX2ludF92YWx1ZRgEIAEoBBIaChJu",
-            "ZWdhdGl2ZV9pbnRfdmFsdWUYBSABKAMSFAoMZG91YmxlX3ZhbHVlGAYgASgB",
-            "EhQKDHN0cmluZ192YWx1ZRgHIAEoDBIXCg9hZ2dyZWdhdGVfdmFsdWUYCCAB",
-            "KAkaMwoITmFtZVBhcnQSEQoJbmFtZV9wYXJ0GAEgAigJEhQKDGlzX2V4dGVu",
-            "c2lvbhgCIAIoCCLVAQoOU291cmNlQ29kZUluZm8SOgoIbG9jYXRpb24YASAD",
-            "KAsyKC5nb29nbGUucHJvdG9idWYuU291cmNlQ29kZUluZm8uTG9jYXRpb24a",
-            "hgEKCExvY2F0aW9uEhAKBHBhdGgYASADKAVCAhABEhAKBHNwYW4YAiADKAVC",
-            "AhABEhgKEGxlYWRpbmdfY29tbWVudHMYAyABKAkSGQoRdHJhaWxpbmdfY29t",
-            "bWVudHMYBCABKAkSIQoZbGVhZGluZ19kZXRhY2hlZF9jb21tZW50cxgGIAMo",
-            "CSKnAQoRR2VuZXJhdGVkQ29kZUluZm8SQQoKYW5ub3RhdGlvbhgBIAMoCzIt",
-            "Lmdvb2dsZS5wcm90b2J1Zi5HZW5lcmF0ZWRDb2RlSW5mby5Bbm5vdGF0aW9u",
-            "Gk8KCkFubm90YXRpb24SEAoEcGF0aBgBIAMoBUICEAESEwoLc291cmNlX2Zp",
-            "bGUYAiABKAkSDQoFYmVnaW4YAyABKAUSCwoDZW5kGAQgASgFQowBChNjb20u",
-            "Z29vZ2xlLnByb3RvYnVmQhBEZXNjcmlwdG9yUHJvdG9zSAFaPmdpdGh1Yi5j",
-            "b20vZ29sYW5nL3Byb3RvYnVmL3Byb3RvYy1nZW4tZ28vZGVzY3JpcHRvcjtk",
-            "ZXNjcmlwdG9yogIDR1BCqgIaR29vZ2xlLlByb3RvYnVmLlJlZmxlY3Rpb24="));
+            "IlAKEElkZW1wb3RlbmN5TGV2ZWwSFwoTSURFTVBPVEVOQ1lfVU5LTk9XThAA",
+            "EhMKD05PX1NJREVfRUZGRUNUUxABEg4KCklERU1QT1RFTlQQAioJCOgHEICA",
+            "gIACIp4CChNVbmludGVycHJldGVkT3B0aW9uEjsKBG5hbWUYAiADKAsyLS5n",
+            "b29nbGUucHJvdG9idWYuVW5pbnRlcnByZXRlZE9wdGlvbi5OYW1lUGFydBIY",
+            "ChBpZGVudGlmaWVyX3ZhbHVlGAMgASgJEhoKEnBvc2l0aXZlX2ludF92YWx1",
+            "ZRgEIAEoBBIaChJuZWdhdGl2ZV9pbnRfdmFsdWUYBSABKAMSFAoMZG91Ymxl",
+            "X3ZhbHVlGAYgASgBEhQKDHN0cmluZ192YWx1ZRgHIAEoDBIXCg9hZ2dyZWdh",
+            "dGVfdmFsdWUYCCABKAkaMwoITmFtZVBhcnQSEQoJbmFtZV9wYXJ0GAEgAigJ",
+            "EhQKDGlzX2V4dGVuc2lvbhgCIAIoCCLVAQoOU291cmNlQ29kZUluZm8SOgoI",
+            "bG9jYXRpb24YASADKAsyKC5nb29nbGUucHJvdG9idWYuU291cmNlQ29kZUlu",
+            "Zm8uTG9jYXRpb24ahgEKCExvY2F0aW9uEhAKBHBhdGgYASADKAVCAhABEhAK",
+            "BHNwYW4YAiADKAVCAhABEhgKEGxlYWRpbmdfY29tbWVudHMYAyABKAkSGQoR",
+            "dHJhaWxpbmdfY29tbWVudHMYBCABKAkSIQoZbGVhZGluZ19kZXRhY2hlZF9j",
+            "b21tZW50cxgGIAMoCSKnAQoRR2VuZXJhdGVkQ29kZUluZm8SQQoKYW5ub3Rh",
+            "dGlvbhgBIAMoCzItLmdvb2dsZS5wcm90b2J1Zi5HZW5lcmF0ZWRDb2RlSW5m",
+            "by5Bbm5vdGF0aW9uGk8KCkFubm90YXRpb24SEAoEcGF0aBgBIAMoBUICEAES",
+            "EwoLc291cmNlX2ZpbGUYAiABKAkSDQoFYmVnaW4YAyABKAUSCwoDZW5kGAQg",
+            "ASgFQowBChNjb20uZ29vZ2xlLnByb3RvYnVmQhBEZXNjcmlwdG9yUHJvdG9z",
+            "SAFaPmdpdGh1Yi5jb20vZ29sYW5nL3Byb3RvYnVmL3Byb3RvYy1nZW4tZ28v",
+            "ZGVzY3JpcHRvcjtkZXNjcmlwdG9yogIDR1BCqgIaR29vZ2xlLlByb3RvYnVm",
+            "LlJlZmxlY3Rpb24="));
       descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
           new pbr::FileDescriptor[] { },
           new pbr::GeneratedClrTypeInfo(null, new pbr::GeneratedClrTypeInfo[] {

+ 14 - 1
csharp/src/Google.Protobuf/WellKnownTypes/Duration.cs

@@ -46,6 +46,8 @@ namespace Google.Protobuf.WellKnownTypes {
   /// two Timestamp values is a Duration and it can be added or subtracted
   /// from a Timestamp. Range is approximately +-10,000 years.
   ///
+  /// # Examples
+  ///
   /// Example 1: Compute Duration from two Timestamps in pseudo code.
   ///
   ///     Timestamp start = ...;
@@ -85,6 +87,16 @@ namespace Google.Protobuf.WellKnownTypes {
   ///     td = datetime.timedelta(days=3, minutes=10)
   ///     duration = Duration()
   ///     duration.FromTimedelta(td)
+  ///
+  /// # JSON Mapping
+  ///
+  /// In JSON format, the Duration type is encoded as a string rather than an
+  /// object, where the string ends in the suffix "s" (indicating seconds) and
+  /// is preceded by the number of seconds, with nanoseconds expressed as
+  /// fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+  /// encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+  /// be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+  /// microsecond should be expressed in JSON format as "3.000001s".
   /// </summary>
   public sealed partial class Duration : pb::IMessage<Duration> {
     private static readonly pb::MessageParser<Duration> _parser = new pb::MessageParser<Duration>(() => new Duration());
@@ -124,7 +136,8 @@ namespace Google.Protobuf.WellKnownTypes {
     private long seconds_;
     /// <summary>
     /// Signed seconds of the span of time. Must be from -315,576,000,000
-    /// to +315,576,000,000 inclusive.
+    /// to +315,576,000,000 inclusive. Note: these bounds are computed from:
+    /// 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
     /// </summary>
     [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
     public long Seconds {

+ 25 - 0
csharp/src/Google.Protobuf/WellKnownTypes/Timestamp.cs

@@ -51,6 +51,8 @@ namespace Google.Protobuf.WellKnownTypes {
   /// and from  RFC 3339 date strings.
   /// See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
   ///
+  /// # Examples
+  ///
   /// Example 1: Compute Timestamp from POSIX `time()`.
   ///
   ///     Timestamp timestamp;
@@ -89,6 +91,29 @@ namespace Google.Protobuf.WellKnownTypes {
   ///
   ///     timestamp = Timestamp()
   ///     timestamp.GetCurrentTime()
+  ///
+  /// # JSON Mapping
+  ///
+  /// In JSON format, the Timestamp type is encoded as a string in the
+  /// [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+  /// format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+  /// where {year} is always expressed using four digits while {month}, {day},
+  /// {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+  /// seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+  /// are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+  /// is required, though only UTC (as indicated by "Z") is presently supported.
+  ///
+  /// For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+  /// 01:30 UTC on January 15, 2017.
+  ///
+  /// In JavaScript, one can convert a Date object to this format using the
+  /// standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
+  /// method. In Python, a standard `datetime.datetime` object can be converted
+  /// to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
+  /// with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
+  /// can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
+  /// http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime())
+  /// to obtain a formatter capable of generating timestamps in this format.
   /// </summary>
   public sealed partial class Timestamp : pb::IMessage<Timestamp> {
     private static readonly pb::MessageParser<Timestamp> _parser = new pb::MessageParser<Timestamp>(() => new Timestamp());

+ 1 - 1
csharp/src/Google.Protobuf/project.json

@@ -1,5 +1,5 @@
 {
-  "version": "3.2.0",
+  "version": "3.3.0",
   "title": "Google Protocol Buffers",
   "description": "See project site for more info.",
   "authors": [ "Google Inc." ],

+ 8 - 13
generate_descriptor_proto.sh

@@ -41,6 +41,11 @@ declare -a RUNTIME_PROTO_FILES=(\
   google/protobuf/type.proto \
   google/protobuf/wrappers.proto)
 
+declare -a COMPILER_PROTO_FILES=(\
+  google/protobuf/compiler/plugin.proto \
+  google/protobuf/compiler/profile.proto \
+)
+
 CORE_PROTO_IS_CORRECT=0
 PROCESS_ROUND=1
 TMP=$(mktemp -d)
@@ -57,9 +62,9 @@ do
   fi
 
   ./protoc --cpp_out=dllexport_decl=LIBPROTOBUF_EXPORT:$TMP ${RUNTIME_PROTO_FILES[@]} && \
-  ./protoc --cpp_out=dllexport_decl=LIBPROTOC_EXPORT:$TMP google/protobuf/compiler/plugin.proto
+  ./protoc --cpp_out=dllexport_decl=LIBPROTOC_EXPORT:$TMP ${COMPILER_PROTO_FILES[@]}
 
-  for PROTO_FILE in ${RUNTIME_PROTO_FILES[@]}; do
+  for PROTO_FILE in ${RUNTIME_PROTO_FILES[@]} ${COMPILER_PROTO_FILES[@]}; do
     BASE_NAME=${PROTO_FILE%.*}
     diff ${BASE_NAME}.pb.h $TMP/${BASE_NAME}.pb.h > /dev/null
     if test $? -ne 0; then
@@ -71,24 +76,14 @@ do
     fi
   done
 
-  diff google/protobuf/compiler/plugin.pb.h $TMP/google/protobuf/compiler/plugin.pb.h > /dev/null
-  if test $? -ne 0; then
-    CORE_PROTO_IS_CORRECT=0
-  fi
-  diff google/protobuf/compiler/plugin.pb.cc $TMP/google/protobuf/compiler/plugin.pb.cc > /dev/null
-  if test $? -ne 0; then
-    CORE_PROTO_IS_CORRECT=0
-  fi
-
   # Only override the output if the files are different to avoid re-compilation
   # of the protoc.
   if [ $CORE_PROTO_IS_CORRECT -ne 1 ]; then
-    for PROTO_FILE in ${RUNTIME_PROTO_FILES[@]}; do
+    for PROTO_FILE in ${RUNTIME_PROTO_FILES[@]} ${COMPILER_PROTO_FILES[@]}; do
       BASE_NAME=${PROTO_FILE%.*}
       mv $TMP/${BASE_NAME}.pb.h ${BASE_NAME}.pb.h
       mv $TMP/${BASE_NAME}.pb.cc ${BASE_NAME}.pb.cc
     done
-    mv $TMP/google/protobuf/compiler/plugin.pb.* google/protobuf/compiler/
   fi
 
   PROCESS_ROUND=$((PROCESS_ROUND + 1))

+ 6 - 1
java/core/pom.xml

@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.2.0</version>
+    <version>3.3.0</version>
   </parent>
 
   <artifactId>protobuf-java</artifactId>
@@ -34,6 +34,11 @@
       <artifactId>easymockclassextension</artifactId>
       <scope>test</scope>
     </dependency>
+    <dependency>
+      <groupId>com.google.truth</groupId>
+      <artifactId>truth</artifactId>
+      <scope>test</scope>
+    </dependency>
   </dependencies>
 
   <build>

+ 2 - 2
java/core/src/main/java/com/google/protobuf/AbstractMessage.java

@@ -34,7 +34,6 @@ import com.google.protobuf.Descriptors.EnumValueDescriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
 import com.google.protobuf.Descriptors.OneofDescriptor;
 import com.google.protobuf.Internal.EnumLite;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Arrays;
@@ -328,7 +327,8 @@ public abstract class AbstractMessage
       extends AbstractMessageLite.Builder
       implements Message.Builder {
     // The compiler produces an error if this is not declared explicitly.
-    // Method isn't abstract to bypass Java 1.6 compiler issue http://bugs.java.com/view_bug.do?bug_id=6908259
+    // Method isn't abstract to bypass Java 1.6 compiler issue:
+    //     http://bugs.java.com/view_bug.do?bug_id=6908259
     @Override
     public BuilderType clone() {
       throw new UnsupportedOperationException("clone() should be implemented in subclasses.");

+ 11 - 10
java/core/src/main/java/com/google/protobuf/AbstractMessageLite.java

@@ -30,6 +30,8 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.Internal.checkNotNull;
+
 import java.io.FilterInputStream;
 import java.io.IOException;
 import java.io.InputStream;
@@ -351,22 +353,23 @@ public abstract class AbstractMessageLite<
      */
     protected static <T> void addAll(final Iterable<T> values,
                                      final Collection<? super T> list) {
-      if (values == null) {
-        throw new NullPointerException();
-      }
+      checkNotNull(values);
       if (values instanceof LazyStringList) {
         // For StringOrByteStringLists, check the underlying elements to avoid
         // forcing conversions of ByteStrings to Strings.
+        // TODO(dweis): Could we just prohibit nulls in all protobuf lists and get rid of this? Is
+        // if even possible to hit this condition as all protobuf methods check for null first,
+        // right?
         checkForNullValues(((LazyStringList) values).getUnderlyingElements());
         list.addAll((Collection<T>) values);
       } else if (values instanceof Collection) {
-        checkForNullValues(values);
+        if (!(values instanceof PrimitiveNonBoxingCollection)) {
+          checkForNullValues(values);
+        }
         list.addAll((Collection<T>) values);
       } else {
         for (final T value : values) {
-          if (value == null) {
-            throw new NullPointerException();
-          }
+          checkNotNull(value);
           list.add(value);
         }
       }
@@ -374,9 +377,7 @@ public abstract class AbstractMessageLite<
 
     private static void checkForNullValues(final Iterable<?> values) {
       for (final Object value : values) {
-        if (value == null) {
-          throw new NullPointerException();
-        }
+        checkNotNull(value);
       }
     }
   }

+ 25 - 1
java/core/src/main/java/com/google/protobuf/AbstractParser.java

@@ -31,9 +31,9 @@
 package com.google.protobuf;
 
 import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream;
-
 import java.io.IOException;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
 
 /**
  * A partial implementation of the {@link Parser} interface which implements
@@ -130,6 +130,30 @@ public abstract class AbstractParser<MessageType extends MessageLite>
     return parseFrom(data, EMPTY_REGISTRY);
   }
 
+  @Override
+  public MessageType parseFrom(ByteBuffer data, ExtensionRegistryLite extensionRegistry)
+      throws InvalidProtocolBufferException {
+    MessageType message;
+    try {
+      CodedInputStream input = CodedInputStream.newInstance(data);
+      message = parsePartialFrom(input, extensionRegistry);
+      try {
+        input.checkLastTagWas(0);
+      } catch (InvalidProtocolBufferException e) {
+        throw e.setUnfinishedMessage(message);
+      }
+    } catch (InvalidProtocolBufferException e) {
+      throw e;
+    }
+
+    return checkMessageInitialized(message);
+  }
+
+  @Override
+  public MessageType parseFrom(ByteBuffer data) throws InvalidProtocolBufferException {
+    return parseFrom(data, EMPTY_REGISTRY);
+  }
+
   @Override
   public MessageType parsePartialFrom(
       byte[] data, int off, int len, ExtensionRegistryLite extensionRegistry)

+ 5 - 7
java/core/src/main/java/com/google/protobuf/BooleanArrayList.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.Internal.BooleanList;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.Internal.BooleanList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.RandomAccess;
@@ -41,9 +42,8 @@ import java.util.RandomAccess;
  *
  * @author dweis@google.com (Daniel Weis)
  */
-final class BooleanArrayList
-    extends AbstractProtobufList<Boolean>
-    implements BooleanList, RandomAccess {
+final class BooleanArrayList extends AbstractProtobufList<Boolean>
+    implements BooleanList, RandomAccess, PrimitiveNonBoxingCollection {
 
   private static final BooleanArrayList EMPTY_LIST = new BooleanArrayList();
   static {
@@ -198,9 +198,7 @@ final class BooleanArrayList
   public boolean addAll(Collection<? extends Boolean> collection) {
     ensureIsMutable();
 
-    if (collection == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(collection);
 
     // We specialize when adding another BooleanArrayList to avoid boxing elements.
     if (!(collection instanceof BooleanArrayList)) {

+ 2 - 2
java/core/src/main/java/com/google/protobuf/CodedInputStream.java

@@ -355,8 +355,8 @@ public abstract class CodedInputStream {
    * <p>Set the maximum message size. In order to prevent malicious messages from exhausting memory
    * or causing integer overflows, {@code CodedInputStream} limits how large a message may be. The
    * default limit is {@code Integer.MAX_INT}. You should set this limit as small as you can without
-   * harming your app's functionality. Note that size limits only apply when reading from an
-   * {@code InputStream}, not when constructed around a raw byte array.
+   * harming your app's functionality. Note that size limits only apply when reading from an {@code
+   * InputStream}, not when constructed around a raw byte array.
    *
    * <p>If you want to read several messages from a single CodedInputStream, you could call {@link
    * #resetSizeCounter()} after each one to avoid hitting the size limit.

+ 2 - 2
java/core/src/main/java/com/google/protobuf/CodedOutputStream.java

@@ -184,7 +184,7 @@ public abstract class CodedOutputStream extends ByteOutput {
    *     maps are sorted on the lexicographical order of the UTF8 encoded keys.
    * </ul>
    */
-  void useDeterministicSerialization() {
+  public void useDeterministicSerialization() {
     serializationDeterministic = true;
   }
 
@@ -1854,7 +1854,7 @@ public abstract class CodedOutputStream extends ByteOutput {
     }
 
     static boolean isSupported() {
-      return UnsafeUtil.hasUnsafeByteBufferOperations();
+      return UnsafeUtil.hasUnsafeByteBufferOperations() && UnsafeUtil.hasUnsafeCopyMemory();
     }
 
     @Override

+ 3 - 4
java/core/src/main/java/com/google/protobuf/Descriptors.java

@@ -30,9 +30,10 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.Internal.checkNotNull;
+
 import com.google.protobuf.DescriptorProtos.*;
 import com.google.protobuf.Descriptors.FileDescriptor.Syntax;
-
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -682,9 +683,7 @@ public final class Descriptors {
 
     /** Determines if the given field name is reserved. */
     public boolean isReservedName(final String name) {
-      if (name == null) {
-        throw new NullPointerException();
-      }
+      checkNotNull(name);
       for (final String reservedName : proto.getReservedNameList()) {
         if (reservedName.equals(name)) {
           return true;

+ 5 - 7
java/core/src/main/java/com/google/protobuf/DoubleArrayList.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.Internal.DoubleList;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.Internal.DoubleList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.RandomAccess;
@@ -41,9 +42,8 @@ import java.util.RandomAccess;
  *
  * @author dweis@google.com (Daniel Weis)
  */
-final class DoubleArrayList
-    extends AbstractProtobufList<Double>
-    implements DoubleList, RandomAccess {
+final class DoubleArrayList extends AbstractProtobufList<Double>
+    implements DoubleList, RandomAccess, PrimitiveNonBoxingCollection {
 
   private static final DoubleArrayList EMPTY_LIST = new DoubleArrayList();
   static {
@@ -199,9 +199,7 @@ final class DoubleArrayList
   public boolean addAll(Collection<? extends Double> collection) {
     ensureIsMutable();
 
-    if (collection == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(collection);
 
     // We specialize when adding another DoubleArrayList to avoid boxing elements.
     if (!(collection instanceof DoubleArrayList)) {

+ 3 - 4
java/core/src/main/java/com/google/protobuf/DynamicMessage.java

@@ -30,11 +30,12 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.Internal.checkNotNull;
+
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
 import com.google.protobuf.Descriptors.OneofDescriptor;
-
 import java.io.IOException;
 import java.io.InputStream;
 import java.util.Collections;
@@ -631,9 +632,7 @@ public final class DynamicMessage extends AbstractMessage {
     /** Verifies that the value is EnumValueDescriptor and matches Enum Type. */
     private void ensureSingularEnumValueDescriptor(
         FieldDescriptor field, Object value) {
-      if (value == null) {
-        throw new NullPointerException();
-      }
+      checkNotNull(value);
       if (!(value instanceof EnumValueDescriptor)) {
         throw new IllegalArgumentException(
           "DynamicMessage should use EnumValueDescriptor to set Enum Value.");

+ 3 - 4
java/core/src/main/java/com/google/protobuf/FieldSet.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.LazyField.LazyIterator;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.LazyField.LazyIterator;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -385,9 +386,7 @@ final class FieldSet<FieldDescriptorType extends
    */
   private static void verifyType(final WireFormat.FieldType type,
                                  final Object value) {
-    if (value == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(value);
 
     boolean isValid = false;
     switch (type.getJavaType()) {

+ 5 - 7
java/core/src/main/java/com/google/protobuf/FloatArrayList.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.Internal.FloatList;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.Internal.FloatList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.RandomAccess;
@@ -41,9 +42,8 @@ import java.util.RandomAccess;
  *
  * @author dweis@google.com (Daniel Weis)
  */
-final class FloatArrayList
-    extends AbstractProtobufList<Float>
-    implements FloatList, RandomAccess {
+final class FloatArrayList extends AbstractProtobufList<Float>
+    implements FloatList, RandomAccess, PrimitiveNonBoxingCollection {
 
   private static final FloatArrayList EMPTY_LIST = new FloatArrayList();
   static {
@@ -198,9 +198,7 @@ final class FloatArrayList
   public boolean addAll(Collection<? extends Float> collection) {
     ensureIsMutable();
 
-    if (collection == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(collection);
 
     // We specialize when adding another FloatArrayList to avoid boxing elements.
     if (!(collection instanceof FloatArrayList)) {

+ 20 - 2
java/core/src/main/java/com/google/protobuf/GeneratedMessageLite.java

@@ -34,6 +34,7 @@ import com.google.protobuf.AbstractMessageLite.Builder.LimitedInputStream;
 import com.google.protobuf.GeneratedMessageLite.EqualsVisitor.NotEqualsException;
 import com.google.protobuf.Internal.BooleanList;
 import com.google.protobuf.Internal.DoubleList;
+import com.google.protobuf.Internal.EnumLiteMap;
 import com.google.protobuf.Internal.FloatList;
 import com.google.protobuf.Internal.IntList;
 import com.google.protobuf.Internal.LongList;
@@ -45,6 +46,7 @@ import java.io.ObjectStreamException;
 import java.io.Serializable;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Iterator;
@@ -136,6 +138,7 @@ public abstract class GeneratedMessageLite<
       return false;
     }
 
+
     try {
       visit(EqualsVisitor.INSTANCE, (MessageType) other);
     } catch (NotEqualsException e) {
@@ -1154,6 +1157,7 @@ public abstract class GeneratedMessageLite<
     }
   }
 
+
   /**
    * Lite equivalent to {@link GeneratedMessage.GeneratedExtension}.
    *
@@ -1527,6 +1531,20 @@ public abstract class GeneratedMessageLite<
     return message;
   }
 
+  // Validates last tag.
+  protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom(
+      T defaultInstance, ByteBuffer data, ExtensionRegistryLite extensionRegistry)
+      throws InvalidProtocolBufferException {
+    return checkMessageInitialized(
+        parseFrom(defaultInstance, CodedInputStream.newInstance(data), extensionRegistry));
+  }
+
+  // Validates last tag.
+  protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom(
+      T defaultInstance, ByteBuffer data) throws InvalidProtocolBufferException {
+    return parseFrom(defaultInstance, data, ExtensionRegistryLite.getEmptyRegistry());
+  }
+
   // Validates last tag.
   protected static <T extends GeneratedMessageLite<T, ?>> T parseFrom(
       T defaultInstance, ByteString data)
@@ -1979,13 +1997,13 @@ public abstract class GeneratedMessageLite<
   /**
    * Implements hashCode by accumulating state.
    */
-  private static class HashCodeVisitor implements Visitor {
+  static class HashCodeVisitor implements Visitor {
 
     // The caller must ensure that the visitor is invoked parameterized with this and this such that
     // other is this. This is required due to how oneof cases are handled. See the class comment
     // on Visitor for more information.
 
-    private int hashCode = 0;
+    int hashCode = 0;
 
     @Override
     public boolean visitBoolean(

+ 1 - 1
java/core/src/main/java/com/google/protobuf/GeneratedMessageV3.java

@@ -41,7 +41,7 @@ import com.google.protobuf.Descriptors.OneofDescriptor;
 // class without breaking binary compatibility with old generated code that still subclasses
 // the old GeneratedMessageV3 class. To allow these different GeneratedMessageV3V? classes to
 // interoperate (e.g., a GeneratedMessageV3V3 object has a message extension field whose class
-// type is GeneratedMessageV3V4), these classes still share a common parent class AbstarctMessage
+// type is GeneratedMessageV3V4), these classes still share a common parent class AbstractMessage
 // and are using the same GeneratedMessage.GeneratedExtension class for extension definitions.
 // Since this class becomes GeneratedMessageV3V? in opensource, we have to add an import here
 // to be able to use GeneratedMessage.GeneratedExtension. The GeneratedExtension definition in

+ 5 - 7
java/core/src/main/java/com/google/protobuf/IntArrayList.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.Internal.IntList;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.Internal.IntList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.RandomAccess;
@@ -41,9 +42,8 @@ import java.util.RandomAccess;
  *
  * @author dweis@google.com (Daniel Weis)
  */
-final class IntArrayList
-    extends AbstractProtobufList<Integer>
-    implements IntList, RandomAccess {
+final class IntArrayList extends AbstractProtobufList<Integer>
+    implements IntList, RandomAccess, PrimitiveNonBoxingCollection {
 
   private static final IntArrayList EMPTY_LIST = new IntArrayList();
   static {
@@ -198,9 +198,7 @@ final class IntArrayList
   public boolean addAll(Collection<? extends Integer> collection) {
     ensureIsMutable();
 
-    if (collection == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(collection);
 
     // We specialize when adding another IntArrayList to avoid boxing elements.
     if (!(collection instanceof IntArrayList)) {

+ 15 - 0
java/core/src/main/java/com/google/protobuf/Internal.java

@@ -59,6 +59,16 @@ public final class Internal {
   static final Charset UTF_8 = Charset.forName("UTF-8");
   static final Charset ISO_8859_1 = Charset.forName("ISO-8859-1");
 
+  /**
+   * Throws an appropriate {@link NullPointerException} if the given objects is {@code null}.
+   */
+  static <T> T checkNotNull(T obj) {
+    if (obj == null) {
+      throw new NullPointerException();
+    }
+    return obj;
+  }
+
   /**
    * Throws an appropriate {@link NullPointerException} if the given objects is {@code null}.
    */
@@ -420,6 +430,11 @@ public final class Internal {
       CodedInputStream.newInstance(EMPTY_BYTE_ARRAY);
 
 
+  /** Helper method to merge two MessageLite instances. */
+  static Object mergeMessage(Object destination, Object source) {
+    return ((MessageLite) destination).toBuilder().mergeFrom((MessageLite) source).buildPartial();
+  }
+
   /**
    * Provides an immutable view of {@code List<T>} around a {@code List<F>}.
    *

+ 1 - 0
java/core/src/main/java/com/google/protobuf/LazyFieldLite.java

@@ -394,6 +394,7 @@ public class LazyFieldLite {
     }
   }
 
+
   /**
    * Might lazily parse the bytes that were previously passed in. Is thread-safe.
    */

+ 5 - 7
java/core/src/main/java/com/google/protobuf/LongArrayList.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.Internal.LongList;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.Internal.LongList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.RandomAccess;
@@ -41,9 +42,8 @@ import java.util.RandomAccess;
  *
  * @author dweis@google.com (Daniel Weis)
  */
-final class LongArrayList
-    extends AbstractProtobufList<Long>
-    implements LongList, RandomAccess {
+final class LongArrayList extends AbstractProtobufList<Long>
+    implements LongList, RandomAccess, PrimitiveNonBoxingCollection {
 
   private static final LongArrayList EMPTY_LIST = new LongArrayList();
   static {
@@ -198,9 +198,7 @@ final class LongArrayList
   public boolean addAll(Collection<? extends Long> collection) {
     ensureIsMutable();
 
-    if (collection == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(collection);
 
     // We specialize when adding another LongArrayList to avoid boxing elements.
     if (!(collection instanceof LongArrayList)) {

+ 18 - 6
java/core/src/main/java/com/google/protobuf/MapEntry.java

@@ -33,7 +33,6 @@ package com.google.protobuf;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
-
 import java.io.IOException;
 import java.util.Collections;
 import java.util.Map;
@@ -171,7 +170,7 @@ public final class MapEntry<K, V> extends AbstractMessage {
 
   @Override
   public Builder<K, V> toBuilder() {
-    return new Builder<K, V>(metadata, key, value);
+    return new Builder<K, V>(metadata, key, value, true, true);
   }
 
   @Override
@@ -247,15 +246,19 @@ public final class MapEntry<K, V> extends AbstractMessage {
     private final Metadata<K, V> metadata;
     private K key;
     private V value;
+    private boolean hasKey;
+    private boolean hasValue;
 
     private Builder(Metadata<K, V> metadata) {
-      this(metadata, metadata.defaultKey, metadata.defaultValue);
+      this(metadata, metadata.defaultKey, metadata.defaultValue, false, false);
     }
 
-    private Builder(Metadata<K, V> metadata, K key, V value) {
+    private Builder(Metadata<K, V> metadata, K key, V value, boolean hasKey, boolean hasValue) {
       this.metadata = metadata;
       this.key = key;
       this.value = value;
+      this.hasKey = hasKey;
+      this.hasValue = hasValue;
     }
 
     public K getKey() {
@@ -268,21 +271,25 @@ public final class MapEntry<K, V> extends AbstractMessage {
 
     public Builder<K, V> setKey(K key) {
       this.key = key;
+      this.hasKey = true;
       return this;
     }
 
     public Builder<K, V> clearKey() {
       this.key = metadata.defaultKey;
+      this.hasKey = false;
       return this;
     }
 
     public Builder<K, V> setValue(V value) {
       this.value = value;
+      this.hasValue = true;
       return this;
     }
 
     public Builder<K, V> clearValue() {
       this.value = metadata.defaultValue;
+      this.hasValue = false;
       return this;
     }
 
@@ -404,7 +411,7 @@ public final class MapEntry<K, V> extends AbstractMessage {
     @Override
     public boolean hasField(FieldDescriptor field) {
       checkFieldDescriptor(field);
-      return true;
+      return field.getNumber() == 1 ? hasKey : hasValue;
     }
 
     @Override
@@ -438,7 +445,7 @@ public final class MapEntry<K, V> extends AbstractMessage {
     @Override
     @SuppressWarnings("unchecked")
     public Builder<K, V> clone() {
-      return new Builder(metadata, key, value);
+      return new Builder(metadata, key, value, hasKey, hasValue);
     }
   }
 
@@ -448,4 +455,9 @@ public final class MapEntry<K, V> extends AbstractMessage {
     }
     return true;
   }
+  
+  /** Returns the metadata only for experimental runtime. */
+  final Metadata<K, V> getMetadata() {
+    return metadata;
+  }
 }

+ 5 - 0
java/core/src/main/java/com/google/protobuf/MapEntryLite.java

@@ -223,4 +223,9 @@ public class MapEntryLite<K, V> {
     input.popLimit(oldLimit);
     map.put(key, value);
   }
+
+  /** For experimental runtime internal use only. */
+  Metadata<K, V> getMetadata() {
+    return metadata;
+  }
 }

+ 8 - 0
java/core/src/main/java/com/google/protobuf/MapField.java

@@ -30,6 +30,8 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.Internal.checkNotNull;
+
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -329,6 +331,8 @@ public class MapField<K, V> implements MutabilityOracle {
     @Override
     public V put(K key, V value) {
       mutabilityOracle.ensureMutable();
+      checkNotNull(key);
+      checkNotNull(value);
       return delegate.put(key, value);
     }
 
@@ -341,6 +345,10 @@ public class MapField<K, V> implements MutabilityOracle {
     @Override
     public void putAll(Map<? extends K, ? extends V> m) {
       mutabilityOracle.ensureMutable();
+      for (K key : m.keySet()) {
+        checkNotNull(key);
+        checkNotNull(m.get(key));
+      }
       delegate.putAll(m);
     }
 

+ 13 - 1
java/core/src/main/java/com/google/protobuf/MapFieldLite.java

@@ -30,8 +30,9 @@
 
 package com.google.protobuf;
 
-import com.google.protobuf.Internal.EnumLite;
+import static com.google.protobuf.Internal.checkNotNull;
 
+import com.google.protobuf.Internal.EnumLite;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.LinkedHashMap;
@@ -88,6 +89,9 @@ public final class MapFieldLite<K, V> extends LinkedHashMap<K, V> {
 
   @Override public V put(K key, V value) {
     ensureMutable();
+    checkNotNull(key);
+
+    checkNotNull(value);
     return super.put(key, value);
   }
 
@@ -97,6 +101,7 @@ public final class MapFieldLite<K, V> extends LinkedHashMap<K, V> {
 
   @Override public void putAll(Map<? extends K, ? extends V> m) {
     ensureMutable();
+    checkForNullKeysAndValues(m);
     super.putAll(m);
   }
 
@@ -105,6 +110,13 @@ public final class MapFieldLite<K, V> extends LinkedHashMap<K, V> {
     return super.remove(key);
   }
 
+  private static void checkForNullKeysAndValues(Map<?, ?> m) {
+    for (Object key : m.keySet()) {
+      checkNotNull(key);
+      checkNotNull(m.get(key));
+    }
+  }
+
   private static boolean equals(Object a, Object b) {
     if (a instanceof byte[] && b instanceof byte[]) {
       return Arrays.equals((byte[]) a, (byte[]) b);

+ 13 - 0
java/core/src/main/java/com/google/protobuf/Parser.java

@@ -31,6 +31,7 @@
 package com.google.protobuf;
 
 import java.io.InputStream;
+import java.nio.ByteBuffer;
 
 /**
  * Abstract interface for parsing Protocol Messages.
@@ -92,6 +93,18 @@ public interface Parser<MessageType> {
   // ---------------------------------------------------------------
   // Convenience methods.
 
+  /**
+   * Parses {@code data} as a message of {@code MessageType}. This is just a small wrapper around
+   * {@link #parseFrom(CodedInputStream)}.
+   */
+  public MessageType parseFrom(ByteBuffer data) throws InvalidProtocolBufferException;
+
+  /**
+   * Parses {@code data} as a message of {@code MessageType}. This is just a small wrapper around
+   * {@link #parseFrom(CodedInputStream, ExtensionRegistryLite)}.
+   */
+  public MessageType parseFrom(ByteBuffer data, ExtensionRegistryLite extensionRegistry)
+      throws InvalidProtocolBufferException;
   /**
    * Parses {@code data} as a message of {@code MessageType}.
    * This is just a small wrapper around {@link #parseFrom(CodedInputStream)}.

+ 34 - 0
java/core/src/main/java/com/google/protobuf/PrimitiveNonBoxingCollection.java

@@ -0,0 +1,34 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+package com.google.protobuf;
+
+/** A marker interface indicating that the collection supports primitives and is non-boxing. */
+interface PrimitiveNonBoxingCollection {}

+ 6 - 12
java/core/src/main/java/com/google/protobuf/RepeatedFieldBuilderV3.java

@@ -30,6 +30,8 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.Internal.checkNotNull;
+
 import java.util.AbstractList;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -290,9 +292,7 @@ public class RepeatedFieldBuilderV3
    */
   public RepeatedFieldBuilderV3<MType, BType, IType> setMessage(
       int index, MType message) {
-    if (message == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(message);
     ensureMutableMessageList();
     messages.set(index, message);
     if (builders != null) {
@@ -315,9 +315,7 @@ public class RepeatedFieldBuilderV3
    */
   public RepeatedFieldBuilderV3<MType, BType, IType> addMessage(
       MType message) {
-    if (message == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(message);
     ensureMutableMessageList();
     messages.add(message);
     if (builders != null) {
@@ -339,9 +337,7 @@ public class RepeatedFieldBuilderV3
    */
   public RepeatedFieldBuilderV3<MType, BType, IType> addMessage(
       int index, MType message) {
-    if (message == null) {
-      throw new NullPointerException();
-    }
+    checkNotNull(message);
     ensureMutableMessageList();
     messages.add(index, message);
     if (builders != null) {
@@ -363,9 +359,7 @@ public class RepeatedFieldBuilderV3
   public RepeatedFieldBuilderV3<MType, BType, IType> addAllMessages(
       Iterable<? extends MType> values) {
     for (final MType value : values) {
-      if (value == null) {
-        throw new NullPointerException();
-      }
+      checkNotNull(value);
     }
 
     // If we can inspect the size, we can more efficiently add messages.

+ 4 - 8
java/core/src/main/java/com/google/protobuf/SingleFieldBuilderV3.java

@@ -30,6 +30,8 @@
 
 package com.google.protobuf;
 
+import static com.google.protobuf.Internal.checkNotNull;
+
 /**
  * {@code SingleFieldBuilderV3} implements a structure that a protocol
  * message uses to hold a single field of another protocol message. It supports
@@ -84,10 +86,7 @@ public class SingleFieldBuilderV3
       MType message,
       AbstractMessage.BuilderParent parent,
       boolean isClean) {
-    if (message == null) {
-      throw new NullPointerException();
-    }
-    this.message = message;
+    this.message = checkNotNull(message);
     this.parent = parent;
     this.isClean = isClean;
   }
@@ -169,10 +168,7 @@ public class SingleFieldBuilderV3
    */
   public SingleFieldBuilderV3<MType, BType, IType> setMessage(
       MType message) {
-    if (message == null) {
-      throw new NullPointerException();
-    }
-    this.message = message;
+    this.message = checkNotNull(message);
     if (builder != null) {
       builder.dispose();
       builder = null;

+ 3 - 1
java/core/src/main/java/com/google/protobuf/TextFormat.java

@@ -1442,7 +1442,7 @@ public final class TextFormat {
 
     /**
      * Parse a single field from {@code tokenizer} and merge it into
-     * {@code builder}.
+     * {@code target}.
      */
     private void mergeField(final Tokenizer tokenizer,
                             final ExtensionRegistry extensionRegistry,
@@ -1712,6 +1712,8 @@ public final class TextFormat {
       }
 
       if (field.isRepeated()) {
+        // TODO(b/29122459): If field.isMapField() and FORBID_SINGULAR_OVERWRITES mode,
+        //     check for duplicate map keys here.
         target.addRepeatedField(field, value);
       } else if ((singularOverwritePolicy
               == SingularOverwritePolicy.FORBID_SINGULAR_OVERWRITES)

+ 2 - 2
java/core/src/main/java/com/google/protobuf/UnknownFieldSet.java

@@ -91,7 +91,7 @@ public final class UnknownFieldSet implements MessageLite {
    * Construct an {@code UnknownFieldSet} around the given map.  The map is
    * expected to be immutable.
    */
-  private UnknownFieldSet(final Map<Integer, Field> fields,
+  UnknownFieldSet(final Map<Integer, Field> fields,
       final Map<Integer, Field> fieldsDescending) {
     this.fields = fields;
   }
@@ -715,7 +715,7 @@ public final class UnknownFieldSet implements MessageLite {
    * @see UnknownFieldSet
    */
   public static final class Field {
-    private Field() {}
+    Field() {}
 
     /** Construct a new {@link Builder}. */
     public static Builder newBuilder() {

+ 308 - 93
java/core/src/main/java/com/google/protobuf/UnsafeUtil.java

@@ -33,19 +33,23 @@ package com.google.protobuf;
 import java.lang.reflect.Field;
 import java.nio.Buffer;
 import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
 import java.security.AccessController;
 import java.security.PrivilegedExceptionAction;
-import sun.misc.Unsafe;
+import java.util.logging.Level;
+import java.util.logging.Logger;
 
 /** Utility class for working with unsafe operations. */
-// TODO(nathanmittler): Add support for Android Memory/MemoryBlock
 final class UnsafeUtil {
+  private static final Logger logger = Logger.getLogger(UnsafeUtil.class.getName());
   private static final sun.misc.Unsafe UNSAFE = getUnsafe();
+  private static final MemoryAccessor MEMORY_ACCESSOR = getMemoryAccessor();
   private static final boolean HAS_UNSAFE_BYTEBUFFER_OPERATIONS =
       supportsUnsafeByteBufferOperations();
   private static final boolean HAS_UNSAFE_ARRAY_OPERATIONS = supportsUnsafeArrayOperations();
+  private static final boolean HAS_UNSAFE_COPY_MEMORY = supportsUnsafeCopyMemory();
   private static final long ARRAY_BASE_OFFSET = byteArrayBaseOffset();
-  private static final long BUFFER_ADDRESS_OFFSET = fieldOffset(field(Buffer.class, "address"));
+  private static final long BUFFER_ADDRESS_OFFSET = fieldOffset(bufferAddressField());
 
   private UnsafeUtil() {}
 
@@ -53,20 +57,16 @@ final class UnsafeUtil {
     return HAS_UNSAFE_ARRAY_OPERATIONS;
   }
 
-  static boolean hasUnsafeByteBufferOperations() {
-    return HAS_UNSAFE_BYTEBUFFER_OPERATIONS;
+  static boolean hasUnsafeCopyMemory() {
+    return HAS_UNSAFE_COPY_MEMORY;
   }
 
-  static Object allocateInstance(Class<?> clazz) {
-    try {
-      return UNSAFE.allocateInstance(clazz);
-    } catch (InstantiationException e) {
-      throw new RuntimeException(e);
-    }
+  static boolean hasUnsafeByteBufferOperations() {
+    return HAS_UNSAFE_BYTEBUFFER_OPERATIONS;
   }
 
   static long objectFieldOffset(Field field) {
-    return UNSAFE.objectFieldOffset(field);
+    return MEMORY_ACCESSOR.objectFieldOffset(field);
   }
 
   static long getArrayBaseOffset() {
@@ -74,103 +74,103 @@ final class UnsafeUtil {
   }
 
   static byte getByte(Object target, long offset) {
-    return UNSAFE.getByte(target, offset);
+    return MEMORY_ACCESSOR.getByte(target, offset);
   }
 
   static void putByte(Object target, long offset, byte value) {
-    UNSAFE.putByte(target, offset, value);
+    MEMORY_ACCESSOR.putByte(target, offset, value);
   }
 
   static int getInt(Object target, long offset) {
-    return UNSAFE.getInt(target, offset);
+    return MEMORY_ACCESSOR.getInt(target, offset);
   }
 
   static void putInt(Object target, long offset, int value) {
-    UNSAFE.putInt(target, offset, value);
+    MEMORY_ACCESSOR.putInt(target, offset, value);
   }
 
   static long getLong(Object target, long offset) {
-    return UNSAFE.getLong(target, offset);
+    return MEMORY_ACCESSOR.getLong(target, offset);
   }
 
   static void putLong(Object target, long offset, long value) {
-    UNSAFE.putLong(target, offset, value);
+    MEMORY_ACCESSOR.putLong(target, offset, value);
   }
 
   static boolean getBoolean(Object target, long offset) {
-    return UNSAFE.getBoolean(target, offset);
+    return MEMORY_ACCESSOR.getBoolean(target, offset);
   }
 
   static void putBoolean(Object target, long offset, boolean value) {
-    UNSAFE.putBoolean(target, offset, value);
+    MEMORY_ACCESSOR.putBoolean(target, offset, value);
   }
 
   static float getFloat(Object target, long offset) {
-    return UNSAFE.getFloat(target, offset);
+    return MEMORY_ACCESSOR.getFloat(target, offset);
   }
 
   static void putFloat(Object target, long offset, float value) {
-    UNSAFE.putFloat(target, offset, value);
+    MEMORY_ACCESSOR.putFloat(target, offset, value);
   }
 
   static double getDouble(Object target, long offset) {
-    return UNSAFE.getDouble(target, offset);
+    return MEMORY_ACCESSOR.getDouble(target, offset);
   }
 
   static void putDouble(Object target, long offset, double value) {
-    UNSAFE.putDouble(target, offset, value);
+    MEMORY_ACCESSOR.putDouble(target, offset, value);
   }
 
   static Object getObject(Object target, long offset) {
-    return UNSAFE.getObject(target, offset);
+    return MEMORY_ACCESSOR.getObject(target, offset);
   }
 
   static void putObject(Object target, long offset, Object value) {
-    UNSAFE.putObject(target, offset, value);
+    MEMORY_ACCESSOR.putObject(target, offset, value);
   }
 
   static void copyMemory(
       Object src, long srcOffset, Object target, long targetOffset, long length) {
-    UNSAFE.copyMemory(src, srcOffset, target, targetOffset, length);
+    MEMORY_ACCESSOR.copyMemory(src, srcOffset, target, targetOffset, length);
   }
 
   static byte getByte(long address) {
-    return UNSAFE.getByte(address);
+    return MEMORY_ACCESSOR.getByte(address);
   }
 
   static void putByte(long address, byte value) {
-    UNSAFE.putByte(address, value);
+    MEMORY_ACCESSOR.putByte(address, value);
   }
 
   static int getInt(long address) {
-    return UNSAFE.getInt(address);
+    return MEMORY_ACCESSOR.getInt(address);
   }
 
   static void putInt(long address, int value) {
-    UNSAFE.putInt(address, value);
+    MEMORY_ACCESSOR.putInt(address, value);
   }
 
   static long getLong(long address) {
-    return UNSAFE.getLong(address);
+    return MEMORY_ACCESSOR.getLong(address);
   }
 
   static void putLong(long address, long value) {
-    UNSAFE.putLong(address, value);
+    MEMORY_ACCESSOR.putLong(address, value);
   }
 
   static void copyMemory(long srcAddress, long targetAddress, long length) {
-    UNSAFE.copyMemory(srcAddress, targetAddress, length);
-  }
-
-  static void setMemory(long address, long numBytes, byte value) {
-    UNSAFE.setMemory(address, numBytes, value);
+    MEMORY_ACCESSOR.copyMemory(srcAddress, targetAddress, length);
   }
 
   /**
    * Gets the offset of the {@code address} field of the given direct {@link ByteBuffer}.
    */
   static long addressOffset(ByteBuffer buffer) {
-    return UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET);
+    return MEMORY_ACCESSOR.getLong(buffer, BUFFER_ADDRESS_OFFSET);
+  }
+
+  static Object getStaticObject(Field field) {
+    return MEMORY_ACCESSOR.getStaticObject(field);
   }
 
   /**
@@ -181,7 +181,7 @@ final class UnsafeUtil {
     try {
       unsafe =
           AccessController.doPrivileged(
-              new PrivilegedExceptionAction<Unsafe>() {
+              new PrivilegedExceptionAction<sun.misc.Unsafe>() {
                 @Override
                 public sun.misc.Unsafe run() throws Exception {
                   Class<sun.misc.Unsafe> k = sun.misc.Unsafe.class;
@@ -204,69 +204,114 @@ final class UnsafeUtil {
     return unsafe;
   }
 
+  /** Get a {@link MemoryAccessor} appropriate for the platform, or null if not supported. */
+  private static MemoryAccessor getMemoryAccessor() {
+    if (UNSAFE == null) {
+      return null;
+    }
+    return new JvmMemoryAccessor(UNSAFE);
+  }
+
   /** Indicates whether or not unsafe array operations are supported on this platform. */
   private static boolean supportsUnsafeArrayOperations() {
-    boolean supported = false;
-    if (UNSAFE != null) {
-      try {
-        Class<?> clazz = UNSAFE.getClass();
-        clazz.getMethod("objectFieldOffset", Field.class);
-        clazz.getMethod("allocateInstance", Class.class);
-        clazz.getMethod("arrayBaseOffset", Class.class);
-        clazz.getMethod("getByte", Object.class, long.class);
-        clazz.getMethod("putByte", Object.class, long.class, byte.class);
-        clazz.getMethod("getBoolean", Object.class, long.class);
-        clazz.getMethod("putBoolean", Object.class, long.class, boolean.class);
-        clazz.getMethod("getInt", Object.class, long.class);
-        clazz.getMethod("putInt", Object.class, long.class, int.class);
-        clazz.getMethod("getLong", Object.class, long.class);
-        clazz.getMethod("putLong", Object.class, long.class, long.class);
-        clazz.getMethod("getFloat", Object.class, long.class);
-        clazz.getMethod("putFloat", Object.class, long.class, float.class);
-        clazz.getMethod("getDouble", Object.class, long.class);
-        clazz.getMethod("putDouble", Object.class, long.class, double.class);
-        clazz.getMethod("getObject", Object.class, long.class);
-        clazz.getMethod("putObject", Object.class, long.class, Object.class);
-        clazz.getMethod(
-            "copyMemory", Object.class, long.class, Object.class, long.class, long.class);
-        supported = true;
-      } catch (Throwable e) {
-        // Do nothing.
-      }
-    }
-    return supported;
+    if (UNSAFE == null) {
+      return false;
+    }
+    try {
+      Class<?> clazz = UNSAFE.getClass();
+      clazz.getMethod("objectFieldOffset", Field.class);
+      clazz.getMethod("arrayBaseOffset", Class.class);
+      clazz.getMethod("getInt", Object.class, long.class);
+      clazz.getMethod("putInt", Object.class, long.class, int.class);
+      clazz.getMethod("getLong", Object.class, long.class);
+      clazz.getMethod("putLong", Object.class, long.class, long.class);
+      clazz.getMethod("getObject", Object.class, long.class);
+      clazz.getMethod("putObject", Object.class, long.class, Object.class);
+      clazz.getMethod("getByte", Object.class, long.class);
+      clazz.getMethod("putByte", Object.class, long.class, byte.class);
+      clazz.getMethod("getBoolean", Object.class, long.class);
+      clazz.getMethod("putBoolean", Object.class, long.class, boolean.class);
+      clazz.getMethod("getFloat", Object.class, long.class);
+      clazz.getMethod("putFloat", Object.class, long.class, float.class);
+      clazz.getMethod("getDouble", Object.class, long.class);
+      clazz.getMethod("putDouble", Object.class, long.class, double.class);
+
+      return true;
+    } catch (Throwable e) {
+      logger.log(
+          Level.WARNING,
+          "platform method missing - proto runtime falling back to safer methods: " + e);
+    }
+    return false;
+  }
+
+  /**
+   * Indicates whether or not unsafe copyMemory(object, long, object, long, long) operations are
+   * supported on this platform.
+   */
+  private static boolean supportsUnsafeCopyMemory() {
+    if (UNSAFE == null) {
+      return false;
+    }
+    try {
+      Class<?> clazz = UNSAFE.getClass();
+      clazz.getMethod("copyMemory", Object.class, long.class, Object.class, long.class, long.class);
+
+      return true;
+    } catch (Throwable e) {
+      logger.log(
+          Level.WARNING,
+          "copyMemory is missing from platform - proto runtime falling back to safer methods.");
+    }
+    return false;
   }
 
   private static boolean supportsUnsafeByteBufferOperations() {
-    boolean supported = false;
-    if (UNSAFE != null) {
-      try {
-        Class<?> clazz = UNSAFE.getClass();
-        // Methods for getting direct buffer address.
-        clazz.getMethod("objectFieldOffset", Field.class);
-        clazz.getMethod("getLong", Object.class, long.class);
-
-        clazz.getMethod("getByte", long.class);
-        clazz.getMethod("putByte", long.class, byte.class);
-        clazz.getMethod("getInt", long.class);
-        clazz.getMethod("putInt", long.class, int.class);
-        clazz.getMethod("getLong", long.class);
-        clazz.getMethod("putLong", long.class, long.class);
-        clazz.getMethod("setMemory", long.class, long.class, byte.class);
-        clazz.getMethod("copyMemory", long.class, long.class, long.class);
-        supported = true;
-      } catch (Throwable e) {
-        // Do nothing.
-      }
-    }
-    return supported;
+    if (UNSAFE == null) {
+      return false;
+    }
+    try {
+      Class<?> clazz = UNSAFE.getClass();
+      // Methods for getting direct buffer address.
+      clazz.getMethod("objectFieldOffset", Field.class);
+      clazz.getMethod("getLong", Object.class, long.class);
+
+      clazz.getMethod("getByte", long.class);
+      clazz.getMethod("putByte", long.class, byte.class);
+      clazz.getMethod("getInt", long.class);
+      clazz.getMethod("putInt", long.class, int.class);
+      clazz.getMethod("getLong", long.class);
+      clazz.getMethod("putLong", long.class, long.class);
+      clazz.getMethod("copyMemory", long.class, long.class, long.class);
+      return true;
+    } catch (Throwable e) {
+      logger.log(
+          Level.WARNING,
+          "platform method missing - proto runtime falling back to safer methods: " + e);
+    }
+    return false;
+  }
+
+
+  @SuppressWarnings("unchecked")
+  private static <T> Class<T> getClassForName(String name) {
+    try {
+      return (Class<T>) Class.forName(name);
+    } catch (Throwable e) {
+      return null;
+    }
+  }
+
+  /** Finds the address field within a direct {@link Buffer}. */
+  private static Field bufferAddressField() {
+    return field(Buffer.class, "address");
   }
 
   /**
    * Get the base offset for byte arrays, or {@code -1} if {@code sun.misc.Unsafe} is not available.
    */
   private static int byteArrayBaseOffset() {
-    return HAS_UNSAFE_ARRAY_OPERATIONS ? UNSAFE.arrayBaseOffset(byte[].class) : -1;
+    return HAS_UNSAFE_ARRAY_OPERATIONS ? MEMORY_ACCESSOR.arrayBaseOffset(byte[].class) : -1;
   }
 
   /**
@@ -274,7 +319,7 @@ final class UnsafeUtil {
    * available.
    */
   private static long fieldOffset(Field field) {
-    return field == null || UNSAFE == null ? -1 : UNSAFE.objectFieldOffset(field);
+    return field == null || MEMORY_ACCESSOR == null ? -1 : MEMORY_ACCESSOR.objectFieldOffset(field);
   }
 
   /**
@@ -292,4 +337,174 @@ final class UnsafeUtil {
     }
     return field;
   }
+
+  private abstract static class MemoryAccessor {
+
+    sun.misc.Unsafe unsafe;
+
+    MemoryAccessor(sun.misc.Unsafe unsafe) {
+      this.unsafe = unsafe;
+    }
+
+    public final long objectFieldOffset(Field field) {
+      return unsafe.objectFieldOffset(field);
+    }
+
+    public abstract byte getByte(Object target, long offset);
+
+    public abstract void putByte(Object target, long offset, byte value);
+
+    public final int getInt(Object target, long offset) {
+      return unsafe.getInt(target, offset);
+    }
+
+    public final void putInt(Object target, long offset, int value) {
+      unsafe.putInt(target, offset, value);
+    }
+
+    public final long getLong(Object target, long offset) {
+      return unsafe.getLong(target, offset);
+    }
+
+    public final void putLong(Object target, long offset, long value) {
+      unsafe.putLong(target, offset, value);
+    }
+
+    public abstract boolean getBoolean(Object target, long offset);
+
+    public abstract void putBoolean(Object target, long offset, boolean value);
+
+    public abstract float getFloat(Object target, long offset);
+
+    public abstract void putFloat(Object target, long offset, float value);
+
+    public abstract double getDouble(Object target, long offset);
+
+    public abstract void putDouble(Object target, long offset, double value);
+
+    public final Object getObject(Object target, long offset) {
+      return unsafe.getObject(target, offset);
+    }
+
+    public final void putObject(Object target, long offset, Object value) {
+      unsafe.putObject(target, offset, value);
+    }
+
+    public final int arrayBaseOffset(Class<?> clazz) {
+      return unsafe.arrayBaseOffset(clazz);
+    }
+
+    public abstract byte getByte(long address);
+
+    public abstract void putByte(long address, byte value);
+
+    public abstract int getInt(long address);
+
+    public abstract void putInt(long address, int value);
+
+    public abstract long getLong(long address);
+
+    public abstract void putLong(long address, long value);
+
+    public abstract void copyMemory(long srcAddress, long targetAddress, long length);
+
+    public abstract void copyMemory(
+        Object src, long srcOffset, Object target, long targetOffset, long length);
+
+    public abstract Object getStaticObject(Field field);
+  }
+
+  private static final class JvmMemoryAccessor extends MemoryAccessor {
+
+    JvmMemoryAccessor(sun.misc.Unsafe unsafe) {
+      super(unsafe);
+    }
+
+    @Override
+    public byte getByte(long address) {
+      return unsafe.getByte(address);
+    }
+
+    @Override
+    public void putByte(long address, byte value) {
+      unsafe.putByte(address, value);
+    }
+
+    @Override
+    public int getInt(long address) {
+      return unsafe.getInt(address);
+    }
+
+    @Override
+    public void putInt(long address, int value) {
+      unsafe.putInt(address, value);
+    }
+
+    @Override
+    public long getLong(long address) {
+      return unsafe.getLong(address);
+    }
+
+    @Override
+    public void putLong(long address, long value) {
+      unsafe.putLong(address, value);
+    }
+
+    @Override
+    public byte getByte(Object target, long offset) {
+      return unsafe.getByte(target, offset);
+    }
+
+    @Override
+    public void putByte(Object target, long offset, byte value) {
+      unsafe.putByte(target, offset, value);
+    }
+
+    @Override
+    public boolean getBoolean(Object target, long offset) {
+      return unsafe.getBoolean(target, offset);
+    }
+
+    @Override
+    public void putBoolean(Object target, long offset, boolean value) {
+      unsafe.putBoolean(target, offset, value);
+    }
+
+    @Override
+    public float getFloat(Object target, long offset) {
+      return unsafe.getFloat(target, offset);
+    }
+
+    @Override
+    public void putFloat(Object target, long offset, float value) {
+      unsafe.putFloat(target, offset, value);
+    }
+
+    @Override
+    public double getDouble(Object target, long offset) {
+      return unsafe.getDouble(target, offset);
+    }
+
+    @Override
+    public void putDouble(Object target, long offset, double value) {
+      unsafe.putDouble(target, offset, value);
+    }
+
+    @Override
+    public void copyMemory(
+        Object src, long srcOffset, Object target, long targetOffset, long length) {
+      unsafe.copyMemory(src, srcOffset, target, targetOffset, length);
+    }
+
+    @Override
+    public void copyMemory(long srcAddress, long targetAddress, long length) {
+      unsafe.copyMemory(srcAddress, targetAddress, length);
+    }
+
+    @Override
+    public Object getStaticObject(Field field) {
+      return getObject(unsafe.staticFieldBase(field), unsafe.staticFieldOffset(field));
+    }
+  }
+
 }

+ 1 - 1
java/core/src/main/java/com/google/protobuf/Utf8.java

@@ -1332,7 +1332,7 @@ final class Utf8 {
       // the index (relative to the start of the array) is also 8-byte aligned. We do this by
       // ANDing the index with 7 to determine the number of bytes that need to be read before
       // we're 8-byte aligned.
-      final int unaligned = (int) offset & 7;
+      final int unaligned = 8 - ((int) offset & 7);
       for (int j = unaligned; j > 0; j--) {
         if (UnsafeUtil.getByte(bytes, offset++) < 0) {
           return unaligned - j;

+ 1 - 1
java/core/src/test/java/com/google/protobuf/LazyFieldTest.java

@@ -32,7 +32,6 @@ package com.google.protobuf;
 
 import protobuf_unittest.UnittestProto.TestAllExtensions;
 import protobuf_unittest.UnittestProto.TestAllTypes;
-import java.io.IOException;
 import junit.framework.TestCase;
 
 /**
@@ -89,6 +88,7 @@ public class LazyFieldTest extends TestCase {
     assertFalse(message.equals(lazyField.getValue()));
   }
 
+  @SuppressWarnings("EqualsIncompatibleType") // LazyField.equals() is not symmetric
   public void testEqualsObjectEx() throws Exception {
     TestAllExtensions message = TestUtil.getAllExtensionsSet();
     LazyField lazyField = createLazyFieldFromMessage(message);

+ 108 - 0
java/core/src/test/java/com/google/protobuf/LiteTest.java

@@ -52,6 +52,7 @@ import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.BarPrime;
 import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.Foo;
 import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestOneofEquals;
 import protobuf_unittest.lite_equals_and_hash.LiteEqualsAndHash.TestRecursiveOneof;
+import java.nio.ByteBuffer;
 import junit.framework.TestCase;
 
 /**
@@ -2174,6 +2175,24 @@ public class LiteTest extends TestCase {
     assertFalse(bar.equals(barPrime));
   }
 
+  public void testEqualsAndHashCodeForTrickySchemaTypes() {
+    Foo foo1 = Foo.newBuilder()
+        .build();
+    Foo foo2 = Foo.newBuilder()
+        .setSint64(1)
+        .build();
+    Foo foo3 = Foo.newBuilder()
+        .putMyMap("key", "value2")
+        .build();
+    Foo foo4 = Foo.newBuilder()
+        .setMyGroup(Foo.MyGroup.newBuilder().setValue(4).build())
+        .build();
+
+    assertEqualsAndHashCodeAreFalse(foo1, foo2);
+    assertEqualsAndHashCodeAreFalse(foo1, foo3);
+    assertEqualsAndHashCodeAreFalse(foo1, foo4);
+  }
+
   public void testOneofEquals() throws Exception {
     TestOneofEquals.Builder builder = TestOneofEquals.newBuilder();
     TestOneofEquals message1 = builder.build();
@@ -2270,4 +2289,93 @@ public class LiteTest extends TestCase {
     // This tests that we don't infinite loop.
     TestRecursiveOneof.getDefaultInstance().hashCode();
   }
+
+  public void testParseFromByteBuffer() throws Exception {
+    TestAllTypesLite message =
+        TestAllTypesLite.newBuilder()
+            .setOptionalInt32(123)
+            .addRepeatedString("hello")
+            .setOptionalNestedMessage(TestAllTypesLite.NestedMessage.newBuilder().setBb(7))
+            .build();
+
+    TestAllTypesLite copy =
+        TestAllTypesLite.parseFrom(message.toByteString().asReadOnlyByteBuffer());
+
+    assertEquals(message, copy);
+  }
+
+  public void testParseFromByteBufferThrows() {
+    try {
+      TestAllTypesLite.parseFrom(ByteBuffer.wrap(new byte[] { 0x5 }));
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+
+    TestAllTypesLite message =
+        TestAllTypesLite.newBuilder()
+            .setOptionalInt32(123)
+            .addRepeatedString("hello")
+            .build();
+
+    ByteBuffer buffer = ByteBuffer.wrap(message.toByteArray(), 0, message.getSerializedSize() - 1);
+    try {
+      TestAllTypesLite.parseFrom(buffer);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+      assertEquals(
+          TestAllTypesLite.newBuilder()
+              .setOptionalInt32(123)
+              .build(),
+          expected.getUnfinishedMessage());
+    }
+  }
+
+  public void testParseFromByteBuffer_extensions() throws Exception {
+    TestAllExtensionsLite message =
+        TestAllExtensionsLite.newBuilder()
+            .setExtension(UnittestLite.optionalInt32ExtensionLite, 123)
+            .addExtension(UnittestLite.repeatedStringExtensionLite, "hello")
+            .setExtension(
+                UnittestLite.optionalNestedEnumExtensionLite, TestAllTypesLite.NestedEnum.BAZ)
+            .setExtension(
+                UnittestLite.optionalNestedMessageExtensionLite,
+                TestAllTypesLite.NestedMessage.newBuilder().setBb(7).build())
+            .build();
+
+    ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
+    UnittestLite.registerAllExtensions(registry);
+
+    TestAllExtensionsLite copy =
+        TestAllExtensionsLite.parseFrom(message.toByteString().asReadOnlyByteBuffer(), registry);
+
+    assertEquals(message, copy);
+  }
+
+  public void testParseFromByteBufferThrows_extensions() {
+    ExtensionRegistryLite registry = ExtensionRegistryLite.newInstance();
+    UnittestLite.registerAllExtensions(registry);
+    try {
+      TestAllExtensionsLite.parseFrom(ByteBuffer.wrap(new byte[] { 0x5 }), registry);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+    }
+
+    TestAllExtensionsLite message =
+        TestAllExtensionsLite.newBuilder()
+            .setExtension(UnittestLite.optionalInt32ExtensionLite, 123)
+            .addExtension(UnittestLite.repeatedStringExtensionLite, "hello")
+            .build();
+
+    ByteBuffer buffer = ByteBuffer.wrap(message.toByteArray(), 0, message.getSerializedSize() - 1);
+    try {
+      TestAllExtensionsLite.parseFrom(buffer, registry);
+      fail();
+    } catch (InvalidProtocolBufferException expected) {
+      assertEquals(
+          TestAllExtensionsLite.newBuilder()
+              .setExtension(UnittestLite.optionalInt32ExtensionLite, 123)
+              .build(),
+          expected.getUnfinishedMessage());
+    }
+  }
 }

+ 1 - 0
java/core/src/test/java/com/google/protobuf/MapForProto2Test.java

@@ -759,6 +759,7 @@ public class MapForProto2Test extends TestCase {
     assertEquals(55, message.getInt32ToInt32Field().get(55).intValue());
   }
 
+  // See additional coverage in TextFormatTest.java.
   public void testTextFormat() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
     setMapValuesUsingAccessors(builder);

+ 39 - 1
java/core/src/test/java/com/google/protobuf/MapTest.java

@@ -30,7 +30,7 @@
 
 package com.google.protobuf;
 
-
+import static org.junit.Assert.assertArrayEquals;
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.EnumDescriptor;
 import com.google.protobuf.Descriptors.EnumValueDescriptor;
@@ -864,6 +864,7 @@ public class MapTest extends TestCase {
     assertEquals(55, message.getInt32ToInt32Field().get(55).intValue());
   }
 
+  // See additional coverage in TextFormatTest.java.
   public void testTextFormat() throws Exception {
     TestMap.Builder builder = TestMap.newBuilder();
     setMapValuesUsingAccessors(builder);
@@ -1483,4 +1484,41 @@ public class MapTest extends TestCase {
     map.put(key3, value3);
     return map;
   }
+
+  public void testMap_withNulls() {
+    TestMap.Builder builder = TestMap.newBuilder();
+
+    try {
+      builder.putStringToInt32Field(null, 3);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putAllStringToInt32Field(newMap(null, 3, "hi", 4));
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putInt32ToMessageField(3, null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putAllInt32ToMessageField(
+          MapTest.<Integer, MessageValue>newMap(4, null, 5, null));
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    try {
+      builder.putAllInt32ToMessageField(null);
+      fail();
+    } catch (NullPointerException expected) {
+    }
+
+    assertArrayEquals(new byte[0], builder.build().toByteArray());
+  }
 }

+ 23 - 4
java/core/src/test/java/com/google/protobuf/ParserTest.java

@@ -79,6 +79,8 @@ public class ParserTest extends TestCase {
         new ByteArrayInputStream(data), registry));
     assertMessageEquals(message, parser.parseFrom(
         CodedInputStream.newInstance(data), registry));
+    assertMessageEquals(
+        message, parser.parseFrom(message.toByteString().asReadOnlyByteBuffer(), registry));
   }
 
   @SuppressWarnings("unchecked")
@@ -99,6 +101,7 @@ public class ParserTest extends TestCase {
         new ByteArrayInputStream(data)));
     assertMessageEquals(message, parser.parseFrom(
         CodedInputStream.newInstance(data)));
+    assertMessageEquals(message, parser.parseFrom(message.toByteString().asReadOnlyByteBuffer()));
   }
 
   private void assertMessageEquals(
@@ -178,6 +181,9 @@ public class ParserTest extends TestCase {
   public void testParseExtensions() throws Exception {
     assertRoundTripEquals(TestUtil.getAllExtensionsSet(),
                           TestUtil.getExtensionRegistry());
+  }
+
+  public void testParseExtensionsLite() throws Exception {
     assertRoundTripEquals(
         TestUtilLite.getAllLiteExtensionsSet(), TestUtilLite.getExtensionRegistryLite());
   }
@@ -186,6 +192,9 @@ public class ParserTest extends TestCase {
     assertRoundTripEquals(TestUtil.getPackedSet());
     assertRoundTripEquals(TestUtil.getPackedExtensionsSet(),
                           TestUtil.getExtensionRegistry());
+  }
+
+  public void testParsePackedLite() throws Exception {
     assertRoundTripEquals(
         TestUtilLite.getLitePackedExtensionsSet(), TestUtilLite.getExtensionRegistryLite());
   }
@@ -195,15 +204,26 @@ public class ParserTest extends TestCase {
     TestAllTypes normalMessage = TestUtil.getAllSet();
     ByteArrayOutputStream output = new ByteArrayOutputStream();
     normalMessage.writeDelimitedTo(output);
+    normalMessage.writeDelimitedTo(output);
 
+    InputStream input = new ByteArrayInputStream(output.toByteArray());
+    assertMessageEquals(normalMessage, normalMessage.getParserForType().parseDelimitedFrom(input));
+    assertMessageEquals(normalMessage, normalMessage.getParserForType().parseDelimitedFrom(input));
+  }
+
+  public void testParseDelimitedToLite() throws Exception {
     // Write MessageLite with packed extension fields.
     TestPackedExtensionsLite packedMessage = TestUtilLite.getLitePackedExtensionsSet();
+    ByteArrayOutputStream output = new ByteArrayOutputStream();
+    packedMessage.writeDelimitedTo(output);
     packedMessage.writeDelimitedTo(output);
 
     InputStream input = new ByteArrayInputStream(output.toByteArray());
     assertMessageEquals(
-        normalMessage,
-        normalMessage.getParserForType().parseDelimitedFrom(input));
+        packedMessage,
+        packedMessage
+            .getParserForType()
+            .parseDelimitedFrom(input, TestUtilLite.getExtensionRegistryLite()));
     assertMessageEquals(
         packedMessage,
         packedMessage
@@ -314,8 +334,7 @@ public class ParserTest extends TestCase {
 
   public void testParsingMergeLite() throws Exception {
     // Build messages.
-    TestAllTypesLite.Builder builder =
-        TestAllTypesLite.newBuilder();
+    TestAllTypesLite.Builder builder = TestAllTypesLite.newBuilder();
     TestAllTypesLite msg1 = builder.setOptionalInt32(1).build();
     builder.clear();
     TestAllTypesLite msg2 = builder.setOptionalInt64(2).build();

+ 2 - 0
java/core/src/test/java/com/google/protobuf/TestUtil.java

@@ -2622,6 +2622,8 @@ public final class TestUtil {
         break;
       case FOO_NOT_SET:
         break;
+      default:
+        // TODO(b/18683919): go/enum-switch-lsc
     }
   }
 

+ 92 - 0
java/core/src/test/java/com/google/protobuf/TextFormatTest.java

@@ -30,9 +30,12 @@
 
 package com.google.protobuf;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import com.google.protobuf.Descriptors.Descriptor;
 import com.google.protobuf.Descriptors.FieldDescriptor;
 import com.google.protobuf.TextFormat.Parser.SingularOverwritePolicy;
+import map_test.MapTestProto.TestMap;
 import protobuf_unittest.UnittestMset.TestMessageSetExtension1;
 import protobuf_unittest.UnittestMset.TestMessageSetExtension2;
 import protobuf_unittest.UnittestProto.OneString;
@@ -940,6 +943,7 @@ public class TextFormatTest extends TestCase {
   }
 
 
+  // See additional coverage in testOneofOverwriteForbidden and testMapOverwriteForbidden.
   public void testParseNonRepeatedFields() throws Exception {
     assertParseSuccessWithOverwriteForbidden(
         "repeated_int32: 1\n" +
@@ -950,6 +954,7 @@ public class TextFormatTest extends TestCase {
     assertParseSuccessWithOverwriteForbidden(
         "repeated_nested_message { bb: 1 }\n" +
         "repeated_nested_message { bb: 2 }\n");
+
     assertParseErrorWithOverwriteForbidden(
         "3:17: Non-repeated field " +
         "\"protobuf_unittest.TestAllTypes.optional_int32\" " +
@@ -988,6 +993,7 @@ public class TextFormatTest extends TestCase {
     assertParseSuccessWithOverwriteForbidden("repeated_int32: [ 1, 2 ]\n");
     assertParseSuccessWithOverwriteForbidden("RepeatedGroup [{ a: 1 },{ a: 2 }]\n");
     assertParseSuccessWithOverwriteForbidden("repeated_nested_message [{ bb: 1 }, { bb: 2 }]\n");
+    // See also testMapShortForm.
   }
 
   public void testParseShortRepeatedFormOfEmptyRepeatedFields() throws Exception {
@@ -995,6 +1001,7 @@ public class TextFormatTest extends TestCase {
     assertParseSuccessWithOverwriteForbidden("repeated_int32: []\n");
     assertParseSuccessWithOverwriteForbidden("RepeatedGroup []\n");
     assertParseSuccessWithOverwriteForbidden("repeated_nested_message []\n");
+    // See also testMapShortFormEmpty.
   }
 
   public void testParseShortRepeatedFormWithTrailingComma() throws Exception {
@@ -1010,6 +1017,7 @@ public class TextFormatTest extends TestCase {
     assertParseErrorWithOverwriteForbidden(
         "1:37: Expected \"{\".",
         "repeated_nested_message [{ bb: 1 }, ]\n");
+    // See also testMapShortFormTrailingComma.
   }
 
   public void testParseShortRepeatedFormOfNonRepeatedFields() throws Exception {
@@ -1057,6 +1065,90 @@ public class TextFormatTest extends TestCase {
     assertTrue(oneof.hasFooInt());
   }
 
+  // =======================================================================
+  // test map
+
+  public void testMapTextFormat() throws Exception {
+    TestMap message =
+        TestMap.newBuilder()
+            .putInt32ToStringField(10, "apple")
+            .putInt32ToStringField(20, "banana")
+            .putInt32ToStringField(30, "cherry")
+            .build();
+    String text = TextFormat.printToUnicodeString(message);
+    {
+      TestMap.Builder dest = TestMap.newBuilder();
+      TextFormat.merge(text, dest);
+      assertThat(dest.build()).isEqualTo(message);
+    }
+    {
+      TestMap.Builder dest = TestMap.newBuilder();
+      parserWithOverwriteForbidden.merge(text, dest);
+      assertThat(dest.build()).isEqualTo(message);
+    }
+  }
+
+  public void testMapShortForm() throws Exception {
+    String text =
+        "string_to_int32_field [{ key: 'x' value: 10 }, { key: 'y' value: 20 }]\n"
+        + "int32_to_message_field "
+        + "[{ key: 1 value { value: 100 } }, { key: 2 value: { value: 200 } }]\n";
+    TestMap.Builder dest = TestMap.newBuilder();
+    parserWithOverwriteForbidden.merge(text, dest);
+    TestMap message = dest.build();
+    assertThat(message.getStringToInt32Field().size()).isEqualTo(2);
+    assertThat(message.getInt32ToMessageField().size()).isEqualTo(2);
+    assertThat(message.getStringToInt32Field().get("x")).isEqualTo(10);
+    assertThat(message.getInt32ToMessageField().get(2).getValue()).isEqualTo(200);
+  }
+
+  public void testMapShortFormEmpty() throws Exception {
+    String text = "string_to_int32_field []\n"
+        + "int32_to_message_field: []\n";
+    TestMap.Builder dest = TestMap.newBuilder();
+    parserWithOverwriteForbidden.merge(text, dest);
+    TestMap message = dest.build();
+    assertThat(message.getStringToInt32Field().size()).isEqualTo(0);
+    assertThat(message.getInt32ToMessageField().size()).isEqualTo(0);
+  }
+
+  public void testMapShortFormTrailingComma() throws Exception {
+    String text = "string_to_int32_field [{ key: 'x' value: 10 }, ]\n";
+    TestMap.Builder dest = TestMap.newBuilder();
+    try {
+      parserWithOverwriteForbidden.merge(text, dest);
+      fail("Expected parse exception.");
+    } catch (TextFormat.ParseException e) {
+      assertThat(e).hasMessageThat().isEqualTo("1:48: Expected \"{\".");
+    }
+  }
+
+  public void testMapOverwrite() throws Exception {
+    String text =
+        "int32_to_int32_field { key: 1 value: 10 }\n"
+            + "int32_to_int32_field { key: 2 value: 20 }\n"
+            + "int32_to_int32_field { key: 1 value: 30 }\n";
+
+    {
+      // With default parser, last value set for the key holds.
+      TestMap.Builder builder = TestMap.newBuilder();
+      defaultParser.merge(text, builder);
+      TestMap map = builder.build();
+      assertThat(map.getInt32ToInt32Field().size()).isEqualTo(2);
+      assertThat(map.getInt32ToInt32Field().get(1).intValue()).isEqualTo(30);
+    }
+
+    {
+      // With overwrite forbidden, same behavior.
+      // TODO(b/29122459): Expect parse exception here.
+      TestMap.Builder builder = TestMap.newBuilder();
+      defaultParser.merge(text, builder);
+      TestMap map = builder.build();
+      assertThat(map.getInt32ToInt32Field().size()).isEqualTo(2);
+      assertThat(map.getInt32ToInt32Field().get(1).intValue()).isEqualTo(30);
+    }
+  }
+
   // =======================================================================
   // test location information
 

+ 8 - 0
java/core/src/test/proto/com/google/protobuf/lite_equals_and_hash.proto

@@ -46,6 +46,14 @@ message TestOneofEquals {
 message Foo {
   optional int32 value = 1;
   repeated Bar bar = 2;
+  map<string, string> my_map = 3;
+  oneof Single {
+    sint64 sint64 = 4;
+    // LINT: ALLOW_GROUPS
+    group MyGroup = 5 {
+      optional int32 value = 1;
+    }
+  }
 
   extensions 100 to max;
 }

+ 1 - 0
java/lite/pom.xml

@@ -137,6 +137,7 @@
             <include>**/MutabilityOracle.java</include>
             <include>**/NioByteString.java</include>
             <include>**/Parser.java</include>
+            <include>**/PrimitiveNonBoxingCollection.java</include>
             <include>**/ProtobufArrayList.java</include>
             <include>**/ProtocolStringList.java</include>
             <include>**/RopeByteString.java</include>

+ 8 - 2
java/pom.xml

@@ -11,7 +11,7 @@
 
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-parent</artifactId>
-  <version>3.2.0</version>
+  <version>3.3.0</version>
   <packaging>pom</packaging>
 
   <name>Protocol Buffers [Parent]</name>
@@ -84,7 +84,13 @@
       <dependency>
         <groupId>com.google.guava</groupId>
         <artifactId>guava</artifactId>
-        <version>18.0</version>
+        <version>20.0</version>
+      </dependency>
+      <dependency>
+        <groupId>com.google.truth</groupId>
+        <artifactId>truth</artifactId>
+        <scope>test</scope>
+        <version>0.32</version>
       </dependency>
     </dependencies>
   </dependencyManagement>

+ 1 - 1
java/util/pom.xml

@@ -6,7 +6,7 @@
   <parent>
     <groupId>com.google.protobuf</groupId>
     <artifactId>protobuf-parent</artifactId>
-    <version>3.2.0</version>
+    <version>3.3.0</version>
   </parent>
 
   <artifactId>protobuf-java-util</artifactId>

+ 99 - 77
java/util/src/main/java/com/google/protobuf/util/JsonFormat.java

@@ -30,6 +30,7 @@
 
 package com.google.protobuf.util;
 
+import com.google.common.base.Preconditions;
 import com.google.common.io.BaseEncoding;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -102,7 +103,9 @@ public class JsonFormat {
    * Creates a {@link Printer} with default configurations.
    */
   public static Printer printer() {
-    return new Printer(TypeRegistry.getEmptyTypeRegistry(), false, false, false);
+    return new Printer(
+        TypeRegistry.getEmptyTypeRegistry(), false, Collections.<FieldDescriptor>emptySet(),
+        false, false);
   }
 
   /**
@@ -110,16 +113,27 @@ public class JsonFormat {
    */
   public static class Printer {
     private final TypeRegistry registry;
-    private final boolean includingDefaultValueFields;
+    // NOTE: There are 3 states for these *defaultValueFields variables:
+    // 1) Default - alwaysOutput is false & including is empty set. Fields only output if they are
+    //    set to non-default values.
+    // 2) No-args includingDefaultValueFields() called - alwaysOutput is true & including is
+    //    irrelevant (but set to empty set). All fields are output regardless of their values.
+    // 3) includingDefaultValueFields(Set<FieldDescriptor>) called - alwaysOutput is false &
+    //    including is set to the specified set. Fields in that set are always output & fields not
+    //    in that set are only output if set to non-default values.
+    private boolean alwaysOutputDefaultValueFields;
+    private Set<FieldDescriptor> includingDefaultValueFields;
     private final boolean preservingProtoFieldNames;
     private final boolean omittingInsignificantWhitespace;
 
     private Printer(
         TypeRegistry registry,
-        boolean includingDefaultValueFields,
+        boolean alwaysOutputDefaultValueFields,
+        Set<FieldDescriptor> includingDefaultValueFields,
         boolean preservingProtoFieldNames,
         boolean omittingInsignificantWhitespace) {
       this.registry = registry;
+      this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
       this.includingDefaultValueFields = includingDefaultValueFields;
       this.preservingProtoFieldNames = preservingProtoFieldNames;
       this.omittingInsignificantWhitespace = omittingInsignificantWhitespace;
@@ -137,6 +151,7 @@ public class JsonFormat {
       }
       return new Printer(
           registry,
+          alwaysOutputDefaultValueFields,
           includingDefaultValueFields,
           preservingProtoFieldNames,
           omittingInsignificantWhitespace);
@@ -149,8 +164,41 @@ public class JsonFormat {
      * {@link Printer}.
      */
     public Printer includingDefaultValueFields() {
+      checkUnsetIncludingDefaultValueFields();
       return new Printer(
-          registry, true, preservingProtoFieldNames, omittingInsignificantWhitespace);
+          registry,
+          true,
+          Collections.<FieldDescriptor>emptySet(),
+          preservingProtoFieldNames,
+          omittingInsignificantWhitespace);
+    }
+
+    /**
+     * Creates a new {@link Printer} that will also print default-valued fields if their
+     * FieldDescriptors are found in the supplied set. Empty repeated fields and map fields will be
+     * printed as well, if they match. The new Printer clones all other configurations from the
+     * current {@link Printer}. Call includingDefaultValueFields() with no args to unconditionally
+     * output all fields.
+     */
+    public Printer includingDefaultValueFields(Set<FieldDescriptor> fieldsToAlwaysOutput) {
+      Preconditions.checkArgument(
+          null != fieldsToAlwaysOutput && !fieldsToAlwaysOutput.isEmpty(),
+          "Non-empty Set must be supplied for includingDefaultValueFields.");
+
+      checkUnsetIncludingDefaultValueFields();
+      return new Printer(
+          registry,
+          false,
+          fieldsToAlwaysOutput,
+          preservingProtoFieldNames,
+          omittingInsignificantWhitespace);
+    }
+
+    private void checkUnsetIncludingDefaultValueFields() {
+      if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
+        throw new IllegalStateException(
+            "JsonFormat includingDefaultValueFields has already been set.");
+      }
     }
 
     /**
@@ -161,15 +209,20 @@ public class JsonFormat {
      */
     public Printer preservingProtoFieldNames() {
       return new Printer(
-          registry, includingDefaultValueFields, true, omittingInsignificantWhitespace);
+          registry,
+          alwaysOutputDefaultValueFields,
+          includingDefaultValueFields,
+          true,
+          omittingInsignificantWhitespace);
     }
 
 
     /**
-     * Create a new  {@link Printer}  that will omit all insignificant whitespace
-     * in the JSON output. This new Printer clones all other configurations from the
-     * current Printer. Insignificant whitespace is defined by the JSON spec as whitespace
-     * that appear between JSON structural elements:
+     * Create a new {@link Printer} that will omit all insignificant whitespace in the JSON output.
+     * This new Printer clones all other configurations from the current Printer. Insignificant
+     * whitespace is defined by the JSON spec as whitespace that appear between JSON structural
+     * elements:
+     *
      * <pre>
      * ws = *(
      * %x20 /              ; Space
@@ -177,18 +230,24 @@ public class JsonFormat {
      * %x0A /              ; Line feed or New line
      * %x0D )              ; Carriage return
      * </pre>
+     *
      * See <a href="https://tools.ietf.org/html/rfc7159">https://tools.ietf.org/html/rfc7159</a>
      * current {@link Printer}.
      */
     public Printer omittingInsignificantWhitespace() {
-      return new Printer(registry, includingDefaultValueFields, preservingProtoFieldNames, true);
+      return new Printer(
+          registry,
+          alwaysOutputDefaultValueFields,
+          includingDefaultValueFields,
+          preservingProtoFieldNames,
+          true);
     }
 
     /**
      * Converts a protobuf message to JSON format.
      *
-     * @throws InvalidProtocolBufferException if the message contains Any types
-     *         that can't be resolved.
+     * @throws InvalidProtocolBufferException if the message contains Any types that can't be
+     *     resolved.
      * @throws IOException if writing to the output fails.
      */
     public void appendTo(MessageOrBuilder message, Appendable output) throws IOException {
@@ -196,6 +255,7 @@ public class JsonFormat {
       // mobile.
       new PrinterImpl(
               registry,
+              alwaysOutputDefaultValueFields,
               includingDefaultValueFields,
               preservingProtoFieldNames,
               output,
@@ -428,19 +488,16 @@ public class JsonFormat {
       this.output = output;
     }
 
-    /**
-     * ignored by compact printer
-     */
+    /** ignored by compact printer */
+    @Override
     public void indent() {}
 
-    /**
-     * ignored by compact printer
-     */
+    /** ignored by compact printer */
+    @Override
     public void outdent() {}
 
-    /**
-     * Print text to the output stream.
-     */
+    /** Print text to the output stream. */
+    @Override
     public void print(final CharSequence text) throws IOException {
       output.append(text);
     }
@@ -458,18 +515,17 @@ public class JsonFormat {
     }
 
     /**
-     * Indent text by two spaces.  After calling Indent(), two spaces will be
-     * inserted at the beginning of each line of text.  Indent() may be called
-     * multiple times to produce deeper indents.
+     * Indent text by two spaces. After calling Indent(), two spaces will be inserted at the
+     * beginning of each line of text. Indent() may be called multiple times to produce deeper
+     * indents.
      */
+    @Override
     public void indent() {
       indent.append("  ");
     }
 
-    /**
-     * Reduces the current indent level by two spaces, or crashes if the indent
-     * level is zero.
-     */
+    /** Reduces the current indent level by two spaces, or crashes if the indent level is zero. */
+    @Override
     public void outdent() {
       final int length = indent.length();
       if (length < 2) {
@@ -478,9 +534,8 @@ public class JsonFormat {
       indent.delete(length - 2, length);
     }
 
-    /**
-     * Print text to the output stream.
-     */
+    /** Print text to the output stream. */
+    @Override
     public void print(final CharSequence text) throws IOException {
       final int size = text.length();
       int pos = 0;
@@ -512,7 +567,8 @@ public class JsonFormat {
    */
   private static final class PrinterImpl {
     private final TypeRegistry registry;
-    private final boolean includingDefaultValueFields;
+    private final boolean alwaysOutputDefaultValueFields;
+    private final Set<FieldDescriptor> includingDefaultValueFields;
     private final boolean preservingProtoFieldNames;
     private final TextGenerator generator;
     // We use Gson to help handle string escapes.
@@ -526,11 +582,13 @@ public class JsonFormat {
 
     PrinterImpl(
         TypeRegistry registry,
-        boolean includingDefaultValueFields,
+        boolean alwaysOutputDefaultValueFields,
+        Set<FieldDescriptor> includingDefaultValueFields,
         boolean preservingProtoFieldNames,
         Appendable jsonOutput,
         boolean omittingInsignificantWhitespace) {
       this.registry = registry;
+      this.alwaysOutputDefaultValueFields = alwaysOutputDefaultValueFields;
       this.includingDefaultValueFields = includingDefaultValueFields;
       this.preservingProtoFieldNames = preservingProtoFieldNames;
       this.gson = GsonHolder.DEFAULT_GSON;
@@ -781,23 +839,26 @@ public class JsonFormat {
         printedField = true;
       }
       Map<FieldDescriptor, Object> fieldsToPrint = null;
-      if (includingDefaultValueFields) {
-        fieldsToPrint = new TreeMap<FieldDescriptor, Object>();
+      if (alwaysOutputDefaultValueFields || !includingDefaultValueFields.isEmpty()) {
+        fieldsToPrint = new TreeMap<FieldDescriptor, Object>(message.getAllFields());
         for (FieldDescriptor field : message.getDescriptorForType().getFields()) {
           if (field.isOptional()) {
             if (field.getJavaType() == FieldDescriptor.JavaType.MESSAGE
-                && !message.hasField(field)){
+                && !message.hasField(field)) {
               // Always skip empty optional message fields. If not we will recurse indefinitely if
               // a message has itself as a sub-field.
               continue;
             }
             OneofDescriptor oneof = field.getContainingOneof();
             if (oneof != null && !message.hasField(field)) {
-                // Skip all oneof fields except the one that is actually set
+              // Skip all oneof fields except the one that is actually set
               continue;
             }
           }
-          fieldsToPrint.put(field, message.getField(field));
+          if (!fieldsToPrint.containsKey(field)
+              && (alwaysOutputDefaultValueFields || includingDefaultValueFields.contains(field))) {
+            fieldsToPrint.put(field, message.getField(field));
+          }
         }
       } else {
         fieldsToPrint = message.getAllFields();
@@ -1451,45 +1512,6 @@ public class JsonFormat {
       }
     }
 
-    /**
-     * Gets the default value for a field type. Note that we use proto3
-     * language defaults and ignore any default values set through the
-     * proto "default" option.
-     */
-    private Object getDefaultValue(FieldDescriptor field, Message.Builder builder) {
-      switch (field.getType()) {
-        case INT32:
-        case SINT32:
-        case SFIXED32:
-        case UINT32:
-        case FIXED32:
-          return 0;
-        case INT64:
-        case SINT64:
-        case SFIXED64:
-        case UINT64:
-        case FIXED64:
-          return 0L;
-        case FLOAT:
-          return 0.0f;
-        case DOUBLE:
-          return 0.0;
-        case BOOL:
-          return false;
-        case STRING:
-          return "";
-        case BYTES:
-          return ByteString.EMPTY;
-        case ENUM:
-          return field.getEnumType().getValues().get(0);
-        case MESSAGE:
-        case GROUP:
-          return builder.newBuilderForField(field).getDefaultInstanceForType();
-        default:
-          throw new IllegalStateException("Invalid field type: " + field.getType());
-      }
-    }
-
     private void mergeRepeatedField(
         FieldDescriptor field, JsonElement json, Message.Builder builder)
         throws InvalidProtocolBufferException {

+ 1 - 1
java/util/src/main/java/com/google/protobuf/util/Timestamps.java

@@ -297,7 +297,7 @@ public final class Timestamps {
    * Convert a Timestamp to the number of microseconds elapsed from the epoch.
    *
    * <p>The result will be rounded down to the nearest microsecond. E.g., if the timestamp
-   * represents "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 millisecond.
+   * represents "1969-12-31T23:59:59.999999999Z", it will be rounded to -1 microsecond.
    */
   public static long toMicros(Timestamp timestamp) {
     checkValid(timestamp);

+ 126 - 11
java/util/src/test/java/com/google/protobuf/util/JsonFormatTest.java

@@ -34,6 +34,7 @@ import com.google.protobuf.Any;
 import com.google.protobuf.BoolValue;
 import com.google.protobuf.ByteString;
 import com.google.protobuf.BytesValue;
+import com.google.protobuf.Descriptors.FieldDescriptor;
 import com.google.protobuf.DoubleValue;
 import com.google.protobuf.FloatValue;
 import com.google.protobuf.Int32Value;
@@ -68,9 +69,12 @@ import java.io.Reader;
 import java.io.StringReader;
 import java.math.BigDecimal;
 import java.math.BigInteger;
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Set;
 import junit.framework.TestCase;
 
 public class JsonFormatTest extends TestCase {
@@ -282,8 +286,8 @@ public class JsonFormatTest extends TestCase {
     assertEquals(9012, message.getOptionalSint32());
     assertEquals(3456, message.getOptionalFixed32());
     assertEquals(7890, message.getOptionalSfixed32());
-    assertEquals(1.5f, message.getOptionalFloat());
-    assertEquals(1.25, message.getOptionalDouble());
+    assertEquals(1.5f, message.getOptionalFloat(), 0.0f);
+    assertEquals(1.25, message.getOptionalDouble(), 0.0);
     assertEquals(true, message.getOptionalBool());
   }
 
@@ -1223,6 +1227,115 @@ public class JsonFormatTest extends TestCase {
             + "}",
         JsonFormat.printer().includingDefaultValueFields().print(message));
 
+    Set<FieldDescriptor> fixedFields = new HashSet<FieldDescriptor>();
+    for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
+      if (fieldDesc.getName().contains("_fixed")) {
+        fixedFields.add(fieldDesc);
+      }
+    }
+
+    assertEquals(
+        "{\n"
+            + "  \"optionalFixed32\": 0,\n"
+            + "  \"optionalFixed64\": \"0\",\n"
+            + "  \"repeatedFixed32\": [],\n"
+            + "  \"repeatedFixed64\": []\n"
+            + "}",
+        JsonFormat.printer().includingDefaultValueFields(fixedFields).print(message));
+
+    TestAllTypes messageNonDefaults =
+        message.toBuilder().setOptionalInt64(1234).setOptionalFixed32(3232).build();
+    assertEquals(
+        "{\n"
+            + "  \"optionalInt64\": \"1234\",\n"
+            + "  \"optionalFixed32\": 3232,\n"
+            + "  \"optionalFixed64\": \"0\",\n"
+            + "  \"repeatedFixed32\": [],\n"
+            + "  \"repeatedFixed64\": []\n"
+            + "}",
+        JsonFormat.printer().includingDefaultValueFields(fixedFields).print(messageNonDefaults));
+
+    try {
+      JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields();
+      fail("IllegalStateException is expected.");
+    } catch (IllegalStateException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
+    try {
+      JsonFormat.printer().includingDefaultValueFields().includingDefaultValueFields(fixedFields);
+      fail("IllegalStateException is expected.");
+    } catch (IllegalStateException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
+    try {
+      JsonFormat.printer().includingDefaultValueFields(fixedFields).includingDefaultValueFields();
+      fail("IllegalStateException is expected.");
+    } catch (IllegalStateException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
+    try {
+      JsonFormat.printer()
+          .includingDefaultValueFields(fixedFields)
+          .includingDefaultValueFields(fixedFields);
+      fail("IllegalStateException is expected.");
+    } catch (IllegalStateException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
+    Set<FieldDescriptor> intFields = new HashSet<FieldDescriptor>();
+    for (FieldDescriptor fieldDesc : TestAllTypes.getDescriptor().getFields()) {
+      if (fieldDesc.getName().contains("_int")) {
+        intFields.add(fieldDesc);
+      }
+    }
+
+    try {
+      JsonFormat.printer()
+          .includingDefaultValueFields(intFields)
+          .includingDefaultValueFields(fixedFields);
+      fail("IllegalStateException is expected.");
+    } catch (IllegalStateException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
+    try {
+      JsonFormat.printer().includingDefaultValueFields(null);
+      fail("IllegalArgumentException is expected.");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
+    try {
+      JsonFormat.printer().includingDefaultValueFields(Collections.<FieldDescriptor>emptySet());
+      fail("IllegalArgumentException is expected.");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+      assertTrue(
+          "Exception message should mention includingDefaultValueFields.",
+          e.getMessage().contains("includingDefaultValueFields"));
+    }
+
     TestMap mapMessage = TestMap.getDefaultInstance();
     assertEquals("{\n}", JsonFormat.printer().print(mapMessage));
     assertEquals(
@@ -1291,16 +1404,17 @@ public class JsonFormatTest extends TestCase {
     assertEquals("{\n}", JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
 
     oneofMessage = TestOneof.newBuilder().setOneofInt32(42).build();
-    assertEquals("{\n  \"oneofInt32\": 42\n}",
-        JsonFormat.printer().print(oneofMessage));
-    assertEquals("{\n  \"oneofInt32\": 42\n}",
+    assertEquals("{\n  \"oneofInt32\": 42\n}", JsonFormat.printer().print(oneofMessage));
+    assertEquals(
+        "{\n  \"oneofInt32\": 42\n}",
         JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
 
     TestOneof.Builder oneofBuilder = TestOneof.newBuilder();
     mergeFromJson("{\n" + "  \"oneofNullValue\": null \n" + "}", oneofBuilder);
     oneofMessage = oneofBuilder.build();
     assertEquals("{\n  \"oneofNullValue\": null\n}", JsonFormat.printer().print(oneofMessage));
-    assertEquals("{\n  \"oneofNullValue\": null\n}",
+    assertEquals(
+        "{\n  \"oneofNullValue\": null\n}",
         JsonFormat.printer().includingDefaultValueFields().print(oneofMessage));
   }
 
@@ -1432,11 +1546,12 @@ public class JsonFormatTest extends TestCase {
 
   // Test that we are not leaking out JSON exceptions.
   public void testJsonException() throws Exception {
-    InputStream throwingInputStream = new InputStream() {
-      public int read() throws IOException {
-        throw new IOException("12345");
-      }
-    };
+    InputStream throwingInputStream =
+        new InputStream() {
+          public int read() throws IOException {
+            throw new IOException("12345");
+          }
+        };
     InputStreamReader throwingReader = new InputStreamReader(throwingInputStream);
     // When the underlying reader throws IOException, JsonFormat should forward
     // through this IOException.

+ 1 - 1
jenkins/buildcmds/pull_request_32.sh

@@ -12,5 +12,5 @@
 export DOCKERFILE_DIR=jenkins/docker32
 export DOCKER_RUN_SCRIPT=jenkins/pull_request_in_docker.sh
 export OUTPUT_DIR=testoutput
-export TEST_SET="php_all_32"
+export TEST_SET="php_all"
 ./jenkins/build_and_run_docker.sh

+ 25 - 8
jenkins/docker/Dockerfile

@@ -129,7 +129,7 @@ ENV MVN mvn --batch-mode
 RUN cd /tmp && \
   git clone https://github.com/google/protobuf.git && \
   cd protobuf && \
-  git reset 6b27c1f981a9a93918e4039f236ead27165a8e91 && \
+  git reset --hard c2b3b3e04e7a023efe06f2107705b45428847800 && \
   ./autogen.sh && \
   ./configure && \
   make -j4 && \
@@ -147,6 +147,23 @@ RUN cd php-5.5.38 && ./configure --enable-maintainer-zts --prefix=/usr/local/php
     make && make install && cd ..
 RUN cd php-5.5.38 && make clean && ./configure --prefix=/usr/local/php-5.5 && \
     make && make install && cd ..
+
+RUN wget http://am1.php.net/get/php-5.6.30.tar.bz2/from/this/mirror
+RUN mv mirror php-5.6.30.tar.bz2
+RUN tar -xvf php-5.6.30.tar.bz2
+RUN cd php-5.6.30 && ./configure --enable-maintainer-zts --prefix=/usr/local/php-5.6-zts && \
+    make && make install && cd ..
+RUN cd php-5.6.30 && make clean && ./configure --prefix=/usr/local/php-5.6 && \
+    make && make install && cd ..
+
+RUN wget http://am1.php.net/get/php-7.0.18.tar.bz2/from/this/mirror
+RUN mv mirror php-7.0.18.tar.bz2
+RUN tar -xvf php-7.0.18.tar.bz2
+RUN cd php-7.0.18 && ./configure --enable-maintainer-zts --prefix=/usr/local/php-7.0-zts && \
+    make && make install && cd ..
+RUN cd php-7.0.18 && make clean && ./configure --prefix=/usr/local/php-7.0 && \
+    make && make install && cd ..
+
 RUN php -r "copy('https://getcomposer.org/installer', 'composer-setup.php');"
 RUN php composer-setup.php
 RUN mv composer.phar /usr/bin/composer
@@ -157,21 +174,21 @@ RUN cd /tmp && \
   rm -rf protobuf && \
   git clone https://github.com/google/protobuf.git && \
   cd protobuf && \
-  git reset 46ae90dc5e145b12fffa7e053a908a9f3e066286 && \
+  git reset --hard 6b27c1f981a9a93918e4039f236ead27165a8e91 && \
   cd php && \
   ln -sfn /usr/local/php-5.5/bin/php /usr/bin/php && \
   ln -sfn /usr/local/php-5.5/bin/php-config /usr/bin/php-config && \
   ln -sfn /usr/local/php-5.5/bin/phpize /usr/bin/phpize && \
   composer install && \
   mv vendor /usr/local/vendor-5.5 && \
-  ln -sfn /usr/bin/php5.6 /usr/bin/php && \
-  ln -sfn /usr/bin/php-config5.6 /usr/bin/php-config && \
-  ln -sfn /usr/bin/phpize5.6 /usr/bin/phpize && \
+  ln -sfn /usr/local/php-5.6/bin/php /usr/bin/php && \
+  ln -sfn /usr/local/php-5.6/bin/php-config /usr/bin/php-config && \
+  ln -sfn /usr/local/php-5.6/bin/phpize /usr/bin/phpize && \
   composer install && \
   mv vendor /usr/local/vendor-5.6 && \
-  ln -sfn /usr/bin/php7.0 /usr/bin/php && \
-  ln -sfn /usr/bin/php-config7.0 /usr/bin/php-config && \
-  ln -sfn /usr/bin/phpize7.0 /usr/bin/phpize && \
+  ln -sfn /usr/local/php-7.0/bin/php /usr/bin/php && \
+  ln -sfn /usr/local/php-7.0/bin/php-config /usr/bin/php-config && \
+  ln -sfn /usr/local/php-7.0/bin/phpize /usr/bin/phpize && \
   composer install && \
   mv vendor /usr/local/vendor-7.0
 

+ 19 - 2
jenkins/docker32/Dockerfile

@@ -64,7 +64,7 @@ RUN php -r "unlink('composer-setup.php');"
 RUN cd /tmp && \
   git clone https://github.com/google/protobuf.git && \
   cd protobuf/php && \
-  git reset 6b27c1f981a9a93918e4039f236ead27165a8e91 && \
+  git reset --hard 6b27c1f981a9a93918e4039f236ead27165a8e91 && \
   ln -sfn /usr/bin/php5.5 /usr/bin/php && \
   ln -sfn /usr/bin/php-config5.5 /usr/bin/php-config && \
   ln -sfn /usr/bin/phpize5.5 /usr/bin/phpize && \
@@ -80,14 +80,31 @@ RUN cd /tmp && \
   ln -sfn /usr/bin/phpize7.0 /usr/bin/phpize && \
   composer install && \
   mv vendor /usr/local/vendor-7.0
+
 RUN wget http://am1.php.net/get/php-5.5.38.tar.bz2/from/this/mirror
 RUN mv mirror php-5.5.38.tar.bz2
 RUN tar -xvf php-5.5.38.tar.bz2
 RUN cd php-5.5.38 && ./configure --enable-maintainer-zts --prefix=/usr/local/php-5.5-zts && \
     make && make install && make clean && cd ..
-RUN cd php-5.5.38 && ./configure --enable-bcmath --prefix=/usr/local/php-5.5-bc && \
+RUN cd php-5.5.38 && make clean && ./configure --enable-bcmath --prefix=/usr/local/php-5.5 && \
     make && make install && make clean && cd ..
 
+RUN wget http://am1.php.net/get/php-5.6.30.tar.bz2/from/this/mirror
+RUN mv mirror php-5.6.30.tar.bz2
+RUN tar -xvf php-5.6.30.tar.bz2
+RUN cd php-5.6.30 && ./configure --enable-maintainer-zts --prefix=/usr/local/php-5.6-zts && \
+    make && make install && cd ..
+RUN cd php-5.6.30 && make clean && ./configure --enable-bcmath --prefix=/usr/local/php-5.6 && \
+    make && make install && cd ..
+
+RUN wget http://am1.php.net/get/php-7.0.18.tar.bz2/from/this/mirror
+RUN mv mirror php-7.0.18.tar.bz2
+RUN tar -xvf php-7.0.18.tar.bz2
+RUN cd php-7.0.18 && ./configure --enable-maintainer-zts --prefix=/usr/local/php-7.0-zts && \
+    make && make install && cd ..
+RUN cd php-7.0.18 && make clean && ./configure --enable-bcmath --prefix=/usr/local/php-7.0 && \
+    make && make install && cd ..
+
 ##################
 # Python dependencies
 

+ 11 - 6
js/binary/decoder.js

@@ -71,7 +71,7 @@ jspb.BinaryIterator = function(opt_decoder, opt_next, opt_elements) {
    */
   this.nextMethod_ = null;
 
-  /** @private {Array.<number>} */
+  /** @private {?Array<number|boolean|string>} */
   this.elements_ = null;
 
   /** @private {number} */
@@ -100,7 +100,7 @@ jspb.BinaryIterator.prototype.init_ =
     this.decoder_ = opt_decoder;
     this.nextMethod_ = opt_next;
   }
-  this.elements_ = opt_elements ? opt_elements : null;
+  this.elements_ = opt_elements || null;
   this.cursor_ = 0;
   this.nextValue_ = null;
   this.atEnd_ = !this.decoder_ && !this.elements_;
@@ -953,6 +953,7 @@ jspb.BinaryDecoder.prototype.readString = function(length) {
   var end = cursor + length;
   var codeUnits = [];
 
+  var result = '';
   while (cursor < end) {
     var c = bytes[cursor++];
     if (c < 128) { // Regular 7-bit ASCII.
@@ -973,7 +974,7 @@ jspb.BinaryDecoder.prototype.readString = function(length) {
       var c2 = bytes[cursor++];
       var c3 = bytes[cursor++];
       var c4 = bytes[cursor++];
-      // Characters written on 4 bytes have 21 bits for a codepoint. 
+      // Characters written on 4 bytes have 21 bits for a codepoint.
       // We can't fit that on 16bit characters, so we use surrogates.
       var codepoint = ((c & 7) << 18) | ((c2 & 63) << 12) | ((c3 & 63) << 6) | (c4 & 63);
       // Surrogates formula from wikipedia.
@@ -986,10 +987,14 @@ jspb.BinaryDecoder.prototype.readString = function(length) {
       var high = ((codepoint >> 10) & 1023) + 0xD800;
       codeUnits.push(high, low);
     }
+
+    // Avoid exceeding the maximum stack size when calling {@code apply}.
+    if (codeUnits.length >= 8192) {
+      result += String.fromCharCode.apply(null, codeUnits);
+      codeUnits.length = 0;
+    }
   }
-  // String.fromCharCode.apply is faster than manually appending characters on
-  // Chrome 25+, and generates no additional cons string garbage.
-  var result = String.fromCharCode.apply(null, codeUnits);
+  result += String.fromCharCode.apply(null, codeUnits);
   this.cursor_ = cursor;
   return result;
 };

+ 19 - 0
js/binary/decoder_test.js

@@ -210,6 +210,25 @@ describe('binaryDecoderTest', function() {
     assertEquals(hashD, decoder.readFixedHash64());
   });
 
+  /**
+   * Tests reading and writing large strings
+   */
+  it('testLargeStrings', function() {
+    var encoder = new jspb.BinaryEncoder();
+
+    var len = 150000;
+    var long_string = '';
+    for (var i = 0; i < len; i++) {
+      long_string += 'a';
+    }
+
+    encoder.writeString(long_string);
+
+    var decoder = jspb.BinaryDecoder.alloc(encoder.end());
+
+    assertEquals(long_string, decoder.readString(len));
+  });
+
   /**
    * Test encoding and decoding utf-8.
    */

+ 2 - 2
js/binary/encoder.js

@@ -355,8 +355,8 @@ jspb.BinaryEncoder.prototype.writeInt64 = function(value) {
  */
 jspb.BinaryEncoder.prototype.writeInt64String = function(value) {
   goog.asserts.assert(value == Math.floor(value));
-  goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
-                      (value < jspb.BinaryConstants.TWO_TO_63));
+  goog.asserts.assert((+value >= -jspb.BinaryConstants.TWO_TO_63) &&
+                      (+value < jspb.BinaryConstants.TWO_TO_63));
   jspb.utils.splitHash64(jspb.utils.decimalStringToHash64(value));
   this.writeSplitFixed64(jspb.utils.split64Low, jspb.utils.split64High);
 };

+ 1 - 1
js/binary/reader.js

@@ -971,7 +971,7 @@ jspb.BinaryReader.prototype.readFixedHash64 = function() {
 
 /**
  * Reads a packed scalar field using the supplied raw reader function.
- * @param {function()} decodeMethod
+ * @param {function(this:jspb.BinaryDecoder)} decodeMethod
  * @return {!Array}
  * @private
  */

+ 1 - 1
js/binary/utils.js

@@ -430,7 +430,7 @@ jspb.utils.joinHash64 = function(bitsLow, bitsHigh) {
 
 /**
  * Individual digits for number->string conversion.
- * @const {!Array.<number>}
+ * @const {!Array.<string>}
  */
 jspb.utils.DIGITS = [
   '0', '1', '2', '3', '4', '5', '6', '7',

+ 2 - 2
js/binary/writer.js

@@ -596,8 +596,8 @@ jspb.BinaryWriter.prototype.writeSint64 = function(field, value) {
  */
 jspb.BinaryWriter.prototype.writeSint64String = function(field, value) {
   if (value == null) return;
-  goog.asserts.assert((value >= -jspb.BinaryConstants.TWO_TO_63) &&
-                      (value < jspb.BinaryConstants.TWO_TO_63));
+  goog.asserts.assert((+value >= -jspb.BinaryConstants.TWO_TO_63) &&
+                      (+value < jspb.BinaryConstants.TWO_TO_63));
   this.writeZigzagVarint64String_(field, value);
 };
 

+ 1 - 1
js/binary/writer_test.js

@@ -47,7 +47,7 @@ goog.require('jspb.BinaryWriter');
  * @param {function()} func This function should throw an error when run.
  */
 function assertFails(func) {
-  var e = assertThrows(func);
+  assertThrows(func);
 }
 
 

+ 5 - 5
js/map.js

@@ -136,7 +136,7 @@ jspb.Map.prototype.toArray = function() {
  *
  * @param {boolean=} includeInstance Whether to include the JSPB instance for
  *    transitional soy proto support: http://goto/soy-param-migration
- * @param {!function((boolean|undefined),!V):!Object=} valueToObject
+ * @param {!function((boolean|undefined),V):!Object=} valueToObject
  *    The static toObject() method, if V is a message type.
  * @return {!Array<!Array<!Object>>}
  */
@@ -146,7 +146,7 @@ jspb.Map.prototype.toObject = function(includeInstance, valueToObject) {
   for (var i = 0; i < rawArray.length; i++) {
     var entry = this.map_[rawArray[i][0].toString()];
     this.wrapEntry_(entry);
-    var valueWrapper = /** @type {!V|undefined} */ (entry.valueWrapper);
+    var valueWrapper = /** @type {V|undefined} */ (entry.valueWrapper);
     if (valueWrapper) {
       goog.asserts.assert(valueToObject);
       entries.push([entry.key, valueToObject(includeInstance, valueWrapper)]);
@@ -412,8 +412,8 @@ jspb.Map.prototype.has = function(key) {
  * @param {!jspb.BinaryWriter} writer
  * @param {!function(this:jspb.BinaryWriter,number,K)} keyWriterFn
  *     The method on BinaryWriter that writes type K to the stream.
- * @param {!function(this:jspb.BinaryWriter,number,V)|
- *          function(this:jspb.BinaryReader,V,?)} valueWriterFn
+ * @param {!function(this:jspb.BinaryWriter,number,V,?=)|
+ *          function(this:jspb.BinaryWriter,number,V,?)} valueWriterFn
  *     The method on BinaryWriter that writes type V to the stream.  May be
  *     writeMessage, in which case the second callback arg form is used.
  * @param {function(V,!jspb.BinaryWriter)=} opt_valueWriterCallback
@@ -509,7 +509,7 @@ jspb.Map.prototype.stringKeys_ = function() {
 
 
 /**
- * @param {!K} key The entry's key.
+ * @param {K} key The entry's key.
  * @param {V=} opt_value The entry's value wrapper.
  * @constructor
  * @struct

+ 25 - 6
js/message.js

@@ -106,8 +106,9 @@ jspb.ExtensionFieldInfo = function(fieldNumber, fieldName, ctor, toObjectFn,
 /**
  * Stores binary-related information for a single extension field.
  * @param {!jspb.ExtensionFieldInfo<T>} fieldInfo
- * @param {!function(number,?)} binaryReaderFn
- * @param {!function(number,?)|function(number,?,?,?,?,?)} binaryWriterFn
+ * @param {function(this:jspb.BinaryReader,number,?)} binaryReaderFn
+ * @param {function(this:jspb.BinaryWriter,number,?)
+ *        |function(this:jspb.BinaryWriter,number,?,?,?,?,?)} binaryWriterFn
  * @param {function(?,?)=} opt_binaryMessageSerializeFn
  * @param {function(?,?)=} opt_binaryMessageDeserializeFn
  * @param {boolean=} opt_isPacked
@@ -141,6 +142,21 @@ jspb.ExtensionFieldInfo.prototype.isMessageType = function() {
 
 /**
  * Base class for all JsPb messages.
+ *
+ * Several common methods (toObject, serializeBinary, in particular) are not
+ * defined on the prototype to encourage code patterns that minimize code bloat
+ * due to otherwise unused code on all protos contained in the project.
+ *
+ * If you want to call these methods on a generic message, either
+ * pass in your instance of method as a parameter:
+ *     someFunction(instanceOfKnownProto,
+ *                  KnownProtoClass.prototype.serializeBinary);
+ * or use a lambda that knows the type:
+ *     someFunction(()=>instanceOfKnownProto.serializeBinary());
+ * or, if you don't care about code size, just suppress the
+ *     WARNING - Property serializeBinary never defined on jspb.Message
+ * and call it the intuitive way.
+ *
  * @constructor
  * @struct
  */
@@ -524,7 +540,7 @@ jspb.Message.toObjectExtension = function(proto, obj, extensions,
  * @param {!jspb.Message} proto The proto whose extensions to convert.
  * @param {*} writer The binary-format writer to write to.
  * @param {!Object} extensions The proto class' registered extensions.
- * @param {function(jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
+ * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo) : *} getExtensionFn The proto
  *     class' getExtension function. Passed for effective dead code removal.
  */
 jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
@@ -570,10 +586,13 @@ jspb.Message.serializeBinaryExtensions = function(proto, writer, extensions,
  * Reads an extension field from the given reader and, if a valid extension,
  * sets the extension value.
  * @param {!jspb.Message} msg A jspb proto.
- * @param {{skipField:function(),getFieldNumber:function():number}} reader
+ * @param {{
+ *   skipField:function(this:jspb.BinaryReader),
+ *   getFieldNumber:function(this:jspb.BinaryReader):number
+ * }} reader
  * @param {!Object} extensions The extensions object.
- * @param {function(jspb.ExtensionFieldInfo)} getExtensionFn
- * @param {function(jspb.ExtensionFieldInfo, ?)} setExtensionFn
+ * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo)} getExtensionFn
+ * @param {function(this:jspb.Message,!jspb.ExtensionFieldInfo, ?)} setExtensionFn
  */
 jspb.Message.readBinaryExtension = function(msg, reader, extensions,
     getExtensionFn, setExtensionFn) {

+ 1 - 0
js/message_test.js

@@ -40,6 +40,7 @@ goog.require('goog.userAgent');
 goog.require('jspb.Message');
 
 // CommonJS-LoadFromFile: test8_pb proto.jspb.exttest.nested
+goog.require('proto.jspb.exttest.nested.TestNestedExtensionsMessage');
 goog.require('proto.jspb.exttest.nested.TestOuterMessage');
 
 // CommonJS-LoadFromFile: test5_pb proto.jspb.exttest.beta

+ 1 - 1
js/package.json

@@ -1,6 +1,6 @@
 {
   "name": "google-protobuf",
-  "version": "3.2.0",
+  "version": "3.3.0",
   "description": "Protocol Buffers for JavaScript",
   "main": "google-protobuf.js",
   "files": [

+ 14 - 1
objectivec/google/protobuf/Duration.pbobjc.h

@@ -59,6 +59,8 @@ typedef GPB_ENUM(GPBDuration_FieldNumber) {
  * two Timestamp values is a Duration and it can be added or subtracted
  * from a Timestamp. Range is approximately +-10,000 years.
  *
+ * # Examples
+ *
  * Example 1: Compute Duration from two Timestamps in pseudo code.
  *
  *     Timestamp start = ...;
@@ -98,12 +100,23 @@ typedef GPB_ENUM(GPBDuration_FieldNumber) {
  *     td = datetime.timedelta(days=3, minutes=10)
  *     duration = Duration()
  *     duration.FromTimedelta(td)
+ *
+ * # JSON Mapping
+ *
+ * In JSON format, the Duration type is encoded as a string rather than an
+ * object, where the string ends in the suffix "s" (indicating seconds) and
+ * is preceded by the number of seconds, with nanoseconds expressed as
+ * fractional seconds. For example, 3 seconds with 0 nanoseconds should be
+ * encoded in JSON format as "3s", while 3 seconds and 1 nanosecond should
+ * be expressed in JSON format as "3.000000001s", and 3 seconds and 1
+ * microsecond should be expressed in JSON format as "3.000001s".
  **/
 @interface GPBDuration : GPBMessage
 
 /**
  * Signed seconds of the span of time. Must be from -315,576,000,000
- * to +315,576,000,000 inclusive.
+ * to +315,576,000,000 inclusive. Note: these bounds are computed from:
+ * 60 sec/min * 60 min/hr * 24 hr/day * 365.25 days/year * 10000 years
  **/
 @property(nonatomic, readwrite) int64_t seconds;
 

+ 25 - 0
objectivec/google/protobuf/Timestamp.pbobjc.h

@@ -64,6 +64,8 @@ typedef GPB_ENUM(GPBTimestamp_FieldNumber) {
  * and from  RFC 3339 date strings.
  * See [https://www.ietf.org/rfc/rfc3339.txt](https://www.ietf.org/rfc/rfc3339.txt).
  *
+ * # Examples
+ *
  * Example 1: Compute Timestamp from POSIX `time()`.
  *
  *     Timestamp timestamp;
@@ -103,6 +105,29 @@ typedef GPB_ENUM(GPBTimestamp_FieldNumber) {
  *
  *     timestamp = Timestamp()
  *     timestamp.GetCurrentTime()
+ *
+ * # JSON Mapping
+ *
+ * In JSON format, the Timestamp type is encoded as a string in the
+ * [RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
+ * format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
+ * where {year} is always expressed using four digits while {month}, {day},
+ * {hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
+ * seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
+ * are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
+ * is required, though only UTC (as indicated by "Z") is presently supported.
+ *
+ * For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
+ * 01:30 UTC on January 15, 2017.
+ *
+ * In JavaScript, one can convert a Date object to this format using the
+ * standard [toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString]
+ * method. In Python, a standard `datetime.datetime` object can be converted
+ * to this format using [`strftime`](https://docs.python.org/2/library/time.html#time.strftime)
+ * with the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one
+ * can use the Joda Time's [`ISODateTimeFormat.dateTime()`](
+ * http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime())
+ * to obtain a formatter capable of generating timestamps in this format.
  **/
 @interface GPBTimestamp : GPBMessage
 

+ 157 - 156
php/ext/google/protobuf/array.c

@@ -69,19 +69,20 @@ static zend_function_entry repeated_field_iter_methods[] = {
 
 // Forward declare static functions.
 
-static zend_object_value repeated_field_create(zend_class_entry *ce TSRMLS_DC);
-static void repeated_field_free(void *object TSRMLS_DC);
 static int repeated_field_array_init(zval *array, upb_fieldtype_t type,
                                      uint size ZEND_FILE_LINE_DC);
-static void repeated_field_free_element(void *object);
 static void repeated_field_write_dimension(zval *object, zval *offset,
                                            zval *value TSRMLS_DC);
 static int repeated_field_has_dimension(zval *object, zval *offset TSRMLS_DC);
-static HashTable *repeated_field_get_gc(zval *object, zval ***table,
+static HashTable *repeated_field_get_gc(zval *object, CACHED_VALUE **table,
                                         int *n TSRMLS_DC);
-
+#if PHP_MAJOR_VERSION < 7
+static zend_object_value repeated_field_create(zend_class_entry *ce TSRMLS_DC);
 static zend_object_value repeated_field_iter_create(zend_class_entry *ce TSRMLS_DC);
-static void repeated_field_iter_free(void *object TSRMLS_DC);
+#else
+static zend_object *repeated_field_create(zend_class_entry *ce TSRMLS_DC);
+static zend_object *repeated_field_iter_create(zend_class_entry *ce TSRMLS_DC);
+#endif
 
 // -----------------------------------------------------------------------------
 // RepeatedField creation/desctruction
@@ -90,76 +91,91 @@ static void repeated_field_iter_free(void *object TSRMLS_DC);
 zend_class_entry* repeated_field_type;
 zend_class_entry* repeated_field_iter_type;
 zend_object_handlers* repeated_field_handlers;
-
-void repeated_field_init(TSRMLS_D) {
-  zend_class_entry class_type;
-  const char* class_name = "Google\\Protobuf\\Internal\\RepeatedField";
-  INIT_CLASS_ENTRY_EX(class_type, class_name, strlen(class_name),
-                      repeated_field_methods);
-
-  repeated_field_type = zend_register_internal_class(&class_type TSRMLS_CC);
-  repeated_field_type->create_object = repeated_field_create;
-
-  zend_class_implements(repeated_field_type TSRMLS_CC, 3, spl_ce_ArrayAccess,
-                        zend_ce_aggregate, spl_ce_Countable);
-
-  repeated_field_handlers = PEMALLOC(zend_object_handlers);
-  memcpy(repeated_field_handlers, zend_get_std_object_handlers(),
-         sizeof(zend_object_handlers));
-  repeated_field_handlers->write_dimension = repeated_field_write_dimension;
-  repeated_field_handlers->get_gc = repeated_field_get_gc;
+zend_object_handlers* repeated_field_iter_handlers;
+
+// Define object free method.
+PHP_PROTO_OBJECT_FREE_START(RepeatedField, repeated_field)
+#if PHP_MAJOR_VERSION < 7
+php_proto_zval_ptr_dtor(intern->array);
+#else
+php_proto_zval_ptr_dtor(&intern->array);
+#endif
+PHP_PROTO_OBJECT_FREE_END
+
+PHP_PROTO_OBJECT_DTOR_START(RepeatedField, repeated_field)
+PHP_PROTO_OBJECT_DTOR_END
+
+// Define object create method.
+PHP_PROTO_OBJECT_CREATE_START(RepeatedField, repeated_field)
+#if PHP_MAJOR_VERSION < 7
+intern->array = NULL;
+#endif
+intern->type = 0;
+intern->msg_ce = NULL;
+PHP_PROTO_OBJECT_CREATE_END(RepeatedField, repeated_field)
+
+// Init class entry.
+PHP_PROTO_INIT_CLASS_START("Google\\Protobuf\\Internal\\RepeatedField",
+                           RepeatedField, repeated_field)
+zend_class_implements(repeated_field_type TSRMLS_CC, 3, spl_ce_ArrayAccess,
+                      zend_ce_aggregate, spl_ce_Countable);
+repeated_field_handlers->write_dimension = repeated_field_write_dimension;
+repeated_field_handlers->get_gc = repeated_field_get_gc;
+PHP_PROTO_INIT_CLASS_END
+
+// Define array element free function.
+#if PHP_MAJOR_VERSION < 7
+static inline void php_proto_array_string_release(void *value) {
+  zval_ptr_dtor(value);
 }
 
-static zend_object_value repeated_field_create(zend_class_entry *ce TSRMLS_DC) {
-  zend_object_value retval = {0};
-  RepeatedField *intern;
-
-  intern = emalloc(sizeof(RepeatedField));
-  memset(intern, 0, sizeof(RepeatedField));
-
-  zend_object_std_init(&intern->std, ce TSRMLS_CC);
-  object_properties_init(&intern->std, ce);
-
-  intern->array = NULL;
-  intern->type = 0;
-  intern->msg_ce = NULL;
-
-  retval.handle = zend_objects_store_put(
-      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
-      (zend_objects_free_object_storage_t)repeated_field_free, NULL TSRMLS_CC);
-  retval.handlers = repeated_field_handlers;
-
-  return retval;
+static inline void php_proto_array_object_release(void *value) {
+  zval_ptr_dtor(value);
 }
-
-static void repeated_field_free(void *object TSRMLS_DC) {
-  RepeatedField *intern = object;
-  zend_object_std_dtor(&intern->std TSRMLS_CC);
-  zval_ptr_dtor(&intern->array);
-  efree(object);
+static inline void php_proto_array_default_release(void *value) {
+}
+#else
+static inline void php_proto_array_string_release(zval *value) {
+  void* ptr = Z_PTR_P(value);
+  zend_string* object = *(zend_string**)ptr;
+  zend_string_release(object);
+  efree(ptr);
+}
+static inline void php_proto_array_object_release(zval *value) {
+  void* ptr = Z_PTR_P(value);
+  zend_object* object = *(zend_object**)ptr;
+  if(--GC_REFCOUNT(object) == 0) {
+    zend_objects_store_del(object);
+  }
+  efree(ptr);
 }
+static void php_proto_array_default_release(zval* value) {
+  void* ptr = Z_PTR_P(value);
+  efree(ptr);
+}
+#endif
 
 static int repeated_field_array_init(zval *array, upb_fieldtype_t type,
                                      uint size ZEND_FILE_LINE_DC) {
-  ALLOC_HASHTABLE(Z_ARRVAL_P(array));
+  PHP_PROTO_ALLOC_ARRAY(array);
 
   switch (type) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
+      zend_hash_init(Z_ARRVAL_P(array), size, NULL,
+                     php_proto_array_string_release, 0);
+      break;
     case UPB_TYPE_MESSAGE:
-      zend_hash_init(Z_ARRVAL_P(array), size, NULL, ZVAL_PTR_DTOR, 0);
+      zend_hash_init(Z_ARRVAL_P(array), size, NULL,
+                     php_proto_array_object_release, 0);
       break;
     default:
-      zend_hash_init(Z_ARRVAL_P(array), size, NULL, repeated_field_free_element,
-                     0);
+      zend_hash_init(Z_ARRVAL_P(array), size, NULL,
+                     php_proto_array_default_release, 0);
   }
-  Z_TYPE_P(array) = IS_ARRAY;
   return SUCCESS;
 }
 
-static void repeated_field_free_element(void *object) {
-}
-
 // -----------------------------------------------------------------------------
 // RepeatedField Handlers
 // -----------------------------------------------------------------------------
@@ -168,23 +184,25 @@ static void repeated_field_write_dimension(zval *object, zval *offset,
                                            zval *value TSRMLS_DC) {
   uint64_t index;
 
-  RepeatedField *intern = zend_object_store_get_object(object TSRMLS_CC);
-  HashTable *ht = HASH_OF(intern->array);
+  RepeatedField *intern = UNBOX(RepeatedField, object);
+  HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
   int size = native_slot_size(intern->type);
 
   unsigned char memory[NATIVE_SLOT_MAX_SIZE];
   memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
 
-  if (!native_slot_set(intern->type, intern->msg_ce, memory, value TSRMLS_CC)) {
+  if (!native_slot_set_by_array(intern->type, intern->msg_ce, memory,
+                                value TSRMLS_CC)) {
     return;
   }
 
   if (!offset || Z_TYPE_P(offset) == IS_NULL) {
-    index = zend_hash_num_elements(HASH_OF(intern->array));
+    index = zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array));
   } else {
     if (protobuf_convert_to_uint64(offset, &index)) {
       if (!zend_hash_index_exists(ht, index)) {
-        zend_error(E_USER_ERROR, "Element at %llu doesn't exist.\n", index);
+        zend_error(E_USER_ERROR, "Element at %llu doesn't exist.\n",
+                   (long long unsigned int)index);
         return;
       }
     } else {
@@ -192,15 +210,19 @@ static void repeated_field_write_dimension(zval *object, zval *offset,
     }
   }
 
-  zend_hash_index_update(ht, index, memory, size, NULL);
+  php_proto_zend_hash_index_update(ht, index, memory, size, NULL);
 }
 
+#if PHP_MAJOR_VERSION < 7
 static HashTable *repeated_field_get_gc(zval *object, zval ***table,
                                         int *n TSRMLS_DC) {
+#else
+static HashTable *repeated_field_get_gc(zval *object, zval **table, int *n) {
+#endif
   *table = NULL;
   *n = 0;
-  RepeatedField *intern = zend_object_store_get_object(object TSRMLS_CC);
-  return HASH_OF(intern->array);
+  RepeatedField *intern = UNBOX(RepeatedField, object);
+  return PHP_PROTO_HASH_OF(intern->array);
 }
 
 // -----------------------------------------------------------------------------
@@ -208,10 +230,10 @@ static HashTable *repeated_field_get_gc(zval *object, zval ***table,
 // -----------------------------------------------------------------------------
 
 void *repeated_field_index_native(RepeatedField *intern, int index TSRMLS_DC) {
-  HashTable *ht = HASH_OF(intern->array);
+  HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
   void *value;
 
-  if (zend_hash_index_find(ht, index, (void **)&value) == FAILURE) {
+  if (php_proto_zend_hash_index_find(ht, index, (void **)&value) == FAILURE) {
     zend_error(E_USER_ERROR, "Element at %d doesn't exist.\n", index);
     return NULL;
   }
@@ -219,35 +241,37 @@ void *repeated_field_index_native(RepeatedField *intern, int index TSRMLS_DC) {
   return value;
 }
 
-void repeated_field_push_native(RepeatedField *intern, void *value TSRMLS_DC) {
-  HashTable *ht = HASH_OF(intern->array);
+void repeated_field_push_native(RepeatedField *intern, void *value) {
+  HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
   int size = native_slot_size(intern->type);
-  zend_hash_next_index_insert(ht, (void **)value, size, NULL);
+  php_proto_zend_hash_next_index_insert(ht, (void **)value, size, NULL);
 }
 
-void repeated_field_create_with_field(zend_class_entry *ce,
-                                      const upb_fielddef *field,
-                                      zval **repeated_field TSRMLS_DC) {
+void repeated_field_create_with_field(
+    zend_class_entry *ce, const upb_fielddef *field,
+    CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC) {
   upb_fieldtype_t type = upb_fielddef_type(field);
-  const zend_class_entry *msg_ce = field_type_class(field TSRMLS_CC);
-  repeated_field_create_with_type(ce, type, msg_ce, repeated_field TSRMLS_CC);
+  const zend_class_entry *msg_ce = field_type_class(field PHP_PROTO_TSRMLS_CC);
+  repeated_field_create_with_type(ce, type, msg_ce,
+                                  repeated_field PHP_PROTO_TSRMLS_CC);
 }
 
-void repeated_field_create_with_type(zend_class_entry *ce,
-                                     upb_fieldtype_t type,
-                                     const zend_class_entry* msg_ce,
-                                     zval **repeated_field TSRMLS_DC) {
-  MAKE_STD_ZVAL(*repeated_field);
-  Z_TYPE_PP(repeated_field) = IS_OBJECT;
-  Z_OBJVAL_PP(repeated_field) =
-      repeated_field_type->create_object(repeated_field_type TSRMLS_CC);
+void repeated_field_create_with_type(
+    zend_class_entry *ce, upb_fieldtype_t type, const zend_class_entry *msg_ce,
+    CACHED_VALUE *repeated_field PHP_PROTO_TSRMLS_DC) {
+  CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(CACHED_PTR_TO_ZVAL_PTR(repeated_field),
+                                   repeated_field_type);
 
   RepeatedField *intern =
-      zend_object_store_get_object(*repeated_field TSRMLS_CC);
+      UNBOX(RepeatedField, CACHED_TO_ZVAL_PTR(*repeated_field));
   intern->type = type;
   intern->msg_ce = msg_ce;
+#if PHP_MAJOR_VERSION < 7
   MAKE_STD_ZVAL(intern->array);
   repeated_field_array_init(intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
+#else
+  repeated_field_array_init(&intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
+#endif
 
   // TODO(teboring): Link class entry for message and enum
 }
@@ -271,12 +295,16 @@ PHP_METHOD(RepeatedField, __construct) {
     return;
   }
 
-  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedField *intern = UNBOX(RepeatedField, getThis());
   intern->type = to_fieldtype(type);
   intern->msg_ce = klass;
 
+#if PHP_MAJOR_VERSION < 7
   MAKE_STD_ZVAL(intern->array);
   repeated_field_array_init(intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
+#else
+  repeated_field_array_init(&intern->array, intern->type, 0 ZEND_FILE_LINE_CC);
+#endif
 
   if (intern->type == UPB_TYPE_MESSAGE && klass == NULL) {
     zend_error(E_USER_ERROR, "Message type must have concrete class.");
@@ -313,10 +341,10 @@ PHP_METHOD(RepeatedField, offsetExists) {
     return;
   }
 
-  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedField *intern = UNBOX(RepeatedField, getThis());
 
   RETURN_BOOL(index >= 0 &&
-              index < zend_hash_num_elements(HASH_OF(intern->array)));
+              index < zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array)));
 }
 
 /**
@@ -336,15 +364,16 @@ PHP_METHOD(RepeatedField, offsetGet) {
     return;
   }
 
-  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
-  HashTable *table = HASH_OF(intern->array);
+  RepeatedField *intern = UNBOX(RepeatedField, getThis());
+  HashTable *table = PHP_PROTO_HASH_OF(intern->array);
 
-  if (zend_hash_index_find(table, index, (void **)&memory) == FAILURE) {
+  if (php_proto_zend_hash_index_find(table, index, (void **)&memory) == FAILURE) {
     zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
     return;
   }
 
-  native_slot_get(intern->type, memory, return_value_ptr TSRMLS_CC);
+  native_slot_get_by_array(intern->type, memory,
+                           ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
 }
 
 /**
@@ -379,16 +408,16 @@ PHP_METHOD(RepeatedField, offsetUnset) {
     return;
   }
 
-  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedField *intern = UNBOX(RepeatedField, getThis());
 
   // Only the element at the end of the array can be removed.
   if (index == -1 ||
-      index != (zend_hash_num_elements(HASH_OF(intern->array)) - 1)) {
+      index != (zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array)) - 1)) {
     zend_error(E_USER_ERROR, "Cannot remove element at %ld.\n", index);
     return;
   }
 
-  zend_hash_index_del(HASH_OF(intern->array), index);
+  zend_hash_index_del(PHP_PROTO_HASH_OF(intern->array), index);
 }
 
 /**
@@ -397,13 +426,13 @@ PHP_METHOD(RepeatedField, offsetUnset) {
  * @return long The number of stored elements.
  */
 PHP_METHOD(RepeatedField, count) {
-  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedField *intern = UNBOX(RepeatedField, getThis());
 
   if (zend_parse_parameters_none() == FAILURE) {
     return;
   }
 
-  RETURN_LONG(zend_hash_num_elements(HASH_OF(intern->array)));
+  RETURN_LONG(zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array)));
 }
 
 /**
@@ -412,105 +441,77 @@ PHP_METHOD(RepeatedField, count) {
  * @return object Beginning iterator.
  */
 PHP_METHOD(RepeatedField, getIterator) {
-  zval *iter_php = NULL;
-  MAKE_STD_ZVAL(iter_php);
-  Z_TYPE_P(iter_php) = IS_OBJECT;
-  Z_OBJVAL_P(iter_php) = repeated_field_iter_type->create_object(
-      repeated_field_iter_type TSRMLS_CC);
-
-  RepeatedField *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
-  RepeatedFieldIter *iter = zend_object_store_get_object(iter_php TSRMLS_CC);
+  CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(return_value,
+                                   repeated_field_iter_type);
+
+  RepeatedField *intern = UNBOX(RepeatedField, getThis());
+  RepeatedFieldIter *iter = UNBOX(RepeatedFieldIter, return_value);
   iter->repeated_field = intern;
   iter->position = 0;
-
-  RETURN_ZVAL(iter_php, 1, 1);
 }
 
 // -----------------------------------------------------------------------------
 // RepeatedFieldIter creation/desctruction
 // -----------------------------------------------------------------------------
 
-void repeated_field_iter_init(TSRMLS_D) {
-  zend_class_entry class_type;
-  const char* class_name = "Google\\Protobuf\\Internal\\RepeatedFieldIter";
-  INIT_CLASS_ENTRY_EX(class_type, class_name, strlen(class_name),
-                      repeated_field_iter_methods);
-
-  repeated_field_iter_type =
-      zend_register_internal_class(&class_type TSRMLS_CC);
-  repeated_field_iter_type->create_object = repeated_field_iter_create;
-
-  zend_class_implements(repeated_field_iter_type TSRMLS_CC, 1,
-                        zend_ce_iterator);
-}
-
-static zend_object_value repeated_field_iter_create(
-    zend_class_entry *ce TSRMLS_DC) {
-  zend_object_value retval = {0};
-  RepeatedFieldIter *intern;
-
-  intern = emalloc(sizeof(RepeatedFieldIter));
-  memset(intern, 0, sizeof(RepeatedFieldIter));
-
-  zend_object_std_init(&intern->std, ce TSRMLS_CC);
-  object_properties_init(&intern->std, ce);
+// Define object free method.
+PHP_PROTO_OBJECT_FREE_START(RepeatedFieldIter, repeated_field_iter)
+PHP_PROTO_OBJECT_FREE_END
 
-  intern->repeated_field = NULL;
-  intern->position = 0;
-
-  retval.handle = zend_objects_store_put(
-      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
-      (zend_objects_free_object_storage_t)repeated_field_iter_free,
-      NULL TSRMLS_CC);
-  retval.handlers = zend_get_std_object_handlers();
+PHP_PROTO_OBJECT_DTOR_START(RepeatedFieldIter, repeated_field_iter)
+PHP_PROTO_OBJECT_DTOR_END
 
-  return retval;
-}
+// Define object create method.
+PHP_PROTO_OBJECT_CREATE_START(RepeatedFieldIter, repeated_field_iter)
+intern->repeated_field = NULL;
+intern->position = 0;
+PHP_PROTO_OBJECT_CREATE_END(RepeatedFieldIter, repeated_field_iter)
 
-static void repeated_field_iter_free(void *object TSRMLS_DC) {
-  RepeatedFieldIter *intern = object;
-  zend_object_std_dtor(&intern->std TSRMLS_CC);
-  efree(object);
-}
+// Init class entry.
+PHP_PROTO_INIT_CLASS_START("Google\\Protobuf\\Internal\\RepeatedFieldIter",
+                           RepeatedFieldIter, repeated_field_iter)
+zend_class_implements(repeated_field_iter_type TSRMLS_CC, 1, zend_ce_iterator);
+PHP_PROTO_INIT_CLASS_END
 
 // -----------------------------------------------------------------------------
 // PHP RepeatedFieldIter Methods
 // -----------------------------------------------------------------------------
 
 PHP_METHOD(RepeatedFieldIter, rewind) {
-  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
   intern->position = 0;
 }
 
 PHP_METHOD(RepeatedFieldIter, current) {
-  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
   RepeatedField *repeated_field = intern->repeated_field;
 
   long index;
   void *memory;
 
-  HashTable *table = HASH_OF(repeated_field->array);
+  HashTable *table = PHP_PROTO_HASH_OF(repeated_field->array);
 
-  if (zend_hash_index_find(table, intern->position, (void **)&memory) ==
+  if (php_proto_zend_hash_index_find(table, intern->position, (void **)&memory) ==
       FAILURE) {
     zend_error(E_USER_ERROR, "Element at %ld doesn't exist.\n", index);
     return;
   }
-  native_slot_get(repeated_field->type, memory, return_value_ptr TSRMLS_CC);
+  native_slot_get_by_array(repeated_field->type, memory,
+                           ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
 }
 
 PHP_METHOD(RepeatedFieldIter, key) {
-  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
   RETURN_LONG(intern->position);
 }
 
 PHP_METHOD(RepeatedFieldIter, next) {
-  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
+  RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
   ++intern->position;
 }
 
 PHP_METHOD(RepeatedFieldIter, valid) {
-  RepeatedFieldIter *intern = zend_object_store_get_object(getThis() TSRMLS_CC);
-  RETURN_BOOL(zend_hash_num_elements(HASH_OF(intern->repeated_field->array)) >
-              intern->position);
+  RepeatedFieldIter *intern = UNBOX(RepeatedFieldIter, getThis());
+  RETURN_BOOL(zend_hash_num_elements(PHP_PROTO_HASH_OF(
+                  intern->repeated_field->array)) > intern->position);
 }

+ 240 - 210
php/ext/google/protobuf/def.c

@@ -30,20 +30,17 @@
 
 #include "protobuf.h"
 
+const char* const kReservedNames[] = {"Empty"};
+const int kReservedNamesSize = 1;
+
 // Forward declare.
-static zend_object_value descriptor_create(zend_class_entry *ce TSRMLS_DC);
 static void descriptor_init_c_instance(Descriptor* intern TSRMLS_DC);
 static void descriptor_free_c(Descriptor* object TSRMLS_DC);
-static void descriptor_free(void* object TSRMLS_DC);
 
-static zend_object_value enum_descriptor_create(zend_class_entry *ce TSRMLS_DC);
 static void enum_descriptor_init_c_instance(EnumDescriptor* intern TSRMLS_DC);
 static void enum_descriptor_free_c(EnumDescriptor* object TSRMLS_DC);
-static void enum_descriptor_free(void* object TSRMLS_DC);
 
-static zend_object_value descriptor_pool_create(zend_class_entry *ce TSRMLS_DC);
 static void descriptor_pool_free_c(DescriptorPool* object TSRMLS_DC);
-static void descriptor_pool_free(void* object TSRMLS_DC);
 static void descriptor_pool_init_c_instance(DescriptorPool* pool TSRMLS_DC);
 
 // -----------------------------------------------------------------------------
@@ -104,40 +101,31 @@ static void append_map_entry_name(char *result, const char *field_name,
   } while (0)
 
 // Define PHP class
-#define DEFINE_PROTOBUF_INIT_CLASS(name_lower, string_name)                  \
-  void name_lower##_init(TSRMLS_D) {                                         \
-    zend_class_entry class_type;                                             \
-    INIT_CLASS_ENTRY(class_type, string_name, name_lower##_methods);         \
-    name_lower##_type = zend_register_internal_class(&class_type TSRMLS_CC); \
-    name_lower##_type->create_object = name_lower##_create;                  \
-  }
-
-#define DEFINE_PROTOBUF_CREATE(name, name_lower)                        \
-  static zend_object_value name_lower##_create(                         \
-      zend_class_entry* ce TSRMLS_DC) {                                 \
-    zend_object_value return_value;                                     \
-    name* intern = (name*)emalloc(sizeof(name));                        \
-    memset(intern, 0, sizeof(name));                                    \
-    name_lower##_init_c_instance(intern TSRMLS_CC);                     \
-    return_value.handle = zend_objects_store_put(                       \
-        intern, (zend_objects_store_dtor_t)zend_objects_destroy_object, \
-        name_lower##_free, NULL TSRMLS_CC);                             \
-    return_value.handlers = zend_get_std_object_handlers();             \
-    return return_value;                                                \
-  }
-
-#define DEFINE_PROTOBUF_FREE(name, name_lower)            \
-  static void name_lower##_free(void* object TSRMLS_DC) { \
-    name* intern = (name*)object;                         \
-    name_lower##_free_c(intern TSRMLS_CC);                \
-    efree(object);                                        \
-  }
-
-#define DEFINE_CLASS(name, name_lower, string_name) \
-  zend_class_entry* name_lower##_type;              \
-  DEFINE_PROTOBUF_FREE(name, name_lower)            \
-  DEFINE_PROTOBUF_CREATE(name, name_lower)          \
-  DEFINE_PROTOBUF_INIT_CLASS(name_lower, string_name)
+#define DEFINE_PROTOBUF_INIT_CLASS(CLASSNAME, CAMELNAME, LOWERNAME) \
+  PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWERNAME)       \
+  PHP_PROTO_INIT_CLASS_END
+
+#define DEFINE_PROTOBUF_CREATE(NAME, LOWERNAME)  \
+  PHP_PROTO_OBJECT_CREATE_START(NAME, LOWERNAME) \
+  LOWERNAME##_init_c_instance(intern TSRMLS_CC); \
+  PHP_PROTO_OBJECT_CREATE_END(NAME, LOWERNAME)
+
+#define DEFINE_PROTOBUF_FREE(CAMELNAME, LOWERNAME)  \
+  PHP_PROTO_OBJECT_FREE_START(CAMELNAME, LOWERNAME) \
+  LOWERNAME##_free_c(intern TSRMLS_CC);             \
+  PHP_PROTO_OBJECT_FREE_END
+
+#define DEFINE_PROTOBUF_DTOR(CAMELNAME, LOWERNAME)  \
+  PHP_PROTO_OBJECT_DTOR_START(CAMELNAME, LOWERNAME) \
+  PHP_PROTO_OBJECT_DTOR_END
+
+#define DEFINE_CLASS(NAME, LOWERNAME, string_name) \
+  zend_class_entry *LOWERNAME##_type;              \
+  zend_object_handlers *LOWERNAME##_handlers;      \
+  DEFINE_PROTOBUF_FREE(NAME, LOWERNAME)            \
+  DEFINE_PROTOBUF_DTOR(NAME, LOWERNAME)            \
+  DEFINE_PROTOBUF_CREATE(NAME, LOWERNAME)          \
+  DEFINE_PROTOBUF_INIT_CLASS(string_name, NAME, LOWERNAME)
 
 // -----------------------------------------------------------------------------
 // GPBType
@@ -176,6 +164,114 @@ void gpb_type_init(TSRMLS_D) {
   zend_declare_class_constant_long(gpb_type_type, STR("SINT64"), 18 TSRMLS_CC);
 }
 
+// -----------------------------------------------------------------------------
+// Descriptor
+// -----------------------------------------------------------------------------
+
+static zend_function_entry descriptor_methods[] = {
+  ZEND_FE_END
+};
+
+DEFINE_CLASS(Descriptor, descriptor, "Google\\Protobuf\\Internal\\Descriptor");
+
+static void descriptor_free_c(Descriptor *self TSRMLS_DC) {
+  if (self->layout) {
+    free_layout(self->layout);
+  }
+  if (self->fill_handlers) {
+    upb_handlers_unref(self->fill_handlers, &self->fill_handlers);
+  }
+  if (self->fill_method) {
+    upb_pbdecodermethod_unref(self->fill_method, &self->fill_method);
+  }
+  if (self->json_fill_method) {
+    upb_json_parsermethod_unref(self->json_fill_method,
+                                &self->json_fill_method);
+  }
+  if (self->pb_serialize_handlers) {
+    upb_handlers_unref(self->pb_serialize_handlers,
+                       &self->pb_serialize_handlers);
+  }
+  if (self->json_serialize_handlers) {
+    upb_handlers_unref(self->json_serialize_handlers,
+                       &self->json_serialize_handlers);
+  }
+  if (self->json_serialize_handlers_preserve) {
+    upb_handlers_unref(self->json_serialize_handlers_preserve,
+                       &self->json_serialize_handlers_preserve);
+  }
+}
+
+static void descriptor_init_c_instance(Descriptor *desc TSRMLS_DC) {
+  // zend_object_std_init(&desc->std, descriptor_type TSRMLS_CC);
+  desc->msgdef = NULL;
+  desc->layout = NULL;
+  desc->klass = NULL;
+  desc->fill_handlers = NULL;
+  desc->fill_method = NULL;
+  desc->json_fill_method = NULL;
+  desc->pb_serialize_handlers = NULL;
+  desc->json_serialize_handlers = NULL;
+  desc->json_serialize_handlers_preserve = NULL;
+}
+
+// -----------------------------------------------------------------------------
+// EnumDescriptor
+// -----------------------------------------------------------------------------
+
+static zend_function_entry enum_descriptor_methods[] = {
+  ZEND_FE_END
+};
+
+DEFINE_CLASS(EnumDescriptor, enum_descriptor,
+             "Google\\Protobuf\\Internal\\EnumDescriptor");
+
+static void enum_descriptor_free_c(EnumDescriptor *self TSRMLS_DC) {
+}
+
+static void enum_descriptor_init_c_instance(EnumDescriptor *self TSRMLS_DC) {
+  // zend_object_std_init(&self->std, enum_descriptor_type TSRMLS_CC);
+  self->enumdef = NULL;
+  self->klass = NULL;
+}
+
+// -----------------------------------------------------------------------------
+// FieldDescriptor
+// -----------------------------------------------------------------------------
+
+upb_fieldtype_t to_fieldtype(upb_descriptortype_t type) {
+  switch (type) {
+#define CASE(descriptor_type, type)           \
+  case UPB_DESCRIPTOR_TYPE_##descriptor_type: \
+    return UPB_TYPE_##type;
+
+  CASE(FLOAT,    FLOAT);
+  CASE(DOUBLE,   DOUBLE);
+  CASE(BOOL,     BOOL);
+  CASE(STRING,   STRING);
+  CASE(BYTES,    BYTES);
+  CASE(MESSAGE,  MESSAGE);
+  CASE(GROUP,    MESSAGE);
+  CASE(ENUM,     ENUM);
+  CASE(INT32,    INT32);
+  CASE(INT64,    INT64);
+  CASE(UINT32,   UINT32);
+  CASE(UINT64,   UINT64);
+  CASE(SINT32,   INT32);
+  CASE(SINT64,   INT64);
+  CASE(FIXED32,  UINT32);
+  CASE(FIXED64,  UINT64);
+  CASE(SFIXED32, INT32);
+  CASE(SFIXED64, INT64);
+
+#undef CONVERT
+
+  }
+
+  zend_error(E_ERROR, "Unknown field type.");
+  return 0;
+}
+
 // -----------------------------------------------------------------------------
 // DescriptorPool
 // -----------------------------------------------------------------------------
@@ -190,25 +286,32 @@ static zend_function_entry descriptor_pool_methods[] = {
 DEFINE_CLASS(DescriptorPool, descriptor_pool,
              "Google\\Protobuf\\Internal\\DescriptorPool");
 
-zval* generated_pool_php;  // wrapper of generated pool
+// wrapper of generated pool
+#if PHP_MAJOR_VERSION < 7
+zval* generated_pool_php;
+#else
+zend_object *generated_pool_php;
+#endif
 DescriptorPool *generated_pool;  // The actual generated pool
 
 static void init_generated_pool_once(TSRMLS_D) {
   if (generated_pool_php == NULL) {
+#if PHP_MAJOR_VERSION < 7
     MAKE_STD_ZVAL(generated_pool_php);
-    Z_TYPE_P(generated_pool_php) = IS_OBJECT;
-    generated_pool = ALLOC(DescriptorPool);
-    descriptor_pool_init_c_instance(generated_pool TSRMLS_CC);
-    Z_OBJ_HANDLE_P(generated_pool_php) = zend_objects_store_put(
-        generated_pool, NULL,
-        (zend_objects_free_object_storage_t)descriptor_pool_free,
-        NULL TSRMLS_CC);
-    Z_OBJ_HT_P(generated_pool_php) = zend_get_std_object_handlers();
+    ZVAL_OBJ(generated_pool_php, descriptor_pool_type->create_object(
+                                     descriptor_pool_type TSRMLS_CC));
+    generated_pool = UNBOX(DescriptorPool, generated_pool_php);
+#else
+    generated_pool_php =
+        descriptor_pool_type->create_object(descriptor_pool_type TSRMLS_CC);
+    generated_pool = (DescriptorPool *)((char *)generated_pool_php -
+                                        XtOffsetOf(DescriptorPool, std));
+#endif
   }
 }
 
 static void descriptor_pool_init_c_instance(DescriptorPool *pool TSRMLS_DC) {
-  zend_object_std_init(&pool->std, descriptor_pool_type TSRMLS_CC);
+  // zend_object_std_init(&pool->std, descriptor_pool_type TSRMLS_CC);
   pool->symtab = upb_symtab_new();
 
   ALLOC_HASHTABLE(pool->pending_list);
@@ -247,66 +350,103 @@ static void validate_msgdef(const upb_msgdef* msgdef) {
 
 PHP_METHOD(DescriptorPool, getGeneratedPool) {
   init_generated_pool_once(TSRMLS_C);
+#if PHP_MAJOR_VERSION < 7
   RETURN_ZVAL(generated_pool_php, 1, 0);
+#else
+  ++GC_REFCOUNT(generated_pool_php);
+  RETURN_OBJ(generated_pool_php);
+#endif
 }
 
-static void convert_to_class_name_inplace(char *class_name,
-                                          const char* fullname,
-                                          const char* prefix,
-                                          const char* package_name) {
+static void classname_no_prefix(const char *fullname, const char *package_name,
+                                char *class_name) {
   size_t i = 0, j;
-  bool first_char = true;
+  bool first_char = true, is_reserved = false;
   size_t pkg_name_len = package_name == NULL ? 0 : strlen(package_name);
-  size_t prefix_len = prefix == NULL ? 0 : strlen(prefix);
   size_t message_name_start = package_name == NULL ? 0 : pkg_name_len + 1;
   size_t message_len = (strlen(fullname) - message_name_start);
 
-  // In php, class name cannot be Empty.
-  if (strcmp("google.protobuf.Empty", fullname) == 0) {
-    strcpy(class_name, "\\Google\\Protobuf\\GPBEmpty");
-    return;
+  // Submessage is concatenated with its containing messages by '_'.
+  for (j = message_name_start; j < message_name_start + message_len; j++) {
+    if (fullname[j] == '.') {
+      class_name[i++] = '_';
+    } else {
+      class_name[i++] = fullname[j];
+    }
+  }
+}
+
+static const char *classname_prefix(const char *classname,
+                                    const char *prefix_given,
+                                    const char *package_name) {
+  size_t i;
+  bool is_reserved = false;
+
+  if (prefix_given != NULL && strcmp(prefix_given, "") != 0) {
+    return prefix_given;
   }
 
-  if (pkg_name_len != 0) {
-    class_name[i++] = '\\';
-    for (j = 0; j < pkg_name_len; j++) {
+  for (i = 0; i < kReservedNamesSize; i++) {
+    if (strcmp(kReservedNames[i], classname) == 0) {
+      is_reserved = true;
+      break;
+    }
+  }
+
+  if (is_reserved) {
+    if (package_name != NULL && strcmp("google.protobuf", package_name) == 0) {
+      return "GPB";
+    } else {
+      return "PB";
+    }
+  }
+
+  return "";
+}
+
+static void convert_to_class_name_inplace(const char *package,
+                                          const char *prefix, char *classname) {
+  size_t package_len = package == NULL ? 0 : strlen(package);
+  size_t prefix_len = prefix == NULL ? 0 : strlen(prefix);
+  size_t classname_len = strlen(classname);
+  int i = 0, j;
+  bool first_char = true;
+
+  int offset = package_len != 0 ? 2 : 0;
+
+  for (j = 0; j < classname_len; j++) {
+    classname[package_len + prefix_len + classname_len + offset - 1 - j] =
+        classname[classname_len - j - 1];
+  }
+
+  if (package_len != 0) {
+    classname[i++] = '\\';
+    for (j = 0; j < package_len; j++) {
       // php packages are divided by '\'.
-      if (package_name[j] == '.') {
-        class_name[i++] = '\\';
+      if (package[j] == '.') {
+        classname[i++] = '\\';
         first_char = true;
       } else if (first_char) {
         // PHP package uses camel case.
-        if (package_name[j] < 'A' || package_name[j] > 'Z') {
-          class_name[i++] = package_name[j] + 'A' - 'a';
+        if (package[j] < 'A' || package[j] > 'Z') {
+          classname[i++] = package[j] + 'A' - 'a';
         } else {
-          class_name[i++] = package_name[j];
+          classname[i++] = package[j];
         }
         first_char = false;
       } else {
-        class_name[i++] = package_name[j];
+        classname[i++] = package[j];
       }
     }
-    class_name[i++] = '\\';
+    classname[i++] = '\\';
   }
 
-  if (prefix_len > 0) {
-    strcpy(class_name + i, prefix);
-    i += prefix_len;
-  }
-
-  // Submessage is concatenated with its containing messages by '_'.
-  for (j = message_name_start; j < message_name_start + message_len; j++) {
-    if (fullname[j] == '.') {
-      class_name[i++] = '_';
-    } else {
-      class_name[i++] = fullname[j];
-    }
-  }
+  memcpy(classname + i, prefix, prefix_len);
 }
 
 PHP_METHOD(DescriptorPool, internalAddGeneratedFile) {
   char *data = NULL;
-  int data_len;
+  PHP_PROTO_SIZE data_len;
   upb_filedef **files;
   size_t i;
 
@@ -335,11 +475,7 @@ PHP_METHOD(DescriptorPool, internalAddGeneratedFile) {
     switch (upb_def_type(def)) {
 #define CASE_TYPE(def_type, def_type_lower, desc_type, desc_type_lower)        \
   case UPB_DEF_##def_type: {                                                   \
-    desc_type *desc;                                                           \
-    zval *desc_php;                                                            \
-    CREATE(desc_type, desc, desc_type_lower##_init_c_instance);                \
-    BOX(desc_type, desc_php, desc, desc_type_lower##_free);                    \
-    Z_DELREF_P(desc_php);                                                      \
+    CREATE_HASHTABLE_VALUE(desc, desc_php, desc_type, desc_type_lower##_type); \
     const upb_##def_type_lower *def_type_lower =                               \
         upb_downcast_##def_type_lower(def);                                    \
     desc->def_type_lower = def_type_lower;                                     \
@@ -354,25 +490,27 @@ PHP_METHOD(DescriptorPool, internalAddGeneratedFile) {
      * bytes allocated, one for '.', one for trailing 0, and 3 for 'GPB' if    \
      * given message is google.protobuf.Empty.*/                               \
     const char *fullname = upb_##def_type_lower##_fullname(def_type_lower);    \
-    const char *prefix = upb_filedef_phpprefix(files[0]);                      \
-    size_t klass_name_len = strlen(fullname) + 5;                              \
-    if (prefix != NULL) {                                                      \
-      klass_name_len += strlen(prefix);                                        \
+    const char *prefix_given = upb_filedef_phpprefix(files[0]);                \
+    size_t classname_len = strlen(fullname) + 5;                               \
+    if (prefix_given != NULL) {                                                \
+      classname_len += strlen(prefix_given);                                   \
     }                                                                          \
-    char *klass_name = ecalloc(sizeof(char), klass_name_len);                  \
-    convert_to_class_name_inplace(klass_name, fullname, prefix,                \
-                                  upb_filedef_package(files[0]));              \
-    zend_class_entry **pce;                                                    \
-    if (zend_lookup_class(klass_name, strlen(klass_name), &pce TSRMLS_CC) ==   \
+    char *classname = ecalloc(sizeof(char), classname_len);                    \
+    const char *package = upb_filedef_package(files[0]);                       \
+    classname_no_prefix(fullname, package, classname);                         \
+    const char *prefix = classname_prefix(classname, prefix_given, package);   \
+    convert_to_class_name_inplace(package, prefix, classname);                 \
+    PHP_PROTO_CE_DECLARE pce;                                                  \
+    if (php_proto_zend_lookup_class(classname, strlen(classname), &pce) ==     \
         FAILURE) {                                                             \
       zend_error(E_ERROR, "Generated message class %s hasn't been defined",    \
-                 klass_name);                                                  \
+                 classname);                                                   \
       return;                                                                  \
     } else {                                                                   \
-      desc->klass = *pce;                                                      \
+      desc->klass = PHP_PROTO_CE_UNREF(pce);                                   \
     }                                                                          \
     add_ce_obj(desc->klass, desc_php);                                         \
-    efree(klass_name);                                                         \
+    efree(classname);                                                          \
     break;                                                                     \
   }
 
@@ -389,7 +527,7 @@ PHP_METHOD(DescriptorPool, internalAddGeneratedFile) {
     const upb_def *def = upb_filedef_def(files[0], i);
     if (upb_def_type(def) == UPB_DEF_MSG) {
       const upb_msgdef *msgdef = upb_downcast_msgdef(def);
-      zval *desc_php = get_def_obj(msgdef);
+      PHP_PROTO_HASHTABLE_VALUE desc_php = get_def_obj(msgdef);
       build_class_from_descriptor(desc_php TSRMLS_CC);
     }
   }
@@ -397,111 +535,3 @@ PHP_METHOD(DescriptorPool, internalAddGeneratedFile) {
   upb_filedef_unref(files[0], &pool);
   upb_gfree(files);
 }
-
-// -----------------------------------------------------------------------------
-// Descriptor
-// -----------------------------------------------------------------------------
-
-static zend_function_entry descriptor_methods[] = {
-  ZEND_FE_END
-};
-
-DEFINE_CLASS(Descriptor, descriptor, "Google\\Protobuf\\Internal\\Descriptor");
-
-static void descriptor_free_c(Descriptor *self TSRMLS_DC) {
-  if (self->layout) {
-    free_layout(self->layout);
-  }
-  if (self->fill_handlers) {
-    upb_handlers_unref(self->fill_handlers, &self->fill_handlers);
-  }
-  if (self->fill_method) {
-    upb_pbdecodermethod_unref(self->fill_method, &self->fill_method);
-  }
-  if (self->json_fill_method) {
-    upb_json_parsermethod_unref(self->json_fill_method,
-                                &self->json_fill_method);
-  }
-  if (self->pb_serialize_handlers) {
-    upb_handlers_unref(self->pb_serialize_handlers,
-                       &self->pb_serialize_handlers);
-  }
-  if (self->json_serialize_handlers) {
-    upb_handlers_unref(self->json_serialize_handlers,
-                       &self->json_serialize_handlers);
-  }
-  if (self->json_serialize_handlers_preserve) {
-    upb_handlers_unref(self->json_serialize_handlers_preserve,
-                       &self->json_serialize_handlers_preserve);
-  }
-}
-
-static void descriptor_init_c_instance(Descriptor *desc TSRMLS_DC) {
-  zend_object_std_init(&desc->std, descriptor_type TSRMLS_CC);
-  desc->msgdef = NULL;
-  desc->layout = NULL;
-  desc->klass = NULL;
-  desc->fill_handlers = NULL;
-  desc->fill_method = NULL;
-  desc->json_fill_method = NULL;
-  desc->pb_serialize_handlers = NULL;
-  desc->json_serialize_handlers = NULL;
-  desc->json_serialize_handlers_preserve = NULL;
-}
-
-// -----------------------------------------------------------------------------
-// EnumDescriptor
-// -----------------------------------------------------------------------------
-
-static zend_function_entry enum_descriptor_methods[] = {
-  ZEND_FE_END
-};
-
-DEFINE_CLASS(EnumDescriptor, enum_descriptor,
-             "Google\\Protobuf\\Internal\\EnumDescriptor");
-
-static void enum_descriptor_free_c(EnumDescriptor *self TSRMLS_DC) {
-}
-
-static void enum_descriptor_init_c_instance(EnumDescriptor *self TSRMLS_DC) {
-  zend_object_std_init(&self->std, enum_descriptor_type TSRMLS_CC);
-  self->enumdef = NULL;
-  self->klass = NULL;
-}
-
-// -----------------------------------------------------------------------------
-// FieldDescriptor
-// -----------------------------------------------------------------------------
-
-upb_fieldtype_t to_fieldtype(upb_descriptortype_t type) {
-  switch (type) {
-#define CASE(descriptor_type, type)           \
-  case UPB_DESCRIPTOR_TYPE_##descriptor_type: \
-    return UPB_TYPE_##type;
-
-  CASE(FLOAT,    FLOAT);
-  CASE(DOUBLE,   DOUBLE);
-  CASE(BOOL,     BOOL);
-  CASE(STRING,   STRING);
-  CASE(BYTES,    BYTES);
-  CASE(MESSAGE,  MESSAGE);
-  CASE(GROUP,    MESSAGE);
-  CASE(ENUM,     ENUM);
-  CASE(INT32,    INT32);
-  CASE(INT64,    INT64);
-  CASE(UINT32,   UINT32);
-  CASE(UINT64,   UINT64);
-  CASE(SINT32,   INT32);
-  CASE(SINT64,   INT64);
-  CASE(FIXED32,  UINT32);
-  CASE(FIXED64,  UINT64);
-  CASE(SFIXED32, INT32);
-  CASE(SFIXED64, INT64);
-
-#undef CONVERT
-
-  }
-
-  zend_error(E_ERROR, "Unknown field type.");
-  return 0;
-}

+ 341 - 182
php/ext/google/protobuf/encode_decode.c

@@ -197,19 +197,18 @@ static const void *newoneofhandlerdata(upb_handlers *h,
 static void *startseq_handler(void* closure, const void* hd) {
   MessageHeader* msg = closure;
   const size_t *ofs = hd;
-  return (void*)(*DEREF(msg, *ofs, zval**));
+  return CACHED_PTR_TO_ZVAL_PTR(DEREF(message_data(msg), *ofs, CACHED_VALUE*));
 }
 
 // Handlers that append primitive values to a repeated field.
-#define DEFINE_APPEND_HANDLER(type, ctype)                             \
-  static bool append##type##_handler(void* closure, const void* hd,    \
-                                     ctype val) {                      \
-    zval* array = (zval*)closure;                                      \
-    TSRMLS_FETCH();                                                    \
-    RepeatedField* intern =                                            \
-        (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC); \
-    repeated_field_push_native(intern, &val TSRMLS_CC);                \
-    return true;                                                       \
+#define DEFINE_APPEND_HANDLER(type, ctype)                          \
+  static bool append##type##_handler(void* closure, const void* hd, \
+                                     ctype val) {                   \
+    zval* array = (zval*)closure;                                   \
+    TSRMLS_FETCH();                                                 \
+    RepeatedField* intern = UNBOX(RepeatedField, array);            \
+    repeated_field_push_native(intern, &val);                       \
+    return true;                                                    \
   }
 
 DEFINE_APPEND_HANDLER(bool,   bool)
@@ -226,15 +225,19 @@ static void* appendstr_handler(void *closure,
                                size_t size_hint) {
   zval* array = (zval*)closure;
   TSRMLS_FETCH();
-  RepeatedField* intern =
-      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+  RepeatedField* intern = UNBOX(RepeatedField, array);
 
+#if PHP_MAJOR_VERSION < 7
   zval* str;
   MAKE_STD_ZVAL(str);
-  ZVAL_STRING(str, "", 1);
-
-  repeated_field_push_native(intern, &str TSRMLS_CC);
+  PHP_PROTO_ZVAL_STRING(str, "", 1);
+  repeated_field_push_native(intern, &str);
   return (void*)str;
+#else
+  zend_string* str = zend_string_init("", 0, 1);
+  repeated_field_push_native(intern, &str);
+  return intern;
+#endif
 }
 
 // Appends a 'bytes' string to a repeated field.
@@ -243,24 +246,51 @@ static void* appendbytes_handler(void *closure,
                                  size_t size_hint) {
   zval* array = (zval*)closure;
   TSRMLS_FETCH();
-  RepeatedField* intern =
-      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+  RepeatedField* intern = UNBOX(RepeatedField, array);
 
+#if PHP_MAJOR_VERSION < 7
   zval* str;
   MAKE_STD_ZVAL(str);
-  ZVAL_STRING(str, "", 1);
-
-  repeated_field_push_native(intern, &str TSRMLS_CC);
+  PHP_PROTO_ZVAL_STRING(str, "", 1);
+  repeated_field_push_native(intern, &str);
   return (void*)str;
+#else
+  zend_string* str = zend_string_init("", 0, 1);
+  repeated_field_push_native(intern, &str);
+  return intern;
+#endif
 }
 
+// Handlers that append primitive values to a repeated field.
+#define DEFINE_SINGULAR_HANDLER(type, ctype)                \
+  static bool type##_handler(void* closure, const void* hd, \
+                                     ctype val) {           \
+    MessageHeader* msg = (MessageHeader*)closure;           \
+    const size_t *ofs = hd;                                 \
+    DEREF(message_data(msg), *ofs, ctype) = val;            \
+    return true;                                            \
+  }
+
+DEFINE_SINGULAR_HANDLER(bool,   bool)
+DEFINE_SINGULAR_HANDLER(int32,  int32_t)
+DEFINE_SINGULAR_HANDLER(uint32, uint32_t)
+DEFINE_SINGULAR_HANDLER(float,  float)
+DEFINE_SINGULAR_HANDLER(int64,  int64_t)
+DEFINE_SINGULAR_HANDLER(uint64, uint64_t)
+DEFINE_SINGULAR_HANDLER(double, double)
+
+#undef DEFINE_SINGULAR_HANDLER
+
+#if PHP_MAJOR_VERSION < 7
 static void *empty_php_string(zval** value_ptr) {
   SEPARATE_ZVAL_IF_NOT_REF(value_ptr);
-  zval* str = *value_ptr;
-  zval_dtor(str);
-  ZVAL_STRINGL(str, "", 0, 1);
-  return (void*)str;
+  return (void*)(*value_ptr);
+}
+#else
+static void *empty_php_string(zval* value_ptr) {
+  return value_ptr;
 }
+#endif
 
 // Sets a non-repeated string field in a message.
 static void* str_handler(void *closure,
@@ -268,7 +298,7 @@ static void* str_handler(void *closure,
                          size_t size_hint) {
   MessageHeader* msg = closure;
   const size_t *ofs = hd;
-  return empty_php_string(DEREF(msg, *ofs, zval**));
+  return empty_php_string(DEREF(message_data(msg), *ofs, CACHED_VALUE*));
 }
 
 // Sets a non-repeated 'bytes' field in a message.
@@ -277,52 +307,73 @@ static void* bytes_handler(void *closure,
                            size_t size_hint) {
   MessageHeader* msg = closure;
   const size_t *ofs = hd;
-  return empty_php_string(DEREF(msg, *ofs, zval**));
+  return empty_php_string(DEREF(message_data(msg), *ofs, CACHED_VALUE*));
 }
 
 static size_t stringdata_handler(void* closure, const void* hd,
                                  const char* str, size_t len,
                                  const upb_bufhandle* handle) {
   zval* php_str = (zval*)closure;
+#if PHP_MAJOR_VERSION < 7
+  // Oneof string/bytes fields may have NULL initial value, which doesn't need
+  // to be freed.
+  if (Z_TYPE_P(php_str) == IS_STRING && !IS_INTERNED(Z_STRVAL_P(php_str))) {
+    FREE(Z_STRVAL_P(php_str));
+  }
+  ZVAL_STRINGL(php_str, str, len, 1);
+#else
+  if (Z_TYPE_P(php_str) == IS_STRING) {
+    zend_string_release(Z_STR_P(php_str));
+  }
+  ZVAL_NEW_STR(php_str, zend_string_init(str, len, 0));
+#endif
+  return len;
+}
 
-  char* old_str = Z_STRVAL_P(php_str);
-  size_t old_len = Z_STRLEN_P(php_str);
-  assert(old_str != NULL);
-
-  char* new_str = emalloc(old_len + len + 1);
+#if PHP_MAJOR_VERSION >= 7
+static size_t zendstringdata_handler(void* closure, const void* hd,
+                                     const char* str, size_t len,
+                                     const upb_bufhandle* handle) {
+  RepeatedField* intern = (RepeatedField*)closure;
 
-  memcpy(new_str, old_str, old_len);
-  memcpy(new_str + old_len, str, len);
-  new_str[old_len + len] = 0;
-  FREE(old_str);
+  unsigned char memory[NATIVE_SLOT_MAX_SIZE];
+  memset(memory, 0, NATIVE_SLOT_MAX_SIZE);
+  *(zend_string**)memory = zend_string_init(str, len, 0);
 
-  Z_STRVAL_P(php_str) = new_str;
-  Z_STRLEN_P(php_str) = old_len + len;
+  HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
+  int index = zend_hash_num_elements(ht) - 1;
+  php_proto_zend_hash_index_update(
+      ht, index, memory, sizeof(zend_string*), NULL);
 
   return len;
 }
+#endif
 
 // Appends a submessage to a repeated field.
 static void *appendsubmsg_handler(void *closure, const void *hd) {
   zval* array = (zval*)closure;
   TSRMLS_FETCH();
-  RepeatedField* intern =
-      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
+  RepeatedField* intern = UNBOX(RepeatedField, array);
 
   const submsg_handlerdata_t *submsgdata = hd;
-  zval* subdesc_php = get_def_obj((void*)submsgdata->md);
-  Descriptor* subdesc = zend_object_store_get_object(subdesc_php TSRMLS_CC);
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md));
   zend_class_entry* subklass = subdesc->klass;
   MessageHeader* submsg;
 
+#if PHP_MAJOR_VERSION < 7
   zval* val = NULL;
   MAKE_STD_ZVAL(val);
-  Z_TYPE_P(val) = IS_OBJECT;
-  Z_OBJVAL_P(val) = subklass->create_object(subklass TSRMLS_CC);
-
-  repeated_field_push_native(intern, &val TSRMLS_CC);
+  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);
 
-  submsg = zend_object_store_get_object(val TSRMLS_CC);
   return submsg;
 }
 
@@ -330,26 +381,35 @@ static void *appendsubmsg_handler(void *closure, const void *hd) {
 static void *submsg_handler(void *closure, const void *hd) {
   MessageHeader* msg = closure;
   const submsg_handlerdata_t* submsgdata = hd;
-  zval* subdesc_php = get_def_obj((void*)submsgdata->md);
   TSRMLS_FETCH();
-  Descriptor* subdesc = zend_object_store_get_object(subdesc_php TSRMLS_CC);
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)submsgdata->md));
   zend_class_entry* subklass = subdesc->klass;
   zval* submsg_php;
   MessageHeader* submsg;
 
-  if (Z_TYPE_P(*DEREF(msg, submsgdata->ofs, zval**)) == IS_NULL) {
+  if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(message_data(msg), submsgdata->ofs,
+                                            CACHED_VALUE*))) == IS_NULL) {
+#if PHP_MAJOR_VERSION < 7
     zval* val = NULL;
     MAKE_STD_ZVAL(val);
-    Z_TYPE_P(val) = IS_OBJECT;
-    Z_OBJVAL_P(val) = subklass->create_object(subklass TSRMLS_CC);
-
-    zval_ptr_dtor(DEREF(msg, submsgdata->ofs, zval**));
-    *DEREF(msg, submsgdata->ofs, 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);
+    php_proto_zval_ptr_dtor(*DEREF(message_data(msg), submsgdata->ofs, zval**));
+    *DEREF(message_data(msg), submsgdata->ofs, zval**) = val;
+#else
+    zend_object* obj = subklass->create_object(subklass TSRMLS_CC);
+    ZVAL_OBJ(DEREF(message_data(msg), submsgdata->ofs, zval*), obj);
+    MessageHeader* intern = UNBOX_HASHTABLE_VALUE(MessageHeader, obj);
+    custom_data_init(subklass, intern PHP_PROTO_TSRMLS_CC);
+#endif
   }
 
-  submsg_php = *DEREF(msg, submsgdata->ofs, zval**);
+  submsg_php = CACHED_PTR_TO_ZVAL_PTR(
+      DEREF(message_data(msg), submsgdata->ofs, CACHED_VALUE*));
 
-  submsg = zend_object_store_get_object(submsg_php TSRMLS_CC);
+  submsg = UNBOX(MessageHeader, submsg_php);
   return submsg;
 }
 
@@ -372,32 +432,52 @@ typedef struct {
 // submessage. When the submessage ends, another handler is called to insert the
 // value into the map.
 typedef struct {
-  zval* map;
   char key_storage[NATIVE_SLOT_MAX_SIZE];
   char value_storage[NATIVE_SLOT_MAX_SIZE];
-} map_parse_frame_t;
+} map_parse_frame_data_t;
 
-static void map_slot_init(void* memory, upb_fieldtype_t type) {
+PHP_PROTO_WRAP_OBJECT_START(map_parse_frame_t)
+  map_parse_frame_data_t* data;  // Place needs to be consistent with
+                                 // MessageHeader.
+  zval* map;
+  // In php7, we cannot allocate zval dynamically. So we need to add zval here
+  // to help decoding.
+  zval key_zval;
+  zval value_zval;
+PHP_PROTO_WRAP_OBJECT_END
+typedef struct map_parse_frame_t map_parse_frame_t;
+
+static void map_slot_init(void* memory, upb_fieldtype_t type, zval* cache) {
   switch (type) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
+#if PHP_MAJOR_VERSION < 7
       // Store zval** in memory in order to be consistent with the layout of
       // singular fields.
       zval** holder = ALLOC(zval*);
       zval* tmp;
       MAKE_STD_ZVAL(tmp);
-      ZVAL_STRINGL(tmp, "", 0, 1);
+      PHP_PROTO_ZVAL_STRINGL(tmp, "", 0, 1);
       *holder = tmp;
       *(zval***)memory = holder;
+#else
+      *(zval**)memory = cache;
+      PHP_PROTO_ZVAL_STRINGL(*(zval**)memory, "", 0, 1);
+#endif
       break;
     }
     case UPB_TYPE_MESSAGE: {
+#if PHP_MAJOR_VERSION < 7
       zval** holder = ALLOC(zval*);
       zval* tmp;
       MAKE_STD_ZVAL(tmp);
       ZVAL_NULL(tmp);
       *holder = tmp;
       *(zval***)memory = holder;
+#else
+      *(zval**)memory = cache;
+      ZVAL_NULL(*(zval**)memory);
+#endif
       break;
     }
     default:
@@ -410,9 +490,13 @@ static void map_slot_uninit(void* memory, upb_fieldtype_t type) {
     case UPB_TYPE_MESSAGE:
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
+#if PHP_MAJOR_VERSION < 7
       zval** holder = *(zval***)memory;
-      zval_ptr_dtor(holder);
+      php_proto_zval_ptr_dtor(*holder);
       FREE(holder);
+#else
+      php_proto_zval_ptr_dtor(*(zval**)memory);
+#endif
       break;
     }
     default:
@@ -424,7 +508,11 @@ static void map_slot_key(upb_fieldtype_t type, const void* from,
                          const char** keyval,
                          size_t* length) {
   if (type == UPB_TYPE_STRING) {
+#if PHP_MAJOR_VERSION < 7
     zval* key_php = **(zval***)from;
+#else
+    zval* key_php = *(zval**)from;
+#endif
     *keyval = Z_STRVAL_P(key_php);
     *length = Z_STRLEN_P(key_php);
   } else {
@@ -444,6 +532,7 @@ static void map_slot_value(upb_fieldtype_t type, const void* from,
   memset(to, 0, native_slot_size(type));
 
   switch (type) {
+#if PHP_MAJOR_VERSION < 7
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
     case UPB_TYPE_MESSAGE: {
@@ -451,6 +540,17 @@ static void map_slot_value(upb_fieldtype_t type, const void* from,
       Z_ADDREF_PP((zval**)to);
       break;
     }
+#else
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES:
+      *(zend_string**)to = Z_STR_P(*(zval**)from);
+      zend_string_addref(*(zend_string**)to);
+      break;
+    case UPB_TYPE_MESSAGE:
+      *(zend_object**)to = Z_OBJ_P(*(zval**)from);
+      ++GC_REFCOUNT(*(zend_object**)to);
+      break;
+#endif
     default:
       len = native_slot_size(type);
       memcpy(to, from, len);
@@ -462,13 +562,17 @@ static void map_slot_value(upb_fieldtype_t type, const void* from,
 static void *startmapentry_handler(void *closure, const void *hd) {
   MessageHeader* msg = closure;
   const map_handlerdata_t* mapdata = hd;
-  zval* map = *DEREF(msg, mapdata->ofs, zval**);
+  zval* map = CACHED_PTR_TO_ZVAL_PTR(
+      DEREF(message_data(msg), mapdata->ofs, CACHED_VALUE*));
 
   map_parse_frame_t* frame = ALLOC(map_parse_frame_t);
+  frame->data = ALLOC(map_parse_frame_data_t);
   frame->map = map;
 
-  map_slot_init(&frame->key_storage, mapdata->key_field_type);
-  map_slot_init(&frame->value_storage, mapdata->value_field_type);
+  map_slot_init(&frame->data->key_storage, mapdata->key_field_type,
+                &frame->key_zval);
+  map_slot_init(&frame->data->value_storage, mapdata->value_field_type,
+                &frame->value_zval);
 
   return frame;
 }
@@ -480,19 +584,20 @@ static bool endmap_handler(void* closure, const void* hd, upb_status* s) {
   const map_handlerdata_t* mapdata = hd;
 
   TSRMLS_FETCH();
-  Map *map = (Map *)zend_object_store_get_object(frame->map TSRMLS_CC);
+  Map *map = UNBOX(Map, frame->map);
 
   const char* keyval = NULL;
   upb_value v;
   size_t length;
 
-  map_slot_key(map->key_type, &frame->key_storage, &keyval, &length);
-  map_slot_value(map->value_type, &frame->value_storage, &v);
+  map_slot_key(map->key_type, &frame->data->key_storage, &keyval, &length);
+  map_slot_value(map->value_type, &frame->data->value_storage, &v);
 
   map_index_set(map, keyval, length, v);
 
-  map_slot_uninit(&frame->key_storage, mapdata->key_field_type);
-  map_slot_uninit(&frame->value_storage, mapdata->value_field_type);
+  map_slot_uninit(&frame->data->key_storage, mapdata->key_field_type);
+  map_slot_uninit(&frame->data->value_storage, mapdata->value_field_type);
+  FREE(frame->data);
   FREE(frame);
 
   return true;
@@ -528,14 +633,15 @@ static map_handlerdata_t* new_map_handlerdata(
 }
 
 // Handlers that set primitive values in oneofs.
-#define DEFINE_ONEOF_HANDLER(type, ctype)                           \
-  static bool oneof##type##_handler(void *closure, const void *hd,  \
-                                     ctype val) {                   \
-    const oneof_handlerdata_t *oneofdata = hd;                      \
-    DEREF(closure, oneofdata->case_ofs, uint32_t) =                 \
-        oneofdata->oneof_case_num;                                  \
-    DEREF(closure, oneofdata->ofs, ctype) = val;                    \
-    return true;                                                    \
+#define DEFINE_ONEOF_HANDLER(type, ctype)                          \
+  static bool oneof##type##_handler(void* closure, const void* hd, \
+                                    ctype val) {                   \
+    const oneof_handlerdata_t* oneofdata = hd;                     \
+    MessageHeader* msg = (MessageHeader*)closure;                  \
+    DEREF(message_data(closure), oneofdata->case_ofs, uint32_t) =  \
+        oneofdata->oneof_case_num;                                 \
+    DEREF(message_data(closure), oneofdata->ofs, ctype) = val;     \
+    return true;                                                   \
   }
 
 DEFINE_ONEOF_HANDLER(bool,   bool)
@@ -548,74 +654,71 @@ DEFINE_ONEOF_HANDLER(double, double)
 
 #undef DEFINE_ONEOF_HANDLER
 
-// Handlers for strings in a oneof.
-static void *oneofstr_handler(void *closure,
-                              const void *hd,
-                              size_t size_hint) {
-  MessageHeader* msg = closure;
-  const oneof_handlerdata_t *oneofdata = hd;
-
-  DEREF(msg, oneofdata->case_ofs, uint32_t) =
-      oneofdata->oneof_case_num;
-  DEREF(msg, oneofdata->ofs, zval**) =
-      &(msg->std.properties_table)[oneofdata->property_ofs];
-
-  return empty_php_string(DEREF(msg, oneofdata->ofs, zval**));
-}
-
+// Handlers for string/bytes in a oneof.
 static void *oneofbytes_handler(void *closure,
                                 const void *hd,
                                 size_t size_hint) {
   MessageHeader* msg = closure;
   const oneof_handlerdata_t *oneofdata = hd;
 
-  DEREF(msg, oneofdata->case_ofs, uint32_t) =
+  DEREF(message_data(msg), oneofdata->case_ofs, uint32_t) =
       oneofdata->oneof_case_num;
-  DEREF(msg, oneofdata->ofs, zval**) =
+  DEREF(message_data(msg), oneofdata->ofs, CACHED_VALUE*) =
       &(msg->std.properties_table)[oneofdata->property_ofs];
 
+   return empty_php_string(DEREF(
+       message_data(msg), oneofdata->ofs, CACHED_VALUE*));
+}
+
+static void *oneofstr_handler(void *closure,
+                              const void *hd,
+                              size_t size_hint) {
   // TODO(teboring): Add it back.
   // rb_enc_associate(str, kRubyString8bitEncoding);
-
-  SEPARATE_ZVAL_IF_NOT_REF(DEREF(msg, oneofdata->ofs, zval**));
-  zval* str = *DEREF(msg, oneofdata->ofs, zval**);
-  zval_dtor(str);
-  ZVAL_STRINGL(str, "", 0, 1);
-  return (void*)str;
+  return oneofbytes_handler(closure, hd, size_hint);
 }
 
 // Handler for a submessage field in a oneof.
 static void* oneofsubmsg_handler(void* closure, const void* hd) {
   MessageHeader* msg = closure;
   const oneof_handlerdata_t *oneofdata = hd;
-  uint32_t oldcase = DEREF(msg, oneofdata->case_ofs, uint32_t);
-  zval* subdesc_php = get_def_obj((void*)oneofdata->md);
+  uint32_t oldcase = DEREF(message_data(msg), oneofdata->case_ofs, uint32_t);
   TSRMLS_FETCH();
-  Descriptor* subdesc = zend_object_store_get_object(subdesc_php TSRMLS_CC);
+  Descriptor* subdesc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)oneofdata->md));
   zend_class_entry* subklass = subdesc->klass;
   zval* submsg_php;
   MessageHeader* submsg;
 
   if (oldcase != oneofdata->oneof_case_num) {
-    DEREF(msg, oneofdata->ofs, zval**) =
+    // Ideally, we should clean up the old data. However, we don't even know the
+    // type of the old data. So, we will defer the desctruction of the old data
+    // to the time that containing message's destroyed or the same oneof field
+    // is accessed again and find that the old data hasn't been cleaned.
+    DEREF(message_data(msg), oneofdata->ofs, CACHED_VALUE*) =
         &(msg->std.properties_table)[oneofdata->property_ofs];
-  }
 
-  if (Z_TYPE_P(*DEREF(msg, oneofdata->ofs, zval**)) == IS_NULL) {
-    zval* val = NULL;
-    MAKE_STD_ZVAL(val);
-    Z_TYPE_P(val) = IS_OBJECT;
-    Z_OBJVAL_P(val) = subklass->create_object(subklass TSRMLS_CC);
+    // Old data was't cleaned when the oneof was accessed from another field.
+    if (Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(
+        message_data(msg), oneofdata->ofs, CACHED_VALUE*))) != IS_NULL) {
+          php_proto_zval_ptr_dtor(
+              CACHED_PTR_TO_ZVAL_PTR(
+                  DEREF(message_data(msg), oneofdata->ofs, CACHED_VALUE*)));
+    }
 
-    zval_ptr_dtor(DEREF(msg, oneofdata->ofs, zval**));
-    *DEREF(msg, oneofdata->ofs, zval**) = val;
+    // Create new message.
+    ZVAL_OBJ(CACHED_PTR_TO_ZVAL_PTR(
+        DEREF(message_data(msg), oneofdata->ofs, CACHED_VALUE*)),
+        subklass->create_object(subklass TSRMLS_CC));
   }
 
-  DEREF(msg, oneofdata->case_ofs, uint32_t) =
+  DEREF(message_data(msg), oneofdata->case_ofs, uint32_t) =
       oneofdata->oneof_case_num;
 
-  submsg_php = *DEREF(msg, oneofdata->ofs, zval**);
-  submsg = zend_object_store_get_object(submsg_php TSRMLS_CC);
+  submsg_php = CACHED_PTR_TO_ZVAL_PTR(
+      DEREF(message_data(msg), oneofdata->ofs, CACHED_VALUE*));
+  submsg = UNBOX(MessageHeader, submsg_php);
+  custom_data_init(subklass, submsg PHP_PROTO_TSRMLS_CC);
   return submsg;
 }
 
@@ -652,7 +755,11 @@ static void add_handlers_for_repeated_field(upb_handlers *h,
       upb_handlers_setstartstr(h, f, is_bytes ?
                                appendbytes_handler : appendstr_handler,
                                NULL);
+#if PHP_MAJOR_VERSION < 7
       upb_handlers_setstring(h, f, stringdata_handler, NULL);
+#else
+      upb_handlers_setstring(h, f, zendstringdata_handler, NULL);
+#endif
       break;
     }
     case UPB_TYPE_MESSAGE: {
@@ -670,16 +777,26 @@ static void add_handlers_for_singular_field(upb_handlers *h,
                                             const upb_fielddef *f,
                                             size_t offset) {
   switch (upb_fielddef_type(f)) {
-    case UPB_TYPE_BOOL:
-    case UPB_TYPE_INT32:
-    case UPB_TYPE_UINT32:
-    case UPB_TYPE_ENUM:
-    case UPB_TYPE_FLOAT:
-    case UPB_TYPE_INT64:
-    case UPB_TYPE_UINT64:
-    case UPB_TYPE_DOUBLE:
-      upb_msg_setscalarhandler(h, f, offset, -1);
-      break;
+
+#define SET_HANDLER(utype, ltype)                                     \
+  case utype: {                                                       \
+    upb_handlerattr attr = UPB_HANDLERATTR_INITIALIZER;               \
+    upb_handlerattr_sethandlerdata(&attr, newhandlerdata(h, offset)); \
+    upb_handlers_set##ltype(h, f, ltype##_handler, &attr);            \
+    break;                                                            \
+  }
+
+    SET_HANDLER(UPB_TYPE_BOOL,   bool);
+    SET_HANDLER(UPB_TYPE_INT32,  int32);
+    SET_HANDLER(UPB_TYPE_UINT32, uint32);
+    SET_HANDLER(UPB_TYPE_ENUM,   int32);
+    SET_HANDLER(UPB_TYPE_FLOAT,  float);
+    SET_HANDLER(UPB_TYPE_INT64,  int64);
+    SET_HANDLER(UPB_TYPE_UINT64, uint64);
+    SET_HANDLER(UPB_TYPE_DOUBLE, double);
+
+#undef SET_HANDLER
+
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
       bool is_bytes = upb_fielddef_type(f) == UPB_TYPE_BYTES;
@@ -730,9 +847,11 @@ static void add_handlers_for_mapentry(const upb_msgdef* msgdef, upb_handlers* h,
   upb_handlers_setendmsg(h, endmap_handler, &attr);
 
   add_handlers_for_singular_field(h, key_field,
-                                  offsetof(map_parse_frame_t, key_storage));
+                                  offsetof(map_parse_frame_data_t,
+                                           key_storage));
   add_handlers_for_singular_field(h, value_field,
-                                  offsetof(map_parse_frame_t, value_storage));
+                                  offsetof(map_parse_frame_data_t, 
+                                           value_storage));
 }
 
 // Set up handlers for a oneof field.
@@ -787,8 +906,8 @@ static void add_handlers_for_message(const void* closure,
                                      upb_handlers* h) {
   const upb_msgdef* msgdef = upb_handlers_msgdef(h);
   TSRMLS_FETCH();
-  Descriptor* desc = (Descriptor*)zend_object_store_get_object(
-      get_def_obj((void*)msgdef) TSRMLS_CC);
+  Descriptor* desc =
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj((void*)msgdef));
   upb_msg_field_iter i;
 
   // If this is a mapentry message type, set up a special set of handlers and
@@ -810,13 +929,11 @@ static void add_handlers_for_message(const void* closure,
        !upb_msg_field_done(&i);
        upb_msg_field_next(&i)) {
     const upb_fielddef *f = upb_msg_iter_field(&i);
-    size_t offset = desc->layout->fields[upb_fielddef_index(f)].offset +
-        sizeof(MessageHeader);
+    size_t offset = desc->layout->fields[upb_fielddef_index(f)].offset;
 
     if (upb_fielddef_containingoneof(f)) {
       size_t oneof_case_offset =
-          desc->layout->fields[upb_fielddef_index(f)].case_offset +
-          sizeof(MessageHeader);
+          desc->layout->fields[upb_fielddef_index(f)].case_offset;
       int property_cache_index =
           desc->layout->fields[upb_fielddef_index(f)].cache_index;
       add_handlers_for_oneof_field(h, f, offset, oneof_case_offset,
@@ -883,6 +1000,8 @@ static const upb_json_parsermethod *msgdef_jsonparsermethod(Descriptor* desc) {
 
 static void putmsg(zval* msg, const Descriptor* desc, upb_sink* sink,
                    int depth TSRMLS_DC);
+static void putrawmsg(MessageHeader* msg, const Descriptor* desc,
+                      upb_sink* sink, int depth TSRMLS_DC);
 
 static void putstr(zval* str, const upb_fielddef* f, upb_sink* sink);
 
@@ -891,6 +1010,8 @@ static void putrawstr(const char* str, int len, const upb_fielddef* f,
 
 static void putsubmsg(zval* submsg, const upb_fielddef* f, upb_sink* sink,
                       int depth TSRMLS_DC);
+static void putrawsubmsg(MessageHeader* submsg, const upb_fielddef* f,
+                         upb_sink* sink, int depth TSRMLS_DC);
 
 static void putarray(zval* array, const upb_fielddef* f, upb_sink* sink,
                      int depth TSRMLS_DC);
@@ -933,8 +1054,14 @@ static void put_optional_value(const void* memory, int len, const upb_fielddef*
       putrawstr(memory, len, f, sink);
       break;
     case UPB_TYPE_MESSAGE: {
-      zval* submsg = *(zval**)memory;
-      putsubmsg(submsg, f, sink, depth TSRMLS_CC);
+#if PHP_MAJOR_VERSION < 7
+      MessageHeader *submsg = UNBOX(MessageHeader, *(zval**)memory);
+#else
+      MessageHeader *submsg =
+          (MessageHeader*)((char*)(*(zend_object**)memory) -
+              XtOffsetOf(MessageHeader, std));
+#endif
+      putrawsubmsg(submsg, f, sink, depth TSRMLS_CC);
       break;
     }
     default:
@@ -947,7 +1074,11 @@ static const char* raw_value(void* memory, const upb_fielddef* f) {
   switch (upb_fielddef_type(f)) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
+#if PHP_MAJOR_VERSION < 7
       return Z_STRVAL_PP((zval**)memory);
+#else
+      return ZSTR_VAL(*(zend_string**)memory);
+#endif
       break;
     default:
       return memory;
@@ -958,8 +1089,11 @@ static int raw_value_len(void* memory, int len, const upb_fielddef* f) {
   switch (upb_fielddef_type(f)) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
+#if PHP_MAJOR_VERSION < 7
       return Z_STRLEN_PP((zval**)memory);
-      break;
+#else
+      return ZSTR_LEN(*(zend_string**)memory);
+#endif
     default:
       return len;
   }
@@ -967,7 +1101,6 @@ static int raw_value_len(void* memory, int len, const upb_fielddef* f) {
 
 static void putmap(zval* map, const upb_fielddef* f, upb_sink* sink,
                    int depth TSRMLS_DC) {
-  Map* self;
   upb_sink subsink;
   const upb_fielddef* key_field;
   const upb_fielddef* value_field;
@@ -975,8 +1108,7 @@ static void putmap(zval* map, const upb_fielddef* f, upb_sink* sink,
   int len, size;
 
   assert(map != NULL);
-  Map* intern =
-      (Map*)zend_object_store_get_object(map TSRMLS_CC);
+  Map* intern = UNBOX(Map, map);
   size = upb_strtable_count(&intern->table);
   if (size == 0) return;
 
@@ -1013,6 +1145,12 @@ static void putmap(zval* map, const upb_fielddef* f, upb_sink* sink,
 
 static void putmsg(zval* msg_php, const Descriptor* desc, upb_sink* sink,
                    int depth TSRMLS_DC) {
+  MessageHeader* msg = UNBOX(MessageHeader, msg_php);
+  putrawmsg(msg, desc, sink, depth TSRMLS_CC);
+}
+
+static void putrawmsg(MessageHeader* msg, const Descriptor* desc,
+                      upb_sink* sink, int depth TSRMLS_DC) {
   upb_msg_field_iter i;
   upb_status status;
 
@@ -1025,21 +1163,18 @@ static void putmsg(zval* msg_php, const Descriptor* desc, upb_sink* sink,
              "Maximum recursion depth exceeded during encoding.");
   }
 
-  MessageHeader* msg = zend_object_store_get_object(msg_php TSRMLS_CC);
-
   for (upb_msg_field_begin(&i, desc->msgdef); !upb_msg_field_done(&i);
        upb_msg_field_next(&i)) {
     upb_fielddef* f = upb_msg_iter_field(&i);
-    uint32_t offset = desc->layout->fields[upb_fielddef_index(f)].offset +
-                      sizeof(MessageHeader);
+    uint32_t offset = desc->layout->fields[upb_fielddef_index(f)].offset;
 
     if (upb_fielddef_containingoneof(f)) {
       uint32_t oneof_case_offset =
-          desc->layout->fields[upb_fielddef_index(f)].case_offset +
-          sizeof(MessageHeader);
+          desc->layout->fields[upb_fielddef_index(f)].case_offset;
       // For a oneof, check that this field is actually present -- skip all the
       // below if not.
-      if (DEREF(msg, oneof_case_offset, uint32_t) != upb_fielddef_number(f)) {
+      if (DEREF(message_data(msg), oneof_case_offset, uint32_t) !=
+          upb_fielddef_number(f)) {
         continue;
       }
       // Otherwise, fall through to the appropriate singular-field handler
@@ -1047,31 +1182,36 @@ static void putmsg(zval* msg_php, const Descriptor* desc, upb_sink* sink,
     }
 
     if (is_map_field(f)) {
-      zval* map = *DEREF(msg, offset, zval**);
+      zval* map = CACHED_PTR_TO_ZVAL_PTR(
+          DEREF(message_data(msg), offset, CACHED_VALUE*));
       if (map != NULL) {
         putmap(map, f, sink, depth TSRMLS_CC);
       }
     } else if (upb_fielddef_isseq(f)) {
-      zval* array = *DEREF(msg, offset, zval**);
+      zval* array = CACHED_PTR_TO_ZVAL_PTR(
+          DEREF(message_data(msg), offset, CACHED_VALUE*));
       if (array != NULL) {
         putarray(array, f, sink, depth TSRMLS_CC);
       }
     } else if (upb_fielddef_isstring(f)) {
-      zval* str = *DEREF(msg, offset, zval**);
+      zval* str = CACHED_PTR_TO_ZVAL_PTR(
+          DEREF(message_data(msg), offset, CACHED_VALUE*));
       if (Z_STRLEN_P(str) > 0) {
         putstr(str, f, sink);
       }
     } else if (upb_fielddef_issubmsg(f)) {
-      putsubmsg(*DEREF(msg, offset, zval**), f, sink, depth TSRMLS_CC);
+      putsubmsg(CACHED_PTR_TO_ZVAL_PTR(
+                    DEREF(message_data(msg), offset, CACHED_VALUE*)),
+                f, sink, depth TSRMLS_CC);
     } else {
       upb_selector_t sel = getsel(f, upb_handlers_getprimitivehandlertype(f));
 
-#define T(upbtypeconst, upbtype, ctype, default_value) \
-  case upbtypeconst: {                                 \
-    ctype value = DEREF(msg, offset, ctype);           \
-    if (value != default_value) {                      \
-      upb_sink_put##upbtype(sink, sel, value);         \
-    }                                                  \
+#define T(upbtypeconst, upbtype, ctype, default_value)     \
+  case upbtypeconst: {                                     \
+    ctype value = DEREF(message_data(msg), offset, ctype); \
+    if (value != default_value) {                          \
+      upb_sink_put##upbtype(sink, sel, value);             \
+    }                                                      \
   } break;
 
       switch (upb_fielddef_type(f)) {
@@ -1138,18 +1278,23 @@ static void putrawstr(const char* str, int len, const upb_fielddef* f,
   upb_sink_endstr(sink, getsel(f, UPB_HANDLER_ENDSTR));
 }
 
-static void putsubmsg(zval* submsg, const upb_fielddef* f, upb_sink* sink,
+static void putsubmsg(zval* submsg_php, const upb_fielddef* f, upb_sink* sink,
                       int depth TSRMLS_DC) {
-  upb_sink subsink;
+  if (Z_TYPE_P(submsg_php) == IS_NULL) return;
 
-  if (Z_TYPE_P(submsg) == IS_NULL) return;
+  MessageHeader *submsg = UNBOX(MessageHeader, submsg_php);
+  putrawsubmsg(submsg, f, sink, depth TSRMLS_CC);
+}
+
+static void putrawsubmsg(MessageHeader* submsg, const upb_fielddef* f,
+                         upb_sink* sink, int depth TSRMLS_DC) {
+  upb_sink subsink;
 
-  zval* php_descriptor = get_def_obj(upb_fielddef_msgsubdef(f));
   Descriptor* subdesc =
-      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(upb_fielddef_msgsubdef(f)));
 
   upb_sink_startsubmsg(sink, getsel(f, UPB_HANDLER_STARTSUBMSG), &subsink);
-  putmsg(submsg, subdesc, &subsink, depth + 1 TSRMLS_CC);
+  putrawmsg(submsg, subdesc, &subsink, depth + 1 TSRMLS_CC);
   upb_sink_endsubmsg(sink, getsel(f, UPB_HANDLER_ENDSUBMSG));
 }
 
@@ -1161,9 +1306,10 @@ static void putarray(zval* array, const upb_fielddef* f, upb_sink* sink,
   int size, i;
 
   assert(array != NULL);
-  RepeatedField* intern =
-      (RepeatedField*)zend_object_store_get_object(array TSRMLS_CC);
-  size = zend_hash_num_elements(HASH_OF(intern->array));
+  RepeatedField* intern = UNBOX(RepeatedField, array);
+  HashTable *ht = PHP_PROTO_HASH_OF(intern->array);
+  size = zend_hash_num_elements(ht);
+  // size = zend_hash_num_elements(PHP_PROTO_HASH_OF(intern->array));
   if (size == 0) return;
 
   upb_sink_startseq(sink, getsel(f, UPB_HANDLER_STARTSEQ), &subsink);
@@ -1190,12 +1336,28 @@ static void putarray(zval* array, const upb_fielddef* f, upb_sink* sink,
         T(UPB_TYPE_UINT64, uint64, uint64_t)
 
       case UPB_TYPE_STRING:
-      case UPB_TYPE_BYTES:
-        putstr(*((zval**)memory), f, &subsink);
+      case UPB_TYPE_BYTES: {
+#if PHP_MAJOR_VERSION < 7
+        const char* rawstr = Z_STRVAL_P(*(zval**)memory);
+        int len = Z_STRLEN_P(*(zval**)memory);
+#else
+        const char* rawstr = ZSTR_VAL(*(zend_string**)memory);
+        int len = ZSTR_LEN(*(zend_string**)memory);
+#endif
+        putrawstr(rawstr, len, f, &subsink);
         break;
-      case UPB_TYPE_MESSAGE:
-        putsubmsg(*((zval**)memory), f, &subsink, depth TSRMLS_CC);
+      }
+      case UPB_TYPE_MESSAGE: {
+#if PHP_MAJOR_VERSION < 7
+        MessageHeader *submsg = UNBOX(MessageHeader, *(zval**)memory);
+#else
+        MessageHeader *submsg =
+            (MessageHeader*)((char*)(*(zend_object**)memory) -
+                XtOffsetOf(MessageHeader, std));
+#endif
+        putrawsubmsg(submsg, f, &subsink, depth TSRMLS_CC);
         break;
+      }
 
 #undef T
     }
@@ -1235,9 +1397,8 @@ static const upb_handlers* msgdef_json_serialize_handlers(
 // -----------------------------------------------------------------------------
 
 PHP_METHOD(Message, serializeToString) {
-  zval* php_descriptor = get_ce_obj(Z_OBJCE_P(getThis()));
   Descriptor* desc =
-      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(getThis())));
 
   stringsink sink;
   stringsink_init(&sink);
@@ -1253,7 +1414,7 @@ PHP_METHOD(Message, serializeToString) {
 
     putmsg(getThis(), desc, upb_pb_encoder_input(encoder), 0 TSRMLS_CC);
 
-    RETVAL_STRINGL(sink.ptr, sink.len, 1);
+    PHP_PROTO_RETVAL_STRINGL(sink.ptr, sink.len, 1);
 
     stackenv_uninit(&se);
     stringsink_uninit(&sink);
@@ -1261,13 +1422,13 @@ PHP_METHOD(Message, serializeToString) {
 }
 
 PHP_METHOD(Message, mergeFromString) {
-  zval* php_descriptor = get_ce_obj(Z_OBJCE_P(getThis()));
   Descriptor* desc =
-      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
-  MessageHeader* msg = zend_object_store_get_object(getThis() TSRMLS_CC);
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(getThis())));
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
 
   char *data = NULL;
-  int data_len;
+  PHP_PROTO_SIZE data_len;
+
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &data, &data_len) ==
       FAILURE) {
     return;
@@ -1290,9 +1451,8 @@ PHP_METHOD(Message, mergeFromString) {
 }
 
 PHP_METHOD(Message, jsonEncode) {
-  zval* php_descriptor = get_ce_obj(Z_OBJCE_P(getThis()));
   Descriptor* desc =
-      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(getThis())));
 
   zend_bool preserve_proto_fieldnames = false;
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|b",
@@ -1314,7 +1474,7 @@ PHP_METHOD(Message, jsonEncode) {
 
     putmsg(getThis(), desc, upb_json_printer_input(printer), 0 TSRMLS_CC);
 
-    RETVAL_STRINGL(sink.ptr, sink.len, 1);
+    PHP_PROTO_RETVAL_STRINGL(sink.ptr, sink.len, 1);
 
     stackenv_uninit(&se);
     stringsink_uninit(&sink);
@@ -1322,10 +1482,9 @@ PHP_METHOD(Message, jsonEncode) {
 }
 
 PHP_METHOD(Message, jsonDecode) {
-  zval* php_descriptor = get_ce_obj(Z_OBJCE_P(getThis()));
   Descriptor* desc =
-      (Descriptor*)zend_object_store_get_object(php_descriptor TSRMLS_CC);
-  MessageHeader* msg = zend_object_store_get_object(getThis() TSRMLS_CC);
+      UNBOX_HASHTABLE_VALUE(Descriptor, get_ce_obj(Z_OBJCE_P(getThis())));
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
 
   char *data = NULL;
   int data_len;

+ 90 - 105
php/ext/google/protobuf/map.c

@@ -103,16 +103,16 @@ static bool table_key(Map* self, zval* key,
       *out_length = Z_STRLEN_P(key);
       break;
 
-#define CASE_TYPE(upb_type, type, c_type, php_type)                   \
-  case UPB_TYPE_##upb_type: {                                         \
-    c_type type##_value;                                              \
-    if (!protobuf_convert_to_##type(key, &type##_value)) {            \
-      return false;                                                   \
-    }                                                                 \
-    native_slot_set(self->key_type, NULL, buf, key TSRMLS_CC);        \
-    *out_key = buf;                                                   \
-    *out_length = native_slot_size(self->key_type);                   \
-    break;                                                            \
+#define CASE_TYPE(upb_type, type, c_type, php_type)                     \
+  case UPB_TYPE_##upb_type: {                                           \
+    c_type type##_value;                                                \
+    if (!protobuf_convert_to_##type(key, &type##_value)) {              \
+      return false;                                                     \
+    }                                                                   \
+    native_slot_set_by_array(self->key_type, NULL, buf, key TSRMLS_CC); \
+    *out_key = buf;                                                     \
+    *out_length = native_slot_size(self->key_type);                     \
+    break;                                                              \
   }
       CASE_TYPE(BOOL, bool, int8_t, BOOL)
       CASE_TYPE(INT32, int32, int32_t, LONG)
@@ -148,7 +148,7 @@ static zend_function_entry map_field_methods[] = {
 
 // Forward declare static functions.
 
-static bool map_field_write_dimension(zval *object, zval *key,
+static void map_field_write_dimension(zval *object, zval *key,
                                       zval *value TSRMLS_DC);
 
 // -----------------------------------------------------------------------------
@@ -163,8 +163,7 @@ static void map_begin_internal(Map *map, MapIter *iter) {
   upb_strtable_begin(&iter->it, &map->table);
 }
 
-static HashTable *map_field_get_gc(zval *object, zval ***table,
-                                        int *n TSRMLS_DC) {
+static HashTable *map_field_get_gc(zval *object, CACHED_VALUE **table, int *n) {
   // TODO(teboring): Unfortunately, zend engine does not support garbage
   // collection for custom array. We have to use zend engine's native array
   // instead.
@@ -173,111 +172,101 @@ static HashTable *map_field_get_gc(zval *object, zval ***table,
   return NULL;
 }
 
-void map_field_init(TSRMLS_D) {
-  zend_class_entry class_type;
-  const char* class_name = "Google\\Protobuf\\Internal\\MapField";
-  INIT_CLASS_ENTRY_EX(class_type, class_name, strlen(class_name),
-                      map_field_methods);
-
-  map_field_type = zend_register_internal_class(&class_type TSRMLS_CC);
-  map_field_type->create_object = map_field_create;
-
-  zend_class_implements(map_field_type TSRMLS_CC, 2, spl_ce_ArrayAccess,
-                        spl_ce_Countable);
-
-  map_field_handlers = PEMALLOC(zend_object_handlers);
-  memcpy(map_field_handlers, zend_get_std_object_handlers(),
-         sizeof(zend_object_handlers));
-  map_field_handlers->write_dimension = map_field_write_dimension;
-  map_field_handlers->get_gc = map_field_get_gc;
+// Define map value element free function.
+#if PHP_MAJOR_VERSION < 7
+static inline void php_proto_map_string_release(void *value) {
+  zval_ptr_dtor(value);
 }
 
-zend_object_value map_field_create(zend_class_entry *ce TSRMLS_DC) {
-  zend_object_value retval = {0};
-  Map *intern;
-
-  intern = emalloc(sizeof(Map));
-  memset(intern, 0, sizeof(Map));
-
-  zend_object_std_init(&intern->std, ce TSRMLS_CC);
-  object_properties_init(&intern->std, ce);
-
-  // Table value type is always UINT64: this ensures enough space to store the
-  // native_slot value.
-  if (!upb_strtable_init(&intern->table, UPB_CTYPE_UINT64)) {
-    zend_error(E_USER_ERROR, "Could not allocate table.");
+static inline void php_proto_map_object_release(void *value) {
+  zval_ptr_dtor(value);
+}
+#else
+static inline void php_proto_map_string_release(void *value) {
+  zend_string* object = *(zend_string**)value;
+  zend_string_release(object);
+}
+static inline void php_proto_map_object_release(void *value) {
+  zend_object* object = *(zend_object**)value;
+  if(--GC_REFCOUNT(object) == 0) {
+    zend_objects_store_del(object);
   }
-
-  retval.handle = zend_objects_store_put(
-      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,
-      (zend_objects_free_object_storage_t)map_field_free, NULL TSRMLS_CC);
-  retval.handlers = map_field_handlers;
-
-  return retval;
 }
+#endif
 
-void map_field_free(void *object TSRMLS_DC) {
-  Map *map = (Map *)object;
-
-  switch (map->value_type) {
+// Define object free method.
+PHP_PROTO_OBJECT_FREE_START(Map, map_field)
+MapIter it;
+int len;
+for (map_begin_internal(intern, &it); !map_done(&it); map_next(&it)) {
+  upb_value value = map_iter_value(&it, &len);
+  void *mem = upb_value_memory(&value);
+  switch (intern->value_type) {
     case UPB_TYPE_MESSAGE:
+      php_proto_map_object_release(mem);
+      break;
     case UPB_TYPE_STRING:
-    case UPB_TYPE_BYTES: {
-      MapIter it;
-      int len;
-      for (map_begin_internal(map, &it); !map_done(&it); map_next(&it)) {
-        upb_value value = map_iter_value(&it, &len);
-        void *mem = upb_value_memory(&value);
-        zval_ptr_dtor(mem);
-      }
+    case UPB_TYPE_BYTES:
+      php_proto_map_string_release(mem);
       break;
-    }
     default:
       break;
   }
-
-  upb_strtable_uninit(&map->table);
-  zend_object_std_dtor(&map->std TSRMLS_CC);
-  efree(object);
 }
-
-void map_field_create_with_field(zend_class_entry *ce, const upb_fielddef *field,
-                                zval **map_field TSRMLS_DC) {
+upb_strtable_uninit(&intern->table);
+PHP_PROTO_OBJECT_FREE_END
+
+PHP_PROTO_OBJECT_DTOR_START(Map, map_field)
+PHP_PROTO_OBJECT_DTOR_END
+
+// Define object create method.
+PHP_PROTO_OBJECT_CREATE_START(Map, map_field)
+// Table value type is always UINT64: this ensures enough space to store the
+// native_slot value.
+if (!upb_strtable_init(&intern->table, UPB_CTYPE_UINT64)) {
+  zend_error(E_USER_ERROR, "Could not allocate table.");
+}
+PHP_PROTO_OBJECT_CREATE_END(Map, map_field)
+
+// Init class entry.
+PHP_PROTO_INIT_CLASS_START("Google\\Protobuf\\Internal\\MapField", Map,
+                           map_field)
+zend_class_implements(map_field_type TSRMLS_CC, 2, spl_ce_ArrayAccess,
+                      spl_ce_Countable);
+map_field_handlers->write_dimension = map_field_write_dimension;
+map_field_handlers->get_gc = map_field_get_gc;
+PHP_PROTO_INIT_CLASS_END
+
+void map_field_create_with_field(const zend_class_entry *ce,
+                                 const upb_fielddef *field,
+                                 CACHED_VALUE *map_field PHP_PROTO_TSRMLS_DC) {
   const upb_fielddef *key_field = map_field_key(field);
   const upb_fielddef *value_field = map_field_value(field);
   map_field_create_with_type(
       ce, upb_fielddef_type(key_field), upb_fielddef_type(value_field),
-      field_type_class(value_field TSRMLS_CC), map_field TSRMLS_CC);
+      field_type_class(value_field TSRMLS_CC), map_field PHP_PROTO_TSRMLS_CC);
 }
 
-void map_field_create_with_type(zend_class_entry *ce, upb_fieldtype_t key_type,
+void map_field_create_with_type(const zend_class_entry *ce,
+                                upb_fieldtype_t key_type,
                                 upb_fieldtype_t value_type,
                                 const zend_class_entry *msg_ce,
-                                zval **map_field TSRMLS_DC) {
-  MAKE_STD_ZVAL(*map_field);
-  Z_TYPE_PP(map_field) = IS_OBJECT;
-  Z_OBJVAL_PP(map_field) =
-      map_field_type->create_object(map_field_type TSRMLS_CC);
-
-  Map* intern =
-      (Map*)zend_object_store_get_object(*map_field TSRMLS_CC);
-
+                                CACHED_VALUE *map_field PHP_PROTO_TSRMLS_DC) {
+  CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(CACHED_PTR_TO_ZVAL_PTR(map_field),
+                                   map_field_type);
+  Map *intern = UNBOX(Map, CACHED_TO_ZVAL_PTR(*map_field));
   intern->key_type = key_type;
   intern->value_type = value_type;
   intern->msg_ce = msg_ce;
 }
 
-static void map_field_free_element(void *object) {
-}
-
 // -----------------------------------------------------------------------------
 // MapField Handlers
 // -----------------------------------------------------------------------------
 
 static bool map_field_read_dimension(zval *object, zval *key, int type,
-                                     zval **retval TSRMLS_DC) {
-  Map *intern =
-      (Map *)zend_object_store_get_object(object TSRMLS_CC);
+                                     CACHED_VALUE *retval TSRMLS_DC) {
+  Map *intern = UNBOX(Map, object);
 
   char keybuf[TABLE_KEY_BUF_LENGTH];
   const char* keyval = NULL;
@@ -292,7 +281,7 @@ static bool map_field_read_dimension(zval *object, zval *key, int type,
 
   if (upb_strtable_lookup2(&intern->table, keyval, length, &v)) {
     void* mem = upb_value_memory(&v);
-    native_slot_get(intern->value_type, mem, retval TSRMLS_CC);
+    native_slot_get_by_array(intern->value_type, mem, retval TSRMLS_CC);
     return true;
   } else {
     zend_error(E_USER_ERROR, "Given key doesn't exist.");
@@ -310,9 +299,9 @@ bool map_index_set(Map *intern, const char* keyval, int length, upb_value v) {
   return true;
 }
 
-static bool map_field_write_dimension(zval *object, zval *key,
+static void map_field_write_dimension(zval *object, zval *key,
                                       zval *value TSRMLS_DC) {
-  Map *intern = (Map *)zend_object_store_get_object(object TSRMLS_CC);
+  Map *intern = UNBOX(Map, object);
 
   char keybuf[TABLE_KEY_BUF_LENGTH];
   const char* keyval = NULL;
@@ -320,14 +309,14 @@ static bool map_field_write_dimension(zval *object, zval *key,
   upb_value v;
   void* mem;
   if (!table_key(intern, key, keybuf, &keyval, &length TSRMLS_CC)) {
-    return false;
+    return;
   }
 
   mem = upb_value_memory(&v);
   memset(mem, 0, native_slot_size(intern->value_type));
-  if (!native_slot_set(intern->value_type, intern->msg_ce, mem,
-                       value TSRMLS_CC)) {
-    return false;
+  if (!native_slot_set_by_array(intern->value_type, intern->msg_ce, mem,
+                                value TSRMLS_CC)) {
+    return;
   }
 #ifndef NDEBUG
   v.ctype = UPB_CTYPE_UINT64;
@@ -337,14 +326,12 @@ static bool map_field_write_dimension(zval *object, zval *key,
   upb_strtable_remove2(&intern->table, keyval, length, NULL);
   if (!upb_strtable_insert2(&intern->table, keyval, length, v)) {
     zend_error(E_USER_ERROR, "Could not insert into table");
-    return false;
+    return;
   }
-
-  return true;
 }
 
 static bool map_field_unset_dimension(zval *object, zval *key TSRMLS_DC) {
-  Map *intern = (Map *)zend_object_store_get_object(object TSRMLS_CC);
+  Map *intern = UNBOX(Map, object);
 
   char keybuf[TABLE_KEY_BUF_LENGTH];
   const char* keyval = NULL;
@@ -375,8 +362,7 @@ PHP_METHOD(MapField, __construct) {
     return;
   }
 
-  Map* intern =
-      (Map*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  Map *intern = UNBOX(Map, getThis());
   intern->key_type = to_fieldtype(key_type);
   intern->value_type = to_fieldtype(value_type);
   intern->msg_ce = klass;
@@ -404,7 +390,7 @@ PHP_METHOD(MapField, offsetExists) {
     return;
   }
 
-  Map *intern = (Map *)zend_object_store_get_object(getThis() TSRMLS_CC);
+  Map *intern = UNBOX(Map, getThis());
 
   char keybuf[TABLE_KEY_BUF_LENGTH];
   const char* keyval = NULL;
@@ -427,7 +413,7 @@ PHP_METHOD(MapField, offsetGet) {
     return;
   }
   map_field_read_dimension(getThis(), index, BP_VAR_R,
-                           return_value_ptr TSRMLS_CC);
+                           ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
 }
 
 PHP_METHOD(MapField, offsetSet) {
@@ -449,8 +435,7 @@ PHP_METHOD(MapField, offsetUnset) {
 }
 
 PHP_METHOD(MapField, count) {
-  Map *intern =
-      (Map *)zend_object_store_get_object(getThis() TSRMLS_CC);
+  Map *intern = UNBOX(Map, getThis());
 
   if (zend_parse_parameters_none() == FAILURE) {
     return;

+ 108 - 116
php/ext/google/protobuf/message.c

@@ -53,40 +53,64 @@ static  zend_function_entry message_methods[] = {
 
 // Forward declare static functions.
 
+#if PHP_MAJOR_VERSION < 7
 static void message_set_property(zval* object, zval* member, zval* value,
-                                 const zend_literal* key TSRMLS_DC);
+                                 php_proto_zend_literal key TSRMLS_DC);
 static zval* message_get_property(zval* object, zval* member, int type,
                                   const zend_literal* key TSRMLS_DC);
 static zval** message_get_property_ptr_ptr(zval* object, zval* member, int type,
-                                           const zend_literal* key TSRMLS_DC);
-static HashTable* message_get_properties(zval* object TSRMLS_DC);
+                                           php_proto_zend_literal key TSRMLS_DC);
 static HashTable* message_get_gc(zval* object, zval*** table, int* n TSRMLS_DC);
-
-static zend_object_value message_create(zend_class_entry* ce TSRMLS_DC);
-static void message_free(void* object TSRMLS_DC);
+#else
+static void message_set_property(zval* object, zval* member, zval* value,
+                                 void** cache_slot);
+static zval* message_get_property(zval* object, zval* member, int type,
+                                  void** cache_slot, zval* rv);
+static zval* message_get_property_ptr_ptr(zval* object, zval* member, int type,
+                                          void** cache_slot);
+static HashTable* message_get_gc(zval* object, zval** table, int* n);
+#endif
+static HashTable* message_get_properties(zval* object TSRMLS_DC);
 
 // -----------------------------------------------------------------------------
 // PHP Message Handlers
 // -----------------------------------------------------------------------------
 
-void message_init(TSRMLS_D) {
-  zend_class_entry class_type;
-  INIT_CLASS_ENTRY(class_type, "Google\\Protobuf\\Internal\\Message",
-                   message_methods);
-  message_type = zend_register_internal_class(&class_type TSRMLS_CC);
-
-  message_handlers = PEMALLOC(zend_object_handlers);
-  memcpy(message_handlers, zend_get_std_object_handlers(),
-         sizeof(zend_object_handlers));
+// Define object free method.
+PHP_PROTO_OBJECT_FREE_START(MessageHeader, message)
+  FREE(intern->data);
+PHP_PROTO_OBJECT_FREE_END
+
+PHP_PROTO_OBJECT_DTOR_START(MessageHeader, message)
+PHP_PROTO_OBJECT_DTOR_END
+
+// Define object create method.
+PHP_PROTO_OBJECT_CREATE_START(MessageHeader, message)
+// Because php call this create func before calling the sub-message's
+// constructor defined in PHP, it's possible that the decriptor of this class
+// hasn't been added to descritpor pool (when the class is first
+// instantiated). In that case, we will defer the initialization of the custom
+// data to the parent Message's constructor, which will be called by
+// sub-message's constructors after the descriptor has been added.
+PHP_PROTO_OBJECT_CREATE_END(MessageHeader, message)
+
+// Init class entry.
+PHP_PROTO_INIT_CLASS_START("Google\\Protobuf\\Internal\\Message",
+                           MessageHeader, message)
   message_handlers->write_property = message_set_property;
   message_handlers->read_property = message_get_property;
   message_handlers->get_property_ptr_ptr = message_get_property_ptr_ptr;
   message_handlers->get_properties = message_get_properties;
   message_handlers->get_gc = message_get_gc;
-}
+PHP_PROTO_INIT_CLASS_END
 
+#if PHP_MAJOR_VERSION < 7
 static void message_set_property(zval* object, zval* member, zval* value,
-                                 const zend_literal* key TSRMLS_DC) {
+                                 php_proto_zend_literal key TSRMLS_DC) {
+#else
+static void message_set_property(zval* object, zval* member, zval* value,
+                                 void** cache_slot) {
+#endif
   if (Z_TYPE_P(member) != IS_STRING) {
     zend_error(E_USER_ERROR, "Unexpected type for field name");
     return;
@@ -100,7 +124,7 @@ static void message_set_property(zval* object, zval* member, zval* value,
 
   const upb_fielddef* field;
 
-  MessageHeader* self = zend_object_store_get_object(object TSRMLS_CC);
+  MessageHeader* self = UNBOX(MessageHeader, object);
 
   field = upb_msgdef_ntofz(self->descriptor->msgdef, Z_STRVAL_P(member));
   if (field == NULL) {
@@ -110,46 +134,55 @@ static void message_set_property(zval* object, zval* member, zval* value,
   layout_set(self->descriptor->layout, self, field, value TSRMLS_CC);
 }
 
+#if PHP_MAJOR_VERSION < 7
 static zval* message_get_property(zval* object, zval* member, int type,
                                   const zend_literal* key TSRMLS_DC) {
+#else
+static zval* message_get_property(zval* object, zval* member, int type,
+                                  void** cache_slot, zval* rv) {
+#endif
   if (Z_TYPE_P(member) != IS_STRING) {
     zend_error(E_USER_ERROR, "Property name has to be a string.");
-    return EG(uninitialized_zval_ptr);
+    return PHP_PROTO_GLOBAL_UNINITIALIZED_ZVAL;
   }
 
   if (Z_OBJCE_P(object) != EG(scope)) {
     // User cannot get property directly (e.g., $a = $m->a)
     zend_error(E_USER_ERROR, "Cannot access private property.");
-    return EG(uninitialized_zval_ptr);
+    return PHP_PROTO_GLOBAL_UNINITIALIZED_ZVAL;
   }
 
-  zend_property_info* property_info = NULL;
-
-  // All properties should have been declared in the generated code and have
-  // corresponding zvals in properties_table.
-  ulong h = zend_get_hash_value(Z_STRVAL_P(member), Z_STRLEN_P(member) + 1);
-  if (zend_hash_quick_find(&Z_OBJCE_P(object)->properties_info,
-                           Z_STRVAL_P(member), Z_STRLEN_P(member) + 1, h,
-                           (void**)&property_info) != SUCCESS) {
-    zend_error(E_USER_ERROR, "Property does not exist.");
-    return EG(uninitialized_zval_ptr);
-  }
-
-  MessageHeader* self =
-      (MessageHeader*)zend_object_store_get_object(object TSRMLS_CC);
-
+  MessageHeader* self = UNBOX(MessageHeader, object);
   const upb_fielddef* field;
   field = upb_msgdef_ntofz(self->descriptor->msgdef, Z_STRVAL_P(member));
   if (field == NULL) {
-    return EG(uninitialized_zval_ptr);
+    return PHP_PROTO_GLOBAL_UNINITIALIZED_ZVAL;
   }
+
+  zend_property_info* property_info;
+#if PHP_MAJOR_VERSION < 7
+  property_info =
+      zend_get_property_info(Z_OBJCE_P(object), member, true TSRMLS_CC);
   return layout_get(
       self->descriptor->layout, message_data(self), field,
       &Z_OBJ_P(object)->properties_table[property_info->offset] TSRMLS_CC);
+#else
+  property_info =
+      zend_get_property_info(Z_OBJCE_P(object), Z_STR_P(member), true);
+  return layout_get(
+      self->descriptor->layout, message_data(self), field,
+      OBJ_PROP(Z_OBJ_P(object), property_info->offset) TSRMLS_CC);
+#endif
 }
 
+#if PHP_MAJOR_VERSION < 7
 static zval** message_get_property_ptr_ptr(zval* object, zval* member, int type,
-                                           const zend_literal* key TSRMLS_DC) {
+                                           php_proto_zend_literal key
+                                               TSRMLS_DC) {
+#else
+static zval* message_get_property_ptr_ptr(zval* object, zval* member, int type,
+                                          void** cache_slot) {
+#endif
   return NULL;
 }
 
@@ -157,68 +190,37 @@ static HashTable* message_get_properties(zval* object TSRMLS_DC) {
   return NULL;
 }
 
-static HashTable* message_get_gc(zval* object, zval*** table, int* n TSRMLS_DC) {
-    zend_object* zobj = Z_OBJ_P(object);
-    *table = zobj->properties_table;
-    *n = zobj->ce->default_properties_count;
-    return NULL;
+static HashTable* message_get_gc(zval* object, CACHED_VALUE** table,
+                                 int* n TSRMLS_DC) {
+  zend_object* zobj = Z_OBJ_P(object);
+  *table = zobj->properties_table;
+  *n = zobj->ce->default_properties_count;
+  return NULL;
 }
 
 // -----------------------------------------------------------------------------
 // C Message Utilities
 // -----------------------------------------------------------------------------
 
-void* message_data(void* msg) {
-  return ((uint8_t*)msg) + sizeof(MessageHeader);
+void* message_data(MessageHeader* msg) {
+  return msg->data;
 }
 
-static void message_free(void* object TSRMLS_DC) {
-  MessageHeader* msg = (MessageHeader*)object;
-  int i;
-
-  for (i = 0; i < msg->std.ce->default_properties_count; i++) {
-    zval_ptr_dtor(&msg->std.properties_table[i]);
-  }
-  efree(msg->std.properties_table);
-  efree(msg);
-}
-
-static zend_object_value message_create(zend_class_entry* ce TSRMLS_DC) {
-  zend_object_value return_value;
-
-  zval* php_descriptor = get_ce_obj(ce);
-
-  Descriptor* desc = zend_object_store_get_object(php_descriptor TSRMLS_CC);
-  MessageHeader* msg = (MessageHeader*)ALLOC_N(
-      uint8_t, sizeof(MessageHeader) + desc->layout->size);
-  memset(message_data(msg), 0, desc->layout->size);
-
+void custom_data_init(const zend_class_entry* ce,
+                      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().
-  msg->descriptor = desc;
-
-  zend_object_std_init(&msg->std, ce TSRMLS_CC);
-  object_properties_init(&msg->std, ce);
-  layout_init(desc->layout, message_data(msg),
-              msg->std.properties_table TSRMLS_CC);
-
-  return_value.handle = zend_objects_store_put(
-      msg, (zend_objects_store_dtor_t)zend_objects_destroy_object, message_free,
-      NULL TSRMLS_CC);
-
-  return_value.handlers = message_handlers;
-  return return_value;
+  intern->descriptor = desc;
+  layout_init(desc->layout, message_data(intern),
+              intern->std.properties_table PHP_PROTO_TSRMLS_CC);
 }
 
-void message_create_with_type(zend_class_entry* ce, zval** message TSRMLS_DC) {
-  MAKE_STD_ZVAL(*message);
-  Z_TYPE_PP(message) = IS_OBJECT;
-  Z_OBJVAL_PP(message) = ce->create_object(ce TSRMLS_CC);
-  Z_DELREF_PP(message);
-}
-
-void build_class_from_descriptor(zval* php_descriptor TSRMLS_DC) {
-  Descriptor* desc = UNBOX(Descriptor, php_descriptor);
+void build_class_from_descriptor(
+    PHP_PROTO_HASHTABLE_VALUE php_descriptor TSRMLS_DC) {
+  Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, php_descriptor);
 
   // Map entries don't have existing php class.
   if (upb_msgdef_mapentry(desc->msgdef)) {
@@ -243,26 +245,18 @@ void build_class_from_descriptor(zval* php_descriptor TSRMLS_DC) {
 // modified. As a result, the first created instance will be a normal zend
 // object. Here, we manually modify it to our message in such a case.
 PHP_METHOD(Message, __construct) {
-  if (Z_OBJVAL_P(getThis()).handlers != message_handlers) {
-    zend_class_entry* ce = Z_OBJCE_P(getThis());
-    zval_dtor(getThis());
-    Z_OBJVAL_P(getThis()) = message_create(ce TSRMLS_CC);
+  zend_class_entry* ce = Z_OBJCE_P(getThis());
+  if (EXPECTED(class_added(ce))) {
+    MessageHeader* intern = UNBOX(MessageHeader, getThis());
+    custom_data_init(ce, intern PHP_PROTO_TSRMLS_CC);
   }
 }
 
 PHP_METHOD(Message, clear) {
-  MessageHeader* msg =
-      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
   Descriptor* desc = msg->descriptor;
   zend_class_entry* ce = desc->klass;
-  int i;
-
-  for (i = 0; i < msg->std.ce->default_properties_count; i++) {
-    zval_ptr_dtor(&msg->std.properties_table[i]);
-  }
-  efree(msg->std.properties_table);
 
-  zend_object_std_init(&msg->std, ce TSRMLS_CC);
   object_properties_init(&msg->std, ce);
   layout_init(desc->layout, message_data(msg),
               msg->std.properties_table TSRMLS_CC);
@@ -275,10 +269,8 @@ PHP_METHOD(Message, mergeFrom) {
     return;
   }
 
-  MessageHeader* from =
-      (MessageHeader*)zend_object_store_get_object(value TSRMLS_CC);
-  MessageHeader* to =
-      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  MessageHeader* from = UNBOX(MessageHeader, value);
+  MessageHeader* to = UNBOX(MessageHeader, getThis());
 
   if(from->descriptor != to->descriptor) {
     zend_error(E_USER_ERROR, "Cannot merge messages with different class.");
@@ -289,36 +281,37 @@ PHP_METHOD(Message, mergeFrom) {
 }
 
 PHP_METHOD(Message, readOneof) {
-  long index;
+  PHP_PROTO_LONG index;
 
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "l", &index) ==
       FAILURE) {
     return;
   }
 
-  MessageHeader* msg =
-      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
 
   const upb_fielddef* field = upb_msgdef_itof(msg->descriptor->msgdef, index);
 
   int property_cache_index =
       msg->descriptor->layout->fields[upb_fielddef_index(field)].cache_index;
-  zval** cache_ptr = &(msg->std.properties_table)[property_cache_index];
+  zval* property_ptr = OBJ_PROP(Z_OBJ_P(getThis()), property_cache_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
+  // value directly.
   layout_get(msg->descriptor->layout, message_data(msg), field,
-             &return_value TSRMLS_CC);
+             ZVAL_PTR_TO_CACHED_PTR(return_value) TSRMLS_CC);
 }
 
 PHP_METHOD(Message, writeOneof) {
-  long index;
+  PHP_PROTO_LONG index;
   zval* value;
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "lz", &index, &value) ==
       FAILURE) {
     return;
   }
 
-  MessageHeader* msg =
-      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
 
   const upb_fielddef* field = upb_msgdef_itof(msg->descriptor->msgdef, index);
 
@@ -327,19 +320,18 @@ PHP_METHOD(Message, writeOneof) {
 
 PHP_METHOD(Message, whichOneof) {
   char* oneof_name;
-  int length;
+  PHP_PROTO_SIZE length;
 
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "s", &oneof_name,
                             &length) == FAILURE) {
     return;
   }
 
-  MessageHeader* msg =
-      (MessageHeader*)zend_object_store_get_object(getThis() TSRMLS_CC);
+  MessageHeader* msg = UNBOX(MessageHeader, getThis());
 
   const upb_oneofdef* oneof =
       upb_msgdef_ntoo(msg->descriptor->msgdef, oneof_name, length);
   const char* oneof_case_name = layout_get_oneof_case(
       msg->descriptor->layout, message_data(msg), oneof TSRMLS_CC);
-  RETURN_STRING(oneof_case_name, 1);
+  PHP_PROTO_RETURN_STRING(oneof_case_name, 1);
 }

+ 21 - 5
php/ext/google/protobuf/package.xml

@@ -13,16 +13,16 @@
  <date>2017-01-13</date>
  <time>16:06:07</time>
  <version>
-  <release>3.2.0a1</release>
-  <api>3.2.0a1</api>
+  <release>3.3.0</release>
+  <api>3.3.0</api>
  </version>
  <stability>
-  <release>alpha</release>
-  <api>alpha</api>
+  <release>stable</release>
+  <api>stable</api>
  </stability>
  <license uri="https://opensource.org/licenses/BSD-3-Clause">3-Clause BSD License</license>
  <notes>
-Second alpha release.
+GA release.
  </notes>
  <contents>
   <dir baseinstalldir="/" name="/">
@@ -87,5 +87,21 @@ First alpha release
 Second alpha release.
    </notes>
   </release>
+  <release>
+   <version>
+    <release>3.3.0</release>
+    <api>3.3.0</api>
+   </version>
+   <stability>
+    <release>stable</release>
+    <api>stable</api>
+   </stability>
+   <date>2017-04-28</date>
+   <time>16:06:07</time>
+   <license uri="https://opensource.org/licenses/BSD-3-Clause">3-Clause BSD License</license>
+   <notes>
+GA release.
+   </notes>
+  </release>
  </changelog>
 </package>

+ 46 - 11
php/ext/google/protobuf/protobuf.c

@@ -55,39 +55,60 @@ static void add_to_table(HashTable* t, const void* def, void* value) {
   uint nIndex = (ulong)def & t->nTableMask;
 
   zval* pDest = NULL;
-  zend_hash_index_update(t, (zend_ulong)def, &value, sizeof(zval*), (void**)&pDest);
+  php_proto_zend_hash_index_update(t, (zend_ulong)def, &value, sizeof(zval*),
+                                   (void**)&pDest);
 }
 
 static void* get_from_table(const HashTable* t, const void* def) {
   void** value;
-  if (zend_hash_index_find(t, (zend_ulong)def, (void**)&value) == FAILURE) {
+  if (php_proto_zend_hash_index_find(t, (zend_ulong)def, (void**)&value) ==
+      FAILURE) {
     zend_error(E_ERROR, "PHP object not found for given definition.\n");
     return NULL;
   }
   return *value;
 }
 
+static bool exist_in_table(const HashTable* t, const void* def) {
+  void** value;
+  return (php_proto_zend_hash_index_find(t, (zend_ulong)def, (void**)&value) ==
+          SUCCESS);
+}
+
 static void add_to_list(HashTable* t, void* value) {
   zval* pDest = NULL;
-  zend_hash_next_index_insert(t, &value, sizeof(void*), (void**)&pDest);
+  php_proto_zend_hash_next_index_insert(t, &value, sizeof(void*),
+                                        (void**)&pDest);
 }
 
-void add_def_obj(const void* def, zval* value) {
+void add_def_obj(const void* def, PHP_PROTO_HASHTABLE_VALUE value) {
+#if PHP_MAJOR_VERSION < 7
   Z_ADDREF_P(value);
+#else
+  ++GC_REFCOUNT(value);
+#endif
   add_to_table(upb_def_to_php_obj_map, def, value);
 }
 
-zval* get_def_obj(const void* def) {
-  return (zval*)get_from_table(upb_def_to_php_obj_map, def);
+PHP_PROTO_HASHTABLE_VALUE get_def_obj(const void* def) {
+  return (PHP_PROTO_HASHTABLE_VALUE)get_from_table(upb_def_to_php_obj_map, def);
 }
 
-void add_ce_obj(const void* ce, zval* value) {
+void add_ce_obj(const void* ce, PHP_PROTO_HASHTABLE_VALUE value) {
+#if PHP_MAJOR_VERSION < 7
   Z_ADDREF_P(value);
+#else
+  ++GC_REFCOUNT(value);
+#endif
   add_to_table(ce_to_php_obj_map, ce, value);
 }
 
-zval* get_ce_obj(const void* ce) {
-  return (zval*)get_from_table(ce_to_php_obj_map, ce);
+PHP_PROTO_HASHTABLE_VALUE get_ce_obj(const void* ce) {
+  return (PHP_PROTO_HASHTABLE_VALUE)get_from_table(ce_to_php_obj_map, ce);
+}
+
+bool class_added(const void* ce) {
+  return exist_in_table(ce_to_php_obj_map, ce);
 }
 
 // -----------------------------------------------------------------------------
@@ -125,12 +146,23 @@ static PHP_GINIT_FUNCTION(protobuf) {
 static PHP_GSHUTDOWN_FUNCTION(protobuf) {
 }
 
+#if PHP_MAJOR_VERSION >= 7
+static void php_proto_hashtable_descriptor_release(zval* value) {
+  void* ptr = Z_PTR_P(value);
+  zend_object* object = *(zend_object**)ptr;
+  if(--GC_REFCOUNT(object) == 0) {
+    zend_objects_store_del(object);
+  }
+  efree(ptr);
+}
+#endif
+
 static PHP_RINIT_FUNCTION(protobuf) {
   ALLOC_HASHTABLE(upb_def_to_php_obj_map);
-  zend_hash_init(upb_def_to_php_obj_map, 16, NULL, ZVAL_PTR_DTOR, 0);
+  zend_hash_init(upb_def_to_php_obj_map, 16, NULL, HASHTABLE_VALUE_DTOR, 0);
 
   ALLOC_HASHTABLE(ce_to_php_obj_map);
-  zend_hash_init(ce_to_php_obj_map, 16, NULL, ZVAL_PTR_DTOR, 0);
+  zend_hash_init(ce_to_php_obj_map, 16, NULL, HASHTABLE_VALUE_DTOR, 0);
 
   generated_pool = NULL;
   generated_pool_php = NULL;
@@ -145,10 +177,12 @@ static PHP_RSHUTDOWN_FUNCTION(protobuf) {
   zend_hash_destroy(ce_to_php_obj_map);
   FREE_HASHTABLE(ce_to_php_obj_map);
 
+#if PHP_MAJOR_VERSION < 7
   if (generated_pool_php != NULL) {
     zval_dtor(generated_pool_php);
     FREE_ZVAL(generated_pool_php);
   }
+#endif
 
   return 0;
 }
@@ -170,6 +204,7 @@ static PHP_MINIT_FUNCTION(protobuf) {
 static PHP_MSHUTDOWN_FUNCTION(protobuf) {
   PEFREE(message_handlers);
   PEFREE(repeated_field_handlers);
+  PEFREE(repeated_field_iter_handlers);
   PEFREE(map_field_handlers);
 
   return 0;

+ 399 - 79
php/ext/google/protobuf/protobuf.h

@@ -37,11 +37,328 @@
 #include "upb.h"
 
 #define PHP_PROTOBUF_EXTNAME "protobuf"
-#define PHP_PROTOBUF_VERSION "3.2.0a1"
+#define PHP_PROTOBUF_VERSION "3.3.0"
 
 #define MAX_LENGTH_OF_INT64 20
 #define SIZEOF_INT64 8
 
+// -----------------------------------------------------------------------------
+// PHP7 Wrappers
+// ----------------------------------------------------------------------------
+
+#if PHP_MAJOR_VERSION < 7
+
+#define php_proto_zend_literal const zend_literal*
+#define PHP_PROTO_CASE_IS_BOOL IS_BOOL
+#define PHP_PROTO_SIZE int
+#define PHP_PROTO_LONG long
+#define PHP_PROTO_TSRMLS_DC TSRMLS_DC
+#define PHP_PROTO_TSRMLS_CC TSRMLS_CC
+
+// PHP String
+
+#define PHP_PROTO_ZVAL_STRING(zval_ptr, s, copy) \
+  ZVAL_STRING(zval_ptr, s, copy)
+#define PHP_PROTO_ZVAL_STRINGL(zval_ptr, s, len, copy) \
+  ZVAL_STRINGL(zval_ptr, s, len, copy)
+#define PHP_PROTO_RETURN_STRING(s, copy) RETURN_STRING(s, copy)
+#define PHP_PROTO_RETURN_STRINGL(s, len, copy) RETURN_STRINGL(s, len, copy)
+#define PHP_PROTO_RETVAL_STRINGL(s, len, copy) RETVAL_STRINGL(s, len, copy)
+#define php_proto_zend_make_printable_zval(from, to) \
+  {                                                  \
+    int use_copy;                                    \
+    zend_make_printable_zval(from, to, &use_copy);   \
+  }
+
+// PHP Array
+
+#define PHP_PROTO_HASH_OF(array) Z_ARRVAL_P(array)
+
+#define php_proto_zend_hash_index_update(ht, h, pData, nDataSize, pDest) \
+  zend_hash_index_update(ht, h, pData, nDataSize, pDest)
+
+#define php_proto_zend_hash_index_find(ht, h, pDest) \
+  zend_hash_index_find(ht, h, pDest)
+
+#define php_proto_zend_hash_next_index_insert(ht, pData, nDataSize, pDest) \
+  zend_hash_next_index_insert(ht, pData, nDataSize, pDest)
+
+#define php_proto_zend_hash_get_current_data_ex(ht, pDest, pos) \
+  zend_hash_get_current_data_ex(ht, pDest, pos)
+
+// PHP Object
+
+#define PHP_PROTO_WRAP_OBJECT_START(name) \
+  struct name {                           \
+    zend_object std;
+#define PHP_PROTO_WRAP_OBJECT_END \
+  };
+
+#define PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWWERNAME)         \
+  void LOWWERNAME##_init(TSRMLS_D) {                                         \
+    zend_class_entry class_type;                                             \
+    const char* class_name = CLASSNAME;                                      \
+    INIT_CLASS_ENTRY_EX(class_type, CLASSNAME, strlen(CLASSNAME),            \
+                        LOWWERNAME##_methods);                               \
+    LOWWERNAME##_type = zend_register_internal_class(&class_type TSRMLS_CC); \
+    LOWWERNAME##_type->create_object = LOWWERNAME##_create;                  \
+    LOWWERNAME##_handlers = PEMALLOC(zend_object_handlers);                  \
+    memcpy(LOWWERNAME##_handlers, zend_get_std_object_handlers(),            \
+           sizeof(zend_object_handlers));
+#define PHP_PROTO_INIT_CLASS_END \
+  }
+
+#define PHP_PROTO_OBJECT_CREATE_START(NAME, LOWWERNAME) \
+  static zend_object_value LOWWERNAME##_create(         \
+      zend_class_entry* ce TSRMLS_DC) {                 \
+    PHP_PROTO_ALLOC_CLASS_OBJECT(NAME, ce);             \
+    zend_object_std_init(&intern->std, ce TSRMLS_CC);   \
+    object_properties_init(&intern->std, ce);
+#define PHP_PROTO_OBJECT_CREATE_END(NAME, LOWWERNAME)                          \
+  PHP_PROTO_FREE_CLASS_OBJECT(NAME, LOWWERNAME##_free, LOWWERNAME##_handlers); \
+  }
+
+#define PHP_PROTO_OBJECT_FREE_START(classname, lowername) \
+  void lowername##_free(void* object TSRMLS_DC) {         \
+    classname* intern = object;
+#define PHP_PROTO_OBJECT_FREE_END                 \
+    zend_object_std_dtor(&intern->std TSRMLS_CC); \
+    efree(intern);                                \
+  }
+
+#define PHP_PROTO_OBJECT_DTOR_START(classname, lowername)
+#define PHP_PROTO_OBJECT_DTOR_END
+
+#define CACHED_VALUE zval*
+#define CACHED_TO_ZVAL_PTR(VALUE) (VALUE)
+#define CACHED_PTR_TO_ZVAL_PTR(VALUE) (*VALUE)
+#define ZVAL_PTR_TO_CACHED_PTR(VALUE) (&VALUE)
+
+#define CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(zval_ptr, class_type) \
+  ZVAL_OBJ(zval_ptr, class_type->create_object(class_type TSRMLS_CC));
+
+#define PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(value) \
+  SEPARATE_ZVAL_IF_NOT_REF(value)
+
+#define PHP_PROTO_GLOBAL_UNINITIALIZED_ZVAL EG(uninitialized_zval_ptr)
+
+#define OBJ_PROP(PROPERTIES, OFFSET) (PROPERTIES)->properties_table[OFFSET]
+
+#define php_proto_zval_ptr_dtor(zval_ptr) \
+  zval_ptr_dtor(&(zval_ptr))
+
+#define PHP_PROTO_ALLOC_CLASS_OBJECT(class_object, class_type) \
+  class_object* intern;                                        \
+  intern = (class_object*)emalloc(sizeof(class_object));       \
+  memset(intern, 0, sizeof(class_object));
+
+#define PHP_PROTO_FREE_CLASS_OBJECT(class_object, class_object_free, handler) \
+  zend_object_value retval = {0};                                             \
+  retval.handle = zend_objects_store_put(                                     \
+      intern, (zend_objects_store_dtor_t)zend_objects_destroy_object,         \
+      class_object_free, NULL TSRMLS_CC);                                     \
+  retval.handlers = handler;                                                  \
+  return retval;
+
+#define PHP_PROTO_ALLOC_ARRAY(zval_ptr)  \
+  ALLOC_HASHTABLE(Z_ARRVAL_P(zval_ptr)); \
+  Z_TYPE_P(zval_ptr) = IS_ARRAY;
+
+#define ZVAL_OBJ(zval_ptr, call_create) \
+  Z_TYPE_P(zval_ptr) = IS_OBJECT;       \
+  Z_OBJVAL_P(zval_ptr) = call_create;
+
+#define UNBOX(class_name, val) \
+  (class_name*)zend_object_store_get_object(val TSRMLS_CC);
+
+#define UNBOX_HASHTABLE_VALUE(class_name, val) UNBOX(class_name, val)
+
+#define HASHTABLE_VALUE_DTOR ZVAL_PTR_DTOR
+
+#define PHP_PROTO_HASHTABLE_VALUE zval*
+
+#define CREATE_HASHTABLE_VALUE(OBJ, WRAPPED_OBJ, OBJ_TYPE, OBJ_CLASS_ENTRY) \
+  OBJ_TYPE* OBJ;                                                            \
+  PHP_PROTO_HASHTABLE_VALUE WRAPPED_OBJ;                                    \
+  MAKE_STD_ZVAL(WRAPPED_OBJ);                                               \
+  ZVAL_OBJ(WRAPPED_OBJ,                                                     \
+           OBJ_CLASS_ENTRY->create_object(OBJ_CLASS_ENTRY TSRMLS_CC));      \
+  OBJ = UNBOX_HASHTABLE_VALUE(OBJ_TYPE, WRAPPED_OBJ);                       \
+  Z_DELREF_P(desc_php);
+
+#define PHP_PROTO_CE_DECLARE zend_class_entry**
+#define PHP_PROTO_CE_UNREF(ce) (*ce)
+
+#define php_proto_zend_lookup_class(name, name_length, ce) \
+  zend_lookup_class(name, name_length, ce TSRMLS_CC)
+
+#else  // PHP_MAJOR_VERSION >= 7
+
+#define php_proto_zend_literal void**
+#define PHP_PROTO_CASE_IS_BOOL IS_TRUE: case IS_FALSE
+#define PHP_PROTO_SIZE size_t
+#define PHP_PROTO_LONG zend_long
+#define PHP_PROTO_TSRMLS_DC
+#define PHP_PROTO_TSRMLS_CC
+
+// PHP String
+
+#define PHP_PROTO_ZVAL_STRING(zval_ptr, s, copy) \
+  ZVAL_STRING(zval_ptr, s)
+#define PHP_PROTO_ZVAL_STRINGL(zval_ptr, s, len, copy) \
+  ZVAL_STRINGL(zval_ptr, s, len)
+#define PHP_PROTO_RETURN_STRING(s, copy) RETURN_STRING(s)
+#define PHP_PROTO_RETURN_STRINGL(s, len, copy) RETURN_STRINGL(s, len)
+#define PHP_PROTO_RETVAL_STRINGL(s, len, copy) RETVAL_STRINGL(s, len)
+#define php_proto_zend_make_printable_zval(from, to) \
+  zend_make_printable_zval(from, to)
+
+// PHP Array
+
+#define PHP_PROTO_HASH_OF(array) Z_ARRVAL_P(&array)
+
+static inline int php_proto_zend_hash_index_update(HashTable* ht, ulong h,
+                                                   void* pData, uint nDataSize,
+                                                   void** pDest) {
+  void* result = NULL;
+  result = zend_hash_index_update_mem(ht, h, pData, nDataSize);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
+static inline int php_proto_zend_hash_index_find(const HashTable* ht, ulong h,
+                                                 void** pDest) {
+  void* result = NULL;
+  result = zend_hash_index_find_ptr(ht, h);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
+static inline int php_proto_zend_hash_next_index_insert(HashTable* ht,
+                                                        void* pData,
+                                                        uint nDataSize,
+                                                        void** pDest) {
+  void* result = NULL;
+  result = zend_hash_next_index_insert_mem(ht, pData, nDataSize);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
+static inline int php_proto_zend_hash_get_current_data_ex(HashTable* ht,
+                                                          void** pDest,
+                                                          HashPosition* pos) {
+  void* result = NULL;
+  result = zend_hash_get_current_data_ex(ht, pos);
+  if (pDest != NULL) *pDest = result;
+  return result != NULL ? SUCCESS : FAILURE;
+}
+
+// PHP Object
+
+#define PHP_PROTO_WRAP_OBJECT_START(name) struct name {
+#define PHP_PROTO_WRAP_OBJECT_END \
+  zend_object std;                \
+  };
+
+#define PHP_PROTO_INIT_CLASS_START(CLASSNAME, CAMELNAME, LOWWERNAME)         \
+  void LOWWERNAME##_init(TSRMLS_D) {                                         \
+    zend_class_entry class_type;                                             \
+    const char* class_name = CLASSNAME;                                      \
+    INIT_CLASS_ENTRY_EX(class_type, CLASSNAME, strlen(CLASSNAME),            \
+                        LOWWERNAME##_methods);                               \
+    LOWWERNAME##_type = zend_register_internal_class(&class_type TSRMLS_CC); \
+    LOWWERNAME##_type->create_object = LOWWERNAME##_create;                  \
+    LOWWERNAME##_handlers = PEMALLOC(zend_object_handlers);                  \
+    memcpy(LOWWERNAME##_handlers, zend_get_std_object_handlers(),            \
+           sizeof(zend_object_handlers));                                    \
+    LOWWERNAME##_handlers->free_obj = LOWWERNAME##_free;                     \
+    LOWWERNAME##_handlers->dtor_obj = LOWWERNAME##_dtor;                     \
+    LOWWERNAME##_handlers->offset = XtOffsetOf(CAMELNAME, std);
+#define PHP_PROTO_INIT_CLASS_END \
+  }
+
+#define PHP_PROTO_OBJECT_FREE_START(classname, lowername) \
+  void lowername##_free(zend_object* object) {            \
+    classname* intern =                                   \
+        (classname*)((char*)object - XtOffsetOf(classname, std));
+#define PHP_PROTO_OBJECT_FREE_END           \
+  }
+
+#define PHP_PROTO_OBJECT_DTOR_START(classname, lowername) \
+  void lowername##_dtor(zend_object* object) {            \
+    classname* intern =                                   \
+        (classname*)((char*)object - XtOffsetOf(classname, std));
+#define PHP_PROTO_OBJECT_DTOR_END           \
+    zend_object_std_dtor(object TSRMLS_CC); \
+  }
+
+#define PHP_PROTO_OBJECT_CREATE_START(NAME, LOWWERNAME)                     \
+  static zend_object* LOWWERNAME##_create(zend_class_entry* ce TSRMLS_DC) { \
+    PHP_PROTO_ALLOC_CLASS_OBJECT(NAME, ce);                                 \
+    zend_object_std_init(&intern->std, ce TSRMLS_CC);                       \
+    object_properties_init(&intern->std, ce);
+#define PHP_PROTO_OBJECT_CREATE_END(NAME, LOWWERNAME)                          \
+  PHP_PROTO_FREE_CLASS_OBJECT(NAME, LOWWERNAME##_free, LOWWERNAME##_handlers); \
+  }
+
+#define CACHED_VALUE zval
+#define CACHED_TO_ZVAL_PTR(VALUE) (&VALUE)
+#define CACHED_PTR_TO_ZVAL_PTR(VALUE) (VALUE)
+#define ZVAL_PTR_TO_CACHED_PTR(VALUE) (VALUE)
+
+#define CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(zval_ptr, class_type) \
+  ZVAL_OBJ(zval_ptr, class_type->create_object(class_type));
+
+#define PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(value) ;
+
+#define PHP_PROTO_GLOBAL_UNINITIALIZED_ZVAL &EG(uninitialized_zval)
+
+#define php_proto_zval_ptr_dtor(zval_ptr) \
+  zval_ptr_dtor(zval_ptr)
+
+#define PHP_PROTO_ALLOC_CLASS_OBJECT(class_object, class_type)               \
+  class_object* intern;                                                      \
+  int size = sizeof(class_object) + zend_object_properties_size(class_type); \
+  intern = ecalloc(1, size);                                                 \
+  memset(intern, 0, size);
+
+#define PHP_PROTO_FREE_CLASS_OBJECT(class_object, class_object_free, handler) \
+  intern->std.handlers = handler;                                             \
+  return &intern->std;
+
+#define PHP_PROTO_ALLOC_ARRAY(zval_ptr) \
+  ZVAL_NEW_ARR(zval_ptr)
+
+#define UNBOX(class_name, val) \
+  (class_name*)((char*)Z_OBJ_P(val) - XtOffsetOf(class_name, std));
+
+#define UNBOX_HASHTABLE_VALUE(class_name, val) \
+  (class_name*)((char*)val - XtOffsetOf(class_name, std))
+
+#define HASHTABLE_VALUE_DTOR php_proto_hashtable_descriptor_release
+
+#define PHP_PROTO_HASHTABLE_VALUE zend_object*
+
+#define CREATE_HASHTABLE_VALUE(OBJ, WRAPPED_OBJ, OBJ_TYPE, OBJ_CLASS_ENTRY) \
+  OBJ_TYPE* OBJ;                                                            \
+  PHP_PROTO_HASHTABLE_VALUE WRAPPED_OBJ;                                    \
+  WRAPPED_OBJ = OBJ_CLASS_ENTRY->create_object(OBJ_CLASS_ENTRY);            \
+  OBJ = UNBOX_HASHTABLE_VALUE(OBJ_TYPE, WRAPPED_OBJ);                       \
+  --GC_REFCOUNT(WRAPPED_OBJ);
+
+#define PHP_PROTO_CE_DECLARE zend_class_entry*
+#define PHP_PROTO_CE_UNREF(ce) (ce)
+
+static inline int php_proto_zend_lookup_class(
+    const char* name, int name_length, zend_class_entry** ce TSRMLS_DC) {
+  zend_string *zstr_name = zend_string_init(name, name_length, 0);
+  *ce = zend_lookup_class(zstr_name);
+  zend_string_release(zstr_name);
+  return *ce != NULL ? SUCCESS : FAILURE;
+}
+
+#endif  // PHP_MAJOR_VERSION >= 7
+
 // -----------------------------------------------------------------------------
 // Forward Declaration
 // ----------------------------------------------------------------------------
@@ -55,7 +372,8 @@ struct MessageHeader;
 struct MessageLayout;
 struct RepeatedField;
 struct RepeatedFieldIter;
-struct MapField;
+struct Map;
+struct Oneof;
 
 typedef struct DescriptorPool DescriptorPool;
 typedef struct Descriptor Descriptor;
@@ -66,7 +384,8 @@ typedef struct MessageHeader MessageHeader;
 typedef struct MessageLayout MessageLayout;
 typedef struct RepeatedField RepeatedField;
 typedef struct RepeatedFieldIter RepeatedFieldIter;
-typedef struct MapField MapField;
+typedef struct Map Map;
+typedef struct Oneof Oneof;
 
 // -----------------------------------------------------------------------------
 // Globals.
@@ -88,13 +407,14 @@ void message_init(TSRMLS_D);
 
 // Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor
 // instances.
-void add_def_obj(const void* def, zval* value);
-zval* get_def_obj(const void* def);
+void add_def_obj(const void* def, PHP_PROTO_HASHTABLE_VALUE value);
+PHP_PROTO_HASHTABLE_VALUE get_def_obj(const void* def);
 
 // Global map from PHP class entries to wrapper Descriptor/EnumDescriptor
 // instances.
-void add_ce_obj(const void* ce, zval* value);
-zval* get_ce_obj(const void* ce);
+void add_ce_obj(const void* ce, PHP_PROTO_HASHTABLE_VALUE value);
+PHP_PROTO_HASHTABLE_VALUE get_ce_obj(const void* ce);
+bool class_added(const void* ce);
 
 extern zend_class_entry* map_field_type;
 extern zend_class_entry* repeated_field_type;
@@ -103,20 +423,25 @@ extern zend_class_entry* repeated_field_type;
 // Descriptor.
 // -----------------------------------------------------------------------------
 
-struct DescriptorPool {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(DescriptorPool)
   upb_symtab* symtab;
   HashTable* pending_list;
-};
+PHP_PROTO_WRAP_OBJECT_END
 
 PHP_METHOD(DescriptorPool, getGeneratedPool);
 PHP_METHOD(DescriptorPool, internalAddGeneratedFile);
 
-extern zval* generated_pool_php;  // wrapper of generated pool
+// wrapper of generated pool
+#if PHP_MAJOR_VERSION < 7
+extern zval* generated_pool_php;
+void descriptor_pool_free(void* object TSRMLS_DC);
+#else
+extern zend_object *generated_pool_php;
+void descriptor_pool_free(zend_object* object);
+#endif
 extern DescriptorPool* generated_pool;  // The actual generated pool
 
-struct Descriptor {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(Descriptor)
   const upb_msgdef* msgdef;
   MessageLayout* layout;
   zend_class_entry* klass;  // begins as NULL
@@ -126,23 +451,21 @@ struct Descriptor {
   const upb_handlers* pb_serialize_handlers;
   const upb_handlers* json_serialize_handlers;
   const upb_handlers* json_serialize_handlers_preserve;
-};
+PHP_PROTO_WRAP_OBJECT_END
 
 extern zend_class_entry* descriptor_type;
 
 void descriptor_name_set(Descriptor *desc, const char *name);
 
-struct FieldDescriptor {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(FieldDescriptor)
   const upb_fielddef* fielddef;
-};
+PHP_PROTO_WRAP_OBJECT_END
 
-struct EnumDescriptor {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(EnumDescriptor)
   const upb_enumdef* enumdef;
   zend_class_entry* klass;  // begins as NULL
   // VALUE module;  // begins as nil
-};
+PHP_PROTO_WRAP_OBJECT_END
 
 extern zend_class_entry* enum_descriptor_type;
 
@@ -150,13 +473,15 @@ extern zend_class_entry* enum_descriptor_type;
 // Message class creation.
 // -----------------------------------------------------------------------------
 
-void* message_data(void* msg);
-void message_create_with_type(zend_class_entry* ce, zval** message TSRMLS_DC);
+void* message_data(MessageHeader* msg);
+void custom_data_init(const zend_class_entry* ce,
+                      MessageHeader* msg PHP_PROTO_TSRMLS_DC);
 
 // Build PHP class for given descriptor. Instead of building from scratch, this
 // function modifies existing class which has been partially defined in PHP
 // code.
-void build_class_from_descriptor(zval* php_descriptor TSRMLS_DC);
+void build_class_from_descriptor(
+    PHP_PROTO_HASHTABLE_VALUE php_descriptor TSRMLS_DC);
 
 extern zend_object_handlers* message_handlers;
 
@@ -227,18 +552,17 @@ struct MessageLayout {
   size_t size;
 };
 
-struct MessageHeader {
-  zend_object std;  // Stores properties table and class info of PHP instance.
-                    // This is needed for MessageHeader to be accessed via PHP.
+PHP_PROTO_WRAP_OBJECT_START(MessageHeader)
+  void* data;  // Point to the real message data.
+               // Place needs to be consistent with map_parse_frame_data_t.
   Descriptor* descriptor;  // Kept alive by self.class.descriptor reference.
-  // The real message data is appended after MessageHeader.
-};
+PHP_PROTO_WRAP_OBJECT_END
 
 MessageLayout* create_layout(const upb_msgdef* msgdef);
 void layout_init(MessageLayout* layout, void* storage,
-                 zval** properties_table TSRMLS_DC);
+                 CACHED_VALUE* properties_table PHP_PROTO_TSRMLS_DC);
 zval* layout_get(MessageLayout* layout, const void* storage,
-                 const upb_fielddef* field, zval** cache TSRMLS_DC);
+                 const upb_fielddef* field, CACHED_VALUE* cache TSRMLS_DC);
 void layout_set(MessageLayout* layout, MessageHeader* header,
                 const upb_fielddef* field, zval* val TSRMLS_DC);
 void layout_merge(MessageLayout* layout, MessageHeader* from,
@@ -308,7 +632,12 @@ PHP_METHOD(Util, checkRepeatedField);
 size_t native_slot_size(upb_fieldtype_t type);
 bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
                      void* memory, zval* value TSRMLS_DC);
-void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache);
+// String/Message is stored differently in array/map from normal message fields.
+// So we need to make a special method to handle that.
+bool native_slot_set_by_array(upb_fieldtype_t type,
+                              const zend_class_entry* klass, void* memory,
+                              zval* value TSRMLS_DC);
+void native_slot_init(upb_fieldtype_t type, void* memory, void* cache);
 // For each property, in order to avoid conversion between the zval object and
 // the actual data type during parsing/serialization, the containing message
 // object use the custom memory layout to store the actual data type for each
@@ -317,8 +646,13 @@ void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache);
 // for providing such a zval object. Instead the caller needs to provide one
 // (cache) and update it with the actual data (memory).
 void native_slot_get(upb_fieldtype_t type, const void* memory,
-                     zval** cache TSRMLS_DC);
-void native_slot_get_default(upb_fieldtype_t type, zval** cache TSRMLS_DC);
+                     CACHED_VALUE* cache TSRMLS_DC);
+// String/Message is stored differently in array/map from normal message fields.
+// So we need to make a special method to handle that.
+void native_slot_get_by_array(upb_fieldtype_t type, const void* memory,
+                     CACHED_VALUE* cache TSRMLS_DC);
+void native_slot_get_default(upb_fieldtype_t type,
+                             CACHED_VALUE* cache TSRMLS_DC);
 
 // -----------------------------------------------------------------------------
 // Map Field.
@@ -326,13 +660,12 @@ void native_slot_get_default(upb_fieldtype_t type, zval** cache TSRMLS_DC);
 
 extern zend_object_handlers* map_field_handlers;
 
-typedef struct {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(Map)
   upb_fieldtype_t key_type;
   upb_fieldtype_t value_type;
   const zend_class_entry* msg_ce;  // class entry for value message
   upb_strtable table;
-} Map;
+PHP_PROTO_WRAP_OBJECT_END
 
 typedef struct {
   Map* self;
@@ -349,14 +682,14 @@ upb_value map_iter_value(MapIter* iter, int* len);
 const upb_fielddef* map_entry_key(const upb_msgdef* msgdef);
 const upb_fielddef* map_entry_value(const upb_msgdef* msgdef);
 
-zend_object_value map_field_create(zend_class_entry *ce TSRMLS_DC);
-void map_field_create_with_field(zend_class_entry *ce, const upb_fielddef *field,
-                                zval **map_field TSRMLS_DC);
-void map_field_create_with_type(zend_class_entry *ce, upb_fieldtype_t key_type,
+void map_field_create_with_field(const zend_class_entry* ce,
+                                 const upb_fielddef* field,
+                                 CACHED_VALUE* map_field PHP_PROTO_TSRMLS_DC);
+void map_field_create_with_type(const zend_class_entry* ce,
+                                upb_fieldtype_t key_type,
                                 upb_fieldtype_t value_type,
-                                const zend_class_entry *msg_ce,
-                                zval **map_field TSRMLS_DC);
-void map_field_free(void* object TSRMLS_DC);
+                                const zend_class_entry* msg_ce,
+                                CACHED_VALUE* map_field PHP_PROTO_TSRMLS_DC);
 void* upb_value_memory(upb_value* v);
 
 #define MAP_KEY_FIELD 1
@@ -382,33 +715,36 @@ PHP_METHOD(MapField, count);
 // -----------------------------------------------------------------------------
 
 extern zend_object_handlers* repeated_field_handlers;
+extern zend_object_handlers* repeated_field_iter_handlers;
 
-struct RepeatedField {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(RepeatedField)
+#if PHP_MAJOR_VERSION < 7
   zval* array;
+#else
+  zval array;
+#endif
   upb_fieldtype_t type;
   const zend_class_entry* msg_ce;  // class entry for containing message
                                    // (for message field only).
-};
+PHP_PROTO_WRAP_OBJECT_END
 
-struct RepeatedFieldIter {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(RepeatedFieldIter)
   RepeatedField* repeated_field;
   long position;
-};
-
-void repeated_field_create_with_field(zend_class_entry* ce,
-                                     const upb_fielddef* field,
-                                     zval** repeated_field TSRMLS_DC);
-void repeated_field_create_with_type(zend_class_entry* ce, upb_fieldtype_t type,
-                                     const zend_class_entry* msg_ce,
-                                     zval** repeated_field TSRMLS_DC);
+PHP_PROTO_WRAP_OBJECT_END
+
+void repeated_field_create_with_field(
+    zend_class_entry* ce, const upb_fielddef* field,
+    CACHED_VALUE* repeated_field PHP_PROTO_TSRMLS_DC);
+void repeated_field_create_with_type(
+    zend_class_entry* ce, upb_fieldtype_t type, const zend_class_entry* msg_ce,
+    CACHED_VALUE* repeated_field PHP_PROTO_TSRMLS_DC);
 // Return the element at the index position from the repeated field. There is
 // not restriction on the type of stored elements.
 void *repeated_field_index_native(RepeatedField *intern, int index TSRMLS_DC);
 // Add the element to the end of the repeated field. There is not restriction on
 // the type of stored elements.
-void repeated_field_push_native(RepeatedField *intern, void *value TSRMLS_DC);
+void repeated_field_push_native(RepeatedField *intern, void *value);
 
 PHP_METHOD(RepeatedField, __construct);
 PHP_METHOD(RepeatedField, append);
@@ -429,12 +765,11 @@ PHP_METHOD(RepeatedFieldIter, valid);
 // Oneof Field.
 // -----------------------------------------------------------------------------
 
-typedef struct {
-  zend_object std;
+PHP_PROTO_WRAP_OBJECT_START(Oneof)
   upb_oneofdef* oneofdef;
   int index;    // Index of field in oneof. -1 if not set.
   char value[NATIVE_SLOT_MAX_SIZE];
-} Oneof;
+PHP_PROTO_WRAP_OBJECT_END
 
 // Oneof case slot value to indicate that no oneof case is set. The value `0` is
 // safe because field numbers are used as case identifiers, and no field can
@@ -446,24 +781,13 @@ typedef struct {
 // -----------------------------------------------------------------------------
 
 upb_fieldtype_t to_fieldtype(upb_descriptortype_t type);
-const zend_class_entry *field_type_class(const upb_fielddef *field TSRMLS_DC);
+const zend_class_entry* field_type_class(
+    const upb_fielddef* field PHP_PROTO_TSRMLS_DC);
 
 // -----------------------------------------------------------------------------
 // Utilities.
 // -----------------------------------------------------------------------------
 
-// PHP <-> C conversion.
-#define UNBOX(class_name, val) \
-  (class_name*)zend_object_store_get_object(val TSRMLS_CC);
-
-#define BOX(class_name, wrapper, intern, free_func)                    \
-  MAKE_STD_ZVAL(wrapper);                                              \
-  Z_TYPE_P(wrapper) = IS_OBJECT;                                       \
-  Z_OBJVAL_P(wrapper)                                                  \
-      .handle =                                                        \
-      zend_objects_store_put(intern, NULL, free_func, NULL TSRMLS_CC); \
-  Z_OBJVAL_P(wrapper).handlers = zend_get_std_object_handlers();
-
 // Memory management
 #define ALLOC(class_name) (class_name*) emalloc(sizeof(class_name))
 #define PEMALLOC(class_name) (class_name*) pemalloc(sizeof(class_name), 1)
@@ -471,19 +795,15 @@ const zend_class_entry *field_type_class(const upb_fielddef *field TSRMLS_DC);
 #define FREE(object) efree(object)
 #define PEFREE(object) pefree(object, 1)
 
-// Create PHP internal instance.
-#define CREATE(class_name, intern, init_func) \
-  intern = ALLOC(class_name);                 \
-  memset(intern, 0, sizeof(class_name));      \
-  init_func(intern TSRMLS_CC);
-
 // String argument.
 #define STR(str) (str), strlen(str)
 
 // Zend Value
+#if PHP_MAJOR_VERSION < 7
 #define Z_OBJ_P(zval_p)                                       \
   ((zend_object*)(EG(objects_store)                           \
                       .object_buckets[Z_OBJ_HANDLE_P(zval_p)] \
                       .bucket.obj.object))
+#endif
 
 #endif  // __GOOGLE_PROTOBUF_PHP_PROTOBUF_H__

+ 349 - 229
php/ext/google/protobuf/storage.c

@@ -57,7 +57,7 @@ size_t native_slot_size(upb_fieldtype_t type) {
   }
 }
 
-static bool native_slot_is_default(upb_fieldtype_t type, void* memory) {
+static bool native_slot_is_default(upb_fieldtype_t type, const void* memory) {
   switch (type) {
 #define CASE_TYPE(upb_type, c_type)    \
   case UPB_TYPE_##upb_type: {          \
@@ -75,15 +75,17 @@ static bool native_slot_is_default(upb_fieldtype_t type, void* memory) {
 #undef CASE_TYPE
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
-      return Z_STRLEN_PP(DEREF(memory, zval**)) == 0;
+      return Z_STRLEN_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(memory, CACHED_VALUE*))) ==
+             0;
     case UPB_TYPE_MESSAGE:
-      return Z_TYPE_PP(DEREF(memory, zval**)) == IS_NULL;
+      return Z_TYPE_P(CACHED_PTR_TO_ZVAL_PTR(DEREF(memory, CACHED_VALUE*))) ==
+             IS_NULL;
     default: return false;
   }
 }
 
 bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
-                     void* memory, zval* value TSRMLS_DC) {
+                     void* memory, zval* value PHP_PROTO_TSRMLS_DC) {
   switch (type) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
@@ -95,14 +97,14 @@ bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
         zend_error(E_USER_ERROR, "Given string is not UTF8 encoded.");
         return false;
       }
-      if (*(zval**)memory != NULL) {
+
+      zval* cached_zval = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+      if (EXPECTED(cached_zval != NULL)) {
+#if PHP_MAJOR_VERSION < 7
         REPLACE_ZVAL_VALUE((zval**)memory, value, 1);
-      } else {
-        // Handles repeated/map string field. Memory provided by
-        // RepeatedField/Map is not initialized.
-        MAKE_STD_ZVAL(DEREF(memory, zval*));
-        ZVAL_STRINGL(DEREF(memory, zval*), Z_STRVAL_P(value), Z_STRLEN_P(value),
-                     1);
+#else
+        zend_assign_to_variable(cached_zval, value, IS_CV);
+#endif
       }
       break;
     }
@@ -115,13 +117,18 @@ bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
         zend_error(E_USER_ERROR, "Given message does not have correct class.");
         return false;
       }
-      if (EXPECTED(DEREF(memory, zval*) != value)) {
-        if (DEREF(memory, zval*) != NULL) {
-          zval_ptr_dtor((zval**)memory);
-        }
-        DEREF(memory, zval*) = value;
-        Z_ADDREF_P(value);
+
+      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;
     }
 
@@ -151,7 +158,59 @@ bool native_slot_set(upb_fieldtype_t type, const zend_class_entry* klass,
   return true;
 }
 
-void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache) {
+bool native_slot_set_by_array(upb_fieldtype_t type,
+                              const zend_class_entry* klass, void* memory,
+                              zval* value TSRMLS_DC) {
+  switch (type) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+      if (!protobuf_convert_to_string(value)) {
+        return false;
+      }
+      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 false;
+      }
+
+      // Handles repeated/map string field. Memory provided by
+      // RepeatedField/Map is not initialized.
+#if PHP_MAJOR_VERSION < 7
+      MAKE_STD_ZVAL(DEREF(memory, zval*));
+      PHP_PROTO_ZVAL_STRINGL(DEREF(memory, zval*), Z_STRVAL_P(value),
+                             Z_STRLEN_P(value), 1);
+#else
+      *(zend_string**)memory = zend_string_dup(Z_STR_P(value), 0);
+#endif
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      if (Z_TYPE_P(value) != IS_OBJECT) {
+        zend_error(E_USER_ERROR, "Given value is not message.");
+        return false;
+      }
+      if (Z_TYPE_P(value) == IS_OBJECT && klass != Z_OBJCE_P(value)) {
+        zend_error(E_USER_ERROR, "Given message does not have correct class.");
+        return false;
+      }
+#if PHP_MAJOR_VERSION < 7
+      if (EXPECTED(DEREF(memory, zval*) != value)) {
+        DEREF(memory, zval*) = value;
+        Z_ADDREF_P(value);
+      }
+#else
+      DEREF(memory, zend_object*) = Z_OBJ_P(value);
+      ++GC_REFCOUNT(Z_OBJ_P(value));
+#endif
+      break;
+    }
+    default:
+      return native_slot_set(type, klass, memory, value TSRMLS_CC);
+  }
+  return true;
+}
+
+void native_slot_init(upb_fieldtype_t type, void* memory, void* cache) {
   zval* tmp = NULL;
   switch (type) {
     case UPB_TYPE_FLOAT:
@@ -166,7 +225,7 @@ void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
     case UPB_TYPE_MESSAGE:
-      DEREF(memory, zval**) = cache;
+      DEREF(memory, CACHED_VALUE*) = cache;
       break;
     case UPB_TYPE_ENUM:
     case UPB_TYPE_INT32:
@@ -187,38 +246,38 @@ void native_slot_init(upb_fieldtype_t type, void* memory, zval** cache) {
 }
 
 void native_slot_get(upb_fieldtype_t type, const void* memory,
-                     zval** cache TSRMLS_DC) {
+                     CACHED_VALUE* cache TSRMLS_DC) {
   switch (type) {
-#define CASE(upb_type, php_type, c_type) \
-    case UPB_TYPE_##upb_type: \
-      SEPARATE_ZVAL_IF_NOT_REF(cache); \
-      ZVAL_##php_type(*cache, DEREF(memory, c_type)); \
-      return;
+#define CASE(upb_type, php_type, c_type)                                   \
+  case UPB_TYPE_##upb_type:                                                \
+    PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);                             \
+    ZVAL_##php_type(CACHED_PTR_TO_ZVAL_PTR(cache), DEREF(memory, c_type)); \
+    return;
 
-CASE(FLOAT,  DOUBLE, float)
-CASE(DOUBLE, DOUBLE, double)
-CASE(BOOL,   BOOL,   int8_t)
-CASE(INT32,  LONG,   int32_t)
-CASE(ENUM,   LONG,   uint32_t)
+    CASE(FLOAT, DOUBLE, float)
+    CASE(DOUBLE, DOUBLE, double)
+    CASE(BOOL, BOOL, int8_t)
+    CASE(INT32, LONG, int32_t)
+    CASE(ENUM, LONG, uint32_t)
 
 #undef CASE
 
 #if SIZEOF_LONG == 4
-#define CASE(upb_type, c_type)                        \
-    case UPB_TYPE_##upb_type: {                       \
-      SEPARATE_ZVAL_IF_NOT_REF(cache);                \
-      char buffer[MAX_LENGTH_OF_INT64];               \
-      sprintf(buffer, "%lld", DEREF(memory, c_type)); \
-      ZVAL_STRING(*cache, buffer, 1);                 \
-      return;                                         \
-    }
+#define CASE(upb_type, c_type)                                       \
+  case UPB_TYPE_##upb_type: {                                        \
+    PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);                       \
+    char buffer[MAX_LENGTH_OF_INT64];                                \
+    sprintf(buffer, "%lld", DEREF(memory, c_type));                  \
+    PHP_PROTO_ZVAL_STRING(CACHED_PTR_TO_ZVAL_PTR(cache), buffer, 1); \
+    return;                                                          \
+  }
 #else
-#define CASE(upb_type, c_type)                        \
-    case UPB_TYPE_##upb_type: {                       \
-      SEPARATE_ZVAL_IF_NOT_REF(cache);                \
-      ZVAL_LONG(*cache, DEREF(memory, c_type));       \
-      return;                                         \
-    }
+#define CASE(upb_type, c_type)                                       \
+  case UPB_TYPE_##upb_type: {                                        \
+    PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);                       \
+    ZVAL_LONG(CACHED_PTR_TO_ZVAL_PTR(cache), DEREF(memory, c_type)); \
+    return;                                                          \
+  }
 #endif
 CASE(UINT64, uint64_t)
 CASE(INT64,  int64_t)
@@ -227,32 +286,34 @@ CASE(INT64,  int64_t)
     case UPB_TYPE_UINT32: {
       // Prepend bit-1 for negative numbers, so that uint32 value will be
       // consistent on both 32-bit and 64-bit architectures.
-      SEPARATE_ZVAL_IF_NOT_REF(cache);
+      PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);
       int value = DEREF(memory, int32_t);
       if (sizeof(int) == 8) {
         value |= (-((value >> 31) & 0x1) & 0xFFFFFFFF00000000);
       }
-      ZVAL_LONG(*cache, value);
+      ZVAL_LONG(CACHED_PTR_TO_ZVAL_PTR(cache), value);
       return;
     }
 
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
-      // For optional string/bytes fields, the cache is owned by the containing
-      // message and should have been updated during setting/decoding. However,
-      // for repeated string/bytes fields, the cache is provided by zend engine
-      // and has not been updated.
-      zval* value = DEREF(memory, zval*);
-      if (*cache != value) {
-        ZVAL_STRINGL(*cache, Z_STRVAL_P(value), Z_STRLEN_P(value), 1);
+      // For optional string/bytes/message fields, the cache is owned by the
+      // containing message and should have been updated during
+      // setting/decoding. However, oneof accessor call this function by
+      // providing the return value directly, which is not the same as the cache
+      // value.
+      zval* value = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+      if (CACHED_PTR_TO_ZVAL_PTR(cache) != value) {
+        PHP_PROTO_ZVAL_STRINGL(CACHED_PTR_TO_ZVAL_PTR(cache), Z_STRVAL_P(value),
+                               Z_STRLEN_P(value), 1);
       }
       break;
     }
     case UPB_TYPE_MESSAGE: {
       // Same as above for string/bytes fields.
-      zval* value = DEREF(memory, zval*);
-      if (*cache != value) {
-        ZVAL_ZVAL(*cache, value, 1, 0);
+      zval* value = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+      if (CACHED_PTR_TO_ZVAL_PTR(cache) != value) {
+        ZVAL_ZVAL(CACHED_PTR_TO_ZVAL_PTR(cache), value, 1, 0);
       }
       return;
     }
@@ -261,12 +322,46 @@ CASE(INT64,  int64_t)
   }
 }
 
-void native_slot_get_default(upb_fieldtype_t type, zval** cache TSRMLS_DC) {
+void native_slot_get_by_array(upb_fieldtype_t type, const void* memory,
+                              CACHED_VALUE* cache TSRMLS_DC) {
   switch (type) {
-#define CASE(upb_type, php_type)     \
-  case UPB_TYPE_##upb_type:          \
-    SEPARATE_ZVAL_IF_NOT_REF(cache); \
-    ZVAL_##php_type(*cache, 0);      \
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+#if PHP_MAJOR_VERSION < 7
+      zval* value = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+      if (EXPECTED(CACHED_PTR_TO_ZVAL_PTR(cache) != value)) {
+        PHP_PROTO_ZVAL_STRINGL(CACHED_PTR_TO_ZVAL_PTR(cache),
+                               Z_STRVAL_P(value), Z_STRLEN_P(value), 1);
+      }
+#else
+      ZVAL_NEW_STR(cache, zend_string_dup(*(zend_string**)memory, 0));
+#endif
+      return;
+    }
+    case UPB_TYPE_MESSAGE: {
+#if PHP_MAJOR_VERSION < 7
+      zval* value = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+      if (EXPECTED(CACHED_PTR_TO_ZVAL_PTR(cache) != value)) {
+        ZVAL_ZVAL(CACHED_PTR_TO_ZVAL_PTR(cache), value, 1, 0);
+      }
+#else
+      ++GC_REFCOUNT(*(zend_object**)memory);
+      ZVAL_OBJ(cache, *(zend_object**)memory);
+#endif
+      return;
+    }
+    default:
+      native_slot_get(type, memory, cache TSRMLS_CC);
+  }
+}
+
+void native_slot_get_default(upb_fieldtype_t type,
+                             CACHED_VALUE* cache TSRMLS_DC) {
+  switch (type) {
+#define CASE(upb_type, php_type)                       \
+  case UPB_TYPE_##upb_type:                            \
+    PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);         \
+    ZVAL_##php_type(CACHED_PTR_TO_ZVAL_PTR(cache), 0); \
     return;
 
     CASE(FLOAT, DOUBLE)
@@ -279,19 +374,19 @@ void native_slot_get_default(upb_fieldtype_t type, zval** cache TSRMLS_DC) {
 #undef CASE
 
 #if SIZEOF_LONG == 4
-#define CASE(upb_type)                                \
-    case UPB_TYPE_##upb_type: {                       \
-      SEPARATE_ZVAL_IF_NOT_REF(cache);                \
-      ZVAL_STRING(*cache, "0", 1);                    \
-      return;                                         \
-    }
+#define CASE(upb_type)                                            \
+  case UPB_TYPE_##upb_type: {                                     \
+    PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);                    \
+    PHP_PROTO_ZVAL_STRING(CACHED_PTR_TO_ZVAL_PTR(cache), "0", 1); \
+    return;                                                       \
+  }
 #else
-#define CASE(upb_type)                                \
-    case UPB_TYPE_##upb_type: {                       \
-      SEPARATE_ZVAL_IF_NOT_REF(cache);                \
-      ZVAL_LONG(*cache, 0);                           \
-      return;                                         \
-    }
+#define CASE(upb_type)                           \
+  case UPB_TYPE_##upb_type: {                    \
+    PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);   \
+    ZVAL_LONG(CACHED_PTR_TO_ZVAL_PTR(cache), 0); \
+    return;                                      \
+  }
 #endif
 CASE(UINT64)
 CASE(INT64)
@@ -299,13 +394,13 @@ CASE(INT64)
 
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES: {
-      SEPARATE_ZVAL_IF_NOT_REF(cache);
-      ZVAL_STRINGL(*cache, "", 0, 1);
+      PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);
+      PHP_PROTO_ZVAL_STRINGL(CACHED_PTR_TO_ZVAL_PTR(cache), "", 0, 1);
       break;
     }
     case UPB_TYPE_MESSAGE: {
-      SEPARATE_ZVAL_IF_NOT_REF(cache);
-      ZVAL_NULL(*cache);
+      PHP_PROTO_SEPARATE_ZVAL_IF_NOT_REF(cache);
+      ZVAL_NULL(CACHED_PTR_TO_ZVAL_PTR(cache));
       return;
     }
     default:
@@ -359,14 +454,15 @@ const upb_fielddef* map_entry_value(const upb_msgdef* msgdef) {
   return value_field;
 }
 
-const zend_class_entry* field_type_class(const upb_fielddef* field TSRMLS_DC) {
+const zend_class_entry* field_type_class(
+    const upb_fielddef* field PHP_PROTO_TSRMLS_DC) {
   if (upb_fielddef_type(field) == UPB_TYPE_MESSAGE) {
-    zval* desc_php = get_def_obj(upb_fielddef_subdef(field));
-    Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+    Descriptor* desc = UNBOX_HASHTABLE_VALUE(
+        Descriptor, get_def_obj(upb_fielddef_subdef(field)));
     return desc->klass;
   } else if (upb_fielddef_type(field) == UPB_TYPE_ENUM) {
-    zval* desc_php = get_def_obj(upb_fielddef_subdef(field));
-    EnumDescriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+    EnumDescriptor* desc = UNBOX_HASHTABLE_VALUE(
+        EnumDescriptor, get_def_obj(upb_fielddef_subdef(field)));
     return desc->klass;
   }
   return NULL;
@@ -501,7 +597,7 @@ void free_layout(MessageLayout* layout) {
 }
 
 void layout_init(MessageLayout* layout, void* storage,
-                 zval** properties_table TSRMLS_DC) {
+                 CACHED_VALUE* properties_table PHP_PROTO_TSRMLS_DC) {
   int i;
   upb_msg_field_iter it;
   for (upb_msg_field_begin(&it, layout->msgdef), i = 0; !upb_msg_field_done(&it);
@@ -510,20 +606,27 @@ void layout_init(MessageLayout* layout, void* storage,
     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);
-    zval** property_ptr = &properties_table[cache_index];
+    CACHED_VALUE* property_ptr = &properties_table[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);
-      map_field_create_with_field(map_field_type, field, property_ptr TSRMLS_CC);
-      DEREF(memory, zval**) = 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 TSRMLS_CC);
-      DEREF(memory, zval**) = property_ptr;
+                                       property_ptr PHP_PROTO_TSRMLS_CC);
+      DEREF(memory, CACHED_VALUE*) = property_ptr;
     } else {
       native_slot_init(upb_fielddef_type(field), memory, property_ptr);
     }
@@ -537,7 +640,7 @@ static void* value_memory(const upb_fielddef* field, void* memory) {
     case UPB_TYPE_STRING:
     case UPB_TYPE_BYTES:
     case UPB_TYPE_MESSAGE:
-      memory = DEREF(memory, zval**);
+      memory = DEREF(memory, CACHED_VALUE*);
       break;
     default:
       // No operation
@@ -547,7 +650,7 @@ static void* value_memory(const upb_fielddef* field, void* memory) {
 }
 
 zval* layout_get(MessageLayout* layout, const void* storage,
-                 const upb_fielddef* field, zval** cache TSRMLS_DC) {
+                 const upb_fielddef* field, CACHED_VALUE* cache TSRMLS_DC) {
   void* memory = slot_memory(layout, storage, field);
   uint32_t* oneof_case = slot_oneof_case(layout, storage, field);
 
@@ -558,13 +661,13 @@ zval* layout_get(MessageLayout* layout, const void* storage,
       native_slot_get(upb_fielddef_type(field), value_memory(field, memory),
                       cache TSRMLS_CC);
     }
-    return *cache;
+    return CACHED_PTR_TO_ZVAL_PTR(cache);
   } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-    return *cache;
+    return CACHED_PTR_TO_ZVAL_PTR(cache);
   } else {
     native_slot_get(upb_fielddef_type(field), value_memory(field, memory),
                     cache TSRMLS_CC);
-    return *cache;
+    return CACHED_PTR_TO_ZVAL_PTR(cache);
   }
 }
 
@@ -583,8 +686,7 @@ void layout_set(MessageLayout* layout, MessageHeader* header,
     switch (type) {
       case UPB_TYPE_MESSAGE: {
         const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
-        zval* desc_php = get_def_obj(msg);
-        Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+        Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
         ce = desc->klass;
         // Intentionally fall through.
       }
@@ -593,9 +695,9 @@ void layout_set(MessageLayout* layout, MessageHeader* header,
         int property_cache_index =
             header->descriptor->layout->fields[upb_fielddef_index(field)]
                 .cache_index;
-        DEREF(memory, zval**) =
+        DEREF(memory, CACHED_VALUE*) =
             &(header->std.properties_table)[property_cache_index];
-        memory = DEREF(memory, zval**);
+        memory = DEREF(memory, CACHED_VALUE*);
         break;
       }
       default:
@@ -606,27 +708,130 @@ void layout_set(MessageLayout* layout, MessageHeader* header,
     *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, zval**);
-    if (EXPECTED(DEREF(memory, zval*) != val)) {
-        zval_ptr_dtor(memory);
-        DEREF(memory, zval*) = val;
-        Z_ADDREF_P(val);
+    memory = DEREF(memory, void**);
+    zval* property_ptr = CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory);
+
+    if (EXPECTED(property_ptr != val)) {
+#if PHP_MAJOR_VERSION < 7
+        REPLACE_ZVAL_VALUE((zval**)memory, val, 1);
+#else
+        php_proto_zval_ptr_dtor(property_ptr);
+        ZVAL_ZVAL(property_ptr, val, 1, 0);
+#endif
     }
   } 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);
-      zval* desc_php = get_def_obj(msg);
-      Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
+      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);
   }
 }
 
+static 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)) {
+    switch (type) {
+#define CASE_TYPE(upb_type, c_type)                        \
+  case UPB_TYPE_##upb_type: {                              \
+    DEREF(to_memory, c_type) = DEREF(from_memory, c_type); \
+    break;                                                 \
+  }
+      CASE_TYPE(INT32, int32_t)
+      CASE_TYPE(UINT32, uint32_t)
+      CASE_TYPE(ENUM, int32_t)
+      CASE_TYPE(INT64, int64_t)
+      CASE_TYPE(UINT64, uint64_t)
+      CASE_TYPE(FLOAT, float)
+      CASE_TYPE(DOUBLE, double)
+      CASE_TYPE(BOOL, int8_t)
+
+#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);
+        break;
+      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;
+        if (native_slot_is_default(type, to_memory)) {
+#if PHP_MAJOR_VERSION < 7
+          SEPARATE_ZVAL_IF_NOT_REF((zval**)value_memory(field, to_memory));
+#endif
+          CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(
+              CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*)), ce);
+          MessageHeader* submsg =
+              UNBOX(MessageHeader,
+                    CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*)));
+          custom_data_init(ce, submsg PHP_PROTO_TSRMLS_CC);
+        }
+
+        MessageHeader* sub_from =
+            UNBOX(MessageHeader,
+                  CACHED_PTR_TO_ZVAL_PTR(DEREF(from_memory, CACHED_VALUE*)));
+        MessageHeader* sub_to =
+            UNBOX(MessageHeader,
+                  CACHED_PTR_TO_ZVAL_PTR(DEREF(to_memory, CACHED_VALUE*)));
+
+        layout_merge(desc->layout, sub_from, sub_to PHP_PROTO_TSRMLS_CC);
+        break;
+      }
+    }
+  }
+}
+
+static native_slot_merge_by_array(const upb_fielddef* field, const void* from_memory,
+                         void* to_memory PHP_PROTO_TSRMLS_DC) {
+  upb_fieldtype_t type = upb_fielddef_type(field);
+  switch (type) {
+    case UPB_TYPE_STRING:
+    case UPB_TYPE_BYTES: {
+#if PHP_MAJOR_VERSION < 7
+      MAKE_STD_ZVAL(DEREF(to_memory, zval*));
+      PHP_PROTO_ZVAL_STRINGL(DEREF(to_memory, zval*),
+                             Z_STRVAL_P(*(zval**)from_memory),
+                             Z_STRLEN_P(*(zval**)from_memory), 1);
+#else
+      DEREF(to_memory, zend_string*) =
+          zend_string_dup(*(zend_string**)from_memory, 0);
+#endif
+      break;
+    }
+    case UPB_TYPE_MESSAGE: {
+      const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
+      Descriptor* desc = UNBOX_HASHTABLE_VALUE(Descriptor, get_def_obj(msg));
+      zend_class_entry* ce = desc->klass;
+#if PHP_MAJOR_VERSION < 7
+      MAKE_STD_ZVAL(DEREF(to_memory, zval*));
+      CREATE_OBJ_ON_ALLOCATED_ZVAL_PTR(DEREF(to_memory, zval*), ce);
+#else
+      DEREF(to_memory, zend_object*) = ce->create_object(ce TSRMLS_CC);
+#endif
+      MessageHeader* sub_from = UNBOX_HASHTABLE_VALUE(
+          MessageHeader, DEREF(from_memory, PHP_PROTO_HASHTABLE_VALUE));
+      MessageHeader* sub_to = UNBOX_HASHTABLE_VALUE(
+          MessageHeader, DEREF(to_memory, PHP_PROTO_HASHTABLE_VALUE));
+      custom_data_init(ce, sub_to PHP_PROTO_TSRMLS_CC);
+
+      layout_merge(desc->layout, sub_from, sub_to PHP_PROTO_TSRMLS_CC);
+      break;
+    }
+    default:
+      native_slot_merge(field, from_memory, to_memory PHP_PROTO_TSRMLS_CC);
+      break;
+  }
+}
+
 void layout_merge(MessageLayout* layout, MessageHeader* from,
-                  MessageHeader* to TSRMLS_DC) {
+                  MessageHeader* to PHP_PROTO_TSRMLS_DC) {
   int i, j;
   upb_msg_field_iter it;
 
@@ -639,11 +844,10 @@ void layout_merge(MessageLayout* layout, MessageHeader* from,
 
     if (upb_fielddef_containingoneof(field)) {
       uint32_t oneof_case_offset =
-          layout->fields[upb_fielddef_index(field)].case_offset +
-          sizeof(MessageHeader);
+          layout->fields[upb_fielddef_index(field)].case_offset;
       // For a oneof, check that this field is actually present -- skip all the
       // below if not.
-      if (DEREF(((uint8_t*)from + oneof_case_offset), uint32_t) !=
+      if (DEREF((message_data(from) + oneof_case_offset), uint32_t) !=
           upb_fielddef_number(field)) {
         continue;
       }
@@ -658,7 +862,7 @@ void layout_merge(MessageLayout* layout, MessageHeader* from,
         case UPB_TYPE_BYTES: {
           int property_cache_index =
               layout->fields[upb_fielddef_index(field)].cache_index;
-          DEREF(to_memory, zval**) =
+          DEREF(to_memory, CACHED_VALUE*) =
               &(to->std.properties_table)[property_cache_index];
           break;
         }
@@ -676,141 +880,57 @@ void layout_merge(MessageLayout* layout, MessageHeader* from,
       int size, key_length, value_length;
       MapIter map_it;
 
-      zval* to_map_php = *DEREF(to_memory, zval**);
-      zval* from_map_php = *DEREF(from_memory, zval**);
-      Map* to_map = zend_object_store_get_object(to_map_php TSRMLS_CC);
-      Map* from_map = zend_object_store_get_object(from_map_php TSRMLS_CC);
+      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*));
+      Map* to_map = UNBOX(Map, to_map_php);
+      Map* from_map = UNBOX(Map, from_map_php);
 
       size = upb_strtable_count(&from_map->table);
       if (size == 0) continue;
 
+      const upb_msgdef *mapentry_def = upb_fielddef_msgsubdef(field);
+      const upb_fielddef *value_field = upb_msgdef_itof(mapentry_def, 2);
+
       for (map_begin(from_map_php, &map_it TSRMLS_CC); !map_done(&map_it);
            map_next(&map_it)) {
         const char* key = map_iter_key(&map_it, &key_length);
-        upb_value value = map_iter_value(&map_it, &value_length);
-        void* mem = upb_value_memory(&value);
-        switch (to_map->value_type) {
-          case UPB_TYPE_MESSAGE: {
-            zval* new_message;
-            message_create_with_type(to_map->msg_ce, &new_message TSRMLS_CC);
-            Z_ADDREF_P(new_message);
-
-            zval* subdesc_php = get_ce_obj(to_map->msg_ce);
-            Descriptor* subdesc =
-                zend_object_store_get_object(subdesc_php TSRMLS_CC);
-            MessageHeader* sub_from =
-                (MessageHeader*)zend_object_store_get_object(DEREF(mem, zval*)
-                                                                 TSRMLS_CC);
-            MessageHeader* sub_to =
-                (MessageHeader*)zend_object_store_get_object(
-                    new_message TSRMLS_CC);
-            layout_merge(subdesc->layout, sub_from, sub_to TSRMLS_CC);
-            DEREF(mem, zval*) = new_message;
-            break;
-          }
-          case UPB_TYPE_BYTES:
-          case UPB_TYPE_STRING:
-            Z_ADDREF_PP((zval**)mem);
-            break;
-          default:
-            break;
-        }
-        map_index_set(to_map, key, key_length, value);
+        upb_value from_value = map_iter_value(&map_it, &value_length);
+        upb_value to_value;
+        void* from_mem = upb_value_memory(&from_value);
+        void* to_mem = upb_value_memory(&to_value);
+        memset(to_mem, 0, native_slot_size(to_map->value_type));
+
+        native_slot_merge_by_array(value_field, from_mem,
+                                   to_mem PHP_PROTO_TSRMLS_CC);
+
+        map_index_set(to_map, key, key_length, to_value);
       }
 
     } else if (upb_fielddef_label(field) == UPB_LABEL_REPEATED) {
-      zval* to_array_php = *DEREF(to_memory, zval**);
-      zval* from_array_php = *DEREF(from_memory, zval**);
-      RepeatedField* to_array =
-          zend_object_store_get_object(to_array_php TSRMLS_CC);
-      RepeatedField* from_array =
-          zend_object_store_get_object(from_array_php TSRMLS_CC);
-
-      int size = zend_hash_num_elements(HASH_OF(from_array->array));
+      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*));
+      RepeatedField* to_array = UNBOX(RepeatedField, to_array_php);
+      RepeatedField* from_array = UNBOX(RepeatedField, from_array_php);
+
+      int size = zend_hash_num_elements(PHP_PROTO_HASH_OF(from_array->array));
       if (size > 0) {
         for (j = 0; j < size; j++) {
-          void* memory = NULL;
-          zend_hash_index_find(HASH_OF(from_array->array), j, (void**)&memory);
-          switch (to_array->type) {
-            case UPB_TYPE_STRING:
-            case UPB_TYPE_BYTES: {
-              zval* str;
-              MAKE_STD_ZVAL(str);
-              ZVAL_STRINGL(str, Z_STRVAL_PP((zval**)memory),
-                           Z_STRLEN_PP((zval**)memory), 1);
-              memory = &str;
-              break;
-            }
-            case UPB_TYPE_MESSAGE: {
-              zval* new_message;
-              message_create_with_type(from_array->msg_ce, &new_message TSRMLS_CC);
-              Z_ADDREF_P(new_message);
-
-              zval* subdesc_php = get_ce_obj(from_array->msg_ce);
-              Descriptor* subdesc =
-                  zend_object_store_get_object(subdesc_php TSRMLS_CC);
-              MessageHeader* sub_from =
-                  (MessageHeader*)zend_object_store_get_object(
-                      DEREF(memory, zval*) TSRMLS_CC);
-              MessageHeader* sub_to =
-                  (MessageHeader*)zend_object_store_get_object(
-                      new_message TSRMLS_CC);
-              layout_merge(subdesc->layout, sub_from, sub_to TSRMLS_CC);
-
-              memory = &new_message;
-            }
-            default:
-              break;
-          }
-          repeated_field_push_native(to_array, memory TSRMLS_CC);
+          void* from_memory = NULL;
+          void* to_memory =
+              ALLOC_N(char, native_slot_size(upb_fielddef_type(field)));
+          memset(to_memory, 0, native_slot_size(upb_fielddef_type(field)));
+          php_proto_zend_hash_index_find(PHP_PROTO_HASH_OF(from_array->array),
+                                         j, (void**)&from_memory);
+          native_slot_merge_by_array(field, from_memory,
+                                     to_memory PHP_PROTO_TSRMLS_CC);
+          repeated_field_push_native(to_array, to_memory);
+          FREE(to_memory);
         }
       }
     } else {
-      upb_fieldtype_t type = upb_fielddef_type(field);
-      zend_class_entry *ce = NULL;
-      if (!native_slot_is_default(type, from_memory)) {
-        switch (type) {
-#define CASE_TYPE(upb_type, c_type)                        \
-  case UPB_TYPE_##upb_type: {                              \
-    DEREF(to_memory, c_type) = DEREF(from_memory, c_type); \
-    break;                                                 \
-  }
-          CASE_TYPE(INT32,  int32_t)
-          CASE_TYPE(UINT32, uint32_t)
-          CASE_TYPE(ENUM,   int32_t)
-          CASE_TYPE(INT64,  int64_t)
-          CASE_TYPE(UINT64, uint64_t)
-          CASE_TYPE(FLOAT,  float)
-          CASE_TYPE(DOUBLE, double)
-          CASE_TYPE(BOOL,   int8_t)
-
-#undef CASE_TYPE
-          case UPB_TYPE_STRING:
-          case UPB_TYPE_BYTES:
-            native_slot_set(type, NULL, value_memory(field, to_memory),
-                            *DEREF(from_memory, zval**) TSRMLS_CC);
-            break;
-          case UPB_TYPE_MESSAGE: {
-            const upb_msgdef* msg = upb_fielddef_msgsubdef(field);
-            zval* desc_php = get_def_obj(msg);
-            Descriptor* desc = zend_object_store_get_object(desc_php TSRMLS_CC);
-            ce = desc->klass;
-            if (native_slot_is_default(type, to_memory)) {
-              zval* new_message = NULL;
-              message_create_with_type(ce, &new_message TSRMLS_CC);
-              native_slot_set(type, ce, value_memory(field, to_memory),
-                              new_message TSRMLS_CC);
-            }
-            MessageHeader* sub_from =
-                (MessageHeader*)zend_object_store_get_object(
-                    *DEREF(from_memory, zval**) TSRMLS_CC);
-            MessageHeader* sub_to =
-                (MessageHeader*)zend_object_store_get_object(
-                    *DEREF(to_memory, zval**) TSRMLS_CC);
-            layout_merge(desc->layout, sub_from, sub_to TSRMLS_CC);
-          }
-        }
-      }
+      native_slot_merge(field, from_memory, to_memory PHP_PROTO_TSRMLS_CC);
     }
   }
 }

+ 60 - 25
php/ext/google/protobuf/type_check.c

@@ -325,9 +325,18 @@ CONVERT_TO_FLOAT(double);
 
 bool protobuf_convert_to_bool(zval* from, int8_t* to) {
   switch (Z_TYPE_P(from)) {
+#if PHP_MAJOR_VERSION < 7
     case IS_BOOL:
       *to = (int8_t)Z_BVAL_P(from);
       break;
+#else
+    case IS_TRUE:
+      *to = 1;
+      break;
+    case IS_FALSE:
+      *to = 0;
+      break;
+#endif
     case IS_LONG:
       *to = (int8_t)(Z_LVAL_P(from) != 0);
       break;
@@ -357,12 +366,16 @@ bool protobuf_convert_to_string(zval* from) {
     case IS_STRING: {
       return true;
     }
+#if PHP_MAJOR_VERSION < 7
     case IS_BOOL:
+#else
+    case IS_TRUE:
+    case IS_FALSE:
+#endif
     case IS_LONG:
     case IS_DOUBLE: {
-      int use_copy;
       zval tmp;
-      zend_make_printable_zval(from, &tmp, &use_copy);
+      php_proto_zend_make_printable_zval(from, &tmp);
       ZVAL_COPY_VALUE(from, &tmp);
       return true;
     }
@@ -417,34 +430,45 @@ PHP_METHOD(Util, checkMessage) {
 
 PHP_METHOD(Util, checkRepeatedField) {
   zval* val;
-  long type;
+  PHP_PROTO_LONG type;
   const zend_class_entry* klass = NULL;
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zl|C", &val, &type,
                             &klass) == FAILURE) {
     return;
   }
 
+#if PHP_MAJOR_VERSION >= 7
+  if (Z_ISREF_P(val)) {
+    ZVAL_DEREF(val);
+  }
+#endif
+
   if (Z_TYPE_P(val) == IS_ARRAY) {
-    HashTable* table = Z_ARRVAL_P(val);
+    HashTable* table = HASH_OF(val);
     HashPosition pointer;
     void* memory;
+
+#if PHP_MAJOR_VERSION < 7
     zval* repeated_field;
+    MAKE_STD_ZVAL(repeated_field);
+#else
+    zval repeated_field;
+#endif
 
     repeated_field_create_with_type(repeated_field_type, to_fieldtype(type),
                                     klass, &repeated_field TSRMLS_CC);
-    RepeatedField* intern =
-        (RepeatedField*)zend_object_store_get_object(repeated_field TSRMLS_CC);
 
     for (zend_hash_internal_pointer_reset_ex(table, &pointer);
-         zend_hash_get_current_data_ex(table, (void**)&memory, &pointer) ==
-         SUCCESS;
+         php_proto_zend_hash_get_current_data_ex(table, (void**)&memory,
+                                                 &pointer) == SUCCESS;
          zend_hash_move_forward_ex(table, &pointer)) {
-      repeated_field_handlers->write_dimension(repeated_field, NULL,
-                                               *(zval**)memory TSRMLS_CC);
+      repeated_field_handlers->write_dimension(
+          CACHED_TO_ZVAL_PTR(repeated_field), NULL,
+          CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)memory) TSRMLS_CC);
     }
 
-    Z_DELREF_P(repeated_field);
-    RETURN_ZVAL(repeated_field, 1, 0);
+    Z_DELREF_P(CACHED_TO_ZVAL_PTR(repeated_field));
+    RETURN_ZVAL(CACHED_TO_ZVAL_PTR(repeated_field), 1, 0);
 
   } else if (Z_TYPE_P(val) == IS_OBJECT) {
     if (!instanceof_function(Z_OBJCE_P(val), repeated_field_type TSRMLS_CC)) {
@@ -452,8 +476,7 @@ PHP_METHOD(Util, checkRepeatedField) {
                  repeated_field_type->name);
       return;
     }
-    RepeatedField* intern =
-        (RepeatedField*)zend_object_store_get_object(val TSRMLS_CC);
+    RepeatedField* intern = UNBOX(RepeatedField, val);
     if (to_fieldtype(type) != intern->type) {
       zend_error(E_USER_ERROR, "Incorrect repeated field type.");
       return;
@@ -474,43 +497,55 @@ PHP_METHOD(Util, checkRepeatedField) {
 
 PHP_METHOD(Util, checkMapField) {
   zval* val;
-  long key_type, value_type;
+  PHP_PROTO_LONG key_type, value_type;
   const zend_class_entry* klass = NULL;
   if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "zll|C", &val, &key_type,
                             &value_type, &klass) == FAILURE) {
     return;
   }
 
+#if PHP_MAJOR_VERSION >= 7
+  if (Z_ISREF_P(val)) {
+    ZVAL_DEREF(val);
+  }
+#endif
+
   if (Z_TYPE_P(val) == IS_ARRAY) {
     HashTable* table = Z_ARRVAL_P(val);
     HashPosition pointer;
-    zval key, *map_field;
+    zval key;
     void* value;
 
+#if PHP_MAJOR_VERSION < 7
+    zval* map_field;
+    MAKE_STD_ZVAL(map_field);
+#else
+    zval map_field;
+#endif
+
     map_field_create_with_type(map_field_type, to_fieldtype(key_type),
                                to_fieldtype(value_type), klass,
                                &map_field TSRMLS_CC);
-    Map* intern =
-        (Map*)zend_object_store_get_object(map_field TSRMLS_CC);
 
     for (zend_hash_internal_pointer_reset_ex(table, &pointer);
-         zend_hash_get_current_data_ex(table, (void**)&value, &pointer) ==
-         SUCCESS;
+         php_proto_zend_hash_get_current_data_ex(table, (void**)&value,
+                                                 &pointer) == SUCCESS;
          zend_hash_move_forward_ex(table, &pointer)) {
       zend_hash_get_current_key_zval_ex(table, &key, &pointer);
-      map_field_handlers->write_dimension(map_field, &key,
-                                          *(zval**)value TSRMLS_CC);
+      map_field_handlers->write_dimension(
+          CACHED_TO_ZVAL_PTR(map_field), &key,
+          CACHED_PTR_TO_ZVAL_PTR((CACHED_VALUE*)value) TSRMLS_CC);
     }
 
-    Z_DELREF_P(map_field);
-    RETURN_ZVAL(map_field, 1, 0);
+    Z_DELREF_P(CACHED_TO_ZVAL_PTR(map_field));
+    RETURN_ZVAL(CACHED_TO_ZVAL_PTR(map_field), 1, 0);
   } else if (Z_TYPE_P(val) == IS_OBJECT) {
     if (!instanceof_function(Z_OBJCE_P(val), map_field_type TSRMLS_CC)) {
       zend_error(E_USER_ERROR, "Given value is not an instance of %s.",
                  map_field_type->name);
       return;
     }
-    Map* intern = (Map*)zend_object_store_get_object(val TSRMLS_CC);
+    Map* intern = UNBOX(Map, val);
     if (to_fieldtype(key_type) != intern->key_type) {
       zend_error(E_USER_ERROR, "Incorrect map field key type.");
       return;

+ 3 - 0
php/src/Google/Protobuf/Internal/DescriptorPool.php

@@ -95,6 +95,9 @@ class DescriptorPool
         foreach ($descriptor->getNestedType() as $nested_type) {
             $this->addDescriptor($nested_type);
         }
+        foreach ($descriptor->getEnumType() as $enum_type) {
+            $this->addEnumDescriptor($enum_type);
+        }
     }
 
     public function addEnumDescriptor($descriptor)

+ 47 - 0
php/src/Google/Protobuf/Internal/GPBDecodeException.php

@@ -0,0 +1,47 @@
+<?php
+
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 Google Inc.  All rights reserved.
+// https://developers.google.com/protocol-buffers/
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are
+// met:
+//
+//     * Redistributions of source code must retain the above copyright
+// notice, this list of conditions and the following disclaimer.
+//     * Redistributions in binary form must reproduce the above
+// copyright notice, this list of conditions and the following disclaimer
+// in the documentation and/or other materials provided with the
+// distribution.
+//     * Neither the name of Google Inc. nor the names of its
+// contributors may be used to endorse or promote products derived from
+// this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+namespace Google\Protobuf\Internal;
+
+class GPBDecodeException extends \Exception
+{
+    public function __construct(
+        $message,
+        $code = 0,
+        \Exception $previous = null)
+    {
+        parent::__construct(
+            "Error occurred during parsing: " . $message,
+            $code,
+            $previous);
+    }
+}

+ 7 - 10
php/src/Google/Protobuf/Internal/InputStream.php

@@ -330,6 +330,7 @@ class InputStream
      * passed unchanged to the corresponding call to popLimit().
      *
      * @param integer $byte_limit
+     * @throws Exception Fail to push limit.
      */
     public function pushLimit($byte_limit)
     {
@@ -339,19 +340,15 @@ class InputStream
 
         // security: byte_limit is possibly evil, so check for negative values
         // and overflow.
-        if ($byte_limit >= 0 && $byte_limit <= PHP_INT_MAX - $current_position) {
+        if ($byte_limit >= 0 &&
+            $byte_limit <= PHP_INT_MAX - $current_position &&
+            $byte_limit <= $this->current_limit - $current_position) {
             $this->current_limit = $current_position + $byte_limit;
+            $this->recomputeBufferLimits();
         } else {
-            // Negative or overflow.
-            $this->current_limit = PHP_INT_MAX;
+            throw new GPBDecodeException("Fail to push limit.");
         }
 
-        // We need to enforce all limits, not just the new one, so if the previous
-        // limit was before the new requested limit, we continue to enforce the
-        // previous limit.
-        $this->current_limit = min($this->current_limit, $old_limit);
-
-        $this->recomputeBufferLimits();
         return $old_limit;
     }
 
@@ -370,7 +367,7 @@ class InputStream
     }
 
     public function incrementRecursionDepthAndPushLimit(
-    $byte_limit, &$old_limit, &$recursion_budget)
+        $byte_limit, &$old_limit, &$recursion_budget)
     {
         $old_limit = $this->pushLimit($byte_limit);
         $recursion_limit = --$this->recursion_limit;

+ 3 - 1
php/src/Google/Protobuf/Internal/MapField.php

@@ -155,7 +155,6 @@ function checkKey($key_type, &$key)
             GPBUtil::checkString($key, true);
             break;
         default:
-            var_dump($key_type);
             trigger_error(
                 "Given type cannot be map key.",
                 E_USER_ERROR);
@@ -284,6 +283,9 @@ class MapField implements \ArrayAccess, \IteratorAggregate, \Countable
                 GPBUtil::checkString($value, true);
                 break;
             case GPBType::MESSAGE:
+                if (is_null($value)) {
+                  trigger_error("Map element cannot be null.", E_USER_ERROR);
+                }
                 GPBUtil::checkMessage($value, $this->klass);
                 break;
             default:

+ 42 - 31
php/src/Google/Protobuf/Internal/Message.php

@@ -224,48 +224,57 @@ class Message
         switch ($field->getType()) {
             case GPBType::DOUBLE:
                 if (!GPBWire::readDouble($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside double field.");
                 }
                 break;
             case GPBType::FLOAT:
                 if (!GPBWire::readFloat($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside float field.");
                 }
                 break;
             case GPBType::INT64:
                 if (!GPBWire::readInt64($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside int64 field.");
                 }
                 break;
             case GPBType::UINT64:
                 if (!GPBWire::readUint64($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside uint64 field.");
                 }
                 break;
             case GPBType::INT32:
                 if (!GPBWire::readInt32($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside int32 field.");
                 }
                 break;
             case GPBType::FIXED64:
                 if (!GPBWire::readFixed64($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside fixed64 field.");
                 }
                 break;
             case GPBType::FIXED32:
                 if (!GPBWire::readFixed32($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside fixed32 field.");
                 }
                 break;
             case GPBType::BOOL:
                 if (!GPBWire::readBool($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside bool field.");
                 }
                 break;
             case GPBType::STRING:
                 // TODO(teboring): Add utf-8 check.
                 if (!GPBWire::readString($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside string field.");
                 }
                 break;
             case GPBType::GROUP:
@@ -280,43 +289,51 @@ class Message
                     $value = new $klass;
                 }
                 if (!GPBWire::readMessage($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside message.");
                 }
                 break;
             case GPBType::BYTES:
                 if (!GPBWire::readString($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside bytes field.");
                 }
                 break;
             case GPBType::UINT32:
                 if (!GPBWire::readUint32($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside uint32 field.");
                 }
                 break;
             case GPBType::ENUM:
                 // TODO(teboring): Check unknown enum value.
                 if (!GPBWire::readInt32($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside enum field.");
                 }
                 break;
             case GPBType::SFIXED32:
                 if (!GPBWire::readSfixed32($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside sfixed32 field.");
                 }
                 break;
             case GPBType::SFIXED64:
                 if (!GPBWire::readSfixed64($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside sfixed64 field.");
                 }
                 break;
             case GPBType::SINT32:
                 if (!GPBWire::readSint32($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside sint32 field.");
                 }
                 break;
             case GPBType::SINT64:
                 if (!GPBWire::readSint64($input, $value)) {
-                    return false;
+                    throw new GPBDecodeException(
+                        "Unexpected EOF inside sint64 field.");
                 }
                 break;
             default:
@@ -345,24 +362,21 @@ class Message
         }
 
         if ($value_format === GPBWire::NORMAL_FORMAT) {
-            if (!self::parseFieldFromStreamNoTag($input, $field, $value)) {
-                return false;
-            }
+            self::parseFieldFromStreamNoTag($input, $field, $value);
         } elseif ($value_format === GPBWire::PACKED_FORMAT) {
             $length = 0;
             if (!GPBWire::readInt32($input, $length)) {
-                return false;
+                throw new GPBDecodeException(
+                    "Unexpected EOF inside packed length.");
             }
             $limit = $input->pushLimit($length);
             $getter = $field->getGetter();
             while ($input->bytesUntilLimit() > 0) {
-                if (!self::parseFieldFromStreamNoTag($input, $field, $value)) {
-                    return false;
-                }
+                self::parseFieldFromStreamNoTag($input, $field, $value);
                 $this->$getter()[] = $value;
             }
             $input->popLimit($limit);
-            return true;
+            return;
         } else {
             return false;
         }
@@ -377,8 +391,6 @@ class Message
             $setter = $field->getSetter();
             $this->$setter($value);
         }
-
-        return true;
     }
 
     /**
@@ -567,7 +579,8 @@ class Message
      * specified message.
      *
      * @param string $data Binary protobuf data.
-     * @return bool Return true on success.
+     * @return null.
+     * @throws Exception Invalid data.
      */
     public function mergeFromString($data)
     {
@@ -595,9 +608,7 @@ class Message
               continue;
             }
 
-            if (!$this->parseFieldFromStream($tag, $input, $field)) {
-                return false;
-            }
+            $this->parseFieldFromStream($tag, $input, $field);
         }
     }
 

+ 4 - 0
php/src/Google/Protobuf/Internal/RepeatedField.php

@@ -225,6 +225,10 @@ class RepeatedField implements \ArrayAccess, \IteratorAggregate, \Countable
                 GPBUtil::checkString($value, true);
                 break;
             case GPBType::MESSAGE:
+                if (is_null($value)) {
+                  trigger_error("RepeatedField element cannot be null.",
+                                E_USER_ERROR);
+                }
                 GPBUtil::checkMessage($value, $this->klass);
                 break;
             default:

+ 32 - 10
php/src/Google/Protobuf/descriptor.php

@@ -210,6 +210,12 @@ class Descriptor
               $nested_proto, $file_proto, $message_name_without_package));
         }
 
+        // Handle nested enum.
+        foreach ($proto->getEnumType() as $enum_proto) {
+            $desc->addEnumType(EnumDescriptor::buildFromProto(
+              $enum_proto, $file_proto, $message_name_without_package));
+        }
+
         // Handle oneof fields.
         foreach ($proto->getOneofDecl() as $oneof_proto) {
             $desc->addOneofDecl(
@@ -220,20 +226,36 @@ class Descriptor
     }
 }
 
+function getClassNamePrefix(
+    $classname,
+    $file_proto)
+{
+    $option = $file_proto->getOptions();
+    $prefix = is_null($option) ? "" : $option->getPhpClassPrefix();
+    if ($prefix !== "") {
+        return $prefix;
+    }
+
+    $reserved_words = array("Empty");
+    foreach ($reserved_words as $reserved_word) {
+        if ($classname === $reserved_word) {
+            if ($file_proto->getPackage() === "google.protobuf") {
+                return "GPB";
+            } else {
+                return "PB";
+            }
+        }
+    }
+
+    return "";
+}
+
 function getClassNameWithoutPackage(
     $name,
     $file_proto)
 {
-    if ($name === "Empty" && $file_proto->getPackage() === "google.protobuf") {
-        return "GPBEmpty";
-    } else {
-        $option = $file_proto->getOptions();
-        $prefix = is_null($option) ? "" : $option->getPhpClassPrefix();
-        // Nested message class names are seperated by '_', and package names
-        // are seperated by '\'.
-        return $prefix . implode('_', array_map('ucwords',
-                               explode('.', $name)));
-    }
+    $classname = implode('_', array_map('ucwords', explode('.', $name)));
+    return getClassNamePrefix($classname, $file_proto) . $classname;
 }
 
 function getFullClassName(

Някои файлове не бяха показани, защото твърде много файлове са промени