diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index f0192c04142ae0..309350160edf4a 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -4726,7 +4726,7 @@ def test(klass, args) } # Chilled string setivar trigger warning -assert_equal 'literal string will be frozen in the future', %q{ +assert_match(/literal string will be frozen in the future/, %q{ Warning[:deprecated] = true $VERBOSE = true $warning = "no-warning" @@ -4754,7 +4754,7 @@ def setivar!(str) setivar!("chilled") # Emit warning $warning -} +}) # arity=-2 cfuncs assert_equal '["", "1/2", [0, [:ok, 1]]]', %q{ diff --git a/internal/string.h b/internal/string.h index 10879bd1d98b48..a9ec493754035c 100644 --- a/internal/string.h +++ b/internal/string.h @@ -14,6 +14,7 @@ #include "ruby/internal/stdbool.h" /* for bool */ #include "ruby/encoding.h" /* for rb_encoding */ #include "ruby/ruby.h" /* for VALUE */ +#include "id.h" /* for id_debug_created_info */ #define STR_NOEMBED FL_USER1 #define STR_SHARED FL_USER2 /* = ELTS_SHARED */ @@ -123,8 +124,28 @@ CHILLED_STRING_P(VALUE obj) static inline void CHILLED_STRING_MUTATED(VALUE str) { + bool rb_warning_category_enabled_p(rb_warning_category_t category); + FL_UNSET_RAW(str, STR_CHILLED); - rb_category_warn(RB_WARN_CATEGORY_DEPRECATED, "literal string will be frozen in the future"); + + if (RB_UNLIKELY(rb_warning_category_enabled_p(RB_WARN_CATEGORY_DEPRECATED))) { + VALUE debug_info = rb_attr_get(str, id_debug_created_info); + if (NIL_P(debug_info)) { + rb_category_warn( + RB_WARN_CATEGORY_DEPRECATED, + "literal string will be frozen in the future " + "(run with --debug-frozen-string-literal for more information)"); + } else { + VALUE path = rb_ary_entry(debug_info, 0); + VALUE line = rb_ary_entry(debug_info, 1); + + rb_category_warn( + RB_WARN_CATEGORY_DEPRECATED, + "literal string will be frozen in the future\n%"PRIsVALUE":%"PRIsVALUE": the string was created here", + path, + line); + } + } } static inline void diff --git a/spec/ruby/command_line/fixtures/debug_info.rb b/spec/ruby/command_line/fixtures/debug_info.rb index ee607910c0d8f4..f02b0419208c34 100644 --- a/spec/ruby/command_line/fixtures/debug_info.rb +++ b/spec/ruby/command_line/fixtures/debug_info.rb @@ -1,4 +1,3 @@ -# frozen_string_literal: true a = 'string' b = a c = b diff --git a/spec/ruby/command_line/frozen_strings_spec.rb b/spec/ruby/command_line/frozen_strings_spec.rb index 06889755d254cf..db1230d0dd5e56 100644 --- a/spec/ruby/command_line/frozen_strings_spec.rb +++ b/spec/ruby/command_line/frozen_strings_spec.rb @@ -57,9 +57,16 @@ describe "The --debug flag produces" do it "debugging info on attempted frozen string modification" do - error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--debug', args: "2>&1") + error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '--enable-frozen-string-literal --debug', args: "2>&1") error_str.should include("can't modify frozen String") error_str.should include("created at") - error_str.should include("command_line/fixtures/debug_info.rb:2") + error_str.should include("command_line/fixtures/debug_info.rb:1") + end + + it "debugging info on mutating chilled string" do + error_str = ruby_exe(fixture(__FILE__, 'debug_info.rb'), options: '-w --debug', args: "2>&1") + error_str.should include("literal string will be frozen in the future") + error_str.should include("the string was created here") + error_str.should include("command_line/fixtures/debug_info.rb:1") end end diff --git a/string.c b/string.c index e193539f4faa81..ba6c8235f657cc 100644 --- a/string.c +++ b/string.c @@ -1914,6 +1914,12 @@ rb_ec_str_resurrect(struct rb_execution_context_struct *ec, VALUE str, bool chil RUBY_DTRACE_CREATE_HOOK(STRING, RSTRING_LEN(str)); VALUE new_str = ec_str_duplicate(ec, rb_cString, str); if (chilled) { + if (RB_UNLIKELY(FL_TEST_RAW(str, FL_EXIVAR))) { + VALUE debug_info = rb_ivar_get(str, id_debug_created_info); + if (!NIL_P(debug_info)) { + rb_ivar_set(new_str, id_debug_created_info, debug_info); + } + } STR_CHILL_RAW(new_str); } return new_str;