1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

$SAFE as a process global state. [Feature #14250]

* vm_core.h (rb_vm_t): move `rb_execution_context_t::safe_level` to
  `rb_vm_t::safe_level_` because `$SAFE` is a process (VM) global state.

* vm_core.h (rb_proc_t): remove `rb_proc_t::safe_level` because `Proc`
  objects don't need to keep `$SAFE` at the creation.
  Also make `is_from_method` and `is_lambda` as 1 bit fields.

* cont.c (cont_restore_thread): no need to keep `$SAFE` for Continuation.

* eval.c (ruby_cleanup): use `rb_set_safe_level_force()` instead of access
  `vm->safe_level_` directly.

* eval_jump.c: End procs `END{}` doesn't keep `$SAFE`.

* proc.c (proc_dup): removed and introduce `rb_proc_dup` in vm.c.

* safe.c (rb_set_safe_level): don't check `$SAFE` 1 -> 0 changes.

* safe.c (safe_setter): use `rb_set_safe_level()`.

* thread.c (rb_thread_safe_level): `Thread#safe_level` returns `$SAFE`.
  It should be obsolete.

* transcode.c (load_transcoder_entry): `rb_safe_level()` only returns
  0 or 1 so that this check is not needed.

* vm.c (vm_proc_create_from_captured): don't need to keep `$SAFE` for Proc.

* vm.c (rb_proc_create): renamed to `proc_create`.

* vm.c (rb_proc_dup): moved from proc.c.

* vm.c (vm_invoke_proc): do not need to set and restore `$SAFE`
  for `Proc#call`.

* vm_eval.c (rb_eval_cmd): rename a local variable to represent clearer
  meaning.

* lib/drb/drb.rb: restore `$SAFE`.

* lib/erb.rb: restore `$SAFE`, too.

* test/lib/leakchecker.rb: check `$SAFE == 0` at the end of tests.

* test/rubygems/test_gem.rb: do not set `$SAFE = 1`.

* bootstraptest/test_proc.rb: catch up this change.

* spec/ruby/optional/capi/string_spec.rb: ditto.

* test/bigdecimal/test_bigdecimal.rb: ditto.

* test/fiddle/test_func.rb: ditto.

* test/fiddle/test_handle.rb: ditto.

* test/net/imap/test_imap_response_parser.rb: ditto.

* test/pathname/test_pathname.rb: ditto.

* test/readline/test_readline.rb: ditto.

* test/ruby/test_file.rb: ditto.

* test/ruby/test_optimization.rb: ditto.

* test/ruby/test_proc.rb: ditto.

* test/ruby/test_require.rb: ditto.

* test/ruby/test_thread.rb: ditto.

* test/rubygems/test_gem_specification.rb: ditto.

* test/test_tempfile.rb: ditto.

* test/test_tmpdir.rb: ditto.

* test/win32ole/test_win32ole.rb: ditto.

* test/win32ole/test_win32ole_event.rb: ditto.


git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@61510 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
This commit is contained in:
ko1 2017-12-28 20:09:24 +00:00
parent 67850e8a9e
commit c39bdb798d
33 changed files with 159 additions and 119 deletions

24
NEWS
View file

@ -11,3 +11,27 @@ with all sufficient information, see the ChangeLog file or Redmine
(e.g. <tt>https://bugs.ruby-lang.org/issues/$FEATURE_OR_BUG_NUMBER</tt>)
== Changes since the 2.5.0 release
=== Language changes
* $SAFE is a process global state and we can set 0 again. [Feature #14250]
=== Core classes updates (outstanding ones only)
* Proc
* Proc#call doesn't change $SAFE any more. [Feature #14250]
=== Stdlib updates (outstanding ones only)
=== Compatibility issues (excluding feature bug fixes)
=== Stdlib compatibility issues (excluding feature bug fixes)
=== C API updates
=== Supported platform changes
=== Implementation improvements
=== Miscellaneous changes

View file

@ -224,14 +224,14 @@ assert_equal %q{[[nil, []], [1, []], [1, [2]], [1, [2, 3]]]}, %q{
Proc.new{|a, *b| [a, b]}.call(1, 2, 3),
]
}
assert_equal %q{0}, %q{
assert_equal %q{1}, %q{
pr = proc{
$SAFE
}
$SAFE = 1
pr.call
}
assert_equal %q{[1, 0]}, %q{
assert_equal %q{[1, 1]}, %q{
pr = proc{
$SAFE += 1
}

1
cont.c
View file

@ -696,7 +696,6 @@ cont_restore_thread(rb_context_t *cont)
/* other members of ec */
th->ec->cfp = sec->cfp;
th->ec->safe_level = sec->safe_level;
th->ec->raised_flag = sec->raised_flag;
th->ec->tag = sec->tag;
th->ec->protect_tag = sec->protect_tag;

2
eval.c
View file

@ -175,7 +175,7 @@ ruby_cleanup(volatile int ex)
step_0: step++;
errs[1] = th->ec->errinfo;
th->ec->safe_level = 0;
rb_set_safe_level_force(0);
ruby_init_stack(&errs[STACK_UPPER(errs, 0, 1)]);
SAVE_ROOT_JMPBUF(th, ruby_finalize_0());

View file

@ -50,7 +50,6 @@ rb_f_at_exit(void)
struct end_proc_data {
void (*func) ();
VALUE data;
int safe;
struct end_proc_data *next;
};
@ -72,7 +71,6 @@ rb_set_end_proc(void (*func)(VALUE), VALUE data)
link->next = *list;
link->func = func;
link->data = data;
link->safe = rb_safe_level();
*list = link;
}
@ -104,7 +102,6 @@ exec_end_procs_chain(struct end_proc_data *volatile *procs, VALUE *errp)
*procs = link->next;
endproc = *link;
xfree(link);
rb_set_safe_level_force(endproc.safe);
(*endproc.func) (endproc.data);
*errp = errinfo;
}
@ -114,7 +111,6 @@ void
rb_exec_end_proc(void)
{
enum ruby_tag_type state;
volatile int safe = rb_safe_level();
rb_execution_context_t * volatile ec = GET_EC();
volatile VALUE errinfo = ec->errinfo;
@ -133,7 +129,6 @@ rb_exec_end_proc(void)
}
EC_POP_TAG();
rb_set_safe_level_force(safe);
ec->errinfo = errinfo;
}

View file

@ -1571,17 +1571,23 @@ module DRb
if $SAFE < @safe_level
info = Thread.current['DRb']
if @block
@result = Thread.new {
@result = Thread.new do
Thread.current['DRb'] = info
prev_safe_level = $SAFE
$SAFE = @safe_level
perform_with_block
}.value
ensure
$SAFE = prev_safe_level
end.value
else
@result = Thread.new {
@result = Thread.new do
Thread.current['DRb'] = info
prev_safe_level = $SAFE
$SAFE = @safe_level
perform_without_block
}.value
ensure
$SAFE = prev_safe_level
end.value
end
else
if @block

View file

@ -864,10 +864,13 @@ class ERB
#
def result(b=new_toplevel)
if @safe_level
proc {
proc do
prev_safe_level = $SAFE
$SAFE = @safe_level
eval(@src, b, (@filename || '(erb)'), @lineno)
}.call
ensure
$SAFE = prev_safe_level
end.call
else
eval(@src, b, (@filename || '(erb)'), @lineno)
end

25
proc.c
View file

@ -124,28 +124,11 @@ rb_obj_is_proc(VALUE proc)
}
}
VALUE rb_proc_create(VALUE klass, const struct rb_block *block,
int8_t safe_level, int8_t is_from_method, int8_t is_lambda);
/* :nodoc: */
static VALUE
proc_dup(VALUE self)
{
VALUE procval;
rb_proc_t *src;
GetProcPtr(self, src);
procval = rb_proc_create(rb_cProc, &src->block,
src->safe_level, src->is_from_method, src->is_lambda);
RB_GC_GUARD(self); /* for: body = proc_dup(body) */
return procval;
}
/* :nodoc: */
static VALUE
proc_clone(VALUE self)
{
VALUE procval = proc_dup(self);
VALUE procval = rb_proc_dup(self);
CLONESETUP(procval, self);
return procval;
}
@ -752,7 +735,7 @@ proc_new(VALUE klass, int8_t is_lambda)
return procval;
}
else {
VALUE newprocval = proc_dup(procval);
VALUE newprocval = rb_proc_dup(procval);
RBASIC_SET_CLASS(newprocval, klass);
return newprocval;
}
@ -1982,7 +1965,7 @@ rb_mod_define_method(int argc, VALUE *argv, VALUE mod)
RB_GC_GUARD(body);
}
else {
VALUE procval = proc_dup(body);
VALUE procval = rb_proc_dup(body);
if (vm_proc_iseq(procval) != NULL) {
rb_proc_t *proc;
GetProcPtr(procval, proc);
@ -3115,7 +3098,7 @@ Init_Proc(void)
rb_define_method(rb_cProc, "to_proc", proc_to_proc, 0);
rb_define_method(rb_cProc, "arity", proc_arity, 0);
rb_define_method(rb_cProc, "clone", proc_clone, 0);
rb_define_method(rb_cProc, "dup", proc_dup, 0);
rb_define_method(rb_cProc, "dup", rb_proc_dup, 0);
rb_define_method(rb_cProc, "hash", proc_hash, 0);
rb_define_method(rb_cProc, "to_s", proc_to_s, 0);
rb_define_alias(rb_cProc, "inspect", "to_s");

46
safe.c
View file

@ -34,28 +34,34 @@ ruby_safe_level_2_warning(void)
int
rb_safe_level(void)
{
return GET_EC()->safe_level;
return GET_VM()->safe_level_;
}
void
rb_set_safe_level_force(int safe)
{
GET_EC()->safe_level = safe;
GET_VM()->safe_level_ = safe;
}
void
rb_set_safe_level(int level)
{
rb_execution_context_t *ec = GET_EC();
rb_vm_t *vm = GET_VM();
if (level > ec->safe_level) {
if (level > SAFE_LEVEL_MAX) {
rb_raise(rb_eArgError, "$SAFE=2 to 4 are obsolete");
}
/* block parameters */
rb_vm_stack_to_heap(ec);
if (level > SAFE_LEVEL_MAX) {
rb_raise(rb_eArgError, "$SAFE=2 to 4 are obsolete");
}
else if (level < 0) {
rb_raise(rb_eArgError, "$SAFE should be >= 0");
}
else {
int line;
const char *path = rb_source_location_cstr(&line);
ec->safe_level = level;
if (0) fprintf(stderr, "%s:%d $SAFE %d -> %d\n",
path ? path : "-", line, vm->safe_level_, level);
vm->safe_level_ = level;
}
}
@ -68,26 +74,8 @@ safe_getter(void)
static void
safe_setter(VALUE val)
{
rb_execution_context_t *ec = GET_EC();
int current_level = ec->safe_level;
int level = NUM2INT(val);
if (level == current_level) {
return;
}
else if (level < current_level) {
rb_raise(rb_eSecurityError,
"tried to downgrade safe level from %d to %d",
current_level, level);
}
else if (level > SAFE_LEVEL_MAX) {
rb_raise(rb_eArgError, "$SAFE=2 to 4 are obsolete");
}
/* block parameters */
rb_vm_stack_to_heap(ec);
ec->safe_level = level;
rb_set_safe_level(level);
}
void

View file

@ -485,6 +485,8 @@ describe "C-API String function" do
@s.SafeStringValue("str".taint)
}.should raise_error(SecurityError)
}.join
ensure
$SAFE = 0
end
it_behaves_like :string_value_macro, :SafeStringValue

View file

@ -125,6 +125,8 @@ class TestBigDecimal < Test::Unit::TestCase
$SAFE = 1
BigDecimal('1'.taint)
}.join
ensure
$SAFE = 0
end
def test_s_ver
@ -195,6 +197,8 @@ class TestBigDecimal < Test::Unit::TestCase
$SAFE = 1
BigDecimal('1'.taint)
}.join
ensure
$SAFE = 0
end
def _test_mode(type)

View file

@ -19,6 +19,8 @@ module Fiddle
f.call("uname -rs".dup.taint)
end
}.join
ensure
$SAFE = 0
end
def test_sinf

View file

@ -15,6 +15,8 @@ module Fiddle
Fiddle::Handle.new(LIBC_SO.dup.taint)
}
end.join
ensure
$SAFE = 0
end
def test_safe_function_lookup
@ -25,6 +27,8 @@ module Fiddle
h["qsort".dup.taint]
}
end.join
ensure
$SAFE = 0
end
def test_to_i

View file

@ -15,10 +15,15 @@ class LeakChecker
check_tempfile_leak(test_name),
check_env(test_name),
check_encodings(test_name),
check_safe(test_name),
]
GC.start if leaks.any?
end
def check_safe test_name
puts "#{test_name}: $SAFE == #{$SAFE}" unless $SAFE == 0
end
def find_fds
if IO.respond_to?(:console) and (m = IO.method(:console)).arity.nonzero?
m[:close]

View file

@ -29,6 +29,8 @@ class IMAPResponseParserTest < Test::Unit::TestCase
EOF
}.call
assert_equal [:Haschildren], response.data.attr
ensure
$SAFE = 0
end
def test_flag_list_too_many_flags

View file

@ -1411,6 +1411,8 @@ class TestPathname < Test::Unit::TestCase
$SAFE = 1
assert_equal("foo/bar", File.join(Pathname.new("foo"), Pathname.new("bar").taint))
}.call
ensure
$SAFE = 0
end
def test_relative_path_from_casefold

View file

@ -53,6 +53,8 @@ class TestReadline < Test::Unit::TestCase
end
end
}.join
ensure
$SAFE = 0
end
end

View file

@ -465,6 +465,8 @@ class TestFile < Test::Unit::TestCase
(0..1).each do |level|
assert_nothing_raised(SecurityError, bug5374) {in_safe[level]}
end
ensure
$SAFE = 0
end
if /(bcc|ms|cyg)win|mingw|emx/ =~ RUBY_PLATFORM

View file

@ -677,7 +677,7 @@ class TestRubyOptimization < Test::Unit::TestCase
$SAFE = 1
b.call
end
assert_equal 0, foo{$SAFE}
assert_equal 1, foo{$SAFE}
END
end

View file

@ -160,26 +160,34 @@ class TestProc < Test::Unit::TestCase
$SAFE += 1
proc {$SAFE}
}.call
assert_equal(safe, $SAFE)
assert_equal(safe + 1, p.call)
assert_equal(safe, $SAFE)
assert_equal(safe + 1, $SAFE)
assert_equal(safe + 1, p.call)
assert_equal(safe + 1, $SAFE)
$SAFE = 0
c.class_eval {define_method(:safe, p)}
assert_equal(safe, x.safe)
assert_equal(safe, x.method(:safe).call)
assert_equal(safe, x.method(:safe).to_proc.call)
$SAFE = 0
p = proc {$SAFE += 1}
assert_equal(safe + 1, p.call)
assert_equal(safe, $SAFE)
assert_equal(safe + 1, $SAFE)
$SAFE = 0
c.class_eval {define_method(:inc, p)}
assert_equal(safe + 1, proc {x.inc; $SAFE}.call)
assert_equal(safe, $SAFE)
assert_equal(safe + 1, $SAFE)
$SAFE = 0
assert_equal(safe + 1, proc {x.method(:inc).call; $SAFE}.call)
assert_equal(safe, $SAFE)
assert_equal(safe + 1, $SAFE)
$SAFE = 0
assert_equal(safe + 1, proc {x.method(:inc).to_proc.call; $SAFE}.call)
assert_equal(safe, $SAFE)
assert_equal(safe + 1, $SAFE)
ensure
$SAFE = 0
end
def m2

View file

@ -112,6 +112,8 @@ class TestRequire < Test::Unit::TestCase
proc do |require_path|
$SAFE = 1
require(require_path)
ensure
$SAFE = 0
end
end

View file

@ -505,10 +505,10 @@ class TestThread < Test::Unit::TestCase
sleep
end
Thread.pass until ok
assert_equal(0, Thread.current.safe_level)
assert_equal(1, t.safe_level)
assert_equal($SAFE, Thread.current.safe_level)
assert_equal($SAFE, t.safe_level)
ensure
$SAFE = 0
t.kill if t
end

View file

@ -7,7 +7,7 @@ require 'pathname'
require 'tmpdir'
# TODO: push this up to test_case.rb once battle tested
$SAFE=1
$LOAD_PATH.map! do |path|
path.dup.untaint
end

View file

@ -948,6 +948,9 @@ dependencies: []
@a2.files.clear
assert_equal @a2, spec
ensure
$SAFE = 0
end
def test_self_load_escape_curly

View file

@ -38,6 +38,8 @@ class TestTempfile < Test::Unit::TestCase
assert_nothing_raised(SecurityError, bug3733) {
proc {$SAFE = 1; File.expand_path(Dir.tmpdir)}.call
}
ensure
$SAFE = 0
end
def test_saves_in_given_directory

View file

@ -20,6 +20,8 @@ class TestTmpdir < Test::Unit::TestCase
tmpdir << "foo"
assert_equal(tmpdir_org, Dir.tmpdir)
}.join
ensure
$SAFE = 0
end
def test_world_writable

View file

@ -188,6 +188,8 @@ if defined?(WIN32OLE)
th.join
}
assert_match(/insecure object creation - `Scripting.Dictionary'/, exc.message)
ensure
$SAFE = 0
end
def test_s_new_exc_host_tainted
@ -203,6 +205,8 @@ if defined?(WIN32OLE)
th.join
}
assert_match(/insecure object creation - `localhost'/, exc.message)
ensure
$SAFE = 0
end
def test_s_new_DCOM
@ -242,6 +246,8 @@ if defined?(WIN32OLE)
th.join
}
assert_match(/insecure connection - `winmgmts:'/, exc.message)
ensure
$SAFE = 0
end
def test_invoke_accept_symbol_hash_key

View file

@ -395,6 +395,8 @@ if defined?(WIN32OLE_EVENT)
th.join
}
assert_match(/insecure event creation - `ConnectionEvents'/, exc.message)
ensure
$SAFE = 0
end
end
end

View file

@ -2952,18 +2952,16 @@ rb_thread_stop_p(VALUE thread)
* call-seq:
* thr.safe_level -> integer
*
* Returns the safe level in effect for <i>thr</i>. Setting thread-local safe
* levels can help when implementing sandboxes which run insecure code.
* Returns the safe level.
*
* thr = Thread.new { $SAFE = 1; sleep }
* Thread.current.safe_level #=> 0
* thr.safe_level #=> 1
* This method is obsolete because $SAFE is a process global state.
* Simply check $SAFE.
*/
static VALUE
rb_thread_safe_level(VALUE thread)
{
return INT2NUM(rb_thread_ptr(thread)->ec->safe_level);
return UINT2NUM(rb_safe_level());
}
/*

View file

@ -368,14 +368,13 @@ load_transcoder_entry(transcoder_entry_t *entry)
const size_t total_len = sizeof(transcoder_lib_prefix) - 1 + len;
const VALUE fn = rb_str_new(0, total_len);
char *const path = RSTRING_PTR(fn);
const int safe = rb_safe_level();
memcpy(path, transcoder_lib_prefix, sizeof(transcoder_lib_prefix) - 1);
memcpy(path + sizeof(transcoder_lib_prefix) - 1, lib, len);
rb_str_set_len(fn, total_len);
FL_UNSET(fn, FL_TAINT);
OBJ_FREEZE(fn);
rb_require_safe(fn, safe > 3 ? 3 : safe);
rb_require_safe(fn, rb_safe_level());
}
if (entry->transcoder)

43
vm.c
View file

@ -809,7 +809,7 @@ static VALUE
vm_proc_create_from_captured(VALUE klass,
const struct rb_captured_block *captured,
enum rb_block_type block_type,
int8_t safe_level, int8_t is_from_method, int8_t is_lambda)
int8_t is_from_method, int8_t is_lambda)
{
VALUE procval = rb_proc_alloc(klass);
rb_proc_t *proc = RTYPEDDATA_DATA(procval);
@ -822,7 +822,6 @@ vm_proc_create_from_captured(VALUE klass,
rb_vm_block_ep_update(procval, &proc->block, captured->ep);
vm_block_type_set(&proc->block, block_type);
proc->safe_level = safe_level;
proc->is_from_method = is_from_method;
proc->is_lambda = is_lambda;
@ -849,9 +848,8 @@ rb_vm_block_copy(VALUE obj, const struct rb_block *dst, const struct rb_block *s
}
}
VALUE
rb_proc_create(VALUE klass, const struct rb_block *block,
int8_t safe_level, int8_t is_from_method, int8_t is_lambda)
static VALUE
proc_create(VALUE klass, const struct rb_block *block, int8_t is_from_method, int8_t is_lambda)
{
VALUE procval = rb_proc_alloc(klass);
rb_proc_t *proc = RTYPEDDATA_DATA(procval);
@ -859,13 +857,25 @@ rb_proc_create(VALUE klass, const struct rb_block *block,
VM_ASSERT(VM_EP_IN_HEAP_P(GET_EC(), vm_block_ep(block)));
rb_vm_block_copy(procval, &proc->block, block);
vm_block_type_set(&proc->block, block->type);
proc->safe_level = safe_level;
proc->is_from_method = is_from_method;
proc->is_lambda = is_lambda;
return procval;
}
VALUE
rb_proc_dup(VALUE self)
{
VALUE procval;
rb_proc_t *src;
GetProcPtr(self, src);
procval = proc_create(rb_cProc, &src->block, src->is_from_method, src->is_lambda);
RB_GC_GUARD(self); /* for: body = rb_proc_dup(body) */
return procval;
}
VALUE
rb_vm_make_proc_lambda(const rb_execution_context_t *ec, const struct rb_captured_block *captured, VALUE klass, int8_t is_lambda)
{
@ -880,8 +890,7 @@ rb_vm_make_proc_lambda(const rb_execution_context_t *ec, const struct rb_capture
imemo_type_p(captured->code.val, imemo_ifunc));
procval = vm_proc_create_from_captured(klass, captured,
imemo_type(captured->code.val) == imemo_iseq ? block_type_iseq : block_type_ifunc,
(int8_t)ec->safe_level, FALSE, is_lambda);
imemo_type(captured->code.val) == imemo_iseq ? block_type_iseq : block_type_ifunc, FALSE, is_lambda);
return procval;
}
@ -1139,23 +1148,7 @@ static VALUE
vm_invoke_proc(rb_execution_context_t *ec, rb_proc_t *proc, VALUE self,
int argc, const VALUE *argv, VALUE passed_block_handler)
{
VALUE val = Qundef;
enum ruby_tag_type state;
volatile int stored_safe = ec->safe_level;
EC_PUSH_TAG(ec);
if ((state = EC_EXEC_TAG()) == TAG_NONE) {
ec->safe_level = proc->safe_level;
val = invoke_block_from_c_proc(ec, proc, self, argc, argv, passed_block_handler, proc->is_lambda);
}
EC_POP_TAG();
ec->safe_level = stored_safe;
if (state) {
EC_JUMP_TAG(ec, state);
}
return val;
return invoke_block_from_c_proc(ec, proc, self, argc, argv, passed_block_handler, proc->is_lambda);
}
static VALUE

View file

@ -515,6 +515,9 @@ typedef struct rb_vm_struct {
unsigned int running: 1;
unsigned int thread_abort_on_exception: 1;
unsigned int thread_report_on_exception: 1;
unsigned int safe_level_: 1;
int trace_running;
volatile int sleeper;
@ -736,7 +739,6 @@ typedef struct rb_execution_context_struct {
struct rb_vm_tag *tag;
struct rb_vm_protect_tag *protect_tag;
int safe_level;
int raised_flag;
/* interrupt flags */
@ -899,9 +901,8 @@ RUBY_SYMBOL_EXPORT_END
typedef struct {
const struct rb_block block;
int8_t safe_level; /* 0..1 */
int8_t is_from_method; /* bool */
int8_t is_lambda; /* bool */
unsigned int is_from_method: 1; /* bool */
unsigned int is_lambda: 1; /* bool */
} rb_proc_t;
typedef struct {
@ -1464,8 +1465,9 @@ VM_BH_FROM_PROC(VALUE procval)
/* VM related object allocate functions */
VALUE rb_thread_alloc(VALUE klass);
VALUE rb_proc_alloc(VALUE klass);
VALUE rb_binding_alloc(VALUE klass);
VALUE rb_proc_alloc(VALUE klass);
VALUE rb_proc_dup(VALUE self);
/* for debug */
extern void rb_vmdebug_stack_dump_raw(const rb_execution_context_t *ec, const rb_control_frame_t *cfp);

View file

@ -1490,7 +1490,7 @@ rb_eval_cmd(VALUE cmd, VALUE arg, int level)
{
enum ruby_tag_type state;
volatile VALUE val = Qnil; /* OK */
const int VAR_NOCLOBBERED(safe) = rb_safe_level();
const int VAR_NOCLOBBERED(current_safe_level) = rb_safe_level();
rb_execution_context_t * volatile ec = GET_EC();
if (OBJ_TAINTED(cmd)) {
@ -1510,7 +1510,7 @@ rb_eval_cmd(VALUE cmd, VALUE arg, int level)
}
EC_POP_TAG();
rb_set_safe_level_force(safe);
rb_set_safe_level_force(current_safe_level);
if (state) EC_JUMP_TAG(ec, state);
return val;
}