2012-11-28 21:58:34 -05:00
|
|
|
# -*- coding: us-ascii -*-
|
2017-06-20 07:10:37 -04:00
|
|
|
# frozen_string_literal: true
|
2008-04-23 11:22:13 -04:00
|
|
|
require "open3"
|
|
|
|
require "timeout"
|
2014-07-07 04:42:57 -04:00
|
|
|
require_relative "find_executable"
|
2017-02-19 21:16:22 -05:00
|
|
|
begin
|
|
|
|
require 'rbconfig'
|
|
|
|
rescue LoadError
|
|
|
|
end
|
2017-03-01 23:54:19 -05:00
|
|
|
begin
|
|
|
|
require "rbconfig/sizeof"
|
|
|
|
rescue LoadError
|
2016-05-01 11:16:17 -04:00
|
|
|
end
|
2008-04-23 11:22:13 -04:00
|
|
|
|
2003-10-04 22:56:42 -04:00
|
|
|
module EnvUtil
|
|
|
|
def rubybin
|
2004-05-19 10:45:49 -04:00
|
|
|
if ruby = ENV["RUBY"]
|
|
|
|
return ruby
|
|
|
|
end
|
2005-12-29 03:05:26 -05:00
|
|
|
ruby = "ruby"
|
2013-12-23 01:52:35 -05:00
|
|
|
exeext = RbConfig::CONFIG["EXEEXT"]
|
|
|
|
rubyexe = (ruby + exeext if exeext and !exeext.empty?)
|
2003-10-13 09:05:24 -04:00
|
|
|
3.times do
|
2006-02-03 04:15:42 -05:00
|
|
|
if File.exist? ruby and File.executable? ruby and !File.directory? ruby
|
|
|
|
return File.expand_path(ruby)
|
|
|
|
end
|
2013-12-23 01:52:35 -05:00
|
|
|
if rubyexe and File.exist? rubyexe and File.executable? rubyexe
|
2008-05-12 08:35:37 -04:00
|
|
|
return File.expand_path(rubyexe)
|
2003-10-13 09:05:24 -04:00
|
|
|
end
|
2005-12-29 03:05:26 -05:00
|
|
|
ruby = File.join("..", ruby)
|
2003-10-13 10:59:25 -04:00
|
|
|
end
|
2010-01-12 02:41:40 -05:00
|
|
|
if defined?(RbConfig.ruby)
|
2009-12-31 10:00:04 -05:00
|
|
|
RbConfig.ruby
|
2010-01-12 02:41:40 -05:00
|
|
|
else
|
2003-10-13 10:59:25 -04:00
|
|
|
"ruby"
|
2003-10-04 22:56:42 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
module_function :rubybin
|
2008-04-23 11:22:13 -04:00
|
|
|
|
|
|
|
LANG_ENVS = %w"LANG LC_ALL LC_CTYPE"
|
2009-11-19 17:47:53 -05:00
|
|
|
|
2015-03-02 01:44:22 -05:00
|
|
|
DEFAULT_SIGNALS = Signal.list
|
|
|
|
DEFAULT_SIGNALS.delete("TERM") if /mswin|mingw/ =~ RUBY_PLATFORM
|
|
|
|
|
2017-11-12 20:58:18 -05:00
|
|
|
RUBYLIB = ENV["RUBYLIB"]
|
|
|
|
|
2016-12-23 19:51:01 -05:00
|
|
|
class << self
|
2019-06-10 22:03:55 -04:00
|
|
|
attr_accessor :timeout_scale
|
2017-12-12 18:41:50 -05:00
|
|
|
attr_reader :original_internal_encoding, :original_external_encoding,
|
2020-08-31 01:58:31 -04:00
|
|
|
:original_verbose, :original_warning
|
2017-12-12 18:41:50 -05:00
|
|
|
|
|
|
|
def capture_global_values
|
|
|
|
@original_internal_encoding = Encoding.default_internal
|
|
|
|
@original_external_encoding = Encoding.default_external
|
|
|
|
@original_verbose = $VERBOSE
|
2020-12-23 02:54:54 -05:00
|
|
|
@original_warning = defined?(Warning.[]) ? %i[deprecated experimental].to_h {|i| [i, Warning[i]]} : nil
|
2017-12-12 18:41:50 -05:00
|
|
|
end
|
2016-12-23 19:51:01 -05:00
|
|
|
end
|
|
|
|
|
2017-05-25 19:43:33 -04:00
|
|
|
def apply_timeout_scale(t)
|
2019-06-10 22:03:55 -04:00
|
|
|
if scale = EnvUtil.timeout_scale
|
2017-05-25 19:43:33 -04:00
|
|
|
t * scale
|
|
|
|
else
|
|
|
|
t
|
|
|
|
end
|
|
|
|
end
|
|
|
|
module_function :apply_timeout_scale
|
|
|
|
|
2019-05-26 04:19:46 -04:00
|
|
|
def timeout(sec, klass = nil, message = nil, &blk)
|
|
|
|
return yield(sec) if sec == nil or sec.zero?
|
|
|
|
sec = apply_timeout_scale(sec)
|
|
|
|
Timeout.timeout(sec, klass, message, &blk)
|
|
|
|
end
|
|
|
|
module_function :timeout
|
|
|
|
|
2020-06-02 04:18:09 -04:00
|
|
|
def terminate(pid, signal = :TERM, pgroup = nil, reprieve = 1)
|
2019-01-10 20:18:59 -05:00
|
|
|
reprieve = apply_timeout_scale(reprieve) if reprieve
|
|
|
|
|
|
|
|
signals = Array(signal).select do |sig|
|
|
|
|
DEFAULT_SIGNALS[sig.to_s] or
|
|
|
|
DEFAULT_SIGNALS[Signal.signame(sig)] rescue false
|
|
|
|
end
|
2020-06-02 04:18:09 -04:00
|
|
|
signals |= [:ABRT, :KILL]
|
2019-01-10 20:18:59 -05:00
|
|
|
case pgroup
|
|
|
|
when 0, true
|
|
|
|
pgroup = -pid
|
|
|
|
when nil, false
|
|
|
|
pgroup = pid
|
|
|
|
end
|
2020-06-02 04:18:09 -04:00
|
|
|
|
2020-06-03 04:23:04 -04:00
|
|
|
lldb = true if /darwin/ =~ RUBY_PLATFORM
|
2020-06-02 04:18:09 -04:00
|
|
|
|
2019-01-10 20:18:59 -05:00
|
|
|
while signal = signals.shift
|
2020-06-03 04:23:04 -04:00
|
|
|
|
|
|
|
if lldb and [:ABRT, :KILL].include?(signal)
|
|
|
|
lldb = false
|
|
|
|
# sudo -n: --non-interactive
|
|
|
|
# lldb -p: attach
|
|
|
|
# -o: run command
|
|
|
|
system(*%W[sudo -n lldb -p #{pid} --batch -o bt\ all -o call\ rb_vmdebug_stack_dump_all_threads() -o quit])
|
|
|
|
true
|
|
|
|
end
|
|
|
|
|
2019-01-10 20:18:59 -05:00
|
|
|
begin
|
|
|
|
Process.kill signal, pgroup
|
|
|
|
rescue Errno::EINVAL
|
|
|
|
next
|
|
|
|
rescue Errno::ESRCH
|
|
|
|
break
|
|
|
|
end
|
|
|
|
if signals.empty? or !reprieve
|
|
|
|
Process.wait(pid)
|
|
|
|
else
|
|
|
|
begin
|
|
|
|
Timeout.timeout(reprieve) {Process.wait(pid)}
|
|
|
|
rescue Timeout::Error
|
2020-06-03 04:23:04 -04:00
|
|
|
else
|
|
|
|
break
|
2019-01-10 20:18:59 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
$?
|
|
|
|
end
|
|
|
|
module_function :terminate
|
|
|
|
|
2013-06-20 00:08:41 -04:00
|
|
|
def invoke_ruby(args, stdin_data = "", capture_stdout = false, capture_stderr = false,
|
2015-03-02 01:44:34 -05:00
|
|
|
encoding: nil, timeout: 10, reprieve: 1, timeout_error: Timeout::Error,
|
2014-04-10 21:31:00 -04:00
|
|
|
stdout_filter: nil, stderr_filter: nil,
|
2015-03-02 01:44:22 -05:00
|
|
|
signal: :TERM,
|
2017-10-14 22:20:20 -04:00
|
|
|
rubybin: EnvUtil.rubybin, precommand: nil,
|
2014-04-10 21:31:00 -04:00
|
|
|
**opt)
|
2017-05-25 19:43:33 -04:00
|
|
|
timeout = apply_timeout_scale(timeout)
|
|
|
|
|
2010-07-01 08:37:58 -04:00
|
|
|
in_c, in_p = IO.pipe
|
|
|
|
out_p, out_c = IO.pipe if capture_stdout
|
2010-08-07 06:53:13 -04:00
|
|
|
err_p, err_c = IO.pipe if capture_stderr && capture_stderr != :merge_to_stdout
|
2010-07-01 08:37:58 -04:00
|
|
|
opt[:in] = in_c
|
|
|
|
opt[:out] = out_c if capture_stdout
|
2010-08-07 06:53:13 -04:00
|
|
|
opt[:err] = capture_stderr == :merge_to_stdout ? out_c : err_c if capture_stderr
|
2013-06-20 00:08:41 -04:00
|
|
|
if encoding
|
|
|
|
out_p.set_encoding(encoding) if out_p
|
|
|
|
err_p.set_encoding(encoding) if err_p
|
2010-07-01 08:37:58 -04:00
|
|
|
end
|
2008-04-23 11:22:13 -04:00
|
|
|
c = "C"
|
2010-07-01 08:37:58 -04:00
|
|
|
child_env = {}
|
|
|
|
LANG_ENVS.each {|lc| child_env[lc] = c}
|
|
|
|
if Array === args and Hash === args.first
|
|
|
|
child_env.update(args.shift)
|
2008-04-23 11:22:13 -04:00
|
|
|
end
|
2017-11-12 20:58:18 -05:00
|
|
|
if RUBYLIB and lib = child_env["RUBYLIB"]
|
|
|
|
child_env["RUBYLIB"] = [lib, RUBYLIB].join(File::PATH_SEPARATOR)
|
|
|
|
end
|
2020-09-28 11:28:10 -04:00
|
|
|
child_env['ASAN_OPTIONS'] = ENV['ASAN_OPTIONS'] if ENV['ASAN_OPTIONS']
|
2010-06-05 09:47:09 -04:00
|
|
|
args = [args] if args.kind_of?(String)
|
2017-10-14 22:20:20 -04:00
|
|
|
pid = spawn(child_env, *precommand, rubybin, *args, **opt)
|
2010-07-01 08:37:58 -04:00
|
|
|
in_c.close
|
2020-04-16 10:44:24 -04:00
|
|
|
out_c&.close
|
|
|
|
out_c = nil
|
|
|
|
err_c&.close
|
|
|
|
err_c = nil
|
2010-07-01 08:37:58 -04:00
|
|
|
if block_given?
|
2012-01-31 00:27:27 -05:00
|
|
|
return yield in_p, out_p, err_p, pid
|
2010-07-01 08:37:58 -04:00
|
|
|
else
|
2009-11-19 17:47:53 -05:00
|
|
|
th_stdout = Thread.new { out_p.read } if capture_stdout
|
2010-08-07 06:53:13 -04:00
|
|
|
th_stderr = Thread.new { err_p.read } if capture_stderr && capture_stderr != :merge_to_stdout
|
2013-06-17 06:24:48 -04:00
|
|
|
in_p.write stdin_data.to_str unless stdin_data.empty?
|
2010-06-24 20:57:03 -04:00
|
|
|
in_p.close
|
2015-08-11 23:50:59 -04:00
|
|
|
if (!th_stdout || th_stdout.join(timeout)) && (!th_stderr || th_stderr.join(timeout))
|
|
|
|
timeout_error = nil
|
|
|
|
else
|
2019-01-10 20:18:59 -05:00
|
|
|
status = terminate(pid, signal, opt[:pgroup], reprieve)
|
2019-08-04 21:47:36 -04:00
|
|
|
terminated = Time.now
|
2009-11-19 17:47:53 -05:00
|
|
|
end
|
2015-03-02 01:44:34 -05:00
|
|
|
stdout = th_stdout.value if capture_stdout
|
|
|
|
stderr = th_stderr.value if capture_stderr && capture_stderr != :merge_to_stdout
|
2009-11-19 17:47:53 -05:00
|
|
|
out_p.close if capture_stdout
|
2010-08-07 06:53:13 -04:00
|
|
|
err_p.close if capture_stderr && capture_stderr != :merge_to_stdout
|
2015-03-02 01:44:34 -05:00
|
|
|
status ||= Process.wait2(pid)[1]
|
2014-04-10 21:31:00 -04:00
|
|
|
stdout = stdout_filter.call(stdout) if stdout_filter
|
|
|
|
stderr = stderr_filter.call(stderr) if stderr_filter
|
2015-08-11 23:50:59 -04:00
|
|
|
if timeout_error
|
|
|
|
bt = caller_locations
|
2018-01-05 15:36:46 -05:00
|
|
|
msg = "execution of #{bt.shift.label} expired timeout (#{timeout} sec)"
|
2019-08-04 21:47:36 -04:00
|
|
|
msg = failure_description(status, terminated, msg, [stdout, stderr].join("\n"))
|
2015-08-11 23:50:59 -04:00
|
|
|
raise timeout_error, msg, bt.map(&:to_s)
|
|
|
|
end
|
2010-07-01 08:37:58 -04:00
|
|
|
return stdout, stderr, status
|
|
|
|
end
|
|
|
|
ensure
|
2012-09-04 08:45:31 -04:00
|
|
|
[th_stdout, th_stderr].each do |th|
|
|
|
|
th.kill if th
|
|
|
|
end
|
2010-07-01 08:37:58 -04:00
|
|
|
[in_c, in_p, out_c, out_p, err_c, err_p].each do |io|
|
2016-12-05 07:35:03 -05:00
|
|
|
io&.close
|
2010-07-01 08:37:58 -04:00
|
|
|
end
|
|
|
|
[th_stdout, th_stderr].each do |th|
|
2012-09-04 08:45:31 -04:00
|
|
|
th.join if th
|
2009-11-19 17:47:53 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
module_function :invoke_ruby
|
2009-09-29 02:26:46 -04:00
|
|
|
|
|
|
|
def verbose_warning
|
2017-06-20 07:10:37 -04:00
|
|
|
class << (stderr = "".dup)
|
2017-10-23 10:05:07 -04:00
|
|
|
alias write concat
|
2017-12-17 23:23:16 -05:00
|
|
|
def flush; end
|
2009-09-29 02:26:46 -04:00
|
|
|
end
|
2017-12-12 18:41:50 -05:00
|
|
|
stderr, $stderr = $stderr, stderr
|
|
|
|
$VERBOSE = true
|
2009-09-29 02:26:46 -04:00
|
|
|
yield stderr
|
2012-03-16 04:38:24 -04:00
|
|
|
return $stderr
|
2009-09-29 02:26:46 -04:00
|
|
|
ensure
|
2017-12-12 18:41:50 -05:00
|
|
|
stderr, $stderr = $stderr, stderr
|
|
|
|
$VERBOSE = EnvUtil.original_verbose
|
2020-12-23 02:54:54 -05:00
|
|
|
EnvUtil.original_warning&.each {|i, v| Warning[i] = v}
|
2009-09-29 02:26:46 -04:00
|
|
|
end
|
|
|
|
module_function :verbose_warning
|
2010-06-05 21:44:38 -04:00
|
|
|
|
2014-06-01 18:31:30 -04:00
|
|
|
def default_warning
|
2017-12-12 18:41:50 -05:00
|
|
|
$VERBOSE = false
|
2014-06-01 18:31:30 -04:00
|
|
|
yield
|
|
|
|
ensure
|
2017-12-12 18:41:50 -05:00
|
|
|
$VERBOSE = EnvUtil.original_verbose
|
2014-06-01 18:31:30 -04:00
|
|
|
end
|
|
|
|
module_function :default_warning
|
|
|
|
|
2010-07-24 16:26:34 -04:00
|
|
|
def suppress_warning
|
2017-12-12 18:41:50 -05:00
|
|
|
$VERBOSE = nil
|
2010-07-24 16:26:34 -04:00
|
|
|
yield
|
|
|
|
ensure
|
2017-12-12 18:41:50 -05:00
|
|
|
$VERBOSE = EnvUtil.original_verbose
|
2010-07-24 16:26:34 -04:00
|
|
|
end
|
|
|
|
module_function :suppress_warning
|
|
|
|
|
2013-11-09 11:46:17 -05:00
|
|
|
def under_gc_stress(stress = true)
|
|
|
|
stress, GC.stress = GC.stress, stress
|
2010-06-05 21:44:38 -04:00
|
|
|
yield
|
|
|
|
ensure
|
|
|
|
GC.stress = stress
|
|
|
|
end
|
|
|
|
module_function :under_gc_stress
|
2013-03-11 04:29:46 -04:00
|
|
|
|
|
|
|
def with_default_external(enc)
|
2017-12-12 18:41:50 -05:00
|
|
|
suppress_warning { Encoding.default_external = enc }
|
2013-03-11 04:29:46 -04:00
|
|
|
yield
|
|
|
|
ensure
|
2017-12-12 18:41:50 -05:00
|
|
|
suppress_warning { Encoding.default_external = EnvUtil.original_external_encoding }
|
2013-03-11 04:29:46 -04:00
|
|
|
end
|
|
|
|
module_function :with_default_external
|
|
|
|
|
|
|
|
def with_default_internal(enc)
|
2017-12-12 18:41:50 -05:00
|
|
|
suppress_warning { Encoding.default_internal = enc }
|
2013-03-11 04:29:46 -04:00
|
|
|
yield
|
|
|
|
ensure
|
2017-12-12 18:41:50 -05:00
|
|
|
suppress_warning { Encoding.default_internal = EnvUtil.original_internal_encoding }
|
2013-03-11 04:29:46 -04:00
|
|
|
end
|
|
|
|
module_function :with_default_internal
|
2013-11-15 17:14:09 -05:00
|
|
|
|
2014-02-25 23:26:09 -05:00
|
|
|
def labeled_module(name, &block)
|
|
|
|
Module.new do
|
2020-05-05 00:53:44 -04:00
|
|
|
singleton_class.class_eval {
|
|
|
|
define_method(:to_s) {name}
|
|
|
|
alias inspect to_s
|
|
|
|
alias name to_s
|
|
|
|
}
|
2014-02-25 23:26:09 -05:00
|
|
|
class_eval(&block) if block
|
|
|
|
end
|
|
|
|
end
|
|
|
|
module_function :labeled_module
|
|
|
|
|
|
|
|
def labeled_class(name, superclass = Object, &block)
|
|
|
|
Class.new(superclass) do
|
2020-05-05 00:53:44 -04:00
|
|
|
singleton_class.class_eval {
|
|
|
|
define_method(:to_s) {name}
|
|
|
|
alias inspect to_s
|
|
|
|
alias name to_s
|
|
|
|
}
|
2014-02-25 23:26:09 -05:00
|
|
|
class_eval(&block) if block
|
|
|
|
end
|
|
|
|
end
|
|
|
|
module_function :labeled_class
|
|
|
|
|
2013-11-15 17:14:09 -05:00
|
|
|
if /darwin/ =~ RUBY_PLATFORM
|
|
|
|
DIAGNOSTIC_REPORTS_PATH = File.expand_path("~/Library/Logs/DiagnosticReports")
|
|
|
|
DIAGNOSTIC_REPORTS_TIMEFORMAT = '%Y-%m-%d-%H%M%S'
|
2017-02-19 21:16:22 -05:00
|
|
|
@ruby_install_name = RbConfig::CONFIG['RUBY_INSTALL_NAME']
|
|
|
|
|
|
|
|
def self.diagnostic_reports(signame, pid, now)
|
2015-03-03 19:24:06 -05:00
|
|
|
return unless %w[ABRT QUIT SEGV ILL TRAP].include?(signame)
|
2017-03-14 00:16:02 -04:00
|
|
|
cmd = File.basename(rubybin)
|
|
|
|
cmd = @ruby_install_name if "ruby-runner#{RbConfig::CONFIG["EXEEXT"]}" == cmd
|
2013-11-15 17:14:09 -05:00
|
|
|
path = DIAGNOSTIC_REPORTS_PATH
|
|
|
|
timeformat = DIAGNOSTIC_REPORTS_TIMEFORMAT
|
|
|
|
pat = "#{path}/#{cmd}_#{now.strftime(timeformat)}[-_]*.crash"
|
|
|
|
first = true
|
2013-12-14 17:34:47 -05:00
|
|
|
30.times do
|
|
|
|
first ? (first = false) : sleep(0.1)
|
2013-11-15 17:14:09 -05:00
|
|
|
Dir.glob(pat) do |name|
|
|
|
|
log = File.read(name) rescue next
|
|
|
|
if /\AProcess:\s+#{cmd} \[#{pid}\]$/ =~ log
|
|
|
|
File.unlink(name)
|
2014-06-10 10:44:26 -04:00
|
|
|
File.unlink("#{path}/.#{File.basename(name)}.plist") rescue nil
|
2013-11-15 17:14:09 -05:00
|
|
|
return log
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
nil
|
|
|
|
end
|
|
|
|
else
|
2017-02-19 21:16:22 -05:00
|
|
|
def self.diagnostic_reports(signame, pid, now)
|
2013-11-15 17:14:09 -05:00
|
|
|
end
|
|
|
|
end
|
2015-05-27 04:02:20 -04:00
|
|
|
|
2019-08-04 21:47:36 -04:00
|
|
|
def self.failure_description(status, now, message = "", out = "")
|
|
|
|
pid = status.pid
|
|
|
|
if signo = status.termsig
|
|
|
|
signame = Signal.signame(signo)
|
|
|
|
sigdesc = "signal #{signo}"
|
|
|
|
end
|
|
|
|
log = diagnostic_reports(signame, pid, now)
|
|
|
|
if signame
|
|
|
|
sigdesc = "SIG#{signame} (#{sigdesc})"
|
|
|
|
end
|
|
|
|
if status.coredump?
|
|
|
|
sigdesc = "#{sigdesc} (core dumped)"
|
|
|
|
end
|
|
|
|
full_message = ''.dup
|
|
|
|
message = message.call if Proc === message
|
|
|
|
if message and !message.empty?
|
|
|
|
full_message << message << "\n"
|
|
|
|
end
|
|
|
|
full_message << "pid #{pid}"
|
|
|
|
full_message << " exit #{status.exitstatus}" if status.exited?
|
|
|
|
full_message << " killed by #{sigdesc}" if sigdesc
|
|
|
|
if out and !out.empty?
|
|
|
|
full_message << "\n" << out.b.gsub(/^/, '| ')
|
|
|
|
full_message.sub!(/(?<!\n)\z/, "\n")
|
|
|
|
end
|
|
|
|
if log
|
|
|
|
full_message << "Diagnostic reports:\n" << log.b.gsub(/^/, '| ')
|
|
|
|
end
|
|
|
|
full_message
|
|
|
|
end
|
|
|
|
|
2015-05-27 04:02:20 -04:00
|
|
|
def self.gc_stress_to_class?
|
|
|
|
unless defined?(@gc_stress_to_class)
|
|
|
|
_, _, status = invoke_ruby(["-e""exit GC.respond_to?(:add_stress_to_class)"])
|
|
|
|
@gc_stress_to_class = status.success?
|
|
|
|
end
|
|
|
|
@gc_stress_to_class
|
|
|
|
end
|
2003-10-04 22:56:42 -04:00
|
|
|
end
|
2008-05-03 07:57:55 -04:00
|
|
|
|
2017-02-19 21:16:22 -05:00
|
|
|
if defined?(RbConfig)
|
2010-01-12 02:41:40 -05:00
|
|
|
module RbConfig
|
|
|
|
@ruby = EnvUtil.rubybin
|
|
|
|
class << self
|
2010-01-25 08:33:02 -05:00
|
|
|
undef ruby if method_defined?(:ruby)
|
2010-01-12 02:41:40 -05:00
|
|
|
attr_reader :ruby
|
|
|
|
end
|
|
|
|
dir = File.dirname(ruby)
|
|
|
|
CONFIG['bindir'] = dir
|
|
|
|
end
|
|
|
|
end
|
2017-12-12 18:41:50 -05:00
|
|
|
|
|
|
|
EnvUtil.capture_global_values
|