[BUG] Small bugs with frozen things

Moin!

Some oddities I noticed:

irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
         from (irb):34:in `inspect'
# This one also happens for File#path etc.

irb(main):063:0> Dir.new(".").freeze.send(:initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen

irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
         from (irb):97:in `to_s'

Regards,
Florian Gross

"Florian Gross" <flgr@ccan.de> schrieb im Newsbeitrag
news:2j7p3jFt323lU1@uni-berlin.de...

Moin!

Moin moin!

Some oddities I noticed:

irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
         from (irb):34:in `inspect'
# This one also happens for File#path etc.

IMHO an IO instance can't really be frozen in a meaningful way because it
must maintain changing state (namely the stream position). Maybe
IO#freeze should throw an exception ("illegal operation", "cannot freeze"
or similar).

irb(main):063:0> Dir.new(".").freeze.send(:initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen

I would not regard this a bug since initialize is expected to change the
instance, isn't it?

irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
         from (irb):97:in `to_s'

That's really surprising. Maybe it's due to lazy initialization of the
class's name.

Btw, do people frequently use #freeze?

Kind regards

    robert

Moin,

At Tue, 15 Jun 2004 17:08:36 +0900,
Florian Gross wrote in [ruby-talk:103646]:

irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
         from (irb):34:in `inspect'
# This one also happens for File#path etc.

IO#inspect also fails in secure mode (when $SAFE >= 4). I'm
not sure whether it should be possible or not.

irb(main):063:0> Dir.new(".").freeze.send(:initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen

This is simple to fix, adding rb_check_frozen() to every
initialize methods... but tiresome.

irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
         from (irb):97:in `to_s'

Seems wrong.

Index: variable.c

···

===================================================================
RCS file: /var/cvs/src/ruby/variable.c,v
retrieving revision 1.113
diff -u -2 -p -r1.113 variable.c
--- variable.c 14 May 2004 16:39:15 -0000 1.113
+++ variable.c 16 Jun 2004 13:35:57 -0000
@@ -23,4 +23,7 @@ st_table *rb_class_tbl;
static ID autoload, classpath, tmp_classpath;

+#define ENSURE_IV_TBL(tbl) \
+ ((tbl) ? (tbl) : ((tbl) = st_init_numtable(), (st_table *)0))
+
void
Init_var_tables()
@@ -131,7 +134,5 @@ find_class_path(klass)
     }
     if (arg.path) {
- if (!ROBJECT(klass)->iv_tbl) {
- ROBJECT(klass)->iv_tbl = st_init_numtable();
- }
+ ENSURE_IV_TBL(ROBJECT(klass)->iv_tbl);
   st_insert(ROBJECT(klass)->iv_tbl, classpath, arg.path);
   st_delete(RCLASS(klass)->iv_tbl, &tmp_classpath, 0);
@@ -209,5 +210,6 @@ rb_class_path(klass)
   sprintf(RSTRING(path)->ptr, "#<%s:0x%lx>", s, klass);
   RSTRING(path)->len = strlen(RSTRING(path)->ptr);
- rb_ivar_set(klass, tmp_classpath, path);
+ ENSURE_IV_TBL(ROBJECT(klass)->iv_tbl);
+ st_insert(ROBJECT(klass)->iv_tbl, tmp_classpath, path);

   return path;
@@ -873,7 +875,5 @@ generic_ivar_set(obj, id, val)
   special_generic_ivar = 1;
     }
- if (!generic_iv_tbl) {
- generic_iv_tbl = st_init_numtable();
- }
+ ENSURE_IV_TBL(generic_iv_tbl);

     if (!st_lookup(generic_iv_tbl, obj, (st_data_t *)&tbl)) {
@@ -1050,5 +1050,5 @@ rb_ivar_set(obj, id, val)
       case T_CLASS:
       case T_MODULE:
- if (!ROBJECT(obj)->iv_tbl) ROBJECT(obj)->iv_tbl = st_init_numtable();
+ ENSURE_IV_TBL(ROBJECT(obj)->iv_tbl);
   st_insert(ROBJECT(obj)->iv_tbl, id, val);
   break;
@@ -1510,7 +1510,5 @@ rb_mod_const_at(mod, data)
{
     st_table *tbl = data;
- if (!tbl) {
- tbl = st_init_numtable();
- }
+ ENSURE_IV_TBL(tbl);
     if (RCLASS(mod)->iv_tbl) {
   st_foreach(RCLASS(mod)->iv_tbl, sv_i, (st_data_t)tbl);
@@ -1645,8 +1643,5 @@ mod_av_set(klass, id, val, isconst)
   }
     }
- if (!RCLASS(klass)->iv_tbl) {
- RCLASS(klass)->iv_tbl = st_init_numtable();
- }
- else if (isconst) {
+ if (ENSURE_IV_TBL(RCLASS(klass)->iv_tbl) && isconst) {
   VALUE value = Qfalse;

--
Nobu Nakada

Robert Klemme wrote:

Moin moin!

Moin moin moin!

irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
TypeError: can't modify frozen File # inspect doesn't modify
        from (irb):34:in `inspect'
# This one also happens for File#path etc.

IMHO an IO instance can't really be frozen in a meaningful way because it
must maintain changing state (namely the stream position). Maybe
IO#freeze should throw an exception ("illegal operation", "cannot freeze"
or similar).

Yup, and there definitely shouldn't be errors when trying to access the path of a frozen File etc. :slight_smile:

(But IMHO an IO instance can also be frozen -- the internal state doesn't have to be frozen just because the Object is.)

irb(main):063:0> Dir.new(".").freeze.send(:initialize, "C:/").read
=> "katapult.zip" # Initialize executed despite Dir being frozen

I would not regard this a bug since initialize is expected to change the
instance, isn't it?

Well, [1, 2, 3].freeze.send(:initialize, 3) { rand } raises an error. :slight_smile:

irb(main):097:0> Class.new.freeze.to_s
TypeError: can't modify frozen object # to_s doesn't modify
        from (irb):97:in `to_s'

That's really surprising. Maybe it's due to lazy initialization of the
class's name.

Still, would be part of the internal state -- a frozen Object should still be able to change its non-exposed state IMHO. (Which means that freezing is an operation that is only visible to the outside, not the inside)

Btw, do people frequently use #freeze?

I dunno. :slight_smile:

Kind regards
    robert

More regards,
Florian Gross

I use it occasionally.

BTW, Date.today.freeze.to_s gives an error too. When I raised it on
ruby-core Matz agreed it's an issue but didn't really know off hand
how to fix it. (The problem is caused by caclulations on instance
vars for caching - I think.)

And I guess one could write their own #to_s or #inspect in particular
cases if it were a problem for them.

Gavin

···

On Tuesday, June 15, 2004, 7:43:36 PM, Robert wrote:

Btw, do people frequently use #freeze?

"Florian Gross" <flgr@ccan.de> schrieb im Newsbeitrag
news:2j81l7FttudvU1@uni-berlin.de...

Robert Klemme wrote:

> Moin moin!

Moin moin moin!

Moin ** 4!
:slight_smile:

>>irb(main):034:0> File.open("C:/autoexec.bat").freeze.inspect
>>TypeError: can't modify frozen File # inspect doesn't modify
>> from (irb):34:in `inspect'
>># This one also happens for File#path etc.
> IMHO an IO instance can't really be frozen in a meaningful way because

it

> must maintain changing state (namely the stream position). Maybe
> IO#freeze should throw an exception ("illegal operation", "cannot

freeze"

> or similar).

Yup, and there definitely shouldn't be errors when trying to access the
path of a frozen File etc. :slight_smile:

(But IMHO an IO instance can also be frozen -- the internal state
doesn't have to be frozen just because the Object is.)

Well, technically you are right. However, I'd expect #freeze to prevent
further modifications of the instance. In case of a stream that would
include reading and closing - which isn't really useful.

>>irb(main):063:0> Dir.new(".").freeze.send(:initialize, "C:/").read
>>=> "katapult.zip" # Initialize executed despite Dir being frozen
> I would not regard this a bug since initialize is expected to change

the

> instance, isn't it?

Well, [1, 2, 3].freeze.send(:initialize, 3) { rand } raises an error. :slight_smile:

Oh, it's the other way round! Sorry. Just forget my remark.

>>irb(main):097:0> Class.new.freeze.to_s
>>TypeError: can't modify frozen object # to_s doesn't modify
>> from (irb):97:in `to_s'
> That's really surprising. Maybe it's due to lazy initialization of

the

> class's name.

Still, would be part of the internal state -- a frozen Object should
still be able to change its non-exposed state IMHO. (Which means that
freezing is an operation that is only visible to the outside, not the
inside)

Well, there's much debate (see also C++ with 'const' and 'mutable'). IMHO
it's quite difficult to state what constness means in the general case.
I guess that's why this is used rather sparingly.

> Btw, do people frequently use #freeze?

I dunno. :slight_smile:

Well... I don't either. I can only say that I use it very sparingly.

Cheers

    robert

"Gavin Sinclair" <gsinclair@soyabean.com.au> schrieb im Newsbeitrag
news:721279650.20040616004600@soyabean.com.au...

> Btw, do people frequently use #freeze?

I use it occasionally.

Ah! *You* are the one... :-))

BTW, Date.today.freeze.to_s gives an error too. When I raised it on
ruby-core Matz agreed it's an issue but didn't really know off hand
how to fix it. (The problem is caused by caclulations on instance
vars for caching - I think.)

Most likely.

And I guess one could write their own #to_s or #inspect in particular
cases if it were a problem for them.

The cleanest might be to introduce something similar to C++'s keyword
'mutable', which allows instance variables to be changed even if the
container is const (aka frozen). But I don't like this approach because
things soon get messy. Constness is difficult to get right (not so much
for the language but rather for users of the language) IMHO.

Alternative solutions could make use of WeakReference somehow, although
this is just a faint idea - nothing concrete.

Kind regards

    robert

···

On Tuesday, June 15, 2004, 7:43:36 PM, Robert wrote:

I had a thought about a solution today. Let's just say that Date#to_s
is broken when the object is frozen because it's modifying internal
state for caching purposes. Well, why not outsource the caching bit
to a different class. For example (psuedo-code):

  class Date
    def to_s
      DateCache.to_s(self) {
        # Calculate the string rep here. This code
        # is only called if necessary to populate the
        # cache for the first time.
      }
    end
  end

Then again, you can just store the cache info in a class variable.

Not that I've scrutinised the code or anything.

Gavin

···

On Wednesday, June 16, 2004, 9:18:24 PM, Robert wrote:

And I guess one could write their own #to_s or #inspect in particular
cases if it were a problem for them.

The cleanest might be to introduce something similar to C++'s keyword
'mutable', which allows instance variables to be changed even if the
container is const (aka frozen). But I don't like this approach because
things soon get messy. Constness is difficult to get right (not so much
for the language but rather for users of the language) IMHO.

The cleanest might be to introduce something similar to C++'s keyword
'mutable', which allows instance variables to be changed even if the
container is const (aka frozen). But I don't like this approach
because things soon get messy. Constness is difficult to get right
(not so much for the language but rather for users of the language)
IMHO.

There's no need for this. You can't change what object an instance
variable is referring to in a frozen object, but you can modify the
object itself. So to get the effect you are looking for, you can write:

  class Foo
    Mutables = Struct.new(:foo)

    def initialize
      @mutables = Mutables.new(42)
    end

    def mutate(x)
      @mutables.foo = x
    end
  end

  f = Foo.new
  f.freeze
  f.mutate(10)
  p f

Alternative solutions could make use of WeakReference somehow,
although this is just a faint idea - nothing concrete.

If I understand you correctly you are talking about solving the problem
of wanting some part of the code to be able to modify an object but some
other part of the code to not be able to do this. I've toyed with this
idea before.

One idea I had is to create a reference that unfreezes the object
before allowing you to use it. In order for this to work, it has to set
Thread.critical=true (otherwise threads that are working with const
references would be able to modify the object). The problem with this
is that the reference has know knowledge about the behavior of the
underlying object (it may make certain assumptions about frozen
instances; see [ruby-talk:8047]).

Another idea I had was to create a reference that only allows the user
to call read-only methods. The reference has to "know" somehow which
methods are safe to allow; it can do this by freezing the object and
trying the operation (same problem as above) or by keeping a list of
safe operations (which can be a maintenance headache, plus there's the
question of what to do when the user calls #to_s on a reference to a
String).

One problem in general with using delegates as references is that you
have something that looks like a real object but is not. If you try to
pass a weakref to a string into a C extension that is expecting a
string, it won't work.

IMO the only sensible implementation would be to change the interpreter
to include two types of references: one that allows changes and another
that does not. It's not a small change, and Matz has already rejected
this idea (see RCR#92 at RCRS or
http://www.google.com/search?q=cache:dzj80Q78zgMJ:rubygarden.com/article.php%3Fsid%3D222+"rcr+92"&hl=en
if you want a threaded view of the discussion).

Lastly, I can definitely see some potential uses for this feature (e.g.
for safe.rb), but for most cases dup and/or freeze and/or the trick
mentioned above will work. I'm not sure if it makes sense to complicate
a library or the interpreter for a feature that is rarely useful (though
the same argument could have been made about callcc).

Paul

···

On Wed, Jun 16, 2004 at 08:18:24PM +0900, Robert Klemme wrote:

Just an idea regarding frozen objects: this and other myriad situations could
be handled by object "states." That is, when in a certain "state", only
certain methods are available to an object, and those methods can be aliased.
When state changes, the methods swap out. For example, and this is just
pseudocode, mind you (I am making up some syntax that doesn't really exist):

class MyClass

  def initialize
    state[:on] = false
  end

  def on_true
    return "TRUE"
  end

  def on_false
    return "FALSE"
  end

  alias :on :on_false

  state :on, :state => :on_state

end

m = MyClass.new
p m.on => "FALSE"
m.state[:on] = true
p m.on => "TRUE"

The way a frozen state could be implemented (aside from how it already is,
this is just a from-scratch idea) would be through states. Whenever an
object state changes, it would first restore the last state it was in, so you
could have a class like this:

class MyClass
  def initialize
    state[:frozen] = false
    @var = nil
  end

  def var
    @var
  end

  def var=(value)
    @var = value
  end

  state :frozen, :var= => nil
end

m = MyClass.new
p m.var => nil
m.var = "SOMEVALUE"
p m.var => "SOMEVALUE"
m.state[:frozen] = true
m.var = "ANOTHERVALUE" => no such method error

  Sean O'Dell

"Paul Brannan" <pbrannan@atdesk.com> schrieb im Newsbeitrag
news:20040616174712.GB2788@atdesk.com...

> Alternative solutions could make use of WeakReference somehow,
> although this is just a faint idea - nothing concrete.

If I understand you correctly you are talking about solving the problem
of wanting some part of the code to be able to modify an object but some
other part of the code to not be able to do this.

I rather thought along the lines of having cached data be referenced by a
WekRef so it can be discarded. But the WeakRef then must be able to
change, which it can't at the moment.

Regards

    robert

"Sean O'Dell" <sean@celsoft.com> schrieb im Newsbeitrag
news:200406161109.06083.sean@celsoft.com...

Just an idea regarding frozen objects: this and other myriad situations

could

be handled by object "states." That is, when in a certain "state", only
certain methods are available to an object, and those methods can be

aliased.

When state changes, the methods swap out. For example, and this is just
pseudocode, mind you (I am making up some syntax that doesn't really

exist):

class MyClass

  def initialize
    state[:on] = false
  end

  def on_true
    return "TRUE"
  end

  def on_false
    return "FALSE"
  end

  alias :on :on_false

  state :on, :state => :on_state

end

m = MyClass.new
p m.on => "FALSE"
m.state[:on] = true
p m.on => "TRUE"

The way a frozen state could be implemented (aside from how it already

is,

this is just a from-scratch idea) would be through states. Whenever an
object state changes, it would first restore the last state it was in,

so you

could have a class like this:

class MyClass
  def initialize
    state[:frozen] = false
    @var = nil
  end

  def var
    @var
  end

  def var=(value)
    @var = value
  end

  state :frozen, :var= => nil
end

m = MyClass.new
p m.var => nil
m.var = "SOMEVALUE"
p m.var => "SOMEVALUE"
m.state[:frozen] = true
m.var = "ANOTHERVALUE" => no such method error

That's basically the State Pattern: behavior of the instance changes
according to state. You can do that with current Ruby alread, if you
refrain from using #freeze and just implement some state pattern handling:

# roughly sketched

module Stateful
  def initialize
    @states = {}
    @state = nil
  end

  def state; @state; end

  def state=(st)
    raise "Illegal" unless @states.has_key? st
    @state = st
  end

  def send(sym, *args, &b)
    @states[@state].send(sym, *args, &b)
  end
end

Regards

    robert