diff options
29 files changed, 419 insertions, 127 deletions
diff --git a/include/mruby.h b/include/mruby.h index bc6800e29..6e222057f 100644 --- a/include/mruby.h +++ b/include/mruby.h @@ -221,6 +221,9 @@ typedef struct mrb_state { struct RClass *eException_class; struct RClass *eStandardError_class; struct RObject *nomem_err; /* pre-allocated NoMemoryError */ +#ifdef MRB_GC_FIXED_ARENA + struct RObject *arena_err; /* pre-allocated arena overfow error */ +#endif void *ud; /* auxiliary data */ @@ -430,7 +433,7 @@ MRB_API void mrb_define_const(mrb_state*, struct RClass*, const char *name, mrb_ * * mrb_value * mrb_example_method(mrb_state *mrb){ - * return mrb_str_new_cstr(mrb, "example"); + * return mrb_str_new_lit(mrb, "example"); * } * * void @@ -473,7 +476,7 @@ MRB_API void mrb_undef_method(mrb_state*, struct RClass*, const char*); * * mrb_value * mrb_example_method(mrb_state *mrb){ - * return mrb_str_new_cstr(mrb, "example"); + * return mrb_str_new_lit(mrb, "example"); * } * * void @@ -696,7 +699,7 @@ MRB_API mrb_value mrb_check_to_integer(mrb_state *mrb, mrb_value val, const char * * example_class = mrb_define_class(mrb, "ExampleClass", mrb->object_class); * mrb_define_method(mrb, example_class, "example_method", exampleMethod, MRB_ARGS_NONE()); - * mid = mrb_intern_str(mrb, mrb_str_new_cstr(mrb, "example_method" )); + * mid = mrb_intern_str(mrb, mrb_str_new_lit(mrb, "example_method" )); * obj_resp = mrb_obj_respond_to(mrb, example_class, mid); // => 1(true in Ruby world) * * // If mrb_obj_respond_to returns 1 then puts "True" @@ -884,7 +887,7 @@ MRB_API mrb_value mrb_funcall(mrb_state*, mrb_value, const char*, mrb_int,...); * mrb_state *mrb = mrb_open(); * * if (!mrb) { } - * mrb_sym m_sym = mrb_intern_cstr(mrb, "method_name"); // Symbol for method. + * mrb_sym m_sym = mrb_intern_lit(mrb, "method_name"); // Symbol for method. * * FILE *fp = fopen("test.rb","r"); * mrb_value obj = mrb_load_file(mrb,fp); @@ -912,7 +915,7 @@ MRB_API mrb_value mrb_funcall_with_block(mrb_state*, mrb_value, mrb_sym, mrb_int * :pizza # => :pizza * * // C style: - * mrb_sym m_sym = mrb_intern_cstr(mrb, "pizza"); // => :pizza + * mrb_sym m_sym = mrb_intern_lit(mrb, "pizza"); // => :pizza * @param [mrb_state*] mrb_state* The current mruby state. * @param [const char*] const char* The name of the method. * @return [mrb_sym] mrb_sym A symbol. diff --git a/include/mruby/boxing_nan.h b/include/mruby/boxing_nan.h index 052164ffc..4cb82bf14 100644 --- a/include/mruby/boxing_nan.h +++ b/include/mruby/boxing_nan.h @@ -67,14 +67,12 @@ typedef struct mrb_value { #endif #define BOXNAN_SET_VALUE(o, tt, attr, v) do {\ - switch (tt) {\ - case MRB_TT_FALSE:\ - case MRB_TT_TRUE:\ - case MRB_TT_UNDEF:\ - case MRB_TT_FIXNUM:\ - case MRB_TT_SYMBOL: (o).attr = (v); break;\ - default: (o).value.i = 0; (o).value.p = (void*)((uintptr_t)(o).value.p | (((uintptr_t)(v))>>2)); break;\ - }\ + (o).attr = (v);\ + (o).value.ttt = 0xfff00000 | (((tt)+1)<<14);\ +} while (0) + +#define BOXNAN_SET_OBJ_VALUE(o, tt, v) do {\ + (o).value.p = (void*)((uintptr_t)(v)>>2);\ (o).value.ttt = (0xfff00000|(((tt)+1)<<14)|BOXNAN_SHIFT_LONG_POINTER(v));\ } while (0) @@ -92,8 +90,8 @@ typedef struct mrb_value { #define SET_BOOL_VALUE(r,b) BOXNAN_SET_VALUE(r, b ? MRB_TT_TRUE : MRB_TT_FALSE, value.i, 1) #define SET_INT_VALUE(r,n) BOXNAN_SET_VALUE(r, MRB_TT_FIXNUM, value.i, (n)) #define SET_SYM_VALUE(r,v) BOXNAN_SET_VALUE(r, MRB_TT_SYMBOL, value.sym, (v)) -#define SET_OBJ_VALUE(r,v) BOXNAN_SET_VALUE(r, (((struct RObject*)(v))->tt), value.p, (v)) -#define SET_CPTR_VALUE(mrb,r,v) BOXNAN_SET_VALUE(r, MRB_TT_CPTR, value.p, v) +#define SET_OBJ_VALUE(r,v) BOXNAN_SET_OBJ_VALUE(r, (((struct RObject*)(v))->tt), (v)) +#define SET_CPTR_VALUE(mrb,r,v) BOXNAN_SET_OBJ_VALUE(r, MRB_TT_CPTR, v) #define SET_UNDEF_VALUE(r) BOXNAN_SET_VALUE(r, MRB_TT_UNDEF, value.i, 0) #endif /* MRUBY_BOXING_NAN_H */ diff --git a/include/mruby/boxing_word.h b/include/mruby/boxing_word.h index 8754087a3..30d69842f 100644 --- a/include/mruby/boxing_word.h +++ b/include/mruby/boxing_word.h @@ -11,6 +11,10 @@ # error MRB_INT16 is too small for MRB_WORD_BOXING. #endif +#if defined(MRB_INT64) && !defined(MRB_64BIT) +#error MRB_INT64 cannot be used with MRB_WORD_BOXING in 32-bit mode. +#endif + struct RFloat { MRB_OBJECT_HEADER; mrb_float f; diff --git a/include/mruby/istruct.h b/include/mruby/istruct.h index 293a13788..4d2393ccd 100644 --- a/include/mruby/istruct.h +++ b/include/mruby/istruct.h @@ -1,5 +1,5 @@ /* -** mruby/instruct.h - Inline structures +** mruby/istruct.h - Inline structures ** ** See Copyright Notice in mruby.h */ diff --git a/include/mruby/string.h b/include/mruby/string.h index e45846e87..b30c1ed98 100644 --- a/include/mruby/string.h +++ b/include/mruby/string.h @@ -110,8 +110,8 @@ MRB_API void mrb_str_modify(mrb_state*, struct RString*); * } * * // Creates new Ruby strings. - * str1 = mrb_str_new_cstr(mrb, "abc"); - * str2 = mrb_str_new_cstr(mrb, "def"); + * str1 = mrb_str_new_lit(mrb, "abc"); + * str2 = mrb_str_new_lit(mrb, "def"); * * // Concatnates str2 to str1. * mrb_str_concat(mrb, str1, str2); @@ -158,8 +158,8 @@ MRB_API void mrb_str_concat(mrb_state*, mrb_value, mrb_value); * } * * // Creates two Ruby strings from the passed in C strings. - * a = mrb_str_new_cstr(mrb, "abc"); - * b = mrb_str_new_cstr(mrb, "def"); + * a = mrb_str_new_lit(mrb, "abc"); + * b = mrb_str_new_lit(mrb, "def"); * * // Prints both C strings. * mrb_p(mrb, a); @@ -227,7 +227,7 @@ MRB_API mrb_value mrb_obj_as_string(mrb_state *mrb, mrb_value obj); * // handle error * } * // Creates a new string. - * str = mrb_str_new_cstr(mrb, "Hello, world!"); + * str = mrb_str_new_lit(mrb, "Hello, world!"); * // Returns 5 characters of * mrb_str_resize(mrb, str, 5); * mrb_p(mrb, str); @@ -267,7 +267,7 @@ MRB_API mrb_value mrb_str_resize(mrb_state *mrb, mrb_value str, mrb_int len); * // handle error * } * // Creates new string. - * str1 = mrb_str_new_cstr(mrb, "Hello, world!"); + * str1 = mrb_str_new_lit(mrb, "Hello, world!"); * // Returns a sub-string within the range of 0..2 * str2 = mrb_str_substr(mrb, str1, 0, 2); * diff --git a/include/mruby/variable.h b/include/mruby/variable.h index 15068039a..2f2bbbf98 100644 --- a/include/mruby/variable.h +++ b/include/mruby/variable.h @@ -54,9 +54,67 @@ MRB_API mrb_bool mrb_iv_defined(mrb_state*, mrb_value, mrb_sym); MRB_API mrb_value mrb_iv_remove(mrb_state *mrb, mrb_value obj, mrb_sym sym); MRB_API void mrb_iv_copy(mrb_state *mrb, mrb_value dst, mrb_value src); MRB_API mrb_bool mrb_const_defined_at(mrb_state *mrb, mrb_value mod, mrb_sym id); + +/** + * Get a global variable. Will return nil if the var does not exist + * + * Example: + * + * !!!ruby + * # Ruby style + * var = $value + * + * !!!c + * // C style + * mrb_sym sym = mrb_intern_lit(mrb, "$value"); + * mrb_value var = mrb_gv_get(mrb, sym); + * + * @param mrb The mruby state reference + * @param sym The name of the global variable + * @return The value of that global variable. May be nil + */ MRB_API mrb_value mrb_gv_get(mrb_state *mrb, mrb_sym sym); + +/** + * Set a global variable + * + * Example: + * + * !!!ruby + * # Ruby style + * $value = "foo" + * + * !!!c + * // C style + * mrb_sym sym = mrb_intern_lit(mrb, "$value"); + * mrb_gv_set(mrb, sym, mrb_str_new_lit("foo")); + * + * @param mrb The mruby state reference + * @param sym The name of the global variable + * @param val The value of the global variable + */ MRB_API void mrb_gv_set(mrb_state *mrb, mrb_sym sym, mrb_value val); + +/** + * Remove a global variable. + * + * Example: + * + * !!!ruby + * # Ruby style + * $value = nil + * + * !!!c + * // C style + * mrb_sym sym = mrb_intern_lit(mrb, "$value"); + * mrb_gv_remove(mrb, sym); + * + * @param mrb The mruby state reference + * @param sym The name of the global variable + * @param val The value of the global variable + */ MRB_API void mrb_gv_remove(mrb_state *mrb, mrb_sym sym); + MRB_API mrb_value mrb_cv_get(mrb_state *mrb, mrb_value mod, mrb_sym sym); MRB_API void mrb_mod_cv_set(mrb_state *mrb, struct RClass * c, mrb_sym sym, mrb_value v); MRB_API void mrb_cv_set(mrb_state *mrb, mrb_value mod, mrb_sym sym, mrb_value v); diff --git a/mrbgems/mruby-compiler/bintest/mrbc.rb b/mrbgems/mruby-compiler/bintest/mrbc.rb index e4dc6a9a8..e27365edb 100644 --- a/mrbgems/mruby-compiler/bintest/mrbc.rb +++ b/mrbgems/mruby-compiler/bintest/mrbc.rb @@ -10,3 +10,12 @@ assert('Compiling multiple files without new line in last line. #2361') do assert_equal "#{cmd('mrbc')}:#{a.path}:Syntax OK", result.chomp assert_equal 0, $?.exitstatus end + +assert('parsing function with void argument') do + a, out = Tempfile.new('a.rb'), Tempfile.new('out.mrb') + a.write('f ()') + a.flush + result = `#{cmd('mrbc')} -c -o #{out.path} #{a.path} 2>&1` + assert_equal "#{cmd('mrbc')}:#{a.path}:Syntax OK", result.chomp + assert_equal 0, $?.exitstatus +end diff --git a/mrbgems/mruby-compiler/core/codegen.c b/mrbgems/mruby-compiler/core/codegen.c index 0c84dd558..553baa116 100644 --- a/mrbgems/mruby-compiler/core/codegen.c +++ b/mrbgems/mruby-compiler/core/codegen.c @@ -772,6 +772,8 @@ attrsym(codegen_scope *s, mrb_sym a) return mrb_intern(s->mrb, name2, len+1); } +#define CALL_MAXARGS 127 + static int gen_values(codegen_scope *s, node *t, int val) { @@ -780,7 +782,9 @@ gen_values(codegen_scope *s, node *t, int val) while (t) { is_splat = (intptr_t)t->car->car == NODE_SPLAT; /* splat mode */ - if (n >= 127 || is_splat) { + if ( + n >= CALL_MAXARGS - 1 /* need to subtract one because vm.c expects an array if n == CALL_MAXARGS */ + || is_splat) { if (val) { if (is_splat && n == 0 && (intptr_t)t->car->cdr->car == NODE_ARRAY) { codegen(s, t->car->cdr, VAL); @@ -831,8 +835,6 @@ gen_values(codegen_scope *s, node *t, int val) return n; } -#define CALL_MAXARGS 127 - static void gen_call(codegen_scope *s, node *tree, mrb_sym name, int sp, int val, int safe) { @@ -1362,6 +1364,10 @@ codegen(codegen_scope *s, node *tree, int val) int pos1, pos2; node *e = tree->cdr->cdr->car; + if (!tree->car) { + codegen(s, e, val); + return; + } switch ((intptr_t)tree->car->car) { case NODE_TRUE: case NODE_INT: @@ -1798,8 +1804,10 @@ codegen(codegen_scope *s, node *tree, int val) int pos; pop(); - if (val && vsp >= 0) { - genop(s, MKOP_AB(OP_MOVE, vsp, cursp())); + if (val) { + if (vsp >= 0) { + genop(s, MKOP_AB(OP_MOVE, vsp, cursp())); + } pos = genop(s, MKOP_AsBx(name[0]=='|'?OP_JMPIF:OP_JMPNOT, cursp(), 0)); } else { @@ -2024,6 +2032,7 @@ codegen(codegen_scope *s, node *tree, int val) } genop(s, MKOP_sBx(OP_JMP, s->loop->pc2 - s->pc)); } + if (val) push(); break; case NODE_RETRY: @@ -2058,6 +2067,7 @@ codegen(codegen_scope *s, node *tree, int val) genop(s, MKOP_sBx(OP_JMP, lp->pc1 - s->pc)); } } + if (val) push(); } break; @@ -2285,7 +2295,11 @@ codegen(codegen_scope *s, node *tree, int val) if (val) { node *n = tree; - if (!n) break; + if (!n) { + genop(s, MKOP_A(OP_LOADNIL, cursp())); + push(); + break; + } codegen(s, n->car, VAL); n = n->cdr; while (n) { diff --git a/mrbgems/mruby-compiler/core/parse.y b/mrbgems/mruby-compiler/core/parse.y index 0ff7d819c..9f6914104 100644 --- a/mrbgems/mruby-compiler/core/parse.y +++ b/mrbgems/mruby-compiler/core/parse.y @@ -2109,7 +2109,7 @@ primary : literal } | tLPAREN_ARG {p->lstate = EXPR_ENDARG;} rparen { - $$ = 0; + $$ = new_nil(p); } | tLPAREN compstmt ')' { @@ -2137,17 +2137,9 @@ primary : literal { $$ = new_return(p, 0); } - | keyword_yield '(' call_args rparen - { - $$ = new_yield(p, $3); - } - | keyword_yield '(' rparen + | keyword_yield opt_paren_args { - $$ = new_yield(p, 0); - } - | keyword_yield - { - $$ = new_yield(p, 0); + $$ = new_yield(p, $2); } | keyword_not '(' expr rparen { @@ -6541,7 +6533,7 @@ mrb_parser_dump(mrb_state *mrb, node *tree, int offset) case NODE_HEREDOC: printf("NODE_HEREDOC (<<%s):\n", ((parser_heredoc_info*)tree)->term); - mrb_parser_dump(mrb, ((parser_heredoc_info*)tree)->doc, offset+1); + dump_recur(mrb, ((parser_heredoc_info*)tree)->doc, offset+1); break; default: diff --git a/mrbgems/mruby-enum-ext/mrblib/enum.rb b/mrbgems/mruby-enum-ext/mrblib/enum.rb index 5b50aba00..113b470a9 100644 --- a/mrbgems/mruby-enum-ext/mrblib/enum.rb +++ b/mrbgems/mruby-enum-ext/mrblib/enum.rb @@ -215,21 +215,28 @@ module Enumerable # Returns the first element, or the first +n+ elements, of the enumerable. # If the enumerable is empty, the first form returns <code>nil</code>, and the # second form returns an empty array. - def first(n=NONE) - if n == NONE + def first(*args) + case args.length + when 0 self.each do |*val| return val.__svalue end return nil - else - a = [] - i = 0 + when 1 + n = args[0] + raise TypeError, "no implicit conversion of #{n.class} into Integer" unless n.respond_to?(:to_int) + i = n.to_int + raise ArgumentError, "attempt to take negative size" if i < 0 + ary = [] + return ary if i == 0 self.each do |*val| - break if n<=i - a.push val.__svalue - i += 1 + ary << val.__svalue + i -= 1 + break if i == 0 end - a + ary + else + raise ArgumentError, "wrong number of arguments (given #{args.length}, expected 0..1)" end end diff --git a/mrbgems/mruby-object-ext/test/object.rb b/mrbgems/mruby-object-ext/test/object.rb index fe56f1ec5..f0742f8ce 100644 --- a/mrbgems/mruby-object-ext/test/object.rb +++ b/mrbgems/mruby-object-ext/test/object.rb @@ -23,3 +23,31 @@ assert('Object#tap') do ], ret assert_equal(:tap_ok, Class.new {def m; tap{return :tap_ok}; end}.new.m) end + +assert('instance_exec on primitives with class and module definition') do + begin + class A + 1.instance_exec do + class B + end + end + end + + assert_kind_of Class, A::B + ensure + Object.remove_const :A + end + + begin + class A + 1.instance_exec do + module B + end + end + end + + assert_kind_of Module, A::B + ensure + Object.remove_const :A + end +end diff --git a/mrbgems/mruby-proc-ext/src/proc.c b/mrbgems/mruby-proc-ext/src/proc.c index c8c8f1aa1..34f6230dc 100644 --- a/mrbgems/mruby-proc-ext/src/proc.c +++ b/mrbgems/mruby-proc-ext/src/proc.c @@ -114,6 +114,9 @@ mrb_proc_parameters(mrb_state *mrb, mrb_value self) // TODO cfunc aspec is not implemented yet return mrb_ary_new(mrb); } + if (!irep) { + return mrb_ary_new(mrb); + } if (!irep->lv) { return mrb_ary_new(mrb); } diff --git a/mrbgems/mruby-proc-ext/test/proc.rb b/mrbgems/mruby-proc-ext/test/proc.rb index 75e11dd93..7a078aabf 100644 --- a/mrbgems/mruby-proc-ext/test/proc.rb +++ b/mrbgems/mruby-proc-ext/test/proc.rb @@ -58,6 +58,17 @@ assert('Proc#parameters') do assert_equal([[:req, :a], [:req, :b], [:opt, :c], [:opt, :d], [:rest, :e], [:req, :f], [:req, :g], [:block, :h]], lambda {|a,b,c=:c,d=:d,*e,f,g,&h|}.parameters) end +assert('Proc#parameters with uninitialized Proc') do + begin + Proc.alias_method(:original_initialize, :initialize) + Proc.remove_method(:initialize) + assert_equal [], Proc.new{|a, b, c| 1}.parameters + ensure + Proc.alias_method(:initialize, :original_initialize) + Proc.remove_method(:original_initialize) + end +end + assert('Proc#to_proc') do proc = Proc.new {} assert_equal proc, proc.to_proc diff --git a/mrbgems/mruby-string-ext/src/string.c b/mrbgems/mruby-string-ext/src/string.c index 122ee5454..dfac907ec 100644 --- a/mrbgems/mruby-string-ext/src/string.c +++ b/mrbgems/mruby-string-ext/src/string.c @@ -23,10 +23,11 @@ static mrb_value mrb_str_setbyte(mrb_state *mrb, mrb_value str) { mrb_int pos, byte; - long len = RSTRING_LEN(str); + long len; mrb_get_args(mrb, "ii", &pos, &byte); + len = RSTRING_LEN(str); if (pos < -len || len <= pos) mrb_raisef(mrb, E_INDEX_ERROR, "index %S is out of array", mrb_fixnum_value(pos)); if (pos < 0) diff --git a/mrbgems/mruby-string-ext/test/string.rb b/mrbgems/mruby-string-ext/test/string.rb index a5d55a7ee..228a236af 100644 --- a/mrbgems/mruby-string-ext/test/string.rb +++ b/mrbgems/mruby-string-ext/test/string.rb @@ -30,6 +30,18 @@ assert('String#setbyte') do assert_equal("Hello", str1) end +assert("String#setbyte raises IndexError if arg conversion resizes String") do + $s = "01234\n" + class Tmp + def to_i + $s.chomp! '' + 95 + end + end + tmp = Tmp.new + assert_raise(IndexError) { $s.setbyte(5, tmp) } +end + assert('String#byteslice') do str1 = "hello" assert_equal("e", str1.byteslice(1)) diff --git a/mrbgems/mruby-struct/src/struct.c b/mrbgems/mruby-struct/src/struct.c index 0ccb7f4cb..93bd1e2b2 100644 --- a/mrbgems/mruby-struct/src/struct.c +++ b/mrbgems/mruby-struct/src/struct.c @@ -203,7 +203,7 @@ make_struct(mrb_state *mrb, mrb_value name, mrb_value members, struct RClass * k } if (mrb_const_defined_at(mrb, mrb_obj_value(klass), id)) { mrb_warn(mrb, "redefining constant Struct::%S", name); - /* ?rb_mod_remove_const(klass, mrb_sym2name(mrb, id)); */ + mrb_const_remove(mrb, mrb_obj_value(klass), id); } c = mrb_define_class_under(mrb, klass, RSTRING_PTR(name), klass); } @@ -273,31 +273,21 @@ mrb_struct_s_def(mrb_state *mrb, mrb_value klass) } else { if (argc > 0) name = argv[0]; - if (argc > 1) rest = argv[1]; - if (mrb_array_p(rest)) { - if (!mrb_nil_p(name) && mrb_symbol_p(name)) { - /* 1stArgument:symbol -> name=nil rest=argv[0]-[n] */ - mrb_ary_unshift(mrb, rest, name); - name = mrb_nil_value(); - } - } - else { - pargv = &argv[1]; - argcnt = argc-1; - if (!mrb_nil_p(name) && mrb_symbol_p(name)) { - /* 1stArgument:symbol -> name=nil rest=argv[0]-[n] */ - name = mrb_nil_value(); - pargv = &argv[0]; - argcnt++; - } - rest = mrb_ary_new_from_values(mrb, argcnt, pargv); + pargv = &argv[1]; + argcnt = argc-1; + if (!mrb_nil_p(name) && mrb_symbol_p(name)) { + /* 1stArgument:symbol -> name=nil rest=argv[0]-[n] */ + name = mrb_nil_value(); + pargv = &argv[0]; + argcnt++; } + rest = mrb_ary_new_from_values(mrb, argcnt, pargv); for (i=0; i<RARRAY_LEN(rest); i++) { id = mrb_obj_to_sym(mrb, RARRAY_PTR(rest)[i]); mrb_ary_set(mrb, rest, i, mrb_symbol_value(id)); } } - st = make_struct(mrb, name, rest, struct_class(mrb)); + st = make_struct(mrb, name, rest, mrb_class_ptr(klass)); if (!mrb_nil_p(b)) { mrb_yield_with_class(mrb, b, 1, &st, st, mrb_class_ptr(st)); } diff --git a/mrbgems/mruby-struct/test/struct.rb b/mrbgems/mruby-struct/test/struct.rb index 02ecf69e4..1c0939e7f 100644 --- a/mrbgems/mruby-struct/test/struct.rb +++ b/mrbgems/mruby-struct/test/struct.rb @@ -158,3 +158,42 @@ assert("Struct#dig") do assert_equal 1, a.dig(:purple, :red) assert_equal 1, a.dig(1, 0) end + +assert("Struct.new removes existing constant") do + skip "redefining Struct with same name cause warnings" + begin + assert_not_equal Struct.new("Test", :a), Struct.new("Test", :a, :b) + ensure + Struct.remove_const :Test + end +end + +assert("Struct#initialize_copy requires struct to be the same type") do + begin + Struct.new("Test", :a) + a = Struct::Test.new("a") + Struct.remove_const :Test + Struct.new("Test", :a, :b) + assert_raise(TypeError) do + a.initialize_copy(Struct::Test.new("a", "b")) + end + ensure + Struct.remove_const :Test + end +end + +assert("Struct.new does not allow array") do + assert_raise(TypeError) do + Struct.new("Test", [:a]) + end +end + +assert("Struct.new generates subclass of Struct") do + begin + original_struct = Struct + Struct = String + assert_equal original_struct, original_struct.new.superclass + ensure + Struct = original_struct + end +end diff --git a/mrbgems/mruby-time/src/time.c b/mrbgems/mruby-time/src/time.c index 5c23bd44a..089814890 100644 --- a/mrbgems/mruby-time/src/time.c +++ b/mrbgems/mruby-time/src/time.c @@ -368,6 +368,17 @@ mrb_time_local(mrb_state *mrb, mrb_value self) time_mktime(mrb, ayear, amonth, aday, ahour, amin, asec, ausec, MRB_TIMEZONE_LOCAL)); } +static struct mrb_time* +time_get_ptr(mrb_state *mrb, mrb_value time) +{ + struct mrb_time *tm; + + tm = DATA_GET_PTR(mrb, time, &mrb_time_type, struct mrb_time); + if (!tm) { + mrb_raise(mrb, E_ARGUMENT_ERROR, "uninitialized time"); + } + return tm; +} static mrb_value mrb_time_eq(mrb_state *mrb, mrb_value self) @@ -377,7 +388,7 @@ mrb_time_eq(mrb_state *mrb, mrb_value self) mrb_bool eq_p; mrb_get_args(mrb, "o", &other); - tm1 = DATA_CHECK_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm1 = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); tm2 = DATA_CHECK_GET_PTR(mrb, other, &mrb_time_type, struct mrb_time); eq_p = tm1 && tm2 && tm1->sec == tm2->sec && tm1->usec == tm2->usec; @@ -391,7 +402,7 @@ mrb_time_cmp(mrb_state *mrb, mrb_value self) struct mrb_time *tm1, *tm2; mrb_get_args(mrb, "o", &other); - tm1 = DATA_CHECK_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm1 = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); tm2 = DATA_CHECK_GET_PTR(mrb, other, &mrb_time_type, struct mrb_time); if (!tm1 || !tm2) return mrb_nil_value(); if (tm1->sec > tm2->sec) { @@ -417,7 +428,7 @@ mrb_time_plus(mrb_state *mrb, mrb_value self) struct mrb_time *tm; mrb_get_args(mrb, "f", &f); - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_time_make(mrb, mrb_obj_class(mrb, self), (double)tm->sec+f, (double)tm->usec, tm->timezone); } @@ -429,8 +440,7 @@ mrb_time_minus(mrb_state *mrb, mrb_value self) struct mrb_time *tm, *tm2; mrb_get_args(mrb, "o", &other); - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); - + tm = time_get_ptr(mrb, self); tm2 = DATA_CHECK_GET_PTR(mrb, other, &mrb_time_type, struct mrb_time); if (tm2) { f = (mrb_float)(tm->sec - tm2->sec) @@ -450,7 +460,7 @@ mrb_time_wday(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_wday); } @@ -461,7 +471,7 @@ mrb_time_yday(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_yday + 1); } @@ -472,7 +482,7 @@ mrb_time_year(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_year + 1900); } @@ -483,7 +493,7 @@ mrb_time_zone(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); if (tm->timezone <= MRB_TIMEZONE_NONE) return mrb_nil_value(); if (tm->timezone >= MRB_TIMEZONE_LAST) return mrb_nil_value(); return mrb_str_new_static(mrb, @@ -501,7 +511,7 @@ mrb_time_asctime(mrb_state *mrb, mrb_value self) char buf[256]; int len; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); d = &tm->datetime; len = snprintf(buf, sizeof(buf), "%s %s %02d %02d:%02d:%02d %s%d", wday_names[d->tm_wday], mon_names[d->tm_mon], d->tm_mday, @@ -518,8 +528,7 @@ mrb_time_day(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); - if (!tm) return mrb_nil_value(); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_mday); } @@ -531,7 +540,7 @@ mrb_time_dst_p(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_bool_value(tm->datetime.tm_isdst); } @@ -543,7 +552,7 @@ mrb_time_getutc(mrb_state *mrb, mrb_value self) { struct mrb_time *tm, *tm2; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); tm2 = (struct mrb_time *)mrb_malloc(mrb, sizeof(*tm)); *tm2 = *tm; tm2->timezone = MRB_TIMEZONE_UTC; @@ -558,7 +567,7 @@ mrb_time_getlocal(mrb_state *mrb, mrb_value self) { struct mrb_time *tm, *tm2; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); tm2 = (struct mrb_time *)mrb_malloc(mrb, sizeof(*tm)); *tm2 = *tm; tm2->timezone = MRB_TIMEZONE_LOCAL; @@ -573,7 +582,7 @@ mrb_time_hour(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_hour); } @@ -587,14 +596,14 @@ mrb_time_initialize(mrb_state *mrb, mrb_value self) int n; struct mrb_time *tm; + n = mrb_get_args(mrb, "|iiiiiii", + &ayear, &amonth, &aday, &ahour, &amin, &asec, &ausec); tm = (struct mrb_time*)DATA_PTR(self); if (tm) { mrb_free(mrb, tm); } mrb_data_init(self, NULL, &mrb_time_type); - n = mrb_get_args(mrb, "|iiiiiii", - &ayear, &amonth, &aday, &ahour, &amin, &asec, &ausec); if (n == 0) { tm = current_mrb_time(mrb); } @@ -638,7 +647,7 @@ mrb_time_localtime(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); tm->timezone = MRB_TIMEZONE_LOCAL; mrb_time_update_datetime(tm); return self; @@ -651,7 +660,7 @@ mrb_time_mday(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_mday); } @@ -662,7 +671,7 @@ mrb_time_min(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_min); } @@ -673,7 +682,7 @@ mrb_time_mon(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_mon + 1); } @@ -684,7 +693,7 @@ mrb_time_sec(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_fixnum_value(tm->datetime.tm_sec); } @@ -696,7 +705,7 @@ mrb_time_to_f(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_float_value(mrb, (mrb_float)tm->sec + (mrb_float)tm->usec/1.0e6); } @@ -707,7 +716,7 @@ mrb_time_to_i(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); if (tm->sec > MRB_INT_MAX || tm->sec < MRB_INT_MIN) { return mrb_float_value(mrb, (mrb_float)tm->sec); } @@ -721,7 +730,7 @@ mrb_time_usec(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); if (tm->usec > MRB_INT_MAX || tm->usec < MRB_INT_MIN) { return mrb_float_value(mrb, (mrb_float)tm->usec); } @@ -735,7 +744,7 @@ mrb_time_utc(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); tm->timezone = MRB_TIMEZONE_UTC; mrb_time_update_datetime(tm); return self; @@ -748,7 +757,7 @@ mrb_time_utc_p(mrb_state *mrb, mrb_value self) { struct mrb_time *tm; - tm = DATA_GET_PTR(mrb, self, &mrb_time_type, struct mrb_time); + tm = time_get_ptr(mrb, self); return mrb_bool_value(tm->timezone == MRB_TIMEZONE_UTC); } diff --git a/src/array.c b/src/array.c index 106353c07..df037a121 100644 --- a/src/array.c +++ b/src/array.c @@ -20,15 +20,12 @@ static struct RArray* ary_new_capa(mrb_state *mrb, mrb_int capa) { struct RArray *a; - mrb_int blen; + size_t blen; if (capa > ARY_MAX_SIZE) { mrb_raise(mrb, E_ARGUMENT_ERROR, "array size too big"); } blen = capa * sizeof(mrb_value); - if (blen < capa) { - mrb_raise(mrb, E_ARGUMENT_ERROR, "array size too big"); - } a = (struct RArray*)mrb_obj_alloc(mrb, MRB_TT_ARRAY, mrb->array_class); a->ptr = (mrb_value *)mrb_malloc(mrb, blen); @@ -245,13 +242,15 @@ mrb_ary_s_create(mrb_state *mrb, mrb_value self) } static void -ary_concat(mrb_state *mrb, struct RArray *a, mrb_value *ptr, mrb_int blen) +ary_concat(mrb_state *mrb, struct RArray *a, struct RArray *a2) { - mrb_int len = a->len + blen; + mrb_int len = a->len + a2->len; ary_modify(mrb, a); - if (a->aux.capa < len) ary_expand_capa(mrb, a, len); - array_copy(a->ptr+a->len, ptr, blen); + if (a->aux.capa < len) { + ary_expand_capa(mrb, a, len); + } + array_copy(a->ptr+a->len, a2->ptr, a2->len); mrb_write_barrier(mrb, (struct RBasic*)a); a->len = len; } @@ -261,17 +260,16 @@ mrb_ary_concat(mrb_state *mrb, mrb_value self, mrb_value other) { struct RArray *a2 = mrb_ary_ptr(other); - ary_concat(mrb, mrb_ary_ptr(self), a2->ptr, a2->len); + ary_concat(mrb, mrb_ary_ptr(self), a2); } static mrb_value mrb_ary_concat_m(mrb_state *mrb, mrb_value self) { - mrb_value *ptr; - mrb_int blen; + mrb_value ary; - mrb_get_args(mrb, "a", &ptr, &blen); - ary_concat(mrb, mrb_ary_ptr(self), ptr, blen); + mrb_get_args(mrb, "A", &ary); + mrb_ary_concat(mrb, self, ary); return self; } diff --git a/src/class.c b/src/class.c index d02253c57..d120f1fec 100644 --- a/src/class.c +++ b/src/class.c @@ -2068,7 +2068,7 @@ mrb_mod_remove_method(mrb_state *mrb, mrb_value mod) mrb_get_args(mrb, "*", &argv, &argc); while (argc--) { - remove_method(mrb, mod, mrb_symbol(*argv)); + remove_method(mrb, mod, to_sym(mrb, *argv)); argv++; } return mod; diff --git a/src/error.c b/src/error.c index 13032b136..b24aed798 100644 --- a/src/error.c +++ b/src/error.c @@ -278,6 +278,8 @@ mrb_exc_set(mrb_state *mrb, mrb_value exc) mrb->exc = 0; } else { + if (!mrb_obj_is_kind_of(mrb, exc, mrb->eException_class)) + mrb_raise(mrb, E_TYPE_ERROR, "exception object expected"); mrb->exc = mrb_obj_ptr(exc); } } @@ -463,7 +465,7 @@ exception_call: } if (argc > 0) { if (!mrb_obj_is_kind_of(mrb, mesg, mrb->eException_class)) - mrb_raise(mrb, E_TYPE_ERROR, "exception object expected"); + mrb_raise(mrb, mrb->eException_class, "exception object expected"); if (argc > 2) set_backtrace(mrb, mesg, argv[2]); } @@ -530,6 +532,9 @@ mrb_init_exception(mrb_state *mrb) mrb->eStandardError_class = mrb_define_class(mrb, "StandardError", mrb->eException_class); /* 15.2.23 */ runtime_error = mrb_define_class(mrb, "RuntimeError", mrb->eStandardError_class); /* 15.2.28 */ mrb->nomem_err = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, runtime_error, "Out of memory")); +#ifdef MRB_GC_FIXED_ARENA + mrb->arena_err = mrb_obj_ptr(mrb_exc_new_str_lit(mrb, runtime_error, "arena overflow error")); +#endif script_error = mrb_define_class(mrb, "ScriptError", mrb->eException_class); /* 15.2.37 */ mrb_define_class(mrb, "SyntaxError", script_error); /* 15.2.38 */ mrb_define_class(mrb, "SystemStackError", exception); @@ -403,7 +403,7 @@ gc_protect(mrb_state *mrb, mrb_gc *gc, struct RBasic *p) if (gc->arena_idx >= MRB_GC_ARENA_SIZE) { /* arena overflow error */ gc->arena_idx = MRB_GC_ARENA_SIZE - 4; /* force room in arena */ - mrb_raise(mrb, E_RUNTIME_ERROR, "arena overflow error"); + mrb_exc_raise(mrb, mrb_obj_value(mrb->arena_err)); } #else if (gc->arena_idx >= gc->arena_capa) { @@ -479,6 +479,17 @@ mrb_obj_alloc(mrb_state *mrb, enum mrb_vtype ttype, struct RClass *cls) static const RVALUE RVALUE_zero = { { { MRB_TT_FALSE } } }; mrb_gc *gc = &mrb->gc; + if (cls) { + enum mrb_vtype tt = MRB_INSTANCE_TT(cls); + if (tt != MRB_TT_FALSE && + ttype != MRB_TT_SCLASS && + ttype != MRB_TT_ICLASS && + ttype != MRB_TT_ENV && + ttype != tt) { + mrb_raisef(mrb, E_TYPE_ERROR, "allocation failure of %S", mrb_obj_value(cls)); + } + } + #ifdef MRB_GC_STRESS mrb_full_gc(mrb); #endif @@ -816,6 +827,9 @@ root_scan_phase(mrb_state *mrb, mrb_gc *gc) mrb_gc_mark(mrb, (struct RBasic*)mrb->exc); /* mark pre-allocated exception */ mrb_gc_mark(mrb, (struct RBasic*)mrb->nomem_err); +#ifdef MRB_GC_FIXED_ARENA + mrb_gc_mark(mrb, (struct RBasic*)mrb->arena_err); +#endif mark_context(mrb, mrb->root_c); if (mrb->root_c->fib) { diff --git a/src/proc.c b/src/proc.c index 1620bddf8..4f770932b 100644 --- a/src/proc.c +++ b/src/proc.c @@ -188,18 +188,13 @@ mrb_proc_call_cfunc(mrb_state *mrb, struct RProc *p, mrb_value self) return (p->body.func)(mrb, self); } -mrb_code* -mrb_proc_iseq(mrb_state *mrb, struct RProc *p) -{ - return p->body.irep->iseq; -} - /* 15.2.17.4.2 */ static mrb_value mrb_proc_arity(mrb_state *mrb, mrb_value self) { struct RProc *p = mrb_proc_ptr(self); - mrb_code *iseq = mrb_proc_iseq(mrb, p); + struct mrb_irep *irep; + mrb_code *iseq; mrb_aspec aspec; int ma, op, ra, pa, arity; @@ -208,6 +203,12 @@ mrb_proc_arity(mrb_state *mrb, mrb_value self) return mrb_fixnum_value(-1); } + irep = p->body.irep; + if (!irep) { + return mrb_fixnum_value(0); + } + + iseq = irep->iseq; /* arity is depend on OP_ENTER */ if (GET_OPCODE(*iseq) != OP_ENTER) { return mrb_fixnum_value(0); diff --git a/src/string.c b/src/string.c index 5e490bf03..f47294291 100644 --- a/src/string.c +++ b/src/string.c @@ -1235,11 +1235,13 @@ mrb_str_chomp_bang(mrb_state *mrb, mrb_value str) char *p, *pp; mrb_int rslen; mrb_int len; + mrb_int argc; struct RString *s = mrb_str_ptr(str); mrb_str_modify(mrb, s); + argc = mrb_get_args(mrb, "|S", &rs); len = RSTR_LEN(s); - if (mrb_get_args(mrb, "|S", &rs) == 0) { + if (argc == 0) { if (len == 0) return mrb_nil_value(); smart_chomp: if (RSTR_PTR(s)[len-1] == '\n') { @@ -2261,7 +2261,7 @@ RETRY_TRY_BLOCK: CASE(OP_CLASS) { /* A B R(A) := newclass(R(A),Syms(B),R(A+1)) */ - struct RClass *c = 0; + struct RClass *c = 0, *baseclass; int a = GETARG_A(i); mrb_value base, super; mrb_sym id = syms[GETARG_B(i)]; @@ -2269,7 +2269,10 @@ RETRY_TRY_BLOCK: base = regs[a]; super = regs[a+1]; if (mrb_nil_p(base)) { - base = mrb_obj_value(mrb->c->ci->target_class); + baseclass = mrb->c->ci->proc->target_class; + if (!baseclass) baseclass = mrb->c->ci->target_class; + + base = mrb_obj_value(baseclass); } c = mrb_vm_define_class(mrb, base, super, id); regs[a] = mrb_obj_value(c); @@ -2279,14 +2282,17 @@ RETRY_TRY_BLOCK: CASE(OP_MODULE) { /* A B R(A) := newmodule(R(A),Syms(B)) */ - struct RClass *c = 0; + struct RClass *c = 0, *baseclass; int a = GETARG_A(i); mrb_value base; mrb_sym id = syms[GETARG_B(i)]; base = regs[a]; if (mrb_nil_p(base)) { - base = mrb_obj_value(mrb->c->ci->target_class); + baseclass = mrb->c->ci->proc->target_class; + if (!baseclass) baseclass = mrb->c->ci->target_class; + + base = mrb_obj_value(baseclass); } c = mrb_vm_define_module(mrb, base, id); regs[a] = mrb_obj_value(c); diff --git a/test/t/class.rb b/test/t/class.rb index 7bcaaf90d..597999d3e 100644 --- a/test/t/class.rb +++ b/test/t/class.rb @@ -401,3 +401,12 @@ assert('class with non-class/module outer raises TypeError') do assert_raise(TypeError) { class 0::C1; end } assert_raise(TypeError) { class []::C2; end } end + +assert("remove_method doesn't segfault if the passed in argument isn't a symbol") do + klass = Class.new + assert_raise(TypeError) { klass.remove_method nil } + assert_raise(TypeError) { klass.remove_method 123 } + assert_raise(TypeError) { klass.remove_method 1.23 } + assert_raise(NameError) { klass.remove_method "hello" } + assert_raise(TypeError) { klass.remove_method Class.new } +end diff --git a/test/t/codegen.rb b/test/t/codegen.rb new file mode 100644 index 000000000..1ac689a82 --- /dev/null +++ b/test/t/codegen.rb @@ -0,0 +1,56 @@ +## +# Codegen tests + +assert('peephole optimization does not eliminate move whose result is reused') do + assert_raise LocalJumpError do + def method + yield + end + method(&a &&= 0) + end +end + +assert('empty condition in ternary expression parses correctly') do + assert_equal(() ? 1 : 2, 2) +end + +assert('codegen absorbs arguments to redo and retry if they are the argument of a call') do + assert_nothing_raised do + a=*"1", case nil + when 1 + redo | + 1 + end + end + + assert_nothing_raised do + a=*"1", case nil + when 1 + retry | + 1 + end + end +end + +assert('method call with exactly 127 arguments') do + def args_to_ary(*args) + args + end + + assert_equal [0]*127, args_to_ary( + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, \ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 + ) +end + +assert('nested empty heredoc') do + _, a = nil, <<B +#{<<A} +A +B + assert_equal "\n", a +end diff --git a/test/t/proc.rb b/test/t/proc.rb index 888b7d56a..bc9821f7c 100644 --- a/test/t/proc.rb +++ b/test/t/proc.rb @@ -46,6 +46,17 @@ assert('Proc#arity', '15.2.17.4.2') do assert_equal(-1, g) end +assert('Proc#arity with unitialized Proc') do + begin + Proc.alias_method(:original_initialize, :initialize) + Proc.remove_method(:initialize) + assert_equal 0, Proc.new{|a, b, c| 1}.arity + ensure + Proc.alias_method(:initialize, :original_initialize) + Proc.remove_method(:original_initialize) + end +end + assert('Proc#call', '15.2.17.4.3') do a = 0 b = Proc.new { a += 1 } diff --git a/test/t/string.rb b/test/t/string.rb index e67389b5c..80fcbe6fa 100644 --- a/test/t/string.rb +++ b/test/t/string.rb @@ -251,6 +251,19 @@ assert('String#chomp!', '15.2.10.5.10') do assert_equal 'abc', e end +assert('String#chomp! uses the correct length') do + class A + def to_str + $s.replace("AA") + "A" + end + end + + $s = "AAA" + $s.chomp!(A.new) + assert_equal $s, "A" +end + assert('String#chop', '15.2.10.5.11') do a = ''.chop b = 'abc'.chop @@ -683,4 +696,3 @@ assert('String#freeze') do assert_raise(RuntimeError) { str.upcase! } end - |
