From 4685338c2648b69283f9a2437555a33bc794b8f8 Mon Sep 17 00:00:00 2001 From: Protobuf Team Bot Date: Fri, 21 Feb 2025 09:42:57 -0800 Subject: [PATCH] Fixes #18726 by backslash escaping descriptor data containing `#` if the hashmark appears immediately before any of `$`, `{`, or `@`. PiperOrigin-RevId: 729560636 --- .../protobuf/compiler/ruby/ruby_generator.cc | 28 +++++++++++++++++-- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/google/protobuf/compiler/ruby/ruby_generator.cc b/src/google/protobuf/compiler/ruby/ruby_generator.cc index 61ac82bf53d14..2568140f7afb3 100644 --- a/src/google/protobuf/compiler/ruby/ruby_generator.cc +++ b/src/google/protobuf/compiler/ruby/ruby_generator.cc @@ -250,6 +250,29 @@ std::string DumpImportList(const FileDescriptor* file) { return ret; } +// Escape a string for use in a Ruby string literal. This is a superset of +// absl::CEscape() that also includes handling of the following characters: +// - # (hashmark) +// +// This is needed because Ruby double-quoted string literals interpolate the +// contents of the string, and the hashmark character is used in the +// interpolation syntax. Informed by MRI Ruby's implementation of String#dump. +std::string RubyEscape(absl::string_view s) { + std::string c_escaped = absl::CHexEscape(s); + std::string result; + result.reserve(c_escaped.length()); + for (size_t i = 0; i < c_escaped.length(); ++i) { + if (c_escaped[i] == '#' && + (i + 1 < c_escaped.length() && + (c_escaped[i + 1] == '{' || c_escaped[i + 1] == '$' || + c_escaped[i + 1] == '@'))) { + result += '\\'; + } + result += c_escaped[i]; + } + return result; +} + void GenerateBinaryDescriptor(const FileDescriptor* file, io::Printer* printer, std::string* error) { printer->Print(R"( @@ -259,9 +282,8 @@ pool = Google::Protobuf::DescriptorPool.generated_pool pool.add_serialized_file(descriptor_data) )", - "descriptor_data", - absl::CHexEscape(SerializedDescriptor(file)), "imports", - DumpImportList(file)); + "descriptor_data", RubyEscape(SerializedDescriptor(file)), + "imports", DumpImportList(file)); } bool GenerateFile(const FileDescriptor* file, io::Printer* printer,