Skip to content

Commit fe48893

Browse files
committed
Raise FrozenError when modifying a frozen digest
1 parent 27cc1d1 commit fe48893

3 files changed

Lines changed: 96 additions & 4 deletions

File tree

ext/digest/digest.c

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,15 @@
1414
************************************************/
1515

1616
#include "digest.h"
17+
#include <ruby/version.h>
1718

1819
static VALUE rb_mDigest;
1920
static VALUE rb_mDigest_Instance;
2021
static VALUE rb_cDigest_Class;
2122
static VALUE rb_cDigest_Base;
2223

2324
static ID id_reset, id_update, id_finish, id_digest, id_hexdigest, id_digest_length;
25+
static ID id_clone, id_freeze;
2426
static ID id_metadata;
2527

2628
RUBY_EXTERN void Init_digest_base(void);
@@ -217,12 +219,23 @@ rb_digest_instance_reset(VALUE self)
217219
* digest_obj.new -> another_digest_obj
218220
*
219221
* Returns a new, initialized copy of the digest object. Equivalent
220-
* to digest_obj.clone().reset().
222+
* to digest_obj.clone(freeze: false).reset().
221223
*/
222224
static VALUE
223225
rb_digest_instance_new(VALUE self)
224226
{
225-
VALUE clone = rb_obj_clone(self);
227+
VALUE clone;
228+
229+
#if RUBY_API_VERSION_MAJOR >= 3
230+
/* dup() would drop the singleton class, and the copy must not stay frozen */
231+
VALUE kwargs = rb_hash_new();
232+
rb_hash_aset(kwargs, ID2SYM(id_freeze), Qfalse);
233+
clone = rb_funcallv_kw(self, id_clone, 1, &kwargs, RB_PASS_KEYWORDS);
234+
#else
235+
/* clone(freeze:) needs Ruby 3.0; dup() loses the singleton class but resets */
236+
clone = OBJ_FROZEN(self) ? rb_obj_dup(self) : rb_obj_clone(self);
237+
#endif
238+
226239
rb_funcall(clone, id_reset, 0);
227240
return clone;
228241
}
@@ -266,7 +279,11 @@ rb_digest_instance_digest(int argc, VALUE *argv, VALUE self)
266279
static VALUE
267280
rb_digest_instance_digest_bang(VALUE self)
268281
{
269-
VALUE value = rb_funcall(self, id_finish, 0);
282+
VALUE value;
283+
284+
rb_check_frozen(self);
285+
286+
value = rb_funcall(self, id_finish, 0);
270287
rb_funcall(self, id_reset, 0);
271288

272289
return value;
@@ -311,7 +328,11 @@ rb_digest_instance_hexdigest(int argc, VALUE *argv, VALUE self)
311328
static VALUE
312329
rb_digest_instance_hexdigest_bang(VALUE self)
313330
{
314-
VALUE value = rb_funcall(self, id_finish, 0);
331+
VALUE value;
332+
333+
rb_check_frozen(self);
334+
335+
value = rb_funcall(self, id_finish, 0);
315336
rb_funcall(self, id_reset, 0);
316337

317338
return hexencode_str_new(value);
@@ -683,6 +704,8 @@ rb_digest_base_reset(VALUE self)
683704
rb_digest_metadata_t *algo;
684705
void *pctx;
685706

707+
rb_check_frozen(self);
708+
686709
algo = get_digest_obj_metadata(self);
687710

688711
TypedData_Get_Struct(self, void, &digest_type, pctx);
@@ -705,6 +728,8 @@ rb_digest_base_update(VALUE self, VALUE str)
705728
rb_digest_metadata_t *algo;
706729
void *pctx;
707730

731+
rb_check_frozen(self);
732+
708733
algo = get_digest_obj_metadata(self);
709734

710735
TypedData_Get_Struct(self, void, &digest_type, pctx);
@@ -732,6 +757,7 @@ rb_digest_base_finish(VALUE self)
732757
algo->finish_func(pctx, (unsigned char *)RSTRING_PTR(str));
733758

734759
/* avoid potential coredump caused by use of a finished context */
760+
/* not frozen-checked: #digest and #hexdigest call this on a clone */
735761
algo_init(algo, pctx);
736762

737763
return str;
@@ -777,6 +803,8 @@ Init_digest(void)
777803
id_digest = rb_intern("digest");
778804
id_hexdigest = rb_intern("hexdigest");
779805
id_digest_length = rb_intern("digest_length");
806+
id_clone = rb_intern("clone");
807+
id_freeze = rb_intern("freeze");
780808
id_metadata = rb_id_metadata();
781809
InitVM(digest);
782810
}

lib/digest/sha2.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ def initialize(bitlen = 256)
7777
#
7878
# Reset the digest to the initial state and return self.
7979
def reset
80+
raise FrozenError, "can't modify frozen #{self.class}" if frozen?
8081
@sha2.reset
8182
self
8283
end
@@ -87,6 +88,7 @@ def reset
8788
#
8889
# Update the digest using a given _string_ and return self.
8990
def update(str)
91+
raise FrozenError, "can't modify frozen #{self.class}" if frozen?
9092
@sha2.update(str)
9193
self
9294
end

test/digest/test_digest.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,42 @@ def test_eq
8181
assert_equal(md1, md2, self.class::ALGO)
8282
end
8383

84+
def test_frozen
85+
md = self.class::ALGO.new
86+
md << "a"
87+
md.freeze
88+
89+
assert_raise(FrozenError) { md.update("b") }
90+
assert_raise(FrozenError) { md << "b" }
91+
assert_raise(FrozenError) { md.reset }
92+
assert_raise(FrozenError) { md.digest! }
93+
assert_raise(FrozenError) { md.hexdigest! }
94+
assert_raise(FrozenError) { md.base64digest! }
95+
assert_raise(FrozenError) { md.digest("b") }
96+
assert_raise(FrozenError) { md.hexdigest("b") }
97+
98+
assert_equal(self.class::ALGO.hexdigest("a"), md.hexdigest)
99+
end
100+
101+
def test_new_keeps_singleton
102+
md = self.class::ALGO.new
103+
md.extend(Module.new { def extended_marker; end })
104+
def md.singleton_marker; end
105+
106+
assert_respond_to(md.new, :extended_marker)
107+
assert_respond_to(md.new, :singleton_marker)
108+
end
109+
110+
def test_frozen_copy
111+
md = self.class::ALGO.new
112+
md << "a"
113+
md.freeze
114+
115+
assert_equal(self.class::ALGO.hexdigest("a"), md.clone.hexdigest)
116+
assert_equal(self.class::ALGO.hexdigest("a"), md.dup.hexdigest)
117+
assert_equal(self.class::ALGO.hexdigest(""), md.new.hexdigest)
118+
end
119+
84120
def test_s_file
85121
Tempfile.create("test_digest_file", mode: File::BINARY) { |tmpfile|
86122
str = "hello, world.\r\n"
@@ -174,6 +210,32 @@ class TestSHA512 < Test::Unit::TestCase
174210

175211
class TestSHA2 < Test::Unit::TestCase
176212

213+
def test_frozen
214+
md = Digest::SHA2.new
215+
md << "a"
216+
md.freeze
217+
218+
assert_raise(FrozenError) { md.update("b") }
219+
assert_raise(FrozenError) { md.reset }
220+
assert_raise(FrozenError) { md.digest! }
221+
assert_raise(FrozenError) { md.hexdigest! }
222+
assert_raise(FrozenError) { md.base64digest! }
223+
assert_raise(FrozenError) { md.digest("b") }
224+
assert_raise(FrozenError) { md.hexdigest("b") }
225+
226+
assert_equal(Digest::SHA256.hexdigest("a"), md.hexdigest)
227+
end
228+
229+
def test_frozen_copy
230+
md = Digest::SHA2.new
231+
md << "a"
232+
md.freeze
233+
234+
assert_equal(Digest::SHA256.hexdigest("a"), md.clone.hexdigest)
235+
assert_equal(Digest::SHA256.hexdigest("a"), md.dup.hexdigest)
236+
assert_equal(Digest::SHA256.hexdigest(""), md.new.hexdigest)
237+
end
238+
177239
def test_s_file
178240
Tempfile.create("test_digest_file") { |tmpfile|
179241
str = Data1

0 commit comments

Comments
 (0)