diff --git a/NEWS.md b/NEWS.md index 6804fd1bc55f69..ab9c0c32b1f958 100644 --- a/NEWS.md +++ b/NEWS.md @@ -213,6 +213,7 @@ The following bundled gems are added. The following bundled gems are updated. * minitest 5.26.0 +* power_assert 3.0.0 * rake 13.3.1 * test-unit 3.7.0 * rexml 3.4.4 diff --git a/array.c b/array.c index 12f45e2cbb421a..b71123532d19cc 100644 --- a/array.c +++ b/array.c @@ -194,7 +194,9 @@ ary_embed_capa(VALUE ary) static size_t ary_embed_size(long capa) { - return offsetof(struct RArray, as.ary) + (sizeof(VALUE) * capa); + size_t size = offsetof(struct RArray, as.ary) + (sizeof(VALUE) * capa); + if (size < sizeof(struct RArray)) size = sizeof(struct RArray); + return size; } static bool diff --git a/class.c b/class.c index 5e50a281d9a9fe..a4249425a13a93 100644 --- a/class.c +++ b/class.c @@ -1605,13 +1605,8 @@ VALUE rb_define_class(const char *name, VALUE super) { VALUE klass; - ID id; - const rb_namespace_t *ns = rb_current_namespace(); + ID id = rb_intern(name); - id = rb_intern(name); - if (NAMESPACE_OPTIONAL_P(ns)) { - return rb_define_class_id_under(ns->ns_object, id, super); - } if (rb_const_defined(rb_cObject, id)) { klass = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(klass, T_CLASS)) { @@ -1723,13 +1718,8 @@ VALUE rb_define_module(const char *name) { VALUE module; - ID id; - const rb_namespace_t *ns = rb_current_namespace(); + ID id = rb_intern(name); - id = rb_intern(name); - if (NAMESPACE_OPTIONAL_P(ns)) { - return rb_define_module_id_under(ns->ns_object, id); - } if (rb_const_defined(rb_cObject, id)) { module = rb_const_get(rb_cObject, id); if (!RB_TYPE_P(module, T_MODULE)) { diff --git a/ext/json/generator/generator.c b/ext/json/generator/generator.c index 72155abe523358..024a8572726098 100644 --- a/ext/json/generator/generator.c +++ b/ext/json/generator/generator.c @@ -996,13 +996,12 @@ static inline VALUE vstate_get(struct generate_json_data *data) return data->vstate; } -struct hash_foreach_arg { - VALUE hash; - struct generate_json_data *data; - int first_key_type; - bool first; - bool mixed_keys_encountered; -}; +static VALUE +json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key) +{ + VALUE proc_args[2] = {object, is_key}; + return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil); +} static VALUE convert_string_subclass(VALUE key) @@ -1019,6 +1018,129 @@ convert_string_subclass(VALUE key) return key_to_s; } +static bool enc_utf8_compatible_p(int enc_idx) +{ + if (enc_idx == usascii_encindex) return true; + if (enc_idx == utf8_encindex) return true; + return false; +} + +static VALUE encode_json_string_try(VALUE str) +{ + return rb_funcall(str, i_encode, 1, Encoding_UTF_8); +} + +static VALUE encode_json_string_rescue(VALUE str, VALUE exception) +{ + raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); + return Qundef; +} + +static inline bool valid_json_string_p(VALUE str) +{ + int coderange = rb_enc_str_coderange(str); + + if (RB_LIKELY(coderange == ENC_CODERANGE_7BIT)) { + return true; + } + + if (RB_LIKELY(coderange == ENC_CODERANGE_VALID)) { + return enc_utf8_compatible_p(RB_ENCODING_GET_INLINED(str)); + } + + return false; +} + +static inline VALUE ensure_valid_encoding(struct generate_json_data *data, VALUE str, bool as_json_called, bool is_key) +{ + if (RB_LIKELY(valid_json_string_p(str))) { + return str; + } + + if (!as_json_called && data->state->strict && RTEST(data->state->as_json)) { + VALUE coerced_str = json_call_as_json(data->state, str, Qfalse); + if (coerced_str != str) { + if (RB_TYPE_P(coerced_str, T_STRING)) { + if (!valid_json_string_p(coerced_str)) { + raise_generator_error(str, "source sequence is illegal/malformed utf-8"); + } + } else { + // as_json could return another type than T_STRING + if (is_key) { + raise_generator_error(coerced_str, "%"PRIsVALUE" not allowed as object key in JSON", CLASS_OF(coerced_str)); + } + } + + return coerced_str; + } + } + + if (RB_ENCODING_GET_INLINED(str) == binary_encindex) { + VALUE utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); + switch (rb_enc_str_coderange(utf8_string)) { + case ENC_CODERANGE_7BIT: + return utf8_string; + case ENC_CODERANGE_VALID: + // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. + // TODO: Raise in 3.0.0 + rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); + return utf8_string; + break; + } + } + + return rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); +} + +static void raw_generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + fbuffer_append_char(buffer, '"'); + + long len; + search_state search; + search.buffer = buffer; + RSTRING_GETMEM(obj, search.ptr, len); + search.cursor = search.ptr; + search.end = search.ptr + len; + +#ifdef HAVE_SIMD + search.matches_mask = 0; + search.has_matches = false; + search.chunk_base = NULL; +#endif /* HAVE_SIMD */ + + switch (rb_enc_str_coderange(obj)) { + case ENC_CODERANGE_7BIT: + case ENC_CODERANGE_VALID: + if (RB_UNLIKELY(data->state->ascii_only)) { + convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); + } else if (RB_UNLIKELY(data->state->script_safe)) { + convert_UTF8_to_script_safe_JSON(&search); + } else { + convert_UTF8_to_JSON(&search); + } + break; + default: + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + break; + } + fbuffer_append_char(buffer, '"'); +} + +static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) +{ + obj = ensure_valid_encoding(data, obj, false, false); + raw_generate_json_string(buffer, data, obj); +} + +struct hash_foreach_arg { + VALUE hash; + struct generate_json_data *data; + int first_key_type; + bool first; + bool mixed_keys_encountered; +}; + NOINLINE() static void json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) @@ -1035,13 +1157,6 @@ json_inspect_hash_with_mixed_keys(struct hash_foreach_arg *arg) } } -static VALUE -json_call_as_json(JSON_Generator_State *state, VALUE object, VALUE is_key) -{ - VALUE proc_args[2] = {object, is_key}; - return rb_proc_call_with_block(state->as_json, 2, proc_args, Qnil); -} - static int json_object_i(VALUE key, VALUE val, VALUE _arg) { @@ -1107,8 +1222,10 @@ json_object_i(VALUE key, VALUE val, VALUE _arg) break; } + key_to_s = ensure_valid_encoding(data, key_to_s, as_json_called, true); + if (RB_LIKELY(RBASIC_CLASS(key_to_s) == rb_cString)) { - generate_json_string(buffer, data, key_to_s); + raw_generate_json_string(buffer, data, key_to_s); } else { generate_json(buffer, data, key_to_s); } @@ -1191,85 +1308,6 @@ static void generate_json_array(FBuffer *buffer, struct generate_json_data *data fbuffer_append_char(buffer, ']'); } -static inline int enc_utf8_compatible_p(int enc_idx) -{ - if (enc_idx == usascii_encindex) return 1; - if (enc_idx == utf8_encindex) return 1; - return 0; -} - -static VALUE encode_json_string_try(VALUE str) -{ - return rb_funcall(str, i_encode, 1, Encoding_UTF_8); -} - -static VALUE encode_json_string_rescue(VALUE str, VALUE exception) -{ - raise_generator_error_str(str, rb_funcall(exception, rb_intern("message"), 0)); - return Qundef; -} - -static inline VALUE ensure_valid_encoding(VALUE str) -{ - int encindex = RB_ENCODING_GET(str); - VALUE utf8_string; - if (RB_UNLIKELY(!enc_utf8_compatible_p(encindex))) { - if (encindex == binary_encindex) { - utf8_string = rb_enc_associate_index(rb_str_dup(str), utf8_encindex); - switch (rb_enc_str_coderange(utf8_string)) { - case ENC_CODERANGE_7BIT: - return utf8_string; - case ENC_CODERANGE_VALID: - // For historical reason, we silently reinterpret binary strings as UTF-8 if it would work. - // TODO: Raise in 3.0.0 - rb_warn("JSON.generate: UTF-8 string passed as BINARY, this will raise an encoding error in json 3.0"); - return utf8_string; - break; - } - } - - str = rb_rescue(encode_json_string_try, str, encode_json_string_rescue, str); - } - return str; -} - -static void generate_json_string(FBuffer *buffer, struct generate_json_data *data, VALUE obj) -{ - obj = ensure_valid_encoding(obj); - - fbuffer_append_char(buffer, '"'); - - long len; - search_state search; - search.buffer = buffer; - RSTRING_GETMEM(obj, search.ptr, len); - search.cursor = search.ptr; - search.end = search.ptr + len; - -#ifdef HAVE_SIMD - search.matches_mask = 0; - search.has_matches = false; - search.chunk_base = NULL; -#endif /* HAVE_SIMD */ - - switch (rb_enc_str_coderange(obj)) { - case ENC_CODERANGE_7BIT: - case ENC_CODERANGE_VALID: - if (RB_UNLIKELY(data->state->ascii_only)) { - convert_UTF8_to_ASCII_only_JSON(&search, data->state->script_safe ? script_safe_escape_table : ascii_only_escape_table); - } else if (RB_UNLIKELY(data->state->script_safe)) { - convert_UTF8_to_script_safe_JSON(&search); - } else { - convert_UTF8_to_JSON(&search); - } - break; - default: - raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); - break; - } - fbuffer_append_char(buffer, '"'); -} - static void generate_json_fallback(FBuffer *buffer, struct generate_json_data *data, VALUE obj) { VALUE tmp; @@ -1408,7 +1446,16 @@ static void generate_json(FBuffer *buffer, struct generate_json_data *data, VALU break; case T_STRING: if (klass != rb_cString) goto general; - generate_json_string(buffer, data, obj); + + if (RB_LIKELY(valid_json_string_p(obj))) { + raw_generate_json_string(buffer, data, obj); + } else if (as_json_called) { + raise_generator_error(obj, "source sequence is illegal/malformed utf-8"); + } else { + obj = ensure_valid_encoding(data, obj, false, false); + as_json_called = true; + goto start; + } break; case T_SYMBOL: generate_json_symbol(buffer, data, obj); diff --git a/gems/bundled_gems b/gems/bundled_gems index 80c93ea007b704..3ff5e3f6b013f4 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -7,7 +7,7 @@ # if `revision` is not given, "v"+`version` or `version` will be used. minitest 5.26.0 https://github.com/minitest/minitest -power_assert 2.0.5 https://github.com/ruby/power_assert f88e406e7c9e0810cc149869582afbae1fb84c4a +power_assert 3.0.0 https://github.com/ruby/power_assert rake 13.3.1 https://github.com/ruby/rake test-unit 3.7.0 https://github.com/test-unit/test-unit rexml 3.4.4 https://github.com/ruby/rexml diff --git a/namespace.c b/namespace.c index 0f0230b5fdbd9a..0efc75e28bf009 100644 --- a/namespace.c +++ b/namespace.c @@ -876,8 +876,6 @@ Init_enable_namespace(void) } } -#if RUBY_DEBUG > 0 - /* :nodoc: */ static VALUE rb_namespace_s_root(VALUE recv) @@ -892,6 +890,24 @@ rb_namespace_s_main(VALUE recv) return main_namespace->ns_object; } +/* :nodoc: */ +static VALUE +rb_namespace_root_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_ROOT_P(ns)); +} + +/* :nodoc: */ +static VALUE +rb_namespace_main_p(VALUE namespace) +{ + const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); + return RBOOL(NAMESPACE_MAIN_P(ns)); +} + +#if RUBY_DEBUG + static const char * classname(VALUE klass) { @@ -1027,30 +1043,6 @@ rb_f_dump_classext(VALUE recv, VALUE klass) return res; } -/* :nodoc: */ -static VALUE -rb_namespace_root_p(VALUE namespace) -{ - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_ROOT_P(ns)); -} - -/* :nodoc: */ -static VALUE -rb_namespace_main_p(VALUE namespace) -{ - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_MAIN_P(ns)); -} - -/* :nodoc: */ -static VALUE -rb_namespace_user_p(VALUE namespace) -{ - const rb_namespace_t *ns = (const rb_namespace_t *)rb_get_namespace_t(namespace); - return RBOOL(NAMESPACE_USER_P(ns)); -} - #endif /* RUBY_DEBUG */ /* @@ -1084,14 +1076,13 @@ Init_Namespace(void) if (rb_namespace_available()) { rb_include_module(rb_cObject, rb_mNamespaceLoader); -#if RUBY_DEBUG > 0 rb_define_singleton_method(rb_cNamespace, "root", rb_namespace_s_root, 0); rb_define_singleton_method(rb_cNamespace, "main", rb_namespace_s_main, 0); - rb_define_global_function("dump_classext", rb_f_dump_classext, 1); - rb_define_method(rb_cNamespace, "root?", rb_namespace_root_p, 0); rb_define_method(rb_cNamespace, "main?", rb_namespace_main_p, 0); - rb_define_method(rb_cNamespace, "user?", rb_namespace_user_p, 0); + +#if RUBY_DEBUG + rb_define_global_function("dump_classext", rb_f_dump_classext, 1); #endif } diff --git a/string.c b/string.c index fa6ce7f3ec1d07..472159ffe470e1 100644 --- a/string.c +++ b/string.c @@ -240,9 +240,11 @@ rb_str_reembeddable_p(VALUE str) } static inline size_t -rb_str_embed_size(long capa) +rb_str_embed_size(long capa, long termlen) { - return offsetof(struct RString, as.embed.ary) + capa; + size_t size = offsetof(struct RString, as.embed.ary) + capa + termlen; + if (size < sizeof(struct RString)) size = sizeof(struct RString); + return size; } size_t @@ -250,28 +252,30 @@ rb_str_size_as_embedded(VALUE str) { size_t real_size; if (STR_EMBED_P(str)) { - real_size = rb_str_embed_size(RSTRING(str)->len) + TERM_LEN(str); + size_t capa = RSTRING(str)->len; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) capa += sizeof(st_index_t); + + real_size = rb_str_embed_size(capa, TERM_LEN(str)); } /* if the string is not currently embedded, but it can be embedded, how * much space would it require */ else if (rb_str_reembeddable_p(str)) { - real_size = rb_str_embed_size(RSTRING(str)->as.heap.aux.capa) + TERM_LEN(str); + size_t capa = RSTRING(str)->as.heap.aux.capa; + if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) capa += sizeof(st_index_t); + + real_size = rb_str_embed_size(capa, TERM_LEN(str)); } else { real_size = sizeof(struct RString); } - if (FL_TEST_RAW(str, STR_PRECOMPUTED_HASH)) { - real_size += sizeof(st_index_t); - } - return real_size; } static inline bool STR_EMBEDDABLE_P(long len, long termlen) { - return rb_gc_size_allocatable_p(rb_str_embed_size(len + termlen)); + return rb_gc_size_allocatable_p(rb_str_embed_size(len, termlen)); } static VALUE str_replace_shared_without_enc(VALUE str2, VALUE str); @@ -1004,7 +1008,7 @@ must_not_null(const char *ptr) static inline VALUE str_alloc_embed(VALUE klass, size_t capa) { - size_t size = rb_str_embed_size(capa); + size_t size = rb_str_embed_size(capa, 0); RUBY_ASSERT(size > 0); RUBY_ASSERT(rb_gc_size_allocatable_p(size)); @@ -1881,7 +1885,7 @@ str_replace(VALUE str, VALUE str2) static inline VALUE ec_str_alloc_embed(struct rb_execution_context_struct *ec, VALUE klass, size_t capa) { - size_t size = rb_str_embed_size(capa); + size_t size = rb_str_embed_size(capa, 0); RUBY_ASSERT(size > 0); RUBY_ASSERT(rb_gc_size_allocatable_p(size)); @@ -7067,7 +7071,7 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * '3.14159'.to_f # => 3.14159 * '1.234e-2'.to_f # => 0.01234 * - * Characters past a leading valid number (in the given +base+) are ignored: + * Characters past a leading valid number are ignored: * * '3.14 (pi to two places)'.to_f # => 3.14 * @@ -7075,6 +7079,7 @@ rb_str_to_i(int argc, VALUE *argv, VALUE str) * * 'abcdef'.to_f # => 0.0 * + * See {Converting to Non-String}[rdoc-ref:String@Converting+to+Non--5CString]. */ static VALUE diff --git a/test/json/json_coder_test.rb b/test/json/json_coder_test.rb index fb9d7b30a59a11..83b89a3b13dea9 100755 --- a/test/json/json_coder_test.rb +++ b/test/json/json_coder_test.rb @@ -67,6 +67,71 @@ def test_json_coder_dump_NaN_or_Infinity_loop assert_include error.message, "NaN not allowed in JSON" end + def test_json_coder_string_invalid_encoding + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.dup + end + + error = assert_raise JSON::GeneratorError do + coder.dump("\xFF") + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "source sequence is illegal/malformed utf-8", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + object.bytes + end + + assert_equal "[255]", coder.dump("\xFF") + assert_equal 1, calls + + error = assert_raise JSON::GeneratorError do + coder.dump({ "\xFF" => 1 }) + end + assert_equal "Array not allowed as object key in JSON", error.message + assert_equal 2, calls + + calls = 0 + coder = JSON::Coder.new do |object, is_key| + calls += 1 + [object].pack("m") + end + + assert_equal '"/w==\\n"', coder.dump("\xFF") + assert_equal 1, calls + + assert_equal '{"/w==\\n":1}', coder.dump({ "\xFF" => 1 }) + assert_equal 2, calls + end + def test_nesting_recovery coder = JSON::Coder.new ary = [] diff --git a/test/ruby/test_namespace.rb b/test/ruby/test_namespace.rb index 5a014ad7148a51..daa4e9e0ba3881 100644 --- a/test/ruby/test_namespace.rb +++ b/test/ruby/test_namespace.rb @@ -183,7 +183,7 @@ def test_proc_defined_in_namespace_refers_module_in_namespace pend unless Namespace.enabled? # require_relative dosn't work well in assert_separately even with __FILE__ and __LINE__ - assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}") + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "here = '#{__dir__}'; #{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) begin; ns1 = Namespace.new ns1.require(File.join("#{here}", 'namespace/proc_callee')) @@ -695,4 +695,100 @@ def test_root_and_main_methods pat = "_ruby_ns_*."+RbConfig::CONFIG["DLEXT"] File.unlink(*Dir.glob(pat, base: tmp).map {|so| "#{tmp}/#{so}"}) end + + def test_basic_namespace_detections + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + ns = Namespace.new + code = <<~EOC + NS1 = Namespace.current + class Foo + NS2 = Namespace.current + NS2_proc = ->(){ NS2 } + NS3_proc = ->(){ Namespace.current } + + def ns4 = Namespace.current + def self.ns5 = NS2 + def self.ns6 = Namespace.current + def self.ns6_proc = ->(){ Namespace.current } + def self.ns7 + res = [] + [1,2].chunk{ it.even? }.each do |bool, members| + res << Namespace.current.object_id.to_s + ":" + bool.to_s + ":" + members.map(&:to_s).join(",") + end + res + end + + def self.yield_block = yield + def self.call_block(&b) = b.call + end + FOO_NAME = Foo.name + + module Kernel + def foo_namespace = Namespace.current + module_function :foo_namespace + end + + NS_X = Foo.new.ns4 + NS_Y = foo_namespace + EOC + ns.eval(code) + outer = Namespace.current + assert_equal ns, ns::NS1 # on TOP frame + assert_equal ns, ns::Foo::NS2 # on CLASS frame + assert_equal ns, ns::Foo::NS2_proc.call # proc -> a const on CLASS + assert_equal ns, ns::Foo::NS3_proc.call # proc -> the current + assert_equal ns, ns::Foo.new.ns4 # instance method -> the current + assert_equal ns, ns::Foo.ns5 # singleton method -> a const on CLASS + assert_equal ns, ns::Foo.ns6 # singleton method -> the current + assert_equal ns, ns::Foo.ns6_proc.call # method returns a proc -> the current + + # a block after CFUNC/IFUNC in a method -> the current + assert_equal ["#{ns.object_id}:false:1", "#{ns.object_id}:true:2"], ns::Foo.ns7 + + assert_equal outer, ns::Foo.yield_block{ Namespace.current } # method yields + assert_equal outer, ns::Foo.call_block{ Namespace.current } # method calls a block + + assert_equal ns, ns::NS_X # on TOP frame, referring a class in the current + assert_equal ns, ns::NS_Y # on TOP frame, referring Kernel method defined by a CFUNC method + + assert_equal "Foo", ns::FOO_NAME + assert_equal "Foo", ns::Foo.name + end; + end + + def test_loading_extension_libs_in_main_namespace + pend if /mswin|mingw/ =~ RUBY_PLATFORM # timeout on windows environments + assert_separately([ENV_ENABLE_NAMESPACE], __FILE__, __LINE__, "#{<<~"begin;"}\n#{<<~'end;'}", ignore_stderr: true) + begin; + require "prism" + require "optparse" + require "date" + require "time" + require "delegate" + require "singleton" + require "pp" + require "fileutils" + require "tempfile" + require "tmpdir" + require "json" + require "psych" + require "yaml" + require "zlib" + require "open3" + require "ipaddr" + require "net/http" + require "openssl" + require "socket" + require "uri" + require "digest" + require "erb" + require "stringio" + require "monitor" + require "timeout" + require "securerandom" + expected = 1 + assert_equal expected, 1 + end; + end end diff --git a/vm.c b/vm.c index c11ac9f7a10510..32785dbcc8cbca 100644 --- a/vm.c +++ b/vm.c @@ -113,39 +113,62 @@ VM_EP_RUBY_LEP(const rb_execution_context_t *ec, const rb_control_frame_t *curre // rb_vmdebug_namespace_env_dump_raw() simulates this function const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ - const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; + const rb_control_frame_t *cfp = current_cfp; + + if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_IFUNC)) { + ep = VM_EP_LEP(current_cfp->ep); + /** + * Returns CFUNC frame only in this case. + * + * Usually CFUNC frame doesn't represent the current namespace and it should operate + * the caller namespace. See the example: + * + * # in the main namespace + * module Kernel + * def foo = "foo" + * module_function :foo + * end + * + * In the case above, `module_function` is defined in the root namespace. + * If `module_function` worked in the root namespace, `Kernel#foo` is invisible + * from it and it causes NameError: undefined method `foo` for module `Kernel`. + * + * But in cases of IFUNC (blocks written in C), IFUNC doesn't have its own namespace + * and its local env frame will be CFUNC frame. + * For example, `Enumerator#chunk` calls IFUNC blocks, written as `chunk_i` function. + * + * [1].chunk{ it.even? }.each{ ... } + * + * Before calling the Ruby block `{ it.even? }`, `#chunk` calls `chunk_i` as IFUNC + * to iterate the array's members (it's just like `#each`). + * We expect that `chunk_i` works as expected by the implementation of `#chunk` + * without any overwritten definitions from namespaces. + * So the definitions on IFUNC frames should be equal to the caller CFUNC. + */ + VM_ASSERT(VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)); + return ep; + } - while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { - while (!VM_ENV_LOCAL_P(ep)) { - ep = VM_ENV_PREV_EP(ep); - } - while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { - if (!cfp) { - cfp = rb_vm_search_cf_from_ep(ec, checkpoint_cfp, ep); - VM_NAMESPACE_ASSERT(cfp, "Failed to search cfp from ep"); - VM_NAMESPACE_ASSERT(cfp->ep == ep, "Searched cfp's ep is not equal to ep"); - } - if (!cfp) { - return NULL; - } - VM_NAMESPACE_ASSERT(cfp->ep, "cfp->ep == NULL"); - VM_NAMESPACE_ASSERT(cfp->ep == ep, "cfp->ep != ep"); + while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - VM_NAMESPACE_ASSERT(!VM_FRAME_FINISHED_P(cfp), "CFUNC frame should not FINISHED"); + VM_NAMESPACE_ASSERT(cfp, "CFUNC should have a valid previous control frame"); + VM_NAMESPACE_ASSERT(cfp < eocfp, "CFUNC should have a valid caller frame"); + if (!cfp || cfp >= eocfp) { + return NULL; + } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - if (cfp >= eocfp) { - return NULL; - } - VM_NAMESPACE_ASSERT(cfp, "CFUNC should have a valid previous control frame"); - ep = cfp->ep; - if (!ep) { - return NULL; - } + VM_NAMESPACE_ASSERT(cfp->ep, "CFUNC should have a valid caller frame with env"); + ep = cfp->ep; + if (!ep) { + return NULL; } - checkpoint_cfp = cfp; - cfp = NULL; } + + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + } + return ep; } @@ -3096,7 +3119,7 @@ current_namespace_on_cfp(const rb_execution_context_t *ec, const rb_control_fram VM_NAMESPACE_ASSERT(lep, "lep should be valid"); VM_NAMESPACE_ASSERT(rb_namespace_available(), "namespace should be available here"); - if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD)) { + if (VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_METHOD) || VM_ENV_FRAME_TYPE_P(lep, VM_FRAME_MAGIC_CFUNC)) { cme = check_method_entry(lep[VM_ENV_DATA_INDEX_ME_CREF], TRUE); VM_NAMESPACE_ASSERT(cme, "cme should be valid"); VM_NAMESPACE_ASSERT(cme->def, "cme->def shold be valid"); diff --git a/vm_dump.c b/vm_dump.c index 0fcef846db8704..2ed1f955b3445e 100644 --- a/vm_dump.c +++ b/vm_dump.c @@ -421,39 +421,42 @@ rb_vmdebug_namespace_env_dump_raw(const rb_execution_context_t *ec, const rb_con // See VM_EP_RUBY_LEP for the original logic const VALUE *ep = current_cfp->ep; const rb_control_frame_t * const eocfp = RUBY_VM_END_CONTROL_FRAME(ec); /* end of control frame pointer */ - const rb_control_frame_t *cfp = NULL, *checkpoint_cfp = current_cfp; + const rb_control_frame_t *cfp = current_cfp, *checkpoint_cfp = current_cfp; kprintf("-- Namespace detection information " "-----------------------------------------\n"); namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); - while (!VM_ENV_LOCAL_P(ep) || VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + if (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_IFUNC)) { while (!VM_ENV_LOCAL_P(ep)) { ep = VM_ENV_PREV_EP(ep); namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); } - while (VM_ENV_FLAGS(ep, VM_FRAME_FLAG_CFRAME) != 0) { - if (!cfp) { - cfp = vmdebug_search_cf_from_ep(ec, checkpoint_cfp, ep); - } - if (!cfp) { - goto stop; - } - cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); - if (cfp >= eocfp) { - kprintf("[PREVIOUS CONTROL FRAME IS OUT OF BOUND]\n"); - goto stop; - } - ep = cfp->ep; - namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); - if (!ep) { - goto stop; - } + goto stop; + } + + while (VM_ENV_FRAME_TYPE_P(ep, VM_FRAME_MAGIC_CFUNC)) { + cfp = RUBY_VM_PREVIOUS_CONTROL_FRAME(cfp); + if (!cfp) { + goto stop; + } + if (cfp >= eocfp) { + kprintf("[PREVIOUS CONTROL FRAME IS OUT OF BOUND]\n"); + goto stop; + } + ep = cfp->ep; + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + if (!ep) { + goto stop; } - checkpoint_cfp = cfp; - cfp = NULL; } + + while (!VM_ENV_LOCAL_P(ep)) { + ep = VM_ENV_PREV_EP(ep); + namespace_env_dump_unchecked(ec, ep, checkpoint_cfp, errout); + } + stop: kprintf("\n"); return true;