From 8476860636adeab9ecf45f0e9bed553b6ee29724 Mon Sep 17 00:00:00 2001 From: Maxime Chevalier-Boisvert Date: Thu, 29 Apr 2021 13:51:38 -0400 Subject: [PATCH] Merge upstream Ruby (#18) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Evaluate multiple assignment left hand side before right hand side In regular assignment, Ruby evaluates the left hand side before the right hand side. For example: ```ruby foo[0] = bar ``` Calls `foo`, then `bar`, then `[]=` on the result of `foo`. Previously, multiple assignment didn't work this way. If you did: ```ruby abc.def, foo[0] = bar, baz ``` Ruby would previously call `bar`, then `baz`, then `abc`, then `def=` on the result of `abc`, then `foo`, then `[]=` on the result of `foo`. This change makes multiple assignment similar to single assignment, changing the evaluation order of the above multiple assignment code to calling `abc`, then `foo`, then `bar`, then `baz`, then `def=` on the result of `abc`, then `[]=` on the result of `foo`. Implementing this is challenging with the stack-based virtual machine. We need to keep track of all of the left hand side attribute setter receivers and setter arguments, and then keep track of the stack level while handling the assignment processing, so we can issue the appropriate topn instructions to get the receiver. Here's an example of how the multiple assignment is executed, showing the stack and instructions: ``` self # putself abc # send abc, self # putself abc, foo # send abc, foo, 0 # putobject 0 abc, foo, 0, [bar, baz] # evaluate RHS abc, foo, 0, [bar, baz], baz, bar # expandarray abc, foo, 0, [bar, baz], baz, bar, abc # topn 5 abc, foo, 0, [bar, baz], baz, abc, bar # swap abc, foo, 0, [bar, baz], baz, def= # send abc, foo, 0, [bar, baz], baz # pop abc, foo, 0, [bar, baz], baz, foo # topn 3 abc, foo, 0, [bar, baz], baz, foo, 0 # topn 3 abc, foo, 0, [bar, baz], baz, foo, 0, baz # topn 2 abc, foo, 0, [bar, baz], baz, []= # send abc, foo, 0, [bar, baz], baz # pop abc, foo, 0, [bar, baz] # pop [bar, baz], foo, 0, [bar, baz] # setn 3 [bar, baz], foo, 0 # pop [bar, baz], foo # pop [bar, baz] # pop ``` As multiple assignment must deal with splats, post args, and any level of nesting, it gets quite a bit more complex than this in non-trivial cases. To handle this, struct masgn_state is added to keep track of the overall state of the mass assignment, which stores a linked list of struct masgn_attrasgn, one for each assigned attribute. This adds a new optimization that replaces a topn 1/pop instruction combination with a single swap instruction for multiple assignment to non-aref attributes. This new approach isn't compatible with one of the optimizations previously used, in the case where the multiple assignment return value was not needed, there was no lhs splat, and one of the left hand side used an attribute setter. This removes that optimization. Removing the optimization allowed for removing the POP_ELEMENT and adjust_stack functions. This adds a benchmark to measure how much slower multiple assignment is with the correct evaluation order. This benchmark shows: * 4-9% decrease for attribute sets * 14-23% decrease for array member sets * Basically same speed for local variable sets Importantly, it shows no significant difference between the popped (where return value of the multiple assignment is not needed) and !popped (where return value of the multiple assignment is needed) cases for attribute and array member sets. This indicates the previous optimization, which was dropped in the evaluation order fix and only affected the popped case, is not important to performance. Fixes [Bug #4443] * * 2021-04-22 [ci skip] * Remove reverse VM instruction This was previously only used by the multiple assignment code, but is no longer needed after the multiple assignment execution order fix. * fix raise in exception with jump add_ensure_iseq() adds ensure block to the end of jump such as next/redo/return. However, if the rescue cause are in the body, this rescue catches the exception in ensure clause. iter do next rescue R ensure raise end In this case, R should not be executed, but executed without this patch. Fixes [Bug #13930] Fixes [Bug #16618] A part of tests are written by @jeremyevans https://github.com/ruby/ruby/pull/4291 * [ruby/time] Make Time friendly to Ractor https://github.com/ruby/time/commit/c784e4f166 * [ruby/cgi] handle invalid encoding https://github.com/ruby/cgi/commit/2b1c2e21a4 * [ruby/cgi] Add test for escapeHTML/unescapeHTML invalid encoding fix in pure ruby version Also, remove pointless assert_nothing_raised(ArgumentError) while here. https://github.com/ruby/cgi/commit/c05edf5608 * [ruby/cgi] gemspec: Explicitly empty executables list The gem exposes no executables https://github.com/ruby/cgi/commit/cd7106ad97 * [ruby/benchmark] Add comment about terminating newline in captions; fix test method name. https://github.com/ruby/benchmark/commit/02ce298d3e * [ruby/benchmark] gemspec: Explicitly have 0 executables This gem exposes no executables. https://github.com/ruby/benchmark/commit/ff1ef7ae06 * Ignore JRuby files on io-console * [ruby/io-console] Enable building the C extension on TruffleRuby. https://github.com/ruby/io-console/commit/c17b8cf3a9 * [ruby/io-console] Move FFI console under lib Having the separate dir makes testing difficult and doesn't reflect the structure the gem will eventually have. We can filter these files out if necessary when building the CRuby gem. https://github.com/ruby/io-console/commit/881010447c * Separate test used by test_ractor for Ractor in test_time.rb * Merge net-imap-0.2.0 * [ruby/net-imap] Set timeout for IDLE responses Fixes #14 https://github.com/ruby/net-imap/commit/39d39ff9bb * [ruby/net-imap] Bump version to 0.2.1 https://github.com/ruby/net-imap/commit/31f96ea884 * [ruby/uri] Upstream Java proxy property checks from JRuby These Java properties, retrieved from JRuby's "Java env" ENV_JAVA, allow JRuby users to use the same proxy properties the rest of the Java platform uses. This resolves https://bugs.ruby-lang.org/issues/11194 https://github.com/ruby/uri/commit/3bd2bcc95a * [ruby/uri] Optimize URI#hostname and URI#hostname= https://github.com/ruby/uri/commit/3b7ccfd835 * [ruby/uri] Add tests for URI::RFC{2396,3986}_Parser#inspect https://github.com/ruby/uri/commit/d47dae2f8e * [ruby/uri] Only use UnboundMethod#bind_call if it is available This allows tests to pass on Ruby 2.4-2.6. Fixes #19 https://github.com/ruby/uri/commit/67ca99ca87 * [ruby/uri] Set required_ruby_version to 2.4 in gemspec Tests pass on Ruby 2.4, but not on Ruby 2.3. https://github.com/ruby/uri/commit/594418079a * [ruby/uri] remove comment about URI::escape as it is removed https://github.com/ruby/uri/commit/0f0057e1b2 * [ruby/uri] Use Regexp#match? to avoid extra allocations `#=~` builds `MatchData`, requiring extra allocations as compared to `#match?`, which returns a boolean w/o having to build the `MatchData`. https://github.com/ruby/uri/commit/158f58a9cc * Update bundled_gems * Suppress warnings for unsued variable * * 2021-04-23 [ci skip] * Remove unneeded comment * test/ruby/test_assignment.rb: Avoid "assigned but unused variable" * Fix wrong documentation It doesn't return `nil` but raises an exception, as explained a few lines after * * 2021-04-24 [ci skip] * Fix setting method visibility for a refinement without an origin class If a class has been refined but does not have an origin class, there is a single method entry marked with VM_METHOD_TYPE_REFINED, but it contains the original method entry. If the original method entry is present, we shouldn't skip the method when searching even when skipping refined methods. Fixes [Bug #17519] * Remove unnecessary checks for empty kw splat These two checks are surrounded by an if that ensures the call site is not a kw splat call site. * Remove part of comment that is no longer accurate In Ruby 2.7, empty keyword splats could be added back for backwards compatibility. However, that stopped in Ruby 3.0. * Add back checks for empty kw splat with tests (#4405) This reverts commit a224ce8150f2bc687cf79eb415c931d87a4cd247. Turns out the checks are needed to handle splatting an array with an empty ruby2 keywords hash. * [Doc] Fix a typo s/invokations/invocations/ * * 2021-04-25 [ci skip] * [Doc] Fix a typo s/evel/eval/ * [Doc] Fix a typo s/oher/other/ * [Doc] Fix a typo s/visilibity/visibility/ * [Doc] Fix a typo s/arround/around/ * [Doc] Fix a typo s/daguten/dakuten/ * [ci skip] Fix a typo s/certificiate/certificate/ * [Doc] Fix a typo s/algorthm/algorithm/ * Fix some typos by spell checker * * 2021-04-26 [ci skip] * Remove test of removed reverse VM instruction since 5512353d97250e85c13bf10b9b32e750478cf474 * spec/ruby/core/file/shared/read.rb: The behavior of FreeBSD was changed http://rubyci.s3.amazonaws.com/freebsd12/ruby-master/log/20210426T003001Z.fail.html.gz#rubyspec * disable shareable_constant_value for CI To debug CI failures on FreeBSD, disable `shareable_constant_value`. * [ruby/irb] Fix typo ture -> true [ci skip] https://github.com/ruby/irb/commit/783a0569e8 * [ruby/irb] Added assert_equal_with_term https://github.com/ruby/irb/commit/b690da96d8 * [ruby/irb] Added test_colorize https://github.com/ruby/irb/commit/10e290fc3a * [ruby/irb] Assertions on non-tty https://github.com/ruby/irb/commit/ede12890d2 * [ruby/irb] Added `colorable` keyword option Currently `IRB::Color.colorize` and `IRB::Color.colorize_code` refer `$stdin.tty?` internally. This patch adds `colorable` keyword option which overrides it. https://github.com/ruby/irb/commit/402e3f1907 * [ruby/irb] Added setup and teardown to TestIRB::TestInit Not to be affected by existing rc files in all tests. https://github.com/ruby/irb/commit/bf434892b4 * node.c (rb_ast_new): imemo_ast is WB-unprotected Previously imemo_ast was handled as WB-protected which caused a segfault of the following code: # shareable_constant_value: literal M0 = {} M1 = {} ... M100000 = {} My analysis is here: `shareable_constant_value: literal` creates many Hash instances during parsing, and add them to node_buffer of imemo_ast. However, the contents are missed because imemo_ast is incorrectly WB-protected. This changeset makes imemo_ast as WB-unprotected. * Revert "disable shareable_constant_value for CI" This reverts commit c647205c3eb1f17409a859149bb7d2ea38b43bed. Maybe the root issue was fixed by 7ac078e5b67ba752a755d6bd9c3a99999767fd3a * Document binding behavior for C call/return events for TracePoint/set_trace_func C methods do not have bindings, so binding returns the binding of the nearest C method. Fixes [Bug #9009] * * 2021-04-27 [ci skip] * Fix compiler warnings in objspace_dump.c when assertions are turned on Example: ``` In file included from ../../../include/ruby/defines.h:72, from ../../../include/ruby/ruby.h:23, from ../../../gc.h:3, from ../../../ext/objspace/objspace_dump.c:15: ../../../ext/objspace/objspace_dump.c: In function ‘dump_append_ld’: ../../../ext/objspace/objspace_dump.c:95:26: warning: comparison of integer expressions of different signedness: ‘long unsigned int’ and ‘int’ [-Wsign-compare] 95 | RUBY_ASSERT(required <= width); | ^~ ``` * Fix type-o in insns.def "redefine" -> "redefined" * Partially revert 2c7d3b3a722c4636ab1e9d289cbca47ddd168d3e to make imemo_ast WB-protected again. Only the test is kept. * Make imemo_ast WB-protected again by firing the write barrier of imemo_ast after nd_lit is modified. This will fix the issue of https://github.com/ruby/ruby/pull/4416 more gracefully. * test/ruby/test_exception.rb: suppress "warning: statement not reached" * [ruby/pathname] gemspec: Explicitly list 0 executables This gem exposes no executables. https://github.com/ruby/pathname/commit/c401d97d58 * [ruby/gdbm] Add dependency to gdbm package on mingw RubyInstaller2 supports metadata tags for installation of dependent MSYS2/MINGW libraries. The openssl gem requires the mingw-openssl package to be installed on the system, which the gem installer takes care about, when this tag is set. The feature is documented here: https://github.com/oneclick/rubyinstaller2/wiki/For-gem-developers#msys2-library-dependency Fixes https://github.com/oneclick/rubyinstaller2/issues/163 https://github.com/ruby/gdbm/commit/d95eed3e86 * [ruby/matrix] Use Gemfile instead of Gem::Specification#add_development_dependency. https://github.com/ruby/matrix/commit/1381fde5c1 * [ruby/matrix] v0.4.0 https://github.com/ruby/matrix/commit/baea4b90d4 * [ruby/matrix] v0.4.1 https://github.com/ruby/matrix/commit/f7c9981907 * [ruby/matrix] Guard for < Ruby 3.0 https://github.com/ruby/matrix/commit/1ef660c627 * [ruby/net-ftp] Re-apply 827e471d438fdec1ae329afb5912b8e06d534823 https://github.com/ruby/net-ftp/commit/3ca80368c4 * [ruby/net-ftp] Replace Timeout.timeout with socket timeout Timeout.timeout is inefficient since it spins up a new thread for each invocation, use Socket.tcp's connect_timeout option instead when we aren't using SOCKS (we can't replace Timeout.timeout for SOCKS yet since SOCKSSocket doesn't have a connect_timeout option). https://github.com/ruby/net-ftp/commit/d65910132f * [ruby/net-ftp] Close the passive connection data socket if there is an error setting up the transfer Previously, the connection leaked in this case. This uses begin/ensure and checking for an error in the ensure block. An alternative approach would be to not even perform the connection until after the RETR (or other) command has been sent. However, I'm not sure all FTP servers support that. The current behavior is: * Send (PASV/EPSV) * Connect to the host/port returned in 227/229 reply * Send (RETR/other command) Changing it to connect after the RETR could break things. FTP servers might expect that the client has already connected before sending the RETR. The alternative approach is more likely to introduce backwards compatibility issues, compared to the begin/ensure approach taken here. Fixes Ruby Bug 17027 https://github.com/ruby/net-ftp/commit/6e8535f076 * [ruby/net-ftp] Reduce resource cosumption of Net::FTP::TIME_PARSER Reported by Alexandr Savca as a DoS vulnerability, but Net::FTP is a client library and the impact of the issue is low, so I have decided to fix it as a normal issue. Based on patch by nobu. https://github.com/ruby/net-ftp/commit/a93af636f8 * [ruby/net-ftp] Add test cases https://github.com/ruby/net-ftp/commit/865232bb2a * [ruby/net-ftp] Replace "iff" with "if and only if" iff means if and only if, but readers without that knowledge might assume this to be a spelling mistake. To me, this seems like exclusionary language that is unnecessary. Simply using "if and only if" instead should suffice. https://github.com/ruby/net-ftp/commit/e920473618 * lldb: Add Freelist Index to dump_page output * lldb: dump_page_rvalue - dump a heap page containing an RVALUE rather than having to do this in a two step process: 1. heap_page obj 2. dump_page $2 (or whatever lldb variable heap_page set) we can now just dump_page_rvalue obj * lldb: highlight the slot when using dump_page_rvalue * Fix Monitor to lock per Fiber, like Mutex [Bug #17827] * * 2021-04-28 [ci skip] * test/ruby/test_fiber.rb: reduce the count of object creation to cause GC ... on Solaris. This is the same as 547887138f19959f649b1c0dbcde5659ae3878ed. http://rubyci.s3.amazonaws.com/solaris10-gcc/ruby-master/log/20210427T160003Z.fail.html.gz ``` [ 7667/20965] TestFiber#test_fork_from_fiber/export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:397:in `transfer': can't alloc machine stack to fiber (1 x 139264 bytes): Not enough space (FiberError) from /export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:397:in `block (6 levels) in test_fork_from_fiber' from /export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:396:in `times' from /export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:396:in `block (5 levels) in test_fork_from_fiber' from /export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:392:in `fork' from /export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:392:in `block (4 levels) in test_fork_from_fiber' = 0.88 s ... 1) Failure: TestFiber#test_fork_from_fiber [/export/home/users/chkbuild/cb-gcc/tmp/build/20210427T160003Z/ruby/test/ruby/test_fiber.rb:409]: [ruby-core:41456]. <0> expected but was <1>. ``` * test/net/ftp/test_ftp.rb: remove unused variable * test/net/ftp/test_ftp.rb: reduce the size of a long response "9" * 999999999 (about 1 GB) was too large for some CI servers. This commit changes the size to 999999 (about 1 MB). http://rubyci.s3.amazonaws.com/scw-9d6766/ruby-master/log/20210427T141707Z.fail.html.gz http://rubyci.s3.amazonaws.com/raspbian10-aarch64/ruby-master/log/20210427T145408Z.fail.html.gz * test/net/ftp/test_ftp.rb: Use RubyVM::JIT instead of RubyVM::MJIT * [ruby/net-smtp] Net::SMTP.start() and #start() accepts ssl_context_params keyword argument Additional params are passed to OpenSSL::SSL::SSLContext#set_params. For example, `Net::SMTP#start(ssl_context_params: { cert_store: my_store, timeout: 123 })` calls `set_params({ cert_store: my_store, timeout: 123 })`. https://github.com/ruby/net-smtp/commit/4213389c21 * [ruby/net-smtp] Replace Timeout.timeout with socket timeout Timeout.timeout is inefficient since it spins up a new thread for each invocation, use Socket.tcp's connect_timeout option instead https://github.com/ruby/net-smtp/commit/6ae4a59f05 * [ruby/net-smtp] Removed needless files from Gem::Specification#files https://github.com/ruby/net-smtp/commit/69bba6b125 * [ruby/net-smtp] mod: bump to a new VERSION that could be checked for testings >0.2.1 https://github.com/ruby/net-smtp/commit/8f2c9323e2 Co-authored-by: Jeremy Evans Co-authored-by: git Co-authored-by: Koichi Sasada Co-authored-by: Kir Shatrov Co-authored-by: pavel Co-authored-by: Olle Jonsson Co-authored-by: Keith Bennett Co-authored-by: Hiroshi SHIBATA Co-authored-by: Duncan MacGregor Co-authored-by: Charles Oliver Nutter Co-authored-by: Shugo Maeda Co-authored-by: Lukas Zapletal Co-authored-by: Felix Wong Co-authored-by: Steven Harman Co-authored-by: Kazuhiro NISHIYAMA Co-authored-by: S-H-GAMELINKS Co-authored-by: Yusuke Endoh Co-authored-by: romainsalles Co-authored-by: Alan Wu Co-authored-by: wonda-tea-coffee Co-authored-by: wonda-tea-coffee Co-authored-by: Ryuta Kamizono Co-authored-by: Nobuyoshi Nakada Co-authored-by: Peter Zhu Co-authored-by: ebrohman Co-authored-by: Lars Kanis Co-authored-by: Marc-Andre Lafortune Co-authored-by: mohamed Co-authored-by: Gannon McGibbon Co-authored-by: Matt Valentine-House Co-authored-by: Benoit Daloze Co-authored-by: Tom Freudenberg --- NEWS.md | 46 ++ benchmark/README.md | 2 +- benchmark/masgn.yml | 29 ++ ccan/list/list.h | 2 +- compile.c | 397 ++++++++++----- cont.c | 4 +- debug_counter.h | 4 +- doc/extension.rdoc | 2 +- doc/irb/irb.rd.ja | 2 +- doc/syntax/methods.rdoc | 2 +- ext/digest/sha2/sha2.c | 2 +- ext/gdbm/gdbm.gemspec | 1 + ext/io/console/extconf.rb | 2 +- ext/io/console/io-console.gemspec | 16 +- ext/monitor/monitor.c | 10 +- ext/nkf/nkf-utf8/config.h | 2 +- ext/nkf/nkf-utf8/nkf.c | 10 +- ext/nkf/nkf.c | 2 +- ext/objspace/objspace_dump.c | 10 +- ext/openssl/ossl_ocsp.c | 2 +- ext/pathname/pathname.gemspec | 2 +- ext/win32/lib/win32/sspi.rb | 2 +- ext/win32ole/sample/xml.rb | 12 +- ext/win32ole/win32ole_type.c | 2 +- ext/win32ole/win32ole_variant.c | 2 +- gc.c | 4 +- gems/bundled_gems | 2 +- hash.c | 1 - include/ruby/backward/2/stdarg.h | 2 +- include/ruby/internal/attr/cold.h | 2 +- include/ruby/internal/compiler_since.h | 4 +- include/ruby/internal/core.h | 2 +- include/ruby/internal/warning_push.h | 2 +- insns.def | 21 +- internal/warnings.h | 2 +- iseq.h | 1 + lib/benchmark.rb | 3 + lib/benchmark/benchmark.gemspec | 2 +- lib/cgi/cgi.gemspec | 2 +- lib/cgi/util.rb | 11 +- lib/irb/color.rb | 16 +- lib/irb/ruby-lex.rb | 2 +- lib/matrix/matrix.gemspec | 3 - lib/matrix/version.rb | 2 +- lib/net/ftp.rb | 48 +- lib/net/imap.rb | 544 ++++++++++++++++++--- lib/net/net-smtp.gemspec | 8 +- lib/net/smtp.rb | 52 +- lib/time.rb | 1 + lib/uri.rb | 1 - lib/uri/common.rb | 2 +- lib/uri/generic.rb | 20 +- lib/uri/rfc2396_parser.rb | 10 +- lib/uri/rfc3986_parser.rb | 10 +- lib/uri/uri.gemspec | 2 + memory_view.c | 2 +- misc/lldb_cruby.py | 87 +++- parse.y | 6 +- ractor.c | 4 +- ractor.rb | 6 +- spec/ruby/core/file/shared/read.rb | 4 +- test/benchmark/test_benchmark.rb | 2 +- test/cgi/test_cgi_util.rb | 33 +- test/irb/test_color.rb | 91 +++- test/irb/test_init.rb | 37 +- test/matrix/test_matrix.rb | 2 +- test/monitor/test_monitor.rb | 7 + test/net/ftp/test_ftp.rb | 57 ++- test/net/imap/test_imap.rb | 69 ++- test/net/imap/test_imap_response_parser.rb | 78 +++ test/objspace/test_objspace.rb | 2 +- test/open-uri/test_open-uri.rb | 2 +- test/ruby/test_array.rb | 6 +- test/ruby/test_assignment.rb | 58 +++ test/ruby/test_econv.rb | 2 +- test/ruby/test_exception.rb | 60 +++ test/ruby/test_fiber.rb | 2 +- test/ruby/test_gc.rb | 7 + test/ruby/test_jit.rb | 4 - test/ruby/test_keyword.rb | 20 + test/ruby/test_module.rb | 110 ++++- test/test_time.rb | 9 + test/uri/test_generic.rb | 4 + test/uri/test_parser.rb | 5 + tool/sync_default_gems.rb | 1 + trace_point.rb | 12 +- transcode.c | 2 +- version.h | 2 +- vm.c | 2 - vm_insnhelper.c | 5 - vm_method.c | 3 +- vm_trace.c | 4 + 92 files changed, 1725 insertions(+), 431 deletions(-) create mode 100644 benchmark/masgn.yml diff --git a/NEWS.md b/NEWS.md index 8670a0e6f4dbc0..a41cf0bb349f63 100644 --- a/NEWS.md +++ b/NEWS.md @@ -6,6 +6,7 @@ since the **3.0.0** release, except for bug fixes. Note that each entry is kept to a minimum, see links for details. ## Language changes + * Pin operator now takes an expression. [[Feature #17411]] ```ruby @@ -13,6 +14,50 @@ Note that each entry is kept to a minimum, see links for details. #=> [[3, 5], [5, 7], [11, 13]] ``` +* Multiple assignment evaluation order has been made consistent with + single assignment evaluation order. With single assignment, Ruby + uses a left-to-right evaluation order. With this code: + + ```ruby + foo[0] = bar + ``` + + The following evaluation order is used: + + 1. `foo` + 2. `bar` + 3. `[]=` called on the result of `foo` + + In Ruby before 3.1.0, multiple assignment did not follow this + evaluation order. With this code: + + ```ruby + foo[0], bar.baz = a, b + ``` + + Versions of Ruby before 3.1.0 would evaluate in the following + order + + 1. `a` + 2. `b` + 3. `foo` + 4. `[]=` called on the result of `foo` + 5. `bar` + 6. `baz=` called on the result of `bar` + + Starting in Ruby 3.1.0, evaluation order is now consistent with + single assignment, with the left hand side being evaluated before + the right hand side: + + 1. `foo` + 2. `bar` + 3. `a` + 4. `b` + 5. `[]=` called on the result of `foo` + 6. `baz=` called on the result of `bar` + + [[Bug #4443]] + ## Command line options ## Core classes updates @@ -96,6 +141,7 @@ Excluding feature bug fixes. ## Miscellaneous changes +[Bug #4443]: https://bugs.ruby-lang.org/issues/4443 [Feature #12194]: https://bugs.ruby-lang.org/issues/12194 [Feature #14256]: https://bugs.ruby-lang.org/issues/14256 [Feature #15198]: https://bugs.ruby-lang.org/issues/15198 diff --git a/benchmark/README.md b/benchmark/README.md index 39a5aa139b8a32..c222164be3ab41 100644 --- a/benchmark/README.md +++ b/benchmark/README.md @@ -37,7 +37,7 @@ Usage: benchmark-driver [options] RUBY|YAML... --bundler Install and use gems specified in Gemfile --filter REGEXP Filter out benchmarks with given regexp --run-duration SECONDS Warmup estimates loop_count to run for this duration (default: 3) - -v, --verbose Verbose mode. Multiple -v options increase visilibity (max: 2) + -v, --verbose Verbose mode. Multiple -v options increase visibility (max: 2) ``` ## make benchmark diff --git a/benchmark/masgn.yml b/benchmark/masgn.yml new file mode 100644 index 00000000000000..4be9333e232c1f --- /dev/null +++ b/benchmark/masgn.yml @@ -0,0 +1,29 @@ +prelude: | + a = [nil] * 3 + b = Class.new{attr_writer :a, :b, :c}.new + c, d, e, f = nil, nil, nil, nil +benchmark: + array2_2: "c = (a[0], a[1] = 1, 2)" + array2_3: "c = (a[0], a[1] = 1, 2, 3)" + array3_2: "c = (a[0], a[1], a[2] = 1, 2)" + array3_3: "c = (a[0], a[1], a[2] = 1, 2, 3)" + attr2_2: "c = (b.a, b.b = 1, 2)" + attr2_3: "c = (b.a, b.b = 1, 2, 3)" + attr3_2: "c = (b.a, b.b, b.c = 1, 2)" + attr3_3: "c = (b.a, b.b, b.c = 1, 2, 3)" + lvar2_2: "c = (d, e = 1, 2)" + lvar2_3: "c = (d, e = 1, 2, 3)" + lvar3_2: "c = (d, e, f = 1, 2)" + lvar3_3: "c = (d, e, f = 1, 2, 3)" + array2_2p: "(a[0], a[1] = 1, 2; nil)" + array2_3p: "(a[0], a[1] = 1, 2, 3; nil)" + array3_2p: "(a[0], a[1], a[2] = 1, 2; nil)" + array3_3p: "(a[0], a[1], a[2] = 1, 2, 3; nil)" + attr2_2p: "(b.a, b.b = 1, 2; nil)" + attr2_3p: "(b.a, b.b = 1, 2, 3; nil)" + attr3_2p: "(b.a, b.b, b.c = 1, 2; nil)" + attr3_3p: "(b.a, b.b, b.c = 1, 2, 3; nil)" + lvar2_2p: "(d, e = 1, 2; nil)" + lvar2_3p: "(d, e = 1, 2, 3; nil)" + lvar3_2p: "(d, e, f = 1, 2; nil)" + lvar3_3p: "(d, e, f = 1, 2, 3; nil)" diff --git a/ccan/list/list.h b/ccan/list/list.h index 7d219307bc6273..c434ad8106be0f 100644 --- a/ccan/list/list.h +++ b/ccan/list/list.h @@ -658,7 +658,7 @@ static inline void list_prepend_list_(struct list_head *to, * @off: offset(relative to @i) at which list node data resides. * * This is a low-level wrapper to iterate @i over the entire list, used to - * implement all oher, more high-level, for-each constructs. It's a for loop, + * implement all other, more high-level, for-each constructs. It's a for loop, * so you can break and continue as normal. * * WARNING! Being the low-level macro that it is, this wrapper doesn't know diff --git a/compile.c b/compile.c index ebc7afee35e180..0b6e8a456e4716 100644 --- a/compile.c +++ b/compile.c @@ -1098,19 +1098,6 @@ LAST_ELEMENT(LINK_ANCHOR *const anchor) return anchor->last; } -static LINK_ELEMENT * -POP_ELEMENT(ISEQ_ARG_DECLARE LINK_ANCHOR *const anchor) -{ - LINK_ELEMENT *elem = anchor->last; - anchor->last = anchor->last->prev; - anchor->last->next = 0; - verify_list("pop", anchor); - return elem; -} -#if CPDEBUG < 0 -#define POP_ELEMENT(anchor) POP_ELEMENT(iseq, (anchor)) -#endif - static LINK_ELEMENT * ELEM_FIRST_INSN(LINK_ELEMENT *elem) { @@ -4609,27 +4596,163 @@ when_splat_vals(rb_iseq_t *iseq, LINK_ANCHOR *const cond_seq, const NODE *vals, return COMPILE_OK; } +/* Multiple Assignment Handling + * + * In order to handle evaluation of multiple assignment such that the left hand side + * is evaluated before the right hand side, we need to process the left hand side + * and see if there are any attributes that need to be assigned. If so, we add + * instructions to evaluate the receiver of any assigned attributes before we + * process the right hand side. + * + * For a multiple assignment such as: + * + * l1.m1, l2[0] = r3, r4 + * + * We start off evaluating l1 and l2, then we evaluate r3 and r4, then we + * assign the result of r3 to l1.m1, and then the result of r4 to l2.m2. + * On the VM stack, this looks like: + * + * self # putself + * l1 # send + * l1, self # putself + * l1, l2 # send + * l1, l2, 0 # putobject 0 + * l1, l2, 0, [r3, r4] # after evaluation of RHS + * l1, l2, 0, [r3, r4], r4, r3 # expandarray + * l1, l2, 0, [r3, r4], r4, r3, l1 # topn 5 + * l1, l2, 0, [r3, r4], r4, l1, r3 # swap + * l1, l2, 0, [r3, r4], r4, m1= # send + * l1, l2, 0, [r3, r4], r4 # pop + * l1, l2, 0, [r3, r4], r4, l2 # topn 3 + * l1, l2, 0, [r3, r4], r4, l2, 0 # topn 3 + * l1, l2, 0, [r3, r4], r4, l2, 0, r4 # topn 2 + * l1, l2, 0, [r3, r4], r4, []= # send + * l1, l2, 0, [r3, r4], r4 # pop + * l1, l2, 0, [r3, r4] # pop + * [r3, r4], l2, 0, [r3, r4] # setn 3 + * [r3, r4], l2, 0 # pop + * [r3, r4], l2 # pop + * [r3, r4] # pop + * + * This is made more complex when you have to handle splats, post args, + * and arbitrary levels of nesting. You need to keep track of the total + * number of attributes to set, and for each attribute, how many entries + * are on the stack before the final attribute, in order to correctly + * calculate the topn value to use to get the receiver of the attribute + * setter method. + * + * A brief description of the VM stack for simple multiple assignment + * with no splat (rhs_array will not be present if the return value of + * the multiple assignment is not needed): + * + * lhs_attr1, lhs_attr2, ..., rhs_array, ..., rhs_arg2, rhs_arg1 + * + * For multiple assignment with splats, while processing the part before + * the splat (splat+post here is an array of the splat and the post arguments): + * + * lhs_attr1, lhs_attr2, ..., rhs_array, splat+post, ..., rhs_arg2, rhs_arg1 + * + * When processing the splat and post arguments: + * + * lhs_attr1, lhs_attr2, ..., rhs_array, ..., post_arg2, post_arg1, splat + * + * When processing nested multiple assignment, existing values on the stack + * are kept. So for: + * + * (l1.m1, l2.m2), l3.m3, l4* = [r1, r2], r3, r4 + * + * The stack layout would be the following before processing the nested + * multiple assignment: + * + * l1, l2, [[r1, r2], r3, r4], [r4], r3, [r1, r2] + * + * In order to handle this correctly, we need to keep track of the nesting + * level for each attribute assignment, as well as the attribute number + * (left hand side attributes are processed left to right) and number of + * arguments to pass to the setter method. struct masgn_attrasgn tracks + * this information. + * + * We also need to track information for the entire multiple assignment, such + * as the total number of arguments, and the current nesting level, to + * handle both nested multiple assignment as well as cases where the + * rhs is not needed. We also need to keep track of all attribute + * assignments in this, which we do using a linked listed. struct masgn_state + * tracks this information. + */ + +struct masgn_attrasgn { + INSN *before_insn; + struct masgn_attrasgn *next; + int line; + int argn; + int num_args; + int lhs_pos; +}; + +struct masgn_state { + struct masgn_attrasgn *first_memo; + struct masgn_attrasgn *last_memo; + int lhs_level; + int num_args; + bool nested; +}; + +static int compile_massign0(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const rhs, LINK_ANCHOR *const lhs, LINK_ANCHOR *const post, const NODE *const node, struct masgn_state *state, int popped); static int -compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node) +compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const rhs, LINK_ANCHOR *const lhs, LINK_ANCHOR *const post, const NODE *const node, struct masgn_state *state, int lhs_pos) { switch (nd_type(node)) { case NODE_ATTRASGN: { + if (!state) { + rb_bug("no masgn_state"); + } + INSN *iobj; - VALUE dupidx; int line = nd_line(node); - CHECK(COMPILE_POPPED(ret, "masgn lhs (NODE_ATTRASGN)", node)); + CHECK(COMPILE_POPPED(pre, "masgn lhs (NODE_ATTRASGN)", node)); + + LINK_ELEMENT *insn_element = LAST_ELEMENT(pre); + iobj = (INSN *)get_prev_insn((INSN *)insn_element); /* send insn */ + ELEM_REMOVE(LAST_ELEMENT(pre)); + ELEM_REMOVE((LINK_ELEMENT *)iobj); + pre->last = iobj->link.prev; - iobj = (INSN *)get_prev_insn((INSN *)LAST_ELEMENT(ret)); /* send insn */ const struct rb_callinfo *ci = (struct rb_callinfo *)OPERAND_AT(iobj, 0); int argc = vm_ci_argc(ci) + 1; ci = ci_argc_set(iseq, ci, argc); OPERAND_AT(iobj, 0) = (VALUE)ci; RB_OBJ_WRITTEN(iseq, Qundef, ci); - dupidx = INT2FIX(argc); - INSERT_BEFORE_INSN1(iobj, line, topn, dupidx); + if (argc == 1) { + ADD_INSN(lhs, line, swap); + } + else { + ADD_INSN1(lhs, line, topn, INT2FIX(argc)); + } + + struct masgn_attrasgn *memo; + memo = malloc(sizeof(struct masgn_attrasgn)); + if (!memo) { + return 0; + } + memo->before_insn = (INSN *)LAST_ELEMENT(lhs); + memo->line = line; + memo->argn = state->num_args + 1; + memo->num_args = argc; + state->num_args += argc; + memo->lhs_pos = lhs_pos; + memo->next = NULL; + if (!state->first_memo) { + state->first_memo = memo; + } + else { + state->last_memo->next = memo; + } + state->last_memo = memo; + + ADD_ELEM(lhs, (LINK_ELEMENT *)iobj); if (vm_ci_flag(ci) & VM_CALL_ARGS_SPLAT) { int argc = vm_ci_argc(ci); ci = ci_argc_set(iseq, ci, argc - 1); @@ -4638,15 +4761,31 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const n INSERT_BEFORE_INSN1(iobj, line, newarray, INT2FIX(1)); INSERT_BEFORE_INSN(iobj, line, concatarray); } - ADD_INSN(ret, line, pop); /* result */ + ADD_INSN(lhs, line, pop); + if (argc != 1) { + ADD_INSN(lhs, line, pop); + } + for (int i=0; i < argc; i++) { + ADD_INSN(post, line, pop); + } break; } case NODE_MASGN: { - DECL_ANCHOR(anchor); - INIT_ANCHOR(anchor); - CHECK(COMPILE_POPPED(anchor, "nest masgn lhs", node)); - ELEM_REMOVE(FIRST_ELEMENT(anchor)); - ADD_SEQ(ret, anchor); + DECL_ANCHOR(nest_rhs); + INIT_ANCHOR(nest_rhs); + DECL_ANCHOR(nest_lhs); + INIT_ANCHOR(nest_lhs); + + int prev_level = state->lhs_level; + bool prev_nested = state->nested; + state->nested = 1; + state->lhs_level = lhs_pos - 1; + CHECK(compile_massign0(iseq, pre, nest_rhs, nest_lhs, post, node, state, 1)); + state->lhs_level = prev_level; + state->nested = prev_nested; + + ADD_SEQ(lhs, nest_rhs); + ADD_SEQ(lhs, nest_lhs); break; } default: { @@ -4654,7 +4793,7 @@ compile_massign_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const n INIT_ANCHOR(anchor); CHECK(COMPILE_POPPED(anchor, "masgn lhs", node)); ELEM_REMOVE(FIRST_ELEMENT(anchor)); - ADD_SEQ(ret, anchor); + ADD_SEQ(lhs, anchor); } } @@ -4666,7 +4805,7 @@ compile_massign_opt_lhs(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *lhs { if (lhsn) { CHECK(compile_massign_opt_lhs(iseq, ret, lhsn->nd_next)); - CHECK(compile_massign_lhs(iseq, ret, lhsn->nd_head)); + CHECK(compile_massign_lhs(iseq, ret, ret, ret, ret, lhsn->nd_head, NULL, 0)); } return COMPILE_OK; } @@ -4735,98 +4874,112 @@ compile_massign_opt(rb_iseq_t *iseq, LINK_ANCHOR *const ret, return 1; } -static void -adjust_stack(rb_iseq_t *iseq, LINK_ANCHOR *const ret, int line, int rlen, int llen) -{ - if (rlen < llen) { - do {ADD_INSN(ret, line, putnil);} while (++rlen < llen); - } - else if (rlen > llen) { - do {ADD_INSN(ret, line, pop);} while (--rlen > llen); - } -} - static int -compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) +compile_massign0(rb_iseq_t *iseq, LINK_ANCHOR *const pre, LINK_ANCHOR *const rhs, LINK_ANCHOR *const lhs, LINK_ANCHOR *const post, const NODE *const node, struct masgn_state *state, int popped) { const NODE *rhsn = node->nd_value; const NODE *splatn = node->nd_args; const NODE *lhsn = node->nd_head; + const NODE *lhsn_count = lhsn; int lhs_splat = (splatn && NODE_NAMED_REST_P(splatn)) ? 1 : 0; - if (!popped || splatn || !compile_massign_opt(iseq, ret, rhsn, lhsn)) { - int llen = 0; - int expand = 1; - DECL_ANCHOR(lhsseq); + int llen = 0; + int lpos = 0; + int expand = 1; - INIT_ANCHOR(lhsseq); + while (lhsn_count) { + llen++; + lhsn_count = lhsn_count->nd_next; + } + while (lhsn) { + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, lhsn->nd_head, state, (llen - lpos) + lhs_splat + state->lhs_level)); + lpos++; + lhsn = lhsn->nd_next; + } - while (lhsn) { - CHECK(compile_massign_lhs(iseq, lhsseq, lhsn->nd_head)); - llen += 1; - lhsn = lhsn->nd_next; - } + if (lhs_splat) { + if (nd_type(splatn) == NODE_POSTARG) { + /*a, b, *r, p1, p2 */ + const NODE *postn = splatn->nd_2nd; + const NODE *restn = splatn->nd_1st; + int plen = (int)postn->nd_alen; + int ppos = 0; + int flag = 0x02 | (NODE_NAMED_REST_P(restn) ? 0x01 : 0x00); - NO_CHECK(COMPILE(ret, "normal masgn rhs", rhsn)); + ADD_INSN2(lhs, nd_line(splatn), expandarray, + INT2FIX(plen), INT2FIX(flag)); - if (!popped) { - ADD_INSN(ret, nd_line(node), dup); - } - else if (!lhs_splat) { - INSN *last = (INSN*)ret->last; - if (IS_INSN(&last->link) && - IS_INSN_ID(last, newarray) && - last->operand_size == 1) { - int rlen = FIX2INT(OPERAND_AT(last, 0)); - /* special case: assign to aset or attrset */ - if (llen == 2) { - POP_ELEMENT(ret); - adjust_stack(iseq, ret, nd_line(node), rlen, llen); - ADD_INSN(ret, nd_line(node), swap); - expand = 0; - } - else if (llen > 2 && llen != rlen) { - POP_ELEMENT(ret); - adjust_stack(iseq, ret, nd_line(node), rlen, llen); - ADD_INSN1(ret, nd_line(node), reverse, INT2FIX(llen)); - expand = 0; - } - else if (llen > 2) { - last->insn_id = BIN(reverse); - expand = 0; - } - } - } - if (expand) { - ADD_INSN2(ret, nd_line(node), expandarray, - INT2FIX(llen), INT2FIX(lhs_splat)); - } - ADD_SEQ(ret, lhsseq); + if (NODE_NAMED_REST_P(restn)) { + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, restn, state, 1 + plen + state->lhs_level)); + } + while (postn) { + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, postn->nd_head, state, (plen - ppos) + state->lhs_level)); + ppos++; + postn = postn->nd_next; + } + } + else { + /* a, b, *r */ + CHECK(compile_massign_lhs(iseq, pre, rhs, lhs, post, splatn, state, 1 + state->lhs_level)); + } + } - if (lhs_splat) { - if (nd_type(splatn) == NODE_POSTARG) { - /*a, b, *r, p1, p2 */ - const NODE *postn = splatn->nd_2nd; - const NODE *restn = splatn->nd_1st; - int num = (int)postn->nd_alen; - int flag = 0x02 | (NODE_NAMED_REST_P(restn) ? 0x01 : 0x00); - ADD_INSN2(ret, nd_line(splatn), expandarray, - INT2FIX(num), INT2FIX(flag)); + if (!state->nested) { + NO_CHECK(COMPILE(rhs, "normal masgn rhs", rhsn)); + } - if (NODE_NAMED_REST_P(restn)) { - CHECK(compile_massign_lhs(iseq, ret, restn)); - } - while (postn) { - CHECK(compile_massign_lhs(iseq, ret, postn->nd_head)); - postn = postn->nd_next; - } - } - else { - /* a, b, *r */ - CHECK(compile_massign_lhs(iseq, ret, splatn)); - } - } + if (!popped) { + ADD_INSN(rhs, nd_line(node), dup); + } + if (expand) { + ADD_INSN2(rhs, nd_line(node), expandarray, + INT2FIX(llen), INT2FIX(lhs_splat)); + } + return COMPILE_OK; +} + +static int +compile_massign(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, int popped) +{ + if (!popped || node->nd_args || !compile_massign_opt(iseq, ret, node->nd_value, node->nd_head)) { + struct masgn_state state; + state.lhs_level = popped ? 0 : 1; + state.nested = 0; + state.num_args = 0; + state.first_memo = NULL; + state.last_memo = NULL; + + DECL_ANCHOR(pre); + INIT_ANCHOR(pre); + DECL_ANCHOR(rhs); + INIT_ANCHOR(rhs); + DECL_ANCHOR(lhs); + INIT_ANCHOR(lhs); + DECL_ANCHOR(post); + INIT_ANCHOR(post); + int ok = compile_massign0(iseq, pre, rhs, lhs, post, node, &state, popped); + + struct masgn_attrasgn *memo = state.first_memo, *tmp_memo; + while (memo) { + VALUE topn_arg = INT2FIX((state.num_args - memo->argn) + memo->lhs_pos); + for(int i = 0; i < memo->num_args; i++) { + INSERT_BEFORE_INSN1(memo->before_insn, memo->line, topn, topn_arg); + } + tmp_memo = memo->next; + free(memo); + memo = tmp_memo; + } + CHECK(ok); + + ADD_SEQ(ret, pre); + ADD_SEQ(ret, rhs); + ADD_SEQ(ret, lhs); + if (!popped && state.num_args >= 1) { + /* make sure rhs array is returned before popping */ + ADD_INSN1(ret, nd_line(node), setn, INT2FIX(state.num_args)); + } + ADD_SEQ(ret, post); } return COMPILE_OK; } @@ -5216,9 +5369,22 @@ add_ensure_range(rb_iseq_t *iseq, struct ensure_range *erange, erange->next = ne; } +static bool +can_add_ensure_iseq(const rb_iseq_t *iseq) +{ + if (ISEQ_COMPILE_DATA(iseq)->in_rescue && ISEQ_COMPILE_DATA(iseq)->ensure_node_stack) { + return false; + } + else { + return true; + } +} + static void add_ensure_iseq(LINK_ANCHOR *const ret, rb_iseq_t *iseq, int is_return) { + assert(can_add_ensure_iseq(iseq)); + struct iseq_compile_data_ensure_node_stack *enlp = ISEQ_COMPILE_DATA(iseq)->ensure_node_stack; struct iseq_compile_data_ensure_node_stack *prev_enlp = enlp; @@ -6710,7 +6876,7 @@ compile_break(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, i const int line = nd_line(node); unsigned long throw_flag = 0; - if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0) { + if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) { /* while/until */ LABEL *splabel = NEW_LABEL(0); ADD_LABEL(ret, splabel); @@ -6769,7 +6935,7 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in const int line = nd_line(node); unsigned long throw_flag = 0; - if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0) { + if (ISEQ_COMPILE_DATA(iseq)->redo_label != 0 && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("next in while loop\n"); ADD_LABEL(ret, splabel); @@ -6782,7 +6948,7 @@ compile_next(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in ADD_INSN(ret, line, putnil); } } - else if (ISEQ_COMPILE_DATA(iseq)->end_label) { + else if (ISEQ_COMPILE_DATA(iseq)->end_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("next in block\n"); ADD_LABEL(ret, splabel); @@ -6842,7 +7008,7 @@ compile_redo(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in { const int line = nd_line(node); - if (ISEQ_COMPILE_DATA(iseq)->redo_label) { + if (ISEQ_COMPILE_DATA(iseq)->redo_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("redo in while"); ADD_LABEL(ret, splabel); @@ -6854,7 +7020,7 @@ compile_redo(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, in ADD_INSN(ret, line, putnil); } } - else if (iseq->body->type != ISEQ_TYPE_EVAL && ISEQ_COMPILE_DATA(iseq)->start_label) { + else if (iseq->body->type != ISEQ_TYPE_EVAL && ISEQ_COMPILE_DATA(iseq)->start_label && can_add_ensure_iseq(iseq)) { LABEL *splabel = NEW_LABEL(0); debugs("redo in block"); @@ -6940,7 +7106,14 @@ compile_rescue(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, lstart->rescued = LABEL_RESCUE_BEG; lend->rescued = LABEL_RESCUE_END; ADD_LABEL(ret, lstart); - CHECK(COMPILE(ret, "rescue head", node->nd_head)); + + bool prev_in_rescue = ISEQ_COMPILE_DATA(iseq)->in_rescue; + ISEQ_COMPILE_DATA(iseq)->in_rescue = true; + { + CHECK(COMPILE(ret, "rescue head", node->nd_head)); + } + ISEQ_COMPILE_DATA(iseq)->in_rescue = prev_in_rescue; + ADD_LABEL(ret, lend); if (node->nd_else) { ADD_INSN(ret, line, pop); @@ -7101,7 +7274,7 @@ compile_return(rb_iseq_t *iseq, LINK_ANCHOR *const ret, const NODE *const node, CHECK(COMPILE(ret, "return nd_stts (return val)", retval)); - if (type == ISEQ_TYPE_METHOD) { + if (type == ISEQ_TYPE_METHOD && can_add_ensure_iseq(iseq)) { add_ensure_iseq(ret, iseq, 1); ADD_TRACE(ret, RUBY_EVENT_RETURN); ADD_INSN(ret, line, leave); diff --git a/cont.c b/cont.c index bdd308569cee42..35007cac3515b6 100644 --- a/cont.c +++ b/cont.c @@ -436,7 +436,7 @@ fiber_pool_allocate_memory(size_t * count, size_t stride) #if defined(MADV_FREE_REUSE) // On Mac MADV_FREE_REUSE is necessary for the task_info api // to keep the accounting accurate as possible when a page is marked as reusable - // it can possibly not occuring at first call thus re-iterating if necessary. + // it can possibly not occurring at first call thus re-iterating if necessary. while (madvise(base, (*count)*stride, MADV_FREE_REUSE) == -1 && errno == EAGAIN); #endif return base; @@ -657,7 +657,7 @@ fiber_pool_stack_free(struct fiber_pool_stack * stack) #elif defined(MADV_FREE_REUSABLE) // Acknowledge the kernel down to the task info api we make this // page reusable for future use. - // As for MADV_FREE_REUSE below we ensure in the rare occassions the task was not + // As for MADV_FREE_REUSE below we ensure in the rare occasions the task was not // completed at the time of the call to re-iterate. while (madvise(base, size, MADV_FREE_REUSABLE) == -1 && errno == EAGAIN); #elif defined(MADV_FREE) diff --git a/debug_counter.h b/debug_counter.h index 3c20821db67985..9452f4c737ebe4 100644 --- a/debug_counter.h +++ b/debug_counter.h @@ -46,10 +46,10 @@ RB_DEBUG_COUNTER(cc_not_found_in_ccs) // count for CC lookup success in CCS RB_DEBUG_COUNTER(cc_ent_invalidate) // count for invalidating cc (cc->klass = 0) RB_DEBUG_COUNTER(cc_cme_invalidate) // count for invalidating CME -RB_DEBUG_COUNTER(cc_invalidate_leaf) // count for invalidating klass if klass has no-sublcasses +RB_DEBUG_COUNTER(cc_invalidate_leaf) // count for invalidating klass if klass has no-subclasses RB_DEBUG_COUNTER(cc_invalidate_leaf_ccs) // corresponding CCS RB_DEBUG_COUNTER(cc_invalidate_leaf_callable) // complimented cache (no-subclasses) -RB_DEBUG_COUNTER(cc_invalidate_tree) // count for invalidating klass if klass has sublcasses +RB_DEBUG_COUNTER(cc_invalidate_tree) // count for invalidating klass if klass has subclasses RB_DEBUG_COUNTER(cc_invalidate_tree_cme) // cme if cme is found in this class or superclasses RB_DEBUG_COUNTER(cc_invalidate_tree_callable) // complimented cache (subclasses) RB_DEBUG_COUNTER(cc_invalidate_negative) // count for invalidating negative cache diff --git a/doc/extension.rdoc b/doc/extension.rdoc index c33dea22286337..f8e0a1ec3d6c6b 100644 --- a/doc/extension.rdoc +++ b/doc/extension.rdoc @@ -2127,7 +2127,7 @@ To make "Ractor-safe" C extension, we need to check the following points: (1) Do not share unshareable objects between ractors For example, C's global variable can lead sharing an unshareable objects -betwee ractors. +between ractors. VALUE g_var; VALUE set(VALUE self, VALUE v){ return g_var = v; } diff --git a/doc/irb/irb.rd.ja b/doc/irb/irb.rd.ja index 81247ce4b0c714..fd03b35a0ada6b 100644 --- a/doc/irb/irb.rd.ja +++ b/doc/irb/irb.rd.ja @@ -381,7 +381,7 @@ rubyでは, 以下のプログラムはエラーになります. パイルしてローカル変数を決定するからです. それに対し, irbは実行可能に なる(式が閉じる)と自動的に評価しているからです. 上記の例では, - evel "foo = 0" + eval "foo = 0" を行なった時点で評価を行ない, その時点で変数が定義されるため, 次式で 変数fooは定義されているからです. diff --git a/doc/syntax/methods.rdoc b/doc/syntax/methods.rdoc index 1b75922578e8ec..b9ec8da5ff2cc1 100644 --- a/doc/syntax/methods.rdoc +++ b/doc/syntax/methods.rdoc @@ -615,7 +615,7 @@ defined with .... end Since Ruby 3.0, there can be leading arguments before ... -both in definitions and in invokations (but in definitions they can be +both in definitions and in invocations (but in definitions they can be only positional arguments without default values). def request(method, path, **headers) diff --git a/ext/digest/sha2/sha2.c b/ext/digest/sha2/sha2.c index c86eab37a0b5f9..f55de33eb3c376 100644 --- a/ext/digest/sha2/sha2.c +++ b/ext/digest/sha2/sha2.c @@ -94,7 +94,7 @@ /* * Define the followingsha2_* types to types of the correct length on - * the native archtecture. Most BSD systems and Linux define u_intXX_t + * the native architecture. Most BSD systems and Linux define u_intXX_t * types. Machines with very recent ANSI C headers, can use the * uintXX_t definintions from inttypes.h by defining SHA2_USE_INTTYPES_H * during compile or in the sha.h header file. diff --git a/ext/gdbm/gdbm.gemspec b/ext/gdbm/gdbm.gemspec index 6fd42c022fce77..d15d20847d6966 100644 --- a/ext/gdbm/gdbm.gemspec +++ b/ext/gdbm/gdbm.gemspec @@ -18,4 +18,5 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.extensions = ["ext/gdbm/extconf.rb"] spec.required_ruby_version = ">= 2.3.0" + spec.metadata["msys2_mingw_dependencies"] = "gdbm" end diff --git a/ext/io/console/extconf.rb b/ext/io/console/extconf.rb index 32ad9d3311d9e2..e8c5923b18aba1 100644 --- a/ext/io/console/extconf.rb +++ b/ext/io/console/extconf.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false require 'mkmf' -ok = true if RUBY_ENGINE == "ruby" +ok = true if RUBY_ENGINE == "ruby" || RUBY_ENGINE == "truffleruby" hdr = nil case when macro_defined?("_WIN32", "") diff --git a/ext/io/console/io-console.gemspec b/ext/io/console/io-console.gemspec index 5e875cc06e19a0..dabe9e68f82605 100644 --- a/ext/io/console/io-console.gemspec +++ b/ext/io/console/io-console.gemspec @@ -25,15 +25,15 @@ Gem::Specification.new do |s| if Gem::Platform === s.platform and s.platform =~ 'java' s.files.delete_if {|f| f.start_with?("ext/")} s.extensions.clear - s.require_paths.unshift("jruby") s.files.concat(%w[ - jruby/io/console.rb - jruby/io/console/bsd_console.rb - jruby/io/console/common.rb - jruby/io/console/linux_console.rb - jruby/io/console/native_console.rb - jruby/io/console/stty_console.rb - jruby/io/console/stub_console.rb + lib/io/console.rb + lib/io/console/ffi/bsd_console.rb + lib/io/console/ffi/common.rb + lib/io/console/ffi/console.rb + lib/io/console/ffi/linux_console.rb + lib/io/console/ffi/native_console.rb + lib/io/console/ffi/stty_console.rb + lib/io/console/ffi/stub_console.rb ]) end diff --git a/ext/monitor/monitor.c b/ext/monitor/monitor.c index 26a564f4c3dce3..43a18f58af29e2 100644 --- a/ext/monitor/monitor.c +++ b/ext/monitor/monitor.c @@ -53,7 +53,7 @@ monitor_ptr(VALUE monitor) static int mc_owner_p(struct rb_monitor *mc) { - return mc->owner == rb_thread_current(); + return mc->owner == rb_fiber_current(); } static VALUE @@ -65,7 +65,7 @@ monitor_try_enter(VALUE monitor) if (!rb_mutex_trylock(mc->mutex)) { return Qfalse; } - RB_OBJ_WRITE(monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); mc->count = 0; } mc->count += 1; @@ -78,7 +78,7 @@ monitor_enter(VALUE monitor) struct rb_monitor *mc = monitor_ptr(monitor); if (!mc_owner_p(mc)) { rb_mutex_lock(mc->mutex); - RB_OBJ_WRITE(monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(monitor, &mc->owner, rb_fiber_current()); mc->count = 0; } mc->count++; @@ -90,7 +90,7 @@ monitor_check_owner(VALUE monitor) { struct rb_monitor *mc = monitor_ptr(monitor); if (!mc_owner_p(mc)) { - rb_raise(rb_eThreadError, "current thread not owner"); + rb_raise(rb_eThreadError, "current fiber not owner"); } return Qnil; } @@ -161,7 +161,7 @@ monitor_enter_for_cond(VALUE v) struct wait_for_cond_data *data = (struct wait_for_cond_data *)v; struct rb_monitor *mc = monitor_ptr(data->monitor); - RB_OBJ_WRITE(data->monitor, &mc->owner, rb_thread_current()); + RB_OBJ_WRITE(data->monitor, &mc->owner, rb_fiber_current()); mc->count = NUM2LONG(data->count); return Qnil; } diff --git a/ext/nkf/nkf-utf8/config.h b/ext/nkf/nkf-utf8/config.h index 51dc2a5152bad1..36898c0b4b1d98 100644 --- a/ext/nkf/nkf-utf8/config.h +++ b/ext/nkf/nkf-utf8/config.h @@ -30,7 +30,7 @@ /* --exec-in, --exec-out option * require pipe, fork, execvp and so on. * please undef this on MS-DOS, MinGW - * this is still buggy arround child process + * this is still buggy around child process */ /* #define EXEC_IO */ diff --git a/ext/nkf/nkf-utf8/nkf.c b/ext/nkf/nkf-utf8/nkf.c index cc438a50d6761f..08b372ffab2497 100644 --- a/ext/nkf/nkf-utf8/nkf.c +++ b/ext/nkf/nkf-utf8/nkf.c @@ -581,7 +581,7 @@ static const unsigned char cv[]= { 0x00,0x00}; -/* X0201 kana conversion table for daguten */ +/* X0201 kana conversion table for dakuten */ /* 90-9F A0-DF */ static const unsigned char dv[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -602,7 +602,7 @@ static const unsigned char dv[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00}; -/* X0201 kana conversion table for han-daguten */ +/* X0201 kana conversion table for han-dakuten */ /* 90-9F A0-DF */ static const unsigned char ev[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -623,7 +623,7 @@ static const unsigned char ev[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00}; -/* X0201 kana to X0213 conversion table for han-daguten */ +/* X0201 kana to X0213 conversion table for han-dakuten */ /* 90-9F A0-DF */ static const unsigned char ev_x0213[]= { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, @@ -3817,7 +3817,7 @@ oconv_newline(void (*func)(nkf_char, nkf_char)) LF new line SP space - This fold algorthm does not preserve heading space in a line. + This fold algorithm does not preserve heading space in a line. This is the main difference from fmt. */ @@ -6787,7 +6787,7 @@ options(unsigned char *cp) case 'S': /* Shift_JIS input */ input_encoding = nkf_enc_from_index(SHIFT_JIS); continue; - case 'Z': /* Convert X0208 alphabet to asii */ + case 'Z': /* Convert X0208 alphabet to ascii */ /* alpha_f bit:0 Convert JIS X 0208 Alphabet to ASCII bit:1 Convert Kankaku to one space diff --git a/ext/nkf/nkf.c b/ext/nkf/nkf.c index 37717e4799aa07..76f7648d1bbe6e 100644 --- a/ext/nkf/nkf.c +++ b/ext/nkf/nkf.c @@ -458,7 +458,7 @@ rb_nkf_guess(VALUE obj, VALUE src) * with this and -x option, nkf can be used as UTF converter. * (In other words, without this and -x option, nkf doesn't save some characters) * - * When nkf convert string which related to path, you should use this opion. + * When nkf convert string which related to path, you should use this option. * * === --cap-input * diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index c3619a96566536..cf7acb5c6f9597 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -89,7 +89,7 @@ static void buffer_append(struct dump_config *dc, const char *cstr, unsigned lon static void dump_append_ld(struct dump_config *dc, const long number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%ld", number); RUBY_ASSERT(required <= width); @@ -99,7 +99,7 @@ dump_append_ld(struct dump_config *dc, const long number) static void dump_append_lu(struct dump_config *dc, const unsigned long number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%lu", number); RUBY_ASSERT(required <= width); @@ -123,7 +123,7 @@ dump_append_g(struct dump_config *dc, const double number) static void dump_append_d(struct dump_config *dc, const int number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%d", number); RUBY_ASSERT(required <= width); @@ -133,7 +133,7 @@ dump_append_d(struct dump_config *dc, const int number) static void dump_append_sizet(struct dump_config *dc, const size_t number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%"PRIuSIZE, number); RUBY_ASSERT(required <= width); @@ -144,7 +144,7 @@ static void dump_append_c(struct dump_config *dc, char c) { if (c <= 0x1f) { - const int width = (sizeof(c) * CHAR_BIT / 4) + 5; + const unsigned int width = (sizeof(c) * CHAR_BIT / 4) + 5; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "\\u00%02x", c); RUBY_ASSERT(required <= width); diff --git a/ext/openssl/ossl_ocsp.c b/ext/openssl/ossl_ocsp.c index 7a92e5df68b8a8..0ade7adde7b6cc 100644 --- a/ext/openssl/ossl_ocsp.c +++ b/ext/openssl/ossl_ocsp.c @@ -1787,7 +1787,7 @@ Init_ossl_ocsp(void) * single_response = basic_response.find_response(certificate_id) * * unless single_response - * raise 'basic_response does not have the status for the certificiate' + * raise 'basic_response does not have the status for the certificate' * end * * Then check the validity. A status issued in the future must be rejected. diff --git a/ext/pathname/pathname.gemspec b/ext/pathname/pathname.gemspec index 8593f9e9c791f8..317029afb11a31 100644 --- a/ext/pathname/pathname.gemspec +++ b/ext/pathname/pathname.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |spec| `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] spec.extensions = %w[ext/pathname/extconf.rb] end diff --git a/ext/win32/lib/win32/sspi.rb b/ext/win32/lib/win32/sspi.rb index a73819f24ea92a..20205fd4d6e4f8 100644 --- a/ext/win32/lib/win32/sspi.rb +++ b/ext/win32/lib/win32/sspi.rb @@ -223,7 +223,7 @@ class NegotiateAuth B64_TOKEN_PREFIX = ["NTLMSSP"].pack("m").delete("=\n") # Given a connection and a request path, performs authentication as the current user and returns - # the response from a GET request. The connnection should be a Net::HTTP object, and it should + # the response from a GET request. The connection should be a Net::HTTP object, and it should # have been constructed using the Net::HTTP.Proxy method, but anything that responds to "get" will work. # If a user and domain are given, will authenticate as the given user. # Returns the response received from the get method (usually Net::HTTPResponse) diff --git a/ext/win32ole/sample/xml.rb b/ext/win32ole/sample/xml.rb index 36c3db32ef04d8..5a239c93367598 100644 --- a/ext/win32ole/sample/xml.rb +++ b/ext/win32ole/sample/xml.rb @@ -1032,8 +1032,8 @@ def loadXML(arg0) end # VOID save - # save the document to a specified desination - # VARIANT arg0 --- desination [IN] + # save the document to a specified destination + # VARIANT arg0 --- destination [IN] def save(arg0) ret = _invoke(64, [arg0], [VT_VARIANT]) @lastargs = WIN32OLE::ARGV @@ -6224,8 +6224,8 @@ def loadXML(arg0) end # VOID save - # save the document to a specified desination - # VARIANT arg0 --- desination [IN] + # save the document to a specified destination + # VARIANT arg0 --- destination [IN] def save(arg0) ret = @dispatch._invoke(64, [arg0], [VT_VARIANT]) @lastargs = WIN32OLE::ARGV @@ -6831,8 +6831,8 @@ def loadXML(arg0) end # VOID save - # save the document to a specified desination - # VARIANT arg0 --- desination [IN] + # save the document to a specified destination + # VARIANT arg0 --- destination [IN] def save(arg0) ret = @dispatch._invoke(64, [arg0], [VT_VARIANT]) @lastargs = WIN32OLE::ARGV diff --git a/ext/win32ole/win32ole_type.c b/ext/win32ole/win32ole_type.c index fa39bf3696ecd2..48dbc9dbde638b 100644 --- a/ext/win32ole/win32ole_type.c +++ b/ext/win32ole/win32ole_type.c @@ -56,7 +56,7 @@ static const rb_data_type_t oletype_datatype = { /* * Document-class: WIN32OLE_TYPE * - * WIN32OLE_TYPE objects represent OLE type libarary information. + * WIN32OLE_TYPE objects represent OLE type library information. */ static void diff --git a/ext/win32ole/win32ole_variant.c b/ext/win32ole/win32ole_variant.c index 93f0636593ce78..5ab29669f60dcd 100644 --- a/ext/win32ole/win32ole_variant.c +++ b/ext/win32ole/win32ole_variant.c @@ -371,7 +371,7 @@ check_type_val2variant(VALUE val) * Win32OLE converts Ruby object into OLE variant automatically when * invoking OLE methods. If OLE method requires the argument which is * different from the variant by automatic conversion of Win32OLE, you - * can convert the specfied variant type by using WIN32OLE_VARIANT class. + * can convert the specified variant type by using WIN32OLE_VARIANT class. * * param = WIN32OLE_VARIANT.new(10, WIN32OLE::VARIANT::VT_R4) * oleobj.method(param) diff --git a/gc.c b/gc.c index deaa56ff60173a..f4e52fdb8162cf 100644 --- a/gc.c +++ b/gc.c @@ -3344,7 +3344,7 @@ objspace_each_objects(rb_objspace_t *objspace, each_obj_callback *callback, void * heap. This will be a snapshot of the state of the heap before we * call the callback over each page that exists in this buffer. Thus it * is safe for the callback to allocate objects without possibly entering - * an infinte loop. */ + * an infinite loop. */ struct heap_page *page = 0; size_t pages_count = 0; list_for_each(&heap_eden->pages, page, page_node) { @@ -4033,7 +4033,7 @@ id2ref_obj_tbl(rb_objspace_t *objspace, VALUE objid) * r = ObjectSpace._id2ref(s.object_id) #=> "I am a string" * r == s #=> true * - * On multi-ractor mode, if the object is not sharable, it raises + * On multi-ractor mode, if the object is not shareable, it raises * RangeError. */ diff --git a/gems/bundled_gems b/gems/bundled_gems index 587b94b60a7fa6..fa2f585f6a8ddf 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -6,4 +6,4 @@ test-unit 3.4.1 https://github.com/test-unit/test-unit 3.4.1 rexml 3.2.5 https://github.com/ruby/rexml rss 0.2.9 https://github.com/ruby/rss 0.2.9 typeprof 0.13.0 https://github.com/ruby/typeprof -rbs 1.1.1 https://github.com/ruby/rbs +rbs 1.2.0 https://github.com/ruby/rbs diff --git a/hash.c b/hash.c index 1498f30f5af8e1..a36310209b474f 100644 --- a/hash.c +++ b/hash.c @@ -2120,7 +2120,6 @@ rb_hash_lookup(VALUE hash, VALUE key) * If +key+ is not found and no block was given, * returns +default_value+: * {}.fetch(:nosuch, :default) # => :default - * {}.fetch(:nosuch) # => nil * * If +key+ is not found and a block was given, * yields +key+ to the block and returns the block's return value: diff --git a/include/ruby/backward/2/stdarg.h b/include/ruby/backward/2/stdarg.h index c2a9ca1e2f2348..5c5e1b31ce5574 100644 --- a/include/ruby/backward/2/stdarg.h +++ b/include/ruby/backward/2/stdarg.h @@ -20,7 +20,7 @@ * extension libraries. They could be written in C++98. * @brief Defines old #_ * - * Nobody should ever use these macros any longer. No konwn compilers lack + * Nobody should ever use these macros any longer. No known compilers lack * prototypes today. It's 21st century. Just forget them. */ diff --git a/include/ruby/internal/attr/cold.h b/include/ruby/internal/attr/cold.h index fcee507456006a..6db57fc9c26340 100644 --- a/include/ruby/internal/attr/cold.h +++ b/include/ruby/internal/attr/cold.h @@ -25,7 +25,7 @@ /** Wraps (or simulates) `__attribute__((cold))` */ #if RBIMPL_COMPILER_IS(SunPro) -# /* Recent SunPro has __has_attribute, and is borken. */ +# /* Recent SunPro has __has_attribute, and is broken. */ # /* It reports it has attribute cold, reality isn't (warnings issued). */ # define RBIMPL_ATTR_COLD() /* void */ #elif RBIMPL_HAS_ATTRIBUTE(cold) diff --git a/include/ruby/internal/compiler_since.h b/include/ruby/internal/compiler_since.h index 92abb8acc8b2db..b213cfd8b9293c 100644 --- a/include/ruby/internal/compiler_since.h +++ b/include/ruby/internal/compiler_since.h @@ -30,7 +30,7 @@ * @param y Minor version. * @param z Patchlevel. * @retval true cc >= x.y.z. - * @retval false oherwise. + * @retval false otherwise. */ #define RBIMPL_COMPILER_SINCE(cc, x, y, z) \ (RBIMPL_COMPILER_IS(cc) && \ @@ -48,7 +48,7 @@ * @param y Minor version. * @param z Patchlevel. * @retval true cc < x.y.z. - * @retval false oherwise. + * @retval false otherwise. */ #define RBIMPL_COMPILER_BEFORE(cc, x, y, z) \ (RBIMPL_COMPILER_IS(cc) && \ diff --git a/include/ruby/internal/core.h b/include/ruby/internal/core.h index 53a00a4603b2ea..279a697ea10f22 100644 --- a/include/ruby/internal/core.h +++ b/include/ruby/internal/core.h @@ -18,7 +18,7 @@ * Do not expect for instance `__VA_ARGS__` is always available. * We assume C99 for ruby itself but we don't assume languages of * extension libraries. They could be written in C++98. - * @brief Core data structures, definitions and manupulations. + * @brief Core data structures, definitions and manipulations. */ #include "ruby/internal/core/rarray.h" #include "ruby/internal/core/rbasic.h" diff --git a/include/ruby/internal/warning_push.h b/include/ruby/internal/warning_push.h index b8a21aaeab9d00..ca521290c96d66 100644 --- a/include/ruby/internal/warning_push.h +++ b/include/ruby/internal/warning_push.h @@ -25,7 +25,7 @@ * * Q: Why all the macros defined in this file are function-like macros? * - * A: Sigh. This is because of Doxgen. Its `SKIP_FUNCTION_MACROS = YES` + * A: Sigh. This is because of Doxygen. Its `SKIP_FUNCTION_MACROS = YES` * configuration setting requests us that if we want it to ignore these * macros, then we have to do two things: (1) let them be defined as * function-like macros, and (2) place them separately in their own line, diff --git a/insns.def b/insns.def index 5704bf44e7f491..8d609927b5871f 100644 --- a/insns.def +++ b/insns.def @@ -592,25 +592,6 @@ swap /* none */ } -/* reverse stack top N order. */ -DEFINE_INSN -reverse -(rb_num_t n) -(...) -(...) -// attr rb_snum_t sp_inc = 0; -{ - rb_num_t i; - VALUE *sp = STACK_ADDR_FROM_TOP(n); - - for (i=0; i/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/lib/cgi/cgi.gemspec b/lib/cgi/cgi.gemspec index 1a883934e58882..5d23ef0f6144b7 100644 --- a/lib/cgi/cgi.gemspec +++ b/lib/cgi/cgi.gemspec @@ -26,6 +26,6 @@ Gem::Specification.new do |spec| `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } end spec.bindir = "exe" - spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } + spec.executables = [] spec.require_paths = ["lib"] end diff --git a/lib/cgi/util.rb b/lib/cgi/util.rb index aab8b000cbc205..69a252b5e36fe7 100644 --- a/lib/cgi/util.rb +++ b/lib/cgi/util.rb @@ -49,9 +49,12 @@ def escapeHTML(string) table = Hash[TABLE_FOR_ESCAPE_HTML__.map {|pair|pair.map {|s|s.encode(enc)}}] string = string.gsub(/#{"['&\"<>]".encode(enc)}/, table) string.encode!(origenc) if origenc - return string + string + else + string = string.b + string.gsub!(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) + string.force_encoding(enc) end - string.gsub(/['&\"<>]/, TABLE_FOR_ESCAPE_HTML__) end begin @@ -90,7 +93,8 @@ def unescapeHTML(string) when Encoding::ISO_8859_1; 256 else 128 end - string.gsub(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do + string = string.b + string.gsub!(/&(apos|amp|quot|gt|lt|\#[0-9]+|\#[xX][0-9A-Fa-f]+);/) do match = $1.dup case match when 'apos' then "'" @@ -116,6 +120,7 @@ def unescapeHTML(string) "&#{match};" end end + string.force_encoding enc end # Synonym for CGI.escapeHTML(str) diff --git a/lib/irb/color.rb b/lib/irb/color.rb index fce4d53191f807..40e9e04c975685 100644 --- a/lib/irb/color.rb +++ b/lib/irb/color.rb @@ -101,22 +101,22 @@ def inspect_colorable?(obj, seen: {}.compare_by_identity) end end - def clear - return '' unless colorable? + def clear(colorable: colorable?) + return '' unless colorable "\e[#{CLEAR}m" end - def colorize(text, seq) - return text unless colorable? + def colorize(text, seq, colorable: colorable?) + return text unless colorable seq = seq.map { |s| "\e[#{const_get(s)}m" }.join('') - "#{seq}#{text}#{clear}" + "#{seq}#{text}#{clear(colorable: colorable)}" end # If `complete` is false (code is incomplete), this does not warn compile_error. # This option is needed to avoid warning a user when the compile_error is happening # because the input is not wrong but just incomplete. - def colorize_code(code, complete: true, ignore_error: false) - return code unless colorable? + def colorize_code(code, complete: true, ignore_error: false, colorable: colorable?) + return code unless colorable symbol_state = SymbolState.new colored = +'' @@ -134,7 +134,7 @@ def colorize_code(code, complete: true, ignore_error: false) line = Reline::Unicode.escape_for_print(line) if seq = dispatch_seq(token, expr, line, in_symbol: in_symbol) colored << seq.map { |s| "\e[#{s}m" }.join('') - colored << line.sub(/\Z/, clear) + colored << line.sub(/\Z/, clear(colorable: colorable)) else colored << line end diff --git a/lib/irb/ruby-lex.rb b/lib/irb/ruby-lex.rb index 82df06da2b94fb..3b3b9b393626a3 100644 --- a/lib/irb/ruby-lex.rb +++ b/lib/irb/ruby-lex.rb @@ -339,7 +339,7 @@ def check_code_block(code, tokens = @tokens) # "syntax error, unexpected end-of-input, expecting keyword_end" # # example: - # if ture + # if true # hoge # if false # fuga diff --git a/lib/matrix/matrix.gemspec b/lib/matrix/matrix.gemspec index d2ff9ce7c6d271..6f68cbd8165568 100644 --- a/lib/matrix/matrix.gemspec +++ b/lib/matrix/matrix.gemspec @@ -23,7 +23,4 @@ Gem::Specification.new do |spec| spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] - - spec.add_development_dependency "bundler" - spec.add_development_dependency "rake" end diff --git a/lib/matrix/version.rb b/lib/matrix/version.rb index 4a8bc36aaa78b7..82b835f038c6fc 100644 --- a/lib/matrix/version.rb +++ b/lib/matrix/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true class Matrix - VERSION = "0.3.1" + VERSION = "0.4.1" end diff --git a/lib/net/ftp.rb b/lib/net/ftp.rb index 88e8655c1ceb3d..bb746098df9aa7 100644 --- a/lib/net/ftp.rb +++ b/lib/net/ftp.rb @@ -330,14 +330,19 @@ def return_code=(s) # :nodoc: # SOCKS_SERVER, then a SOCKSSocket is returned, else a Socket is # returned. def open_socket(host, port) # :nodoc: - return Timeout.timeout(@open_timeout, OpenTimeout) { - if defined? SOCKSSocket and ENV["SOCKS_SERVER"] - @passive = true + if defined? SOCKSSocket and ENV["SOCKS_SERVER"] + @passive = true + Timeout.timeout(@open_timeout, OpenTimeout) do SOCKSSocket.open(host, port) - else - Socket.tcp(host, port) end - } + else + begin + Socket.tcp host, port, nil, nil, connect_timeout: @open_timeout + rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions + raise Net::OpenTimeout, "Timeout to open TCP connection to "\ + "#{host}:#{port} (exceeds #{@open_timeout} seconds)" + end + end end private :open_socket @@ -542,18 +547,22 @@ def makepasv # :nodoc: def transfercmd(cmd, rest_offset = nil) # :nodoc: if @passive host, port = makepasv - conn = open_socket(host, port) - if @resume and rest_offset - resp = sendcmd("REST " + rest_offset.to_s) - if !resp.start_with?("3") + begin + conn = open_socket(host, port) + if @resume and rest_offset + resp = sendcmd("REST " + rest_offset.to_s) + if !resp.start_with?("3") + raise FTPReplyError, resp + end + end + resp = sendcmd(cmd) + # skip 2XX for some ftp servers + resp = getresp if resp.start_with?("2") + if !resp.start_with?("1") raise FTPReplyError, resp end - end - resp = sendcmd(cmd) - # skip 2XX for some ftp servers - resp = getresp if resp.start_with?("2") - if !resp.start_with?("1") - raise FTPReplyError, resp + ensure + conn.close if conn && $! end else sock = makeport @@ -1045,10 +1054,11 @@ def writable? TIME_PARSER = ->(value, local = false) { unless /\A(?\d{4})(?\d{2})(?\d{2}) (?\d{2})(?\d{2})(?\d{2}) - (?:\.(?\d+))?/x =~ value + (?:\.(?\d{1,17}))?/x =~ value + value = value[0, 97] + "..." if value.size > 100 raise FTPProtoError, "invalid time-val: #{value}" end - usec = fractions.to_i * 10 ** (6 - fractions.to_s.size) + usec = ".#{fractions}".to_r * 1_000_000 if fractions Time.public_send(local ? :local : :utc, year, month, day, hour, min, sec, usec) } FACT_PARSERS = Hash.new(CASE_DEPENDENT_PARSER) @@ -1356,7 +1366,7 @@ def close end # - # Returns +true+ iff the connection is closed. + # Returns +true+ if and only if the connection is closed. # def closed? @sock == nil or @sock.closed? diff --git a/lib/net/imap.rb b/lib/net/imap.rb index 36920c4a915d9d..834ff0dee718e2 100644 --- a/lib/net/imap.rb +++ b/lib/net/imap.rb @@ -201,7 +201,7 @@ module Net # Unicode", RFC 2152, May 1997. # class IMAP < Protocol - VERSION = "0.1.1" + VERSION = "0.2.1" include MonitorMixin if defined?(OpenSSL::SSL) @@ -229,6 +229,9 @@ class IMAP < Protocol # it raises a Net::OpenTimeout exception. The default value is 30 seconds. attr_reader :open_timeout + # Seconds to wait until an IDLE response is received. + attr_reader :idle_response_timeout + # The thread to receive exceptions. attr_accessor :client_thread @@ -304,6 +307,16 @@ def self.add_authenticator(auth_type, authenticator) @@authenticators[auth_type] = authenticator end + # Builds an authenticator for Net::IMAP#authenticate. + def self.authenticator(auth_type, *args) + auth_type = auth_type.upcase + unless @@authenticators.has_key?(auth_type) + raise ArgumentError, + format('unknown auth type - "%s"', auth_type) + end + @@authenticators[auth_type].new(*args) + end + # The default port for IMAP connections, port 143 def self.default_port return PORT @@ -365,6 +378,30 @@ def capability end end + # Sends an ID command, and returns a hash of the server's + # response, or nil if the server does not identify itself. + # + # Note that the user should first check if the server supports the ID + # capability. For example: + # + # capabilities = imap.capability + # if capabilities.include?("ID") + # id = imap.id( + # name: "my IMAP client (ruby)", + # version: MyIMAP::VERSION, + # "support-url": "mailto:bugs@example.com", + # os: RbConfig::CONFIG["host_os"], + # ) + # end + # + # See RFC 2971, Section 3.3, for defined fields. + def id(client_id=nil) + synchronize do + send_command("ID", ClientID.new(client_id)) + @responses.delete("ID")&.last + end + end + # Sends a NOOP command to the server. It does nothing. def noop send_command("NOOP") @@ -408,7 +445,7 @@ def starttls(options = {}, verify = true) # the form "AUTH=LOGIN" or "AUTH=CRAM-MD5". # # Authentication is done using the appropriate authenticator object: - # see @@authenticators for more information on plugging in your own + # see +add_authenticator+ for more information on plugging in your own # authenticator. # # For example: @@ -417,12 +454,7 @@ def starttls(options = {}, verify = true) # # A Net::IMAP::NoResponseError is raised if authentication fails. def authenticate(auth_type, *args) - auth_type = auth_type.upcase - unless @@authenticators.has_key?(auth_type) - raise ArgumentError, - format('unknown auth type - "%s"', auth_type) - end - authenticator = @@authenticators[auth_type].new(*args) + authenticator = self.class.authenticator(auth_type, *args) send_command("AUTHENTICATE", auth_type) do |resp| if resp.instance_of?(ContinuationRequest) data = authenticator.process(resp.data.text.unpack("m")[0]) @@ -552,6 +584,60 @@ def list(refname, mailbox) end end + # Sends a NAMESPACE command [RFC2342] and returns the namespaces that are + # available. The NAMESPACE command allows a client to discover the prefixes + # of namespaces used by a server for personal mailboxes, other users' + # mailboxes, and shared mailboxes. + # + # This extension predates IMAP4rev1 (RFC3501), so most IMAP servers support + # it. Many popular IMAP servers are configured with the default personal + # namespaces as `("" "/")`: no prefix and "/" hierarchy delimiter. In that + # common case, the naive client may not have any trouble naming mailboxes. + # + # But many servers are configured with the default personal namespace as + # e.g. `("INBOX." ".")`, placing all personal folders under INBOX, with "." + # as the hierarchy delimiter. If the client does not check for this, but + # naively assumes it can use the same folder names for all servers, then + # folder creation (and listing, moving, etc) can lead to errors. + # + # From RFC2342: + # + # Although typically a server will support only a single Personal + # Namespace, and a single Other User's Namespace, circumstances exist + # where there MAY be multiples of these, and a client MUST be prepared + # for them. If a client is configured such that it is required to create + # a certain mailbox, there can be circumstances where it is unclear which + # Personal Namespaces it should create the mailbox in. In these + # situations a client SHOULD let the user select which namespaces to + # create the mailbox in. + # + # The user of this method should first check if the server supports the + # NAMESPACE capability. The return value is a +Net::IMAP::Namespaces+ + # object which has +personal+, +other+, and +shared+ fields, each an array + # of +Net::IMAP::Namespace+ objects. These arrays will be empty when the + # server responds with nil. + # + # For example: + # + # capabilities = imap.capability + # if capabilities.include?("NAMESPACE") + # namespaces = imap.namespace + # if namespace = namespaces.personal.first + # prefix = namespace.prefix # e.g. "" or "INBOX." + # delim = namespace.delim # e.g. "/" or "." + # # personal folders should use the prefix and delimiter + # imap.create(prefix + "foo") + # imap.create(prefix + "bar") + # imap.create(prefix + %w[path to my folder].join(delim)) + # end + # end + def namespace + synchronize do + send_command("NAMESPACE") + return @responses.delete("NAMESPACE")[-1] + end + end + # Sends a XLIST command, and returns a subset of names from # the complete set of all names available to the client. # +refname+ provides a context (for instance, a base directory @@ -973,7 +1059,7 @@ def idle(timeout = nil, &response_handler) unless @receiver_thread_terminating remove_response_handler(response_handler) put_string("DONE#{CRLF}") - response = get_tagged_response(tag, "IDLE") + response = get_tagged_response(tag, "IDLE", @idle_response_timeout) end end end @@ -1059,6 +1145,7 @@ def self.format_datetime(time) # If +options[:ssl]+ is a hash, it's passed to # OpenSSL::SSL::SSLContext#set_params as parameters. # open_timeout:: Seconds to wait until a connection is opened + # idle_response_timeout:: Seconds to wait until an IDLE response is received # # The most common errors are: # @@ -1088,6 +1175,7 @@ def initialize(host, port_or_options = {}, @tag_prefix = "RUBY" @tagno = 0 @open_timeout = options[:open_timeout] || 30 + @idle_response_timeout = options[:idle_response_timeout] || 5 @parser = ResponseParser.new @sock = tcp_socket(@host, @port) begin @@ -1211,10 +1299,19 @@ def receive_responses end end - def get_tagged_response(tag, cmd) + def get_tagged_response(tag, cmd, timeout = nil) + if timeout + deadline = Time.now + timeout + end until @tagged_responses.key?(tag) raise @exception if @exception - @tagged_response_arrival.wait + if timeout + timeout = deadline - Time.now + if timeout <= 0 + return nil + end + end + @tagged_response_arrival.wait(timeout) end resp = @tagged_responses.delete(tag) case resp.name @@ -1656,6 +1753,74 @@ def validate_internal(data) end end + class ClientID # :nodoc: + + def send_data(imap, tag) + imap.__send__(:send_data, format_internal(@data), tag) + end + + def validate + validate_internal(@data) + end + + private + + def initialize(data) + @data = data + end + + def validate_internal(client_id) + client_id.to_h.each do |k,v| + unless StringFormatter.valid_string?(k) + raise DataFormatError, client_id.inspect + end + end + rescue NoMethodError, TypeError # to_h failed + raise DataFormatError, client_id.inspect + end + + def format_internal(client_id) + return nil if client_id.nil? + client_id.to_h.flat_map {|k,v| + [StringFormatter.string(k), StringFormatter.nstring(v)] + } + end + + end + + module StringFormatter + + LITERAL_REGEX = /[\x80-\xff\r\n]/n + + module_function + + # Allows symbols in addition to strings + def valid_string?(str) + str.is_a?(Symbol) || str.respond_to?(:to_str) + end + + # Allows nil, symbols, and strings + def valid_nstring?(str) + str.nil? || valid_string?(str) + end + + # coerces using +to_s+ + def string(str) + str = str.to_s + if str =~ LITERAL_REGEX + Literal.new(str) + else + QuotedString.new(str) + end + end + + # coerces non-nil using +to_s+ + def nstring(str) + str.nil? ? nil : string(str) + end + + end + # Common validators of number and nz_number types module NumValidator # :nodoc class << self @@ -1747,6 +1912,18 @@ def ensure_mod_sequence_value(num) # raw_data:: Returns the raw data string. UntaggedResponse = Struct.new(:name, :data, :raw_data) + # Net::IMAP::IgnoredResponse represents intentionaly ignored responses. + # + # This includes untagged response "NOOP" sent by eg. Zimbra to avoid some + # clients to close the connection. + # + # It matches no IMAP standard. + # + # ==== Fields: + # + # raw_data:: Returns the raw data string. + IgnoredResponse = Struct.new(:raw_data) + # Net::IMAP::TaggedResponse represents tagged responses. # # The server completion result response indicates the success or @@ -1774,8 +1951,7 @@ def ensure_mod_sequence_value(num) # Net::IMAP::ResponseText represents texts of responses. # The text may be prefixed by the response code. # - # resp_text ::= ["[" resp_text_code "]" SPACE] (text_mime2 / text) - # ;; text SHOULD NOT begin with "[" or "=" + # resp_text ::= ["[" resp-text-code "]" SP] text # # ==== Fields: # @@ -1787,12 +1963,15 @@ def ensure_mod_sequence_value(num) # Net::IMAP::ResponseCode represents response codes. # - # resp_text_code ::= "ALERT" / "PARSE" / - # "PERMANENTFLAGS" SPACE "(" #(flag / "\*") ")" / + # resp_text_code ::= "ALERT" / + # "BADCHARSET" [SP "(" astring *(SP astring) ")" ] / + # capability_data / "PARSE" / + # "PERMANENTFLAGS" SP "(" + # [flag_perm *(SP flag_perm)] ")" / # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / - # "UIDVALIDITY" SPACE nz_number / - # "UNSEEN" SPACE nz_number / - # atom [SPACE 1*] + # "UIDNEXT" SP nz_number / "UIDVALIDITY" SP nz_number / + # "UNSEEN" SP nz_number / + # atom [SP 1*] # # ==== Fields: # @@ -1872,6 +2051,39 @@ def ensure_mod_sequence_value(num) # MailboxACLItem = Struct.new(:user, :rights, :mailbox) + # Net::IMAP::Namespace represents a single [RFC-2342] namespace. + # + # Namespace = nil / "(" 1*( "(" string SP (<"> QUOTED_CHAR <"> / + # nil) *(Namespace_Response_Extension) ")" ) ")" + # + # Namespace_Response_Extension = SP string SP "(" string *(SP string) + # ")" + # + # ==== Fields: + # + # prefix:: Returns the namespace prefix string. + # delim:: Returns nil or the hierarchy delimiter character. + # extensions:: Returns a hash of extension names to extension flag arrays. + # + Namespace = Struct.new(:prefix, :delim, :extensions) + + # Net::IMAP::Namespaces represents the response from [RFC-2342] NAMESPACE. + # + # Namespace_Response = "*" SP "NAMESPACE" SP Namespace SP Namespace SP + # Namespace + # + # ; The first Namespace is the Personal Namespace(s) + # ; The second Namespace is the Other Users' Namespace(s) + # ; The third Namespace is the Shared Namespace(s) + # + # ==== Fields: + # + # personal:: Returns an array of Personal Net::IMAP::Namespace objects. + # other:: Returns an array of Other Users' Net::IMAP::Namespace objects. + # shared:: Returns an array of Shared Net::IMAP::Namespace objects. + # + Namespaces = Struct.new(:personal, :other, :shared) + # Net::IMAP::StatusData represents the contents of the STATUS response. # # ==== Fields: @@ -2291,8 +2503,12 @@ def response_untagged return response_cond when /\A(?:FLAGS)\z/ni return flags_response + when /\A(?:ID)\z/ni + return id_response when /\A(?:LIST|LSUB|XLIST)\z/ni return list_response + when /\A(?:NAMESPACE)\z/ni + return namespace_response when /\A(?:QUOTA)\z/ni return getquota_response when /\A(?:QUOTAROOT)\z/ni @@ -2307,6 +2523,8 @@ def response_untagged return status_response when /\A(?:CAPABILITY)\z/ni return capability_response + when /\A(?:NOOP)\z/ni + return ignored_response else return text_response end @@ -2316,7 +2534,7 @@ def response_untagged end def response_tagged - tag = atom + tag = astring_chars match(T_SPACE) token = match(T_ATOM) name = token.value.upcase @@ -2876,14 +3094,18 @@ def modseq_data return name, modseq end + def ignored_response + while lookahead.symbol != T_CRLF + shift_token + end + return IgnoredResponse.new(@str) + end + def text_response token = match(T_ATOM) name = token.value.upcase match(T_SPACE) - @lex_state = EXPR_TEXT - token = match(T_TEXT) - @lex_state = EXPR_BEG - return UntaggedResponse.new(name, token.value) + return UntaggedResponse.new(name, text) end def flags_response @@ -3114,11 +3336,15 @@ def capability_response token = match(T_ATOM) name = token.value.upcase match(T_SPACE) + UntaggedResponse.new(name, capability_data, @str) + end + + def capability_data data = [] while true token = lookahead case token.symbol - when T_CRLF + when T_CRLF, T_RBRA break when T_SPACE shift_token @@ -3126,30 +3352,142 @@ def capability_response end data.push(atom.upcase) end + data + end + + def id_response + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + token = match(T_LPAR, T_NIL) + if token.symbol == T_NIL + return UntaggedResponse.new(name, nil, @str) + else + data = {} + while true + token = lookahead + case token.symbol + when T_RPAR + shift_token + break + when T_SPACE + shift_token + next + else + key = string + match(T_SPACE) + val = nstring + data[key] = val + end + end + return UntaggedResponse.new(name, data, @str) + end + end + + def namespace_response + @lex_state = EXPR_DATA + token = lookahead + token = match(T_ATOM) + name = token.value.upcase + match(T_SPACE) + personal = namespaces + match(T_SPACE) + other = namespaces + match(T_SPACE) + shared = namespaces + @lex_state = EXPR_BEG + data = Namespaces.new(personal, other, shared) return UntaggedResponse.new(name, data, @str) end - def resp_text - @lex_state = EXPR_RTEXT + def namespaces token = lookahead - if token.symbol == T_LBRA - code = resp_text_code + # empty () is not allowed, so nil is functionally identical to empty. + data = [] + if token.symbol == T_NIL + shift_token else - code = nil + match(T_LPAR) + loop do + data << namespace + break unless lookahead.symbol == T_SPACE + shift_token + end + match(T_RPAR) + end + data + end + + def namespace + match(T_LPAR) + prefix = match(T_QUOTED, T_LITERAL).value + match(T_SPACE) + delimiter = string + extensions = namespace_response_extensions + match(T_RPAR) + Namespace.new(prefix, delimiter, extensions) + end + + def namespace_response_extensions + data = {} + token = lookahead + if token.symbol == T_SPACE + shift_token + name = match(T_QUOTED, T_LITERAL).value + data[name] ||= [] + match(T_SPACE) + match(T_LPAR) + loop do + data[name].push match(T_QUOTED, T_LITERAL).value + break unless lookahead.symbol == T_SPACE + shift_token + end + match(T_RPAR) + end + data + end + + # text = 1*TEXT-CHAR + # TEXT-CHAR = + def text + match(T_TEXT, lex_state: EXPR_TEXT).value + end + + # resp-text = ["[" resp-text-code "]" SP] text + def resp_text + token = match(T_LBRA, T_TEXT, lex_state: EXPR_RTEXT) + case token.symbol + when T_LBRA + code = resp_text_code + match(T_RBRA) + accept_space # violating RFC + ResponseText.new(code, text) + when T_TEXT + ResponseText.new(nil, token.value) end - token = match(T_TEXT) - @lex_state = EXPR_BEG - return ResponseText.new(code, token.value) end + # See https://www.rfc-editor.org/errata/rfc3501 + # + # resp-text-code = "ALERT" / + # "BADCHARSET" [SP "(" charset *(SP charset) ")" ] / + # capability-data / "PARSE" / + # "PERMANENTFLAGS" SP "(" + # [flag-perm *(SP flag-perm)] ")" / + # "READ-ONLY" / "READ-WRITE" / "TRYCREATE" / + # "UIDNEXT" SP nz-number / "UIDVALIDITY" SP nz-number / + # "UNSEEN" SP nz-number / + # atom [SP 1*] def resp_text_code - @lex_state = EXPR_BEG - match(T_LBRA) token = match(T_ATOM) name = token.value.upcase case name when /\A(?:ALERT|PARSE|READ-ONLY|READ-WRITE|TRYCREATE|NOMODSEQ)\z/n result = ResponseCode.new(name, nil) + when /\A(?:BADCHARSET)\z/n + result = ResponseCode.new(name, charset_list) + when /\A(?:CAPABILITY)\z/ni + result = ResponseCode.new(name, capability_data) when /\A(?:PERMANENTFLAGS)\z/n match(T_SPACE) result = ResponseCode.new(name, flag_list) @@ -3160,19 +3498,28 @@ def resp_text_code token = lookahead if token.symbol == T_SPACE shift_token - @lex_state = EXPR_CTEXT - token = match(T_TEXT) - @lex_state = EXPR_BEG + token = match(T_TEXT, lex_state: EXPR_CTEXT) result = ResponseCode.new(name, token.value) else result = ResponseCode.new(name, nil) end end - match(T_RBRA) - @lex_state = EXPR_RTEXT return result end + def charset_list + result = [] + if accept(T_SPACE) + match(T_LPAR) + result << charset + while accept(T_SPACE) + result << charset + end + match(T_RPAR) + end + result + end + def address_list token = lookahead if token.symbol == T_NIL @@ -3269,7 +3616,7 @@ def astring if string_token?(token) return string else - return atom + return astring_chars end end @@ -3299,34 +3646,49 @@ def case_insensitive_string return token.value.upcase end - def atom - result = String.new - while true - token = lookahead - if atom_token?(token) - result.concat(token.value) - shift_token - else - if result.empty? - parse_error("unexpected token %s", token.symbol) - else - return result - end - end - end - end - + # atom = 1*ATOM-CHAR + # ATOM-CHAR = ATOM_TOKENS = [ T_ATOM, T_NUMBER, T_NIL, T_LBRA, - T_RBRA, T_PLUS ] - def atom_token?(token) - return ATOM_TOKENS.include?(token.symbol) + def atom + -combine_adjacent(*ATOM_TOKENS) + end + + # ASTRING-CHAR = ATOM-CHAR / resp-specials + # resp-specials = "]" + ASTRING_CHARS_TOKENS = [*ATOM_TOKENS, T_RBRA] + + def astring_chars + combine_adjacent(*ASTRING_CHARS_TOKENS) + end + + def combine_adjacent(*tokens) + result = "".b + while token = accept(*tokens) + result << token.value + end + if result.empty? + parse_error('unexpected token %s (expected %s)', + lookahead.symbol, args.join(" or ")) + end + result + end + + # See https://www.rfc-editor.org/errata/rfc3501 + # + # charset = atom / quoted + def charset + if token = accept(T_QUOTED) + token.value + else + atom + end end def number @@ -3344,22 +3706,62 @@ def nil_atom return nil end - def match(*args) + SPACES_REGEXP = /\G */n + + # This advances @pos directly so it's safe before changing @lex_state. + def accept_space + if @token + shift_token if @token.symbol == T_SPACE + elsif @str[@pos] == " " + @pos += 1 + end + end + + # The RFC is very strict about this and usually we should be too. + # But skipping spaces is usually a safe workaround for buggy servers. + # + # This advances @pos directly so it's safe before changing @lex_state. + def accept_spaces + shift_token if @token&.symbol == T_SPACE + if @str.index(SPACES_REGEXP, @pos) + @pos = $~.end(0) + end + end + + def match(*args, lex_state: @lex_state) + if @token && lex_state != @lex_state + parse_error("invalid lex_state change to %s with unconsumed token", + lex_state) + end + begin + @lex_state, original_lex_state = lex_state, @lex_state + token = lookahead + unless args.include?(token.symbol) + parse_error('unexpected token %s (expected %s)', + token.symbol.id2name, + args.collect {|i| i.id2name}.join(" or ")) + end + shift_token + return token + ensure + @lex_state = original_lex_state + end + end + + # like match, but does not raise error on failure. + # + # returns and shifts token on successful match + # returns nil and leaves @token unshifted on no match + def accept(*args) token = lookahead - unless args.include?(token.symbol) - parse_error('unexpected token %s (expected %s)', - token.symbol.id2name, - args.collect {|i| i.id2name}.join(" or ")) + if args.include?(token.symbol) + shift_token + token end - shift_token - return token end def lookahead - unless @token - @token = next_token - end - return @token + @token ||= next_token end def shift_token diff --git a/lib/net/net-smtp.gemspec b/lib/net/net-smtp.gemspec index bd0a2dd84c2594..3c210429b911d7 100644 --- a/lib/net/net-smtp.gemspec +++ b/lib/net/net-smtp.gemspec @@ -22,9 +22,11 @@ Gem::Specification.new do |spec| spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage - spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do - `git ls-files -z 2>/dev/null`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) } - end + spec.files = %w[ + LICENSE.txt + lib/net/smtp.rb + net-smtp.gemspec + ] spec.bindir = "exe" spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ["lib"] diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index a97c0c33952d24..56820befc64b6a 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -168,7 +168,7 @@ class SMTPUnsupportedCommand < ProtocolError # user: 'Your Account', secret: 'Your Password', authtype: :cram_md5) # class SMTP < Protocol - VERSION = "0.2.1" + VERSION = "0.2.1-patch-ssl-context" Revision = %q$Revision$.split[1] @@ -191,12 +191,9 @@ class << self alias default_ssl_port default_tls_port end - def SMTP.default_ssl_context(verify_peer=true) + def SMTP.default_ssl_context(ssl_context_params = nil) context = OpenSSL::SSL::SSLContext.new - context.verify_mode = verify_peer ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE - store = OpenSSL::X509::Store.new - store.set_default_paths - context.cert_store = store + context.set_params(ssl_context_params ? ssl_context_params : {}) context end @@ -409,14 +406,14 @@ def debug_output=(arg) # # :call-seq: - # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil) { |smtp| ... } + # start(address, port = nil, helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } # start(address, port = nil, helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } # # Creates a new Net::SMTP object and connects to the server. # # This method is equivalent to: # - # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname) + # Net::SMTP.new(address, port).start(helo: helo_domain, user: account, secret: password, authtype: authtype, tls_verify: flag, tls_hostname: hostname, ssl_context_params: nil) # # === Example # @@ -450,6 +447,11 @@ def debug_output=(arg) # If the hostname in the server certificate is different from +address+, # it can be specified with +tls_hostname+. # + # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to + # +OpenSSL::SSL::SSLContext#set_params+ + # + # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. + # # === Errors # # This method may raise: @@ -465,14 +467,14 @@ def debug_output=(arg) # def SMTP.start(address, port = nil, *args, helo: nil, user: nil, secret: nil, password: nil, authtype: nil, - tls_verify: true, tls_hostname: nil, + tls_verify: true, tls_hostname: nil, ssl_context_params: nil, &block) raise ArgumentError, "wrong number of arguments (given #{args.size + 2}, expected 1..6)" if args.size > 4 helo ||= args[0] || 'localhost' user ||= args[1] secret ||= password || args[2] authtype ||= args[3] - new(address, port).start(helo: helo, user: user, secret: secret, authtype: authtype, tls_verify: tls_verify, tls_hostname: tls_hostname, &block) + new(address, port).start(helo: helo, user: user, secret: secret, authtype: authtype, tls_verify: tls_verify, tls_hostname: tls_hostname, ssl_context_params: ssl_context_params, &block) end # +true+ if the SMTP session has been started. @@ -482,7 +484,7 @@ def started? # # :call-seq: - # start(helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil) { |smtp| ... } + # start(helo: 'localhost', user: nil, secret: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) { |smtp| ... } # start(helo = 'localhost', user = nil, secret = nil, authtype = nil) { |smtp| ... } # # Opens a TCP connection and starts the SMTP session. @@ -501,6 +503,11 @@ def started? # If the hostname in the server certificate is different from +address+, # it can be specified with +tls_hostname+. # + # Additional SSLContext params can be added to +ssl_context_params+ hash argument and are passed to + # +OpenSSL::SSL::SSLContext#set_params+ + # + # +tls_verify: true+ is equivalent to +ssl_context_params: { verify_mode: OpenSSL::SSL::VERIFY_PEER }+. + # # === Block Usage # # When this methods is called with a block, the newly-started SMTP @@ -539,17 +546,23 @@ def started? # * IOError # def start(*args, helo: nil, - user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil) + user: nil, secret: nil, password: nil, authtype: nil, tls_verify: true, tls_hostname: nil, ssl_context_params: nil) raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..4)" if args.size > 4 helo ||= args[0] || 'localhost' user ||= args[1] secret ||= password || args[2] authtype ||= args[3] + ssl_context_params = ssl_context_params ? ssl_context_params : {} + if ssl_context_params.has_key?(:verify_mode) + tls_verify = ssl_context_params[:verify_mode] != OpenSSL::SSL::VERIFY_NONE + else + ssl_context_params[:verify_mode] = tls_verify ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE + end if @tls && @ssl_context_tls.nil? - @ssl_context_tls = SMTP.default_ssl_context(tls_verify) + @ssl_context_tls = SMTP.default_ssl_context(ssl_context_params) end if @starttls && @ssl_context_starttls.nil? - @ssl_context_starttls = SMTP.default_ssl_context(tls_verify) + @ssl_context_starttls = SMTP.default_ssl_context(ssl_context_params) end @tls_hostname = tls_hostname if block_given? @@ -575,7 +588,12 @@ def finish private def tcp_socket(address, port) - TCPSocket.open address, port + begin + Socket.tcp address, port, nil, nil, connect_timeout: @open_timeout + rescue Errno::ETIMEDOUT #raise Net:OpenTimeout instead for compatibility with previous versions + raise Net::OpenTimeout, "Timeout to open TCP connection to "\ + "#{address}:#{port} (exceeds #{@open_timeout} seconds)" + end end def do_start(helo_domain, user, secret, authtype) @@ -584,9 +602,7 @@ def do_start(helo_domain, user, secret, authtype) check_auth_method(authtype || DEFAULT_AUTH_TYPE) check_auth_args user, secret end - s = Timeout.timeout(@open_timeout, Net::OpenTimeout) do - tcp_socket(@address, @port) - end + s = tcp_socket(@address, @port) logging "Connection opened: #{@address}:#{@port}" @socket = new_internet_message_io(tls? ? tlsconnect(s, @ssl_context_tls) : s) check_response critical { recv_response() } diff --git a/lib/time.rb b/lib/time.rb index a40e2febc633c2..bd20a1a8e9b79b 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true +# shareable_constant_value: literal require 'date' diff --git a/lib/uri.rb b/lib/uri.rb index b544a5ee652e96..5e820f46c32207 100644 --- a/lib/uri.rb +++ b/lib/uri.rb @@ -70,7 +70,6 @@ # - URI::REGEXP - (in uri/common.rb) # - URI::REGEXP::PATTERN - (in uri/common.rb) # - URI::Util - (in uri/common.rb) -# - URI::Escape - (in uri/common.rb) # - URI::Error - (in uri/common.rb) # - URI::InvalidURIError - (in uri/common.rb) # - URI::InvalidComponentError - (in uri/common.rb) diff --git a/lib/uri/common.rb b/lib/uri/common.rb index d818592b742338..915c0e9519b993 100644 --- a/lib/uri/common.rb +++ b/lib/uri/common.rb @@ -321,7 +321,7 @@ def self.encode_www_form_component(str, enc=nil) # # See URI.encode_www_form_component, URI.decode_www_form. def self.decode_www_form_component(str, enc=Encoding::UTF_8) - raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/ =~ str + raise ArgumentError, "invalid %-encoding (#{str})" if /%(?!\h\h)/.match?(str) str.b.gsub(/\+|%\h\h/, TBLDECWWWCOMP_).force_encoding(enc) end diff --git a/lib/uri/generic.rb b/lib/uri/generic.rb index a4192c65571766..cfa0de6b74f061 100644 --- a/lib/uri/generic.rb +++ b/lib/uri/generic.rb @@ -643,7 +643,7 @@ def host=(v) # def hostname v = self.host - /\A\[(.*)\]\z/ =~ v ? $1 : v + v&.start_with?('[') && v.end_with?(']') ? v[1..-2] : v end # Sets the host part of the URI as the argument with brackets for IPv6 addresses. @@ -659,7 +659,7 @@ def hostname # it is wrapped with brackets. # def hostname=(v) - v = "[#{v}]" if /\A\[.*\]\z/ !~ v && /:/ =~ v + v = "[#{v}]" if !(v&.start_with?('[') && v&.end_with?(']')) && v&.index(':') self.host = v end @@ -1514,9 +1514,19 @@ def find_proxy(env=ENV) proxy_uri = env["CGI_#{name.upcase}"] end elsif name == 'http_proxy' - unless proxy_uri = env[name] - if proxy_uri = env[name.upcase] - warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + if RUBY_ENGINE == 'jruby' && p_addr = ENV_JAVA['http.proxyHost'] + p_port = ENV_JAVA['http.proxyPort'] + if p_user = ENV_JAVA['http.proxyUser'] + p_pass = ENV_JAVA['http.proxyPass'] + proxy_uri = "http://#{p_user}:#{p_pass}@#{p_addr}:#{p_port}" + else + proxy_uri = "http://#{p_addr}:#{p_port}" + end + else + unless proxy_uri = env[name] + if proxy_uri = env[name.upcase] + warn 'The environment variable HTTP_PROXY is discouraged. Use http_proxy.', uplevel: 1 + end end end else diff --git a/lib/uri/rfc2396_parser.rb b/lib/uri/rfc2396_parser.rb index 1c9ce177bc3404..76a8f99fd48ccd 100644 --- a/lib/uri/rfc2396_parser.rb +++ b/lib/uri/rfc2396_parser.rb @@ -322,8 +322,14 @@ def unescape(str, escaped = @regexp[:ESCAPED]) end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private diff --git a/lib/uri/rfc3986_parser.rb b/lib/uri/rfc3986_parser.rb index 49a594c17d49a0..3e07de4805c374 100644 --- a/lib/uri/rfc3986_parser.rb +++ b/lib/uri/rfc3986_parser.rb @@ -79,8 +79,14 @@ def join(*uris) # :nodoc: end @@to_s = Kernel.instance_method(:to_s) - def inspect - @@to_s.bind_call(self) + if @@to_s.respond_to?(:bind_call) + def inspect + @@to_s.bind_call(self) + end + else + def inspect + @@to_s.bind(self).call + end end private diff --git a/lib/uri/uri.gemspec b/lib/uri/uri.gemspec index c4a16f47eb6740..584a4faa112e95 100644 --- a/lib/uri/uri.gemspec +++ b/lib/uri/uri.gemspec @@ -15,6 +15,8 @@ Gem::Specification.new do |spec| spec.homepage = "https://github.com/ruby/uri" spec.licenses = ["Ruby", "BSD-2-Clause"] + spec.required_ruby_version = '>= 2.4' + spec.metadata["homepage_uri"] = spec.homepage spec.metadata["source_code_uri"] = spec.homepage diff --git a/memory_view.c b/memory_view.c index a1ab240656ae3b..ce2803717b4125 100644 --- a/memory_view.c +++ b/memory_view.c @@ -396,7 +396,7 @@ rb_memory_view_parse_item_format(const char *format, ssize_t max_alignment_size = 0; const char *p = format; - if (*p == '|') { // alginment specifier + if (*p == '|') { // alignment specifier alignment = true; ++format; ++p; diff --git a/misc/lldb_cruby.py b/misc/lldb_cruby.py index 83a28acbd7b66b..9e64957958d568 100755 --- a/misc/lldb_cruby.py +++ b/misc/lldb_cruby.py @@ -529,10 +529,68 @@ def dump_bits(target, result, page, object_address, end = "\n"): check_bits(page, "wb_unprotected_bits", bitmap_index, bitmap_bit, "U"), ), end=end, file=result) -def dump_page(debugger, command, result, internal_dict): +class HeapPageIter: + def __init__(self, page, target): + self.page = page + self.target = target + self.start = page.GetChildMemberWithName('start').GetValueAsUnsigned(); + self.num_slots = page.GetChildMemberWithName('total_slots').unsigned + self.counter = 0 + self.tRBasic = target.FindFirstType("struct RBasic") + self.tRValue = target.FindFirstType("struct RVALUE") + + def __iter__(self): + return self + + def __next__(self): + if self.counter < self.num_slots: + obj_addr_i = self.start + (self.counter * self.tRValue.GetByteSize()) + obj_addr = lldb.SBAddress(obj_addr_i, self.target) + slot_info = (self.counter, obj_addr_i, self.target.CreateValueFromAddress("object", obj_addr, self.tRBasic)) + self.counter += 1 + + return slot_info + else: + raise StopIteration + + +def dump_page_internal(page, target, process, thread, frame, result, debugger, highlight=None): if not ('RUBY_Qfalse' in globals()): lldb_init(debugger) + ruby_type_map = ruby_types(debugger) + + freelist = [] + fl_start = page.GetChildMemberWithName('freelist').GetValueAsUnsigned() + tRVALUE = target.FindFirstType("struct RVALUE") + + while fl_start > 0: + freelist.append(fl_start) + obj_addr = lldb.SBAddress(fl_start, target) + obj = target.CreateValueFromAddress("object", obj_addr, tRVALUE) + fl_start = obj.GetChildMemberWithName("as").GetChildMemberWithName("free").GetChildMemberWithName("next").GetValueAsUnsigned() + + for (page_index, obj_addr, obj) in HeapPageIter(page, target): + dump_bits(target, result, page, obj_addr, end= " ") + flags = obj.GetChildMemberWithName('flags').GetValueAsUnsigned() + flType = flags & RUBY_T_MASK + + flidx = ' ' + if flType == RUBY_T_NONE: + try: + flidx = "%3d" % freelist.index(obj_addr) + except ValueError: + flidx = ' ' + + result_str = "%s idx: [%3d] freelist_idx: {%s} Addr: %0#x (flags: %0#x)" % (rb_type(flags, ruby_type_map), page_index, flidx, obj_addr, flags) + + if highlight == obj_addr: + result_str = ' '.join([result_str, "<<<<<"]) + + print(result_str, file=result) + + +def dump_page(debugger, command, result, internal_dict): target = debugger.GetSelectedTarget() process = target.GetProcess() thread = process.GetSelectedThread() @@ -542,21 +600,23 @@ def dump_page(debugger, command, result, internal_dict): page = frame.EvaluateExpression(command) page = page.Cast(tHeapPageP) - tRBasic = target.FindFirstType("struct RBasic") - tRValue = target.FindFirstType("struct RVALUE") + dump_page_internal(page, target, process, thread, frame, result, debugger) - obj_address = page.GetChildMemberWithName('start').GetValueAsUnsigned(); - num_slots = page.GetChildMemberWithName('total_slots').unsigned - ruby_type_map = ruby_types(debugger) +def dump_page_rvalue(debugger, command, result, internal_dict): + target = debugger.GetSelectedTarget() + process = target.GetProcess() + thread = process.GetSelectedThread() + frame = thread.GetSelectedFrame() + + val = frame.EvaluateExpression(command) + page = get_page(lldb, target, val) + page_type = target.FindFirstType("struct heap_page").GetPointerType() + page.Cast(page_type) + + dump_page_internal(page, target, process, thread, frame, result, debugger, highlight=val.GetValueAsUnsigned()) + - for j in range(0, num_slots): - offset = obj_address + (j * tRValue.GetByteSize()) - obj_addr = lldb.SBAddress(offset, target) - p = target.CreateValueFromAddress("object", obj_addr, tRBasic) - dump_bits(target, result, page, offset, end = " ") - flags = p.GetChildMemberWithName('flags').GetValueAsUnsigned() - print("%s [%3d]: Addr: %0#x (flags: %0#x)" % (rb_type(flags, ruby_type_map), j, offset, flags), file=result) def rb_type(flags, ruby_types): flType = flags & RUBY_T_MASK @@ -588,6 +648,7 @@ def __lldb_init_module(debugger, internal_dict): debugger.HandleCommand("command script add -f lldb_cruby.heap_page_body heap_page_body") debugger.HandleCommand("command script add -f lldb_cruby.rb_backtrace rbbt") debugger.HandleCommand("command script add -f lldb_cruby.dump_page dump_page") + debugger.HandleCommand("command script add -f lldb_cruby.dump_page_rvalue dump_page_rvalue") lldb_init(debugger) print("lldb scripts for ruby has been installed.") diff --git a/parse.y b/parse.y index 909a4ec3b18287..6909e9c777e925 100644 --- a/parse.y +++ b/parse.y @@ -11072,6 +11072,7 @@ const_decl_path(struct parser_params *p, NODE **dest) path = rb_fstring(path); } *dest = n = NEW_LIT(path, loc); + RB_OBJ_WRITTEN(p->ast, Qnil, n->nd_lit); } return n; } @@ -11159,7 +11160,9 @@ shareable_literal_constant(struct parser_params *p, enum shareability shareable, case NODE_ZLIST: lit = rb_ary_new(); OBJ_FREEZE_RAW(lit); - return NEW_LIT(lit, loc); + NODE *n = NEW_LIT(lit, loc); + RB_OBJ_WRITTEN(p->ast, Qnil, n->nd_lit); + return n; case NODE_LIST: lit = rb_ary_new(); @@ -11245,6 +11248,7 @@ shareable_literal_constant(struct parser_params *p, enum shareability shareable, } else { value = NEW_LIT(rb_ractor_make_shareable(lit), loc); + RB_OBJ_WRITTEN(p->ast, Qnil, value->nd_lit); } return value; diff --git a/ractor.c b/ractor.c index e3cd602d59928d..cb86f667510550 100644 --- a/ractor.c +++ b/ractor.c @@ -559,7 +559,7 @@ wait_status_str(enum ractor_wait_status wait_status) case wait_taking|wait_yielding: return "taking|yielding"; case wait_receiving|wait_taking|wait_yielding: return "receiving|taking|yielding"; } - rb_bug("unrechable"); + rb_bug("unreachable"); } static const char * @@ -574,7 +574,7 @@ wakeup_status_str(enum ractor_wakeup_status wakeup_status) case wakeup_by_interrupt: return "by_interrupt"; case wakeup_by_retry: return "by_retry"; } - rb_bug("unrechable"); + rb_bug("unreachable"); } #endif // USE_RUBY_DEBUG_LOG diff --git a/ractor.rb b/ractor.rb index 2238e090c9725f..de7c9a93a7471e 100644 --- a/ractor.rb +++ b/ractor.rb @@ -147,7 +147,7 @@ # on a moved object. # # Besides frozen objects, there are shareable objects. Class and Module objects are shareable so -# the Class/Module definitons are shared between ractors. Ractor objects are also shareable objects. +# the Class/Module definitions are shared between ractors. Ractor objects are also shareable objects. # All operations for the shareable mutable objects are thread-safe, so the thread-safety property # will be kept. We can not define mutable shareable objects in Ruby, but C extensions can introduce them. # @@ -223,7 +223,7 @@ # # == Reference # -# See {Ractor desgin doc}[rdoc-ref:doc/ractor.md] for more details. +# See {Ractor design doc}[rdoc-ref:doc/ractor.md] for more details. # class Ractor # @@ -457,7 +457,7 @@ class << self # # If the block returns a truthy value, the message will be removed from the incoming queue # and returned. - # Otherwise, the messsage remains in the incoming queue and the following received + # Otherwise, the message remains in the incoming queue and the following received # messages are checked by the given block. # # If there are no messages left in the incoming queue, the method will diff --git a/spec/ruby/core/file/shared/read.rb b/spec/ruby/core/file/shared/read.rb index a2d479966d7a27..f2322352984c5b 100644 --- a/spec/ruby/core/file/shared/read.rb +++ b/spec/ruby/core/file/shared/read.rb @@ -1,13 +1,13 @@ require_relative '../../dir/fixtures/common' describe :file_read_directory, shared: true do - platform_is :darwin, :linux, :openbsd, :windows do + platform_is :darwin, :linux, :freebsd, :openbsd, :windows do it "raises an Errno::EISDIR when passed a path that is a directory" do -> { @object.send(@method, ".") }.should raise_error(Errno::EISDIR) end end - platform_is :freebsd, :netbsd do + platform_is :netbsd do it "does not raises any exception when passed a path that is a directory" do -> { @object.send(@method, ".") }.should_not raise_error end diff --git a/test/benchmark/test_benchmark.rb b/test/benchmark/test_benchmark.rb index 3030bc5dec012f..ddd4a591bb378a 100644 --- a/test/benchmark/test_benchmark.rb +++ b/test/benchmark/test_benchmark.rb @@ -71,7 +71,7 @@ def test_benchmark_does_not_print_any_space_if_the_given_caption_is_empty BENCH end - def test_benchmark_makes_extra_calcultations_with_an_Array_at_the_end_of_the_benchmark_and_show_the_result + def test_benchmark_makes_extra_calculations_with_an_Array_at_the_end_of_the_benchmark_and_show_the_result assert_equal(BENCHMARK_OUTPUT_WITH_TOTAL_AVG, capture_bench_output(:benchmark, Benchmark::CAPTION, 7, diff --git a/test/cgi/test_cgi_util.rb b/test/cgi/test_cgi_util.rb index b7bb7b8eae7bde..6ce8b42c20f5fb 100644 --- a/test/cgi/test_cgi_util.rb +++ b/test/cgi/test_cgi_util.rb @@ -36,9 +36,7 @@ def test_cgi_escape_with_unreserved_characters end def test_cgi_escape_with_invalid_byte_sequence - assert_nothing_raised(ArgumentError) do - assert_equal('%C0%3C%3C', CGI.escape("\xC0\<\<".dup.force_encoding("UTF-8"))) - end + assert_equal('%C0%3C%3C', CGI.escape("\xC0\<\<".dup.force_encoding("UTF-8"))) end def test_cgi_escape_preserve_encoding @@ -191,3 +189,32 @@ def test_cgi_unescapeElement assert_equal('<BR>', unescape_element(escapeHTML('
'), ["A", "IMG"])) end end + +class CGIUtilPureRubyTest < Test::Unit::TestCase + def setup + CGI::Escape.module_eval do + alias _escapeHTML escapeHTML + remove_method :escapeHTML + alias _unescapeHTML unescapeHTML + remove_method :unescapeHTML + end + end + + def teardown + CGI::Escape.module_eval do + alias escapeHTML _escapeHTML + remove_method :_escapeHTML + alias unescapeHTML _unescapeHTML + remove_method :_unescapeHTML + end + end + + def test_cgi_escapeHTML_with_invalid_byte_sequence + assert_equal("<\xA4??>", CGI.escapeHTML(%[<\xA4??>])) + end + + def test_cgi_unescapeHTML_with_invalid_byte_sequence + input = "\xFF&" + assert_equal(input, CGI.unescapeHTML(input)) + end +end diff --git a/test/irb/test_color.rb b/test/irb/test_color.rb index a28ae06117bbc7..ba99b1253f4e02 100644 --- a/test/irb/test_color.rb +++ b/test/irb/test_color.rb @@ -17,6 +17,27 @@ class TestColor < Test::Unit::TestCase MAGENTA = "\e[35m" CYAN = "\e[36m" + def test_colorize + text = "text" + { + [:BOLD] => "#{BOLD}#{text}#{CLEAR}", + [:UNDERLINE] => "#{UNDERLINE}#{text}#{CLEAR}", + [:REVERSE] => "#{REVERSE}#{text}#{CLEAR}", + [:RED] => "#{RED}#{text}#{CLEAR}", + [:GREEN] => "#{GREEN}#{text}#{CLEAR}", + [:YELLOW] => "#{YELLOW}#{text}#{CLEAR}", + [:BLUE] => "#{BLUE}#{text}#{CLEAR}", + [:MAGENTA] => "#{MAGENTA}#{text}#{CLEAR}", + [:CYAN] => "#{CYAN}#{text}#{CLEAR}", + }.each do |seq, result| + assert_equal_with_term(result, text, seq: seq) + + assert_equal_with_term(text, text, seq: seq, tty: false) + assert_equal_with_term(text, text, seq: seq, colorable: false) + assert_equal_with_term(result, text, seq: seq, tty: false, colorable: true) + end + end + def test_colorize_code # Common behaviors. Warn parser error, but do not warn compile error. tests = { @@ -105,14 +126,21 @@ def test_colorize_code tests.each do |code, result| if colorize_code_supported? - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: IRB::Color.colorize_code(#{code.dump}, complete: true)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: true) + assert_equal_with_term(result, code, complete: false) + + assert_equal_with_term(code, code, complete: true, tty: false) + assert_equal_with_term(code, code, complete: false, tty: false) - actual = with_term { IRB::Color.colorize_code(code, complete: false) } - assert_equal(result, actual, "Case: IRB::Color.colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(code, code, complete: true, colorable: false) + + assert_equal_with_term(code, code, complete: false, colorable: false) + + assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) + + assert_equal_with_term(result, code, complete: false, tty: false, colorable: true) else - actual = with_term { IRB::Color.colorize_code(code) } - assert_equal(code, actual) + assert_equal_with_term(code, code) end end end @@ -127,8 +155,13 @@ def test_colorize_code_complete_true "'foo' + 'bar" => "#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}#{RED}#{BOLD}'#{CLEAR} + #{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}bar#{CLEAR}", "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}#{REVERSE}foo#{CLEAR}", }.each do |code, result| - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: true)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: true) + + assert_equal_with_term(code, code, complete: true, tty: false) + + assert_equal_with_term(code, code, complete: true, colorable: false) + + assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) end end @@ -139,16 +172,25 @@ def test_colorize_code_complete_false "('foo" => "(#{RED}#{BOLD}'#{CLEAR}#{RED}foo#{CLEAR}", }.each do |code, result| if colorize_code_supported? - actual = with_term { IRB::Color.colorize_code(code, complete: false) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: false) + + assert_equal_with_term(code, code, complete: false, tty: false) + + assert_equal_with_term(code, code, complete: false, colorable: false) + + assert_equal_with_term(result, code, complete: false, tty: false, colorable: true) unless complete_option_supported? - actual = with_term { IRB::Color.colorize_code(code, complete: true) } - assert_equal(result, actual, "Case: colorize_code(#{code.dump}, complete: false)\nResult: #{humanized_literal(actual)}") + assert_equal_with_term(result, code, complete: true) + + assert_equal_with_term(code, code, complete: true, tty: false) + + assert_equal_with_term(code, code, complete: true, colorable: false) + + assert_equal_with_term(result, code, complete: true, tty: false, colorable: true) end else - actual = with_term { IRB::Color.colorize_code(code) } - assert_equal(code, actual) + assert_equal_with_term(code, code) end end end @@ -185,10 +227,10 @@ def complete_option_supported? Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('2.7.0') end - def with_term + def with_term(tty: true) stdout = $stdout io = StringIO.new - def io.tty?; true; end + def io.tty?; true; end if tty $stdout = io env = ENV.to_h.dup @@ -200,6 +242,23 @@ def io.tty?; true; end ENV.replace(env) if env end + def assert_equal_with_term(result, code, seq: nil, tty: true, **opts) + actual = with_term(tty: tty) do + if seq + IRB::Color.colorize(code, seq, **opts) + else + IRB::Color.colorize_code(code, **opts) + end + end + message = -> { + args = [code.dump] + args << seq.inspect if seq + opts.each {|kwd, val| args << "#{kwd}: #{val}"} + "Case: colorize#{seq ? "" : "_code"}(#{args.join(', ')})\nResult: #{humanized_literal(actual)}" + } + assert_equal(result, actual, message) + end + def humanized_literal(str) str .gsub(CLEAR, '@@@{CLEAR}') diff --git a/test/irb/test_init.rb b/test/irb/test_init.rb index 2c50b5da3aef07..d57f0752bb7ffa 100644 --- a/test/irb/test_init.rb +++ b/test/irb/test_init.rb @@ -5,6 +5,19 @@ module TestIRB class TestInit < Test::Unit::TestCase + def setup + # IRBRC is for RVM... + @backup_env = %w[HOME XDG_CONFIG_HOME IRBRC].each_with_object({}) do |env, hash| + hash[env] = ENV.delete(env) + end + ENV["HOME"] = @tmpdir = Dir.mktmpdir("test_irb_init_#{$$}") + end + + def teardown + ENV.update(@backup_env) + FileUtils.rm_rf(@tmpdir) + end + def test_setup_with_argv_preserves_global_argv argv = ["foo", "bar"] with_argv(argv) do @@ -20,12 +33,8 @@ def test_setup_with_minimum_argv_does_not_change_dollar0 end def test_rc_file - backup_irbrc = ENV.delete("IRBRC") # This is for RVM... - backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - backup_home = ENV["HOME"] - Dir.mktmpdir("test_irb_init_#{$$}") do |tmpdir| - ENV["HOME"] = tmpdir - + tmpdir = @tmpdir + Dir.chdir(tmpdir) do IRB.conf[:RC_NAME_GENERATOR] = nil assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) @@ -34,19 +43,11 @@ def test_rc_file assert_equal(tmpdir+"/.irb#{IRB::IRBRC_EXT}", IRB.rc_file) assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) end - ensure - ENV["HOME"] = backup_home - ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home - ENV["IRBRC"] = backup_irbrc end def test_rc_file_in_subdir - backup_irbrc = ENV.delete("IRBRC") # This is for RVM... - backup_xdg_config_home = ENV.delete("XDG_CONFIG_HOME") - backup_home = ENV["HOME"] - Dir.mktmpdir("test_irb_init_#{$$}") do |tmpdir| - ENV["HOME"] = tmpdir - + tmpdir = @tmpdir + Dir.chdir(tmpdir) do FileUtils.mkdir_p("#{tmpdir}/mydir") Dir.chdir("#{tmpdir}/mydir") do IRB.conf[:RC_NAME_GENERATOR] = nil @@ -58,10 +59,6 @@ def test_rc_file_in_subdir assert_equal(tmpdir+"/.irb_history", IRB.rc_file("_history")) end end - ensure - ENV["HOME"] = backup_home - ENV["XDG_CONFIG_HOME"] = backup_xdg_config_home - ENV["IRBRC"] = backup_irbrc end def test_recovery_sigint diff --git a/test/matrix/test_matrix.rb b/test/matrix/test_matrix.rb index 53887729eff549..25ad7b6b95466d 100644 --- a/test/matrix/test_matrix.rb +++ b/test/matrix/test_matrix.rb @@ -828,7 +828,7 @@ def test_ractor end.take assert_same obj1, obj2 RUBY - end + end if defined?(Ractor) def test_rotate_with_symbol assert_equal(Matrix[[4, 1], [5, 2], [6, 3]], @m1.rotate_entries) diff --git a/test/monitor/test_monitor.rb b/test/monitor/test_monitor.rb index 734b639d4cf731..0f17d58f712113 100644 --- a/test/monitor/test_monitor.rb +++ b/test/monitor/test_monitor.rb @@ -10,6 +10,13 @@ def setup @monitor = Monitor.new end + def test_enter_in_different_fibers + @monitor.enter + Fiber.new { + assert_equal false, @monitor.try_enter + }.resume + end + def test_enter ary = [] queue = Queue.new diff --git a/test/net/ftp/test_ftp.rb b/test/net/ftp/test_ftp.rb index 6cd32add9018f3..ee64290f2b292a 100644 --- a/test/net/ftp/test_ftp.rb +++ b/test/net/ftp/test_ftp.rb @@ -882,6 +882,40 @@ def test_getbinaryfile_with_filename_and_block end end + def test_getbinaryfile_error + commands = [] + server = create_ftp_server { |sock| + sock.print("220 (test_ftp).\r\n") + commands.push(sock.gets) + sock.print("331 Please specify the password.\r\n") + commands.push(sock.gets) + sock.print("230 Login successful.\r\n") + commands.push(sock.gets) + sock.print("200 Switching to Binary mode.\r\n") + line = sock.gets + commands.push(line) + sock.print("450 No Dice\r\n") + } + begin + begin + ftp = Net::FTP.new + ftp.passive = true + ftp.read_timeout *= 5 if defined?(RubyVM::JIT) && RubyVM::JIT.enabled? # for --jit-wait + ftp.connect(SERVER_ADDR, server.port) + ftp.login + assert_match(/\AUSER /, commands.shift) + assert_match(/\APASS /, commands.shift) + assert_equal("TYPE I\r\n", commands.shift) + assert_raise(Net::FTPTempError) {ftp.getbinaryfile("foo", nil)} + assert_match(/\A(PASV|EPSV)\r\n/, commands.shift) + ensure + ftp.close if ftp + end + ensure + server.close + end + end + def test_storbinary commands = [] binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 @@ -1935,7 +1969,7 @@ def test_active_private_data_connection assert_equal(nil, commands.shift) # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. # See https://github.com/openssl/openssl/pull/5967 for details. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ + if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/ assert_equal(true, session_reused_for_data_connection) end ensure @@ -2019,7 +2053,7 @@ def test_passive_private_data_connection assert_equal("RETR foo\r\n", commands.shift) assert_equal(nil, commands.shift) # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ + if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h|LibreSSL/ assert_equal(true, session_reused_for_data_connection) end ensure @@ -2393,7 +2427,7 @@ def test_putbinaryfile_command_injection File.binwrite("./|echo hello", binary_data) begin ftp = Net::FTP.new - ftp.read_timeout = RubyVM::JIT.enabled? ? 300 : 0.2 # use large timeout for --jit-wait + ftp.read_timeout = defined?(RubyVM::JIT) && RubyVM::JIT.enabled? ? 300 : 0.2 # use large timeout for --jit-wait ftp.connect(SERVER_ADDR, server.port) ftp.login assert_match(/\AUSER /, commands.shift) @@ -2474,6 +2508,23 @@ def test_puttextfile_command_injection end end + def test_time_parser + s = "20371231000000" + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0), + Net::FTP::TIME_PARSER[s]) + s = "20371231000000.123456" + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0, 123456), + Net::FTP::TIME_PARSER[s]) + s = "20371231000000." + "9" * 999999 + assert_equal(Time.utc(2037, 12, 31, 0, 0, 0, + 99999999999999999r / 100000000000), + Net::FTP::TIME_PARSER[s]) + e = assert_raise(Net::FTPProtoError) { + Net::FTP::TIME_PARSER["x" * 999999] + } + assert_equal("invalid time-val: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx...", e.message) + end + private def create_ftp_server(sleep_time = nil) diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb index 8b924b524e9d4f..4fb9f744fcf102 100644 --- a/test/net/imap/test_imap.rb +++ b/test/net/imap/test_imap.rb @@ -578,23 +578,23 @@ def test_send_invalid_number begin imap = Net::IMAP.new(server_addr, :port => port) assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", -1) + imap.__send__(:send_command, "TEST", -1) end - imap.send(:send_command, "TEST", 0) - imap.send(:send_command, "TEST", 4294967295) + imap.__send__(:send_command, "TEST", 0) + imap.__send__(:send_command, "TEST", 4294967295) assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", 4294967296) + imap.__send__(:send_command, "TEST", 4294967296) end assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(-1)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(-1)) end assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(0)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(0)) end - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(1)) - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967295)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(1)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967295)) assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967296)) + imap.__send__(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967296)) end imap.logout ensure @@ -628,7 +628,7 @@ def test_send_literal end begin imap = Net::IMAP.new(server_addr, :port => port) - imap.send(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b]) + imap.__send__(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b]) assert_equal(2, requests.length) assert_equal("RUBY0001 TEST ({4}\r\n", requests[0]) assert_equal("\xDE\xAD\xBE\xEF".b, literal) @@ -753,6 +753,55 @@ def test_append_fail end end + def test_id + server = create_tcp_server + port = server.addr[1] + requests = Queue.new + server_id = {"name" => "test server", "version" => "v0.1.0"} + server_id_str = '("name" "test server" "version" "v0.1.0")' + @threads << Thread.start do + sock = server.accept + begin + sock.print("* OK test server\r\n") + requests.push(sock.gets) + # RFC 2971 very clearly states (in section 3.2): + # "a server MUST send a tagged ID response to an ID command." + # And yet... some servers report ID capability but won't the response. + sock.print("RUBY0001 OK ID completed\r\n") + requests.push(sock.gets) + sock.print("* ID #{server_id_str}\r\n") + sock.print("RUBY0002 OK ID completed\r\n") + requests.push(sock.gets) + sock.print("* ID #{server_id_str}\r\n") + sock.print("RUBY0003 OK ID completed\r\n") + requests.push(sock.gets) + sock.print("* BYE terminating connection\r\n") + sock.print("RUBY0004 OK LOGOUT completed\r\n") + ensure + sock.close + server.close + end + end + + begin + imap = Net::IMAP.new(server_addr, :port => port) + resp = imap.id + assert_equal(nil, resp) + assert_equal("RUBY0001 ID NIL\r\n", requests.pop) + resp = imap.id({}) + assert_equal(server_id, resp) + assert_equal("RUBY0002 ID ()\r\n", requests.pop) + resp = imap.id("name" => "test client", "version" => "latest") + assert_equal(server_id, resp) + assert_equal("RUBY0003 ID (\"name\" \"test client\" \"version\" \"latest\")\r\n", + requests.pop) + imap.logout + assert_equal("RUBY0004 LOGOUT\r\n", requests.pop) + ensure + imap.disconnect if imap + end + end + private def imaps_test diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb index 4e470459c944e5..5b519edeff1b14 100644 --- a/test/net/imap/test_imap_response_parser.rb +++ b/test/net/imap/test_imap_response_parser.rb @@ -234,6 +234,27 @@ def test_capability response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN \r\n") assert_equal("CAPABILITY", response.name) assert_equal("AUTH=PLAIN", response.data.last) + response = parser.parse("* OK [CAPABILITY IMAP4rev1 SASL-IR 1234 NIL THIS+THAT + AUTH=PLAIN ID] IMAP4rev1 Hello\r\n") + assert_equal("OK", response.name) + assert_equal("IMAP4rev1 Hello", response.data.text) + code = response.data.code + assert_equal("CAPABILITY", code.name) + assert_equal( + ["IMAP4REV1", "SASL-IR", "1234", "NIL", "THIS+THAT", "+", "AUTH=PLAIN", "ID"], + code.data + ) + end + + def test_id + parser = Net::IMAP::ResponseParser.new + response = parser.parse("* ID NIL\r\n") + assert_equal("ID", response.name) + assert_equal(nil, response.data) + response = parser.parse("* ID (\"name\" \"GImap\" \"vendor\" \"Google, Inc.\" \"support-url\" NIL)\r\n") + assert_equal("ID", response.name) + assert_equal("GImap", response.data["name"]) + assert_equal("Google, Inc.", response.data["vendor"]) + assert_equal(nil, response.data.fetch("support-url")) end def test_mixed_boundary @@ -301,6 +322,22 @@ def test_msg_att_modseq_data assert_equal(12345, response.data.attr["MODSEQ"]) end + def test_msg_rfc3501_response_text_with_T_LBRA + parser = Net::IMAP::ResponseParser.new + response = parser.parse("RUBY0004 OK [READ-WRITE] [Gmail]/Sent Mail selected. (Success)\r\n") + assert_equal("RUBY0004", response.tag) + assert_equal("READ-WRITE", response.data.code.name) + assert_equal("[Gmail]/Sent Mail selected. (Success)", response.data.text) + end + + def test_msg_rfc3501_response_text_with_BADCHARSET_astrings + parser = Net::IMAP::ResponseParser.new + response = parser.parse("t BAD [BADCHARSET (US-ASCII \"[astring with brackets]\")] unsupported charset foo.\r\n") + assert_equal("t", response.tag) + assert_equal("unsupported charset foo.", response.data.text) + assert_equal("BADCHARSET", response.data.code.name) + end + def test_continuation_request_without_response_text parser = Net::IMAP::ResponseParser.new response = parser.parse("+\r\n") @@ -308,4 +345,45 @@ def test_continuation_request_without_response_text assert_equal(nil, response.data.code) assert_equal("", response.data.text) end + + def test_ignored_response + parser = Net::IMAP::ResponseParser.new + response = nil + assert_nothing_raised do + response = parser.parse("* NOOP\r\n") + end + assert_instance_of(Net::IMAP::IgnoredResponse, response) + end + + def test_namespace + parser = Net::IMAP::ResponseParser.new + # RFC2342 Example 5.1 + response = parser.parse(%Q{* NAMESPACE (("" "/")) NIL NIL\r\n}) + assert_equal("NAMESPACE", response.name) + assert_equal([Net::IMAP::Namespace.new("", "/", {})], response.data.personal) + assert_equal([], response.data.other) + assert_equal([], response.data.shared) + # RFC2342 Example 5.4 + response = parser.parse(%Q{* NAMESPACE (("" "/")) (("~" "/")) (("#shared/" "/")} + + %Q{ ("#public/" "/") ("#ftp/" "/") ("#news." "."))\r\n}) + assert_equal("NAMESPACE", response.name) + assert_equal([Net::IMAP::Namespace.new("", "/", {})], response.data.personal) + assert_equal([Net::IMAP::Namespace.new("~", "/", {})], response.data.other) + assert_equal( + [ + Net::IMAP::Namespace.new("#shared/", "/", {}), + Net::IMAP::Namespace.new("#public/", "/", {}), + Net::IMAP::Namespace.new("#ftp/", "/", {}), + Net::IMAP::Namespace.new("#news.", ".", {}), + ], + response.data.shared + ) + # RFC2342 Example 5.6 + response = parser.parse(%Q{* NAMESPACE (("" "/") ("#mh/" "/" "X-PARAM" ("FLAG1" "FLAG2"))) NIL NIL\r\n}) + assert_equal("NAMESPACE", response.name) + namespace = response.data.personal.last + assert_equal("#mh/", namespace.prefix) + assert_equal("/", namespace.delim) + assert_equal({"X-PARAM" => ["FLAG1", "FLAG2"]}, namespace.extensions) + end end diff --git a/test/objspace/test_objspace.rb b/test/objspace/test_objspace.rb index 97f3d60882361a..58e086c0a01681 100644 --- a/test/objspace/test_objspace.rb +++ b/test/objspace/test_objspace.rb @@ -168,7 +168,7 @@ def test_trace_object_allocations_stop_first assert_separately([], "#{<<~"begin;"}\n#{<<~'end;'}") begin; require "objspace" - # Make sure stoping before the tracepoints are initialized doesn't raise. See [Bug #17020] + # Make sure stopping before the tracepoints are initialized doesn't raise. See [Bug #17020] ObjectSpace.trace_object_allocations_stop end; end diff --git a/test/open-uri/test_open-uri.rb b/test/open-uri/test_open-uri.rb index 9a52e7a2872151..51e8e503a00b33 100644 --- a/test/open-uri/test_open-uri.rb +++ b/test/open-uri/test_open-uri.rb @@ -581,7 +581,7 @@ def test_progress_chunked ) {|f| assert_equal(1, length.length) assert_equal(nil, length[0]) - assert(progress.length>1,"maybe test is worng") + assert(progress.length>1,"maybe test is wrong") assert(progress.sort == progress,"monotone increasing expected but was\n#{progress.inspect}") assert_equal(content.length, progress[-1]) assert_equal(content, f.read) diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 4a9863cc54a3a8..fb50f581fea93e 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -2741,9 +2741,9 @@ def test_zip_with_enumerator step = 0.step e = Enumerator.produce { step.next } a = %w(a b c) - assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e)) - assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e)) - assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e)) + assert_equal([["a", 0], ["b", 1], ["c", 2]], a.zip(e), bug17814) + assert_equal([["a", 3], ["b", 4], ["c", 5]], a.zip(e), bug17814) + assert_equal([["a", 6], ["b", 7], ["c", 8]], a.zip(e), bug17814) end def test_transpose diff --git a/test/ruby/test_assignment.rb b/test/ruby/test_assignment.rb index 5a6ec97e67244d..41e8bffe82f36d 100644 --- a/test/ruby/test_assignment.rb +++ b/test/ruby/test_assignment.rb @@ -81,6 +81,64 @@ def test_massign_simple a,b,*c = [*[1,2]]; assert_equal([1,2,[]], [a,b,c]) end + def test_massign_order + order = [] + define_singleton_method(:x1){order << :x1; self} + define_singleton_method(:y1){order << :y1; self} + define_singleton_method(:z=){|x| order << [:z=, x]} + define_singleton_method(:x2){order << :x2; self} + define_singleton_method(:x3){order << :x3; self} + define_singleton_method(:x4){order << :x4; self} + define_singleton_method(:x5=){|x| order << [:x5=, x]; self} + define_singleton_method(:[]=){|*args| order << [:[]=, *args]} + define_singleton_method(:r1){order << :r1; :r1} + define_singleton_method(:r2){order << :r2; :r2} + + x1.y1.z, x2[1, 2, 3], self[4] = r1, 6, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, 6], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], self[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4] = r1, 6, 7, r2 + assert_equal([:x1, :y1, :x2, :x3, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2]], order) + order.clear + + x1.y1.z, *x2[1, 2, 3], x3[4], x4.x5 = r1, 6, 7, r2, 8 + assert_equal([:x1, :y1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x2.x5), _a = [r1, r2], 7 + assert_equal([:x1, :y1, :x2, :r1, :r2, [:z=, :r1], [:x5=, :r2]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3] = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7, :r2, 8]]], order) + order.clear + + *x2[1, 2, 3], (x3[4], x4.x5) = 6, 7, [r2, 8] + assert_equal([:x2, :x3, :x4, :r2, [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], x3[4], x4.x5 = [r1, 5], 6, 7, r2, 8 + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + (x1.y1.z, x1.x5), *x2[1, 2, 3], (x3[4], x4.x5) = [r1, 5], 6, 7, [r2, 8] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, x1.x5), _a), *x2[1, 2, 3], ((x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, 5], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, :r2], [:x5=, 8]], order) + order.clear + + ((x1.y1.z, *x1.x5), _a), *x2[1, 2, 3], ((*x3[4], x4.x5), _b) = [[r1, 5], 10], 6, 7, [[r2, 8], 11] + assert_equal([:x1, :y1, :x1, :x2, :x3, :x4, :r1, :r2, [:z=, :r1], [:x5=, [5]], [:[]=, 1, 2, 3, [6, 7]], [:[]=, 4, [:r2]], [:x5=, 8]], order) + order.clear + end + def test_massign_splat a,b,*c = *[]; assert_equal([nil,nil,[]], [a,b,c]) a,b,*c = *[1]; assert_equal([1,nil,[]], [a,b,c]) diff --git a/test/ruby/test_econv.rb b/test/ruby/test_econv.rb index a1ab1c8df6b8fd..1aad0de3474d7d 100644 --- a/test/ruby/test_econv.rb +++ b/test/ruby/test_econv.rb @@ -922,7 +922,7 @@ def test_newline_option end newlines.each do |nl| opts = {newline: :universal, nl => true} - ec2 = assert_warning(/:newline option preceds/, opts.inspect) do + ec2 = assert_warning(/:newline option precedes/, opts.inspect) do Encoding::Converter.new("", "", **opts) end assert_equal(ec1, ec2) diff --git a/test/ruby/test_exception.rb b/test/ruby/test_exception.rb index 087bcda5acbfca..af29163ce3dd7f 100644 --- a/test/ruby/test_exception.rb +++ b/test/ruby/test_exception.rb @@ -78,6 +78,66 @@ def test_exception_ensure_2 # just duplication? assert(!bad) end + def test_exception_in_ensure_with_next + string = "[ruby-core:82936] [Bug #13930]" + assert_raise_with_message(RuntimeError, string) do + lambda do + next + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + + assert_raise_with_message(RuntimeError, string) do + flag = true + while flag + flag = false + begin + next + rescue + assert(false) + ensure + raise string + end + end + end + end + + def test_exception_in_ensure_with_redo + string = "[ruby-core:82936] [Bug #13930]" + + assert_raise_with_message(RuntimeError, string) do + i = 0 + lambda do + i += 1 + redo if i < 2 + rescue + assert(false) + ensure + raise string + end.call + assert(false) + end + end + + def test_exception_in_ensure_with_return + @string = "[ruby-core:97104] [Bug #16618]" + def self.meow + return if true # This if modifier suppresses "warning: statement not reached" + assert(false) + rescue + assert(false) + ensure + raise @string + end + assert_raise_with_message(RuntimeError, @string) do + meow + end + end + def test_errinfo_in_debug bug9568 = EnvUtil.labeled_class("[ruby-core:61091] [Bug #9568]", RuntimeError) do def to_s diff --git a/test/ruby/test_fiber.rb b/test/ruby/test_fiber.rb index b2518c32f06b75..26f8268140a6b1 100644 --- a/test/ruby/test_fiber.rb +++ b/test/ruby/test_fiber.rb @@ -392,7 +392,7 @@ def test_fork_from_fiber xpid = fork do # enough to trigger GC on old root fiber count = 10000 - count = 1000 if /openbsd/i =~ RUBY_PLATFORM + count = 1000 if /solaris|openbsd/i =~ RUBY_PLATFORM count.times do Fiber.new {}.transfer Fiber.new { Fiber.yield } diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index 1f75a34cacede7..f10946bd390fe9 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -494,4 +494,11 @@ def test_object_ids_never_repeat b = 1000.times.map { Object.new.object_id } assert_empty(a & b) end + + def test_ast_node_buffer + # https://github.com/ruby/ruby/pull/4416 + Module.new.class_eval do + eval((["# shareable_constant_value: literal"] + (0..100000).map {|i| "M#{ i } = {}" }).join("\n")) + end + end end diff --git a/test/ruby/test_jit.rb b/test/ruby/test_jit.rb index d7d4ee159c4cd8..55922b2d8c6dfe 100644 --- a/test/ruby/test_jit.rb +++ b/test/ruby/test_jit.rb @@ -318,10 +318,6 @@ def test_compile_insn_swap_topn assert_compile_once('{}["true"] = true', result_inspect: 'true', insns: %i[swap topn]) end - def test_compile_insn_reverse - assert_compile_once('q, (w, e), r = 1, [2, 3], 4; [q, w, e, r]', result_inspect: '[1, 2, 3, 4]', insns: %i[reverse]) - end - def test_compile_insn_reput skip "write test" end diff --git a/test/ruby/test_keyword.rb b/test/ruby/test_keyword.rb index 0bdcb79c6d4677..c8191a722c9c73 100644 --- a/test/ruby/test_keyword.rb +++ b/test/ruby/test_keyword.rb @@ -2411,6 +2411,13 @@ def baz(*args) args end + def empty_method + end + + def opt(arg = :opt) + arg + end + ruby2_keywords def foo_dbar(*args) dbar(*args) end @@ -2419,6 +2426,16 @@ def baz(*args) dbaz(*args) end + ruby2_keywords def clear_last_empty_method(*args) + args.last.clear + empty_method(*args) + end + + ruby2_keywords def clear_last_opt(*args) + args.last.clear + opt(*args) + end + define_method(:dbar) do |*args, **kw| [args, kw] end @@ -2653,6 +2670,9 @@ def method_missing(*args) assert_equal([[1, h1], {}], o.foo(:pass_bar, 1, :a=>1)) assert_equal([[1, h1], {}], o.foo(:pass_cfunc, 1, :a=>1)) + assert_equal(:opt, o.clear_last_opt(a: 1)) + assert_nothing_raised(ArgumentError) { o.clear_last_empty_method(a: 1) } + assert_warn(/Skipping set of ruby2_keywords flag for bar \(method accepts keywords or method does not accept argument splat\)/) do assert_nil(c.send(:ruby2_keywords, :bar)) end diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index 3cd2f04eff4cdd..84e74693aa2a1c 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -2294,7 +2294,7 @@ def /(other) assert_equal(0, 1 / 2) end - def test_visibility_after_refine_and_visibility_change + def test_visibility_after_refine_and_visibility_change_with_origin_class m = Module.new c = Class.new do def x; :x end @@ -2317,6 +2317,114 @@ def x; :y end assert_equal(:x, o2.public_send(:x)) end + def test_visibility_after_multiple_refine_and_visibility_change_with_origin_class + m = Module.new + c = Class.new do + def x; :x end + end + c.prepend(m) + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + + o1 = c.new + o2 = c.new + assert_equal(:x, o1.public_send(:x)) + assert_equal(:x, o2.public_send(:x)) + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_without_origin_class + c = Class.new do + def x; :x end + end + Module.new do + refine c do + def x; :y end + end + end + Module.new do + refine c do + def x; :z end + end + end + o1 = c.new + o2 = c.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + + def test_visibility_after_multiple_refine_and_visibility_change_with_superclass + c = Class.new do + def x; :x end + end + sc = Class.new(c) + Module.new do + refine sc do + def x; :y end + end + end + Module.new do + refine sc do + def x; :z end + end + end + o1 = sc.new + o2 = sc.new + o1.singleton_class.send(:private, :x) + o2.singleton_class.send(:public, :x) + assert_raise(NoMethodError) { o1.public_send(:x) } + assert_equal(:x, o2.public_send(:x)) + end + def test_prepend_visibility bug8005 = '[ruby-core:53106] [Bug #8005]' c = Class.new do diff --git a/test/test_time.rb b/test/test_time.rb index ca20788aace140..b50d8417cf1785 100644 --- a/test/test_time.rb +++ b/test/test_time.rb @@ -62,6 +62,15 @@ def test_rfc2822 assert_equal(true, t.utc?) end + if defined?(Ractor) + def test_rfc2822_ractor + assert_ractor(<<~RUBY, require: 'time') + actual = Ractor.new { Time.rfc2822("Fri, 21 Nov 1997 09:55:06 -0600") }.take + assert_equal(Time.utc(1997, 11, 21, 9, 55, 6) + 6 * 3600, actual) + RUBY + end + end + def test_encode_rfc2822 t = Time.utc(1) assert_equal("Mon, 01 Jan 0001 00:00:00 -0000", t.rfc2822) diff --git a/test/uri/test_generic.rb b/test/uri/test_generic.rb index b449a0a0d1fb6c..d122587031e073 100644 --- a/test/uri/test_generic.rb +++ b/test/uri/test_generic.rb @@ -799,8 +799,12 @@ def test_ipv6 u = URI("http://foo/bar") assert_equal("http://foo/bar", u.to_s) + u.hostname = "[::1]" + assert_equal("http://[::1]/bar", u.to_s) u.hostname = "::1" assert_equal("http://[::1]/bar", u.to_s) + u.hostname = "" + assert_equal("http:///bar", u.to_s) end def test_build diff --git a/test/uri/test_parser.rb b/test/uri/test_parser.rb index b13a26ca84edd2..03de137788b981 100644 --- a/test/uri/test_parser.rb +++ b/test/uri/test_parser.rb @@ -7,6 +7,11 @@ def uri_to_ary(uri) uri.class.component.collect {|c| uri.send(c)} end + def test_inspect + assert_match(/URI::RFC2396_Parser/, URI::Parser.new.inspect) + assert_match(/URI::RFC3986_Parser/, URI::RFC3986_Parser.new.inspect) + end + def test_compare url = 'http://a/b/c/d;p?q' u0 = URI.parse(url) diff --git a/tool/sync_default_gems.rb b/tool/sync_default_gems.rb index 3b8dd5474d05cf..ae13621f7eb174 100644 --- a/tool/sync_default_gems.rb +++ b/tool/sync_default_gems.rb @@ -174,6 +174,7 @@ def sync_default_gems(gem) cp_r("#{upstream}/test/io/console", "test/io") mkdir_p("ext/io/console/lib") cp_r("#{upstream}/lib/io/console", "ext/io/console/lib") + rm_rf("ext/io/console/lib/console/ffi") cp_r("#{upstream}/io-console.gemspec", "ext/io/console") `git checkout ext/io/console/depend` when "io-nonblock" diff --git a/trace_point.rb b/trace_point.rb index d68eed42487be0..2e85369e65be00 100644 --- a/trace_point.rb +++ b/trace_point.rb @@ -309,15 +309,21 @@ def defined_class Primitive.tracepoint_attr_defined_class end - # Return the generated binding object from event + # Return the generated binding object from event. + # + # Note that for +c_call+ and +c_return+ events, the binding returned is the + # binding of the nearest Ruby method calling the C method, since C methods + # themselves do not have bindings. def binding Primitive.tracepoint_attr_binding end # Return the trace object during event # - # Same as TracePoint#binding: - # trace.binding.eval('self') + # Same as the following, except it returns the correct object (the method + # receiver) for +c_call+ and +c_return+ events: + # + # trace.binding.eval('self') def self Primitive.tracepoint_attr_self end diff --git a/transcode.c b/transcode.c index dcb85a532b8a99..505c8177fb3eb0 100644 --- a/transcode.c +++ b/transcode.c @@ -2514,7 +2514,7 @@ econv_opts(VALUE opt, int ecflags) break; case 3: - rb_warning(":newline option preceds other newline options"); + rb_warning(":newline option precedes other newline options"); break; } } diff --git a/version.h b/version.h index 3539a0260afcce..fe80071c4b2397 100644 --- a/version.h +++ b/version.h @@ -16,7 +16,7 @@ #define RUBY_RELEASE_YEAR 2021 #define RUBY_RELEASE_MONTH 4 -#define RUBY_RELEASE_DAY 21 +#define RUBY_RELEASE_DAY 28 #include "ruby/version.h" diff --git a/vm.c b/vm.c index 3edb174354cb59..79dacc1d8f3c8e 100644 --- a/vm.c +++ b/vm.c @@ -423,8 +423,6 @@ static const struct rb_callcache vm_empty_cc = { static void thread_free(void *ptr); -// - void rb_vm_inc_const_missing_count(void) { diff --git a/vm_insnhelper.c b/vm_insnhelper.c index a476a3a8ffee23..b10fa76b3304c2 100644 --- a/vm_insnhelper.c +++ b/vm_insnhelper.c @@ -2269,11 +2269,6 @@ CALLER_REMOVE_EMPTY_KW_SPLAT(struct rb_control_frame_struct *restrict cfp, if (UNLIKELY(calling->kw_splat)) { /* This removes the last Hash object if it is empty. * So, vm_ci_flag(ci) & VM_CALL_KW_SPLAT is now inconsistent. - * However, you can use vm_ci_flag(ci) & VM_CALL_KW_SPLAT to - * determine whether a hash should be added back with - * warning (for backwards compatibility in cases where - * the method does not have the number of required - * arguments. */ if (RHASH_EMPTY_P(cfp->sp[-1])) { cfp->sp--; diff --git a/vm_method.c b/vm_method.c index fa00d4fd2e2a6c..1cf86fa6d5a2f5 100644 --- a/vm_method.c +++ b/vm_method.c @@ -997,7 +997,8 @@ search_method0(VALUE klass, ID id, VALUE *defined_class_ptr, bool skip_refined) for (; klass; klass = RCLASS_SUPER(klass)) { RB_DEBUG_COUNTER_INC(mc_search_super); if ((me = lookup_method_table(klass, id)) != 0) { - if (!skip_refined || me->def->type != VM_METHOD_TYPE_REFINED) { + if (!skip_refined || me->def->type != VM_METHOD_TYPE_REFINED || + me->def->body.refined.orig_me) { break; } } diff --git a/vm_trace.c b/vm_trace.c index 383f255799ae92..6e2c0587798b51 100644 --- a/vm_trace.c +++ b/vm_trace.c @@ -519,6 +519,10 @@ static void call_trace_func(rb_event_flag_t, VALUE data, VALUE self, ID id, VALU * line prog.rb:3 test Test * line prog.rb:4 test Test * return prog.rb:4 test Test + * + * Note that for +c-call+ and +c-return+ events, the binding returned is the + * binding of the nearest Ruby method calling the C method, since C methods + * themselves do not have bindings. */ static VALUE