diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 32c49518595817..f699cba2253a53 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -77,15 +77,15 @@ jobs: run: sudo rm /usr/lib/ruby/vendor_ruby/rubygems/defaults/operating_system.rb - name: Initialize CodeQL - uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/init@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: languages: ${{ matrix.language }} - name: Autobuild - uses: github/codeql-action/autobuild@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/autobuild@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/analyze@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: category: '/language:${{ matrix.language }}' upload: False @@ -115,7 +115,7 @@ jobs: continue-on-error: true - name: Upload SARIF - uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: sarif_file: sarif-results/${{ matrix.language }}.sarif continue-on-error: true diff --git a/.github/workflows/scorecards.yml b/.github/workflows/scorecards.yml index 4dc912f3db4ecd..097f086202af3c 100644 --- a/.github/workflows/scorecards.yml +++ b/.github/workflows/scorecards.yml @@ -67,6 +67,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard. - name: 'Upload to code-scanning' - uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12 + uses: github/codeql-action/upload-sarif@f779452ac5af1c261dce0346a8f964149f49322b # v3.26.13 with: sarif_file: results.sarif diff --git a/LEGAL b/LEGAL index 162ba784836c1f..55c7ffc2917040 100644 --- a/LEGAL +++ b/LEGAL @@ -702,31 +702,6 @@ mentioned below. OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -[ext/json/generator/generator.c] - - The file contains the following copyright notice. - - >>> - Copyright 2001-2004:: Unicode, Inc. - - Disclaimer:: - - This source code is provided as is by Unicode, Inc. No claims are - made as to fitness for any particular purpose. No warranties of any - kind are expressed or implied. The recipient agrees to determine - applicability of information provided. If this file has been - purchased on magnetic or optical media from Unicode, Inc., the - sole remedy for any claim will be exchange of defective media - within 90 days of receipt. - - Limitations on Rights to Redistribute This Code:: - - Unicode, Inc. hereby grants the right to freely use the information - supplied in this file in the creation of products supporting the - Unicode Standard, and to make copies of this file in any form - for internal or external distribution as long as this notice - remains attached. - [ext/psych] [test/psych] diff --git a/NEWS.md b/NEWS.md index 89aad671870881..25e42db1a944eb 100644 --- a/NEWS.md +++ b/NEWS.md @@ -107,7 +107,7 @@ The following bundled gems are updated. * rexml 3.3.8 * rss 0.3.1 * net-ftp 0.3.8 -* net-imap 0.4.16 +* net-imap 0.4.17 * net-smtp 0.5.0 * rbs 3.6.1 * typeprof 0.21.11 diff --git a/array.c b/array.c index b6768e52d3b4c7..015395f74caa72 100644 --- a/array.c +++ b/array.c @@ -1512,35 +1512,40 @@ rb_ary_shift(VALUE ary) /* * call-seq: - * array.shift -> object or nil - * array.shift(n) -> new_array + * shift -> object or nil + * shift(count) -> new_array or nil * - * Removes and returns leading elements. + * Removes and returns leading elements from +self+. * - * When no argument is given, removes and returns the first element: + * With no argument, removes and returns one element, if available, + * or +nil+ otherwise: * - * a = [:foo, 'bar', 2] - * a.shift # => :foo - * a # => ['bar', 2] - * - * Returns +nil+ if +self+ is empty. + * a = [0, 1, 2, 3] + * a.shift # => 0 + * a # => [1, 2, 3] + * [].shift # => nil * - * When positive Integer argument +n+ is given, removes the first +n+ elements; - * returns those elements in a new +Array+: + * With non-negative numeric argument +count+ given, + * removes and returns the first +count+ elements: * - * a = [:foo, 'bar', 2] - * a.shift(2) # => [:foo, 'bar'] - * a # => [2] + * a = [0, 1, 2, 3] + * a.shift(2) # => [0, 1] + * a # => [2, 3] + * a.shift(1.1) # => [2] + * a # => [3] + * a.shift(0) # => [] + * a # => [3] * - * If +n+ is as large as or larger than self.length, - * removes all elements; returns those elements in a new +Array+: + * If +count+ is large, + * removes and returns all elements: * - * a = [:foo, 'bar', 2] - * a.shift(3) # => [:foo, 'bar', 2] + * a = [0, 1, 2, 3] + * a.shift(50) # => [0, 1, 2, 3] + * a # => [] * - * If +n+ is zero, returns a new empty +Array+; +self+ is unmodified. + * If +self+ is empty, returns a new empty array. * - * Related: #push, #pop, #unshift. + * Related: see {Methods for Deleting}[rdoc-ref:Array@Methods+for+Deleting]. */ static VALUE diff --git a/array.rb b/array.rb index 3d64d31e95ee3e..23d38ebc655f7e 100644 --- a/array.rb +++ b/array.rb @@ -45,15 +45,25 @@ def each end # call-seq: - # array.shuffle!(random: Random) -> array + # shuffle!(random: Random) -> self # - # Shuffles the elements of +self+ in place. - # a = [1, 2, 3] #=> [1, 2, 3] - # a.shuffle! #=> [2, 3, 1] - # a #=> [2, 3, 1] + # Shuffles all elements in +self+ into a random order, + # as selected by the object given by keyword argument +random+; + # returns +self+: # - # The optional +random+ argument will be used as the random number generator: - # a.shuffle!(random: Random.new(1)) #=> [1, 3, 2] + # a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + # a.shuffle! # => [5, 3, 8, 7, 6, 1, 9, 4, 2, 0] + # a.shuffle! # => [9, 4, 0, 6, 2, 8, 1, 5, 3, 7] + # + # Duplicate elements are included: + # + # a = [0, 1, 0, 1, 0, 1, 0, 1, 0, 1] + # a.shuffle! # => [1, 0, 0, 1, 1, 0, 1, 0, 0, 1] + # a.shuffle! # => [0, 1, 0, 1, 1, 0, 1, 0, 1, 0] + # + # The object given with keyword argument +random+ is used as the random number generator. + # + # Related: see {Methods for Fetching}[rdoc-ref:Array@Methods+for+Fetching]. def shuffle!(random: Random) Primitive.rb_ary_shuffle_bang(random) end diff --git a/bootstraptest/test_yjit.rb b/bootstraptest/test_yjit.rb index f0192c04142ae0..545243a0e4c6b4 100644 --- a/bootstraptest/test_yjit.rb +++ b/bootstraptest/test_yjit.rb @@ -123,7 +123,7 @@ def test(name, args) # regression test for GC marking stubs in invalidated code assert_normal_exit %q{ - skip true unless defined?(GC.compact) + skip true unless GC.respond_to?(:compact) garbage = Array.new(10_000) { [] } # create garbage to cause iseq movement eval(<<~RUBY) def foo(n, garbage) @@ -158,7 +158,7 @@ def call_foo = foo(0, 1, 2, 3, &->{}) # regression test for invokeblock iseq guard assert_equal 'ok', %q{ - skip :ok unless defined?(GC.compact) + skip :ok unless GC.respond_to?(:compact) def foo = yield 10.times do |i| ret = eval("foo { #{i} }") @@ -1230,7 +1230,7 @@ def special.[](idx) # Test that object references in generated code get marked and moved assert_equal "good", %q{ - skip :good unless defined?(GC.compact) + skip :good unless GC.respond_to?(:compact) def bar "good" end @@ -2351,7 +2351,7 @@ def foo(obj) # Test EP == BP invalidation with moving ISEQs assert_equal 'ok', %q{ - skip :ok unless defined?(GC.compact) + skip :ok unless GC.respond_to?(:compact) def entry ok = proc { :ok } # set #entry as an EP-escaping ISEQ [nil].reverse_each do # avoid exiting the JIT frame on the constant diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec index 5a951a8b25db83..c714ba86d19c87 100644 --- a/ext/json/json.gemspec +++ b/ext/json/json.gemspec @@ -17,7 +17,9 @@ Gem::Specification.new do |s| s.rdoc_options = ["--title", "JSON implementation for Ruby", "--main", "README.md"] s.files = [ "CHANGES.md", - "LICENSE", + "COPYING", + "BSDL", + "LEGAL", "README.md", "ext/json/ext/fbuffer/fbuffer.h", "ext/json/ext/generator/depend", diff --git a/ext/socket/init.c b/ext/socket/init.c index b02ac5fef5748f..83af8c5b0e090e 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -473,10 +473,11 @@ rsock_socket(int domain, int type, int proto) /* emulate blocking connect behavior on EINTR or non-blocking socket */ static int -wait_connectable(int fd, struct timeval *timeout) +wait_connectable(VALUE self, VALUE timeout) { - int sockerr, revents; + int sockerr; socklen_t sockerrlen; + int fd = rb_io_descriptor(self); sockerrlen = (socklen_t)sizeof(sockerr); if (getsockopt(fd, SOL_SOCKET, SO_ERROR, (void *)&sockerr, &sockerrlen) < 0) @@ -510,7 +511,13 @@ wait_connectable(int fd, struct timeval *timeout) * * Note: rb_wait_for_single_fd already retries on EINTR/ERESTART */ - revents = rb_wait_for_single_fd(fd, RB_WAITFD_IN|RB_WAITFD_OUT, timeout); + VALUE result = rb_io_wait(self, RB_INT2NUM(RUBY_IO_READABLE|RUBY_IO_WRITABLE), timeout); + + if (result == Qfalse) { + rb_raise(rb_eIOTimeoutError, "Connect timed out!"); + } + + int revents = RB_NUM2INT(result); if (revents < 0) return -1; @@ -525,12 +532,6 @@ wait_connectable(int fd, struct timeval *timeout) * be defensive in case some platforms set SO_ERROR on the original, * interrupted connect() */ - - /* when the connection timed out, no errno is set and revents is 0. */ - if (timeout && revents == 0) { - errno = ETIMEDOUT; - return -1; - } case EINTR: #ifdef ERESTART case ERESTART: @@ -578,7 +579,7 @@ socks_connect_blocking(void *data) #endif int -rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout) +rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, VALUE timeout) { int descriptor = rb_io_descriptor(self); rb_blocking_function_t *func = connect_blocking; @@ -602,7 +603,7 @@ rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, s #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(descriptor, timeout); + return wait_connectable(self, timeout); } } return status; diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index 7992212b87c324..3de84454355b66 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -55,13 +55,6 @@ init_inetsock_internal(VALUE v) int family = AF_UNSPEC; const char *syscall = 0; VALUE connect_timeout = arg->connect_timeout; - struct timeval tv_storage; - struct timeval *tv = NULL; - - if (!NIL_P(connect_timeout)) { - tv_storage = rb_time_interval(connect_timeout); - tv = &tv_storage; - } arg->remote.res = rsock_addrinfo(arg->remote.host, arg->remote.serv, family, SOCK_STREAM, @@ -130,7 +123,7 @@ init_inetsock_internal(VALUE v) } if (status >= 0) { - status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), tv); + status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), connect_timeout); syscall = "connect(2)"; } } diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index e4ab412f6e834a..b4daa66071a472 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -379,7 +379,7 @@ VALUE rsock_s_recvfrom_nonblock(VALUE sock, VALUE len, VALUE flg, VALUE str, VALUE ex, enum sock_recv_type from); VALUE rsock_s_recvfrom(VALUE sock, int argc, VALUE *argv, enum sock_recv_type from); -int rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout); +int rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, VALUE timeout); VALUE rsock_s_accept(VALUE klass, VALUE io, struct sockaddr *sockaddr, socklen_t *len); VALUE rsock_s_accept_nonblock(VALUE klass, VALUE ex, rb_io_t *fptr, diff --git a/ext/socket/socket.c b/ext/socket/socket.c index c974aafe55102c..487a293c4e2e61 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -387,16 +387,15 @@ rsock_sock_s_socketpair(int argc, VALUE *argv, VALUE klass) * * connect function in Microsoft's Winsock functions reference */ static VALUE -sock_connect(VALUE sock, VALUE addr) +sock_connect(VALUE self, VALUE addr) { VALUE rai; - rb_io_t *fptr; SockAddrStringValueWithAddrinfo(addr, rai); addr = rb_str_new4(addr); - GetOpenFile(sock, fptr); - int result = rsock_connect(sock, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, NULL); + int result = rsock_connect(self, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, RUBY_IO_TIMEOUT_DEFAULT); + if (result < 0) { rsock_sys_fail_raddrinfo_or_sockaddr("connect(2)", addr, rai); } diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index c31e9dbf6facac..a984933c9fd3fe 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -56,7 +56,7 @@ udp_connect_internal(VALUE v) struct addrinfo *res; for (res = arg->res->ai; res; res = res->ai_next) { - if (rsock_connect(arg->io, res->ai_addr, res->ai_addrlen, 0, NULL) >= 0) { + if (rsock_connect(arg->io, res->ai_addr, res->ai_addrlen, 0, RUBY_IO_TIMEOUT_DEFAULT) >= 0) { return Qtrue; } } diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index 11d7c0451fee96..31e2acee04e896 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -21,7 +21,7 @@ static VALUE unixsock_connect_internal(VALUE a) { struct unixsock_arg *arg = (struct unixsock_arg *)a; - return (VALUE)rsock_connect(arg->io, (struct sockaddr*)arg->sockaddr, arg->sockaddrlen, 0, NULL); + return (VALUE)rsock_connect(arg->io, (struct sockaddr*)arg->sockaddr, arg->sockaddrlen, 0, RUBY_IO_TIMEOUT_DEFAULT); } static VALUE diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 50d8dd40c6580c..0899861c028f75 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -1521,7 +1521,7 @@ rb_zstream_total_out(VALUE obj) } /* - * Guesses the type of the data which have been inputed into the stream. The + * Guesses the type of the data which have been inputted into the stream. The * returned value is either BINARY, ASCII, or * UNKNOWN. */ @@ -4054,7 +4054,7 @@ rb_gzreader_read(int argc, VALUE *argv, VALUE obj) * call-seq: * gzipreader.readpartial(maxlen [, outbuf]) => string, outbuf * - * Reads at most maxlen bytes from the gziped stream but + * Reads at most maxlen bytes from the gzipped stream but * it blocks only if gzipreader has no data immediately available. * If the optional outbuf argument is present, * it must reference a String, which will receive the data. diff --git a/gems/bundled_gems b/gems/bundled_gems index 0e88f06f21f422..8d0fc34ba0a3cb 100644 --- a/gems/bundled_gems +++ b/gems/bundled_gems @@ -13,7 +13,7 @@ test-unit 3.6.2 https://github.com/test-unit/test-unit rexml 3.3.8 https://github.com/ruby/rexml rss 0.3.1 https://github.com/ruby/rss net-ftp 0.3.8 https://github.com/ruby/net-ftp -net-imap 0.4.16 https://github.com/ruby/net-imap +net-imap 0.4.17 https://github.com/ruby/net-imap net-pop 0.1.2 https://github.com/ruby/net-pop net-smtp 0.5.0 https://github.com/ruby/net-smtp matrix 0.4.2 https://github.com/ruby/matrix diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 547102be41c22b..296bcfff38fccd 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -384,13 +384,13 @@ def extensions_dir end end - remove_method :ignored? if new.respond_to?(:ignored?) + # Can be removed once RubyGems 3.5.22 support is dropped + unless new.respond_to?(:ignored?) + def ignored? + return @ignored unless @ignored.nil? - # Same as RubyGems, but without warnings, because Bundler prints its own warnings - def ignored? - return @ignored unless @ignored.nil? - - @ignored = missing_extensions? + @ignored = missing_extensions? + end end end diff --git a/lib/irb/history.rb b/lib/irb/history.rb index a428003d2de946..25fa71b9c3721d 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -5,10 +5,7 @@ module History class << self # Integer representation of IRB.conf[:HISTORY_FILE]. def save_history - num = IRB.conf[:SAVE_HISTORY].to_i - # Bignums cause RangeErrors when slicing arrays. - # Treat such values as 'infinite'. - (num > save_history_max) ? -1 : num + IRB.conf[:SAVE_HISTORY].to_i end def save_history? @@ -27,13 +24,6 @@ def history_file IRB.rc_file("_history") end end - - private - - def save_history_max - # Max fixnum (32-bit) that can be used without getting RangeError. - 2**30 - 1 - end end end @@ -90,7 +80,8 @@ def save_history hist = history.map { |l| l.scrub.split("\n").join("\\\n") } unless append_history || History.infinite? - hist = hist.last(History.save_history) + # Check size before slicing because array.last(huge_number) raises RangeError. + hist = hist.last(History.save_history) if hist.size > History.save_history end f.puts(hist) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index b8156597135430..f6627ca430a5a9 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -846,7 +846,11 @@ def editing_mode when CompletionState::NORMAL @completion_state = CompletionState::COMPLETION when CompletionState::PERFECT_MATCH - @dig_perfect_match_proc&.(@perfect_matched) + if @dig_perfect_match_proc + @dig_perfect_match_proc.(@perfect_matched) + else + @completion_state = CompletionState::COMPLETION + end end if just_show_list is_menu = true @@ -2368,9 +2372,9 @@ def finish private def search_next_char(key, arg, need_prev_char: false, inclusive: false) if key.instance_of?(String) - inputed_char = key + inputted_char = key else - inputed_char = key.chr + inputted_char = key.chr end prev_total = nil total = nil @@ -2382,7 +2386,7 @@ def finish width = Reline::Unicode.get_mbchar_width(mbchar) total = [mbchar.bytesize, width] else - if inputed_char == mbchar + if inputted_char == mbchar arg -= 1 if arg.zero? found = true @@ -2420,9 +2424,9 @@ def finish private def search_prev_char(key, arg, need_next_char = false) if key.instance_of?(String) - inputed_char = key + inputted_char = key else - inputed_char = key.chr + inputted_char = key.chr end prev_total = nil total = nil @@ -2434,7 +2438,7 @@ def finish width = Reline::Unicode.get_mbchar_width(mbchar) total = [mbchar.bytesize, width] else - if inputed_char == mbchar + if inputted_char == mbchar arg -= 1 if arg.zero? found = true diff --git a/lib/rubygems/basic_specification.rb b/lib/rubygems/basic_specification.rb index 8c75fd41d41a57..a09e8ed0e13c81 100644 --- a/lib/rubygems/basic_specification.rb +++ b/lib/rubygems/basic_specification.rb @@ -71,7 +71,14 @@ def base_dir # Return true if this spec can require +file+. def contains_requirable_file?(file) - return false if ignored? + if ignored? + if platform == Gem::Platform::RUBY || Gem::Platform.local === platform + warn "Ignoring #{full_name} because its extensions are not built. " \ + "Try: gem pristine #{name} --version #{version}" + end + + return false + end is_soext = file.end_with?(".so", ".o") @@ -89,14 +96,6 @@ def ignored? return @ignored unless @ignored.nil? @ignored = missing_extensions? - return false unless @ignored - - if platform == Gem::Platform::RUBY || Gem::Platform.local === platform - warn "Ignoring #{full_name} because its extensions are not built. " \ - "Try: gem pristine #{name} --version #{version}" - end - - true end def default_gem? diff --git a/lib/rubygems/commands/contents_command.rb b/lib/rubygems/commands/contents_command.rb index 807158d9c97c3e..bd0cbce8ba69f8 100644 --- a/lib/rubygems/commands/contents_command.rb +++ b/lib/rubygems/commands/contents_command.rb @@ -103,16 +103,23 @@ def files_in_gem(spec) def files_in_default_gem(spec) spec.files.map do |file| - case file - when %r{\A#{spec.bindir}/} - # $' is POSTMATCH - [RbConfig::CONFIG["bindir"], $'] - when /\.so\z/ - [RbConfig::CONFIG["archdir"], file] + if file.start_with?("#{spec.bindir}/") + [RbConfig::CONFIG["bindir"], file.delete_prefix("#{spec.bindir}/")] else - [RbConfig::CONFIG["rubylibdir"], file] + gem spec.name, spec.version + + require_path = spec.require_paths.find do |path| + file.start_with?("#{path}/") + end + + requirable_part = file.delete_prefix("#{require_path}/") + + resolve = $LOAD_PATH.resolve_feature_path(requirable_part)&.last + next unless resolve + + [resolve.delete_suffix(requirable_part), requirable_part] end - end + end.compact end def gem_contents(name) diff --git a/lib/rubygems/specification_record.rb b/lib/rubygems/specification_record.rb index d838fcea26912a..195a35549670ed 100644 --- a/lib/rubygems/specification_record.rb +++ b/lib/rubygems/specification_record.rb @@ -30,7 +30,7 @@ def initialize(dirs) # Returns the list of all specifications in the record def all - @all ||= Gem.loaded_specs.values + stubs.map(&:to_spec) + @all ||= stubs.map(&:to_spec) end ## diff --git a/prism/extension.c b/prism/extension.c index 47603fd9b4f506..26415c2b6da977 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -1153,6 +1153,7 @@ string_query(pm_string_query_t result) { case PM_STRING_QUERY_TRUE: return Qtrue; } + return Qfalse; } /** diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index d686355270c75d..fa5b5c56d60f5a 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -625,15 +625,21 @@ def obj.to_int; 3; end -> { Time.new("2020-12-25 00:56:17 +23:59:60") - }.should raise_error(ArgumentError, "utc_offset out of range") + }.should raise_error(ArgumentError, /utc_offset/) -> { Time.new("2020-12-25 00:56:17 +24:00") - }.should raise_error(ArgumentError, "utc_offset out of range") + }.should raise_error(ArgumentError, /utc_offset/) -> { Time.new("2020-12-25 00:56:17 +23:61") - }.should raise_error(ArgumentError, '"+HH:MM", "-HH:MM", "UTC" or "A".."I","K".."Z" expected for utc_offset: +23:61') + }.should raise_error(ArgumentError, /utc_offset/) + + ruby_bug '#20797', ''...'3.4' do + -> { + Time.new("2020-12-25 00:56:17 +00:23:61") + }.should raise_error(ArgumentError, /utc_offset/) + end end it "raises ArgumentError if string has not ascii-compatible encoding" do diff --git a/spec/ruby/library/socket/socket/connect_spec.rb b/spec/ruby/library/socket/socket/connect_spec.rb index 8653fba552bdcd..175c9942bcd667 100644 --- a/spec/ruby/library/socket/socket/connect_spec.rb +++ b/spec/ruby/library/socket/socket/connect_spec.rb @@ -53,4 +53,18 @@ end end end + + ruby_version_is "3.4" do + it "fails with timeout" do + # TEST-NET-1 IP address are reserved for documentation and example purposes. + address = Socket.pack_sockaddr_in(1, "192.0.2.1") + + client = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM) + client.timeout = 0 + + -> { + client.connect(address) + }.should raise_error(IO::TimeoutError) + end + end end diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 834c501d5c6035..ac6ce5346ea6c9 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -39,49 +39,44 @@ def teardown end def test_launch - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) 'Hello, World!' EOC - close assert_screen(<<~EOC) - start IRB irb(main):001> 'Hello, World!' => "Hello, World!" irb(main):002> EOC + close end def test_configuration_file_is_skipped_with_dash_f write_irbrc <<~'LINES' puts '.irbrc file should be ignored when -f is used' LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: /irb\(main\)/) write(<<~EOC) 'Hello, World!' EOC - close assert_screen(<<~EOC) irb(main):001> 'Hello, World!' => "Hello, World!" irb(main):002> EOC + close end def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions write_irbrc <<~'LINES' puts '.irbrc file should be ignored when -f is used' LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: '') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb -f}, startup_message: /irb\(main\)/) write(<<~EOC) 'Hello, World!' binding.irb exit! EOC - close assert_screen(<<~EOC) irb(main):001> 'Hello, World!' => "Hello, World!" @@ -89,13 +84,11 @@ def test_configuration_file_is_skipped_with_dash_f_for_nested_sessions irb(main):003> exit! irb(main):001> EOC + close end def test_nomultiline - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: 'start IRB') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb --nomultiline}, startup_message: /irb\(main\)/) write(<<~EOC) if true if false @@ -105,9 +98,7 @@ def test_nomultiline end end EOC - close assert_screen(<<~EOC) - start IRB irb(main):001> if true irb(main):002* if false irb(main):003* a = "hello @@ -118,13 +109,11 @@ def test_nomultiline => nil irb(main):008> EOC + close end def test_multiline_paste - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(25, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) class A def inspect; '#'; end @@ -139,9 +128,7 @@ def b; true; end .b .itself EOC - close assert_screen(<<~EOC) - start IRB irb(main):001* class A irb(main):002* def inspect; '#'; end irb(main):003* def a; self; end @@ -159,13 +146,11 @@ def b; true; end => true irb(main):013> EOC + close end def test_evaluate_each_toplevel_statement_by_multiline_paste - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) class A def inspect; '#'; end @@ -193,9 +178,7 @@ class A def b; self; end; def c; true; end; end; &.b() .itself EOC - close assert_screen(<<~EOC) - start IRB irb(main):001* class A irb(main):002* def inspect; '#'; end irb(main):003* def b; self; end @@ -230,36 +213,28 @@ class A def b; self; end; def c; true; end; end; => # irb(main):026> EOC + close end def test_symbol_with_backtick - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write(<<~EOC) :` EOC - close assert_screen(<<~EOC) - start IRB irb(main):001> :` => :` irb(main):002> EOC + close end def test_autocomplete_with_multiple_doc_namespaces - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(3, 50, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("{}.__id_") write("\C-i") - sleep 0.2 + assert_screen(/irb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/) close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/start\ IRB\nirb\(main\):001> {}\.__id__\n }\.__id__(?:Press )?/, screen) end def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right @@ -273,31 +248,27 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_right :PROMPT_C => "%03n> " } IRB.conf[:PROMPT_MODE] = :MY_PROMPT - puts 'start IRB' LINES - start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(4, 19, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/) write("IR") write("\C-i") - sleep 0.2 - close # This is because on macOS we display different shortcut for displaying the full doc # 'O' is for 'Option' and 'A' is for 'Alt' if RUBY_PLATFORM =~ /darwin/ assert_screen(<<~EOC) - start IRB 001> IRB IRBPress Opti IRB EOC else assert_screen(<<~EOC) - start IRB 001> IRB IRBPress Alt+ IRB EOC end + close end def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left @@ -311,154 +282,129 @@ def test_autocomplete_with_showdoc_in_gaps_on_narrow_screen_left :PROMPT_C => "%03n> " } IRB.conf[:PROMPT_MODE] = :MY_PROMPT - puts 'start IRB' LINES - start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(4, 12, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /001>/) write("IR") write("\C-i") - sleep 0.2 - close assert_screen(<<~EOC) - start IRB 001> IRB PressIRB IRB EOC + close end def test_assignment_expression_truncate - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) # Assignment expression code that turns into non-assignment expression after evaluation code = "a /'/i if false; a=1; x=1000.times.to_a#'.size" write(code + "\n") - close assert_screen(<<~EOC) - start IRB irb(main):001> #{code} => [0, ... irb(main):002> EOC + close end def test_ctrl_c_is_handled - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) # Assignment expression code that turns into non-assignment expression after evaluation write("\C-c") - close assert_screen(<<~EOC) - start IRB irb(main):001> ^C irb(main):001> EOC + close end def test_show_cmds_with_pager_can_quit_with_ctrl_c - write_irbrc <<~'LINES' - puts 'start IRB' - LINES - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("help\n") write("G") # move to the end of the screen write("\C-c") # quit pager write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - # IRB::Abort should be rescued - assert_not_match(/IRB::Abort/, screen) # IRB should resume - assert_match(/foobar/, screen) + assert_screen(/foobar/) + # IRB::Abort should be rescued + assert_screen(/\A(?!IRB::Abort)/) + close end def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_total_length write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("IRB::Pager.page_content('a' * (80 * 8))\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/a{80}/, screen) + assert_screen(/a{80}/) # because pager is invoked, foobar will not be evaluated - assert_not_match(/foobar/, screen) + assert_screen(/\A(?!foobar)/) + close end def test_pager_page_content_pages_output_when_it_does_not_fit_in_the_screen_because_of_screen_height write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("IRB::Pager.page_content('a\n' * 8)\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/(a\n){8}/, screen) + assert_screen(/(a\n){8}/) # because pager is invoked, foobar will not be evaluated - assert_not_match(/foobar/, screen) + assert_screen(/\A(?!foobar)/) + close end def test_pager_page_content_doesnt_page_output_when_it_fits_in_the_screen write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("IRB::Pager.page_content('a' * (80 * 7))\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/a{80}/, screen) + assert_screen(/a{80}/) # because pager is not invoked, foobar will be evaluated - assert_match(/foobar/, screen) + assert_screen(/foobar/) + close end def test_long_evaluation_output_is_paged write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("'a' * 80 * 11\n") write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_match(/(a{80}\n){8}/, screen) + assert_screen(/(a{80}\n){8}/) # because pager is invoked, foobar will not be evaluated - assert_not_match(/foobar/, screen) + assert_screen(/\A(?!foobar)/) + close end def test_long_evaluation_output_is_preserved_after_paging write_irbrc <<~'LINES' - puts 'start IRB' require "irb/pager" LINES - start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: 'start IRB') + start_terminal(10, 80, %W{ruby -I#{@pwd}/lib #{@pwd}/exe/irb}, startup_message: /irb\(main\)/) write("'a' * 80 * 11\n") write("q") # quit pager write("'foo' + 'bar'\n") # eval something to make sure IRB resumes - close - screen = result.join("\n").sub(/\n*\z/, "\n") # confirm pager has exited - assert_match(/foobar/, screen) + assert_screen(/foobar/) # confirm output is preserved - assert_match(/(a{80}\n){6}/, screen) + assert_screen(/(a{80}\n){6}/) + close end def test_debug_integration_hints_debugger_commands @@ -467,21 +413,19 @@ def test_debug_integration_hints_debugger_commands LINES script = Tempfile.create(["debug", ".rb"]) script.write <<~RUBY - puts 'start IRB' binding.irb RUBY script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/) write("debug\n") write("pp 1\n") write("pp 1") - close - screen = result.join("\n").sub(/\n*\z/, "\n") # submitted input shouldn't contain hint - assert_include(screen, "irb:rdbg(main):002> pp 1\n") + assert_screen(/irb:rdbg\(main\):002> pp 1\n/) # unsubmitted input should contain hint - assert_include(screen, "irb:rdbg(main):003> pp 1 # debug command\n") + assert_screen(/irb:rdbg\(main\):003> pp 1 # debug command\n/) + close ensure File.unlink(script) if script end @@ -492,17 +436,14 @@ def test_debug_integration_doesnt_hint_non_debugger_commands LINES script = Tempfile.create(["debug", ".rb"]) script.write <<~RUBY - puts 'start IRB' binding.irb RUBY script.close - start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') + start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: /irb\(main\)/) write("debug\n") write("foo") + assert_screen(/irb:rdbg\(main\):002> foo\n/) close - - screen = result.join("\n").sub(/\n*\z/, "\n") - assert_include(screen, "irb:rdbg(main):002> foo\n") ensure File.unlink(script) if script end @@ -520,11 +461,9 @@ def test_debug_integration_doesnt_hint_debugger_commands_in_nomultiline_mode start_terminal(40, 80, %W{ruby -I#{@pwd}/lib #{script.to_path}}, startup_message: 'start IRB') write("debug\n") write("pp 1") - close - - screen = result.join("\n").sub(/\n*\z/, "\n") # submitted input shouldn't contain hint - assert_include(screen, "irb:rdbg(main):002> pp 1\n") + assert_screen(/irb:rdbg\(main\):002> pp 1\n/) + close ensure File.unlink(script) if script end diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index dc12c803f57787..2ebd91dc2ecf45 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -260,7 +260,7 @@ def test_ed_clear_screen assert_empty(@line_editor.instance_variable_get(:@rendered_screen).lines) end - def test_ed_clear_screen_with_inputed + def test_ed_clear_screen_with_inputted input_keys('abc') input_keys("\C-b", false) @line_editor.instance_variable_get(:@rendered_screen).lines = [[]] @@ -920,6 +920,29 @@ def test_completion_with_perfect_match assert_equal('foo_bar', matched) end + def test_continuous_completion_with_perfect_match + @line_editor.completion_proc = proc { |word| + word == 'f' ? ['foo'] : %w[foobar foobaz] + } + input_keys('f') + input_keys("\C-i", false) + assert_line_around_cursor('foo', '') + input_keys("\C-i", false) + assert_line_around_cursor('fooba', '') + end + + def test_continuous_completion_disabled_with_perfect_match + @line_editor.completion_proc = proc { |word| + word == 'f' ? ['foo'] : %w[foobar foobaz] + } + @line_editor.dig_perfect_match_proc = proc {} + input_keys('f') + input_keys("\C-i", false) + assert_line_around_cursor('foo', '') + input_keys("\C-i", false) + assert_line_around_cursor('foo', '') + end + def test_completion_with_completion_ignore_case @line_editor.completion_proc = proc { |word| %w{ diff --git a/test/ruby/test_objectspace.rb b/test/ruby/test_objectspace.rb index beb66da7e216e9..5c79983b7e87d0 100644 --- a/test/ruby/test_objectspace.rb +++ b/test/ruby/test_objectspace.rb @@ -208,7 +208,6 @@ def test_finalizer_thread_raise main_th.raise(my_error) end GC.start - puts "After GC" sleep(10) assert(false) rescue my_error diff --git a/test/ruby/test_time.rb b/test/ruby/test_time.rb index 99ee84f247e2d0..333edb80218a64 100644 --- a/test/ruby/test_time.rb +++ b/test/ruby/test_time.rb @@ -152,6 +152,18 @@ def test_new_from_string assert_raise_with_message(ArgumentError, /can't parse/) { Time.new("2020-12-02 00:00:00 ") } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +0960") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +09:60") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +090060") + } + assert_raise_with_message(ArgumentError, /utc_offset/) { + Time.new("2020-12-25 00:00:00 +09:00:60") + } end def test_time_add() diff --git a/test/rubygems/test_gem.rb b/test/rubygems/test_gem.rb index 0d048b08b7f01f..cdc3479e3739d3 100644 --- a/test/rubygems/test_gem.rb +++ b/test/rubygems/test_gem.rb @@ -1026,6 +1026,20 @@ def test_self_refresh_keeps_loaded_specs_activated Gem.refresh end + def test_activated_specs_does_not_cause_duplicates_when_looping_through_specs + util_make_gems + + s = Gem::Specification.first + s.activate + + Gem.refresh + + assert_equal 1, Gem::Specification.count {|spec| spec.full_name == s.full_name } + + Gem.loaded_specs.delete(s) + Gem.refresh + end + def test_self_ruby_escaping_spaces_in_path with_clean_path_to_ruby do with_rb_config_ruby("C:/Ruby 1.8/bin/ruby.exe") do diff --git a/test/rubygems/test_gem_commands_contents_command.rb b/test/rubygems/test_gem_commands_contents_command.rb index d8e6ba3dec534b..049afe8a014ad1 100644 --- a/test/rubygems/test_gem_commands_contents_command.rb +++ b/test/rubygems/test_gem_commands_contents_command.rb @@ -227,7 +227,6 @@ def test_execute_default_gem default_gem_spec = new_default_spec("default", "2.0.0.0", nil, "default/gem.rb") default_gem_spec.executables = ["default_command"] - default_gem_spec.files += ["default_gem.so"] install_default_gems(default_gem_spec) @cmd.options[:args] = %w[default] @@ -237,9 +236,8 @@ def test_execute_default_gem end expected = [ - [RbConfig::CONFIG["bindir"], "default_command"], - [RbConfig::CONFIG["rubylibdir"], "default/gem.rb"], - [RbConfig::CONFIG["archdir"], "default_gem.so"], + [File.join(@gemhome, "bin"), "default_command"], + [File.join(@tempdir, "default_gems", "lib"), "default/gem.rb"], ].sort.map {|a|File.join a }.join "\n" assert_equal expected, @ui.output.chomp diff --git a/time.c b/time.c index 54db29c2af1d33..3c8ff365164caf 100644 --- a/time.c +++ b/time.c @@ -2153,6 +2153,9 @@ invalid_utc_offset(VALUE zone) zone); } +#define have_2digits(ptr) (ISDIGIT((ptr)[0]) && ISDIGIT((ptr)[1])) +#define num_from_2digits(ptr) ((ptr)[0] * 10 + (ptr)[1] - '0' * 11) + static VALUE utc_offset_arg(VALUE arg) { @@ -2207,18 +2210,19 @@ utc_offset_arg(VALUE arg) goto invalid_utc_offset; } if (sec) { - if (!ISDIGIT(sec[0]) || !ISDIGIT(sec[1])) goto invalid_utc_offset; - n += (sec[0] * 10 + sec[1] - '0' * 11); + if (!have_2digits(sec)) goto invalid_utc_offset; + if (sec[0] > '5') goto invalid_utc_offset; + n += num_from_2digits(sec); ASSUME(min); } if (min) { - if (!ISDIGIT(min[0]) || !ISDIGIT(min[1])) goto invalid_utc_offset; + if (!have_2digits(min)) goto invalid_utc_offset; if (min[0] > '5') goto invalid_utc_offset; - n += (min[0] * 10 + min[1] - '0' * 11) * 60; + n += num_from_2digits(min) * 60; } if (s[0] != '+' && s[0] != '-') goto invalid_utc_offset; - if (!ISDIGIT(s[1]) || !ISDIGIT(s[2])) goto invalid_utc_offset; - n += (s[1] * 10 + s[2] - '0' * 11) * 3600; + if (!have_2digits(s+1)) goto invalid_utc_offset; + n += num_from_2digits(s+1) * 3600; if (s[0] == '-') { if (n == 0) return UTC_ZONE; n = -n; @@ -2528,8 +2532,7 @@ static int two_digits(const char *ptr, const char *end, const char **endp, const char *name) { ssize_t len = end - ptr; - if (len < 2 || (!ISDIGIT(ptr[0]) || !ISDIGIT(ptr[1])) || - ((len > 2) && ISDIGIT(ptr[2]))) { + if (len < 2 || !have_2digits(ptr) || ((len > 2) && ISDIGIT(ptr[2]))) { VALUE mesg = rb_sprintf("two digits %s is expected", name); if (ptr[-1] == '-' || ptr[-1] == ':') { rb_str_catf(mesg, " after '%c'", ptr[-1]); @@ -2538,7 +2541,7 @@ two_digits(const char *ptr, const char *end, const char **endp, const char *name rb_exc_raise(rb_exc_new_str(rb_eArgError, mesg)); } *endp = ptr + 2; - return (ptr[0] - '0') * 10 + (ptr[1] - '0'); + return num_from_2digits(ptr); } static VALUE