1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00
ruby--ruby/test/ruby/test_eval.rb
John Hawthorn 733500e9d0
Lazily create singletons on instance_{exec,eval} (#5146)
* Lazily create singletons on instance_{exec,eval}

Previously when instance_exec or instance_eval was called on an object,
that object would be given a singleton class so that method
definitions inside the block would be added to the object rather than
its class.

This commit aims to improve performance by delaying the creation of the
singleton class unless/until one is needed for method definition. Most
of the time instance_eval is used without any method definition.

This was implemented by adding a flag to the cref indicating that it
represents a singleton of the object rather than a class itself. In this
case CREF_CLASS returns the object's existing class, but in cases that
we are defining a method (either via definemethod or
VM_SPECIAL_OBJECT_CBASE which is used for undef and alias).

This also happens to fix what I believe is a bug. Previously
instance_eval behaved differently with regards to constant access for
true/false/nil than for all other objects. I don't think this was
intentional.

    String::Foo = "foo"
    "".instance_eval("Foo")   # => "foo"
    Integer::Foo = "foo"
    123.instance_eval("Foo")  # => "foo"
    TrueClass::Foo = "foo"
    true.instance_eval("Foo") # NameError: uninitialized constant Foo

This also slightly changes the error message when trying to define a method
through instance_eval on an object which can't have a singleton class.

Before:

    $ ruby -e '123.instance_eval { def foo; end }'
    -e:1:in `block in <main>': no class/module to add method (TypeError)

After:

    $ ./ruby -e '123.instance_eval { def foo; end }'
    -e:1:in `block in <main>': can't define singleton (TypeError)

IMO this error is a small improvement on the original and better matches
the (both old and new) message when definging a method using `def self.`

    $ ruby -e '123.instance_eval{ def self.foo; end }'
    -e:1:in `block in <main>': can't define singleton (TypeError)

Co-authored-by: Matthew Draper <matthew@trebex.net>

* Remove "under" argument from yield_under

* Move CREF_SINGLETON_SET into vm_cref_new

* Simplify vm_get_const_base

* Fix leaf VM_SPECIAL_OBJECT_CONST_BASE

Co-authored-by: Matthew Draper <matthew@trebex.net>
2021-12-02 15:53:39 -08:00

606 lines
17 KiB
Ruby

# frozen_string_literal: false
require 'test/unit'
class TestEval < Test::Unit::TestCase
@ivar = 12
@@cvar = 13
$gvar__eval = 14
Const = 15
def ruby(*args)
args = ['-e', '$>.write($<.read)'] if args.empty?
ruby = EnvUtil.rubybin
f = IO.popen([ruby] + args, 'r+')
yield(f)
ensure
f.close unless !f || f.closed?
end
def test_eval_basic
assert_equal nil, eval("nil")
assert_equal true, eval("true")
assert_equal false, eval("false")
assert_equal self, eval("self")
assert_equal 1, eval("1")
assert_equal :sym, eval(":sym")
assert_equal 11, eval("11")
@ivar = 12
assert_equal 12, eval("@ivar")
assert_equal 13, eval("@@cvar")
assert_equal 14, eval("$gvar__eval")
assert_equal 15, eval("Const")
assert_equal 16, eval("7 + 9")
assert_equal 17, eval("17.to_i")
assert_equal "18", eval(%q("18"))
assert_equal "19", eval(%q("1#{9}"))
1.times {
assert_equal 12, eval("@ivar")
assert_equal 13, eval("@@cvar")
assert_equal 14, eval("$gvar__eval")
assert_equal 15, eval("Const")
}
end
def test_eval_binding_basic
assert_equal nil, eval("nil", binding())
assert_equal true, eval("true", binding())
assert_equal false, eval("false", binding())
assert_equal self, eval("self", binding())
assert_equal 1, eval("1", binding())
assert_equal :sym, eval(":sym", binding())
assert_equal 11, eval("11", binding())
@ivar = 12
assert_equal 12, eval("@ivar", binding())
assert_equal 13, eval("@@cvar", binding())
assert_equal 14, eval("$gvar__eval", binding())
assert_equal 15, eval("Const", binding())
assert_equal 16, eval("7 + 9", binding())
assert_equal 17, eval("17.to_i", binding())
assert_equal "18", eval(%q("18"), binding())
assert_equal "19", eval(%q("1#{9}"), binding())
1.times {
assert_equal 12, eval("@ivar")
assert_equal 13, eval("@@cvar")
assert_equal 14, eval("$gvar__eval")
assert_equal 15, eval("Const")
}
end
def test_module_eval_string_basic
c = self.class
assert_equal nil, c.module_eval("nil")
assert_equal true, c.module_eval("true")
assert_equal false, c.module_eval("false")
assert_equal c, c.module_eval("self")
assert_equal :sym, c.module_eval(":sym")
assert_equal 11, c.module_eval("11")
@ivar = 12
assert_equal 12, c.module_eval("@ivar")
assert_equal 13, c.module_eval("@@cvar")
assert_equal 14, c.module_eval("$gvar__eval")
assert_equal 15, c.module_eval("Const")
assert_equal 16, c.module_eval("7 + 9")
assert_equal 17, c.module_eval("17.to_i")
assert_equal "18", c.module_eval(%q("18"))
assert_equal "19", c.module_eval(%q("1#{9}"))
@ivar = 12
1.times {
assert_equal 12, c.module_eval("@ivar")
assert_equal 13, c.module_eval("@@cvar")
assert_equal 14, c.module_eval("$gvar__eval")
assert_equal 15, c.module_eval("Const")
}
end
def test_module_eval_block_basic
c = self.class
assert_equal nil, c.module_eval { nil }
assert_equal true, c.module_eval { true }
assert_equal false, c.module_eval { false }
assert_equal c, c.module_eval { self }
assert_equal :sym, c.module_eval { :sym }
assert_equal 11, c.module_eval { 11 }
@ivar = 12
assert_equal 12, c.module_eval { @ivar }
assert_equal 13, c.module_eval { @@cvar }
assert_equal 14, c.module_eval { $gvar__eval }
assert_equal 15, c.module_eval { Const }
assert_equal 16, c.module_eval { 7 + 9 }
assert_equal 17, c.module_eval { "17".to_i }
assert_equal "18", c.module_eval { "18" }
assert_equal "19", c.module_eval { "1#{9}" }
@ivar = 12
1.times {
assert_equal 12, c.module_eval { @ivar }
assert_equal 13, c.module_eval { @@cvar }
assert_equal 14, c.module_eval { $gvar__eval }
assert_equal 15, c.module_eval { Const }
}
end
def test_module_eval_block_symbol
assert_equal "Math", Math.module_eval(&:to_s)
end
def forall_TYPE
objects = [Object.new, [], nil, true, false] # TODO: check
objects.each do |obj|
obj.instance_variable_set :@ivar, 12 unless obj.frozen?
yield obj
end
end
def test_instance_eval_string_basic
forall_TYPE do |o|
assert_equal nil, o.instance_eval("nil")
assert_equal true, o.instance_eval("true")
assert_equal false, o.instance_eval("false")
assert_equal o, o.instance_eval("self")
assert_equal 1, o.instance_eval("1")
assert_equal :sym, o.instance_eval(":sym")
assert_equal 11, o.instance_eval("11")
assert_equal 12, o.instance_eval("@ivar") unless o.frozen?
assert_equal 13, o.instance_eval("@@cvar")
assert_equal 14, o.instance_eval("$gvar__eval")
assert_equal 15, o.instance_eval("Const")
assert_equal 16, o.instance_eval("7 + 9")
assert_equal 17, o.instance_eval("17.to_i")
assert_equal "18", o.instance_eval(%q("18"))
assert_equal "19", o.instance_eval(%q("1#{9}"))
1.times {
assert_equal 12, o.instance_eval("@ivar") unless o.frozen?
assert_equal 13, o.instance_eval("@@cvar")
assert_equal 14, o.instance_eval("$gvar__eval")
assert_equal 15, o.instance_eval("Const")
}
end
end
def test_instance_eval_block_basic
forall_TYPE do |o|
assert_equal nil, o.instance_eval { nil }
assert_equal true, o.instance_eval { true }
assert_equal false, o.instance_eval { false }
assert_equal o, o.instance_eval { self }
assert_equal 1, o.instance_eval { 1 }
assert_equal :sym, o.instance_eval { :sym }
assert_equal 11, o.instance_eval { 11 }
assert_equal 12, o.instance_eval { @ivar } unless o.frozen?
assert_equal 13, o.instance_eval { @@cvar }
assert_equal 14, o.instance_eval { $gvar__eval }
assert_equal 15, o.instance_eval { Const }
assert_equal 16, o.instance_eval { 7 + 9 }
assert_equal 17, o.instance_eval { 17.to_i }
assert_equal "18", o.instance_eval { "18" }
assert_equal "19", o.instance_eval { "1#{9}" }
1.times {
assert_equal 12, o.instance_eval { @ivar } unless o.frozen?
assert_equal 13, o.instance_eval { @@cvar }
assert_equal 14, o.instance_eval { $gvar__eval }
assert_equal 15, o.instance_eval { Const }
}
end
end
def test_instance_eval_block_self
# instance_eval(&block)'s self must not be sticky (jruby/jruby#2060)
pr = proc { self }
assert_equal self, pr.call
o = Object.new
assert_equal o, o.instance_eval(&pr)
assert_equal self, pr.call
end
def test_instance_eval_block_symbol
forall_TYPE do |o|
assert_equal o.to_s, o.instance_eval(&:to_s)
end
end
def test_instance_eval_cvar
[Object.new, [], 7, :sym, true, false, nil].each do |obj|
assert_equal(13, obj.instance_eval("@@cvar"))
assert_equal(13, obj.instance_eval{@@cvar})
# assert_raise(NameError){obj.instance_eval("@@cvar")}
# assert_raise(NameError){obj.instance_eval{@@cvar}}
end
end
def test_instance_exec_cvar
[Object.new, [], 7, :sym, true, false, nil].each do |obj|
assert_equal(13, obj.instance_exec{@@cvar})
end
end
def test_instance_eval_method
bug2788 = '[ruby-core:28324]'
[Object.new, [], nil, true, false].each do |o|
assert_nothing_raised(TypeError, "#{bug2788} (#{o.inspect})") do
o.instance_eval {
def defd_using_instance_eval() :ok end
}
end
assert_equal(:ok, o.defd_using_instance_eval)
class << o
remove_method :defd_using_instance_eval
end
end
end
def test_instance_eval_on_argf_singleton_class
bug8188 = '[ruby-core:53839] [Bug #8188]'
assert_warning('', bug8188) do
ARGF.singleton_class.instance_eval{}
end
end
class Foo
Bar = 2
end
def test_instance_eval_const
bar = nil
assert_nothing_raised(NameError) do
bar = Foo.new.instance_eval("Bar")
end
assert_equal(2, bar)
end
def test_instance_exec_block_basic
forall_TYPE do |o|
assert_equal nil, o.instance_exec { nil }
assert_equal true, o.instance_exec { true }
assert_equal false, o.instance_exec { false }
assert_equal o, o.instance_exec { self }
assert_equal 1, o.instance_exec { 1 }
assert_equal :sym, o.instance_exec { :sym }
assert_equal 11, o.instance_exec { 11 }
assert_equal 12, o.instance_exec { @ivar } unless o.frozen?
assert_equal 13, o.instance_exec { @@cvar }
assert_equal 14, o.instance_exec { $gvar__eval }
assert_equal 15, o.instance_exec { Const }
assert_equal 16, o.instance_exec { 7 + 9 }
assert_equal 17, o.instance_exec { 17.to_i }
assert_equal "18", o.instance_exec { "18" }
assert_equal "19", o.instance_exec { "1#{9}" }
1.times {
assert_equal 12, o.instance_exec { @ivar } unless o.frozen?
assert_equal 13, o.instance_exec { @@cvar }
assert_equal 14, o.instance_exec { $gvar__eval }
assert_equal 15, o.instance_exec { Const }
}
end
end
def test_instance_exec_method_definition
klass = Class.new
o = klass.new
o.instance_exec do
def foo
:foo_result
end
end
assert_respond_to o, :foo
refute_respond_to klass, :foo
refute_respond_to klass.new, :foo
assert_equal :foo_result, o.foo
end
def test_instance_exec_eval_method_definition
klass = Class.new
o = klass.new
o.instance_exec do
eval %{
def foo
:foo_result
end
}
end
assert_respond_to o, :foo
refute_respond_to klass, :foo
refute_respond_to klass.new, :foo
assert_equal :foo_result, o.foo
end
#
# From ruby/test/ruby/test_eval.rb
#
def make_test_binding
local1 = local1 = "local1"
lambda {
local2 = local2 = "local2"
return binding
}.call
end
def test_eval_orig
assert_nil(eval(""))
bad=false
eval 'while false; bad = true; print "foo\n" end'
assert(!bad)
assert(eval('Object'))
assert(eval('true'))
assert(!eval('nil'))
assert(!eval('false'))
$foo = 'assert(true)'
begin
eval $foo
rescue
assert(false)
end
assert_equal('assert(true)', eval("$foo"))
assert_equal(true, eval("true"))
i = i = 5
assert(eval("i == 5"))
assert_equal(5, eval("i"))
assert(eval("defined? i"))
x = make_test_binding
assert_equal("local1", eval("local1", x)) # normal local var
assert_equal("local2", eval("local2", x)) # nested local var
bad = true
begin
p eval("local1")
rescue NameError # must raise error
bad = false
end
assert(!bad)
# !! use class_eval to avoid nested definition
x = self.class.class_eval %q(
module EvTest
EVTEST1 = 25
evtest2 = 125
evtest2 = evtest2
binding
end
)
assert_equal(25, eval("EVTEST1", x)) # constant in module
assert_equal(125, eval("evtest2", x)) # local var in module
bad = true
begin
eval("EVTEST1")
rescue NameError # must raise error
bad = false
end
assert(!bad)
x = binding
eval "i = 1", x
assert_equal(1, eval("i", x))
x = proc{binding}.call
eval "i = 22", x
assert_equal(22, eval("i", x))
t = []
x = proc{binding}.call
eval "(0..9).each{|i5| t[i5] = proc{i5*2}}", x
assert_equal(8, t[4].call)
x = proc{binding}.call
eval "for i6 in 1..1; j6=i6; end", x
assert(eval("defined? i6", x))
assert(eval("defined? j6", x))
proc {
p = binding
eval "foo11 = 1", p
foo22 = 5
proc{foo11=22;foo11}.call
proc{foo22=55}.call
# assert_equal(eval("foo11"), eval("foo11", p))
# assert_equal(1, eval("foo11"))
assert_equal(eval("foo22"), eval("foo22", p))
assert_equal(55, eval("foo22"))
assert_equal(55, foo22)
}.call
self.class.class_eval do
remove_const :EvTest
end
end
def test_nil_instance_eval_cvar
def nil.test_binding
binding
end
bb = eval("nil.instance_eval \"binding\"", nil.test_binding)
assert_raise(NameError, "[ruby-dev:24103]") { eval("@@a", bb) }
class << nil
remove_method :test_binding
end
end
def test_fixnum_instance_eval_cvar
assert_raise(NameError, "[ruby-dev:24213]") { 1.instance_eval "@@a" }
end
def test_cvar_scope_with_instance_eval
# TODO: check
Integer.class_eval "@@test_cvar_scope_with_instance_eval = 1" # depends on [ruby-dev:24229]
@@test_cvar_scope_with_instance_eval = 4
assert_equal(4, 1.instance_eval("@@test_cvar_scope_with_instance_eval"), "[ruby-dev:24223]")
Integer.__send__(:remove_class_variable, :@@test_cvar_scope_with_instance_eval)
end
def test_eval_and_define_method
assert_nothing_raised("[ruby-dev:24228]") {
def temporally_method_for_test_eval_and_define_method(&block)
lambda {
class << Object.new; self end.send(:define_method, :zzz, &block)
}
end
v = eval("temporally_method_for_test_eval_and_define_method {}")
{}[0] = {}
v.call
}
end
def test_define_method_block
cc = Class.new do
define_method(:foo) {|&block|
block.call if block
}
end
c = cc.new
x = "ng"
c.foo() {x = "ok"}
assert_equal("ok", x)
end
def test_define_method_toplevel
feature6609 = '[ruby-core:45715]'
main = eval("self", TOPLEVEL_BINDING)
assert_nothing_raised(NoMethodError, feature6609) do
main.instance_eval do
define_method("feature6609_block") {feature6609}
end
end
assert_equal(feature6609, feature6609_block)
assert_nothing_raised(NoMethodError, feature6609) do
main.instance_eval do
define_method("feature6609_method", Object.instance_method(:feature6609_block))
end
end
assert_equal(feature6609, feature6609_method)
end
def test_eval_using_integer_as_binding
assert_raise(TypeError) { eval("", 1) }
end
def test_eval_raise
assert_raise(RuntimeError) { eval("raise ''") }
end
def test_eval_with_toplevel_binding # [ruby-dev:37142]
ruby("-e", "x = 0; eval('p x', TOPLEVEL_BINDING)") do |f|
f.close_write
assert_equal("0", f.read.chomp)
end
end
def test_eval_ascii_incompatible
assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-16be"))}
assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-16le"))}
assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-32be"))}
assert_raise(ArgumentError) {eval("__ENCODING__".encode("utf-32le"))}
end
def test_instance_eval_method_proc
bug3860 = Class.new do
def initialize(a);
@a=a
end
def get(*args)
@a
end
end
foo = bug3860.new 1
foo_pr = foo.method(:get).to_proc
result = foo.instance_eval(&foo_pr)
assert_equal(1, result, 'Bug #3786, Bug #3860, [ruby-core:32501]')
end
def test_file_encoding
fname = "\u{3042}".encode("euc-jp")
assert_equal(fname, eval("__FILE__", nil, fname, 1))
end
def test_eval_location_fstring
o = Object.new
o.instance_eval "def foo() end", "generated code"
o.instance_eval "def bar() end", "generated code"
a, b = o.method(:foo).source_location[0],
o.method(:bar).source_location[0]
assert_same a, b
end
def test_eval_location_binding
assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", nil))
assert_equal(['(eval)', 1], eval("[__FILE__, __LINE__]", binding))
assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", nil, 'foo'))
assert_equal(['foo', 1], eval("[__FILE__, __LINE__]", binding, 'foo'))
assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", nil, 'foo', 2))
assert_equal(['foo', 2], eval("[__FILE__, __LINE__]", binding, 'foo', 2))
end
def test_fstring_instance_eval
bug = "[ruby-core:78116] [Bug #12930]".freeze
assert_same bug, (bug.instance_eval {self})
assert_raise(FrozenError) {
bug.instance_eval {@ivar = true}
}
end
def test_gced_binding_block
assert_normal_exit %q{
def m
binding
end
GC.stress = true
b = nil
tap do
b = m {}
end
0.times.to_a
b.eval('yield')
}, '[Bug #10368]'
end
def test_gced_eval_location
Dir.mktmpdir do |d|
File.write("#{d}/2.rb", "")
File.write("#{d}/1.rb", "require_relative '2'\n""__FILE__\n")
file = "1.rb"
path = File.expand_path(file, d)
assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d)))
assert_equal(path, eval(File.read(path), nil, File.expand_path(file, d)))
end
end
def orphan_proc
proc {eval("return :ng")}
end
def orphan_lambda
lambda {eval("return :ok")}
end
def test_return_in_eval_proc
x = orphan_proc
assert_raise(LocalJumpError) {x.call}
end
def test_return_in_eval_lambda
x = orphan_lambda
assert_equal(:ok, x.call)
end
end