Skip to content

Commit abb197c

Browse files
zhangskzcopybara-github
authored andcommitted
Raise ParseError for non-numeric strings in numeric fields in Ruby and PHP JSON parsing.
This fixes Ruby and PHP to be conformant with the Protobuf's JSON spec. Note this fix is not accompanied by a major version bump for Ruby or PHP, but was pre-announced in https://engdoc.corp.google.com/eng/doc/devguide/proto/news/2024-11-07.md#ruby-and-php-errors-in-json-parsing and landed as a warning in 29.x. PiperOrigin-RevId: 704855202
1 parent 828716e commit abb197c

File tree

10 files changed

+6
-68
lines changed

10 files changed

+6
-68
lines changed
-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
Required.*.JsonInput.DoubleFieldEmptyString # Should have failed to parse, but didn't.
2-
Required.*.JsonInput.FloatFieldEmptyString # Should have failed to parse, but didn't.
3-
Required.*.JsonInput.Int32FieldEmptyString # Should have failed to parse, but didn't.
4-
Required.*.JsonInput.Int64FieldEmptyString # Should have failed to parse, but didn't.
5-
Required.*.JsonInput.Uint32FieldEmptyString # Should have failed to parse, but didn't.
6-
Required.*.JsonInput.Uint64FieldEmptyString # Should have failed to parse, but didn't.
7-
Required.*.JsonInput.DoubleFieldStringValueNonNumeric # Should have failed to parse, but didn't.
8-
Required.*.JsonInput.DoubleFieldStringValuePartiallyNumeric # Should have failed to parse, but didn't.
9-
Required.*.JsonInput.FloatFieldStringValueNonNumeric # Should have failed to parse, but didn't.
10-
Required.*.JsonInput.FloatFieldStringValuePartiallyNumeric # Should have failed to parse, but didn't.
111
Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output.

conformance/failure_list_php_c.txt

-10
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,4 @@
11
Recommended.Proto2.JsonInput.FieldNameExtension.Validator
2-
Required.*.JsonInput.DoubleFieldEmptyString # Should have failed to parse, but didn't.
3-
Required.*.JsonInput.FloatFieldEmptyString # Should have failed to parse, but didn't.
4-
Required.*.JsonInput.Int32FieldEmptyString # Should have failed to parse, but didn't.
5-
Required.*.JsonInput.Int64FieldEmptyString # Should have failed to parse, but didn't.
6-
Required.*.JsonInput.Uint32FieldEmptyString # Should have failed to parse, but didn't.
7-
Required.*.JsonInput.Uint64FieldEmptyString # Should have failed to parse, but didn't.
8-
Required.*.JsonInput.DoubleFieldStringValueNonNumeric # Should have failed to parse, but didn't.
9-
Required.*.JsonInput.DoubleFieldStringValuePartiallyNumeric # Should have failed to parse, but didn't.
10-
Required.*.JsonInput.FloatFieldStringValueNonNumeric # Should have failed to parse, but didn't.
11-
Required.*.JsonInput.FloatFieldStringValuePartiallyNumeric # Should have failed to parse, but didn't.
122
Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output.
133
Required.Proto2.JsonInput.BoolFieldFalse.JsonOutput
144
Required.Proto2.JsonInput.BoolFieldFalse.ProtobufOutput

conformance/failure_list_ruby.txt

-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
Required.*.JsonInput.DoubleFieldEmptyString # Should have failed to parse, but didn't.
2-
Required.*.JsonInput.FloatFieldEmptyString # Should have failed to parse, but didn't.
3-
Required.*.JsonInput.Int32FieldEmptyString # Should have failed to parse, but didn't.
4-
Required.*.JsonInput.Int64FieldEmptyString # Should have failed to parse, but didn't.
5-
Required.*.JsonInput.Uint32FieldEmptyString # Should have failed to parse, but didn't.
6-
Required.*.JsonInput.Uint64FieldEmptyString # Should have failed to parse, but didn't.
7-
Required.*.JsonInput.DoubleFieldStringValueNonNumeric # Should have failed to parse, but didn't.
8-
Required.*.JsonInput.DoubleFieldStringValuePartiallyNumeric # Should have failed to parse, but didn't.
9-
Required.*.JsonInput.FloatFieldStringValueNonNumeric # Should have failed to parse, but didn't.
10-
Required.*.JsonInput.FloatFieldStringValuePartiallyNumeric # Should have failed to parse, but didn't.
111
Required.*.JsonInput.Int32FieldQuotedExponentialValue.* # Failed to parse input or produce output.

php/ext/google/protobuf/message.c

-3
Original file line numberDiff line numberDiff line change
@@ -735,9 +735,6 @@ PHP_METHOD(Message, mergeFromJsonString) {
735735
switch (result) {
736736
case kUpb_JsonDecodeResult_Ok:
737737
break;
738-
case kUpb_JsonDecodeResult_OkWithEmptyStringNumerics:
739-
zend_error(E_USER_WARNING, "%s", upb_Status_ErrorMessage(&status));
740-
return;
741738
case kUpb_JsonDecodeResult_Error:
742739
zend_throw_exception_ex(NULL, 0, "Error occurred during parsing: %s",
743740
upb_Status_ErrorMessage(&status));

ruby/ext/google/protobuf_c/message.c

-3
Original file line numberDiff line numberDiff line change
@@ -1046,9 +1046,6 @@ static VALUE Message_decode_json(int argc, VALUE* argv, VALUE klass) {
10461046
switch (result) {
10471047
case kUpb_JsonDecodeResult_Ok:
10481048
break;
1049-
case kUpb_JsonDecodeResult_OkWithEmptyStringNumerics:
1050-
rb_warn("%s", upb_Status_ErrorMessage(&status));
1051-
break;
10521049
case kUpb_JsonDecodeResult_Error:
10531050
rb_raise(cParseError, "Error occurred during parsing: %s",
10541051
upb_Status_ErrorMessage(&status));

ruby/lib/google/protobuf/ffi/ffi.rb

-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ class FFI
3838

3939
## JSON Decoding results
4040
Upb_JsonDecodeResult_Ok = 0
41-
Upb_JsonDecodeResult_OkWithEmptyStringNumerics = 1
4241
Upb_JsonDecodeResult_Error = 2
4342

4443
typedef :pointer, :Array

ruby/lib/google/protobuf/ffi/message.rb

-2
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,6 @@ def self.decode_json(data, options = {})
272272
status = Google::Protobuf::FFI::Status.new
273273
result = Google::Protobuf::FFI.json_decode_message_detecting_nonconformance(data, data.bytesize, message.instance_variable_get(:@msg), message.class.descriptor, pool_def, decoding_options, message.instance_variable_get(:@arena), status)
274274
case result
275-
when Google::Protobuf::FFI::Upb_JsonDecodeResult_OkWithEmptyStringNumerics
276-
warn Google::Protobuf::FFI.error_message(status)
277275
when Google::Protobuf::FFI::Upb_JsonDecodeResult_Error
278276
raise ParseError.new "Error occurred during parsing: #{Google::Protobuf::FFI.error_message(status)}"
279277
end

ruby/tests/encode_decode_test.rb

+4-26
Original file line numberDiff line numberDiff line change
@@ -38,36 +38,14 @@ def hex2bin(s)
3838

3939
class NonConformantNumericsTest < Test::Unit::TestCase
4040
def test_empty_json_numerics
41-
if defined? JRUBY_VERSION and Google::Protobuf::IMPLEMENTATION != :FFI
42-
# In a future version, CRuby and JRuby FFI will also have this behavior.
43-
assert_raises Google::Protobuf::ParseError do
44-
msg = ::BasicTestProto2::TestMessage.decode_json('{"optionalInt32":""}')
45-
end
46-
else
47-
warnings = CaptureWarnings.capture {
48-
msg = ::BasicTestProto2::TestMessage.decode_json('{"optionalInt32":""}')
49-
assert_equal 0, msg.optional_int32
50-
assert msg.has_optional_int32?
51-
}
52-
assert_equal 1, warnings.size
53-
assert_match "Empty string is not a valid number (field: basic_test_proto2.TestMessage.optional_int32)", warnings[0]
41+
assert_raises Google::Protobuf::ParseError do
42+
msg = ::BasicTestProto2::TestMessage.decode_json('{"optionalInt32":""}')
5443
end
5544
end
5645

5746
def test_trailing_non_numeric_characters
58-
if defined? JRUBY_VERSION and Google::Protobuf::IMPLEMENTATION != :FFI
59-
# In a future version, CRuby and JRuby FFI will also have this behavior.
60-
assert_raises Google::Protobuf::ParseError do
61-
msg = ::BasicTestProto2::TestMessage.decode_json('{"optionalDouble":"123abc"}')
62-
end
63-
else
64-
warnings = CaptureWarnings.capture {
65-
msg = ::BasicTestProto2::TestMessage.decode_json('{"optionalDouble":"123abc"}')
66-
assert_equal 123, msg.optional_double
67-
assert msg.has_optional_double?
68-
}
69-
assert_equal 1, warnings.size
70-
assert_match "Non-number characters in quoted number (field: basic_test_proto2.TestMessage.optional_double)", warnings[0]
47+
assert_raises Google::Protobuf::ParseError do
48+
msg = ::BasicTestProto2::TestMessage.decode_json('{"optionalDouble":"123abc"}')
7149
end
7250
end
7351
end

upb/json/decode.c

+2-2
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,7 @@ static int64_t jsondec_strtoint64(jsondec* d, upb_StringView str) {
674674
static void jsondec_checkempty(jsondec* d, upb_StringView str,
675675
const upb_FieldDef* f) {
676676
if (str.size != 0) return;
677-
d->result = kUpb_JsonDecodeResult_OkWithEmptyStringNumerics;
677+
d->result = kUpb_JsonDecodeResult_Error;
678678
upb_Status_SetErrorFormat(d->status,
679679
"Empty string is not a valid number (field: %s). "
680680
"This will be an error in a future version.",
@@ -782,7 +782,7 @@ static upb_MessageValue jsondec_double(jsondec* d, const upb_FieldDef* f) {
782782
char* end;
783783
val.double_val = strtod(str.data, &end);
784784
if (end != str.data + str.size) {
785-
d->result = kUpb_JsonDecodeResult_OkWithEmptyStringNumerics;
785+
d->result = kUpb_JsonDecodeResult_Error;
786786
upb_Status_SetErrorFormat(
787787
d->status,
788788
"Non-number characters in quoted number (field: %s). "

upb/json/decode.h

-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ enum { upb_JsonDecode_IgnoreUnknown = 1 };
2626

2727
enum {
2828
kUpb_JsonDecodeResult_Ok = 0,
29-
kUpb_JsonDecodeResult_OkWithEmptyStringNumerics = 1,
3029
kUpb_JsonDecodeResult_Error = 2,
3130
};
3231

0 commit comments

Comments
 (0)