protocolbuffers / protocolbuffers/protobuf

[Ruby] Cannot read message-typed custom options from a rebuilt DescriptorPool (FieldDescriptor#get raises TypeError)

Open
#28,390 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C++
Stars
72k
Forks
16.3k
Avg merge
1d 17h
Merged PRs (30d)
140

Description

What

Reading a message-typed custom option via FieldDescriptor#get raises TypeError when the extension is resolved from a DescriptorPool that was built from serialized FileDescriptorProtos (i.e. not the process's generated pool). Scalar custom options read fine in the same setup, and message-typed options read fine from DescriptorPool.generated_pool.

This is exactly the situation a protoc plugin is in: it receives already-compiled descriptors in a CodeGeneratorRequest and must rebuild a pool from them to resolve custom-option extensions.

message_opt => raises TypeError: wrong argument type nil (expected Google::Protobuf::Descriptor)

Repro

repro.proto:

syntax = "proto3";
package repro;
import "google/protobuf/descriptor.proto";

message Meta { string owner = 1; }

extend google.protobuf.MessageOptions {
  string scalar_opt = 50001;
  Meta   message_opt = 50002;
}

message Subject {
  option (repro.scalar_opt) = "hello";
  option (repro.message_opt) = { owner: "team" };
}

repro.rb:

require "google/protobuf"
require "google/protobuf/descriptor_pb"

# repro.fds stands in for a CodeGeneratorRequest's proto_file: repro.proto plus
# its dependency, google/protobuf/descriptor.proto.
#   protoc --include_imports --descriptor_set_out=repro.fds repro.proto
fds = Google::Protobuf::FileDescriptorSet.decode(File.read("repro.fds"))

# Rebuild a pool from the received files so the option extensions resolve.
pool = Google::Protobuf::DescriptorPool.new
fds.file.each { |f| pool.add_serialized_file(f.to_proto) }

subject = fds.file.flat_map { |f| f.message_type.to_a }.find { |m| m.name == "Subject" }
options = pool.lookup("google.protobuf.MessageOptions").msgclass.decode(subject.options.to_proto)

scalar_ext  = pool.lookup("repro.scalar_opt")
message_ext = pool.lookup("repro.message_opt")

puts "scalar_opt  present? #{scalar_ext.has?(options)}"   # true
puts "message_opt present? #{message_ext.has?(options)}"  # true

puts "scalar_opt  => #{scalar_ext.get(options).inspect}"  # "hello"
puts "message_opt => #{message_ext.get(options).inspect}" # raises TypeError

Actual

scalar_opt  present? true
message_opt present? true
scalar_opt  => "hello"
message_opt => (raises) TypeError: wrong argument type nil (expected Google::Protobuf::Descriptor)

The option is present (has? is true) and round-trips through serialization, but get cannot materialize its value.

Expected

message_ext.get(options) returns the Meta message { owner: "team" }, the way scalar_ext.get(options) returns "hello".

Key diagnostic

The failure is specific to reading from a rebuilt pool. If the same extension is registered directly in DescriptorPool.generated_pool (e.g. via generated Ruby code, or by adding the file to the generated pool), get returns the message value correctly. It only fails when the extension is resolved from a separately-built DescriptorPool — which is the only option available to a protoc plugin, since the descriptors in a CodeGeneratorRequest are not in the generated pool.

Relationship to earlier issues

This is a gap left by two previously-closed issues:

  • #1198 ("No way to use custom options in Ruby", closed as completed) added the ability to read custom options via DescriptorPool.generated_pool.lookup(ext_name).get(options).
  • #16834 ("[Ruby] unable to read custom options from descriptor", closed as completed) documented that same ext.get(options) pattern, and a follow-up there gave the guidance for plugins: build your own DescriptorPool from the request's files and read through it.

That guidance works for scalar and repeated-scalar options, so those issues correctly resolved the common case. It does not work for message-typed options: get raises TypeError for them specifically when read from a rebuilt pool, so a plugin still cannot read message-typed custom options (a common shape — e.g. protovalidate rules, google.api.http). The earlier issues did not cover this case.

Environment

  • google-protobuf 4.35.1 (also reproduces on 4.28.0)
  • libprotoc 33.0
  • Ruby 3.4.1
  • macOS (arm64-darwin)

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with repro.rb and the rebuilt DescriptorPool path using add_serialized_file, then inspect DescriptorPool#lookup and the extension get call for message-typed options. Reproduce the TypeError with the provided repro.proto and verify that message_ext.get(options) returns the Meta message while scalar options continue to work.

Written by the indexing model from the issue text.

Assessment

Tech stack
ruby
Domain
backend-api-design, devtools
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
58/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.