Browse Source

Add frozen checks in Ruby (#5726)

* add frozen checks

* Use rb_check_frozen

* Correct assertion on frozen error message

The second argument for the method assert_raise is the message
to show when the assertion fails. It does not check the error
object's message.
Add an additional assertion that does check the error's message.

* do frozen check first
Joe Bolinger 6 years ago
parent
commit
76685c6fae

+ 6 - 0
ruby/ext/google/protobuf_c/map.c

@@ -386,6 +386,8 @@ VALUE Map_index(VALUE _self, VALUE key) {
  * was just inserted.
  */
 VALUE Map_index_set(VALUE _self, VALUE key, VALUE value) {
+  rb_check_frozen(_self);
+
   Map* self = ruby_to_Map(_self);
 
   char keybuf[TABLE_KEY_BUF_LENGTH];
@@ -438,6 +440,8 @@ VALUE Map_has_key(VALUE _self, VALUE key) {
  * nil if none was present. Throws an exception if the key is of the wrong type.
  */
 VALUE Map_delete(VALUE _self, VALUE key) {
+  rb_check_frozen(_self);
+
   Map* self = ruby_to_Map(_self);
 
   char keybuf[TABLE_KEY_BUF_LENGTH];
@@ -461,6 +465,8 @@ VALUE Map_delete(VALUE _self, VALUE key) {
  * Removes all entries from the map.
  */
 VALUE Map_clear(VALUE _self) {
+  rb_check_frozen(_self);
+
   Map* self = ruby_to_Map(_self);
 
   // Uninit and reinit the table -- this is faster than iterating and doing a

+ 1 - 0
ruby/ext/google/protobuf_c/message.c

@@ -242,6 +242,7 @@ VALUE Message_method_missing(int argc, VALUE* argv, VALUE _self) {
     if (argc != 2) {
       rb_raise(rb_eArgError, "Expected 2 arguments, received %d", argc);
     }
+    rb_check_frozen(_self);
   } else if (argc != 1) {
     rb_raise(rb_eArgError, "Expected 1 argument, received %d", argc);
   }

+ 17 - 0
ruby/tests/basic.rb

@@ -357,5 +357,22 @@ module BasicTest
       assert_equal nil, file_descriptor.name
       assert_equal :proto3, file_descriptor.syntax
     end
+
+    def test_map_freeze
+      m = proto_module::MapMessage.new
+      m.map_string_int32['a'] = 5
+      m.map_string_msg['b'] = proto_module::TestMessage2.new
+
+      m.map_string_int32.freeze
+      m.map_string_msg.freeze
+
+      assert m.map_string_int32.frozen?
+      assert m.map_string_msg.frozen?
+
+      assert_raise(FrozenError) { m.map_string_int32['foo'] = 1 }
+      assert_raise(FrozenError) { m.map_string_msg['bar'] = proto_module::TestMessage2.new }
+      assert_raise(FrozenError) { m.map_string_int32.delete('a') }
+      assert_raise(FrozenError) { m.map_string_int32.clear }
+    end
   end
 end

+ 33 - 0
ruby/tests/common_tests.rb

@@ -1269,6 +1269,39 @@ module CommonTests
     assert proto_module::TestMessage.new != nil
   end
 
+  def test_freeze
+    m = proto_module::TestMessage.new
+    m.optional_int32 = 10
+    m.freeze
+
+    frozen_error = assert_raise(FrozenError) { m.optional_int32 = 20 }
+    assert_equal "can't modify frozen #{proto_module}::TestMessage", frozen_error.message
+    assert_equal 10, m.optional_int32
+    assert_equal true, m.frozen?
+
+    assert_raise(FrozenError) { m.optional_int64 = 2 }
+    assert_raise(FrozenError) { m.optional_uint32 = 3 }
+    assert_raise(FrozenError) { m.optional_uint64 = 4 }
+    assert_raise(FrozenError) { m.optional_bool = true }
+    assert_raise(FrozenError) { m.optional_float = 6.0 }
+    assert_raise(FrozenError) { m.optional_double = 7.0 }
+    assert_raise(FrozenError) { m.optional_string = '8' }
+    assert_raise(FrozenError) { m.optional_bytes = nil }
+    assert_raise(FrozenError) { m.optional_msg = proto_module::TestMessage2.new }
+    assert_raise(FrozenError) { m.optional_enum = :A }
+    assert_raise(FrozenError) { m.repeated_int32 = 1 }
+    assert_raise(FrozenError) { m.repeated_int64 = 2 }
+    assert_raise(FrozenError) { m.repeated_uint32 = 3 }
+    assert_raise(FrozenError) { m.repeated_uint64 = 4 }
+    assert_raise(FrozenError) { m.repeated_bool = true }
+    assert_raise(FrozenError) { m.repeated_float = 6.0 }
+    assert_raise(FrozenError) { m.repeated_double = 7.0 }
+    assert_raise(FrozenError) { m.repeated_string = '8' }
+    assert_raise(FrozenError) { m.repeated_bytes = nil }
+    assert_raise(FrozenError) { m.repeated_msg = proto_module::TestMessage2.new }
+    assert_raise(FrozenError) { m.repeated_enum = :A }
+  end
+  
   def test_eq
     m1 = proto_module::TestMessage.new(:optional_string => 'foo', :repeated_string => ['bar1', 'bar2'])
     m2 = proto_module::TestMessage.new(:optional_string => 'foo', :repeated_string => ['bar1', 'bar2'])