ruby-changes:54538
From: nobu <ko1@a...>
Date: Tue, 8 Jan 2019 18:08:37 +0900 (JST)
Subject: [ruby-changes:54538] nobu:r66753 (trunk): Defer escaping control char in error messages
nobu 2019-01-08 18:08:31 +0900 (Tue, 08 Jan 2019) New Revision: 66753 https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=66753 Log: Defer escaping control char in error messages * eval_error.c (print_errinfo): defer escaping control char in error messages until writing to stderr, instead of quoting at building the message. [ruby-core:90853] [Bug #15497] Modified files: trunk/error.c trunk/eval_error.c trunk/internal.h trunk/ruby.c trunk/string.c trunk/test/ruby/test_exception.rb trunk/test/ruby/test_module.rb trunk/test/ruby/test_rubyoptions.rb Index: string.c =================================================================== --- string.c (revision 66752) +++ string.c (revision 66753) @@ -5818,6 +5818,24 @@ rb_str_buf_cat_escaped_char(VALUE result https://github.com/ruby/ruby/blob/trunk/string.c#L5818 return l; } +const char * +ruby_escaped_char(int c) +{ + switch (c) { + case '\0': return "\\0"; + case '\n': return "\\n"; + case '\r': return "\\r"; + case '\t': return "\\t"; + case '\f': return "\\f"; + case '\013': return "\\v"; + case '\010': return "\\b"; + case '\007': return "\\a"; + case '\033': return "\\e"; + case '\x7f': return "\\c?"; + } + return NULL; +} + VALUE rb_str_escape(VALUE str) { @@ -5832,7 +5850,8 @@ rb_str_escape(VALUE str) https://github.com/ruby/ruby/blob/trunk/string.c#L5850 int asciicompat = rb_enc_asciicompat(enc); while (p < pend) { - unsigned int c, cc; + unsigned int c; + const char *cc; int n = rb_enc_precise_mbclen(p, pend, enc); if (!MBCLEN_CHARFOUND_P(n)) { if (p > prev) str_buf_cat(result, prev, p - prev); @@ -5849,22 +5868,10 @@ rb_str_escape(VALUE str) https://github.com/ruby/ruby/blob/trunk/string.c#L5868 n = MBCLEN_CHARFOUND_LEN(n); c = rb_enc_mbc_to_codepoint(p, pend, enc); p += n; - switch (c) { - case '\n': cc = 'n'; break; - case '\r': cc = 'r'; break; - case '\t': cc = 't'; break; - case '\f': cc = 'f'; break; - case '\013': cc = 'v'; break; - case '\010': cc = 'b'; break; - case '\007': cc = 'a'; break; - case 033: cc = 'e'; break; - default: cc = 0; break; - } + cc = ruby_escaped_char(c); if (cc) { if (p - n > prev) str_buf_cat(result, prev, p - n - prev); - buf[0] = '\\'; - buf[1] = (char)cc; - str_buf_cat(result, buf, 2); + str_buf_cat(result, cc, strlen(cc)); prev = p; } else if (asciicompat && rb_enc_isascii(c, enc) && ISPRINT(c)) { Index: test/ruby/test_rubyoptions.rb =================================================================== --- test/ruby/test_rubyoptions.rb (revision 66752) +++ test/ruby/test_rubyoptions.rb (revision 66753) @@ -310,7 +310,7 @@ class TestRubyOptions < Test::Unit::Test https://github.com/ruby/ruby/blob/trunk/test/ruby/test_rubyoptions.rb#L310 assert_in_out_err(%W(-\r -e) + [""], "", [], []) - assert_in_out_err(%W(-\rx), "", [], /invalid option -\\x0D \(-h will show valid options\) \(RuntimeError\)/) + assert_in_out_err(%W(-\rx), "", [], /invalid option -\\r \(-h will show valid options\) \(RuntimeError\)/) assert_in_out_err(%W(-\x01), "", [], /invalid option -\\x01 \(-h will show valid options\) \(RuntimeError\)/) Index: test/ruby/test_exception.rb =================================================================== --- test/ruby/test_exception.rb (revision 66752) +++ test/ruby/test_exception.rb (revision 66753) @@ -1071,6 +1071,43 @@ $stderr = $stdout; raise "\x82\xa0"') do https://github.com/ruby/ruby/blob/trunk/test/ruby/test_exception.rb#L1071 end; end + def assert_null_char(src, *args, **opts) + begin + eval(src) + rescue => e + end + assert_not_nil(e) + assert_include(e.message, "\0") + assert_in_out_err([], src, [], [], *args, **opts) do |_, err,| + err.each do |e| + assert_not_include(e, "\0") + end + end + e + end + + def test_control_in_message + bug7574 = '[ruby-dev:46749]' + assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574) + begin; + Object.const_defined?("String\0") + end; + assert_null_char("#{<<~"begin;"}\n#{<<~'end;'}", bug7574) + begin; + Object.const_get("String\0") + end; + end + + def test_encoding_in_message + name = "\u{e9}t\u{e9}" + e = EnvUtil.with_default_external("US-ASCII") do + assert_raise(NameError) do + Object.const_get(name) + end + end + assert_include(e.message, name) + end + def test_method_missing_reason_clear bug10969 = '[ruby-core:68515] [Bug #10969]' a = Class.new {def method_missing(*) super end}.new Index: test/ruby/test_module.rb =================================================================== --- test/ruby/test_module.rb (revision 66752) +++ test/ruby/test_module.rb (revision 66753) @@ -745,10 +745,6 @@ class TestModule < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_module.rb#L745 assert_raise(NameError) { c1.const_get(:foo) } bug5084 = '[ruby-dev:44200]' assert_raise(TypeError, bug5084) { c1.const_get(1) } - bug7574 = '[ruby-dev:46749]' - assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) { - Object.const_get("String\0") - } end def test_const_defined_invalid_name @@ -756,10 +752,6 @@ class TestModule < Test::Unit::TestCase https://github.com/ruby/ruby/blob/trunk/test/ruby/test_module.rb#L752 assert_raise(NameError) { c1.const_defined?(:foo) } bug5084 = '[ruby-dev:44200]' assert_raise(TypeError, bug5084) { c1.const_defined?(1) } - bug7574 = '[ruby-dev:46749]' - assert_raise_with_message(NameError, "wrong constant name \"String\\u0000\"", bug7574) { - Object.const_defined?("String\0") - } end def test_const_get_no_inherited Index: internal.h =================================================================== --- internal.h (revision 66752) +++ internal.h (revision 66753) @@ -2021,6 +2021,7 @@ VALUE rb_sym_to_proc(VALUE sym); https://github.com/ruby/ruby/blob/trunk/internal.h#L2021 char *rb_str_to_cstr(VALUE str); VALUE rb_str_eql(VALUE str1, VALUE str2); VALUE rb_obj_as_string_result(VALUE str, VALUE obj); +const char *ruby_escaped_char(int c); /* symbol.c */ #ifdef RUBY_ENCODING_H Index: eval_error.c =================================================================== --- eval_error.c (revision 66752) +++ eval_error.c (revision 66753) @@ -79,6 +79,44 @@ error_print(rb_execution_context_t *ec) https://github.com/ruby/ruby/blob/trunk/eval_error.c#L79 rb_ec_error_print(ec, ec->errinfo); } +static void +write_warnq(VALUE out, VALUE str, const char *ptr, long len) +{ + if (NIL_P(out)) { + const char *beg = ptr; + const long olen = len; + for (; len > 0; --len, ++ptr) { + unsigned char c = *ptr; + if (rb_iscntrl(c)) { + char buf[5]; + const char *cc = 0; + if (ptr > beg) rb_write_error2(beg, ptr - beg); + beg = ptr + 1; + cc = ruby_escaped_char(c); + if (cc) { + rb_write_error2(cc, strlen(cc)); + } + else { + rb_write_error2(buf, snprintf(buf, sizeof(buf), "\\x%02X", c)); + } + } + else if (c == '\\') { + rb_write_error2(beg, ptr - beg + 1); + beg = ptr; + } + } + if (ptr > beg) { + if (beg == RSTRING_PTR(str) && olen == RSTRING_LEN(str)) + rb_write_error_str(str); + else + rb_write_error2(beg, ptr - beg); + } + } + else { + rb_str_cat(out, ptr, len); + } +} + #define CSI_BEGIN "\033[" #define CSI_SGR "m" @@ -134,11 +172,11 @@ print_errinfo(const VALUE eclass, const https://github.com/ruby/ruby/blob/trunk/eval_error.c#L172 if (RSTRING_PTR(epath)[0] == '#') epath = 0; if ((tail = memchr(einfo, '\n', elen)) != 0) { - write_warn2(str, einfo, tail - einfo); + write_warnq(str, emesg, einfo, tail - einfo); tail++; /* skip newline */ } else { - write_warn_str(str, emesg); + write_warnq(str, emesg, einfo, elen); } if (epath) { write_warn(str, " ("); @@ -154,7 +192,7 @@ print_errinfo(const VALUE eclass, const https://github.com/ruby/ruby/blob/trunk/eval_error.c#L192 } if (tail && einfo+elen > tail) { if (!highlight) { - write_warn2(str, tail, einfo+elen-tail); + write_warnq(str, emesg, tail, einfo+elen-tail); if (einfo[elen-1] != '\n') write_warn2(str, "\n", 1); } else { @@ -164,7 +202,7 @@ print_errinfo(const VALUE eclass, const https://github.com/ruby/ruby/blob/trunk/eval_error.c#L202 tail = memchr(einfo, '\n', elen); if (!tail || tail > einfo) { write_warn(str, bold); - write_warn2(str, einfo, tail ? tail-einfo : elen); + write_warnq(str, emesg, einfo, tail ? tail-einfo : elen); write_warn(str, reset); if (!tail) { write_warn2(str, "\n", 1); @@ -174,7 +212,7 @@ print_errinfo(const VALUE eclass, const https://github.com/ruby/ruby/blob/trunk/eval_error.c#L212 elen -= tail - einfo; einfo = tail; do ++tail; while (tail < einfo+elen && *tail == '\n'); - write_warn2(str, einfo, tail-einfo); + write_warnq(str, emesg, einfo, tail-einfo); elen -= tail - einfo; einfo = tail; } Index: ruby.c =================================================================== --- ruby.c (revision 66752) +++ ruby.c (revision 66753) @@ -1367,16 +1367,9 @@ proc_options(long argc, char **argv, rub https://github.com/ruby/ruby/blob/trunk/ruby.c#L1367 default: { - if (ISPRINT(*s)) { - rb_raise(rb_eRuntimeError, + rb_raise(rb_eRuntimeError, "invalid option -%c (-h will show valid options)", (int)(unsigned char)*s); - } - else { - rb_raise(rb_eRuntimeError, - "invalid option -\\x%02X (-h will show valid options)", - (int)(unsigned char)*s); - } } goto switch_end; Index: error.c =================================================================== --- error.c (revision 66752) +++ error.c (revision 66753) @@ -1675,7 +1675,6 @@ name_err_mesg_to_str(VALUE obj) https://github.com/ruby/ruby/blob/trunk/error.c#L1675 d = rb_any_to_s(obj); } singleton = (RSTRING_LEN(d) > 0 && RSTRING_PTR(d)[0] == '#'); - d = QUOTE(d); break; } if (!singleton) { @@ -1685,7 +1684,7 @@ name_err_mesg_to_str(VALUE obj) https://github.com/ruby/ruby/blob/trunk/error.c#L1684 else { c = s = FAKE_CSTR(&s_str, ""); } - args[0] = QUOTE(rb_obj_as_string(ptr[NAME_ERR_MESG__NAME])); + args[0] = rb_obj_as_string(ptr[NAME_ERR_MESG__NAME]); args[1] = d; args[2] = s; args[3] = c; -- ML: ruby-changes@q... Info: http://www.atdot.net/~ko1/quickml/