From 1ad990a366a343a84c089b771be4fe6ff80b5078 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 10 Oct 2024 19:30:26 +0200 Subject: [PATCH 001/140] [rubygems/rubygems] Only pristine executables for default gems https://github.com/rubygems/rubygems/commit/1cfc1d626c --- lib/rubygems/commands/pristine_command.rb | 18 +++++++++++++++--- .../test_gem_commands_pristine_command.rb | 15 +++++++++++---- 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/rubygems/commands/pristine_command.rb b/lib/rubygems/commands/pristine_command.rb index 96e48df4e61aa3..ec791d310a2800 100644 --- a/lib/rubygems/commands/pristine_command.rb +++ b/lib/rubygems/commands/pristine_command.rb @@ -137,6 +137,13 @@ def execute specs.group_by(&:full_name_with_location).values.each do |grouped_specs| spec = grouped_specs.find {|s| !s.default_gem? } || grouped_specs.first + unless only_executables_or_plugins? + # Default gemspecs include changes provided by ruby-core installer that + # can't currently be pristined (inclusion of compiled extension targets in + # the file list). So stick to resetting executables if it's a default gem. + options[:only_executables] = true if spec.default_gem? + end + if options.key? :skip if options[:skip].include? spec.name say "Skipped #{spec.full_name}, it was given through options" @@ -144,14 +151,14 @@ def execute end end - unless spec.extensions.empty? || options[:extensions] || options[:only_executables] || options[:only_plugins] + unless spec.extensions.empty? || options[:extensions] || only_executables_or_plugins? say "Skipped #{spec.full_name_with_location}, it needs to compile an extension" next end gem = spec.cache_file - unless File.exist?(gem) || options[:only_executables] || options[:only_plugins] + unless File.exist?(gem) || only_executables_or_plugins? require_relative "../remote_fetcher" say "Cached gem for #{spec.full_name_with_location} not found, attempting to fetch..." @@ -185,7 +192,6 @@ def execute env_shebang: env_shebang, build_args: spec.build_args, bin_dir: bin_dir, - install_as_default: spec.default_gem?, } if options[:only_executables] @@ -202,4 +208,10 @@ def execute say "Restored #{spec.full_name_with_location}" end end + + private + + def only_executables_or_plugins? + options[:only_executables] || options[:only_plugins] + end end diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index 190f78c79f39c3..2a715921b33d32 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -630,8 +630,16 @@ def test_execute_unknown_gem_at_remote_source def test_execute_default_gem default_gem_spec = new_default_spec("default", "2.0.0.0", - nil, "default/gem.rb") - install_default_gems(default_gem_spec) + nil, "exe/executable") + default_gem_spec.executables = "executable" + install_default_gems default_gem_spec + + exe = File.join @gemhome, "bin", "executable" + + assert_path_exist exe, "default gem's executable not installed" + + content_with_replaced_shebang = File.read(exe).gsub(/^#![^\n]+ruby/, "#!/usr/bin/env ruby_executable_hooks") + File.write(exe, content_with_replaced_shebang) @cmd.options[:args] = %w[default] @@ -642,8 +650,7 @@ def test_execute_default_gem assert_equal( [ "Restoring gems to pristine condition...", - "Cached gem for default-2.0.0.0 not found, attempting to fetch...", - "Skipped default-2.0.0.0, it was not found from cache and remote sources", + "Restored default-2.0.0.0", ], @ui.output.split("\n") ) From 09100508e66694cdc3fb8c0867cf56b2fbfb1ceb Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Thu, 10 Oct 2024 15:03:40 -0500 Subject: [PATCH 002/140] [DOC] Tweaks for Array#reverse_each (#11855) --- array.c | 46 +++++++++++++--------------------------------- 1 file changed, 13 insertions(+), 33 deletions(-) diff --git a/array.c b/array.c index d28f03cb4c81ea..2a2893cffeec35 100644 --- a/array.c +++ b/array.c @@ -2633,6 +2633,7 @@ rb_ary_each(VALUE ary) * * a = [:foo, 'bar', 2] * a.each_index {|index| puts index; a.clear if index > 0 } + * a # => [] * * Output: * @@ -2658,47 +2659,26 @@ rb_ary_each_index(VALUE ary) /* * call-seq: - * array.reverse_each {|element| ... } -> self - * array.reverse_each -> Enumerator + * reverse_each {|element| ... } -> self + * reverse_each -> Enumerator * - * Iterates backwards over array elements. - * - * When a block given, passes, in reverse order, each element to the block; + * When a block given, iterates backwards over the elements of +self+, + * passing, in reverse order, each element to the block; * returns +self+: * - * a = [:foo, 'bar', 2] - * a.reverse_each {|element| puts "#{element.class} #{element}" } - * - * Output: - * - * Integer 2 - * String bar - * Symbol foo + * a = [] + * [0, 1, 2].reverse_each {|element| a.push(element) } + * a # => [2, 1, 0] * * Allows the array to be modified during iteration: * - * a = [:foo, 'bar', 2] - * a.reverse_each {|element| puts element; a.clear if element.to_s.start_with?('b') } - * - * Output: - * - * 2 - * bar - * - * When no block given, returns a new Enumerator: - * - * a = [:foo, 'bar', 2] - * e = a.reverse_each - * e # => # - * a1 = e.each {|element| puts "#{element.class} #{element}" } - * - * Output: + * a = ['a', 'b', 'c'] + * a.reverse_each {|element| a.clear if element.start_with?('b') } + * a # => [] * - * Integer 2 - * String bar - * Symbol foo + * When no block given, returns a new Enumerator. * - * Related: #each, #each_index. + * Related: see {Methods for Iterating}[rdoc-ref:Array@Methods+for+Iterating]. */ static VALUE From c43be94f76982d3ffa2ecd28d34172600b81ca31 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Fri, 11 Oct 2024 18:36:11 +1300 Subject: [PATCH 003/140] Update `rsock_connect` to take `VALUE io` argument. (#11847) --- ext/socket/init.c | 16 ++++++------ ext/socket/ipsocket.c | 55 +++++++++++++++++++++++------------------ ext/socket/rubysocket.h | 4 +-- ext/socket/socket.c | 9 +++---- ext/socket/udpsocket.c | 54 +++++++++++++++++++--------------------- ext/socket/unixsocket.c | 36 ++++++++++++++------------- 6 files changed, 88 insertions(+), 86 deletions(-) diff --git a/ext/socket/init.c b/ext/socket/init.c index 90870fec6915a8..b02ac5fef5748f 100644 --- a/ext/socket/init.c +++ b/ext/socket/init.c @@ -578,19 +578,19 @@ socks_connect_blocking(void *data) #endif int -rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout) +rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout) { - int status; + int descriptor = rb_io_descriptor(self); rb_blocking_function_t *func = connect_blocking; - struct connect_arg arg; + struct connect_arg arg = {.fd = descriptor, .sockaddr = sockaddr, .len = len}; + + rb_io_t *fptr; + RB_IO_POINTER(self, fptr); - arg.fd = fd; - arg.sockaddr = sockaddr; - arg.len = len; #if defined(SOCKS) && !defined(SOCKS5) if (socks) func = socks_connect_blocking; #endif - status = (int)BLOCKING_REGION_FD(func, &arg); + int status = (int)rb_io_blocking_region(fptr, func, &arg); if (status < 0) { switch (errno) { @@ -602,7 +602,7 @@ rsock_connect(int fd, const struct sockaddr *sockaddr, int len, int socks, struc #ifdef EINPROGRESS case EINPROGRESS: #endif - return wait_connectable(fd, timeout); + return wait_connectable(descriptor, timeout); } } return status; diff --git a/ext/socket/ipsocket.c b/ext/socket/ipsocket.c index d94584c90baae1..7992212b87c324 100644 --- a/ext/socket/ipsocket.c +++ b/ext/socket/ipsocket.c @@ -9,16 +9,18 @@ ************************************************/ #include "rubysocket.h" +#include struct inetsock_arg { - VALUE sock; + VALUE self; + VALUE io; + struct { VALUE host, serv; struct rb_addrinfo *res; } remote, local; int type; - int fd; VALUE resolv_timeout; VALUE connect_timeout; }; @@ -35,8 +37,9 @@ inetsock_cleanup(VALUE v) rb_freeaddrinfo(arg->local.res); arg->local.res = 0; } - if (arg->fd >= 0) { - close(arg->fd); + if (arg->io != Qnil) { + rb_io_close(arg->io); + arg->io = Qnil; } return Qnil; } @@ -48,7 +51,7 @@ init_inetsock_internal(VALUE v) int error = 0; int type = arg->type; struct addrinfo *res, *lres; - int fd, status = 0, local = 0; + int status = 0, local = 0; int family = AF_UNSPEC; const char *syscall = 0; VALUE connect_timeout = arg->connect_timeout; @@ -74,7 +77,8 @@ init_inetsock_internal(VALUE v) family, SOCK_STREAM, 0); } - arg->fd = fd = -1; + VALUE io = Qnil; + for (res = arg->remote.res->ai; res; res = res->ai_next) { #if !defined(INET6) && defined(AF_INET6) if (res->ai_family == AF_INET6) @@ -96,12 +100,14 @@ init_inetsock_internal(VALUE v) } status = rsock_socket(res->ai_family,res->ai_socktype,res->ai_protocol); syscall = "socket(2)"; - fd = status; - if (fd < 0) { + if (status < 0) { error = errno; continue; } - arg->fd = fd; + + int fd = status; + io = arg->io = rsock_init_sock(arg->self, fd); + if (type == INET_SERVER) { #if !defined(_WIN32) && !defined(__CYGWIN__) status = 1; @@ -124,20 +130,22 @@ init_inetsock_internal(VALUE v) } if (status >= 0) { - status = rsock_connect(fd, res->ai_addr, res->ai_addrlen, - (type == INET_SOCKS), tv); + status = rsock_connect(io, res->ai_addr, res->ai_addrlen, (type == INET_SOCKS), tv); syscall = "connect(2)"; } } if (status < 0) { error = errno; - close(fd); - arg->fd = fd = -1; + arg->io = Qnil; + rb_io_close(io); + io = Qnil; continue; - } else + } else { break; + } } + if (status < 0) { VALUE host, port; @@ -152,28 +160,28 @@ init_inetsock_internal(VALUE v) rsock_syserr_fail_host_port(error, syscall, host, port); } - arg->fd = -1; + // Don't close the socket in `inetsock_cleanup` if we are returning it: + arg->io = Qnil; - if (type == INET_SERVER) { - status = listen(fd, SOMAXCONN); + if (type == INET_SERVER && io != Qnil) { + status = listen(rb_io_descriptor(io), SOMAXCONN); if (status < 0) { error = errno; - close(fd); + rb_io_close(io); rb_syserr_fail(error, "listen(2)"); } } /* create new instance */ - return rsock_init_sock(arg->sock, fd); + return io; } VALUE -rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, - VALUE local_host, VALUE local_serv, int type, - VALUE resolv_timeout, VALUE connect_timeout) +rsock_init_inetsock(VALUE self, VALUE remote_host, VALUE remote_serv, VALUE local_host, VALUE local_serv, int type, VALUE resolv_timeout, VALUE connect_timeout) { struct inetsock_arg arg; - arg.sock = sock; + arg.self = self; + arg.io = Qnil; arg.remote.host = remote_host; arg.remote.serv = remote_serv; arg.remote.res = 0; @@ -181,7 +189,6 @@ rsock_init_inetsock(VALUE sock, VALUE remote_host, VALUE remote_serv, arg.local.serv = local_serv; arg.local.res = 0; arg.type = type; - arg.fd = -1; arg.resolv_timeout = resolv_timeout; arg.connect_timeout = connect_timeout; return rb_ensure(init_inetsock_internal, (VALUE)&arg, diff --git a/ext/socket/rubysocket.h b/ext/socket/rubysocket.h index f486db4262abb3..e4ab412f6e834a 100644 --- a/ext/socket/rubysocket.h +++ b/ext/socket/rubysocket.h @@ -298,8 +298,6 @@ int Rconnect(); #include "constdefs.h" -#define BLOCKING_REGION_FD(func, arg) (long)rb_thread_io_blocking_region((func), (arg), (arg)->fd) - #define SockAddrStringValue(v) rsock_sockaddr_string_value(&(v)) #define SockAddrStringValuePtr(v) rsock_sockaddr_string_value_ptr(&(v)) #define SockAddrStringValueWithAddrinfo(v, rai_ret) rsock_sockaddr_string_value_with_addrinfo(&(v), &(rai_ret)) @@ -381,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(int fd, const struct sockaddr *sockaddr, int len, int socks, struct timeval *timeout); +int rsock_connect(VALUE self, const struct sockaddr *sockaddr, int len, int socks, struct timeval *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 c780d77cf6c317..c974aafe55102c 100644 --- a/ext/socket/socket.c +++ b/ext/socket/socket.c @@ -391,18 +391,17 @@ sock_connect(VALUE sock, VALUE addr) { VALUE rai; rb_io_t *fptr; - int fd, n; SockAddrStringValueWithAddrinfo(addr, rai); addr = rb_str_new4(addr); GetOpenFile(sock, fptr); - fd = fptr->fd; - n = rsock_connect(fd, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, NULL); - if (n < 0) { + + int result = rsock_connect(sock, (struct sockaddr*)RSTRING_PTR(addr), RSTRING_SOCKLEN(addr), 0, NULL); + if (result < 0) { rsock_sys_fail_raddrinfo_or_sockaddr("connect(2)", addr, rai); } - return INT2FIX(n); + return INT2FIX(result); } /* :nodoc: */ diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index 8aada76b26dc81..c5740d82856705 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -45,22 +45,18 @@ udp_init(int argc, VALUE *argv, VALUE sock) struct udp_arg { + VALUE io; struct rb_addrinfo *res; - rb_io_t *fptr; }; static VALUE udp_connect_internal(VALUE v) { struct udp_arg *arg = (void *)v; - rb_io_t *fptr; - int fd; struct addrinfo *res; - rb_io_check_closed(fptr = arg->fptr); - fd = fptr->fd; for (res = arg->res->ai; res; res = res->ai_next) { - if (rsock_connect(fd, res->ai_addr, res->ai_addrlen, 0, NULL) >= 0) { + if (rsock_connect(arg->io, res->ai_addr, res->ai_addrlen, 0, NULL) >= 0) { return Qtrue; } } @@ -84,16 +80,17 @@ udp_connect_internal(VALUE v) * */ static VALUE -udp_connect(VALUE sock, VALUE host, VALUE port) +udp_connect(VALUE self, VALUE host, VALUE port) { - struct udp_arg arg; - VALUE ret; + struct udp_arg arg = {.io = self}; + + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); + + int result = (int)rb_ensure(udp_connect_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); + if (!result) { + rsock_sys_fail_host_port("connect(2)", host, port); + } - GetOpenFile(sock, arg.fptr); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0); - ret = rb_ensure(udp_connect_internal, (VALUE)&arg, - rsock_freeaddrinfo, (VALUE)arg.res); - if (!ret) rsock_sys_fail_host_port("connect(2)", host, port); return INT2FIX(0); } @@ -101,14 +98,13 @@ static VALUE udp_bind_internal(VALUE v) { struct udp_arg *arg = (void *)v; - rb_io_t *fptr; - int fd; struct addrinfo *res; - rb_io_check_closed(fptr = arg->fptr); - fd = fptr->fd; + rb_io_t *fptr; + RB_IO_POINTER(arg->io, fptr); + for (res = arg->res->ai; res; res = res->ai_next) { - if (bind(fd, res->ai_addr, res->ai_addrlen) < 0) { + if (bind(fptr->fd, res->ai_addr, res->ai_addrlen) < 0) { continue; } return Qtrue; @@ -129,22 +125,23 @@ udp_bind_internal(VALUE v) * */ static VALUE -udp_bind(VALUE sock, VALUE host, VALUE port) +udp_bind(VALUE self, VALUE host, VALUE port) { - struct udp_arg arg; - VALUE ret; + struct udp_arg arg = {.io = self}; + + arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); + + int result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); + if (!result) { + rsock_sys_fail_host_port("bind(2)", host, port); + } - GetOpenFile(sock, arg.fptr); - arg.res = rsock_addrinfo(host, port, rsock_fd_family(arg.fptr->fd), SOCK_DGRAM, 0); - ret = rb_ensure(udp_bind_internal, (VALUE)&arg, - rsock_freeaddrinfo, (VALUE)arg.res); - if (!ret) rsock_sys_fail_host_port("bind(2)", host, port); return INT2FIX(0); } struct udp_send_arg { - struct rb_addrinfo *res; rb_io_t *fptr; + struct rb_addrinfo *res; struct rsock_send_arg sarg; }; @@ -156,7 +153,6 @@ udp_send_internal(VALUE v) struct addrinfo *res; rb_io_check_closed(fptr = arg->fptr); - for (res = arg->res->ai; res; res = res->ai_next) { retry: arg->sarg.fd = fptr->fd; diff --git a/ext/socket/unixsocket.c b/ext/socket/unixsocket.c index b08e3bb708c91d..11d7c0451fee96 100644 --- a/ext/socket/unixsocket.c +++ b/ext/socket/unixsocket.c @@ -14,15 +14,14 @@ struct unixsock_arg { struct sockaddr_un *sockaddr; socklen_t sockaddrlen; - int fd; + VALUE io; }; static VALUE unixsock_connect_internal(VALUE a) { struct unixsock_arg *arg = (struct unixsock_arg *)a; - return (VALUE)rsock_connect(arg->fd, (struct sockaddr*)arg->sockaddr, - arg->sockaddrlen, 0, NULL); + return (VALUE)rsock_connect(arg->io, (struct sockaddr*)arg->sockaddr, arg->sockaddrlen, 0, NULL); } static VALUE @@ -51,7 +50,7 @@ unixsock_path_value(VALUE path) } VALUE -rsock_init_unixsock(VALUE sock, VALUE path, int server) +rsock_init_unixsock(VALUE self, VALUE path, int server) { struct sockaddr_un sockaddr; socklen_t sockaddrlen; @@ -73,43 +72,46 @@ rsock_init_unixsock(VALUE sock, VALUE path, int server) rsock_sys_fail_path("socket(2)", path); } + VALUE io = rsock_init_sock(self, fd); + RB_IO_POINTER(io, fptr); + if (server) { status = bind(fd, (struct sockaddr*)&sockaddr, sockaddrlen); } else { - int prot; + int error_tag; struct unixsock_arg arg; arg.sockaddr = &sockaddr; arg.sockaddrlen = sockaddrlen; - arg.fd = fd; - status = (int)rb_protect(unixsock_connect_internal, (VALUE)&arg, &prot); - if (prot) { - close(fd); - rb_jump_tag(prot); + arg.io = io; + + status = (int)rb_protect(unixsock_connect_internal, (VALUE)&arg, &error_tag); + + if (error_tag) { + rb_io_close(io); + rb_jump_tag(error_tag); } } if (status < 0) { int e = errno; - close(fd); + rb_io_close(io); rsock_syserr_fail_path(e, "connect(2)", path); } if (server) { if (listen(fd, SOMAXCONN) < 0) { int e = errno; - close(fd); + rb_io_close(io); rsock_syserr_fail_path(e, "listen(2)", path); } } - rsock_init_sock(sock, fd); if (server) { - GetOpenFile(sock, fptr); fptr->pathv = rb_str_new_frozen(path); } - return sock; + return io; } /* @@ -125,9 +127,9 @@ rsock_init_unixsock(VALUE sock, VALUE path, int server) * */ static VALUE -unix_init(VALUE sock, VALUE path) +unix_init(VALUE self, VALUE path) { - return rsock_init_unixsock(sock, path, 0); + return rsock_init_unixsock(self, path, 0); } /* From 2c3d26cfd70277954e12e3af15432aadff8ad21f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 11 Oct 2024 16:37:51 +0900 Subject: [PATCH 004/140] Fix an implicit conversion that loses integer precision Both `rb_ensure` and `udp_bind_internal` return `VALUE`. --- ext/socket/udpsocket.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/socket/udpsocket.c b/ext/socket/udpsocket.c index c5740d82856705..c31e9dbf6facac 100644 --- a/ext/socket/udpsocket.c +++ b/ext/socket/udpsocket.c @@ -131,7 +131,7 @@ udp_bind(VALUE self, VALUE host, VALUE port) arg.res = rsock_addrinfo(host, port, rsock_fd_family(rb_io_descriptor(self)), SOCK_DGRAM, 0); - int result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); + VALUE result = rb_ensure(udp_bind_internal, (VALUE)&arg, rsock_freeaddrinfo, (VALUE)arg.res); if (!result) { rsock_sys_fail_host_port("bind(2)", host, port); } From b4eb7e2281d2abf3c33a4e9bc8a096be287970c1 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 10 Oct 2024 15:11:47 +0900 Subject: [PATCH 005/140] Refine assertion failure message --- vm_method.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/vm_method.c b/vm_method.c index e436538f566b57..fb6fe3b5933bfa 100644 --- a/vm_method.c +++ b/vm_method.c @@ -759,9 +759,11 @@ static rb_method_entry_t * rb_method_entry_alloc(ID called_id, VALUE owner, VALUE defined_class, rb_method_definition_t *def, bool complement) { if (def) method_definition_addref(def, complement); - VM_ASSERT(!defined_class || - NIL_P(defined_class) || // negative cache - RB_TYPE_P(defined_class, T_CLASS) || RB_TYPE_P(defined_class, T_ICLASS)); + if (RTEST(defined_class)) { + // not negative cache + VM_ASSERT(RB_TYPE_P(defined_class, T_CLASS) || RB_TYPE_P(defined_class, T_ICLASS), + "defined_class: %s", rb_obj_info(defined_class)); + } rb_method_entry_t *me = IMEMO_NEW(rb_method_entry_t, imemo_ment, defined_class); *((rb_method_definition_t **)&me->def) = def; me->called_id = called_id; @@ -1083,8 +1085,6 @@ rb_method_entry_make(VALUE klass, ID mid, VALUE defined_class, rb_method_visibil return me; } -static rb_method_entry_t *rb_method_entry_alloc(ID called_id, VALUE owner, VALUE defined_class, rb_method_definition_t *def, bool refined); - static st_table * overloaded_cme_table(void) { From 01abc2f79e770df5e6cc75a4610f4831ff0b5bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 10 Oct 2024 22:00:05 +0200 Subject: [PATCH 006/140] [rubygems/rubygems] Check that the binstub was actually reset https://github.com/rubygems/rubygems/commit/d46fca6126 --- test/rubygems/test_gem_commands_pristine_command.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/rubygems/test_gem_commands_pristine_command.rb b/test/rubygems/test_gem_commands_pristine_command.rb index 2a715921b33d32..46c06db014db47 100644 --- a/test/rubygems/test_gem_commands_pristine_command.rb +++ b/test/rubygems/test_gem_commands_pristine_command.rb @@ -655,6 +655,8 @@ def test_execute_default_gem @ui.output.split("\n") ) assert_empty(@ui.error) + + refute_includes "ruby_executable_hooks", File.read(exe) end def test_execute_multi_platform From aad4bfd7bc1170394fcedf1997d644f8374ad7b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Wed, 9 Oct 2024 09:13:49 +0200 Subject: [PATCH 007/140] [rubygems/rubygems] Fix `gem install` on NFS shares NFS shares seem to support flock these days, but they need read-write permissions. https://github.com/rubygems/rubygems/commit/1c492804cd --- lib/bundler/rubygems_ext.rb | 5 ++--- lib/rubygems.rb | 5 ++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/bundler/rubygems_ext.rb b/lib/bundler/rubygems_ext.rb index 14f10af9d75d71..547102be41c22b 100644 --- a/lib/bundler/rubygems_ext.rb +++ b/lib/bundler/rubygems_ext.rb @@ -36,15 +36,14 @@ class << self remove_method :open_file_with_flock if Gem.respond_to?(:open_file_with_flock) def open_file_with_flock(path, &block) - mode = IO::RDONLY | IO::APPEND | IO::CREAT | IO::BINARY + # read-write mode is used rather than read-only in order to support NFS + mode = IO::RDWR | IO::APPEND | IO::CREAT | IO::BINARY mode |= IO::SHARE_DELETE if IO.const_defined?(:SHARE_DELETE) File.open(path, mode) do |io| begin io.flock(File::LOCK_EX) rescue Errno::ENOSYS, Errno::ENOTSUP - rescue Errno::ENOLCK # NFS - raise unless Thread.main == Thread.current end yield io end diff --git a/lib/rubygems.rb b/lib/rubygems.rb index 43c0d8f13c6b4d..8c3e8bdc68bd32 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -808,15 +808,14 @@ def self.open_file_with_lock(path, &block) # Open a file with given flags, and protect access with flock def self.open_file_with_flock(path, &block) - mode = IO::RDONLY | IO::APPEND | IO::CREAT | IO::BINARY + # read-write mode is used rather than read-only in order to support NFS + mode = IO::RDWR | IO::APPEND | IO::CREAT | IO::BINARY mode |= IO::SHARE_DELETE if IO.const_defined?(:SHARE_DELETE) File.open(path, mode) do |io| begin io.flock(File::LOCK_EX) rescue Errno::ENOSYS, Errno::ENOTSUP - rescue Errno::ENOLCK # NFS - raise unless Thread.main == Thread.current end yield io end From 047a7750d1064c9d13811f3498325f4d2101b681 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Fri, 11 Oct 2024 18:55:57 +0900 Subject: [PATCH 008/140] [ruby/time] [DOC] Escape the word "Date" that does not mean Date class https://github.com/ruby/time/commit/933eccf8d9 --- lib/time.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/time.rb b/lib/time.rb index 7cb3a0948282b8..c06e97e74fb1f8 100644 --- a/lib/time.rb +++ b/lib/time.rb @@ -407,7 +407,7 @@ def parse(date, now=self.now) # %c :: The preferred local date and time representation # %C :: Century (20 in 2009) # %d :: Day of the month (01..31) - # %D :: Date (%m/%d/%y) + # %D :: \Date (%m/%d/%y) # %e :: Day of the month, blank-padded ( 1..31) # %F :: Equivalent to %Y-%m-%d (the ISO 8601 date format) # %g :: The last two digits of the commercial year From d641b7d172a715b4407218de250d93e6a2f5e158 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 10 Oct 2024 11:09:53 -0400 Subject: [PATCH 009/140] Improve RUBY_GC_LIBRARY Instead of passing the full GC SO file name to RUBY_GC_LIBRARY, we now only need to pass the GC name. For example, before we needed to pass `RUBY_GC_LIBRARY=librubygc.default.so` but now we only need to pass `RUBY_GC_LIBRARY=default`. --- .github/workflows/ubuntu.yml | 2 +- configure.ac | 2 ++ gc.c | 22 ++++++++++++++++------ 3 files changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 750944e754ed30..7587762d8cc52a 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -99,7 +99,7 @@ jobs: - name: Build shared GC run: | - echo "RUBY_GC_LIBRARY=librubygc.default.so" >> $GITHUB_ENV + echo "RUBY_GC_LIBRARY=default" >> $GITHUB_ENV make shared-gc SHARED_GC=default if: ${{ matrix.shared_gc }} diff --git a/configure.ac b/configure.ac index ca578f8e9f4246..21fa34e307bef8 100644 --- a/configure.ac +++ b/configure.ac @@ -3403,6 +3403,8 @@ AC_DEFINE_UNQUOTED(DLEXT_MAXLEN, `expr $len + 1`) test ".$DLEXT" = "." || AC_DEFINE_UNQUOTED(DLEXT, ".$DLEXT") AC_SUBST(DLEXT) +AC_DEFINE_UNQUOTED(SOEXT, ".$SOEXT") + : "strip" && { AC_MSG_CHECKING([for $STRIP flags]) AC_LINK_IFELSE([AC_LANG_PROGRAM], [AS_IF( diff --git a/gc.c b/gc.c index 7b6decf3d2a6c5..c2889a78c25f8f 100644 --- a/gc.c +++ b/gc.c @@ -659,7 +659,7 @@ ruby_external_gc_init(void) char *gc_so_path = NULL; void *handle = NULL; if (gc_so_file) { - /* Check to make sure that gc_so_file matches /[\w-_.]+/ so that it does + /* Check to make sure that gc_so_file matches /[\w-_]+/ so that it does * not load a shared object outside of the directory. */ for (size_t i = 0; i < strlen(gc_so_file); i++) { char c = gc_so_file[i]; @@ -667,17 +667,27 @@ ruby_external_gc_init(void) switch (c) { case '-': case '_': - case '.': break; default: - fprintf(stderr, "Only alphanumeric, dash, underscore, and period is allowed in "RUBY_GC_LIBRARY"\n"); + fprintf(stderr, "Only alphanumeric, dash, and underscore is allowed in "RUBY_GC_LIBRARY"\n"); exit(1); } } - gc_so_path = alloca(strlen(SHARED_GC_DIR) + strlen(gc_so_file) + 1); - strcpy(gc_so_path, SHARED_GC_DIR); - strcpy(gc_so_path + strlen(SHARED_GC_DIR), gc_so_file); + size_t gc_so_path_size = strlen(SHARED_GC_DIR "librubygc." SOEXT) + strlen(gc_so_file) + 1; + gc_so_path = alloca(gc_so_path_size); + { + size_t gc_so_path_idx = 0; +#define GC_SO_PATH_APPEND(str) do { \ + gc_so_path_idx += strlcpy(gc_so_path + gc_so_path_idx, str, gc_so_path_size - gc_so_path_idx); \ +} while (0) + GC_SO_PATH_APPEND(SHARED_GC_DIR); + GC_SO_PATH_APPEND("librubygc."); + GC_SO_PATH_APPEND(gc_so_file); + GC_SO_PATH_APPEND(SOEXT); + GC_ASSERT(gc_so_path_idx == gc_so_path_size - 1); +#undef GC_SO_PATH_APPEND + } handle = dlopen(gc_so_path, RTLD_LAZY | RTLD_GLOBAL); if (!handle) { From df2fb8802800ada0cf709cdd0ad9abe0a6b238a3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 10 Oct 2024 15:24:17 -0400 Subject: [PATCH 010/140] Remove unused SOEXT macro in ruby-runner.h --- template/ruby-runner.h.in | 1 - 1 file changed, 1 deletion(-) diff --git a/template/ruby-runner.h.in b/template/ruby-runner.h.in index fdfe88dcb9e225..83fb0b4e87e55f 100644 --- a/template/ruby-runner.h.in +++ b/template/ruby-runner.h.in @@ -6,4 +6,3 @@ #define PATH_SEP '@PATH_SEPARATOR@' #define EXTOUT "@EXTOUT@" #define ARCH "@arch@" -#define SOEXT "@SOEXT@" From 372bb990ac5ea8e348b9187b53d108d06049e6a3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 10 Oct 2024 15:25:59 -0400 Subject: [PATCH 011/140] Remove unused PRELOADENV macro in ruby-runner.h --- template/ruby-runner.h.in | 1 - 1 file changed, 1 deletion(-) diff --git a/template/ruby-runner.h.in b/template/ruby-runner.h.in index 83fb0b4e87e55f..b94086c5652c7a 100644 --- a/template/ruby-runner.h.in +++ b/template/ruby-runner.h.in @@ -1,7 +1,6 @@ #define ABS_SRCDIR "@abs_srcdir@" #define BUILDDIR "@abs_top_builddir@" #define LIBPATHENV "@LIBPATHENV@" -#define PRELOADENV "@PRELOADENV@" #define PATH_SEPARATOR "@PATH_SEPARATOR@" #define PATH_SEP '@PATH_SEPARATOR@' #define EXTOUT "@EXTOUT@" From 11e7ab79deb5935573d15ba4f33fc41e5c2b7522 Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 11 Oct 2024 10:22:44 -0400 Subject: [PATCH 012/140] Remove 1 allocation in Enumerable#each_with_index (#11868) * Remove 1 allocation in Enumerable#each_with_index Previously, each call to Enumerable#each_with_index allocates 2 objects, one for the counting index, the other an imemo_ifunc passed to `self.each` as a block. Use `struct vm_ifunc::data` to hold the counting index directly to remove 1 allocation. * [DOC] Brief summary for usages of `struct vm_ifunc` --- enum.c | 14 +++++--------- internal/imemo.h | 7 ++++++- internal/vm.h | 1 + vm_eval.c | 11 +++++++++++ 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/enum.c b/enum.c index 6aec34d8500c35..d8a7cb73f3f707 100644 --- a/enum.c +++ b/enum.c @@ -2984,13 +2984,12 @@ enum_member(VALUE obj, VALUE val) } static VALUE -each_with_index_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, memo)) +each_with_index_i(RB_BLOCK_CALL_FUNC_ARGLIST(_, index)) { - struct MEMO *m = MEMO_CAST(memo); - VALUE n = imemo_count_value(m); + struct vm_ifunc *ifunc = rb_current_ifunc(); + ifunc->data = (const void *)rb_int_succ(index); - imemo_count_up(m); - return rb_yield_values(2, rb_enum_values_pack(argc, argv), n); + return rb_yield_values(2, rb_enum_values_pack(argc, argv), index); } /* @@ -3024,12 +3023,9 @@ each_with_index_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, memo)) static VALUE enum_each_with_index(int argc, VALUE *argv, VALUE obj) { - struct MEMO *memo; - RETURN_SIZED_ENUMERATOR(obj, argc, argv, enum_size); - memo = MEMO_NEW(0, 0, 0); - rb_block_call(obj, id_each, argc, argv, each_with_index_i, (VALUE)memo); + rb_block_call(obj, id_each, argc, argv, each_with_index_i, INT2FIX(0)); return obj; } diff --git a/internal/imemo.h b/internal/imemo.h index 7420909a140546..cb0a52851849a3 100644 --- a/internal/imemo.h +++ b/internal/imemo.h @@ -79,7 +79,12 @@ struct vm_ifunc_argc { #endif }; -/*! IFUNC (Internal FUNCtion) */ +/*! IFUNC (Internal FUNCtion) + * + * Bookkeeping for converting a C function and some closed-over data into a + * block passable to methods. Like Ruby Proc, but not directly accessible at + * Ruby level since this is an imemo. See rb_block_call() and friends. + */ struct vm_ifunc { VALUE flags; VALUE *svar_lep; diff --git a/internal/vm.h b/internal/vm.h index 0b6b92c2799b09..c30e26a8b4a7d8 100644 --- a/internal/vm.h +++ b/internal/vm.h @@ -77,6 +77,7 @@ VALUE rb_lambda_call(VALUE obj, ID mid, int argc, const VALUE *argv, void rb_check_stack_overflow(void); #define RB_BLOCK_NO_USE_PACKED_ARGS 2 VALUE rb_block_call2(VALUE obj, ID mid, int argc, const VALUE *argv, rb_block_call_func_t bl_proc, VALUE data2, long flags); +struct vm_ifunc *rb_current_ifunc(void); #if USE_YJIT /* vm_exec.c */ diff --git a/vm_eval.c b/vm_eval.c index ff8e2e6780ffd7..469b89c03ac7e5 100644 --- a/vm_eval.c +++ b/vm_eval.c @@ -2702,6 +2702,17 @@ rb_current_realfilepath(void) return Qnil; } +// Assert that an internal function is running and return +// the imemo object that represents it. +struct vm_ifunc * +rb_current_ifunc(void) +{ + // Search VM_FRAME_MAGIC_IFUNC to see ifunc imemos put on the iseq field. + VALUE ifunc = (VALUE)GET_EC()->cfp->iseq; + RUBY_ASSERT_ALWAYS(imemo_type_p(ifunc, imemo_ifunc)); + return (struct vm_ifunc *)ifunc; +} + void Init_vm_eval(void) { From a9fb0a20832f71d3240d43cf22e4655bd3a8c2ab Mon Sep 17 00:00:00 2001 From: Alan Wu Date: Fri, 11 Oct 2024 10:24:47 -0400 Subject: [PATCH 013/140] YJIT: Improve build instructions for non-hackers (#11878) --- doc/yjit/yjit.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/doc/yjit/yjit.md b/doc/yjit/yjit.md index 906a827d51d1ba..d57d5dceda43e5 100644 --- a/doc/yjit/yjit.md +++ b/doc/yjit/yjit.md @@ -64,12 +64,19 @@ You can change how much executable memory is allocated using [YJIT's command-lin ### Requirements You will need to install: -- A C compiler such as GCC or Clang -- GNU Make and Autoconf -- The Rust compiler `rustc` and Cargo (if you want to build in dev/debug mode) - - The Rust version must be [>= 1.58.0](../../yjit/Cargo.toml). -To install the Rust build toolchain, we suggest following the [recommended installation method][rust-install]. Rust also provides first class [support][editor-tools] for many source code editors. + - All the usual build tools for Ruby. See [Building Ruby](../contributing/building_ruby.md) + - The Rust compiler `rustc` + - The Rust version must be [>= 1.58.0](../../yjit/Cargo.toml). + - Optionally, only if you wish to build in dev/debug mode, Rust's `cargo` + +If you don't intend on making code changes to YJIT itself, we recommend +obtaining `rustc` through your OS's package manager since that +likely reuses the same vendor which provides the C toolchain. + +If you will be changing YJIT's Rust code, we suggest using the +[first-party installation method][rust-install] for Rust. Rust also provides +first class [support][editor-tools] for many source code editors. [rust-install]: https://www.rust-lang.org/tools/install [editor-tools]: https://www.rust-lang.org/tools From c51947671ee338a66f299f17f7713c654cf47745 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 11 Oct 2024 10:12:12 -0500 Subject: [PATCH 014/140] [DOC] Tweaks for Array#repeated_permutation (#11873) --- array.c | 71 ++++++++++++++++++--------------------------------------- 1 file changed, 22 insertions(+), 49 deletions(-) diff --git a/array.c b/array.c index 2a2893cffeec35..643a6352538fbd 100644 --- a/array.c +++ b/array.c @@ -7213,68 +7213,41 @@ rb_ary_repeated_permutation_size(VALUE ary, VALUE args, VALUE eobj) /* * call-seq: - * array.repeated_permutation(n) {|permutation| ... } -> self - * array.repeated_permutation(n) -> new_enumerator + * repeated_permutation(size) {|permutation| ... } -> self + * repeated_permutation(size) -> new_enumerator * - * Calls the block with each repeated permutation of length +n+ of the elements of +self+; - * each permutation is an +Array+; + * With a block given, calls the block with each repeated permutation of length +size+ + * of the elements of +self+; + * each permutation is an array; * returns +self+. The order of the permutations is indeterminate. * - * When a block and a positive Integer argument +n+ are given, calls the block with each - * +n+-tuple repeated permutation of the elements of +self+. - * The number of permutations is self.size**n. - * - * +n+ = 1: - * - * a = [0, 1, 2] - * a.repeated_permutation(1) {|permutation| p permutation } - * - * Output: - * - * [0] - * [1] - * [2] - * - * +n+ = 2: - * - * a.repeated_permutation(2) {|permutation| p permutation } + * If a positive integer argument +size+ is given, + * calls the block with each +size+-tuple repeated permutation of the elements of +self+. + * The number of permutations is self.size**size. * - * Output: + * Examples: * - * [0, 0] - * [0, 1] - * [0, 2] - * [1, 0] - * [1, 1] - * [1, 2] - * [2, 0] - * [2, 1] - * [2, 2] + * - +size+ is 1: * - * If +n+ is zero, calls the block once with an empty +Array+. + * p = [] + * [0, 1, 2].repeated_permutation(1) {|permutation| p.push(permutation) } + * p # => [[0], [1], [2]] * - * If +n+ is negative, does not call the block: + * - +size+ is 2: * - * a.repeated_permutation(-1) {|permutation| fail 'Cannot happen' } + * p = [] + * [0, 1, 2].repeated_permutation(2) {|permutation| p.push(permutation) } + * p # => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] * - * Returns a new Enumerator if no block given: + * If +size+ is zero, calls the block once with an empty array. * - * a = [0, 1, 2] - * a.repeated_permutation(2) # => # + * If +size+ is negative, does not call the block: * - * Using Enumerators, it's convenient to show the permutations and counts - * for some values of +n+: + * [0, 1, 2].repeated_permutation(-1) {|permutation| fail 'Cannot happen' } * - * e = a.repeated_permutation(0) - * e.size # => 1 - * e.to_a # => [[]] - * e = a.repeated_permutation(1) - * e.size # => 3 - * e.to_a # => [[0], [1], [2]] - * e = a.repeated_permutation(2) - * e.size # => 9 - * e.to_a # => [[0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], [2, 0], [2, 1], [2, 2]] + * With no block given, returns a new Enumerator. * + * Related: see {Methods for Combining}[rdoc-ref:Array@Methods+for+Combining]. */ static VALUE rb_ary_repeated_permutation(VALUE ary, VALUE num) From c044777562c72dd66d906a774d944208576e5ea2 Mon Sep 17 00:00:00 2001 From: BurdetteLamar Date: Thu, 10 Oct 2024 15:43:41 -0500 Subject: [PATCH 015/140] [DOC] Tweaks for Array#rotate --- array.c | 44 ++++++++++++++------------------------------ 1 file changed, 14 insertions(+), 30 deletions(-) diff --git a/array.c b/array.c index 643a6352538fbd..275ac006c25c63 100644 --- a/array.c +++ b/array.c @@ -3240,51 +3240,35 @@ rb_ary_rotate_bang(int argc, VALUE *argv, VALUE ary) /* * call-seq: - * array.rotate -> new_array - * array.rotate(count) -> new_array + * rotate(count = 1) -> new_array * - * Returns a new +Array+ formed from +self+ with elements + * Returns a new array formed from +self+ with elements * rotated from one end to the other. * - * When no argument given, returns a new +Array+ that is like +self+, - * except that the first element has been rotated to the last position: + * With non-negative numeric +count+, + * rotates elements from the beginning to the end: * - * a = [:foo, 'bar', 2, 'bar'] - * a1 = a.rotate - * a1 # => ["bar", 2, "bar", :foo] - * - * When given a non-negative Integer +count+, - * returns a new +Array+ with +count+ elements rotated from the beginning to the end: - * - * a = [:foo, 'bar', 2] - * a1 = a.rotate(2) - * a1 # => [2, :foo, "bar"] + * [0, 1, 2, 3].rotate(2) # => [2, 3, 0, 1] + * [0, 1, 2, 3].rotate(2.1) # => [2, 3, 0, 1] * * If +count+ is large, uses count % array.size as the count: * - * a = [:foo, 'bar', 2] - * a1 = a.rotate(20) - * a1 # => [2, :foo, "bar"] + * [0, 1, 2, 3].rotate(22) # => [2, 3, 0, 1] * - * If +count+ is zero, returns a copy of +self+, unmodified: + * With a +count+ of zero, rotates no elements: * - * a = [:foo, 'bar', 2] - * a1 = a.rotate(0) - * a1 # => [:foo, "bar", 2] + * [0, 1, 2, 3].rotate(0) # => [0, 1, 2, 3] * - * When given a negative Integer +count+, rotates in the opposite direction, - * from end to beginning: + * With negative numeric +count+, rotates in the opposite direction, + * from the end to the beginning: * - * a = [:foo, 'bar', 2] - * a1 = a.rotate(-2) - * a1 # => ["bar", 2, :foo] + * [0, 1, 2, 3].rotate(-1) # => [3, 0, 1, 2] * * If +count+ is small (far from zero), uses count % array.size as the count: * - * a = [:foo, 'bar', 2] - * a1 = a.rotate(-5) - * a1 # => ["bar", 2, :foo] + * [0, 1, 2, 3].rotate(-21) # => [3, 0, 1, 2] * + * Related: see {Methods for Fetching}[rdoc-ref:Array@Methods+for+Fetching]. */ static VALUE From 5e799cc182fdab41b696b0dea036751d621ca4f5 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Sat, 12 Oct 2024 01:16:05 +1000 Subject: [PATCH 016/140] Fix spelling --- .github/actions/launchable/setup/action.yml | 2 +- doc/date/calendars.rdoc | 2 +- yjit/src/core.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/launchable/setup/action.yml b/.github/actions/launchable/setup/action.yml index 4b469ccd64fcc4..2d92cb7d9967c1 100644 --- a/.github/actions/launchable/setup/action.yml +++ b/.github/actions/launchable/setup/action.yml @@ -35,7 +35,7 @@ inputs: required: false default: ${{ github.workspace }} description: >- - Directory to (re-)checkout source codes. Launchable retrives the commit information + Directory to (re-)checkout source codes. Launchable retrieves the commit information from the directory. launchable-workspace: diff --git a/doc/date/calendars.rdoc b/doc/date/calendars.rdoc index b8690841b1d45c..2f5fe038c7c926 100644 --- a/doc/date/calendars.rdoc +++ b/doc/date/calendars.rdoc @@ -18,7 +18,7 @@ The reasons for the difference are religious/political histories. - On October 15, 1582, several countries changed from the Julian calendar to the Gregorian calendar; these included Italy, Poland, Portugal, and Spain. - Other contries in the Western world retained the Julian calendar. + Other countries in the Western world retained the Julian calendar. - On September 14, 1752, most of the British empire changed from the Julian calendar to the Gregorian calendar. diff --git a/yjit/src/core.rs b/yjit/src/core.rs index 732c5cbe2f1fc2..32bce76a015219 100644 --- a/yjit/src/core.rs +++ b/yjit/src/core.rs @@ -4014,7 +4014,7 @@ unsafe fn remove_from_graph(blockref: BlockRef) { /// Tear down a block and deallocate it. /// Caller has to ensure that the code tracked by the block is not -/// running, as running code may hit [branch_stub_hit] who exepcts +/// running, as running code may hit [branch_stub_hit] who expects /// [Branch] to be live. /// /// We currently ensure this through the `jit_cont` system in cont.c From 628da153bb184285aee864b4548f974e547d839d Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 11 Oct 2024 10:25:58 -0500 Subject: [PATCH 017/140] [DOC] Tweaks for Array#sample (#11876) --- array.rb | 51 ++++++++++++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 17 deletions(-) diff --git a/array.rb b/array.rb index 866d8877c7ca9c..3d64d31e95ee3e 100644 --- a/array.rb +++ b/array.rb @@ -73,35 +73,52 @@ def shuffle(random: Random) end # call-seq: - # array.sample(random: Random) -> object - # array.sample(n, random: Random) -> new_ary + # sample(random: Random) -> object + # sample(count, random: Random) -> new_ary # - # Returns random elements from +self+. + # Returns random elements from +self+, + # as selected by the object given by keyword argument +random+. # - # When no arguments are given, returns a random element from +self+: - # a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + # With no argument +count+ given, returns one random element from +self+: + # + # a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] # a.sample # => 3 # a.sample # => 8 - # If +self+ is empty, returns +nil+. # - # When argument +n+ is given, returns a new +Array+ containing +n+ random - # elements from +self+: + # Returns +nil+ if +self+ is empty: + # + # [].sample # => nil + # + # + # With non-negative numeric argument +count+ given, + # returns a new array containing +count+ random elements from +self+: + # # a.sample(3) # => [8, 9, 2] - # a.sample(6) # => [9, 6, 10, 3, 1, 4] + # a.sample(6) # => [9, 6, 0, 3, 1, 4] + # + # The order of the result array is unrelated to the order of +self+. + # + # Returns a new empty +Array+ if +self+ is empty: + # + # [].sample(4) # => [] + # + # May return duplicates in +self+: + # + # a = [1, 1, 1, 2, 2, 3] + # a.sample(a.size) # => [1, 1, 3, 2, 1, 2] + # # Returns no more than a.size elements # (because no new duplicates are introduced): - # a.sample(a.size * 2) # => [6, 4, 1, 8, 5, 9, 10, 2, 3, 7] - # But +self+ may contain duplicates: - # a = [1, 1, 1, 2, 2, 3] - # a.sample(a.size * 2) # => [1, 1, 3, 2, 1, 2] - # The argument +n+ must be a non-negative numeric value. - # The order of the result array is unrelated to the order of +self+. - # Returns a new empty +Array+ if +self+ is empty. # - # The optional +random+ argument will be used as the random number generator: + # a.sample(50) # => [6, 4, 1, 8, 5, 9, 0, 2, 3, 7] + # + # The object given with keyword argument +random+ is used as the random number generator: + # # a = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] # a.sample(random: Random.new(1)) #=> 6 # a.sample(4, random: Random.new(1)) #=> [6, 10, 9, 2] + # + # Related: see {Methods for Fetching}[rdoc-ref:Array@Methods+for+Fetching]. def sample(n = (ary = false), random: Random) if Primitive.mandatory_only? # Primitive.cexpr! %{ rb_ary_sample(self, rb_cRandom, Qfalse, Qfalse) } From 77c7d88015aa7454f2c34d54ec313dc4dc464ae0 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Fri, 11 Oct 2024 10:30:52 -0500 Subject: [PATCH 018/140] [DOC] Tweaks for Array#rotate! (#11875) --- array.c | 36 +++++++++++------------------------- 1 file changed, 11 insertions(+), 25 deletions(-) diff --git a/array.c b/array.c index 275ac006c25c63..b6768e52d3b4c7 100644 --- a/array.c +++ b/array.c @@ -3186,48 +3186,34 @@ rb_ary_rotate(VALUE ary, long cnt) /* * call-seq: - * array.rotate! -> self - * array.rotate!(count) -> self + * rotate!(count = 1) -> self * * Rotates +self+ in place by moving elements from one end to the other; returns +self+. * - * When no argument given, rotates the first element to the last position: - * - * a = [:foo, 'bar', 2, 'bar'] - * a.rotate! # => ["bar", 2, "bar", :foo] - * - * When given a non-negative Integer +count+, + * With non-negative numeric +count+, * rotates +count+ elements from the beginning to the end: * - * a = [:foo, 'bar', 2] - * a.rotate!(2) - * a # => [2, :foo, "bar"] + * [0, 1, 2, 3].rotate!(2) # => [2, 3, 0, 1] + [0, 1, 2, 3].rotate!(2.1) # => [2, 3, 0, 1] * * If +count+ is large, uses count % array.size as the count: * - * a = [:foo, 'bar', 2] - * a.rotate!(20) - * a # => [2, :foo, "bar"] + * [0, 1, 2, 3].rotate!(21) # => [1, 2, 3, 0] * - * If +count+ is zero, returns +self+ unmodified: + * If +count+ is zero, rotates no elements: * - * a = [:foo, 'bar', 2] - * a.rotate!(0) - * a # => [:foo, "bar", 2] + * [0, 1, 2, 3].rotate!(0) # => [0, 1, 2, 3] * - * When given a negative Integer +count+, rotates in the opposite direction, + * With a negative numeric +count+, rotates in the opposite direction, * from end to beginning: * - * a = [:foo, 'bar', 2] - * a.rotate!(-2) - * a # => ["bar", 2, :foo] + * [0, 1, 2, 3].rotate!(-1) # => [3, 0, 1, 2] * * If +count+ is small (far from zero), uses count % array.size as the count: * - * a = [:foo, 'bar', 2] - * a.rotate!(-5) - * a # => ["bar", 2, :foo] + * [0, 1, 2, 3].rotate!(-21) # => [3, 0, 1, 2] * + * Related: see {Methods for Assigning}[rdoc-ref:Array@Methods+for+Assigning]. */ static VALUE From 2dab59933c469e50e66ac2adc2d9bb73554e2e21 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Thu, 10 Oct 2024 11:52:33 -0400 Subject: [PATCH 019/140] Remove defined check for GC.config in test_gc.rb GC.config is always defined. --- test/ruby/test_gc.rb | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/ruby/test_gc.rb b/test/ruby/test_gc.rb index faa0b1f89365a8..7c49f0f5bb9a4c 100644 --- a/test/ruby/test_gc.rb +++ b/test/ruby/test_gc.rb @@ -53,22 +53,16 @@ def test_enable_disable end def test_gc_config_full_mark_by_default - omit "unsupported platform/GC" unless defined?(GC.config) - config = GC.config assert_not_empty(config) assert_true(config[:rgengc_allow_full_mark]) end def test_gc_config_invalid_args - omit "unsupported platform/GC" unless defined?(GC.config) - assert_raise(ArgumentError) { GC.config(0) } end def test_gc_config_setting_returns_updated_config_hash - omit "unsupported platform/GC" unless defined?(GC.config) - old_value = GC.config[:rgengc_allow_full_mark] assert_true(old_value) @@ -82,8 +76,6 @@ def test_gc_config_setting_returns_updated_config_hash end def test_gc_config_setting_returns_nil_for_missing_keys - omit "unsupported platform/GC" unless defined?(GC.config) - missing_value = GC.config(no_such_key: true)[:no_such_key] assert_nil(missing_value) ensure @@ -92,8 +84,6 @@ def test_gc_config_setting_returns_nil_for_missing_keys end def test_gc_config_disable_major - omit "unsupported platform/GC" unless defined?(GC.config) - GC.enable GC.start @@ -116,8 +106,6 @@ def test_gc_config_disable_major end def test_gc_config_disable_major_gc_start_always_works - omit "unsupported platform/GC" unless defined?(GC.config) - GC.config(full_mark: false) major_count = GC.stat[:major_gc_count] From eccfb6e60c2dba67ce5b1315a07598c2ba8b1ffb Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Fri, 11 Oct 2024 18:34:15 +0200 Subject: [PATCH 020/140] [ruby/irb] History refactors (https://github.com/ruby/irb/pull/1013) * Extract logic save_history in separate helper * Extract logic history_file in helper * Allow for readonly history https://github.com/ruby/irb/commit/52307f9026 --- lib/irb.rb | 8 ++- lib/irb/history.rb | 144 ++++++++++++++++++++++++--------------- test/irb/test_history.rb | 17 ++++- 3 files changed, 110 insertions(+), 59 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 213e231174423f..7e91b0f64507e1 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -14,6 +14,7 @@ require_relative "irb/ruby-lex" require_relative "irb/statement" +require_relative "irb/history" require_relative "irb/input-method" require_relative "irb/locale" require_relative "irb/color" @@ -972,7 +973,7 @@ def debug_readline(binding) # debugger. input = nil forced_exit = catch(:IRB_EXIT) do - if IRB.conf[:SAVE_HISTORY] && context.io.support_history_saving? + if History.save_history? && context.io.support_history_saving? # Previous IRB session's history has been saved when `Irb#run` is exited We need # to make sure the saved history is not saved again by resetting the counter context.io.reset_history_counter @@ -1003,9 +1004,10 @@ def run(conf = IRB.conf) prev_context = conf[:MAIN_CONTEXT] conf[:MAIN_CONTEXT] = context - save_history = !in_nested_session && conf[:SAVE_HISTORY] && context.io.support_history_saving? + load_history = !in_nested_session && context.io.support_history_saving? + save_history = load_history && History.save_history? - if save_history + if load_history context.io.load_history end diff --git a/lib/irb/history.rb b/lib/irb/history.rb index 685354b2d8400b..a428003d2de946 100644 --- a/lib/irb/history.rb +++ b/lib/irb/history.rb @@ -1,6 +1,42 @@ require "pathname" module IRB + 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 + end + + def save_history? + !save_history.zero? + end + + def infinite? + save_history.negative? + end + + # Might be nil when HOME and XDG_CONFIG_HOME are not available. + def history_file + if (history_file = IRB.conf[:HISTORY_FILE]) + File.expand_path(history_file) + else + 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 + module HistorySavingAbility # :nodoc: def support_history_saving? true @@ -11,76 +47,74 @@ def reset_history_counter end def load_history + history_file = History.history_file + return unless File.exist?(history_file.to_s) + history = self.class::HISTORY - if history_file = IRB.conf[:HISTORY_FILE] - history_file = File.expand_path(history_file) - end - history_file = IRB.rc_file("_history") unless history_file - if history_file && File.exist?(history_file) - File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| - f.each { |l| - l = l.chomp - if self.class == RelineInputMethod and history.last&.end_with?("\\") - history.last.delete_suffix!("\\") - history.last << "\n" << l - else - history << l - end - } - end - @loaded_history_lines = history.size - @loaded_history_mtime = File.mtime(history_file) + File.open(history_file, "r:#{IRB.conf[:LC_MESSAGES].encoding}") do |f| + f.each { |l| + l = l.chomp + if self.class == RelineInputMethod and history.last&.end_with?("\\") + history.last.delete_suffix!("\\") + history.last << "\n" << l + else + history << l + end + } end + @loaded_history_lines = history.size + @loaded_history_mtime = File.mtime(history_file) end def save_history + return unless History.save_history? + return unless (history_file = History.history_file) + unless ensure_history_file_writable(history_file) + warn <<~WARN + Can't write history to #{History.history_file.inspect} due to insufficient permissions. + Please verify the value of `IRB.conf[:HISTORY_FILE]`. Ensure the folder exists and that both the folder and file (if it exists) are writable. + WARN + return + end + history = self.class::HISTORY.to_a - if num = IRB.conf[:SAVE_HISTORY] and (num = num.to_i) != 0 - if history_file = IRB.conf[:HISTORY_FILE] - history_file = File.expand_path(history_file) - end - history_file = IRB.rc_file("_history") unless history_file + if File.exist?(history_file) && + File.mtime(history_file) != @loaded_history_mtime + history = history[@loaded_history_lines..-1] if @loaded_history_lines + append_history = true + end - # When HOME and XDG_CONFIG_HOME are not available, history_file might be nil - return unless history_file + File.open(history_file, (append_history ? "a" : "w"), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| + hist = history.map { |l| l.scrub.split("\n").join("\\\n") } - # Change the permission of a file that already exists[BUG #7694] - begin - if File.stat(history_file).mode & 066 != 0 - File.chmod(0600, history_file) - end - rescue Errno::ENOENT - rescue Errno::EPERM - return - rescue - raise + unless append_history || History.infinite? + hist = hist.last(History.save_history) end - if File.exist?(history_file) && - File.mtime(history_file) != @loaded_history_mtime - history = history[@loaded_history_lines..-1] if @loaded_history_lines - append_history = true - end + f.puts(hist) + end + end - pathname = Pathname.new(history_file) - unless Dir.exist?(pathname.dirname) - warn "Warning: The directory to save IRB's history file does not exist. Please double check `IRB.conf[:HISTORY_FILE]`'s value." - return - end + private - File.open(history_file, (append_history ? 'a' : 'w'), 0o600, encoding: IRB.conf[:LC_MESSAGES]&.encoding) do |f| - hist = history.map{ |l| l.scrub.split("\n").join("\\\n") } - unless append_history - begin - hist = hist.last(num) if hist.size > num and num > 0 - rescue RangeError # bignum too big to convert into `long' - # Do nothing because the bignum should be treated as infinity - end - end - f.puts(hist) + # Returns boolean whether writing to +history_file+ will be possible. + # Permissions of already existing +history_file+ are changed to + # owner-only-readable if necessary [BUG #7694]. + def ensure_history_file_writable(history_file) + history_file = Pathname.new(history_file) + + return false unless history_file.dirname.writable? + return true unless history_file.exist? + + begin + if history_file.stat.mode & 0o66 != 0 + history_file.chmod 0o600 end + true + rescue Errno::EPERM # no permissions + false end end end diff --git a/test/irb/test_history.rb b/test/irb/test_history.rb index 7a17491d891711..791eef1acd0747 100644 --- a/test/irb/test_history.rb +++ b/test/irb/test_history.rb @@ -39,6 +39,21 @@ class TestInputMethodWithReadlineHistory < TestInputMethod include IRB::HistorySavingAbility end + def test_history_dont_save + omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) + IRB.conf[:SAVE_HISTORY] = nil + assert_history(<<~EXPECTED_HISTORY, <<~INITIAL_HISTORY, <<~INPUT) + 1 + 2 + EXPECTED_HISTORY + 1 + 2 + INITIAL_HISTORY + 3 + exit + INPUT + end + def test_history_save_1 omit "Skip Editline" if /EditLine/n.match(Readline::VERSION) IRB.conf[:SAVE_HISTORY] = 1 @@ -166,7 +181,7 @@ def test_history_does_not_raise_when_history_file_directory_does_not_exist IRB.conf[:HISTORY_FILE] = "fake/fake/fake/history_file" io = TestInputMethodWithRelineHistory.new - assert_warn(/history file does not exist/) do + assert_warn(/ensure the folder exists/i) do io.save_history end From 95abf679c546da0e9abf36bc9bdfc60a826b1811 Mon Sep 17 00:00:00 2001 From: Gert Goet Date: Fri, 11 Oct 2024 18:34:56 +0200 Subject: [PATCH 021/140] [ruby/irb] Document infinite history (https://github.com/ruby/irb/pull/1012) As introduced in 824473e8 https://github.com/ruby/irb/commit/15e3f50c3f --- lib/irb.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/irb.rb b/lib/irb.rb index 7e91b0f64507e1..12eb69c5b187a7 100644 --- a/lib/irb.rb +++ b/lib/irb.rb @@ -221,11 +221,11 @@ # *new_filepath*, which becomes the history file for the session. # # You can change the number of commands saved by adding to your configuration -# file: `IRB.conf[:SAVE_HISTORY] = *n*`, wheHISTORY_FILEre *n* is one of: +# file: `IRB.conf[:SAVE_HISTORY] = *n*`, where *n* is one of: # -# * Positive integer: the number of commands to be saved, -# * Zero: all commands are to be saved. -# * `nil`: no commands are to be saved,. +# * Positive integer: the number of commands to be saved. +# * Negative integer: all commands are to be saved. +# * Zero or `nil`: no commands are to be saved. # # # During the session, you can use methods `conf.save_history` or From 6a88e9d43088f51d3d9dde9c89f24836ab715810 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 11 Oct 2024 14:01:08 -0400 Subject: [PATCH 022/140] Used respond_to? check for compaction in test_gc_compact.rb --- test/ruby/test_gc_compact.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index 26d7c71687fa96..15bf574f6b5129 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -9,7 +9,7 @@ class TestGCCompact < Test::Unit::TestCase module CompactionSupportInspector def supports_auto_compact? - GC::OPTS.include?("GC_COMPACTION_SUPPORTED") + GC.respond_to?(:compact) end end From 8aeb60aec88dd68fdfbaa75ca06e65188233ccbf Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 11 Oct 2024 14:08:59 -0400 Subject: [PATCH 023/140] Rename supports_auto_compact? to supports_compact? It's testing whether GC compaction is supported in general. --- test/ruby/test_gc_compact.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/ruby/test_gc_compact.rb b/test/ruby/test_gc_compact.rb index 15bf574f6b5129..4c8aa20215fb90 100644 --- a/test/ruby/test_gc_compact.rb +++ b/test/ruby/test_gc_compact.rb @@ -8,7 +8,7 @@ class TestGCCompact < Test::Unit::TestCase module CompactionSupportInspector - def supports_auto_compact? + def supports_compact? GC.respond_to?(:compact) end end @@ -17,7 +17,7 @@ module OmitUnlessCompactSupported include CompactionSupportInspector def setup - omit "autocompact not supported on this platform" unless supports_auto_compact? + omit "GC compaction not supported on this platform" unless supports_compact? super end end @@ -83,7 +83,7 @@ class CompactMethodsNotImplemented < Test::Unit::TestCase include CompactionSupportInspector def assert_not_implemented(method, *args) - omit "autocompact is supported on this platform" if supports_auto_compact? + omit "autocompact is supported on this platform" if supports_compact? assert_raise(NotImplementedError) { GC.send(method, *args) } refute(GC.respond_to?(method), "GC.#{method} should be defined as rb_f_notimplement") From 5f62522d5b8bd162ddf657680b8532eadeaae21f Mon Sep 17 00:00:00 2001 From: Kevin Newton Date: Fri, 11 Oct 2024 14:43:23 -0400 Subject: [PATCH 024/140] [ruby/prism] Prism::StringQuery Introduce StringQuery to provide methods to access some metadata about the Ruby lexer. https://github.com/ruby/prism/commit/d3f55b67b9 --- lib/prism.rb | 9 +- lib/prism/ffi.rb | 39 +++++++ lib/prism/prism.gemspec | 3 + lib/prism/string_query.rb | 30 +++++ prism/extension.c | 67 +++++++++++ prism/prism.c | 163 +++++++++++++++++++++++++++ prism/prism.h | 47 ++++++++ test/prism/ruby/string_query_test.rb | 60 ++++++++++ 8 files changed, 414 insertions(+), 4 deletions(-) create mode 100644 lib/prism/string_query.rb create mode 100644 test/prism/ruby/string_query_test.rb diff --git a/lib/prism.rb b/lib/prism.rb index 66a64e7fd0369e..50b14a54861919 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -25,6 +25,7 @@ module Prism autoload :Pattern, "prism/pattern" autoload :Reflection, "prism/reflection" autoload :Serialize, "prism/serialize" + autoload :StringQuery, "prism/string_query" autoload :Translation, "prism/translation" autoload :Visitor, "prism/visitor" @@ -75,13 +76,13 @@ def self.load(source, serialized) # it's going to require the built library. Otherwise, it's going to require a # module that uses FFI to call into the library. if RUBY_ENGINE == "ruby" and !ENV["PRISM_FFI_BACKEND"] - require "prism/prism" - # The C extension is the default backend on CRuby. Prism::BACKEND = :CEXT -else - require_relative "prism/ffi" + require "prism/prism" +else # The FFI backend is used on other Ruby implementations. Prism::BACKEND = :FFI + + require_relative "prism/ffi" end diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 0520f7cdd24810..5caae440f4c336 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -73,6 +73,7 @@ def self.load_exported_functions_from(header, *functions, callbacks) callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY] + enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE] load_exported_functions_from( "prism.h", @@ -83,6 +84,9 @@ def self.load_exported_functions_from(header, *functions, callbacks) "pm_serialize_lex", "pm_serialize_parse_lex", "pm_parse_success_p", + "pm_string_query_local", + "pm_string_query_constant", + "pm_string_query_method_name", [:pm_parse_stream_fgets_t] ) @@ -492,4 +496,39 @@ def dump_options(options) values.pack(template) end end + + # Here we are going to patch StringQuery to put in the class-level methods so + # that it can maintain a consistent interface + class StringQuery + class << self + # Mirrors the C extension's StringQuery::local? method. + def local?(string) + query(LibRubyParser.pm_string_query_local(string, string.bytesize, string.encoding.name)) + end + + # Mirrors the C extension's StringQuery::constant? method. + def constant?(string) + query(LibRubyParser.pm_string_query_constant(string, string.bytesize, string.encoding.name)) + end + + # Mirrors the C extension's StringQuery::method_name? method. + def method_name?(string) + query(LibRubyParser.pm_string_query_method_name(string, string.bytesize, string.encoding.name)) + end + + private + + # Parse the enum result and return an appropriate boolean. + def query(result) + case result + when :PM_STRING_QUERY_ERROR + raise ArgumentError, "Invalid or non ascii-compatible encoding" + when :PM_STRING_QUERY_FALSE + false + when :PM_STRING_QUERY_TRUE + true + end + end + end + end end diff --git a/lib/prism/prism.gemspec b/lib/prism/prism.gemspec index 37aa9795765aad..6123b71fc8ea8a 100644 --- a/lib/prism/prism.gemspec +++ b/lib/prism/prism.gemspec @@ -89,6 +89,7 @@ Gem::Specification.new do |spec| "lib/prism/polyfill/unpack1.rb", "lib/prism/reflection.rb", "lib/prism/serialize.rb", + "lib/prism/string_query.rb", "lib/prism/translation.rb", "lib/prism/translation/parser.rb", "lib/prism/translation/parser33.rb", @@ -109,6 +110,7 @@ Gem::Specification.new do |spec| "rbi/prism/node.rbi", "rbi/prism/parse_result.rbi", "rbi/prism/reflection.rbi", + "rbi/prism/string_query.rbi", "rbi/prism/translation/parser.rbi", "rbi/prism/translation/parser33.rbi", "rbi/prism/translation/parser34.rbi", @@ -129,6 +131,7 @@ Gem::Specification.new do |spec| "sig/prism/pattern.rbs", "sig/prism/reflection.rbs", "sig/prism/serialize.rbs", + "sig/prism/string_query.rbs", "sig/prism/visitor.rbs", "src/diagnostic.c", "src/encoding.c", diff --git a/lib/prism/string_query.rb b/lib/prism/string_query.rb new file mode 100644 index 00000000000000..9011051d2b1fa4 --- /dev/null +++ b/lib/prism/string_query.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Prism + # Query methods that allow categorizing strings based on their context for + # where they could be valid in a Ruby syntax tree. + class StringQuery + # The string that this query is wrapping. + attr_reader :string + + # Initialize a new query with the given string. + def initialize(string) + @string = string + end + + # Whether or not this string is a valid local variable name. + def local? + StringQuery.local?(string) + end + + # Whether or not this string is a valid constant name. + def constant? + StringQuery.constant?(string) + end + + # Whether or not this string is a valid method name. + def method_name? + StringQuery.method_name?(string) + end + end +end diff --git a/prism/extension.c b/prism/extension.c index 93fa7b09897747..47603fd9b4f506 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -23,6 +23,7 @@ VALUE rb_cPrismResult; VALUE rb_cPrismParseResult; VALUE rb_cPrismLexResult; VALUE rb_cPrismParseLexResult; +VALUE rb_cPrismStringQuery; VALUE rb_cPrismDebugEncoding; @@ -1133,6 +1134,67 @@ parse_file_failure_p(int argc, VALUE *argv, VALUE self) { return RTEST(parse_file_success_p(argc, argv, self)) ? Qfalse : Qtrue; } +/******************************************************************************/ +/* String query methods */ +/******************************************************************************/ + +/** + * Process the result of a call to a string query method and return an + * appropriate value. + */ +static VALUE +string_query(pm_string_query_t result) { + switch (result) { + case PM_STRING_QUERY_ERROR: + rb_raise(rb_eArgError, "Invalid or non ascii-compatible encoding"); + return Qfalse; + case PM_STRING_QUERY_FALSE: + return Qfalse; + case PM_STRING_QUERY_TRUE: + return Qtrue; + } +} + +/** + * call-seq: + * Prism::StringQuery::local?(string) -> bool + * + * Returns true if the string constitutes a valid local variable name. Note that + * this means the names that can be set through Binding#local_variable_set, not + * necessarily the ones that can be set through a local variable assignment. + */ +static VALUE +string_query_local_p(VALUE self, VALUE string) { + const uint8_t *source = (const uint8_t *) check_string(string); + return string_query(pm_string_query_local(source, RSTRING_LEN(string), rb_enc_get(string)->name)); +} + +/** + * call-seq: + * Prism::StringQuery::constant?(string) -> bool + * + * Returns true if the string constitutes a valid constant name. Note that this + * means the names that can be set through Module#const_set, not necessarily the + * ones that can be set through a constant assignment. + */ +static VALUE +string_query_constant_p(VALUE self, VALUE string) { + const uint8_t *source = (const uint8_t *) check_string(string); + return string_query(pm_string_query_constant(source, RSTRING_LEN(string), rb_enc_get(string)->name)); +} + +/** + * call-seq: + * Prism::StringQuery::method_name?(string) -> bool + * + * Returns true if the string constitutes a valid method name. + */ +static VALUE +string_query_method_name_p(VALUE self, VALUE string) { + const uint8_t *source = (const uint8_t *) check_string(string); + return string_query(pm_string_query_method_name(source, RSTRING_LEN(string), rb_enc_get(string)->name)); +} + /******************************************************************************/ /* Initialization of the extension */ /******************************************************************************/ @@ -1170,6 +1232,7 @@ Init_prism(void) { rb_cPrismParseResult = rb_define_class_under(rb_cPrism, "ParseResult", rb_cPrismResult); rb_cPrismLexResult = rb_define_class_under(rb_cPrism, "LexResult", rb_cPrismResult); rb_cPrismParseLexResult = rb_define_class_under(rb_cPrism, "ParseLexResult", rb_cPrismResult); + rb_cPrismStringQuery = rb_define_class_under(rb_cPrism, "StringQuery", rb_cObject); // Intern all of the IDs eagerly that we support so that we don't have to do // it every time we parse. @@ -1211,6 +1274,10 @@ Init_prism(void) { rb_define_singleton_method(rb_cPrism, "dump_file", dump_file, -1); #endif + rb_define_singleton_method(rb_cPrismStringQuery, "local?", string_query_local_p, 1); + rb_define_singleton_method(rb_cPrismStringQuery, "constant?", string_query_constant_p, 1); + rb_define_singleton_method(rb_cPrismStringQuery, "method_name?", string_query_method_name_p, 1); + // Next, initialize the other APIs. Init_prism_api_node(); Init_prism_pack(); diff --git a/prism/prism.c b/prism/prism.c index 00485b68ad9ff0..07d188dcc8bdf6 100644 --- a/prism/prism.c +++ b/prism/prism.c @@ -22642,3 +22642,166 @@ pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t s } #endif + +/******************************************************************************/ +/* Slice queries for the Ruby API */ +/******************************************************************************/ + +/** The category of slice returned from pm_slice_type. */ +typedef enum { + /** Returned when the given encoding name is invalid. */ + PM_SLICE_TYPE_ERROR = -1, + + /** Returned when no other types apply to the slice. */ + PM_SLICE_TYPE_NONE, + + /** Returned when the slice is a valid local variable name. */ + PM_SLICE_TYPE_LOCAL, + + /** Returned when the slice is a valid constant name. */ + PM_SLICE_TYPE_CONSTANT, + + /** Returned when the slice is a valid method name. */ + PM_SLICE_TYPE_METHOD_NAME +} pm_slice_type_t; + +/** + * Check that the slice is a valid local variable name or constant. + */ +pm_slice_type_t +pm_slice_type(const uint8_t *source, size_t length, const char *encoding_name) { + // first, get the right encoding object + const pm_encoding_t *encoding = pm_encoding_find((const uint8_t *) encoding_name, (const uint8_t *) (encoding_name + strlen(encoding_name))); + if (encoding == NULL) return PM_SLICE_TYPE_ERROR; + + // check that there is at least one character + if (length == 0) return PM_SLICE_TYPE_NONE; + + size_t width; + if ((width = encoding->alpha_char(source, (ptrdiff_t) length)) != 0) { + // valid because alphabetical + } else if (*source == '_') { + // valid because underscore + width = 1; + } else if ((*source >= 0x80) && ((width = encoding->char_width(source, (ptrdiff_t) length)) > 0)) { + // valid because multibyte + } else { + // invalid because no match + return PM_SLICE_TYPE_NONE; + } + + // determine the type of the slice based on the first character + const uint8_t *end = source + length; + pm_slice_type_t result = encoding->isupper_char(source, end - source) ? PM_SLICE_TYPE_CONSTANT : PM_SLICE_TYPE_LOCAL; + + // next, iterate through all of the bytes of the string to ensure that they + // are all valid identifier characters + source += width; + + while (source < end) { + if ((width = encoding->alnum_char(source, end - source)) != 0) { + // valid because alphanumeric + source += width; + } else if (*source == '_') { + // valid because underscore + source++; + } else if ((*source >= 0x80) && ((width = encoding->char_width(source, end - source)) > 0)) { + // valid because multibyte + source += width; + } else { + // invalid because no match + break; + } + } + + // accept a ! or ? at the end of the slice as a method name + if (*source == '!' || *source == '?' || *source == '=') { + source++; + result = PM_SLICE_TYPE_METHOD_NAME; + } + + // valid if we are at the end of the slice + return source == end ? result : PM_SLICE_TYPE_NONE; +} + +/** + * Check that the slice is a valid local variable name. + */ +PRISM_EXPORTED_FUNCTION pm_string_query_t +pm_string_query_local(const uint8_t *source, size_t length, const char *encoding_name) { + switch (pm_slice_type(source, length, encoding_name)) { + case PM_SLICE_TYPE_ERROR: + return PM_STRING_QUERY_ERROR; + case PM_SLICE_TYPE_NONE: + case PM_SLICE_TYPE_CONSTANT: + case PM_SLICE_TYPE_METHOD_NAME: + return PM_STRING_QUERY_FALSE; + case PM_SLICE_TYPE_LOCAL: + return PM_STRING_QUERY_TRUE; + } + + assert(false && "unreachable"); + return PM_STRING_QUERY_FALSE; +} + +/** + * Check that the slice is a valid constant name. + */ +PRISM_EXPORTED_FUNCTION pm_string_query_t +pm_string_query_constant(const uint8_t *source, size_t length, const char *encoding_name) { + switch (pm_slice_type(source, length, encoding_name)) { + case PM_SLICE_TYPE_ERROR: + return PM_STRING_QUERY_ERROR; + case PM_SLICE_TYPE_NONE: + case PM_SLICE_TYPE_LOCAL: + case PM_SLICE_TYPE_METHOD_NAME: + return PM_STRING_QUERY_FALSE; + case PM_SLICE_TYPE_CONSTANT: + return PM_STRING_QUERY_TRUE; + } + + assert(false && "unreachable"); + return PM_STRING_QUERY_FALSE; +} + +/** + * Check that the slice is a valid method name. + */ +PRISM_EXPORTED_FUNCTION pm_string_query_t +pm_string_query_method_name(const uint8_t *source, size_t length, const char *encoding_name) { +#define B(p) ((p) ? PM_STRING_QUERY_TRUE : PM_STRING_QUERY_FALSE) +#define C1(c) (*source == c) +#define C2(s) (memcmp(source, s, 2) == 0) +#define C3(s) (memcmp(source, s, 3) == 0) + + switch (pm_slice_type(source, length, encoding_name)) { + case PM_SLICE_TYPE_ERROR: + return PM_STRING_QUERY_ERROR; + case PM_SLICE_TYPE_NONE: + break; + case PM_SLICE_TYPE_LOCAL: + // numbered parameters are not valid method names + return B((length != 2) || (source[0] != '_') || (source[1] == '0') || !pm_char_is_decimal_digit(source[1])); + case PM_SLICE_TYPE_CONSTANT: + // all constants are valid method names + case PM_SLICE_TYPE_METHOD_NAME: + // all method names are valid method names + return PM_STRING_QUERY_TRUE; + } + + switch (length) { + case 1: + return B(C1('&') || C1('`') || C1('!') || C1('^') || C1('>') || C1('<') || C1('-') || C1('%') || C1('|') || C1('+') || C1('/') || C1('*') || C1('~')); + case 2: + return B(C2("!=") || C2("!~") || C2("[]") || C2("==") || C2("=~") || C2(">=") || C2(">>") || C2("<=") || C2("<<") || C2("**")); + case 3: + return B(C3("===") || C3("<=>") || C3("[]=")); + default: + return PM_STRING_QUERY_FALSE; + } + +#undef B +#undef C1 +#undef C2 +#undef C3 +} diff --git a/prism/prism.h b/prism/prism.h index 755c38fca29248..6f7b850a316c73 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -234,6 +234,53 @@ PRISM_EXPORTED_FUNCTION void pm_dump_json(pm_buffer_t *buffer, const pm_parser_t #endif +/** + * Represents the results of a slice query. + */ +typedef enum { + /** Returned if the encoding given to a slice query was invalid. */ + PM_STRING_QUERY_ERROR = -1, + + /** Returned if the result of the slice query is false. */ + PM_STRING_QUERY_FALSE, + + /** Returned if the result of the slice query is true. */ + PM_STRING_QUERY_TRUE +} pm_string_query_t; + +/** + * Check that the slice is a valid local variable name. + * + * @param source The source to check. + * @param length The length of the source. + * @param encoding_name The name of the encoding of the source. + * @return PM_STRING_QUERY_TRUE if the query is true, PM_STRING_QUERY_FALSE if + * the query is false, and PM_STRING_QUERY_ERROR if the encoding was invalid. + */ +PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_local(const uint8_t *source, size_t length, const char *encoding_name); + +/** + * Check that the slice is a valid constant name. + * + * @param source The source to check. + * @param length The length of the source. + * @param encoding_name The name of the encoding of the source. + * @return PM_STRING_QUERY_TRUE if the query is true, PM_STRING_QUERY_FALSE if + * the query is false, and PM_STRING_QUERY_ERROR if the encoding was invalid. + */ +PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_constant(const uint8_t *source, size_t length, const char *encoding_name); + +/** + * Check that the slice is a valid method name. + * + * @param source The source to check. + * @param length The length of the source. + * @param encoding_name The name of the encoding of the source. + * @return PM_STRING_QUERY_TRUE if the query is true, PM_STRING_QUERY_FALSE if + * the query is false, and PM_STRING_QUERY_ERROR if the encoding was invalid. + */ +PRISM_EXPORTED_FUNCTION pm_string_query_t pm_string_query_method_name(const uint8_t *source, size_t length, const char *encoding_name); + /** * @mainpage * diff --git a/test/prism/ruby/string_query_test.rb b/test/prism/ruby/string_query_test.rb new file mode 100644 index 00000000000000..aa50c10ff326a2 --- /dev/null +++ b/test/prism/ruby/string_query_test.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require_relative "../test_helper" + +module Prism + class StringQueryTest < TestCase + def test_local? + assert_predicate StringQuery.new("a"), :local? + assert_predicate StringQuery.new("a1"), :local? + assert_predicate StringQuery.new("self"), :local? + + assert_predicate StringQuery.new("_a"), :local? + assert_predicate StringQuery.new("_1"), :local? + + assert_predicate StringQuery.new("😀"), :local? + assert_predicate StringQuery.new("ア".encode("Windows-31J")), :local? + + refute_predicate StringQuery.new("1"), :local? + refute_predicate StringQuery.new("A"), :local? + end + + def test_constant? + assert_predicate StringQuery.new("A"), :constant? + assert_predicate StringQuery.new("A1"), :constant? + assert_predicate StringQuery.new("A_B"), :constant? + assert_predicate StringQuery.new("BEGIN"), :constant? + + assert_predicate StringQuery.new("À"), :constant? + assert_predicate StringQuery.new("A".encode("US-ASCII")), :constant? + + refute_predicate StringQuery.new("a"), :constant? + refute_predicate StringQuery.new("1"), :constant? + end + + def test_method_name? + assert_predicate StringQuery.new("a"), :method_name? + assert_predicate StringQuery.new("A"), :method_name? + assert_predicate StringQuery.new("__FILE__"), :method_name? + + assert_predicate StringQuery.new("a?"), :method_name? + assert_predicate StringQuery.new("a!"), :method_name? + assert_predicate StringQuery.new("a="), :method_name? + + assert_predicate StringQuery.new("+"), :method_name? + assert_predicate StringQuery.new("<<"), :method_name? + assert_predicate StringQuery.new("==="), :method_name? + + assert_predicate StringQuery.new("_0"), :method_name? + + refute_predicate StringQuery.new("1"), :method_name? + refute_predicate StringQuery.new("_1"), :method_name? + end + + def test_invalid_encoding + assert_raise ArgumentError do + StringQuery.new("A".encode("UTF-16LE")).local? + end + end + end +end From 2e6ddd968d5601689f59c029bb401d7cdabab3b7 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 11 Oct 2024 15:42:45 -0400 Subject: [PATCH 025/140] Don't enable GC.auto_compact in EnvUtil.under_gc_compact_stress when not supported --- tool/lib/envutil.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tool/lib/envutil.rb b/tool/lib/envutil.rb index 952614b8cf0e12..7b8aa99a39dbac 100644 --- a/tool/lib/envutil.rb +++ b/tool/lib/envutil.rb @@ -259,11 +259,15 @@ def under_gc_stress(stress = true) def under_gc_compact_stress(val = :empty, &block) raise "compaction doesn't work well on s390x. Omit the test in the caller." if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 - auto_compact = GC.auto_compact - GC.auto_compact = val + + if GC.respond_to?(:auto_compact) + auto_compact = GC.auto_compact + GC.auto_compact = val + end + under_gc_stress(&block) ensure - GC.auto_compact = auto_compact + GC.auto_compact = auto_compact if GC.respond_to?(:auto_compact) end module_function :under_gc_compact_stress From ad5641fd3424790789871300b7c0dd6c069f3614 Mon Sep 17 00:00:00 2001 From: Samuel Williams Date: Sat, 12 Oct 2024 10:08:34 +1300 Subject: [PATCH 026/140] Support `IO#timeout` for `rsock_connect`. (#11880) --- ext/socket/init.c | 23 ++++++++++--------- ext/socket/ipsocket.c | 9 +------- ext/socket/rubysocket.h | 2 +- ext/socket/socket.c | 7 +++--- ext/socket/udpsocket.c | 2 +- ext/socket/unixsocket.c | 2 +- .../library/socket/socket/connect_spec.rb | 14 +++++++++++ 7 files changed, 33 insertions(+), 26 deletions(-) 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/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 From 199b2980496a6725f4bb44bc6e9088c800f67a50 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sat, 12 Oct 2024 13:24:32 +0900 Subject: [PATCH 027/140] [ruby/irb] Make rendering test faster using updated yamatanooroti (https://github.com/ruby/irb/pull/1001) https://github.com/ruby/irb/commit/2c2956bc1f --- test/irb/yamatanooroti/test_rendering.rb | 169 ++++++++--------------- 1 file changed, 55 insertions(+), 114 deletions(-) diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 834c501d5c6035..57aa0ecb3cc189 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 From afacb8ada5788299468cce6247601412e12002a4 Mon Sep 17 00:00:00 2001 From: John Bampton Date: Sat, 12 Oct 2024 22:48:10 +1000 Subject: [PATCH 028/140] [DOC] Fix spelling --- ext/zlib/zlib.c | 4 ++-- lib/reline/line_editor.rb | 12 ++++++------ test/reline/test_key_actor_emacs.rb | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) 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/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index b8156597135430..e01d859262b4ea 100644 --- a/lib/reline/line_editor.rb +++ b/lib/reline/line_editor.rb @@ -2368,9 +2368,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 +2382,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 +2420,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 +2434,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/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index dc12c803f57787..cd8971298d1fb7 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 = [[]] From 81910a93ff2688fc1a6d4a5200782869105e8872 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Sat, 12 Oct 2024 09:36:29 -0500 Subject: [PATCH 029/140] [DOC] Tweaks for Array#shift (#11886) --- array.c | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) 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 From c12c95462a22551af2f0437cd08136b55c4bb4b7 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 13 Oct 2024 13:30:30 +0900 Subject: [PATCH 030/140] [ruby/irb] Fix rendering test broken by conflict (https://github.com/ruby/irb/pull/1016) https://github.com/ruby/irb/commit/a21b953a99 --- test/irb/yamatanooroti/test_rendering.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/test/irb/yamatanooroti/test_rendering.rb b/test/irb/yamatanooroti/test_rendering.rb index 57aa0ecb3cc189..ac6ce5346ea6c9 100644 --- a/test/irb/yamatanooroti/test_rendering.rb +++ b/test/irb/yamatanooroti/test_rendering.rb @@ -461,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 From 98fce00cab460be81cb1f5e4cf8c0d66e006a35b Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 13 Oct 2024 13:55:38 +0900 Subject: [PATCH 031/140] [ruby/reline] Support continuous tab completion (https://github.com/ruby/reline/pull/761) Continuous tab completion is possible in GNU Readline. If dig_perfect_match_proc is set, continuous tab completion will be disabled. https://github.com/ruby/reline/commit/469a52846b --- lib/reline/line_editor.rb | 6 +++++- test/reline/test_key_actor_emacs.rb | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/lib/reline/line_editor.rb b/lib/reline/line_editor.rb index e01d859262b4ea..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 diff --git a/test/reline/test_key_actor_emacs.rb b/test/reline/test_key_actor_emacs.rb index cd8971298d1fb7..2ebd91dc2ecf45 100644 --- a/test/reline/test_key_actor_emacs.rb +++ b/test/reline/test_key_actor_emacs.rb @@ -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{ From 6393d2950ddcaa75f92cde9917cf3e4edf9b5347 Mon Sep 17 00:00:00 2001 From: S-H-GAMELINKS Date: Sat, 12 Oct 2024 21:45:42 +0900 Subject: [PATCH 032/140] [ruby/prism] Supress string_query function warning https://github.com/ruby/prism/commit/0635814327 --- prism/extension.c | 1 + 1 file changed, 1 insertion(+) 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; } /** From cf8388f76c4c2ff2f46d0d2aa2cf5186e05ff606 Mon Sep 17 00:00:00 2001 From: tomoya ishida Date: Sun, 13 Oct 2024 22:00:17 +0900 Subject: [PATCH 033/140] [ruby/irb] Remove bignum check from save_history (https://github.com/ruby/irb/pull/1018) IRB need to accept bignum history size, but we don't want explicit bignum checks because threshold of bignum and fixnum depends on platform. https://github.com/ruby/irb/commit/5151467e6a --- lib/irb/history.rb | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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) From 0641951e7bd3fa7b8032a5a1206d15cfab8fae4f Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 14 Oct 2024 13:17:55 +0900 Subject: [PATCH 034/140] `2digits` macros --- time.c | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/time.c b/time.c index 54db29c2af1d33..8abcc520efd97b 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,18 @@ 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; + 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 +2531,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 +2540,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 From 9611c619ac60e9aeb0341b0c8cf322a42707ce38 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Mon, 14 Oct 2024 13:55:55 +0900 Subject: [PATCH 035/140] [Bug #20797] Check seconds in UTC offset as well as minutes --- spec/ruby/core/time/new_spec.rb | 6 +++--- test/ruby/test_time.rb | 12 ++++++++++++ time.c | 1 + 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index d686355270c75d..9b3e4f90f3db25 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -625,15 +625,15 @@ 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/) end it "raises ArgumentError if string has not ascii-compatible encoding" do 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/time.c b/time.c index 8abcc520efd97b..3c8ff365164caf 100644 --- a/time.c +++ b/time.c @@ -2211,6 +2211,7 @@ utc_offset_arg(VALUE arg) } if (sec) { if (!have_2digits(sec)) goto invalid_utc_offset; + if (sec[0] > '5') goto invalid_utc_offset; n += num_from_2digits(sec); ASSUME(min); } From 1001ea9606fd9bbbce70deaab0e6a31c5af1a20c Mon Sep 17 00:00:00 2001 From: git Date: Mon, 14 Oct 2024 07:01:30 +0000 Subject: [PATCH 036/140] Update bundled gems list as of 2024-10-13 --- NEWS.md | 2 +- gems/bundled_gems | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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/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 From 57404e4369df11f77f089d5d11d310679b2e749f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Thu, 10 Oct 2024 20:50:48 +0200 Subject: [PATCH 037/140] [rubygems/rubygems] Fix duplicated specs when they have been previously activated https://github.com/rubygems/rubygems/commit/b44bf2ac74 --- lib/rubygems/specification_record.rb | 2 +- test/rubygems/test_gem.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) 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/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 From 48fdb9faa0a408c0855e84cfd6451393c8b67180 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Tue, 8 Oct 2024 23:56:32 +0200 Subject: [PATCH 038/140] [rubygems/rubygems] Fix `gem contents` for default gems A default gem does not always live in the same place. For example, Bundler may be installed to `site_dir` when RubyGems have been upgraded. A more reliable way seems to actually activate the default gem, so that we can know for sure where it lives. https://github.com/rubygems/rubygems/commit/c69f6dfb18 --- lib/rubygems/commands/contents_command.rb | 23 ++++++++++++------- .../test_gem_commands_contents_command.rb | 6 ++--- 2 files changed, 17 insertions(+), 12 deletions(-) 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/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 From 8240fe88f32ff4fec54cb6c425782597efa4d397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Rodr=C3=ADguez?= Date: Mon, 14 Oct 2024 19:55:38 +0200 Subject: [PATCH 039/140] [rubygems/rubygems] Prevent some test suite warnings about missing extensions We fixed some issues recently where Bundler would try to activate a pysch spec with missing extensions and crash. However, as a side effect, we started printing warnings about missing extensions in situations where we did not warn before. It may be interesting to warn on these new situations too, but in order to minimize changes for now, I'm reverting to printing warnings in the same situations as before. https://github.com/rubygems/rubygems/commit/51ebff6982 --- lib/bundler/rubygems_ext.rb | 12 ++++++------ lib/rubygems/basic_specification.rb | 17 ++++++++--------- 2 files changed, 14 insertions(+), 15 deletions(-) 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/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? From 6c7209cd3788ceec01e504d99057f9d3b396be84 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 02:08:58 +0000 Subject: [PATCH 040/140] Bump github/codeql-action from 3.26.12 to 3.26.13 Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.26.12 to 3.26.13. - [Release notes](https://github.com/github/codeql-action/releases) - [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/github/codeql-action/compare/c36620d31ac7c881962c3d9dd939c40ec9434f2b...f779452ac5af1c261dce0346a8f964149f49322b) --- updated-dependencies: - dependency-name: github/codeql-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- .github/workflows/codeql-analysis.yml | 8 ++++---- .github/workflows/scorecards.yml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) 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 From d58ec11945a1f8bd26bb6d61a7e2c2f983350930 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Tue, 15 Oct 2024 14:52:17 +0900 Subject: [PATCH 041/140] [Bug #20797] Yet another test --- spec/ruby/core/time/new_spec.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/spec/ruby/core/time/new_spec.rb b/spec/ruby/core/time/new_spec.rb index 9b3e4f90f3db25..fa5b5c56d60f5a 100644 --- a/spec/ruby/core/time/new_spec.rb +++ b/spec/ruby/core/time/new_spec.rb @@ -634,6 +634,12 @@ def obj.to_int; 3; end -> { Time.new("2020-12-25 00:56:17 +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 From 7eed1df1ab6b7dbf81fcff96eb0106699a140775 Mon Sep 17 00:00:00 2001 From: Jean Boussier Date: Tue, 15 Oct 2024 13:35:38 +0200 Subject: [PATCH 042/140] [ruby/json] Update gemspec files https://github.com/ruby/json/commit/0f9564104f --- ext/json/json.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec index 5a951a8b25db83..3ded3613d67b1a 100644 --- a/ext/json/json.gemspec +++ b/ext/json/json.gemspec @@ -17,7 +17,7 @@ Gem::Specification.new do |s| s.rdoc_options = ["--title", "JSON implementation for Ruby", "--main", "README.md"] s.files = [ "CHANGES.md", - "LICENSE", + "COPYING", "README.md", "ext/json/ext/fbuffer/fbuffer.h", "ext/json/ext/generator/depend", From 914608bed8105b23be0c77eb1b99b27b4d47e90b Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 15 Oct 2024 20:46:41 +0900 Subject: [PATCH 043/140] The part of ext/json/generator/generator.c is replaced from CVTUTF code. * https://github.com/ruby/json/pull/567 * https://github.com/ruby/json/commit/c96351f874 --- LEGAL | 25 ------------------------- 1 file changed, 25 deletions(-) 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] From 48f953d6bab8cc4ef60430d5dc8058be5b2f1e37 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Tue, 15 Oct 2024 20:40:21 +0900 Subject: [PATCH 044/140] [ruby/json] Added license files on gemspec https://github.com/ruby/json/commit/81092639e8 --- ext/json/json.gemspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ext/json/json.gemspec b/ext/json/json.gemspec index 3ded3613d67b1a..c714ba86d19c87 100644 --- a/ext/json/json.gemspec +++ b/ext/json/json.gemspec @@ -18,6 +18,8 @@ Gem::Specification.new do |s| s.files = [ "CHANGES.md", "COPYING", + "BSDL", + "LEGAL", "README.md", "ext/json/ext/fbuffer/fbuffer.h", "ext/json/ext/generator/depend", From 3da3cabf982eaa4d2c9732651f6a9e18ffd0aba3 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Fri, 11 Oct 2024 11:23:50 -0400 Subject: [PATCH 045/140] Remove a puts in test_finalizer_thread_raise --- test/ruby/test_objectspace.rb | 1 - 1 file changed, 1 deletion(-) 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 From 644153bfa8255903d0c0b7b3013bda3b33f6c706 Mon Sep 17 00:00:00 2001 From: Burdette Lamar Date: Tue, 15 Oct 2024 13:33:59 -0500 Subject: [PATCH 046/140] [DOC] Tweaks for Array#shuffle! (#11891) --- array.rb | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) 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 From f45eb3dcb9c7d849064cb802953f37e1cf9f3996 Mon Sep 17 00:00:00 2001 From: Peter Zhu Date: Tue, 15 Oct 2024 12:55:05 -0400 Subject: [PATCH 047/140] Use GC.respond_to?(:compact) in bootstraptest/test_yjit.rb defined?(GC.compact) will always return true even when compaction is not supported. We should use GC.respond_to?(:compact) instead. --- bootstraptest/test_yjit.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 From ed993b5bcc4fcae661dd022d3211dcc770425218 Mon Sep 17 00:00:00 2001 From: Vinicius Stock Date: Tue, 15 Oct 2024 18:59:29 -0400 Subject: [PATCH 048/140] [ruby/rdoc] Generate meta tags based on page's content (https://github.com/ruby/rdoc/pull/1091) https://github.com/ruby/rdoc/commit/716bc16a7d --- lib/rdoc/generator/darkfish.rb | 15 +++++++ .../generator/template/darkfish/_head.rhtml | 22 +++++++++ test/rdoc/test_rdoc_generator_darkfish.rb | 45 +++++++++++++++++++ 3 files changed, 82 insertions(+) diff --git a/lib/rdoc/generator/darkfish.rb b/lib/rdoc/generator/darkfish.rb index 96bb4fb66f39f0..5709fabf810206 100644 --- a/lib/rdoc/generator/darkfish.rb +++ b/lib/rdoc/generator/darkfish.rb @@ -780,4 +780,19 @@ def template_for file, page = true, klass = ERB template end + # Returns an excerpt of the content for usage in meta description tags + def excerpt(content) + text = content.is_a?(RDoc::Comment) ? content.text : content + + # Match from a capital letter to the first period, discarding any links, so + # that we don't end up matching badges in the README + first_paragraph_match = text.match(/[A-Z][^\.:\/]+\./) + return text[0...150].gsub(/\n/, " ").squeeze(" ") unless first_paragraph_match + + extracted_text = first_paragraph_match[0] + second_paragraph = first_paragraph_match.post_match.match(/[A-Z][^\.:\/]+\./) + extracted_text << " " << second_paragraph[0] if second_paragraph + + extracted_text[0...150].gsub(/\n/, " ").squeeze(" ") + end end diff --git a/lib/rdoc/generator/template/darkfish/_head.rhtml b/lib/rdoc/generator/template/darkfish/_head.rhtml index 69649ad3b53736..9e6fb4f94ca90c 100644 --- a/lib/rdoc/generator/template/darkfish/_head.rhtml +++ b/lib/rdoc/generator/template/darkfish/_head.rhtml @@ -3,6 +3,28 @@ <%= h @title %> +<%- if defined?(klass) -%> + "> + + <%- if klass.comment.empty? -%> + "> + <%- else -%> + "> + <%- end -%> +<%- elsif defined?(file) -%> + + "> +<%- elsif @title -%> + + + <%- if @options.main_page and + main_page = @files.find { |f| f.full_name == @options.main_page } then %> + "> + <%- else -%> + + <%- end -%> +<%- end -%> +