Redirect to RUBY_BUGREPORT_PATH file
This commit is contained in:
parent
85984a53e8
commit
89b3c111ff
180
error.c
180
error.c
@ -622,18 +622,187 @@ rb_bug_reporter_add(void (*func)(FILE *, void *), void *data)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* returns true if x can not be used as file name */
|
||||
static bool
|
||||
path_sep_p(char x)
|
||||
{
|
||||
#if defined __CYGWIN__ || defined DOSISH
|
||||
# define PATH_SEP_ENCODING 1
|
||||
// Assume that "/" is only the first byte in any encoding.
|
||||
if (x == ':') return true; // drive letter or ADS
|
||||
if (x == '\\') return true;
|
||||
#endif
|
||||
return x == '/';
|
||||
}
|
||||
|
||||
struct path_string {
|
||||
const char *ptr;
|
||||
size_t len;
|
||||
};
|
||||
|
||||
static const char PATHSEP_REPLACE = '!';
|
||||
|
||||
static char *
|
||||
append_pathname(char *p, const char *pe, VALUE str)
|
||||
{
|
||||
#ifdef PATH_SEP_ENCODING
|
||||
rb_encoding *enc = rb_enc_get(str);
|
||||
#endif
|
||||
const char *s = RSTRING_PTR(str);
|
||||
const char *const se = s + RSTRING_LEN(str);
|
||||
char c;
|
||||
|
||||
--pe; // for terminator
|
||||
|
||||
while (p < pe && s < se && (c = *s) != '\0') {
|
||||
if (c == '.') {
|
||||
if (s == se || !*s) break; // chomp "." basename
|
||||
if (path_sep_p(s[1])) goto skipsep; // skip "./"
|
||||
}
|
||||
else if (path_sep_p(c)) {
|
||||
// squeeze successive separators
|
||||
*p++ = PATHSEP_REPLACE;
|
||||
skipsep:
|
||||
while (++s < se && path_sep_p(*s));
|
||||
continue;
|
||||
}
|
||||
const char *const ss = s;
|
||||
while (p < pe && s < se && *s && !path_sep_p(*s)) {
|
||||
#ifdef PATH_SEP_ENCODING
|
||||
int n = rb_enc_mbclen(s, se, enc);
|
||||
#else
|
||||
const int n = 1;
|
||||
#endif
|
||||
p += n;
|
||||
s += n;
|
||||
}
|
||||
if (s > ss) memcpy(p - (s - ss), ss, s - ss);
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static char *
|
||||
append_basename(char *p, const char *pe, struct path_string *path, VALUE str)
|
||||
{
|
||||
if (!path->ptr) {
|
||||
#ifdef PATH_SEP_ENCODING
|
||||
rb_encoding *enc = rb_enc_get(str);
|
||||
#endif
|
||||
const char *const b = RSTRING_PTR(str), *const e = RSTRING_END(str), *p = e;
|
||||
|
||||
while (p > b) {
|
||||
if (path_sep_p(p[-1])) {
|
||||
#ifdef PATH_SEP_ENCODING
|
||||
const char *t = rb_enc_prev_char(b, p, e, enc);
|
||||
if (t == p-1) break;
|
||||
p = t;
|
||||
#else
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
--p;
|
||||
}
|
||||
}
|
||||
|
||||
path->ptr = p;
|
||||
path->len = e - p;
|
||||
}
|
||||
size_t n = path->len;
|
||||
if (p + n > pe) n = pe - p;
|
||||
memcpy(p, path->ptr, n);
|
||||
return p + n;
|
||||
}
|
||||
|
||||
static void
|
||||
finish_report(FILE *out)
|
||||
{
|
||||
if (out != stdout && out != stderr) fclose(out);
|
||||
}
|
||||
|
||||
/*
|
||||
* Open a bug report file to write. The `RUBY_BUGREPORT_PATH`
|
||||
* environment variable can be set to define a template that is used
|
||||
* to name bug report files. The template can contain % specifiers
|
||||
* which are substituted by the following values when a bug report
|
||||
* file is created:
|
||||
*
|
||||
* %% A single % character.
|
||||
* %e The base name of the executable filename.
|
||||
* %E Pathname of executable, with slashes ('/') replaced by
|
||||
* exclamation marks ('!').
|
||||
* %f Similar to %e with the main script filename.
|
||||
* %F Similar to %E with the main script filename.
|
||||
* %p PID of dumped process in decimal.
|
||||
* %t Time of dump, expressed as seconds since the Epoch,
|
||||
* 1970-01-01 00:00:00 +0000 (UTC).
|
||||
*/
|
||||
static FILE *
|
||||
open_report_path(const char *template, char *buf, size_t size)
|
||||
{
|
||||
if (template && *template) {
|
||||
char *p = buf;
|
||||
char *end = buf + size;
|
||||
rb_pid_t pid = 0;
|
||||
struct path_string exe = {0};
|
||||
struct path_string script = {0};
|
||||
time_t t = 0;
|
||||
while (p < end-1 && *template) {
|
||||
char c = *template++;
|
||||
if (c == '%') {
|
||||
switch (c = *template++) {
|
||||
case 'e':
|
||||
p = append_basename(p, end, &exe, rb_argv0);
|
||||
continue;
|
||||
case 'E':
|
||||
p = append_pathname(p, end, rb_argv0);
|
||||
continue;
|
||||
case 'f':
|
||||
p = append_basename(p, end, &script, GET_VM()->orig_progname);
|
||||
continue;
|
||||
case 'F':
|
||||
p = append_pathname(p, end, GET_VM()->orig_progname);
|
||||
continue;
|
||||
case 'p':
|
||||
if (!pid) pid = getpid();
|
||||
snprintf(p, end-p, "%" PRI_PIDT_PREFIX "d", pid);
|
||||
p += strlen(p);
|
||||
continue;
|
||||
case 't':
|
||||
if (!t) t = time(NULL);
|
||||
snprintf(p, end-p, "%" PRI_TIMET_PREFIX "d", t);
|
||||
p += strlen(p);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*p++ = c;
|
||||
}
|
||||
*p = '\0';
|
||||
if (0) fprintf(stderr, "RUBY_BUGREPORT_PATH=%s\n", buf);
|
||||
return fopen(buf, "w");
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* SIGSEGV handler might have a very small stack. Thus we need to use it carefully. */
|
||||
#define REPORT_BUG_BUFSIZ 256
|
||||
static FILE *
|
||||
bug_report_file(const char *file, int line)
|
||||
{
|
||||
char buf[REPORT_BUG_BUFSIZ];
|
||||
FILE *out = stderr;
|
||||
FILE *out = open_report_path(getenv("RUBY_BUGREPORT_PATH"), buf, sizeof(buf));
|
||||
int len = err_position_0(buf, sizeof(buf), file, line);
|
||||
|
||||
if ((ssize_t)fwrite(buf, 1, len, out) == (ssize_t)len ||
|
||||
(ssize_t)fwrite(buf, 1, len, (out = stdout)) == (ssize_t)len) {
|
||||
return out;
|
||||
if (out) {
|
||||
if ((ssize_t)fwrite(buf, 1, len, out) == (ssize_t)len) return out;
|
||||
fclose(out);
|
||||
}
|
||||
if ((ssize_t)fwrite(buf, 1, len, stderr) == (ssize_t)len) {
|
||||
return stderr;
|
||||
}
|
||||
if ((ssize_t)fwrite(buf, 1, len, stdout) == (ssize_t)len) {
|
||||
return stdout;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
@ -751,6 +920,7 @@ bug_report_end(FILE *out)
|
||||
}
|
||||
}
|
||||
postscript_dump(out);
|
||||
finish_report(out);
|
||||
}
|
||||
|
||||
#define report_bug(file, line, fmt, ctx) do { \
|
||||
@ -763,7 +933,7 @@ bug_report_end(FILE *out)
|
||||
} while (0) \
|
||||
|
||||
#define report_bug_valist(file, line, fmt, ctx, args) do { \
|
||||
FILE *out = bug_report_file(file, line); \
|
||||
FILE *out = bug_report_file(file, line); \
|
||||
if (out) { \
|
||||
bug_report_begin_valist(out, fmt, args); \
|
||||
rb_vm_bugreport(ctx, out); \
|
||||
|
@ -793,29 +793,32 @@ class TestRubyOptions < Test::Unit::TestCase
|
||||
)?
|
||||
)x,
|
||||
]
|
||||
|
||||
KILL_SELF = "Process.kill :SEGV, $$"
|
||||
end
|
||||
|
||||
def assert_segv(args, message=nil)
|
||||
def assert_segv(args, message=nil, list: SEGVTest::ExpectedStderrList, **opt)
|
||||
# We want YJIT to be enabled in the subprocess if it's enabled for us
|
||||
# so that the Ruby description matches.
|
||||
env = Hash === args.first ? args.shift : {}
|
||||
args.unshift("--yjit") if self.class.yjit_enabled?
|
||||
args.unshift({'RUBY_ON_BUG' => nil})
|
||||
env.update({'RUBY_ON_BUG' => nil})
|
||||
args.unshift(env)
|
||||
|
||||
test_stdin = ""
|
||||
opt = SEGVTest::ExecOptions.dup
|
||||
list = SEGVTest::ExpectedStderrList
|
||||
|
||||
assert_in_out_err(args, test_stdin, //, list, encoding: "ASCII-8BIT", **opt)
|
||||
assert_in_out_err(args, test_stdin, //, list, encoding: "ASCII-8BIT",
|
||||
**SEGVTest::ExecOptions, **opt)
|
||||
end
|
||||
|
||||
def test_segv_test
|
||||
assert_segv(["--disable-gems", "-e", "Process.kill :SEGV, $$"])
|
||||
assert_segv(["--disable-gems", "-e", SEGVTest::KILL_SELF])
|
||||
end
|
||||
|
||||
def test_segv_loaded_features
|
||||
bug7402 = '[ruby-core:49573]'
|
||||
|
||||
status = assert_segv(['-e', 'END {Process.kill :SEGV, $$}',
|
||||
status = assert_segv(['-e', "END {#{SEGVTest::KILL_SELF}}",
|
||||
'-e', 'class Bogus; def to_str; exit true; end; end',
|
||||
'-e', '$".clear',
|
||||
'-e', '$".unshift Bogus.new',
|
||||
@ -829,10 +832,54 @@ class TestRubyOptions < Test::Unit::TestCase
|
||||
Tempfile.create(["test_ruby_test_bug7597", ".rb"]) {|t|
|
||||
t.write "f" * 100
|
||||
t.flush
|
||||
assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; Process.kill :SEGV, $$", t.path], bug7597)
|
||||
assert_segv(["--disable-gems", "-e", "$0=ARGV[0]; #{SEGVTest::KILL_SELF}", t.path], bug7597)
|
||||
}
|
||||
end
|
||||
|
||||
def assert_bugreport_path(path, cmd = nil)
|
||||
Dir.mktmpdir("ruby_bugreport") do |dir|
|
||||
list = SEGVTest::ExpectedStderrList
|
||||
if cmd
|
||||
FileUtils.mkpath(File.join(dir, File.dirname(cmd)))
|
||||
File.write(File.join(dir, cmd), SEGVTest::KILL_SELF+"\n")
|
||||
c = Regexp.quote(cmd)
|
||||
list = list.map {|re| Regexp.new(re.source.gsub(/-e/) {c}, re.options)}
|
||||
else
|
||||
cmd = ['-e', SEGVTest::KILL_SELF]
|
||||
end
|
||||
status = assert_segv([{"RUBY_BUGREPORT_PATH"=>path}, *cmd], list: [], chdir: dir)
|
||||
reports = Dir.glob("*.log", File::FNM_DOTMATCH, base: dir)
|
||||
assert_equal(1, reports.size)
|
||||
assert_pattern_list(list, File.read(File.join(dir, reports.first)))
|
||||
break status, reports.first
|
||||
end
|
||||
end
|
||||
|
||||
def test_bugreport_path
|
||||
assert_bugreport_path("%e.%f.%p.log") do |status, report|
|
||||
assert_equal("#{File.basename(EnvUtil.rubybin)}.-e.#{status.pid}.log", report)
|
||||
end
|
||||
end
|
||||
|
||||
def test_bugreport_path_script
|
||||
assert_bugreport_path("%e.%f.%p.log", "bug.rb") do |status, report|
|
||||
assert_equal("#{File.basename(EnvUtil.rubybin)}.bug.rb.#{status.pid}.log", report)
|
||||
end
|
||||
end
|
||||
|
||||
def test_bugreport_path_executable_path
|
||||
omit if EnvUtil.rubybin.size > 245
|
||||
assert_bugreport_path("%E.%p.log") do |status, report|
|
||||
assert_equal("#{EnvUtil.rubybin.tr('/', '!')}.#{status.pid}.log", report)
|
||||
end
|
||||
end
|
||||
|
||||
def test_bugreport_path_script_path
|
||||
assert_bugreport_path("%F.%p.log", "test/bug.rb") do |status, report|
|
||||
assert_equal("test!bug.rb.#{status.pid}.log", report)
|
||||
end
|
||||
end
|
||||
|
||||
def test_DATA
|
||||
Tempfile.create(["test_ruby_test_rubyoption", ".rb"]) {|t|
|
||||
t.puts "puts DATA.read.inspect"
|
||||
|
Loading…
x
Reference in New Issue
Block a user