遠藤です。

ObjectSpace を使うと種々のリテラルを書き換えることができてしまうようですが、
仕様でしょうか。


def foo
  "foobarbaz"
end

ObjectSpace.each_object(String) do |s|
  s.replace("evil") if /foobarbaz/ =~ s && !s.frozen?
end

p foo  #=> "evil"

def bar
  `ls -l`
end

ObjectSpace.each_object(String) do |s|
  s.replace("echo rm -rf /") if /ls -l/ =~ s && !s.frozen?
end

p bar  #=> "rm -rf /\n"


バグだとして、リテラルを freeze するパッチを書きましたが、IRC では

  - freeze で解決するのが正しいやり方なのか
  - freeze しても finalizer が付け替えできるのではないか

という感じの指摘がありました。どう直すのがよいでしょう。


Index: compile.c
===================================================================
--- compile.c	(revision 22217)
+++ compile.c	(working copy)
@@ -2120,7 +2120,7 @@
     int cnt = 1;

     debugp_param("nd_lit", lit);
-    ADD_INSN1(ret, nd_line(node), putobject, node->nd_lit);
+    ADD_INSN1(ret, nd_line(node), putobject, rb_obj_freeze(node->nd_lit));

     while (list) {
 	COMPILE(ret, "each string", list->nd_head);
@@ -2236,6 +2236,7 @@
 		rb_ary_push(ary, node->nd_head->nd_lit);
 		node = node->nd_next;
 	    }
+	    rb_obj_freeze(ary);

 	    iseq_add_mark_object_compile_time(iseq, ary);
 	    ADD_INSN1(ret, nd_line(node_root), duparray, ary);
@@ -2708,7 +2709,7 @@

     if (estr != 0) {
 	if (needstr != Qfalse) {
-	    VALUE str = rb_str_new2(estr);
+	    VALUE str = rb_obj_freeze(rb_str_new2(estr));
 	    ADD_INSN1(ret, nd_line(node), putstring, str);
 	    iseq_add_mark_object_compile_time(iseq, str);
 	}
@@ -4353,7 +4354,7 @@
       case NODE_STR:{
 	debugp_param("nd_lit", node->nd_lit);
 	if (!poped) {
-	    ADD_INSN1(ret, nd_line(node), putstring, node->nd_lit);
+	    ADD_INSN1(ret, nd_line(node), putstring, rb_obj_freeze(node->nd_lit));
 	}
 	break;
       }
@@ -4367,7 +4368,7 @@
       }
       case NODE_XSTR:{
 	ADD_CALL_RECEIVER(ret, nd_line(node));
-	ADD_INSN1(ret, nd_line(node), putobject, node->nd_lit);
+	ADD_INSN1(ret, nd_line(node), putobject, rb_obj_freeze(node->nd_lit));
 	ADD_CALL(ret, nd_line(node), ID2SYM(idBackquote), INT2FIX(1));

 	if (poped) {

-- 
Yusuke ENDOH <mame / tsg.ne.jp>