diff --git a/.cirrus.yml b/.cirrus.yml index c496d5941bcede..6c471599211423 100644 --- a/.cirrus.yml +++ b/.cirrus.yml @@ -89,7 +89,7 @@ yjit_task: matrix: - CC: clang-12 configure: --enable-yjit=dev - rustup_init: --default-toolchain=1.58.1 + rustup_init: --default-toolchain=1.58.0 - CC: gcc-11 configure: --enable-yjit id_script: id diff --git a/.github/workflows/yjit-ubuntu.yml b/.github/workflows/yjit-ubuntu.yml index d0e50154d2ca05..c38743ff02d361 100644 --- a/.github/workflows/yjit-ubuntu.yml +++ b/.github/workflows/yjit-ubuntu.yml @@ -48,8 +48,8 @@ jobs: configure: "--with-gcc=clang-12 --enable-yjit=dev" - test_task: "check" - configure: "--enable-yjit RUSTC='rustc +1.58.1'" # release build - rust_version: "1.58.1" + configure: "--enable-yjit RUSTC='rustc +1.58.0'" # release build + rust_version: "1.58.0" - test_task: "check" configure: "--enable-yjit=dev" diff --git a/NEWS.md b/NEWS.md index bff9d1e7ab51b4..2f5ca3c248f800 100644 --- a/NEWS.md +++ b/NEWS.md @@ -192,6 +192,14 @@ Note: We're only listing outstanding class updates. * RubyVM::AbstractSyntaxTree * Add `error_tolerant` option for `parse`, `parse_file` and `of`. [[Feature #19013]] + * Add `keep_tokens` option for `parse`, `parse_file` and `of`. Add `#tokens` and `#all_tokens` + for `RubyVM::AbstractSyntaxTree::Node` [[Feature #19070]] + + ```ruby + root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + root.tokens.map{_1[2]}.join # => "x = 1 + 2" + ``` * Set * Set is now available as a built-in class without the need for `require "set"`. [[Feature #16989]] @@ -217,6 +225,10 @@ Note: We're only listing outstanding class updates. * A Struct class can also be initialized with keyword arguments without `keyword_init: true` on `Struct.new` [[Feature #16806]] +* Time + * `Time#deconstruct_keys` is added, allowing to use `Time` instances + in pattern-matching expressions [[Feature #19071]] + * TracePoint * TracePoint#binding now returns `nil` for `c_call`/`c_return` TracePoints. [[Bug #18487]] @@ -235,7 +247,7 @@ Note: We're only listing outstanding class updates. * RubyGems 3.4.0.dev * bigdecimal 3.1.2 * bundler 2.4.0.dev - * cgi 0.3.3 + * cgi 0.3.5 * date 3.2.3 * erb 3.0.0 * error_highlight 0.5.1 @@ -245,7 +257,7 @@ Note: We're only listing outstanding class updates. * io-nonblock 0.1.1 * io-wait 0.3.0.pre * ipaddr 1.2.4 - * irb 1.4.3 + * irb 1.5.0 * json 2.6.2 * logger 1.5.1 * net-http 0.3.0 @@ -359,7 +371,7 @@ The following deprecated APIs are removed. ### YJIT * Support arm64 / aarch64 on UNIX platforms. -* Building YJIT requires Rust 1.58.1+. [[Feature #18481]] +* Building YJIT requires Rust 1.58.0+. [[Feature #18481]] * Physical memory for JIT code is lazily allocated. Unlike Ruby 3.1, the RSS of a Ruby process is minimized because virtual memory pages allocated by `--yjit-exec-mem-size` will not be mapped to physical @@ -437,5 +449,7 @@ The following deprecated APIs are removed. [Feature #19013]: https://bugs.ruby-lang.org/issues/19013 [Feature #19026]: https://bugs.ruby-lang.org/issues/19026 [Feature #19060]: https://bugs.ruby-lang.org/issues/19060 +[Feature #19070]: https://bugs.ruby-lang.org/issues/19070 [Bug #19100]: https://bugs.ruby-lang.org/issues/19100 [Feature #19135]: https://bugs.ruby-lang.org/issues/19135 +[Feature #19071]: https://bugs.ruby-lang.org/issues/19071 diff --git a/README.md b/README.md index 5b1f21abc90298..35ef2d9e8989bc 100644 --- a/README.md +++ b/README.md @@ -54,50 +54,6 @@ see [Building Ruby](doc/contributing/building_ruby.md) https://www.ruby-lang.org/ -## Commands - -The following commands are available on IRB. - -* `cwws` - * Show the current workspace. -* `cb`, `cws`, `chws` - * Change the current workspace to an object. -* `bindings`, `workspaces` - * Show workspaces. -* `pushb`, `pushws` - * Push an object to the workspace stack. -* `popb`, `popws` - * Pop a workspace from the workspace stack. -* `load` - * Load a Ruby file. -* `require` - * Require a Ruby file. -* `source` - * Loads a given file in the current session. -* `irb` - * Start a child IRB. -* `jobs` - * List of current sessions. -* `fg` - * Switches to the session of the given number. -* `kill` - * Kills the session with the given number. -* `help` - * Enter the mode to look up RI documents. -* `irb_info` - * Show information about IRB. -* `ls` - * Show methods, constants, and variables. - `-g [query]` or `-G [query]` allows you to filter out the output. -* `measure` - * `measure` enables the mode to measure processing time. `measure :off` disables it. -* `$`, `show_source` - * Show the source code of a given method or constant. -* `@`, `whereami` - * Show the source code around binding.irb again. -* `debug` - * Start the debugger of debug.gem. - ## Documentation - [English](https://docs.ruby-lang.org/en/master/index.html) diff --git a/array.c b/array.c index f242031b4fffb7..adc4b3147a990c 100644 --- a/array.c +++ b/array.c @@ -2086,7 +2086,7 @@ rb_ary_first(int argc, VALUE *argv, VALUE ary) * * If +self+ is empty, returns +nil+. * - * When non-negative \Innteger argument +n+ is given, + * When non-negative \Integer argument +n+ is given, * returns the last +n+ elements in a new \Array: * * a = [:foo, 'bar', 2] diff --git a/ast.c b/ast.c index 93c33a1f8dd89e..bf3781d820a213 100644 --- a/ast.c +++ b/ast.c @@ -64,8 +64,8 @@ ast_new_internal(rb_ast_t *ast, const NODE *node) return obj; } -static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant); -static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant); +static VALUE rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens); +static VALUE rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens); static VALUE ast_parse_new(void) @@ -85,13 +85,13 @@ ast_parse_done(rb_ast_t *ast) } static VALUE -ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines, VALUE error_tolerant) +ast_s_parse(rb_execution_context_t *ec, VALUE module, VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - return rb_ast_parse_str(str, keep_script_lines, error_tolerant); + return rb_ast_parse_str(str, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant) +rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; @@ -99,18 +99,19 @@ rb_ast_parse_str(VALUE str, VALUE keep_script_lines, VALUE error_tolerant) VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_string_path(vparser, Qnil, str, 1); return ast_parse_done(ast); } static VALUE -ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines, VALUE error_tolerant) +ast_s_parse_file(rb_execution_context_t *ec, VALUE module, VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { - return rb_ast_parse_file(path, keep_script_lines, error_tolerant); + return rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } static VALUE -rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant) +rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE f; rb_ast_t *ast = 0; @@ -122,6 +123,7 @@ rb_ast_parse_file(VALUE path, VALUE keep_script_lines, VALUE error_tolerant) VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_file_path(vparser, Qnil, f, 1); rb_io_close(f); return ast_parse_done(ast); @@ -141,7 +143,7 @@ lex_array(VALUE array, int index) } static VALUE -rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant) +rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { rb_ast_t *ast = 0; @@ -149,6 +151,7 @@ rb_ast_parse_array(VALUE array, VALUE keep_script_lines, VALUE error_tolerant) VALUE vparser = ast_parse_new(); if (RTEST(keep_script_lines)) rb_parser_keep_script_lines(vparser); if (RTEST(error_tolerant)) rb_parser_error_tolerant(vparser); + if (RTEST(keep_tokens)) rb_parser_keep_tokens(vparser); ast = rb_parser_compile_generic(vparser, lex_array, Qnil, array, 1); return ast_parse_done(ast); } @@ -208,7 +211,7 @@ node_id_for_backtrace_location(rb_execution_context_t *ec, VALUE module, VALUE l } static VALUE -ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant) +ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script_lines, VALUE error_tolerant, VALUE keep_tokens) { VALUE node, lines = Qnil; const rb_iseq_t *iseq; @@ -247,13 +250,13 @@ ast_s_of(rb_execution_context_t *ec, VALUE module, VALUE body, VALUE keep_script } if (!NIL_P(lines) || !NIL_P(lines = script_lines(path))) { - node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant); + node = rb_ast_parse_array(lines, keep_script_lines, error_tolerant, keep_tokens); } else if (e_option) { - node = rb_ast_parse_str(rb_e_script, keep_script_lines, error_tolerant); + node = rb_ast_parse_str(rb_e_script, keep_script_lines, error_tolerant, keep_tokens); } else { - node = rb_ast_parse_file(path, keep_script_lines, error_tolerant); + node = rb_ast_parse_file(path, keep_script_lines, error_tolerant, keep_tokens); } return node_find(node, node_id); @@ -715,6 +718,15 @@ ast_node_last_column(rb_execution_context_t *ec, VALUE self) return INT2NUM(nd_last_column(data->node)); } +static VALUE +ast_node_all_tokens(rb_execution_context_t *ec, VALUE self) +{ + struct ASTNodeData *data; + TypedData_Get_Struct(self, struct ASTNodeData, &rb_node_type, data); + + return rb_ast_tokens(data->ast); +} + static VALUE ast_node_inspect(rb_execution_context_t *ec, VALUE self) { diff --git a/ast.rb b/ast.rb index 1bc1168b806172..24740ebe28ca0f 100644 --- a/ast.rb +++ b/ast.rb @@ -29,8 +29,8 @@ module RubyVM::AbstractSyntaxTree # # RubyVM::AbstractSyntaxTree.parse("x = 1 + 2") # # => # - def self.parse string, keep_script_lines: false, error_tolerant: false - Primitive.ast_s_parse string, keep_script_lines, error_tolerant + def self.parse string, keep_script_lines: false, error_tolerant: false, keep_tokens: false + Primitive.ast_s_parse string, keep_script_lines, error_tolerant, keep_tokens end # call-seq: @@ -44,8 +44,8 @@ def self.parse string, keep_script_lines: false, error_tolerant: false # # RubyVM::AbstractSyntaxTree.parse_file("my-app/app.rb") # # => # - def self.parse_file pathname, keep_script_lines: false, error_tolerant: false - Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant + def self.parse_file pathname, keep_script_lines: false, error_tolerant: false, keep_tokens: false + Primitive.ast_s_parse_file pathname, keep_script_lines, error_tolerant, keep_tokens end # call-seq: @@ -63,8 +63,8 @@ def self.parse_file pathname, keep_script_lines: false, error_tolerant: false # # RubyVM::AbstractSyntaxTree.of(method(:hello)) # # => # - def self.of body, keep_script_lines: false, error_tolerant: false - Primitive.ast_s_of body, keep_script_lines, error_tolerant + def self.of body, keep_script_lines: false, error_tolerant: false, keep_tokens: false + Primitive.ast_s_of body, keep_script_lines, error_tolerant, keep_tokens end # call-seq: @@ -136,6 +136,46 @@ def last_column Primitive.ast_node_last_column end + # call-seq: + # node.tokens -> array + # + # Returns tokens corresponding to the location of the node. + # Returns nil if keep_tokens is not enabled when parse method is called. + # Token is an array of: + # + # - id + # - token type + # - source code text + # - location [first_lineno, first_column, last_lineno, last_column] + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + # root.tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + # root.tokens.map{_1[2]}.join # => "x = 1 + 2" + def tokens + return nil unless all_tokens + + all_tokens.each_with_object([]) do |token, a| + loc = token.last + if ([first_lineno, first_column] <=> [loc[0], loc[1]]) <= 0 && + ([last_lineno, last_column] <=> [loc[2], loc[3]]) >= 0 + a << token + end + end + end + + # call-seq: + # node.all_tokens -> array + # + # Returns all tokens for the input script regardless the receiver node. + # Returns nil if keep_tokens is not enabled when parse method is called. + # + # root = RubyVM::AbstractSyntaxTree.parse("x = 1 + 2", keep_tokens: true) + # root.all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + # root.children[-1].all_tokens # => [[0, :tIDENTIFIER, "x", [1, 0, 1, 1]], [1, :tSP, " ", [1, 1, 1, 2]], ...] + def all_tokens + Primitive.ast_node_all_tokens + end + # call-seq: # node.children -> array # diff --git a/common.mk b/common.mk index 24c1b39c4a2a34..fecc64c1cf9953 100644 --- a/common.mk +++ b/common.mk @@ -1400,6 +1400,17 @@ extract-gems$(gnumake:yes=-sequential): PHONY -e 'end' \ gems/bundled_gems +extract-gems: outdate-bundled-gems +outdate-bundled-gems: PHONY + $(Q) $(BASERUBY) -C "$(srcdir)" \ + -rfileutils \ + -e "Dir.glob('.bundle/gems/*/') {|g|" \ + -e "FileUtils::Verbose.rm_rf(g) unless File.exist?(%[gems/#{File.basename(g)}.gem])" \ + -e "}" \ + -e "Dir.glob('.bundle/specifications/*.gemspec') {|g|" \ + -e "FileUtils::Verbose.rm_f(g) unless File.exist?(%[gems/#{File.basename(g, '.gemspec')}.gem])" \ + -e "}" + update-bundled_gems: PHONY $(Q) $(RUNRUBY) -rrubygems \ $(tooldir)/update-bundled_gems.rb \ @@ -15822,6 +15833,7 @@ time.$(OBJEXT): $(top_srcdir)/internal/compar.h time.$(OBJEXT): $(top_srcdir)/internal/compilers.h time.$(OBJEXT): $(top_srcdir)/internal/fixnum.h time.$(OBJEXT): $(top_srcdir)/internal/gc.h +time.$(OBJEXT): $(top_srcdir)/internal/hash.h time.$(OBJEXT): $(top_srcdir)/internal/numeric.h time.$(OBJEXT): $(top_srcdir)/internal/rational.h time.$(OBJEXT): $(top_srcdir)/internal/serial.h diff --git a/configure.ac b/configure.ac index a90a8ff1bb74e9..1d55674224023b 100644 --- a/configure.ac +++ b/configure.ac @@ -65,6 +65,7 @@ AC_ARG_WITH(baseruby, [ AC_PATH_PROG([BASERUBY], [ruby], [false]) ]) +# BASERUBY must be >= 2.2.0. Note that `"2.2.0" > "2.2"` is true. AS_IF([test "$HAVE_BASERUBY" != no -a "`RUBYOPT=- $BASERUBY --disable=gems -e 'print 42 if RUBY_VERSION > "2.2"' 2>/dev/null`" = 42], [ AS_CASE(["$build_os"], [mingw*], [ # Can MSys shell run a command with a drive letter? diff --git a/doc/contributing/building_ruby.md b/doc/contributing/building_ruby.md index 33ed732a2bc458..469c9d8361c7d0 100644 --- a/doc/contributing/building_ruby.md +++ b/doc/contributing/building_ruby.md @@ -18,7 +18,7 @@ * libffi * libyaml * libexecinfo (FreeBSD) - * rustc - 1.58.1 or later (if you wish to build [YJIT](/doc/yjit/yjit.md)) + * rustc - 1.58.0 or later (if you wish to build [YJIT](/doc/yjit/yjit.md)) 3. Checkout the CRuby source code: diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index 139e5571117fc1..f4c1b312c28c94 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -14,6 +14,11 @@ Ruby on Rails, an area where MJIT has not yet managed to deliver speedups. YJIT is currently supported for macOS and Linux on x86-64 and arm64/aarch64 CPUs. This project is open source and falls under the same license as CRuby. +

+ If you're using YJIT in production, please + share your success stories with us! +

+ If you wish to learn more about the approach taken, here are some conference talks and publications: - RubyKaigi 2021 talk: [YJIT: Building a New JIT Compiler Inside CRuby](https://www.youtube.com/watch?v=PBVLf3yfMs8) - Blog post: [YJIT: Building a New JIT Compiler Inside CRuby](https://pointersgonewild.com/2021/06/02/yjit-building-a-new-jit-compiler-inside-cruby/) @@ -51,7 +56,7 @@ You will need to install: - A C compiler such as GCC or Clang - GNU Make and Autoconf - The Rust compiler `rustc` and Cargo (if you want to build in dev/debug mode) - - The Rust version must be [>= 1.58.1](../../yjit/Cargo.toml). + - The Rust version must be [>= 1.58.0](../../yjit/Cargo.toml). To install the Rust build toolchain, we suggest following the [recommended installation method][rust-install]. Rust also provides first class [support][editor-tools] for many source code editors. diff --git a/enc/make_encmake.rb b/enc/make_encmake.rb index bc0597e3f4457e..fcfc2c92678b3d 100755 --- a/enc/make_encmake.rb +++ b/enc/make_encmake.rb @@ -134,7 +134,7 @@ def target_transcoders end mkin = File.read(File.join($srcdir, "Makefile.in")) mkin.gsub!(/@(#{CONFIG.keys.join('|')})@/) {CONFIG[$1]} -open(ARGV[0], 'wb') {|f| +File.open(ARGV[0], 'wb') {|f| f.puts mkin, dep } if MODULE_TYPE == :static diff --git a/eval.c b/eval.c index c95c3fb9edaf5e..ea0d80c87b756e 100644 --- a/eval.c +++ b/eval.c @@ -43,7 +43,7 @@ NORETURN(static void rb_raise_jump(VALUE, VALUE)); void rb_ec_clear_current_thread_trace_func(const rb_execution_context_t *ec); void rb_ec_clear_all_trace_func(const rb_execution_context_t *ec); -static int rb_ec_cleanup(rb_execution_context_t *ec, int ex); +static int rb_ec_cleanup(rb_execution_context_t *ec, enum ruby_tag_type ex); static int rb_ec_exec_node(rb_execution_context_t *ec, void *n); VALUE rb_eLocalJumpError; @@ -100,8 +100,10 @@ ruby_init(void) { int state = ruby_setup(); if (state) { - if (RTEST(ruby_debug)) - error_print(GET_EC()); + if (RTEST(ruby_debug)) { + rb_execution_context_t *ec = GET_EC(); + rb_ec_error_print(ec, ec->errinfo); + } exit(EXIT_FAILURE); } } @@ -120,8 +122,9 @@ ruby_options(int argc, char **argv) } else { rb_ec_clear_current_thread_trace_func(ec); - state = error_handle(ec, state); - iseq = (void *)INT2FIX(state); + int exitcode = error_handle(ec, ec->errinfo, state); + ec->errinfo = Qnil; /* just been handled */ + iseq = (void *)INT2FIX(exitcode); } EC_POP_TAG(); return iseq; @@ -137,7 +140,7 @@ rb_ec_fiber_scheduler_finalize(rb_execution_context_t *ec) rb_fiber_scheduler_set(Qnil); } else { - state = error_handle(ec, state); + state = error_handle(ec, ec->errinfo, state); } EC_POP_TAG(); } @@ -176,20 +179,21 @@ ruby_finalize(void) int ruby_cleanup(int ex) { - return rb_ec_cleanup(GET_EC(), ex); + return rb_ec_cleanup(GET_EC(), (enum ruby_tag_type)ex); } static int -rb_ec_cleanup(rb_execution_context_t *ec, int ex0) +rb_ec_cleanup(rb_execution_context_t *ec, enum ruby_tag_type ex) { int state; - volatile VALUE errs[2] = { Qundef, Qundef }; - int nerr; + volatile VALUE save_error = Qundef; + volatile int sysex = EXIT_SUCCESS; + volatile int signaled = 0; rb_thread_t *th = rb_ec_thread_ptr(ec); rb_thread_t *const volatile th0 = th; - volatile int sysex = EXIT_SUCCESS; volatile int step = 0; - volatile int ex = ex0; + volatile VALUE message = Qnil; + VALUE buf; rb_threadptr_interrupt(th); rb_threadptr_check_signal(th); @@ -199,56 +203,57 @@ rb_ec_cleanup(rb_execution_context_t *ec, int ex0) SAVE_ROOT_JMPBUF(th, { RUBY_VM_CHECK_INTS(ec); }); step_0: step++; - errs[1] = ec->errinfo; + save_error = ec->errinfo; if (THROW_DATA_P(ec->errinfo)) ec->errinfo = Qnil; - ruby_init_stack(&errs[STACK_UPPER(errs, 0, 1)]); + ruby_init_stack(&message); + /* exits with failure but silently when an exception raised + * here */ SAVE_ROOT_JMPBUF(th, rb_ec_teardown(ec)); step_1: step++; + VALUE err = ec->errinfo; + int mode0 = 0, mode1 = 0; + if (err != save_error && !NIL_P(err)) { + mode0 = exiting_split(err, &sysex, &signaled); + } + + /* exceptions after here will be ignored */ + + /* build error message including causes */ + err = ATOMIC_VALUE_EXCHANGE(save_error, Qnil); + + if (!NIL_P(err) && !THROW_DATA_P(err)) { + mode1 = exiting_split(err, (mode0 & EXITING_WITH_STATUS) ? NULL : &sysex, &signaled); + if (mode1 & EXITING_WITH_MESSAGE) { + buf = rb_str_new(NULL, 0); + SAVE_ROOT_JMPBUF(th, rb_ec_error_print_detailed(ec, err, buf, Qundef)); + message = buf; + } + } + + step_2: step++; /* protect from Thread#raise */ th->status = THREAD_KILLED; - errs[0] = ec->errinfo; SAVE_ROOT_JMPBUF(th, rb_ractor_terminate_all()); + + step_3: step++; + if (!NIL_P(buf = message)) { + warn_print_str(buf); + } + else if (!NIL_OR_UNDEF_P(err = save_error) || + (ex != TAG_NONE && !((mode0|mode1) & EXITING_WITH_STATUS))) { + sysex = error_handle(ec, err, ex); + } } else { th = th0; switch (step) { case 0: goto step_0; case 1: goto step_1; - } - if (ex == 0) ex = state; - } - ec->errinfo = errs[1]; - sysex = error_handle(ec, ex); - - state = 0; - for (nerr = 0; nerr < numberof(errs); ++nerr) { - VALUE err = ATOMIC_VALUE_EXCHANGE(errs[nerr], Qnil); - VALUE sig; - - if (!RTEST(err)) continue; - - /* ec->errinfo contains a NODE while break'ing */ - if (THROW_DATA_P(err)) continue; - - if (rb_obj_is_kind_of(err, rb_eSystemExit)) { - sysex = sysexit_status(err); - break; - } - else if (rb_obj_is_kind_of(err, rb_eSignal)) { - VALUE sig = rb_ivar_get(err, id_signo); - state = NUM2INT(sig); - break; - } - else if (rb_obj_is_kind_of(err, rb_eSystemCallError) && - FIXNUM_P(sig = rb_attr_get(err, id_signo))) { - state = NUM2INT(sig); - break; - } - else if (sysex == EXIT_SUCCESS) { - sysex = EXIT_FAILURE; + case 2: goto step_2; + case 3: goto step_3; } } @@ -265,7 +270,8 @@ rb_ec_cleanup(rb_execution_context_t *ec, int ex0) ruby_vm_destruct(th->vm); // For YJIT, call this after ruby_vm_destruct() frees jit_cont for the root fiber. rb_jit_cont_finish(); - if (state) ruby_default_signal(state); + + if (signaled) ruby_default_signal(signaled); return sysex; } @@ -317,7 +323,7 @@ ruby_run_node(void *n) rb_execution_context_t *ec = GET_EC(); int status; if (!ruby_executable_node(n, &status)) { - rb_ec_cleanup(ec, 0); + rb_ec_cleanup(ec, (NIL_P(ec->errinfo) ? TAG_NONE : TAG_RAISE)); return status; } ruby_init_stack((void *)&status); diff --git a/eval_error.c b/eval_error.c index dfb3e6b6762e5d..0112dece0d1790 100644 --- a/eval_error.c +++ b/eval_error.c @@ -73,12 +73,6 @@ set_backtrace(VALUE info, VALUE bt) rb_check_funcall(info, set_backtrace, 1, &bt); } -static void -error_print(rb_execution_context_t *ec) -{ - rb_ec_error_print(ec, ec->errinfo); -} - #define CSI_BEGIN "\033[" #define CSI_SGR "m" @@ -338,12 +332,11 @@ rb_error_write(VALUE errinfo, VALUE emesg, VALUE errat, VALUE str, VALUE opt, VA } } -void -rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) +static void +rb_ec_error_print_detailed(rb_execution_context_t *volatile ec, volatile VALUE errinfo, VALUE str, volatile VALUE emesg) { volatile uint8_t raised_flag = ec->raised_flag; volatile VALUE errat = Qundef; - volatile VALUE emesg = Qundef; volatile bool written = false; VALUE opt = rb_hash_new(); @@ -365,7 +358,7 @@ rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) if (!written) { written = true; - rb_error_write(errinfo, emesg, errat, Qnil, opt, highlight, Qfalse); + rb_error_write(errinfo, emesg, errat, str, opt, highlight, Qfalse); } EC_POP_TAG(); @@ -373,6 +366,12 @@ rb_ec_error_print(rb_execution_context_t * volatile ec, volatile VALUE errinfo) rb_ec_raised_set(ec, raised_flag); } +void +rb_ec_error_print(rb_execution_context_t *volatile ec, volatile VALUE errinfo) +{ + rb_ec_error_print_detailed(ec, errinfo, Qnil, Qundef); +} + #define undef_mesg_for(v, k) rb_fstring_lit("undefined"v" method `%1$s' for "k" `%2$s'") #define undef_mesg(v) ( \ is_mod ? \ @@ -429,11 +428,58 @@ sysexit_status(VALUE err) return NUM2INT(st); } +enum { + EXITING_WITH_MESSAGE = 1, + EXITING_WITH_STATUS = 2, + EXITING_WITH_SIGNAL = 4 +}; +static int +exiting_split(VALUE errinfo, volatile int *exitcode, volatile int *sigstatus) +{ + int ex = EXIT_SUCCESS; + VALUE signo; + int sig = 0; + int result = 0; + + if (NIL_P(errinfo)) return 0; + + if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { + ex = sysexit_status(errinfo); + result |= EXITING_WITH_STATUS; + } + else if (rb_obj_is_kind_of(errinfo, rb_eSignal)) { + signo = rb_ivar_get(errinfo, id_signo); + sig = FIX2INT(signo); + result |= EXITING_WITH_SIGNAL; + /* no message when exiting by signal */ + if (signo == INT2FIX(SIGSEGV) || !rb_obj_is_instance_of(errinfo, rb_eSignal)) + /* except for SEGV and subclasses */ + result |= EXITING_WITH_MESSAGE; + } + else if (rb_obj_is_kind_of(errinfo, rb_eSystemCallError) && + FIXNUM_P(signo = rb_attr_get(errinfo, id_signo))) { + sig = FIX2INT(signo); + result |= EXITING_WITH_SIGNAL; + /* no message when exiting by error to be mapped to signal */ + } + else { + ex = EXIT_FAILURE; + result |= EXITING_WITH_STATUS | EXITING_WITH_MESSAGE; + } + + if (exitcode && (result & EXITING_WITH_STATUS)) + *exitcode = ex; + if (sigstatus && (result & EXITING_WITH_SIGNAL)) + *sigstatus = sig; + + return result; +} + #define unknown_longjmp_status(status) \ rb_bug("Unknown longjmp status %d", status) static int -error_handle(rb_execution_context_t *ec, int ex) +error_handle(rb_execution_context_t *ec, VALUE errinfo, enum ruby_tag_type ex) { int status = EXIT_FAILURE; @@ -469,26 +515,13 @@ error_handle(rb_execution_context_t *ec, int ex) error_pos(Qnil); warn_print("unexpected throw\n"); break; - case TAG_RAISE: { - VALUE errinfo = ec->errinfo; - if (rb_obj_is_kind_of(errinfo, rb_eSystemExit)) { - status = sysexit_status(errinfo); - } - else if (rb_obj_is_instance_of(errinfo, rb_eSignal) && - rb_ivar_get(errinfo, id_signo) != INT2FIX(SIGSEGV)) { - /* no message when exiting by signal */ - } - else if (rb_obj_is_kind_of(errinfo, rb_eSystemCallError) && - FIXNUM_P(rb_attr_get(errinfo, id_signo))) { - /* no message when exiting by error to be mapped to signal */ + case TAG_RAISE: + if (!(exiting_split(errinfo, &status, NULL) & EXITING_WITH_MESSAGE)) { + break; } - else { - rb_ec_error_print(ec, errinfo); - } - break; - } + /* fallthrough */ case TAG_FATAL: - error_print(ec); + rb_ec_error_print(ec, errinfo); break; default: unknown_longjmp_status(ex); diff --git a/eval_jump.c b/eval_jump.c index a6139bc27b6bc3..e8e74f4e70050a 100644 --- a/eval_jump.c +++ b/eval_jump.c @@ -121,7 +121,7 @@ rb_ec_exec_end_proc(rb_execution_context_t * ec) } else { EC_TMPPOP_TAG(); - error_handle(ec, state); + error_handle(ec, ec->errinfo, state); if (!NIL_P(ec->errinfo)) errinfo = ec->errinfo; EC_REPUSH_TAG(); goto again; diff --git a/ext/coverage/coverage.c b/ext/coverage/coverage.c index c8229ae8fe37cc..4578de54e42b85 100644 --- a/ext/coverage/coverage.c +++ b/ext/coverage/coverage.c @@ -131,7 +131,7 @@ rb_coverage_setup(int argc, VALUE *argv, VALUE klass) * Start/resume the coverage measurement. * * Caveat: Currently, only process-global coverage measurement is supported. - * You cannot measure per-thread covearge. If your process has multiple thread, + * You cannot measure per-thread coverage. If your process has multiple thread, * using Coverage.resume/suspend to capture code coverage executed from only * a limited code block, may yield misleading results. */ @@ -470,7 +470,7 @@ rb_coverage_running(VALUE klass) * This feature is experimental, so these APIs may be changed in future. * * Caveat: Currently, only process-global coverage measurement is supported. - * You cannot measure per-thread covearge. + * You cannot measure per-thread coverage. * * = Usage * diff --git a/ext/extmk.rb b/ext/extmk.rb index cc560be1f22c93..cab9a519c1a493 100755 --- a/ext/extmk.rb +++ b/ext/extmk.rb @@ -144,7 +144,7 @@ def extmake(target, basedir = 'ext', maybestatic = true) d = target until (d = File.dirname(d)) == '.' if File.exist?("#{$top_srcdir}/#{basedir}/#{d}/extconf.rb") - parent = (/^all:\s*install/ =~ IO.read("#{d}/Makefile") rescue false) + parent = (/^all:\s*install/ =~ File.read("#{d}/Makefile") rescue false) break end end @@ -447,9 +447,8 @@ def $mflags.defined?(var) end for dir in ["ext", File::join($top_srcdir, "ext")] setup = File::join(dir, CONFIG['setup']) - if File.file? setup - f = open(setup) - while line = f.gets() + if (f = File.stat(setup) and f.file? rescue next) + File.foreach(setup) do |line| line.chomp! line.sub!(/#.*$/, '') next if /^\s*$/ =~ line @@ -466,7 +465,6 @@ def $mflags.defined?(var) end MTIMES << f.mtime $setup = setup - f.close break end end unless $extstatic diff --git a/ext/ripper/eventids2.c b/ext/ripper/eventids2.c index ac38663f2d2398..05687497aca1bc 100644 --- a/ext/ripper/eventids2.c +++ b/ext/ripper/eventids2.c @@ -1,22 +1,3 @@ -enum { - tIGNORED_NL = tLAST_TOKEN + 1, -# define tIGNORED_NL ((enum yytokentype)tIGNORED_NL) - tCOMMENT, -# define tCOMMENT ((enum yytokentype)tCOMMENT) - tEMBDOC_BEG, -# define tEMBDOC_BEG ((enum yytokentype)tEMBDOC_BEG) - tEMBDOC, -# define tEMBDOC ((enum yytokentype)tEMBDOC) - tEMBDOC_END, -# define tEMBDOC_END ((enum yytokentype)tEMBDOC_END) - tHEREDOC_BEG, -# define tHEREDOC_BEG ((enum yytokentype)tHEREDOC_BEG) - tHEREDOC_END, -# define tHEREDOC_END ((enum yytokentype)tHEREDOC_END) - k__END__, -# define k__END__ ((enum yytokentype)k__END__) -}; - typedef struct { ID ripper_id_backref; ID ripper_id_backtick; diff --git a/ext/socket/extconf.rb b/ext/socket/extconf.rb index 73bbc8e6871436..37ff216560f70c 100644 --- a/ext/socket/extconf.rb +++ b/ext/socket/extconf.rb @@ -552,7 +552,7 @@ def %(s) s || self end end if !have_macro("IPPROTO_IPV6", headers) && have_const("IPPROTO_IPV6", headers) - IO.read(File.join(File.dirname(__FILE__), "mkconstants.rb")).sub(/\A.*^__END__$/m, '').split(/\r?\n/).grep(/\AIPPROTO_\w*/){$&}.each {|name| + File.read(File.join(File.dirname(__FILE__), "mkconstants.rb")).sub(/\A.*^__END__$/m, '').split(/\r?\n/).grep(/\AIPPROTO_\w*/){$&}.each {|name| have_const(name, headers) unless $defs.include?("-DHAVE_CONST_#{name.upcase}") } end @@ -679,7 +679,7 @@ def %(s) s || self end end end FileUtils.mkdir_p(File.dirname(hdr)) - open(hdr, "w") {|f| f.write(in6)} + File.write(hdr, in6) $distcleanfiles << hdr $distcleandirs << File.dirname(hdr) "done" diff --git a/file.c b/file.c index 10eb1081e328a9..5b17093ca35003 100644 --- a/file.c +++ b/file.c @@ -7284,7 +7284,7 @@ const char ruby_null_device[] = * * - ::blockdev?: Returns whether the file at the given path is a block device. * - ::chardev?: Returns whether the file at the given path is a character device. - * - ::directory?: Returns whether the file at the given path is a diretory. + * - ::directory?: Returns whether the file at the given path is a directory. * - ::executable?: Returns whether the file at the given path is executable * by the effective user and group of the current process. * - ::executable_real?: Returns whether the file at the given path is executable diff --git a/gc.c b/gc.c index 8817538194a7d4..e3f986880b3def 100644 --- a/gc.c +++ b/gc.c @@ -641,17 +641,29 @@ typedef struct RVALUE { VALUE v3; } values; } as; + + /* Start of RVALUE_OVERHEAD. + * Do not directly read these members from the RVALUE as they're located + * at the end of the slot (which may differ in size depending on the size + * pool). */ +#if RACTOR_CHECK_MODE + uint32_t _ractor_belonging_id; +#endif #if GC_DEBUG const char *file; int line; #endif } RVALUE; -#if GC_DEBUG -STATIC_ASSERT(sizeof_rvalue, offsetof(RVALUE, file) == SIZEOF_VALUE * 5); +#if RACTOR_CHECK_MODE +# define RVALUE_OVERHEAD (sizeof(RVALUE) - offsetof(RVALUE, _ractor_belonging_id)) +#elif GC_DEBUG +# define RVALUE_OVERHEAD (sizeof(RVALUE) - offsetof(RVALUE, file)) #else -STATIC_ASSERT(sizeof_rvalue, sizeof(RVALUE) == SIZEOF_VALUE * 5); +# define RVALUE_OVERHEAD 0 #endif + +STATIC_ASSERT(sizeof_rvalue, sizeof(RVALUE) == (SIZEOF_VALUE * 5) + RVALUE_OVERHEAD); STATIC_ASSERT(alignof_rvalue, RUBY_ALIGNOF(RVALUE) == SIZEOF_VALUE); typedef uintptr_t bits_t; @@ -2726,7 +2738,7 @@ rb_gc_obj_slot_size(VALUE obj) return *(size_t*)(obj - MMTK_OBJREF_OFFSET); } #endif - return GET_HEAP_PAGE(obj)->slot_size; + return GET_HEAP_PAGE(obj)->slot_size - RVALUE_OVERHEAD; } static inline size_t @@ -2741,6 +2753,8 @@ size_pool_slot_size(unsigned char pool_id) GC_ASSERT(size_pools[pool_id].slot_size == (short)slot_size); #endif + slot_size -= RVALUE_OVERHEAD; + return slot_size; } @@ -2855,6 +2869,8 @@ static inline size_t size_pool_idx_for_size(size_t size) { #if USE_RVARGC + size += RVALUE_OVERHEAD; + size_t slot_count = CEILDIV(size, BASE_SLOT_SIZE); /* size_pool_idx is ceil(log2(slot_count)) */ @@ -3107,7 +3123,7 @@ rb_ec_wb_protected_newobj_of(rb_execution_context_t *ec, VALUE klass, VALUE flag VALUE rb_newobj(void) { - return newobj_of(0, T_NONE, 0, 0, 0, FALSE, sizeof(RVALUE)); + return newobj_of(0, T_NONE, 0, 0, 0, FALSE, RVALUE_SIZE); } static size_t @@ -3159,7 +3175,7 @@ rb_newobj_of(VALUE klass, VALUE flags) return rb_class_instance_allocate_internal(klass, (flags | ROBJECT_EMBED) & ~FL_WB_PROTECTED, flags & FL_WB_PROTECTED); } else { - return newobj_of(klass, flags & ~FL_WB_PROTECTED, 0, 0, 0, flags & FL_WB_PROTECTED, sizeof(RVALUE)); + return newobj_of(klass, flags & ~FL_WB_PROTECTED, 0, 0, 0, flags & FL_WB_PROTECTED, RVALUE_SIZE); } } @@ -3197,7 +3213,7 @@ rb_imemo_name(enum imemo_type type) VALUE rb_imemo_new(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0) { - size_t size = sizeof(RVALUE); + size_t size = RVALUE_SIZE; VALUE flags = T_IMEMO | (type << FL_USHIFT); return newobj_of(v0, flags, v1, v2, v3, TRUE, size); } @@ -3205,7 +3221,7 @@ rb_imemo_new(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0) static VALUE rb_imemo_tmpbuf_new(VALUE v1, VALUE v2, VALUE v3, VALUE v0) { - size_t size = sizeof(RVALUE); + size_t size = sizeof(struct rb_imemo_tmpbuf_struct); VALUE flags = T_IMEMO | (imemo_tmpbuf << FL_USHIFT); return newobj_of(v0, flags, v1, v2, v3, FALSE, size); } @@ -3266,7 +3282,7 @@ rb_imemo_new_debug(enum imemo_type type, VALUE v1, VALUE v2, VALUE v3, VALUE v0, } #endif -VALUE +MJIT_FUNC_EXPORTED VALUE rb_class_allocate_instance(VALUE klass) { return rb_class_instance_allocate_internal(klass, T_OBJECT | ROBJECT_EMBED, RGENGC_WB_PROTECTED_OBJECT); @@ -3286,7 +3302,7 @@ rb_data_object_wrap(VALUE klass, void *datap, RUBY_DATA_FUNC dmark, RUBY_DATA_FU { RUBY_ASSERT_ALWAYS(dfree != (RUBY_DATA_FUNC)1); if (klass) rb_data_object_check(klass); - return newobj_of(klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, FALSE, sizeof(RVALUE)); + return newobj_of(klass, T_DATA, (VALUE)dmark, (VALUE)dfree, (VALUE)datap, FALSE, sizeof(struct RTypedData)); } VALUE @@ -3302,7 +3318,7 @@ rb_data_typed_object_wrap(VALUE klass, void *datap, const rb_data_type_t *type) { RBIMPL_NONNULL_ARG(type); if (klass) rb_data_object_check(klass); - return newobj_of(klass, T_DATA, (VALUE)type, (VALUE)1, (VALUE)datap, type->flags & RUBY_FL_WB_PROTECTED, sizeof(RVALUE)); + return newobj_of(klass, T_DATA, (VALUE)type, (VALUE)1, (VALUE)datap, type->flags & RUBY_FL_WB_PROTECTED, sizeof(struct RTypedData)); } VALUE @@ -5340,7 +5356,7 @@ obj_memsize_of(VALUE obj, int use_all_types) BUILTIN_TYPE(obj), (void*)obj); } - return size + GET_HEAP_PAGE(obj)->slot_size; + return size + rb_gc_obj_slot_size(obj); } size_t @@ -10345,6 +10361,14 @@ gc_move(rb_objspace_t *objspace, VALUE scan, VALUE free, size_t src_slot_size, s /* Move the object */ memcpy(dest, src, MIN(src_slot_size, slot_size)); + + if (RVALUE_OVERHEAD > 0) { + void *dest_overhead = (void *)(((uintptr_t)dest) + slot_size - RVALUE_OVERHEAD); + void *src_overhead = (void *)(((uintptr_t)src) + src_slot_size - RVALUE_OVERHEAD); + + memcpy(dest_overhead, src_overhead, RVALUE_OVERHEAD); + } + memset(src, 0, src_slot_size); /* Set bits for object in new location */ @@ -10467,7 +10491,7 @@ gc_ref_update_array(rb_objspace_t * objspace, VALUE v) } #if USE_RVARGC - if ((size_t)GET_HEAP_PAGE(v)->slot_size >= rb_ary_size_as_embedded(v)) { + if (rb_gc_obj_slot_size(v) >= rb_ary_size_as_embedded(v)) { if (rb_ary_embeddable_p(v)) { rb_ary_make_embedded(v); } @@ -10978,7 +11002,7 @@ gc_update_object_references(rb_objspace_t *objspace, VALUE obj) // if, after move the string is not embedded, and can fit in the // slot it's been placed in, then re-embed it - if ((size_t)GET_HEAP_PAGE(obj)->slot_size >= rb_str_size_as_embedded(obj)) { + if (rb_gc_obj_slot_size(obj) >= rb_str_size_as_embedded(obj)) { if (!STR_EMBED_P(obj) && rb_str_reembeddable_p(obj)) { rb_str_make_embedded(obj); } @@ -14868,7 +14892,8 @@ Init_GC(void) gc_constants = rb_hash_new(); rb_hash_aset(gc_constants, ID2SYM(rb_intern("DEBUG")), RBOOL(GC_DEBUG)); - rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("BASE_SLOT_SIZE")), SIZET2NUM(BASE_SLOT_SIZE - RVALUE_OVERHEAD)); + rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_OVERHEAD")), SIZET2NUM(RVALUE_OVERHEAD)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("RVALUE_SIZE")), SIZET2NUM(sizeof(RVALUE))); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_OBJ_LIMIT")), SIZET2NUM(HEAP_PAGE_OBJ_LIMIT)); rb_hash_aset(gc_constants, ID2SYM(rb_intern("HEAP_PAGE_BITMAP_SIZE")), SIZET2NUM(HEAP_PAGE_BITMAP_SIZE)); diff --git a/internal/parse.h b/internal/parse.h index 37133827f569d5..f242c384ad244f 100644 --- a/internal/parse.h +++ b/internal/parse.h @@ -16,6 +16,7 @@ VALUE rb_parser_set_yydebug(VALUE, VALUE); void *rb_parser_load_file(VALUE parser, VALUE name); void rb_parser_keep_script_lines(VALUE vparser); void rb_parser_error_tolerant(VALUE vparser); +void rb_parser_keep_tokens(VALUE vparser); RUBY_SYMBOL_EXPORT_BEGIN VALUE rb_parser_set_context(VALUE, const struct rb_iseq_struct *, int); diff --git a/internal/variable.h b/internal/variable.h index 553e87c4a8b325..e59a0f19247c30 100644 --- a/internal/variable.h +++ b/internal/variable.h @@ -57,6 +57,7 @@ void rb_const_warn_if_deprecated(const rb_const_entry_t *, VALUE, ID); rb_shape_t * rb_grow_iv_list(VALUE obj); void rb_ensure_iv_list_size(VALUE obj, uint32_t len, uint32_t newsize); struct gen_ivtbl * rb_ensure_generic_iv_list_size(VALUE obj, uint32_t newsize); +attr_index_t rb_obj_ivar_set(VALUE obj, ID id, VALUE val); MJIT_SYMBOL_EXPORT_END static inline bool diff --git a/io_buffer.c b/io_buffer.c index dc9d613524d5ea..e5d6c1961246c5 100644 --- a/io_buffer.c +++ b/io_buffer.c @@ -535,7 +535,7 @@ io_flags_for_size(size_t size) * Create a new zero-filled IO::Buffer of +size+ bytes. * By default, the buffer will be _internal_: directly allocated chunk * of the memory. But if the requested +size+ is more than OS-specific - * IO::Bufer::PAGE_SIZE, the buffer would be allocated using the + * IO::Buffer::PAGE_SIZE, the buffer would be allocated using the * virtual memory mechanism (anonymous +mmap+ on Unix, +VirtualAlloc+ * on Windows). The behavior can be forced by passing IO::Buffer::MAPPED * as a second parameter. diff --git a/iseq.c b/iseq.c index a2e79cd55badcf..e20e1a608a1ba7 100644 --- a/iseq.c +++ b/iseq.c @@ -2490,6 +2490,47 @@ rb_iseq_disasm(const rb_iseq_t *iseq) return str; } +/* + * Estimates the number of instance variables that will be set on + * a given `class` with the initialize method defined in + * `initialize_iseq` + */ +attr_index_t +rb_estimate_iv_count(VALUE klass, const rb_iseq_t * initialize_iseq) +{ + bool calls_super = false; + + struct rb_id_table * iv_names = rb_id_table_create(0); + + VALUE * code = ISEQ_BODY(initialize_iseq)->iseq_encoded; + + for (unsigned int i = 0; i < ISEQ_BODY(initialize_iseq)->iseq_size; ) { + VALUE insn = code[i]; + int original_insn = rb_vm_insn_addr2insn((const void *)insn); + + if (BIN(setinstancevariable) == original_insn) { + ID name = (ID)code[i + 1]; + rb_id_table_insert(iv_names, name, Qtrue); + } + else if (BIN(invokesuper) == original_insn) { + calls_super = true; + } + + i += insn_len(original_insn); + } + + attr_index_t count = (attr_index_t)rb_id_table_size(iv_names); + + if (calls_super) { + VALUE superclass = rb_class_superclass(klass); + count += RCLASS_EXT(superclass)->max_iv_count; + } + + rb_id_table_free(iv_names); + + return count; +} + /* * call-seq: * iseq.disasm -> str diff --git a/lib/cgi.rb b/lib/cgi.rb index 8d51cb24fe17f9..dda5eb1712f723 100644 --- a/lib/cgi.rb +++ b/lib/cgi.rb @@ -288,7 +288,7 @@ # class CGI - VERSION = "0.3.3" + VERSION = "0.3.5" end require 'cgi/core' diff --git a/lib/cgi/cookie.rb b/lib/cgi/cookie.rb index 6b0d89ca3baa5f..1a9c1a82c12307 100644 --- a/lib/cgi/cookie.rb +++ b/lib/cgi/cookie.rb @@ -40,6 +40,10 @@ class CGI class Cookie < Array @@accept_charset="UTF-8" unless defined?(@@accept_charset) + TOKEN_RE = %r"\A[[!-~]&&[^()<>@,;:\\\"/?=\[\]{}]]+\z" + PATH_VALUE_RE = %r"\A[[ -~]&&[^;]]*\z" + DOMAIN_VALUE_RE = %r"\A(?