[#81999] [Ruby trunk Bug#13737] "can't modify frozen String" when installing bundled gems — ko1@...
Issue #13737 has been updated by ko1 (Koichi Sasada).
4 messages
2017/07/11
[#82005] [Ruby trunk Bug#13737] "can't modify frozen String" when installing bundled gems — nobu@...
Issue #13737 has been updated by nobu (Nobuyoshi Nakada).
3 messages
2017/07/12
[#82102] Re: register_fstring_tainted:FL_TEST_RAW(str, RSTRING_FSTR) — Eric Wong <normalperson@...>
Koichi Sasada <ko1@atdot.net> wrote:
4 messages
2017/07/18
[#82151] [Ruby trunk Feature#13637] [PATCH] tool/runruby.rb: test with smallest possible machine stack — Rei.Odaira@...
Issue #13637 has been updated by ReiOdaira (Rei Odaira).
3 messages
2017/07/24
[ruby-core:82004] Re: [Ruby trunk Bug#13737] "can't modify frozen String" when installing bundled gems
From:
Eric Wong <normalperson@...>
Date:
2017-07-12 01:08:17 UTC
List:
ruby-core #82004
Eric Wong <normalperson@yhbt.net> wrote:
> ko1@atdot.net wrote:
> > Not sure why but r59310 will fix it (at least on my
> > environment). To complete it i think we need to dig into
> > rubygems deeply....
>
> It seems Psych is correct to be tainting keys with YAML.load.
> Perhaps there needs to be a separate table for frozen tainted
> strings?
Maybe the following works (barely tested, but I have to go...)
---
hash.c | 23 +----------
internal.h | 3 +-
string.c | 94 ++++++++++++++++++++++++++++++++++++++++++
test/ruby/test_optimization.rb | 20 +++++++++
vm.c | 12 ++++++
vm_core.h | 1 +
6 files changed, 130 insertions(+), 23 deletions(-)
diff --git a/hash.c b/hash.c
index 7a0beb7225..e17d906e9b 100644
--- a/hash.c
+++ b/hash.c
@@ -18,7 +18,6 @@
#include "probes.h"
#include "id.h"
#include "symbol.h"
-#include "gc.h"
#ifdef __APPLE__
# ifdef HAVE_CRT_EXTERNS_H
@@ -1516,33 +1515,13 @@ hash_aset(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing)
return ST_CONTINUE;
}
-static VALUE
-fstring_existing_str(VALUE str)
-{
- st_data_t fstr;
- st_table *tbl = rb_vm_fstring_table();
-
- if (st_lookup(tbl, str, &fstr)) {
- if (rb_objspace_garbage_object_p(fstr)) {
- return rb_fstring(str);
- }
- else {
- return (VALUE)fstr;
- }
- }
- else {
- return Qnil;
- }
-}
-
static int
hash_aset_str(st_data_t *key, st_data_t *val, struct update_arg *arg, int existing)
{
if (!existing && !RB_OBJ_FROZEN(*key)) {
VALUE k;
- if (!RB_OBJ_TAINTED(*key) &&
- (k = fstring_existing_str(*key)) != Qnil) {
+ if ((k = rb_fstring_existing(*key)) != Qnil) {
*key = k;
}
else {
diff --git a/internal.h b/internal.h
index 81f4d2b91d..e6d50ebcda 100644
--- a/internal.h
+++ b/internal.h
@@ -1574,6 +1574,7 @@ VALUE rb_strftime(const char *format, size_t format_len, rb_encoding *enc,
/* string.c */
VALUE rb_fstring(VALUE);
VALUE rb_fstring_new(const char *ptr, long len);
+VALUE rb_fstring_existing(VALUE);
#define rb_fstring_lit(str) rb_fstring_new((str), rb_strlen_lit(str))
#define rb_fstring_literal(str) rb_fstring_lit(str)
VALUE rb_fstring_cstr(const char *str);
@@ -1726,7 +1727,7 @@ void rb_vm_check_redefinition_by_prepend(VALUE klass);
VALUE rb_yield_refine_block(VALUE refinement, VALUE refinements);
VALUE ruby_vm_special_exception_copy(VALUE);
PUREFUNC(st_table *rb_vm_fstring_table(void));
-
+PUREFUNC(st_table *rb_vm_tfstring_table(void));
/* vm_dump.c */
void rb_print_backtrace(void);
diff --git a/string.c b/string.c
index 2012a281d6..751e0c0a4a 100644
--- a/string.c
+++ b/string.c
@@ -349,6 +349,99 @@ register_fstring(VALUE str)
return ret;
}
+static int
+tainted_fstr_update(st_data_t *key, st_data_t *val, st_data_t arg, int existing)
+{
+ VALUE *fstr = (VALUE *)arg;
+ VALUE str = (VALUE)*key;
+
+ if (existing) {
+ /* because of lazy sweep, str may be unmarked already and swept
+ * at next time */
+ if (rb_objspace_garbage_object_p(str)) {
+ *fstr = Qundef;
+ return ST_DELETE;
+ }
+
+ *fstr = str;
+ return ST_STOP;
+ }
+ else {
+ str = rb_str_dup(str);
+ RB_OBJ_TAINT_RAW(str);
+ RB_FL_SET_RAW(str, RSTRING_FSTR);
+ RB_OBJ_FREEZE_RAW(str);
+
+ *key = *val = *fstr = str;
+ return ST_CONTINUE;
+ }
+}
+
+static VALUE
+rb_fstring_existing0(VALUE str)
+{
+ st_table *frozen_strings = rb_vm_fstring_table();
+ st_data_t fstr;
+
+ if (st_lookup(frozen_strings, str, &fstr)) {
+ if (rb_objspace_garbage_object_p(fstr)) {
+ return register_fstring(str);
+ }
+ else {
+ return (VALUE)fstr;
+ }
+ }
+ return Qnil;
+}
+
+static VALUE
+rb_tainted_fstring_existing(VALUE str)
+{
+ VALUE ret;
+ st_data_t fstr;
+ st_table *tfstrings = rb_vm_tfstring_table();
+
+ if (st_lookup(tfstrings, str, &fstr)) {
+ ret = (VALUE)fstr;
+ if (!rb_objspace_garbage_object_p(ret)) {
+ return ret;
+ }
+ }
+ ret = rb_fstring_existing0(str);
+ if (NIL_P(ret)) {
+ return Qnil;
+ }
+ if (!RB_FL_TEST_RAW(ret, RSTRING_FSTR)) {
+ return Qnil;
+ }
+ do {
+ fstr = (st_data_t)ret;
+ st_update(tfstrings, fstr, tainted_fstr_update, (st_data_t)&fstr);
+ } while ((VALUE)fstr == Qundef);
+
+ ret = (VALUE)fstr;
+ assert(OBJ_FROZEN_RAW(ret));
+ assert(!FL_TEST_RAW(ret, STR_FAKESTR));
+ assert(!FL_TEST_RAW(ret, FL_EXIVAR));
+ assert(FL_TEST_RAW(ret, RSTRING_FSTR));
+ assert(FL_TEST_RAW(ret, FL_TAINT));
+ assert(RBASIC_CLASS(ret) == rb_cString);
+
+ return ret;
+}
+
+VALUE
+rb_fstring_existing(VALUE str)
+{
+ if (FL_TEST_RAW(str, RSTRING_FSTR))
+ return str;
+
+ if (!RB_OBJ_TAINTED_RAW(str))
+ return rb_fstring_existing0(str);
+
+ return rb_tainted_fstring_existing(str);
+}
+
static VALUE
setup_fake_str(struct RString *fake_str, const char *name, long len, int encidx)
{
@@ -1311,6 +1404,7 @@ rb_str_free(VALUE str)
if (FL_TEST(str, RSTRING_FSTR)) {
st_data_t fstr = (st_data_t)str;
st_delete(rb_vm_fstring_table(), &fstr, NULL);
+ st_delete(rb_vm_tfstring_table(), &fstr, NULL);
RB_DEBUG_COUNTER_INC(obj_str_fstr);
}
diff --git a/test/ruby/test_optimization.rb b/test/ruby/test_optimization.rb
index 2d0eec02fd..3559820f9f 100644
--- a/test/ruby/test_optimization.rb
+++ b/test/ruby/test_optimization.rb
@@ -181,6 +181,26 @@ def test_hash_empty?
assert_redefine_method('Hash', 'empty?', 'assert_nil({}.empty?); assert_nil({1=>1}.empty?)')
end
+ def test_hash_reuse_fstring
+ key = %w(h e l l o).join
+ h = {}
+ h[key] = true
+ exp = "hello".freeze
+ assert_not_same key, h.keys[0]
+ assert h.keys[0].frozen?
+ assert_same exp, h.keys[0]
+
+ h1 = {}
+ h2 = {}
+ key.taint
+ h1[key] = true
+ h2[key] = true
+ k1 = h1.keys[0]
+ k2 = h2.keys[0]
+ assert_same k1, k2
+ assert_predicate k1, :tainted?
+ end
+
def test_hash_aref_with
h = { "foo" => 1 }
assert_equal 1, h["foo"]
diff --git a/vm.c b/vm.c
index 814f8b6780..75ca12f2a5 100644
--- a/vm.c
+++ b/vm.c
@@ -2206,6 +2206,10 @@ ruby_vm_destruct(rb_vm_t *vm)
st_free_table(vm->frozen_strings);
vm->frozen_strings = 0;
}
+ if (vm->tainted_frozen_strings) {
+ st_free_table(vm->tainted_frozen_strings);
+ vm->tainted_frozen_strings = 0;
+ }
rb_vm_gvl_destroy(vm);
if (objspace) {
rb_objspace_free(objspace);
@@ -3142,6 +3146,8 @@ Init_vm_objects(void)
vm->mark_object_ary = rb_ary_tmp_new(128);
vm->loading_table = st_init_strtable();
vm->frozen_strings = st_init_table_with_size(&rb_fstring_hash_type, 1000);
+ vm->tainted_frozen_strings =
+ st_init_table_with_size(&rb_fstring_hash_type, 1000);
}
/* top self */
@@ -3203,6 +3209,12 @@ rb_vm_fstring_table(void)
return GET_VM()->frozen_strings;
}
+st_table *
+rb_vm_tfstring_table(void)
+{
+ return GET_VM()->tainted_frozen_strings;
+}
+
#if VM_COLLECT_USAGE_DETAILS
#define HASH_ASET(h, k, v) rb_hash_aset((h), (st_data_t)(k), (st_data_t)(v))
diff --git a/vm_core.h b/vm_core.h
index 679512390d..a808a1b1f7 100644
--- a/vm_core.h
+++ b/vm_core.h
@@ -574,6 +574,7 @@ typedef struct rb_vm_struct {
VALUE *defined_strings;
st_table *frozen_strings;
+ st_table *tainted_frozen_strings;
/* params */
struct { /* size in byte */
--
EW
Unsubscribe: <mailto:ruby-core-request@ruby-lang.org?subject=unsubscribe>
<http://lists.ruby-lang.org/cgi-bin/mailman/options/ruby-core>