# coding: US-ASCII # frozen_string_literal: false require 'test/unit' require 'stringio' class TestParse < Test::Unit::TestCase def setup @verbose = $VERBOSE $VERBOSE = nil end def teardown $VERBOSE = @verbose end def test_else_without_rescue assert_syntax_error(<<-END, %r":#{__LINE__+2}: else without rescue"o, [__FILE__, __LINE__+1]) begin else 42 end END end def test_alias_backref assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 alias $foo $1 END end end def test_command_call t = Object.new def t.foo(x); x; end a = false b = c = d = true assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a &&= t.foo 42 b &&= t.foo 42 c &&= t.foo nil d &&= t.foo false END end assert_equal([false, 42, nil, false], [a, b, c, d]) a = 3 assert_nothing_raised { eval("a &= t.foo 5") } assert_equal(1, a) a = [nil, nil, true, true] assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a[0] ||= t.foo 42 a[1] &&= t.foo 42 a[2] ||= t.foo 42 a[3] &&= t.foo 42 END end assert_equal([42, nil, true, 42], a) o = Object.new class << o attr_accessor :foo, :bar, :Foo, :Bar, :baz, :qux end o.foo = o.Foo = o::baz = nil o.bar = o.Bar = o::qux = 1 assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 o.foo ||= t.foo 42 o.bar &&= t.foo 42 o.Foo ||= t.foo 42 o.Bar &&= t.foo 42 o::baz ||= t.foo 42 o::qux &&= t.foo 42 END end assert_equal([42, 42], [o.foo, o.bar]) assert_equal([42, 42], [o.Foo, o.Bar]) assert_equal([42, 42], [o::baz, o::qux]) assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 $1 ||= t.foo 42 END end def t.bar(x); x + yield; end a = b = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a = t.bar "foo" do "bar" end.gsub "ob", "OB" b = t.bar "foo" do "bar" end::gsub "ob", "OB" END end assert_equal("foOBar", a) assert_equal("foOBar", b) a = nil assert_nothing_raised do t.instance_eval <<-END, __FILE__, __LINE__+1 a = bar "foo" do "bar" end END end assert_equal("foobar", a) a = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a = t::bar "foo" do "bar" end END end assert_equal("foobar", a) def t.baz(*r) @baz = r + (block_given? ? [yield] : []) end assert_nothing_raised do t.instance_eval "baz (1), 2" end assert_equal([1, 2], t.instance_eval { @baz }) end def test_mlhs_node c = Class.new class << c attr_accessor :foo, :bar, :Foo, :Bar FOO = BAR = nil end assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 c::foo, c::bar = 1, 2 c.Foo, c.Bar = 1, 2 c::FOO, c::BAR = 1, 2 END end assert_equal([1, 2], [c::foo, c::bar]) assert_equal([1, 2], [c.Foo, c.Bar]) assert_equal([1, 2], [c::FOO, c::BAR]) end def test_dynamic_constant_assignment assert_raise(SyntaxError) do Object.new.instance_eval <<-END, __FILE__, __LINE__+1 def foo self::FOO, self::BAR = 1, 2 ::FOO, ::BAR = 1, 2 end END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 $1, $2 = 1, 2 END end assert_raise(SyntaxError) do Object.new.instance_eval <<-END, __FILE__, __LINE__+1 def foo ::FOO = 1 end END end c = Class.new c.freeze assert_nothing_raised(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 if false c::FOO &= 1 ::FOO &= 1 end END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 $1 &= 1 END end end def test_class_module assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 class foo; end END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def foo class Foo; end module Bar; end end END end assert_nothing_raised(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 class Foo 1; end END end end def test_op_name o = Object.new def o.>(x); x; end def o./(x); x; end assert_nothing_raised do o.instance_eval <<-END, __FILE__, __LINE__+1 undef >, / END end end def test_arg o = Object.new class << o attr_accessor :foo, :bar, :Foo, :Bar, :baz, :qux end o.foo = o.Foo = o::baz = nil o.bar = o.Bar = o::qux = 1 assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 o.foo ||= 42 o.bar &&= 42 o.Foo ||= 42 o.Bar &&= 42 o::baz ||= 42 o::qux &&= 42 END end assert_equal([42, 42], [o.foo, o.bar]) assert_equal([42, 42], [o.Foo, o.Bar]) assert_equal([42, 42], [o::baz, o::qux]) a = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a = -2.0 ** 2 END end assert_equal(-4.0, a) end def test_block_variable o = Object.new def o.foo(*r); yield(*r); end a = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 o.foo 1 do|; a| a = 42 end END end assert_nil(a) end def test_bad_arg assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def foo(FOO); end END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def foo(@foo); end END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def foo($foo); end END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def foo(@@foo); end END end o = Object.new def o.foo(*r); yield(*r); end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 o.foo 1 {|; @a| @a = 42 } END end end def test_do_lambda a = b = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a = -> do b = 42 end END end a.call assert_equal(42, b) end def test_block_call_colon2 o = Object.new def o.foo(x); x + yield; end a = b = nil assert_nothing_raised do o.instance_eval <<-END, __FILE__, __LINE__+1 a = foo 1 do 42 end.to_s b = foo 1 do 42 end::to_s END end assert_equal("43", a) assert_equal("43", b) end def test_call_method a = b = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a = proc {|x| x + "bar" }.("foo") b = proc {|x| x + "bar" }::("foo") END end assert_equal("foobar", a) assert_equal("foobar", b) end def test_xstring assert_raise(Errno::ENOENT) do eval("``") end end def test_words assert_equal([], %W( )) end def test_dstr @@foo = 1 assert_equal("foo 1 bar", "foo #@@foo bar") "1" =~ /(.)/ assert_equal("foo 1 bar", "foo #$1 bar") end def test_dstr_disallowed_variable bug8375 = '[ruby-core:54885] [Bug #8375]' %w[@ @1 @. @@ @@1 @@. $ $%].each do |src| src = '#'+src+' ' str = assert_nothing_raised(SyntaxError, "#{bug8375} #{src.dump}") do break eval('"'+src+'"') end assert_equal(src, str, bug8375) end end def test_dsym assert_nothing_raised { eval(':""') } end def assert_disallowed_variable(type, noname, *invalid) assert_syntax_error(noname, "`#{noname}' without identifiers is not allowed as #{type} variable name") invalid.each do |name| assert_syntax_error(name, "`#{name}' is not allowed as #{type} variable name") end end def test_disallowed_instance_variable assert_disallowed_variable("an instance", *%w[@ @1 @.]) end def test_disallowed_class_variable assert_disallowed_variable("a class", *%w[@@ @@1 @@.]) end def test_disallowed_gloal_variable assert_disallowed_variable("a global", *%w[$ $%]) end def test_arg2 o = Object.new assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(a=42,*r,z,&b); b.call(r.inject(a*1000+z*100, :+)); end END end assert_equal(-1405, o.foo(1,2,3,4) {|x| -x }) assert_equal(-1302, o.foo(1,2,3) {|x| -x }) assert_equal(-1200, o.foo(1,2) {|x| -x }) assert_equal(-42100, o.foo(1) {|x| -x }) assert_raise(ArgumentError) { o.foo() } assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(a=42,z,&b); b.call(a*1000+z*100); end END end assert_equal(-1200, o.foo(1,2) {|x| -x } ) assert_equal(-42100, o.foo(1) {|x| -x } ) assert_raise(ArgumentError) { o.foo() } assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def o.foo(*r,z,&b); b.call(r.inject(z*100, :+)); end END end assert_equal(-303, o.foo(1,2,3) {|x| -x } ) assert_equal(-201, o.foo(1,2) {|x| -x } ) assert_equal(-100, o.foo(1) {|x| -x } ) assert_raise(ArgumentError) { o.foo() } end def test_duplicate_argument assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 1.times {|&b?| } END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 1.times {|a, a|} END end assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def foo(a, a); end END end end def test_define_singleton_error assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 def ("foo").foo; end END end end def test_op_asgn1_with_block t = Object.new a = [] blk = proc {|x| a << x } def t.[](_) yield(:aref) nil end def t.[]=(_, _) yield(:aset) end def t.dummy(_) end eval <<-END, nil, __FILE__, __LINE__+1 t[42, &blk] ||= 42 END assert_equal([:aref, :aset], a) a.clear eval <<-END, nil, __FILE__, __LINE__+1 t[42, &blk] ||= t.dummy 42 # command_asgn test END assert_equal([:aref, :aset], a) end def test_backquote t = Object.new assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 def t.`(x); "foo" + x + "bar"; end END end a = b = nil assert_nothing_raised do eval <<-END, nil, __FILE__, __LINE__+1 a = t.` "zzz" 1.times {|;z| t.` ("zzz") } END t.instance_eval <<-END, __FILE__, __LINE__+1 b = `zzz` END end assert_equal("foozzzbar", a) assert_equal("foozzzbar", b) end def test_carrige_return assert_equal(2, eval("1 +\r\n1")) end def test_string mesg = 'from the backslash through the invalid char' e = assert_syntax_error('"\xg1"', /hex escape/) assert_equal(' ^', e.message.lines.last, mesg) e = assert_syntax_error('"\u{1234"', 'unterminated Unicode escape') assert_equal(' ^', e.message.lines.last, mesg) e = assert_syntax_error('"\u{xxxx}"', 'invalid Unicode escape') assert_equal(' ^', e.message.lines.last, mesg) e = assert_syntax_error('"\u{xxxx', 'Unicode escape') assert_pattern_list([ /.*: invalid Unicode escape\n.*\n/, / \^/, /\n/, /.*: unterminated Unicode escape\n.*\n/, / \^/, /\n/, /.*: unterminated string.*\n.*\n/, / \^/, ], e.message) e = assert_syntax_error('"\M1"', /escape character syntax/) assert_equal(' ^~~', e.message.lines.last, mesg) e = assert_syntax_error('"\C1"', /escape character syntax/) assert_equal(' ^~~', e.message.lines.last, mesg) src = '"\xD0\u{90'"\n""000000000000000000000000" assert_syntax_error(src, /:#{__LINE__}: unterminated/o) assert_syntax_error('"\u{100000000}"', /invalid Unicode escape/) assert_equal("", eval('"\u{}"')) assert_equal("", eval('"\u{ }"')) assert_equal("\x81", eval('"\C-\M-a"')) assert_equal("\177", eval('"\c?"')) end def test_question assert_raise(SyntaxError) { eval('?') } assert_raise(SyntaxError) { eval('? ') } assert_raise(SyntaxError) { eval("?\n") } assert_raise(SyntaxError) { eval("?\t") } assert_raise(SyntaxError) { eval("?\v") } assert_raise(SyntaxError) { eval("?\r") } assert_raise(SyntaxError) { eval("?\f") } assert_raise(SyntaxError) { eval("?\f") } assert_raise(SyntaxError) { eval(" ?a\x8a".force_encoding("utf-8")) } assert_equal("\u{1234}", eval("?\u{1234}")) assert_equal("\u{1234}", eval('?\u{1234}')) end def test_percent assert_equal(:foo, eval('%s(foo)')) assert_raise(SyntaxError) { eval('%s') } assert_raise(SyntaxError) { eval('%ss') } assert_raise(SyntaxError) { eval('%z()') } end def test_symbol bug = '[ruby-dev:41447]' sym = "foo\0bar".to_sym assert_nothing_raised(SyntaxError, bug) do assert_equal(sym, eval(":'foo\0bar'")) end assert_nothing_raised(SyntaxError, bug) do assert_equal(sym, eval(':"foo\u0000bar"')) end assert_nothing_raised(SyntaxError, bug) do assert_equal(sym, eval(':"foo\u{0}bar"')) end assert_nothing_raised(SyntaxError) do assert_equal(:foobar, eval(':"foo\u{}bar"')) assert_equal(:foobar, eval(':"foo\u{ }bar"')) end end def test_parse_string assert_raise(SyntaxError) do eval <<-END, nil, __FILE__, __LINE__+1 / END end end def test_here_document x = nil assert_raise(SyntaxError) do eval %Q( <\)/ =~ ''")} a = "\u{3042}" assert_warning('') {eval("#{a} = 1; /(?<#{a}>)/ =~ ''")} end def test_rescue_in_command_assignment bug = '[ruby-core:75621] [Bug #12402]' all_assertions(bug) do |a| a.for("lhs = arg") do v = bug v = raise(bug) rescue "ok" assert_equal("ok", v) end a.for("lhs op_asgn arg") do v = 0 v += raise(bug) rescue 1 assert_equal(1, v) end a.for("lhs[] op_asgn arg") do v = [0] v[0] += raise(bug) rescue 1 assert_equal([1], v) end a.for("lhs.m op_asgn arg") do k = Struct.new(:m) v = k.new(0) v.m += raise(bug) rescue 1 assert_equal(k.new(1), v) end a.for("lhs::m op_asgn arg") do k = Struct.new(:m) v = k.new(0) v::m += raise(bug) rescue 1 assert_equal(k.new(1), v) end a.for("lhs.C op_asgn arg") do k = Struct.new(:C) v = k.new(0) v.C += raise(bug) rescue 1 assert_equal(k.new(1), v) end a.for("lhs::C op_asgn arg") do v = Class.new v::C ||= raise(bug) rescue 1 assert_equal(1, v::C) end a.for("lhs = command") do v = bug v = raise bug rescue "ok" assert_equal("ok", v) end a.for("lhs op_asgn command") do v = 0 v += raise bug rescue 1 assert_equal(1, v) end a.for("lhs[] op_asgn command") do v = [0] v[0] += raise bug rescue 1 assert_equal([1], v) end a.for("lhs.m op_asgn command") do k = Struct.new(:m) v = k.new(0) v.m += raise bug rescue 1 assert_equal(k.new(1), v) end a.for("lhs::m op_asgn command") do k = Struct.new(:m) v = k.new(0) v::m += raise bug rescue 1 assert_equal(k.new(1), v) end a.for("lhs.C op_asgn command") do k = Struct.new(:C) v = k.new(0) v.C += raise bug rescue 1 assert_equal(k.new(1), v) end a.for("lhs::C op_asgn command") do v = Class.new v::C ||= raise bug rescue 1 assert_equal(1, v::C) end end end def test_yyerror_at_eol assert_syntax_error(" 0b", /\^/) assert_syntax_error(" 0b\n", /\^/) end def test_error_def_in_argument assert_separately([], "#{<<-"begin;"}\n#{<<~"end;"}") begin; assert_syntax_error("def f r:def d; def f 0end", /unexpected/) end; assert_syntax_error("def\nf(000)end", /^ \^~~/) assert_syntax_error("def\nf(&)end", /^ \^/) end def test_method_location_in_rescue bug = '[ruby-core:79388] [Bug #13181]' obj, line = Object.new, __LINE__+1 def obj.location # raise rescue caller_locations(1, 1)[0] end assert_equal(line, obj.location.lineno, bug) end def test_negative_line_number bug = '[ruby-core:80920] [Bug #13523]' obj = Object.new obj.instance_eval("def t(e = false);raise if e; __LINE__;end", "test", -100) assert_equal(-100, obj.t, bug) assert_equal(-100, obj.method(:t).source_location[1], bug) e = assert_raise(RuntimeError) {obj.t(true)} assert_equal(-100, e.backtrace_locations.first.lineno, bug) end def test_file_in_indented_heredoc name = '[ruby-core:80987] [Bug #13540]' # long enough to be shared assert_equal(name+"\n", eval("#{<<-"begin;"}\n#{<<-'end;'}", nil, name)) begin; <<~HEREDOC #{__FILE__} HEREDOC end; end def test_unexpected_token_error assert_raise(SyntaxError) do eval('"x"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx') end end def test_unexpected_token_after_numeric assert_raise_with_message(SyntaxError, /^ \^~~\z/) do eval('0000xyz') end assert_raise_with_message(SyntaxError, /^ \^~~\z/) do eval('1.2i1.1') end end def test_truncated_source_line e = assert_raise_with_message(SyntaxError, /unexpected tIDENTIFIER/) do eval("'0123456789012345678901234567890123456789' abcdefghijklmnopqrstuvwxyz0123456789 0123456789012345678901234567890123456789") end line = e.message.lines[1] assert_operator(line, :start_with?, "...") assert_operator(line, :end_with?, "...\n") end def test_unterminated_regexp_error e = assert_raise(SyntaxError) do eval("/x") end.message assert_match(/unterminated regexp meets end of file/, e) assert_not_match(/unexpected tSTRING_END/, e) end def test_lparenarg o = Struct.new(:x).new def o.i(x) self.x = x end o.instance_eval {i (-1.3).abs} assert_equal(1.3, o.x) o.instance_eval {i = 0; i (-1.3).abs} assert_equal(1.3, o.x) end def test_serial_comparison assert_warning(/comparison '<' after/) do $VERBOSE = true x = 1 eval("if false; 0 < x < 2; end") end end def test_eof_in_def assert_raise(SyntaxError) { eval("def m\n\0""end") } assert_raise(SyntaxError) { eval("def m\n\C-d""end") } assert_raise(SyntaxError) { eval("def m\n\C-z""end") } end def test_location_of_invalid_token assert_raise_with_message(SyntaxError, /^ \^~~\z/) do eval('class xxx end') end end def test_whitespace_warning assert_raise_with_message(SyntaxError, /backslash/) do eval("\\foo") end assert_raise_with_message(SyntaxError, /escaped space/) do eval("\\ ") end assert_raise_with_message(SyntaxError, /escaped horizontal tab/) do eval("\\\t") end assert_raise_with_message(SyntaxError, /escaped form feed/) do eval("\\\f") end assert_raise_with_message(SyntaxError, /escaped carriage return/) do assert_warn(/middle of line/) {eval("\\\r")} end assert_raise_with_message(SyntaxError, /escaped vertical tab/) do eval("\\\v") end end def test_command_def_cmdarg assert_valid_syntax("\n#{<<~"begin;"}\n#{<<~'end;'}") begin; m def x(); end 1.tap do end end; end NONASCII_CONSTANTS = [ *%W"\u{00de} \u{00C0}".flat_map {|c| [c, c.encode("iso-8859-15")]}, "\u{1c4}", "\u{1f2}", "\u{1f88}", "\u{370}", *%W"\u{391} \u{ff21}".flat_map {|c| [c, c.encode("cp932"), c.encode("euc-jp")]}, ] def assert_nonascii_const assert_all_assertions_foreach("NONASCII_CONSTANTS", *NONASCII_CONSTANTS) do |n| m = Module.new assert_not_operator(m, :const_defined?, n) assert_raise_with_message(NameError, /uninitialized/) do m.const_get(n) end assert_nil(eval("defined?(m::#{n})")) v = yield m, n assert_operator(m, :const_defined?, n) assert_equal("constant", eval("defined?(m::#{n})")) assert_same(v, m.const_get(n)) m.__send__(:remove_const, n) assert_not_operator(m, :const_defined?, n) assert_nil(eval("defined?(m::#{n})")) end end def test_nonascii_const_set assert_nonascii_const do |m, n| m.const_set(n, 42) end end def test_nonascii_constant assert_nonascii_const do |m, n| m.module_eval("class #{n}; self; end") end end def test_cdmarg_after_command_args_and_tlbrace_arg assert_valid_syntax('let () { m(a) do; end }') end =begin def test_past_scope_variable assert_warning(/past scope/) {catch {|tag| eval("BEGIN{throw tag}; tap {a = 1}; a")}} end =end end