Bug #21151
openIO and StringIO raise FrozenError even for read-only methods
Description
Problem¶
While fixing a small regression from my recent StringIO fixes I discovered that a large number of read-only methods will fail if the StringIO is frozen. A few examples:
$ ruby -rstringio -e 's = StringIO.new; p s.lineno; s.freeze; p s.lineno'
0
-e:1:in 'StringIO#lineno': can't modify frozen StringIO: #<StringIO:0x0000000103711ea8> (FrozenError)
from -e:1:in '<main>'
$ ruby -rstringio -e 's = StringIO.new; p s.closed?; s.freeze; p s.closed?'
false
-e:1:in 'StringIO#closed?': can't modify frozen StringIO: #<StringIO:0x00000001008c1e68> (FrozenError)
from -e:1:in '<main>'
$ ruby -rstringio -e 's = StringIO.new; p s.eof?; s.freeze; p s.eof?'
true
-e:1:in 'StringIO#eof?': can't modify frozen StringIO: #<StringIO:0x000000011c171e80> (FrozenError)
from -e:1:in '<main>'
$ ruby -rstringio -e 's = StringIO.new; p s.pos; s.freeze; p s.pos'
0
-e:1:in 'StringIO#pos': can't modify frozen StringIO: #<StringIO:0x0000000105551df0> (FrozenError)
from -e:1:in '<main>'
@kou (Kouhei Sutou) pointed out that IO also raises for at least lineno
and suggested I re-open the issue here. I still believe these read-only operations should be allowed on frozen IO or StringIO.
Cause in StringIO¶
This is because the StringIO
macro calls get_strio
which calls rb_io_taint_check
which calls rb_check_frozen
. The StringIO
macro is used in almost every method to access the rb_stringio_t data structure.
A list of methods I believe should be operable when the StringIO is frozen (in stringio.c definition order):
- string (returns underlying String but does not mutate anything)
- lineno
- pos
- closed?/closed_read?/closed_write?
- eof/eof?
- sync
- pid (a dummy method but it writes nothing)
- fileno (dummy)
- pread (by definition does not modify state)
- isatty/tty?
- size/length
- external_encoding
- internal_encoding
In addition, initialize_copy
probably should not require the original StringIO to be writable:
$ ruby -rstringio -e 's = StringIO.new("foo"); s.freeze; p s.dup'
-e:1:in 'StringIO#initialize_copy': can't modify frozen StringIO: #<StringIO:0x0000000102eb1df8> (FrozenError)
from -e:1:in 'Kernel#initialize_dup'
from -e:1:in 'Kernel#dup'
from -e:1:in '<main>'
The data from the original StringIO is unmodified by initialize_copy
, other than the reference-counting ptr->count
(which should not be subject to frozen checks).
Cause in IO¶
The GetOpenFile
macro calls RB_IO_POINTER
macro which calls rb_io_taint_check
which calls rb_check_frozen
.
Methods in IO that should work on a frozen IO include those from the StringIO list above that currently raise. For example lineno
uses GetOpenFile
and raises, but fileno
does not and does not raise.
There's clearly some inconsistency here we can clean up.
Origin¶
I believe most of the StringIO cases are caused by this change from 2011 that added frozen checks to StringIO (the class and the macro): https://github.com/ruby/ruby/commit/d8d9bac5c8b071135e50ad3f21c8a9b6a9c06e54
In IO, this behavior dates way back to 2000 by @matz (Yukihiro Matsumoto) himself: https://github.com/ruby/ruby/commit/087c83d7ceed6893afff93066937fb570ae4a115
Notes¶
Originally filed as https://github.com/ruby/stringio/issues/120.
I have started to fix the StringIO cases for JRuby in https://github.com/ruby/stringio/pull/122. I could probably fix the C version of StringIO as well, but I'm a little more unsure about how to fix IO.