diff --git a/.bazelrc b/.bazelrc index 554440cfe3d0..6b4a48243b8a 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1 +1,5 @@ build --cxxopt=-std=c++14 --host_cxxopt=-std=c++14 +common --action_env=PATH +common --action_env=GEM_HOME +common --action_env=GEM_PATH +common --test_env=BAZEL=true \ No newline at end of file diff --git a/.github/workflows/codespell.yml b/.github/workflows/codespell.yml index 2e5ee80cdeda..d82d308cc283 100644 --- a/.github/workflows/codespell.yml +++ b/.github/workflows/codespell.yml @@ -13,4 +13,4 @@ jobs: with: check_filenames: true skip: ./.git,./conformance/third_party,*.snk,*.pb,*.pb.cc,*.pb.h,./src/google/protobuf/testdata,./objectivec/Tests,./python/compatibility_tests/v2.5.0/tests/google/protobuf/internal,./.github/workflows/codespell.yml - ignore_words_list: "alow,alse,ba,cleare,copyable,cloneable,dedup,dur,errorprone,files',fo,fundementals,hel,importd,inout,leapyear,nd,nin,ois,ons,parseable,process',te,testof,ue,unparseable,wasn,wee,gae,keyserver,objext,od,optin,streem,sur,falsy" + ignore_words_list: "alow,alse,ba,cleare,copyable,cloneable,dedup,dur,errorprone,files',fo,fundementals,hel,importd,inout,leapyear,nd,nin,ois,ons,parseable,process',te,testof,ue,unparseable,wasn,wee,gae,keyserver,objext,od,optin,streem,sur,falsy,upto" diff --git a/conformance/failure_list_jruby.txt b/conformance/failure_list_jruby.txt index 516192ec009d..ff230a26a9c3 100644 --- a/conformance/failure_list_jruby.txt +++ b/conformance/failure_list_jruby.txt @@ -1,6 +1,3 @@ -Recommended.FieldMaskNumbersDontRoundTrip.JsonOutput -Recommended.FieldMaskPathsDontRoundTrip.JsonOutput -Recommended.FieldMaskTooManyUnderscore.JsonOutput Recommended.Proto2.JsonInput.FieldNameExtension.Validator Recommended.Proto2.ProtobufInput.ValidDataRepeated.BOOL.PackedInput.PackedOutput.ProtobufOutput Recommended.Proto2.ProtobufInput.ValidDataRepeated.BOOL.UnpackedInput.PackedOutput.ProtobufOutput @@ -30,33 +27,6 @@ Recommended.Proto2.ProtobufInput.ValidDataRepeated.UINT32.PackedInput.PackedOutp Recommended.Proto2.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.PackedOutput.ProtobufOutput Recommended.Proto2.ProtobufInput.ValidDataRepeated.UINT64.PackedInput.PackedOutput.ProtobufOutput Recommended.Proto2.ProtobufInput.ValidDataRepeated.UINT64.UnpackedInput.PackedOutput.ProtobufOutput -Recommended.Proto3.JsonInput.BoolFieldAllCapitalFalse -Recommended.Proto3.JsonInput.BoolFieldAllCapitalTrue -Recommended.Proto3.JsonInput.BoolFieldCamelCaseFalse -Recommended.Proto3.JsonInput.BoolFieldCamelCaseTrue -Recommended.Proto3.JsonInput.BoolFieldDoubleQuotedFalse -Recommended.Proto3.JsonInput.BoolFieldDoubleQuotedTrue -Recommended.Proto3.JsonInput.BoolMapFieldKeyNotQuoted -Recommended.Proto3.JsonInput.DoubleFieldInfinityNotQuoted -Recommended.Proto3.JsonInput.DoubleFieldNanNotQuoted -Recommended.Proto3.JsonInput.DoubleFieldNegativeInfinityNotQuoted -Recommended.Proto3.JsonInput.FieldMaskInvalidCharacter -Recommended.Proto3.JsonInput.FieldNameDuplicate -Recommended.Proto3.JsonInput.FieldNameNotQuoted -Recommended.Proto3.JsonInput.FloatFieldInfinityNotQuoted -Recommended.Proto3.JsonInput.FloatFieldNanNotQuoted -Recommended.Proto3.JsonInput.FloatFieldNegativeInfinityNotQuoted -Recommended.Proto3.JsonInput.Int32MapFieldKeyNotQuoted -Recommended.Proto3.JsonInput.Int64MapFieldKeyNotQuoted -Recommended.Proto3.JsonInput.JsonWithComments -Recommended.Proto3.JsonInput.StringFieldSingleQuoteBoth -Recommended.Proto3.JsonInput.StringFieldSingleQuoteKey -Recommended.Proto3.JsonInput.StringFieldSingleQuoteValue -Recommended.Proto3.JsonInput.StringFieldSurrogateInWrongOrder -Recommended.Proto3.JsonInput.StringFieldUnpairedHighSurrogate -Recommended.Proto3.JsonInput.StringFieldUnpairedLowSurrogate -Recommended.Proto3.JsonInput.Uint32MapFieldKeyNotQuoted -Recommended.Proto3.JsonInput.Uint64MapFieldKeyNotQuoted Recommended.Proto3.ProtobufInput.ValidDataOneofBinary.MESSAGE.Merge.ProtobufOutput Recommended.Proto3.ProtobufInput.ValidDataRepeated.BOOL.PackedInput.UnpackedOutput.ProtobufOutput Recommended.Proto3.ProtobufInput.ValidDataRepeated.BOOL.UnpackedInput.UnpackedOutput.ProtobufOutput @@ -86,10 +56,6 @@ Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.PackedInput.UnpackedOu Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT32.UnpackedInput.UnpackedOutput.ProtobufOutput Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT64.PackedInput.UnpackedOutput.ProtobufOutput Recommended.Proto3.ProtobufInput.ValidDataRepeated.UINT64.UnpackedInput.UnpackedOutput.ProtobufOutput -Required.Proto3.JsonInput.EnumFieldNotQuoted -Required.Proto3.JsonInput.Int32FieldLeadingZero -Required.Proto3.JsonInput.Int32FieldNegativeWithLeadingZero -Required.Proto3.JsonInput.Int32FieldPlusSign -Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotBool -Required.Proto3.JsonInput.RepeatedFieldWrongElementTypeExpectingStringsGotInt -Required.Proto3.JsonInput.StringFieldNotAString +Required.Proto3.JsonInput.DurationNegativeNanos.JsonOutput +Required.Proto3.JsonInput.DurationNegativeNanos.ProtobufOutput +Required.Proto3.JsonInput.DurationNegativeSeconds.JsonOutput diff --git a/kokoro/linux/jruby92/continuous.cfg b/kokoro/linux/jruby92/continuous.cfg index 9db8f50637de..d66d147a73c3 100644 --- a/kokoro/linux/jruby92/continuous.cfg +++ b/kokoro/linux/jruby92/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:jruby-9.2.20.1-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:jruby-9.2.20.1-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/jruby92/presubmit.cfg b/kokoro/linux/jruby92/presubmit.cfg index 9db8f50637de..d66d147a73c3 100644 --- a/kokoro/linux/jruby92/presubmit.cfg +++ b/kokoro/linux/jruby92/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:jruby-9.2.20.1-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:jruby-9.2.20.1-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/jruby93/continuous.cfg b/kokoro/linux/jruby93/continuous.cfg index a5a9bd2374e5..c94a07da7d72 100644 --- a/kokoro/linux/jruby93/continuous.cfg +++ b/kokoro/linux/jruby93/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:jruby-9.3.4.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:jruby-9.3.4.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/jruby93/presubmit.cfg b/kokoro/linux/jruby93/presubmit.cfg index a5a9bd2374e5..c94a07da7d72 100644 --- a/kokoro/linux/jruby93/presubmit.cfg +++ b/kokoro/linux/jruby93/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:jruby-9.3.4.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:jruby-9.3.4.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby25/continuous.cfg b/kokoro/linux/ruby25/continuous.cfg index ec975cd7d26c..ef02ef8c8562 100644 --- a/kokoro/linux/ruby25/continuous.cfg +++ b/kokoro/linux/ruby25/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-2.5.1-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-2.5.1-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby25/presubmit.cfg b/kokoro/linux/ruby25/presubmit.cfg index ec975cd7d26c..ef02ef8c8562 100644 --- a/kokoro/linux/ruby25/presubmit.cfg +++ b/kokoro/linux/ruby25/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-2.5.1-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-2.5.1-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby26/continuous.cfg b/kokoro/linux/ruby26/continuous.cfg index d09a405d8c62..cd4cc48c6db5 100644 --- a/kokoro/linux/ruby26/continuous.cfg +++ b/kokoro/linux/ruby26/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-2.6.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-2.6.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby26/presubmit.cfg b/kokoro/linux/ruby26/presubmit.cfg index d09a405d8c62..cd4cc48c6db5 100644 --- a/kokoro/linux/ruby26/presubmit.cfg +++ b/kokoro/linux/ruby26/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-2.6.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-2.6.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby27/continuous.cfg b/kokoro/linux/ruby27/continuous.cfg index 51afa75d27ca..1586ec772518 100644 --- a/kokoro/linux/ruby27/continuous.cfg +++ b/kokoro/linux/ruby27/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-2.7.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-2.7.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby27/presubmit.cfg b/kokoro/linux/ruby27/presubmit.cfg index 51afa75d27ca..1586ec772518 100644 --- a/kokoro/linux/ruby27/presubmit.cfg +++ b/kokoro/linux/ruby27/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-2.7.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-2.7.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby30/continuous.cfg b/kokoro/linux/ruby30/continuous.cfg index 338505e57eec..c5e3ab4c75b2 100644 --- a/kokoro/linux/ruby30/continuous.cfg +++ b/kokoro/linux/ruby30/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-3.0.2-2f706fd1ab49f4e97af769388be486069b63efee" + value: "gcr.io/protobuf-build/ruby/linux:ruby-3.0.2-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby30/presubmit.cfg b/kokoro/linux/ruby30/presubmit.cfg index 338505e57eec..c5e3ab4c75b2 100644 --- a/kokoro/linux/ruby30/presubmit.cfg +++ b/kokoro/linux/ruby30/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-3.0.2-2f706fd1ab49f4e97af769388be486069b63efee" + value: "gcr.io/protobuf-build/ruby/linux:ruby-3.0.2-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby31/continuous.cfg b/kokoro/linux/ruby31/continuous.cfg index 588fe136373f..9524a4481794 100644 --- a/kokoro/linux/ruby31/continuous.cfg +++ b/kokoro/linux/ruby31/continuous.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-3.1.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-3.1.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/linux/ruby31/presubmit.cfg b/kokoro/linux/ruby31/presubmit.cfg index 588fe136373f..9524a4481794 100644 --- a/kokoro/linux/ruby31/presubmit.cfg +++ b/kokoro/linux/ruby31/presubmit.cfg @@ -6,7 +6,7 @@ timeout_mins: 120 env_vars { key: "CONTAINER_IMAGE" - value: "gcr.io/protobuf-build/ruby/linux:ruby-3.1.0-64e8944e4f18d7d6c9649112a8a93be57e693cd8" + value: "gcr.io/protobuf-build/ruby/linux:ruby-3.1.0-ef6c39622a830821ce540804f815a8466b8fdc27" } env_vars { diff --git a/kokoro/release/ruby/linux/ruby/ruby_build.sh b/kokoro/release/ruby/linux/ruby/ruby_build.sh index 12d270eabdc3..fb022a483311 100755 --- a/kokoro/release/ruby/linux/ruby/ruby_build.sh +++ b/kokoro/release/ruby/linux/ruby/ruby_build.sh @@ -12,8 +12,8 @@ export PROTOC=$PWD/protoc umask 0022 pushd ruby -gem install bundler -v 2.1.4 -bundle update && bundle exec rake gem:native +gem install bundler +bundle update && bundle exec rake gem ls pkg mv pkg/* $ARTIFACT_DIR popd diff --git a/kokoro/release/ruby/linux/ruby/ruby_build_environment.sh b/kokoro/release/ruby/linux/ruby/ruby_build_environment.sh index 87c0f75448d8..370e4ff2c098 100755 --- a/kokoro/release/ruby/linux/ruby/ruby_build_environment.sh +++ b/kokoro/release/ruby/linux/ruby/ruby_build_environment.sh @@ -4,6 +4,5 @@ set +ex [[ -s /etc/profile.d/rvm.sh ]] && . /etc/profile.d/rvm.sh set -e # rvm commands are very verbose rvm --default use ruby-2.4.1 -# The version needs to be updated if the version specified in Gemfile.lock is changed -gem install bundler -v '1.17.3' +gem install bundler set -ex diff --git a/kokoro/release/ruby/macos/ruby/ruby_build.sh b/kokoro/release/ruby/macos/ruby/ruby_build.sh index bbfc631197e8..9ea961731962 100755 --- a/kokoro/release/ruby/macos/ruby/ruby_build.sh +++ b/kokoro/release/ruby/macos/ruby/ruby_build.sh @@ -8,7 +8,8 @@ export PROTOC=$PWD/bazel-bin/protoc umask 0022 pushd ruby -bundle update && bundle exec rake gem:native +gem install bundler +bundle update && bundle exec rake gem ls pkg mv pkg/* $ARTIFACT_DIR popd diff --git a/ruby/.gitignore b/ruby/.gitignore index 653309818f32..ac3790594b06 100644 --- a/ruby/.gitignore +++ b/ruby/.gitignore @@ -1,8 +1,7 @@ *.bundle tags .idea/ -lib/google/protobuf_java.jar -protobuf-jruby.iml +*.iml target/ pkg/ tmp/ diff --git a/ruby/BUILD.bazel b/ruby/BUILD.bazel index d014402f4519..91de8824c36c 100644 --- a/ruby/BUILD.bazel +++ b/ruby/BUILD.bazel @@ -26,43 +26,31 @@ filegroup( name = "srcs", srcs = glob([ "lib/**/*.rb", - "src/**/*.proto", + "ext/**/*.{c,h}}", ]) + [ "Gemfile", "Rakefile", "google-protobuf.gemspec", - "pom.xml", ], ) internal_ruby_extension( name = "protobuf_c_mac", - extension = "lib/google/protobuf_c.bundle", + extension = "ext/google/x86_64-darwin/libprotobuf_c.bundle", deps = glob(["ext/google/protobuf_c/*"]), target_compatible_with = select({ - ":java_ruby": ["@platforms//:incompatible"], + "@platforms//os:linux": ["@platforms//:incompatible"], "//conditions:default": ["@platforms//os:osx"], }), ) internal_ruby_extension( name = "protobuf_c", - extension = "lib/google/protobuf_c.so", + extension = "ext/google/x86_64-linux/libprotobuf_c.so", deps = glob(["ext/google/protobuf_c/*"]), target_compatible_with = select({ - ":java_ruby": ["@platforms//:incompatible"], "@platforms//os:osx": ["@platforms//:incompatible"], - "//conditions:default": [], - }), -) - -internal_ruby_extension( - name = "protobuf_java", - extension = "lib/google/protobuf_java.jar", - deps = glob(["src/**/*.java"]), - target_compatible_with = select({ - ":java_ruby": [], - "//conditions:default": ["@platforms//:incompatible"], + "//conditions:default": ["@platforms//os:linux"], }), ) @@ -72,7 +60,6 @@ filegroup( ":srcs", "//third_party/utf8_range:all_files", ] + select({ - ":java_ruby": [":protobuf_java"], "@bazel_tools//src/conditions:darwin": [":protobuf_c_mac"], "//conditions:default": [":protobuf_c"], }), @@ -116,7 +103,7 @@ inline_sh_test( ], cmd = """ pushd `dirname $(location Rakefile)` - RUBYLIB=../src:tests:. BAZEL=true rake test + RUBYLIB=../src:tests:. rake test popd """, ) @@ -134,9 +121,15 @@ inline_sh_test( ], cmd = """ pushd `dirname $(location Rakefile)` - RUBYLIB=../src:tests:. BAZEL=true rake gc_test + RUBYLIB=../src:tests:. rake gc_test popd """, + timeout = "long", + # Under JRuby, GC.stress is a no-op, so skip running this test. + target_compatible_with = select({ + ":java_ruby": ["@platforms//:incompatible"], + "//conditions:default": [], + }), ) conformance_test( diff --git a/ruby/README.md b/ruby/README.md index be8d6bc8b1cf..df76d51a9e86 100644 --- a/ruby/README.md +++ b/ruby/README.md @@ -59,12 +59,6 @@ To build this Ruby extension, you will need: * Ruby development headers * a C compiler -To Build the JRuby extension, you will need: - -* Maven -* The latest version of the protobuf java library (see ../java/README.md) -* Install JRuby via rbenv or RVM - First switch to the desired platform with rbenv or RVM. Then install the required Ruby gems: diff --git a/ruby/Rakefile b/ruby/Rakefile index 89a00e1ddb58..104c72794fe9 100644 --- a/ruby/Rakefile +++ b/ruby/Rakefile @@ -1,7 +1,7 @@ require "rubygems" require "rubygems/package_task" -require "rake/extensiontask" unless RUBY_PLATFORM == "java" require "rake/testtask" +require "ffi-compiler/compile_task" spec = Gem::Specification.load("google-protobuf.gemspec") @@ -49,7 +49,7 @@ genproto_output = [] # We won't have access to .. from within docker, but the proto files # will be there, thanks to the :genproto rule dependency for gem:native. -unless ENV['IN_DOCKER'] == 'true' or ENV['BAZEL'] == 'true' +unless ENV['BAZEL'] == 'true' well_known_protos.each do |proto_file| input_file = "../src/" + proto_file output_file = "lib/" + proto_file.sub(/\.proto$/, "_pb.rb") @@ -68,22 +68,9 @@ unless ENV['IN_DOCKER'] == 'true' or ENV['BAZEL'] == 'true' end end -if RUBY_PLATFORM == "java" - task :clean => :require_mvn do - system("mvn --batch-mode clean") - end - - task :compile => :require_mvn do - system("mvn --batch-mode package") - end - - task :require_mvn do - raise ArgumentError, "maven needs to be installed" if `which mvn` == '' - end - -else - unless ENV['IN_DOCKER'] == 'true' - # We need utf8_range in-tree. +task :copy_third_party do + # We need utf8_range in-tree. + unless File.exist? 'ext/google/protobuf_c/third_party/utf8_range' FileUtils.mkdir_p("ext/google/protobuf_c/third_party/utf8_range") FileUtils.cp("../third_party/utf8_range/utf8_range.h", "ext/google/protobuf_c/third_party/utf8_range") FileUtils.cp("../third_party/utf8_range/naive.c", "ext/google/protobuf_c/third_party/utf8_range") @@ -91,63 +78,47 @@ else FileUtils.cp("../third_party/utf8_range/range2-sse.c", "ext/google/protobuf_c/third_party/utf8_range") FileUtils.cp("../third_party/utf8_range/LICENSE", "ext/google/protobuf_c/third_party/utf8_range") end +end - Rake::ExtensionTask.new("protobuf_c", spec) do |ext| - unless RUBY_PLATFORM =~ /darwin/ - # TODO: also set "no_native to true" for mac if possible. As is, - # "no_native" can only be set if the RUBY_PLATFORM doing - # cross-compilation is contained in the "ext.cross_platform" array. - ext.no_native = true - end - ext.ext_dir = "ext/google/protobuf_c" - ext.lib_dir = "lib/google" - ext.cross_compile = true - ext.cross_platform = [ - 'x86-mingw32', 'x64-mingw32', 'x64-mingw-ucrt', - 'x86_64-linux', 'x86-linux', - 'x86_64-darwin', 'arm64-darwin', - ] +# FFI::CompilerTask's constructor walks the filesystem at initialization time +# in order to create subtasks for each source file, so the files from third_party +# must be copied into place before the task is defined. +ffi_compiler_config_block = Proc.new do |c| + c.cflags << "-std=gnu99 -O3 -DNDEBUG" + if RbConfig::CONFIG['target_os'] =~ /darwin|linux/ + # c.cflags << "-fvisibility=hidden -Wall -Wsign-compare -Wno-declaration-after-statement" + c.cflags << "-Wall -Wsign-compare -Wno-declaration-after-statement" end - - task 'gem:java' do - sh "rm Gemfile.lock" - require 'rake_compiler_dock' - # Specify the repo root as the working and mount directory to provide access - # to the java directory - repo_root = File.realdirpath File.join(Dir.pwd, '..') - RakeCompilerDock.sh <<-"EOT", platform: 'jruby', rubyvm: :jruby, mountdir: repo_root, workdir: repo_root - sudo apt-get install maven -y && \ - cd java && mvn install -Dmaven.test.skip=true && cd ../ruby && \ - bundle && \ - IN_DOCKER=true rake compile gem - EOT + if RbConfig::CONFIG['target_os'] =~ /linux/ + # Instruct the linker to point memcpy calls at our __wrap_memcpy wrapper. + c.ldflags << "-Wl,-wrap,memcpy" end +end - task 'gem:windows' do - sh "rm Gemfile.lock" - require 'rake_compiler_dock' - ['x86-mingw32', 'x64-mingw32', 'x64-mingw-ucrt', 'x86_64-linux', 'x86-linux'].each do |plat| - RakeCompilerDock.sh <<-"EOT", platform: plat - bundle && \ - IN_DOCKER=true rake native:#{plat} pkg/#{spec.full_name}-#{plat}.gem RUBY_CC_VERSION=3.1.0:3.0.0:2.7.0:2.6.0:2.5.0 - EOT - end +if File.exist? 'ext/google/protobuf_c/third_party/utf8_range' + desc "compiler tasks" + namespace "ffi-compiler" do + FFI::Compiler::CompileTask.new('ext/google/protobuf_c', &ffi_compiler_config_block) end - - if RUBY_PLATFORM =~ /darwin/ - task 'gem:native' do - system "rake genproto" - system "rake cross native gem RUBY_CC_VERSION=3.1.0:3.0.0:2.7.0:2.6.0:2.5.1" +else + task "ffi-compiler:default" do + Rake::Task[:copy_third_party].invoke + desc "compiler tasks" + namespace "ffi-compiler" do + FFI::Compiler::CompileTask.new('ext/google/protobuf_c', &ffi_compiler_config_block) + Rake::Task[Rake::Task[:default].prereqs.first].invoke end - else - task 'gem:native' => [:genproto, 'gem:windows', 'gem:java'] end end +task :compile => ["ffi-compiler:default"] + task :genproto => genproto_output task :clean do sh "rm -f #{genproto_output.join(' ')}" + sh "rm -rf `find ext/google/protobuf_c/* -maxdepth 0 -type d`" + sh "rm -rf pkg" end Gem::PackageTask.new(spec) do |pkg| @@ -165,7 +136,8 @@ Rake::TestTask.new(:gc_test => ENV['BAZEL'] == 'true' ? [] : :build) do |t| t.test_files = FileList["tests/gc_test.rb"] end -task :build => [:clean, :genproto, :compile] +Rake::Task[:gem].prereqs.unshift(:clean, :genproto, :copy_third_party) +task :build => :compile task :default => [:build] # vim:sw=2:et diff --git a/ruby/benchmark.rb b/ruby/benchmark.rb new file mode 100755 index 000000000000..ed42ee0eb9fd --- /dev/null +++ b/ruby/benchmark.rb @@ -0,0 +1,84 @@ +#!/usr/bin/ruby + +require 'google/protobuf' +require 'benchmark' + +puts "Protobuf Benchmarks for Ruby Platform #{RUBY_PLATFORM}" + +pool = Google::Protobuf::DescriptorPool.new +pool.build do + add_message "BenchmarkMessage" do + repeated :a, :message, 1, "BenchmarkSubMessage" + optional :b, :int32, 2 + optional :c, :uint32, 3 + optional :d, :int64, 4 + optional :e, :uint64, 5 + optional :f, :float, 6 + optional :g, :double, 7 + optional :h, :enum, 8, "BenchmarkEnum" + optional :i, :string, 9 + optional :j, :bytes, 10 + end + add_enum "BenchmarkEnum" do + value :Default, 0 + value :A, 1 + value :B, 2 + value :C, 3 + end + add_message "BenchmarkSubMessage" do + optional :foo, :string, 1 + end +end + +BenchmarkMessage = pool.lookup("BenchmarkMessage").msgclass +BenchmarkSubMessage = pool.lookup("BenchmarkSubMessage").msgclass + +def get_msg + BenchmarkMessage.new( + a: [BenchmarkSubMessage.new(:foo => "hello"), + BenchmarkSubMessage.new(:foo => "world")], + b: -1000, + c: 1000, + d: -10_000_000_000, + e: 10_000_000_000, + f: 1.123, + g: 1.123456, + h: :C, + i: "❤️", + j: "\0\0\0" + ) +end + +foo = get_msg +string = "hi" +byte_string = "\0\0\0\0\0\0" +n = 1000 +Benchmark.bmbm do |x| + x.report("inspect") { n.times { foo.inspect } } + x.report("to_h") { n.times { foo.to_h } } + x.report("encode_json") { n.times { foo.to_json } } + x.report("new") { n.times { get_msg } } + x.report("dup") { n.times { foo.dup } } + x.report("repeated field accessor") { n.times { foo.a } } + x.report("repeated field index accessor") { n.times { foo.a[1] } } + x.report("repeated field index + string accessor") { n.times { foo.a[1].foo } } + x.report("int32 accessor") { n.times { foo.b } } + x.report("uint32 accessor") { n.times { foo.c } } + x.report("int64 accessor") { n.times { foo.d } } + x.report("uint64 accessor") { n.times { foo.e } } + x.report("float accessor") { n.times { foo.f } } + x.report("double accessor") { n.times { foo.g } } + x.report("enum accessor") { n.times { foo.h } } + x.report("string accessor") { n.times { foo.i } } + x.report("bytes accessor") { n.times { foo.j } } + + x.report("int32 writer") { n.times { foo.b = -42 } } + x.report("uint32 writer") { n.times { foo.c = 42 } } + x.report("int64 writer") { n.times { foo.d = -42_000_000_000 } } + x.report("uint64 writer") { n.times { foo.e = 42_000_000_000 } } + x.report("float writer") { n.times { foo.f = 9.8765 } } + x.report("double writer") { n.times { foo.g = -9.8765432109 } } + x.report("enum writer") { n.times { foo.h = :B } } + x.report("string writer") { n.times { foo.i = string } } + x.report("bytes writer") { n.times { foo.j = byte_string } } +end diff --git a/ruby/compatibility_tests/v3.0.0/BUILD.bazel b/ruby/compatibility_tests/v3.0.0/BUILD.bazel index b5fcede943ee..bee94371ffbb 100644 --- a/ruby/compatibility_tests/v3.0.0/BUILD.bazel +++ b/ruby/compatibility_tests/v3.0.0/BUILD.bazel @@ -31,7 +31,7 @@ inline_sh_test( ], cmd = """ pushd `dirname $(location Rakefile)` - RUBYLIB=.:tests:../../lib:../../../src BAZEL=true rake test + RUBYLIB=.:tests:../../lib:../../../src rake test popd """, ) diff --git a/ruby/compatibility_tests/v3.0.0/test.sh b/ruby/compatibility_tests/v3.0.0/test.sh index 1a9b79842239..d5a8fd74d7a5 100755 --- a/ruby/compatibility_tests/v3.0.0/test.sh +++ b/ruby/compatibility_tests/v3.0.0/test.sh @@ -14,4 +14,4 @@ wget https://repo1.maven.org/maven2/com/google/protobuf/protoc/3.0.0/${PROTOC_BI chmod +x protoc # Run tests -RUBYLIB=../../lib:. rake test +rake test diff --git a/ruby/ext/google/protobuf_c/convert.c b/ruby/ext/google/protobuf_c/convert.c index bdc71599fbe9..b38dc0fc6d70 100644 --- a/ruby/ext/google/protobuf_c/convert.c +++ b/ruby/ext/google/protobuf_c/convert.c @@ -40,279 +40,10 @@ #include "convert.h" #include "message.h" -#include "protobuf.h" - -static upb_StringView Convert_StringData(VALUE str, upb_Arena* arena) { - upb_StringView ret; - if (arena) { - char* ptr = upb_Arena_Malloc(arena, RSTRING_LEN(str)); - memcpy(ptr, RSTRING_PTR(str), RSTRING_LEN(str)); - ret.data = ptr; - } else { - // Data is only needed temporarily (within map lookup). - ret.data = RSTRING_PTR(str); - } - ret.size = RSTRING_LEN(str); - return ret; -} - -static bool is_ruby_num(VALUE value) { - return (TYPE(value) == T_FLOAT || TYPE(value) == T_FIXNUM || - TYPE(value) == T_BIGNUM); -} - -static void Convert_CheckInt(const char* name, upb_CType type, VALUE val) { - if (!is_ruby_num(val)) { - rb_raise(cTypeError, - "Expected number type for integral field '%s' (given %s).", name, - rb_class2name(CLASS_OF(val))); - } - - // NUM2{INT,UINT,LL,ULL} macros do the appropriate range checks on upper - // bound; we just need to do precision checks (i.e., disallow rounding) and - // check for < 0 on unsigned types. - if (TYPE(val) == T_FLOAT) { - double dbl_val = NUM2DBL(val); - if (floor(dbl_val) != dbl_val) { - rb_raise(rb_eRangeError, - "Non-integral floating point value assigned to integer field " - "'%s' (given %s).", - name, rb_class2name(CLASS_OF(val))); - } - } - if (type == kUpb_CType_UInt32 || type == kUpb_CType_UInt64) { - if (NUM2DBL(val) < 0) { - rb_raise( - rb_eRangeError, - "Assigning negative value to unsigned integer field '%s' (given %s).", - name, rb_class2name(CLASS_OF(val))); - } - } -} - -static int32_t Convert_ToEnum(VALUE value, const char* name, - const upb_EnumDef* e) { - int32_t val; - - switch (TYPE(value)) { - case T_FLOAT: - case T_FIXNUM: - case T_BIGNUM: - Convert_CheckInt(name, kUpb_CType_Int32, value); - val = NUM2INT(value); - break; - case T_STRING: { - const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNameWithSize( - e, RSTRING_PTR(value), RSTRING_LEN(value)); - if (!ev) goto unknownval; - val = upb_EnumValueDef_Number(ev); - break; - } - case T_SYMBOL: { - const upb_EnumValueDef* ev = - upb_EnumDef_FindValueByName(e, rb_id2name(SYM2ID(value))); - if (!ev) - goto unknownval; - val = upb_EnumValueDef_Number(ev); - break; - } - default: - rb_raise(cTypeError, - "Expected number or symbol type for enum field '%s'.", name); - } - - return val; - -unknownval: - rb_raise(rb_eRangeError, "Unknown symbol value for enum field '%s'.", name); -} - -upb_MessageValue Convert_RubyToUpb(VALUE value, const char* name, - TypeInfo type_info, upb_Arena* arena) { - upb_MessageValue ret; - - switch (type_info.type) { - case kUpb_CType_Float: - if (!is_ruby_num(value)) { - rb_raise(cTypeError, - "Expected number type for float field '%s' (given %s).", name, - rb_class2name(CLASS_OF(value))); - } - ret.float_val = NUM2DBL(value); - break; - case kUpb_CType_Double: - if (!is_ruby_num(value)) { - rb_raise(cTypeError, - "Expected number type for double field '%s' (given %s).", name, - rb_class2name(CLASS_OF(value))); - } - ret.double_val = NUM2DBL(value); - break; - case kUpb_CType_Bool: { - if (value == Qtrue) { - ret.bool_val = 1; - } else if (value == Qfalse) { - ret.bool_val = 0; - } else { - rb_raise(cTypeError, - "Invalid argument for boolean field '%s' (given %s).", name, - rb_class2name(CLASS_OF(value))); - } - break; - } - case kUpb_CType_String: { - VALUE utf8 = rb_enc_from_encoding(rb_utf8_encoding()); - if (rb_obj_class(value) == rb_cSymbol) { - value = rb_funcall(value, rb_intern("to_s"), 0); - } else if (rb_obj_class(value) != rb_cString) { - rb_raise(cTypeError, - "Invalid argument for string field '%s' (given %s).", name, - rb_class2name(CLASS_OF(value))); - } - - if (rb_obj_encoding(value) != utf8) { - // Note: this will not duplicate underlying string data unless - // necessary. - value = rb_str_encode(value, utf8, 0, Qnil); - - if (rb_enc_str_coderange(value) == ENC_CODERANGE_BROKEN) { - rb_raise(rb_eEncodingError, "String is invalid UTF-8"); - } - } - - ret.str_val = Convert_StringData(value, arena); - break; - } - case kUpb_CType_Bytes: { - VALUE bytes = rb_enc_from_encoding(rb_ascii8bit_encoding()); - if (rb_obj_class(value) != rb_cString) { - rb_raise(cTypeError, - "Invalid argument for bytes field '%s' (given %s).", name, - rb_class2name(CLASS_OF(value))); - } - - if (rb_obj_encoding(value) != bytes) { - // Note: this will not duplicate underlying string data unless - // necessary. - // TODO(haberman): is this really necessary to get raw bytes? - value = rb_str_encode(value, bytes, 0, Qnil); - } - - ret.str_val = Convert_StringData(value, arena); - break; - } - case kUpb_CType_Message: - ret.msg_val = - Message_GetUpbMessage(value, type_info.def.msgdef, name, arena); - break; - case kUpb_CType_Enum: - ret.int32_val = Convert_ToEnum(value, name, type_info.def.enumdef); - break; - case kUpb_CType_Int32: - case kUpb_CType_Int64: - case kUpb_CType_UInt32: - case kUpb_CType_UInt64: - Convert_CheckInt(name, type_info.type, value); - switch (type_info.type) { - case kUpb_CType_Int32: - ret.int32_val = NUM2INT(value); - break; - case kUpb_CType_Int64: - ret.int64_val = NUM2LL(value); - break; - case kUpb_CType_UInt32: - ret.uint32_val = NUM2UINT(value); - break; - case kUpb_CType_UInt64: - ret.uint64_val = NUM2ULL(value); - break; - default: - break; - } - break; - default: - break; - } - - return ret; -} - -VALUE Convert_UpbToRuby(upb_MessageValue upb_val, TypeInfo type_info, - VALUE arena) { - switch (type_info.type) { - case kUpb_CType_Float: - return DBL2NUM(upb_val.float_val); - case kUpb_CType_Double: - return DBL2NUM(upb_val.double_val); - case kUpb_CType_Bool: - return upb_val.bool_val ? Qtrue : Qfalse; - case kUpb_CType_Int32: - return INT2NUM(upb_val.int32_val); - case kUpb_CType_Int64: - return LL2NUM(upb_val.int64_val); - case kUpb_CType_UInt32: - return UINT2NUM(upb_val.uint32_val); - case kUpb_CType_UInt64: - return ULL2NUM(upb_val.int64_val); - case kUpb_CType_Enum: { - const upb_EnumValueDef *ev = upb_EnumDef_FindValueByNumber( - type_info.def.enumdef, upb_val.int32_val); - if (ev) { - return ID2SYM(rb_intern(upb_EnumValueDef_Name(ev))); - } else { - return INT2NUM(upb_val.int32_val); - } - } - case kUpb_CType_String: { - VALUE str_rb = rb_str_new(upb_val.str_val.data, upb_val.str_val.size); - rb_enc_associate(str_rb, rb_utf8_encoding()); - rb_obj_freeze(str_rb); - return str_rb; - } - case kUpb_CType_Bytes: { - VALUE str_rb = rb_str_new(upb_val.str_val.data, upb_val.str_val.size); - rb_enc_associate(str_rb, rb_ascii8bit_encoding()); - rb_obj_freeze(str_rb); - return str_rb; - } - case kUpb_CType_Message: - return Message_GetRubyWrapper((upb_Message*)upb_val.msg_val, - type_info.def.msgdef, arena); - default: - rb_raise(rb_eRuntimeError, "Convert_UpbToRuby(): Unexpected type %d", - (int)type_info.type); - } -} - -upb_MessageValue Msgval_DeepCopy(upb_MessageValue msgval, TypeInfo type_info, - upb_Arena* arena) { - upb_MessageValue new_msgval; - - switch (type_info.type) { - default: - memcpy(&new_msgval, &msgval, sizeof(msgval)); - break; - case kUpb_CType_String: - case kUpb_CType_Bytes: { - size_t n = msgval.str_val.size; - char* mem = upb_Arena_Malloc(arena, n); - new_msgval.str_val.data = mem; - new_msgval.str_val.size = n; - memcpy(mem, msgval.str_val.data, n); - break; - } - case kUpb_CType_Message: - new_msgval.msg_val = - Message_deep_copy(msgval.msg_val, type_info.def.msgdef, arena); - break; - } - - return new_msgval; -} bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2, - TypeInfo type_info) { - switch (type_info.type) { + upb_CType type, upb_MessageDef* msgdef) { + switch (type) { case kUpb_CType_Bool: return memcmp(&val1, &val2, 1) == 0; case kUpb_CType_Float: @@ -330,15 +61,15 @@ bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2, memcmp(val1.str_val.data, val2.str_val.data, val1.str_val.size) == 0; case kUpb_CType_Message: - return Message_Equal(val1.msg_val, val2.msg_val, type_info.def.msgdef); + return Message_Equal(val1.msg_val, val2.msg_val, msgdef); default: - rb_raise(rb_eRuntimeError, "Internal error, unexpected type"); + assert(0); } } -uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info, +uint64_t Msgval_GetHash(upb_MessageValue val, upb_CType type, upb_MessageDef* msgdef, uint64_t seed) { - switch (type_info.type) { + switch (type) { case kUpb_CType_Bool: return _upb_Hash(&val, 1, seed); case kUpb_CType_Float: @@ -354,8 +85,8 @@ uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info, case kUpb_CType_Bytes: return _upb_Hash(val.str_val.data, val.str_val.size, seed); case kUpb_CType_Message: - return Message_Hash(val.msg_val, type_info.def.msgdef, seed); + return Message_Hash(val.msg_val, msgdef, seed); default: - rb_raise(rb_eRuntimeError, "Internal error, unexpected type"); + assert(0); } } diff --git a/ruby/ext/google/protobuf_c/convert.h b/ruby/ext/google/protobuf_c/convert.h index e48c979455e7..cc3974e35e2a 100644 --- a/ruby/ext/google/protobuf_c/convert.h +++ b/ruby/ext/google/protobuf_c/convert.h @@ -31,45 +31,15 @@ #ifndef RUBY_PROTOBUF_CONVERT_H_ #define RUBY_PROTOBUF_CONVERT_H_ -#include - #include "protobuf.h" #include "ruby-upb.h" -// Converts |ruby_val| to a upb_MessageValue according to |type_info|. -// -// The |arena| parameter indicates the lifetime of the container where this -// value will be assigned. It is used as follows: -// - If type is string or bytes, the string data will be copied into |arena|. -// - If type is message, and we need to auto-construct a message due to implicit -// conversions (eg. Time -> Google::Protobuf::Timestamp), the new message -// will be created in |arena|. -// - If type is message and the Ruby value is a message instance, we will fuse -// the message's arena into |arena|, to ensure that this message outlives the -// container. -upb_MessageValue Convert_RubyToUpb(VALUE ruby_val, const char *name, - TypeInfo type_info, upb_Arena *arena); - -// Converts |upb_val| to a Ruby VALUE according to |type_info|. This may involve -// creating a Ruby wrapper object. -// -// The |arena| parameter indicates the arena that owns the lifetime of -// |upb_val|. Any Ruby wrapper object that is created will reference |arena| -// and ensure it outlives the wrapper. -VALUE Convert_UpbToRuby(upb_MessageValue upb_val, TypeInfo type_info, - VALUE arena); - -// Creates a deep copy of |msgval| in |arena|. -upb_MessageValue Msgval_DeepCopy(upb_MessageValue msgval, TypeInfo type_info, - upb_Arena *arena); - -// Returns true if |val1| and |val2| are equal. Their type is given by -// |type_info|. +// Returns true if |val1| and |val2| are equal. bool Msgval_IsEqual(upb_MessageValue val1, upb_MessageValue val2, - TypeInfo type_info); + upb_CType type, upb_MessageDef* msgdef); // Returns a hash value for the given upb_MessageValue. -uint64_t Msgval_GetHash(upb_MessageValue val, TypeInfo type_info, - uint64_t seed); +uint64_t Msgval_GetHash(upb_MessageValue val, + upb_CType type, upb_MessageDef* msgdef, uint64_t seed); #endif // RUBY_PROTOBUF_CONVERT_H_ diff --git a/ruby/ext/google/protobuf_c/defs.c b/ruby/ext/google/protobuf_c/defs.c index 3bd18e840028..036945f4bda4 100644 --- a/ruby/ext/google/protobuf_c/defs.c +++ b/ruby/ext/google/protobuf_c/defs.c @@ -28,1253 +28,15 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -#include -#include -#include - -#include "convert.h" -#include "message.h" #include "protobuf.h" -// ----------------------------------------------------------------------------- -// Global map from upb {msg,enum}defs to wrapper Descriptor/EnumDescriptor -// instances. -// ----------------------------------------------------------------------------- - -static VALUE get_msgdef_obj(VALUE descriptor_pool, const upb_MessageDef* def); -static VALUE get_enumdef_obj(VALUE descriptor_pool, const upb_EnumDef* def); -static VALUE get_fielddef_obj(VALUE descriptor_pool, const upb_FieldDef* def); -static VALUE get_filedef_obj(VALUE descriptor_pool, const upb_FileDef* def); -static VALUE get_oneofdef_obj(VALUE descriptor_pool, const upb_OneofDef* def); - -// A distinct object that is not accessible from Ruby. We use this as a -// constructor argument to enforce that certain objects cannot be created from -// Ruby. -VALUE c_only_cookie = Qnil; - -// ----------------------------------------------------------------------------- -// Common utilities. -// ----------------------------------------------------------------------------- - -static const char* get_str(VALUE str) { - Check_Type(str, T_STRING); - return RSTRING_PTR(str); -} - -static VALUE rb_str_maybe_null(const char* s) { - if (s == NULL) { - s = ""; - } - return rb_str_new2(s); -} - -// ----------------------------------------------------------------------------- -// DescriptorPool. -// ----------------------------------------------------------------------------- - -typedef struct { - VALUE def_to_descriptor; // Hash table of def* -> Ruby descriptor. - upb_DefPool* symtab; -} DescriptorPool; - -VALUE cDescriptorPool = Qnil; - -// Global singleton DescriptorPool. The user is free to create others, but this -// is used by generated code. -VALUE generated_pool = Qnil; - -static void DescriptorPool_mark(void* _self) { - DescriptorPool* self = _self; - rb_gc_mark(self->def_to_descriptor); -} - -static void DescriptorPool_free(void* _self) { - DescriptorPool* self = _self; - upb_DefPool_Free(self->symtab); - xfree(self); -} - -static const rb_data_type_t DescriptorPool_type = { - "Google::Protobuf::DescriptorPool", - {DescriptorPool_mark, DescriptorPool_free, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static DescriptorPool* ruby_to_DescriptorPool(VALUE val) { - DescriptorPool* ret; - TypedData_Get_Struct(val, DescriptorPool, &DescriptorPool_type, ret); - return ret; -} - -// Exposed to other modules in defs.h. -const upb_DefPool* DescriptorPool_GetSymtab(VALUE desc_pool_rb) { - DescriptorPool* pool = ruby_to_DescriptorPool(desc_pool_rb); - return pool->symtab; -} - -/* - * call-seq: - * DescriptorPool.new => pool - * - * Creates a new, empty, descriptor pool. - */ -static VALUE DescriptorPool_alloc(VALUE klass) { - DescriptorPool* self = ALLOC(DescriptorPool); - VALUE ret; - - self->def_to_descriptor = Qnil; - ret = TypedData_Wrap_Struct(klass, &DescriptorPool_type, self); - - self->def_to_descriptor = rb_hash_new(); - self->symtab = upb_DefPool_New(); - ObjectCache_Add(self->symtab, ret); - - return ret; -} - -/* - * call-seq: - * DescriptorPool.add_serialized_file(serialized_file_proto) - * - * Adds the given serialized FileDescriptorProto to the pool. - */ -VALUE DescriptorPool_add_serialized_file(VALUE _self, - VALUE serialized_file_proto) { - DescriptorPool* self = ruby_to_DescriptorPool(_self); - Check_Type(serialized_file_proto, T_STRING); - VALUE arena_rb = Arena_new(); - upb_Arena* arena = Arena_get(arena_rb); - google_protobuf_FileDescriptorProto* file_proto = - google_protobuf_FileDescriptorProto_parse( - RSTRING_PTR(serialized_file_proto), - RSTRING_LEN(serialized_file_proto), arena); - if (!file_proto) { - rb_raise(rb_eArgError, "Unable to parse FileDescriptorProto"); - } - upb_Status status; - upb_Status_Clear(&status); - const upb_FileDef* filedef = - upb_DefPool_AddFile(self->symtab, file_proto, &status); - if (!filedef) { - rb_raise(cTypeError, "Unable to build file to DescriptorPool: %s", - upb_Status_ErrorMessage(&status)); - } - RB_GC_GUARD(arena_rb); - return get_filedef_obj(_self, filedef); -} - -/* - * call-seq: - * DescriptorPool.lookup(name) => descriptor - * - * Finds a Descriptor or EnumDescriptor by name and returns it, or nil if none - * exists with the given name. - */ -static VALUE DescriptorPool_lookup(VALUE _self, VALUE name) { - DescriptorPool* self = ruby_to_DescriptorPool(_self); - const char* name_str = get_str(name); - const upb_MessageDef* msgdef; - const upb_EnumDef* enumdef; - - msgdef = upb_DefPool_FindMessageByName(self->symtab, name_str); - if (msgdef) { - return get_msgdef_obj(_self, msgdef); - } - - enumdef = upb_DefPool_FindEnumByName(self->symtab, name_str); - if (enumdef) { - return get_enumdef_obj(_self, enumdef); - } - - return Qnil; -} - -/* - * call-seq: - * DescriptorPool.generated_pool => descriptor_pool - * - * Class method that returns the global DescriptorPool. This is a singleton into - * which generated-code message and enum types are registered. The user may also - * register types in this pool for convenience so that they do not have to hold - * a reference to a private pool instance. - */ -static VALUE DescriptorPool_generated_pool(VALUE _self) { - return generated_pool; -} - -static void DescriptorPool_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "DescriptorPool", rb_cObject); - rb_define_alloc_func(klass, DescriptorPool_alloc); - rb_define_method(klass, "add_serialized_file", - DescriptorPool_add_serialized_file, 1); - rb_define_method(klass, "lookup", DescriptorPool_lookup, 1); - rb_define_singleton_method(klass, "generated_pool", - DescriptorPool_generated_pool, 0); - rb_gc_register_address(&cDescriptorPool); - cDescriptorPool = klass; - - rb_gc_register_address(&generated_pool); - generated_pool = rb_class_new_instance(0, NULL, klass); -} - -// ----------------------------------------------------------------------------- -// Descriptor. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_MessageDef* msgdef; - VALUE klass; - VALUE descriptor_pool; -} Descriptor; - -VALUE cDescriptor = Qnil; - -static void Descriptor_mark(void* _self) { - Descriptor* self = _self; - rb_gc_mark(self->klass); - rb_gc_mark(self->descriptor_pool); -} - -static const rb_data_type_t Descriptor_type = { - "Google::Protobuf::Descriptor", - {Descriptor_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static Descriptor* ruby_to_Descriptor(VALUE val) { - Descriptor* ret; - TypedData_Get_Struct(val, Descriptor, &Descriptor_type, ret); - return ret; -} - -/* - * call-seq: - * Descriptor.new => descriptor - * - * Creates a new, empty, message type descriptor. At a minimum, its name must be - * set before it is added to a pool. It cannot be used to create messages until - * it is added to a pool, after which it becomes immutable (as part of a - * finalization process). - */ -static VALUE Descriptor_alloc(VALUE klass) { - Descriptor* self = ALLOC(Descriptor); - VALUE ret = TypedData_Wrap_Struct(klass, &Descriptor_type, self); - self->msgdef = NULL; - self->klass = Qnil; - self->descriptor_pool = Qnil; - return ret; -} - -/* - * call-seq: - * Descriptor.new(c_only_cookie, ptr) => Descriptor - * - * Creates a descriptor wrapper object. May only be called from C. - */ -static VALUE Descriptor_initialize(VALUE _self, VALUE cookie, - VALUE descriptor_pool, VALUE ptr) { - Descriptor* self = ruby_to_Descriptor(_self); - - if (cookie != c_only_cookie) { - rb_raise(rb_eRuntimeError, - "Descriptor objects may not be created from Ruby."); - } - - self->descriptor_pool = descriptor_pool; - self->msgdef = (const upb_MessageDef*)NUM2ULL(ptr); - - return Qnil; -} - -/* - * call-seq: - * Descriptor.file_descriptor - * - * Returns the FileDescriptor object this message belongs to. - */ -static VALUE Descriptor_file_descriptor(VALUE _self) { - Descriptor* self = ruby_to_Descriptor(_self); - return get_filedef_obj(self->descriptor_pool, - upb_MessageDef_File(self->msgdef)); -} - -/* - * call-seq: - * Descriptor.name => name - * - * Returns the name of this message type as a fully-qualified string (e.g., - * My.Package.MessageType). - */ -static VALUE Descriptor_name(VALUE _self) { - Descriptor* self = ruby_to_Descriptor(_self); - return rb_str_maybe_null(upb_MessageDef_FullName(self->msgdef)); -} - -/* - * call-seq: - * Descriptor.each(&block) - * - * Iterates over fields in this message type, yielding to the block on each one. - */ -static VALUE Descriptor_each(VALUE _self) { - Descriptor* self = ruby_to_Descriptor(_self); - - int n = upb_MessageDef_FieldCount(self->msgdef); - for (int i = 0; i < n; i++) { - const upb_FieldDef* field = upb_MessageDef_Field(self->msgdef, i); - VALUE obj = get_fielddef_obj(self->descriptor_pool, field); - rb_yield(obj); - } - return Qnil; -} - -/* - * call-seq: - * Descriptor.lookup(name) => FieldDescriptor - * - * Returns the field descriptor for the field with the given name, if present, - * or nil if none. - */ -static VALUE Descriptor_lookup(VALUE _self, VALUE name) { - Descriptor* self = ruby_to_Descriptor(_self); - const char* s = get_str(name); - const upb_FieldDef* field = upb_MessageDef_FindFieldByName(self->msgdef, s); - if (field == NULL) { - return Qnil; - } - return get_fielddef_obj(self->descriptor_pool, field); -} - -/* - * call-seq: - * Descriptor.each_oneof(&block) => nil - * - * Invokes the given block for each oneof in this message type, passing the - * corresponding OneofDescriptor. - */ -static VALUE Descriptor_each_oneof(VALUE _self) { - Descriptor* self = ruby_to_Descriptor(_self); - - int n = upb_MessageDef_OneofCount(self->msgdef); - for (int i = 0; i < n; i++) { - const upb_OneofDef* oneof = upb_MessageDef_Oneof(self->msgdef, i); - VALUE obj = get_oneofdef_obj(self->descriptor_pool, oneof); - rb_yield(obj); - } - return Qnil; -} - -/* - * call-seq: - * Descriptor.lookup_oneof(name) => OneofDescriptor - * - * Returns the oneof descriptor for the oneof with the given name, if present, - * or nil if none. - */ -static VALUE Descriptor_lookup_oneof(VALUE _self, VALUE name) { - Descriptor* self = ruby_to_Descriptor(_self); - const char* s = get_str(name); - const upb_OneofDef* oneof = upb_MessageDef_FindOneofByName(self->msgdef, s); - if (oneof == NULL) { - return Qnil; - } - return get_oneofdef_obj(self->descriptor_pool, oneof); -} - -/* - * call-seq: - * Descriptor.msgclass => message_klass - * - * Returns the Ruby class created for this message type. - */ -static VALUE Descriptor_msgclass(VALUE _self) { - Descriptor* self = ruby_to_Descriptor(_self); - if (self->klass == Qnil) { - self->klass = build_class_from_descriptor(_self); - } - return self->klass; -} - -static void Descriptor_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "Descriptor", rb_cObject); - rb_define_alloc_func(klass, Descriptor_alloc); - rb_define_method(klass, "initialize", Descriptor_initialize, 3); - rb_define_method(klass, "each", Descriptor_each, 0); - rb_define_method(klass, "lookup", Descriptor_lookup, 1); - rb_define_method(klass, "each_oneof", Descriptor_each_oneof, 0); - rb_define_method(klass, "lookup_oneof", Descriptor_lookup_oneof, 1); - rb_define_method(klass, "msgclass", Descriptor_msgclass, 0); - rb_define_method(klass, "name", Descriptor_name, 0); - rb_define_method(klass, "file_descriptor", Descriptor_file_descriptor, 0); - rb_include_module(klass, rb_mEnumerable); - rb_gc_register_address(&cDescriptor); - cDescriptor = klass; -} - -// ----------------------------------------------------------------------------- -// FileDescriptor. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_FileDef* filedef; - VALUE descriptor_pool; // Owns the upb_FileDef. -} FileDescriptor; - -static VALUE cFileDescriptor = Qnil; - -static void FileDescriptor_mark(void* _self) { - FileDescriptor* self = _self; - rb_gc_mark(self->descriptor_pool); -} - -static const rb_data_type_t FileDescriptor_type = { - "Google::Protobuf::FileDescriptor", - {FileDescriptor_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static FileDescriptor* ruby_to_FileDescriptor(VALUE val) { - FileDescriptor* ret; - TypedData_Get_Struct(val, FileDescriptor, &FileDescriptor_type, ret); - return ret; -} - -static VALUE FileDescriptor_alloc(VALUE klass) { - FileDescriptor* self = ALLOC(FileDescriptor); - VALUE ret = TypedData_Wrap_Struct(klass, &FileDescriptor_type, self); - self->descriptor_pool = Qnil; - self->filedef = NULL; - return ret; -} - -/* - * call-seq: - * FileDescriptor.new => file - * - * Returns a new file descriptor. The syntax must be set before it's passed - * to a builder. - */ -static VALUE FileDescriptor_initialize(VALUE _self, VALUE cookie, - VALUE descriptor_pool, VALUE ptr) { - FileDescriptor* self = ruby_to_FileDescriptor(_self); - - if (cookie != c_only_cookie) { - rb_raise(rb_eRuntimeError, - "Descriptor objects may not be created from Ruby."); - } - - self->descriptor_pool = descriptor_pool; - self->filedef = (const upb_FileDef*)NUM2ULL(ptr); - - return Qnil; -} - -/* - * call-seq: - * FileDescriptor.name => name - * - * Returns the name of the file. - */ -static VALUE FileDescriptor_name(VALUE _self) { - FileDescriptor* self = ruby_to_FileDescriptor(_self); - const char* name = upb_FileDef_Name(self->filedef); - return name == NULL ? Qnil : rb_str_new2(name); -} - -/* - * call-seq: - * FileDescriptor.syntax => syntax - * - * Returns this file descriptors syntax. - * - * Valid syntax versions are: - * :proto2 or :proto3. - */ -static VALUE FileDescriptor_syntax(VALUE _self) { - FileDescriptor* self = ruby_to_FileDescriptor(_self); - - switch (upb_FileDef_Syntax(self->filedef)) { - case kUpb_Syntax_Proto3: - return ID2SYM(rb_intern("proto3")); - case kUpb_Syntax_Proto2: - return ID2SYM(rb_intern("proto2")); - default: - return Qnil; - } -} - -static void FileDescriptor_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "FileDescriptor", rb_cObject); - rb_define_alloc_func(klass, FileDescriptor_alloc); - rb_define_method(klass, "initialize", FileDescriptor_initialize, 3); - rb_define_method(klass, "name", FileDescriptor_name, 0); - rb_define_method(klass, "syntax", FileDescriptor_syntax, 0); - rb_gc_register_address(&cFileDescriptor); - cFileDescriptor = klass; -} - -// ----------------------------------------------------------------------------- -// FieldDescriptor. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_FieldDef* fielddef; - VALUE descriptor_pool; // Owns the upb_FieldDef. -} FieldDescriptor; - -static VALUE cFieldDescriptor = Qnil; - -static void FieldDescriptor_mark(void* _self) { - FieldDescriptor* self = _self; - rb_gc_mark(self->descriptor_pool); -} - -static const rb_data_type_t FieldDescriptor_type = { - "Google::Protobuf::FieldDescriptor", - {FieldDescriptor_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static FieldDescriptor* ruby_to_FieldDescriptor(VALUE val) { - FieldDescriptor* ret; - TypedData_Get_Struct(val, FieldDescriptor, &FieldDescriptor_type, ret); - return ret; -} - -/* - * call-seq: - * FieldDescriptor.new => field - * - * Returns a new field descriptor. Its name, type, etc. must be set before it is - * added to a message type. - */ -static VALUE FieldDescriptor_alloc(VALUE klass) { - FieldDescriptor* self = ALLOC(FieldDescriptor); - VALUE ret = TypedData_Wrap_Struct(klass, &FieldDescriptor_type, self); - self->fielddef = NULL; - return ret; -} - -/* - * call-seq: - * EnumDescriptor.new(c_only_cookie, pool, ptr) => EnumDescriptor - * - * Creates a descriptor wrapper object. May only be called from C. - */ -static VALUE FieldDescriptor_initialize(VALUE _self, VALUE cookie, - VALUE descriptor_pool, VALUE ptr) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - - if (cookie != c_only_cookie) { - rb_raise(rb_eRuntimeError, - "Descriptor objects may not be created from Ruby."); - } - - self->descriptor_pool = descriptor_pool; - self->fielddef = (const upb_FieldDef*)NUM2ULL(ptr); - - return Qnil; -} - -/* - * call-seq: - * FieldDescriptor.name => name - * - * Returns the name of this field. - */ -static VALUE FieldDescriptor_name(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - return rb_str_maybe_null(upb_FieldDef_Name(self->fielddef)); -} - -// Non-static, exposed to other .c files. -upb_CType ruby_to_fieldtype(VALUE type) { - if (TYPE(type) != T_SYMBOL) { - rb_raise(rb_eArgError, "Expected symbol for field type."); - } - -#define CONVERT(upb, ruby) \ - if (SYM2ID(type) == rb_intern(#ruby)) { \ - return kUpb_CType_##upb; \ - } - - CONVERT(Float, float); - CONVERT(Double, double); - CONVERT(Bool, bool); - CONVERT(String, string); - CONVERT(Bytes, bytes); - CONVERT(Message, message); - CONVERT(Enum, enum); - CONVERT(Int32, int32); - CONVERT(Int64, int64); - CONVERT(UInt32, uint32); - CONVERT(UInt64, uint64); - -#undef CONVERT - - rb_raise(rb_eArgError, "Unknown field type."); - return 0; -} - -static VALUE descriptortype_to_ruby(upb_FieldType type) { - switch (type) { -#define CONVERT(upb, ruby) \ - case kUpb_FieldType_##upb: \ - return ID2SYM(rb_intern(#ruby)); - CONVERT(Float, float); - CONVERT(Double, double); - CONVERT(Bool, bool); - CONVERT(String, string); - CONVERT(Bytes, bytes); - CONVERT(Message, message); - CONVERT(Group, group); - CONVERT(Enum, enum); - CONVERT(Int32, int32); - CONVERT(Int64, int64); - CONVERT(UInt32, uint32); - CONVERT(UInt64, uint64); - CONVERT(SInt32, sint32); - CONVERT(SInt64, sint64); - CONVERT(Fixed32, fixed32); - CONVERT(Fixed64, fixed64); - CONVERT(SFixed32, sfixed32); - CONVERT(SFixed64, sfixed64); -#undef CONVERT - } - return Qnil; -} - -/* - * call-seq: - * FieldDescriptor.type => type - * - * Returns this field's type, as a Ruby symbol, or nil if not yet set. - * - * Valid field types are: - * :int32, :int64, :uint32, :uint64, :float, :double, :bool, :string, - * :bytes, :message. - */ -static VALUE FieldDescriptor__type(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - return descriptortype_to_ruby(upb_FieldDef_Type(self->fielddef)); -} - -/* - * call-seq: - * FieldDescriptor.default => default - * - * Returns this field's default, as a Ruby object, or nil if not yet set. - */ -static VALUE FieldDescriptor_default(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - const upb_FieldDef* f = self->fielddef; - upb_MessageValue default_val = {0}; - if (upb_FieldDef_IsSubMessage(f)) { - return Qnil; - } else if (!upb_FieldDef_IsRepeated(f)) { - default_val = upb_FieldDef_Default(f); - } - return Convert_UpbToRuby(default_val, TypeInfo_get(self->fielddef), Qnil); +google_protobuf_FileDescriptorProto* FileDescriptorProto_parse (const char* serialized_file_proto, size_t length) { + upb_Arena* arena = Arena_create(); + return google_protobuf_FileDescriptorProto_parse( + serialized_file_proto, + length, arena); } -/* - * call-seq: - * FieldDescriptor.json_name => json_name - * - * Returns this field's json_name, as a Ruby string, or nil if not yet set. - */ -static VALUE FieldDescriptor_json_name(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - const upb_FieldDef* f = self->fielddef; - const char* json_name = upb_FieldDef_JsonName(f); - return rb_str_new2(json_name); -} - -/* - * call-seq: - * FieldDescriptor.label => label - * - * Returns this field's label (i.e., plurality), as a Ruby symbol. - * - * Valid field labels are: - * :optional, :repeated - */ -static VALUE FieldDescriptor_label(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - switch (upb_FieldDef_Label(self->fielddef)) { -#define CONVERT(upb, ruby) \ - case kUpb_Label_##upb: \ - return ID2SYM(rb_intern(#ruby)); - - CONVERT(Optional, optional); - CONVERT(Required, required); - CONVERT(Repeated, repeated); - -#undef CONVERT - } - - return Qnil; -} - -/* - * call-seq: - * FieldDescriptor.number => number - * - * Returns the tag number for this field. - */ -static VALUE FieldDescriptor_number(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - return INT2NUM(upb_FieldDef_Number(self->fielddef)); -} - -/* - * call-seq: - * FieldDescriptor.submsg_name => submsg_name - * - * Returns the name of the message or enum type corresponding to this field, if - * it is a message or enum field (respectively), or nil otherwise. This type - * name will be resolved within the context of the pool to which the containing - * message type is added. - */ -static VALUE FieldDescriptor_submsg_name(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - switch (upb_FieldDef_CType(self->fielddef)) { - case kUpb_CType_Enum: - return rb_str_new2( - upb_EnumDef_FullName(upb_FieldDef_EnumSubDef(self->fielddef))); - case kUpb_CType_Message: - return rb_str_new2( - upb_MessageDef_FullName(upb_FieldDef_MessageSubDef(self->fielddef))); - default: - return Qnil; - } -} - -/* - * call-seq: - * FieldDescriptor.subtype => message_or_enum_descriptor - * - * Returns the message or enum descriptor corresponding to this field's type if - * it is a message or enum field, respectively, or nil otherwise. Cannot be - * called *until* the containing message type is added to a pool (and thus - * resolved). - */ -static VALUE FieldDescriptor_subtype(VALUE _self) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - switch (upb_FieldDef_CType(self->fielddef)) { - case kUpb_CType_Enum: - return get_enumdef_obj(self->descriptor_pool, - upb_FieldDef_EnumSubDef(self->fielddef)); - case kUpb_CType_Message: - return get_msgdef_obj(self->descriptor_pool, - upb_FieldDef_MessageSubDef(self->fielddef)); - default: - return Qnil; - } -} - -/* - * call-seq: - * FieldDescriptor.get(message) => value - * - * Returns the value set for this field on the given message. Raises an - * exception if message is of the wrong type. - */ -static VALUE FieldDescriptor_get(VALUE _self, VALUE msg_rb) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - const upb_MessageDef* m; - - Message_Get(msg_rb, &m); - - if (m != upb_FieldDef_ContainingType(self->fielddef)) { - rb_raise(cTypeError, "get method called on wrong message type"); - } - - return Message_getfield(msg_rb, self->fielddef); -} - -/* - * call-seq: - * FieldDescriptor.has?(message) => boolean - * - * Returns whether the value is set on the given message. Raises an - * exception when calling for fields that do not have presence. - */ -static VALUE FieldDescriptor_has(VALUE _self, VALUE msg_rb) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - const upb_MessageDef* m; - const upb_MessageDef* msg = Message_Get(msg_rb, &m); - - if (m != upb_FieldDef_ContainingType(self->fielddef)) { - rb_raise(cTypeError, "has method called on wrong message type"); - } else if (!upb_FieldDef_HasPresence(self->fielddef)) { - rb_raise(rb_eArgError, "does not track presence"); - } - - return upb_Message_Has(msg, self->fielddef) ? Qtrue : Qfalse; -} - -/* - * call-seq: - * FieldDescriptor.clear(message) - * - * Clears the field from the message if it's set. - */ -static VALUE FieldDescriptor_clear(VALUE _self, VALUE msg_rb) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - const upb_MessageDef* m; - upb_MessageDef* msg = Message_GetMutable(msg_rb, &m); - - if (m != upb_FieldDef_ContainingType(self->fielddef)) { - rb_raise(cTypeError, "has method called on wrong message type"); - } - - upb_Message_ClearField(msg, self->fielddef); - return Qnil; -} - -/* - * call-seq: - * FieldDescriptor.set(message, value) - * - * Sets the value corresponding to this field to the given value on the given - * message. Raises an exception if message is of the wrong type. Performs the - * ordinary type-checks for field setting. - */ -static VALUE FieldDescriptor_set(VALUE _self, VALUE msg_rb, VALUE value) { - FieldDescriptor* self = ruby_to_FieldDescriptor(_self); - const upb_MessageDef* m; - upb_MessageDef* msg = Message_GetMutable(msg_rb, &m); - upb_Arena* arena = Arena_get(Message_GetArena(msg_rb)); - upb_MessageValue msgval; - - if (m != upb_FieldDef_ContainingType(self->fielddef)) { - rb_raise(cTypeError, "set method called on wrong message type"); - } - - msgval = Convert_RubyToUpb(value, upb_FieldDef_Name(self->fielddef), - TypeInfo_get(self->fielddef), arena); - upb_Message_Set(msg, self->fielddef, msgval, arena); - return Qnil; -} - -static void FieldDescriptor_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "FieldDescriptor", rb_cObject); - rb_define_alloc_func(klass, FieldDescriptor_alloc); - rb_define_method(klass, "initialize", FieldDescriptor_initialize, 3); - rb_define_method(klass, "name", FieldDescriptor_name, 0); - rb_define_method(klass, "type", FieldDescriptor__type, 0); - rb_define_method(klass, "default", FieldDescriptor_default, 0); - rb_define_method(klass, "json_name", FieldDescriptor_json_name, 0); - rb_define_method(klass, "label", FieldDescriptor_label, 0); - rb_define_method(klass, "number", FieldDescriptor_number, 0); - rb_define_method(klass, "submsg_name", FieldDescriptor_submsg_name, 0); - rb_define_method(klass, "subtype", FieldDescriptor_subtype, 0); - rb_define_method(klass, "has?", FieldDescriptor_has, 1); - rb_define_method(klass, "clear", FieldDescriptor_clear, 1); - rb_define_method(klass, "get", FieldDescriptor_get, 1); - rb_define_method(klass, "set", FieldDescriptor_set, 2); - rb_gc_register_address(&cFieldDescriptor); - cFieldDescriptor = klass; -} - -// ----------------------------------------------------------------------------- -// OneofDescriptor. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_OneofDef* oneofdef; - VALUE descriptor_pool; // Owns the upb_OneofDef. -} OneofDescriptor; - -static VALUE cOneofDescriptor = Qnil; - -static void OneofDescriptor_mark(void* _self) { - OneofDescriptor* self = _self; - rb_gc_mark(self->descriptor_pool); -} - -static const rb_data_type_t OneofDescriptor_type = { - "Google::Protobuf::OneofDescriptor", - {OneofDescriptor_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static OneofDescriptor* ruby_to_OneofDescriptor(VALUE val) { - OneofDescriptor* ret; - TypedData_Get_Struct(val, OneofDescriptor, &OneofDescriptor_type, ret); - return ret; -} - -/* - * call-seq: - * OneofDescriptor.new => oneof_descriptor - * - * Creates a new, empty, oneof descriptor. The oneof may only be modified prior - * to being added to a message descriptor which is subsequently added to a pool. - */ -static VALUE OneofDescriptor_alloc(VALUE klass) { - OneofDescriptor* self = ALLOC(OneofDescriptor); - VALUE ret = TypedData_Wrap_Struct(klass, &OneofDescriptor_type, self); - self->oneofdef = NULL; - self->descriptor_pool = Qnil; - return ret; -} - -/* - * call-seq: - * OneofDescriptor.new(c_only_cookie, pool, ptr) => OneofDescriptor - * - * Creates a descriptor wrapper object. May only be called from C. - */ -static VALUE OneofDescriptor_initialize(VALUE _self, VALUE cookie, - VALUE descriptor_pool, VALUE ptr) { - OneofDescriptor* self = ruby_to_OneofDescriptor(_self); - - if (cookie != c_only_cookie) { - rb_raise(rb_eRuntimeError, - "Descriptor objects may not be created from Ruby."); - } - - self->descriptor_pool = descriptor_pool; - self->oneofdef = (const upb_OneofDef*)NUM2ULL(ptr); - - return Qnil; -} - -/* - * call-seq: - * OneofDescriptor.name => name - * - * Returns the name of this oneof. - */ -static VALUE OneofDescriptor_name(VALUE _self) { - OneofDescriptor* self = ruby_to_OneofDescriptor(_self); - return rb_str_maybe_null(upb_OneofDef_Name(self->oneofdef)); -} - -/* - * call-seq: - * OneofDescriptor.each(&block) => nil - * - * Iterates through fields in this oneof, yielding to the block on each one. - */ -static VALUE OneofDescriptor_each(VALUE _self) { - OneofDescriptor* self = ruby_to_OneofDescriptor(_self); - - int n = upb_OneofDef_FieldCount(self->oneofdef); - for (int i = 0; i < n; i++) { - const upb_FieldDef* f = upb_OneofDef_Field(self->oneofdef, i); - VALUE obj = get_fielddef_obj(self->descriptor_pool, f); - rb_yield(obj); - } - return Qnil; -} - -static void OneofDescriptor_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "OneofDescriptor", rb_cObject); - rb_define_alloc_func(klass, OneofDescriptor_alloc); - rb_define_method(klass, "initialize", OneofDescriptor_initialize, 3); - rb_define_method(klass, "name", OneofDescriptor_name, 0); - rb_define_method(klass, "each", OneofDescriptor_each, 0); - rb_include_module(klass, rb_mEnumerable); - rb_gc_register_address(&cOneofDescriptor); - cOneofDescriptor = klass; -} - -// ----------------------------------------------------------------------------- -// EnumDescriptor. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_EnumDef* enumdef; - VALUE module; // begins as nil - VALUE descriptor_pool; // Owns the upb_EnumDef. -} EnumDescriptor; - -static VALUE cEnumDescriptor = Qnil; - -static void EnumDescriptor_mark(void* _self) { - EnumDescriptor* self = _self; - rb_gc_mark(self->module); - rb_gc_mark(self->descriptor_pool); -} - -static const rb_data_type_t EnumDescriptor_type = { - "Google::Protobuf::EnumDescriptor", - {EnumDescriptor_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static EnumDescriptor* ruby_to_EnumDescriptor(VALUE val) { - EnumDescriptor* ret; - TypedData_Get_Struct(val, EnumDescriptor, &EnumDescriptor_type, ret); - return ret; -} - -static VALUE EnumDescriptor_alloc(VALUE klass) { - EnumDescriptor* self = ALLOC(EnumDescriptor); - VALUE ret = TypedData_Wrap_Struct(klass, &EnumDescriptor_type, self); - self->enumdef = NULL; - self->module = Qnil; - self->descriptor_pool = Qnil; - return ret; -} - -// Exposed to other modules in defs.h. -const upb_EnumDef* EnumDescriptor_GetEnumDef(VALUE enum_desc_rb) { - EnumDescriptor* desc = ruby_to_EnumDescriptor(enum_desc_rb); - return desc->enumdef; -} - -/* - * call-seq: - * EnumDescriptor.new(c_only_cookie, ptr) => EnumDescriptor - * - * Creates a descriptor wrapper object. May only be called from C. - */ -static VALUE EnumDescriptor_initialize(VALUE _self, VALUE cookie, - VALUE descriptor_pool, VALUE ptr) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - - if (cookie != c_only_cookie) { - rb_raise(rb_eRuntimeError, - "Descriptor objects may not be created from Ruby."); - } - - self->descriptor_pool = descriptor_pool; - self->enumdef = (const upb_EnumDef*)NUM2ULL(ptr); - - return Qnil; -} - -/* - * call-seq: - * EnumDescriptor.file_descriptor - * - * Returns the FileDescriptor object this enum belongs to. - */ -static VALUE EnumDescriptor_file_descriptor(VALUE _self) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - return get_filedef_obj(self->descriptor_pool, - upb_EnumDef_File(self->enumdef)); -} - -/* - * call-seq: - * EnumDescriptor.name => name - * - * Returns the name of this enum type. - */ -static VALUE EnumDescriptor_name(VALUE _self) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - return rb_str_maybe_null(upb_EnumDef_FullName(self->enumdef)); -} - -/* - * call-seq: - * EnumDescriptor.lookup_name(name) => value - * - * Returns the numeric value corresponding to the given key name (as a Ruby - * symbol), or nil if none. - */ -static VALUE EnumDescriptor_lookup_name(VALUE _self, VALUE name) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - const char* name_str = rb_id2name(SYM2ID(name)); - const upb_EnumValueDef *ev = - upb_EnumDef_FindValueByName(self->enumdef, name_str); - if (ev) { - return INT2NUM(upb_EnumValueDef_Number(ev)); - } else { - return Qnil; - } -} - -/* - * call-seq: - * EnumDescriptor.lookup_value(name) => value - * - * Returns the key name (as a Ruby symbol) corresponding to the integer value, - * or nil if none. - */ -static VALUE EnumDescriptor_lookup_value(VALUE _self, VALUE number) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - int32_t val = NUM2INT(number); - const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNumber(self->enumdef, val); - if (ev) { - return ID2SYM(rb_intern(upb_EnumValueDef_Name(ev))); - } else { - return Qnil; - } -} - -/* - * call-seq: - * EnumDescriptor.each(&block) - * - * Iterates over key => value mappings in this enum's definition, yielding to - * the block with (key, value) arguments for each one. - */ -static VALUE EnumDescriptor_each(VALUE _self) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - - int n = upb_EnumDef_ValueCount(self->enumdef); - for (int i = 0; i < n; i++) { - const upb_EnumValueDef* ev = upb_EnumDef_Value(self->enumdef, i); - VALUE key = ID2SYM(rb_intern(upb_EnumValueDef_Name(ev))); - VALUE number = INT2NUM(upb_EnumValueDef_Number(ev)); - rb_yield_values(2, key, number); - } - - return Qnil; -} - -/* - * call-seq: - * EnumDescriptor.enummodule => module - * - * Returns the Ruby module corresponding to this enum type. - */ -static VALUE EnumDescriptor_enummodule(VALUE _self) { - EnumDescriptor* self = ruby_to_EnumDescriptor(_self); - if (self->module == Qnil) { - self->module = build_module_from_enumdesc(_self); - } - return self->module; -} - -static void EnumDescriptor_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "EnumDescriptor", rb_cObject); - rb_define_alloc_func(klass, EnumDescriptor_alloc); - rb_define_method(klass, "initialize", EnumDescriptor_initialize, 3); - rb_define_method(klass, "name", EnumDescriptor_name, 0); - rb_define_method(klass, "lookup_name", EnumDescriptor_lookup_name, 1); - rb_define_method(klass, "lookup_value", EnumDescriptor_lookup_value, 1); - rb_define_method(klass, "each", EnumDescriptor_each, 0); - rb_define_method(klass, "enummodule", EnumDescriptor_enummodule, 0); - rb_define_method(klass, "file_descriptor", EnumDescriptor_file_descriptor, 0); - rb_include_module(klass, rb_mEnumerable); - rb_gc_register_address(&cEnumDescriptor); - cEnumDescriptor = klass; -} - -static VALUE get_def_obj(VALUE _descriptor_pool, const void* ptr, VALUE klass) { - DescriptorPool* descriptor_pool = ruby_to_DescriptorPool(_descriptor_pool); - VALUE key = ULL2NUM((intptr_t)ptr); - VALUE def; - - def = rb_hash_aref(descriptor_pool->def_to_descriptor, key); - - if (ptr == NULL) { - return Qnil; - } - - if (def == Qnil) { - // Lazily create wrapper object. - VALUE args[3] = {c_only_cookie, _descriptor_pool, key}; - def = rb_class_new_instance(3, args, klass); - rb_hash_aset(descriptor_pool->def_to_descriptor, key, def); - } - - return def; -} - -static VALUE get_msgdef_obj(VALUE descriptor_pool, const upb_MessageDef* def) { - return get_def_obj(descriptor_pool, def, cDescriptor); -} - -static VALUE get_enumdef_obj(VALUE descriptor_pool, const upb_EnumDef* def) { - return get_def_obj(descriptor_pool, def, cEnumDescriptor); -} - -static VALUE get_fielddef_obj(VALUE descriptor_pool, const upb_FieldDef* def) { - return get_def_obj(descriptor_pool, def, cFieldDescriptor); -} - -static VALUE get_filedef_obj(VALUE descriptor_pool, const upb_FileDef* def) { - return get_def_obj(descriptor_pool, def, cFileDescriptor); -} - -static VALUE get_oneofdef_obj(VALUE descriptor_pool, const upb_OneofDef* def) { - return get_def_obj(descriptor_pool, def, cOneofDescriptor); -} - -// ----------------------------------------------------------------------------- -// Shared functions -// ----------------------------------------------------------------------------- - -// Functions exposed to other modules in defs.h. - -VALUE Descriptor_DefToClass(const upb_MessageDef* m) { - const upb_DefPool* symtab = upb_FileDef_Pool(upb_MessageDef_File(m)); - VALUE pool = ObjectCache_Get(symtab); - PBRUBY_ASSERT(pool != Qnil); - VALUE desc_rb = get_msgdef_obj(pool, m); - const Descriptor* desc = ruby_to_Descriptor(desc_rb); - return desc->klass; -} - -const upb_MessageDef* Descriptor_GetMsgDef(VALUE desc_rb) { - const Descriptor* desc = ruby_to_Descriptor(desc_rb); - return desc->msgdef; -} - -VALUE TypeInfo_InitArg(int argc, VALUE* argv, int skip_arg) { - if (argc > skip_arg) { - if (argc > 1 + skip_arg) { - rb_raise(rb_eArgError, "Expected a maximum of %d arguments.", - skip_arg + 1); - } - return argv[skip_arg]; - } else { - return Qnil; - } -} - -TypeInfo TypeInfo_FromClass(int argc, VALUE* argv, int skip_arg, - VALUE* type_class, VALUE* init_arg) { - TypeInfo ret = {ruby_to_fieldtype(argv[skip_arg])}; - - if (ret.type == kUpb_CType_Message || ret.type == kUpb_CType_Enum) { - *init_arg = TypeInfo_InitArg(argc, argv, skip_arg + 2); - - if (argc < 2 + skip_arg) { - rb_raise(rb_eArgError, "Expected at least %d arguments for message/enum.", - 2 + skip_arg); - } - - VALUE klass = argv[1 + skip_arg]; - VALUE desc = MessageOrEnum_GetDescriptor(klass); - *type_class = klass; - - if (desc == Qnil) { - rb_raise(rb_eArgError, - "Type class has no descriptor. Please pass a " - "class or enum as returned by the DescriptorPool."); - } - - if (ret.type == kUpb_CType_Message) { - ret.def.msgdef = ruby_to_Descriptor(desc)->msgdef; - Message_CheckClass(klass); - } else { - PBRUBY_ASSERT(ret.type == kUpb_CType_Enum); - ret.def.enumdef = ruby_to_EnumDescriptor(desc)->enumdef; - } - } else { - *init_arg = TypeInfo_InitArg(argc, argv, skip_arg + 1); - } - - return ret; -} - -void Defs_register(VALUE module) { - DescriptorPool_register(module); - Descriptor_register(module); - FileDescriptor_register(module); - FieldDescriptor_register(module); - OneofDescriptor_register(module); - EnumDescriptor_register(module); - - rb_gc_register_address(&c_only_cookie); - c_only_cookie = rb_class_new_instance(0, NULL, rb_cObject); +void* upb_Arena_FastMalloc(upb_Arena* a, size_t size) { + return upb_Arena_Malloc(a, size); } diff --git a/ruby/ext/google/protobuf_c/defs.h b/ruby/ext/google/protobuf_c/defs.h deleted file mode 100644 index f559cb0b9def..000000000000 --- a/ruby/ext/google/protobuf_c/defs.h +++ /dev/null @@ -1,107 +0,0 @@ -// 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. - -#ifndef RUBY_PROTOBUF_DEFS_H_ -#define RUBY_PROTOBUF_DEFS_H_ - -#include - -#include "protobuf.h" -#include "ruby-upb.h" - -// ----------------------------------------------------------------------------- -// TypeInfo -// ----------------------------------------------------------------------------- - -// This bundles a upb_CType and msgdef/enumdef when appropriate. This is -// convenient for functions that need type information but cannot necessarily -// assume a upb_FieldDef will be available. -// -// For example, Google::Protobuf::Map and Google::Protobuf::RepeatedField can -// be constructed with type information alone: -// -// # RepeatedField will internally store the type information in a TypeInfo. -// Google::Protobuf::RepeatedField.new(:message, FooMessage) - -typedef struct { - upb_CType type; - union { - const upb_MessageDef* msgdef; // When type == kUpb_CType_Message - const upb_EnumDef* enumdef; // When type == kUpb_CType_Enum - } def; -} TypeInfo; - -static inline TypeInfo TypeInfo_get(const upb_FieldDef* f) { - TypeInfo ret = {upb_FieldDef_CType(f), {NULL}}; - switch (ret.type) { - case kUpb_CType_Message: - ret.def.msgdef = upb_FieldDef_MessageSubDef(f); - break; - case kUpb_CType_Enum: - ret.def.enumdef = upb_FieldDef_EnumSubDef(f); - break; - default: - break; - } - return ret; -} - -TypeInfo TypeInfo_FromClass(int argc, VALUE* argv, int skip_arg, - VALUE* type_class, VALUE* init_arg); - -static inline TypeInfo TypeInfo_from_type(upb_CType type) { - TypeInfo ret = {type}; - assert(type != kUpb_CType_Message && type != kUpb_CType_Enum); - return ret; -} - -// ----------------------------------------------------------------------------- -// Other utilities -// ----------------------------------------------------------------------------- - -VALUE Descriptor_DefToClass(const upb_MessageDef* m); - -// Returns the underlying msgdef, enumdef, or symtab (respectively) for the -// given Descriptor, EnumDescriptor, or DescriptorPool Ruby object. -const upb_EnumDef* EnumDescriptor_GetEnumDef(VALUE enum_desc_rb); -const upb_DefPool* DescriptorPool_GetSymtab(VALUE desc_pool_rb); -const upb_MessageDef* Descriptor_GetMsgDef(VALUE desc_rb); - -// Returns a upb field type for the given Ruby symbol -// (eg. :float => kUpb_CType_Float). -upb_CType ruby_to_fieldtype(VALUE type); - -// The singleton generated pool (a DescriptorPool object). -extern VALUE generated_pool; - -// Call at startup to register all types in this module. -void Defs_register(VALUE module); - -#endif // RUBY_PROTOBUF_DEFS_H_ diff --git a/ruby/ext/google/protobuf_c/extconf.rb b/ruby/ext/google/protobuf_c/extconf.rb deleted file mode 100755 index 5cc45337b804..000000000000 --- a/ruby/ext/google/protobuf_c/extconf.rb +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/ruby - -require 'mkmf' - -ext_name = "google/protobuf_c" - -dir_config(ext_name) - -if RUBY_PLATFORM =~ /darwin/ || RUBY_PLATFORM =~ /linux/ - $CFLAGS += " -std=gnu99 -O3 -DNDEBUG -fvisibility=hidden -Wall -Wsign-compare -Wno-declaration-after-statement" -else - $CFLAGS += " -std=gnu99 -O3 -DNDEBUG" -end - - -if RUBY_PLATFORM =~ /linux/ - # Instruct the linker to point memcpy calls at our __wrap_memcpy wrapper. - $LDFLAGS += " -Wl,-wrap,memcpy" -end - -$VPATH << "$(srcdir)/third_party/utf8_range" -$INCFLAGS << "$(srcdir)/third_party/utf8_range" - -$srcs = ["protobuf.c", "convert.c", "defs.c", "message.c", - "repeated_field.c", "map.c", "ruby-upb.c", "wrap_memcpy.c", - "naive.c", "range2-neon.c", "range2-sse.c"] - -create_makefile(ext_name, Dir.pwd+"/../../../../ext/google/protobuf_c") diff --git a/ruby/ext/google/protobuf_c/map.c b/ruby/ext/google/protobuf_c/map.c deleted file mode 100644 index 5d30319c198e..000000000000 --- a/ruby/ext/google/protobuf_c/map.c +++ /dev/null @@ -1,702 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2014 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. - -#include "convert.h" -#include "defs.h" -#include "message.h" -#include "protobuf.h" - -// ----------------------------------------------------------------------------- -// Basic map operations on top of upb_Map. -// -// Note that we roll our own `Map` container here because, as for -// `RepeatedField`, we want a strongly-typed container. This is so that any user -// errors due to incorrect map key or value types are raised as close as -// possible to the error site, rather than at some deferred point (e.g., -// serialization). -// ----------------------------------------------------------------------------- - -// ----------------------------------------------------------------------------- -// Map container type. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_Map* map; // Can convert to mutable when non-frozen. - upb_CType key_type; - TypeInfo value_type_info; - VALUE value_type_class; - VALUE arena; -} Map; - -static void Map_mark(void* _self) { - Map* self = _self; - rb_gc_mark(self->value_type_class); - rb_gc_mark(self->arena); -} - -const rb_data_type_t Map_type = { - "Google::Protobuf::Map", - {Map_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -VALUE cMap; - -static Map* ruby_to_Map(VALUE _self) { - Map* self; - TypedData_Get_Struct(_self, Map, &Map_type, self); - return self; -} - -static VALUE Map_alloc(VALUE klass) { - Map* self = ALLOC(Map); - self->map = NULL; - self->value_type_class = Qnil; - self->value_type_info.def.msgdef = NULL; - self->arena = Qnil; - return TypedData_Wrap_Struct(klass, &Map_type, self); -} - -VALUE Map_GetRubyWrapper(upb_Map* map, upb_CType key_type, TypeInfo value_type, - VALUE arena) { - PBRUBY_ASSERT(map); - - VALUE val = ObjectCache_Get(map); - - if (val == Qnil) { - val = Map_alloc(cMap); - Map* self; - ObjectCache_Add(map, val); - TypedData_Get_Struct(val, Map, &Map_type, self); - self->map = map; - self->arena = arena; - self->key_type = key_type; - self->value_type_info = value_type; - if (self->value_type_info.type == kUpb_CType_Message) { - const upb_MessageDef* val_m = self->value_type_info.def.msgdef; - self->value_type_class = Descriptor_DefToClass(val_m); - } - } - - return val; -} - -static VALUE Map_new_this_type(Map* from) { - VALUE arena_rb = Arena_new(); - upb_Map* map = upb_Map_New(Arena_get(arena_rb), from->key_type, - from->value_type_info.type); - VALUE ret = - Map_GetRubyWrapper(map, from->key_type, from->value_type_info, arena_rb); - PBRUBY_ASSERT(ruby_to_Map(ret)->value_type_class == from->value_type_class); - return ret; -} - -static TypeInfo Map_keyinfo(Map* self) { - TypeInfo ret; - ret.type = self->key_type; - ret.def.msgdef = NULL; - return ret; -} - -static upb_Map* Map_GetMutable(VALUE _self) { - rb_check_frozen(_self); - return (upb_Map*)ruby_to_Map(_self)->map; -} - -VALUE Map_CreateHash(const upb_Map* map, upb_CType key_type, - TypeInfo val_info) { - VALUE hash = rb_hash_new(); - size_t iter = kUpb_Map_Begin; - TypeInfo key_info = TypeInfo_from_type(key_type); - - if (!map) return hash; - - while (upb_MapIterator_Next(map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(map, iter); - upb_MessageValue val = upb_MapIterator_Value(map, iter); - VALUE key_val = Convert_UpbToRuby(key, key_info, Qnil); - VALUE val_val = Scalar_CreateHash(val, val_info); - rb_hash_aset(hash, key_val, val_val); - } - - return hash; -} - -VALUE Map_deep_copy(VALUE obj) { - Map* self = ruby_to_Map(obj); - VALUE new_arena_rb = Arena_new(); - upb_Arena* arena = Arena_get(new_arena_rb); - upb_Map* new_map = - upb_Map_New(arena, self->key_type, self->value_type_info.type); - size_t iter = kUpb_Map_Begin; - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(self->map, iter); - upb_MessageValue val = upb_MapIterator_Value(self->map, iter); - upb_MessageValue val_copy = - Msgval_DeepCopy(val, self->value_type_info, arena); - upb_Map_Set(new_map, key, val_copy, arena); - } - - return Map_GetRubyWrapper(new_map, self->key_type, self->value_type_info, - new_arena_rb); -} - -const upb_Map* Map_GetUpbMap(VALUE val, const upb_FieldDef* field, - upb_Arena* arena) { - const upb_FieldDef* key_field = map_field_key(field); - const upb_FieldDef* value_field = map_field_value(field); - TypeInfo value_type_info = TypeInfo_get(value_field); - Map* self; - - if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) || - RTYPEDDATA_TYPE(val) != &Map_type) { - rb_raise(cTypeError, "Expected Map instance"); - } - - self = ruby_to_Map(val); - if (self->key_type != upb_FieldDef_CType(key_field)) { - rb_raise(cTypeError, "Map key type does not match field's key type"); - } - if (self->value_type_info.type != value_type_info.type) { - rb_raise(cTypeError, "Map value type does not match field's value type"); - } - if (self->value_type_info.def.msgdef != value_type_info.def.msgdef) { - rb_raise(cTypeError, "Map value type has wrong message/enum class"); - } - - Arena_fuse(self->arena, arena); - return self->map; -} - -void Map_Inspect(StringBuilder* b, const upb_Map* map, upb_CType key_type, - TypeInfo val_type) { - bool first = true; - TypeInfo key_type_info = {key_type}; - StringBuilder_Printf(b, "{"); - if (map) { - size_t iter = kUpb_Map_Begin; - while (upb_MapIterator_Next(map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(map, iter); - upb_MessageValue val = upb_MapIterator_Value(map, iter); - if (first) { - first = false; - } else { - StringBuilder_Printf(b, ", "); - } - StringBuilder_PrintMsgval(b, key, key_type_info); - StringBuilder_Printf(b, "=>"); - StringBuilder_PrintMsgval(b, val, val_type); - } - } - StringBuilder_Printf(b, "}"); -} - -static int merge_into_self_callback(VALUE key, VALUE val, VALUE _self) { - Map* self = ruby_to_Map(_self); - upb_Arena* arena = Arena_get(self->arena); - upb_MessageValue key_val = - Convert_RubyToUpb(key, "", Map_keyinfo(self), arena); - upb_MessageValue val_val = - Convert_RubyToUpb(val, "", self->value_type_info, arena); - upb_Map_Set(Map_GetMutable(_self), key_val, val_val, arena); - return ST_CONTINUE; -} - -// Used only internally -- shared by #merge and #initialize. -static VALUE Map_merge_into_self(VALUE _self, VALUE hashmap) { - if (TYPE(hashmap) == T_HASH) { - rb_hash_foreach(hashmap, merge_into_self_callback, _self); - } else if (RB_TYPE_P(hashmap, T_DATA) && RTYPEDDATA_P(hashmap) && - RTYPEDDATA_TYPE(hashmap) == &Map_type) { - Map* self = ruby_to_Map(_self); - Map* other = ruby_to_Map(hashmap); - upb_Arena* arena = Arena_get(self->arena); - upb_Message* self_msg = Map_GetMutable(_self); - size_t iter = kUpb_Map_Begin; - - Arena_fuse(other->arena, arena); - - if (self->key_type != other->key_type || - self->value_type_info.type != other->value_type_info.type || - self->value_type_class != other->value_type_class) { - rb_raise(rb_eArgError, "Attempt to merge Map with mismatching types"); - } - - while (upb_MapIterator_Next(other->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(other->map, iter); - upb_MessageValue val = upb_MapIterator_Value(other->map, iter); - upb_Map_Set(self_msg, key, val, arena); - } - } else { - rb_raise(rb_eArgError, "Unknown type merging into Map"); - } - return _self; -} - -/* - * call-seq: - * Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) - * => new map - * - * Allocates a new Map container. This constructor may be called with 2, 3, or 4 - * arguments. The first two arguments are always present and are symbols (taking - * on the same values as field-type symbols in message descriptors) that - * indicate the type of the map key and value fields. - * - * The supported key types are: :int32, :int64, :uint32, :uint64, :bool, - * :string, :bytes. - * - * The supported value types are: :int32, :int64, :uint32, :uint64, :bool, - * :string, :bytes, :enum, :message. - * - * The third argument, value_typeclass, must be present if value_type is :enum - * or :message. As in RepeatedField#new, this argument must be a message class - * (for :message) or enum module (for :enum). - * - * The last argument, if present, provides initial content for map. Note that - * this may be an ordinary Ruby hashmap or another Map instance with identical - * key and value types. Also note that this argument may be present whether or - * not value_typeclass is present (and it is unambiguously separate from - * value_typeclass because value_typeclass's presence is strictly determined by - * value_type). The contents of this initial hashmap or Map instance are - * shallow-copied into the new Map: the original map is unmodified, but - * references to underlying objects will be shared if the value type is a - * message type. - */ -static VALUE Map_init(int argc, VALUE* argv, VALUE _self) { - Map* self = ruby_to_Map(_self); - VALUE init_arg; - - // We take either two args (:key_type, :value_type), three args (:key_type, - // :value_type, "ValueMessageType"), or four args (the above plus an initial - // hashmap). - if (argc < 2 || argc > 4) { - rb_raise(rb_eArgError, "Map constructor expects 2, 3 or 4 arguments."); - } - - self->key_type = ruby_to_fieldtype(argv[0]); - self->value_type_info = - TypeInfo_FromClass(argc, argv, 1, &self->value_type_class, &init_arg); - self->arena = Arena_new(); - - // Check that the key type is an allowed type. - switch (self->key_type) { - case kUpb_CType_Int32: - case kUpb_CType_Int64: - case kUpb_CType_UInt32: - case kUpb_CType_UInt64: - case kUpb_CType_Bool: - case kUpb_CType_String: - case kUpb_CType_Bytes: - // These are OK. - break; - default: - rb_raise(rb_eArgError, "Invalid key type for map."); - } - - self->map = upb_Map_New(Arena_get(self->arena), self->key_type, - self->value_type_info.type); - ObjectCache_Add(self->map, _self); - - if (init_arg != Qnil) { - Map_merge_into_self(_self, init_arg); - } - - return Qnil; -} - -/* - * call-seq: - * Map.each(&block) - * - * Invokes &block on each |key, value| pair in the map, in unspecified order. - * Note that Map also includes Enumerable; map thus acts like a normal Ruby - * sequence. - */ -static VALUE Map_each(VALUE _self) { - Map* self = ruby_to_Map(_self); - size_t iter = kUpb_Map_Begin; - - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(self->map, iter); - upb_MessageValue val = upb_MapIterator_Value(self->map, iter); - VALUE key_val = Convert_UpbToRuby(key, Map_keyinfo(self), self->arena); - VALUE val_val = Convert_UpbToRuby(val, self->value_type_info, self->arena); - rb_yield_values(2, key_val, val_val); - } - - return Qnil; -} - -/* - * call-seq: - * Map.keys => [list_of_keys] - * - * Returns the list of keys contained in the map, in unspecified order. - */ -static VALUE Map_keys(VALUE _self) { - Map* self = ruby_to_Map(_self); - size_t iter = kUpb_Map_Begin; - VALUE ret = rb_ary_new(); - - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(self->map, iter); - VALUE key_val = Convert_UpbToRuby(key, Map_keyinfo(self), self->arena); - rb_ary_push(ret, key_val); - } - - return ret; -} - -/* - * call-seq: - * Map.values => [list_of_values] - * - * Returns the list of values contained in the map, in unspecified order. - */ -static VALUE Map_values(VALUE _self) { - Map* self = ruby_to_Map(_self); - size_t iter = kUpb_Map_Begin; - VALUE ret = rb_ary_new(); - - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue val = upb_MapIterator_Value(self->map, iter); - VALUE val_val = Convert_UpbToRuby(val, self->value_type_info, self->arena); - rb_ary_push(ret, val_val); - } - - return ret; -} - -/* - * call-seq: - * Map.[](key) => value - * - * Accesses the element at the given key. Throws an exception if the key type is - * incorrect. Returns nil when the key is not present in the map. - */ -static VALUE Map_index(VALUE _self, VALUE key) { - Map* self = ruby_to_Map(_self); - upb_MessageValue key_upb = - Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL); - upb_MessageValue val; - - if (upb_Map_Get(self->map, key_upb, &val)) { - return Convert_UpbToRuby(val, self->value_type_info, self->arena); - } else { - return Qnil; - } -} - -/* - * call-seq: - * Map.[]=(key, value) => value - * - * Inserts or overwrites the value at the given key with the given new value. - * Throws an exception if the key type is incorrect. Returns the new value that - * was just inserted. - */ -static VALUE Map_index_set(VALUE _self, VALUE key, VALUE val) { - Map* self = ruby_to_Map(_self); - upb_Arena* arena = Arena_get(self->arena); - upb_MessageValue key_upb = - Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL); - upb_MessageValue val_upb = - Convert_RubyToUpb(val, "", self->value_type_info, arena); - - upb_Map_Set(Map_GetMutable(_self), key_upb, val_upb, arena); - - return val; -} - -/* - * call-seq: - * Map.has_key?(key) => bool - * - * Returns true if the given key is present in the map. Throws an exception if - * the key has the wrong type. - */ -static VALUE Map_has_key(VALUE _self, VALUE key) { - Map* self = ruby_to_Map(_self); - upb_MessageValue key_upb = - Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL); - - if (upb_Map_Get(self->map, key_upb, NULL)) { - return Qtrue; - } else { - return Qfalse; - } -} - -/* - * call-seq: - * Map.delete(key) => old_value - * - * Deletes the value at the given key, if any, returning either the old value or - * nil if none was present. Throws an exception if the key is of the wrong type. - */ -static VALUE Map_delete(VALUE _self, VALUE key) { - Map* self = ruby_to_Map(_self); - upb_MessageValue key_upb = - Convert_RubyToUpb(key, "", Map_keyinfo(self), NULL); - upb_MessageValue val_upb; - VALUE ret; - - rb_check_frozen(_self); - - // TODO(haberman): make upb_Map_Delete() also capable of returning the deleted - // value. - if (upb_Map_Get(self->map, key_upb, &val_upb)) { - ret = Convert_UpbToRuby(val_upb, self->value_type_info, self->arena); - } else { - ret = Qnil; - } - - upb_Map_Delete(Map_GetMutable(_self), key_upb); - - return ret; -} - -/* - * call-seq: - * Map.clear - * - * Removes all entries from the map. - */ -static VALUE Map_clear(VALUE _self) { - upb_Map_Clear(Map_GetMutable(_self)); - return Qnil; -} - -/* - * call-seq: - * Map.length - * - * Returns the number of entries (key-value pairs) in the map. - */ -static VALUE Map_length(VALUE _self) { - Map* self = ruby_to_Map(_self); - return ULL2NUM(upb_Map_Size(self->map)); -} - -/* - * call-seq: - * Map.dup => new_map - * - * Duplicates this map with a shallow copy. References to all non-primitive - * element objects (e.g., submessages) are shared. - */ -static VALUE Map_dup(VALUE _self) { - Map* self = ruby_to_Map(_self); - VALUE new_map_rb = Map_new_this_type(self); - Map* new_self = ruby_to_Map(new_map_rb); - size_t iter = kUpb_Map_Begin; - upb_Arena* arena = Arena_get(new_self->arena); - upb_Map* new_map = Map_GetMutable(new_map_rb); - - Arena_fuse(self->arena, arena); - - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(self->map, iter); - upb_MessageValue val = upb_MapIterator_Value(self->map, iter); - upb_Map_Set(new_map, key, val, arena); - } - - return new_map_rb; -} - -/* - * call-seq: - * Map.==(other) => boolean - * - * Compares this map to another. Maps are equal if they have identical key sets, - * and for each key, the values in both maps compare equal. Elements are - * compared as per normal Ruby semantics, by calling their :== methods (or - * performing a more efficient comparison for primitive types). - * - * Maps with dissimilar key types or value types/typeclasses are never equal, - * even if value comparison (for example, between integers and floats) would - * have otherwise indicated that every element has equal value. - */ -VALUE Map_eq(VALUE _self, VALUE _other) { - Map* self = ruby_to_Map(_self); - Map* other; - - // Allow comparisons to Ruby hashmaps by converting to a temporary Map - // instance. Slow, but workable. - if (TYPE(_other) == T_HASH) { - VALUE other_map = Map_new_this_type(self); - Map_merge_into_self(other_map, _other); - _other = other_map; - } - - other = ruby_to_Map(_other); - - if (self == other) { - return Qtrue; - } - if (self->key_type != other->key_type || - self->value_type_info.type != other->value_type_info.type || - self->value_type_class != other->value_type_class) { - return Qfalse; - } - if (upb_Map_Size(self->map) != upb_Map_Size(other->map)) { - return Qfalse; - } - - // For each member of self, check that an equal member exists at the same key - // in other. - size_t iter = kUpb_Map_Begin; - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(self->map, iter); - upb_MessageValue val = upb_MapIterator_Value(self->map, iter); - upb_MessageValue other_val; - if (!upb_Map_Get(other->map, key, &other_val)) { - // Not present in other map. - return Qfalse; - } - if (!Msgval_IsEqual(val, other_val, self->value_type_info)) { - // Present but different value. - return Qfalse; - } - } - - return Qtrue; -} - -/* - * call-seq: - * Message.freeze => self - * - * Freezes the message object. We have to intercept this so we can pin the - * Ruby object into memory so we don't forget it's frozen. - */ -static VALUE Map_freeze(VALUE _self) { - Map* self = ruby_to_Map(_self); - if (!RB_OBJ_FROZEN(_self)) { - Arena_Pin(self->arena, _self); - RB_OBJ_FREEZE(_self); - } - return _self; -} - -/* - * call-seq: - * Map.hash => hash_value - * - * Returns a hash value based on this map's contents. - */ -VALUE Map_hash(VALUE _self) { - Map* self = ruby_to_Map(_self); - uint64_t hash = 0; - - size_t iter = kUpb_Map_Begin; - TypeInfo key_info = {self->key_type}; - while (upb_MapIterator_Next(self->map, &iter)) { - upb_MessageValue key = upb_MapIterator_Key(self->map, iter); - upb_MessageValue val = upb_MapIterator_Value(self->map, iter); - hash = Msgval_GetHash(key, key_info, hash); - hash = Msgval_GetHash(val, self->value_type_info, hash); - } - - return LL2NUM(hash); -} - -/* - * call-seq: - * Map.to_h => {} - * - * Returns a Ruby Hash object containing all the values within the map - */ -VALUE Map_to_h(VALUE _self) { - Map* self = ruby_to_Map(_self); - return Map_CreateHash(self->map, self->key_type, self->value_type_info); -} - -/* - * call-seq: - * Map.inspect => string - * - * Returns a string representing this map's elements. It will be formatted as - * "{key => value, key => value, ...}", with each key and value string - * representation computed by its own #inspect method. - */ -VALUE Map_inspect(VALUE _self) { - Map* self = ruby_to_Map(_self); - - StringBuilder* builder = StringBuilder_New(); - Map_Inspect(builder, self->map, self->key_type, self->value_type_info); - VALUE ret = StringBuilder_ToRubyString(builder); - StringBuilder_Free(builder); - return ret; -} - -/* - * call-seq: - * Map.merge(other_map) => map - * - * Copies key/value pairs from other_map into a copy of this map. If a key is - * set in other_map and this map, the value from other_map overwrites the value - * in the new copy of this map. Returns the new copy of this map with merged - * contents. - */ -static VALUE Map_merge(VALUE _self, VALUE hashmap) { - VALUE dupped = Map_dup(_self); - return Map_merge_into_self(dupped, hashmap); -} - -void Map_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "Map", rb_cObject); - rb_define_alloc_func(klass, Map_alloc); - rb_gc_register_address(&cMap); - cMap = klass; - - rb_define_method(klass, "initialize", Map_init, -1); - rb_define_method(klass, "each", Map_each, 0); - rb_define_method(klass, "keys", Map_keys, 0); - rb_define_method(klass, "values", Map_values, 0); - rb_define_method(klass, "[]", Map_index, 1); - rb_define_method(klass, "[]=", Map_index_set, 2); - rb_define_method(klass, "has_key?", Map_has_key, 1); - rb_define_method(klass, "delete", Map_delete, 1); - rb_define_method(klass, "clear", Map_clear, 0); - rb_define_method(klass, "length", Map_length, 0); - rb_define_method(klass, "size", Map_length, 0); - rb_define_method(klass, "dup", Map_dup, 0); - // Also define #clone so that we don't inherit Object#clone. - rb_define_method(klass, "clone", Map_dup, 0); - rb_define_method(klass, "==", Map_eq, 1); - rb_define_method(klass, "freeze", Map_freeze, 0); - rb_define_method(klass, "hash", Map_hash, 0); - rb_define_method(klass, "to_h", Map_to_h, 0); - rb_define_method(klass, "inspect", Map_inspect, 0); - rb_define_method(klass, "merge", Map_merge, 1); - rb_include_module(klass, rb_mEnumerable); -} diff --git a/ruby/ext/google/protobuf_c/map.h b/ruby/ext/google/protobuf_c/map.h deleted file mode 100644 index 7b4a1514cafa..000000000000 --- a/ruby/ext/google/protobuf_c/map.h +++ /dev/null @@ -1,66 +0,0 @@ -// 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. - -#ifndef RUBY_PROTOBUF_MAP_H_ -#define RUBY_PROTOBUF_MAP_H_ - -#include - -#include "protobuf.h" -#include "ruby-upb.h" - -// Returns a Ruby wrapper object for the given map, which will be created if -// one does not exist already. -VALUE Map_GetRubyWrapper(upb_Map *map, upb_CType key_type, TypeInfo value_type, - VALUE arena); - -// Gets the underlying upb_Map for this Ruby map object, which must have -// key/value type that match |field|. If this is not a map or the type doesn't -// match, raises an exception. -const upb_Map *Map_GetUpbMap(VALUE val, const upb_FieldDef *field, - upb_Arena *arena); - -// Implements #inspect for this map by appending its contents to |b|. -void Map_Inspect(StringBuilder *b, const upb_Map *map, upb_CType key_type, - TypeInfo val_type); - -// Returns a new Hash object containing the contents of this Map. -VALUE Map_CreateHash(const upb_Map *map, upb_CType key_type, TypeInfo val_info); - -// Returns a deep copy of this Map object. -VALUE Map_deep_copy(VALUE obj); - -// Ruby class of Google::Protobuf::Map. -extern VALUE cMap; - -// Call at startup to register all types in this module. -void Map_register(VALUE module); - -#endif // RUBY_PROTOBUF_MAP_H_ diff --git a/ruby/ext/google/protobuf_c/message.c b/ruby/ext/google/protobuf_c/message.c index b11878545f03..0d47ecd1999e 100644 --- a/ruby/ext/google/protobuf_c/message.c +++ b/ruby/ext/google/protobuf_c/message.c @@ -30,666 +30,6 @@ #include "message.h" -#include "convert.h" -#include "defs.h" -#include "map.h" -#include "protobuf.h" -#include "repeated_field.h" - -static VALUE cParseError = Qnil; -static VALUE cAbstractMessage = Qnil; -static ID descriptor_instancevar_interned; - -static VALUE initialize_rb_class_with_no_args(VALUE klass) { - return rb_funcall(klass, rb_intern("new"), 0); -} - -VALUE MessageOrEnum_GetDescriptor(VALUE klass) { - return rb_ivar_get(klass, descriptor_instancevar_interned); -} - -// ----------------------------------------------------------------------------- -// Class/module creation from msgdefs and enumdefs, respectively. -// ----------------------------------------------------------------------------- - -typedef struct { - VALUE arena; - const upb_Message* msg; // Can get as mutable when non-frozen. - const upb_MessageDef* - msgdef; // kept alive by self.class.descriptor reference. -} Message; - -static void Message_mark(void* _self) { - Message* self = (Message*)_self; - rb_gc_mark(self->arena); -} - -static rb_data_type_t Message_type = { - "Message", - {Message_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static Message* ruby_to_Message(VALUE msg_rb) { - Message* msg; - TypedData_Get_Struct(msg_rb, Message, &Message_type, msg); - return msg; -} - -static VALUE Message_alloc(VALUE klass) { - VALUE descriptor = rb_ivar_get(klass, descriptor_instancevar_interned); - Message* msg = ALLOC(Message); - VALUE ret; - - msg->msgdef = Descriptor_GetMsgDef(descriptor); - msg->arena = Qnil; - msg->msg = NULL; - - ret = TypedData_Wrap_Struct(klass, &Message_type, msg); - rb_ivar_set(ret, descriptor_instancevar_interned, descriptor); - - return ret; -} - -const upb_Message* Message_Get(VALUE msg_rb, const upb_MessageDef** m) { - Message* msg = ruby_to_Message(msg_rb); - if (m) *m = msg->msgdef; - return msg->msg; -} - -upb_Message* Message_GetMutable(VALUE msg_rb, const upb_MessageDef** m) { - rb_check_frozen(msg_rb); - return (upb_Message*)Message_Get(msg_rb, m); -} - -void Message_InitPtr(VALUE self_, upb_Message* msg, VALUE arena) { - Message* self = ruby_to_Message(self_); - self->msg = msg; - self->arena = arena; - ObjectCache_Add(msg, self_); -} - -VALUE Message_GetArena(VALUE msg_rb) { - Message* msg = ruby_to_Message(msg_rb); - return msg->arena; -} - -void Message_CheckClass(VALUE klass) { - if (rb_get_alloc_func(klass) != &Message_alloc) { - rb_raise(rb_eArgError, - "Message class was not returned by the DescriptorPool."); - } -} - -VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m, - VALUE arena) { - if (msg == NULL) return Qnil; - - VALUE val = ObjectCache_Get(msg); - - if (val == Qnil) { - VALUE klass = Descriptor_DefToClass(m); - val = Message_alloc(klass); - Message_InitPtr(val, msg, arena); - } - - return val; -} - -void Message_PrintMessage(StringBuilder* b, const upb_Message* msg, - const upb_MessageDef* m) { - bool first = true; - int n = upb_MessageDef_FieldCount(m); - VALUE klass = Descriptor_DefToClass(m); - StringBuilder_Printf(b, "<%s: ", rb_class2name(klass)); - - for (int i = 0; i < n; i++) { - const upb_FieldDef* field = upb_MessageDef_Field(m, i); - - if (upb_FieldDef_HasPresence(field) && !upb_Message_Has(msg, field)) { - continue; - } - - if (!first) { - StringBuilder_Printf(b, ", "); - } else { - first = false; - } - - upb_MessageValue msgval = upb_Message_Get(msg, field); - - StringBuilder_Printf(b, "%s: ", upb_FieldDef_Name(field)); - - if (upb_FieldDef_IsMap(field)) { - const upb_MessageDef* entry_m = upb_FieldDef_MessageSubDef(field); - const upb_FieldDef* key_f = upb_MessageDef_FindFieldByNumber(entry_m, 1); - const upb_FieldDef* val_f = upb_MessageDef_FindFieldByNumber(entry_m, 2); - TypeInfo val_info = TypeInfo_get(val_f); - Map_Inspect(b, msgval.map_val, upb_FieldDef_CType(key_f), val_info); - } else if (upb_FieldDef_IsRepeated(field)) { - RepeatedField_Inspect(b, msgval.array_val, TypeInfo_get(field)); - } else { - StringBuilder_PrintMsgval(b, msgval, TypeInfo_get(field)); - } - } - - StringBuilder_Printf(b, ">"); -} - -// Helper functions for #method_missing //////////////////////////////////////// - -enum { - METHOD_UNKNOWN = 0, - METHOD_GETTER = 1, - METHOD_SETTER = 2, - METHOD_CLEAR = 3, - METHOD_PRESENCE = 4, - METHOD_ENUM_GETTER = 5, - METHOD_WRAPPER_GETTER = 6, - METHOD_WRAPPER_SETTER = 7 -}; - -// Check if the field is a well known wrapper type -static bool IsWrapper(const upb_MessageDef* m) { - if (!m) return false; - switch (upb_MessageDef_WellKnownType(m)) { - case kUpb_WellKnown_DoubleValue: - case kUpb_WellKnown_FloatValue: - case kUpb_WellKnown_Int64Value: - case kUpb_WellKnown_UInt64Value: - case kUpb_WellKnown_Int32Value: - case kUpb_WellKnown_UInt32Value: - case kUpb_WellKnown_StringValue: - case kUpb_WellKnown_BytesValue: - case kUpb_WellKnown_BoolValue: - return true; - default: - return false; - } -} - -static bool IsFieldWrapper(const upb_FieldDef* f) { - return IsWrapper(upb_FieldDef_MessageSubDef(f)); -} - -static bool Match(const upb_MessageDef* m, const char* name, - const upb_FieldDef** f, const upb_OneofDef** o, - const char* prefix, const char* suffix) { - size_t sp = strlen(prefix); - size_t ss = strlen(suffix); - size_t sn = strlen(name); - - if (sn <= sp + ss) return false; - - if (memcmp(name, prefix, sp) != 0 || - memcmp(name + sn - ss, suffix, ss) != 0) { - return false; - } - - return upb_MessageDef_FindByNameWithSize(m, name + sp, sn - sp - ss, f, o); -} - -static int extract_method_call(VALUE method_name, Message* self, - const upb_FieldDef** f, const upb_OneofDef** o) { - const upb_MessageDef* m = self->msgdef; - const char* name; - - Check_Type(method_name, T_SYMBOL); - name = rb_id2name(SYM2ID(method_name)); - - if (Match(m, name, f, o, "", "")) return METHOD_GETTER; - if (Match(m, name, f, o, "", "=")) return METHOD_SETTER; - if (Match(m, name, f, o, "clear_", "")) return METHOD_CLEAR; - if (Match(m, name, f, o, "has_", "?") && - (*o || (*f && upb_FieldDef_HasPresence(*f)))) { - // Disallow oneof hazzers for proto3. - // TODO(haberman): remove this test when we are enabling oneof hazzers for - // proto3. - if (*f && !upb_FieldDef_IsSubMessage(*f) && - upb_FieldDef_RealContainingOneof(*f) && - upb_MessageDef_Syntax(upb_FieldDef_ContainingType(*f)) != - kUpb_Syntax_Proto2) { - return METHOD_UNKNOWN; - } - return METHOD_PRESENCE; - } - if (Match(m, name, f, o, "", "_as_value") && *f && - !upb_FieldDef_IsRepeated(*f) && IsFieldWrapper(*f)) { - return METHOD_WRAPPER_GETTER; - } - if (Match(m, name, f, o, "", "_as_value=") && *f && - !upb_FieldDef_IsRepeated(*f) && IsFieldWrapper(*f)) { - return METHOD_WRAPPER_SETTER; - } - if (Match(m, name, f, o, "", "_const") && *f && - upb_FieldDef_CType(*f) == kUpb_CType_Enum) { - return METHOD_ENUM_GETTER; - } - - return METHOD_UNKNOWN; -} - -static VALUE Message_oneof_accessor(VALUE _self, const upb_OneofDef* o, - int accessor_type) { - Message* self = ruby_to_Message(_self); - const upb_FieldDef* oneof_field = upb_Message_WhichOneof(self->msg, o); - - switch (accessor_type) { - case METHOD_PRESENCE: - return oneof_field == NULL ? Qfalse : Qtrue; - case METHOD_CLEAR: - if (oneof_field != NULL) { - upb_Message_ClearField(Message_GetMutable(_self, NULL), oneof_field); - } - return Qnil; - case METHOD_GETTER: - return oneof_field == NULL - ? Qnil - : ID2SYM(rb_intern(upb_FieldDef_Name(oneof_field))); - case METHOD_SETTER: - rb_raise(rb_eRuntimeError, "Oneof accessors are read-only."); - } - rb_raise(rb_eRuntimeError, "Invalid access of oneof field."); -} - -static void Message_setfield(upb_Message* msg, const upb_FieldDef* f, VALUE val, - upb_Arena* arena) { - upb_MessageValue msgval; - if (upb_FieldDef_IsMap(f)) { - msgval.map_val = Map_GetUpbMap(val, f, arena); - } else if (upb_FieldDef_IsRepeated(f)) { - msgval.array_val = RepeatedField_GetUpbArray(val, f, arena); - } else { - if (val == Qnil && - (upb_FieldDef_IsSubMessage(f) || upb_FieldDef_RealContainingOneof(f))) { - upb_Message_ClearField(msg, f); - return; - } - msgval = - Convert_RubyToUpb(val, upb_FieldDef_Name(f), TypeInfo_get(f), arena); - } - upb_Message_Set(msg, f, msgval, arena); -} - -VALUE Message_getfield(VALUE _self, const upb_FieldDef* f) { - Message* self = ruby_to_Message(_self); - // This is a special-case: upb_Message_Mutable() for map & array are logically - // const (they will not change what is serialized) but physically - // non-const, as they do allocate a repeated field or map. The logical - // constness means it's ok to do even if the message is frozen. - upb_Message* msg = (upb_Message*)self->msg; - upb_Arena* arena = Arena_get(self->arena); - if (upb_FieldDef_IsMap(f)) { - upb_Map* map = upb_Message_Mutable(msg, f, arena).map; - const upb_FieldDef* key_f = map_field_key(f); - const upb_FieldDef* val_f = map_field_value(f); - upb_CType key_type = upb_FieldDef_CType(key_f); - TypeInfo value_type_info = TypeInfo_get(val_f); - return Map_GetRubyWrapper(map, key_type, value_type_info, self->arena); - } else if (upb_FieldDef_IsRepeated(f)) { - upb_Array* arr = upb_Message_Mutable(msg, f, arena).array; - return RepeatedField_GetRubyWrapper(arr, TypeInfo_get(f), self->arena); - } else if (upb_FieldDef_IsSubMessage(f)) { - if (!upb_Message_Has(self->msg, f)) return Qnil; - upb_Message* submsg = upb_Message_Mutable(msg, f, arena).msg; - const upb_MessageDef* m = upb_FieldDef_MessageSubDef(f); - return Message_GetRubyWrapper(submsg, m, self->arena); - } else { - upb_MessageValue msgval = upb_Message_Get(self->msg, f); - return Convert_UpbToRuby(msgval, TypeInfo_get(f), self->arena); - } -} - -static VALUE Message_field_accessor(VALUE _self, const upb_FieldDef* f, - int accessor_type, int argc, VALUE* argv) { - upb_Arena* arena = Arena_get(Message_GetArena(_self)); - - switch (accessor_type) { - case METHOD_SETTER: - Message_setfield(Message_GetMutable(_self, NULL), f, argv[1], arena); - return Qnil; - case METHOD_CLEAR: - upb_Message_ClearField(Message_GetMutable(_self, NULL), f); - return Qnil; - case METHOD_PRESENCE: - if (!upb_FieldDef_HasPresence(f)) { - rb_raise(rb_eRuntimeError, "Field does not have presence."); - } - return upb_Message_Has(Message_Get(_self, NULL), f); - case METHOD_WRAPPER_GETTER: { - Message* self = ruby_to_Message(_self); - if (upb_Message_Has(self->msg, f)) { - PBRUBY_ASSERT(upb_FieldDef_IsSubMessage(f) && - !upb_FieldDef_IsRepeated(f)); - upb_MessageValue wrapper = upb_Message_Get(self->msg, f); - const upb_MessageDef* wrapper_m = upb_FieldDef_MessageSubDef(f); - const upb_FieldDef* value_f = - upb_MessageDef_FindFieldByNumber(wrapper_m, 1); - upb_MessageValue value = upb_Message_Get(wrapper.msg_val, value_f); - return Convert_UpbToRuby(value, TypeInfo_get(value_f), self->arena); - } else { - return Qnil; - } - } - case METHOD_WRAPPER_SETTER: { - upb_Message* msg = Message_GetMutable(_self, NULL); - if (argv[1] == Qnil) { - upb_Message_ClearField(msg, f); - } else { - const upb_FieldDef* val_f = - upb_MessageDef_FindFieldByNumber(upb_FieldDef_MessageSubDef(f), 1); - upb_MessageValue msgval = Convert_RubyToUpb( - argv[1], upb_FieldDef_Name(f), TypeInfo_get(val_f), arena); - upb_Message* wrapper = upb_Message_Mutable(msg, f, arena).msg; - upb_Message_Set(wrapper, val_f, msgval, arena); - } - return Qnil; - } - case METHOD_ENUM_GETTER: { - upb_MessageValue msgval = upb_Message_Get(Message_Get(_self, NULL), f); - - if (upb_FieldDef_Label(f) == kUpb_Label_Repeated) { - // Map repeated fields to a new type with ints - VALUE arr = rb_ary_new(); - size_t i, n = upb_Array_Size(msgval.array_val); - for (i = 0; i < n; i++) { - upb_MessageValue elem = upb_Array_Get(msgval.array_val, i); - rb_ary_push(arr, INT2NUM(elem.int32_val)); - } - return arr; - } else { - return INT2NUM(msgval.int32_val); - } - } - case METHOD_GETTER: - return Message_getfield(_self, f); - default: - rb_raise(rb_eRuntimeError, "Internal error, no such accessor: %d", - accessor_type); - } -} - -/* - * call-seq: - * Message.method_missing(*args) - * - * Provides accessors and setters and methods to clear and check for presence of - * message fields according to their field names. - * - * For any field whose name does not conflict with a built-in method, an - * accessor is provided with the same name as the field, and a setter is - * provided with the name of the field plus the '=' suffix. Thus, given a - * message instance 'msg' with field 'foo', the following code is valid: - * - * msg.foo = 42 - * puts msg.foo - * - * This method also provides read-only accessors for oneofs. If a oneof exists - * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to - * the name of the field in that oneof that is currently set, or nil if none. - * - * It also provides methods of the form 'clear_fieldname' to clear the value - * of the field 'fieldname'. For basic data types, this will set the default - * value of the field. - * - * Additionally, it provides methods of the form 'has_fieldname?', which returns - * true if the field 'fieldname' is set in the message object, else false. For - * 'proto3' syntax, calling this for a basic type field will result in an error. - */ -static VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) { - Message* self = ruby_to_Message(_self); - const upb_OneofDef* o; - const upb_FieldDef* f; - int accessor_type; - - if (argc < 1) { - rb_raise(rb_eArgError, "Expected method name as first argument."); - } - - accessor_type = extract_method_call(argv[0], self, &f, &o); - - if (accessor_type == METHOD_UNKNOWN) return rb_call_super(argc, argv); - - // Validate argument count. - switch (accessor_type) { - case METHOD_SETTER: - case METHOD_WRAPPER_SETTER: - if (argc != 2) { - rb_raise(rb_eArgError, "Expected 2 arguments, received %d", argc); - } - rb_check_frozen(_self); - break; - default: - if (argc != 1) { - rb_raise(rb_eArgError, "Expected 1 argument, received %d", argc); - } - break; - } - - // Dispatch accessor. - if (o != NULL) { - return Message_oneof_accessor(_self, o, accessor_type); - } else { - return Message_field_accessor(_self, f, accessor_type, argc, argv); - } -} - -static VALUE Message_respond_to_missing(int argc, VALUE* argv, VALUE _self) { - Message* self = ruby_to_Message(_self); - const upb_OneofDef* o; - const upb_FieldDef* f; - int accessor_type; - - if (argc < 1) { - rb_raise(rb_eArgError, "Expected method name as first argument."); - } - - accessor_type = extract_method_call(argv[0], self, &f, &o); - - if (accessor_type == METHOD_UNKNOWN) { - return rb_call_super(argc, argv); - } else if (o != NULL) { - return accessor_type == METHOD_SETTER ? Qfalse : Qtrue; - } else { - return Qtrue; - } -} - -void Message_InitFromValue(upb_Message* msg, const upb_MessageDef* m, VALUE val, - upb_Arena* arena); - -typedef struct { - upb_Map* map; - TypeInfo key_type; - TypeInfo val_type; - upb_Arena* arena; -} MapInit; - -static int Map_initialize_kwarg(VALUE key, VALUE val, VALUE _self) { - MapInit* map_init = (MapInit*)_self; - upb_MessageValue k, v; - k = Convert_RubyToUpb(key, "", map_init->key_type, NULL); - - if (map_init->val_type.type == kUpb_CType_Message && TYPE(val) == T_HASH) { - upb_Message* msg = - upb_Message_New(map_init->val_type.def.msgdef, map_init->arena); - Message_InitFromValue(msg, map_init->val_type.def.msgdef, val, - map_init->arena); - v.msg_val = msg; - } else { - v = Convert_RubyToUpb(val, "", map_init->val_type, map_init->arena); - } - upb_Map_Set(map_init->map, k, v, map_init->arena); - return ST_CONTINUE; -} - -static void Map_InitFromValue(upb_Map* map, const upb_FieldDef* f, VALUE val, - upb_Arena* arena) { - const upb_MessageDef* entry_m = upb_FieldDef_MessageSubDef(f); - const upb_FieldDef* key_f = upb_MessageDef_FindFieldByNumber(entry_m, 1); - const upb_FieldDef* val_f = upb_MessageDef_FindFieldByNumber(entry_m, 2); - if (TYPE(val) != T_HASH) { - rb_raise(rb_eArgError, - "Expected Hash object as initializer value for map field '%s' " - "(given %s).", - upb_FieldDef_Name(f), rb_class2name(CLASS_OF(val))); - } - MapInit map_init = {map, TypeInfo_get(key_f), TypeInfo_get(val_f), arena}; - rb_hash_foreach(val, Map_initialize_kwarg, (VALUE)&map_init); -} - -static upb_MessageValue MessageValue_FromValue(VALUE val, TypeInfo info, - upb_Arena* arena) { - if (info.type == kUpb_CType_Message) { - upb_MessageValue msgval; - upb_Message* msg = upb_Message_New(info.def.msgdef, arena); - Message_InitFromValue(msg, info.def.msgdef, val, arena); - msgval.msg_val = msg; - return msgval; - } else { - return Convert_RubyToUpb(val, "", info, arena); - } -} - -static void RepeatedField_InitFromValue(upb_Array* arr, const upb_FieldDef* f, - VALUE val, upb_Arena* arena) { - TypeInfo type_info = TypeInfo_get(f); - - if (TYPE(val) != T_ARRAY) { - rb_raise(rb_eArgError, - "Expected array as initializer value for repeated field '%s' " - "(given %s).", - upb_FieldDef_Name(f), rb_class2name(CLASS_OF(val))); - } - - for (int i = 0; i < RARRAY_LEN(val); i++) { - VALUE entry = rb_ary_entry(val, i); - upb_MessageValue msgval; - if (upb_FieldDef_IsSubMessage(f) && TYPE(entry) == T_HASH) { - msgval = MessageValue_FromValue(entry, type_info, arena); - } else { - msgval = Convert_RubyToUpb(entry, upb_FieldDef_Name(f), type_info, arena); - } - upb_Array_Append(arr, msgval, arena); - } -} - -static void Message_InitFieldFromValue(upb_Message* msg, const upb_FieldDef* f, - VALUE val, upb_Arena* arena) { - if (TYPE(val) == T_NIL) return; - - if (upb_FieldDef_IsMap(f)) { - upb_Map* map = upb_Message_Mutable(msg, f, arena).map; - Map_InitFromValue(map, f, val, arena); - } else if (upb_FieldDef_Label(f) == kUpb_Label_Repeated) { - upb_Array* arr = upb_Message_Mutable(msg, f, arena).array; - RepeatedField_InitFromValue(arr, f, val, arena); - } else if (upb_FieldDef_IsSubMessage(f)) { - if (TYPE(val) == T_HASH) { - upb_Message* submsg = upb_Message_Mutable(msg, f, arena).msg; - Message_InitFromValue(submsg, upb_FieldDef_MessageSubDef(f), val, arena); - } else { - Message_setfield(msg, f, val, arena); - } - } else { - upb_MessageValue msgval = - Convert_RubyToUpb(val, upb_FieldDef_Name(f), TypeInfo_get(f), arena); - upb_Message_Set(msg, f, msgval, arena); - } -} - -typedef struct { - upb_Message* msg; - const upb_MessageDef* msgdef; - upb_Arena* arena; -} MsgInit; - -static int Message_initialize_kwarg(VALUE key, VALUE val, VALUE _self) { - MsgInit* msg_init = (MsgInit*)_self; - const char* name; - - if (TYPE(key) == T_STRING) { - name = RSTRING_PTR(key); - } else if (TYPE(key) == T_SYMBOL) { - name = RSTRING_PTR(rb_id2str(SYM2ID(key))); - } else { - rb_raise(rb_eArgError, - "Expected string or symbols as hash keys when initializing proto " - "from hash."); - } - - const upb_FieldDef* f = - upb_MessageDef_FindFieldByName(msg_init->msgdef, name); - - if (f == NULL) { - rb_raise(rb_eArgError, - "Unknown field name '%s' in initialization map entry.", name); - } - - Message_InitFieldFromValue(msg_init->msg, f, val, msg_init->arena); - return ST_CONTINUE; -} - -void Message_InitFromValue(upb_Message* msg, const upb_MessageDef* m, VALUE val, - upb_Arena* arena) { - MsgInit msg_init = {msg, m, arena}; - if (TYPE(val) == T_HASH) { - rb_hash_foreach(val, Message_initialize_kwarg, (VALUE)&msg_init); - } else { - rb_raise(rb_eArgError, "Expected hash arguments or message, not %s", - rb_class2name(CLASS_OF(val))); - } -} - -/* - * call-seq: - * Message.new(kwargs) => new_message - * - * Creates a new instance of the given message class. Keyword arguments may be - * provided with keywords corresponding to field names. - * - * Note that no literal Message class exists. Only concrete classes per message - * type exist, as provided by the #msgclass method on Descriptors after they - * have been added to a pool. The method definitions described here on the - * Message class are provided on each concrete message class. - */ -static VALUE Message_initialize(int argc, VALUE* argv, VALUE _self) { - Message* self = ruby_to_Message(_self); - VALUE arena_rb = Arena_new(); - upb_Arena* arena = Arena_get(arena_rb); - upb_Message* msg = upb_Message_New(self->msgdef, arena); - - Message_InitPtr(_self, msg, arena_rb); - - if (argc == 0) { - return Qnil; - } - if (argc != 1) { - rb_raise(rb_eArgError, "Expected 0 or 1 arguments."); - } - Message_InitFromValue((upb_Message*)self->msg, self->msgdef, argv[0], arena); - return Qnil; -} - -/* - * call-seq: - * Message.dup => new_message - * - * Performs a shallow copy of this message and returns the new copy. - */ -static VALUE Message_dup(VALUE _self) { - Message* self = ruby_to_Message(_self); - VALUE new_msg = rb_class_new_instance(0, NULL, CLASS_OF(_self)); - Message* new_msg_self = ruby_to_Message(new_msg); - size_t size = upb_MessageDef_MiniTable(self->msgdef)->size; - - // TODO(copy unknown fields?) - // TODO(use official upb msg copy function) - memcpy((upb_Message*)new_msg_self->msg, self->msg, size); - Arena_fuse(self->arena, Arena_get(new_msg_self->arena)); - return new_msg; -} - // Support function for Message_eq, and also used by other #eq functions. bool Message_Equal(const upb_Message* m1, const upb_Message* m2, const upb_MessageDef* m) { @@ -710,29 +50,10 @@ bool Message_Equal(const upb_Message* m1, const upb_Message* m2, return ret; } else { upb_Arena_Free(arena_tmp); - rb_raise(cParseError, "Error comparing messages"); + assert(0); } } -/* - * call-seq: - * Message.==(other) => boolean - * - * Performs a deep comparison of this message with another. Messages are equal - * if they have the same type and if each field is equal according to the :== - * method's semantics (a more efficient comparison may actually be done if the - * field is of a primitive type). - */ -static VALUE Message_eq(VALUE _self, VALUE _other) { - if (CLASS_OF(_self) != CLASS_OF(_other)) return Qfalse; - - Message* self = ruby_to_Message(_self); - Message* other = ruby_to_Message(_other); - assert(self->msgdef == other->msgdef); - - return Message_Equal(self->msg, other->msg, self->msgdef) ? Qtrue : Qfalse; -} - uint64_t Message_Hash(const upb_Message* msg, const upb_MessageDef* m, uint64_t seed) { upb_Arena* arena = upb_Arena_New(); @@ -750,656 +71,6 @@ uint64_t Message_Hash(const upb_Message* msg, const upb_MessageDef* m, return ret; } else { upb_Arena_Free(arena); - rb_raise(cParseError, "Error calculating hash"); - } -} - -/* - * call-seq: - * Message.hash => hash_value - * - * Returns a hash value that represents this message's field values. - */ -static VALUE Message_hash(VALUE _self) { - Message* self = ruby_to_Message(_self); - uint64_t hash_value = Message_Hash(self->msg, self->msgdef, 0); - // RUBY_FIXNUM_MAX should be one less than a power of 2. - assert((RUBY_FIXNUM_MAX & (RUBY_FIXNUM_MAX + 1)) == 0); - return INT2FIX(hash_value & RUBY_FIXNUM_MAX); -} - -/* - * call-seq: - * Message.inspect => string - * - * Returns a human-readable string representing this message. It will be - * formatted as "". Each - * field's value is represented according to its own #inspect method. - */ -static VALUE Message_inspect(VALUE _self) { - Message* self = ruby_to_Message(_self); - - StringBuilder* builder = StringBuilder_New(); - Message_PrintMessage(builder, self->msg, self->msgdef); - VALUE ret = StringBuilder_ToRubyString(builder); - StringBuilder_Free(builder); - return ret; -} - -// Support functions for Message_to_h ////////////////////////////////////////// - -static VALUE RepeatedField_CreateArray(const upb_Array* arr, - TypeInfo type_info) { - int size = arr ? upb_Array_Size(arr) : 0; - VALUE ary = rb_ary_new2(size); - - for (int i = 0; i < size; i++) { - upb_MessageValue msgval = upb_Array_Get(arr, i); - VALUE val = Scalar_CreateHash(msgval, type_info); - rb_ary_push(ary, val); - } - - return ary; -} - -static VALUE Message_CreateHash(const upb_Message* msg, - const upb_MessageDef* m) { - if (!msg) return Qnil; - - VALUE hash = rb_hash_new(); - int n = upb_MessageDef_FieldCount(m); - bool is_proto2; - - // We currently have a few behaviors that are specific to proto2. - // This is unfortunate, we should key behaviors off field attributes (like - // whether a field has presence), not proto2 vs. proto3. We should see if we - // can change this without breaking users. - is_proto2 = upb_MessageDef_Syntax(m) == kUpb_Syntax_Proto2; - - for (int i = 0; i < n; i++) { - const upb_FieldDef* field = upb_MessageDef_Field(m, i); - TypeInfo type_info = TypeInfo_get(field); - upb_MessageValue msgval; - VALUE msg_value; - VALUE msg_key; - - if (!is_proto2 && upb_FieldDef_IsSubMessage(field) && - !upb_FieldDef_IsRepeated(field) && !upb_Message_Has(msg, field)) { - // TODO: Legacy behavior, remove when we fix the is_proto2 differences. - msg_key = ID2SYM(rb_intern(upb_FieldDef_Name(field))); - rb_hash_aset(hash, msg_key, Qnil); - continue; - } - - // Do not include fields that are not present (oneof or optional fields). - if (is_proto2 && upb_FieldDef_HasPresence(field) && - !upb_Message_Has(msg, field)) { - continue; - } - - msg_key = ID2SYM(rb_intern(upb_FieldDef_Name(field))); - msgval = upb_Message_Get(msg, field); - - // Proto2 omits empty map/repeated filds also. - - if (upb_FieldDef_IsMap(field)) { - const upb_MessageDef* entry_m = upb_FieldDef_MessageSubDef(field); - const upb_FieldDef* key_f = upb_MessageDef_FindFieldByNumber(entry_m, 1); - const upb_FieldDef* val_f = upb_MessageDef_FindFieldByNumber(entry_m, 2); - upb_CType key_type = upb_FieldDef_CType(key_f); - msg_value = Map_CreateHash(msgval.map_val, key_type, TypeInfo_get(val_f)); - } else if (upb_FieldDef_IsRepeated(field)) { - if (is_proto2 && - (!msgval.array_val || upb_Array_Size(msgval.array_val) == 0)) { - continue; - } - msg_value = RepeatedField_CreateArray(msgval.array_val, type_info); - } else { - msg_value = Scalar_CreateHash(msgval, type_info); - } - - rb_hash_aset(hash, msg_key, msg_value); - } - - return hash; -} - -VALUE Scalar_CreateHash(upb_MessageValue msgval, TypeInfo type_info) { - if (type_info.type == kUpb_CType_Message) { - return Message_CreateHash(msgval.msg_val, type_info.def.msgdef); - } else { - return Convert_UpbToRuby(msgval, type_info, Qnil); - } -} - -/* - * call-seq: - * Message.to_h => {} - * - * Returns the message as a Ruby Hash object, with keys as symbols. - */ -static VALUE Message_to_h(VALUE _self) { - Message* self = ruby_to_Message(_self); - return Message_CreateHash(self->msg, self->msgdef); -} - -/* - * call-seq: - * Message.freeze => self - * - * Freezes the message object. We have to intercept this so we can pin the - * Ruby object into memory so we don't forget it's frozen. - */ -static VALUE Message_freeze(VALUE _self) { - Message* self = ruby_to_Message(_self); - if (!RB_OBJ_FROZEN(_self)) { - Arena_Pin(self->arena, _self); - RB_OBJ_FREEZE(_self); - } - return _self; -} - -/* - * call-seq: - * Message.[](index) => value - * - * Accesses a field's value by field name. The provided field name should be a - * string. - */ -static VALUE Message_index(VALUE _self, VALUE field_name) { - Message* self = ruby_to_Message(_self); - const upb_FieldDef* field; - - Check_Type(field_name, T_STRING); - field = upb_MessageDef_FindFieldByName(self->msgdef, RSTRING_PTR(field_name)); - - if (field == NULL) { - return Qnil; - } - - return Message_getfield(_self, field); -} - -/* - * call-seq: - * Message.[]=(index, value) - * - * Sets a field's value by field name. The provided field name should be a - * string. - */ -static VALUE Message_index_set(VALUE _self, VALUE field_name, VALUE value) { - Message* self = ruby_to_Message(_self); - const upb_FieldDef* f; - upb_MessageValue val; - upb_Arena* arena = Arena_get(self->arena); - - Check_Type(field_name, T_STRING); - f = upb_MessageDef_FindFieldByName(self->msgdef, RSTRING_PTR(field_name)); - - if (f == NULL) { - rb_raise(rb_eArgError, "Unknown field: %s", RSTRING_PTR(field_name)); - } - - val = Convert_RubyToUpb(value, upb_FieldDef_Name(f), TypeInfo_get(f), arena); - upb_Message_Set(Message_GetMutable(_self, NULL), f, val, arena); - - return Qnil; -} - -/* - * call-seq: - * MessageClass.decode(data, options) => message - * - * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretration given by this message class's definition - * and returns a message object with the corresponding field values. - * @param options [Hash] options for the decoder - * recursion_limit: set to maximum decoding depth for message (default is 64) - */ -static VALUE Message_decode(int argc, VALUE* argv, VALUE klass) { - VALUE data = argv[0]; - int options = 0; - - if (argc < 1 || argc > 2) { - rb_raise(rb_eArgError, "Expected 1 or 2 arguments."); - } - - if (argc == 2) { - VALUE hash_args = argv[1]; - if (TYPE(hash_args) != T_HASH) { - rb_raise(rb_eArgError, "Expected hash arguments."); - } - - VALUE depth = rb_hash_lookup(hash_args, ID2SYM(rb_intern("recursion_limit"))); - - if (depth != Qnil && TYPE(depth) == T_FIXNUM) { - options |= UPB_DECODE_MAXDEPTH(FIX2INT(depth)); - } - } - - if (TYPE(data) != T_STRING) { - rb_raise(rb_eArgError, "Expected string for binary protobuf data."); - } - - VALUE msg_rb = initialize_rb_class_with_no_args(klass); - Message* msg = ruby_to_Message(msg_rb); - - upb_DecodeStatus status = upb_Decode( - RSTRING_PTR(data), RSTRING_LEN(data), (upb_Message*)msg->msg, - upb_MessageDef_MiniTable(msg->msgdef), NULL, options, Arena_get(msg->arena)); - - if (status != kUpb_DecodeStatus_Ok) { - rb_raise(cParseError, "Error occurred during parsing"); - } - - return msg_rb; -} - -/* - * call-seq: - * MessageClass.decode_json(data, options = {}) => message - * - * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretration given by this message class's definition - * and returns a message object with the corresponding field values. - * - * @param options [Hash] options for the decoder - * ignore_unknown_fields: set true to ignore unknown fields (default is to - * raise an error) - */ -static VALUE Message_decode_json(int argc, VALUE* argv, VALUE klass) { - VALUE data = argv[0]; - int options = 0; - upb_Status status; - - // TODO(haberman): use this message's pool instead. - const upb_DefPool* symtab = DescriptorPool_GetSymtab(generated_pool); - - if (argc < 1 || argc > 2) { - rb_raise(rb_eArgError, "Expected 1 or 2 arguments."); - } - - if (argc == 2) { - VALUE hash_args = argv[1]; - if (TYPE(hash_args) != T_HASH) { - rb_raise(rb_eArgError, "Expected hash arguments."); - } - - if (RTEST(rb_hash_lookup2( - hash_args, ID2SYM(rb_intern("ignore_unknown_fields")), Qfalse))) { - options |= upb_JsonDecode_IgnoreUnknown; - } - } - - if (TYPE(data) != T_STRING) { - rb_raise(rb_eArgError, "Expected string for JSON data."); - } - - // TODO(cfallin): Check and respect string encoding. If not UTF-8, we need to - // convert, because string handlers pass data directly to message string - // fields. - - VALUE msg_rb = initialize_rb_class_with_no_args(klass); - Message* msg = ruby_to_Message(msg_rb); - - // We don't allow users to decode a wrapper type directly. - if (IsWrapper(msg->msgdef)) { - rb_raise(rb_eRuntimeError, "Cannot parse a wrapper directly."); - } - - upb_Status_Clear(&status); - if (!upb_JsonDecode(RSTRING_PTR(data), RSTRING_LEN(data), - (upb_Message*)msg->msg, msg->msgdef, symtab, options, - Arena_get(msg->arena), &status)) { - rb_raise(cParseError, "Error occurred during parsing: %s", - upb_Status_ErrorMessage(&status)); - } - - return msg_rb; -} - -/* - * call-seq: - * MessageClass.encode(msg, options) => bytes - * - * Encodes the given message object to its serialized form in protocol buffers - * wire format. - * @param options [Hash] options for the encoder - * recursion_limit: set to maximum encoding depth for message (default is 64) - */ -static VALUE Message_encode(int argc, VALUE* argv, VALUE klass) { - Message* msg = ruby_to_Message(argv[0]); - int options = 0; - const char* data; - size_t size; - - if (CLASS_OF(argv[0]) != klass) { - rb_raise(rb_eArgError, "Message of wrong type."); - } - - if (argc < 1 || argc > 2) { - rb_raise(rb_eArgError, "Expected 1 or 2 arguments."); - } - - if (argc == 2) { - VALUE hash_args = argv[1]; - if (TYPE(hash_args) != T_HASH) { - rb_raise(rb_eArgError, "Expected hash arguments."); - } - VALUE depth = rb_hash_lookup(hash_args, ID2SYM(rb_intern("recursion_limit"))); - - if (depth != Qnil && TYPE(depth) == T_FIXNUM) { - options |= UPB_DECODE_MAXDEPTH(FIX2INT(depth)); - } - } - - upb_Arena *arena = upb_Arena_New(); - - data = upb_Encode(msg->msg, upb_MessageDef_MiniTable(msg->msgdef), - options, arena, &size); - - if (data) { - VALUE ret = rb_str_new(data, size); - rb_enc_associate(ret, rb_ascii8bit_encoding()); - upb_Arena_Free(arena); - return ret; - } else { - upb_Arena_Free(arena); - rb_raise(rb_eRuntimeError, "Exceeded maximum depth (possibly cycle)"); - } -} - -/* - * call-seq: - * MessageClass.encode_json(msg, options = {}) => json_string - * - * Encodes the given message object into its serialized JSON representation. - * @param options [Hash] options for the decoder - * preserve_proto_fieldnames: set true to use original fieldnames (default is - * to camelCase) emit_defaults: set true to emit 0/false values (default is to - * omit them) - */ -static VALUE Message_encode_json(int argc, VALUE* argv, VALUE klass) { - Message* msg = ruby_to_Message(argv[0]); - int options = 0; - char buf[1024]; - size_t size; - upb_Status status; - - // TODO(haberman): use this message's pool instead. - const upb_DefPool* symtab = DescriptorPool_GetSymtab(generated_pool); - - if (argc < 1 || argc > 2) { - rb_raise(rb_eArgError, "Expected 1 or 2 arguments."); - } - - if (argc == 2) { - VALUE hash_args = argv[1]; - if (TYPE(hash_args) != T_HASH) { - if (RTEST(rb_funcall(hash_args, rb_intern("respond_to?"), 1, rb_str_new2("to_h")))) { - hash_args = rb_funcall(hash_args, rb_intern("to_h"), 0); - } else { - rb_raise(rb_eArgError, "Expected hash arguments."); - } - } - - if (RTEST(rb_hash_lookup2(hash_args, - ID2SYM(rb_intern("preserve_proto_fieldnames")), - Qfalse))) { - options |= upb_JsonEncode_UseProtoNames; - } - - if (RTEST(rb_hash_lookup2(hash_args, ID2SYM(rb_intern("emit_defaults")), - Qfalse))) { - options |= upb_JsonEncode_EmitDefaults; - } - } - - upb_Status_Clear(&status); - size = upb_JsonEncode(msg->msg, msg->msgdef, symtab, options, buf, - sizeof(buf), &status); - - if (!upb_Status_IsOk(&status)) { - rb_raise(cParseError, "Error occurred during encoding: %s", - upb_Status_ErrorMessage(&status)); - } - - VALUE ret; - if (size >= sizeof(buf)) { - char* buf2 = malloc(size + 1); - upb_JsonEncode(msg->msg, msg->msgdef, symtab, options, buf2, size + 1, - &status); - ret = rb_str_new(buf2, size); - free(buf2); - } else { - ret = rb_str_new(buf, size); - } - - rb_enc_associate(ret, rb_utf8_encoding()); - return ret; -} - -/* - * call-seq: - * Message.descriptor => descriptor - * - * Class method that returns the Descriptor instance corresponding to this - * message class's type. - */ -static VALUE Message_descriptor(VALUE klass) { - return rb_ivar_get(klass, descriptor_instancevar_interned); -} - -VALUE build_class_from_descriptor(VALUE descriptor) { - const char* name; - VALUE klass; - - name = upb_MessageDef_FullName(Descriptor_GetMsgDef(descriptor)); - if (name == NULL) { - rb_raise(rb_eRuntimeError, "Descriptor does not have assigned name."); - } - - klass = rb_define_class_id( - // Docs say this parameter is ignored. User will assign return value to - // their own toplevel constant class name. - rb_intern("Message"), cAbstractMessage); - rb_ivar_set(klass, descriptor_instancevar_interned, descriptor); - return klass; -} - -/* - * call-seq: - * Enum.lookup(number) => name - * - * This module method, provided on each generated enum module, looks up an enum - * value by number and returns its name as a Ruby symbol, or nil if not found. - */ -static VALUE enum_lookup(VALUE self, VALUE number) { - int32_t num = NUM2INT(number); - VALUE desc = rb_ivar_get(self, descriptor_instancevar_interned); - const upb_EnumDef* e = EnumDescriptor_GetEnumDef(desc); - const upb_EnumValueDef* ev = upb_EnumDef_FindValueByNumber(e, num); - if (ev) { - return ID2SYM(rb_intern(upb_EnumValueDef_Name(ev))); - } else { - return Qnil; - } -} - -/* - * call-seq: - * Enum.resolve(name) => number - * - * This module method, provided on each generated enum module, looks up an enum - * value by name (as a Ruby symbol) and returns its name, or nil if not found. - */ -static VALUE enum_resolve(VALUE self, VALUE sym) { - const char* name = rb_id2name(SYM2ID(sym)); - VALUE desc = rb_ivar_get(self, descriptor_instancevar_interned); - const upb_EnumDef* e = EnumDescriptor_GetEnumDef(desc); - const upb_EnumValueDef* ev = upb_EnumDef_FindValueByName(e, name); - if (ev) { - return INT2NUM(upb_EnumValueDef_Number(ev)); - } else { - return Qnil; - } -} - -/* - * call-seq: - * Enum.descriptor - * - * This module method, provided on each generated enum module, returns the - * EnumDescriptor corresponding to this enum type. - */ -static VALUE enum_descriptor(VALUE self) { - return rb_ivar_get(self, descriptor_instancevar_interned); -} - -VALUE build_module_from_enumdesc(VALUE _enumdesc) { - const upb_EnumDef* e = EnumDescriptor_GetEnumDef(_enumdesc); - VALUE mod = rb_define_module_id(rb_intern(upb_EnumDef_FullName(e))); - - int n = upb_EnumDef_ValueCount(e); - for (int i = 0; i < n; i++) { - const upb_EnumValueDef* ev = upb_EnumDef_Value(e, i); - const char* name = upb_EnumValueDef_Name(ev); - int32_t value = upb_EnumValueDef_Number(ev); - if (name[0] < 'A' || name[0] > 'Z') { - rb_warn( - "Enum value '%s' does not start with an uppercase letter " - "as is required for Ruby constants.", - name); - } - rb_define_const(mod, name, INT2NUM(value)); - } - - rb_define_singleton_method(mod, "lookup", enum_lookup, 1); - rb_define_singleton_method(mod, "resolve", enum_resolve, 1); - rb_define_singleton_method(mod, "descriptor", enum_descriptor, 0); - rb_ivar_set(mod, descriptor_instancevar_interned, _enumdesc); - - return mod; -} - -// Internal only; used by Google::Protobuf.deep_copy. -upb_Message* Message_deep_copy(const upb_Message* msg, const upb_MessageDef* m, - upb_Arena* arena) { - // Serialize and parse. - upb_Arena* tmp_arena = upb_Arena_New(); - const upb_MiniTable* layout = upb_MessageDef_MiniTable(m); - size_t size; - - char* data = upb_Encode(msg, layout, 0, tmp_arena, &size); - upb_Message* new_msg = upb_Message_New(m, arena); - - if (!data || upb_Decode(data, size, new_msg, layout, NULL, 0, arena) != - kUpb_DecodeStatus_Ok) { - upb_Arena_Free(tmp_arena); - rb_raise(cParseError, "Error occurred copying proto"); + assert(0); } - - upb_Arena_Free(tmp_arena); - return new_msg; -} - -const upb_Message* Message_GetUpbMessage(VALUE value, const upb_MessageDef* m, - const char* name, upb_Arena* arena) { - if (value == Qnil) { - rb_raise(cTypeError, "nil message not allowed here."); - } - - VALUE klass = CLASS_OF(value); - VALUE desc_rb = rb_ivar_get(klass, descriptor_instancevar_interned); - const upb_MessageDef* val_m = - desc_rb == Qnil ? NULL : Descriptor_GetMsgDef(desc_rb); - - if (val_m != m) { - // Check for possible implicit conversions - // TODO: hash conversion? - - switch (upb_MessageDef_WellKnownType(m)) { - case kUpb_WellKnown_Timestamp: { - // Time -> Google::Protobuf::Timestamp - upb_Message* msg = upb_Message_New(m, arena); - upb_MessageValue sec, nsec; - struct timespec time; - const upb_FieldDef* sec_f = upb_MessageDef_FindFieldByNumber(m, 1); - const upb_FieldDef* nsec_f = upb_MessageDef_FindFieldByNumber(m, 2); - - if (!rb_obj_is_kind_of(value, rb_cTime)) goto badtype; - - time = rb_time_timespec(value); - sec.int64_val = time.tv_sec; - nsec.int32_val = time.tv_nsec; - upb_Message_Set(msg, sec_f, sec, arena); - upb_Message_Set(msg, nsec_f, nsec, arena); - return msg; - } - case kUpb_WellKnown_Duration: { - // Numeric -> Google::Protobuf::Duration - upb_Message* msg = upb_Message_New(m, arena); - upb_MessageValue sec, nsec; - const upb_FieldDef* sec_f = upb_MessageDef_FindFieldByNumber(m, 1); - const upb_FieldDef* nsec_f = upb_MessageDef_FindFieldByNumber(m, 2); - - if (!rb_obj_is_kind_of(value, rb_cNumeric)) goto badtype; - - sec.int64_val = NUM2LL(value); - nsec.int32_val = round((NUM2DBL(value) - NUM2LL(value)) * 1000000000); - upb_Message_Set(msg, sec_f, sec, arena); - upb_Message_Set(msg, nsec_f, nsec, arena); - return msg; - } - default: - badtype: - rb_raise(cTypeError, - "Invalid type %s to assign to submessage field '%s'.", - rb_class2name(CLASS_OF(value)), name); - } - } - - Message* self = ruby_to_Message(value); - Arena_fuse(self->arena, arena); - - return self->msg; -} - -static void Message_define_class(VALUE klass) { - rb_define_alloc_func(klass, Message_alloc); - - rb_require("google/protobuf/message_exts"); - rb_define_method(klass, "method_missing", Message_method_missing, -1); - rb_define_method(klass, "respond_to_missing?", Message_respond_to_missing, - -1); - rb_define_method(klass, "initialize", Message_initialize, -1); - rb_define_method(klass, "dup", Message_dup, 0); - // Also define #clone so that we don't inherit Object#clone. - rb_define_method(klass, "clone", Message_dup, 0); - rb_define_method(klass, "==", Message_eq, 1); - rb_define_method(klass, "eql?", Message_eq, 1); - rb_define_method(klass, "freeze", Message_freeze, 0); - rb_define_method(klass, "hash", Message_hash, 0); - rb_define_method(klass, "to_h", Message_to_h, 0); - rb_define_method(klass, "inspect", Message_inspect, 0); - rb_define_method(klass, "to_s", Message_inspect, 0); - rb_define_method(klass, "[]", Message_index, 1); - rb_define_method(klass, "[]=", Message_index_set, 2); - rb_define_singleton_method(klass, "decode", Message_decode, -1); - rb_define_singleton_method(klass, "encode", Message_encode, -1); - rb_define_singleton_method(klass, "decode_json", Message_decode_json, -1); - rb_define_singleton_method(klass, "encode_json", Message_encode_json, -1); - rb_define_singleton_method(klass, "descriptor", Message_descriptor, 0); -} - -void Message_register(VALUE protobuf) { - cParseError = rb_const_get(protobuf, rb_intern("ParseError")); - cAbstractMessage = rb_define_class_under(protobuf, "AbstractMessage", rb_cObject); - Message_define_class(cAbstractMessage); - rb_gc_register_address(&cAbstractMessage); - - // Ruby-interned string: "descriptor". We use this identifier to store an - // instance variable on message classes we create in order to link them back - // to their descriptors. - descriptor_instancevar_interned = rb_intern("@descriptor"); } diff --git a/ruby/ext/google/protobuf_c/message.h b/ruby/ext/google/protobuf_c/message.h index b409650ef8c9..52a5433297b2 100644 --- a/ruby/ext/google/protobuf_c/message.h +++ b/ruby/ext/google/protobuf_c/message.h @@ -31,74 +31,14 @@ #ifndef RUBY_PROTOBUF_MESSAGE_H_ #define RUBY_PROTOBUF_MESSAGE_H_ -#include - -#include "protobuf.h" #include "ruby-upb.h" -// Gets the underlying upb_Message* and upb_MessageDef for the given Ruby -// message wrapper. Requires that |value| is indeed a message object. -const upb_Message* Message_Get(VALUE value, const upb_MessageDef** m); - -// Like Message_Get(), but checks that the object is not frozen and returns a -// mutable pointer. -upb_Message* Message_GetMutable(VALUE value, const upb_MessageDef** m); - -// Returns the Arena object for this message. -VALUE Message_GetArena(VALUE value); - -// Converts |value| into a upb_Message value of the expected upb_MessageDef -// type, raising an error if this is not possible. Used when assigning |value| -// to a field of another message, which means the message must be of a -// particular type. -// -// This will perform automatic conversions in some cases (for example, Time -> -// Google::Protobuf::Timestamp). If any new message is created, it will be -// created on |arena|, and any existing message will have its arena fused with -// |arena|. -const upb_Message* Message_GetUpbMessage(VALUE value, const upb_MessageDef* m, - const char* name, upb_Arena* arena); - -// Gets or constructs a Ruby wrapper object for the given message. The wrapper -// object will reference |arena| and ensure that it outlives this object. -VALUE Message_GetRubyWrapper(upb_Message* msg, const upb_MessageDef* m, - VALUE arena); - -// Gets the given field from this message. -VALUE Message_getfield(VALUE _self, const upb_FieldDef* f); - -// Implements #inspect for this message, printing the text to |b|. -void Message_PrintMessage(StringBuilder* b, const upb_Message* msg, - const upb_MessageDef* m); - // Returns a hash value for the given message. uint64_t Message_Hash(const upb_Message* msg, const upb_MessageDef* m, uint64_t seed); -// Returns a deep copy of the given message. -upb_Message* Message_deep_copy(const upb_Message* msg, const upb_MessageDef* m, - upb_Arena* arena); - // Returns true if these two messages are equal. bool Message_Equal(const upb_Message* m1, const upb_Message* m2, const upb_MessageDef* m); -// Checks that this Ruby object is a message, and raises an exception if not. -void Message_CheckClass(VALUE klass); - -// Returns a new Hash object containing the contents of this message. -VALUE Scalar_CreateHash(upb_MessageValue val, TypeInfo type_info); - -// Creates a message class or enum module for this descriptor, respectively. -VALUE build_class_from_descriptor(VALUE descriptor); -VALUE build_module_from_enumdesc(VALUE _enumdesc); - -// Returns the Descriptor/EnumDescriptor for the given message class or enum -// module, respectively. Returns nil if this is not a message class or enum -// module. -VALUE MessageOrEnum_GetDescriptor(VALUE klass); - -// Call at startup to register all types in this module. -void Message_register(VALUE protobuf); - #endif // RUBY_PROTOBUF_MESSAGE_H_ diff --git a/ruby/ext/google/protobuf_c/protobuf.c b/ruby/ext/google/protobuf_c/protobuf.c index 3c765c564d23..d321cc739331 100644 --- a/ruby/ext/google/protobuf_c/protobuf.c +++ b/ruby/ext/google/protobuf_c/protobuf.c @@ -30,451 +30,10 @@ #include "protobuf.h" -#include - -#include "defs.h" -#include "map.h" -#include "message.h" -#include "repeated_field.h" - -VALUE cParseError; -VALUE cTypeError; - -const upb_FieldDef *map_field_key(const upb_FieldDef *field) { - const upb_MessageDef *entry = upb_FieldDef_MessageSubDef(field); - return upb_MessageDef_FindFieldByNumber(entry, 1); -} - -const upb_FieldDef *map_field_value(const upb_FieldDef *field) { - const upb_MessageDef *entry = upb_FieldDef_MessageSubDef(field); - return upb_MessageDef_FindFieldByNumber(entry, 2); -} - -// ----------------------------------------------------------------------------- -// StringBuilder, for inspect -// ----------------------------------------------------------------------------- - -struct StringBuilder { - size_t size; - size_t cap; - char *data; -}; - -typedef struct StringBuilder StringBuilder; - -static size_t StringBuilder_SizeOf(size_t cap) { - return sizeof(StringBuilder) + cap; -} - -StringBuilder *StringBuilder_New() { - const size_t cap = 128; - StringBuilder *builder = malloc(sizeof(*builder)); - builder->size = 0; - builder->cap = cap; - builder->data = malloc(builder->cap); - return builder; -} - -void StringBuilder_Free(StringBuilder *b) { - free(b->data); - free(b); -} - -void StringBuilder_Printf(StringBuilder *b, const char *fmt, ...) { - size_t have = b->cap - b->size; - size_t n; - va_list args; - - va_start(args, fmt); - n = vsnprintf(&b->data[b->size], have, fmt, args); - va_end(args); - - if (have <= n) { - while (have <= n) { - b->cap *= 2; - have = b->cap - b->size; - } - b->data = realloc(b->data, StringBuilder_SizeOf(b->cap)); - va_start(args, fmt); - n = vsnprintf(&b->data[b->size], have, fmt, args); - va_end(args); - PBRUBY_ASSERT(n < have); - } - - b->size += n; -} - -VALUE StringBuilder_ToRubyString(StringBuilder *b) { - VALUE ret = rb_str_new(b->data, b->size); - rb_enc_associate(ret, rb_utf8_encoding()); - return ret; -} - -static void StringBuilder_PrintEnum(StringBuilder *b, int32_t val, - const upb_EnumDef *e) { - const upb_EnumValueDef *ev = upb_EnumDef_FindValueByNumber(e, val); - if (ev) { - StringBuilder_Printf(b, ":%s", upb_EnumValueDef_Name(ev)); - } else { - StringBuilder_Printf(b, "%" PRId32, val); - } -} - -void StringBuilder_PrintMsgval(StringBuilder *b, upb_MessageValue val, - TypeInfo info) { - switch (info.type) { - case kUpb_CType_Bool: - StringBuilder_Printf(b, "%s", val.bool_val ? "true" : "false"); - break; - case kUpb_CType_Float: { - VALUE str = rb_inspect(DBL2NUM(val.float_val)); - StringBuilder_Printf(b, "%s", RSTRING_PTR(str)); - break; - } - case kUpb_CType_Double: { - VALUE str = rb_inspect(DBL2NUM(val.double_val)); - StringBuilder_Printf(b, "%s", RSTRING_PTR(str)); - break; - } - case kUpb_CType_Int32: - StringBuilder_Printf(b, "%" PRId32, val.int32_val); - break; - case kUpb_CType_UInt32: - StringBuilder_Printf(b, "%" PRIu32, val.uint32_val); - break; - case kUpb_CType_Int64: - StringBuilder_Printf(b, "%" PRId64, val.int64_val); - break; - case kUpb_CType_UInt64: - StringBuilder_Printf(b, "%" PRIu64, val.uint64_val); - break; - case kUpb_CType_String: - StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size, - val.str_val.data); - break; - case kUpb_CType_Bytes: - StringBuilder_Printf(b, "\"%.*s\"", (int)val.str_val.size, - val.str_val.data); - break; - case kUpb_CType_Enum: - StringBuilder_PrintEnum(b, val.int32_val, info.def.enumdef); - break; - case kUpb_CType_Message: - Message_PrintMessage(b, val.msg_val, info.def.msgdef); - break; - } -} - // ----------------------------------------------------------------------------- // Arena // ----------------------------------------------------------------------------- -typedef struct { - upb_Arena *arena; - VALUE pinned_objs; -} Arena; - -static void Arena_mark(void *data) { - Arena *arena = data; - rb_gc_mark(arena->pinned_objs); -} - -static void Arena_free(void *data) { - Arena *arena = data; - upb_Arena_Free(arena->arena); - xfree(arena); -} - -static VALUE cArena; - -const rb_data_type_t Arena_type = { - "Google::Protobuf::Internal::Arena", - {Arena_mark, Arena_free, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static void* ruby_upb_allocfunc(upb_alloc* alloc, void* ptr, size_t oldsize, size_t size) { - if (size == 0) { - xfree(ptr); - return NULL; - } else { - return xrealloc(ptr, size); - } -} - -upb_alloc ruby_upb_alloc = {&ruby_upb_allocfunc}; - -static VALUE Arena_alloc(VALUE klass) { - Arena *arena = ALLOC(Arena); - arena->arena = upb_Arena_Init(NULL, 0, &ruby_upb_alloc); - arena->pinned_objs = Qnil; - return TypedData_Wrap_Struct(klass, &Arena_type, arena); -} - -upb_Arena *Arena_get(VALUE _arena) { - Arena *arena; - TypedData_Get_Struct(_arena, Arena, &Arena_type, arena); - return arena->arena; -} - -void Arena_fuse(VALUE _arena, upb_Arena *other) { - Arena *arena; - TypedData_Get_Struct(_arena, Arena, &Arena_type, arena); - if (!upb_Arena_Fuse(arena->arena, other)) { - rb_raise(rb_eRuntimeError, - "Unable to fuse arenas. This should never happen since Ruby does " - "not use initial blocks"); - } -} - -VALUE Arena_new() { return Arena_alloc(cArena); } - -void Arena_Pin(VALUE _arena, VALUE obj) { - Arena *arena; - TypedData_Get_Struct(_arena, Arena, &Arena_type, arena); - if (arena->pinned_objs == Qnil) { - arena->pinned_objs = rb_ary_new(); - } - rb_ary_push(arena->pinned_objs, obj); -} - -void Arena_register(VALUE module) { - VALUE internal = rb_define_module_under(module, "Internal"); - VALUE klass = rb_define_class_under(internal, "Arena", rb_cObject); - rb_define_alloc_func(klass, Arena_alloc); - rb_gc_register_address(&cArena); - cArena = klass; -} - -// ----------------------------------------------------------------------------- -// Object Cache -// ----------------------------------------------------------------------------- - -// A pointer -> Ruby Object cache that keeps references to Ruby wrapper -// objects. This allows us to look up any Ruby wrapper object by the address -// of the object it is wrapping. That way we can avoid ever creating two -// different wrapper objects for the same C object, which saves memory and -// preserves object identity. -// -// We use WeakMap for the cache. For Ruby <2.7 we also need a secondary Hash -// to store WeakMap keys because Ruby <2.7 WeakMap doesn't allow non-finalizable -// keys. -// -// We also need the secondary Hash if sizeof(long) < sizeof(VALUE), because this -// means it may not be possible to fit a pointer into a Fixnum. Keys are -// pointers, and if they fit into a Fixnum, Ruby doesn't collect them, but if -// they overflow and require allocating a Bignum, they could get collected -// prematurely, thus removing the cache entry. This happens on 64-bit Windows, -// on which pointers are 64 bits but longs are 32 bits. In this case, we enable -// the secondary Hash to hold the keys and prevent them from being collected. - -#if RUBY_API_VERSION_CODE >= 20700 && SIZEOF_LONG >= SIZEOF_VALUE -#define USE_SECONDARY_MAP 0 -#else -#define USE_SECONDARY_MAP 1 -#endif - -#if USE_SECONDARY_MAP - -// Maps Numeric -> Object. The object is then used as a key into the WeakMap. -// This is needed for Ruby <2.7 where a number cannot be a key to WeakMap. -// The object is used only for its identity; it does not contain any data. -VALUE secondary_map = Qnil; - -// Mutations to the map are under a mutex, because SeconaryMap_MaybeGC() -// iterates over the map which cannot happen in parallel with insertions, or -// Ruby will throw: -// can't add a new key into hash during iteration (RuntimeError) -VALUE secondary_map_mutex = Qnil; - -// Lambda that will GC entries from the secondary map that are no longer present -// in the primary map. -VALUE gc_secondary_map_lambda = Qnil; -ID length; - -extern VALUE weak_obj_cache; - -static void SecondaryMap_Init() { - rb_gc_register_address(&secondary_map); - rb_gc_register_address(&gc_secondary_map_lambda); - rb_gc_register_address(&secondary_map_mutex); - secondary_map = rb_hash_new(); - gc_secondary_map_lambda = rb_eval_string( - "->(secondary, weak) {\n" - " secondary.delete_if { |k, v| !weak.key?(v) }\n" - "}\n"); - secondary_map_mutex = rb_mutex_new(); - length = rb_intern("length"); -} - -// The secondary map is a regular Hash, and will never shrink on its own. -// The main object cache is a WeakMap that will automatically remove entries -// when the target object is no longer reachable, but unless we manually -// remove the corresponding entries from the secondary map, it will grow -// without bound. -// -// To avoid this unbounded growth we periodically remove entries from the -// secondary map that are no longer present in the WeakMap. The logic of -// how often to perform this GC is an artbirary tuning parameter that -// represents a straightforward CPU/memory tradeoff. -// -// Requires: secondary_map_mutex is held. -static void SecondaryMap_MaybeGC() { - PBRUBY_ASSERT(rb_mutex_locked_p(secondary_map_mutex) == Qtrue); - size_t weak_len = NUM2ULL(rb_funcall(weak_obj_cache, length, 0)); - size_t secondary_len = RHASH_SIZE(secondary_map); - if (secondary_len < weak_len) { - // Logically this case should not be possible: a valid entry cannot exist in - // the weak table unless there is a corresponding entry in the secondary - // table. It should *always* be the case that secondary_len >= weak_len. - // - // However ObjectSpace::WeakMap#length (and therefore weak_len) is - // unreliable: it overreports its true length by including non-live objects. - // However these non-live objects are not yielded in iteration, so we may - // have previously deleted them from the secondary map in a previous - // invocation of SecondaryMap_MaybeGC(). - // - // In this case, we can't measure any waste, so we just return. - return; - } - size_t waste = secondary_len - weak_len; - // GC if we could remove at least 2000 entries or 20% of the table size - // (whichever is greater). Since the cost of the GC pass is O(N), we - // want to make sure that we condition this on overall table size, to - // avoid O(N^2) CPU costs. - size_t threshold = PBRUBY_MAX(secondary_len * 0.2, 2000); - if (waste > threshold) { - rb_funcall(gc_secondary_map_lambda, rb_intern("call"), 2, secondary_map, - weak_obj_cache); - } -} - -// Requires: secondary_map_mutex is held by this thread iff create == true. -static VALUE SecondaryMap_Get(VALUE key, bool create) { - PBRUBY_ASSERT(!create || rb_mutex_locked_p(secondary_map_mutex) == Qtrue); - VALUE ret = rb_hash_lookup(secondary_map, key); - if (ret == Qnil && create) { - SecondaryMap_MaybeGC(); - ret = rb_class_new_instance(0, NULL, rb_cObject); - rb_hash_aset(secondary_map, key, ret); - } - return ret; -} - -#endif - -// Requires: secondary_map_mutex is held by this thread iff create == true. -static VALUE ObjectCache_GetKey(const void *key, bool create) { - VALUE key_val = (VALUE)key; - PBRUBY_ASSERT((key_val & 3) == 0); - VALUE ret = LL2NUM(key_val >> 2); -#if USE_SECONDARY_MAP - ret = SecondaryMap_Get(ret, create); -#endif - return ret; -} - -// Public ObjectCache API. - -VALUE weak_obj_cache = Qnil; -ID item_get; -ID item_set; - -static void ObjectCache_Init() { - rb_gc_register_address(&weak_obj_cache); - VALUE klass = rb_eval_string("ObjectSpace::WeakMap"); - weak_obj_cache = rb_class_new_instance(0, NULL, klass); - item_get = rb_intern("[]"); - item_set = rb_intern("[]="); -#if USE_SECONDARY_MAP - SecondaryMap_Init(); -#endif -} - -void ObjectCache_Add(const void *key, VALUE val) { - PBRUBY_ASSERT(ObjectCache_Get(key) == Qnil); -#if USE_SECONDARY_MAP - rb_mutex_lock(secondary_map_mutex); -#endif - VALUE key_rb = ObjectCache_GetKey(key, true); - rb_funcall(weak_obj_cache, item_set, 2, key_rb, val); -#if USE_SECONDARY_MAP - rb_mutex_unlock(secondary_map_mutex); -#endif - PBRUBY_ASSERT(ObjectCache_Get(key) == val); -} - -// Returns the cached object for this key, if any. Otherwise returns Qnil. -VALUE ObjectCache_Get(const void *key) { - VALUE key_rb = ObjectCache_GetKey(key, false); - return rb_funcall(weak_obj_cache, item_get, 1, key_rb); -} - -/* - * call-seq: - * Google::Protobuf.discard_unknown(msg) - * - * Discard unknown fields in the given message object and recursively discard - * unknown fields in submessages. - */ -static VALUE Google_Protobuf_discard_unknown(VALUE self, VALUE msg_rb) { - const upb_MessageDef *m; - upb_Message *msg = Message_GetMutable(msg_rb, &m); - if (!upb_Message_DiscardUnknown(msg, m, 128)) { - rb_raise(rb_eRuntimeError, "Messages nested too deeply."); - } - - return Qnil; -} - -/* - * call-seq: - * Google::Protobuf.deep_copy(obj) => copy_of_obj - * - * Performs a deep copy of a RepeatedField instance, a Map instance, or a - * message object, recursively copying its members. - */ -VALUE Google_Protobuf_deep_copy(VALUE self, VALUE obj) { - VALUE klass = CLASS_OF(obj); - if (klass == cRepeatedField) { - return RepeatedField_deep_copy(obj); - } else if (klass == cMap) { - return Map_deep_copy(obj); - } else { - VALUE new_arena_rb = Arena_new(); - upb_Arena *new_arena = Arena_get(new_arena_rb); - const upb_MessageDef *m; - const upb_Message *msg = Message_Get(obj, &m); - upb_Message *new_msg = Message_deep_copy(msg, m, new_arena); - return Message_GetRubyWrapper(new_msg, m, new_arena_rb); - } -} - -// ----------------------------------------------------------------------------- -// Initialization/entry point. -// ----------------------------------------------------------------------------- - -// This must be named "Init_protobuf_c" because the Ruby module is named -// "protobuf_c" -- the VM looks for this symbol in our .so. -__attribute__((visibility("default"))) void Init_protobuf_c() { - ObjectCache_Init(); - - VALUE google = rb_define_module("Google"); - VALUE protobuf = rb_define_module_under(google, "Protobuf"); - - Arena_register(protobuf); - Defs_register(protobuf); - RepeatedField_register(protobuf); - Map_register(protobuf); - Message_register(protobuf); - - cParseError = rb_const_get(protobuf, rb_intern("ParseError")); - rb_gc_register_mark_object(cParseError); - cTypeError = rb_const_get(protobuf, rb_intern("TypeError")); - rb_gc_register_mark_object(cTypeError); - - rb_define_singleton_method(protobuf, "discard_unknown", - Google_Protobuf_discard_unknown, 1); - rb_define_singleton_method(protobuf, "deep_copy", Google_Protobuf_deep_copy, - 1); -} +upb_Arena * Arena_create() { + return upb_Arena_Init(NULL, 0, &upb_alloc_global); +} \ No newline at end of file diff --git a/ruby/ext/google/protobuf_c/protobuf.h b/ruby/ext/google/protobuf_c/protobuf.h index c6af98fa4727..0cf2fd28e619 100644 --- a/ruby/ext/google/protobuf_c/protobuf.h +++ b/ruby/ext/google/protobuf_c/protobuf.h @@ -31,18 +31,8 @@ #ifndef __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__ #define __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__ -#include -#include -#include - -#include "defs.h" #include "ruby-upb.h" -// These operate on a map field (i.e., a repeated field of submessages whose -// submessage type is a map-entry msgdef). -const upb_FieldDef* map_field_key(const upb_FieldDef* field); -const upb_FieldDef* map_field_value(const upb_FieldDef* field); - // ----------------------------------------------------------------------------- // Arena // ----------------------------------------------------------------------------- @@ -52,69 +42,6 @@ const upb_FieldDef* map_field_value(const upb_FieldDef* field); // ensure that the object's underlying memory outlives any Ruby object that can // reach it. -VALUE Arena_new(); -upb_Arena* Arena_get(VALUE arena); - -// Fuses this arena to another, throwing a Ruby exception if this is not -// possible. -void Arena_fuse(VALUE arena, upb_Arena* other); - -// Pins this Ruby object to the lifetime of this arena, so that as long as the -// arena is alive this object will not be collected. -// -// We use this to guarantee that the "frozen" bit on the object will be -// remembered, even if the user drops their reference to this precise object. -void Arena_Pin(VALUE arena, VALUE obj); - -// ----------------------------------------------------------------------------- -// ObjectCache -// ----------------------------------------------------------------------------- - -// Global object cache from upb array/map/message/symtab to wrapper object. -// -// This is a conceptually "weak" cache, in that it does not prevent "val" from -// being collected (though in Ruby <2.7 is it effectively strong, due to -// implementation limitations). - -// Adds an entry to the cache. The "arena" parameter must give the arena that -// "key" was allocated from. In Ruby <2.7.0, it will be used to remove the key -// from the cache when the arena is destroyed. -void ObjectCache_Add(const void* key, VALUE val); - -// Returns the cached object for this key, if any. Otherwise returns Qnil. -VALUE ObjectCache_Get(const void* key); - -// ----------------------------------------------------------------------------- -// StringBuilder, for inspect -// ----------------------------------------------------------------------------- - -struct StringBuilder; -typedef struct StringBuilder StringBuilder; - -StringBuilder* StringBuilder_New(); -void StringBuilder_Free(StringBuilder* b); -void StringBuilder_Printf(StringBuilder* b, const char* fmt, ...); -VALUE StringBuilder_ToRubyString(StringBuilder* b); - -void StringBuilder_PrintMsgval(StringBuilder* b, upb_MessageValue val, - TypeInfo info); - -// ----------------------------------------------------------------------------- -// Utilities. -// ----------------------------------------------------------------------------- - -extern VALUE cTypeError; - -#ifdef NDEBUG -#define PBRUBY_ASSERT(expr) \ - do { \ - } while (false && (expr)) -#else -#define PBRUBY_ASSERT(expr) assert(expr) -#endif - -#define PBRUBY_MAX(x, y) (((x) > (y)) ? (x) : (y)) - -#define UPB_UNUSED(var) (void)var +upb_Arena * Arena_create(); #endif // __GOOGLE_PROTOBUF_RUBY_PROTOBUF_H__ diff --git a/ruby/ext/google/protobuf_c/repeated_field.c b/ruby/ext/google/protobuf_c/repeated_field.c deleted file mode 100644 index 700ca16f38a6..000000000000 --- a/ruby/ext/google/protobuf_c/repeated_field.c +++ /dev/null @@ -1,657 +0,0 @@ -// Protocol Buffers - Google's data interchange format -// Copyright 2014 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. - -#include "repeated_field.h" - -#include "convert.h" -#include "defs.h" -#include "message.h" -#include "protobuf.h" - -// ----------------------------------------------------------------------------- -// Repeated field container type. -// ----------------------------------------------------------------------------- - -typedef struct { - const upb_Array* array; // Can get as mutable when non-frozen. - TypeInfo type_info; - VALUE type_class; // To GC-root the msgdef/enumdef in type_info. - VALUE arena; // To GC-root the upb_Array. -} RepeatedField; - -VALUE cRepeatedField; - -static void RepeatedField_mark(void* _self) { - RepeatedField* self = (RepeatedField*)_self; - rb_gc_mark(self->type_class); - rb_gc_mark(self->arena); -} - -const rb_data_type_t RepeatedField_type = { - "Google::Protobuf::RepeatedField", - {RepeatedField_mark, RUBY_DEFAULT_FREE, NULL}, - .flags = RUBY_TYPED_FREE_IMMEDIATELY, -}; - -static RepeatedField* ruby_to_RepeatedField(VALUE _self) { - RepeatedField* self; - TypedData_Get_Struct(_self, RepeatedField, &RepeatedField_type, self); - return self; -} - -static upb_Array* RepeatedField_GetMutable(VALUE _self) { - rb_check_frozen(_self); - return (upb_Array*)ruby_to_RepeatedField(_self)->array; -} - -VALUE RepeatedField_alloc(VALUE klass) { - RepeatedField* self = ALLOC(RepeatedField); - self->arena = Qnil; - self->type_class = Qnil; - self->array = NULL; - return TypedData_Wrap_Struct(klass, &RepeatedField_type, self); -} - -VALUE RepeatedField_GetRubyWrapper(upb_Array* array, TypeInfo type_info, - VALUE arena) { - PBRUBY_ASSERT(array); - VALUE val = ObjectCache_Get(array); - - if (val == Qnil) { - val = RepeatedField_alloc(cRepeatedField); - RepeatedField* self; - ObjectCache_Add(array, val); - TypedData_Get_Struct(val, RepeatedField, &RepeatedField_type, self); - self->array = array; - self->arena = arena; - self->type_info = type_info; - if (self->type_info.type == kUpb_CType_Message) { - self->type_class = Descriptor_DefToClass(type_info.def.msgdef); - } - } - - PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.type == type_info.type); - PBRUBY_ASSERT(ruby_to_RepeatedField(val)->type_info.def.msgdef == - type_info.def.msgdef); - return val; -} - -static VALUE RepeatedField_new_this_type(RepeatedField* from) { - VALUE arena_rb = Arena_new(); - upb_Array* array = upb_Array_New(Arena_get(arena_rb), from->type_info.type); - VALUE ret = RepeatedField_GetRubyWrapper(array, from->type_info, arena_rb); - PBRUBY_ASSERT(ruby_to_RepeatedField(ret)->type_class == from->type_class); - return ret; -} - -void RepeatedField_Inspect(StringBuilder* b, const upb_Array* array, - TypeInfo info) { - bool first = true; - StringBuilder_Printf(b, "["); - size_t n = array ? upb_Array_Size(array) : 0; - for (size_t i = 0; i < n; i++) { - if (first) { - first = false; - } else { - StringBuilder_Printf(b, ", "); - } - StringBuilder_PrintMsgval(b, upb_Array_Get(array, i), info); - } - StringBuilder_Printf(b, "]"); -} - -VALUE RepeatedField_deep_copy(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - VALUE new_rptfield = RepeatedField_new_this_type(self); - RepeatedField* new_self = ruby_to_RepeatedField(new_rptfield); - VALUE arena_rb = new_self->arena; - upb_Array* new_array = RepeatedField_GetMutable(new_rptfield); - upb_Arena* arena = Arena_get(arena_rb); - size_t elements = upb_Array_Size(self->array); - - upb_Array_Resize(new_array, elements, arena); - - size_t size = upb_Array_Size(self->array); - for (size_t i = 0; i < size; i++) { - upb_MessageValue msgval = upb_Array_Get(self->array, i); - upb_MessageValue copy = Msgval_DeepCopy(msgval, self->type_info, arena); - upb_Array_Set(new_array, i, copy); - } - - return new_rptfield; -} - -const upb_Array* RepeatedField_GetUpbArray(VALUE val, const upb_FieldDef* field, - upb_Arena* arena) { - RepeatedField* self; - TypeInfo type_info = TypeInfo_get(field); - - if (!RB_TYPE_P(val, T_DATA) || !RTYPEDDATA_P(val) || - RTYPEDDATA_TYPE(val) != &RepeatedField_type) { - rb_raise(cTypeError, "Expected repeated field array"); - } - - self = ruby_to_RepeatedField(val); - if (self->type_info.type != type_info.type) { - rb_raise(cTypeError, "Repeated field array has wrong element type"); - } - - if (self->type_info.def.msgdef != type_info.def.msgdef) { - rb_raise(cTypeError, "Repeated field array has wrong message/enum class"); - } - - Arena_fuse(self->arena, arena); - return self->array; -} - -static int index_position(VALUE _index, RepeatedField* repeated_field) { - int index = NUM2INT(_index); - if (index < 0) index += upb_Array_Size(repeated_field->array); - return index; -} - -static VALUE RepeatedField_subarray(RepeatedField* self, long beg, long len) { - size_t size = upb_Array_Size(self->array); - VALUE ary = rb_ary_new2(size); - long i; - - for (i = beg; i < beg + len; i++) { - upb_MessageValue msgval = upb_Array_Get(self->array, i); - VALUE elem = Convert_UpbToRuby(msgval, self->type_info, self->arena); - rb_ary_push(ary, elem); - } - return ary; -} - -/* - * call-seq: - * RepeatedField.each(&block) - * - * Invokes the block once for each element of the repeated field. RepeatedField - * also includes Enumerable; combined with this method, the repeated field thus - * acts like an ordinary Ruby sequence. - */ -static VALUE RepeatedField_each(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - int size = upb_Array_Size(self->array); - int i; - - for (i = 0; i < size; i++) { - upb_MessageValue msgval = upb_Array_Get(self->array, i); - VALUE val = Convert_UpbToRuby(msgval, self->type_info, self->arena); - rb_yield(val); - } - return _self; -} - -/* - * call-seq: - * RepeatedField.[](index) => value - * - * Accesses the element at the given index. Returns nil on out-of-bounds - */ -static VALUE RepeatedField_index(int argc, VALUE* argv, VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - long size = upb_Array_Size(self->array); - - VALUE arg = argv[0]; - long beg, len; - - if (argc == 1) { - if (FIXNUM_P(arg)) { - /* standard case */ - upb_MessageValue msgval; - int index = index_position(argv[0], self); - if (index < 0 || (size_t)index >= upb_Array_Size(self->array)) { - return Qnil; - } - msgval = upb_Array_Get(self->array, index); - return Convert_UpbToRuby(msgval, self->type_info, self->arena); - } else { - /* check if idx is Range */ - switch (rb_range_beg_len(arg, &beg, &len, size, 0)) { - case Qfalse: - break; - case Qnil: - return Qnil; - default: - return RepeatedField_subarray(self, beg, len); - } - } - } - - /* assume 2 arguments */ - beg = NUM2LONG(argv[0]); - len = NUM2LONG(argv[1]); - if (beg < 0) { - beg += size; - } - if (beg >= size) { - return Qnil; - } - return RepeatedField_subarray(self, beg, len); -} - -/* - * call-seq: - * RepeatedField.[]=(index, value) - * - * Sets the element at the given index. On out-of-bounds assignments, extends - * the array and fills the hole (if any) with default values. - */ -static VALUE RepeatedField_index_set(VALUE _self, VALUE _index, VALUE val) { - RepeatedField* self = ruby_to_RepeatedField(_self); - int size = upb_Array_Size(self->array); - upb_Array* array = RepeatedField_GetMutable(_self); - upb_Arena* arena = Arena_get(self->arena); - upb_MessageValue msgval = Convert_RubyToUpb(val, "", self->type_info, arena); - - int index = index_position(_index, self); - if (index < 0 || index >= (INT_MAX - 1)) { - return Qnil; - } - - if (index >= size) { - upb_Array_Resize(array, index + 1, arena); - upb_MessageValue fill; - memset(&fill, 0, sizeof(fill)); - for (int i = size; i < index; i++) { - // Fill default values. - // TODO(haberman): should this happen at the upb level? - upb_Array_Set(array, i, fill); - } - } - - upb_Array_Set(array, index, msgval); - return Qnil; -} - -/* - * call-seq: - * RepeatedField.push(value, ...) - * - * Adds a new element to the repeated field. - */ -static VALUE RepeatedField_push_vararg(int argc, VALUE* argv, VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - upb_Arena* arena = Arena_get(self->arena); - upb_Array* array = RepeatedField_GetMutable(_self); - int i; - - for (i = 0; i < argc; i++) { - upb_MessageValue msgval = - Convert_RubyToUpb(argv[i], "", self->type_info, arena); - upb_Array_Append(array, msgval, arena); - } - - return _self; -} - -/* - * call-seq: - * RepeatedField.<<(value) - * - * Adds a new element to the repeated field. - */ -static VALUE RepeatedField_push(VALUE _self, VALUE val) { - RepeatedField* self = ruby_to_RepeatedField(_self); - upb_Arena* arena = Arena_get(self->arena); - upb_Array* array = RepeatedField_GetMutable(_self); - - upb_MessageValue msgval = Convert_RubyToUpb(val, "", self->type_info, arena); - upb_Array_Append(array, msgval, arena); - - return _self; -} - -/* - * Private ruby method, used by RepeatedField.pop - */ -static VALUE RepeatedField_pop_one(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - size_t size = upb_Array_Size(self->array); - upb_Array* array = RepeatedField_GetMutable(_self); - upb_MessageValue last; - VALUE ret; - - if (size == 0) { - return Qnil; - } - - last = upb_Array_Get(self->array, size - 1); - ret = Convert_UpbToRuby(last, self->type_info, self->arena); - - upb_Array_Resize(array, size - 1, Arena_get(self->arena)); - return ret; -} - -/* - * call-seq: - * RepeatedField.replace(list) - * - * Replaces the contents of the repeated field with the given list of elements. - */ -static VALUE RepeatedField_replace(VALUE _self, VALUE list) { - RepeatedField* self = ruby_to_RepeatedField(_self); - upb_Array* array = RepeatedField_GetMutable(_self); - int i; - - Check_Type(list, T_ARRAY); - upb_Array_Resize(array, 0, Arena_get(self->arena)); - - for (i = 0; i < RARRAY_LEN(list); i++) { - RepeatedField_push(_self, rb_ary_entry(list, i)); - } - - return list; -} - -/* - * call-seq: - * RepeatedField.clear - * - * Clears (removes all elements from) this repeated field. - */ -static VALUE RepeatedField_clear(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - upb_Array* array = RepeatedField_GetMutable(_self); - upb_Array_Resize(array, 0, Arena_get(self->arena)); - return _self; -} - -/* - * call-seq: - * RepeatedField.length - * - * Returns the length of this repeated field. - */ -static VALUE RepeatedField_length(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - return INT2NUM(upb_Array_Size(self->array)); -} - -/* - * call-seq: - * RepeatedField.dup => repeated_field - * - * Duplicates this repeated field with a shallow copy. References to all - * non-primitive element objects (e.g., submessages) are shared. - */ -static VALUE RepeatedField_dup(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - VALUE new_rptfield = RepeatedField_new_this_type(self); - RepeatedField* new_rptfield_self = ruby_to_RepeatedField(new_rptfield); - upb_Array* new_array = RepeatedField_GetMutable(new_rptfield); - upb_Arena* arena = Arena_get(new_rptfield_self->arena); - int size = upb_Array_Size(self->array); - int i; - - Arena_fuse(self->arena, arena); - - for (i = 0; i < size; i++) { - upb_MessageValue msgval = upb_Array_Get(self->array, i); - upb_Array_Append(new_array, msgval, arena); - } - - return new_rptfield; -} - -/* - * call-seq: - * RepeatedField.to_ary => array - * - * Used when converted implicitly into array, e.g. compared to an Array. - * Also called as a fallback of Object#to_a - */ -VALUE RepeatedField_to_ary(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - int size = upb_Array_Size(self->array); - VALUE ary = rb_ary_new2(size); - int i; - - for (i = 0; i < size; i++) { - upb_MessageValue msgval = upb_Array_Get(self->array, i); - VALUE val = Convert_UpbToRuby(msgval, self->type_info, self->arena); - rb_ary_push(ary, val); - } - - return ary; -} - -/* - * call-seq: - * RepeatedField.==(other) => boolean - * - * Compares this repeated field to another. Repeated fields are equal if their - * element types are equal, their lengths are equal, and each element is equal. - * Elements are compared as per normal Ruby semantics, by calling their :== - * methods (or performing a more efficient comparison for primitive types). - * - * Repeated fields with dissimilar element types are never equal, even if value - * comparison (for example, between integers and floats) would have otherwise - * indicated that every element has equal value. - */ -VALUE RepeatedField_eq(VALUE _self, VALUE _other) { - RepeatedField* self; - RepeatedField* other; - - if (_self == _other) { - return Qtrue; - } - - if (TYPE(_other) == T_ARRAY) { - VALUE self_ary = RepeatedField_to_ary(_self); - return rb_equal(self_ary, _other); - } - - self = ruby_to_RepeatedField(_self); - other = ruby_to_RepeatedField(_other); - size_t n = upb_Array_Size(self->array); - - if (self->type_info.type != other->type_info.type || - self->type_class != other->type_class || - upb_Array_Size(other->array) != n) { - return Qfalse; - } - - for (size_t i = 0; i < n; i++) { - upb_MessageValue val1 = upb_Array_Get(self->array, i); - upb_MessageValue val2 = upb_Array_Get(other->array, i); - if (!Msgval_IsEqual(val1, val2, self->type_info)) { - return Qfalse; - } - } - - return Qtrue; -} - -/* - * call-seq: - * RepeatedField.freeze => self - * - * Freezes the repeated field. We have to intercept this so we can pin the Ruby - * object into memory so we don't forget it's frozen. - */ -static VALUE RepeatedField_freeze(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - if (!RB_OBJ_FROZEN(_self)) { - Arena_Pin(self->arena, _self); - RB_OBJ_FREEZE(_self); - } - return _self; -} - -/* - * call-seq: - * RepeatedField.hash => hash_value - * - * Returns a hash value computed from this repeated field's elements. - */ -VALUE RepeatedField_hash(VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - uint64_t hash = 0; - size_t n = upb_Array_Size(self->array); - - for (size_t i = 0; i < n; i++) { - upb_MessageValue val = upb_Array_Get(self->array, i); - hash = Msgval_GetHash(val, self->type_info, hash); - } - - return LL2NUM(hash); -} - -/* - * call-seq: - * RepeatedField.+(other) => repeated field - * - * Returns a new repeated field that contains the concatenated list of this - * repeated field's elements and other's elements. The other (second) list may - * be either another repeated field or a Ruby array. - */ -VALUE RepeatedField_plus(VALUE _self, VALUE list) { - VALUE dupped_ = RepeatedField_dup(_self); - - if (TYPE(list) == T_ARRAY) { - int i; - for (i = 0; i < RARRAY_LEN(list); i++) { - VALUE elem = rb_ary_entry(list, i); - RepeatedField_push(dupped_, elem); - } - } else if (RB_TYPE_P(list, T_DATA) && RTYPEDDATA_P(list) && - RTYPEDDATA_TYPE(list) == &RepeatedField_type) { - RepeatedField* self = ruby_to_RepeatedField(_self); - RepeatedField* list_rptfield = ruby_to_RepeatedField(list); - RepeatedField* dupped = ruby_to_RepeatedField(dupped_); - upb_Array* dupped_array = RepeatedField_GetMutable(dupped_); - upb_Arena* arena = Arena_get(dupped->arena); - Arena_fuse(list_rptfield->arena, arena); - int size = upb_Array_Size(list_rptfield->array); - int i; - - if (self->type_info.type != list_rptfield->type_info.type || - self->type_class != list_rptfield->type_class) { - rb_raise(rb_eArgError, - "Attempt to append RepeatedField with different element type."); - } - - for (i = 0; i < size; i++) { - upb_MessageValue msgval = upb_Array_Get(list_rptfield->array, i); - upb_Array_Append(dupped_array, msgval, arena); - } - } else { - rb_raise(rb_eArgError, "Unknown type appending to RepeatedField"); - } - - return dupped_; -} - -/* - * call-seq: - * RepeatedField.concat(other) => self - * - * concats the passed in array to self. Returns a Ruby array. - */ -VALUE RepeatedField_concat(VALUE _self, VALUE list) { - int i; - - Check_Type(list, T_ARRAY); - for (i = 0; i < RARRAY_LEN(list); i++) { - RepeatedField_push(_self, rb_ary_entry(list, i)); - } - return _self; -} - -/* - * call-seq: - * RepeatedField.new(type, type_class = nil, initial_elems = []) - * - * Creates a new repeated field. The provided type must be a Ruby symbol, and - * can take on the same values as those accepted by FieldDescriptor#type=. If - * the type is :message or :enum, type_class must be non-nil, and must be the - * Ruby class or module returned by Descriptor#msgclass or - * EnumDescriptor#enummodule, respectively. An initial list of elements may also - * be provided. - */ -VALUE RepeatedField_init(int argc, VALUE* argv, VALUE _self) { - RepeatedField* self = ruby_to_RepeatedField(_self); - upb_Arena* arena; - VALUE ary = Qnil; - - self->arena = Arena_new(); - arena = Arena_get(self->arena); - - if (argc < 1) { - rb_raise(rb_eArgError, "Expected at least 1 argument."); - } - - self->type_info = TypeInfo_FromClass(argc, argv, 0, &self->type_class, &ary); - self->array = upb_Array_New(arena, self->type_info.type); - ObjectCache_Add(self->array, _self); - - if (ary != Qnil) { - if (!RB_TYPE_P(ary, T_ARRAY)) { - rb_raise(rb_eArgError, "Expected array as initialize argument"); - } - for (int i = 0; i < RARRAY_LEN(ary); i++) { - RepeatedField_push(_self, rb_ary_entry(ary, i)); - } - } - return Qnil; -} - -void RepeatedField_register(VALUE module) { - VALUE klass = rb_define_class_under(module, "RepeatedField", rb_cObject); - rb_define_alloc_func(klass, RepeatedField_alloc); - rb_gc_register_address(&cRepeatedField); - cRepeatedField = klass; - - rb_define_method(klass, "initialize", RepeatedField_init, -1); - rb_define_method(klass, "each", RepeatedField_each, 0); - rb_define_method(klass, "[]", RepeatedField_index, -1); - rb_define_method(klass, "at", RepeatedField_index, -1); - rb_define_method(klass, "[]=", RepeatedField_index_set, 2); - rb_define_method(klass, "push", RepeatedField_push_vararg, -1); - rb_define_method(klass, "<<", RepeatedField_push, 1); - rb_define_private_method(klass, "pop_one", RepeatedField_pop_one, 0); - rb_define_method(klass, "replace", RepeatedField_replace, 1); - rb_define_method(klass, "clear", RepeatedField_clear, 0); - rb_define_method(klass, "length", RepeatedField_length, 0); - rb_define_method(klass, "size", RepeatedField_length, 0); - rb_define_method(klass, "dup", RepeatedField_dup, 0); - // Also define #clone so that we don't inherit Object#clone. - rb_define_method(klass, "clone", RepeatedField_dup, 0); - rb_define_method(klass, "==", RepeatedField_eq, 1); - rb_define_method(klass, "to_ary", RepeatedField_to_ary, 0); - rb_define_method(klass, "freeze", RepeatedField_freeze, 0); - rb_define_method(klass, "hash", RepeatedField_hash, 0); - rb_define_method(klass, "+", RepeatedField_plus, 1); - rb_define_method(klass, "concat", RepeatedField_concat, 1); - rb_include_module(klass, rb_mEnumerable); -} diff --git a/ruby/ext/google/protobuf_c/repeated_field.h b/ruby/ext/google/protobuf_c/repeated_field.h deleted file mode 100644 index b0581635b85a..000000000000 --- a/ruby/ext/google/protobuf_c/repeated_field.h +++ /dev/null @@ -1,63 +0,0 @@ -// 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. - -#ifndef RUBY_PROTOBUF_REPEATED_FIELD_H_ -#define RUBY_PROTOBUF_REPEATED_FIELD_H_ - -#include - -#include "protobuf.h" -#include "ruby-upb.h" - -// Returns a Ruby wrapper object for the given upb_Array, which will be created -// if one does not exist already. -VALUE RepeatedField_GetRubyWrapper(upb_Array* msg, TypeInfo type_info, - VALUE arena); - -// Gets the underlying upb_Array for this Ruby RepeatedField object, which must -// have a type that matches |f|. If this is not a repeated field or the type -// doesn't match, raises an exception. -const upb_Array* RepeatedField_GetUpbArray(VALUE value, const upb_FieldDef* f, - upb_Arena* arena); - -// Implements #inspect for this repeated field by appending its contents to |b|. -void RepeatedField_Inspect(StringBuilder* b, const upb_Array* array, - TypeInfo info); - -// Returns a deep copy of this RepeatedField object. -VALUE RepeatedField_deep_copy(VALUE obj); - -// Ruby class of Google::Protobuf::RepeatedField. -extern VALUE cRepeatedField; - -// Call at startup to register all types in this module. -void RepeatedField_register(VALUE module); - -#endif // RUBY_PROTOBUF_REPEATED_FIELD_H_ diff --git a/ruby/google-protobuf.gemspec b/ruby/google-protobuf.gemspec index 3d776fecea58..337768b37ce2 100644 --- a/ruby/google-protobuf.gemspec +++ b/ruby/google-protobuf.gemspec @@ -9,19 +9,12 @@ Gem::Specification.new do |s| s.authors = ["Protobuf Authors"] s.email = "protobuf@googlegroups.com" s.metadata = { "source_code_uri" => "https://github.com/protocolbuffers/protobuf/tree/#{git_tag}/ruby" } - s.require_paths = ["lib"] - s.files = Dir.glob('lib/**/*.rb') - if RUBY_PLATFORM == "java" - s.platform = "java" - s.files += ["lib/google/protobuf_java.jar"] - else - s.files += Dir.glob('ext/**/*') - s.extensions= ["ext/google/protobuf_c/extconf.rb"] - s.add_development_dependency "rake-compiler-dock", "= 1.2.1" end - s.test_files = ["tests/basic.rb", - "tests/stress.rb", - "tests/generated_code_test.rb"] - s.required_ruby_version = '>= 2.3' - s.add_development_dependency "rake-compiler", "~> 1.1.0" - s.add_development_dependency "test-unit", '~> 3.0', '>= 3.0.9' + s.files = Dir.glob("lib/**/*.rb") + s.extensions = Dir.glob(%w[Rakefile ext/**/*.{c,h}]) + s.test_files = %w[tests/basic.rb tests/stress.rb tests/generated_code_test.rb] + s.required_ruby_version = ">= 2.3" + s.add_dependency "ffi", "~>1" + s.add_dependency "ffi-compiler", "~>1" + s.add_dependency "rake", "~>13" + s.add_development_dependency "test-unit", "~> 3.0", ">= 3.0.9" end diff --git a/ruby/internal.bzl b/ruby/internal.bzl index 74a2d10ef8c8..57520b249c10 100644 --- a/ruby/internal.bzl +++ b/ruby/internal.bzl @@ -15,7 +15,6 @@ def internal_ruby_extension( **kwargs: extra arguments to forward to the genrule. """ - native.genrule( name = name, srcs = deps + [ @@ -28,8 +27,8 @@ def internal_ruby_extension( tags = ["manual"], outs = [extension], cmd = "pushd `dirname $(location Rakefile)`\n" + - "BAZEL=true rake\n" + + "rake\n" + "popd\n" + "cp `dirname $(location Rakefile)`/%s $(OUTS)\n" % extension, - **kwargs, + **kwargs ) diff --git a/ruby/lib/google/protobuf.rb b/ruby/lib/google/protobuf.rb index b7a671105158..266186a131bf 100644 --- a/ruby/lib/google/protobuf.rb +++ b/ruby/lib/google/protobuf.rb @@ -28,37 +28,30 @@ # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# require mixins before we hook them into the java & c code require 'google/protobuf/message_exts' +require 'ffi' +require 'google/protobuf/internal/type_safety' +require 'google/protobuf/internal/arena' -# We define these before requiring the platform-specific modules. -# That way the module init can grab references to these. -module Google - module Protobuf - class Error < StandardError; end - class ParseError < Error; end - class TypeError < ::TypeError; end - end -end - -if RUBY_PLATFORM == "java" - require 'json' - require 'google/protobuf_java' -else - begin - require "google/#{RUBY_VERSION.sub(/\.\d+$/, '')}/protobuf_c" - rescue LoadError - require 'google/protobuf_c' - end - -end +require 'google/protobuf/internal/convert' +require 'google/protobuf/descriptor' +require 'google/protobuf/enum_descriptor' +require 'google/protobuf/field_descriptor' +require 'google/protobuf/oneof_descriptor' +require 'google/protobuf/ffi' +require 'google/protobuf/descriptor_pool' +require 'google/protobuf/file_descriptor' +require 'google/protobuf/map' +require 'google/protobuf/object_cache' +require 'google/protobuf/repeated_field' require 'google/protobuf/descriptor_dsl' -require 'google/protobuf/repeated_field' module Google module Protobuf - + class Error < StandardError; end + class ParseError < Error; end + class TypeError < ::TypeError; end def self.encode(msg, options = {}) msg.to_proto(options) end @@ -75,5 +68,26 @@ def self.decode_json(klass, json, options = {}) klass.decode_json(json, options) end + def self.deep_copy(object) + case object + when RepeatedField + RepeatedField.send(:deep_copy, object) + when Google::Protobuf::Map + Google::Protobuf::Map.deep_copy(object) + when Google::Protobuf::MessageExts + object.class.send(:deep_copy, object.instance_variable_get(:@msg)) + else + raise NotImplementedError + end + end + + def self.discard_unknown(message) + raise FrozenError if message.frozen? + raise ArgumentError.new "Expected message, got #{message.class} instead." if message.instance_variable_get(:@msg).nil? + unless Google::Protobuf::FFI.message_discard_unknown(message.instance_variable_get(:@msg), message.class.descriptor, 128) + raise RuntimeError.new "Messages nested too deeply." + end + nil + end end end diff --git a/ruby/lib/google/protobuf/descriptor.rb b/ruby/lib/google/protobuf/descriptor.rb new file mode 100644 index 000000000000..fa30c1693146 --- /dev/null +++ b/ruby/lib/google/protobuf/descriptor.rb @@ -0,0 +1,765 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + ## + # Message Descriptor - Descriptor for short. + class Descriptor + attr :descriptor_pool, :msg_class + include Enumerable + + # FFI Interface methods and setup + extend ::FFI::DataConverter + native_type ::FFI::Type::POINTER + + class << self + prepend Google::Protobuf::Internal::TypeSafety + + # @param value [Descriptor] Descriptor to convert to an FFI native type + # @param _ [Object] Unused + def to_native(value, _ = nil) + msg_def_ptr = value.nil? ? nil : value.instance_variable_get(:@msg_def) + return ::FFI::Pointer::NULL if msg_def_ptr.nil? + raise "Underlying msg_def was null!" if msg_def_ptr.null? + msg_def_ptr + end + + ## + # @param msg_def [::FFI::Pointer] MsgDef pointer to be wrapped + # @param _ [Object] Unused + def from_native(msg_def, _ = nil) + return nil if msg_def.nil? or msg_def.null? + # Calling get_message_file_def(msg_def) would create a cyclic + # dependency because FFI would then complain about passing an + # FFI::Pointer instance instead of a Descriptor. Instead, directly + # read the top of the MsgDef structure an extract the FileDef*. + # file_def = Google::Protobuf::FFI.get_message_file_def msg_def + message_def_struct = Google::Protobuf::FFI::Upb_MessageDef.new(msg_def) + file_def = message_def_struct[:file_def] + raise RuntimeError.new "FileDef is nil" if file_def.nil? + raise RuntimeError.new "FileDef is null" if file_def.null? + pool_def = Google::Protobuf::FFI.file_def_pool file_def + raise RuntimeError.new "PoolDef is nil" if pool_def.nil? + raise RuntimeError.new "PoolDef is null" if pool_def.null? + pool = Google::Protobuf::ObjectCache.get(pool_def) + raise "Cannot find pool in ObjectCache!" if pool.nil? + descriptor = pool.descriptor_class_by_def[msg_def.address] + if descriptor.nil? + pool.descriptor_class_by_def[msg_def.address] = private_constructor(msg_def, pool) + else + descriptor + end + end + end + + ## + # Great write up of this strategy: + # See https://blog.appsignal.com/2018/08/07/ruby-magic-changing-the-way-ruby-creates-objects.html + def self.new(*arguments, &block) + raise "Descriptor objects may not be created from Ruby." + end + + def to_s + inspect + end + + def inspect + "Descriptor - (not the message class) #{name}" + end + + def file_descriptor + @descriptor_pool.send(:get_file_descriptor, Google::Protobuf::FFI.get_message_file_def(self)) + end + + def name + @name ||= Google::Protobuf::FFI.get_message_fullname(self) + end + + def each_oneof &block + n = Google::Protobuf::FFI.oneof_count(self) + 0.upto(n-1) do |i| + yield(Google::Protobuf::FFI.get_oneof_by_index(self, i)) + end + nil + end + + def each &block + n = Google::Protobuf::FFI.field_count(self) + 0.upto(n-1) do |i| + yield(Google::Protobuf::FFI.get_field_by_index(self, i)) + end + nil + end + + def lookup(name) + Google::Protobuf::FFI.get_field_by_name(self, name, name.size) + end + + def lookup_oneof(name) + Google::Protobuf::FFI.get_oneof_by_name(self, name, name.size) + end + + def msgclass + @msg_class ||= build_message_class + end + + private + + extend Google::Protobuf::Internal::Convert + + def initialize(msg_def, descriptor_pool) + @msg_def = msg_def + @msg_class = nil + @descriptor_pool = descriptor_pool + end + + def self.private_constructor(msg_def, descriptor_pool) + instance = allocate + instance.send(:initialize, msg_def, descriptor_pool) + instance + end + + def wrapper? + if defined? @wrapper + @wrapper + else + @wrapper = case Google::Protobuf::FFI.get_well_known_type self + when :DoubleValue, :FloatValue, :Int64Value, :UInt64Value, :Int32Value, :UInt32Value, :StringValue, :BytesValue, :BoolValue + true + else + false + end + end + end + + def self.get_message(msg, descriptor, arena) + return nil if msg.nil? or msg.null? + message = ObjectCache.get(msg) + if message.nil? + message = descriptor.msgclass.send(:private_constructor, arena, msg: msg) + end + message + end + + def pool + @descriptor_pool + end + + def build_message_class + descriptor = self + Class.new(Google::Protobuf::const_get(:AbstractMessage)) do + @descriptor = descriptor + class << self + attr_accessor :descriptor + private + attr_accessor :oneof_field_names + include ::Google::Protobuf::Internal::Convert + end + + alias original_method_missing method_missing + def method_missing(method_name, *args) + method_missing_internal method_name, *args, mode: :method_missing + end + + def respond_to_missing?(method_name, include_private = false) + method_missing_internal(method_name, mode: :respond_to_missing?) || super + end + + ## + # Public constructor. Automatically allocates from a new Arena. + def self.new(initial_value = nil) + instance = allocate + instance.send(:initialize, initial_value) + instance + end + + def dup + duplicate = self.class.private_constructor(@arena) + mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) + size = mini_table[:size] + duplicate.instance_variable_get(:@msg).write_string_length(@msg.read_string_length(size), size) + duplicate + end + alias clone dup + + def eql?(other) + return false unless self.class === other + encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown + temporary_arena = Google::Protobuf::FFI.create_arena + mini_table = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) + size_one = ::FFI::MemoryPointer.new(:size_t, 1) + encoding_one = Google::Protobuf::FFI.encode_message(@msg, mini_table, encoding_options, temporary_arena, size_one) + size_two = ::FFI::MemoryPointer.new(:size_t, 1) + encoding_two = Google::Protobuf::FFI.encode_message(other.instance_variable_get(:@msg), mini_table, encoding_options, temporary_arena, size_two) + if encoding_one.null? or encoding_two.null? + raise ParseError.new "Error comparing messages" + end + size_one.read(:size_t) == size_two.read(:size_t) and Google::Protobuf::FFI.memcmp(encoding_one, encoding_two, size_one.read(:size_t)).zero? + end + alias == eql? + + def hash + encoding_options = Google::Protobuf::FFI::Upb_Encode_Deterministic | Google::Protobuf::FFI::Upb_Encode_SkipUnknown + temporary_arena = Google::Protobuf::FFI.create_arena + mini_table_ptr = Google::Protobuf::FFI.get_mini_table(self.class.descriptor) + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + encoding = Google::Protobuf::FFI.encode_message(@msg, mini_table_ptr, encoding_options, temporary_arena, size_ptr) + if encoding.null? + raise ParseError.new "Error calculating hash" + end + Google::Protobuf::FFI.hash(encoding, size_ptr.read(:size_t), 0) + end + + def to_h + to_h_internal @msg, self.class.descriptor + end + + ## + # call-seq: + # Message.inspect => string + # + # Returns a human-readable string representing this message. It will be + # formatted as "". Each + # field's value is represented according to its own #inspect method. + def inspect + self.class.inspect_internal @msg + end + + def to_s + self.inspect + end + + ## + # call-seq: + # Message.[](index) => value + # Accesses a field's value by field name. The provided field name + # should be a string. + def [](name) + raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String + index_internal name + end + + ## + # call-seq: + # Message.[]=(index, value) + # Sets a field's value by field name. The provided field name should + # be a string. + # @param name [String] Name of the field to be set + # @param value [Object] Value to set the field to + def []=(name, value) + raise TypeError.new "Expected String for name but got #{name.class}" unless name.is_a? String + index_assign_internal(value, name: name) + end + + ## + # call-seq: + # MessageClass.decode(data, options) => message + # + # Decodes the given data (as a string containing bytes in protocol buffers wire + # format) under the interpretation given by this message class's definition + # and returns a message object with the corresponding field values. + # @param data [String] Binary string in Protobuf wire format to decode + # @param options [Hash] options for the decoder + # @option options [Integer] :recursion_limit Set to maximum decoding depth for message (default is 64) + def self.decode(data, options = {}) + raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash + raise ArgumentError.new "Expected string for binary protobuf data." unless data.is_a? String + decoding_options = 0 + depth = options[:recursion_limit] + + if depth.is_a? Numeric + decoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i) + end + + message = new + mini_table_ptr = Google::Protobuf::FFI.get_mini_table(message.class.descriptor) + status = Google::Protobuf::FFI.decode_message(data, data.bytesize, message.instance_variable_get(:@msg), mini_table_ptr, nil, decoding_options, message.instance_variable_get(:@arena)) + raise ParseError.new "Error occurred during parsing" unless status == :Ok + message + end + + ## + # call-seq: + # MessageClass.encode(msg, options) => bytes + # + # Encodes the given message object to its serialized form in protocol buffers + # wire format. + # @param options [Hash] options for the encoder + # @option options [Integer] :recursion_limit Set to maximum encoding depth for message (default is 64) + def self.encode(message, options = {}) + raise ArgumentError.new "Message of wrong type." unless message.is_a? self + raise ArgumentError.new "Expected hash arguments." unless options.is_a? Hash + + encoding_options = 0 + depth = options[:recursion_limit] + + if depth.is_a? Numeric + encoding_options |= Google::Protobuf::FFI.decode_max_depth(depth.to_i) + end + + encode_internal(message.instance_variable_get(:@msg), encoding_options) do |encoding, size, _| + if encoding.nil? or encoding.null? + raise RuntimeError.new "Exceeded maximum depth (possibly cycle)" + else + encoding.read_string_length(size).force_encoding("ASCII-8BIT").freeze + end + end + end + + ## + # all-seq: + # MessageClass.decode_json(data, options = {}) => message + # + # Decodes the given data (as a string containing bytes in protocol buffers wire + # format) under the interpretation given by this message class's definition + # and returns a message object with the corresponding field values. + # + # @param options [Hash] options for the decoder + # @option options [Boolean] :ignore_unknown_fields Set true to ignore unknown fields (default is to raise an error) + # @return [Message] + def self.decode_json(data, options = {}) + decoding_options = 0 + unless options.is_a? Hash + if options.respond_to? :to_h + options options.to_h + else + #TODO(jatl) can this error message be improve to include what was received? + raise ArgumentError.new "Expected hash arguments" + end + end + raise ArgumentError.new "Expected string for JSON data." unless data.is_a? String + raise RuntimeError.new "Cannot parse a wrapper directly" if descriptor.send(:wrapper?) + + if options[:ignore_unknown_fields] + decoding_options |= Google::Protobuf::FFI::Upb_JsonDecode_IgnoreUnknown + end + + message = new + pool_def = pool_def_from_message_definition(message.class.descriptor) + status = Google::Protobuf::FFI::Status.new + unless Google::Protobuf::FFI.json_decode_message(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status) + raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}" + end + message + end + + def self.encode_json(message, options = {}) + encoding_options = 0 + unless options.is_a? Hash + if options.respond_to? :to_h + options = options.to_h + else + #TODO(jatl) can this error message be improve to include what was received? + raise ArgumentError.new "Expected hash arguments" + end + end + + if options[:preserve_proto_fieldnames] + encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_UseProtoNames + end + if options[:emit_defaults] + encoding_options |= Google::Protobuf::FFI::Upb_JsonEncode_EmitDefaults + end + + buffer_size = 1024 + buffer = ::FFI::MemoryPointer.new(:char, buffer_size) + status = Google::Protobuf::FFI::Status.new + msg = message.instance_variable_get(:@msg) + pool_def = pool_def_from_message_definition(message.class.descriptor) + size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status) + unless status[:ok] + raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}" + end + + if size >= buffer_size + buffer_size = size + 1 + buffer = ::FFI::MemoryPointer.new(:char, buffer_size) + status.clear + size = Google::Protobuf::FFI::json_encode_message(msg, message.class.descriptor, pool_def, encoding_options, buffer, buffer_size, status) + unless status[:ok] + raise ParseError.new "Error occurred during encoding: #{Google::Protobuf::FFI.error_message(status)}" + end + if size >= buffer_size + raise ParseError.new "Inconsistent JSON encoding sizes - was #{buffer_size - 1}, now #{size}" + end + end + + buffer.read_string_length(size).force_encoding("UTF-8").freeze + end + + @descriptor.each do |field_descriptor| + field_name = field_descriptor.name + unless instance_methods(true).include?(field_name.to_sym) + #TODO(jatl) - at a high level, dispatching to either + # index_internal or get_field would be logically correct, but slightly slower. + if field_descriptor.map? + define_method(field_name) do + mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena + get_map_field(mutable_message_value[:map], field_descriptor) + end + elsif field_descriptor.repeated? + define_method(field_name) do + mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena + get_repeated_field(mutable_message_value[:array], field_descriptor) + end + elsif field_descriptor.sub_message? + define_method(field_name) do + return nil unless Google::Protobuf::FFI.get_message_has @msg, field_descriptor + mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field_descriptor, @arena + sub_message = mutable_message[:msg] + sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor) + Descriptor.send(:get_message, sub_message, sub_message_def, @arena) + end + else + c_type = field_descriptor.send(:c_type) + if c_type == :enum + define_method(field_name) do + message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor + convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field_descriptor) + end + else + define_method(field_name) do + message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor + convert_upb_to_ruby message_value, c_type + end + end + end + define_method("#{field_name}=") do |value| + index_assign_internal(value, field_descriptor: field_descriptor) + end + define_method("clear_#{field_name}") do + clear_internal(field_descriptor) + end + if field_descriptor.type == :enum + define_method("#{field_name}_const") do + if field_descriptor.repeated? + return_value = [] + get_field(field_descriptor).send(:each_msg_val) do |msg_val| + return_value << msg_val[:int32_val] + end + return_value + else + message_value = Google::Protobuf::FFI.get_message_value @msg, field_descriptor + message_value[:int32_val] + end + end + end + if !field_descriptor.repeated? and field_descriptor.wrapper? + define_method("#{field_name}_as_value") do + get_field(field_descriptor, unwrap: true) + end + define_method("#{field_name}_as_value=") do |value| + if value.nil? + clear_internal(field_descriptor) + else + index_assign_internal(value, field_descriptor: field_descriptor, wrap: true) + end + end + end + if field_descriptor.has_presence? + if field_descriptor.sub_message? or field_descriptor.send(:real_containing_oneof).nil? or + Google::Protobuf::FFI.message_def_syntax(Google::Protobuf::FFI.get_containing_message_def(field_descriptor)) == :Proto2 + define_method("has_#{field_name}?") do + Google::Protobuf::FFI.get_message_has(@msg, field_descriptor) + end + end + end + end + end + + @oneof_field_names = [] + + @descriptor.each_oneof do |oneof_descriptor| + field_name = oneof_descriptor.name.to_sym + @oneof_field_names << field_name + unless instance_methods(true).include?(field_name) + define_method(field_name) do + field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor) + if field_descriptor.nil? + return + else + return field_descriptor.name.to_sym + end + end + define_method("clear_#{field_name}") do + field_descriptor = Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor) + unless field_descriptor.nil? + clear_internal(field_descriptor) + end + end + define_method("has_#{field_name}?") do + !Google::Protobuf::FFI.get_message_which_oneof(@msg, oneof_descriptor).nil? + end + end + end + + private + # Implementation details below are subject to breaking changes without + # warning and are intended for use only within the gem. + + def method_missing_internal(method_name, *args, mode: nil) + raise ArgumentError.new "method_missing_internal called with invalid mode #{mode.inspect}" unless [:respond_to_missing?, :method_missing].include? mode + + #TODO(jatl) not being allowed is not the same thing as not responding, but this is needed to pass tests + if method_name.to_s.end_with? '=' + if self.class.send(:oneof_field_names).include? method_name.to_s[0..-2].to_sym + return false if mode == :respond_to_missing? + raise RuntimeError.new "Oneof accessors are read-only." + end + end + + original_method_missing(method_name, *args) if mode == :method_missing + end + + def self.private_constructor(arena, msg: nil, initial_value: nil) + instance = allocate + instance.send(:initialize, initial_value, arena, msg) + instance + end + + def clear_internal(field_def) + raise FrozenError.new "can't modify frozen #{self.class}" if frozen? + Google::Protobuf::FFI.clear_message_field(@msg, field_def) + end + + def index_internal(name) + field_descriptor = self.class.descriptor.lookup(name) + get_field field_descriptor unless field_descriptor.nil? + end + + #TODO(jatl) - well known types keeps us on our toes by overloading methods. + # How much of the public API needs to be defended? + def index_assign_internal(value, name: nil, field_descriptor: nil, wrap: false) + raise FrozenError.new "can't modify frozen #{self.class}" if frozen? + if field_descriptor.nil? + field_descriptor = self.class.descriptor.lookup(name) + if field_descriptor.nil? + raise ArgumentError.new "Unknown field: #{name}" + end + end + unless field_descriptor.send :set_value_on_message, value, @msg, @arena, wrap: wrap + raise RuntimeError.new "allocation failed" + end + end + + ## + # @param initial_value [Object] initial value of this Message + # @param arena [Arena] Optional; Arena where this message will be allocated + # @param msg [::FFI::Pointer] Optional; value of this message + def initialize(initial_value = nil, arena = nil, msg = nil) + @arena = arena || Google::Protobuf::FFI.create_arena + @msg = msg || Google::Protobuf::FFI.new_message_from_def(self.class.descriptor, @arena) + + unless initial_value.nil? + raise ArgumentError.new "Expected hash arguments or message, not #{initial_value.class}" unless initial_value.respond_to? :each + + field_def_ptr = ::FFI::MemoryPointer.new :pointer + oneof_def_ptr = ::FFI::MemoryPointer.new :pointer + + initial_value.each do |key, value| + raise ArgumentError.new "Expected string or symbols as hash keys when initializing proto from hash." unless [String, Symbol].include? key.class + + unless Google::Protobuf::FFI.find_msg_def_by_name self.class.descriptor, key.to_s, key.to_s.bytesize, field_def_ptr, oneof_def_ptr + raise ArgumentError.new "Unknown field name '#{key}' in initialization map entry." + end + raise NotImplementedError.new "Haven't added oneofsupport yet" unless oneof_def_ptr.get_pointer(0).null? + raise NotImplementedError.new "Expected a field def" if field_def_ptr.get_pointer(0).null? + + field_descriptor = FieldDescriptor.from_native field_def_ptr.get_pointer(0) + + next if value.nil? + if field_descriptor.map? + index_assign_internal(Google::Protobuf::Map.send(:construct_for_field, field_descriptor, @arena, value: value), name: key.to_s) + elsif field_descriptor.repeated? + index_assign_internal(RepeatedField.send(:construct_for_field, field_descriptor, @arena, values: value), name: key.to_s) + # TODO - Is it OK not trap this and just let []= convert it for me?? + # elsif field_descriptor.sub_message? + # raise NotImplementedError + else + index_assign_internal(value, name: key.to_s) + end + end + end + + # Should always be the last expression of the initializer to avoid + # leaking references to this object before construction is complete. + Google::Protobuf::ObjectCache.add @msg, self + end + + include Google::Protobuf::Internal::Convert + + + def self.inspect_field(field_descriptor, c_type, message_value) + if field_descriptor.sub_message? + sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(field_descriptor) + sub_msg_descriptor.msgclass.send(:inspect_internal, message_value[:msg_val]) + else + convert_upb_to_ruby(message_value, c_type, field_descriptor.subtype).inspect + end + end + + # @param field_def [::FFI::Pointer] Pointer to the Message + def self.inspect_internal(msg) + field_output = [] + descriptor.each do |field_descriptor| + next if field_descriptor.has_presence? && !Google::Protobuf::FFI.get_message_has(msg, field_descriptor) + if field_descriptor.map? + # TODO(jatl) Adapted - from map#each_msg_val and map#inspect- can this be refactored to reduce echo without introducing a arena allocation? + message_descriptor = field_descriptor.subtype + key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1) + key_field_type = Google::Protobuf::FFI.get_type(key_field_def) + + value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2) + value_field_type = Google::Protobuf::FFI.get_type(value_field_def) + + message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor) + iter = ::FFI::MemoryPointer.new(:size_t, 1) + iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) + key_value_pairs = [] + while Google::Protobuf::FFI.map_next(message_value[:map_val], iter) do + iter_size_t = iter.read(:size_t) + key_message_value = Google::Protobuf::FFI.map_key(message_value[:map_val], iter_size_t) + value_message_value = Google::Protobuf::FFI.map_value(message_value[:map_val], iter_size_t) + key_string = convert_upb_to_ruby(key_message_value, key_field_type).inspect + value_string = inspect_field(value_field_def, value_field_type, value_message_value) + key_value_pairs << "#{key_string}=>#{value_string}" + end + field_output << "#{field_descriptor.name}: {#{key_value_pairs.join(", ")}}" + elsif field_descriptor.repeated? + # TODO(jatl) Adapted - from repeated_field#each - can this be refactored to reduce echo? + repeated_field_output = [] + message_value = Google::Protobuf::FFI.get_message_value(msg, field_descriptor) + array = message_value[:array_val] + n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array) + 0.upto(n - 1) do |i| + element = Google::Protobuf::FFI.get_msgval_at(array, i) + repeated_field_output << inspect_field(field_descriptor, field_descriptor.send(:c_type), element) + end + field_output << "#{field_descriptor.name}: [#{repeated_field_output.join(", ")}]" + else + message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor + rendered_value = inspect_field(field_descriptor, field_descriptor.send(:c_type), message_value) + field_output << "#{field_descriptor.name}: #{rendered_value}" + end + end + "<#{name}: #{field_output.join(', ')}>" + end + + ## + # Gets a field of this message identified by the argument definition. + # + # @param field [FieldDescriptor] Descriptor of the field to get + def get_field(field, unwrap: false) + if field.map? + mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena + get_map_field(mutable_message_value[:map], field) + elsif field.repeated? + mutable_message_value = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena + get_repeated_field(mutable_message_value[:array], field) + elsif field.sub_message? + return nil unless Google::Protobuf::FFI.get_message_has @msg, field + sub_message_def = Google::Protobuf::FFI.get_subtype_as_message(field) + if unwrap + if field.has?(self) + wrapper_message_value = Google::Protobuf::FFI.get_message_value @msg, field + fields = Google::Protobuf::FFI.field_count(sub_message_def) + raise "Sub message has #{fields} fields! Expected exactly 1." unless fields == 1 + value_field_def = Google::Protobuf::FFI.get_field_by_number sub_message_def, 1 + message_value = Google::Protobuf::FFI.get_message_value wrapper_message_value[:msg_val], value_field_def + convert_upb_to_ruby message_value, Google::Protobuf::FFI.get_c_type(value_field_def) + else + nil + end + else + mutable_message = Google::Protobuf::FFI.get_mutable_message @msg, field, @arena + sub_message = mutable_message[:msg] + Descriptor.send(:get_message, sub_message, sub_message_def, @arena) + end + else + c_type = field.send(:c_type) + message_value = Google::Protobuf::FFI.get_message_value @msg, field + if c_type == :enum + convert_upb_to_ruby message_value, c_type, Google::Protobuf::FFI.get_subtype_as_enum(field) + else + convert_upb_to_ruby message_value, c_type + end + end + end + + ## + # @param array [::FFI::Pointer] Pointer to the Array + # @param field [Google::Protobuf::FieldDescriptor] Type of the repeated field + def get_repeated_field(array, field) + return nil if array.nil? or array.null? + repeated_field = ObjectCache.get(array) + if repeated_field.nil? + repeated_field = RepeatedField.send(:construct_for_field, field, @arena, array: array) + end + repeated_field + end + + + ## + # @param map [::FFI::Pointer] Pointer to the Map + # @param field [Google::Protobuf::FieldDescriptor] Type of the map field + def get_map_field(map, field) + return nil if map.nil? or map.null? + map_field = ObjectCache.get(map) + if map_field.nil? + map_field = Google::Protobuf::Map.send(:construct_for_field, field, @arena, map: map) + end + map_field + end + + def self.deep_copy(msg, arena = nil) + arena ||= Google::Protobuf::FFI.create_arena + encode_internal(msg) do |encoding, size, mini_table_ptr| + message = private_constructor(arena) + if encoding.nil? or encoding.null? or Google::Protobuf::FFI.decode_message(encoding, size, message.instance_variable_get(:@msg), mini_table_ptr, nil, 0, arena) != :Ok + raise ParseError.new "Error occurred copying proto" + end + message + end + end + + def self.encode_internal(msg, encoding_options = 0) + temporary_arena = Google::Protobuf::FFI.create_arena + + mini_table_ptr = Google::Protobuf::FFI.get_mini_table(descriptor) + size_ptr = ::FFI::MemoryPointer.new(:size_t, 1) + encoding = Google::Protobuf::FFI.encode_message(msg, mini_table_ptr, encoding_options, temporary_arena, size_ptr) + yield encoding, size_ptr.read(:size_t), mini_table_ptr + end + end + end + end + end +end diff --git a/ruby/lib/google/protobuf/descriptor_pool.rb b/ruby/lib/google/protobuf/descriptor_pool.rb new file mode 100644 index 000000000000..c12e7293f374 --- /dev/null +++ b/ruby/lib/google/protobuf/descriptor_pool.rb @@ -0,0 +1,83 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + class DescriptorPool + attr :descriptor_pool + attr_accessor :descriptor_class_by_def + + def initialize + @descriptor_pool = ::FFI::AutoPointer.new(Google::Protobuf::FFI.create_DescriptorPool, Google::Protobuf::FFI.method(:free_DescriptorPool)) + @descriptor_class_by_def = {} + + # Should always be the last expression of the initializer to avoid + # leaking references to this object before construction is complete. + Google::Protobuf::ObjectCache.add @descriptor_pool, self + end + + def add_serialized_file(file_contents) + # Allocate memory sized to file_contents + memBuf = ::FFI::MemoryPointer.new(:char, file_contents.bytesize) + # Insert the data + memBuf.put_bytes(0, file_contents) + file_descriptor_proto = Google::Protobuf::FFI.parse memBuf, file_contents.bytesize + raise ArgumentError.new("Unable to parse FileDescriptorProto") if file_descriptor_proto.null? + + status = Google::Protobuf::FFI::Status.new + file_descriptor = Google::Protobuf::FFI.add_serialized_file @descriptor_pool, file_descriptor_proto, status + if file_descriptor.null? + raise TypeError.new("Unable to build file to DescriptorPool: #{Google::Protobuf::FFI.error_message(status)}") + else + @descriptor_class_by_def[file_descriptor.address] = FileDescriptor.new file_descriptor, self + end + end + + def lookup name + Google::Protobuf::FFI.lookup_msg(@descriptor_pool, name) || + Google::Protobuf::FFI.lookup_enum(@descriptor_pool, name) + end + + def self.generated_pool + @@generated_pool ||= DescriptorPool.new + end + + private + + # Implementation details below are subject to breaking changes without + # warning and are intended for use only within the gem. + + def get_file_descriptor file_def + return nil if file_def.null? + @descriptor_class_by_def[file_def.address] ||= FileDescriptor.new(file_def, self) + end + end + end +end diff --git a/ruby/lib/google/protobuf/enum_descriptor.rb b/ruby/lib/google/protobuf/enum_descriptor.rb new file mode 100644 index 000000000000..a36f2fe07ef6 --- /dev/null +++ b/ruby/lib/google/protobuf/enum_descriptor.rb @@ -0,0 +1,192 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + class EnumDescriptor + attr :descriptor_pool, :enum_def + include Enumerable + + # FFI Interface methods and setup + extend ::FFI::DataConverter + native_type ::FFI::Type::POINTER + + class << self + prepend Google::Protobuf::Internal::TypeSafety + + # @param value [Arena] Arena to convert to an FFI native type + # @param _ [Object] Unused + def to_native(value, _) + value.instance_variable_get(:@enum_def) || ::FFI::Pointer::NULL + end + + ## + # @param enum_def [::FFI::Pointer] EnumDef pointer to be wrapped + # @param _ [Object] Unused + def from_native(enum_def, _) + return nil if enum_def.nil? or enum_def.null? + # Calling get_message_file_def(enum_def) would create a cyclic + # dependency because FFI would then complain about passing an + # FFI::Pointer instance instead of a Descriptor. Instead, directly + # read the top of the MsgDef structure an extract the FileDef*. + # file_def = Google::Protobuf::FFI.get_message_file_def enum_def + enum_def_struct = Google::Protobuf::FFI::Upb_EnumDef.new(enum_def) + file_def = enum_def_struct[:file_def] + raise RuntimeError.new "FileDef is nil" if file_def.nil? + raise RuntimeError.new "FileDef is null" if file_def.null? + pool_def = Google::Protobuf::FFI.file_def_pool file_def + raise RuntimeError.new "PoolDef is nil" if pool_def.nil? + raise RuntimeError.new "PoolDef is null" if pool_def.null? + pool = Google::Protobuf::ObjectCache.get(pool_def) + raise "Cannot find pool in ObjectCache!" if pool.nil? + descriptor = pool.descriptor_class_by_def[enum_def.address] + if descriptor.nil? + pool.descriptor_class_by_def[enum_def.address] = private_constructor(enum_def, pool) + else + descriptor + end + end + end + + def self.new(*arguments, &block) + raise "Descriptor objects may not be created from Ruby." + end + + def file_descriptor + @descriptor_pool.send(:get_file_descriptor, Google::Protobuf::FFI.get_enum_file_descriptor(self)) + end + + def name + Google::Protobuf::FFI.get_enum_fullname(self) + end + + def to_s + inspect + end + + def inspect + "#{self.class.name}: #{name}" + end + + def lookup_name(name) + self.class.send(:lookup_name, self, name) + end + + def lookup_value(number) + self.class.send(:lookup_value, self, number) + end + + def each &block + n = Google::Protobuf::FFI.enum_value_count(self) + 0.upto(n-1) do |i| + enum_value = Google::Protobuf::FFI.enum_value_by_index(self, i) + yield(Google::Protobuf::FFI.enum_name(enum_value).to_sym, Google::Protobuf::FFI.enum_number(enum_value)) + end + nil + end + + def enummodule + if @module.nil? + @module = build_enum_module + end + @module + end + + private + + def initialize(enum_def, descriptor_pool) + @descriptor_pool = descriptor_pool + @enum_def = enum_def + @module = nil + end + + def self.private_constructor(enum_def, descriptor_pool) + instance = allocate + instance.send(:initialize, enum_def, descriptor_pool) + instance + end + + def self.lookup_value(enum_def, number) + enum_value = Google::Protobuf::FFI.enum_value_by_number(enum_def, number) + if enum_value.null? + nil + else + Google::Protobuf::FFI.enum_name(enum_value).to_sym + end + end + + def self.lookup_name(enum_def, name) + enum_value = Google::Protobuf::FFI.enum_value_by_name(enum_def, name.to_s, name.size) + if enum_value.null? + nil + else + Google::Protobuf::FFI.enum_number(enum_value) + end + end + + def definition + @enum_def + end + + def build_enum_module + descriptor = self + dynamic_module = Module.new do + @descriptor = descriptor + class << self + attr_accessor :descriptor + end + + def self.lookup(number) + descriptor.lookup_value number + end + def self.resolve(name) + descriptor.lookup_name name + end + + private + def definition + self.class.send(:descriptor) + end + + end + self.each do |name, value| + if name[0] < 'A' || name[0] > 'Z' + warn( + "Enum value '#{name}' does not start with an uppercase letter " + + "as is required for Ruby constants.") + else + dynamic_module.const_set(name.to_sym, value) + end + end + dynamic_module + end + end + end +end diff --git a/ruby/lib/google/protobuf/ffi.rb b/ruby/lib/google/protobuf/ffi.rb new file mode 100644 index 000000000000..9a9ac0639420 --- /dev/null +++ b/ruby/lib/google/protobuf/ffi.rb @@ -0,0 +1,371 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +require 'ffi' +require 'ffi-compiler/loader' + +module Google + module Protobuf + class FFI + extend ::FFI::Library + # Workaround for Bazel's use of symlinks + JRuby's __FILE__ and `caller` + # that resolves them. + if ENV['BAZEL'] == 'true' + ffi_lib ::FFI::Compiler::Loader.find 'protobuf_c', ENV['PWD'] + else + ffi_lib ::FFI::Compiler::Loader.find 'protobuf_c' + end + + + Arena = Google::Protobuf::Internal::Arena + + # Partial definitions of the top of structs used for bootstrapping FFI. + class Upb_MessageDef < ::FFI::Struct + layout :opts, :pointer, + :mini_table, :pointer, + :file_def, :pointer + end + class Upb_EnumDef < ::FFI::Struct + layout :opts, :pointer, + :mini_table, :pointer, + :file_def, :pointer + end + class Upb_FieldDef < ::FFI::Struct + layout :opts, :pointer, + :file_def, :pointer + end + class Upb_OneofDef < ::FFI::Struct + layout :opts, :pointer, + :parent, :pointer + end + + MessageDef = Google::Protobuf::Descriptor + EnumDef = Google::Protobuf::EnumDescriptor + FieldDef = Google::Protobuf::FieldDescriptor + OneofDef = Google::Protobuf::OneofDescriptor + + typedef :pointer, :Array + typedef :pointer, :DefPool + typedef :pointer, :EnumValueDef + typedef :pointer, :ExtensionRegistry + typedef :pointer, :FieldDefPointer + typedef :pointer, :FileDef + typedef :pointer, :FileDescriptorProto + typedef :pointer, :Map + typedef :pointer, :Message # Instances of a message + typedef :pointer, :OneofDefPointer + typedef :pointer, :binary_string + if ::FFI::Platform::ARCH == "aarch64" + typedef :u_int8_t, :uint8_t + typedef :u_int16_t, :uint16_t + typedef :u_int32_t, :uint32_t + typedef :u_int64_t, :uint64_t + end + + FieldType = enum( + :double, 1, + :float, + :int64, + :uint64, + :int32, + :fixed64, + :fixed32, + :bool, + :string, + :group, + :message, + :bytes, + :uint32, + :enum, + :sfixed32, + :sfixed64, + :sint32, + :sint64 + ) + + CType = enum( + :bool, 1, + :float, + :int32, + :uint32, + :enum, + :message, + :double, + :int64, + :uint64, + :string, + :bytes + ) + + Label = enum( + :optional, 1, + :required, + :repeated + ) + + class StringView < ::FFI::Struct + layout :data, :pointer, + :size, :size_t + end + + class MessageValue < ::FFI::Union + layout :bool_val, :bool, + :float_val, :float, + :double_val, :double, + :int32_val, :int32_t, + :int64_val, :int64_t, + :uint32_val, :uint32_t, + :uint64_val,:uint64_t, + :map_val, :pointer, + :msg_val, :pointer, + :array_val,:pointer, + :str_val, StringView + end + + class MutableMessageValue < ::FFI::Union + layout :map, :Map, + :msg, :Message, + :array, :Array + end + + Syntax = enum( + :Proto2, 2, + :Proto3 + ) + + # All the different kind of well known type messages. For simplicity of check, + # number wrappers and string wrappers are grouped together. Make sure the + # order and merber of these groups are not changed. + + WellKnown = enum( + :Unspecified, + :Any, + :FieldMask, + :Duration, + :Timestamp, + # number wrappers + :DoubleValue, + :FloatValue, + :Int64Value, + :UInt64Value, + :Int32Value, + :UInt32Value, + # string wrappers + :StringValue, + :BytesValue, + :BoolValue, + :Value, + :ListValue, + :Struct + ) + + DecodeStatus = enum( + :Ok, + :Malformed, # Wire format was corrupt + :OutOfMemory, # Arena alloc failed + :BadUtf8, # String field had bad UTF-8 + :MaxDepthExceeded, # Exceeded UPB_DECODE_MAXDEPTH + + # CheckRequired failed, but the parse otherwise succeeded. + :MissingRequired, + ) + + class MiniTable < ::FFI::Struct + layout :subs, :pointer, + :fields, :pointer, + :size, :uint16_t, + :field_count, :uint16_t, + :ext, :uint8_t, # upb_ExtMode, declared as uint8_t so sizeof(ext) == 1 + :dense_below, :uint8_t, + :table_mask, :uint8_t, + :required_count, :uint8_t # Required fields have the lowest hasbits. + # To statically initialize the tables of variable length, we need a flexible + # array member, and we need to compile in gnu99 mode (constant initialization + # of flexible array members is a GNU extension, not in C99 unfortunately. */ + # _upb_FastTable_Entry fasttable[]; + end + + ## Map + Upb_Map_Begin = -1 + + ## Encoding Status + Upb_Status_MaxMessage = 127 + Upb_Encode_Deterministic = 1 + Upb_Encode_SkipUnknown = 2 + + ## JSON Encoding options + # When set, emits 0/default values. TODO(haberman): proto3 only? + Upb_JsonEncode_EmitDefaults = 1 + # When set, use normal (snake_case) field names instead of JSON (camelCase) names. + Upb_JsonEncode_UseProtoNames = 2 + + ## JSON Decoding options + Upb_JsonDecode_IgnoreUnknown = 1 + + class Status < ::FFI::Struct + layout :ok, :bool, + :msg, [:char, Upb_Status_MaxMessage] + + def initialize + super + FFI.clear self + end + end + + # Arena + attach_function :create_arena, :Arena_create, [], Arena + attach_function :fuse_arena, :upb_Arena_Fuse, [Arena, Arena], :bool + # Argument takes a :pointer rather than a typed Arena here due to + # implementation details of FFI::AutoPointer. + attach_function :free_arena, :upb_Arena_Free, [:pointer], :void + attach_function :arena_malloc, :upb_Arena_FastMalloc, [Arena, :size_t], :pointer + + # Array + attach_function :append_array, :upb_Array_Append, [:Array, MessageValue.by_value, Arena], :bool + attach_function :get_msgval_at,:upb_Array_Get, [:Array, :size_t], MessageValue.by_value + attach_function :create_array, :upb_Array_New, [Arena, CType], :Array + attach_function :array_resize, :upb_Array_Resize, [:Array, :size_t, Arena], :bool + attach_function :array_set, :upb_Array_Set, [:Array, :size_t, MessageValue.by_value], :void + attach_function :array_size, :upb_Array_Size, [:Array], :size_t + + # DefPool + attach_function :add_serialized_file, :upb_DefPool_AddFile, [:DefPool, :FileDescriptorProto, Status.by_ref], :FileDef + attach_function :free_DescriptorPool, :upb_DefPool_Free, [:DefPool], :void + attach_function :create_DescriptorPool, :upb_DefPool_New, [], :DefPool + attach_function :lookup_enum, :upb_DefPool_FindEnumByName, [:DefPool, :string], EnumDef + attach_function :lookup_msg, :upb_DefPool_FindMessageByName, [:DefPool, :string], MessageDef + + # EnumDescriptor + attach_function :get_enum_file_descriptor, :upb_EnumDef_File, [EnumDef], :FileDef + attach_function :enum_value_by_name, :upb_EnumDef_FindValueByNameWithSize,[EnumDef, :string, :size_t], :EnumValueDef + attach_function :enum_value_by_number, :upb_EnumDef_FindValueByNumber, [EnumDef, :int], :EnumValueDef + attach_function :get_enum_fullname, :upb_EnumDef_FullName, [EnumDef], :string + attach_function :enum_value_by_index, :upb_EnumDef_Value, [EnumDef, :int], :EnumValueDef + attach_function :enum_value_count, :upb_EnumDef_ValueCount, [EnumDef], :int + attach_function :enum_name, :upb_EnumValueDef_Name, [:EnumValueDef], :string + attach_function :enum_number, :upb_EnumValueDef_Number, [:EnumValueDef], :int + + # FileDescriptor + attach_function :file_def_name, :upb_FileDef_Name, [:FileDef], :string + attach_function :file_def_syntax, :upb_FileDef_Syntax, [:FileDef], Syntax + attach_function :file_def_pool, :upb_FileDef_Pool, [:FileDef], :DefPool + + # FileDescriptorProto + attach_function :parse, :FileDescriptorProto_parse, [:binary_string, :size_t], :FileDescriptorProto + + # FieldDescriptor + attach_function :get_containing_message_def, :upb_FieldDef_ContainingType, [FieldDescriptor], MessageDef + attach_function :get_c_type, :upb_FieldDef_CType, [FieldDescriptor], CType + attach_function :get_default, :upb_FieldDef_Default, [FieldDescriptor], MessageValue.by_value + attach_function :get_subtype_as_enum, :upb_FieldDef_EnumSubDef, [FieldDescriptor], EnumDef + attach_function :get_has_presence, :upb_FieldDef_HasPresence, [FieldDescriptor], :bool + attach_function :is_map, :upb_FieldDef_IsMap, [FieldDescriptor], :bool + attach_function :is_repeated, :upb_FieldDef_IsRepeated, [FieldDescriptor], :bool + attach_function :is_sub_message, :upb_FieldDef_IsSubMessage, [FieldDescriptor], :bool + attach_function :get_json_name, :upb_FieldDef_JsonName, [FieldDescriptor], :string + attach_function :get_label, :upb_FieldDef_Label, [FieldDescriptor], Label + attach_function :get_subtype_as_message, :upb_FieldDef_MessageSubDef, [FieldDescriptor], MessageDef + attach_function :get_full_name, :upb_FieldDef_Name, [FieldDescriptor], :string + attach_function :get_number, :upb_FieldDef_Number, [FieldDescriptor], :uint32_t + attach_function :real_containing_oneof, :upb_FieldDef_RealContainingOneof,[FieldDescriptor], OneofDef + attach_function :get_type, :upb_FieldDef_Type, [FieldDescriptor], FieldType + + # Map + attach_function :map_clear, :upb_Map_Clear, [:Map], :void + attach_function :map_delete, :upb_Map_Delete, [:Map, MessageValue.by_value], :bool + attach_function :map_get, :upb_Map_Get, [:Map, MessageValue.by_value, MessageValue.by_ref], :bool + attach_function :create_map, :upb_Map_New, [Arena, CType, CType], :Map + attach_function :map_size, :upb_Map_Size, [:Map], :size_t + attach_function :map_set, :upb_Map_Set, [:Map, MessageValue.by_value, MessageValue.by_value, Arena], :bool + + # MapIterator + attach_function :map_next, :upb_MapIterator_Next, [:Map, :pointer], :bool + attach_function :map_done, :upb_MapIterator_Done, [:Map, :size_t], :bool + attach_function :map_key, :upb_MapIterator_Key, [:Map, :size_t], MessageValue.by_value + attach_function :map_value, :upb_MapIterator_Value, [:Map, :size_t], MessageValue.by_value + + # MessageDef + attach_function :new_message_from_def, :upb_Message_New, [MessageDef, Arena], :Message + attach_function :get_field_by_index, :upb_MessageDef_Field, [MessageDef, :int], FieldDescriptor + attach_function :field_count, :upb_MessageDef_FieldCount, [MessageDef], :int + attach_function :get_message_file_def, :upb_MessageDef_File, [MessageDef], :FileDef + attach_function :get_field_by_name, :upb_MessageDef_FindFieldByNameWithSize, [MessageDef, :string, :size_t], FieldDescriptor + attach_function :get_field_by_number, :upb_MessageDef_FindFieldByNumber, [MessageDef, :uint32_t], FieldDescriptor + attach_function :get_oneof_by_name, :upb_MessageDef_FindOneofByNameWithSize, [MessageDef, :string, :size_t], OneofDef + attach_function :get_message_fullname, :upb_MessageDef_FullName, [MessageDef], :string + attach_function :get_mini_table, :upb_MessageDef_MiniTable, [MessageDef], MiniTable.ptr + attach_function :get_oneof_by_index, :upb_MessageDef_Oneof, [MessageDef, :int], OneofDef + attach_function :oneof_count, :upb_MessageDef_OneofCount, [MessageDef], :int + attach_function :get_well_known_type, :upb_MessageDef_WellKnownType, [MessageDef], WellKnown + attach_function :message_def_syntax, :upb_MessageDef_Syntax, [MessageDef], Syntax + attach_function :find_msg_def_by_name, :upb_MessageDef_FindByNameWithSize, [MessageDef, :string, :size_t, :FieldDefPointer, :OneofDefPointer], :bool + + # Message + attach_function :clear_message_field, :upb_Message_ClearField, [:Message, FieldDescriptor], :void + attach_function :get_message_value, :upb_Message_Get, [:Message, FieldDescriptor], MessageValue.by_value + attach_function :get_message_has, :upb_Message_Has, [:Message, FieldDescriptor], :bool + attach_function :set_message_field, :upb_Message_Set, [:Message, FieldDescriptor, MessageValue.by_value, Arena], :bool + attach_function :encode_message, :upb_Encode, [:Message, MiniTable.by_ref, :size_t, Arena, :pointer], :binary_string + attach_function :json_decode_message, :upb_JsonDecode, [:binary_string, :size_t, :Message, MessageDef, :DefPool, :int, Arena, Status.by_ref], :bool + attach_function :json_encode_message, :upb_JsonEncode, [:Message, MessageDef, :DefPool, :int, :binary_string, :size_t, Status.by_ref], :size_t + attach_function :decode_message, :upb_Decode, [:binary_string, :size_t, :Message, MiniTable.by_ref, :ExtensionRegistry, :int, Arena], DecodeStatus + attach_function :get_mutable_message, :upb_Message_Mutable, [:Message, FieldDescriptor, Arena], MutableMessageValue.by_value + attach_function :get_message_which_oneof, :upb_Message_WhichOneof, [:Message, OneofDef], FieldDescriptor + attach_function :message_discard_unknown, :upb_Message_DiscardUnknown, [:Message, MessageDef, :int], :bool + + # MessageValue + attach_function :message_value_equal, :Msgval_IsEqual, [MessageValue.by_value, MessageValue.by_value, CType, MessageDef], :bool + attach_function :message_value_hash, :Msgval_GetHash, [MessageValue.by_value, CType, MessageDef, :uint64_t], :uint64_t + + # OneofDescriptor + attach_function :get_oneof_name, :upb_OneofDef_Name, [OneofDef], :string + attach_function :get_oneof_field_count, :upb_OneofDef_FieldCount, [OneofDef], :int + attach_function :get_oneof_field_by_index, :upb_OneofDef_Field, [OneofDef, :int], FieldDescriptor + + # RepeatableField + + # Status + attach_function :clear, :upb_Status_Clear, [Status.by_ref], :void + attach_function :error_message, :upb_Status_ErrorMessage, [Status.by_ref], :string + + # Generic + attach_function :memcmp, [:pointer, :pointer, :size_t], :int + attach_function :memcpy, [:pointer, :pointer, :size_t], :int + + # Misc + attach_function :hash, :_upb_Hash, [:pointer, :size_t, :uint64_t], :uint32_t + + # Alternatives to pre-processor macros + def self.decode_max_depth(i) + i << 16 + end + end + end +end diff --git a/ruby/lib/google/protobuf/field_descriptor.rb b/ruby/lib/google/protobuf/field_descriptor.rb new file mode 100644 index 000000000000..b163d1bfdc3c --- /dev/null +++ b/ruby/lib/google/protobuf/field_descriptor.rb @@ -0,0 +1,326 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + class FieldDescriptor + attr :field_def, :descriptor_pool + + include Google::Protobuf::Internal::Convert + + # FFI Interface methods and setup + extend ::FFI::DataConverter + native_type ::FFI::Type::POINTER + + class << self + prepend Google::Protobuf::Internal::TypeSafety + + # @param value [FieldDescriptor] FieldDescriptor to convert to an FFI native type + # @param _ [Object] Unused + def to_native(value, _) + field_def_ptr = value.instance_variable_get(:@field_def) + warn "Underlying field_def was nil!" if field_def_ptr.nil? + raise "Underlying field_def was null!" if !field_def_ptr.nil? and field_def_ptr.null? + # || ::FFI::Pointer::NULL + field_def_ptr + end + + ## + # @param field_def [::FFI::Pointer] FieldDef pointer to be wrapped + # @param _ [Object] Unused + def from_native(field_def, _ = nil) + return nil if field_def.nil? or field_def.null? + # Calling upb_FieldDef_File(field_def) would create a cyclic + # dependency because either 1) we'd have to define the method to accept + # an untyped pointer or 2) FFI would complain about passing a + # FFI::Pointer instance instead of a FieldDescriptor. Instead, directly + # read the top of the FieldDef structure and extract the FileDef*. + field_def_struct = Google::Protobuf::FFI::Upb_FieldDef.new(field_def) + file_def = field_def_struct[:file_def] + raise RuntimeError.new "FileDef is nil" if file_def.nil? + raise RuntimeError.new "FileDef is null" if file_def.null? + pool_def = Google::Protobuf::FFI.file_def_pool file_def + raise RuntimeError.new "PoolDef is nil" if pool_def.nil? + raise RuntimeError.new "PoolDef is null" if pool_def.null? + pool = Google::Protobuf::ObjectCache.get(pool_def) + raise "Cannot find pool in ObjectCache!" if pool.nil? + descriptor = pool.descriptor_class_by_def[field_def.address] + if descriptor.nil? + pool.descriptor_class_by_def[field_def.address] = private_constructor(field_def, pool) + else + descriptor + end + end + end + + def self.new(*arguments, &block) + raise "Descriptor objects may not be created from Ruby." + end + + def to_s + inspect + end + + def inspect + "#{self.class.name}: #{name}" + end + + def name + @name ||= Google::Protobuf::FFI.get_full_name(self) + end + + def json_name + @json_name ||= Google::Protobuf::FFI.get_json_name(self) + end + + def number + @number ||= Google::Protobuf::FFI.get_number(self) + end + + def type + @type ||= Google::Protobuf::FFI.get_type(self) + end + + def label + @label ||= Google::Protobuf::FFI::Label[Google::Protobuf::FFI.get_label(self)] + end + + def default + return nil if Google::Protobuf::FFI.is_sub_message(self) + if Google::Protobuf::FFI.is_repeated(self) + message_value = Google::Protobuf::FFI::MessageValue.new + else + message_value = Google::Protobuf::FFI.get_default(self) + end + enum_def = Google::Protobuf::FFI.get_subtype_as_enum(self) + if enum_def.null? + convert_upb_to_ruby message_value, c_type + else + convert_upb_to_ruby message_value, c_type, enum_def + end + end + + def submsg_name + if defined? @submsg_name + @submsg_name + else + @submsg_name = case c_type + when :enum + Google::Protobuf::FFI.get_enum_fullname Google::Protobuf::FFI.get_subtype_as_enum self + when :message + Google::Protobuf::FFI.get_message_fullname Google::Protobuf::FFI.get_subtype_as_message self + else + nil + end + end + end + + ## + # Tests if this field has been set on the argument message. + # + # @param msg [Google::Protobuf::Message] + # @return [Object] Value of the field on this message. + # @raise [TypeError] If the field is not defined on this message. + def get(msg) + if msg.class.descriptor == Google::Protobuf::FFI.get_containing_message_def(self) + msg.send :get_field, self + else + raise TypeError.new "get method called on wrong message type" + end + end + + def subtype + if defined? @subtype + @subtype + else + @subtype = case c_type + when :enum + Google::Protobuf::FFI.get_subtype_as_enum(self) + when :message + Google::Protobuf::FFI.get_subtype_as_message(self) + else + nil + end + end + end + + ## + # Tests if this field has been set on the argument message. + # + # @param msg [Google::Protobuf::Message] + # @return [Boolean] True iff message has this field set + # @raise [TypeError] If this field does not exist on the message + # @raise [ArgumentError] If this field does not track presence + def has?(msg) + if msg.class.descriptor != Google::Protobuf::FFI.get_containing_message_def(self) + raise TypeError.new "has method called on wrong message type" + end + unless has_presence? + raise ArgumentError.new "does not track presence" + end + + Google::Protobuf::FFI.get_message_has msg.instance_variable_get(:@msg), self + end + + ## + # Tests if this field tracks presence. + # + # @return [Boolean] True iff this field tracks presence + def has_presence? + @has_presence ||= Google::Protobuf::FFI.get_has_presence(self) + end + + # @param msg [Google::Protobuf::Message] + def clear(msg) + if msg.class.descriptor != Google::Protobuf::FFI.get_containing_message_def(self) + raise TypeError.new "clear method called on wrong message type" + end + Google::Protobuf::FFI.clear_message_field msg.instance_variable_get(:@msg), self + nil + end + + ## + # call-seq: + # FieldDescriptor.set(message, value) + # + # Sets the value corresponding to this field to the given value on the given + # message. Raises an exception if message is of the wrong type. Performs the + # ordinary type-checks for field setting. + # + # @param msg [Google::Protobuf::Message] + # @param value [Object] + def set(msg, value) + if msg.class.descriptor != Google::Protobuf::FFI.get_containing_message_def(self) + raise TypeError.new "set method called on wrong message type" + end + unless set_value_on_message value, msg.instance_variable_get(:@msg), msg.instance_variable_get(:@arena) + raise RuntimeError.new "allocation failed" + end + nil + end + + def map? + @map ||= Google::Protobuf::FFI.is_map self + end + + def repeated? + @repeated ||= Google::Protobuf::FFI.is_repeated self + end + + def sub_message? + @sub_message ||= Google::Protobuf::FFI.is_sub_message self + end + + def wrapper? + if defined? @wrapper + @wrapper + else + message_descriptor = Google::Protobuf::FFI.get_subtype_as_message(self) + @wrapper = message_descriptor.nil? ? false : message_descriptor.send(:wrapper?) + end + end + + private + + def initialize(field_def, descriptor_pool) + @field_def = field_def + @descriptor_pool = descriptor_pool + end + + def self.private_constructor(field_def, descriptor_pool) + instance = allocate + instance.send(:initialize, field_def, descriptor_pool) + instance + end + + # TODO(jatl) Can this be added to the public API? + def real_containing_oneof + @real_containing_oneof ||= Google::Protobuf::FFI.real_containing_oneof self + end + + # Implementation details below are subject to breaking changes without + # warning and are intended for use only within the gem. + + ## + # Sets the field this FieldDescriptor represents to the given value on the given message. + # @param value [Object] Value to be set + # @param msg [::FFI::Pointer] Pointer the the upb_Message + # @param arena [Arena] Arena of the message that owns msg + def set_value_on_message(value, msg, arena, wrap: false) + message_to_alter = msg + field_def_to_set = self + if map? + raise TypeError.new "Expected map" unless value.is_a? Google::Protobuf::Map + message_descriptor = subtype + + key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1) + key_field_type = Google::Protobuf::FFI.get_type(key_field_def) + raise TypeError.new "Map key type does not match field's key type" unless key_field_type == value.send(:key_type) + + value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2) + value_field_type = Google::Protobuf::FFI.get_type(value_field_def) + raise TypeError.new "Map value type does not match field's value type" unless value_field_type == value.send(:value_type) + + raise TypeError.new "Map value type has wrong message/enum class" unless value_field_def.subtype == value.send(:descriptor) + + arena.fuse(value.send(:arena)) + message_value = Google::Protobuf::FFI::MessageValue.new + message_value[:map_val] = value.send(:map_ptr) + elsif repeated? + raise TypeError.new "Expected repeated field array" unless value.is_a? RepeatedField + raise TypeError.new "Repeated field array has wrong message/enum class" unless value.send(:type) == type + arena.fuse(value.send(:arena)) + message_value = Google::Protobuf::FFI::MessageValue.new + message_value[:array_val] = value.send(:array) + else + if value.nil? and (sub_message? or !real_containing_oneof.nil?) + Google::Protobuf::FFI.clear_message_field message_to_alter, field_def_to_set + return true + end + if wrap + value_field_def = Google::Protobuf::FFI.get_field_by_number subtype, 1 + type_for_conversion = Google::Protobuf::FFI.get_c_type(value_field_def) + raise RuntimeError.new "Not expecting to get a msg or enum when unwrapping" if [:enum, :message].include? type_for_conversion + message_value = convert_ruby_to_upb(value, arena, type_for_conversion, nil) + message_to_alter = Google::Protobuf::FFI.get_mutable_message(msg, self, arena)[:msg] + field_def_to_set = value_field_def + else + message_value = convert_ruby_to_upb(value, arena, c_type, subtype) + end + end + Google::Protobuf::FFI.set_message_field message_to_alter, field_def_to_set, message_value, arena + end + + def c_type + @c_type ||= Google::Protobuf::FFI.get_c_type(self) + end + end + end +end diff --git a/ruby/lib/google/protobuf/file_descriptor.rb b/ruby/lib/google/protobuf/file_descriptor.rb new file mode 100644 index 000000000000..d19838f5e470 --- /dev/null +++ b/ruby/lib/google/protobuf/file_descriptor.rb @@ -0,0 +1,65 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + class FileDescriptor + attr :descriptor_pool, :file_def + + def initialize(file_def, descriptor_pool) + @descriptor_pool = descriptor_pool + @file_def = file_def + end + + def to_s + inspect + end + + def inspect + "#{self.class.name}: #{name}" + end + + def syntax + case Google::Protobuf::FFI.file_def_syntax(@file_def) + when :Proto3 + :proto3 + when :Proto2 + :proto2 + else + nil + end + end + + def name + Google::Protobuf::FFI.file_def_name(@file_def) + end + end + end +end diff --git a/ruby/lib/google/protobuf/internal/arena.rb b/ruby/lib/google/protobuf/internal/arena.rb new file mode 100644 index 000000000000..946176e2e367 --- /dev/null +++ b/ruby/lib/google/protobuf/internal/arena.rb @@ -0,0 +1,73 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +## +# Implementation details below are subject to breaking changes without +# warning and are intended for use only within the gem. +module Google + module Protobuf + module Internal + class Arena + + # FFI Interface methods and setup + extend ::FFI::DataConverter + native_type ::FFI::Type::POINTER + + class << self + prepend Google::Protobuf::Internal::TypeSafety + + # @param value [Arena] Arena to convert to an FFI native type + # @param _ [Object] Unused + def to_native(value, _) + value.instance_variable_get(:@arena) || ::FFI::Pointer::NULL + end + + ## + # @param value [::FFI::Pointer] Arena pointer to be wrapped + # @param _ [Object] Unused + def from_native(value, _) + new(value) + end + end + + def initialize(pointer) + @arena = ::FFI::AutoPointer.new(pointer, Google::Protobuf::FFI.method(:free_arena)) + end + + def fuse(other_arena) + return if other_arena == self + unless Google::Protobuf::FFI.fuse_arena(self, other_arena) + raise RuntimeError.new "Unable to fuse arenas. This should never happen since Ruby does not use initial blocks" + end + end + end + end + end +end diff --git a/ruby/lib/google/protobuf/internal/convert.rb b/ruby/lib/google/protobuf/internal/convert.rb new file mode 100644 index 000000000000..aa1dc4a8958e --- /dev/null +++ b/ruby/lib/google/protobuf/internal/convert.rb @@ -0,0 +1,342 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +## +# Implementation details below are subject to breaking changes without +# warning and are intended for use only within the gem. +module Google + module Protobuf + module Internal + module Convert + + # Arena should be the + # @param value [Object] Value to convert + # @param arena [Arena] Arena that owns the Message where the MessageValue + # will be set + # @return [Google::Protobuf::FFI::MessageValue] + def convert_ruby_to_upb(value, arena, c_type, msg_or_enum_def) + raise ArgumentError.new "Expected Descriptor or EnumDescriptor, instead got #{msg_or_enum_def.class}" unless [NilClass, Descriptor, EnumDescriptor].include? msg_or_enum_def.class + return_value = Google::Protobuf::FFI::MessageValue.new + case c_type + when :float + raise TypeError.new "Expected number type for float field '#{name}' (given #{value.class})." unless value.respond_to? :to_f + return_value[:float_val] = value.to_f + when :double + raise TypeError.new "Expected number type for double field '#{name}' (given #{value.class})." unless value.respond_to? :to_f + return_value[:double_val] = value.to_f + when :bool + raise TypeError.new "Invalid argument for boolean field '#{name}' (given #{value.class})." unless [TrueClass, FalseClass].include? value.class + return_value[:bool_val] = value + when :string + raise TypeError.new "Invalid argument for string field '#{name}' (given #{value.class})." unless [Symbol, String].include? value.class + begin + string_value = value.to_s.encode("UTF-8") + rescue Encoding::UndefinedConversionError + # TODO(jatl) - why not include the field name here? + raise Encoding::UndefinedConversionError.new "String is invalid UTF-8" + end + return_value[:str_val][:size] = string_value.bytesize + return_value[:str_val][:data] = Google::Protobuf::FFI.arena_malloc(arena, string_value.bytesize) + # TODO(jatl) - how important is it to still use arena malloc, versus the following? + # buffer = ::FFI::MemoryPointer.new(:char, string_value.bytesize) + # buffer.put_bytes(0, string_value) + # return_value[:str_val][:data] = buffer + raise NoMemoryError.new "Cannot allocate #{string_value.bytesize} bytes for string on Arena" if return_value[:str_val][:data].nil? || return_value[:str_val][:data].null? + return_value[:str_val][:data].write_string(string_value) + when :bytes + raise TypeError.new "Invalid argument for bytes field '#{name}' (given #{value.class})." unless value.is_a? String + string_value = value.encode("ASCII-8BIT") + return_value[:str_val][:size] = string_value.bytesize + return_value[:str_val][:data] = Google::Protobuf::FFI.arena_malloc(arena, string_value.bytesize) + raise NoMemoryError.new "Cannot allocate #{string_value.bytesize} bytes for bytes on Arena" if return_value[:str_val][:data].nil? || return_value[:str_val][:data].null? + return_value[:str_val][:data].write_string_length(string_value, string_value.bytesize) + when :message + raise TypeError.new "nil message not allowed here." if value.nil? + if value.is_a? Hash + raise RuntimeError.new "Attempted to initialize message from Hash for field #{name} but have no definition" if msg_or_enum_def.nil? + new_message = msg_or_enum_def.msgclass. + send(:private_constructor, arena, initial_value: value) + return_value[:msg_val] = new_message.instance_variable_get(:@msg) + return return_value + end + + descriptor = value.class.respond_to?(:descriptor) ? value.class.descriptor : nil + if descriptor != msg_or_enum_def + wkt = Google::Protobuf::FFI.get_well_known_type(msg_or_enum_def) + case wkt + when :Timestamp + raise TypeError.new "Invalid type #{value.class} to assign to submessage field '#{name}'." unless value.kind_of? Time + new_message = Google::Protobuf::FFI.new_message_from_def msg_or_enum_def, arena + sec = Google::Protobuf::FFI::MessageValue.new + sec[:int64_val] = value.tv_sec + sec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 1 + raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, sec_field_def, sec, arena + nsec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 2 + nsec = Google::Protobuf::FFI::MessageValue.new + nsec[:int32_val] = value.tv_nsec + raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, nsec_field_def, nsec, arena + return_value[:msg_val] = new_message + when :Duration + raise TypeError.new "Invalid type #{value.class} to assign to submessage field '#{name}'." unless value.kind_of? Numeric + new_message = Google::Protobuf::FFI.new_message_from_def msg_or_enum_def, arena + sec = Google::Protobuf::FFI::MessageValue.new + sec[:int64_val] = value + sec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 1 + raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, sec_field_def, sec, arena + nsec_field_def = Google::Protobuf::FFI.get_field_by_number msg_or_enum_def, 2 + nsec = Google::Protobuf::FFI::MessageValue.new + nsec[:int32_val] = ((value.to_f - value.to_i) * 1000000000).round + raise "Should be impossible" unless Google::Protobuf::FFI.set_message_field new_message, nsec_field_def, nsec, arena + return_value[:msg_val] = new_message + else + raise TypeError.new "Invalid type #{value.class} to assign to submessage field '#{name}'." + end + else + arena.fuse(value.instance_variable_get(:@arena)) + return_value[:msg_val] = value.instance_variable_get :@msg + end + when :enum + return_value[:int32_val] = case value + when Numeric + value.to_i + when String, Symbol + enum_number = EnumDescriptor.send(:lookup_name, msg_or_enum_def, value.to_s) + #TODO(jatl) add the bad value to the error message after tests pass + raise RangeError.new "Unknown symbol value for enum field '#{name}'." if enum_number.nil? + enum_number + else + raise TypeError.new "Expected number or symbol type for enum field '#{name}'." + end + #TODO(jatl) After all tests pass, improve error message across integer type by including actual offending value + when :int32 + raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric + raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value + raise RangeError.new "Value assigned to int32 field '#{name}' (given #{value.class}) with more than 32-bits." unless value.to_i.bit_length < 32 + return_value[:int32_val] = value.to_i + when :uint32 + raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric + raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value + raise RangeError.new "Assigning negative value to unsigned integer field '#{name}' (given #{value.class})." if value < 0 + raise RangeError.new "Value assigned to uint32 field '#{name}' (given #{value.class}) with more than 32-bits." unless value.to_i.bit_length < 33 + return_value[:uint32_val] = value.to_i + when :int64 + raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric + raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value + raise RangeError.new "Value assigned to int64 field '#{name}' (given #{value.class}) with more than 64-bits." unless value.to_i.bit_length < 64 + return_value[:int64_val] = value.to_i + when :uint64 + raise TypeError.new "Expected number type for integral field '#{name}' (given #{value.class})." unless value.is_a? Numeric + raise RangeError.new "Non-integral floating point value assigned to integer field '#{name}' (given #{value.class})." if value.floor != value + raise RangeError.new "Assigning negative value to unsigned integer field '#{name}' (given #{value.class})." if value < 0 + raise RangeError.new "Value assigned to uint64 field '#{name}' (given #{value.class}) with more than 64-bits." unless value.to_i.bit_length < 65 + return_value[:uint64_val] = value.to_i + else + raise RuntimeError.new "Unsupported type #{c_type}" + end + return_value + end + + ## + # Safe to call without an arena if the caller has checked that c_type + # is not :message. + # @param message_value [Google::Protobuf::FFI::MessageValue] Value to be converted. + # @param c_type [Google::Protobuf::FFI::CType] Enum representing the type of message_value + # @param msg_or_enum_def [::FFI::Pointer] Pointer to the MsgDef or EnumDef definition + # @param arena [Google::Protobuf::Internal::Arena] Arena to create Message instances, if needed + def convert_upb_to_ruby(message_value, c_type, msg_or_enum_def = nil, arena = nil) + throw TypeError.new "Expected MessageValue but got #{message_value.class}" unless message_value.is_a? Google::Protobuf::FFI::MessageValue + + case c_type + when :bool + message_value[:bool_val] + when :int32 + message_value[:int32_val] + when :uint32 + message_value[:uint32_val] + when :double + message_value[:double_val] + when :int64 + message_value[:int64_val] + when :uint64 + message_value[:uint64_val] + when :string + if message_value[:str_val][:size].zero? + "" + else + message_value[:str_val][:data].read_string_length(message_value[:str_val][:size]).force_encoding("UTF-8").freeze + end + when :bytes + if message_value[:str_val][:size].zero? + "" + else + message_value[:str_val][:data].read_string_length(message_value[:str_val][:size]).force_encoding("ASCII-8BIT").freeze + end + when :float + message_value[:float_val] + when :enum + EnumDescriptor.send(:lookup_value, msg_or_enum_def, message_value[:int32_val]) || message_value[:int32_val] + when :message + raise "Null Arena for message" if arena.nil? + Descriptor.send(:get_message, message_value[:msg_val], msg_or_enum_def, arena) + else + raise RuntimeError.new "Unexpected type #{c_type}" + end + end + + # @param message_descriptor [Descriptor] Message Descriptor + # @return [::FFI::Pointer] PoolDef pointer + def pool_def_from_message_definition(message_descriptor) + raise RuntimeError.new "Descriptor is nil" if message_descriptor.nil? + file_def = Google::Protobuf::FFI.get_message_file_def message_descriptor + raise RuntimeError.new "FileDef is nil" if file_def.nil? + raise RuntimeError.new "FileDef is null" if file_def.null? + pool_def = Google::Protobuf::FFI.file_def_pool file_def + raise RuntimeError.new "PoolDef is nil" if pool_def.nil? + raise RuntimeError.new "PoolDef is null" if pool_def.null? + pool_def + end + + def to_h_internal(msg, message_descriptor) + return nil if msg.nil? or msg.null? + hash = {} + is_proto2 = Google::Protobuf::FFI.message_def_syntax(message_descriptor) == :Proto2 + message_descriptor.each do |field_descriptor| + # TODO: Legacy behavior, remove when we fix the is_proto2 differences. + if !is_proto2 and + field_descriptor.sub_message? and + !field_descriptor.repeated? and + !Google::Protobuf::FFI.get_message_has(msg, field_descriptor) + hash[field_descriptor.name.to_sym] = nil + next + end + + # Do not include fields that are not present (oneof or optional fields). + if is_proto2 and field_descriptor.has_presence? and !Google::Protobuf::FFI.get_message_has(msg, field_descriptor) + next + end + + message_value = Google::Protobuf::FFI.get_message_value msg, field_descriptor + + # Proto2 omits empty map/repeated fields also. + if field_descriptor.map? + hash_entry = map_create_hash(message_value[:map_val], field_descriptor) + elsif field_descriptor.repeated? + array = message_value[:array_val] + if is_proto2 and (array.null? || Google::Protobuf::FFI.array_size(array).zero?) + next + end + hash_entry = repeated_field_create_array(array, field_descriptor, field_descriptor.type) + else + hash_entry = scalar_create_hash(message_value, field_descriptor.type, field_descriptor: field_descriptor) + end + + hash[field_descriptor.name.to_sym] = hash_entry + + end + + hash + end + + def map_create_hash(map_ptr, field_descriptor) + return {} if map_ptr.nil? or map_ptr.null? + return_value = {} + + message_descriptor = field_descriptor.send(:subtype) + key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1) + key_field_type = Google::Protobuf::FFI.get_type(key_field_def) + + value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2) + value_field_type = Google::Protobuf::FFI.get_type(value_field_def) + + iter = ::FFI::MemoryPointer.new(:size_t, 1) + iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) + while Google::Protobuf::FFI.map_next(map_ptr, iter) do + iter_size_t = iter.read(:size_t) + key_message_value = Google::Protobuf::FFI.map_key(map_ptr, iter_size_t) + value_message_value = Google::Protobuf::FFI.map_value(map_ptr, iter_size_t) + hash_key = convert_upb_to_ruby(key_message_value, key_field_type) + hash_value = scalar_create_hash(value_message_value, value_field_type, msg_or_enum_descriptor: value_field_def.subtype) + return_value[hash_key] = hash_value + end + return_value + end + + def repeated_field_create_array(array, field_descriptor, type) + return_value = [] + # puts "JATL - about to call FFI method array_size(#{array}); stacktrace:\n#{caller.join("\n")}" + n = (array.nil? || array.null?) ? 0 : Google::Protobuf::FFI.array_size(array) + 0.upto(n - 1) do |i| + message_value = Google::Protobuf::FFI.get_msgval_at(array, i) + return_value << scalar_create_hash(message_value, type, field_descriptor: field_descriptor) + end + return_value + end + + # @param field_descriptor [FieldDescriptor] Descriptor of the field to convert to a hash. + def scalar_create_hash(message_value, type, field_descriptor: nil, msg_or_enum_descriptor: nil) + if [:message, :enum].include? type + if field_descriptor.nil? + if msg_or_enum_descriptor.nil? + raise "scalar_create_hash requires either a FieldDescriptor, MessageDescriptor, or EnumDescriptor as an argument, but received only nil" + end + else + msg_or_enum_descriptor = field_descriptor.subtype + end + if type == :message + to_h_internal(message_value[:msg_val], msg_or_enum_descriptor) + elsif type == :enum + convert_upb_to_ruby message_value, type, msg_or_enum_descriptor + end + else + convert_upb_to_ruby message_value, type + end + end + + def message_value_deep_copy(message_value, type, descriptor, arena) + raise unless message_value.is_a? Google::Protobuf::FFI::MessageValue + new_message_value = Google::Protobuf::FFI::MessageValue.new + case type + when :string, :bytes + # TODO(jatl) - how important is it to still use arena malloc, versus using FFI MemoryPointers? + new_message_value[:str_val][:size] = message_value[:str_val][:size] + new_message_value[:str_val][:data] = Google::Protobuf::FFI.arena_malloc(arena, message_value[:str_val][:size]) + raise NoMemoryError.new "Allocation failed" if new_message_value[:str_val][:data].nil? or new_message_value[:str_val][:data].null? + Google::Protobuf::FFI.memcpy(new_message_value[:str_val][:data], message_value[:str_val][:data], message_value[:str_val][:size]) + when :message + new_message_value[:msg_val] = descriptor.msgclass.send(:deep_copy, message_value[:msg_val], arena).instance_variable_get(:@msg) + else + Google::Protobuf::FFI.memcpy(new_message_value.to_ptr, message_value.to_ptr, Google::Protobuf::FFI::MessageValue.size) + end + new_message_value + end + end + end + end +end diff --git a/ruby/lib/google/protobuf/internal/type_safety.rb b/ruby/lib/google/protobuf/internal/type_safety.rb new file mode 100644 index 000000000000..82903a7bc5a3 --- /dev/null +++ b/ruby/lib/google/protobuf/internal/type_safety.rb @@ -0,0 +1,48 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +# A to_native DataConverter method that raises an error if the value is not of the same type. +# Adapted from to https://www.varvet.com/blog/advanced-topics-in-ruby-ffi/ +module Google + module Protobuf + module Internal + module TypeSafety + def to_native(value, ctx) + if value.kind_of?(self) or value.nil? + super + else + raise TypeError.new "Expected a kind of #{name}, was #{value.class}" + end + end + end + end + end +end + diff --git a/ruby/lib/google/protobuf/map.rb b/ruby/lib/google/protobuf/map.rb new file mode 100644 index 000000000000..2a581075f59b --- /dev/null +++ b/ruby/lib/google/protobuf/map.rb @@ -0,0 +1,404 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + class Map + include Enumerable + ## + # call-seq: + # Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) + # => new map + # + # Allocates a new Map container. This constructor may be called with 2, 3, or 4 + # arguments. The first two arguments are always present and are symbols (taking + # on the same values as field-type symbols in message descriptors) that + # indicate the type of the map key and value fields. + # + # The supported key types are: :int32, :int64, :uint32, :uint64, :bool, + # :string, :bytes. + # + # The supported value types are: :int32, :int64, :uint32, :uint64, :bool, + # :string, :bytes, :enum, :message. + # + # The third argument, value_typeclass, must be present if value_type is :enum + # or :message. As in RepeatedField#new, this argument must be a message class + # (for :message) or enum module (for :enum). + # + # The last argument, if present, provides initial content for map. Note that + # this may be an ordinary Ruby hashmap or another Map instance with identical + # key and value types. Also note that this argument may be present whether or + # not value_typeclass is present (and it is unambiguously separate from + # value_typeclass because value_typeclass's presence is strictly determined by + # value_type). The contents of this initial hashmap or Map instance are + # shallow-copied into the new Map: the original map is unmodified, but + # references to underlying objects will be shared if the value type is a + # message type. + def self.new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) + instance = allocate + # TODO(jatl) This argument mangling doesn't agree with the type signature, + # but does align with the text of the comments and is required to make unit tests pass. + if init_hashmap.empty? and ![:enum, :message].include?(value_type) + init_hashmap = value_typeclass + value_typeclass = nil + end + instance.send(:initialize, key_type, value_type, value_type_class: value_typeclass, initial_values: init_hashmap) + instance + end + + ## + # call-seq: + # Map.keys => [list_of_keys] + # + # Returns the list of keys contained in the map, in unspecified order. + def keys + return_value = [] + internal_iterator do |iterator| + key_message_value = Google::Protobuf::FFI.map_key(@map_ptr, iterator) + return_value << convert_upb_to_ruby(key_message_value, key_type) + end + return_value + end + + ## + # call-seq: + # Map.values => [list_of_values] + # + # Returns the list of values contained in the map, in unspecified order. + def values + return_value = [] + internal_iterator do |iterator| + value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator) + return_value << convert_upb_to_ruby(value_message_value, value_type, descriptor, arena) + end + return_value + end + + ## + # call-seq: + # Map.[](key) => value + # + # Accesses the element at the given key. Throws an exception if the key type is + # incorrect. Returns nil when the key is not present in the map. + def [](key) + value = Google::Protobuf::FFI::MessageValue.new + key_message_value = convert_ruby_to_upb(key, arena, key_type, nil) + if Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, value) + convert_upb_to_ruby(value, value_type, descriptor, arena) + end + end + + ## + # call-seq: + # Map.[]=(key, value) => value + # + # Inserts or overwrites the value at the given key with the given new value. + # Throws an exception if the key type is incorrect. Returns the new value that + # was just inserted. + def []=(key, value) + raise FrozenError.new "can't modify frozen #{self.class}" if frozen? + key_message_value = convert_ruby_to_upb(key, arena, key_type, nil) + value_message_value = convert_ruby_to_upb(value, arena, value_type, descriptor) + Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena) + value + end + + def has_key?(key) + key_message_value = convert_ruby_to_upb(key, arena, key_type, nil) + Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, nil) + end + + ## + # call-seq: + # Map.delete(key) => old_value + # + # Deletes the value at the given key, if any, returning either the old value or + # nil if none was present. Throws an exception if the key is of the wrong type. + def delete(key) + raise FrozenError.new "can't modify frozen #{self.class}" if frozen? + value = Google::Protobuf::FFI::MessageValue.new + key_message_value = convert_ruby_to_upb(key, arena, key_type, nil) + return_value = if Google::Protobuf::FFI.map_get(@map_ptr, key_message_value, value) + convert_upb_to_ruby(value, value_type, descriptor, arena) + end + Google::Protobuf::FFI.map_delete(@map_ptr, key_message_value) + return_value + end + + def clear + raise FrozenError.new "can't modify frozen #{self.class}" if frozen? + Google::Protobuf::FFI.map_clear(@map_ptr) + nil + end + + def length + Google::Protobuf::FFI.map_size(@map_ptr) + end + alias size length + + ## + # call-seq: + # Map.dup => new_map + # + # Duplicates this map with a shallow copy. References to all non-primitive + # element objects (e.g., submessages) are shared. + def dup + internal_dup + end + alias clone dup + + ## + # call-seq: + # Map.==(other) => boolean + # + # Compares this map to another. Maps are equal if they have identical key sets, + # and for each key, the values in both maps compare equal. Elements are + # compared as per normal Ruby semantics, by calling their :== methods (or + # performing a more efficient comparison for primitive types). + # + # Maps with dissimilar key types or value types/typeclasses are never equal, + # even if value comparison (for example, between integers and floats) would + # have otherwise indicated that every element has equal value. + def ==(other) + if other.is_a? Hash + other = self.class.send(:private_constructor, key_type, value_type, descriptor, initial_values: other) + elsif !other.is_a? Google::Protobuf::Map + return false + end + + return true if object_id == other.object_id + return false if key_type != other.send(:key_type) or value_type != other.send(:value_type) or descriptor != other.send(:descriptor) or length != other.length + other_map_ptr = other.send(:map_ptr) + each_msg_val do |key_message_value, value_message_value| + other_value = Google::Protobuf::FFI::MessageValue.new + return false unless Google::Protobuf::FFI.map_get(other_map_ptr, key_message_value, other_value) + return false unless Google::Protobuf::FFI.message_value_equal(value_message_value, other_value, value_type, descriptor) + end + true + end + + def hash + return_value = 0 + each_msg_val do |key_message_value, value_message_value| + return_value = Google::Protobuf::FFI.message_value_hash(key_message_value, key_type, nil, return_value) + return_value = Google::Protobuf::FFI.message_value_hash(value_message_value, value_type, descriptor, return_value) + end + return_value + end + + ## + # call-seq: + # Map.to_h => {} + # + # Returns a Ruby Hash object containing all the values within the map + def to_h + return {} if map_ptr.nil? or map_ptr.null? + return_value = {} + each_msg_val do |key_message_value, value_message_value| + hash_key = convert_upb_to_ruby(key_message_value, key_type) + hash_value = scalar_create_hash(value_message_value, value_type, msg_or_enum_descriptor: descriptor) + return_value[hash_key] = hash_value + end + return_value + end + + def inspect + key_value_pairs = [] + each_msg_val do |key_message_value, value_message_value| + key_string = convert_upb_to_ruby(key_message_value, key_type).inspect + if value_type == :message + sub_msg_descriptor = Google::Protobuf::FFI.get_subtype_as_message(descriptor) + value_string = sub_msg_descriptor.msgclass.send(:inspect_internal, value_message_value[:msg_val]) + else + value_string = convert_upb_to_ruby(value_message_value, value_type, descriptor).inspect + end + key_value_pairs << "#{key_string}=>#{value_string}" + end + "{#{key_value_pairs.join(", ")}}" + end + + ## + # call-seq: + # Map.merge(other_map) => map + # + # Copies key/value pairs from other_map into a copy of this map. If a key is + # set in other_map and this map, the value from other_map overwrites the value + # in the new copy of this map. Returns the new copy of this map with merged + # contents. + def merge(other) + internal_merge(other) + end + + ## + # call-seq: + # Map.each(&block) + # + # Invokes &block on each |key, value| pair in the map, in unspecified order. + # Note that Map also includes Enumerable; map thus acts like a normal Ruby + # sequence. + def each &block + each_msg_val do |key_message_value, value_message_value| + key_value = convert_upb_to_ruby(key_message_value, key_type) + value_value = convert_upb_to_ruby(value_message_value, value_type, descriptor, arena) + yield key_value, value_value + end + nil + end + + private + attr :arena, :map_ptr, :key_type, :value_type, :descriptor, :name + + include Google::Protobuf::Internal::Convert + + def internal_iterator + iter = ::FFI::MemoryPointer.new(:size_t, 1) + iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) + while Google::Protobuf::FFI.map_next(@map_ptr, iter) do + iter_size_t = iter.read(:size_t) + yield iter_size_t + end + end + + def each_msg_val &block + internal_iterator do |iterator| + key_message_value = Google::Protobuf::FFI.map_key(@map_ptr, iterator) + value_message_value = Google::Protobuf::FFI.map_value(@map_ptr, iterator) + yield key_message_value, value_message_value + end + end + + def internal_dup + instance = self.class.send(:private_constructor, key_type, value_type, descriptor, arena: arena) + new_map_ptr = instance.send(:map_ptr) + each_msg_val do |key_message_value, value_message_value| + Google::Protobuf::FFI.map_set(new_map_ptr, key_message_value, value_message_value, arena) + end + instance + end + + def internal_merge_into_self(other) + case other + when Hash + other.each do |key, value| + key_message_value = convert_ruby_to_upb(key, arena, key_type, nil) + value_message_value = convert_ruby_to_upb(value, arena, value_type, descriptor) + Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena) + end + when Google::Protobuf::Map + unless key_type == other.send(:key_type) and value_type == other.send(:value_type) and descriptor == other.descriptor + raise ArgumentError.new "Attempt to merge Map with mismatching types" #TODO(jatl) Improve error message by adding type information + end + arena.fuse(other.send(:arena)) + iter = ::FFI::MemoryPointer.new(:size_t, 1) + iter.write(:size_t, Google::Protobuf::FFI::Upb_Map_Begin) + other.send(:each_msg_val) do |key_message_value, value_message_value| + Google::Protobuf::FFI.map_set(@map_ptr, key_message_value, value_message_value, arena) + end + else + raise ArgumentError.new "Unknown type merging into Map" #TODO(jatl) improve this error message by including type information + end + self + end + + def internal_merge(other) + internal_dup.internal_merge_into_self(other) + end + + def initialize(key_type, value_type, value_type_class: nil, initial_values: nil, arena: nil, map: nil, descriptor: nil, name: nil) + @name = name || 'Map' + + unless [:int32, :int64, :uint32, :uint64, :bool, :string, :bytes].include? key_type + raise ArgumentError.new "Invalid key type for map." #TODO(jatl) improve error message to include what type was passed + end + @key_type = key_type + + unless [:int32, :int64, :uint32, :uint64, :bool, :string, :bytes, :enum, :message].include? value_type + raise ArgumentError.new "Invalid value type for map." #TODO(jatl) improve error message to include what type was passed + end + @value_type = value_type + + if !descriptor.nil? + raise ArgumentError "Expected descriptor to be a Descriptor or EnumDescriptor" unless [EnumDescriptor, Descriptor].include? descriptor.class + @descriptor = descriptor + elsif [:message, :enum].include? value_type + raise ArgumentError.new "Expected at least 3 arguments for message/enum." if value_type_class.nil? + descriptor = value_type_class.respond_to?(:descriptor) ? value_type_class.descriptor : nil + raise ArgumentError.new "Type class #{value_type_class} has no descriptor. Please pass a class or enum as returned by the DescriptorPool." if descriptor.nil? + @descriptor = descriptor + else + @descriptor = nil + end + + @arena = arena || Google::Protobuf::FFI.create_arena + @map_ptr = map || Google::Protobuf::FFI.create_map(@arena, @key_type, @value_type) + + internal_merge_into_self(initial_values) unless initial_values.nil? + + # Should always be the last expression of the initializer to avoid + # leaking references to this object before construction is complete. + ObjectCache.add(@map_ptr, self) + end + + # @param field [FieldDescriptor] Descriptor of the field where the RepeatedField will be assigned + # @param values [Hash|Map] Initial value; may be nil or empty + # @param arena [Arena] Owning message's arena + def self.construct_for_field(field, arena, value: nil, map: nil) + raise ArgumentError.new "Expected Hash object as initializer value for map field '#{field.name}' (given #{value.class})." unless value.nil? or value.is_a? Hash + instance = allocate + raise ArgumentError.new "Expected field with type :message, instead got #{field.class}" unless field.type == :message + message_descriptor = field.send(:subtype) + key_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 1) + key_field_type = Google::Protobuf::FFI.get_type(key_field_def) + + value_field_def = Google::Protobuf::FFI.get_field_by_number(message_descriptor, 2) + value_field_type = Google::Protobuf::FFI.get_type(value_field_def) + instance.send(:initialize, key_field_type, value_field_type, initial_values: value, name: field.name, arena: arena, map: map, descriptor: value_field_def.subtype) + instance + end + + def self.private_constructor(key_type, value_type, descriptor, initial_values: nil, arena: nil) + instance = allocate + instance.send(:initialize, key_type, value_type, descriptor: descriptor, initial_values: initial_values, arena: arena) + instance + end + + extend Google::Protobuf::Internal::Convert + + def self.deep_copy(map) + instance = allocate + instance.send(:initialize, map.send(:key_type), map.send(:value_type), descriptor: map.send(:descriptor)) + map.send(:each_msg_val) do |key_message_value, value_message_value| + Google::Protobuf::FFI.map_set(instance.send(:map_ptr), key_message_value, message_value_deep_copy(value_message_value, map.send(:value_type), map.send(:descriptor), instance.send(:arena)), instance.send(:arena)) + end + instance + end + end + end +end diff --git a/ruby/lib/google/protobuf/object_cache.rb b/ruby/lib/google/protobuf/object_cache.rb new file mode 100644 index 000000000000..8ad0de109bee --- /dev/null +++ b/ruby/lib/google/protobuf/object_cache.rb @@ -0,0 +1,94 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +require 'weakref' + +module Google + module Protobuf + module ObjectCache + @@lock = Mutex.new + @@cache = {} + def self.drop(key) + @@lock.synchronize do + @@cache.delete(key.address) + end + end + + def self.get(key) + @@lock.synchronize do + value = @@cache[key.address] + begin + if value.nil? + return nil + else + if value.weakref_alive? + return value.__getobj__ + else + @@cache.delete(key.address) + return nil + end + end + rescue WeakRef::RefError + @@cache.delete(key.address) + return nil + end + end + nil + end + + def self.add(key, value) + raise ArgumentError.new "WeakRef values are not allowed" if value.is_a? WeakRef + @@lock.synchronize do + if @@cache.include? key.address + existing_value = @@cache[key.address] + begin + if existing_value.nil? + raise ArgumentError.new "Key already exists in ObjectCache but has nil value" + else + if existing_value.weakref_alive? + original = existing_value.__getobj__ + raise ArgumentError.new "Key already exists in ObjectCache for different value" unless original.object_id == value.object_id + else + @@cache.delete(key.address) + nil + end + end + rescue WeakRef::RefError + @@cache.delete(key.address) + nil + end + end + @@cache[key.address] = WeakRef.new value + nil + end + end + end + end +end \ No newline at end of file diff --git a/ruby/lib/google/protobuf/oneof_descriptor.rb b/ruby/lib/google/protobuf/oneof_descriptor.rb new file mode 100644 index 000000000000..366c5e8030a1 --- /dev/null +++ b/ruby/lib/google/protobuf/oneof_descriptor.rb @@ -0,0 +1,114 @@ +# Protocol Buffers - Google's data interchange format +# Copyright 2022 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. + +module Google + module Protobuf + class OneofDescriptor + attr :descriptor_pool, :oneof_def + include Enumerable + + # FFI Interface methods and setup + extend ::FFI::DataConverter + native_type ::FFI::Type::POINTER + + class << self + prepend Google::Protobuf::Internal::TypeSafety + + # @param value [OneofDescriptor] FieldDescriptor to convert to an FFI native type + # @param _ [Object] Unused + def to_native(value, _ = nil) + oneof_def_ptr = value.instance_variable_get(:@oneof_def) + warn "Underlying oneof_def was nil!" if oneof_def_ptr.nil? + raise "Underlying oneof_def was null!" if !oneof_def_ptr.nil? and oneof_def_ptr.null? + # || ::FFI::Pointer::NULL + oneof_def_ptr + end + + ## + # @param oneof_def [::FFI::Pointer] OneofDef pointer to be wrapped + # @param _ [Object] Unused + def from_native(oneof_def, _ = nil) + return nil if oneof_def.nil? or oneof_def.null? + # Calling upb_OneofDef_ContainingType(oneof_def) would create a cyclic + # dependency because either 1) we'd have to define the method to accept + # an untyped pointer or 2) FFI would complain about passing a + # FFI::Pointer instance instead of a OneofDescriptor. Instead, directly + # read the top of the OneDef structure and extract the MsgDef*. + oneof_def_struct = Google::Protobuf::FFI::Upb_OneofDef.new(oneof_def) + message_descriptor = Descriptor.from_native(oneof_def_struct[:parent]) + raise RuntimeError.new "Message Descriptor is nil" if message_descriptor.nil? + file_def = Google::Protobuf::FFI.get_message_file_def message_descriptor + raise RuntimeError.new "FileDef is nil" if file_def.nil? + raise RuntimeError.new "FileDef is null" if file_def.null? + pool_def = Google::Protobuf::FFI.file_def_pool file_def + raise RuntimeError.new "PoolDef is nil" if pool_def.nil? + raise RuntimeError.new "PoolDef is null" if pool_def.null? + pool = Google::Protobuf::ObjectCache.get(pool_def) + raise "Cannot find pool in ObjectCache!" if pool.nil? + descriptor = pool.descriptor_class_by_def[oneof_def.address] + if descriptor.nil? + pool.descriptor_class_by_def[oneof_def.address] = private_constructor(oneof_def, pool) + else + descriptor + end + end + end + + def self.new(*arguments, &block) + raise "OneofDescriptor objects may not be created from Ruby." + end + + def name + Google::Protobuf::FFI.get_oneof_name(self) + end + + def each &block + n = Google::Protobuf::FFI.get_oneof_field_count(self) + 0.upto(n-1) do |i| + yield(Google::Protobuf::FFI.get_oneof_field_by_index(self, i)) + end + nil + end + + private + + def initialize(oneof_def, descriptor_pool) + @descriptor_pool = descriptor_pool + @oneof_def = oneof_def + end + + def self.private_constructor(oneof_def, descriptor_pool) + instance = allocate + instance.send(:initialize, oneof_def, descriptor_pool) + instance + end + end + end +end diff --git a/ruby/lib/google/protobuf/repeated_field.rb b/ruby/lib/google/protobuf/repeated_field.rb index 13b9300b2efd..542e57434400 100644 --- a/ruby/lib/google/protobuf/repeated_field.rb +++ b/ruby/lib/google/protobuf/repeated_field.rb @@ -49,22 +49,6 @@ module Google module Protobuf class RepeatedField extend Forwardable - - # methods defined in C or Java: - # + - # [], at - # []= - # concat - # clear - # dup, clone - # each - # push, << - # replace - # length, size - # == - # to_ary, to_a - # also all enumerable - # # NOTE: using delegators rather than method_missing to make the # relationship explicit instead of implicit def_delegators :to_ary, @@ -77,6 +61,219 @@ class RepeatedField :rindex, :rotate, :sample, :shuffle, :shelljoin, :to_s, :transpose, :uniq, :| + include Enumerable + + ## + # call-seq: + # RepeatedField.new(type, type_class = nil, initial_values = []) + # + # Creates a new repeated field. The provided type must be a Ruby symbol, and + # an take on the same values as those accepted by FieldDescriptor#type=. If + # the type is :message or :enum, type_class must be non-nil, and must be the + # Ruby class or module returned by Descriptor#msgclass or + # EnumDescriptor#enummodule, respectively. An initial list of elements may also + # be provided. + def self.new(type, type_class = nil, initial_values = []) + instance = allocate + # TODO(jatl) This argument mangling doesn't agree with the type signature in the comments + # but is required to make unit tests pass; + if type_class.is_a?(Enumerable) and initial_values.empty? and ![:enum, :message].include?(type) + initial_values = type_class + type_class = nil + end + instance.send(:initialize, type, type_class: type_class, initial_values: initial_values) + instance + end + + ## + # call-seq: + # RepeatedField.each(&block) + # + # Invokes the block once for each element of the repeated field. RepeatedField + # also includes Enumerable; combined with this method, the repeated field thus + # acts like an ordinary Ruby sequence. + def each &block + each_msg_val do |element| + yield(convert_upb_to_ruby(element, type, descriptor, arena)) + end + self + end + + def [](*args) + count = length + if args.size < 1 + raise ArgumentError.new "Index or range is a required argument." + end + if args[0].is_a? Range + if args.size > 1 + raise ArgumentError.new "Expected 1 when passing Range argument, but got #{args.size}" + end + range = args[0] + # Handle begin-less and/or endless ranges, when supported. + index_of_first = range.respond_to?(:begin) ? range.begin : range.last + index_of_first = 0 if index_of_first.nil? + end_of_range = range.respond_to?(:end) ? range.end : range.last + index_of_last = end_of_range.nil? ? -1 : end_of_range + + if index_of_last < 0 + index_of_last += count + end + unless range.exclude_end? and !end_of_range.nil? + index_of_last += 1 + end + index_of_first += count if index_of_first < 0 + length = index_of_last - index_of_first + return [] if length.zero? + elsif args[0].is_a? Integer + index_of_first = args[0] + index_of_first += count if index_of_first < 0 + if args.size > 2 + raise ArgumentError.new "Expected 1 or 2 arguments, but got #{args.size}" + end + if args.size == 1 # No length specified, return one element + if array.null? or index_of_first < 0 or index_of_first >= count + return nil + else + return convert_upb_to_ruby(Google::Protobuf::FFI.get_msgval_at(array, index_of_first), type, descriptor, arena) + end + else + length = [args[1],count].min + end + else + raise NotImplementedError + end + + if array.null? or index_of_first < 0 or index_of_first >= count + nil + else + if index_of_first + length > count + length = count - index_of_first + end + if length < 0 + nil + else + subarray(index_of_first, length) + end + end + end + alias at [] + + + def []=(index, value) + raise FrozenError if frozen? + count = length + index += count if index < 0 + return nil if index < 0 + if index >= count + resize(index+1) + empty_message_value = Google::Protobuf::FFI::MessageValue.new # Implicitly clear + count.upto(index-1) do |i| + Google::Protobuf::FFI.array_set(array, i, empty_message_value) + end + end + Google::Protobuf::FFI.array_set(array, index, convert_ruby_to_upb(value, arena, type, descriptor)) + nil + end + + def push(*elements) + raise FrozenError if frozen? + internal_push(*elements) + end + + def <<(element) + raise FrozenError if frozen? + push element + end + + def replace(replacements) + raise FrozenError if frozen? + clear + push(*replacements) + end + + def clear + raise FrozenError if frozen? + resize 0 + self + end + + def length + array.null? ? 0 : Google::Protobuf::FFI.array_size(array) + end + alias size :length + + def dup + instance = self.class.allocate + instance.send(:initialize, type, descriptor: descriptor, arena: arena) + each_msg_val do |element| + instance.send(:append_msg_val, element) + end + instance + end + alias clone dup + + def ==(other) + return true if other.object_id == object_id + if other.is_a? RepeatedField + return false unless other.length == length + each_msg_val_with_index do |msg_val, i| + other_msg_val = Google::Protobuf::FFI.get_msgval_at(other.send(:array), i) + unless Google::Protobuf::FFI.message_value_equal(msg_val, other_msg_val, type, descriptor) + return false + end + end + return true + elsif other.is_a? Enumerable + return to_ary == other.to_a + end + false + end + + ## + # call-seq: + # RepeatedField.to_ary => array + # + # Used when converted implicitly into array, e.g. compared to an Array. + # Also called as a fallback of Object#to_a + def to_ary + return_value = [] + each do |element| + return_value << element + end + return_value + end + + def hash + return_value = 0 + each_msg_val do |msg_val| + return_value = Google::Protobuf::FFI.message_value_hash(msg_val, type, descriptor, return_value) + end + return_value + end + + def +(other) + if other.is_a? RepeatedField + if type != other.instance_variable_get(:@type) or descriptor != other.instance_variable_get(:@descriptor) + raise ArgumentError.new "Attempt to append RepeatedField with different element type." + end + fuse_arena(other.send(:arena)) + super_set = dup + other.send(:each_msg_val) do |msg_val| + super_set.send(:append_msg_val, msg_val) + end + super_set + elsif other.is_a? Enumerable + super_set = dup + super_set.push(*other.to_a) + else + raise ArgumentError.new "Unknown type appending to RepeatedField" + end + end + + def concat(other) + raise ArgumentError.new "Expected Enumerable, but got #{other.class}" unless other.is_a? Enumerable + push(*other.to_a) + end def first(n=nil) if n.nil? @@ -195,6 +392,124 @@ def each(*args, &block) end end + private + include Google::Protobuf::Internal::Convert + + attr :name, :arena, :array, :type, :descriptor + + def internal_push(*elements) + elements.each do |element| + append_msg_val convert_ruby_to_upb(element, arena, type, descriptor) + end + self + end + + def pop_one + raise FrozenError if frozen? + count = length + return nil if length.zero? + last_element = Google::Protobuf::FFI.get_msgval_at(array, count-1) + return_value = convert_upb_to_ruby(last_element, type, descriptor, arena) + resize(count-1) + return_value + end + + def subarray(start, length) + return_result = [] + (start..(start + length - 1)).each do |i| + element = Google::Protobuf::FFI.get_msgval_at(array, i) + return_result << convert_upb_to_ruby(element, type, descriptor, arena) + end + return_result + end + + def each_msg_val_with_index &block + n = array.null? ? 0 : Google::Protobuf::FFI.array_size(array) + 0.upto(n-1) do |i| + yield Google::Protobuf::FFI.get_msgval_at(array, i), i + end + end + + def each_msg_val &block + each_msg_val_with_index do |msg_val, _| + yield msg_val + end + end + + # @param msg_val [Google::Protobuf::FFI::MessageValue] Value to append + def append_msg_val(msg_val) + unless Google::Protobuf::FFI.append_array(array, msg_val, arena) + raise NoMemoryError.new "Could not allocate room for #{msg_val} in Arena" + end + end + + # @param new_size [Integer] New size of the array + def resize(new_size) + unless Google::Protobuf::FFI.array_resize(array, new_size, arena) + raise NoMemoryError.new "Array resize to #{new_size} failed!" + end + end + + def initialize(type, type_class: nil, initial_values: nil, name: nil, arena: nil, array: nil, descriptor: nil) + @name = name || 'RepeatedField' + raise ArgumentError.new "Expected argument type to be a Symbol" unless type.is_a? Symbol + field_number = Google::Protobuf::FFI::FieldType[type] + raise ArgumentError.new "Unsupported type '#{type}'" if field_number.nil? + if !descriptor.nil? + @descriptor = descriptor + elsif [:message, :enum].include? type + raise ArgumentError.new "Expected at least 2 arguments for message/enum." if type_class.nil? + descriptor = type_class.respond_to?(:descriptor) ? type_class.descriptor : nil + raise ArgumentError.new "Type class #{type_class} has no descriptor. Please pass a class or enum as returned by the DescriptorPool." if descriptor.nil? + @descriptor = descriptor + else + @descriptor = nil + end + @type = type + + @arena = arena || Google::Protobuf::FFI.create_arena + @array = array || Google::Protobuf::FFI.create_array(@arena, @type) + unless initial_values.nil? + unless initial_values.is_a? Enumerable + raise ArgumentError.new "Expected array as initializer value for repeated field '#{name}' (given #{initial_values.class})." + end + internal_push(*initial_values) + end + + # Should always be the last expression of the initializer to avoid + # leaking references to this object before construction is complete. + ObjectCache.add(@array, self) + end + + # @param field [FieldDescriptor] Descriptor of the field where the RepeatedField will be assigned + # @param values [Enumerable] Initial values; may be nil or empty + # @param arena [Arena] Owning message's arena + def self.construct_for_field(field, arena, values: nil, array: nil) + instance = allocate + options = {initial_values: values, name: field.name, arena: arena, array: array} + if [:enum, :message].include? field.type + options[:descriptor] = field.subtype + end + instance.send(:initialize, field.type, **options) + instance + end + + def fuse_arena(arena) + arena.fuse(arena) + end + + extend Google::Protobuf::Internal::Convert + + def self.deep_copy(repeated_field) + instance = allocate + instance.send(:initialize, repeated_field.send(:type), descriptor: repeated_field.send(:descriptor)) + instance.send(:resize, repeated_field.length) + new_array = instance.send(:array) + repeated_field.send(:each_msg_val_with_index) do |element, i| + Google::Protobuf::FFI.array_set(new_array, i, message_value_deep_copy(element, repeated_field.send(:type), repeated_field.send(:descriptor), instance.send(:arena))) + end + instance + end end end diff --git a/ruby/lib/google/protobuf/well_known_types.rb b/ruby/lib/google/protobuf/well_known_types.rb index 2d06ca27422a..3421d497bca3 100755 --- a/ruby/lib/google/protobuf/well_known_types.rb +++ b/ruby/lib/google/protobuf/well_known_types.rb @@ -170,6 +170,7 @@ def from_ruby(value) end Struct.class_eval do + #TODO(jatl) overriding [] this way breaks the ability of method_missing to dispatch directly to [] def [](key) self.fields[key].to_ruby rescue NoMethodError diff --git a/ruby/pom.xml b/ruby/pom.xml deleted file mode 100644 index d56c99d785a2..000000000000 --- a/ruby/pom.xml +++ /dev/null @@ -1,88 +0,0 @@ - - - 4.0.0 - - com.google - google - 1 - - - com.google.protobuf.jruby - protobuf-jruby - 3.21.5 - Protocol Buffer JRuby native extension - - Protocol Buffers are a way of encoding structured data in an efficient yet - extensible format. - - 2014 - https://developers.google.com/protocol-buffers/ - - - BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause - repo - - - - https://github.com/protocolbuffers/protobuf - - scm:git:https://github.com/protocolbuffers/protobuf.git - - - - - UTF-8 - lib/google - protobuf_java - - - - - org.apache.maven.plugins - maven-assembly-plugin - 3.3.0 - - ${jar.finalName} - ${ruby.sources} - false - - jar-with-dependencies - - - - - make-assembly - package - - single - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - - - - - com.google.protobuf - protobuf-java-util - 3.21.5 - - - org.jruby - jruby-complete - 9.2.20.1 - provided - - - diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java deleted file mode 100644 index b80925331525..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptor.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.OneofDescriptor; -import java.util.HashMap; -import java.util.Map; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "Descriptor", include = "Enumerable") -public class RubyDescriptor extends RubyObject { - public static void createRubyDescriptor(Ruby runtime) { - RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cDescriptor = - protobuf.defineClassUnder( - "Descriptor", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyDescriptor(runtime, klazz); - } - }); - cDescriptor.includeModule(runtime.getEnumerable()); - cDescriptor.defineAnnotatedMethods(RubyDescriptor.class); - cFieldDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::FieldDescriptor"); - cOneofDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::OneofDescriptor"); - } - - public RubyDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } - - /* - * call-seq: - * Descriptor.name => name - * - * Returns the name of this message type as a fully-qualified string (e.g., - * My.Package.MessageType). - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return name; - } - - /* - * call-seq: - * Descriptor.lookup(name) => FieldDescriptor - * - * Returns the field descriptor for the field with the given name, if present, - * or nil if none. - */ - @JRubyMethod - public IRubyObject lookup(ThreadContext context, IRubyObject fieldName) { - return Helpers.nullToNil(fieldDescriptors.get(fieldName), context.nil); - } - - /* - * call-seq: - * Descriptor.msgclass => message_klass - * - * Returns the Ruby class created for this message type. Valid only once the - * message type has been added to a pool. - */ - @JRubyMethod - public IRubyObject msgclass(ThreadContext context) { - return klazz; - } - - /* - * call-seq: - * Descriptor.each(&block) - * - * Iterates over fields in this message type, yielding to the block on each one. - */ - @JRubyMethod - public IRubyObject each(ThreadContext context, Block block) { - for (Map.Entry entry : fieldDescriptors.entrySet()) { - block.yield(context, entry.getValue()); - } - return context.nil; - } - - /* - * call-seq: - * Descriptor.file_descriptor - * - * Returns the FileDescriptor object this message belongs to. - */ - @JRubyMethod(name = "file_descriptor") - public IRubyObject getFileDescriptor(ThreadContext context) { - return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); - } - - /* - * call-seq: - * Descriptor.each_oneof(&block) => nil - * - * Invokes the given block for each oneof in this message type, passing the - * corresponding OneofDescriptor. - */ - @JRubyMethod(name = "each_oneof") - public IRubyObject eachOneof(ThreadContext context, Block block) { - for (RubyOneofDescriptor oneofDescriptor : oneofDescriptors.values()) { - block.yieldSpecific(context, oneofDescriptor); - } - return context.nil; - } - - /* - * call-seq: - * Descriptor.lookup_oneof(name) => OneofDescriptor - * - * Returns the oneof descriptor for the oneof with the given name, if present, - * or nil if none. - */ - @JRubyMethod(name = "lookup_oneof") - public IRubyObject lookupOneof(ThreadContext context, IRubyObject name) { - return Helpers.nullToNil(oneofDescriptors.get(Utils.symToString(name)), context.nil); - } - - protected FieldDescriptor getField(String name) { - return descriptor.findFieldByName(name); - } - - protected void setDescriptor( - ThreadContext context, Descriptor descriptor, RubyDescriptorPool pool) { - Ruby runtime = context.runtime; - Map cache = new HashMap(); - this.descriptor = descriptor; - - // Populate the field caches - fieldDescriptors = new HashMap(); - oneofDescriptors = new HashMap(); - - for (FieldDescriptor fieldDescriptor : descriptor.getFields()) { - RubyFieldDescriptor fd = - (RubyFieldDescriptor) cFieldDescriptor.newInstance(context, Block.NULL_BLOCK); - fd.setDescriptor(context, fieldDescriptor, pool); - fieldDescriptors.put(runtime.newString(fieldDescriptor.getName()), fd); - cache.put(fieldDescriptor, fd); - } - - for (OneofDescriptor oneofDescriptor : descriptor.getRealOneofs()) { - RubyOneofDescriptor ood = - (RubyOneofDescriptor) cOneofDescriptor.newInstance(context, Block.NULL_BLOCK); - ood.setDescriptor(context, oneofDescriptor, cache); - oneofDescriptors.put(runtime.newString(oneofDescriptor.getName()), ood); - } - - // Make sure our class is built - this.klazz = buildClassFromDescriptor(context); - } - - protected void setName(IRubyObject name) { - this.name = name; - } - - private RubyClass buildClassFromDescriptor(ThreadContext context) { - Ruby runtime = context.runtime; - - ObjectAllocator allocator = - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyMessage(runtime, klazz, descriptor); - } - }; - - // rb_define_class_id - RubyClass klass = RubyClass.newClass(runtime, runtime.getObject()); - klass.setAllocator(allocator); - klass.makeMetaClass(runtime.getObject().getMetaClass()); - klass.inherit(runtime.getObject()); - RubyModule messageExts = runtime.getClassFromPath("Google::Protobuf::MessageExts"); - klass.include(new IRubyObject[] {messageExts}); - klass.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); - klass.defineAnnotatedMethods(RubyMessage.class); - // Workaround for https://github.com/jruby/jruby/issues/7154 - klass.searchMethod("respond_to?").setIsBuiltin(false); - return klass; - } - - private static RubyClass cFieldDescriptor; - private static RubyClass cOneofDescriptor; - - private Descriptor descriptor; - private IRubyObject name; - private Map fieldDescriptors; - private Map oneofDescriptors; - private RubyClass klazz; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java deleted file mode 100644 index d65b412a0a9e..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyDescriptorPool.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.DescriptorProtos.FileDescriptorProto; -import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.DescriptorValidationException; -import com.google.protobuf.Descriptors.EnumDescriptor; -import com.google.protobuf.Descriptors.FileDescriptor; -import com.google.protobuf.InvalidProtocolBufferException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.exceptions.RaiseException; -import org.jruby.runtime.*; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "DescriptorPool") -public class RubyDescriptorPool extends RubyObject { - public static void createRubyDescriptorPool(Ruby runtime) { - RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cDescriptorPool = - protobuf.defineClassUnder( - "DescriptorPool", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyDescriptorPool(runtime, klazz); - } - }); - - cDescriptorPool.defineAnnotatedMethods(RubyDescriptorPool.class); - descriptorPool = - (RubyDescriptorPool) - cDescriptorPool.newInstance(runtime.getCurrentContext(), Block.NULL_BLOCK); - cDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Descriptor"); - cEnumDescriptor = (RubyClass) runtime.getClassFromPath("Google::Protobuf::EnumDescriptor"); - } - - public RubyDescriptorPool(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - this.fileDescriptors = new ArrayList<>(); - this.symtab = new HashMap(); - } - - @JRubyMethod - public IRubyObject build(ThreadContext context, Block block) { - RubyClass cBuilder = - (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::Internal::Builder"); - RubyBasicObject ctx = (RubyBasicObject) cBuilder.newInstance(context, this, Block.NULL_BLOCK); - ctx.instance_eval(context, block); - ctx.callMethod(context, "build"); // Needs to be called to support the deprecated syntax - return context.nil; - } - - /* - * call-seq: - * DescriptorPool.lookup(name) => descriptor - * - * Finds a Descriptor or EnumDescriptor by name and returns it, or nil if none - * exists with the given name. - * - * This currently lazy loads the ruby descriptor objects as they are requested. - * This allows us to leave the heavy lifting to the java library - */ - @JRubyMethod - public IRubyObject lookup(ThreadContext context, IRubyObject name) { - return Helpers.nullToNil(symtab.get(name), context.nil); - } - - /* - * call-seq: - * DescriptorPool.generated_pool => descriptor_pool - * - * Class method that returns the global DescriptorPool. This is a singleton into - * which generated-code message and enum types are registered. The user may also - * register types in this pool for convenience so that they do not have to hold - * a reference to a private pool instance. - */ - @JRubyMethod(meta = true, name = "generated_pool") - public static IRubyObject generatedPool(ThreadContext context, IRubyObject recv) { - return descriptorPool; - } - - @JRubyMethod(required = 1) - public IRubyObject add_serialized_file(ThreadContext context, IRubyObject data) { - byte[] bin = data.convertToString().getBytes(); - try { - FileDescriptorProto.Builder builder = FileDescriptorProto.newBuilder().mergeFrom(bin); - registerFileDescriptor(context, builder); - } catch (InvalidProtocolBufferException e) { - throw RaiseException.from( - context.runtime, - (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), - e.getMessage()); - } - return context.nil; - } - - protected void registerFileDescriptor( - ThreadContext context, FileDescriptorProto.Builder builder) { - final FileDescriptor fd; - try { - fd = FileDescriptor.buildFrom(builder.build(), existingFileDescriptors()); - } catch (DescriptorValidationException e) { - throw context.runtime.newRuntimeError(e.getMessage()); - } - - String packageName = fd.getPackage(); - if (!packageName.isEmpty()) { - packageName = packageName + "."; - } - - // Need to make sure enums are registered first in case anything references them - for (EnumDescriptor ed : fd.getEnumTypes()) registerEnumDescriptor(context, ed, packageName); - for (Descriptor message : fd.getMessageTypes()) - registerDescriptor(context, message, packageName); - - // Mark this as a loaded file - fileDescriptors.add(fd); - } - - private void registerDescriptor(ThreadContext context, Descriptor descriptor, String parentPath) { - String fullName = parentPath + descriptor.getName(); - String fullPath = fullName + "."; - RubyString name = context.runtime.newString(fullName); - - RubyDescriptor des = (RubyDescriptor) cDescriptor.newInstance(context, Block.NULL_BLOCK); - des.setName(name); - des.setDescriptor(context, descriptor, this); - symtab.put(name, des); - - // Need to make sure enums are registered first in case anything references them - for (EnumDescriptor ed : descriptor.getEnumTypes()) - registerEnumDescriptor(context, ed, fullPath); - for (Descriptor message : descriptor.getNestedTypes()) - registerDescriptor(context, message, fullPath); - } - - private void registerEnumDescriptor( - ThreadContext context, EnumDescriptor descriptor, String parentPath) { - RubyString name = context.runtime.newString(parentPath + descriptor.getName()); - RubyEnumDescriptor des = - (RubyEnumDescriptor) cEnumDescriptor.newInstance(context, Block.NULL_BLOCK); - des.setName(name); - des.setDescriptor(context, descriptor); - symtab.put(name, des); - } - - private FileDescriptor[] existingFileDescriptors() { - return fileDescriptors.toArray(new FileDescriptor[fileDescriptors.size()]); - } - - private static RubyClass cDescriptor; - private static RubyClass cEnumDescriptor; - private static RubyDescriptorPool descriptorPool; - - private List fileDescriptors; - private Map symtab; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java deleted file mode 100644 index 95d961e11a2c..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnum.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import org.jruby.RubyModule; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -public class RubyEnum { - /* - * call-seq: - * Enum.lookup(number) => name - * - * This module method, provided on each generated enum module, looks up an enum - * value by number and returns its name as a Ruby symbol, or nil if not found. - */ - @JRubyMethod(meta = true) - public static IRubyObject lookup(ThreadContext context, IRubyObject recv, IRubyObject number) { - RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv); - return rubyEnumDescriptor.numberToName(context, number); - } - - /* - * call-seq: - * Enum.resolve(name) => number - * - * This module method, provided on each generated enum module, looks up an enum - * value by name (as a Ruby symbol) and returns its name, or nil if not found. - */ - @JRubyMethod(meta = true) - public static IRubyObject resolve(ThreadContext context, IRubyObject recv, IRubyObject name) { - RubyEnumDescriptor rubyEnumDescriptor = (RubyEnumDescriptor) getDescriptor(context, recv); - return rubyEnumDescriptor.nameToNumber(context, name); - } - - /* - * call-seq: - * Enum.descriptor - * - * This module method, provided on each generated enum module, returns the - * EnumDescriptor corresponding to this enum type. - */ - @JRubyMethod(meta = true, name = "descriptor") - public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) { - return ((RubyModule) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR); - } -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java deleted file mode 100644 index 65328676e11e..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyEnumDescriptor.java +++ /dev/null @@ -1,196 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.DescriptorProtos.EnumDescriptorProto; -import com.google.protobuf.Descriptors.EnumDescriptor; -import com.google.protobuf.Descriptors.EnumValueDescriptor; -import com.google.protobuf.Descriptors.FileDescriptor; -import org.jruby.Ruby; -import org.jruby.RubyClass; -import org.jruby.RubyModule; -import org.jruby.RubyNumeric; -import org.jruby.RubyObject; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "EnumDescriptor", include = "Enumerable") -public class RubyEnumDescriptor extends RubyObject { - public static void createRubyEnumDescriptor(Ruby runtime) { - RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cEnumDescriptor = - mProtobuf.defineClassUnder( - "EnumDescriptor", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyEnumDescriptor(runtime, klazz); - } - }); - cEnumDescriptor.includeModule(runtime.getEnumerable()); - cEnumDescriptor.defineAnnotatedMethods(RubyEnumDescriptor.class); - } - - public RubyEnumDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } - - /* - * call-seq: - * EnumDescriptor.name => name - * - * Returns the name of this enum type. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return this.name; - } - - /* - * call-seq: - * EnumDescriptor.each(&block) - * - * Iterates over key => value mappings in this enum's definition, yielding to - * the block with (key, value) arguments for each one. - */ - @JRubyMethod - public IRubyObject each(ThreadContext context, Block block) { - Ruby runtime = context.runtime; - for (EnumValueDescriptor enumValueDescriptor : descriptor.getValues()) { - block.yield( - context, - runtime.newArray( - runtime.newSymbol(enumValueDescriptor.getName()), - runtime.newFixnum(enumValueDescriptor.getNumber()))); - } - return context.nil; - } - - /* - * call-seq: - * EnumDescriptor.enummodule => module - * - * Returns the Ruby module corresponding to this enum type. Cannot be called - * until the enum descriptor has been added to a pool. - */ - @JRubyMethod - public IRubyObject enummodule(ThreadContext context) { - return module; - } - - /* - * call-seq: - * EnumDescriptor.file_descriptor - * - * Returns the FileDescriptor object this enum belongs to. - */ - @JRubyMethod(name = "file_descriptor") - public IRubyObject getFileDescriptor(ThreadContext context) { - return RubyFileDescriptor.getRubyFileDescriptor(context, descriptor); - } - - public boolean isValidValue(ThreadContext context, IRubyObject value) { - EnumValueDescriptor enumValue; - - if (Utils.isRubyNum(value)) { - enumValue = descriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); - } else { - enumValue = descriptor.findValueByName(value.asJavaString()); - } - - return enumValue != null; - } - - protected IRubyObject nameToNumber(ThreadContext context, IRubyObject name) { - EnumValueDescriptor value = descriptor.findValueByName(name.asJavaString()); - return value == null ? context.nil : context.runtime.newFixnum(value.getNumber()); - } - - protected IRubyObject numberToName(ThreadContext context, IRubyObject number) { - EnumValueDescriptor value = descriptor.findValueByNumber(RubyNumeric.num2int(number)); - return value == null ? context.nil : context.runtime.newSymbol(value.getName()); - } - - protected void setDescriptor(ThreadContext context, EnumDescriptor descriptor) { - this.descriptor = descriptor; - this.module = buildModuleFromDescriptor(context); - } - - protected void setName(IRubyObject name) { - this.name = name; - } - - private RubyModule buildModuleFromDescriptor(ThreadContext context) { - Ruby runtime = context.runtime; - - RubyModule enumModule = RubyModule.newModule(runtime); - boolean defaultValueRequiredButNotFound = - descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; - for (EnumValueDescriptor value : descriptor.getValues()) { - String name = value.getName(); - // Make sure its a valid constant name before trying to create it - if (Character.isUpperCase(name.codePointAt(0))) { - enumModule.defineConstant(name, runtime.newFixnum(value.getNumber())); - } else { - runtime - .getWarnings() - .warn( - "Enum value " - + name - + " does not start with an uppercase letter as is required for Ruby" - + " constants."); - } - if (value.getNumber() == 0) { - defaultValueRequiredButNotFound = false; - } - } - - if (defaultValueRequiredButNotFound) { - throw Utils.createTypeError( - context, "Enum definition " + name + " does not contain a value for '0'"); - } - enumModule.instance_variable_set(runtime.newString(Utils.DESCRIPTOR_INSTANCE_VAR), this); - enumModule.defineAnnotatedMethods(RubyEnum.class); - return enumModule; - } - - private EnumDescriptor descriptor; - private EnumDescriptorProto.Builder builder; - private IRubyObject name; - private RubyModule module; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java deleted file mode 100644 index bc1fe0cbe64d..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFieldDescriptor.java +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.FileDescriptor; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "FieldDescriptor") -public class RubyFieldDescriptor extends RubyObject { - public static void createRubyFieldDescriptor(Ruby runtime) { - RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cFieldDescriptor = - mProtobuf.defineClassUnder( - "FieldDescriptor", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyFieldDescriptor(runtime, klazz); - } - }); - cFieldDescriptor.defineAnnotatedMethods(RubyFieldDescriptor.class); - } - - public RubyFieldDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } - - /* - * call-seq: - * FieldDescriptor.default => default - * - * Returns this field's default, as a Ruby object, or nil if not yet set. - */ - // VALUE FieldDescriptor_default(VALUE _self) { - // DEFINE_SELF(FieldDescriptor, self, _self); - // return layout_get_default(self->fielddef); - // } - - /* - * call-seq: - * FieldDescriptor.label => label - * - * Returns this field's label (i.e., plurality), as a Ruby symbol. - * - * Valid field labels are: - * :optional, :repeated - */ - @JRubyMethod(name = "label") - public IRubyObject getLabel(ThreadContext context) { - if (label == null) { - calculateLabel(context); - } - return label; - } - - /* - * call-seq: - * FieldDescriptor.name => name - * - * Returns the name of this field as a Ruby String, or nil if it is not set. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return this.name; - } - - /* - * call-seq: - * FieldDescriptor.subtype => message_or_enum_descriptor - * - * Returns the message or enum descriptor corresponding to this field's type if - * it is a message or enum field, respectively, or nil otherwise. Cannot be - * called *until* the containing message type is added to a pool (and thus - * resolved). - */ - @JRubyMethod(name = "subtype") - public IRubyObject getSubtype(ThreadContext context) { - if (subtype == null) { - calculateSubtype(context); - } - return subtype; - } - - /* - * call-seq: - * FieldDescriptor.type => type - * - * Returns this field's type, as a Ruby symbol, or nil if not yet set. - * - * Valid field types are: - * :int32, :int64, :uint32, :uint64, :float, :double, :bool, :string, - * :bytes, :message. - */ - @JRubyMethod(name = "type") - public IRubyObject getType(ThreadContext context) { - return Utils.fieldTypeToRuby(context, descriptor.getType()); - } - - /* - * call-seq: - * FieldDescriptor.number => number - * - * Returns the tag number for this field. - */ - @JRubyMethod(name = "number") - public IRubyObject getNumber(ThreadContext context) { - return this.number; - } - - /* - * call-seq: - * FieldDescriptor.submsg_name => submsg_name - * - * Returns the name of the message or enum type corresponding to this field, if - * it is a message or enum field (respectively), or nil otherwise. This type - * name will be resolved within the context of the pool to which the containing - * message type is added. - */ - // VALUE FieldDescriptor_submsg_name(VALUE _self) { - // DEFINE_SELF(FieldDescriptor, self, _self); - // switch (upb_fielddef_type(self->fielddef)) { - // case UPB_TYPE_ENUM: - // return rb_str_new2( - // upb_enumdef_fullname(upb_fielddef_enumsubdef(self->fielddef))); - // case UPB_TYPE_MESSAGE: - // return rb_str_new2( - // upb_msgdef_fullname(upb_fielddef_msgsubdef(self->fielddef))); - // default: - // return Qnil; - // } - // } - /* - * call-seq: - * FieldDescriptor.submsg_name = submsg_name - * - * Sets the name of the message or enum type corresponding to this field, if it - * is a message or enum field (respectively). This type name will be resolved - * within the context of the pool to which the containing message type is added. - * Cannot be called on field that are not of message or enum type, or on fields - * that are part of a message type already added to a pool. - */ - // @JRubyMethod(name = "submsg_name=") - // public IRubyObject setSubmsgName(ThreadContext context, IRubyObject name) { - // this.builder.setTypeName("." + Utils.escapeIdentifier(name.asJavaString())); - // return context.runtime.getNil(); - // } - - /* - * call-seq: - * FieldDescriptor.clear(message) - * - * Clears the field from the message if it's set. - */ - @JRubyMethod(name = "clear") - public IRubyObject clearValue(ThreadContext context, IRubyObject message) { - return ((RubyMessage) message).clearField(context, descriptor); - } - - /* - * call-seq: - * FieldDescriptor.get(message) => value - * - * Returns the value set for this field on the given message. Raises an - * exception if message is of the wrong type. - */ - @JRubyMethod(name = "get") - public IRubyObject getValue(ThreadContext context, IRubyObject message) { - return ((RubyMessage) message).getField(context, descriptor); - } - - /* - * call-seq: - * FieldDescriptor.has?(message) => boolean - * - * Returns whether the value is set on the given message. Raises an - * exception when calling for fields that do not have presence. - */ - @JRubyMethod(name = "has?") - public IRubyObject has(ThreadContext context, IRubyObject message) { - return ((RubyMessage) message).hasField(context, descriptor); - } - - /* - * call-seq: - * FieldDescriptor.set(message, value) - * - * Sets the value corresponding to this field to the given value on the given - * message. Raises an exception if message is of the wrong type. Performs the - * ordinary type-checks for field setting. - */ - @JRubyMethod(name = "set") - public IRubyObject setValue(ThreadContext context, IRubyObject message, IRubyObject value) { - ((RubyMessage) message).setField(context, descriptor, value); - return context.nil; - } - - protected void setDescriptor( - ThreadContext context, FieldDescriptor descriptor, RubyDescriptorPool pool) { - if (descriptor.isRequired() - && descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3) { - throw Utils.createTypeError( - context, - descriptor.getName() - + " is labeled required but required fields are unsupported in proto3"); - } - this.descriptor = descriptor; - this.name = context.runtime.newString(descriptor.getName()); - this.pool = pool; - } - - private void calculateLabel(ThreadContext context) { - if (descriptor.isRepeated()) { - this.label = context.runtime.newSymbol("repeated"); - } else if (descriptor.isOptional()) { - this.label = context.runtime.newSymbol("optional"); - } else { - this.label = context.nil; - } - } - - private void calculateSubtype(ThreadContext context) { - FieldDescriptor.Type fdType = descriptor.getType(); - if (fdType == FieldDescriptor.Type.MESSAGE) { - RubyString messageName = context.runtime.newString(descriptor.getMessageType().getFullName()); - this.subtype = pool.lookup(context, messageName); - } else if (fdType == FieldDescriptor.Type.ENUM) { - RubyString enumName = context.runtime.newString(descriptor.getEnumType().getFullName()); - this.subtype = pool.lookup(context, enumName); - } else { - this.subtype = context.nil; - } - } - - private static final String DOT = "."; - - private FieldDescriptor descriptor; - private IRubyObject name; - private IRubyObject label; - private IRubyObject number; - private IRubyObject subtype; - private RubyDescriptorPool pool; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java deleted file mode 100644 index 972510b0223f..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyFileDescriptor.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.Descriptors.FileDescriptor; -import com.google.protobuf.Descriptors.FileDescriptor.Syntax.*; -import com.google.protobuf.Descriptors.GenericDescriptor; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "FileDescriptor") -public class RubyFileDescriptor extends RubyObject { - public static void createRubyFileDescriptor(Ruby runtime) { - RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); - cFileDescriptor = - mProtobuf.defineClassUnder( - "FileDescriptor", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyFileDescriptor(runtime, klazz); - } - }); - cFileDescriptor.defineAnnotatedMethods(RubyFileDescriptor.class); - } - - public static RubyFileDescriptor getRubyFileDescriptor( - ThreadContext context, GenericDescriptor descriptor) { - RubyFileDescriptor rfd = - (RubyFileDescriptor) cFileDescriptor.newInstance(context, Block.NULL_BLOCK); - rfd.fileDescriptor = descriptor.getFile(); - return rfd; - } - - public RubyFileDescriptor(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } - - /* - * call-seq: - * FileDescriptor.name => name - * - * Returns the name of the file. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - String name = fileDescriptor.getName(); - return name == null ? context.nil : context.runtime.newString(name); - } - - /* - * call-seq: - * FileDescriptor.syntax => syntax - * - * Returns this file descriptors syntax. - * - * Valid syntax versions are: - * :proto2 or :proto3. - */ - @JRubyMethod(name = "syntax") - public IRubyObject getSyntax(ThreadContext context) { - switch (fileDescriptor.getSyntax()) { - case PROTO2: - return context.runtime.newSymbol("proto2"); - case PROTO3: - return context.runtime.newSymbol("proto3"); - default: - return context.nil; - } - } - - private static RubyClass cFileDescriptor; - - private FileDescriptor fileDescriptor; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java deleted file mode 100644 index 8727b13cf7dc..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMap.java +++ /dev/null @@ -1,487 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.DynamicMessage; -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "Map", include = "Enumerable") -public class RubyMap extends RubyObject { - public static void createRubyMap(Ruby runtime) { - RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cMap = - protobuf.defineClassUnder( - "Map", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) { - return new RubyMap(ruby, rubyClass); - } - }); - cMap.includeModule(runtime.getEnumerable()); - cMap.defineAnnotatedMethods(RubyMap.class); - } - - public RubyMap(Ruby ruby, RubyClass rubyClass) { - super(ruby, rubyClass); - } - - /* - * call-seq: - * Map.new(key_type, value_type, value_typeclass = nil, init_hashmap = {}) - * => new map - * - * Allocates a new Map container. This constructor may be called with 2, 3, or 4 - * arguments. The first two arguments are always present and are symbols (taking - * on the same values as field-type symbols in message descriptors) that - * indicate the type of the map key and value fields. - * - * The supported key types are: :int32, :int64, :uint32, :uint64, :fixed32, - * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes. - * - * The supported value types are: :int32, :int64, :uint32, :uint64, :fixed32, - * :fixed64, :sfixed32, :sfixed64, :sint32, :sint64, :bool, :string, :bytes, - * :enum, :message. - * - * The third argument, value_typeclass, must be present if value_type is :enum - * or :message. As in RepeatedField#new, this argument must be a message class - * (for :message) or enum module (for :enum). - * - * The last argument, if present, provides initial content for map. Note that - * this may be an ordinary Ruby hashmap or another Map instance with identical - * key and value types. Also note that this argument may be present whether or - * not value_typeclass is present (and it is unambiguously separate from - * value_typeclass because value_typeclass's presence is strictly determined by - * value_type). The contents of this initial hashmap or Map instance are - * shallow-copied into the new Map: the original map is unmodified, but - * references to underlying objects will be shared if the value type is a - * message type. - */ - @JRubyMethod(required = 2, optional = 2) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - this.table = new HashMap(); - this.keyType = Utils.rubyToFieldType(args[0]); - this.valueType = Utils.rubyToFieldType(args[1]); - - switch (keyType) { - case STRING: - case BYTES: - this.keyTypeIsString = true; - break; - case INT32: - case INT64: - case SINT32: - case SINT64: - case UINT32: - case UINT64: - case FIXED32: - case FIXED64: - case SFIXED32: - case SFIXED64: - case BOOL: - // These are OK. - break; - default: - throw context.runtime.newArgumentError("Invalid key type for map."); - } - - int initValueArg = 2; - if (needTypeclass(this.valueType) && args.length > 2) { - this.valueTypeClass = args[2]; - Utils.validateTypeClass(context, this.valueType, this.valueTypeClass); - initValueArg = 3; - } else { - this.valueTypeClass = context.runtime.getNilClass(); - } - - if (args.length > initValueArg) { - mergeIntoSelf(context, args[initValueArg]); - } - return this; - } - - /* - * call-seq: - * Map.[]=(key, value) => value - * - * Inserts or overwrites the value at the given key with the given new value. - * Throws an exception if the key type is incorrect. Returns the new value that - * was just inserted. - */ - @JRubyMethod(name = "[]=") - public IRubyObject indexSet(ThreadContext context, IRubyObject key, IRubyObject value) { - checkFrozen(); - - /* - * String types for keys return a different error than - * other types for keys, so deal with them specifically first - */ - if (keyTypeIsString && !(key instanceof RubySymbol || key instanceof RubyString)) { - throw Utils.createTypeError(context, "Expected string for map key"); - } - key = Utils.checkType(context, keyType, "key", key, (RubyModule) valueTypeClass); - value = Utils.checkType(context, valueType, "value", value, (RubyModule) valueTypeClass); - IRubyObject symbol; - if (valueType == FieldDescriptor.Type.ENUM - && Utils.isRubyNum(value) - && !(symbol = RubyEnum.lookup(context, valueTypeClass, value)).isNil()) { - value = symbol; - } - this.table.put(key, value); - return value; - } - - /* - * call-seq: - * Map.[](key) => value - * - * Accesses the element at the given key. Throws an exception if the key type is - * incorrect. Returns nil when the key is not present in the map. - */ - @JRubyMethod(name = "[]") - public IRubyObject index(ThreadContext context, IRubyObject key) { - key = Utils.symToString(key); - return Helpers.nullToNil(table.get(key), context.nil); - } - - /* - * call-seq: - * Map.==(other) => boolean - * - * Compares this map to another. Maps are equal if they have identical key sets, - * and for each key, the values in both maps compare equal. Elements are - * compared as per normal Ruby semantics, by calling their :== methods (or - * performing a more efficient comparison for primitive types). - * - * Maps with dissimilar key types or value types/typeclasses are never equal, - * even if value comparison (for example, between integers and floats) would - * have otherwise indicated that every element has equal value. - */ - @JRubyMethod(name = "==") - public IRubyObject eq(ThreadContext context, IRubyObject _other) { - if (_other instanceof RubyHash) return singleLevelHash(context).op_equal(context, _other); - RubyMap other = (RubyMap) _other; - if (this == other) return context.runtime.getTrue(); - if (!typeCompatible(other) || this.table.size() != other.table.size()) - return context.runtime.getFalse(); - for (IRubyObject key : table.keySet()) { - if (!other.table.containsKey(key)) return context.runtime.getFalse(); - if (!other.table.get(key).equals(table.get(key))) return context.runtime.getFalse(); - } - return context.runtime.getTrue(); - } - - /* - * call-seq: - * Map.inspect => string - * - * Returns a string representing this map's elements. It will be formatted as - * "{key => value, key => value, ...}", with each key and value string - * representation computed by its own #inspect method. - */ - @JRubyMethod - public IRubyObject inspect() { - return singleLevelHash(getRuntime().getCurrentContext()).inspect(); - } - - /* - * call-seq: - * Map.hash => hash_value - * - * Returns a hash value based on this map's contents. - */ - @JRubyMethod - public IRubyObject hash(ThreadContext context) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - for (IRubyObject key : table.keySet()) { - digest.update((byte) key.hashCode()); - digest.update((byte) table.get(key).hashCode()); - } - return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong()); - } catch (NoSuchAlgorithmException ignore) { - return context.runtime.newFixnum(System.identityHashCode(table)); - } - } - - /* - * call-seq: - * Map.keys => [list_of_keys] - * - * Returns the list of keys contained in the map, in unspecified order. - */ - @JRubyMethod - public IRubyObject keys(ThreadContext context) { - return RubyArray.newArray(context.runtime, table.keySet()); - } - - /* - * call-seq: - * Map.values => [list_of_values] - * - * Returns the list of values contained in the map, in unspecified order. - */ - @JRubyMethod - public IRubyObject values(ThreadContext context) { - return RubyArray.newArray(context.runtime, table.values()); - } - - /* - * call-seq: - * Map.clear - * - * Removes all entries from the map. - */ - @JRubyMethod - public IRubyObject clear(ThreadContext context) { - checkFrozen(); - table.clear(); - return context.nil; - } - - /* - * call-seq: - * Map.each(&block) - * - * Invokes &block on each |key, value| pair in the map, in unspecified order. - * Note that Map also includes Enumerable; map thus acts like a normal Ruby - * sequence. - */ - @JRubyMethod - public IRubyObject each(ThreadContext context, Block block) { - for (IRubyObject key : table.keySet()) { - block.yieldSpecific(context, key, table.get(key)); - } - return context.nil; - } - - /* - * call-seq: - * Map.delete(key) => old_value - * - * Deletes the value at the given key, if any, returning either the old value or - * nil if none was present. Throws an exception if the key is of the wrong type. - */ - @JRubyMethod - public IRubyObject delete(ThreadContext context, IRubyObject key) { - checkFrozen(); - return table.remove(key); - } - - /* - * call-seq: - * Map.has_key?(key) => bool - * - * Returns true if the given key is present in the map. Throws an exception if - * the key has the wrong type. - */ - @JRubyMethod(name = "has_key?") - public IRubyObject hasKey(ThreadContext context, IRubyObject key) { - return this.table.containsKey(key) ? context.runtime.getTrue() : context.runtime.getFalse(); - } - - /* - * call-seq: - * Map.length - * - * Returns the number of entries (key-value pairs) in the map. - */ - @JRubyMethod(name = {"length", "size"}) - public IRubyObject length(ThreadContext context) { - return context.runtime.newFixnum(this.table.size()); - } - - /* - * call-seq: - * Map.dup => new_map - * - * Duplicates this map with a shallow copy. References to all non-primitive - * element objects (e.g., submessages) are shared. - */ - @JRubyMethod - public IRubyObject dup(ThreadContext context) { - RubyMap newMap = newThisType(context); - for (Map.Entry entry : table.entrySet()) { - newMap.table.put(entry.getKey(), entry.getValue()); - } - return newMap; - } - - @JRubyMethod(name = "to_h") - public RubyHash toHash(ThreadContext context) { - Map mapForHash = new HashMap(); - - table.forEach( - (key, value) -> { - if (!value.isNil()) { - if (value.respondsTo("to_h")) { - value = Helpers.invoke(context, value, "to_h"); - } else if (value.respondsTo("to_a")) { - value = Helpers.invoke(context, value, "to_a"); - } - mapForHash.put(key, value); - } - }); - - return RubyHash.newHash(context.runtime, mapForHash, context.nil); - } - - // Used by Google::Protobuf.deep_copy but not exposed directly. - protected IRubyObject deepCopy(ThreadContext context) { - RubyMap newMap = newThisType(context); - switch (valueType) { - case MESSAGE: - for (IRubyObject key : table.keySet()) { - RubyMessage message = (RubyMessage) table.get(key); - newMap.table.put(key.dup(), message.deepCopy(context)); - } - break; - default: - for (IRubyObject key : table.keySet()) { - newMap.table.put(key.dup(), table.get(key).dup()); - } - } - return newMap; - } - - protected List build( - ThreadContext context, RubyDescriptor descriptor, int depth, int recursionLimit) { - List list = new ArrayList(); - RubyClass rubyClass = (RubyClass) descriptor.msgclass(context); - FieldDescriptor keyField = descriptor.getField("key"); - FieldDescriptor valueField = descriptor.getField("value"); - for (IRubyObject key : table.keySet()) { - RubyMessage mapMessage = (RubyMessage) rubyClass.newInstance(context, Block.NULL_BLOCK); - mapMessage.setField(context, keyField, key); - mapMessage.setField(context, valueField, table.get(key)); - list.add(mapMessage.build(context, depth + 1, recursionLimit)); - } - return list; - } - - protected RubyMap mergeIntoSelf(final ThreadContext context, IRubyObject hashmap) { - if (hashmap instanceof RubyHash) { - ((RubyHash) hashmap) - .visitAll( - context, - new RubyHash.Visitor() { - @Override - public void visit(IRubyObject key, IRubyObject val) { - if (val instanceof RubyHash && !valueTypeClass.isNil()) { - val = ((RubyClass) valueTypeClass).newInstance(context, val, Block.NULL_BLOCK); - } - indexSet(context, key, val); - } - }, - null); - } else if (hashmap instanceof RubyMap) { - RubyMap other = (RubyMap) hashmap; - if (!typeCompatible(other)) { - throw Utils.createTypeError(context, "Attempt to merge Map with mismatching types"); - } - } else { - throw Utils.createTypeError(context, "Unknown type merging into Map"); - } - return this; - } - - protected boolean typeCompatible(RubyMap other) { - return this.keyType == other.keyType - && this.valueType == other.valueType - && this.valueTypeClass == other.valueTypeClass; - } - - private RubyMap newThisType(ThreadContext context) { - RubyMap newMap; - if (needTypeclass(valueType)) { - newMap = - (RubyMap) - metaClass.newInstance( - context, - Utils.fieldTypeToRuby(context, keyType), - Utils.fieldTypeToRuby(context, valueType), - valueTypeClass, - Block.NULL_BLOCK); - } else { - newMap = - (RubyMap) - metaClass.newInstance( - context, - Utils.fieldTypeToRuby(context, keyType), - Utils.fieldTypeToRuby(context, valueType), - Block.NULL_BLOCK); - } - newMap.table = new HashMap(); - return newMap; - } - - /* - * toHash calls toHash on values, for some camparisons we only need - * a hash with the original objects still as values - */ - private RubyHash singleLevelHash(ThreadContext context) { - return RubyHash.newHash(context.runtime, table, context.nil); - } - - private boolean needTypeclass(FieldDescriptor.Type type) { - switch (type) { - case MESSAGE: - case ENUM: - return true; - default: - return false; - } - } - - private FieldDescriptor.Type keyType; - private FieldDescriptor.Type valueType; - private IRubyObject valueTypeClass; - private Map table; - private boolean keyTypeIsString = false; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java deleted file mode 100644 index 301b95798215..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyMessage.java +++ /dev/null @@ -1,1464 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.ByteString; -import com.google.protobuf.CodedInputStream; -import com.google.protobuf.Descriptors.Descriptor; -import com.google.protobuf.Descriptors.EnumDescriptor; -import com.google.protobuf.Descriptors.EnumValueDescriptor; -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.FileDescriptor; -import com.google.protobuf.Descriptors.OneofDescriptor; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.Message; -import com.google.protobuf.UnknownFieldSet; -import com.google.protobuf.util.JsonFormat; -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.jruby.*; -import org.jruby.anno.JRubyMethod; -import org.jruby.exceptions.RaiseException; -import org.jruby.runtime.Block; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; -import org.jruby.util.ByteList; - -public class RubyMessage extends RubyObject { - private final String DEFAULT_VALUE = "google.protobuf.FieldDescriptorProto.default_value"; - private final String TYPE = "type"; - - public RubyMessage(Ruby runtime, RubyClass klazz, Descriptor descriptor) { - super(runtime, klazz); - - this.descriptor = descriptor; - this.cRepeatedField = (RubyClass) runtime.getClassFromPath("Google::Protobuf::RepeatedField"); - this.cMap = (RubyClass) runtime.getClassFromPath("Google::Protobuf::Map"); - this.builder = DynamicMessage.newBuilder(descriptor); - this.fields = new HashMap(); - this.oneofCases = new HashMap(); - this.proto3 = descriptor.getFile().getSyntax() == FileDescriptor.Syntax.PROTO3; - } - - /* - * call-seq: - * Message.new(kwargs) => new_message - * - * Creates a new instance of the given message class. Keyword arguments may be - * provided with keywords corresponding to field names. - * - * Note that no literal Message class exists. Only concrete classes per message - * type exist, as provided by the #msgclass method on Descriptors after they - * have been added to a pool. The method definitions described here on the - * Message class are provided on each concrete message class. - */ - @JRubyMethod(optional = 1) - public IRubyObject initialize(final ThreadContext context, IRubyObject[] args) { - final Ruby runtime = context.runtime; - if (args.length == 1) { - if (!(args[0] instanceof RubyHash)) { - throw runtime.newArgumentError("expected Hash arguments."); - } - RubyHash hash = args[0].convertToHash(); - hash.visitAll( - context, - new RubyHash.Visitor() { - @Override - public void visit(IRubyObject key, IRubyObject value) { - if (!(key instanceof RubySymbol) && !(key instanceof RubyString)) { - throw Utils.createTypeError( - context, "Expected string or symbols as hash keys in initialization map."); - } - final FieldDescriptor fieldDescriptor = - findField(context, key, ignoreUnknownFieldsOnInit); - - if (value == null || value.isNil()) return; - - if (fieldDescriptor.isMapField()) { - if (!(value instanceof RubyHash)) - throw runtime.newArgumentError( - "Expected Hash object as initializer value for map field '" - + key.asJavaString() - + "' (given " - + value.getMetaClass() - + ")."); - - final RubyMap map = newMapForField(context, fieldDescriptor); - map.mergeIntoSelf(context, value); - fields.put(fieldDescriptor, map); - } else if (fieldDescriptor.isRepeated()) { - if (!(value instanceof RubyArray)) - throw runtime.newArgumentError( - "Expected array as initializer value for repeated field '" - + key.asJavaString() - + "' (given " - + value.getMetaClass() - + ")."); - fields.put(fieldDescriptor, rubyToRepeatedField(context, fieldDescriptor, value)); - } else { - OneofDescriptor oneof = fieldDescriptor.getContainingOneof(); - if (oneof != null) { - oneofCases.put(oneof, fieldDescriptor); - } - - if (value instanceof RubyHash - && fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE) { - RubyDescriptor descriptor = - (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - RubyClass typeClass = (RubyClass) descriptor.msgclass(context); - value = (IRubyObject) typeClass.newInstance(context, value, Block.NULL_BLOCK); - fields.put(fieldDescriptor, value); - } else { - indexSet(context, key, value); - } - } - } - }, - null); - } - return this; - } - - /* - * call-seq: - * Message.[]=(index, value) - * - * Sets a field's value by field name. The provided field name should be a - * string. - */ - @JRubyMethod(name = "[]=") - public IRubyObject indexSet(ThreadContext context, IRubyObject fieldName, IRubyObject value) { - FieldDescriptor fieldDescriptor = findField(context, fieldName); - return setFieldInternal(context, fieldDescriptor, value); - } - - /* - * call-seq: - * Message.[](index) => value - * - * Accesses a field's value by field name. The provided field name should be a - * string. - */ - @JRubyMethod(name = "[]") - public IRubyObject index(ThreadContext context, IRubyObject fieldName) { - FieldDescriptor fieldDescriptor = findField(context, fieldName); - return getFieldInternal(context, fieldDescriptor); - } - - /* - * call-seq: - * Message.inspect => string - * - * Returns a human-readable string representing this message. It will be - * formatted as "". Each - * field's value is represented according to its own #inspect method. - */ - @JRubyMethod(name = {"inspect", "to_s"}) - public IRubyObject inspect() { - ThreadContext context = getRuntime().getCurrentContext(); - String cname = metaClass.getName(); - String colon = ": "; - String comma = ", "; - StringBuilder sb = new StringBuilder("<"); - boolean addComma = false; - - sb.append(cname).append(colon); - - for (FieldDescriptor fd : descriptor.getFields()) { - if (fd.hasPresence() && !fields.containsKey(fd)) { - continue; - } - if (addComma) { - sb.append(comma); - } else { - addComma = true; - } - - sb.append(fd.getName()).append(colon); - - IRubyObject value = getFieldInternal(context, fd); - if (value instanceof RubyBoolean) { - // Booleans don't implement internal "inspect" methods so have to call handle them manually - sb.append(value.isTrue() ? "true" : "false"); - } else { - sb.append(value.inspect()); - } - } - sb.append(">"); - - return context.runtime.newString(sb.toString()); - } - - /* - * call-seq: - * Message.hash => hash_value - * - * Returns a hash value that represents this message's field values. - */ - @JRubyMethod - public IRubyObject hash(ThreadContext context) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-256"); - for (FieldDescriptor fd : descriptor.getFields()) { - digest.update((byte) getFieldInternal(context, fd).hashCode()); - } - return context.runtime.newFixnum(ByteBuffer.wrap(digest.digest()).getLong()); - } catch (NoSuchAlgorithmException ignore) { - return context.runtime.newFixnum(System.identityHashCode(this)); - } - } - - /* - * call-seq: - * Message.==(other) => boolean - * - * Performs a deep comparison of this message with another. Messages are equal - * if they have the same type and if each field is equal according to the :== - * method's semantics (a more efficient comparison may actually be done if the - * field is of a primitive type). - */ - @JRubyMethod(name = {"==", "eql?"}) - public IRubyObject eq(ThreadContext context, IRubyObject other) { - Ruby runtime = context.runtime; - if (!(other instanceof RubyMessage)) return runtime.getFalse(); - RubyMessage message = (RubyMessage) other; - if (descriptor != message.descriptor) { - return runtime.getFalse(); - } - - for (FieldDescriptor fdef : descriptor.getFields()) { - IRubyObject thisVal = getFieldInternal(context, fdef); - IRubyObject thatVal = message.getFieldInternal(context, fdef); - IRubyObject ret = thisVal.callMethod(context, "==", thatVal); - if (!ret.isTrue()) { - return runtime.getFalse(); - } - } - return runtime.getTrue(); - } - - /* - * call-seq: - * Message.respond_to?(method_name, search_private_and_protected) => boolean - * - * Parallels method_missing, returning true when this object implements a method with the given - * method_name. - */ - @JRubyMethod(name = "respond_to?", required = 1, optional = 1) - public IRubyObject respondTo(ThreadContext context, IRubyObject[] args) { - String methodName = args[0].asJavaString(); - if (descriptor.findFieldByName(methodName) != null) { - return context.runtime.getTrue(); - } - RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]); - if (!oneofDescriptor.isNil()) { - return context.runtime.getTrue(); - } - if (methodName.startsWith(CLEAR_PREFIX)) { - String strippedMethodName = methodName.substring(6); - oneofDescriptor = - rubyDescriptor.lookupOneof(context, context.runtime.newSymbol(strippedMethodName)); - if (!oneofDescriptor.isNil()) { - return context.runtime.getTrue(); - } - - if (descriptor.findFieldByName(strippedMethodName) != null) { - return context.runtime.getTrue(); - } - } - if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) { - String strippedMethodName = methodName.substring(4, methodName.length() - 1); - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName); - if (fieldDescriptor != null - && (!proto3 - || fieldDescriptor.getContainingOneof() == null - || fieldDescriptor.getContainingOneof().isSynthetic()) - && fieldDescriptor.hasPresence()) { - return context.runtime.getTrue(); - } - oneofDescriptor = - rubyDescriptor.lookupOneof( - context, RubyString.newString(context.runtime, strippedMethodName)); - if (!oneofDescriptor.isNil()) { - return context.runtime.getTrue(); - } - } - if (methodName.endsWith(AS_VALUE_SUFFIX)) { - FieldDescriptor fieldDescriptor = - descriptor.findFieldByName(methodName.substring(0, methodName.length() - 9)); - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - return context.runtime.getTrue(); - } - } - if (methodName.endsWith(CONST_SUFFIX)) { - FieldDescriptor fieldDescriptor = - descriptor.findFieldByName(methodName.substring(0, methodName.length() - 6)); - if (fieldDescriptor != null) { - if (fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) { - return context.runtime.getTrue(); - } - } - } - if (methodName.endsWith(Utils.EQUAL_SIGN)) { - String strippedMethodName = methodName.substring(0, methodName.length() - 1); - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(strippedMethodName); - if (fieldDescriptor != null) { - return context.runtime.getTrue(); - } - if (strippedMethodName.endsWith(AS_VALUE_SUFFIX)) { - strippedMethodName = methodName.substring(0, strippedMethodName.length() - 9); - fieldDescriptor = descriptor.findFieldByName(strippedMethodName); - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - return context.runtime.getTrue(); - } - } - } - boolean includePrivate = false; - if (args.length == 2) { - includePrivate = context.runtime.getTrue().equals(args[1]); - } - return metaClass.respondsToMethod(methodName, includePrivate) - ? context.runtime.getTrue() - : context.runtime.getFalse(); - } - - /* - * call-seq: - * Message.method_missing(*args) - * - * Provides accessors and setters and methods to clear and check for presence of - * message fields according to their field names. - * - * For any field whose name does not conflict with a built-in method, an - * accessor is provided with the same name as the field, and a setter is - * provided with the name of the field plus the '=' suffix. Thus, given a - * message instance 'msg' with field 'foo', the following code is valid: - * - * msg.foo = 42 - * puts msg.foo - * - * This method also provides read-only accessors for oneofs. If a oneof exists - * with name 'my_oneof', then msg.my_oneof will return a Ruby symbol equal to - * the name of the field in that oneof that is currently set, or nil if none. - * - * It also provides methods of the form 'clear_fieldname' to clear the value - * of the field 'fieldname'. For basic data types, this will set the default - * value of the field. - * - * Additionally, it provides methods of the form 'has_fieldname?', which returns - * true if the field 'fieldname' is set in the message object, else false. For - * 'proto3' syntax, calling this for a basic type field will result in an error. - */ - @JRubyMethod(name = "method_missing", rest = true) - public IRubyObject methodMissing(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.runtime; - String methodName = args[0].asJavaString(); - RubyDescriptor rubyDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - - if (args.length == 1) { - // If we find a Oneof return it's name (use lookupOneof because it has an index) - IRubyObject oneofDescriptor = rubyDescriptor.lookupOneof(context, args[0]); - - if (!oneofDescriptor.isNil()) { - RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor; - OneofDescriptor ood = rubyOneofDescriptor.getDescriptor(); - - // Check to see if we set this through ruby - FieldDescriptor fieldDescriptor = oneofCases.get(ood); - - if (fieldDescriptor == null) { - // See if we set this from decoding a message - fieldDescriptor = builder.getOneofFieldDescriptor(ood); - - if (fieldDescriptor == null) { - return context.nil; - } else { - // Cache it so we don't need to do multiple checks next time - oneofCases.put(ood, fieldDescriptor); - return runtime.newSymbol(fieldDescriptor.getName()); - } - } else { - return runtime.newSymbol(fieldDescriptor.getName()); - } - } - - // If we find a field return its value - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null) { - return getFieldInternal(context, fieldDescriptor); - } - - if (methodName.startsWith(CLEAR_PREFIX)) { - methodName = methodName.substring(6); - oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName)); - if (!oneofDescriptor.isNil()) { - fieldDescriptor = oneofCases.get(((RubyOneofDescriptor) oneofDescriptor).getDescriptor()); - if (fieldDescriptor == null) { - // Clearing an already cleared oneof; return here to avoid NoMethodError. - return context.nil; - } - } - - if (fieldDescriptor == null) { - fieldDescriptor = descriptor.findFieldByName(methodName); - } - - if (fieldDescriptor != null) { - return clearFieldInternal(context, fieldDescriptor); - } - - } else if (methodName.startsWith(HAS_PREFIX) && methodName.endsWith(QUESTION_MARK)) { - methodName = - methodName.substring( - 4, methodName.length() - 1); // Trim "has_" and "?" off the field name - oneofDescriptor = rubyDescriptor.lookupOneof(context, runtime.newSymbol(methodName)); - if (!oneofDescriptor.isNil()) { - RubyOneofDescriptor rubyOneofDescriptor = (RubyOneofDescriptor) oneofDescriptor; - return oneofCases.containsKey(rubyOneofDescriptor.getDescriptor()) - ? runtime.getTrue() - : runtime.getFalse(); - } - - fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null - && (!proto3 - || fieldDescriptor.getContainingOneof() == null - || fieldDescriptor.getContainingOneof().isSynthetic()) - && fieldDescriptor.hasPresence()) { - return fields.containsKey(fieldDescriptor) ? runtime.getTrue() : runtime.getFalse(); - } - - } else if (methodName.endsWith(AS_VALUE_SUFFIX)) { - methodName = methodName.substring(0, methodName.length() - 9); - fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - IRubyObject value = getFieldInternal(context, fieldDescriptor); - - if (!value.isNil() && value instanceof RubyMessage) { - return ((RubyMessage) value).index(context, runtime.newString("value")); - } - - return value; - } - - } else if (methodName.endsWith(CONST_SUFFIX)) { - methodName = methodName.substring(0, methodName.length() - 6); - fieldDescriptor = descriptor.findFieldByName(methodName); - if (fieldDescriptor != null && fieldDescriptor.getType() == FieldDescriptor.Type.ENUM) { - IRubyObject enumValue = getFieldInternal(context, fieldDescriptor); - - if (!enumValue.isNil()) { - EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); - if (enumValue instanceof RubyRepeatedField) { - RubyArray values = (RubyArray) ((RubyRepeatedField) enumValue).toArray(context); - RubyArray retValues = runtime.newArray(values.getLength()); - for (int i = 0; i < values.getLength(); i++) { - String val = values.eltInternal(i).toString(); - retValues.store( - (long) i, runtime.newFixnum(enumDescriptor.findValueByName(val).getNumber())); - } - return retValues; - } - - return runtime.newFixnum( - enumDescriptor.findValueByName(enumValue.asJavaString()).getNumber()); - } - } - } - - } else if (args.length == 2 && methodName.endsWith(Utils.EQUAL_SIGN)) { - - methodName = methodName.substring(0, methodName.length() - 1); // Trim equals sign - FieldDescriptor fieldDescriptor = descriptor.findFieldByName(methodName); - if (fieldDescriptor != null) { - return setFieldInternal(context, fieldDescriptor, args[1]); - } - - IRubyObject oneofDescriptor = - rubyDescriptor.lookupOneof(context, RubyString.newString(context.runtime, methodName)); - if (!oneofDescriptor.isNil()) { - throw runtime.newRuntimeError("Oneof accessors are read-only."); - } - - if (methodName.endsWith(AS_VALUE_SUFFIX)) { - methodName = methodName.substring(0, methodName.length() - 9); - - fieldDescriptor = descriptor.findFieldByName(methodName); - - if (fieldDescriptor != null && isWrappable(fieldDescriptor)) { - if (args[1].isNil()) { - return setFieldInternal(context, fieldDescriptor, args[1]); - } - - RubyClass typeClass = - (RubyClass) - ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)) - .msgclass(context); - RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK); - msg.indexSet(context, runtime.newString("value"), args[1]); - return setFieldInternal(context, fieldDescriptor, msg); - } - } - } - - return Helpers.invokeSuper(context, this, metaClass, "method_missing", args, Block.NULL_BLOCK); - } - - /** - * call-seq: Message.dup => new_message Performs a shallow copy of this message and returns the - * new copy. - */ - @JRubyMethod - public IRubyObject dup(ThreadContext context) { - RubyMessage dup = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK); - for (FieldDescriptor fieldDescriptor : this.descriptor.getFields()) { - if (fieldDescriptor.isRepeated()) { - dup.fields.put(fieldDescriptor, this.getRepeatedField(context, fieldDescriptor)); - } else if (fields.containsKey(fieldDescriptor)) { - dup.setFieldInternal(context, fieldDescriptor, fields.get(fieldDescriptor)); - } else if (this.builder.hasField(fieldDescriptor)) { - dup.fields.put( - fieldDescriptor, - wrapField(context, fieldDescriptor, this.builder.getField(fieldDescriptor))); - } - } - return dup; - } - - /* - * call-seq: - * Message.descriptor => descriptor - * - * Class method that returns the Descriptor instance corresponding to this - * message class's type. - */ - @JRubyMethod(name = "descriptor", meta = true) - public static IRubyObject getDescriptor(ThreadContext context, IRubyObject recv) { - return ((RubyClass) recv).getInstanceVariable(Utils.DESCRIPTOR_INSTANCE_VAR); - } - - /* - * call-seq: - * MessageClass.encode(msg, options = {}) => bytes - * - * Encodes the given message object to its serialized form in protocol buffers - * wire format. - * @param options [Hash] options for the encoder - * recursion_limit: set to maximum encoding depth for message (default is 64) - */ - @JRubyMethod(required = 1, optional = 1, meta = true) - public static IRubyObject encode(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - if (recv != args[0].getMetaClass()) { - throw context.runtime.newArgumentError( - "Tried to encode a " + args[0].getMetaClass() + " message with " + recv); - } - RubyMessage message = (RubyMessage) args[0]; - int recursionLimitInt = SINK_MAXIMUM_NESTING; - - if (args.length > 1) { - RubyHash options = (RubyHash) args[1]; - IRubyObject recursionLimit = options.fastARef(context.runtime.newSymbol("recursion_limit")); - - if (recursionLimit != null) { - recursionLimitInt = ((RubyNumeric) recursionLimit).getIntValue(); - } - } - return context.runtime.newString( - new ByteList(message.build(context, 0, recursionLimitInt).toByteArray())); - } - - /* - * call-seq: - * MessageClass.decode(data, options = {}) => message - * - * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretation given by this message class's definition - * and returns a message object with the corresponding field values. - * @param options [Hash] options for the decoder - * recursion_limit: set to maximum decoding depth for message (default is 100) - */ - @JRubyMethod(required = 1, optional = 1, meta = true) - public static IRubyObject decode(ThreadContext context, IRubyObject recv, IRubyObject[] args) { - IRubyObject data = args[0]; - byte[] bin = data.convertToString().getBytes(); - CodedInputStream input = CodedInputStream.newInstance(bin); - RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK); - - if (args.length == 2) { - if (!(args[1] instanceof RubyHash)) { - throw context.runtime.newArgumentError("Expected hash arguments."); - } - - IRubyObject recursionLimit = - ((RubyHash) args[1]).fastARef(context.runtime.newSymbol("recursion_limit")); - if (recursionLimit != null) { - input.setRecursionLimit(((RubyNumeric) recursionLimit).getIntValue()); - } - } - - try { - ret.builder.mergeFrom(input); - } catch (Exception e) { - throw RaiseException.from( - context.runtime, - (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"), - e.getMessage()); - } - - if (!ret.proto3) { - // Need to reset unknown values in repeated enum fields - ret.builder - .getUnknownFields() - .asMap() - .forEach( - (i, values) -> { - FieldDescriptor fd = ret.builder.getDescriptorForType().findFieldByNumber(i); - if (fd != null && fd.isRepeated() && fd.getType() == FieldDescriptor.Type.ENUM) { - EnumDescriptor ed = fd.getEnumType(); - values - .getVarintList() - .forEach( - value -> { - ret.builder.addRepeatedField( - fd, ed.findValueByNumberCreatingIfUnknown(value.intValue())); - }); - } - }); - } - - return ret; - } - - /* - * call-seq: - * MessageClass.encode_json(msg, options = {}) => json_string - * - * Encodes the given message object into its serialized JSON representation. - * @param options [Hash] options for the decoder - * preserve_proto_fieldnames: set true to use original fieldnames (default is to camelCase) - * emit_defaults: set true to emit 0/false values (default is to omit them) - */ - @JRubyMethod(name = "encode_json", required = 1, optional = 1, meta = true) - public static IRubyObject encodeJson( - ThreadContext context, IRubyObject recv, IRubyObject[] args) { - Ruby runtime = context.runtime; - RubyMessage message = (RubyMessage) args[0]; - JsonFormat.Printer printer = JsonFormat.printer().omittingInsignificantWhitespace(); - String result; - - if (args.length > 1) { - RubyHash options; - if (args[1] instanceof RubyHash) { - options = (RubyHash) args[1]; - } else if (args[1].respondsTo("to_h")) { - options = (RubyHash) args[1].callMethod(context, "to_h"); - } else { - throw runtime.newArgumentError("Expected hash arguments."); - } - - IRubyObject emitDefaults = options.fastARef(runtime.newSymbol("emit_defaults")); - IRubyObject preserveNames = options.fastARef(runtime.newSymbol("preserve_proto_fieldnames")); - - if (emitDefaults != null && emitDefaults.isTrue()) { - printer = printer.includingDefaultValueFields(); - } - - if (preserveNames != null && preserveNames.isTrue()) { - printer = printer.preservingProtoFieldNames(); - } - } - printer = - printer.usingTypeRegistry( - JsonFormat.TypeRegistry.newBuilder().add(message.descriptor).build()); - - try { - result = printer.print(message.build(context, 0, SINK_MAXIMUM_NESTING)); - } catch (InvalidProtocolBufferException e) { - throw runtime.newRuntimeError(e.getMessage()); - } catch (IllegalArgumentException e) { - throw createParseError(context, e.getMessage()); - } - - return runtime.newString(result); - } - - /* - * call-seq: - * MessageClass.decode_json(data, options = {}) => message - * - * Decodes the given data (as a string containing bytes in protocol buffers wire - * format) under the interpretation given by this message class's definition - * and returns a message object with the corresponding field values. - * - * @param options [Hash] options for the decoder - * ignore_unknown_fields: set true to ignore unknown fields (default is to - * raise an error) - */ - @JRubyMethod(name = "decode_json", required = 1, optional = 1, meta = true) - public static IRubyObject decodeJson( - ThreadContext context, IRubyObject recv, IRubyObject[] args) { - Ruby runtime = context.runtime; - boolean ignoreUnknownFields = false; - IRubyObject data = args[0]; - JsonFormat.Parser parser = JsonFormat.parser(); - - if (args.length == 2) { - if (!(args[1] instanceof RubyHash)) { - throw runtime.newArgumentError("Expected hash arguments."); - } - - IRubyObject ignoreSetting = - ((RubyHash) args[1]).fastARef(runtime.newSymbol("ignore_unknown_fields")); - if (ignoreSetting != null && ignoreSetting.isTrue()) { - parser = parser.ignoringUnknownFields(); - } - } - - if (!(data instanceof RubyString)) { - throw runtime.newArgumentError("Expected string for JSON data."); - } - - RubyMessage ret = (RubyMessage) ((RubyClass) recv).newInstance(context, Block.NULL_BLOCK); - parser = - parser.usingTypeRegistry(JsonFormat.TypeRegistry.newBuilder().add(ret.descriptor).build()); - - try { - parser.merge(data.asJavaString(), ret.builder); - } catch (InvalidProtocolBufferException e) { - throw createParseError(context, e.getMessage().replace("Cannot find", "No such")); - } - - if (isWrapper(ret.descriptor)) { - throw runtime.newRuntimeError( - "Parsing a wrapper type from JSON at the top level does not work."); - } - - return ret; - } - - @JRubyMethod(name = "to_h") - public IRubyObject toHash(ThreadContext context) { - Ruby runtime = context.runtime; - RubyHash ret = RubyHash.newHash(runtime); - for (FieldDescriptor fdef : this.descriptor.getFields()) { - IRubyObject value = getFieldInternal(context, fdef, proto3); - - if (!value.isNil()) { - if (fdef.isRepeated() && !fdef.isMapField()) { - if (!proto3 && ((RubyRepeatedField) value).size() == 0) - continue; // Don't output empty repeated fields for proto2 - if (fdef.getType() != FieldDescriptor.Type.MESSAGE) { - value = Helpers.invoke(context, value, "to_a"); - } else { - RubyArray ary = value.convertToArray(); - for (int i = 0; i < ary.size(); i++) { - IRubyObject submsg = Helpers.invoke(context, ary.eltInternal(i), "to_h"); - ary.eltInternalSet(i, submsg); - } - - value = ary.to_ary(); - } - } else if (value.respondsTo("to_h")) { - value = Helpers.invoke(context, value, "to_h"); - } else if (value.respondsTo("to_a")) { - value = Helpers.invoke(context, value, "to_a"); - } - } - if (proto3 || !value.isNil()) { - ret.fastASet(runtime.newSymbol(fdef.getName()), value); - } - } - return ret; - } - - protected DynamicMessage build(ThreadContext context, int depth, int recursionLimit) { - if (depth >= recursionLimit) { - throw context.runtime.newRuntimeError("Recursion limit exceeded during encoding."); - } - - RubySymbol typeBytesSymbol = RubySymbol.newSymbol(context.runtime, "TYPE_BYTES"); - - // Handle the typical case where the fields.keySet contain the fieldDescriptors - for (FieldDescriptor fieldDescriptor : fields.keySet()) { - IRubyObject value = fields.get(fieldDescriptor); - - if (value instanceof RubyMap) { - builder.clearField(fieldDescriptor); - RubyDescriptor mapDescriptor = - (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - for (DynamicMessage kv : - ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) { - builder.addRepeatedField(fieldDescriptor, kv); - } - - } else if (value instanceof RubyRepeatedField) { - RubyRepeatedField repeatedField = (RubyRepeatedField) value; - - builder.clearField(fieldDescriptor); - for (int i = 0; i < repeatedField.size(); i++) { - Object item = - convert( - context, - fieldDescriptor, - repeatedField.get(i), - depth, - recursionLimit, - /*isDefaultValueForBytes*/ false); - builder.addRepeatedField(fieldDescriptor, item); - } - - } else if (!value.isNil()) { - /** - * Detect the special case where default_value strings are provided for byte fields. If so, - * disable normal string encoding behavior within convert. For a more detailed explanation - * of other possible workarounds, see the comments above {@code - * com.google.protobuf.Internal#stringDefaultValue() stringDefaultValue}. - */ - boolean isDefaultStringForBytes = false; - if (DEFAULT_VALUE.equals(fieldDescriptor.getFullName())) { - FieldDescriptor enumFieldDescriptorForType = - this.builder.getDescriptorForType().findFieldByName(TYPE); - if (typeBytesSymbol.equals(fields.get(enumFieldDescriptorForType))) { - isDefaultStringForBytes = true; - } - } - builder.setField( - fieldDescriptor, - convert( - context, fieldDescriptor, value, depth, recursionLimit, isDefaultStringForBytes)); - } - } - - // Handle cases where {@code fields} doesn't contain the value until after getFieldInternal - // is called - typical of a deserialized message. Skip non-maps and descriptors that already - // have an entry in {@code fields}. - for (FieldDescriptor fieldDescriptor : descriptor.getFields()) { - if (!fieldDescriptor.isMapField()) { - continue; - } - IRubyObject value = fields.get(fieldDescriptor); - if (value != null) { - continue; - } - value = getFieldInternal(context, fieldDescriptor); - if (value instanceof RubyMap) { - builder.clearField(fieldDescriptor); - RubyDescriptor mapDescriptor = - (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - for (DynamicMessage kv : - ((RubyMap) value).build(context, mapDescriptor, depth, recursionLimit)) { - builder.addRepeatedField(fieldDescriptor, kv); - } - } - } - return builder.build(); - } - - // Internal use only, called by Google::Protobuf.deep_copy - protected IRubyObject deepCopy(ThreadContext context) { - RubyMessage copy = (RubyMessage) metaClass.newInstance(context, Block.NULL_BLOCK); - for (FieldDescriptor fdef : descriptor.getFields()) { - if (fdef.isRepeated()) { - copy.fields.put(fdef, this.getRepeatedField(context, fdef).deepCopy(context)); - } else if (fields.containsKey(fdef)) { - copy.setFieldInternal(context, fdef, fields.get(fdef)); - } else if (builder.hasField(fdef)) { - copy.fields.put(fdef, wrapField(context, fdef, builder.getField(fdef))); - } - } - return copy; - } - - protected IRubyObject clearField(ThreadContext context, FieldDescriptor fieldDescriptor) { - validateMessageType(context, fieldDescriptor, "clear"); - return clearFieldInternal(context, fieldDescriptor); - } - - protected void discardUnknownFields(ThreadContext context) { - discardUnknownFields(context, builder); - } - - protected IRubyObject getField(ThreadContext context, FieldDescriptor fieldDescriptor) { - validateMessageType(context, fieldDescriptor, "get"); - return getFieldInternal(context, fieldDescriptor); - } - - protected IRubyObject hasField(ThreadContext context, FieldDescriptor fieldDescriptor) { - validateMessageType(context, fieldDescriptor, "has?"); - if (!fieldDescriptor.hasPresence()) { - throw context.runtime.newArgumentError("does not track presence"); - } - return fields.containsKey(fieldDescriptor) - ? context.runtime.getTrue() - : context.runtime.getFalse(); - } - - protected IRubyObject setField( - ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { - validateMessageType(context, fieldDescriptor, "set"); - return setFieldInternal(context, fieldDescriptor, value); - } - - private RubyRepeatedField getRepeatedField( - ThreadContext context, FieldDescriptor fieldDescriptor) { - if (fields.containsKey(fieldDescriptor)) { - return (RubyRepeatedField) fields.get(fieldDescriptor); - } - int count = this.builder.getRepeatedFieldCount(fieldDescriptor); - RubyRepeatedField ret = repeatedFieldForFieldDescriptor(context, fieldDescriptor); - for (int i = 0; i < count; i++) { - ret.push( - context, - new IRubyObject[] { - wrapField(context, fieldDescriptor, this.builder.getRepeatedField(fieldDescriptor, i)) - }); - } - fields.put(fieldDescriptor, ret); - return ret; - } - - private IRubyObject buildFrom(ThreadContext context, DynamicMessage dynamicMessage) { - this.builder.mergeFrom(dynamicMessage); - return this; - } - - private IRubyObject clearFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) { - OneofDescriptor ood = fieldDescriptor.getContainingOneof(); - if (ood != null) oneofCases.remove(ood); - fields.remove(fieldDescriptor); - builder.clearField(fieldDescriptor); - return context.nil; - } - - private void discardUnknownFields(ThreadContext context, Message.Builder messageBuilder) { - messageBuilder.setUnknownFields(UnknownFieldSet.getDefaultInstance()); - messageBuilder - .getAllFields() - .forEach( - (fd, value) -> { - if (fd.getType() == FieldDescriptor.Type.MESSAGE) { - if (fd.isRepeated()) { - messageBuilder.clearField(fd); - ((List) value) - .forEach( - (val) -> { - Message.Builder submessageBuilder = ((DynamicMessage) val).toBuilder(); - discardUnknownFields(context, submessageBuilder); - messageBuilder.addRepeatedField(fd, submessageBuilder.build()); - }); - } else { - Message.Builder submessageBuilder = ((DynamicMessage) value).toBuilder(); - discardUnknownFields(context, submessageBuilder); - messageBuilder.setField(fd, submessageBuilder.build()); - } - } - }); - } - - private FieldDescriptor findField(ThreadContext context, IRubyObject fieldName) { - return findField(context, fieldName, false); - } - - private FieldDescriptor findField( - ThreadContext context, IRubyObject fieldName, boolean ignoreUnknownField) { - String nameStr = fieldName.asJavaString(); - FieldDescriptor ret = this.descriptor.findFieldByName(nameStr); - if (ret == null && !ignoreUnknownField) { - throw context.runtime.newArgumentError("field " + fieldName.asJavaString() + " is not found"); - } - return ret; - } - - // convert a ruby object to protobuf type, skip type check since it is checked on the way in - private Object convert( - ThreadContext context, - FieldDescriptor fieldDescriptor, - IRubyObject value, - int depth, - int recursionLimit, - boolean isDefaultStringForBytes) { - Object val = null; - switch (fieldDescriptor.getType()) { - case INT32: - case SFIXED32: - case SINT32: - val = RubyNumeric.num2int(value); - break; - case INT64: - case SFIXED64: - case SINT64: - val = RubyNumeric.num2long(value); - break; - case FIXED32: - case UINT32: - val = Utils.num2uint(value); - break; - case FIXED64: - case UINT64: - val = Utils.num2ulong(context.runtime, value); - break; - case FLOAT: - val = (float) RubyNumeric.num2dbl(value); - break; - case DOUBLE: - val = (double) RubyNumeric.num2dbl(value); - break; - case BOOL: - val = value.isTrue(); - break; - case BYTES: - val = ByteString.copyFrom(((RubyString) value).getBytes()); - break; - case STRING: - if (isDefaultStringForBytes) { - val = ((RubyString) value).getByteList().toString(); - } else { - val = value.asJavaString(); - } - break; - case MESSAGE: - val = ((RubyMessage) value).build(context, depth + 1, recursionLimit); - break; - case ENUM: - EnumDescriptor enumDescriptor = fieldDescriptor.getEnumType(); - if (Utils.isRubyNum(value)) { - val = enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); - } else { - val = enumDescriptor.findValueByName(value.asJavaString()); - } - break; - default: - break; - } - - return val; - } - - private static RaiseException createParseError(ThreadContext context, String message) { - if (parseErrorClass == null) { - parseErrorClass = - (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::ParseError"); - } - return RaiseException.from(context.runtime, parseErrorClass, message); - } - - private IRubyObject wrapField( - ThreadContext context, FieldDescriptor fieldDescriptor, Object value) { - return wrapField(context, fieldDescriptor, value, false); - } - - private IRubyObject wrapField( - ThreadContext context, FieldDescriptor fieldDescriptor, Object value, boolean encodeBytes) { - if (value == null) { - return context.runtime.getNil(); - } - Ruby runtime = context.runtime; - - switch (fieldDescriptor.getType()) { - case INT32: - case INT64: - case FIXED32: - case SINT32: - case FIXED64: - case SINT64: - case SFIXED64: - case SFIXED32: - case UINT32: - case UINT64: - case FLOAT: - case DOUBLE: - case BOOL: - case BYTES: - case STRING: - return Utils.wrapPrimaryValue(context, fieldDescriptor.getType(), value, encodeBytes); - case MESSAGE: - RubyClass typeClass = - (RubyClass) - ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)) - .msgclass(context); - RubyMessage msg = (RubyMessage) typeClass.newInstance(context, Block.NULL_BLOCK); - return msg.buildFrom(context, (DynamicMessage) value); - case ENUM: - EnumValueDescriptor enumValueDescriptor = (EnumValueDescriptor) value; - if (enumValueDescriptor.getIndex() == -1) { // UNKNOWN ENUM VALUE - return runtime.newFixnum(enumValueDescriptor.getNumber()); - } - return runtime.newSymbol(enumValueDescriptor.getName()); - default: - return runtime.newString(value.toString()); - } - } - - private RubyRepeatedField repeatedFieldForFieldDescriptor( - ThreadContext context, FieldDescriptor fieldDescriptor) { - IRubyObject typeClass = context.runtime.getNilClass(); - IRubyObject descriptor = getDescriptorForField(context, fieldDescriptor); - FieldDescriptor.Type type = fieldDescriptor.getType(); - - if (type == FieldDescriptor.Type.MESSAGE) { - typeClass = ((RubyDescriptor) descriptor).msgclass(context); - - } else if (type == FieldDescriptor.Type.ENUM) { - typeClass = ((RubyEnumDescriptor) descriptor).enummodule(context); - } - - RubyRepeatedField field = - new RubyRepeatedField(context.runtime, cRepeatedField, type, typeClass); - field.setName(fieldDescriptor.getName()); - - return field; - } - - private IRubyObject getFieldInternal(ThreadContext context, FieldDescriptor fieldDescriptor) { - return getFieldInternal(context, fieldDescriptor, true); - } - - private IRubyObject getFieldInternal( - ThreadContext context, FieldDescriptor fieldDescriptor, boolean returnDefaults) { - OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof(); - if (oneofDescriptor != null) { - if (oneofCases.get(oneofDescriptor) == fieldDescriptor) { - IRubyObject value = fields.get(fieldDescriptor); - if (value == null) { - FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor); - if (oneofCase != null) { - Object builderValue = builder.getField(oneofCase); - if (builderValue != null) { - boolean encodeBytes = - oneofCase.hasDefaultValue() && builderValue.equals(oneofCase.getDefaultValue()); - value = wrapField(context, oneofCase, builderValue, encodeBytes); - } - } - if (value == null) { - return context.nil; - } else { - return value; - } - } else { - return value; - } - } else { - FieldDescriptor oneofCase = builder.getOneofFieldDescriptor(oneofDescriptor); - if (oneofCase != fieldDescriptor) { - if (fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE || !returnDefaults) { - return context.nil; - } else { - return wrapField(context, fieldDescriptor, fieldDescriptor.getDefaultValue(), true); - } - } - if (returnDefaults || builder.hasField(fieldDescriptor)) { - Object rawValue = builder.getField(oneofCase); - boolean encodeBytes = - oneofCase.hasDefaultValue() && rawValue.equals(oneofCase.getDefaultValue()); - IRubyObject value = wrapField(context, oneofCase, rawValue, encodeBytes); - fields.put(fieldDescriptor, value); - return value; - } else { - return context.nil; - } - } - } - - if (fieldDescriptor.isMapField()) { - RubyMap map = (RubyMap) fields.get(fieldDescriptor); - if (map == null) { - map = newMapForField(context, fieldDescriptor); - int mapSize = this.builder.getRepeatedFieldCount(fieldDescriptor); - FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1); - FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2); - RubyDescriptor kvDescriptor = - (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - RubyClass kvClass = (RubyClass) kvDescriptor.msgclass(context); - for (int i = 0; i < mapSize; i++) { - RubyMessage kvMessage = (RubyMessage) kvClass.newInstance(context, Block.NULL_BLOCK); - DynamicMessage message = - (DynamicMessage) this.builder.getRepeatedField(fieldDescriptor, i); - kvMessage.buildFrom(context, message); - map.indexSet( - context, - kvMessage.getField(context, keyField), - kvMessage.getField(context, valueField)); - } - fields.put(fieldDescriptor, map); - } - return map; - } - - if (fieldDescriptor.isRepeated()) { - return getRepeatedField(context, fieldDescriptor); - } - - if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE - || builder.hasField(fieldDescriptor) - || fields.containsKey(fieldDescriptor)) { - if (fields.containsKey(fieldDescriptor)) { - return fields.get(fieldDescriptor); - } else if (returnDefaults || builder.hasField(fieldDescriptor)) { - Object rawValue = builder.getField(fieldDescriptor); - boolean encodeBytes = - fieldDescriptor.hasDefaultValue() && rawValue.equals(fieldDescriptor.getDefaultValue()); - IRubyObject value = wrapField(context, fieldDescriptor, rawValue, encodeBytes); - if (builder.hasField(fieldDescriptor)) { - fields.put(fieldDescriptor, value); - } - return value; - } - } - return context.nil; - } - - private IRubyObject setFieldInternal( - ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { - testFrozen("can't modify frozen " + getMetaClass()); - - if (fieldDescriptor.isMapField()) { - if (!(value instanceof RubyMap)) { - throw Utils.createTypeError(context, "Expected Map instance"); - } - RubyMap thisMap = (RubyMap) getFieldInternal(context, fieldDescriptor); - thisMap.mergeIntoSelf(context, value); - - } else if (fieldDescriptor.isRepeated()) { - if (value instanceof RubyRepeatedField) { - fields.put(fieldDescriptor, value); - } else { - throw Utils.createTypeError(context, "Expected repeated field array"); - } - - } else { - boolean addValue = true; - FieldDescriptor.Type fieldType = fieldDescriptor.getType(); - OneofDescriptor oneofDescriptor = fieldDescriptor.getContainingOneof(); - - // Determine the typeclass, if any - IRubyObject typeClass = context.runtime.getObject(); - if (fieldType == FieldDescriptor.Type.MESSAGE) { - typeClass = - ((RubyDescriptor) getDescriptorForField(context, fieldDescriptor)).msgclass(context); - if (value.isNil()) { - addValue = false; - } - } else if (fieldType == FieldDescriptor.Type.ENUM) { - typeClass = - ((RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor)) - .enummodule(context); - value = enumToSymbol(context, fieldDescriptor.getEnumType(), value); - } - - if (oneofDescriptor != null) { - FieldDescriptor oneofCase = oneofCases.get(oneofDescriptor); - - // Remove the existing field if we are setting a different field in the Oneof - if (oneofCase != null && oneofCase != fieldDescriptor) { - fields.remove(oneofCase); - } - - // Keep track of what Oneofs are set - if (value.isNil()) { - oneofCases.remove(oneofDescriptor); - if (!oneofDescriptor.isSynthetic()) { - addValue = false; - } - } else { - oneofCases.put(oneofDescriptor, fieldDescriptor); - } - } - - if (addValue) { - value = - Utils.checkType( - context, fieldType, fieldDescriptor.getName(), value, (RubyModule) typeClass); - fields.put(fieldDescriptor, value); - } else { - fields.remove(fieldDescriptor); - } - } - return context.nil; - } - - private IRubyObject getDescriptorForField( - ThreadContext context, FieldDescriptor fieldDescriptor) { - RubyDescriptor thisRbDescriptor = (RubyDescriptor) getDescriptor(context, metaClass); - RubyFieldDescriptor fd = - (RubyFieldDescriptor) - thisRbDescriptor.lookup(context, context.runtime.newString(fieldDescriptor.getName())); - return fd.getSubtype(context); - } - - private IRubyObject enumToSymbol( - ThreadContext context, EnumDescriptor enumDescriptor, IRubyObject value) { - if (value instanceof RubySymbol) { - return (RubySymbol) value; - } else if (Utils.isRubyNum(value)) { - EnumValueDescriptor enumValue = - enumDescriptor.findValueByNumberCreatingIfUnknown(RubyNumeric.num2int(value)); - if (enumValue.getIndex() != -1) { - return context.runtime.newSymbol(enumValue.getName()); - } else { - return value; - } - } else if (value instanceof RubyString) { - return ((RubyString) value).intern(); - } - - return context.runtime.newSymbol("UNKNOWN"); - } - - private RubyRepeatedField rubyToRepeatedField( - ThreadContext context, FieldDescriptor fieldDescriptor, IRubyObject value) { - RubyArray arr = value.convertToArray(); - RubyRepeatedField repeatedField = repeatedFieldForFieldDescriptor(context, fieldDescriptor); - IRubyObject[] values = new IRubyObject[arr.size()]; - FieldDescriptor.Type fieldType = fieldDescriptor.getType(); - - RubyModule typeClass = null; - if (fieldType == FieldDescriptor.Type.MESSAGE) { - RubyDescriptor descriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - typeClass = (RubyModule) descriptor.msgclass(context); - } else if (fieldType == FieldDescriptor.Type.ENUM) { - RubyEnumDescriptor enumDescriptor = - (RubyEnumDescriptor) getDescriptorForField(context, fieldDescriptor); - typeClass = (RubyModule) enumDescriptor.enummodule(context); - } - - for (int i = 0; i < arr.size(); i++) { - IRubyObject item = arr.eltInternal(i); - if (item.isNil()) { - throw Utils.createTypeError(context, "nil message not allowed here."); - } - if (item instanceof RubyHash && typeClass != null) { - values[i] = ((RubyClass) typeClass).newInstance(context, item, Block.NULL_BLOCK); - } else { - if (fieldType == FieldDescriptor.Type.ENUM) { - item = enumToSymbol(context, fieldDescriptor.getEnumType(), item); - } - - values[i] = item; - } - } - repeatedField.push(context, values); - - return repeatedField; - } - - private RubyMap newMapForField(ThreadContext context, FieldDescriptor fieldDescriptor) { - RubyDescriptor mapDescriptor = (RubyDescriptor) getDescriptorForField(context, fieldDescriptor); - FieldDescriptor keyField = fieldDescriptor.getMessageType().findFieldByNumber(1); - FieldDescriptor valueField = fieldDescriptor.getMessageType().findFieldByNumber(2); - IRubyObject keyType = RubySymbol.newSymbol(context.runtime, keyField.getType().name()); - IRubyObject valueType = RubySymbol.newSymbol(context.runtime, valueField.getType().name()); - - if (valueField.getType() == FieldDescriptor.Type.MESSAGE) { - RubyFieldDescriptor rubyFieldDescriptor = - (RubyFieldDescriptor) mapDescriptor.lookup(context, context.runtime.newString("value")); - RubyDescriptor rubyDescriptor = (RubyDescriptor) rubyFieldDescriptor.getSubtype(context); - return (RubyMap) - cMap.newInstance( - context, keyType, valueType, rubyDescriptor.msgclass(context), Block.NULL_BLOCK); - - } else if (valueField.getType() == FieldDescriptor.Type.ENUM) { - RubyFieldDescriptor rubyFieldDescriptor = - (RubyFieldDescriptor) mapDescriptor.lookup(context, context.runtime.newString("value")); - RubyEnumDescriptor rubyEnumDescriptor = - (RubyEnumDescriptor) rubyFieldDescriptor.getSubtype(context); - return (RubyMap) - cMap.newInstance( - context, - keyType, - valueType, - rubyEnumDescriptor.enummodule(context), - Block.NULL_BLOCK); - - } else { - return (RubyMap) cMap.newInstance(context, keyType, valueType, Block.NULL_BLOCK); - } - } - - private boolean isWrappable(FieldDescriptor fieldDescriptor) { - if (fieldDescriptor.getType() != FieldDescriptor.Type.MESSAGE) return false; - - return isWrapper(fieldDescriptor.getMessageType()); - } - - private static boolean isWrapper(Descriptor messageDescriptor) { - switch (messageDescriptor.getFullName()) { - case "google.protobuf.DoubleValue": - case "google.protobuf.FloatValue": - case "google.protobuf.Int64Value": - case "google.protobuf.UInt64Value": - case "google.protobuf.Int32Value": - case "google.protobuf.UInt32Value": - case "google.protobuf.BoolValue": - case "google.protobuf.StringValue": - case "google.protobuf.BytesValue": - return true; - default: - return false; - } - } - - private void validateMessageType( - ThreadContext context, FieldDescriptor fieldDescriptor, String methodName) { - if (descriptor != fieldDescriptor.getContainingType()) { - throw Utils.createTypeError(context, methodName + " method called on wrong message type"); - } - } - - private static RubyClass parseErrorClass; - - private static final String AS_VALUE_SUFFIX = "_as_value"; - private static final String CLEAR_PREFIX = "clear_"; - private static final String CONST_SUFFIX = "_const"; - private static final String HAS_PREFIX = "has_"; - private static final String QUESTION_MARK = "?"; - private static final int SINK_MAXIMUM_NESTING = 64; - - private Descriptor descriptor; - private DynamicMessage.Builder builder; - private Map fields; - private Map oneofCases; - private RubyClass cRepeatedField; - private RubyClass cMap; - private boolean ignoreUnknownFieldsOnInit = false; - private boolean proto3; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java deleted file mode 100644 index 5ade98b7f32b..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyOneofDescriptor.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.google.protobuf.jruby; - -import com.google.protobuf.Descriptors.FieldDescriptor; -import com.google.protobuf.Descriptors.OneofDescriptor; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import org.jruby.Ruby; -import org.jruby.RubyClass; -import org.jruby.RubyModule; -import org.jruby.RubyObject; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "OneofDescriptor", include = "Enumerable") -public class RubyOneofDescriptor extends RubyObject { - - public static void createRubyOneofDescriptor(Ruby runtime) { - RubyModule protobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cRubyOneofDescriptor = - protobuf.defineClassUnder( - "OneofDescriptor", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby ruby, RubyClass rubyClass) { - return new RubyOneofDescriptor(ruby, rubyClass); - } - }); - cRubyOneofDescriptor.defineAnnotatedMethods(RubyOneofDescriptor.class); - cRubyOneofDescriptor.includeModule(runtime.getEnumerable()); - } - - public RubyOneofDescriptor(Ruby ruby, RubyClass rubyClass) { - super(ruby, rubyClass); - fields = new ArrayList(); - } - - /* - * call-seq: - * OneofDescriptor.name => name - * - * Returns the name of this oneof. - */ - @JRubyMethod(name = "name") - public IRubyObject getName(ThreadContext context) { - return name; - } - - /* - * call-seq: - * OneofDescriptor.each(&block) => nil - * - * Iterates through fields in this oneof, yielding to the block on each one. - */ - @JRubyMethod - public IRubyObject each(ThreadContext context, Block block) { - for (RubyFieldDescriptor field : fields) { - block.yieldSpecific(context, field); - } - return context.nil; - } - - protected Collection getFields() { - return fields; - } - - protected OneofDescriptor getDescriptor() { - return descriptor; - } - - protected void setDescriptor( - ThreadContext context, - OneofDescriptor descriptor, - Map fieldCache) { - this.descriptor = descriptor; - this.name = context.runtime.newString(descriptor.getName()); - - for (FieldDescriptor fd : descriptor.getFields()) { - fields.add(fieldCache.get(fd)); - } - } - - private IRubyObject name; - private List fields; - private OneofDescriptor descriptor; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java deleted file mode 100644 index 8d132be348ff..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyProtobuf.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import org.jruby.Ruby; -import org.jruby.RubyModule; -import org.jruby.anno.JRubyMethod; -import org.jruby.anno.JRubyModule; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyModule(name = "Protobuf") -public class RubyProtobuf { - - public static void createProtobuf(Ruby runtime) { - RubyModule mGoogle = runtime.getModule("Google"); - RubyModule mProtobuf = mGoogle.defineModuleUnder("Protobuf"); - mProtobuf.defineAnnotatedMethods(RubyProtobuf.class); - RubyModule mInternal = mProtobuf.defineModuleUnder("Internal"); - } - - /* - * call-seq: - * Google::Protobuf.deep_copy(obj) => copy_of_obj - * - * Performs a deep copy of either a RepeatedField instance or a message object, - * recursively copying its members. - */ - @JRubyMethod(name = "deep_copy", meta = true) - public static IRubyObject deepCopy(ThreadContext context, IRubyObject self, IRubyObject message) { - if (message instanceof RubyMessage) { - return ((RubyMessage) message).deepCopy(context); - } else if (message instanceof RubyRepeatedField) { - return ((RubyRepeatedField) message).deepCopy(context); - } else { - return ((RubyMap) message).deepCopy(context); - } - } - - /* - * call-seq: - * Google::Protobuf.discard_unknown(msg) - * - * Discard unknown fields in the given message object and recursively discard - * unknown fields in submessages. - */ - @JRubyMethod(name = "discard_unknown", meta = true) - public static IRubyObject discardUnknown( - ThreadContext context, IRubyObject self, IRubyObject message) { - ((RubyMessage) message).discardUnknownFields(context); - return context.nil; - } -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java b/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java deleted file mode 100644 index 77842d1290aa..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/RubyRepeatedField.java +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.Descriptors.FieldDescriptor; -import org.jruby.*; -import org.jruby.anno.JRubyClass; -import org.jruby.anno.JRubyMethod; -import org.jruby.runtime.Block; -import org.jruby.runtime.ObjectAllocator; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -@JRubyClass(name = "RepeatedClass", include = "Enumerable") -public class RubyRepeatedField extends RubyObject { - public static void createRubyRepeatedField(Ruby runtime) { - RubyModule mProtobuf = runtime.getClassFromPath("Google::Protobuf"); - RubyClass cRepeatedField = - mProtobuf.defineClassUnder( - "RepeatedField", - runtime.getObject(), - new ObjectAllocator() { - @Override - public IRubyObject allocate(Ruby runtime, RubyClass klazz) { - return new RubyRepeatedField(runtime, klazz); - } - }); - cRepeatedField.defineAnnotatedMethods(RubyRepeatedField.class); - cRepeatedField.includeModule(runtime.getEnumerable()); - } - - public RubyRepeatedField(Ruby runtime, RubyClass klazz) { - super(runtime, klazz); - } - - public RubyRepeatedField( - Ruby runtime, RubyClass klazz, FieldDescriptor.Type fieldType, IRubyObject typeClass) { - this(runtime, klazz); - this.fieldType = fieldType; - this.storage = runtime.newArray(); - this.typeClass = typeClass; - } - - @JRubyMethod(required = 1, optional = 2) - public IRubyObject initialize(ThreadContext context, IRubyObject[] args) { - Ruby runtime = context.runtime; - this.storage = runtime.newArray(); - IRubyObject ary = null; - if (!(args[0] instanceof RubySymbol)) { - throw runtime.newArgumentError("Expected Symbol for type name"); - } - this.fieldType = Utils.rubyToFieldType(args[0]); - if (fieldType == FieldDescriptor.Type.MESSAGE || fieldType == FieldDescriptor.Type.ENUM) { - if (args.length < 2) - throw runtime.newArgumentError("Expected at least 2 arguments for message/enum"); - typeClass = args[1]; - if (args.length > 2) ary = args[2]; - Utils.validateTypeClass(context, fieldType, typeClass); - } else { - if (args.length > 2) throw runtime.newArgumentError("Too many arguments: expected 1 or 2"); - if (args.length > 1) ary = args[1]; - } - if (ary != null) { - RubyArray arr = ary.convertToArray(); - for (int i = 0; i < arr.size(); i++) { - this.storage.add(arr.eltInternal(i)); - } - } - return this; - } - - /* - * call-seq: - * RepeatedField.[]=(index, value) - * - * Sets the element at the given index. On out-of-bounds assignments, extends - * the array and fills the hole (if any) with default values. - */ - @JRubyMethod(name = "[]=") - public IRubyObject indexSet(ThreadContext context, IRubyObject index, IRubyObject value) { - int arrIndex = normalizeArrayIndex(index); - value = Utils.checkType(context, fieldType, name, value, (RubyModule) typeClass); - IRubyObject defaultValue = defaultValue(context); - for (int i = this.storage.size(); i < arrIndex; i++) { - this.storage.set(i, defaultValue); - } - this.storage.set(arrIndex, value); - return context.runtime.getNil(); - } - - /* - * call-seq: - * RepeatedField.[](index) => value - * - * Accesses the element at the given index. Returns nil on out-of-bounds - */ - @JRubyMethod( - required = 1, - optional = 1, - name = {"at", "[]"}) - public IRubyObject index(ThreadContext context, IRubyObject[] args) { - if (args.length == 1) { - IRubyObject arg = args[0]; - if (Utils.isRubyNum(arg)) { - /* standard case */ - int arrIndex = normalizeArrayIndex(arg); - if (arrIndex < 0 || arrIndex >= this.storage.size()) { - return context.runtime.getNil(); - } - return this.storage.eltInternal(arrIndex); - } else if (arg instanceof RubyRange) { - RubyRange range = ((RubyRange) arg); - - int first = normalizeArrayIndex(range.first(context)); - int last = normalizeArrayIndex(range.last(context)); - - if (last - first < 0) { - return context.runtime.newEmptyArray(); - } - - return this.storage.subseq(first, last - first + (range.isExcludeEnd() ? 0 : 1)); - } - } - /* assume 2 arguments */ - int beg = RubyNumeric.num2int(args[0]); - int len = RubyNumeric.num2int(args[1]); - if (beg < 0) { - beg += this.storage.size(); - } - if (beg >= this.storage.size()) { - return context.runtime.getNil(); - } - return this.storage.subseq(beg, len); - } - - /* - * call-seq: - * RepeatedField.push(value) - * - * Adds a new element to the repeated field. - */ - @JRubyMethod( - name = {"push", "<<"}, - required = 1, - rest = true) - public IRubyObject push(ThreadContext context, IRubyObject[] args) { - for (int i = 0; i < args.length; i++) { - IRubyObject val = args[i]; - if (fieldType != FieldDescriptor.Type.MESSAGE || !val.isNil()) { - val = Utils.checkType(context, fieldType, name, val, (RubyModule) typeClass); - } - storage.add(val); - } - - return this; - } - - /* - * private Ruby method used by RepeatedField.pop - */ - @JRubyMethod(visibility = org.jruby.runtime.Visibility.PRIVATE) - public IRubyObject pop_one(ThreadContext context) { - IRubyObject ret = this.storage.last(); - this.storage.remove(ret); - return ret; - } - - /* - * call-seq: - * RepeatedField.replace(list) - * - * Replaces the contents of the repeated field with the given list of elements. - */ - @JRubyMethod - public IRubyObject replace(ThreadContext context, IRubyObject list) { - RubyArray arr = (RubyArray) list; - checkArrayElementType(context, arr); - this.storage = arr; - return this; - } - - /* - * call-seq: - * RepeatedField.clear - * - * Clears (removes all elements from) this repeated field. - */ - @JRubyMethod - public IRubyObject clear(ThreadContext context) { - this.storage.clear(); - return this; - } - - /* - * call-seq: - * RepeatedField.length - * - * Returns the length of this repeated field. - */ - @JRubyMethod(name = {"length", "size"}) - public IRubyObject length(ThreadContext context) { - return context.runtime.newFixnum(this.storage.size()); - } - - /* - * call-seq: - * RepeatedField.+(other) => repeated field - * - * Returns a new repeated field that contains the concatenated list of this - * repeated field's elements and other's elements. The other (second) list may - * be either another repeated field or a Ruby array. - */ - @JRubyMethod(name = {"+"}) - public IRubyObject plus(ThreadContext context, IRubyObject list) { - RubyRepeatedField dup = (RubyRepeatedField) dup(context); - if (list instanceof RubyArray) { - checkArrayElementType(context, (RubyArray) list); - dup.storage.addAll((RubyArray) list); - } else { - RubyRepeatedField repeatedField = (RubyRepeatedField) list; - if (!fieldType.equals(repeatedField.fieldType) - || (typeClass != null && !typeClass.equals(repeatedField.typeClass))) - throw context.runtime.newArgumentError( - "Attempt to append RepeatedField with different element type."); - dup.storage.addAll((RubyArray) repeatedField.toArray(context)); - } - return dup; - } - - /* - * call-seq: - * RepeatedField.concat(other) => self - * - * concats the passed in array to self. Returns a Ruby array. - */ - @JRubyMethod - public IRubyObject concat(ThreadContext context, IRubyObject list) { - if (list instanceof RubyArray) { - checkArrayElementType(context, (RubyArray) list); - this.storage.addAll((RubyArray) list); - } else { - RubyRepeatedField repeatedField = (RubyRepeatedField) list; - if (!fieldType.equals(repeatedField.fieldType) - || (typeClass != null && !typeClass.equals(repeatedField.typeClass))) - throw context.runtime.newArgumentError( - "Attempt to append RepeatedField with different element type."); - this.storage.addAll((RubyArray) repeatedField.toArray(context)); - } - return this; - } - - /* - * call-seq: - * RepeatedField.hash => hash_value - * - * Returns a hash value computed from this repeated field's elements. - */ - @JRubyMethod - public IRubyObject hash(ThreadContext context) { - int hashCode = this.storage.hashCode(); - return context.runtime.newFixnum(hashCode); - } - - /* - * call-seq: - * RepeatedField.==(other) => boolean - * - * Compares this repeated field to another. Repeated fields are equal if their - * element types are equal, their lengths are equal, and each element is equal. - * Elements are compared as per normal Ruby semantics, by calling their :== - * methods (or performing a more efficient comparison for primitive types). - */ - @JRubyMethod(name = "==") - public IRubyObject eq(ThreadContext context, IRubyObject other) { - return this.toArray(context).op_equal(context, other); - } - - /* - * call-seq: - * RepeatedField.each(&block) - * - * Invokes the block once for each element of the repeated field. RepeatedField - * also includes Enumerable; combined with this method, the repeated field thus - * acts like an ordinary Ruby sequence. - */ - @JRubyMethod - public IRubyObject each(ThreadContext context, Block block) { - this.storage.each(context, block); - return this; - } - - @JRubyMethod(name = {"to_ary", "to_a"}) - public IRubyObject toArray(ThreadContext context) { - return this.storage; - } - - /* - * call-seq: - * RepeatedField.dup => repeated_field - * - * Duplicates this repeated field with a shallow copy. References to all - * non-primitive element objects (e.g., submessages) are shared. - */ - @JRubyMethod - public IRubyObject dup(ThreadContext context) { - RubyRepeatedField dup = new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass); - dup.push(context, storage.toJavaArray()); - return dup; - } - - @JRubyMethod - public IRubyObject inspect() { - return storage.inspect(); - } - - // Java API - protected IRubyObject get(int index) { - return this.storage.eltInternal(index); - } - - protected RubyRepeatedField deepCopy(ThreadContext context) { - RubyRepeatedField copy = - new RubyRepeatedField(context.runtime, metaClass, fieldType, typeClass); - for (int i = 0; i < size(); i++) { - IRubyObject value = storage.eltInternal(i); - if (fieldType == FieldDescriptor.Type.MESSAGE) { - copy.storage.add(((RubyMessage) value).deepCopy(context)); - } else { - copy.storage.add(value); - } - } - return copy; - } - - protected void setName(String name) { - this.name = name; - } - - protected int size() { - return this.storage.size(); - } - - private IRubyObject defaultValue(ThreadContext context) { - SentinelOuterClass.Sentinel sentinel = SentinelOuterClass.Sentinel.getDefaultInstance(); - Object value; - switch (fieldType) { - case INT32: - value = sentinel.getDefaultInt32(); - break; - case INT64: - value = sentinel.getDefaultInt64(); - break; - case UINT32: - value = sentinel.getDefaultUnit32(); - break; - case UINT64: - value = sentinel.getDefaultUint64(); - break; - case FLOAT: - value = sentinel.getDefaultFloat(); - break; - case DOUBLE: - value = sentinel.getDefaultDouble(); - break; - case BOOL: - value = sentinel.getDefaultBool(); - break; - case BYTES: - value = sentinel.getDefaultBytes(); - break; - case STRING: - value = sentinel.getDefaultString(); - break; - case ENUM: - IRubyObject defaultEnumLoc = context.runtime.newFixnum(0); - return RubyEnum.lookup(context, typeClass, defaultEnumLoc); - default: - return context.runtime.getNil(); - } - return Utils.wrapPrimaryValue(context, fieldType, value); - } - - private void checkArrayElementType(ThreadContext context, RubyArray arr) { - for (int i = 0; i < arr.getLength(); i++) { - Utils.checkType(context, fieldType, name, arr.eltInternal(i), (RubyModule) typeClass); - } - } - - private int normalizeArrayIndex(IRubyObject index) { - int arrIndex = RubyNumeric.num2int(index); - int arrSize = this.storage.size(); - if (arrIndex < 0 && arrSize > 0) { - arrIndex = arrSize + arrIndex; - } - return arrIndex; - } - - private FieldDescriptor.Type fieldType; - private IRubyObject typeClass; - private RubyArray storage; - private String name; -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java b/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java deleted file mode 100644 index 3c56cf86a834..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/SentinelOuterClass.java +++ /dev/null @@ -1,693 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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. - */ - -// Generated by the protocol buffer compiler. DO NOT EDIT! -// source: sentinel.proto - -package com.google.protobuf.jruby; - -public final class SentinelOuterClass { - private SentinelOuterClass() {} - - public static void registerAllExtensions(com.google.protobuf.ExtensionRegistry registry) {} - - public interface SentinelOrBuilder - extends - // @@protoc_insertion_point(interface_extends:com.google.protobuf.jruby.Sentinel) - com.google.protobuf.MessageOrBuilder { - - /** optional int32 default_int32 = 1; */ - int getDefaultInt32(); - - /** optional int64 default_int64 = 2; */ - long getDefaultInt64(); - - /** optional uint32 default_unit32 = 3; */ - int getDefaultUnit32(); - - /** optional uint64 default_uint64 = 4; */ - long getDefaultUint64(); - - /** optional string default_string = 5; */ - java.lang.String getDefaultString(); - /** optional string default_string = 5; */ - com.google.protobuf.ByteString getDefaultStringBytes(); - - /** optional bool default_bool = 6; */ - boolean getDefaultBool(); - - /** optional float default_float = 7; */ - float getDefaultFloat(); - - /** optional double default_double = 8; */ - double getDefaultDouble(); - - /** optional bytes default_bytes = 9; */ - com.google.protobuf.ByteString getDefaultBytes(); - } - /** Protobuf type {@code com.google.protobuf.jruby.Sentinel} */ - public static final class Sentinel extends com.google.protobuf.GeneratedMessage - implements - // @@protoc_insertion_point(message_implements:com.google.protobuf.jruby.Sentinel) - SentinelOrBuilder { - // Use Sentinel.newBuilder() to construct. - private Sentinel(com.google.protobuf.GeneratedMessage.Builder builder) { - super(builder); - } - - private Sentinel() { - defaultInt32_ = 0; - defaultInt64_ = 0L; - defaultUnit32_ = 0; - defaultUint64_ = 0L; - defaultString_ = ""; - defaultBool_ = false; - defaultFloat_ = 0F; - defaultDouble_ = 0D; - defaultBytes_ = com.google.protobuf.ByteString.EMPTY; - } - - @java.lang.Override - public final com.google.protobuf.UnknownFieldSet getUnknownFields() { - return com.google.protobuf.UnknownFieldSet.getDefaultInstance(); - } - - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.google.protobuf.jruby.SentinelOuterClass - .internal_static_com_google_protobuf_jruby_Sentinel_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return com.google.protobuf.jruby.SentinelOuterClass - .internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable - .ensureFieldAccessorsInitialized( - com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, - com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class); - } - - public static final com.google.protobuf.Parser PARSER = - new com.google.protobuf.AbstractParser() { - public Sentinel parsePartialFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - Builder builder = newBuilder(); - try { - builder.mergeFrom(input, extensionRegistry); - } catch (com.google.protobuf.InvalidProtocolBufferException e) { - throw e.setUnfinishedMessage(builder.buildPartial()); - } catch (java.io.IOException e) { - throw new com.google.protobuf.InvalidProtocolBufferException(e.getMessage()) - .setUnfinishedMessage(builder.buildPartial()); - } - return builder.buildPartial(); - } - }; - - @java.lang.Override - public com.google.protobuf.Parser getParserForType() { - return PARSER; - } - - public static final int DEFAULT_INT32_FIELD_NUMBER = 1; - private int defaultInt32_; - /** optional int32 default_int32 = 1; */ - public int getDefaultInt32() { - return defaultInt32_; - } - - public static final int DEFAULT_INT64_FIELD_NUMBER = 2; - private long defaultInt64_; - /** optional int64 default_int64 = 2; */ - public long getDefaultInt64() { - return defaultInt64_; - } - - public static final int DEFAULT_UNIT32_FIELD_NUMBER = 3; - private int defaultUnit32_; - /** optional uint32 default_unit32 = 3; */ - public int getDefaultUnit32() { - return defaultUnit32_; - } - - public static final int DEFAULT_UINT64_FIELD_NUMBER = 4; - private long defaultUint64_; - /** optional uint64 default_uint64 = 4; */ - public long getDefaultUint64() { - return defaultUint64_; - } - - public static final int DEFAULT_STRING_FIELD_NUMBER = 5; - private java.lang.Object defaultString_; - /** optional string default_string = 5; */ - public java.lang.String getDefaultString() { - java.lang.Object ref = defaultString_; - if (ref instanceof java.lang.String) { - return (java.lang.String) ref; - } else { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - defaultString_ = s; - } - return s; - } - } - /** optional string default_string = 5; */ - public com.google.protobuf.ByteString getDefaultStringBytes() { - java.lang.Object ref = defaultString_; - if (ref instanceof java.lang.String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - defaultString_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - - public static final int DEFAULT_BOOL_FIELD_NUMBER = 6; - private boolean defaultBool_; - /** optional bool default_bool = 6; */ - public boolean getDefaultBool() { - return defaultBool_; - } - - public static final int DEFAULT_FLOAT_FIELD_NUMBER = 7; - private float defaultFloat_; - /** optional float default_float = 7; */ - public float getDefaultFloat() { - return defaultFloat_; - } - - public static final int DEFAULT_DOUBLE_FIELD_NUMBER = 8; - private double defaultDouble_; - /** optional double default_double = 8; */ - public double getDefaultDouble() { - return defaultDouble_; - } - - public static final int DEFAULT_BYTES_FIELD_NUMBER = 9; - private com.google.protobuf.ByteString defaultBytes_; - /** optional bytes default_bytes = 9; */ - public com.google.protobuf.ByteString getDefaultBytes() { - return defaultBytes_; - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - com.google.protobuf.ByteString data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - com.google.protobuf.ByteString data, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom(byte[] data) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - byte[] data, com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws com.google.protobuf.InvalidProtocolBufferException { - return PARSER.parseFrom(data, extensionRegistry); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - java.io.InputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseDelimitedFrom( - java.io.InputStream input) throws java.io.IOException { - return PARSER.parseDelimitedFrom(input); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseDelimitedFrom( - java.io.InputStream input, com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseDelimitedFrom(input, extensionRegistry); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - com.google.protobuf.CodedInputStream input) throws java.io.IOException { - return PARSER.parseFrom(input); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel parseFrom( - com.google.protobuf.CodedInputStream input, - com.google.protobuf.ExtensionRegistryLite extensionRegistry) - throws java.io.IOException { - return PARSER.parseFrom(input, extensionRegistry); - } - - public static Builder newBuilder() { - return new Builder(); - } - - public Builder newBuilderForType() { - return newBuilder(); - } - - public static Builder newBuilder( - com.google.protobuf.jruby.SentinelOuterClass.Sentinel prototype) { - return newBuilder().mergeFrom(prototype); - } - - public Builder toBuilder() { - return newBuilder(this); - } - - @java.lang.Override - protected Builder newBuilderForType(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - Builder builder = new Builder(parent); - return builder; - } - /** Protobuf type {@code com.google.protobuf.jruby.Sentinel} */ - public static final class Builder extends com.google.protobuf.GeneratedMessage.Builder - implements - // @@protoc_insertion_point(builder_implements:com.google.protobuf.jruby.Sentinel) - com.google.protobuf.jruby.SentinelOuterClass.SentinelOrBuilder { - public static final com.google.protobuf.Descriptors.Descriptor getDescriptor() { - return com.google.protobuf.jruby.SentinelOuterClass - .internal_static_com_google_protobuf_jruby_Sentinel_descriptor; - } - - protected com.google.protobuf.GeneratedMessage.FieldAccessorTable - internalGetFieldAccessorTable() { - return com.google.protobuf.jruby.SentinelOuterClass - .internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable - .ensureFieldAccessorsInitialized( - com.google.protobuf.jruby.SentinelOuterClass.Sentinel.class, - com.google.protobuf.jruby.SentinelOuterClass.Sentinel.Builder.class); - } - - // Construct using com.google.protobuf.jruby.SentinelOuterClass.Sentinel.newBuilder() - private Builder() { - maybeForceBuilderInitialization(); - } - - private Builder(com.google.protobuf.GeneratedMessage.BuilderParent parent) { - super(parent); - maybeForceBuilderInitialization(); - } - - private void maybeForceBuilderInitialization() { - if (com.google.protobuf.GeneratedMessage.alwaysUseFieldBuilders) {} - } - - public Builder clear() { - super.clear(); - defaultInt32_ = 0; - - defaultInt64_ = 0L; - - defaultUnit32_ = 0; - - defaultUint64_ = 0L; - - defaultString_ = ""; - - defaultBool_ = false; - - defaultFloat_ = 0F; - - defaultDouble_ = 0D; - - defaultBytes_ = com.google.protobuf.ByteString.EMPTY; - - return this; - } - - public com.google.protobuf.Descriptors.Descriptor getDescriptorForType() { - return com.google.protobuf.jruby.SentinelOuterClass - .internal_static_com_google_protobuf_jruby_Sentinel_descriptor; - } - - public com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstanceForType() { - return com.google.protobuf.jruby.SentinelOuterClass.Sentinel.getDefaultInstance(); - } - - public com.google.protobuf.jruby.SentinelOuterClass.Sentinel build() { - com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = buildPartial(); - if (!result.isInitialized()) { - throw newUninitializedMessageException(result); - } - return result; - } - - public com.google.protobuf.jruby.SentinelOuterClass.Sentinel buildPartial() { - com.google.protobuf.jruby.SentinelOuterClass.Sentinel result = - new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(this); - result.defaultInt32_ = defaultInt32_; - result.defaultInt64_ = defaultInt64_; - result.defaultUnit32_ = defaultUnit32_; - result.defaultUint64_ = defaultUint64_; - result.defaultString_ = defaultString_; - result.defaultBool_ = defaultBool_; - result.defaultFloat_ = defaultFloat_; - result.defaultDouble_ = defaultDouble_; - result.defaultBytes_ = defaultBytes_; - onBuilt(); - return result; - } - - private int defaultInt32_; - /** optional int32 default_int32 = 1; */ - public int getDefaultInt32() { - return defaultInt32_; - } - /** optional int32 default_int32 = 1; */ - public Builder setDefaultInt32(int value) { - - defaultInt32_ = value; - onChanged(); - return this; - } - /** optional int32 default_int32 = 1; */ - public Builder clearDefaultInt32() { - - defaultInt32_ = 0; - onChanged(); - return this; - } - - private long defaultInt64_; - /** optional int64 default_int64 = 2; */ - public long getDefaultInt64() { - return defaultInt64_; - } - /** optional int64 default_int64 = 2; */ - public Builder setDefaultInt64(long value) { - - defaultInt64_ = value; - onChanged(); - return this; - } - /** optional int64 default_int64 = 2; */ - public Builder clearDefaultInt64() { - - defaultInt64_ = 0L; - onChanged(); - return this; - } - - private int defaultUnit32_; - /** optional uint32 default_unit32 = 3; */ - public int getDefaultUnit32() { - return defaultUnit32_; - } - /** optional uint32 default_unit32 = 3; */ - public Builder setDefaultUnit32(int value) { - - defaultUnit32_ = value; - onChanged(); - return this; - } - /** optional uint32 default_unit32 = 3; */ - public Builder clearDefaultUnit32() { - - defaultUnit32_ = 0; - onChanged(); - return this; - } - - private long defaultUint64_; - /** optional uint64 default_uint64 = 4; */ - public long getDefaultUint64() { - return defaultUint64_; - } - /** optional uint64 default_uint64 = 4; */ - public Builder setDefaultUint64(long value) { - - defaultUint64_ = value; - onChanged(); - return this; - } - /** optional uint64 default_uint64 = 4; */ - public Builder clearDefaultUint64() { - - defaultUint64_ = 0L; - onChanged(); - return this; - } - - private java.lang.Object defaultString_ = ""; - /** optional string default_string = 5; */ - public java.lang.String getDefaultString() { - java.lang.Object ref = defaultString_; - if (!(ref instanceof java.lang.String)) { - com.google.protobuf.ByteString bs = (com.google.protobuf.ByteString) ref; - java.lang.String s = bs.toStringUtf8(); - if (bs.isValidUtf8()) { - defaultString_ = s; - } - return s; - } else { - return (java.lang.String) ref; - } - } - /** optional string default_string = 5; */ - public com.google.protobuf.ByteString getDefaultStringBytes() { - java.lang.Object ref = defaultString_; - if (ref instanceof String) { - com.google.protobuf.ByteString b = - com.google.protobuf.ByteString.copyFromUtf8((java.lang.String) ref); - defaultString_ = b; - return b; - } else { - return (com.google.protobuf.ByteString) ref; - } - } - /** optional string default_string = 5; */ - public Builder setDefaultString(java.lang.String value) { - if (value == null) { - throw new NullPointerException(); - } - - defaultString_ = value; - onChanged(); - return this; - } - /** optional string default_string = 5; */ - public Builder clearDefaultString() { - - defaultString_ = getDefaultInstance().getDefaultString(); - onChanged(); - return this; - } - /** optional string default_string = 5; */ - public Builder setDefaultStringBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - - defaultString_ = value; - onChanged(); - return this; - } - - private boolean defaultBool_; - /** optional bool default_bool = 6; */ - public boolean getDefaultBool() { - return defaultBool_; - } - /** optional bool default_bool = 6; */ - public Builder setDefaultBool(boolean value) { - - defaultBool_ = value; - onChanged(); - return this; - } - /** optional bool default_bool = 6; */ - public Builder clearDefaultBool() { - - defaultBool_ = false; - onChanged(); - return this; - } - - private float defaultFloat_; - /** optional float default_float = 7; */ - public float getDefaultFloat() { - return defaultFloat_; - } - /** optional float default_float = 7; */ - public Builder setDefaultFloat(float value) { - - defaultFloat_ = value; - onChanged(); - return this; - } - /** optional float default_float = 7; */ - public Builder clearDefaultFloat() { - - defaultFloat_ = 0F; - onChanged(); - return this; - } - - private double defaultDouble_; - /** optional double default_double = 8; */ - public double getDefaultDouble() { - return defaultDouble_; - } - /** optional double default_double = 8; */ - public Builder setDefaultDouble(double value) { - - defaultDouble_ = value; - onChanged(); - return this; - } - /** optional double default_double = 8; */ - public Builder clearDefaultDouble() { - - defaultDouble_ = 0D; - onChanged(); - return this; - } - - private com.google.protobuf.ByteString defaultBytes_ = com.google.protobuf.ByteString.EMPTY; - /** optional bytes default_bytes = 9; */ - public com.google.protobuf.ByteString getDefaultBytes() { - return defaultBytes_; - } - /** optional bytes default_bytes = 9; */ - public Builder setDefaultBytes(com.google.protobuf.ByteString value) { - if (value == null) { - throw new NullPointerException(); - } - - defaultBytes_ = value; - onChanged(); - return this; - } - /** optional bytes default_bytes = 9; */ - public Builder clearDefaultBytes() { - - defaultBytes_ = getDefaultInstance().getDefaultBytes(); - onChanged(); - return this; - } - - public final Builder setUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - public final Builder mergeUnknownFields( - final com.google.protobuf.UnknownFieldSet unknownFields) { - return this; - } - - // @@protoc_insertion_point(builder_scope:com.google.protobuf.jruby.Sentinel) - } - - // @@protoc_insertion_point(class_scope:com.google.protobuf.jruby.Sentinel) - private static final com.google.protobuf.jruby.SentinelOuterClass.Sentinel defaultInstance; - - static { - defaultInstance = new com.google.protobuf.jruby.SentinelOuterClass.Sentinel(); - } - - public static com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstance() { - return defaultInstance; - } - - public com.google.protobuf.jruby.SentinelOuterClass.Sentinel getDefaultInstanceForType() { - return defaultInstance; - } - } - - private static final com.google.protobuf.Descriptors.Descriptor - internal_static_com_google_protobuf_jruby_Sentinel_descriptor; - private static com.google.protobuf.GeneratedMessage.FieldAccessorTable - internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable; - - public static com.google.protobuf.Descriptors.FileDescriptor getDescriptor() { - return descriptor; - } - - private static com.google.protobuf.Descriptors.FileDescriptor descriptor; - - static { - java.lang.String[] descriptorData = { - "\n\016sentinel.proto\022\031com.google.protobuf.jr" - + "uby\"\334\001\n\010Sentinel\022\025\n\rdefault_int32\030\001 \001(\005\022" - + "\025\n\rdefault_int64\030\002 \001(\003\022\026\n\016default_unit32" - + "\030\003 \001(\r\022\026\n\016default_uint64\030\004 \001(\004\022\026\n\016defaul" - + "t_string\030\005 \001(\t\022\024\n\014default_bool\030\006 \001(\010\022\025\n\r" - + "default_float\030\007 \001(\002\022\026\n\016default_double\030\010 " - + "\001(\001\022\025\n\rdefault_bytes\030\t \001(\014B\002H\002b\006proto3" - }; - com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = - new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { - public com.google.protobuf.ExtensionRegistry assignDescriptors( - com.google.protobuf.Descriptors.FileDescriptor root) { - descriptor = root; - return null; - } - }; - com.google.protobuf.Descriptors.FileDescriptor.internalBuildGeneratedFileFrom( - descriptorData, new com.google.protobuf.Descriptors.FileDescriptor[] {}, assigner); - internal_static_com_google_protobuf_jruby_Sentinel_descriptor = - getDescriptor().getMessageTypes().get(0); - internal_static_com_google_protobuf_jruby_Sentinel_fieldAccessorTable = - new com.google.protobuf.GeneratedMessage.FieldAccessorTable( - internal_static_com_google_protobuf_jruby_Sentinel_descriptor, - new java.lang.String[] { - "DefaultInt32", - "DefaultInt64", - "DefaultUnit32", - "DefaultUint64", - "DefaultString", - "DefaultBool", - "DefaultFloat", - "DefaultDouble", - "DefaultBytes", - }); - } - - // @@protoc_insertion_point(outer_class_scope) -} diff --git a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java b/ruby/src/main/java/com/google/protobuf/jruby/Utils.java deleted file mode 100644 index 65de683b0226..000000000000 --- a/ruby/src/main/java/com/google/protobuf/jruby/Utils.java +++ /dev/null @@ -1,407 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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.jruby; - -import com.google.protobuf.ByteString; -import com.google.protobuf.DescriptorProtos.FieldDescriptorProto; -import com.google.protobuf.Descriptors.FieldDescriptor; -import java.math.BigInteger; -import org.jcodings.specific.ASCIIEncoding; -import org.jruby.*; -import org.jruby.exceptions.RaiseException; -import org.jruby.ext.bigdecimal.RubyBigDecimal; -import org.jruby.runtime.Block; -import org.jruby.runtime.Helpers; -import org.jruby.runtime.ThreadContext; -import org.jruby.runtime.builtin.IRubyObject; - -public class Utils { - public static FieldDescriptor.Type rubyToFieldType(IRubyObject typeClass) { - return FieldDescriptor.Type.valueOf(typeClass.asJavaString().toUpperCase()); - } - - public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptor.Type type) { - return fieldTypeToRuby(context, type.name()); - } - - public static IRubyObject fieldTypeToRuby(ThreadContext context, FieldDescriptorProto.Type type) { - return fieldTypeToRuby(context, type.name()); - } - - private static IRubyObject fieldTypeToRuby(ThreadContext context, String typeName) { - - return context.runtime.newSymbol(typeName.replace("TYPE_", "").toLowerCase()); - } - - public static IRubyObject checkType( - ThreadContext context, - FieldDescriptor.Type fieldType, - String fieldName, - IRubyObject value, - RubyModule typeClass) { - Ruby runtime = context.runtime; - - switch (fieldType) { - case SFIXED32: - case SFIXED64: - case FIXED64: - case SINT64: - case SINT32: - case FIXED32: - case INT32: - case INT64: - case UINT32: - case UINT64: - if (!isRubyNum(value)) - throw createExpectedTypeError(context, "number", "integral", fieldName, value); - - if (value instanceof RubyFloat) { - double doubleVal = RubyNumeric.num2dbl(value); - if (Math.floor(doubleVal) != doubleVal) { - throw runtime.newRangeError( - "Non-integral floating point value assigned to integer field '" - + fieldName - + "' (given " - + value.getMetaClass() - + ")."); - } - } - if (fieldType == FieldDescriptor.Type.UINT32 - || fieldType == FieldDescriptor.Type.UINT64 - || fieldType == FieldDescriptor.Type.FIXED32 - || fieldType == FieldDescriptor.Type.FIXED64) { - if (((RubyNumeric) value).isNegative()) { - throw runtime.newRangeError( - "Assigning negative value to unsigned integer field '" - + fieldName - + "' (given " - + value.getMetaClass() - + ")."); - } - } - - switch (fieldType) { - case INT32: - RubyNumeric.num2int(value); - break; - case UINT32: - case FIXED32: - num2uint(value); - break; - case UINT64: - case FIXED64: - num2ulong(context.runtime, value); - break; - default: - RubyNumeric.num2long(value); - break; - } - break; - case FLOAT: - if (!isRubyNum(value)) - throw createExpectedTypeError(context, "number", "float", fieldName, value); - break; - case DOUBLE: - if (!isRubyNum(value)) - throw createExpectedTypeError(context, "number", "double", fieldName, value); - break; - case BOOL: - if (!(value instanceof RubyBoolean)) - throw createInvalidTypeError(context, "boolean", fieldName, value); - break; - case BYTES: - value = validateAndEncodeString(context, "bytes", fieldName, value, "Encoding::ASCII_8BIT"); - break; - case STRING: - value = - validateAndEncodeString( - context, "string", fieldName, symToString(value), "Encoding::UTF_8"); - break; - case MESSAGE: - if (value.getMetaClass() != typeClass) { - // See if we can convert the value before flagging it as invalid - String className = typeClass.getName(); - - if (className.equals("Google::Protobuf::Timestamp") && value instanceof RubyTime) { - RubyTime rt = (RubyTime) value; - RubyHash timestampArgs = - Helpers.constructHash( - runtime, - runtime.newString("nanos"), - rt.nsec(), - false, - runtime.newString("seconds"), - rt.to_i(), - false); - return ((RubyClass) typeClass).newInstance(context, timestampArgs, Block.NULL_BLOCK); - - } else if (className.equals("Google::Protobuf::Duration") - && value instanceof RubyNumeric) { - IRubyObject seconds; - if (value instanceof RubyFloat) { - seconds = ((RubyFloat) value).truncate(context); - } else if (value instanceof RubyRational) { - seconds = ((RubyRational) value).to_i(context); - } else if (value instanceof RubyBigDecimal) { - seconds = ((RubyBigDecimal) value).to_int(context); - } else { - seconds = ((RubyInteger) value).to_i(); - } - - IRubyObject nanos = ((RubyNumeric) value).remainder(context, RubyFixnum.one(runtime)); - if (nanos instanceof RubyFloat) { - nanos = ((RubyFloat) nanos).op_mul(context, 1000000000); - } else if (nanos instanceof RubyRational) { - nanos = ((RubyRational) nanos).op_mul(context, runtime.newFixnum(1000000000)); - } else if (nanos instanceof RubyBigDecimal) { - nanos = ((RubyBigDecimal) nanos).op_mul(context, runtime.newFixnum(1000000000)); - } else { - nanos = ((RubyInteger) nanos).op_mul(context, 1000000000); - } - - RubyHash durationArgs = - Helpers.constructHash( - runtime, - runtime.newString("nanos"), - ((RubyNumeric) nanos).round(context), - false, - runtime.newString("seconds"), - seconds, - false); - return ((RubyClass) typeClass).newInstance(context, durationArgs, Block.NULL_BLOCK); - } - - // Not able to convert so flag as invalid - throw createTypeError( - context, - "Invalid type " - + value.getMetaClass() - + " to assign to submessage field '" - + fieldName - + "'."); - } - - break; - case ENUM: - boolean isValid = - ((RubyEnumDescriptor) typeClass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR)) - .isValidValue(context, value); - if (!isValid) { - throw runtime.newRangeError("Unknown symbol value for enum field '" + fieldName + "'."); - } - break; - default: - break; - } - return value; - } - - public static IRubyObject wrapPrimaryValue( - ThreadContext context, FieldDescriptor.Type fieldType, Object value) { - return wrapPrimaryValue(context, fieldType, value, false); - } - - public static IRubyObject wrapPrimaryValue( - ThreadContext context, FieldDescriptor.Type fieldType, Object value, boolean encodeBytes) { - Ruby runtime = context.runtime; - switch (fieldType) { - case INT32: - case SFIXED32: - case SINT32: - return runtime.newFixnum((Integer) value); - case SFIXED64: - case SINT64: - case INT64: - return runtime.newFixnum((Long) value); - case FIXED32: - case UINT32: - return runtime.newFixnum(((Integer) value) & (-1l >>> 32)); - case FIXED64: - case UINT64: - long ret = (Long) value; - return ret >= 0 - ? runtime.newFixnum(ret) - : RubyBignum.newBignum(runtime, UINT64_COMPLEMENTARY.add(new BigInteger(ret + ""))); - case FLOAT: - return runtime.newFloat((Float) value); - case DOUBLE: - return runtime.newFloat((Double) value); - case BOOL: - return (Boolean) value ? runtime.getTrue() : runtime.getFalse(); - case BYTES: - { - IRubyObject wrapped = - encodeBytes - ? RubyString.newString( - runtime, ((ByteString) value).toStringUtf8(), ASCIIEncoding.INSTANCE) - : RubyString.newString(runtime, ((ByteString) value).toByteArray()); - wrapped.setFrozen(true); - return wrapped; - } - case STRING: - { - IRubyObject wrapped = runtime.newString(value.toString()); - wrapped.setFrozen(true); - return wrapped; - } - default: - return runtime.getNil(); - } - } - - public static int num2uint(IRubyObject value) { - long longVal = RubyNumeric.num2long(value); - if (longVal > UINT_MAX) - throw value - .getRuntime() - .newRangeError("Integer " + longVal + " too big to convert to 'unsigned int'"); - long num = longVal; - if (num > Integer.MAX_VALUE || num < Integer.MIN_VALUE) - // encode to UINT32 - num = (-longVal ^ (-1l >>> 32)) + 1; - RubyNumeric.checkInt(value, num); - return (int) num; - } - - public static long num2ulong(Ruby runtime, IRubyObject value) { - if (value instanceof RubyFloat) { - RubyBignum bignum = RubyBignum.newBignum(runtime, ((RubyFloat) value).getDoubleValue()); - return RubyBignum.big2ulong(bignum); - } else if (value instanceof RubyBignum) { - return RubyBignum.big2ulong((RubyBignum) value); - } else { - return RubyNumeric.num2long(value); - } - } - - /* - * Helper to make it easier to support symbols being passed instead of strings - */ - public static IRubyObject symToString(IRubyObject sym) { - if (sym instanceof RubySymbol) { - return ((RubySymbol) sym).id2name(); - } - return sym; - } - - public static void checkNameAvailability(ThreadContext context, String name) { - if (context.runtime.getObject().getConstantAt(name) != null) - throw context.runtime.newNameError(name + " is already defined", name); - } - - public static boolean isMapEntry(FieldDescriptor fieldDescriptor) { - return fieldDescriptor.getType() == FieldDescriptor.Type.MESSAGE - && fieldDescriptor.isRepeated() - && fieldDescriptor.getMessageType().getOptions().getMapEntry(); - } - - public static RaiseException createTypeError(ThreadContext context, String message) { - if (cTypeError == null) { - cTypeError = (RubyClass) context.runtime.getClassFromPath("Google::Protobuf::TypeError"); - } - return RaiseException.from(context.runtime, cTypeError, message); - } - - public static RaiseException createExpectedTypeError( - ThreadContext context, String type, String fieldType, String fieldName, IRubyObject value) { - return createTypeError( - context, - String.format( - EXPECTED_TYPE_ERROR_FORMAT, type, fieldType, fieldName, value.getMetaClass())); - } - - public static RaiseException createInvalidTypeError( - ThreadContext context, String fieldType, String fieldName, IRubyObject value) { - return createTypeError( - context, - String.format(INVALID_TYPE_ERROR_FORMAT, fieldType, fieldName, value.getMetaClass())); - } - - protected static boolean isRubyNum(Object value) { - return value instanceof RubyFixnum || value instanceof RubyFloat || value instanceof RubyBignum; - } - - protected static void validateTypeClass( - ThreadContext context, FieldDescriptor.Type type, IRubyObject value) { - Ruby runtime = context.runtime; - if (!(value instanceof RubyModule)) { - throw runtime.newArgumentError("TypeClass has incorrect type"); - } - RubyModule klass = (RubyModule) value; - IRubyObject descriptor = klass.getInstanceVariable(DESCRIPTOR_INSTANCE_VAR); - if (descriptor.isNil()) { - throw runtime.newArgumentError( - "Type class has no descriptor. Please pass a " - + "class or enum as returned by the DescriptorPool."); - } - if (type == FieldDescriptor.Type.MESSAGE) { - if (!(descriptor instanceof RubyDescriptor)) { - throw runtime.newArgumentError("Descriptor has an incorrect type"); - } - } else if (type == FieldDescriptor.Type.ENUM) { - if (!(descriptor instanceof RubyEnumDescriptor)) { - throw runtime.newArgumentError("Descriptor has an incorrect type"); - } - } - } - - private static IRubyObject validateAndEncodeString( - ThreadContext context, - String fieldType, - String fieldName, - IRubyObject value, - String encoding) { - if (!(value instanceof RubyString)) - throw createInvalidTypeError(context, fieldType, fieldName, value); - - value = ((RubyString) value).encode(context, context.runtime.evalScriptlet(encoding)); - value.setFrozen(true); - return value; - } - - public static final String DESCRIPTOR_INSTANCE_VAR = "@descriptor"; - - public static final String EQUAL_SIGN = "="; - - private static final BigInteger UINT64_COMPLEMENTARY = - new BigInteger("18446744073709551616"); // Math.pow(2, 64) - - private static final String EXPECTED_TYPE_ERROR_FORMAT = - "Expected %s type for %s field '%s' (given %s)."; - private static final String INVALID_TYPE_ERROR_FORMAT = - "Invalid argument for %s field '%s' (given %s)."; - - private static final long UINT_MAX = 0xffffffffl; - - private static RubyClass cTypeError; -} diff --git a/ruby/src/main/java/google/ProtobufJavaService.java b/ruby/src/main/java/google/ProtobufJavaService.java deleted file mode 100644 index 00d60a1498ec..000000000000 --- a/ruby/src/main/java/google/ProtobufJavaService.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Protocol Buffers - Google's data interchange format - * Copyright 2014 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 google; - -import com.google.protobuf.jruby.*; -import java.io.IOException; -import org.jruby.Ruby; -import org.jruby.runtime.load.BasicLibraryService; - -public class ProtobufJavaService implements BasicLibraryService { - @Override - public boolean basicLoad(Ruby ruby) throws IOException { - ruby.defineModule("Google"); - - /* - * The order these happen in is important because we - * save a static reference to some classes and they - * need to exist before we try to save a reference to them - */ - RubyProtobuf.createProtobuf(ruby); - RubyFileDescriptor.createRubyFileDescriptor(ruby); - RubyEnumDescriptor.createRubyEnumDescriptor(ruby); - RubyRepeatedField.createRubyRepeatedField(ruby); - RubyFieldDescriptor.createRubyFieldDescriptor(ruby); - RubyMap.createRubyMap(ruby); - RubyOneofDescriptor.createRubyOneofDescriptor(ruby); - RubyDescriptor.createRubyDescriptor(ruby); - RubyDescriptorPool.createRubyDescriptorPool(ruby); - return true; - } -} diff --git a/ruby/src/main/sentinel.proto b/ruby/src/main/sentinel.proto deleted file mode 100644 index 722041ba0747..000000000000 --- a/ruby/src/main/sentinel.proto +++ /dev/null @@ -1,15 +0,0 @@ -syntax = "proto3"; -package com.google.protobuf.jruby; -option optimize_for = CODE_SIZE; - -message Sentinel { - int32 default_int32 = 1; - int64 default_int64 = 2; - uint32 default_unit32 = 3; - uint64 default_uint64 = 4; - string default_string = 5; - bool default_bool = 6; - float default_float = 7; - double default_double = 8; - bytes default_bytes = 9; -} diff --git a/ruby/travis-test.sh b/ruby/travis-test.sh index 6e9c6123802c..4269ca18ba03 100755 --- a/ruby/travis-test.sh +++ b/ruby/travis-test.sh @@ -7,26 +7,19 @@ test_version() { version=$1 bazel_args=" \ -k --test_output=streamed \ - --action_env=PATH \ - --action_env=GEM_PATH \ - --action_env=GEM_HOME \ --test_env=KOKORO_RUBY_VERSION=$version" if [[ $version == jruby-9* ]] ; then - bash --login -c \ - "rvm install $version && rvm use $version && rvm get head && \ - which ruby && \ - git clean -f && \ - gem install --no-document bundler && bundle && \ - bazel test //ruby/... $bazel_args --define=ruby_platform=java" + RUBY_PLATFORM=java else - bash --login -c \ - "rvm install $version && rvm use $version && \ - which ruby && \ - git clean -f && \ - gem install --no-document bundler -v 1.17.3 && bundle && \ - bazel test //ruby/... $bazel_args --define=ruby_platform=c" + RUBY_PLATFORM=c fi + bash --login -c \ + "rvm install $version && rvm use $version && \ + which ruby && \ + git clean -f && \ + gem install --no-document bundler && bundle && \ + bazel test //ruby/... $bazel_args --define=ruby_platform=$RUBY_PLATFORM" } test_version $1